Split encounter areas, relocate slot finding to obj

Now logic is reasonably split, and each format of area has its own way
of yielding slots

Too much junk with checking flute boosts or catch combo applicability;
just let the area dictate how slots match.
This commit is contained in:
Kurt 2019-09-12 23:20:52 -07:00
parent dc10c057a0
commit 734aa33898
21 changed files with 1664 additions and 1378 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
/// <inheritdoc />
/// <summary>
/// <see cref="GameVersion.RBY"/> encounter area
/// </summary>
public sealed class EncounterArea1 : EncounterAreaGB
{
private static EncounterSlot1[] ReadSlots1FishingYellow(byte[] data, ref int ofs, int count, SlotType t, int rate)
{
// Convert byte to actual number
int[] Levelbytelist = { 0xFF, 0x15, 0x67, 0x1D, 0x3B, 0x5C, 0x72, 0x16, 0x71, 0x18, 0x00, 0x6D, 0x80, };
int[] dexbytelist = { 0x47, 0x6E, 0x18, 0x9B, 0x17, 0x4E, 0x8A, 0x5C, 0x5D, 0x9D, 0x9E, 0x1B, 0x85, 0x16, 0x58, 0x59, };
int[] specieslist = { 060, 061, 072, 073, 090, 098, 099, 116, 117, 118, 119, 120, 129, 130, 147, 148, };
EncounterSlot1[] slots = new EncounterSlot1[count];
for (int i = 0; i < count; i++)
{
int spec = specieslist[Array.IndexOf(dexbytelist, data[ofs++])];
int lvl = Array.IndexOf(Levelbytelist, data[ofs++]) * 5;
slots[i] = new EncounterSlot1
{
LevelMax = lvl,
LevelMin = lvl,
Species = spec,
Type = t,
Rate = rate,
SlotNumber = i,
};
}
return slots;
}
/// <summary>
/// Gets the encounter areas with <see cref="EncounterSlot"/> information from Generation 1 Grass/Water data.
/// </summary>
/// <param name="data">Input raw data.</param>
/// <returns>Array of encounter areas.</returns>
public static EncounterArea1[] GetArray1GrassWater(byte[] data)
{
// RBY Format
var ptr = new int[255];
int count = 0;
for (int i = 0; i < ptr.Length; i++)
{
ptr[i] = BitConverter.ToInt16(data, i * 2);
if (ptr[i] != -1)
continue;
count = i;
break;
}
EncounterArea1[] areas = new EncounterArea1[count];
for (int i = 0; i < areas.Length; i++)
{
var grass = GetSlots1GrassWater(data, ref ptr[i], SlotType.Grass);
var water = GetSlots1GrassWater(data, ref ptr[i], SlotType.Surf);
areas[i] = new EncounterArea1
{
Location = i,
Slots = grass.Concat(water).ToArray()
};
}
return areas.Where(area => area.Slots.Length != 0).ToArray();
}
/// <summary>
/// Gets the encounter areas with <see cref="EncounterSlot"/> information from Pokémon Yellow (Generation 1) Fishing data.
/// </summary>
/// <param name="data">Input raw data.</param>
/// <returns>Array of encounter areas.</returns>
public static EncounterArea1[] GetArray1FishingYellow(byte[] data)
{
const int size = 9;
int count = data.Length / size;
EncounterArea1[] areas = new EncounterArea1[count];
for (int i = 0; i < count; i++)
{
int ofs = (i * size) + 1;
areas[i] = new EncounterArea1
{
Location = data[(i * size) + 0],
Slots = ReadSlots1FishingYellow(data, ref ofs, 4, SlotType.Super_Rod, -1)
};
}
return areas;
}
/// <summary>
/// Gets the encounter areas with <see cref="EncounterSlot"/> information from Generation 1 Fishing data.
/// </summary>
/// <param name="data">Input raw data.</param>
/// <returns>Array of encounter areas.</returns>
public static EncounterArea1[] GetArray1Fishing(byte[] data)
{
var ptr = new int[255];
var map = new int[255];
int count = 0;
for (int i = 0; i < ptr.Length; i++)
{
map[i] = data[(i * 3) + 0];
if (map[i] == 0xFF)
{
count = i;
break;
}
ptr[i] = BitConverter.ToInt16(data, (i * 3) + 1);
}
EncounterArea1[] areas = new EncounterArea1[count];
for (int i = 0; i < areas.Length; i++)
{
areas[i] = new EncounterArea1
{
Location = map[i],
Slots = GetSlots1Fishing(data, ref ptr[i])
};
}
return areas;
}
private static IEnumerable<EncounterSlot1> GetSlots1GrassWater(byte[] data, ref int ofs, SlotType t)
{
int rate = data[ofs++];
return rate == 0 ? Enumerable.Empty<EncounterSlot1>() : ReadSlots1(data, ref ofs, 10, t, rate);
}
private static EncounterSlot1[] GetSlots1Fishing(byte[] data, ref int ofs)
{
int count = data[ofs++];
return ReadSlots1(data, ref ofs, count, SlotType.Super_Rod, -1);
}
}
}

View file

@ -0,0 +1,283 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
/// <inheritdoc />
/// <summary>
/// <see cref="GameVersion.GSC"/> encounter area
/// </summary>
public sealed class EncounterArea2 : EncounterAreaGB
{
/// <summary>
/// Gets the encounter areas with <see cref="EncounterSlot"/> information from Generation 2 Grass/Water data.
/// </summary>
/// <param name="data">Input raw data.</param>
/// <returns>Array of encounter areas.</returns>
public static EncounterArea2[] GetArray2GrassWater(byte[] data)
{
int ofs = 0;
var areas = new List<EncounterArea2>();
areas.AddRange(GetAreas2(data, ref ofs, SlotType.Grass, 3, 7)); // Johto Grass
areas.AddRange(GetAreas2(data, ref ofs, SlotType.Surf, 1, 3)); // Johto Water
areas.AddRange(GetAreas2(data, ref ofs, SlotType.Grass, 3, 7)); // Kanto Grass
areas.AddRange(GetAreas2(data, ref ofs, SlotType.Surf, 1, 3)); // Kanto Water
areas.AddRange(GetAreas2(data, ref ofs, SlotType.Swarm, 3, 7)); // Swarm
areas.AddRange(GetAreas2(data, ref ofs, SlotType.Special, 1, 3)); // Union Cave
return areas.ToArray();
}
// Fishing Tables are not associated to a single map; a map picks a table to use.
// For all maps that use a table, create a new EncounterArea with reference to the table's slots.
private static readonly sbyte[] convMapIDtoFishLocationID =
{
-1, 1, -1, 0, 3, 3, 3, -1, 10, 3, 2, -1, -1, 2, 3, 0,
-1, -1, 3, -1, -1, -1, 3, -1, -1, -1, -1, 0, -1, -1, 0, 9,
1, 0, 2, 2, -1, 3, 7, 3, -1, 3, 4, 8, 2, -1, 2, 1,
-1, 3, -1, -1, -1, -1, -1, 0, 2, 2, -1, -1, 3, 1, -1, -1,
-1, 2, -1, 2, -1, -1, -1, -1, -1, -1, 10, 10, -1, -1, -1, -1,
-1, 7, 0, 1, -1, 1, 1, 3, -1, -1, -1, 1, 1, 2, 3, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
};
/// <summary>
/// Gets the encounter areas with <see cref="EncounterSlot"/> information from Generation 2 Grass/Water data.
/// </summary>
/// <param name="data">Input raw data.</param>
/// <returns>Array of encounter areas.</returns>
public static EncounterArea2[] GetArray2Fishing(byte[] data)
{
int ofs = 0;
var f = GetAreas2Fishing(data, ref ofs);
var areas = new List<EncounterArea2>();
for (int i = 0; i < convMapIDtoFishLocationID.Length; i++)
{
var loc = convMapIDtoFishLocationID[i];
if (loc == -1) // no table for map
continue;
areas.Add(new EncounterArea2 { Location = i, Slots = f[loc].Slots });
}
// Some maps have two tables. Fortunately, there's only two. Add the second table.
areas.Add(new EncounterArea2 { Location = 0x1B, Slots = f[1].Slots }); // Olivine City (0: Harbor, 1: City)
areas.Add(new EncounterArea2 { Location = 0x2E, Slots = f[3].Slots }); // Silver Cave (2: Inside, 3: Outside)
return areas.ToArray();
}
public static EncounterArea2[] GetArray2Headbutt(byte[] data)
{
int ofs = 0;
return GetAreas2Headbutt(data, ref ofs).ToArray();
}
private static EncounterSlot1[] GetSlots2GrassWater(byte[] data, ref int ofs, SlotType t, int slotSets, int slotCount)
{
byte[] rates = new byte[slotSets];
for (int i = 0; i < rates.Length; i++)
rates[i] = data[ofs++];
var slots = ReadSlots1(data, ref ofs, slotSets * slotCount, t, rates[0]);
if (slotSets <= 1)
return slots;
for (int i = 0; i < slotCount; i++)
{
slots[i].Time = EncounterTime.Morning;
}
for (int r = 1; r < slotSets; r++)
{
for (int i = 0; i < slotCount; i++)
{
int index = i + (r * slotCount);
slots[index].Rate = rates[r];
slots[index].SlotNumber = i;
slots[index].Time = r == 1 ? EncounterTime.Day : EncounterTime.Night;
}
}
return slots;
}
private static EncounterSlot1[] GetSlots2Fishing(byte[] data, ref int ofs, SlotType t)
{
// slot set ends with final slot having 0xFF 0x** 0x**
const int size = 3;
int end = ofs; // scan for count
while (data[end] != 0xFF)
end += size;
var count = ((end - ofs) / size) + 1;
var slots = new EncounterSlot1[count];
for (int i = 0; i < slots.Length; i++)
{
int rate = data[ofs++];
int species = data[ofs++];
int level = data[ofs++];
slots[i] = new EncounterSlot1
{
Rate = rate,
Species = species,
LevelMin = level,
LevelMax = level,
SlotNumber = i,
Type = species == 0 ? SlotType.Special : t // day/night specific
};
}
return slots;
}
private static EncounterSlot1[] GetSlots2Headbutt(byte[] data, ref int ofs, SlotType t)
{
// slot set ends in 0xFF
var slots = new List<EncounterSlot1>();
int tableCount = t == SlotType.Headbutt ? 2 : 1;
SlotType slottype = t;
while (tableCount != 0)
{
if (t == SlotType.Headbutt)
slottype = tableCount == 2 ? SlotType.Headbutt_Special : SlotType.Headbutt;
int rate = data[ofs++];
if (rate == 0xFF) // end of table
{
tableCount--;
continue;
}
int species = data[ofs++];
int level = data[ofs++];
slots.Add(new EncounterSlot1
{
Rate = rate,
Species = species,
LevelMin = level,
LevelMax = level,
Type = slottype
});
}
return slots.ToArray();
}
private static IEnumerable<EncounterArea2> GetAreas2(byte[] data, ref int ofs, SlotType t, int slotSets, int slotCount)
{
var areas = new List<EncounterArea2>();
while (data[ofs] != 0xFF) // end
{
var location = data[ofs++] << 8 | data[ofs++];
var slots = GetSlots2GrassWater(data, ref ofs, t, slotSets, slotCount);
var area = new EncounterArea2
{
Location = location,
Slots = slots,
};
foreach (var slot in slots)
slot.Area = area;
areas.Add(area);
}
ofs++;
return areas;
}
private static List<EncounterArea2> GetAreas2Fishing(byte[] data, ref int ofs)
{
var areas = new List<EncounterArea2>();
var types = new[] { SlotType.Old_Rod, SlotType.Good_Rod, SlotType.Super_Rod };
while (ofs != 0x18C)
{
areas.Add(new EncounterArea2
{
Slots = GetSlots2Fishing(data, ref ofs, types[0])
.Concat(GetSlots2Fishing(data, ref ofs, types[1]))
.Concat(GetSlots2Fishing(data, ref ofs, types[2])).ToArray()
});
}
// Read TimeFishGroups
var dl = new List<DexLevel>();
while (ofs < data.Length)
dl.Add(new DexLevel { Species = data[ofs++], Level = data[ofs++] });
// Add TimeSlots
foreach (var area in areas)
{
var slots = area.Slots;
for (int i = 0; i < slots.Length; i++)
{
var slot = slots[i];
if (slot.Type != SlotType.Special)
continue;
Array.Resize(ref slots, slots.Length + 1);
Array.Copy(slots, i, slots, i + 1, slots.Length - i - 1); // shift slots down
slots[i + 1] = slot.Clone(); // differentiate copied slot
int index = slot.LevelMin * 2;
for (int j = 0; j < 2; j++) // load special slot info
{
var s = (EncounterSlot1)slots[i + j];
s.Species = dl[index + j].Species;
s.LevelMin = s.LevelMax = dl[index + j].Level;
s.Type = slots[i - 1].Type; // special slots are never first in a set, so copy previous type
s.Time = j == 0 ? EncounterTime.Morning | EncounterTime.Day : EncounterTime.Night;
}
}
area.Slots = slots;
}
return areas;
}
private static IEnumerable<EncounterArea2> GetAreas2Headbutt(byte[] data, ref int ofs)
{
// Read Location Table
var head = new List<EncounterArea2>();
var headID = new List<int>();
while (data[ofs] != 0xFF)
{
head.Add(new EncounterArea2
{
Location = (data[ofs++] << 8) | data[ofs++],
//Slots = null, // later
});
headID.Add(data[ofs++]);
}
ofs++;
var rock = new List<EncounterArea2>();
var rockID = new List<int>();
while (data[ofs] != 0xFF)
{
rock.Add(new EncounterArea2
{
Location = (data[ofs++] << 8) | data[ofs++],
//Slots = null, // later
});
rockID.Add(data[ofs++]);
}
ofs++;
ofs += 0x16; // jump over GetTreeMons
// Read ptr table
int[] ptr = new int[data.Length == 0x109 ? 6 : 9]; // GS : C
for (int i = 0; i < ptr.Length; i++)
ptr[i] = data[ofs++] | (data[ofs++] << 8);
int baseOffset = ptr.Min() - ofs;
// Read Tables
for (int i = 0; i < head.Count; i++)
{
int o = ptr[headID[i]] - baseOffset;
head[i].Slots = GetSlots2Headbutt(data, ref o, SlotType.Headbutt);
}
for (int i = 0; i < rock.Count; i++)
{
int o = ptr[rockID[i]] - baseOffset;
rock[i].Slots = GetSlots2Headbutt(data, ref o, SlotType.Rock_Smash);
}
return head.Concat(rock);
}
}
}

