From 0b1fcbbe6da36cfe1b4aa5a4a09d9afa9ca0b58e Mon Sep 17 00:00:00 2001 From: javierhimura Date: Wed, 7 Jun 2017 05:10:05 +0200 Subject: [PATCH] New legallity checks (#1196) * Add move source to the check result for current moves, it will be used for analysis of evolution with move to determine how many egg moves had the pokemon and determine if the evolution move could be a egg move that was forgotten * Verify evolution for species that evolved leveling up with an specific move learned, the evolution must be at least one level after the pokemon could legally learn the move or one level after transfer to the first generation where it can evolve * Check to detect traded unevolved Kadabra based in catch rate and moves exclusive from yellow or red/blue If pokemon have data exclusive from one version but is in another version that means it should be evolved * Check no tradeback moves for preevolutions, like Pichu exclusive non tradeback moves for a Pikachu, that Pikachu could not have at the same time Pichu gen 2 moves and gen 1 moves because move reminder do not allow to relearn Pichu moves and gen 2 moves must be forgotten to trade into generation 1 games * Legallity strings for non tradeback checks * https://bulbapedia.bulbagarden.net/wiki/Pok%C3%A9mon_breeding#Passing_moves_down Eggs only inherit a level up move if both parents know the move, that means genderless and male only moves could not have any level up move as an egg except the base egg moves because Ditto is one parent Nidoran male and Volbeat excluded because they can breed with Nidoran female and Illusime * Small check to not search for egg moves in genderless pokemon, generation 2 data include egg moves for Starmie * Fix female only species * Stomp is not a possible egg moves of Stanee * Fix Steenee evolution move, it cant be inherited as an egg --- PKHeX.Core/Legality/Analysis.cs | 5 +- PKHeX.Core/Legality/Checks.cs | 4 +- PKHeX.Core/Legality/Core.cs | 91 ++++++++- .../Legality/Encounters/EncounterFinder.cs | 2 +- PKHeX.Core/Legality/Encounters/LegalInfo.cs | 2 +- .../Legality/Encounters/VerifyCurrentMoves.cs | 192 ++++++++++-------- .../Legality/Encounters/VerifyEvolution.cs | 20 +- .../Legality/Encounters/VerifyRelearnMoves.cs | 9 +- PKHeX.Core/Legality/LegalityCheckStrings.cs | 3 +- .../Legality/Structures/CheckMoveResult.cs | 44 ++++ PKHeX.Core/Legality/Tables.cs | 71 ++++++- .../text/en/LegalityCheckStrings_en.txt | 7 +- .../text/ko/LegalityCheckStrings_ko.txt | 5 +- .../text/zh/LegalityCheckStrings_zh.txt | 5 +- 14 files changed, 345 insertions(+), 115 deletions(-) create mode 100644 PKHeX.Core/Legality/Structures/CheckMoveResult.cs diff --git a/PKHeX.Core/Legality/Analysis.cs b/PKHeX.Core/Legality/Analysis.cs index f971adfb2..0c7ced1ef 100644 --- a/PKHeX.Core/Legality/Analysis.cs +++ b/PKHeX.Core/Legality/Analysis.cs @@ -44,7 +44,10 @@ private IEnumerable AllSuggestedRelearnMoves if (Error) return new int[4]; if (_allSuggestedRelearnMoves == null) - return _allSuggestedRelearnMoves = pkm == null || !pkm.IsOriginValid ? new int[4] : Legal.getValidRelearn(pkm, info.EncounterMatch.Species).ToArray(); + { + var inheritLvlMoves = pkm.PersonalInfo.Gender > 0 && pkm.PersonalInfo.Gender < 255 || Legal.MixedGenderBreeding.Contains(info.EncounterMatch.Species); + return _allSuggestedRelearnMoves = pkm == null || !pkm.IsOriginValid ? new int[4] : Legal.getValidRelearn(pkm, info.EncounterMatch.Species, inheritLvlMoves).ToArray(); + } return _allSuggestedRelearnMoves; } } diff --git a/PKHeX.Core/Legality/Checks.cs b/PKHeX.Core/Legality/Checks.cs index 35213e350..de922d205 100644 --- a/PKHeX.Core/Legality/Checks.cs +++ b/PKHeX.Core/Legality/Checks.cs @@ -626,7 +626,7 @@ private void verifyLevel() } private void verifyG1TradeEvo() { - var mustevolve = pkm.TradebackStatus == TradebackType.WasTradeback || (pkm.Format == 1 && Legal.IsOutsider(pkm)); + var mustevolve = pkm.TradebackStatus == TradebackType.WasTradeback || (pkm.Format == 1 && Legal.IsOutsider(pkm)) || Legal.IsTradedKadabraG1(pkm); if (!mustevolve) return; // Pokemon have been traded but it is not evolved, trade evos are sequential dex numbers @@ -1792,7 +1792,7 @@ private void verifyForm() int index = Array.IndexOf(pkm.Moves, 548); // Secret Sword bool noSword = index < 0; if (pkm.AltForm == 0 ^ noSword) // mismatch - info.vMoves[noSword ? 0 : index] = new CheckResult(Severity.Invalid, V169, CheckIdentifier.Move); + info.vMoves[noSword ? 0 : index] = new CheckMoveResult(info.vMoves[noSword ? 0 : index], Severity.Invalid, V169, CheckIdentifier.Move); break; } case 649: // Genesect diff --git a/PKHeX.Core/Legality/Core.cs b/PKHeX.Core/Legality/Core.cs index 892c4e91d..b1c8f9a89 100644 --- a/PKHeX.Core/Legality/Core.cs +++ b/PKHeX.Core/Legality/Core.cs @@ -1206,7 +1206,7 @@ internal static IEnumerable getValidMoves(PKM pkm, DexLevel[] evoChain, int version = GameVersion.Any; return getValidMoves(pkm, version, evoChain, generation, minLvLG1: minLvLG1, LVL: LVL, Relearn: false, Tutor: Tutor, Machine: Machine, MoveReminder: MoveReminder, RemoveTransferHM: RemoveTransferHM); } - internal static IEnumerable getValidRelearn(PKM pkm, int species) + internal static IEnumerable getValidRelearn(PKM pkm, int species, bool inheritlvlmoves) { List r = new List { 0 }; if (pkm.GenNumber < 6 || pkm.VC) @@ -1219,7 +1219,7 @@ internal static IEnumerable getValidRelearn(PKM pkm, int species) form = 0; r.AddRange(getEggMoves(pkm, species, form)); - if (pkm.Species != 489) + if (inheritlvlmoves) r.AddRange(getRelearnLVLMoves(pkm, species, 100, pkm.AltForm)); return r.Distinct(); } @@ -1997,6 +1997,63 @@ internal static bool getEvolutionValid(PKM pkm, int minSpecies = -1) return curr.Count() >= poss.Count() - 1; return curr.Count() >= poss.Count(); } + internal static bool getEvolutionWithMoveValid(PKM pkm, LegalInfo info) + { + // Exclude species that do not evolve leveling with a move + // Exclude gen 1-3 games + // Exclude Mr Mime and Snorlax for gen 1-3 games + if (!SpeciesEvolutionWithMove.Contains(pkm.Species) || pkm.Format <= 3 || (BabyEvolutionWithMove.Contains(pkm.Species) && pkm.GenNumber <= 3)) + return true; + + var index = Array.FindIndex(SpeciesEvolutionWithMove, p => p == pkm.Species); + var levels = MinLevelEvolutionWithMove[index]; + var moves = MoveEvolutionWithMove[index]; + var allowegg = EggMoveEvolutionWithMove[index][pkm.GenNumber]; + + // Get the minimun level in any generation when the pokemon could learn the evolve move + var LearnLevel = 101; + for(int g = pkm.GenNumber; g <= pkm.Format; g++) + if(pkm.InhabitedGeneration(g) && levels[g] > 0) + LearnLevel = Math.Min(LearnLevel, levels[g]); + + // Check also if the current encounter include the evolve move as an special move + // That means the pokemon have the move from the encounter level + int[] SpecialMoves = (info.EncounterMatch as IMoveset)?.Moves ?? new int[0]; + if (SpecialMoves.Any(m => moves.Contains(m))) + LearnLevel = Math.Min(LearnLevel, info.EncounterMatch.LevelMin); + + // If the encounter is a player hatched egg check if the move could be an egg move or inherited level up move + if (info.EncounterMatch.EggEncounter && !pkm.WasGiftEgg && !pkm.WasEventEgg && allowegg) + { + var inheritmove = false; + if (pkm.GenNumber >= 6) + // 3DS games, if the move is not a relearn move that means the pokemon was hatched without the move + inheritmove = pkm.RelearnMoves.Any(m => moves.Contains(m)); + else if (pkm.Moves.Any(m => moves.Contains(m))) + // Pre-3DS games, if the pokemon was an egg and it have the move and also and egg from this species could hatch with the move + // that means is a valid egg move + inheritmove = true; + else + { + // If the pokemon does not have the move it still could be an egg move that was forgotten + // But that requires for the pokemon to do not have 4 other moves identified as egg moves or inherited level up moves + var eggmoves = info.vMoves.Count(m => m.Source == MoveSource.EggMove || m.Source == MoveSource.InheritLevelUp); + inheritmove = eggmoves < 4; + } + if (inheritmove) + LearnLevel = Math.Min(LearnLevel, pkm.GenNumber < 4 ? 5 : 1); + } + + // If has original met location the minimun evolution level is one level after met level + // Gen 3 pokemon in gen 4 games minimun level is one level after transfer to generation 4 + // VC pokemon minimun level is one leve after transfer to generation 7 + // Sylveon always one level after met level, for gen 4 and 5 eevees in gen 6 games minimun for evolution is one leve after transfer to generation 5 + if (pkm.HasOriginalMetLocation || pkm.Format == 4 && pkm.Gen3 || pkm.VC || pkm.Species == 700) + LearnLevel = Math.Max(pkm.Met_Level, LearnLevel); + + // Current level must be at leats one level after the minimun learn level + return pkm.CurrentLevel > LearnLevel; + } internal static bool getCanFormChange(PKM pkm, int species) { if (FormChange.Contains(species)) @@ -2642,7 +2699,7 @@ private static IEnumerable getMoves(PKM pkm, int species, int minlvlG1, int internal static int[] getEggMoves(PKM pkm, int species, int formnum, GameVersion Version = GameVersion.Any) { - if (!pkm.InhabitedGeneration(pkm.GenNumber, species)) + if (!pkm.InhabitedGeneration(pkm.GenNumber, species) || pkm.PersonalInfo.Gender == 255) return new int[0]; switch (pkm.GenNumber) @@ -2872,6 +2929,34 @@ internal static List[] GetEmptyMovesList(DexLevel[][] EvoChainsAllGens) empty[i] = new List(); return empty; } + internal static bool IsTradedKadabraG1(PKM pkm) + { + if (pkm.SpecForm != 64 || pkm.Format > 1) + return false; + if (pkm.TradebackStatus == TradebackType.WasTradeback) + return true; + var IsYellow = Savegame_Version == GameVersion.Y; + if (pkm.TradebackStatus == TradebackType.Gen1_NotTradeback) + { + // If catch rate is abra catch rate it wont trigger as invalid trade without evolution, it could be traded as Abra + var catch_rate = (pkm as PK1).Catch_Rate; + // Yellow Kadabra catch rate in Red/Blue game, must be Allakazham + if (catch_rate == PersonalTable.Y[64].CatchRate && !IsYellow) + return true; + // Red/Blue Kadabra catch rate in Yellow game, must be Allakazham + if (catch_rate == PersonalTable.RB[64].CatchRate && IsYellow) + return true; + } + if (IsYellow) + return false; + // Yellow only moves in Red/Blue game, must be Allakazham + if (pkm.Moves.Contains(134)) // Kinesis, yellow only move + return true; + if (pkm.CurrentLevel < 20 && pkm.Moves.Contains(50)) // Disable bellow level 20, yellow only move + return true; + + return false; + } internal static bool IsOutsider(PKM pkm) { var Outsider = Savegame_TID != pkm.TID || Savegame_OT != pkm.OT_Name; diff --git a/PKHeX.Core/Legality/Encounters/EncounterFinder.cs b/PKHeX.Core/Legality/Encounters/EncounterFinder.cs index 580f96c93..24dbf98df 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterFinder.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterFinder.cs @@ -41,7 +41,7 @@ public static LegalInfo verifyEncounter(PKM pkm) if (info.vMoves.Any(z => !z.Valid) && encounter.PeekIsNext()) continue; - var evo = VerifyEvolution.verifyEvolution(pkm, EncounterMatch); + var evo = VerifyEvolution.verifyEvolution(pkm, info); if (!evo.Valid && encounter.PeekIsNext()) continue; info.Parse.Add(evo); diff --git a/PKHeX.Core/Legality/Encounters/LegalInfo.cs b/PKHeX.Core/Legality/Encounters/LegalInfo.cs index 356776e49..e91136532 100644 --- a/PKHeX.Core/Legality/Encounters/LegalInfo.cs +++ b/PKHeX.Core/Legality/Encounters/LegalInfo.cs @@ -31,7 +31,7 @@ public IEncounterable EncounterMatch public readonly List Parse = new List(); public CheckResult[] vRelearn = new CheckResult[4]; - public CheckResult[] vMoves = new CheckResult[4]; + public CheckMoveResult[] vMoves = new CheckMoveResult[4]; public DexLevel[][] EvoChainsAllGens => _evochains ?? (_evochains = Legal.getEvolutionChainsAllGens(pkm, EncounterMatch)); public ValidEncounterMoves EncounterMoves { get; set; } diff --git a/PKHeX.Core/Legality/Encounters/VerifyCurrentMoves.cs b/PKHeX.Core/Legality/Encounters/VerifyCurrentMoves.cs index 47a1034a1..c0553023d 100644 --- a/PKHeX.Core/Legality/Encounters/VerifyCurrentMoves.cs +++ b/PKHeX.Core/Legality/Encounters/VerifyCurrentMoves.cs @@ -8,7 +8,7 @@ namespace PKHeX.Core { public static class VerifyCurrentMoves { - public static CheckResult[] verifyMoves(PKM pkm, LegalInfo info, GameVersion game = GameVersion.Any) + public static CheckMoveResult[] verifyMoves(PKM pkm, LegalInfo info, GameVersion game = GameVersion.Any) { int[] Moves = pkm.Moves; var res = parseMovesForEncounters(pkm, info, game, Moves); @@ -16,12 +16,12 @@ public static CheckResult[] verifyMoves(PKM pkm, LegalInfo info, GameVersion gam // Duplicate Moves Check verifyNoEmptyDuplicates(Moves, res); if (Moves[0] == 0) // Can't have an empty moveslot for the first move. - res[0] = new CheckResult(Severity.Invalid, V167, CheckIdentifier.Move); + res[0] = new CheckMoveResult(res[0], Severity.Invalid, V167, CheckIdentifier.Move); return res; } - private static CheckResult[] parseMovesForEncounters(PKM pkm, LegalInfo info, GameVersion game, int[] Moves) + private static CheckMoveResult[] parseMovesForEncounters(PKM pkm, LegalInfo info, GameVersion game, int[] Moves) { if (pkm.Species == 235) // special handling for Smeargle return parseMovesForSmeargle(pkm, Moves, info); // Smeargle can have any moves except a few @@ -55,10 +55,10 @@ private static CheckResult[] parseMovesForEncounters(PKM pkm, LegalInfo info, Ga pkm.TradebackStatus = defaultTradeback; return res; } - private static CheckResult[] parseMovesForSmeargle(PKM pkm, int[] Moves, LegalInfo info) + private static CheckMoveResult[] parseMovesForSmeargle(PKM pkm, int[] Moves, LegalInfo info) { if (!pkm.IsEgg) - return parseMovesSketch(Moves); + return parseMovesSketch(pkm, Moves); // can only know sketch as egg var empty = ValidEncounterMoves.Empty; @@ -66,15 +66,18 @@ private static CheckResult[] parseMovesForSmeargle(PKM pkm, int[] Moves, LegalIn { validLevelUpMoves = Legal.getValidMovesAllGens(pkm, info.EvoChainsAllGens, minLvLG1: 1, Tutor: false, Machine: false, RemoveTransferHM: false) }; - return parseMoves(pkm, pkm.Moves, new int[0], new int[0], new int[0], empty, new int[0], new int[0], false, info); + return parseMoves(pkm, pkm.Moves, new int[0], new int[0], new int[0], new int[0], new int[0], new int[0], false, info); } - private static CheckResult[] parseMovesIsEggPreRelearn(PKM pkm, int[] Moves, int[] SpecialMoves, bool allowinherited, EncounterEgg e) + private static CheckMoveResult[] parseMovesIsEggPreRelearn(PKM pkm, int[] Moves, int[] SpecialMoves, bool allowinherited, EncounterEgg e) { - CheckResult[] res = new CheckResult[4]; + CheckMoveResult[] res = new CheckMoveResult[4]; var ValidSpecialMoves = SpecialMoves.Where(m => m != 0).ToList(); var baseEggMoves = Legal.getBaseEggMoves(pkm, e.Species, e.Game, pkm.GenNumber < 4 ? 5 : 1)?.ToList() ?? new List(); - var InheritedLvlMoves = Legal.getBaseEggMoves(pkm, e.Species, e.Game, 100)?.ToList() ?? new List(); + // Level up moves could not be inherited if Ditto is parent, + // that means genderless species and male only species except Nidoran and Volbet (they breed with female nidoran and illumise) could not have level up moves as an egg + var AllowLvlMoves = pkm.PersonalInfo.Gender > 0 && pkm.PersonalInfo.Gender < 255 || Legal.MixedGenderBreeding.Contains(e.Species); + var InheritedLvlMoves = !AllowLvlMoves? new List() : Legal.getBaseEggMoves(pkm, e.Species, e.Game, 100)?.ToList() ?? new List(); var EggMoves = Legal.getEggMoves(pkm, e.Species, pkm.AltForm)?.ToList() ?? new List(); var InheritedTutorMoves = e.Game == GameVersion.C ? Legal.getTutorMoves(pkm, pkm.Species, pkm.AltForm, false, 2)?.ToList() : new List(); // Only TM Hm moves from the source game of the egg, not any other games from the same generation @@ -94,30 +97,35 @@ private static CheckResult[] parseMovesIsEggPreRelearn(PKM pkm, int[] Moves, int return res; } - private static CheckResult[] parseMovesWasEggPreRelearn(PKM pkm, int[] Moves, LegalInfo info, EncounterEgg e) + private static CheckMoveResult[] parseMovesWasEggPreRelearn(PKM pkm, int[] Moves, LegalInfo info, EncounterEgg e) { var EventEggMoves = (info.EncounterMatch as IMoveset)?.Moves ?? new int[0]; - int BaseLvlMoves = 489 <= pkm.Species && pkm.Species <= 490 ? 1 : 100; + // Level up moves could not be inherited if Ditto is parent, + // that means genderless species and male only species except Nidoran and Volbet (they breed with female nidoran and illumise) could not have level up moves as an egg + var inheritLvlMoves = pkm.PersonalInfo.Gender > 0 && pkm.PersonalInfo.Gender < 255 || Legal.MixedGenderBreeding.Contains(e.Species); + int BaseLvlMoves = inheritLvlMoves ? 100 : pkm.GenNumber <= 3 ? 5 : 1; var LvlupEggMoves = Legal.getBaseEggMoves(pkm, e.Species, e.Game, BaseLvlMoves); - // Level up, TMHM or tutor moves exclusive to the incense egg species, like Azurill, incompatible with the non-incense species egg moves - var ExclusiveIncenseMoves = e.SplitBreed ? Legal.getExclusivePreEvolutionMoves(pkm, Legal.getBaseEggSpecies(pkm), info.EvoChainsAllGens, e.Game) : null; + var TradebackPreevo = pkm.Format == 2 && info.EncounterMatch.Species > 151; + var NonTradebackLvlMoves = new int[0]; + if (TradebackPreevo) + NonTradebackLvlMoves = Legal.getExclusivePreEvolutionMoves(pkm, info.EncounterMatch.Species, info.EvoChainsAllGens[2], 2, e.Game).Where(m => m > Legal.MaxMoveID_1).ToArray(); var EggMoves = Legal.getEggMoves(pkm, e.Species, pkm.AltForm); bool volt = (pkm.GenNumber > 3 || e.Game == GameVersion.E) && Legal.LightBall.Contains(pkm.Species); var SpecialMoves = volt && EventEggMoves.Length == 0 ? new[] { 344 } : new int[0]; // Volt Tackle for bred Pichu line - return parseMoves(pkm, Moves, SpecialMoves, LvlupEggMoves, EggMoves, ExclusiveIncenseMoves, EventEggMoves, new int[0], e.SplitBreed, info); + return parseMoves(pkm, Moves, SpecialMoves, LvlupEggMoves, EggMoves, NonTradebackLvlMoves, EventEggMoves, new int[0], e.SplitBreed, info); } - private static CheckResult[] parseMovesSketch(int[] Moves) + private static CheckMoveResult[] parseMovesSketch(PKM pkm, int[] Moves) { - CheckResult[] res = new CheckResult[4]; + CheckMoveResult[] res = new CheckMoveResult[4]; for (int i = 0; i < 4; i++) res[i] = Legal.InvalidSketch.Contains(Moves[i]) - ? new CheckResult(Severity.Invalid, V166, CheckIdentifier.Move) - : new CheckResult(CheckIdentifier.Move); + ? new CheckMoveResult(MoveSource.Unknown, pkm.Format, Severity.Invalid, V166, CheckIdentifier.Move) + : new CheckMoveResult(MoveSource.Sketch, pkm.Format, CheckIdentifier.Move); return res; } - private static CheckResult[] parseMoves3DS(PKM pkm, GameVersion game, int[] Moves, LegalInfo info) + private static CheckMoveResult[] parseMoves3DS(PKM pkm, GameVersion game, int[] Moves, LegalInfo info) { info.EncounterMoves.Relearn = pkm.GenNumber >= 6 ? pkm.RelearnMoves : new int[0]; if (info.EncounterMatch is IMoveset) @@ -126,7 +134,7 @@ private static CheckResult[] parseMoves3DS(PKM pkm, GameVersion game, int[] Move // Everything else return parseMovesRelearn(pkm, Moves, info); } - private static CheckResult[] parseMovesPre3DS(PKM pkm, int[] Moves, LegalInfo info) + private static CheckMoveResult[] parseMovesPre3DS(PKM pkm, int[] Moves, LegalInfo info) { if (pkm.IsEgg && info.EncounterMatch is EncounterEgg egg) { @@ -142,66 +150,65 @@ private static CheckResult[] parseMovesPre3DS(PKM pkm, int[] Moves, LegalInfo in return parseMovesSpecialMoveset(pkm, Moves, info); } - private static CheckResult[] parseMovesGen1(PKM pkm, int[] Moves, LegalInfo info) + private static CheckMoveResult[] parseMovesGen1(PKM pkm, int[] Moves, LegalInfo info) { GameVersion[] games = Legal.getGen1GameEncounter(pkm); - CheckResult[] res = new CheckResult[4]; + CheckMoveResult[] res = new CheckMoveResult[4]; var G1Encounter = info.EncounterMatch; if (G1Encounter == null) return parseMovesSpecialMoveset(pkm, Moves, info); var InitialMoves = new int[0]; int[] SpecialMoves = (info.EncounterMatch as IMoveset)?.Moves ?? new int[0]; - var empty = Legal.GetEmptyMovesList(info.EvoChainsAllGens); var emptyegg = new int[0]; foreach (GameVersion ver in games) { var VerInitialMoves = Legal.getInitialMovesGBEncounter(G1Encounter.Species, G1Encounter.LevelMin, ver).ToArray(); if (VerInitialMoves.SequenceEqual(InitialMoves)) return res; - res = parseMoves(pkm, Moves, SpecialMoves, emptyegg, emptyegg, empty, new int[0], VerInitialMoves, false, info); + res = parseMoves(pkm, Moves, SpecialMoves, emptyegg, emptyegg, emptyegg, new int[0], VerInitialMoves, false, info); if (res.All(r => r.Valid)) return res; InitialMoves = VerInitialMoves; } return res; } - private static CheckResult[] parseMovesSpecialMoveset(PKM pkm, int[] Moves, LegalInfo info) + private static CheckMoveResult[] parseMovesSpecialMoveset(PKM pkm, int[] Moves, LegalInfo info) { var mg = info.EncounterMatch as IMoveset; int[] SpecialMoves = mg?.Moves ?? new int[0]; - var empty = Legal.GetEmptyMovesList(info.EvoChainsAllGens); var emptyegg = new int[0]; - CheckResult[] res = parseMoves(pkm, Moves, SpecialMoves, emptyegg, emptyegg, empty, new int[0], new int[0], false, info); + CheckMoveResult[] res = parseMoves(pkm, Moves, SpecialMoves, emptyegg, emptyegg, emptyegg, new int[0], new int[0], false, info); if (res.Any(r => !r.Valid)) return res; return res; } - private static CheckResult[] parseMovesRelearn(PKM pkm, int[] Moves, LegalInfo info) + private static CheckMoveResult[] parseMovesRelearn(PKM pkm, int[] Moves, LegalInfo info) { var emptyegg = new int[0]; var issplitbreed = pkm.WasEgg && Legal.SplitBreed.Contains(pkm.Species); var e = info.EncounterMatch as EncounterEgg; var EggMoves = e != null ? Legal.getEggMoves(pkm, e.Species, pkm.AltForm, e.Game) : emptyegg; - // Level up, TMHM or tutor moves exclusive to the incense egg species, like Azurill, incompatible with the non-incense species egg moves - var ExclusiveIncenseMoves = issplitbreed ? Legal.getExclusivePreEvolutionMoves(pkm, Legal.getBaseEggSpecies(pkm), info.EvoChainsAllGens, e.Game) : Legal.GetEmptyMovesList(info.EvoChainsAllGens); - + var TradebackPreevo = pkm.Format == 2 && info.EncounterMatch.Species > 151 && pkm.InhabitedGeneration(1); + var NonTradebackLvlMoves = TradebackPreevo ? Legal.getExclusivePreEvolutionMoves(pkm, info.EncounterMatch.Species, info.EvoChainsAllGens[2], 2, e.Game).Where(m => m > Legal.MaxMoveID_1).ToArray() : new int[0]; + int[] RelearnMoves = pkm.RelearnMoves; int[] SpecialMoves = (info.EncounterMatch as IMoveset)?.Moves ?? new int[0]; - CheckResult[] res = parseMoves(pkm, Moves, SpecialMoves, new int[0], EggMoves, ExclusiveIncenseMoves, new int[0], new int[0], issplitbreed, info); + CheckMoveResult[] res = parseMoves(pkm, Moves, SpecialMoves, new int[0], EggMoves, NonTradebackLvlMoves, new int[0], new int[0], issplitbreed, info); for (int i = 0; i < 4; i++) if ((pkm.IsEgg || res[i].Flag) && !RelearnMoves.Contains(Moves[i])) - res[i] = new CheckResult(Severity.Invalid, string.Format(V170, res[i].Comment), res[i].Identifier); + res[i] = new CheckMoveResult(res[i], Severity.Invalid, string.Format(V170, res[i].Comment), res[i].Identifier); return res; } - private static CheckResult[] parseMoves(PKM pkm, int[] moves, int[] special, int[] lvlupegg, int[] egg, List[] IncenseExclusiveMoves, int[] eventegg, int[] initialmoves, bool issplitbreed, LegalInfo info) + private static CheckMoveResult[] parseMoves(PKM pkm, int[] moves, int[] special, int[] lvlupegg, int[] egg, int[] NonTradebackLvlMoves, int[] eventegg, int[] initialmoves, bool issplitbreed, LegalInfo info) { - CheckResult[] res = new CheckResult[4]; + CheckMoveResult[] res = new CheckMoveResult[4]; var Gen1MovesLearned = new List(); + var Gen2PreevoMovesLearned = new List(); var EggMovesLearned = new List(); var LvlupEggMovesLearned = new List(); var EventEggMovesLearned = new List(); @@ -213,9 +220,9 @@ private static CheckResult[] parseMoves(PKM pkm, int[] moves, int[] special, int for (int m = 0; m < 4; m++) { if (moves[m] == 0) - res[m] = new CheckResult(m < required ? Severity.Invalid : Severity.Valid, V167, CheckIdentifier.Move); + res[m] = new CheckMoveResult(MoveSource.None, pkm.Format, m < required ? Severity.Invalid : Severity.Valid, V167, CheckIdentifier.Move); else if (info.EncounterMoves.Relearn.Contains(moves[m])) - res[m] = new CheckResult(Severity.Valid, V172, CheckIdentifier.Move) { Flag = true }; + res[m] = new CheckMoveResult(MoveSource.Relearn, pkm.GenNumber, Severity.Valid, V172, CheckIdentifier.Move) { Flag = true }; } if (res.All(r => r != null)) @@ -251,21 +258,28 @@ private static CheckResult[] parseMoves(PKM pkm, int[] moves, int[] special, int continue; if (gen == 1 && initialmoves.Contains(moves[m])) - res[m] = new CheckResult(Severity.Valid, native ? V361 : string.Format(V362, gen), CheckIdentifier.Move); + res[m] = new CheckMoveResult(MoveSource.Initial, gen, Severity.Valid, native ? V361 : string.Format(V362, gen), CheckIdentifier.Move); else if (info.EncounterMoves.validLevelUpMoves[gen].Contains(moves[m])) - res[m] = new CheckResult(Severity.Valid, native ? V177 : string.Format(V330, gen), CheckIdentifier.Move); + res[m] = new CheckMoveResult(MoveSource.LevelUp, gen, Severity.Valid, native ? V177 : string.Format(V330, gen), CheckIdentifier.Move); else if (info.EncounterMoves.validTMHMMoves[gen].Contains(moves[m])) - res[m] = new CheckResult(Severity.Valid, native ? V173 : string.Format(V331, gen), CheckIdentifier.Move); + res[m] = new CheckMoveResult(MoveSource.TMHM, gen, Severity.Valid, native ? V173 : string.Format(V331, gen), CheckIdentifier.Move); else if (info.EncounterMoves.validTutorMoves[gen].Contains(moves[m])) - res[m] = new CheckResult(Severity.Valid, native ? V174 : string.Format(V332, gen), CheckIdentifier.Move); + res[m] = new CheckMoveResult(MoveSource.Tutor, gen, Severity.Valid, native ? V174 : string.Format(V332, gen), CheckIdentifier.Move); else if (gen == pkm.GenNumber && special.Contains(moves[m])) - res[m] = new CheckResult(Severity.Valid, V175, CheckIdentifier.Move); + res[m] = new CheckMoveResult(MoveSource.Special, gen, Severity.Valid, V175, CheckIdentifier.Move); if (res[m] == null || gen < 3) continue; + if (res[m].Valid && gen == 2 && NonTradebackLvlMoves.Contains(m)) + Gen2PreevoMovesLearned.Add(m); if (res[m].Valid && gen == 1) + { Gen1MovesLearned.Add(m); + if (Gen2PreevoMovesLearned.Any()) + MixedGen1NonTradebackGen2 = true; + } + if (res[m].Valid && gen <= 2 && pkm.TradebackStatus == TradebackType.Any && pkm.GenNumber != gen) pkm.TradebackStatus = TradebackType.WasTradeback; } @@ -285,11 +299,11 @@ private static CheckResult[] parseMoves(PKM pkm, int[] moves, int[] special, int if (IsGen2Pkm && Gen1MovesLearned.Any() && moves[m] > Legal.MaxMoveID_1) { - res[m] = new CheckResult(Severity.Invalid, V334, CheckIdentifier.Move); + res[m] = new CheckMoveResult(MoveSource.InheritLevelUp, gen, Severity.Invalid, V334, CheckIdentifier.Move); MixedGen1NonTradebackGen2 = true; } else - res[m] = new CheckResult(Severity.Valid, V345, CheckIdentifier.Move); + res[m] = new CheckMoveResult(MoveSource.InheritLevelUp, gen, Severity.Valid, V345, CheckIdentifier.Move); LvlupEggMovesLearned.Add(m); if (pkm.TradebackStatus == TradebackType.Any && pkm.GenNumber == 1) pkm.TradebackStatus = TradebackType.WasTradeback; @@ -310,11 +324,11 @@ private static CheckResult[] parseMoves(PKM pkm, int[] moves, int[] special, int { // To learn exclusive generation 1 moves the pokemon was tradeback, but it can't be trade to generation 1 // without removing moves above MaxMoveID_1, egg moves above MaxMoveID_1 and gen 1 moves are incompatible - res[m] = new CheckResult(Severity.Invalid, V334, CheckIdentifier.Move) { Flag = true }; + res[m] = new CheckMoveResult(MoveSource.EggMove, gen, Severity.Invalid, V334, CheckIdentifier.Move) { Flag = true }; MixedGen1NonTradebackGen2 = true; } else - res[m] = new CheckResult(Severity.Valid, V171, CheckIdentifier.Move) { Flag = true }; + res[m] = new CheckMoveResult(MoveSource.EggMove, gen, Severity.Valid, V171, CheckIdentifier.Move) { Flag = true }; EggMovesLearned.Add(m); if (pkm.TradebackStatus == TradebackType.Any && pkm.GenNumber == 1) @@ -327,11 +341,11 @@ private static CheckResult[] parseMoves(PKM pkm, int[] moves, int[] special, int { if (IsGen2Pkm && Gen1MovesLearned.Any() && moves[m] > Legal.MaxMoveID_1) { - res[m] = new CheckResult(Severity.Invalid, V334, CheckIdentifier.Move) { Flag = true }; + res[m] = new CheckMoveResult(MoveSource.SpecialEgg, gen, Severity.Invalid, V334, CheckIdentifier.Move) { Flag = true }; MixedGen1NonTradebackGen2 = true; } else - res[m] = new CheckResult(Severity.Valid, V333, CheckIdentifier.Move) { Flag = true }; + res[m] = new CheckMoveResult(MoveSource.SpecialEgg, gen, Severity.Valid, V333, CheckIdentifier.Move) { Flag = true }; } if (pkm.TradebackStatus == TradebackType.Any && pkm.GenNumber == 1) pkm.TradebackStatus = TradebackType.WasTradeback; @@ -350,11 +364,11 @@ private static CheckResult[] parseMoves(PKM pkm, int[] moves, int[] special, int foreach (int m in IncompatibleEggMoves) { if (EventEggMovesLearned.Contains(m) && !EggMovesLearned.Contains(m)) - res[m] = new CheckResult(Severity.Invalid, V337, CheckIdentifier.Move); + res[m] = new CheckMoveResult(res[m], Severity.Invalid, V337, CheckIdentifier.Move); else if (!EventEggMovesLearned.Contains(m) && EggMovesLearned.Contains(m)) - res[m] = new CheckResult(Severity.Invalid, V336, CheckIdentifier.Move); + res[m] = new CheckMoveResult(res[m], Severity.Invalid, V336, CheckIdentifier.Move); else if (!EventEggMovesLearned.Contains(m) && LvlupEggMovesLearned.Contains(m)) - res[m] = new CheckResult(Severity.Invalid, V358, CheckIdentifier.Move); + res[m] = new CheckMoveResult(res[m], Severity.Invalid, V358, CheckIdentifier.Move); } } } @@ -364,9 +378,9 @@ private static CheckResult[] parseMoves(PKM pkm, int[] moves, int[] special, int foreach (int m in RegularEggMovesLearned) { if (EggMovesLearned.Contains(m)) - res[m] = new CheckResult(Severity.Invalid, pkm.WasGiftEgg ? V377 : V341, CheckIdentifier.Move); + res[m] = new CheckMoveResult(res[m], Severity.Invalid, pkm.WasGiftEgg ? V377 : V341, CheckIdentifier.Move); else if (LvlupEggMovesLearned.Contains(m)) - res[m] = new CheckResult(Severity.Invalid, pkm.WasGiftEgg ? V378 : V347, CheckIdentifier.Move); + res[m] = new CheckMoveResult(res[m], Severity.Invalid, pkm.WasGiftEgg ? V378 : V347, CheckIdentifier.Move); } } } @@ -383,18 +397,23 @@ private static CheckResult[] parseMoves(PKM pkm, int[] moves, int[] special, int if (invalidCount == 2) // can't know both at the same time for (int i = 0; i < 4; i++) // flag both moves if (moves[i] == 250 || moves[i] == 432) - res[i] = new CheckResult(Severity.Invalid, V338, CheckIdentifier.Move); + res[i] = new CheckMoveResult(res[i], Severity.Invalid, V338, CheckIdentifier.Move); } for (int i = 0; i < HMLearned.Length; i++) if (res[i]?.Valid ?? false) - res[i] = new CheckResult(Severity.Invalid, string.Format(V339, gen, gen + 1), CheckIdentifier.Move); + res[i] = new CheckMoveResult(res[i], Severity.Invalid, string.Format(V339, gen, gen + 1), CheckIdentifier.Move); } // Mark the gen 1 exclusive moves as illegal because the pokemon also have Non tradeback egg moves. if (MixedGen1NonTradebackGen2) + { foreach (int m in Gen1MovesLearned) - res[m] = new CheckResult(Severity.Invalid, V335, CheckIdentifier.Move); + res[m] = new CheckMoveResult(res[m], Severity.Invalid, V335, CheckIdentifier.Move); + + foreach (int m in Gen2PreevoMovesLearned) + res[m] = new CheckMoveResult(res[m], Severity.Invalid, V412, CheckIdentifier.Move); + } if (gen == 1 && pkm.Format == 1 && pkm.Gen1_NotTradeback) { @@ -405,7 +424,7 @@ private static CheckResult[] parseMoves(PKM pkm, int[] moves, int[] special, int ParseEvolutionsIncompatibleMoves(pkm, moves, info.EncounterMoves.validTMHMMoves[1], ref res); } - if (Legal.EvolutionWithMove.Contains(pkm.Species)) + if (Legal.SpeciesEvolutionWithMove.Contains(pkm.Species)) { // Pokemon that evolved by leveling up while learning a specific move // This pokemon could only have 3 moves from preevolutions that are not the move used to evolved @@ -428,12 +447,12 @@ private static CheckResult[] parseMoves(PKM pkm, int[] moves, int[] special, int for (int m = 0; m < 4; m++) { if (res[m] == null) - res[m] = new CheckResult(Severity.Invalid, V176, CheckIdentifier.Move); + res[m] = new CheckMoveResult(MoveSource.Unknown, pkm.GenNumber, Severity.Invalid, V176, CheckIdentifier.Move); } return res; } - private static void ParseRedYellowIncompatibleMoves(PKM pkm, int[] moves, ref CheckResult[] res) + private static void ParseRedYellowIncompatibleMoves(PKM pkm, int[] moves, ref CheckMoveResult[] res) { var incompatible = new List(); if (pkm.Species == 134 && pkm.CurrentLevel < 47 && moves.Contains(151)) @@ -457,10 +476,10 @@ private static void ParseRedYellowIncompatibleMoves(PKM pkm, int[] moves, ref Ch for (int m = 0; m < 4; m++) { if (incompatible.Contains(moves[m])) - res[m] = new CheckResult(Severity.Invalid, V363, CheckIdentifier.Move); + res[m] = new CheckMoveResult(res[m], Severity.Invalid, V363, CheckIdentifier.Move); } } - private static void ParseEvolutionsIncompatibleMoves(PKM pkm, int[] moves, List tmhm, ref CheckResult[] res) + private static void ParseEvolutionsIncompatibleMoves(PKM pkm, int[] moves, List tmhm, ref CheckMoveResult[] res) { var species = specieslist; var currentspecies = species[pkm.Species]; @@ -508,12 +527,12 @@ private static void ParseEvolutionsIncompatibleMoves(PKM pkm, int[] moves, List< for (int m = 0; m < 4; m++) { if (incompatible_current.Contains(moves[m])) - res[m] = new CheckResult(Severity.Invalid, string.Format(V365, currentspecies, previousspecies), CheckIdentifier.Move); + res[m] = new CheckMoveResult(res[m], Severity.Invalid, string.Format(V365, currentspecies, previousspecies), CheckIdentifier.Move); if (incompatible_previous.Contains(moves[m])) - res[m] = new CheckResult(Severity.Invalid, string.Format(V366, currentspecies, previousspecies), CheckIdentifier.Move); + res[m] = new CheckMoveResult(res[m], Severity.Invalid, string.Format(V366, currentspecies, previousspecies), CheckIdentifier.Move); } } - private static void ParseShedinjaEvolveMoves(PKM pkm, int[] moves, ref CheckResult[] res) + private static void ParseShedinjaEvolveMoves(PKM pkm, int[] moves, ref CheckMoveResult[] res) { List[] ShedinjaEvoMoves = Legal.getShedinjaEvolveMoves(pkm); var ShedinjaEvoMovesLearned = new List(); @@ -528,7 +547,7 @@ private static void ParseShedinjaEvolveMoves(PKM pkm, int[] moves, ref CheckResu if (!ShedinjaEvoMoves[gen].Contains(moves[m])) continue; - res[m] = new CheckResult(Severity.Valid, native ? V355 : string.Format(V356, gen), CheckIdentifier.Move); + res[m] = new CheckMoveResult(MoveSource.ShedinjaEvo, gen, Severity.Valid, native ? V355 : string.Format(V356, gen), CheckIdentifier.Move); ShedinjaEvoMovesLearned.Add(m); } } @@ -537,9 +556,9 @@ private static void ParseShedinjaEvolveMoves(PKM pkm, int[] moves, ref CheckResu return; foreach (int m in ShedinjaEvoMovesLearned) - res[m] = new CheckResult(Severity.Invalid, V357, CheckIdentifier.Move); + res[m] = new CheckMoveResult(res[m], Severity.Invalid, V357, CheckIdentifier.Move); } - private static void ParseEvolutionLevelupMove(PKM pkm, int[] moves, List IncenseMovesLearned, ref CheckResult[] res, LegalInfo info) + private static void ParseEvolutionLevelupMove(PKM pkm, int[] moves, List IncenseMovesLearned, ref CheckMoveResult[] res, LegalInfo info) { // Ignore if there is an invalid move or an empty move, this validation is only for 4 non-empty moves that are all valid, but invalid as a 4 combination // Ignore Mr. Mime and Sudowodoo from generations 1 to 3, they cant be evolved from Bonsly or Munchlax @@ -549,11 +568,6 @@ private static void ParseEvolutionLevelupMove(PKM pkm, int[] moves, List In info.EncounterMatch.Species == pkm.Species) return; - // Mr.Mime and Sodowodoo from eggs that does not have any exclusive egg move or level up move from Mime Jr or Bonsly. - // The egg can be assumed to be a non-incense egg if the pokemon was not evolved by the player - if (info.EncounterMatch.EggEncounter && Legal.BabyEvolutionWithMove.Contains(pkm.Species) && !IncenseMovesLearned.Any()) - return; - var ValidMoves = Legal.getValidPostEvolutionMoves(pkm, pkm.Species, info.EvoChainsAllGens, GameVersion.Any); // Add the evolution moves to valid moves in case some of this moves could not be learned after evolving switch (pkm.Species) @@ -586,14 +600,14 @@ private static void ParseEvolutionLevelupMove(PKM pkm, int[] moves, List In return; for (int m = 0; m < 4; m++) - res[m] = new CheckResult(Severity.Invalid, string.Format(V385, specieslist[pkm.Species]), CheckIdentifier.Move); + res[m] = new CheckMoveResult(res[m], Severity.Invalid, string.Format(V385, specieslist[pkm.Species]), CheckIdentifier.Move); } /* Similar to verifyRelearnEgg but in pre relearn generation is the moves what should match the expected order but only if the pokemon is inside an egg */ - private static CheckResult[] verifyPreRelearnEggBase(PKM pkm, int[] Moves, List baseMoves, List eggmoves, List lvlmoves, List tmhmmoves, List tutormoves, List specialmoves, bool AllowInherited, GameVersion ver) + private static CheckMoveResult[] verifyPreRelearnEggBase(PKM pkm, int[] Moves, List baseMoves, List eggmoves, List lvlmoves, List tmhmmoves, List tutormoves, List specialmoves, bool AllowInherited, GameVersion ver) { - CheckResult[] res = new CheckResult[4]; - + CheckMoveResult[] res = new CheckMoveResult[4]; + var gen = pkm.GenNumber; // Obtain level1 moves int baseCt = baseMoves.Count; if (baseCt > 4) baseCt = 4; @@ -614,12 +628,12 @@ private static CheckResult[] verifyPreRelearnEggBase(PKM pkm, int[] Moves, List< for (int i = moveoffset; i < reqBase; i++) { if (baseMoves.Contains(Moves[i])) - res[i] = new CheckResult(Severity.Valid, V179, CheckIdentifier.Move); + res[i] = new CheckMoveResult(MoveSource.Initial, gen, Severity.Valid, V179, CheckIdentifier.Move); else { // mark remaining base egg moves missing for (int z = i; z < reqBase; z++) - res[z] = new CheckResult(Severity.Invalid, V180, CheckIdentifier.Move); + res[z] = new CheckMoveResult(MoveSource.Initial, gen, Severity.Invalid, V180, CheckIdentifier.Move); // provide the list of suggested base moves for the last required slot em = string.Join(", ", baseMoves.Select(m => m >= movelist.Length ? V190 : movelist[m])); @@ -633,12 +647,12 @@ private static CheckResult[] verifyPreRelearnEggBase(PKM pkm, int[] Moves, List< for (int i = moveoffset; i < moveoffset + specialmoves.Count; i++) { if (specialmoves.Contains(Moves[i])) - res[i] = new CheckResult(Severity.Valid, V333, CheckIdentifier.Move); + res[i] = new CheckMoveResult(MoveSource.SpecialEgg, gen, Severity.Valid, V333, CheckIdentifier.Move); else { // mark remaining special egg moves missing for (int z = i; z < moveoffset + specialmoves.Count; z++) - res[z] = new CheckResult(Severity.Invalid, V342, CheckIdentifier.Move); + res[z] = new CheckMoveResult(MoveSource.SpecialEgg, gen, Severity.Invalid, V342, CheckIdentifier.Move); // provide the list of suggested base moves and species moves for the last required slot if (!string.IsNullOrEmpty(em)) em += ", "; @@ -660,23 +674,23 @@ private static CheckResult[] verifyPreRelearnEggBase(PKM pkm, int[] Moves, List< for (int i = reqBase + specialmoves.Count; i < 4; i++) { if (Moves[i] == 0) // empty - res[i] = new CheckResult(Severity.Valid, V167, CheckIdentifier.Move); + res[i] = new CheckMoveResult(MoveSource.None, gen, Severity.Valid, V167, CheckIdentifier.Move); else if (eggmoves.Contains(Moves[i])) // inherited egg move - res[i] = new CheckResult(AllowInheritedSeverity, AllowInherited ? V344 : V341, CheckIdentifier.Move); + res[i] = new CheckMoveResult(MoveSource.EggMove, gen, AllowInheritedSeverity, AllowInherited ? V344 : V341, CheckIdentifier.Move); else if (lvlmoves.Contains(Moves[i])) // inherited lvl moves - res[i] = new CheckResult(AllowInheritedSeverity, AllowInherited ? V345 : V347, CheckIdentifier.Move); + res[i] = new CheckMoveResult(MoveSource.InheritLevelUp, gen, AllowInheritedSeverity, AllowInherited ? V345 : V347, CheckIdentifier.Move); else if (tmhmmoves.Contains(Moves[i])) // inherited TMHM moves - res[i] = new CheckResult(AllowInheritedSeverity, AllowInherited ? V349 : V350, CheckIdentifier.Move); + res[i] = new CheckMoveResult(MoveSource.TMHM, gen, AllowInheritedSeverity, AllowInherited ? V349 : V350, CheckIdentifier.Move); else if (tutormoves.Contains(Moves[i])) // inherited tutor moves - res[i] = new CheckResult(AllowInheritedSeverity, AllowInherited ? V346 : V348, CheckIdentifier.Move); + res[i] = new CheckMoveResult(MoveSource.Tutor, gen, AllowInheritedSeverity, AllowInherited ? V346 : V348, CheckIdentifier.Move); else // not inheritable, flag - res[i] = new CheckResult(Severity.Invalid, V340, CheckIdentifier.Move); + res[i] = new CheckMoveResult(MoveSource.Unknown, gen, Severity.Invalid, V340, CheckIdentifier.Move); } return res; } - private static void verifyNoEmptyDuplicates(int[] Moves, CheckResult[] res) + private static void verifyNoEmptyDuplicates(int[] Moves, CheckMoveResult[] res) { bool emptySlot = false; for (int i = 0; i < 4; i++) @@ -684,9 +698,9 @@ private static void verifyNoEmptyDuplicates(int[] Moves, CheckResult[] res) if (Moves[i] == 0) emptySlot = true; else if (emptySlot) - res[i] = new CheckResult(Severity.Invalid, V167, res[i].Identifier); + res[i] = new CheckMoveResult(res[i], Severity.Invalid, V167, res[i].Identifier); else if (Moves.Count(m => m == Moves[i]) > 1) - res[i] = new CheckResult(Severity.Invalid, V168, res[i].Identifier); + res[i] = new CheckMoveResult(res[i], Severity.Invalid, V168, res[i].Identifier); } } private static void UptateGen1LevelUpMoves(PKM pkm, ValidEncounterMoves EncounterMoves, int defaultLvlG1, int generation, LegalInfo info) diff --git a/PKHeX.Core/Legality/Encounters/VerifyEvolution.cs b/PKHeX.Core/Legality/Encounters/VerifyEvolution.cs index b8d6928a5..3c3875e80 100644 --- a/PKHeX.Core/Legality/Encounters/VerifyEvolution.cs +++ b/PKHeX.Core/Legality/Encounters/VerifyEvolution.cs @@ -1,24 +1,30 @@ using static PKHeX.Core.LegalityCheckStrings; - +using System.Linq; namespace PKHeX.Core { public static class VerifyEvolution { // Evolutions - public static CheckResult verifyEvolution(PKM pkm, IEncounterable EncounterMatch) + public static CheckResult verifyEvolution(PKM pkm, LegalInfo info) { - return isValidEvolution(pkm, EncounterMatch) + return isValidEvolution(pkm, info) ? new CheckResult(CheckIdentifier.Evolution) : new CheckResult(Severity.Invalid, V86, CheckIdentifier.Evolution); } - private static bool isValidEvolution(PKM pkm, IEncounterable EncounterMatch) + private static bool isValidEvolution(PKM pkm, LegalInfo info) { int species = pkm.Species; - if (EncounterMatch.Species == species) + if (info.EncounterMatch.Species == species) return true; - if (EncounterMatch.EggEncounter && species == 350) + if (info.EncounterMatch.EggEncounter && species == 350) return true; - return Legal.getEvolutionValid(pkm, EncounterMatch.Species); + if(!Legal.getEvolutionValid(pkm, info.EncounterMatch.Species)) + return false; + // If current species evolved with a move evolution and encounter species is not current species check if the evolution by move is valid + // Only the evolution by move is checked, if there is another evolution before the evolution by move is covered in getEvolutionValid + if (Legal.SpeciesEvolutionWithMove.Contains(pkm.Species)) + return Legal.getEvolutionWithMoveValid(pkm, info); + return true; } } } diff --git a/PKHeX.Core/Legality/Encounters/VerifyRelearnMoves.cs b/PKHeX.Core/Legality/Encounters/VerifyRelearnMoves.cs index 8515a7a87..8472d9b8f 100644 --- a/PKHeX.Core/Legality/Encounters/VerifyRelearnMoves.cs +++ b/PKHeX.Core/Legality/Encounters/VerifyRelearnMoves.cs @@ -49,7 +49,7 @@ private static CheckResult[] verifyRelearnDexNav(PKM pkm, LegalInfo info) int[] RelearnMoves = pkm.RelearnMoves; // DexNav Pokémon can have 1 random egg move as a relearn move. - res[0] = !Legal.getValidRelearn(pkm, Legal.getBaseEggSpecies(pkm)).Contains(RelearnMoves[0]) + res[0] = !Legal.getValidRelearn(pkm, Legal.getBaseEggSpecies(pkm),true).Contains(RelearnMoves[0]) ? new CheckResult(Severity.Invalid, V183, CheckIdentifier.RelearnMove) : new CheckResult(CheckIdentifier.RelearnMove); @@ -85,6 +85,9 @@ private static CheckResult[] verifyRelearnEggBase(PKM pkm, LegalInfo info, Encou int[] RelearnMoves = pkm.RelearnMoves; info.RelearnBase = new int[4]; CheckResult[] res = new CheckResult[4]; + // Level up moves could not be inherited if Ditto is parent, + // that means genderless species and male only species except Nidoran and Volbet (they breed with female nidoran and illumise) could not have level up moves as an egg + var inheritLvlMoves = pkm.PersonalInfo.Gender > 0 && pkm.PersonalInfo.Gender < 255 || Legal.MixedGenderBreeding.Contains(e.Species); // Obtain level1 moves List baseMoves = new List(Legal.getBaseEggMoves(pkm, e.Species, e.Game, 1)); @@ -92,7 +95,7 @@ private static CheckResult[] verifyRelearnEggBase(PKM pkm, LegalInfo info, Encou if (baseCt > 4) baseCt = 4; // Obtain Inherited moves - var inheritMoves = Legal.getValidRelearn(pkm, e.Species).ToList(); + var inheritMoves = Legal.getValidRelearn(pkm, e.Species, inheritLvlMoves).ToList(); var inherited = RelearnMoves.Where(m => m != 0 && (!baseMoves.Contains(m) || inheritMoves.Contains(m))).ToList(); int inheritCt = inherited.Count; @@ -129,7 +132,7 @@ private static CheckResult[] verifyRelearnEggBase(PKM pkm, LegalInfo info, Encou // If any splitbreed moves are invalid, flag accordingly var splitInvalid = false; - var splitMoves = e.SplitBreed ? Legal.getValidRelearn(pkm, Legal.getBaseEggSpecies(pkm)).ToList() : new List(); + var splitMoves = e.SplitBreed ? Legal.getValidRelearn(pkm, Legal.getBaseEggSpecies(pkm), inheritLvlMoves).ToList() : new List(); // Inherited moves appear after the required base moves. for (int i = reqBase; i < 4; i++) diff --git a/PKHeX.Core/Legality/LegalityCheckStrings.cs b/PKHeX.Core/Legality/LegalityCheckStrings.cs index b193a59c6..89c993d29 100644 --- a/PKHeX.Core/Legality/LegalityCheckStrings.cs +++ b/PKHeX.Core/Legality/LegalityCheckStrings.cs @@ -347,7 +347,7 @@ public static class LegalityCheckStrings public static string V328 {get; set;} = "Version Specific evolution requires a trade to opposite version. A Handling Trainer is required."; public static string V334 {get; set;} = "Non-tradeback egg move. Incompatible with generation 1 exclusive moves."; - public static string V335 {get; set;} = "Generation 1 exclusive move. Incompatible with Non-tradeback egg moves."; + public static string V335 {get; set;} = "Generation 1 exclusive move. Incompatible with Non-tradeback moves."; public static string V336 {get; set;} = "Egg Move. Incompatible with event egg moves."; public static string V337 {get; set;} = "Event Egg Move. Incompatible with normal egg moves."; public static string V338 {get; set;} = "Defog and whirpool. One of the two moves should have been removed before transfered to generation 5."; @@ -405,6 +405,7 @@ public static class LegalityCheckStrings public static string V409 {get; set;} = "Mystery Gift shiny mismatch."; public static string V410 {get; set;} = "Mystery Gift fixed PID mismatch."; public static string V411 {get; set;} = "Encounter Type PID mismatch."; + public static string V412 {get; set;} = "Non-tradeback pre evolution move. Incompatible with generation 1 exclusive moves."; #endregion } diff --git a/PKHeX.Core/Legality/Structures/CheckMoveResult.cs b/PKHeX.Core/Legality/Structures/CheckMoveResult.cs new file mode 100644 index 000000000..e3ce6ca86 --- /dev/null +++ b/PKHeX.Core/Legality/Structures/CheckMoveResult.cs @@ -0,0 +1,44 @@ +namespace PKHeX.Core +{ + public enum MoveSource + { + Unknown, + None, + Relearn, + Initial, + LevelUp, + TMHM, + Tutor, + EggMove, + InheritLevelUp, + Special, + SpecialEgg, + ShedinjaEvo, + Sketch, + } + + public class CheckMoveResult : CheckResult + { + public readonly MoveSource Source; + public readonly int Generation; + + internal CheckMoveResult(MoveSource m, int g, CheckIdentifier i) + : base(i) + { + Source = m; + Generation = g; + } + internal CheckMoveResult(MoveSource m, int g, Severity s, string c, CheckIdentifier i) + : base(s, c, i) + { + Source = m; + Generation = g; + } + internal CheckMoveResult(CheckMoveResult Org, Severity s, string c, CheckIdentifier i) + : base(s, c, i) + { + Source = Org?.Source ?? MoveSource.Unknown; + Generation = Org?.Generation ?? 0; + } + } +} diff --git a/PKHeX.Core/Legality/Tables.cs b/PKHeX.Core/Legality/Tables.cs index 10c547076..067539c18 100644 --- a/PKHeX.Core/Legality/Tables.cs +++ b/PKHeX.Core/Legality/Tables.cs @@ -224,8 +224,9 @@ public static partial class Legal 122, // Mr. Mime (Mime Jr with Mimic) 185, // Sudowoodo (Bonsly with Mimic) }; - - internal static readonly int[] EvolutionWithMove = + + // List of species that evolve from a previous species having a move while leveling up + internal static readonly int[] SpeciesEvolutionWithMove = { 122, // Mr. Mime (Mime Jr with Mimic) 185, // Sudowoodo (Bonsly with Mimic) @@ -237,6 +238,64 @@ public static partial class Legal 700, // Sylveon (Eevee with Fairy Move) 763, // Tsareena (Steenee with Stomp) }; + // Moves that trigger the evolution by move + internal static readonly int[][] MoveEvolutionWithMove = + { + new [] { 102 }, // Mr. Mime (Mime Jr with Mimic) + new [] { 102 }, // Sudowoodo (Bonsly with Mimic) + new [] { 458 }, // Ambipom (Aipom with Double Hit) + new [] { 205 }, // Lickilicky (Lickitung with Rollout) + new [] { 246 }, // Tangrowth (Tangela with Ancient Power) + new [] { 246 }, // Yanmega (Yamma with Ancient Power) + new [] { 246 }, // Mamoswine (Piloswine with Ancient Power) + FairyMoves, // Sylveon (Eevee with Fairy Move) + new [] { 023 }, // Tsareena (Steenee with Stomp) + }; + // Min level for any species for every generation to learn the move for evolution by move + // 0 means it cant be learned in that generation + internal static readonly int[][] MinLevelEvolutionWithMove = + { + // Mr. Mime (Mime Jr with Mimic) + new [] { 0, 0, 0, 0, 18, 15, 15, 15 }, + // Sudowoodo (Bonsly with Mimic) + new [] { 0, 0, 0, 0, 17, 17, 15, 15 }, + // Ambipom (Aipom with Double Hit) + new [] { 0, 0, 0, 0, 32, 32, 32, 32 }, + // Lickilicky (Lickitung with Rollout) + new [] { 0, 0, 1, 0, 1, 33, 33, 33 }, + // Tangrowth (Tangela with Ancient Power) + new [] { 0, 0, 0, 0, 1, 36, 38, 38 }, + // Yanmega (Yanma with Ancient Power) + new [] { 0, 0, 0, 0, 1, 33, 33, 33 }, + // Mamoswine (Piloswine with Ancient Power) + new [] { 0, 0, 0, 0, 1, 1, 1, 1 }, + // Sylveon (Eevee with Fairy Move) + new [] { 0, 0, 0, 0, 0, 29, 9, 9 }, + // Tsareena (Steenee with Stomp) + new [] { 0, 0, 0, 0, 0, 0, 0, 29 }, + }; + // True -> the pokemon could hatch from an egg with the move for evolution as an egg move + internal static readonly bool[][] EggMoveEvolutionWithMove = + { + // Mr. Mime (Mime Jr with Mimic) + new [] { false, false, false, false, true, true, true, true }, + // Sudowoodo (Bonsly with Mimic) + new [] { false, false, false, false, true, true, true, true }, + // Ambipom (Aipom with Double Hit) + new [] { false, false, false, false, true, true, true, true }, + // Lickilicky (Lickitung with Rollout) + new [] { false, false, true, false, true, true, true, true }, + // Tangrowth (Tangela with Ancient Power) + new [] { false, false, false, false, true, true, true, true }, + // Yanmega (Yanma with Ancient Power) + new [] { false, false, false, false, true, true, true, true }, + // Mamoswine (Piloswine with Ancient Power) + new [] { false, false, true, true, true, true, true, true }, + // Sylveon (Eevee with Fairy Move) + new [] { false, false, true, true, true, true, true, true }, + // Tsareena (Steenee with Stomp) + new [] { false, false, false, false, false, false, false, false }, + }; internal static readonly int[] FairyMoves = { @@ -264,7 +323,13 @@ public static partial class Legal 705, //Fleur Cannon 717, //Nature's Madness }; - + internal static readonly int[] MixedGenderBreeding = + { + 29, // Nidoran♀ + 32, // Nidoran♂ + 314, // Volbeat + 314, // Illumise + }; #region Games public static readonly int[] Games_7vc2 = { 39, 40, 41 }; // Gold, Silver, Crystal diff --git a/PKHeX.Core/Resources/text/en/LegalityCheckStrings_en.txt b/PKHeX.Core/Resources/text/en/LegalityCheckStrings_en.txt index 8836048dd..02464b502 100644 --- a/PKHeX.Core/Resources/text/en/LegalityCheckStrings_en.txt +++ b/PKHeX.Core/Resources/text/en/LegalityCheckStrings_en.txt @@ -284,7 +284,7 @@ V326 = Special ingame N's Sparkle flag missing. V327 = Special ingame N's Sparkle flag should not be checked. V328 = Version Specific evolution requires a trade to opposite version. A Handling Trainer is required. V334 = Non-tradeback egg move. Incompatible with generation 1 exclusive moves. -V335 = Generation 1 exclusive move. Incompatible with Non-tradeback egg moves. +V335 = Generation 1 exclusive move. Incompatible with Non-tradeback moves. V336 = Egg Move. Incompatible with event egg moves. V337 = Event Egg Move. Incompatible with normal egg moves. V338 = Defog and whirpool. One of the two moves should have been removed before transfered to generation 5. @@ -339,4 +339,7 @@ V405 = Outsider {0} should have evolved into {1}. V406 = Non Japanese Shadow E-reader Pokémon. Unreleased encounter. V407 = OT from Colosseum/XD cannot be female. V408 = Female OT from Generation 1/2 is invalid. -V409 = Mystery Gift shiny mismatch. \ No newline at end of file +V409 = Mystery Gift shiny mismatch. +V410 = Mystery Gift fixed PID mismatch. +V411 = Encounter Type PID mismatch. +V412 = Non-tradeback pre evolution move. Incompatible with generation 1 exclusive moves. \ No newline at end of file diff --git a/PKHeX.Core/Resources/text/ko/LegalityCheckStrings_ko.txt b/PKHeX.Core/Resources/text/ko/LegalityCheckStrings_ko.txt index 0e62d6ce4..d1f8f4d9c 100644 --- a/PKHeX.Core/Resources/text/ko/LegalityCheckStrings_ko.txt +++ b/PKHeX.Core/Resources/text/ko/LegalityCheckStrings_ko.txt @@ -340,4 +340,7 @@ V405 = Outsider {0} should have evolved into {1}. V406 = Non Japanese Shadow E-reader Pokémon. Unreleased encounter. V407 = OT from Colosseum/XD cannot be female. V408 = Female OT from Generation 1/2 is invalid. -V409 = Mystery Gift shiny mismatch. \ No newline at end of file +V409 = Mystery Gift shiny mismatch. +V410 = Mystery Gift fixed PID mismatch. +V411 = Encounter Type PID mismatch. +V412 = Non-tradeback pre evolution move. Incompatible with generation 1 exclusive moves. \ No newline at end of file diff --git a/PKHeX.Core/Resources/text/zh/LegalityCheckStrings_zh.txt b/PKHeX.Core/Resources/text/zh/LegalityCheckStrings_zh.txt index 2b826308f..65ba5d962 100644 --- a/PKHeX.Core/Resources/text/zh/LegalityCheckStrings_zh.txt +++ b/PKHeX.Core/Resources/text/zh/LegalityCheckStrings_zh.txt @@ -339,4 +339,7 @@ V405 = 外来的{0}应当进化为{1}. V406 = 非日版黑暗E-reader宝可梦。未解禁遇见方式。 V407 = OT from Colosseum/XD cannot be female. V408 = Female OT from Generation 1/2 is invalid. -V409 = Mystery Gift shiny mismatch. \ No newline at end of file +V409 = Mystery Gift shiny mismatch. +V410 = Mystery Gift fixed PID mismatch. +V411 = Encounter Type PID mismatch. +V412 = Non-tradeback pre evolution move. Incompatible with generation 1 exclusive moves. \ No newline at end of file