From cf67d156ad3c4c298709366eb29cb414a0a2852f Mon Sep 17 00:00:00 2001 From: Kurt Date: Sat, 5 Sep 2020 10:07:02 -0700 Subject: [PATCH] Move Gen1/2 Encounter yielding to separate class Specialize some of the methods so that Korean and non-tradeback cases are handled with simplified logic. --- .../Generator/EncounterGenerator.cs | 167 +--------------- .../Generator/EncounterGenerator12.cs | 186 ++++++++++++++++++ 2 files changed, 187 insertions(+), 166 deletions(-) create mode 100644 PKHeX.Core/Legality/Encounters/Generator/EncounterGenerator12.cs diff --git a/PKHeX.Core/Legality/Encounters/Generator/EncounterGenerator.cs b/PKHeX.Core/Legality/Encounters/Generator/EncounterGenerator.cs index 7f5faf8ec..174cfb841 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/EncounterGenerator.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/EncounterGenerator.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using static PKHeX.Core.Legal; - using static PKHeX.Core.MysteryGiftGenerator; using static PKHeX.Core.EncounterTradeGenerator; using static PKHeX.Core.EncounterSlotGenerator; @@ -31,7 +29,7 @@ public static IEnumerable GetEncounters(PKM pkm, LegalInfo info) switch (info.Generation) { case 1: - case 2: return GetEncounters12(pkm, info); + case 2: return EncounterGenerator12.GetEncounters12(pkm, info); case 3: return GetEncounters3(pkm, info); case 4: return GetEncounters4(pkm, info); case 8: return GenerateRawEncounters8(pkm); @@ -39,21 +37,6 @@ public static IEnumerable GetEncounters(PKM pkm, LegalInfo info) } } - private static IEnumerable GetEncounters12(PKM pkm, LegalInfo info) - { - int baseSpecies = EvoBase.GetBaseSpecies(pkm).Species; - - if ((pkm.Format == 1 && baseSpecies > MaxSpeciesID_1) || baseSpecies > MaxSpeciesID_2) - yield break; - - foreach (var z in GenerateFilteredEncounters12(pkm)) - { - info.Generation = z.Generation; - info.Game = z.Version; - yield return z; - } - } - private static IEnumerable GetEncounters3(PKM pkm, LegalInfo info) { info.PIDIV = MethodFinder.Analyze(pkm); @@ -135,154 +118,6 @@ private static IEnumerable GetEncounters4(PKM pkm, LegalInfo inf yield return z; } - private static IEnumerable GenerateRawEncounters12(PKM pkm, GameVersion game) - { - // Since encounter matching is super weak due to limited stored data in the structure - // Calculate all 3 at the same time and pick the best result (by species). - // Favor special event move gifts as Static Encounters when applicable - var chain = EncounterOrigin.GetOriginChain12(pkm, game); - - var deferred = new List(); - foreach (var t in GetValidEncounterTrades(pkm, chain, game)) - { - // some OTs are longer than the keyboard entry; don't defer these - if (pkm.Format >= 7 && pkm.OT_Name.Length <= (pkm.Japanese || pkm.Korean ? 5 : 7)) - { - deferred.Add(t); - continue; - } - yield return t; - } - foreach (var s in GetValidStaticEncounter(pkm, chain, game)) - { - // Valid stadium and non-stadium encounters, return only non-stadium encounters, they are less restrictive - switch (s.Version) - { - case GameVersion.Stadium: - case GameVersion.Stadium2: - deferred.Add(s); - continue; - case GameVersion.EventsGBGen2: - if (!s.EggEncounter && !pkm.HasOriginalMetLocation) - continue; - if (pkm.Japanese) - deferred.Add(s); - continue; - case GameVersion.C when pkm.Format == 2: // Crystal specific data needs to be present - if (!s.EggEncounter && !pkm.HasOriginalMetLocation) - continue; - if (s.Species == 251 && ParseSettings.AllowGBCartEra) // no celebi, the GameVersion.EventsGBGen2 will pass thru - continue; - break; - } - yield return s; - } - foreach (var e in GetValidWildEncounters12(pkm, chain, game)) - { - yield return e; - } - - if (GameVersion.GSC.Contains(game)) - { - foreach (var e in EncounterEggGenerator2.GenerateEggs(pkm, chain)) - yield return e; - } - - foreach (var d in deferred) - yield return d; - } - - private static IEnumerable GenerateFilteredEncounters12(PKM pkm) - { - bool crystal = (pkm is PK2 pk2 && pk2.CaughtData != 0) || (pkm.Format >= 7 && pkm.OT_Gender == 1); - bool kadabra = pkm.Species == 64 && pkm is PK1 pk1 - && (pk1.Catch_Rate == PersonalTable.RB[64].CatchRate - || pk1.Catch_Rate == PersonalTable.Y[64].CatchRate); // catch rate outsider, return gen1 first always - - // iterate over both games, consuming from one list at a time until the other list has higher priority encounters - var get1 = GenerateRawEncounters1(pkm, crystal); - var get2 = GenerateRawEncounters2(pkm, crystal); - using var g1i = new PeekEnumerator(get1); - using var g2i = new PeekEnumerator(get2); - - var deferred = new List(); - while (g2i.PeekIsNext() || g1i.PeekIsNext()) - { - var move = GetPreferredGBIterator(pkm, g1i, g2i); - var obj = move.Peek(); - int gen = obj.Generation; - - if (gen == 1 && (pkm.Korean || (obj is EncounterTrade1 t && !t.IsEncounterTrade1Valid(pkm)))) - deferred.Add(obj); - else if (gen == 2 && ((pkm.Korean && obj.Version == GameVersion.C) || kadabra)) - deferred.Add(obj); - else - yield return obj; - - move.MoveNext(); - } - foreach (var z in deferred) - yield return z; - } - - private static IEnumerable GenerateRawEncounters1(PKM pkm, bool crystal) - { - return pkm.Gen2_NotTradeback || crystal - ? Array.Empty() - : GenerateRawEncounters12(pkm, GameVersion.RBY); - } - - private static IEnumerable GenerateRawEncounters2(PKM pkm, bool crystal) - { - return pkm.Gen1_NotTradeback - ? Array.Empty() - : GenerateRawEncounters12(pkm, crystal ? GameVersion.C : GameVersion.GSC); - } - - private static PeekEnumerator GetPreferredGBIterator(PKM pkm, PeekEnumerator g1i, PeekEnumerator g2i) - { - if (!g1i.PeekIsNext()) - return g2i; - if (!g2i.PeekIsNext()) - return g1i; - var p1 = GetGBEncounterPriority(pkm, g1i.Current); - var p2 = GetGBEncounterPriority(pkm, g2i.Current); - return p1 > p2 ? g1i : g2i; - } - - private static GBEncounterPriority GetGBEncounterPriority(PKM pkm, IEncounterable Encounter) - { - switch (Encounter) - { - case EncounterTrade1 _: - return GBEncounterPriority.TradeEncounterG1; - case EncounterTrade2 _: - return GBEncounterPriority.TradeEncounterG2; - case EncounterStatic s: - if (s.Moves.Count != 0 && s.Moves[0] != 0 && pkm.HasMove(s.Moves[0])) - return GBEncounterPriority.SpecialEncounter; - return GBEncounterPriority.StaticEncounter; - case EncounterSlot _: - return GBEncounterPriority.WildEncounter; - - default: - return GBEncounterPriority.EggEncounter; - } - } - - /// - /// Generation 1/2 Encounter Data type, which serves as a 'best match' priority rating when returning from a list. - /// - private enum GBEncounterPriority - { - EggEncounter, - WildEncounter, - StaticEncounter, - SpecialEncounter, - TradeEncounterG1, - TradeEncounterG2, - } - private static IEnumerable GenerateRawEncounters(PKM pkm) { int ctr = 0; diff --git a/PKHeX.Core/Legality/Encounters/Generator/EncounterGenerator12.cs b/PKHeX.Core/Legality/Encounters/Generator/EncounterGenerator12.cs new file mode 100644 index 000000000..72ba522dd --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Generator/EncounterGenerator12.cs @@ -0,0 +1,186 @@ +using System.Collections.Generic; + +namespace PKHeX.Core +{ + /// + /// This class is essentially a sub-class of specialized for Gen1 & Gen2 encounters. + /// + internal static class EncounterGenerator12 + { + internal static IEnumerable GetEncounters12(PKM pkm, LegalInfo info) + { + foreach (var z in GenerateFilteredEncounters12(pkm)) + { + info.Generation = z.Generation; + info.Game = z.Version; + yield return z; + } + } + + private static IEnumerable GenerateRawEncounters12(PKM pkm, GameVersion game) + { + // Since encounter matching is super weak due to limited stored data in the structure + // Calculate all 3 at the same time and pick the best result (by species). + // Favor special event move gifts as Static Encounters when applicable + var chain = EncounterOrigin.GetOriginChain12(pkm, game); + + var deferred = new List(); + foreach (var t in EncounterTradeGenerator.GetValidEncounterTrades(pkm, chain, game)) + { + // some OTs are longer than the keyboard entry; don't defer these + if (pkm.Format >= 7 && pkm.OT_Name.Length <= (pkm.Japanese || pkm.Korean ? 5 : 7)) + { + deferred.Add(t); + continue; + } + if (t is EncounterTrade1 t1 && !t1.IsEncounterTrade1Valid(pkm)) + { + deferred.Add(t); + continue; + } + yield return t; + } + foreach (var s in EncounterStaticGenerator.GetValidStaticEncounter(pkm, chain, game)) + { + // Valid stadium and non-stadium encounters, return only non-stadium encounters, they are less restrictive + switch (s.Version) + { + case GameVersion.Stadium: + case GameVersion.Stadium2: + deferred.Add(s); + continue; + case GameVersion.EventsGBGen2: + if (!s.EggEncounter && !pkm.HasOriginalMetLocation) + continue; + if (pkm.Japanese) + deferred.Add(s); + continue; + case GameVersion.C when pkm.Format == 2: // Crystal specific data needs to be present + if (!s.EggEncounter && !pkm.HasOriginalMetLocation) + continue; + if (s.Species == (int)Species.Celebi && ParseSettings.AllowGBCartEra) // no Celebi, the GameVersion.EventsGBGen2 will pass thru + continue; + break; + } + yield return s; + } + foreach (var e in EncounterSlotGenerator.GetValidWildEncounters12(pkm, chain, game)) + { + yield return e; + } + if (game != GameVersion.RBY) + { + foreach (var e in EncounterEggGenerator2.GenerateEggs(pkm, chain)) + yield return e; + } + + foreach (var d in deferred) + yield return d; + } + + private static IEnumerable GenerateFilteredEncounters12(PKM pkm) + { + // If the current data indicates that it must have originated from Crystal, only yield encounter data from Crystal. + bool crystal = (pkm is PK2 pk2 && pk2.CaughtData != 0) || (pkm.Format >= 7 && pkm.OT_Gender == 1); + if (crystal) + return GenerateRawEncounters12(pkm, GameVersion.C); + + var visited = GBRestrictions.GetTradebackStatusInitial(pkm); + switch (visited) + { + case TradebackType.Gen1_NotTradeback: + return GenerateRawEncounters12(pkm, GameVersion.RBY); + case TradebackType.Gen2_NotTradeback: + return GenerateRawEncounters12(pkm, GameVersion.GSC); + default: + if (pkm.Korean) + return GenerateFilteredEncounters12BothKorean(pkm); + return GenerateFilteredEncounters12Both(pkm); + } + } + + private static IEnumerable GenerateFilteredEncounters12BothKorean(PKM pkm) + { + // Korean origin PK1/PK2 can only originate from GS, but since we're nice we'll defer & yield matches from other games. + // Yield GS first, then Crystal, then RBY. Anything other than GS will be flagged by later checks. + + var deferred = new List(); + var get2 = GenerateRawEncounters12(pkm, GameVersion.GSC); + foreach (var enc in get2) + { + if (enc.Version == GameVersion.C) + deferred.Add(enc); + else + yield return enc; + } + + foreach (var enc in deferred) + yield return enc; + + var get1 = GenerateRawEncounters12(pkm, GameVersion.RBY); + foreach (var enc in get1) + yield return enc; + } + + private static IEnumerable GenerateFilteredEncounters12Both(PKM pkm) + { + // Iterate over both games, consuming from one list at a time until the other list has higher priority encounters + // Buffer the encounters so that we can consume each iterator separately + var get1 = GenerateRawEncounters12(pkm, GameVersion.RBY); + var get2 = GenerateRawEncounters12(pkm, GameVersion.GSC); + using var g1i = new PeekEnumerator(get1); + using var g2i = new PeekEnumerator(get2); + while (g2i.PeekIsNext() || g1i.PeekIsNext()) + { + var iter = PickPreferredIterator(pkm, g1i, g2i); + yield return iter.Current; + iter.MoveNext(); + } + } + + private static PeekEnumerator PickPreferredIterator(PKM pkm, PeekEnumerator g1i, PeekEnumerator g2i) + { + if (!g1i.PeekIsNext()) + return g2i; + if (!g2i.PeekIsNext()) + return g1i; + var p1 = GetGBEncounterPriority(pkm, g1i.Current); + var p2 = GetGBEncounterPriority(pkm, g2i.Current); + return p1 > p2 ? g1i : g2i; + } + + private static GBEncounterPriority GetGBEncounterPriority(PKM pkm, IEncounterable Encounter) + { + switch (Encounter) + { + case EncounterTrade1 t1: + return t1.IsEncounterTrade1Valid(pkm) ? GBEncounterPriority.TradeEncounterG1 : GBEncounterPriority.Least; + case EncounterTrade2 _: + return GBEncounterPriority.TradeEncounterG2; + case EncounterStatic s: + if (s.Moves.Count != 0 && s.Moves[0] != 0 && pkm.HasMove(s.Moves[0])) + return GBEncounterPriority.SpecialEncounter; + return GBEncounterPriority.StaticEncounter; + case EncounterSlot _: + return GBEncounterPriority.WildEncounter; + + default: + return GBEncounterPriority.EggEncounter; + } + } + + /// + /// Generation 1/2 Encounter Data type, which serves as a 'best match' priority rating when returning from a list. + /// + private enum GBEncounterPriority + { + Least, + EggEncounter, + WildEncounter, + StaticEncounter, + SpecialEncounter, + TradeEncounterG1, + TradeEncounterG2, + } + } +}