View file

@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
/// <summary>
/// <see cref="GameVersion.Gen3"/> encounter area
/// </summary>
public sealed class EncounterArea3 : EncounterArea
{
private static IEnumerable<EncounterSlot> GetSlots3(byte[] data, ref int ofs, int numslots, SlotType t)
{
var slots = new List<EncounterSlot>();
int Ratio = data[ofs];
//1 byte padding
if (Ratio > 0)
ReadInSlots(data, ofs, numslots, t, slots);
ofs += 2 + (numslots * 4);
return slots;
}
private static void ReadInSlots(byte[] data, int ofs, int numslots, SlotType t, List<EncounterSlot> slots)
{
for (int i = 0; i < numslots; i++)
{
int o = ofs + (i * 4);
int species = BitConverter.ToInt16(data, o + 4);
if (species <= 0)
continue;
slots.Add(new EncounterSlot
{
LevelMin = data[o + 2],
LevelMax = data[o + 3],
Species = species,
SlotNumber = i,
Type = t
});
}
}
private static IEnumerable<EncounterSlot> GetSlots3Fishing(byte[] data, ref int ofs, int numslots)
{
var slots = new List<EncounterSlot>();
int Ratio = data[ofs];
//1 byte padding
if (Ratio > 0)
ReadFishingSlots(data, ofs, numslots, slots);
ofs += 2 + (numslots * 4);
return slots;
}
private static void ReadFishingSlots(byte[] data, int ofs, int numslots, List<EncounterSlot> slots)
{
for (int i = 0; i < numslots; i++)
{
int Species = BitConverter.ToInt16(data, ofs + 4 + (i * 4));
if (Species <= 0)
continue;
var slot = new EncounterSlot
{
LevelMin = data[ofs + 2 + (i * 4)],
LevelMax = data[ofs + 3 + (i * 4)],
Species = Species,
};
if (i < 2)
{
slot.Type = SlotType.Old_Rod;
slot.SlotNumber = i; // 0,1
}
else if (i < 5)
{
slot.Type = SlotType.Good_Rod;
slot.SlotNumber = i - 2; // 0,1,2
}
else
{
slot.Type = SlotType.Super_Rod;
slot.SlotNumber = i - 5; // 0,1,2,3,4
}
slots.Add(slot);
}
}
private static EncounterArea3 GetArea3(byte[] data)
{
var HaveGrassSlots = data[1] == 1;
var HaveSurfSlots = data[2] == 1;
var HaveRockSmashSlots = data[3] == 1;
var HaveFishingSlots = data[4] == 1;
int offset = 5;
var slots = new List<EncounterSlot>();
if (HaveGrassSlots)
slots.AddRange(GetSlots3(data, ref offset, 12, SlotType.Grass));
if (HaveSurfSlots)
slots.AddRange(GetSlots3(data, ref offset, 5, SlotType.Surf));
if (HaveRockSmashSlots)
slots.AddRange(GetSlots3(data, ref offset, 5, SlotType.Rock_Smash));
if (HaveFishingSlots)
slots.AddRange(GetSlots3Fishing(data, ref offset, 10));
var area = new EncounterArea3
{
Location = data[0],
Slots = slots.ToArray()
};
foreach (var slot in area.Slots)
slot.Area = area;
return area;
}
/// <summary>
/// Gets the encounter areas with <see cref="EncounterSlot"/> information from Generation 3 data.
/// </summary>
/// <param name="entries">Raw data, one byte array per encounter area</param>
/// <returns>Array of encounter areas.</returns>
public static EncounterArea3[] GetArray3(byte[][] entries)
{
return entries.Select(GetArea3).Where(Area => Area.Slots.Length != 0).ToArray();
}
}
}

View file

@ -0,0 +1,43 @@
using System;
namespace PKHeX.Core
{
/// <inheritdoc />
/// <summary>
/// Base encounter class for manually repacked areas
/// </summary>
/// <remarks>
/// Encounter Data is stored in the following format: (u16 Location, n*[u16 Species/Form, u8 Min, u8 Max]), hence the 32bit name
/// </remarks>
public abstract class EncounterArea32 : EncounterArea
{
protected internal void LoadSlots(byte[] areaData)
{
var count = (areaData.Length - 2) / 4;
Location = BitConverter.ToUInt16(areaData, 0);
Slots = new EncounterSlot[count];
for (int i = 0; i < Slots.Length; i++)
{
int ofs = 2 + (i * 4);
ushort SpecForm = BitConverter.ToUInt16(areaData, ofs);
Slots[i] = new EncounterSlot
{
Species = SpecForm & 0x7FF,
Form = SpecForm >> 11,
LevelMin = areaData[ofs + 2],
LevelMax = areaData[ofs + 3],
};
}
foreach (var slot in Slots)
slot.Area = this;
}
protected static EncounterSlot GetPressureSlot(EncounterSlot s, PKM pkm)
{
var max = s.Clone();
max.Permissions.Pressure = true;
max.Form = pkm.AltForm;
return max;
}
}
}

View file

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
/// <summary>
/// <see cref="GameVersion.Gen4"/> encounter area
/// </summary>
public abstract class EncounterArea4 : EncounterArea
{
/// <summary>
/// Reads the GBA Pak Special slots, cloning <see cref="EncounterSlot"/> data from the area's base encounter slots.
/// </summary>
/// <remarks>
/// These special slots only contain the info of species id; the level is copied from the corresponding <see cref="slotnums"/> index.
/// </remarks>
/// <param name="data">Encounter binary data</param>
/// <param name="ofs">Offset to read from</param>
/// <param name="slotSize">DP/Pt slotSize = 4 bytes/entry, HG/SS slotSize = 2 bytes/entry</param>
/// <param name="ReplacedSlots">Slots from regular encounter table that end up replaced by in-game conditions</param>
/// <param name="slotnums">Slot indexes to replace with read species IDs</param>
/// <param name="t">Slot type of the special encounter</param>
protected static List<EncounterSlot> GetSlots4GrassSlotReplace(byte[] data, int ofs, int slotSize, EncounterSlot[] ReplacedSlots, int[] slotnums, SlotType t = SlotType.Grass)
{
var slots = new List<EncounterSlot>();
int numslots = slotnums.Length;
for (int i = 0; i < numslots; i++)
{
var baseSlot = ReplacedSlots[slotnums[i]];
if (baseSlot.LevelMin <= 0)
continue;
int species = BitConverter.ToUInt16(data, ofs + (i / (4 / slotSize) * slotSize));
if (species <= 0 || baseSlot.Species == species) // Empty or duplicate
continue;
var slot = baseSlot.Clone();
slot.Species = species;
slot.Type = t;
slot.SlotNumber = i;
slots.Add(slot);
}
return slots;
}
protected static IEnumerable<EncounterSlot> MarkStaticMagnetExtras(IEnumerable<IEnumerable<List<EncounterSlot>>> product)
{
var trackPermute = new List<EncounterSlot>();
foreach (var p in product)
MarkStaticMagnetPermute(p.SelectMany(z => z), trackPermute);
return trackPermute;
}
protected static void MarkStaticMagnetPermute(IEnumerable<EncounterSlot> grp, List<EncounterSlot> trackPermute)
{
EncounterUtil.MarkEncountersStaticMagnetPullPermutation(grp, PersonalTable.HGSS, trackPermute);
}
}
}

View file

