From d3a30ebf359bad1e1ea4056dba20fd8b7fa28f05 Mon Sep 17 00:00:00 2001 From: Kurt Date: Sun, 30 Jul 2017 12:31:17 -0700 Subject: [PATCH] Refactoring +docs Add some documentation for the high-level encounter finding Fix encounterstatics not being filtered for the associated game version (closes #1372) was a side effect of refactoring this week --- PKHeX.Core/Legality/Analysis.cs | 46 ++--------- PKHeX.Core/Legality/Checks.cs | 2 +- PKHeX.Core/Legality/Core.cs | 38 +++++++++ .../Legality/Encounters/Data/EncounterUtil.cs | 82 ++++++++++++++++--- .../Legality/Encounters/Data/Encounters3.cs | 10 +-- .../Legality/Encounters/Data/Encounters4.cs | 8 +- .../Legality/Encounters/Data/Encounters5.cs | 8 +- .../Legality/Encounters/Data/Encounters6.cs | 6 +- .../Legality/Encounters/Data/Encounters7.cs | 4 +- .../Legality/Encounters/EncounterFinder.cs | 34 ++++++++ .../Legality/Encounters/EvolutionVerifier.cs | 17 +++- PKHeX.Core/Legality/Encounters/LegalInfo.cs | 2 +- 12 files changed, 193 insertions(+), 64 deletions(-) diff --git a/PKHeX.Core/Legality/Analysis.cs b/PKHeX.Core/Legality/Analysis.cs index b11db3b4e..d41830477 100644 --- a/PKHeX.Core/Legality/Analysis.cs +++ b/PKHeX.Core/Legality/Analysis.cs @@ -17,8 +17,7 @@ public partial class LegalityAnalysis private IEncounterable EncounterOriginalGB; private IEncounterable EncounterMatch => Info.EncounterMatch; private Type Type; // Parent class when applicable (EncounterStatic / MysteryGift) - private Type MatchedType; // Child class if applicable (WC6, PGF, etc) - private string EncounterName => (EncounterOriginalGB ?? EncounterMatch).GetEncounterTypeName() + $" ({SpeciesStrings[EncounterMatch.Species]})"; + private string EncounterName => $"{(EncounterOriginalGB ?? EncounterMatch).GetEncounterTypeName()} ({SpeciesStrings[EncounterMatch.Species]})"; private CheckResult Encounter, History; public bool Parsed { get; } @@ -213,43 +212,14 @@ private void UpdateTradebackG12() { if (pkm.Format == 1) { - if (!Legal.AllowGen1Tradeback) - { - pkm.TradebackStatus = TradebackType.Gen1_NotTradeback; - ((PK1)pkm).CatchRateIsItem = false; - return; - } - - // Detect tradeback status by comparing the catch rate(Gen1)/held item(Gen2) to the species in the pkm's evolution chain. - var catch_rate = ((PK1)pkm).Catch_Rate; - - // For species catch rate, discard any species that has no valid encounters and a different catch rate than their pre-evolutions - var Lineage = Legal.GetLineage(pkm).Where(s => !Legal.Species_NotAvailable_CatchRate.Contains(s)).ToList(); - // Dragonite's Catch Rate is different than Dragonair's in Yellow, but there is no Dragonite encounter. - var RGBCatchRate = Lineage.Any(s => catch_rate == PersonalTable.RB[s].CatchRate); - var YCatchRate = Lineage.Any(s => s != 149 && catch_rate == PersonalTable.Y[s].CatchRate); - // Krabby encounter trade special catch rate - var TradeCatchRate = ((pkm.Species == 098 || pkm.Species == 099) && catch_rate == 204); - var StadiumCatchRate = Legal.Stadium_GiftSpecies.Contains(pkm.Species) && Legal.Stadium_CatchRate.Contains(catch_rate); - bool matchAny = RGBCatchRate || YCatchRate || TradeCatchRate || StadiumCatchRate; - - // If the catch rate value has been modified, the item has either been removed or swapped in Generation 2. - var HeldItemCatchRate = catch_rate == 0 || Legal.HeldItems_GSC.Any(h => h == catch_rate); - if (HeldItemCatchRate && !matchAny) - pkm.TradebackStatus = TradebackType.WasTradeback; - else if (!HeldItemCatchRate && matchAny) - pkm.TradebackStatus = TradebackType.Gen1_NotTradeback; - else - pkm.TradebackStatus = TradebackType.Any; - - - // Update the editing settings for the PKM to acknowledge the tradeback status if the species is changed. - ((PK1)pkm).CatchRateIsItem = !pkm.Gen1_NotTradeback && HeldItemCatchRate && !matchAny; + Legal.GetTradebackStatusRBY(pkm); + return; } - else if (pkm.Format == 2 || pkm.VC2) + + if (pkm.Format == 2 || pkm.VC2) { - // Eggs, pokemon with non-empty Crystal met location, and generation 2 species without generation 1 preevolutions can't be traded to generation 1. - if (pkm.IsEgg || pkm.HasOriginalMetLocation || (pkm.Species > Legal.MaxSpeciesID_1 && !Legal.FutureEvolutionsGen1.Contains(pkm.Species))) + // check for impossible tradeback scenarios + if (pkm.IsEgg || pkm.HasOriginalMetLocation || pkm.Species > Legal.MaxSpeciesID_1 && !Legal.FutureEvolutionsGen1.Contains(pkm.Species)) pkm.TradebackStatus = TradebackType.Gen2_NotTradeback; else pkm.TradebackStatus = TradebackType.Any; @@ -279,7 +249,7 @@ private void UpdateTypeInfo() // Example: GSC Pokemon with only possible encounters in RBY, like the legendary birds pkm.TradebackStatus = TradebackType.WasTradeback; - MatchedType = Type = (EncounterOriginalGB ?? EncounterMatch)?.GetType(); + Type = (EncounterOriginalGB ?? EncounterMatch)?.GetType(); var bt = Type.GetTypeInfo().BaseType; if (bt != null && !(bt == typeof(Array) || bt == typeof(object) || bt.GetTypeInfo().IsPrimitive)) // a parent exists Type = bt; // use base type diff --git a/PKHeX.Core/Legality/Checks.cs b/PKHeX.Core/Legality/Checks.cs index a4fd2c092..248c01e13 100644 --- a/PKHeX.Core/Legality/Checks.cs +++ b/PKHeX.Core/Legality/Checks.cs @@ -1748,7 +1748,7 @@ private CheckResult VerifyHistory() if (pkm.OT_Memory != 0) return new CheckResult(Severity.Invalid, V151, CheckIdentifier.History); } - else if (MatchedType != typeof(WC6)) + else if (!(EncounterMatch is WC6)) { if (pkm.OT_Memory == 0 ^ !pkm.Gen6) return new CheckResult(Severity.Invalid, V152, CheckIdentifier.History); diff --git a/PKHeX.Core/Legality/Core.cs b/PKHeX.Core/Legality/Core.cs index c661fd925..5fbc26cf3 100644 --- a/PKHeX.Core/Legality/Core.cs +++ b/PKHeX.Core/Legality/Core.cs @@ -1340,6 +1340,44 @@ private static int GetMinLevelGeneration(PKM pkm, int generation) return 1; } + private static bool GetCatchRateMatchesPreEvolution(PKM pkm, int catch_rate) + { + // For species catch rate, discard any species that has no valid encounters and a different catch rate than their pre-evolutions + var Lineage = GetLineage(pkm).Where(s => !Species_NotAvailable_CatchRate.Contains(s)).ToList(); + return IsCatchRateRB(Lineage) || IsCatchRateY(Lineage) || IsCatchRateTrade() || IsCatchRateStadium(); + + // Dragonite's Catch Rate is different than Dragonair's in Yellow, but there is no Dragonite encounter. + bool IsCatchRateRB(List ds) => ds.Any(s => catch_rate == PersonalTable.RB[s].CatchRate); + bool IsCatchRateY(List ds) => ds.Any(s => s != 149 && catch_rate == PersonalTable.Y[s].CatchRate); + // Krabby encounter trade special catch rate + bool IsCatchRateTrade() => (pkm.Species == 098 || pkm.Species == 099) && catch_rate == 204; + bool IsCatchRateStadium() => Stadium_GiftSpecies.Contains(pkm.Species) && Stadium_CatchRate.Contains(catch_rate); + } + internal static void GetTradebackStatusRBY(PKM pkm) + { + if (!AllowGen1Tradeback) + { + pkm.TradebackStatus = TradebackType.Gen1_NotTradeback; + ((PK1)pkm).CatchRateIsItem = false; + return; + } + + // Detect tradeback status by comparing the catch rate(Gen1)/held item(Gen2) to the species in the pkm's evolution chain. + var catch_rate = ((PK1)pkm).Catch_Rate; + bool matchAny = GetCatchRateMatchesPreEvolution(pkm, catch_rate); + + // If the catch rate value has been modified, the item has either been removed or swapped in Generation 2. + var HeldItemCatchRate = catch_rate == 0 || HeldItems_GSC.Any(h => h == catch_rate); + if (HeldItemCatchRate && !matchAny) + pkm.TradebackStatus = TradebackType.WasTradeback; + else if (!HeldItemCatchRate && matchAny) + pkm.TradebackStatus = TradebackType.Gen1_NotTradeback; + else + pkm.TradebackStatus = TradebackType.Any; + + // Update the editing settings for the PKM to acknowledge the tradeback status if the species is changed. + ((PK1)pkm).CatchRateIsItem = !pkm.Gen1_NotTradeback && HeldItemCatchRate && !matchAny; + } internal static DexLevel[][] GetEvolutionChainsAllGens(PKM pkm, IEncounterable Encounter) { diff --git a/PKHeX.Core/Legality/Encounters/Data/EncounterUtil.cs b/PKHeX.Core/Legality/Encounters/Data/EncounterUtil.cs index 801f3442f..013bf4764 100644 --- a/PKHeX.Core/Legality/Encounters/Data/EncounterUtil.cs +++ b/PKHeX.Core/Legality/Encounters/Data/EncounterUtil.cs @@ -3,11 +3,31 @@ namespace PKHeX.Core { + /// + /// Miscellaneous setup utility for legality checking data sources. + /// internal static class EncounterUtil { - internal static EncounterArea[] GetEncounterTables(GameVersion Game) + /// + /// Gets the relevant objects that appear in the relevant game. + /// + /// Table of valid encounters that appear for the game pairing + /// Game to filter for + /// Array of encounter objects that are encounterable on the input game + internal static EncounterStatic[] GetStaticEncounters(IEnumerable source, GameVersion game) { - switch (Game) + return source.Where(s => s.Version.Contains(game)).ToArray(); + } + + /// + /// Gets the data for the input game via the program's resource streams. + /// + /// Game to fetch for + /// data is not marked, as the RNG seed is 64 bits (permitting sufficient randomness). + /// Array of areas that are encounterable on the input game. + internal static EncounterArea[] GetEncounterTables(GameVersion game) + { + switch (game) { case GameVersion.B: return GetEncounterTables("51", "b"); case GameVersion.W: return GetEncounterTables("51", "w"); @@ -23,12 +43,23 @@ internal static EncounterArea[] GetEncounterTables(GameVersion Game) return null; // bad request } + /// + /// Direct fetch for data; can also be used to fetch supplementary encounter streams. + /// + /// Unpacking identification ASCII characters (first two bytes of binary) + /// Resource name (will be prefixed with "encounter_" + /// Array of encounter areas internal static EncounterArea[] GetEncounterTables(string ident, string resource) { byte[] mini = Util.GetBinaryResource($"encounter_{resource}.pkl"); return EncounterArea.GetArray(Data.UnpackMini(mini, ident)); } + /// + /// Combines slot arrays with the same . + /// + /// Input encounter areas to combine + /// Combined Array of encounter areas. No duplicate location IDs will be present. internal static EncounterArea[] AddExtraTableSlots(params EncounterArea[][] tables) { return tables.SelectMany(s => s).GroupBy(l => l.Location) @@ -38,6 +69,12 @@ internal static EncounterArea[] AddExtraTableSlots(params EncounterArea[][] tabl .ToArray(); } + /// + /// Marks Encounter Slots for party lead's ability slot influencing. + /// + /// 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) { const int steel = 8; @@ -67,19 +104,37 @@ internal static void MarkEncountersStaticMagnetPull(ref EncounterArea[] Areas, P } } - internal static void MarkEncountersGeneration(EncounterStatic[] Encounters, int Generation) + /// + /// Sets the value, for use in determining split-generation origins. + /// + /// Only used for Gen 1 & 2, as data is not present. + /// Ingame encounter data + /// Generation number to set + internal static void MarkEncountersGeneration(IEnumerable Encounters, int Generation) { foreach (EncounterStatic Encounter in Encounters) Encounter.Generation = Generation; } - internal static void MarkEncountersVersion(EncounterArea[] Areas, GameVersion Version) + /// + /// Sets the value, for use in determining split-generation origins. + /// + /// Only used for Gen 1 & 2, as data is not present. + /// Ingame encounter data + /// Version ID to set + internal static void MarkEncountersVersion(IEnumerable Areas, GameVersion Version) { foreach (EncounterArea Area in Areas) foreach (var Slot in Area.Slots.OfType()) Slot.Version = Version; } + /// + /// Sets the value, for use in determining split-generation origins. + /// + /// Only used for Gen 1 & 2, as data is not present. + /// Ingame encounter data + /// Generation number to set internal static void MarkEncountersGeneration(IEnumerable Areas, int Generation) { foreach (EncounterArea Area in Areas) @@ -87,9 +142,13 @@ internal static void MarkEncountersGeneration(IEnumerable Areas, Slot.Generation = Generation; } + /// + /// Groups areas by location id, raw data has areas with different slots but the same location id. + /// + /// Similar to , this method combines a single array. + /// Ingame encounter data internal static void ReduceAreasSize(ref EncounterArea[] Areas) { - // Group areas by location id, the raw data have areas with different slots but the same location id Areas = Areas.GroupBy(a => a.Location).Select(a => new EncounterArea { Location = a.First().Location, @@ -97,15 +156,16 @@ internal static void ReduceAreasSize(ref EncounterArea[] Areas) }).ToArray(); } + /// + /// Sets the to the for identifying where the slot is encountered. + /// + /// Some games / transferred data do not contain original encounter location IDs; is mainly for info purposes. + /// Ingame encounter data internal static void MarkSlotLocation(ref EncounterArea[] Areas) { foreach (EncounterArea Area in Areas) - { - foreach (EncounterSlot Slot in Area.Slots) - { - Slot.Location = Area.Location; - } - } + foreach (EncounterSlot Slot in Area.Slots) + Slot.Location = Area.Location; } } } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters3.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters3.cs index 7512a4b03..00a175e82 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters3.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters3.cs @@ -15,11 +15,11 @@ internal static class Encounters3 static Encounters3() { - StaticR = Encounter_RSE; - StaticS = Encounter_RSE; - StaticE = Encounter_RSE; - StaticFR = Encounter_FRLG; - StaticLG = Encounter_FRLG; + StaticR = GetStaticEncounters(Encounter_RSE, GameVersion.R); + StaticS = GetStaticEncounters(Encounter_RSE, GameVersion.S); + StaticE = GetStaticEncounters(Encounter_RSE, GameVersion.E); + StaticFR = GetStaticEncounters(Encounter_FRLG, GameVersion.FR); + StaticLG = GetStaticEncounters(Encounter_FRLG, GameVersion.LG); EncounterArea[] get(string resource, string ident) => EncounterArea.GetArray3(Data.UnpackMini(Util.GetBinaryResource($"encounter_{resource}.pkl"), ident)); diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters4.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters4.cs index eccf4867f..f7c803feb 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters4.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters4.cs @@ -12,8 +12,12 @@ internal static class Encounters4 static Encounters4() { MarkG4PokeWalker(Encounter_PokeWalker); - StaticD = StaticP = StaticPt = Encounter_DPPt; - StaticHG = StaticSS = Encounter_HGSS.Concat(Encounter_PokeWalker).ToArray(); + StaticD = GetStaticEncounters(Encounter_DPPt, GameVersion.B); + StaticP = GetStaticEncounters(Encounter_DPPt, GameVersion.W); + StaticPt = GetStaticEncounters(Encounter_DPPt, GameVersion.Pt); + var staticHGSS = Encounter_HGSS.Concat(Encounter_PokeWalker).ToArray(); + StaticHG = GetStaticEncounters(staticHGSS, GameVersion.HG); + StaticSS = GetStaticEncounters(staticHGSS, GameVersion.SS); byte[][] get(string resource, string ident) => Data.UnpackMini(Util.GetBinaryResource($"encounter_{resource}.pkl"), ident); diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters5.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters5.cs index 5d3c1086b..3ed3ac0e5 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters5.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters5.cs @@ -13,8 +13,12 @@ static Encounters5() { MarkG5DreamWorld(BW_DreamWorld); MarkG5DreamWorld(B2W2_DreamWorld); - StaticB = StaticW = Encounter_BW.Concat(BW_DreamWorld).ToArray(); - StaticB2 = StaticW2 = Encounter_B2W2.Concat(B2W2_DreamWorld).ToArray(); + var staticbw = Encounter_BW.Concat(BW_DreamWorld).ToArray(); + var staticb2w2 = Encounter_BW.Concat(BW_DreamWorld).ToArray(); + StaticB = GetStaticEncounters(staticbw, GameVersion.B); + StaticW = GetStaticEncounters(staticbw, GameVersion.W); + StaticB2 = GetStaticEncounters(staticb2w2, GameVersion.B2); + StaticW2 = GetStaticEncounters(staticb2w2, GameVersion.W2); var BSlots = GetEncounterTables(GameVersion.B); var WSlots = GetEncounterTables(GameVersion.W); diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters6.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters6.cs index 0ac9b15c6..12734802c 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters6.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters6.cs @@ -9,8 +9,10 @@ internal static class Encounters6 static Encounters6() { - StaticX = StaticY = Encounter_XY; - StaticA = StaticO = Encounter_AO; + StaticX = GetStaticEncounters(Encounter_XY, GameVersion.X); + StaticY = GetStaticEncounters(Encounter_XY, GameVersion.Y); + StaticA = GetStaticEncounters(Encounter_AO, GameVersion.AS); + StaticO = GetStaticEncounters(Encounter_AO, GameVersion.OR); var XSlots = GetEncounterTables(GameVersion.X); var YSlots = GetEncounterTables(GameVersion.Y); diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters7.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters7.cs index 6e2fc3f04..86f664de7 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters7.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters7.cs @@ -10,7 +10,9 @@ internal static class Encounters7 static Encounters7() { - StaticSN = StaticMN = Encounter_SM; + StaticSN = GetStaticEncounters(Encounter_SM, GameVersion.SN); + StaticMN = GetStaticEncounters(Encounter_SM, GameVersion.MN); + var REG_SN = GetEncounterTables(GameVersion.SN); var REG_MN = GetEncounterTables(GameVersion.MN); var SOS_SN = GetEncounterTables("sm", "sn_sos"); diff --git a/PKHeX.Core/Legality/Encounters/EncounterFinder.cs b/PKHeX.Core/Legality/Encounters/EncounterFinder.cs index eea74200b..d456d4680 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterFinder.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterFinder.cs @@ -3,8 +3,23 @@ namespace PKHeX.Core { + /// + /// Finds matching data and relevant for a . + /// public static class EncounterFinder { + /// + /// Iterates through all possible encounters until a sufficient match is found + /// + /// + /// The iterator lazily finds matching encounters, then verifies secondary checks to weed out any nonexact matches. + /// + /// Source data to find a match for + /// + /// Information containing the matched encounter and any parsed checks. + /// If no clean match is found, the last checked match is returned. + /// If no match is found, an invalid encounter object is returned. + /// public static LegalInfo FindVerifiedEncounter(PKM pkm) { LegalInfo info = new LegalInfo(pkm); @@ -34,6 +49,18 @@ public static LegalInfo FindVerifiedEncounter(PKM pkm) } } + /// + /// Checks supplementary info to see if the encounter is still valid. + /// + /// + /// When an encounter is initially validated, only encounter-related checks are performed. + /// By checking Moves, Evolution, and data, a best match encounter can be found. + /// If the encounter is not valid, the method will not reject it unless another encounter is available to check. + /// + /// Source data to check the match for + /// Information containing the matched encounter + /// Peekable iterator + /// Indication whether or not the encounter passes secondary checks private static bool VerifySecondaryChecks(PKM pkm, LegalInfo info, PeekEnumerator iterator) { if (pkm.Format >= 6) @@ -63,6 +90,13 @@ private static bool VerifySecondaryChecks(PKM pkm, LegalInfo info, PeekEnumerato } return true; } + + /// + /// Returns legality info for an unmatched encounter scenario, including a hint as to what the actual match could be. + /// + /// Source data to check the match for + /// Information containing the unmatched encounter + /// Updated information pertaining to the unmatched encounter private static LegalInfo VerifyWithoutEncounter(PKM pkm, LegalInfo info) { info.EncounterMatch = new EncounterInvalid(pkm); diff --git a/PKHeX.Core/Legality/Encounters/EvolutionVerifier.cs b/PKHeX.Core/Legality/Encounters/EvolutionVerifier.cs index 3696f834a..69bb6e138 100644 --- a/PKHeX.Core/Legality/Encounters/EvolutionVerifier.cs +++ b/PKHeX.Core/Legality/Encounters/EvolutionVerifier.cs @@ -3,15 +3,30 @@ namespace PKHeX.Core { + /// + /// Verify Evolution Information for a matched + /// public static class EvolutionVerifier { - // Evolutions + /// + /// Verifies Evolution scenarios of an for an input and relevant . + /// + /// Source data to verify + /// Source supporting information to verify with + /// public static CheckResult VerifyEvolution(PKM pkm, LegalInfo info) { return IsValidEvolution(pkm, info) ? new CheckResult(CheckIdentifier.Evolution) : new CheckResult(Severity.Invalid, V86, CheckIdentifier.Evolution); } + + /// + /// Checks if the Evolution from the source is valid. + /// + /// Source data to verify + /// Source supporting information to verify with + /// Evolution is valid or not private static bool IsValidEvolution(PKM pkm, LegalInfo info) { int species = pkm.Species; diff --git a/PKHeX.Core/Legality/Encounters/LegalInfo.cs b/PKHeX.Core/Legality/Encounters/LegalInfo.cs index 01f114089..bb7a8dfe6 100644 --- a/PKHeX.Core/Legality/Encounters/LegalInfo.cs +++ b/PKHeX.Core/Legality/Encounters/LegalInfo.cs @@ -11,7 +11,7 @@ public class LegalInfo /// The generation of games the PKM originated from. public int Generation { get; set; } - /// The Game the PPKM originated from. + /// The Game the PKM originated from. public GameVersion Game { get; set; } /// The matched Encounter details for the .