private void VerifyMiscG1Types(LegalityAnalysis data, PK1 pk1) { var Type_A = pk1.Type_A; var Type_B = pk1.Type_B; if (pk1.Species == (int)Species.Porygon) { // Can have any type combination of any species by using Conversion. if (!GBRestrictions.TypeIDExists(Type_A)) { data.AddLine(GetInvalid(LG1TypePorygonFail1)); } if (!GBRestrictions.TypeIDExists(Type_B)) { data.AddLine(GetInvalid(LG1TypePorygonFail2)); } else // Both match a type, ensure a gen1 species has this combo { var TypesAB_Match = PersonalTable.RB.IsValidTypeCombination(Type_A, Type_B); var result = TypesAB_Match ? GetValid(LG1TypeMatchPorygon) : GetInvalid(LG1TypePorygonFail); data.AddLine(result); } } else // Types must match species types { var Type_A_Match = Type_A == PersonalTable.RB[pk1.Species].Type1; var Type_B_Match = Type_B == PersonalTable.RB[pk1.Species].Type2; var first = Type_A_Match ? GetValid(LG1TypeMatch1) : GetInvalid(LG1Type1Fail); var second = Type_B_Match ? GetValid(LG1TypeMatch2) : GetInvalid(LG1Type2Fail); data.AddLine(first); data.AddLine(second); } }
private static CheckMoveResult[] ParseMovesGenGB(PKM pkm, IReadOnlyList <int> currentMoves, LegalInfo info) { var res = new CheckMoveResult[4]; var G1Encounter = info.EncounterMatch; var InitialMoves = Array.Empty <int>(); var SpecialMoves = GetSpecialMoves(info.EncounterMatch); var games = info.EncounterMatch.Generation == 1 ? GBRestrictions.GetGen1Versions(info) : GBRestrictions.GetGen2Versions(info, pkm.Korean); foreach (var ver in games) { var VerInitialMoves = MoveLevelUp.GetEncounterMoves(G1Encounter.Species, 0, G1Encounter.LevelMin, ver); if (VerInitialMoves.Intersect(InitialMoves).Count() == VerInitialMoves.Length) { return(res); } var source = new MoveParseSource { CurrentMoves = currentMoves, SpecialSource = SpecialMoves, Base = VerInitialMoves, }; res = ParseMoves(pkm, source, info); if (res.All(r => r.Valid)) { return(res); } InitialMoves = VerInitialMoves; } return(res); }
private static void ParseEvolutionsIncompatibleMoves(PKM pkm, IList <CheckMoveResult> res, IReadOnlyList <int> moves, IReadOnlyList <int> tmhm) { GBRestrictions.GetIncompatibleEvolutionMoves(pkm, moves, tmhm, out var prevSpeciesID, out var incompatPrev, out var incompatCurr); if (prevSpeciesID == 0) { return; } var prev = SpeciesStrings[prevSpeciesID]; var curr = SpeciesStrings[pkm.Species]; for (int m = 0; m < 4; m++) { if (incompatCurr.Contains(moves[m])) { res[m] = new CheckMoveResult(res[m], Invalid, string.Format(LMoveEvoFLower, curr, prev), Move); } if (incompatPrev.Contains(moves[m])) { res[m] = new CheckMoveResult(res[m], Invalid, string.Format(LMoveEvoFHigher, curr, prev), Move); } } }
private static IEnumerable <IEncounterable> 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 ICaughtData2 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 CheckMoveResult[] ParseMoves(PKM pkm, MoveParseSource source, LegalInfo info) { var res = new CheckMoveResult[4]; bool AllParsed() => res.All(z => z != null); var required = pkm is not PK1 pk1 ? 1 : GBRestrictions.GetRequiredMoveCount(pk1, source.CurrentMoves, info, source.Base); // Special considerations! int reset = 0; if (pkm is IBattleVersion { BattleVersion : not 0 } v) { reset = ((GameVersion)v.BattleVersion).GetGeneration(); source.ResetSources(); } // Check empty moves and relearn moves before generation specific moves for (int m = 0; m < 4; m++) { if (source.CurrentMoves[m] == 0) { res[m] = new CheckMoveResult(None, pkm.Format, m < required ? Fishy : Valid, LMoveSourceEmpty, CurrentMove); } else if (reset == 0 && info.EncounterMoves.Relearn.Contains(source.CurrentMoves[m])) { res[m] = new CheckMoveResult(Relearn, info.Generation, Valid, LMoveSourceRelearn, CurrentMove); } } if (AllParsed()) { return(res); } // Encapsulate arguments to simplify method calls var moveInfo = new LearnInfo(pkm, source); // Check moves going backwards, marking the move valid in the most current generation when it can be learned int[] generations = GenerationTraversal.GetVisitedGenerationOrder(pkm, info.EncounterOriginal.Generation); if (pkm.Format <= 2) { generations = generations.Where(z => z < info.EncounterMoves.LevelUpMoves.Length).ToArray(); } if (reset != 0) { generations = generations.Where(z => z >= reset).ToArray(); } int lastgen = generations.Length == 0 ? 0 : generations[^ 1];
private static CheckMoveResult[] ParseMovesGenGB(PKM pkm, IReadOnlyList <int> currentMoves, LegalInfo info) { var res = new CheckMoveResult[4]; var enc = info.EncounterMatch; var level = pkm.HasOriginalMetLocation ? pkm.Met_Level : enc.LevelMin; var InitialMoves = Array.Empty <int>(); var SpecialMoves = GetSpecialMoves(enc); var games = enc.Generation == 1 ? GBRestrictions.GetGen1Versions(enc) : GBRestrictions.GetGen2Versions(enc, pkm.Korean); foreach (var ver in games) { var VerInitialMoves = MoveLevelUp.GetEncounterMoves(enc.Species, 0, level, ver); if (VerInitialMoves.Intersect(InitialMoves).Count() == VerInitialMoves.Length) { return(res); } var source = new MoveParseSource { CurrentMoves = currentMoves, SpecialSource = SpecialMoves, Base = VerInitialMoves, }; res = ParseMoves(pkm, source, info); // Must have a minimum count of moves, depending on the tradeback state. if (pkm is PK1 pk1) { int count = GBRestrictions.GetRequiredMoveCount(pk1, source.CurrentMoves, info, source.Base); if (count == 1) { return(res); } for (int m = 0; m < count; m++) { var move = source.CurrentMoves[m]; if (move == 0) { res[m] = new CheckMoveResult(None, pkm.Format, Invalid, LMoveSourceEmpty, CurrentMove); } } } if (res.All(r => r.Valid)) { return(res); } InitialMoves = VerInitialMoves; } return(res); }
/// <summary> /// Checks the input <see cref="PKM"/> data for legality. /// </summary> /// <param name="pk">Input data to check</param> /// <param name="pi">Personal info to parse with</param> public LegalityAnalysis(PKM pk, PersonalInfo pi) { pkm = pk; PersonalInfo = pi; if (pkm.Format <= 2) // prior to storing GameVersion { pkm.TradebackStatus = GBRestrictions.GetTradebackStatusInitial(pkm); } #if SUPPRESS try #endif { Info = EncounterFinder.FindVerifiedEncounter(pkm); if (!pkm.IsOriginValid) { AddLine(Severity.Invalid, LEncConditionBadSpecies, CheckIdentifier.GameOrigin); } GetParseMethod()(); if (Parse.Count == 0) // shouldn't ever happen as at least one is yielded above. { AddLine(Severity.Invalid, L_AError, CheckIdentifier.Misc); return; } Valid = Parse.All(chk => chk.Valid) && Info.Moves.All(m => m.Valid) && Info.Relearn.All(m => m.Valid); if (!Valid && pkm.FatefulEncounter && Info.Relearn.Any(chk => !chk.Valid) && EncounterMatch is EncounterInvalid) { AddLine(Severity.Indeterminate, LFatefulGiftMissing, CheckIdentifier.Fateful); } Parsed = true; } #if SUPPRESS // We want to swallow any error from malformed input data from the user. The Valid state is all that we really need. #pragma warning disable CA1031 // Do not catch general exception types catch (Exception e) #pragma warning restore CA1031 // Do not catch general exception types { System.Diagnostics.Debug.WriteLine(e.Message); Info = new LegalInfo(pkm); Valid = false; AddLine(Severity.Invalid, L_AError, CheckIdentifier.Misc); } #endif }
/// <summary> /// Checks the input <see cref="PKM"/> data for legality. /// </summary> /// <param name="pk">Input data to check</param> /// <param name="pi">Personal info to parse with</param> public LegalityAnalysis(PKM pk, PersonalInfo pi) { pkm = pk; PersonalInfo = pi; if (pkm.Format <= 2) // prior to storing GameVersion { pkm.TradebackStatus = GBRestrictions.GetTradebackStatusInitial(pkm); } #if SUPPRESS try #endif { Info = EncounterFinder.FindVerifiedEncounter(pkm); if (!pkm.IsOriginValid) { AddLine(Severity.Invalid, LEncConditionBadSpecies, CheckIdentifier.GameOrigin); } GetParseMethod()(); if (Parse.Count == 0) // shouldn't ever happen as at least one is yielded above. { AddLine(Severity.Invalid, L_AError, CheckIdentifier.Misc); return; } Valid = Parse.All(chk => chk.Valid) && Info.Moves.All(m => m.Valid) && Info.Relearn.All(m => m.Valid); if (!Valid && pkm.FatefulEncounter && Info.Relearn.Any(chk => !chk.Valid) && EncounterMatch is EncounterInvalid) { AddLine(Severity.Indeterminate, LFatefulGiftMissing, CheckIdentifier.Fateful); } Parsed = true; } #if SUPPRESS catch (Exception e) { System.Diagnostics.Debug.WriteLine(e.Message); Info = new LegalInfo(pkm); Valid = false; AddLine(Severity.Invalid, L_AError, CheckIdentifier.Misc); } #endif }
private void ParsePK1() { pkm.TradebackStatus = GBRestrictions.GetTradebackStatusInitial(pkm); UpdateInfo(); if (pkm.TradebackStatus == TradebackType.Any && Info.Generation != pkm.Format) { pkm.TradebackStatus = TradebackType.WasTradeback; // Example: GSC Pokemon with only possible encounters in RBY, like the legendary birds } Nickname.Verify(this); Level.Verify(this); Level.VerifyG1(this); Trainer.VerifyOTG1(this); Misc.VerifyMiscG1(this); if (pkm.Format == 2) { Item.Verify(this); } }
private void VerifyMiscG1Types(LegalityAnalysis data, PK1 pk1) { var Type_A = pk1.Type_A; var Type_B = pk1.Type_B; var species = pk1.Species; if (species == (int)Species.Porygon) { // Can have any type combination of any species by using Conversion. if (!GBRestrictions.TypeIDExists(Type_A)) { data.AddLine(GetInvalid(LG1TypePorygonFail1)); } if (!GBRestrictions.TypeIDExists(Type_B)) { data.AddLine(GetInvalid(LG1TypePorygonFail2)); } else // Both types exist, ensure a Gen1 species has this combination { var TypesAB_Match = PersonalTable.RB.IsValidTypeCombination(Type_A, Type_B); var result = TypesAB_Match ? GetValid(LG1TypeMatchPorygon) : GetInvalid(LG1TypePorygonFail); data.AddLine(result); } } else // Types must match species types { var pi = PersonalTable.RB[species]; var Type_A_Match = Type_A == pi.Type1; var Type_B_Match = Type_B == pi.Type2; var first = Type_A_Match ? GetValid(LG1TypeMatch1) : GetInvalid(LG1Type1Fail); var second = Type_B_Match || (ParseSettings.AllowGBCartEra && ((species == (int)Species.Magnemite || species == (int)Species.Magneton) && Type_B == 9)) // Steel Magnemite via Stadium2 ? GetValid(LG1TypeMatch2) : GetInvalid(LG1Type2Fail); data.AddLine(first); data.AddLine(second); } }
private static EvoCriteria[][] GetChainAll(PKM pkm, IEncounterTemplate enc, EvoCriteria[] fullChain) { int maxgen = ParseSettings.AllowGen1Tradeback && pkm is PK1 ? 2 : pkm.Format; var GensEvoChains = GetAllEmpty(maxgen + 1); var head = 0; // inlined FIFO queue indexing var mostEvolved = fullChain[head++]; var lvl = (byte)pkm.CurrentLevel; var maxLevel = lvl; int pkGen = enc.Generation; // Iterate generations backwards // Maximum level of an earlier generation (GenX) will never be greater than a later generation (GenX+Y). int mingen = pkGen >= 3 ? pkGen : GBRestrictions.GetTradebackStatusInitial(pkm) == PotentialGBOrigin.Gen2Only ? 2 : 1; bool noxfrDecremented = true; for (int g = GensEvoChains.Length - 1; g >= mingen; g--) { if (pkGen <= 2 && g == 6) { g = 2; // skip over 6543 as it never existed in these. } if (g <= 4 && pkm.Format > 2 && pkm.Format > g && !pkm.HasOriginalMetLocation) { // Met location was lost at this point but it also means the pokemon existed in generations 1 to 4 with maximum level equals to met level var met = pkm.Met_Level; if (lvl > pkm.Met_Level) { lvl = (byte)met; } } int maxspeciesgen = g == 2 && pkm.VC1 ? MaxSpeciesID_1 : GetMaxSpeciesOrigin(g); // Remove future gen evolutions after a few special considerations: // If the pokemon origin is illegal (e.g. Gen3 Infernape) the list will be emptied -- species lineage did not exist at any evolution stage. while (mostEvolved.Species > maxspeciesgen) { if (head >= fullChain.Length) { if (g <= 2 && pkm.VC1) { GensEvoChains[pkm.Format] = NONE; // invalidate here since we haven't reached the regular invalidation } return(GensEvoChains); } if (mostEvolved.RequiresLvlUp) { // This is a Gen3 pokemon in a Gen4 phase evolution that requires level up and then transferred to Gen5+ // We can deduce that it existed in Gen4 until met level, // but if current level is met level we can also deduce it existed in Gen3 until maximum met level -1 if (g == 3 && pkm.Format > 4 && lvl == maxLevel) { lvl--; } // The same condition for Gen2 evolution of Gen1 pokemon, level of the pokemon in Gen1 games would be CurrentLevel -1 one level below Gen2 level else if (g == 1 && pkm.Format == 2 && lvl == maxLevel) { lvl--; } } mostEvolved = fullChain[head++]; } // Alolan form evolutions, remove from gens 1-6 chains if (HasAlolanForm(mostEvolved.Species)) { if (g < 7 && pkm.Format >= 7 && mostEvolved.Form > 0) { if (head >= fullChain.Length) { break; } mostEvolved = fullChain[head++]; } } GensEvoChains[g] = GetEvolutionChain(pkm, enc, mostEvolved.Species, lvl); ref var genChain = ref GensEvoChains[g]; if (genChain.Length == 0) { continue; } if (g > 2 && !pkm.HasOriginalMetLocation && g >= pkGen && noxfrDecremented) { bool isTransferred = HasMetLocationUpdatedTransfer(pkGen, g); if (!isTransferred) { continue; } noxfrDecremented = g > (pkGen != 3 ? 4 : 5); // Remove previous evolutions below transfer level // For example a gen3 Charizard in format 7 with current level 36 and met level 36, thus could never be Charmander / Charmeleon in Gen5+. // chain level for Charmander is 35, is below met level. int minlvl = GetMinLevelGeneration(pkm, g); int minIndex = Array.FindIndex(genChain, e => e.LevelMax >= minlvl); if (minIndex != -1) { genChain = genChain.AsSpan(minIndex).ToArray(); } } else if (g == 1) { // Remove Gen7 pre-evolutions and chain break scenarios if (pkm.VC1) { TrimVC1Transfer(pkm, GensEvoChains); } ref var lastGen = ref GensEvoChains[1]; var g1 = lastGen.AsSpan(); // Remove Gen2 post-evolutions (Scizor, Blissey...) if (g1[0].Species > MaxSpeciesID_1) { if (g1.Length == 1) { lastGen = Array.Empty <EvoCriteria>(); continue; // done } g1 = g1[1..];
private static CheckMoveResult[] ParseMoves(PKM pkm, MoveParseSource source, LegalInfo info) { var res = new CheckMoveResult[4]; bool AllParsed() => res.All(z => z != null); var required = pkm.Format != 1 ? 1 : GBRestrictions.GetRequiredMoveCount(pkm, source.CurrentMoves, info, source.Base); // Special considerations! int reset = 0; if (pkm is IBattleVersion v && v.BattleVersion != 0) { reset = ((GameVersion)v.BattleVersion).GetGeneration(); source.EggEventSource = Array.Empty <int>(); source.Base = Array.Empty <int>(); source.EggLevelUpSource = Array.Empty <int>(); source.EggMoveSource = Array.Empty <int>(); source.NonTradeBackLevelUpMoves = Array.Empty <int>(); source.SpecialSource = Array.Empty <int>(); } // Check empty moves and relearn moves before generation specific moves for (int m = 0; m < 4; m++) { if (source.CurrentMoves[m] == 0) { res[m] = new CheckMoveResult(None, pkm.Format, m < required ? Fishy : Valid, LMoveSourceEmpty, Move); } else if (reset == 0 && info.EncounterMoves.Relearn.Contains(source.CurrentMoves[m])) { res[m] = new CheckMoveResult(Relearn, info.Generation, Valid, LMoveSourceRelearn, Move) { Flag = true } } ; } if (AllParsed()) { return(res); } // Encapsulate arguments to simplify method calls var moveInfo = new LearnInfo(pkm, source); // Check moves going backwards, marking the move valid in the most current generation when it can be learned int[] generations = GetGenMovesCheckOrder(pkm); if (pkm.Format <= 2) { generations = generations.Where(z => z < info.EncounterMoves.LevelUpMoves.Length).ToArray(); } if (reset != 0) { generations = generations.Where(z => z >= reset).ToArray(); } int lastgen = generations.LastOrDefault(); foreach (var gen in generations) { ParseMovesByGeneration(pkm, res, gen, info, moveInfo, lastgen); if (AllParsed()) { return(res); } } if (pkm.Species == (int)Species.Shedinja && info.Generation <= 4) { ParseShedinjaEvolveMoves(pkm, res, source.CurrentMoves, info.EvoChainsAllGens); } for (int m = 0; m < 4; m++) { if (res[m] == null) { res[m] = new CheckMoveResult(Unknown, info.Generation, Invalid, LMoveSourceInvalid, Move); } } return(res); }
private static CheckMoveResult[] ParseMovesGenGB(PKM pkm, IReadOnlyList <int> currentMoves, LegalInfo info) { var res = new CheckMoveResult[4]; var enc = info.EncounterMatch; var level = pkm.HasOriginalMetLocation ? pkm.Met_Level : enc.LevelMin; var InitialMoves = Array.Empty <int>(); var SpecialMoves = GetSpecialMoves(enc); var games = enc.Generation == 1 ? GBRestrictions.GetGen1Versions(enc) : GBRestrictions.GetGen2Versions(enc, pkm.Korean); foreach (var ver in games) { var VerInitialMoves = enc is IMoveset { Moves.Count : not 0 } x ? (int[])x.Moves : MoveLevelUp.GetEncounterMoves(enc.Species, 0, level, ver); if (VerInitialMoves.Intersect(InitialMoves).Count() == VerInitialMoves.Length) { return(res); } var source = new MoveParseSource { CurrentMoves = currentMoves, SpecialSource = SpecialMoves, Base = VerInitialMoves, }; res = ParseMoves(pkm, source, info); // Must have a minimum count of moves, depending on the tradeback state. if (pkm is PK1 pk1) { int count = GBRestrictions.GetRequiredMoveCount(pk1, source.CurrentMoves, info, source.Base); if (count == 1) { return(res); } // Reverse for loop and break instead of 0..count continue -- early-breaks for the vast majority of cases. // We already flag for empty interstitial moveslots. for (int m = count - 1; m >= 0; m--) { var move = source.CurrentMoves[m]; if (move != 0) { break; } // There are ways to skip level up moves by leveling up more than once. // https://bulbapedia.bulbagarden.net/wiki/List_of_glitches_(Generation_I)#Level-up_learnset_skipping // Evolution canceling also leads to incorrect assumptions in the above used method, so just indicate them as fishy in that case. // Not leveled up? Not possible to be missing the move slot. var severity = enc.LevelMin == pkm.CurrentLevel ? Invalid : Fishy; res[m] = new CheckMoveResult(None, pkm.Format, severity, LMoveSourceEmpty, CurrentMove); } } if (res.All(r => r.Valid)) { return(res); } InitialMoves = VerInitialMoves; } return(res); }
private void VerifyG1TradeEvo(LegalityAnalysis data) { // Context check is only applicable to gen1/2; transferring to Gen2 is a trade. // Stadium 2 can transfer across game/generation boundaries without initiating a trade. if (ParseSettings.ActiveTrainer.Generation >= 3 || ParseSettings.AllowGBCartEra) { return; } var pkm = data.pkm; var mustevolve = pkm.TradebackStatus == TradebackType.WasTradeback || (pkm.Format == 1 && !ParseSettings.IsFromActiveTrainer(pkm)) || GBRestrictions.IsTradedKadabraG1(pkm); if (!mustevolve) { return; } // Pokemon have been traded but it is not evolved, trade evolutions are sequential dex numbers var evolved = ParseSettings.SpeciesStrings[pkm.Species + 1]; var unevolved = ParseSettings.SpeciesStrings[pkm.Species]; data.AddLine(GetInvalid(string.Format(LEvoTradeReqOutsider, unevolved, evolved))); }
private void VerifyG1TradeEvo(LegalityAnalysis data) { var pkm = data.pkm; var mustevolve = pkm.TradebackStatus == TradebackType.WasTradeback || (pkm.Format == 1 && !ParseSettings.IsFromActiveTrainer(pkm)) || GBRestrictions.IsTradedKadabraG1(pkm); if (!mustevolve) { return; } // Pokemon have been traded but it is not evolved, trade evos are sequential dex numbers var unevolved = LegalityAnalysis.SpeciesStrings[pkm.Species]; var evolved = LegalityAnalysis.SpeciesStrings[pkm.Species + 1]; data.AddLine(GetInvalid(string.Format(LEvoTradeReqOutsider, unevolved, evolved))); }
private void VerifyG1TradeEvo(LegalityAnalysis data) { if (ParseSettings.ActiveTrainer.Generation >= 3) return; // context check is only applicable to gen1/2; transferring to gen2 is a trade. var pkm = data.pkm; var mustevolve = pkm.TradebackStatus == TradebackType.WasTradeback || (pkm.Format == 1 && !ParseSettings.IsFromActiveTrainer(pkm)) || GBRestrictions.IsTradedKadabraG1(pkm); if (!mustevolve) return; // Pokemon have been traded but it is not evolved, trade evos are sequential dex numbers var evolved = LegalityAnalysis.SpeciesStrings[pkm.Species + 1]; var unevolved = LegalityAnalysis.SpeciesStrings[pkm.Species]; data.AddLine(GetInvalid(string.Format(LEvoTradeReqOutsider, unevolved, evolved))); }
private static CheckMoveResult[] ParseMoves(PKM pkm, MoveParseSource source, LegalInfo info) { CheckMoveResult[] res = new CheckMoveResult[4]; bool AllParsed() => res.All(r => r != null); var required = pkm.Format != 1 ? 1 : GBRestrictions.GetRequiredMoveCount(pkm, source.CurrentMoves, info, source.Base); // Check empty moves and relearn moves before generation specific moves for (int m = 0; m < 4; m++) { if (source.CurrentMoves[m] == 0) { res[m] = new CheckMoveResult(MoveSource.None, pkm.Format, m < required ? Severity.Fishy : Severity.Valid, LMoveSourceEmpty, CheckIdentifier.Move); } else if (info.EncounterMoves.Relearn.Contains(source.CurrentMoves[m])) { res[m] = new CheckMoveResult(MoveSource.Relearn, info.Generation, Severity.Valid, LMoveSourceRelearn, CheckIdentifier.Move) { Flag = true } } ; } if (AllParsed()) { return(res); } // Encapsulate arguments to simplify method calls var moveInfo = new LearnInfo(pkm) { Source = source }; // Check moves going backwards, marking the move valid in the most current generation when it can be learned int[] generations = GetGenMovesCheckOrder(pkm); if (pkm.Format <= 2) { generations = generations.Where(z => z < info.EncounterMoves.LevelUpMoves.Length).ToArray(); } int lastgen = generations.LastOrDefault(); foreach (var gen in generations) { ParseMovesByGeneration(pkm, res, gen, info, moveInfo, lastgen); if (AllParsed()) { return(res); } } if (pkm.Species == 292 && info.Generation <= 4) { ParseShedinjaEvolveMoves(pkm, res, source.CurrentMoves); } for (int m = 0; m < 4; m++) { if (res[m] == null) { res[m] = new CheckMoveResult(MoveSource.Unknown, info.Generation, Severity.Invalid, LMoveSourceInvalid, CheckIdentifier.Move); } } return(res); }