@ -0,0 +1,200 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
public sealed class EncounterArea4DPPt : EncounterArea4
{
/// <summary>
/// Gets the encounter areas with <see cref="EncounterSlot"/> information from Generation 4 Diamond, Pearl and Platinum data.
/// </summary>
/// <param name="entries">Raw data, one byte array per encounter area</param>
/// <param name="pt">Platinum flag (for Trophy Garden slot insertion)</param>
/// <returns>Array of encounter areas.</returns>
public static EncounterArea4DPPt[] GetArray4DPPt(byte[][] entries, bool pt = false)
{
return entries.Select(z => GetArea4DPPt(z, pt)).Where(Area => Area.Slots.Length != 0).ToArray();
}
private static EncounterSlot[] GetSlots4GrassDPPt(byte[] data, int ofs, int numslots, SlotType t)
{
var slots = new EncounterSlot[numslots];
for (int i = 0; i < numslots; i++)
{
int o = ofs + (i * 8);
int level = data[o];
int species = BitConverter.ToInt32(data, o + 4);
slots[i] = new EncounterSlot
{
LevelMax = level,
LevelMin = level,
Species = species,
SlotNumber = i,
Type = t
};
}
return slots;
}
private static IEnumerable<EncounterSlot> GetSlots4WaterFishingDPPt(byte[] data, int ofs, int numslots, SlotType t)
{
var slots = new List<EncounterSlot>();
for (int i = 0; i < numslots; i++)
{
// max, min, unused, unused, [32bit species]
int Species = BitConverter.ToInt32(data, ofs + 4 + (i * 8));
if (Species <= 0)
continue;
// Fishing and Surf slots without a species ID are not added
// DPPt does not have fishing or surf swarms, and does not have any Rock Smash encounters.
slots.Add(new EncounterSlot
{
LevelMax = data[ofs + 0 + (i * 8)],
LevelMin = data[ofs + 1 + (i * 8)],
Species = Species,
SlotNumber = i,
Type = t
});
}
EncounterUtil.MarkEncountersStaticMagnetPull(slots, PersonalTable.HGSS);
return slots;
}
private static EncounterArea4DPPt GetArea4DPPt(byte[] data, bool pt = false)
{
var Slots = new List<EncounterSlot>();
int location = BitConverter.ToUInt16(data, 0x00);
var GrassRatio = BitConverter.ToInt32(data, 0x02);
if (GrassRatio > 0)
{
EncounterSlot[] GrassSlots = GetSlots4GrassDPPt(data, 0x06, 12, SlotType.Grass);
//Swarming slots replace slots 0 and 1
var swarm = GetSlots4GrassSlotReplace(data, 0x66, 4, GrassSlots, Legal.Slot4_Swarm, SlotType.Swarm);
//Morning and Night slots replace slots 2 and 3
var morning = GetSlots4GrassSlotReplace(data, 0x6E, 4, GrassSlots, Legal.Slot4_Time); // Morning
var night = GetSlots4GrassSlotReplace(data, 0x76, 4, GrassSlots, Legal.Slot4_Time); // Night
//Pokéradar slots replace slots 4,5,10 and 11
//Pokéradar is marked with different slot type because it have different PID-IV generationn
var radar = GetSlots4GrassSlotReplace(data, 0x7E, 4, GrassSlots, Legal.Slot4_Radar, SlotType.Pokeradar);
//24 bytes padding
//Dual Slots replace slots 8 and 9
var ruby = GetSlots4GrassSlotReplace(data, 0xA6, 4, GrassSlots, Legal.Slot4_Dual); // Ruby
var sapphire = GetSlots4GrassSlotReplace(data, 0xAE, 4, GrassSlots, Legal.Slot4_Dual); // Sapphire
var emerald = GetSlots4GrassSlotReplace(data, 0xB6, 4, GrassSlots, Legal.Slot4_Dual); // Emerald
var firered = GetSlots4GrassSlotReplace(data, 0xBE, 4, GrassSlots, Legal.Slot4_Dual); // FireRed
var leafgreen = GetSlots4GrassSlotReplace(data, 0xC6, 4, GrassSlots, Legal.Slot4_Dual); // LeafGreen
Slots.AddRange(GrassSlots);
Slots.AddRange(swarm);
Slots.AddRange(morning);
Slots.AddRange(night);
Slots.AddRange(radar);
Slots.AddRange(ruby);
Slots.AddRange(sapphire);
Slots.AddRange(emerald);
Slots.AddRange(firered);
Slots.AddRange(leafgreen);
// Permute Static-Magnet Pull combinations
// [None/Swarm]-[None/Morning/Night]-[None/Radar]-[None/R/S/E/F/L] [None/TrophyGarden]
// 2 * 3 * 2 * 6 = 72 different combinations of slots (more with trophy garden)
var regular = new List<List<EncounterSlot>> { GrassSlots.Where(z => z.SlotNumber == 6 || z.SlotNumber == 7).ToList() }; // every other slot is in the product
var pair0 = new List<List<EncounterSlot>> { GrassSlots.Where(z => Legal.Slot4_Swarm.Contains(z.SlotNumber)).ToList() };
var pair1 = new List<List<EncounterSlot>> { GrassSlots.Where(z => Legal.Slot4_Time.Contains(z.SlotNumber)).ToList() };
var pair2 = new List<List<EncounterSlot>> { GrassSlots.Where(z => Legal.Slot4_Radar.Contains(z.SlotNumber)).ToList() };
var pair3 = new List<List<EncounterSlot>> { GrassSlots.Where(z => Legal.Slot4_Dual.Contains(z.SlotNumber)).ToList() };
if (swarm.Count != 0) pair0.Add(swarm);
if (morning.Count != 0) pair1.Add(morning); if (night.Count != 0) pair1.Add(night);
if (radar.Count != 0) pair2.Add(radar);
if (ruby.Count != 0) pair3.Add(ruby); if (sapphire.Count != 0) pair3.Add(sapphire); if (emerald.Count != 0) pair3.Add(emerald);
if (firered.Count != 0) pair3.Add(firered); if (leafgreen.Count != 0) pair3.Add(leafgreen);
if (location == 68) // Trophy Garden
{
// Occupy Slots 6 & 7
var species = pt ? Encounters4.TrophyPt : Encounters4.TrophyDP;
var slots = new List<EncounterSlot>();
foreach (var s in species)
{
var slot = regular[0][0].Clone();
slot.Species = s;
slots.Add(slot);
slot = regular[0][1].Clone();
slot.Species = s;
slots.Add(slot);
}
Slots.AddRange(slots);
// get all permutations of trophy inhabitants
var trophy = regular[0].Concat(slots).ToArray();
for (int i = 0; i < trophy.Length; i++)
{
for (int j = i + 1; j < trophy.Length; j++)
regular.Add(new List<EncounterSlot> { trophy[i], trophy[j] });
}
}
var set = new[] { regular, pair0, pair1, pair2, pair3 };
var product = set.CartesianProduct();
var extra = MarkStaticMagnetExtras(product);
Slots.AddRange(extra);
}
var SurfRatio = BitConverter.ToInt32(data, 0xCE);
if (SurfRatio > 0)
Slots.AddRange(GetSlots4WaterFishingDPPt(data, 0xD2, 5, SlotType.Surf));
//44 bytes padding
var OldRodRatio = BitConverter.ToInt32(data, 0x126);
if (OldRodRatio > 0)
Slots.AddRange(GetSlots4WaterFishingDPPt(data, 0x12A, 5, SlotType.Old_Rod));
var GoodRodRatio = BitConverter.ToInt32(data, 0x152);
if (GoodRodRatio > 0)
Slots.AddRange(GetSlots4WaterFishingDPPt(data, 0x156, 5, SlotType.Good_Rod));
var SuperRodRatio = BitConverter.ToInt32(data, 0x17E);
if (SuperRodRatio > 0)
Slots.AddRange(GetSlots4WaterFishingDPPt(data, 0x182, 5, SlotType.Super_Rod));
var Area4 = new EncounterArea4DPPt
{
Location = location,
Slots = Slots.ToArray()
};
foreach (var slot in Area4.Slots)
slot.Area = Area4;
return Area4;
}
private EncounterArea4DPPt Clone(int location) => new EncounterArea4DPPt { Slots = Slots, Location = location};
public EncounterArea4DPPt[] Clone(int[] locations)
{
var Areas = new EncounterArea4DPPt[locations.Length];
for (int i = 0; i < locations.Length; i++)
Areas[i] = Clone(locations[i]);
return Areas;
}
}
public static class DPEncounterExtensions
{
public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
{
IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>() };
return sequences.Aggregate(
emptyProduct,
(accumulator, sequence) =>
from accseq in accumulator
from item in sequence
select accseq.Concat(new[] { item }));
}
}
}

View file

