diff --git a/PKHeX.Core/Legality/Encounters/Data/EncounterUtil.cs b/PKHeX.Core/Legality/Encounters/Data/EncounterUtil.cs index 7e0ec16ec..5279c5444 100644 --- a/PKHeX.Core/Legality/Encounters/Data/EncounterUtil.cs +++ b/PKHeX.Core/Legality/Encounters/Data/EncounterUtil.cs @@ -77,35 +77,79 @@ namespace PKHeX.Core /// Magnet Pull attracts Steel type slots, and Static attracts Electric /// Encounter Area array for game /// Personal data for use with a given species' type - internal static void MarkEncountersStaticMagnetPull(ref EncounterArea[] Areas, PersonalTable t) + internal static void MarkEncountersStaticMagnetPull(IEnumerable Areas, PersonalTable t) { - const int steel = (int)MoveType.Steel; - const int electric = (int)MoveType.Electric; foreach (EncounterArea Area in Areas) foreach (var grp in Area.Slots.GroupBy(z => z.Type)) + MarkEncountersStaticMagnetPull(grp, t); + } + internal static void MarkEncountersStaticMagnetPull(IEnumerable grp, PersonalTable t) + { + GetStaticMagnet(t, grp, out List s, out List m); + for (var i = 0; i < s.Count; i++) { - var s = new List(); // Static - var m = new List(); // Magnet Pull - foreach (EncounterSlot Slot in grp) + var slot = s[i]; + slot.Permissions.StaticIndex = i; + slot.Permissions.StaticCount = s.Count; + } + for (var i = 0; i < m.Count; i++) + { + var slot = m[i]; + slot.Permissions.MagnetPullIndex = i; + slot.Permissions.MagnetPullCount = s.Count; + } + } + internal static void MarkEncountersStaticMagnetPullPermutation(IEnumerable grp, PersonalTable t, List permuted) + { + GetStaticMagnet(t, grp, out List s, out List m); + + // Apply static/magnet values; if any permutation has a unique slot combination, add it to the slot list. + for (int i = 0; i < s.Count; i++) + { + var slot = s[i]; + if (slot.Permissions.StaticIndex >= 0) // already has unique data { - var types = t[Slot.Species].Types; - if (types[0] == steel || types[1] == steel) - m.Add(Slot); - if (types[0] == electric || types[1] == electric) - s.Add(Slot); + if (slot.IsMatchStatic(i, s.Count)) + continue; // same values, no permutation + if (permuted.Any(z => z.SlotNumber == slot.SlotNumber && z.IsMatchStatic(i, s.Count) && z.Species == slot.Species)) + continue; // same values, previously permuted + + s[i] = slot = slot.Clone(); + permuted.Add(slot); } - for (var i = 0; i < s.Count; i++) + slot.Permissions.StaticIndex = i; + slot.Permissions.StaticCount = s.Count; + } + for (int i = 0; i < m.Count; i++) + { + var slot = m[i]; + if (slot.Permissions.MagnetPullIndex >= 0) // already has unique data { - var slot = s[i]; - slot.Permissions.StaticIndex = i; - slot.Permissions.StaticCount = s.Count; - } - for (var i = 0; i < m.Count; i++) - { - var slot = m[i]; - slot.Permissions.MagnetPullIndex = i; - slot.Permissions.MagnetPullCount = s.Count; + if (slot.IsMatchStatic(i, m.Count)) + continue; // same values, no permutation + if (permuted.Any(z => z.SlotNumber == slot.SlotNumber && z.IsMatchMagnet(i, m.Count) && z.Species == slot.Species)) + continue; // same values, previously permuted + + m[i] = slot = slot.Clone(); + permuted.Add(slot); } + slot.Permissions.MagnetPullIndex = i; + slot.Permissions.MagnetPullCount = m.Count; + } + } + private static void GetStaticMagnet(PersonalTable t, IEnumerable grp, out List s, out List m) + { + const int steel = (int)MoveType.Steel; + const int electric = (int)MoveType.Electric + 1; // offset by 1 in gen3/4 for the ??? type + s = new List(); + m = new List(); + foreach (EncounterSlot Slot in grp) + { + var types = t[Slot.Species].Types; + if (types[0] == steel || types[1] == steel) + m.Add(Slot); + if (types[0] == electric || types[1] == electric) + s.Add(Slot); } } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters3.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters3.cs index 5ae5fefd3..86d03ddf4 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters3.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters3.cs @@ -48,7 +48,7 @@ namespace PKHeX.Core MarkG3SlotsSafariZones(ref FR_Slots, SafariLocation_FRLG); MarkG3SlotsSafariZones(ref LG_Slots, SafariLocation_FRLG); - MarkEncountersStaticMagnetPull(ref E_Slots, PersonalTable.SM); + MarkEncountersStaticMagnetPull(E_Slots, PersonalTable.SM); SlotsR = AddExtraTableSlots(R_Slots, SlotsRSEAlt); SlotsS = AddExtraTableSlots(S_Slots, SlotsRSEAlt); diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters4.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters4.cs index f2e834292..51283f6fa 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters4.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters4.cs @@ -28,21 +28,12 @@ namespace PKHeX.Core var D_Slots = EncounterArea.GetArray4DPPt(get("d", "da")); var P_Slots = EncounterArea.GetArray4DPPt(get("p", "pe")); - var Pt_Slots = EncounterArea.GetArray4DPPt(get("pt", "pt")); + var Pt_Slots = EncounterArea.GetArray4DPPt(get("pt", "pt"), true); var HG_Slots = EncounterArea.GetArray4HGSS(get("hg", "hg")); var SS_Slots = EncounterArea.GetArray4HGSS(get("ss", "ss")); var DP_Feebas = GetFeebasArea(D_Slots[10]); var Pt_Feebas = GetFeebasArea(Pt_Slots[10]); - - MarkEncountersStaticMagnetPull(ref D_Slots, PersonalTable.SM); - MarkEncountersStaticMagnetPull(ref P_Slots, PersonalTable.SM); - MarkEncountersStaticMagnetPull(ref Pt_Slots, PersonalTable.SM); - MarkEncountersStaticMagnetPull(ref HG_Slots, PersonalTable.SM); - MarkEncountersStaticMagnetPull(ref SS_Slots, PersonalTable.SM); - - var DP_Trophy = EncounterArea.GetTrophyArea(TrophyDP, new[] { 16, 18 }); - var Pt_Trophy = EncounterArea.GetTrophyArea(TrophyPt, new[] { 22, 22 }); var HG_Headbutt_Slots = EncounterArea.GetArray4HGSS_Headbutt(get("hb_hg", "hg")); var SS_Headbutt_Slots = EncounterArea.GetArray4HGSS_Headbutt(get("hb_ss", "ss")); @@ -68,12 +59,12 @@ namespace PKHeX.Core MarkG4SlotsGreatMarsh(ref Pt_Slots, 52); MarkEncounterAreaArray(D_HoneyTrees_Slots, P_HoneyTrees_Slots, Pt_HoneyTrees_Slots, - DP_GreatMarshAlt, Pt_GreatMarshAlt, DPPt_Unown, DP_Trophy, DP_Feebas, Pt_Trophy, Pt_Feebas, + DP_GreatMarshAlt, Pt_GreatMarshAlt, DPPt_Unown, DP_Feebas, Pt_Feebas, HG_Headbutt_Slots, SS_Headbutt_Slots, SlotsHGSSAlt); - SlotsD = AddExtraTableSlots(D_Slots, D_HoneyTrees_Slots, DP_GreatMarshAlt, DPPt_Unown, DP_Trophy, DP_Feebas); - SlotsP = AddExtraTableSlots(P_Slots, P_HoneyTrees_Slots, DP_GreatMarshAlt, DPPt_Unown, DP_Trophy, DP_Feebas); - SlotsPt = AddExtraTableSlots(Pt_Slots, Pt_HoneyTrees_Slots, Pt_GreatMarshAlt, DPPt_Unown, Pt_Trophy, Pt_Feebas); + 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); + SlotsPt = AddExtraTableSlots(Pt_Slots, Pt_HoneyTrees_Slots, Pt_GreatMarshAlt, DPPt_Unown, Pt_Feebas); SlotsHG = AddExtraTableSlots(HG_Slots, HG_Headbutt_Slots, SlotsHGSSAlt); SlotsSS = AddExtraTableSlots(SS_Slots, SS_Headbutt_Slots, SlotsHGSSAlt); @@ -167,6 +158,11 @@ namespace PKHeX.Core foreach (var swarmSlot in Area.Slots.Where(s => s.Type == SwarmSlot.Type).Take(slotsnum).Select(slot => slot.Clone())) { swarmSlot.Species = SwarmSlot.Species; + if (swarmSlot.Species == 303) // edge case, mawile is only swarm subject to magnet pull (no other steel types in area) + { + swarmSlot.Permissions.MagnetPullIndex = swarmSlot.SlotNumber; + swarmSlot.Permissions.MagnetPullCount = 2; + } OutputSlots.Add(swarmSlot); } } @@ -1438,8 +1434,8 @@ namespace PKHeX.Core }).ToArray() }; - private static readonly int[] TrophyDP = { 035, 039, 052, 113, 133, 137, 173, 174, 183, 298, 311, 312, 351, 438, 439, 440 }; // Porygon - private static readonly int[] TrophyPt = { 035, 039, 052, 113, 133, 132, 173, 174, 183, 298, 311, 312, 351, 438, 439, 440 }; // Ditto + internal static readonly int[] TrophyDP = { 035, 039, 052, 113, 133, 137, 173, 174, 183, 298, 311, 312, 351, 438, 439, 440 }; // Porygon + internal static readonly int[] TrophyPt = { 035, 039, 052, 113, 133, 132, 173, 174, 183, 298, 311, 312, 351, 438, 439, 440 }; // Ditto private static readonly int[] DP_GreatMarshAlt_Species = { diff --git a/PKHeX.Core/Legality/Structures/EncounterArea.cs b/PKHeX.Core/Legality/Structures/EncounterArea.cs index 10c7e3e24..c1466751f 100644 --- a/PKHeX.Core/Legality/Structures/EncounterArea.cs +++ b/PKHeX.Core/Legality/Structures/EncounterArea.cs @@ -388,7 +388,7 @@ namespace PKHeX.Core return slots; } - private static IEnumerable GetSlots4_G_Replace(byte[] data, int ofs, int slotSize, EncounterSlot[] ReplacedSlots, int[] slotnums, SlotType t = SlotType.Grass) + private static List GetSlots4_G_Replace(byte[] data, int ofs, int slotSize, EncounterSlot[] ReplacedSlots, int[] slotnums, SlotType t = SlotType.Grass) { //Special slots like GBA Dual Slot. Those slot only contain the info of species id, the level is copied from one of the first grass slots //for dppt slotSize = 4, for hgss slotSize = 2 @@ -433,6 +433,7 @@ namespace PKHeX.Core Type = t }); } + EncounterUtil.MarkEncountersStaticMagnetPull(slots, PersonalTable.HGSS); return slots; } private static IEnumerable GetSlots4HGSS_WFR(byte[] data, int ofs, int numslots, SlotType t) @@ -456,6 +457,7 @@ namespace PKHeX.Core Type = t }); } + EncounterUtil.MarkEncountersStaticMagnetPull(slots, PersonalTable.HGSS); return slots; } @@ -488,30 +490,84 @@ namespace PKHeX.Core return Area3; } - private static EncounterArea GetArea4DPPt(byte[] data) + private static EncounterArea GetArea4DPPt(byte[] data, bool pt = false) { var Slots = new List(); + int location = BitConverter.ToUInt16(data, 0x00); var GrassRatio = BitConverter.ToInt32(data, 0x02); if (GrassRatio > 0) { EncounterSlot[] GrassSlots = GetSlots4_DPPt_G(data, 0x06, 12, SlotType.Grass); - Slots.AddRange(GrassSlots); //Swarming slots replace slots 0 and 1 - Slots.AddRange(GetSlots4_G_Replace(data, 0x66, 4, GrassSlots, Legal.Slot4_Swarm, SlotType.Swarm)); + var swarm = GetSlots4_G_Replace(data, 0x66, 4, GrassSlots, Legal.Slot4_Swarm, SlotType.Swarm); //Morning and Night slots replace slots 2 and 3 - Slots.AddRange(GetSlots4_G_Replace(data, 0x6E, 4, GrassSlots, Legal.Slot4_Time)); // Morning - Slots.AddRange(GetSlots4_G_Replace(data, 0x76, 4, GrassSlots, Legal.Slot4_Time)); // Night + var morning = GetSlots4_G_Replace(data, 0x6E, 4, GrassSlots, Legal.Slot4_Time); // Morning + var night = GetSlots4_G_Replace(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 - Slots.AddRange(GetSlots4_G_Replace(data, 0x7E, 4, GrassSlots, Legal.Slot4_Radar, SlotType.Pokeradar)); + var radar = GetSlots4_G_Replace(data, 0x7E, 4, GrassSlots, Legal.Slot4_Radar, SlotType.Pokeradar); + //24 bytes padding + //Dual Slots replace slots 8 and 9 - Slots.AddRange(GetSlots4_G_Replace(data, 0xA6, 4, GrassSlots, Legal.Slot4_Dual)); // Ruby - Slots.AddRange(GetSlots4_G_Replace(data, 0xAE, 4, GrassSlots, Legal.Slot4_Dual)); // Sapphire - Slots.AddRange(GetSlots4_G_Replace(data, 0xB6, 4, GrassSlots, Legal.Slot4_Dual)); // Emerald - Slots.AddRange(GetSlots4_G_Replace(data, 0xBE, 4, GrassSlots, Legal.Slot4_Dual)); // FireRed - Slots.AddRange(GetSlots4_G_Replace(data, 0xC6, 4, GrassSlots, Legal.Slot4_Dual)); // LeafGreen + var ruby = GetSlots4_G_Replace(data, 0xA6, 4, GrassSlots, Legal.Slot4_Dual); // Ruby + var sapphire = GetSlots4_G_Replace(data, 0xAE, 4, GrassSlots, Legal.Slot4_Dual); // Sapphire + var emerald = GetSlots4_G_Replace(data, 0xB6, 4, GrassSlots, Legal.Slot4_Dual); // Emerald + var firered = GetSlots4_G_Replace(data, 0xBE, 4, GrassSlots, Legal.Slot4_Dual); // FireRed + var leafgreen = GetSlots4_G_Replace(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> {GrassSlots.Where(z => z.SlotNumber == 6 || z.SlotNumber == 7).ToList()}; // every other slot is in the product + var pair0 = new List> {GrassSlots.Where(z => Legal.Slot4_Swarm.Contains(z.SlotNumber)).ToList()}; + var pair1 = new List> {GrassSlots.Where(z => Legal.Slot4_Time.Contains(z.SlotNumber)).ToList()}; + var pair2 = new List> {GrassSlots.Where(z => Legal.Slot4_Radar.Contains(z.SlotNumber)).ToList()}; + var pair3 = new List> {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(); + 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{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); @@ -534,7 +590,7 @@ namespace PKHeX.Core EncounterArea Area4 = new EncounterArea { - Location = BitConverter.ToUInt16(data, 0x00), + Location = location, Slots = Slots.ToArray() }; foreach (var slot in Area4.Slots) @@ -542,6 +598,17 @@ namespace PKHeX.Core return Area4; } + private static IEnumerable MarkStaticMagnetExtras(IEnumerable>> product) + { + var trackPermute = new List(); + foreach (var p in product) + MarkStaticMagnetPermute(p.SelectMany(z => z), trackPermute); + return trackPermute; + } + private static void MarkStaticMagnetPermute(IEnumerable grp, List trackPermute) + { + EncounterUtil.MarkEncountersStaticMagnetPullPermutation(grp, PersonalTable.HGSS, trackPermute); + } private static EncounterArea GetArea4HGSS(byte[] data) { @@ -560,12 +627,34 @@ namespace PKHeX.Core // 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 = GetSlots4_HGSS_G(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 add swarms - Slots.AddRange(GrassSlots); + //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 - Slots.AddRange(GetSlots4_G_Replace(data, 0x5E, 2, GrassSlots, Legal.Slot4_Sound)); // Hoenn - Slots.AddRange(GetSlots4_G_Replace(data, 0x62, 2, GrassSlots, Legal.Slot4_Sound)); // Sinnoh + var hoenn = GetSlots4_G_Replace(data, 0x5E, 2, GrassSlots, Legal.Slot4_Sound); // Hoenn + var sinnoh = GetSlots4_G_Replace(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> {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(); + foreach (var t in radio) + MarkStaticMagnetPermute(regular.Concat(t), extra); + Slots.AddRange(extra); + } } if (SurfRatio > 0) @@ -865,10 +954,11 @@ namespace PKHeX.Core /// Gets the encounter areas with information from Generation 4 Diamond, Pearl and Platinum data. /// /// Raw data, one byte array per encounter area + /// Platinum flag (for Trophy Garden slot insertion) /// Array of encounter areas. - public static EncounterArea[] GetArray4DPPt(byte[][] entries) + public static EncounterArea[] GetArray4DPPt(byte[][] entries, bool pt = false) { - return entries?.Select(GetArea4DPPt).Where(Area => Area.Slots.Length != 0).ToArray(); + return entries?.Select(z => GetArea4DPPt(z, pt)).Where(Area => Area.Slots.Length != 0).ToArray(); } /// @@ -891,33 +981,6 @@ namespace PKHeX.Core return entries?.Select(GetArea4HGSS_Headbutt).Where(Area => Area.Slots.Length != 0).ToArray(); } - /// - /// Gets the encounter areas for the Trophy Garden - /// - /// List of special species that can exist in the garden. - /// Levels of the two encounter slots they can replace. differs from - /// - public static EncounterArea[] GetTrophyArea(IEnumerable species, int[] lvls) - { - int[] slotnums = {6, 7}; - var l = new List(); - foreach (var s in species) - { - for (int i = 0; i < 2; i++) - { - l.Add(new EncounterSlot - { - LevelMax = lvls[i], - LevelMin = lvls[i], - Species = s, - SlotNumber = slotnums[i], - Type = SlotType.Grass - }); - } - } - return new[] { new EncounterArea { Location = 68, Slots = l.ToArray() } }; - } - /// /// Gets the encounter areas for species with same level range and same slottype at same location /// @@ -965,4 +1028,18 @@ namespace PKHeX.Core return data; } } + + public partial class Extensions + { + public static IEnumerable> CartesianProduct(this IEnumerable> sequences) + { + IEnumerable> emptyProduct = new[] { Enumerable.Empty() }; + return sequences.Aggregate( + emptyProduct, + (accumulator, sequence) => + from accseq in accumulator + from item in sequence + select accseq.Concat(new[] { item })); + } + } } diff --git a/PKHeX.Core/Legality/Structures/EncounterSlot.cs b/PKHeX.Core/Legality/Structures/EncounterSlot.cs index f9c3139c9..d852ebe76 100644 --- a/PKHeX.Core/Legality/Structures/EncounterSlot.cs +++ b/PKHeX.Core/Legality/Structures/EncounterSlot.cs @@ -14,6 +14,7 @@ public bool BlackFlute { get; set; } public bool IsNormalLead => !(WhiteFlute || BlackFlute || DexNav); public bool IsDexNav => AllowDexNav && DexNav; + public EncounterSlotPermissions Clone() => (EncounterSlotPermissions)MemberwiseClone(); } /// /// Wild Encounter Slot data @@ -34,9 +35,19 @@ internal EncounterArea Area { get; set; } public int Location => Area.Location; - public EncounterSlot Clone() => (EncounterSlot)MemberwiseClone(); + public EncounterSlot Clone() + { + var slot = (EncounterSlot) MemberwiseClone(); + if (_perm != null) + slot._perm = Permissions.Clone(); + return slot; + } + public bool FixedLevel => LevelMin == LevelMax; + public bool IsMatchStatic(int index, int count) => index == Permissions.StaticIndex && count == Permissions.StaticCount; + public bool IsMatchMagnet(int index, int count) => index == Permissions.MagnetPullIndex && count == Permissions.MagnetPullCount; + public string Name { get