diff --git a/PKHeX.Core/Legality/Restrictions/EvolutionRestrictions.cs b/PKHeX.Core/Legality/Restrictions/EvolutionRestrictions.cs index 636e7c14f..ebe6cf1d6 100644 --- a/PKHeX.Core/Legality/Restrictions/EvolutionRestrictions.cs +++ b/PKHeX.Core/Legality/Restrictions/EvolutionRestrictions.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using static PKHeX.Core.Move; using static PKHeX.Core.Species; @@ -16,120 +15,32 @@ internal static class EvolutionRestrictions /// /// List of species that evolve from a previous species having a move while leveling up /// - private static readonly Dictionary SpeciesEvolutionWithMove = new() + private static ushort GetSpeciesEvolutionMove(ushort species) => species switch { - {(int)Eevee, new()}, // FairyMoves - {(int)MimeJr, new(01, (int)Mimic)}, - {(int)Bonsly, new(02, (int)Mimic)}, - {(int)Aipom, new(03, (int)Mimic)}, - {(int)Lickitung, new(04, (int)Rollout)}, - {(int)Tangela, new(05, (int)AncientPower)}, - {(int)Yanma, new(06, (int)AncientPower)}, - {(int)Piloswine, new(07, (int)AncientPower)}, - {(int)Steenee, new(08, (int)Stomp)}, - {(int)Clobbopus, new(09, (int)Taunt)}, - {(int)Stantler, new(10, (int)PsyshieldBash)}, - {(int)Qwilfish, new(11, (int)BarbBarrage)}, - {(int)Primeape, new(12, (int)RageFist)}, - {(int)Girafarig, new(13, (int)TwinBeam)}, - {(int)Dunsparce, new(14, (int)HyperDrill)}, + (int)Eevee => 0, + (int)MimeJr => (int)Mimic, + (int)Bonsly => (int)Mimic, + (int)Aipom => (int)Mimic, + (int)Lickitung => (int)Rollout, + (int)Tangela => (int)AncientPower, + (int)Yanma => (int)AncientPower, + (int)Piloswine => (int)AncientPower, + (int)Steenee => (int)Stomp, + (int)Clobbopus => (int)Taunt, + (int)Stantler => (int)PsyshieldBash, + (int)Qwilfish => (int)BarbBarrage, + (int)Primeape => (int)RageFist, + (int)Girafarig => (int)TwinBeam, + (int)Dunsparce => (int)HyperDrill, + _ => NONE, }; - private readonly record struct MoveEvolution(byte ReferenceIndex, ushort Move); + private const ushort NONE = ushort.MaxValue; - private static ReadOnlySpan FairyMoves => new ushort[] + private static ReadOnlySpan EeveeFairyMoves => new ushort[] { - (int)SweetKiss, (int)Charm, - (int)Moonlight, - (int)DisarmingVoice, - (int)DrainingKiss, - (int)CraftyShield, - (int)FlowerShield, - (int)MistyTerrain, - (int)PlayRough, - (int)FairyWind, - (int)Moonblast, - (int)FairyLock, - (int)AromaticMist, - (int)Geomancy, - (int)DazzlingGleam, (int)BabyDollEyes, - (int)LightofRuin, - (int)TwinkleTackleP, - (int)TwinkleTackleS, - (int)FloralHealing, - (int)GuardianofAlola, - (int)FleurCannon, - (int)NaturesMadness, - (int)LetsSnuggleForever, - (int)SparklySwirl, - (int)MaxStarfall, - (int)Decorate, - (int)SpiritBreak, - (int)StrangeSteam, - (int)MistyExplosion, - (int)SpringtideStorm, - }; - - /// - /// Minimum current level for a given species to have learned the evolve-move and be successfully evolved. - /// - /// Having a value of 0 means the move can't be learned. - private static ReadOnlySpan MinLevelEvolutionWithMove => new byte[] - { - 00, 00, 00, 00, 00, 29, 09, 02, 02, 02, // Sylveon (Eevee with Fairy Move) - 00, 00, 00, 00, 18, 15, 15, 02, 32, 00, // Mr. Mime (Mime Jr with Mimic) - 00, 00, 00, 00, 17, 17, 15, 02, 16, 16, // Sudowoodo (Bonsly with Mimic) - 00, 00, 00, 00, 32, 32, 32, 02, 32, 00, // Ambipom (Aipom with Double Hit) - 00, 00, 02, 00, 02, 33, 33, 02, 06, 00, // Lickilicky (Lickitung with Rollout) - 00, 00, 00, 00, 02, 36, 38, 02, 24, 00, // Tangrowth (Tangela with Ancient Power) - 00, 00, 00, 00, 02, 33, 33, 02, 33, 00, // Yanmega (Yanma with Ancient Power) - 00, 00, 00, 00, 02, 02, 02, 02, 02, 00, // Mamoswine (Piloswine with Ancient Power) - 00, 00, 00, 00, 00, 00, 00, 02, 28, 28, // Tsareena (Steenee with Stomp) - 00, 00, 00, 00, 00, 00, 00, 00, 35, 00, // Grapploct (Clobbopus with Taunt) - 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, // Wyrdeer (Stantler with AGILE Psyshield Bash) - 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, // Overqwil (Qwilfish-1 with STRONG Barb Barrage) - 00, 00, 00, 00, 00, 00, 00, 00, 00, 35, // Annihilape (Primeape with Rage Fist) - 00, 00, 00, 00, 00, 00, 00, 00, 00, 32, // Farigiraf (Girafarig with Twin Beam) - 00, 00, 00, 00, 00, 00, 00, 00, 00, 32, // Dudunsparce (Dunsparce with Hyper Drill) - }; - - private const int MinLevelEvoWidth = 10; - - private static ReadOnlySpan MinLevelEvolutionWithMove_8LA => new byte[] - { - 00, // Sylveon (Eevee with Fairy Move) - 25, // Mr. Mime (Mime Jr with Mimic) - 29, // Sudowoodo (Bonsly with Mimic) - 25, // Ambipom (Aipom with Double Hit) - 34, // Lickilicky (Lickitung with Rollout) - 34, // Tangrowth (Tangela with Ancient Power) - 34, // Yanmega (Yanma with Ancient Power) - 34, // Mamoswine (Piloswine with Ancient Power) - 99, // Tsareena (Steenee with Stomp) - 99, // Grapploct (Clobbopus with Taunt) - 31, // Wyrdeer (Stantler with AGILE Psyshield Bash) - 25, // Overqwil (Qwilfish-1 with STRONG Barb Barrage) - }; - - private static ReadOnlySpan CanEggHatchWithEvolveMove => new byte[] - { - 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, // Sylveon (Eevee with Fairy Move) - 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, // Mr. Mime (Mime Jr with Mimic) - 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, // Sudowoodo (Bonsly with Mimic) - 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, // Ambipom (Aipom with Double Hit) - 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, // Lickilicky (Lickitung with Rollout) - 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, // Tangrowth (Tangela with Ancient Power) - 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, // Yanmega (Yanma with Ancient Power) - 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, // Mamoswine (Piloswine with Ancient Power) - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Tsareena (Steenee with Stomp) - 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, // Grapploct (Clobbopus with Taunt) - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Wyrdeer (Stantler with AGILE Psyshield Bash) - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Overqwil (Qwilfish-1 with STRONG Barb Barrage) - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Annihilape (Primeape with Rage Fist) - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // Farigiraf (Girafarig with Twin Beam) - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // Dudunsparce (Dunsparce with Hyper Drill) }; /// @@ -143,132 +54,57 @@ public static bool IsValidEvolutionWithMove(PKM pk, LegalInfo info) return true; // OK if un-evolved from original encounter + var enc = info.EncounterOriginal; ushort species = pk.Species; - if (info.EncounterMatch.Species == species) + if (enc.Species == species) return true; // Exclude evolution paths that did not require a move w/level-up evolution - var enc = info.EncounterOriginal; - if (!SpeciesEvolutionWithMove.TryGetValue(enc.Species, out var entry)) - return true; - - var move = entry.Move; + var move = GetSpeciesEvolutionMove(enc.Species); + if (move is NONE) + return true; // not a move evolution if (move == 0) - { - // Other evolutions are fine. - if (pk.Species != (int)Sylveon) - return true; - } + return species != (int)Sylveon || IsValidEvolutionWithMoveSylveon(pk, enc, info); + if (!IsMoveSlotAvailable(info.Moves)) + return false; - // Check if the move was already known when it was originally encountered. - var gen = info.Generation; - var index = entry.ReferenceIndex; - if (enc is EncounterEgg) - { - var slice = CanEggHatchWithEvolveMove.Slice(index * MinLevelEvoWidth, MinLevelEvoWidth); - if (slice[gen] != 0) // true - { - var result = move == 0 ? IsMoveInherited(pk, info, FairyMoves) : IsMoveInherited(pk, info, move); - if (result) - return true; - } - } - else if (enc is IMoveset s) - { - var moves = s.Moves; - var result = move == 0 ? moves.ContainsAny(FairyMoves) : moves.Contains(move); - if (result) - return true; - } - - // Current level must be at least the minimum post-evolution level. - var lvl = GetMinLevelKnowRequiredMove(pk, gen, index, info.EvoChainsAllGens); - return pk.CurrentLevel >= lvl; - } - - private static int GetMinLevelKnowRequiredMove(PKM pk, int gen, int index, EvolutionHistory evos) - { - var lvl = GetLevelLearnMove(pk, gen, index); - - // If has original met location the minimum evolution level is one level after met level - // Gen 3 pokemon in gen 4 games: minimum level is one level after transfer to generation 4 - // VC pokemon: minimum level is one level after transfer to generation 7 - // Sylveon: always one level after met level, for gen 4 and 5 Eevee in gen 6 games minimum for evolution is one level after transfer to generation 5 - if (pk.HasOriginalMetLocation || (pk.Format == 4 && gen == 3) || pk.VC || pk.Species == (int)Sylveon) - lvl = Math.Max(pk.Met_Level + 1, lvl); - - if (evos.HasVisitedPLA) // No Level Up required, and different levels than mainline SW/SH. - { - var la = MinLevelEvolutionWithMove_8LA[index]; - if (la <= lvl) - return la; - } - - return lvl; - } - - private static int GetLevelLearnMove(PKM pk, int gen, int index) - { - // Get the minimum level in any generation when the pokemon could learn the evolve move - var levels = MinLevelEvolutionWithMove.Slice(index * MinLevelEvoWidth, MinLevelEvoWidth); - var lvl = 101; - var end = pk.Format; - for (int g = gen; g <= end; g++) - { - var l = levels[g]; - if (l == 0) - continue; - if (l == 2) - return 2; // true minimum - if (l < lvl) - lvl = l; // best minimum - } - return lvl; - } - - private static bool IsMoveInherited(PKM pk, LegalInfo info, ushort move) - { - // In 3DS games, the inherited move must be in the relearn moves. - if (info.Generation >= 6 && !pk.IsOriginalMovesetDeleted()) - return pk.HasRelearnMove(move); - - // In Pre-3DS games, the move is inherited if it has the move and it can be hatched with the move. if (pk.HasMove(move)) return true; - return DidLearnAndForget(info); + var head = LearnGroupUtil.GetCurrentGroup(pk); + return MemoryPermissions.GetCanKnowMove(enc, move, info.EvoChainsAllGens, pk, head); } - private static bool IsMoveInherited(PKM pk, LegalInfo info, ReadOnlySpan moves) + private static bool IsValidEvolutionWithMoveSylveon(PKM pk, IEncounterTemplate enc, LegalInfo info) { - // In 3DS games, the inherited move must be in the relearn moves. - if (info.Generation >= 6) + if (!IsMoveSlotAvailable(info.Moves)) + return false; + + foreach (var move in EeveeFairyMoves) { - Span relearn = stackalloc ushort[4]; - pk.GetRelearnMoves(relearn); - return relearn.IndexOfAny(moves) != -1; + if (pk.HasMove(move)) + return true; } - // In Pre-3DS games, the move is inherited if it has the move and it can be hatched with the move. - Span pkMoves = stackalloc ushort[4]; - pk.GetMoves(pkMoves); - var index = pkMoves.IndexOfAny(moves); - if (index != -1) - return true; - - return DidLearnAndForget(info); + var head = LearnGroupUtil.GetCurrentGroup(pk); + foreach (var move in EeveeFairyMoves) + { + if (MemoryPermissions.GetCanKnowMove(enc, move, info.EvoChainsAllGens, pk, head)) + return true; + } + return false; } - private static bool DidLearnAndForget(LegalInfo info) + private static bool IsMoveSlotAvailable(ReadOnlySpan moves) { // If the pokemon does not currently have the move, it could have been an egg move that was forgotten. // This requires the pokemon to not have 4 other moves identified as egg moves or inherited level up moves. // If any move is not an egg source, then a slot could have been forgotten. - foreach (var move in info.Moves) + foreach (var move in moves) { if (!move.Info.Method.IsEggSource()) - return false; + return true; } - return true; + return false; } } diff --git a/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs b/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs index 876fd1474..64a577c11 100644 --- a/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs +++ b/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs @@ -159,6 +159,11 @@ private static bool GetCanKnowMove(PKM pk, ushort move, EntityContext context, E else return false; + return GetCanKnowMove(enc, move, history, pk, game); + } + + public static bool GetCanKnowMove(IEncounterTemplate enc, ushort move, EvolutionHistory history, PKM pk, ILearnGroup game) + { Span result = stackalloc MoveResult[1]; Span moves = stackalloc ushort[] { move }; LearnVerifierHistory.MarkAndIterate(result, moves, enc, pk, history, game, MoveSourceType.All, LearnOption.AtAnyTime);