@ -0,0 +1,206 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
/// <inheritdoc />
/// <summary>
/// <see cref="GameVersion.HGSS"/> encounter area
/// </summary>
public sealed class EncounterArea4HGSS : EncounterArea4
{
/// <summary>
/// Gets the encounter areas with <see cref="EncounterSlot"/> information from Generation 4 Heart Gold and Soul Silver data.
/// </summary>
/// <param name="entries">Raw data, one byte array per encounter area</param>
/// <returns>Array of encounter areas.</returns>
public static EncounterArea4HGSS[] GetArray4HGSS(byte[][] entries)
{
return entries.Select(GetArea4HGSS).Where(Area => Area.Slots.Length != 0).ToArray();
}
/// <summary>
/// Gets the encounter areas with <see cref="EncounterSlot"/> information from Generation 4 Heart Gold and Soul Silver Headbutt tree data.
/// </summary>
/// <param name="entries">Raw data, one byte array per encounter area</param>
/// <returns>Array of encounter areas.</returns>
public static EncounterArea4HGSS[] GetArray4HGSS_Headbutt(byte[][] entries)
{
return entries.Select(GetArea4HeadbuttHGSS).Where(Area => Area.Slots.Length != 0).ToArray();
}
private static EncounterSlot[] GetSlots4GrassHGSS(byte[] data, int ofs, int numslots, SlotType t)
{
var slots = new EncounterSlot[numslots * 3];
// First 36 slots are morning, day and night grass slots
// The order is 12 level values, 12 morning species, 12 day species and 12 night species
for (int i = 0; i < numslots; i++)
{
int level = data[ofs + i];
int species = BitConverter.ToUInt16(data, ofs + numslots + (i * 2));
slots[i] = new EncounterSlot
{
LevelMin = level,
LevelMax = level,
Species = species,
SlotNumber = i,
Type = t
};
slots[numslots + i] = slots[i].Clone();
slots[numslots + i].Species = BitConverter.ToUInt16(data, ofs + (numslots * 3) + (i * 2));
slots[numslots + i].Type = t;
slots[(numslots * 2) + i] = slots[i].Clone();
slots[(numslots * 2) + i].Species = BitConverter.ToUInt16(data, ofs + (numslots * 5) + (i * 2));
slots[(numslots * 2) + i].Type = t;
}
return slots;
}
private static IEnumerable<EncounterSlot> GetSlots4WaterFishingHGSS(byte[] data, int ofs, int numslots, SlotType t)
{
var slots = new List<EncounterSlot>();
for (int i = 0; i < numslots; i++)
{
// min, max, [16bit species]
int Species = BitConverter.ToInt16(data, ofs + 2 + (i * 4));
if (t == SlotType.Rock_Smash && Species <= 0)
continue;
// Fishing and surf Slots without a species ID are added too; these are needed for the swarm encounters.
// These empty slots will will be deleted after we add swarm slots.
slots.Add(new EncounterSlot
{
LevelMin = data[ofs + 0 + (i * 4)],
LevelMax = data[ofs + 1 + (i * 4)],
Species = Species,
SlotNumber = i,
Type = t
});
}
EncounterUtil.MarkEncountersStaticMagnetPull(slots, PersonalTable.HGSS);
return slots;
}
private static EncounterArea4HGSS GetArea4HGSS(byte[] data)
{
var Slots = new List<EncounterSlot>();
var GrassRatio = data[0x02];
var SurfRatio = data[0x03];
var RockSmashRatio = data[0x04];
var OldRodRatio = data[0x05];
var GoodRodRatio = data[0x06];
var SuperRodRatio = data[0x07];
// 2 bytes padding
if (GrassRatio > 0)
{
// First 36 slots are morning, day and night grass slots
// The order is 12 level values, 12 morning species, 12 day species and 12 night species
var GrassSlots = GetSlots4GrassHGSS(data, 0x0A, 12, SlotType.Grass);
//Grass slots with species = 0 are added too, it is needed for the swarm encounters, it will be deleted after swarms are added
// Hoenn Sound and Sinnoh Sound replace slots 4 and 5
var hoenn = GetSlots4GrassSlotReplace(data, 0x5E, 2, GrassSlots, Legal.Slot4_Sound); // Hoenn
var sinnoh = GetSlots4GrassSlotReplace(data, 0x62, 2, GrassSlots, Legal.Slot4_Sound); // Sinnoh
Slots.AddRange(GrassSlots);
Slots.AddRange(hoenn);
Slots.AddRange(sinnoh);
// Static / Magnet Pull
var grass1 = GrassSlots.Take(12).ToList();
var grass2 = GrassSlots.Skip(12).Take(12).ToList();
var grass3 = GrassSlots.Skip(24).ToList();
// Swarm slots do not displace electric/steel types, with exception of SoulSilver Mawile (which doesn't displace) -- handle separately
foreach (var time in new[] { grass1, grass2, grass3 })
{
// non radio
var regular = time.Where(z => !Legal.Slot4_Sound.Contains(z.SlotNumber)).ToList(); // every other slot is in the product
var radio = new List<List<EncounterSlot>> { time.Where(z => Legal.Slot4_Sound.Contains(z.SlotNumber)).ToList() };
if (hoenn.Count > 0)
radio.Add(hoenn);
if (sinnoh.Count > 0)
radio.Add(sinnoh);
var extra = new List<EncounterSlot>();
foreach (var t in radio)
MarkStaticMagnetPermute(regular.Concat(t), extra);
Slots.AddRange(extra);
}
}
if (SurfRatio > 0)
Slots.AddRange(GetSlots4WaterFishingHGSS(data, 0x66, 5, SlotType.Surf));
if (RockSmashRatio > 0)
Slots.AddRange(GetSlots4WaterFishingHGSS(data, 0x7A, 2, SlotType.Rock_Smash));
if (OldRodRatio > 0)
Slots.AddRange(GetSlots4WaterFishingHGSS(data, 0x82, 5, SlotType.Old_Rod));
if (GoodRodRatio > 0)
Slots.AddRange(GetSlots4WaterFishingHGSS(data, 0x96, 5, SlotType.Good_Rod));
if (SuperRodRatio > 0)
Slots.AddRange(GetSlots4WaterFishingHGSS(data, 0xAA, 5, SlotType.Super_Rod));
// Last 6 bytes only have species ID info
if (data[0xC2] == 120) // Location = 182, 127, 130, 132, 167, 188, 210
Slots.AddRange(SlotsHGSS_Staryu);
var Area4 = new EncounterArea4HGSS
{
Location = BitConverter.ToUInt16(data, 0x00),
Slots = Slots.ToArray()
};
foreach (var slot in Area4.Slots)
slot.Area = Area4;
return Area4;
}
private static readonly EncounterSlot[] SlotsHGSS_Staryu =
{
new EncounterSlot { Species = 120, LevelMin = 20, LevelMax = 20, Type = SlotType.Good_Rod },
new EncounterSlot { Species = 120, LevelMin = 40, LevelMax = 40, Type = SlotType.Super_Rod },
};
private static EncounterArea4HGSS GetArea4HeadbuttHGSS(byte[] data)
{
if (data.Length < 78)
return new EncounterArea4HGSS(); // bad data
//2 byte location ID (defer to end)
//4 bytes padding
var Slots = new List<EncounterSlot>();
// 00-11 Normal trees
// 12-17 Special trees
for (int i = 0; i < 18; i++)
{
int Species = BitConverter.ToInt16(data, 6 + (i * 4));
if (Species <= 0)
continue;
Slots.Add(new EncounterSlot
{
Species = Species,
LevelMin = data[8 + (i * 4)],
LevelMax = data[9 + (i * 4)],
Type = i <= 11 ? SlotType.Headbutt : SlotType.Headbutt_Special
});
}
var Area = new EncounterArea4HGSS
{
Location = BitConverter.ToUInt16(data, 0),
Slots = Slots.ToArray()
};
foreach (var slot in Area.Slots)
slot.Area = Area;
return Area;
}
}
}

View file

@ -0,0 +1,10 @@
namespace PKHeX.Core
{
/// <inheritdoc />
/// <summary>
/// <see cref="GameVersion.Gen5"/> encounter area
/// </summary>
public sealed class EncounterArea5 : EncounterArea32
{
}
}

View file

@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
/// <inheritdoc />
/// <summary>
/// <see cref="GameVersion.ORAS"/> encounter area
/// </summary>
public sealed class EncounterArea6AO : EncounterArea32
{
private const int FluteBoostMax = 3; // Black Flute increases levels, White Flute decreases levels.
private const int DexNavBoost = 30;
protected override IEnumerable<EncounterSlot> GetMatchFromEvoLevel(PKM pkm, IEnumerable<DexLevel> vs, int minLevel)
{
var slots = Slots.Where(slot => vs.Any(evo => evo.Species == slot.Species && evo.Level >= (slot.LevelMin - FluteBoostMax)));
int getMaxLevelBoost(EncounterSlot s) => s.Type == SlotType.Rock_Smash ? FluteBoostMax : DexNavBoost;
int fluteMinLevel = minLevel - FluteBoostMax;
// Get slots where pokemon can exist with respect to level constraints
return slots.Where(s => s.IsLevelWithinRange(fluteMinLevel, minLevel - getMaxLevelBoost(s)));
}
protected override IEnumerable<EncounterSlot> GetFilteredSlots(PKM pkm, IEnumerable<EncounterSlot> slots, int minLevel)
{
EncounterSlot slotMax = null;
foreach (EncounterSlot s in slots)
{
if (Legal.WildForms.Contains(pkm.Species) && s.Form != pkm.AltForm)
{
CachePressureSlot(s);
continue;
}
bool nav = s.Permissions.AllowDexNav && (pkm.RelearnMove1 != 0 || pkm.AbilityNumber == 4);
EncounterSlot slot = s.Clone();
slot.Permissions.DexNav = nav;
if (slot.LevelMin > minLevel)
slot.Permissions.WhiteFlute = true;
if (slot.LevelMax + 1 <= minLevel && minLevel <= slot.LevelMax + FluteBoostMax)
slot.Permissions.BlackFlute = true;
if (slot.LevelMax != minLevel && slot.Permissions.AllowDexNav)
slot.Permissions.DexNav = true;
yield return slot;
CachePressureSlot(slot);
}
void CachePressureSlot(EncounterSlot s)
{
if (slotMax != null && s.LevelMax > slotMax.LevelMax)
slotMax = s;
}
// Pressure Slot
if (slotMax != null)
yield return GetPressureSlot(slotMax, pkm);
}
}
}

View file

@ -0,0 +1,67 @@
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
/// <inheritdoc />
/// <summary>
/// <see cref="GameVersion.XY"/> encounter area
/// </summary>
public sealed class EncounterArea6XY : EncounterArea32
{
protected override IEnumerable<EncounterSlot> GetFilteredSlots(PKM pkm, IEnumerable<EncounterSlot> slots, int minLevel)
{
EncounterSlot slotMax = null;
void CachePressureSlot(EncounterSlot s)
{
if (slotMax == null || s.LevelMax > slotMax.LevelMax)
slotMax = s;
}
int species = pkm.Species;
int form = pkm.AltForm;
bool ShouldMatchSlotForm() => Legal.WildForms.Contains(species);
if (ShouldMatchSlotForm()) // match slot form
{
foreach (var slot in slots)
{
if (slot.Form == form)
yield return slot;
CachePressureSlot(slot);
}
}
else
{
foreach (var slot in slots)
{
yield return slot; // no form checking
CachePressureSlot(slot);
}
}
// Filter for Form Specific
// Pressure Slot
if (slotMax == null)
yield break;
if (ShouldMatchSlotForm()) // match slot form
{
if (slotMax.Form == form)
yield return GetPressureSlot(slotMax, pkm);
}
else
{
yield return GetPressureSlot(slotMax, pkm);
}
}
public static IEnumerable<EncounterSlot> GetValidFriendSafari(PKM pkm)
{
if (!pkm.XY || pkm.Met_Location != 148 || pkm.Met_Level != 30 || pkm.Egg_Location != 0) // Friend Safari
return Enumerable.Empty<EncounterSlot>();
var vs = EvolutionChain.GetValidPreEvolutions(pkm).Where(d => d.Level >= 30);
return vs.SelectMany(z => Encounters6.FriendSafari[z.Species]);
}
}
}

View file

@ -0,0 +1,80 @@
using System.Collections.Generic;
namespace PKHeX.Core
{
/// <summary>
/// <see cref="GameVersion.Gen7"/> encounter area
/// </summary>
public sealed class EncounterArea7 : EncounterArea32
{
protected override IEnumerable<EncounterSlot> GetFilteredSlots(PKM pkm, IEnumerable<EncounterSlot> slots, int minLevel)
{
int species = pkm.Species;
int form = pkm.AltForm;
// Edge Case Handling
switch (species)
{
case 744 when form == 1: // Rockruff Event
case 745 when form == 2: // Lycanroc Event
yield break;
}
EncounterSlot slotMax = null;
void CachePressureSlot(EncounterSlot s)
{
if (slotMax != null && s.LevelMax > slotMax.LevelMax)
slotMax = s;
}
if (Legal.AlolanVariantEvolutions12.Contains(species)) // match form if same species, else form 0.
{
foreach (var slot in slots)
{
if (species == slot.Species ? slot.Form == form : slot.Form == 0)
yield return slot;
CachePressureSlot(slot);
}
}
else if (ShouldMatchSlotForm()) // match slot form
{
foreach (var slot in slots)
{
if (slot.Form == form)
yield return slot;
CachePressureSlot(slot);
}
}
else
{
foreach (var slot in slots)
{
yield return slot; // no form checking
CachePressureSlot(slot);
}
}
// Filter for Form Specific
// Pressure Slot
if (slotMax == null)
yield break;
if (Legal.AlolanVariantEvolutions12.Contains(species)) // match form if same species, else form 0.
{
if (species == slotMax.Species ? slotMax.Form == form : slotMax.Form == 0)
yield return GetPressureSlot(slotMax, pkm);
}
else if (ShouldMatchSlotForm()) // match slot form
{
if (slotMax.Form == form)
yield return GetPressureSlot(slotMax, pkm);
}
else
{
yield return GetPressureSlot(slotMax, pkm);
}
bool ShouldMatchSlotForm() => Legal.WildForms.Contains(species) || Legal.AlolanOriginForms.Contains(species) || FormConverter.IsTotemForm(species, form);
}
}
}

View file

@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
/// <inheritdoc />
/// <summary>
/// <see cref="GameVersion.GG"/> encounter area
/// </summary>
public sealed class EncounterArea7b : EncounterArea32
{
private const int CatchComboBonus = 1;
protected override IEnumerable<EncounterSlot> GetMatchFromEvoLevel(PKM pkm, IEnumerable<DexLevel> vs, int minLevel)
{
var slots = Slots.Where(slot => vs.Any(evo => evo.Species == slot.Species && evo.Level >= (slot.LevelMin - CatchComboBonus)));
// Get slots where pokemon can exist with respect to level constraints
return slots.Where(s => s.IsLevelWithinRange(minLevel, minLevel - CatchComboBonus));
}
protected override IEnumerable<EncounterSlot> GetFilteredSlots(PKM pkm, IEnumerable<EncounterSlot> slots, int minLevel)
{
int species = pkm.Species;
int form = pkm.AltForm;
if (Legal.AlolanVariantEvolutions12.Contains(species)) // match form if same species, else form 0.
{
if (pkm.AltForm != 0 && pkm is PB7)
yield break; // can't get Alolan forms from wild
foreach (var slot in slots)
{
if (species == slot.Species ? slot.Form == form : slot.Form == 0)
yield return slot;
}
}
else if (form == 0)
{
// enforce no form
foreach (var slot in slots)
{
yield return slot;
}
}
}
}
}

View file

@ -0,0 +1,42 @@
using System.Collections.Generic;
namespace PKHeX.Core
{
/// <inheritdoc />
/// <summary>
/// <see cref="GameVersion.GO"/> encounter area for <see cref="GameVersion.GG"/>
/// </summary>
public sealed class EncounterArea7g : EncounterArea32
{
protected override IEnumerable<EncounterSlot> GetFilteredSlots(PKM pkm, IEnumerable<EncounterSlot> slots, int minLevel)
{
int species = pkm.Species;
int form = pkm.AltForm;
if (Legal.AlolanVariantEvolutions12.Contains(species)) // match form if same species, else form 0.
{
foreach (var slot in slots)
{
if (species == slot.Species ? slot.Form == form : slot.Form == 0)
yield return slot;
}
}
else if (Legal.AlolanOriginForms.Contains(species)) // match slot form
{
foreach (var slot in slots)
{
if (slot.Form == form)
yield return slot;
}
}
else if (form == 0)
{
// enforce no form
foreach (var slot in slots)
{
yield return slot;
}
}
}
}
}

View file

@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
/// <inheritdoc />
/// <summary>
/// Fake encounter area used to mock data
/// </summary>
public sealed class EncounterAreaFake : EncounterArea
{
protected override IEnumerable<EncounterSlot> GetMatchFromEvoLevel(PKM pkm, IEnumerable<DexLevel> vs, int minLevel)
=> Enumerable.Empty<EncounterSlot>();
protected override IEnumerable<EncounterSlot> GetFilteredSlots(PKM pkm, IEnumerable<EncounterSlot> slots, int minLevel)
=> Enumerable.Empty<EncounterSlot>();
}
}

View file

@ -0,0 +1,132 @@
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
/// <inheritdoc />
/// <summary>
/// <see cref="GameVersion.GBCartEraOnly"/> encounter area
/// </summary>
public abstract class EncounterAreaGB : EncounterArea
{
/// <summary>
/// RBY Format Slot Getter from data.
/// </summary>
/// <param name="data">Byte array containing complete slot data table.</param>
/// <param name="ofs">Offset to start reading from.</param>
/// <param name="count">Amount of slots to read.</param>
/// <param name="t">Type of encounter slot.</param>
/// <param name="rate">Slot type encounter rate.</param>
/// <returns>Array of encounter slots.</returns>
protected static EncounterSlot1[] ReadSlots1(byte[] data, ref int ofs, int count, SlotType t, int rate)
{
EncounterSlot1[] slots = new EncounterSlot1[count];
for (int i = 0; i < count; i++)
{
int lvl = data[ofs++];
int spec = data[ofs++];
slots[i] = new EncounterSlot1
{
LevelMax = t == SlotType.Surf ? lvl + 4 : lvl,
LevelMin = lvl,
Species = spec,
Type = t,
Rate = rate,
SlotNumber = i,
};
}
return slots;
}
public override IEnumerable<EncounterSlot> GetMatchingSlots(PKM pkm, IReadOnlyList<DexLevel> vs, int minLevel = 0)
{
if (minLevel == 0) // any
return Slots.Where(slot => vs.Any(evo => evo.Species == slot.Species));
var Gen1Version = GameVersion.RBY;
bool RBDragonair = false;
if (minLevel != 0 && !FilterGBSlotsCatchRate(pkm, ref vs, ref Gen1Version, ref RBDragonair))
return Enumerable.Empty<EncounterSlot>();
var encounterSlots = GetMatchFromEvoLevel(pkm, vs, minLevel);
return GetFilteredSlots(pkm, encounterSlots, Gen1Version, RBDragonair).OrderBy(slot => slot.LevelMin); // prefer lowest levels
}
private static bool FilterGBSlotsCatchRate(PKM pkm, ref IReadOnlyList<DexLevel> vs, ref GameVersion Gen1Version, ref bool RBDragonair)
{
if (!(pkm is PK1 pk1) || !pkm.Gen1_NotTradeback)
return true;
// Pure gen 1, slots can be filter by catch rate
var rate = pk1.Catch_Rate;
switch (pkm.Species)
{
// Pikachu
case (int)Species.Pikachu when rate == 163:
case (int)Species.Raichu when rate == 163:
return false; // Yellow Pikachu is not a wild encounter
// Kadabra (YW)
case (int)Species.Kadabra when rate == 96:
case (int)Species.Alakazam when rate == 96:
vs = vs.Where(s => s.Species == (int)Species.Kadabra).ToArray();
Gen1Version = GameVersion.YW;
return true;
// Kadabra (RB)
case (int)Species.Kadabra when rate == 100:
case (int)Species.Alakazam when rate == 100:
vs = vs.Where(s => s.Species == (int)Species.Kadabra).ToArray();
Gen1Version = GameVersion.RB;
return true;
// Dragonair (YW)
case (int)Species.Dragonair when rate == 27:
case (int)Species.Dragonite when rate == 27:
vs = vs.Where(s => s.Species == (int)Species.Dragonair).ToArray(); // Yellow Dragonair, ignore Dratini encounters
Gen1Version = GameVersion.YW;
return true;
// Dragonair (RB)
case (int)Species.Dragonair:
case (int)Species.Dragonite:
// Red blue dragonair have the same catch rate as dratini, it could also be a dratini from any game
vs = vs.Where(s => rate == PersonalTable.RB[s.Species].CatchRate).ToArray();
RBDragonair = true;
return true;
default:
vs = vs.Where(s => rate == PersonalTable.RB[s.Species].CatchRate).ToArray();
return true;
}
}
private static IEnumerable<EncounterSlot> GetFilteredSlots(PKM pkm, IEnumerable<EncounterSlot> slots,
GameVersion Gen1Version, bool RBDragonair)
{
int gen = pkm.GenNumber;
switch (gen)
{
case 1:
if (Gen1Version != GameVersion.RBY)
slots = slots.Where(slot => Gen1Version.Contains(slot.Version));
// Red Blue dragonair or dratini from any gen 1 games
if (RBDragonair)
return slots.Where(slot => GameVersion.RB.Contains(slot.Version) || slot.Species == 147);
return slots;
case 2:
if (pkm is PK2 pk2 && pk2.Met_TimeOfDay != 0)
return slots.Where(slot => ((EncounterSlot1)slot).Time.Contains(pk2.Met_TimeOfDay));
return slots;
default:
return slots;
}
}
}
}

View file

@ -10,7 +10,8 @@ namespace PKHeX.Core
/// </summary>
internal static class Encounters4
{
internal static readonly EncounterArea4[] SlotsD, SlotsP, SlotsPt, SlotsHG, SlotsSS;
internal static readonly EncounterArea4DPPt[] SlotsD, SlotsP, SlotsPt;
internal static readonly EncounterArea4HGSS[] SlotsHG, SlotsSS;
internal static readonly EncounterStatic[] StaticD, StaticP, StaticPt, StaticHG, StaticSS;
static Encounters4()
@ -26,16 +27,16 @@ namespace PKHeX.Core
byte[][] get(string resource, string ident)
=> Data.UnpackMini(Util.GetBinaryResource($"encounter_{resource}.pkl"), ident);
var D_Slots = EncounterArea4.GetArray4DPPt(get("d", "da"));
var P_Slots = EncounterArea4.GetArray4DPPt(get("p", "pe"));
var Pt_Slots = EncounterArea4.GetArray4DPPt(get("pt", "pt"), true);
var HG_Slots = EncounterArea4.GetArray4HGSS(get("hg", "hg"));
var SS_Slots = EncounterArea4.GetArray4HGSS(get("ss", "ss"));
var D_Slots = EncounterArea4DPPt.GetArray4DPPt(get("d", "da"));
var P_Slots = EncounterArea4DPPt.GetArray4DPPt(get("p", "pe"));
var Pt_Slots = EncounterArea4DPPt.GetArray4DPPt(get("pt", "pt"), true);
var HG_Slots = EncounterArea4HGSS.GetArray4HGSS(get("hg", "hg"));
var SS_Slots = EncounterArea4HGSS.GetArray4HGSS(get("ss", "ss"));
var DP_Feebas = GetFeebasArea(D_Slots[10]);
var Pt_Feebas = GetFeebasArea(Pt_Slots[10]);
var HG_Headbutt_Slots = EncounterArea4.GetArray4HGSS_Headbutt(get("hb_hg", "hg"));
var SS_Headbutt_Slots = EncounterArea4.GetArray4HGSS_Headbutt(get("hb_ss", "ss"));
var HG_Headbutt_Slots = EncounterArea4HGSS.GetArray4HGSS_Headbutt(get("hb_hg", "hg"));
var SS_Headbutt_Slots = EncounterArea4HGSS.GetArray4HGSS_Headbutt(get("hb_ss", "ss"));
var D_HoneyTrees_Slots = SlotsD_HoneyTree.Clone(HoneyTreesLocation);
var P_HoneyTrees_Slots = SlotsP_HoneyTree.Clone(HoneyTreesLocation);
@ -58,9 +59,8 @@ namespace PKHeX.Core
MarkG4SlotsGreatMarsh(P_Slots, 52);
MarkG4SlotsGreatMarsh(Pt_Slots, 52);
MarkEncounterAreaArray(D_HoneyTrees_Slots, P_HoneyTrees_Slots, Pt_HoneyTrees_Slots,
DP_GreatMarshAlt, Pt_GreatMarshAlt, DPPt_Unown, DP_Feebas, Pt_Feebas,
HG_Headbutt_Slots, SS_Headbutt_Slots, SlotsHGSSAlt);
MarkEncounterAreaArray(D_HoneyTrees_Slots, P_HoneyTrees_Slots, Pt_HoneyTrees_Slots, DP_GreatMarshAlt, Pt_GreatMarshAlt, DPPt_Unown, DP_Feebas, Pt_Feebas);
MarkEncounterAreaArray(HG_Headbutt_Slots, SS_Headbutt_Slots, SlotsHGSSAlt);
SlotsD = AddExtraTableSlots(D_Slots, D_HoneyTrees_Slots, DP_GreatMarshAlt, DPPt_Unown, DP_Feebas);
SlotsP = AddExtraTableSlots(P_Slots, P_HoneyTrees_Slots, DP_GreatMarshAlt, DPPt_Unown, DP_Feebas);
@ -98,7 +98,7 @@ namespace PKHeX.Core
TradeGift_HGSS.SetVersion(GameVersion.HGSS);
}
private static EncounterArea4[] GetFeebasArea(EncounterArea4 template)
private static EncounterArea4DPPt[] GetFeebasArea(EncounterArea4DPPt template)
{
Debug.Assert(template.Location == 50); // Mt Coronet
Debug.Assert(template.Slots.Last().Species == 340); // Whiscash
@ -110,7 +110,7 @@ namespace PKHeX.Core
s.TypeEncounter = EncounterType.Surfing_Fishing;
}
var area = new EncounterArea4
var area = new EncounterArea4DPPt
{
Location = template.Location,
Slots = slots,
@ -1063,17 +1063,17 @@ namespace PKHeX.Core
52, 202
};
private static readonly EncounterArea4[] DPPt_Unown =
private static readonly EncounterArea4DPPt[] DPPt_Unown =
{
new EncounterArea4 {
new EncounterArea4DPPt {
Location = 53, // Solaceon Ruins
Slots = new int[25].Select((_, i) => new EncounterSlot { Species = 201, LevelMin = 14, LevelMax = 30, Type = SlotType.Grass, Form = i+1 }).ToArray() // B->?, Unown A is loaded from encounters raw file
},
};
private static readonly EncounterArea4 SlotsHGSS_BCC =
private static readonly EncounterArea4HGSS SlotsHGSS_BCC =
new EncounterArea4
new EncounterArea4HGSS
{
// Source http://bulbapedia.bulbagarden.net/wiki/Bug-Catching_Contest#Generation_IV
Location = 207, // National Park Catching Contest
@ -1463,7 +1463,7 @@ namespace PKHeX.Core
new EncounterSlot { Species = 418, LevelMin = 44, LevelMax = 45, Type = SlotType.Grass_Safari }, // Buizel
};
private static readonly EncounterArea4 SlotsHGSS_SafariZone = new EncounterArea4
private static readonly EncounterArea4HGSS SlotsHGSS_SafariZone = new EncounterArea4HGSS
{
// Source http://bulbapedia.bulbagarden.net/wiki/Johto_Safari_Zone#Pok.C3.A9mon
// Supplement http://www.psypokes.com/hgss/safari_areas.php
@ -1483,23 +1483,23 @@ namespace PKHeX.Core
SAFARIZONE_WETLAND)
};
private static readonly EncounterArea4[] SlotsHGSSAlt =
private static readonly EncounterArea4HGSS[] SlotsHGSSAlt =
{
SlotsHGSS_BCC,
new EncounterArea4 {
new EncounterArea4HGSS {
Location = 209, // Ruins of Alph
Slots = new int[25].Select((_, i) => new EncounterSlot { Species = 201, LevelMin = 5, LevelMax = 5, Type = SlotType.Grass, Form = i+1 }).ToArray() // B->?, Unown A is loaded from encounters raw file
},
SlotsHGSS_SafariZone,
//Some edge cases
new EncounterArea4
new EncounterArea4HGSS
{
Location = 219, // Mt. Silver Cave 1F
Slots = new[]{new EncounterSlot { Species = 130, LevelMin = 20, LevelMax = 20, Type = SlotType.Good_Rod },}, // Gyarados at night
},
};
private static readonly EncounterArea4 SlotsPt_HoneyTree = new EncounterArea4
private static readonly EncounterArea4DPPt SlotsPt_HoneyTree = new EncounterArea4DPPt
{
Slots = new[]
{
@ -1513,7 +1513,7 @@ namespace PKHeX.Core
},
};
private static readonly EncounterArea4 SlotsD_HoneyTree = new EncounterArea4
private static readonly EncounterArea4DPPt SlotsD_HoneyTree = new EncounterArea4DPPt
{
Slots = SlotsPt_HoneyTree.Slots.Concat(new[]
{
@ -1521,7 +1521,7 @@ namespace PKHeX.Core
}).ToArray()
};
private static readonly EncounterArea4 SlotsP_HoneyTree = new EncounterArea4
private static readonly EncounterArea4DPPt SlotsP_HoneyTree = new EncounterArea4DPPt
{
Slots = SlotsPt_HoneyTree.Slots.Concat(new[]
{
@ -1540,7 +1540,7 @@ namespace PKHeX.Core
046, 102, 115, 193, 285, 316, 452, 454 // Post-National Pokédex
};
private static readonly EncounterArea4[] DP_GreatMarshAlt = EncounterArea.GetSimpleEncounterArea<EncounterArea4>(DP_GreatMarshAlt_Species, new[] { 22, 22, 24, 24, 26, 26 }, 52, SlotType.Grass_Safari);
private static readonly EncounterArea4DPPt[] DP_GreatMarshAlt = EncounterArea.GetSimpleEncounterArea<EncounterArea4DPPt>(DP_GreatMarshAlt_Species, new[] { 22, 22, 24, 24, 26, 26 }, 52, SlotType.Grass_Safari);
private static readonly int[] Pt_GreatMarshAlt_Species =
{
@ -1549,7 +1549,7 @@ namespace PKHeX.Core
046,102,115,285,316,352,452,454 // Post-National Pokédex
};
private static readonly EncounterArea4[] Pt_GreatMarshAlt = EncounterArea.GetSimpleEncounterArea<EncounterArea4>(Pt_GreatMarshAlt_Species, new[] { 27, 30 }, 52, SlotType.Grass_Safari);
private static readonly EncounterArea4DPPt[] Pt_GreatMarshAlt = EncounterArea.GetSimpleEncounterArea<EncounterArea4DPPt>(Pt_GreatMarshAlt_Species, new[] { 27, 30 }, 52, SlotType.Grass_Safari);
private static readonly int[] Shellos_EastSeaLocation_DP =
{
@ -1602,53 +1602,53 @@ namespace PKHeX.Core
58, // Floaroma Meadow
};
private static readonly EncounterArea4[] SlotsHGSS_Swarm =
private static readonly EncounterArea4HGSS[] SlotsHGSS_Swarm =
{
new EncounterArea4 {Location = 143, Slots = new[]{new EncounterSlot {Species = 278, Type = SlotType.Surf },},}, // Wingull @ Vermillion City
new EncounterArea4 {Location = 149, Slots = new[]{new EncounterSlot {Species = 261, Type = SlotType.Grass },},}, // Poochyena @ Route 1
new EncounterArea4 {Location = 161, Slots = new[]{new EncounterSlot {Species = 113, Type = SlotType.Grass },},}, // Chansey @ Route 13
new EncounterArea4 {Location = 167, Slots = new[]{new EncounterSlot {Species = 366, Type = SlotType.Surf },},}, // Clamperl @ Route 19
new EncounterArea4 {Location = 173, Slots = new[]{new EncounterSlot {Species = 427, Type = SlotType.Grass },},}, // Buneary @ Route 25
new EncounterArea4 {Location = 175, Slots = new[]{new EncounterSlot {Species = 370, Type = SlotType.Surf },},}, // Luvdisc @ Route 27
new EncounterArea4 {Location = 182, Slots = new[]{new EncounterSlot {Species = 280, Type = SlotType.Grass },},}, // Ralts @ Route 34
new EncounterArea4 {Location = 183, Slots = new[]{new EncounterSlot {Species = 193, Type = SlotType.Grass },},}, // Yanma @ Route 35
new EncounterArea4 {Location = 186, Slots = new[]{new EncounterSlot {Species = 209, Type = SlotType.Grass },},}, // Snubbull @ Route 38
new EncounterArea4 {Location = 193, Slots = new[]{new EncounterSlot {Species = 333, Type = SlotType.Grass },},}, // Swablu @ Route 45
new EncounterArea4 {Location = 195, Slots = new[]{new EncounterSlot {Species = 132, Type = SlotType.Grass },},}, // Ditto @ Route 47
new EncounterArea4 {Location = 216, Slots = new[]{new EncounterSlot {Species = 183, Type = SlotType.Grass },},}, // Marill @ Mt. Mortar
new EncounterArea4 {Location = 220, Slots = new[]{new EncounterSlot {Species = 206, Type = SlotType.Grass },},}, // Dunsparce @ Dark Cave
new EncounterArea4 {Location = 224, Slots = new[]{new EncounterSlot {Species = 401, Type = SlotType.Grass },},}, // Kricketot @ Viridian Forest
new EncounterArea4HGSS {Location = 143, Slots = new[]{new EncounterSlot {Species = 278, Type = SlotType.Surf },},}, // Wingull @ Vermillion City
new EncounterArea4HGSS {Location = 149, Slots = new[]{new EncounterSlot {Species = 261, Type = SlotType.Grass },},}, // Poochyena @ Route 1
new EncounterArea4HGSS {Location = 161, Slots = new[]{new EncounterSlot {Species = 113, Type = SlotType.Grass },},}, // Chansey @ Route 13
new EncounterArea4HGSS {Location = 167, Slots = new[]{new EncounterSlot {Species = 366, Type = SlotType.Surf },},}, // Clamperl @ Route 19
new EncounterArea4HGSS {Location = 173, Slots = new[]{new EncounterSlot {Species = 427, Type = SlotType.Grass },},}, // Buneary @ Route 25
new EncounterArea4HGSS {Location = 175, Slots = new[]{new EncounterSlot {Species = 370, Type = SlotType.Surf },},}, // Luvdisc @ Route 27
new EncounterArea4HGSS {Location = 182, Slots = new[]{new EncounterSlot {Species = 280, Type = SlotType.Grass },},}, // Ralts @ Route 34
new EncounterArea4HGSS {Location = 183, Slots = new[]{new EncounterSlot {Species = 193, Type = SlotType.Grass },},}, // Yanma @ Route 35
new EncounterArea4HGSS {Location = 186, Slots = new[]{new EncounterSlot {Species = 209, Type = SlotType.Grass },},}, // Snubbull @ Route 38
new EncounterArea4HGSS {Location = 193, Slots = new[]{new EncounterSlot {Species = 333, Type = SlotType.Grass },},}, // Swablu @ Route 45
new EncounterArea4HGSS {Location = 195, Slots = new[]{new EncounterSlot {Species = 132, Type = SlotType.Grass },},}, // Ditto @ Route 47
new EncounterArea4HGSS {Location = 216, Slots = new[]{new EncounterSlot {Species = 183, Type = SlotType.Grass },},}, // Marill @ Mt. Mortar
new EncounterArea4HGSS {Location = 220, Slots = new[]{new EncounterSlot {Species = 206, Type = SlotType.Grass },},}, // Dunsparce @ Dark Cave
new EncounterArea4HGSS {Location = 224, Slots = new[]{new EncounterSlot {Species = 401, Type = SlotType.Grass },},}, // Kricketot @ Viridian Forest
new EncounterArea4 {Location = 128, Slots = new[]{ // Whiscash @ Violet City
new EncounterArea4HGSS {Location = 128, Slots = new[]{ // Whiscash @ Violet City
new EncounterSlot {Species = 340, Type = SlotType.Old_Rod },
new EncounterSlot {Species = 340, Type = SlotType.Good_Rod },
new EncounterSlot {Species = 340, Type = SlotType.Super_Rod },
},},
new EncounterArea4 {Location = 160, Slots = new[]{ // Relicanth @ Route 12
new EncounterArea4HGSS {Location = 160, Slots = new[]{ // Relicanth @ Route 12
new EncounterSlot {Species = 369, Type = SlotType.Old_Rod },
new EncounterSlot {Species = 369, Type = SlotType.Good_Rod },
new EncounterSlot {Species = 369, Type = SlotType.Super_Rod },
},},
new EncounterArea4 {Location = 180, Slots = new[]{ // Qwilfish @ Route 32
new EncounterArea4HGSS {Location = 180, Slots = new[]{ // Qwilfish @ Route 32
new EncounterSlot {Species = 211, Type = SlotType.Old_Rod },
new EncounterSlot {Species = 211, Type = SlotType.Good_Rod },
new EncounterSlot {Species = 211, Type = SlotType.Super_Rod },
},},
new EncounterArea4 {Location = 192, Slots = new[]{ // Remoraid @ Route 44
new EncounterArea4HGSS {Location = 192, Slots = new[]{ // Remoraid @ Route 44
new EncounterSlot {Species = 223, Type = SlotType.Old_Rod },
new EncounterSlot {Species = 223, Type = SlotType.Good_Rod },
new EncounterSlot {Species = 223, Type = SlotType.Super_Rod },
},},
};
private static readonly EncounterArea4[] SlotsHG_Swarm = SlotsHGSS_Swarm.Concat(new[] {
new EncounterArea4 {Location = 151, Slots = new[]{new EncounterSlot {Species = 343, Type = SlotType.Grass },},}, // Baltoy @ Route 3
new EncounterArea4 {Location = 157, Slots = new[]{new EncounterSlot {Species = 302, Type = SlotType.Grass },},}, // Sableye @ Route 9
private static readonly EncounterArea4HGSS[] SlotsHG_Swarm = SlotsHGSS_Swarm.Concat(new[] {
new EncounterArea4HGSS {Location = 151, Slots = new[]{new EncounterSlot {Species = 343, Type = SlotType.Grass },},}, // Baltoy @ Route 3
new EncounterArea4HGSS {Location = 157, Slots = new[]{new EncounterSlot {Species = 302, Type = SlotType.Grass },},}, // Sableye @ Route 9
}).ToArray();
private static readonly EncounterArea4[] SlotsSS_Swarm = SlotsHGSS_Swarm.Concat(new[] {
new EncounterArea4 {Location = 151, Slots = new[]{new EncounterSlot {Species = 316, Type = SlotType.Grass },},}, // Gulpin @ Route 3
new EncounterArea4 {Location = 157, Slots = new[]{new EncounterSlot {Species = 303, Type = SlotType.Grass },},}, // Mawile @ Route 9
private static readonly EncounterArea4HGSS[] SlotsSS_Swarm = SlotsHGSS_Swarm.Concat(new[] {
new EncounterArea4HGSS {Location = 151, Slots = new[]{new EncounterSlot {Species = 316, Type = SlotType.Grass },},}, // Gulpin @ Route 3
new EncounterArea4HGSS {Location = 157, Slots = new[]{new EncounterSlot {Species = 303, Type = SlotType.Grass },},}, // Mawile @ Route 9
}).ToArray();
#endregion

View file

@ -11,6 +11,10 @@ namespace PKHeX.Core
public int Form { get; set; }
public int LevelMin { get; set; }
public int LevelMax { get; set; }
public bool IsLevelWithinRange(int lvl) => LevelMin <= lvl && lvl <= LevelMax;
public bool IsLevelWithinRange(int min, int max) => LevelMin <= min && max <= LevelMax;
public SlotType Type { get; set; } = SlotType.Any;
public EncounterType TypeEncounter { get; set; } = EncounterType.None;
public int SlotNumber { get; set; }

View file

@ -325,7 +325,7 @@ namespace PKHeX.Core
foreach (var z in GetValidStaticEncounter(pkm))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
foreach (var z in GetValidFriendSafari(pkm))
foreach (var z in EncounterArea6XY.GetValidFriendSafari(pkm))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
foreach (var z in GetValidWildEncounters(pkm))

View file

@ -28,17 +28,18 @@ namespace PKHeX.Core
return possibleAreas.SelectMany(area => area.Slots).Where(z => vs.Any(v => v.Species == z.Species));
}
private static IEnumerable<EncounterSlot> GetRawEncounterSlots(PKM pkm, int lvl, GameVersion gameSource)
{
int maxspeciesorigin = GetMaxSpecies(gameSource);
var vs = EvolutionChain.GetValidPreEvolutions(pkm, maxspeciesorigin: maxspeciesorigin);
return GetRawEncounterSlots(pkm, lvl, vs, gameSource);
}
private static IEnumerable<EncounterSlot> GetRawEncounterSlots(PKM pkm, int lvl, IReadOnlyList<EvoCriteria> vs, GameVersion gameSource)
{
if (pkm.Egg_Location != 0)
yield break;
var possibleAreas = GetEncounterAreas(pkm, gameSource);
return possibleAreas.SelectMany(area => GetValidEncounterSlots(pkm, area, vs, DexNav: pkm.AO, lvl: lvl));
foreach (var area in possibleAreas)
{
var slots = area.GetMatchingSlots(pkm, vs, lvl);
foreach (var s in slots)
yield return s;
}
}
private static int GetMaxSpecies(GameVersion gameSource)
@ -52,25 +53,29 @@ namespace PKHeX.Core
public static IEnumerable<EncounterSlot> GetValidWildEncounters34(PKM pkm, GameVersion gameSource = GameVersion.Any)
{
if (gameSource == GameVersion.Any)
gameSource = (GameVersion)pkm.Version;
int lvl = GetMinLevelEncounter(pkm);
if (lvl <= 0)
return Enumerable.Empty<EncounterSlot>();
var s = GetRawEncounterSlots(pkm, lvl, gameSource);
if (gameSource == GameVersion.Any)
gameSource = (GameVersion)pkm.Version;
int maxspeciesorigin = GetMaxSpecies(gameSource);
var vs = EvolutionChain.GetValidPreEvolutions(pkm, maxspeciesorigin: maxspeciesorigin);
var s = GetRawEncounterSlots(pkm, lvl, vs, gameSource);
return s; // defer deferrals to the method consuming this collection
}
public static IEnumerable<EncounterSlot> GetValidWildEncounters12(PKM pkm, IReadOnlyList<EvoCriteria> vs, GameVersion gameSource = GameVersion.Any)
{
if (gameSource == GameVersion.Any)
gameSource = (GameVersion)pkm.Version;
int lvl = GetMinLevelEncounter(pkm);
if (lvl <= 0)
return Enumerable.Empty<EncounterSlot>();
if (gameSource == GameVersion.Any)
gameSource = (GameVersion)pkm.Version;
return GetRawEncounterSlots(pkm, lvl, vs, gameSource);
}
@ -94,20 +99,9 @@ namespace PKHeX.Core
public static IEnumerable<EncounterSlot> GetValidWildEncounters(PKM pkm, GameVersion gameSource = GameVersion.Any)
{
if (gameSource == GameVersion.Any)
gameSource = (GameVersion)pkm.Version;
int lvl = GetMinLevelEncounter(pkm);
if (lvl <= 0)
return Enumerable.Empty<EncounterSlot>();
var s = GetRawEncounterSlots(pkm, lvl, gameSource);
bool IsSafariBall = pkm.Ball == (int)Ball.Safari;
bool IsSportBall = pkm.Ball == (int)Ball.Sport;
bool IsHidden = pkm.AbilityNumber == 4; // hidden Ability
int species = pkm.Species;
return s.DeferByBoolean(slot => slot.IsDeferred(species, pkm, IsSafariBall, IsSportBall, IsHidden)); // non-deferred first
int maxspeciesorigin = GetMaxSpecies(gameSource);
var vs = EvolutionChain.GetValidPreEvolutions(pkm, maxspeciesorigin: maxspeciesorigin);
return GetValidWildEncounters(pkm, vs, gameSource);
}
public static bool IsDeferred3(this EncounterSlot slot, int currentSpecies, PKM pkm, bool IsSafariBall)
@ -136,271 +130,11 @@ namespace PKHeX.Core
private static bool IsDeferredSport(this EncounterSlot slot, bool IsSportBall) => IsSportBall != ((slot.Type & SlotType.BugContest) != 0);
private static bool IsDeferredHiddenAbility(this EncounterSlot slot, bool IsHidden) => IsHidden != slot.IsHiddenAbilitySlot();
public static IEnumerable<EncounterSlot> GetValidFriendSafari(PKM pkm)
{
if (!pkm.XY || pkm.Met_Location != 148 || pkm.Met_Level != 30 || pkm.Egg_Location != 0) // Friend Safari
return Enumerable.Empty<EncounterSlot>();
var vs = EvolutionChain.GetValidPreEvolutions(pkm).Where(d => d.Level >= 30);
return vs.SelectMany(z => Encounters6.FriendSafari[z.Species]);
}
private static IEnumerable<EncounterSlot> GetValidEncounterSlots(PKM pkm, EncounterArea loc, IEnumerable<DexLevel> vs, bool DexNav = false, int lvl = -1, bool ignoreLevel = false)
private static IEnumerable<EncounterSlot> GetValidEncounterSlots(PKM pkm, EncounterArea loc, IReadOnlyList<DexLevel> vs, int lvl)
{
if (pkm.Egg_Location != 0)
return Enumerable.Empty<EncounterSlot>();
if (lvl < 0)
lvl = GetMinLevelEncounter(pkm);
if (lvl <= 0)
return Enumerable.Empty<EncounterSlot>();
int gen = pkm.GenNumber;
if (gen < 3)
return GetValidEncounterSlots12(pkm, loc, vs, lvl, ignoreLevel);
const int fluteBoost = 4;
const int dexnavBoost = 30;
const int comboLureBonus = 1; // +1 if combo/lure?
int df = DexNav ? fluteBoost : IsCatchCombo(pkm) ? comboLureBonus : 0;
int dn = DexNav ? fluteBoost + dexnavBoost : 0;
// Get Valid levels
var encounterSlots = GetValidEncounterSlotsByEvoLevel(pkm, loc.Slots, lvl, ignoreLevel, vs, df, dn);
// Return enumerable of slots pkm might have originated from
if (gen <= 5)
return GetFilteredSlotsByForm(pkm, encounterSlots);
if (DexNav && gen == 6)
return GetFilteredSlots6DexNav(pkm, lvl, encounterSlots, fluteBoost);
return GetFilteredSlots67(pkm, encounterSlots);
}
private static bool IsCatchCombo(PKM pkm)
{
var ver = pkm.Version;
return ver == (int) GameVersion.GP || ver == (int) GameVersion.GE; // ignore GO Transfers
}
private static IEnumerable<EncounterSlot> GetValidEncounterSlots12(PKM pkm, EncounterArea loc, IEnumerable<DexLevel> vs, int lvl = -1, bool ignoreLevel = false)
{
if (lvl < 0)
lvl = GetMinLevelEncounter(pkm);
if (lvl <= 0)
return Enumerable.Empty<EncounterSlot>();
var Gen1Version = GameVersion.RBY;
bool RBDragonair = false;
if (!ignoreLevel && !FilterGBSlotsCatchRate(pkm, ref vs, ref Gen1Version, ref RBDragonair))
return Enumerable.Empty<EncounterSlot>();
var encounterSlots = GetValidEncounterSlotsByEvoLevel(pkm, loc.Slots, lvl, ignoreLevel, vs);
return GetFilteredSlots12(pkm, pkm.GenNumber, Gen1Version, encounterSlots, RBDragonair).OrderBy(slot => slot.LevelMin); // prefer lowest levels
}
private static IEnumerable<EncounterSlot> GetValidEncounterSlotsByEvoLevel(PKM pkm, IEnumerable<EncounterSlot> slots, int lvl, bool ignoreLevel, IEnumerable<DexLevel> vs, int df = 0, int dn = 0)
{
// Get slots where pokemon can exist with respect to the evolution chain
if (ignoreLevel)
return slots.Where(slot => vs.Any(evo => evo.Species == slot.Species));
slots = slots.Where(slot => vs.Any(evo => evo.Species == slot.Species && evo.Level >= slot.LevelMin - df));
// Get slots where pokemon can exist with respect to level constraints
if (pkm.HasOriginalMetLocation)
return slots.Where(slot => slot.LevelMin - df <= lvl && lvl <= slot.LevelMax + (slot.Permissions.AllowDexNav ? dn : df));
// check for any less than current level
return slots.Where(slot => slot.LevelMin <= lvl);
}
private static IEnumerable<EncounterSlot> GetFilteredSlotsByForm(PKM pkm, IEnumerable<EncounterSlot> encounterSlots)
{
return WildForms.Contains(pkm.Species)
? encounterSlots.Where(slot => slot.Form == pkm.AltForm)
: encounterSlots;
}
private static IEnumerable<EncounterSlot> GetFilteredSlots67(PKM pkm, IEnumerable<EncounterSlot> encounterSlots)
{
int species = pkm.Species;
int form = pkm.AltForm;
// Edge Case Handling
switch (species)
{
case 744 when form == 1: // Rockruff Event
case 745 when form == 2: // Lycanroc Event
yield break;
}
EncounterSlot slotMax = null;
void CachePressureSlot(EncounterSlot s)
{
if (slotMax != null && s.LevelMax > slotMax.LevelMax)
slotMax = s;
}
if (AlolanVariantEvolutions12.Contains(species)) // match form if same species, else form 0.
{
foreach (var slot in encounterSlots)
{
if (species == slot.Species ? slot.Form == form : slot.Form == 0)
yield return slot;
CachePressureSlot(slot);
}
}
else if (ShouldMatchSlotForm()) // match slot form
{
foreach (var slot in encounterSlots)
{
if (slot.Form == form)
yield return slot;
CachePressureSlot(slot);
}
}
else
{
foreach (var slot in encounterSlots)
{
yield return slot; // no form checking
CachePressureSlot(slot);
}
}
// Filter for Form Specific
// Pressure Slot
if (slotMax == null)
yield break;
if (AlolanVariantEvolutions12.Contains(species)) // match form if same species, else form 0.
{
if (species == slotMax.Species ? slotMax.Form == form : slotMax.Form == 0)
yield return GetPressureSlot(slotMax, pkm);
}
else if (ShouldMatchSlotForm()) // match slot form
{
if (slotMax.Form == form)
yield return GetPressureSlot(slotMax, pkm);
}
else
{
yield return GetPressureSlot(slotMax, pkm);
}
bool ShouldMatchSlotForm() => WildForms.Contains(species) || AlolanOriginForms.Contains(species) || FormConverter.IsTotemForm(species, form);
}
private static IEnumerable<EncounterSlot> GetFilteredSlots6DexNav(PKM pkm, int lvl, IEnumerable<EncounterSlot> encounterSlots, int fluteBoost)
{
EncounterSlot slotMax = null;
foreach (EncounterSlot s in encounterSlots)
{
if (WildForms.Contains(pkm.Species) && s.Form != pkm.AltForm)
{
CachePressureSlot(s);
continue;
}
bool nav = s.Permissions.AllowDexNav && (pkm.RelearnMove1 != 0 || pkm.AbilityNumber == 4);
EncounterSlot slot = s.Clone();
slot.Permissions.DexNav = nav;
if (slot.LevelMin > lvl)
slot.Permissions.WhiteFlute = true;
if (slot.LevelMax + 1 <= lvl && lvl <= slot.LevelMax + fluteBoost)
slot.Permissions.BlackFlute = true;
if (slot.LevelMax != lvl && slot.Permissions.AllowDexNav)
slot.Permissions.DexNav = true;
yield return slot;
CachePressureSlot(slot);
}
void CachePressureSlot(EncounterSlot s)
{
if (slotMax != null && s.LevelMax > slotMax.LevelMax)
slotMax = s;
}
// Pressure Slot
if (slotMax != null)
yield return GetPressureSlot(slotMax, pkm);
}
private static EncounterSlot GetPressureSlot(EncounterSlot s, PKM pkm)
{
var max = s.Clone();
max.Permissions.Pressure = true;
max.Form = pkm.AltForm;
return max;
}
private static bool FilterGBSlotsCatchRate(PKM pkm, ref IEnumerable<DexLevel> vs, ref GameVersion Gen1Version, ref bool RBDragonair)
{
if (!(pkm is PK1 pk1) || !pkm.Gen1_NotTradeback)
return true;
// Pure gen 1, slots can be filter by catch rate
var rate = pk1.Catch_Rate;
switch (pkm.Species)
{
// Pikachu
case (int)Species.Pikachu when rate == 163:
case (int)Species.Raichu when rate == 163:
return false; // Yellow Pikachu is not a wild encounter
// Kadabra (YW)
case (int)Species.Kadabra when rate == 96:
case (int)Species.Alakazam when rate == 96:
vs = vs.Where(s => s.Species == (int)Species.Kadabra);
Gen1Version = GameVersion.YW;
return true;
// Kadabra (RB)
case (int)Species.Kadabra when rate == 100:
case (int)Species.Alakazam when rate == 100:
vs = vs.Where(s => s.Species == (int)Species.Kadabra);
Gen1Version = GameVersion.RB;
return true;
// Dragonair (YW)
case (int)Species.Dragonair when rate == 27:
case (int)Species.Dragonite when rate == 27:
vs = vs.Where(s => s.Species == (int)Species.Dragonair); // Yellow Dragonair, ignore Dratini encounters
Gen1Version = GameVersion.YW;
return true;
// Dragonair (RB)
case (int)Species.Dragonair:
case (int)Species.Dragonite:
// Red blue dragonair have the same catch rate as dratini, it could also be a dratini from any game
vs = vs.Where(s => rate == PersonalTable.RB[s.Species].CatchRate);
RBDragonair = true;
return true;
default:
vs = vs.Where(s => rate == PersonalTable.RB[s.Species].CatchRate);
return true;
}
}
private static IEnumerable<EncounterSlot> GetFilteredSlots12(PKM pkm, int gen, GameVersion Gen1Version, IEnumerable<EncounterSlot> slots, bool RBDragonair)
{
switch (gen)
{
case 1:
if (Gen1Version != GameVersion.RBY)
slots = slots.Where(slot => Gen1Version.Contains(((EncounterSlot1)slot).Version));
// Red Blue dragonair or dratini from any gen 1 games
if (RBDragonair)
return slots.Where(slot => GameVersion.RB.Contains(((EncounterSlot1)slot).Version) || slot.Species == 147);
return slots;
case 2:
if (pkm is PK2 pk2 && pk2.Met_TimeOfDay != 0)
return slots.Where(slot => ((EncounterSlot1)slot).Time.Contains(pk2.Met_TimeOfDay));
return slots;
default:
return slots;
}
return loc.GetMatchingSlots(pkm, vs, lvl);
}
public static IEnumerable<EncounterArea> GetEncounterSlots(PKM pkm, GameVersion gameSource = GameVersion.Any)
@ -435,7 +169,8 @@ namespace PKHeX.Core
var table = pkm.Version == (int) GameVersion.AS ? SlotsA : SlotsO;
int loc = pkm.Met_Location;
var areas = table.Where(l => l.Location == loc);
var d_areas = areas.Select(area => GetValidEncounterSlots(pkm, area, vs, DexNav: true));
var minlvl = GetMinLevelEncounter(pkm);
var d_areas = areas.Select(area => GetValidEncounterSlots(pkm, area, vs, minlvl));
return d_areas.Any(slots => slots.Any(slot => slot.Permissions.AllowDexNav && slot.Permissions.DexNav));
}
@ -443,7 +178,7 @@ namespace PKHeX.Core
{
var vs = EvolutionChain.GetValidPreEvolutions(pkm);
return (from area in GetEncounterSlots(pkm)
let slots = GetValidEncounterSlots(pkm, area, vs, DexNav: pkm.AO, ignoreLevel: true).ToArray()
let slots = GetValidEncounterSlots(pkm, area, vs, lvl: 0).ToArray()
where slots.Length != 0
select new EncounterAreaFake
{

View file

@ -0,0 +1,36 @@
using FluentAssertions;
using PKHeX.Core;
using Xunit;
namespace PKHeX.Tests.Legality
{
public class LegalityRules
{
[Theory]
[InlineData(GameVersion.B)]
public void HasOriginalMetLocation5(GameVersion g)
{
var pk5 = new PK5 {Version = (int) g};
pk5.HasOriginalMetLocation.Should().BeTrue();
}
[Theory]
[InlineData(GameVersion.B)]
[InlineData(GameVersion.X)]
public void HasOriginalMetLocation6(GameVersion g)
{
var pk5 = new PK6 { Version = (int)g };
pk5.HasOriginalMetLocation.Should().BeTrue();
}
[Theory]
[InlineData(GameVersion.B)]
[InlineData(GameVersion.X)]
[InlineData(GameVersion.SN)]
public void HasOriginalMetLocation7(GameVersion g)
{
var pk5 = new PK7 { Version = (int)g };
pk5.HasOriginalMetLocation.Should().BeTrue();
}
}
}