diff --git a/PKHeX.WinForms/MainWindow/Main.cs b/PKHeX.WinForms/MainWindow/Main.cs index c5159bc96..11edacb68 100644 --- a/PKHeX.WinForms/MainWindow/Main.cs +++ b/PKHeX.WinForms/MainWindow/Main.cs @@ -1498,6 +1498,7 @@ private void InitializeStrings() // Update Legality Analysis strings LegalityAnalysis.movelist = GameInfo.Strings.movelist; + LegalityAnalysis.specieslist = GameInfo.Strings.specieslist; if (fieldsInitialized) updateIVs(null, null); // Prompt an update for the characteristics diff --git a/PKHeX/Legality/Checks.cs b/PKHeX/Legality/Checks.cs index 26ae963a8..718f06702 100644 --- a/PKHeX/Legality/Checks.cs +++ b/PKHeX/Legality/Checks.cs @@ -2413,7 +2413,7 @@ private CheckResult[] parseMovesForEncounters(GameVersion game, List[] vali { EncounterMatch = enc; var EncounterMatchGen = EncounterMatch as IGeneration; - if(EncounterMatchGen != null) + if (EncounterMatchGen != null) UptateGen1LevelUpMoves(ref validLevelMoves, defaultGen1Moves, EncounterMatchGen.Generation); if (pkm.GenNumber <= 3) pkm.WasEgg = EncounterMatch == null || ((EncounterMatch as IEncounterable)?.EggEncounter ?? false); @@ -2686,12 +2686,12 @@ private CheckResult[] parseMoves(int[] moves, List[] learn, int[] relearn, var LvlupEggMovesLearned = new List(); var EventEggMovesLearned = new List(); var IsGen2Pkm = pkm.Format == 2 || pkm.VC2; - var usedslots = Legal.getUsedMoveSlots(pkm, moves, learn, tmhm, tutor, initialmoves); + var required = Legal.getRequiredMoveCount(pkm, moves, learn, tmhm, tutor, initialmoves); // Check none moves and relearn moves before generation moves for (int m = 0; m < 4; m++) { if (moves[m] == 0) - res[m] = new CheckResult(m < usedslots ? Severity.Invalid : Severity.Valid, V167, CheckIdentifier.Move); + res[m] = new CheckResult(m < required ? Severity.Invalid : Severity.Valid, V167, CheckIdentifier.Move); else if (relearn.Contains(moves[m])) res[m] = new CheckResult(Severity.Valid, V172, CheckIdentifier.Move) { Flag = true }; } @@ -2728,7 +2728,7 @@ private CheckResult[] parseMoves(int[] moves, List[] learn, int[] relearn, if (moves[m] == 0) continue; - if (gen ==1 && initialmoves.Contains(moves[m])) + if (gen == 1 && initialmoves.Contains(moves[m])) res[m] = new CheckResult(Severity.Valid, native ? V361 : string.Format(V362, gen), CheckIdentifier.Move); else if (learn[gen].Contains(moves[m])) res[m] = new CheckResult(Severity.Valid, native ? V177 : string.Format(V330, gen), CheckIdentifier.Move); @@ -2883,7 +2883,7 @@ private CheckResult[] parseMoves(int[] moves, List[] learn, int[] relearn, private void ParseRedYellowIncompatibleMoves(int[] moves, ref CheckResult[] res) { var incompatible = new List(); - if(pkm.Species == 134 && moves.Contains(151) && pkm.CurrentLevel < 47) + if (pkm.Species == 134 && moves.Contains(151) && pkm.CurrentLevel < 47) { // Vaporeon in Yellow learn Mist and Acid Armor at level 42, Mist only if level up in day-care // Vaporeon in Red Blue learn Acid Armor at level 42 and level 47 in Yellow @@ -2891,7 +2891,7 @@ private void ParseRedYellowIncompatibleMoves(int[] moves, ref CheckResult[] res) incompatible.Add(54); if (moves.Contains(114)) incompatible.Add(114); - if(incompatible.Any()) + if (incompatible.Any()) incompatible.Add(151); } if (pkm.Species == 136 && moves.Contains(43) && moves.Contains(123) && pkm.CurrentLevel < 47) @@ -2901,7 +2901,7 @@ private void ParseRedYellowIncompatibleMoves(int[] moves, ref CheckResult[] res) incompatible.Add(43); incompatible.Add(123); } - for(int m = 0; m < 4; m++) + for (int m = 0; m < 4; m++) { if (incompatible.Contains(moves[m])) res[m] = new CheckResult(Severity.Invalid, V363, CheckIdentifier.Move); @@ -2909,7 +2909,7 @@ private void ParseRedYellowIncompatibleMoves(int[] moves, ref CheckResult[] res) } private void ParseEvolutionsIncompatibleMoves(int[] moves, List tmhm, ref CheckResult[] res) { - var species = Util.getSpeciesList("en"); + var species = specieslist; var currentspecies = species[pkm.Species]; var previousspecies = string.Empty; var incompatible_previous = new List(); @@ -2950,8 +2950,8 @@ private void ParseEvolutionsIncompatibleMoves(int[] moves, List tmhm, ref C if (EeveeLevels.Any(ev => ev > EvoLevels[i])) incompatible_current.Add(ExclusiveMoves[1][i]); } - // Vaporeon Mist and Eevee Take Down, special case, they are learned at the same level, but mist only in daycare - if (pkm.Species ==134 && moves.Contains(36) && moves.Contains(54)) + // Vaporeon Mist and Eevee Take Down, special case, they are learned at the same level, but Mist only in daycare + if (pkm.Species == 134 && moves.Contains(36) && moves.Contains(54)) { incompatible_current.Add(54); incompatible_previous.Add(36); @@ -2961,7 +2961,7 @@ private void ParseEvolutionsIncompatibleMoves(int[] moves, List tmhm, ref C 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 CheckResult(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); } @@ -2996,7 +2996,6 @@ private void ParseShedinjaEvolveMoves(int[] moves, ref CheckResult[] res) private void verifyPreRelearn() { // For origins prior to relearn moves, need to try to match a mystery gift if applicable. - if (pkm.WasEvent || pkm.WasEventEgg) { EventGiftMatch = new List(Legal.getValidGifts(pkm)); @@ -3259,19 +3258,19 @@ private CheckResult[] verifyPreRelearnEggBase(int[] Moves, List baseMoves, for (int z = i; z < moveoffset + specialmoves.Count; z++) res[z] = new CheckResult(Severity.Invalid, V342, CheckIdentifier.Move); - // provide the list of suggested base moves and specia moves for the last required slot - if (!string.IsNullOrEmpty(em)) em += ","; + // provide the list of suggested base moves and species moves for the last required slot + if (!string.IsNullOrEmpty(em)) em += ", "; else - em = string.Join(", ", baseMoves.Select(m => m >= movelist.Length ? V190 : movelist[m])) + ","; + em = string.Join(", ", baseMoves.Select(m => m >= movelist.Length ? V190 : movelist[m])) + ", "; em += string.Join(", ", specialmoves.Select(m => m >= movelist.Length ? V190 : movelist[m])); break; } } - if(!string.IsNullOrEmpty(em)) + if (!string.IsNullOrEmpty(em)) res[reqBase > 0 ? reqBase - 1 : 0].Comment = string.Format(Environment.NewLine + V343, em); // Non-Base moves that can magically appear in the regular movepool - if (pkm.GenNumber >=3 && Legal.LightBall.Contains(pkm.Species)) + if (pkm.GenNumber >= 3 && Legal.LightBall.Contains(pkm.Species)) eggmoves.Add(344); // Inherited moves appear after the required base moves. @@ -3310,6 +3309,7 @@ private void verifyNoEmptyDuplicates(int[] Moves, CheckResult[] res) } #endregion public static string[] movelist = Util.getMovesList("en"); + public static string[] specieslist = Util.getMovesList("en"); private static readonly string[] EventRibName = { "Country", "National", "Earth", "World", "Classic", diff --git a/PKHeX/Legality/Core.cs b/PKHeX/Legality/Core.cs index ec7143163..b5a5836d1 100644 --- a/PKHeX/Legality/Core.cs +++ b/PKHeX/Legality/Core.cs @@ -1319,7 +1319,7 @@ internal static int getMoveMinLevelGBEncounter(int species, int lvl, GameVersion case GameVersion.RBY: { movelvl = LevelUpRB[species].getMinMoveLevel(lvl); - if(ver.Contains(GameVersion.YW)) + if (ver.Contains(GameVersion.YW)) goto case GameVersion.YW; return movelvl; } @@ -1362,130 +1362,149 @@ private static EncounterTrade[] getEncounterTradeTable(PKM pkm) } return null; } - internal static int getUsedMoveSlots(PKM pk, int[] moves, List[] learn, List[] tmhm, List[] tutor, int[] initialmoves) + internal static int getRequiredMoveCount(PKM pk, int[] moves, List[] learn, List[] tmhm, List[] tutor, int[] initialmoves) { - if (pk.Format == 1 && !AllowGBCartEra) // No MoveDeleter - { - // A pokemon is captured with initial moves and cant forget any until have all 4 slots used - // If it has learn a move before having 4 it will be in one of the free slots - var usedslots = initialmoves.Union(learn[1]).Where(m => m != 0).Distinct().Count(); - // Caterpie and Metapod evolution lines have different count of possible slots available if captured in different evolutionary phases - // Example: a level 7 caterpie evolved into metapod will have 3 learned moves, a captured metapod will have only 1 move - if (10 <= pk.Species && pk.Species <= 11) - { - if (pk.Species == 11 && !moves.Any(m => G1MetapodMoves.Contains(m))) - // Captured as Metapod without Caterpie moves - usedslots = initialmoves.Union(learn[1]).Distinct().Count(lm => lm != 0 && !G1MetapodMoves.Contains(lm)); - } - if (14 <= pk.Species && pk.Species <= 15) - { - if (pk.Species == 15 && !moves.Any(m => G1KakunaMoves.Contains(m))) - // Captured as Beedril without Weedle and Kakuna moves - usedslots = initialmoves.Union(learn[1]).Distinct().Count(lm => lm != 0 && !G1KakunaMoves.Contains(lm)); - else if (!moves.Any(m => G1WeedleMoves.Contains(m))) - // Captured as Kakuna without Weedle moves - usedslots = initialmoves.Union(learn[1]).Distinct().Count(lm => lm != 0 && !G1WeedleMoves.Contains(lm)); - } - // Species that evolve and learn the 4th move as evolved species at a greather level than base species - // The 4th move is included in the level up table set as a preevolution move, - // it should be removed from the used slots count if is not the learn move - if ( (pk.Species == 017 && pk.CurrentLevel < 21 && !moves.Contains(18)) || // Pidgeotto without Whirlwind - (pk.Species == 028 && pk.CurrentLevel < 27 && !moves.Contains(40)) || // Sandslash without Poison Sting - (pk.Species == 047 && pk.CurrentLevel < 30 && !moves.Contains(147)) || // Parasect without Spore - (pk.Species == 055 && pk.CurrentLevel < 39 && !moves.Contains(93)) || // Golduck without Confusion - (pk.Species == 087 && pk.CurrentLevel < 44 && !moves.Contains(156)) || // Dewgong without Rest - (93 <= pk.Species && pk.Species <= 94 && pk.CurrentLevel < 29 && !moves.Contains(95)) || // Haunter/Gengar without Hypnosis - (pk.Species == 110 && pk.CurrentLevel < 39 && !moves.Contains(108)) || // Weezing without Smoke Screen - (pk.Species == 130 && pk.CurrentLevel < 32 ) // Wild Gyarados from yellow do not learn splash, evolved gyarados do not learn tackle - ) - { - usedslots = 3; - } + if (pk.Format != 1 || AllowGBCartEra) // No MoveDeleter + return 1; // Move deleter exits, slots from 2 onwards can allways be empty - // Yellow optional moves, reduce usedslots if the yellow move is not present - // The count wont go bellow 1 because the yellow moves were already counted and are not the only initial or level up moves - if (pk.Species == 031) //Venonat - { - //ignore Venomoth, by the time Venonat evolved it will always have 4 moves - if (pk.CurrentLevel >= 11 && !moves.Contains(48)) // Supersonic - usedslots--; - if (pk.CurrentLevel >= 19 && !moves.Contains(93)) // Confusion - usedslots--; - } - if (pk.Species == 056 && pk.CurrentLevel >= 9 && !moves.Contains(67)) - //Mankey Yellow Low Kick, Primeape will always have 4 moves - usedslots--; + int required = getRequiredMoveCount(pk, moves, learn, initialmoves); + if (required >= 4) + return 4; - if(64 <= pk.Species && pk.Species <= 065) - { - if(!moves.Contains(134))// Initial Yellow Kadabra Kinesis - usedslots--; - if (pk.CurrentLevel <10 && !moves.Contains(50))// Kadabra Disable, not learned until 20 if captured as Abra - usedslots--; - } - if (104 <= pk.Species && pk.Species <= 105) //Cubone and Marowak - { - if (!moves.Contains(39))// Initial Yellow Tail Whip - usedslots--; - if (!moves.Contains(125))// Initial Yellow Bone Club - usedslots--; - if (pk.Species == 105 && pk.CurrentLevel < 33 && !moves.Contains(116)) // Marowak evolved without Focus Energy - usedslots--; - } - if (pk.Species == 113) // Chansey - { - if (!moves.Contains(39))// Yellow Initial Tail Whip - usedslots--; - if (!moves.Contains(3))// Yellow Lvl 12 and Initial Red/Blue Double Slap - usedslots--; - } - if (pk.Species == 127 && pk.CurrentLevel >=21 && !moves.Contains(20)) // Pinsir Yellow Bind - usedslots--; + // tm, hm and tutor moves replace a free slots if the pokemon have less than 4 moves + // Ignore tm, hm and tutor moves already in the learnset table + required += moves.Where(m => m != 0 && initialmoves.Union(learn[1]).All(l => l != m) && (tmhm[1].Any(t => t == m) || tutor[1].Any(t => t == m))).Count(); - if (SpecialMinMoveSlots.Contains(pk.Species)) - { - // Species with few mandatory slots, species with stone evolutions that could evolve at lower level and do not learn any more moves - // and Pikachu and Nidoran family, those only have mandatory the initial moves and a few have one level up moves, - // every other move could be avoided switching game or evolving - var basespecies = getBaseSpecies(pk); - var maxlevel = 1; - var minlevel = 1; - if (29 <= pk.Species && pk.Species <= 34 && pk.CurrentLevel >= 8) - maxlevel = 8; // Allways lean a third move at level 8 - if(pk.Species == 114) - { - //Tangela moves before level 32 are different in red/blue and yellow - minlevel = 32; - maxlevel = pk.CurrentLevel; - } - var mandatorymoves = minlevel <= pk.CurrentLevel ? getLvlMoves(basespecies, 0, 1, minlevel, maxlevel).Where(m => m != 0).Distinct().ToList(): new List(); - if (pk.Species == 103 && pk.CurrentLevel >= 28) // Exeggutor - { - // At level 28 learn different move if is a Exeggute or Exeggutor - if (moves.Contains(73)) - mandatorymoves.Add(73);// Leech Seed level 28 Exeggute - if (moves.Contains(23)) - mandatorymoves.Add(23);// Stomp level 28 Exeggutor - } - if (pk.Species == 25 && pk.CurrentLevel >= 33) - mandatorymoves.Add(97); // Pikachu always learn Agility - if (pk.Species == 114) - mandatorymoves.Add(132); // Tangela always learn Constrict as Initial Move - // Add to used slots the non-mandatory moves from the learnset table that the pokemon have learned - usedslots = mandatorymoves.Count + moves.Where(m => m != 0 && mandatorymoves.All(l => l != m) && learn[1].Any(t => t == m)).Count(); - } - - if (usedslots >= 4) - return 4; - // tm, hm and tutor moves replace a free slots if the pokemon have less than 4 moves - // Ignore tm, hm and tutor moves already in the learnset table - usedslots += moves.Where(m => m != 0 && initialmoves.Union(learn[1]).All(l => l != m) && (tmhm[1].Any(t => t == m) || tutor[1].Any(t => t == m))).Count(); - - return usedslots >= 4 ? 4 : usedslots; - } - // Move deleter exits, slots from 2 onwards can allways be empty - return 1; + return Math.Min(4, required); } + private static int getRequiredMoveCount(PKM pk, int[] moves, List[] learn, int[] initialmoves) + { + if (SpecialMinMoveSlots.Contains(pk.Species)) + return getRequiredMoveCountSpecial(pk, moves, learn); + + // A pokemon is captured with initial moves and can't forget any until have all 4 slots used + // If it has learn a move before having 4 it will be in one of the free slots + int required = getRequiredMoveSlotsRegular(pk, moves, learn, initialmoves); + return required != 0 ? required : getRequiredMoveCountDecrement(pk, moves, learn, initialmoves); + } + private static int getRequiredMoveSlotsRegular(PKM pk, int[] moves, List[] learn, int[] initialmoves) + { + int species = pk.Species; + // Caterpie and Metapod evolution lines have different count of possible slots available if captured in different evolutionary phases + // Example: a level 7 caterpie evolved into metapod will have 3 learned moves, a captured metapod will have only 1 move + if (010 == species || species == 011) + { + if (species == 11 && !moves.Any(m => G1MetapodMoves.Contains(m))) // Captured as Metapod without Caterpie moves + return initialmoves.Union(learn[1]).Distinct().Count(lm => lm != 0 && !G1MetapodMoves.Contains(lm)); + } + if (species == 014 || species == 015) + { + if (species == 15 && !moves.Any(m => G1KakunaMoves.Contains(m))) // Captured as Beedril without Weedle and Kakuna moves + return initialmoves.Union(learn[1]).Distinct().Count(lm => lm != 0 && !G1KakunaMoves.Contains(lm)); + + if (!moves.Any(m => G1WeedleMoves.Contains(m))) // Captured as Kakuna without Weedle moves + return initialmoves.Union(learn[1]).Distinct().Count(lm => lm != 0 && !G1WeedleMoves.Contains(lm)); + } + + return getRequiredMoveCountSpecies3(species, pk.CurrentLevel, moves) ? 3 : 0; // no match + } + private static bool getRequiredMoveCountSpecies3(int species, int level, int[] moves) + { + // Species that evolve and learn the 4th move as evolved species at a greather level than base species + // The 4th move is included in the level up table set as a preevolution move, + // it should be removed from the used slots count if is not the learn move + switch (species) + { + case 017: return level < 21 && !moves.Contains(018); // Pidgeotto without Whirlwind + case 028: return level < 27 && !moves.Contains(040); // Sandslash without Poison Sting + case 047: return level < 30 && !moves.Contains(147); // Parasect without Spore + case 055: return level < 39 && !moves.Contains(093); // Golduck without Confusion + case 087: return level < 44 && !moves.Contains(156); // Dewgong without Rest + case 093: + case 094: return level < 29 && !moves.Contains(095); // Haunter/Gengar without Hypnosis + case 110: return level < 39 && !moves.Contains(108); // Weezing without Smoke Screen + case 130: return level < 32; // Wild Gyarados from yellow do not learn splash, evolved gyarados do not learn tackle + } + return false; + } + private static int getRequiredMoveCountDecrement(PKM pk, int[] moves, List[] learn, int[] initialmoves) + { + int usedslots = initialmoves.Union(learn[1]).Where(m => m != 0).Distinct().Count(); + // Yellow optional moves, reduce usedslots if the yellow move is not present + // The count wont go bellow 1 because the yellow moves were already counted and are not the only initial or level up moves + if (pk.Species == 031) //Venonat + { + // ignore Venomoth, by the time Venonat evolved it will always have 4 moves + if (pk.CurrentLevel >= 11 && !moves.Contains(48)) // Supersonic + usedslots--; + if (pk.CurrentLevel >= 19 && !moves.Contains(93)) // Confusion + usedslots--; + } + if (pk.Species == 056 && pk.CurrentLevel >= 9 && !moves.Contains(67)) // Mankey Yellow Low Kick, Primeape will always have 4 moves + usedslots--; + + if (064 == pk.Species || pk.Species == 065) + { + if (!moves.Contains(134))// Initial Yellow Kadabra Kinesis + usedslots--; + if (pk.CurrentLevel < 10 && !moves.Contains(50)) // Kadabra Disable, not learned until 20 if captured as Abra + usedslots--; + } + if (104 == pk.Species || pk.Species == 105) // Cubone and Marowak + { + if (!moves.Contains(39)) // Initial Yellow Tail Whip + usedslots--; + if (!moves.Contains(125)) // Initial Yellow Bone Club + usedslots--; + if (pk.Species == 105 && pk.CurrentLevel < 33 && !moves.Contains(116)) // Marowak evolved without Focus Energy + usedslots--; + } + if (pk.Species == 113) // Chansey + { + if (!moves.Contains(39)) // Yellow Initial Tail Whip + usedslots--; + if (!moves.Contains(3)) // Yellow Lvl 12 and Initial Red/Blue Double Slap + usedslots--; + } + if (pk.Species == 127 && pk.CurrentLevel >= 21 && !moves.Contains(20)) // Pinsir Yellow Bind + usedslots--; + return usedslots; + } + + private static int getRequiredMoveCountSpecial(PKM pk, int[] moves, List[] learn) + { + // Species with few mandatory slots, species with stone evolutions that could evolve at lower level and do not learn any more moves + // and Pikachu and Nidoran family, those only have mandatory the initial moves and a few have one level up moves, + // every other move could be avoided switching game or evolving + var basespecies = getBaseSpecies(pk); + var maxlevel = 1; + var minlevel = 1; + if (029 <= pk.Species && pk.Species <= 034 && pk.CurrentLevel >= 8) + maxlevel = 8; // Always lean a third move at level 8 + if (pk.Species == 114) + { + //Tangela moves before level 32 are different in red/blue and yellow + minlevel = 32; + maxlevel = pk.CurrentLevel; + } + var mandatory = minlevel <= pk.CurrentLevel ? getLvlMoves(basespecies, 0, 1, minlevel, maxlevel).Where(m => m != 0).Distinct().ToList() : new List(); + if (pk.Species == 103 && pk.CurrentLevel >= 28) // Exeggutor + { + // At level 28 learn different move if is a Exeggute or Exeggutor + if (moves.Contains(73)) + mandatory.Add(73); // Leech Seed level 28 Exeggute + if (moves.Contains(23)) + mandatory.Add(23); // Stomp level 28 Exeggutor + } + if (pk.Species == 25 && pk.CurrentLevel >= 33) + mandatory.Add(97); // Pikachu always learn Agility + if (pk.Species == 114) + mandatory.Add(132); // Tangela always learn Constrict as Initial Move + + // Add to used slots the non-mandatory moves from the learnset table that the pokemon have learned + return mandatory.Count + moves.Where(m => m != 0 && mandatory.All(l => l != m) && learn[1].Any(t => t == m)).Count(); + } + private static EncounterTrade getValidEncounterTradeVC(PKM pkm, GameVersion gameSource) { var p = getValidPreEvolutions(pkm).ToArray(); @@ -1657,7 +1676,7 @@ internal static bool getWasEgg23(PKM pkm) if (lvl < 5) return false; - if(pkm.Format > 3 && pkm.Met_Level <5) + if (pkm.Format > 3 && pkm.Met_Level <5) return false; if (pkm.Format > 3 && pkm.FatefulEncounter) return false; @@ -2787,7 +2806,7 @@ private static IEnumerable getValidPreEvolutions(PKM pkm, int maxspeci if (pkm.Species == 292 && lvl >= 20 && (!pkm.HasOriginalMetLocation || pkm.Met_Level + 1 <= lvl)) return new List { - new DexLevel { Species = 292, Level = lvl, MinLevel =20 }, + new DexLevel { Species = 292, Level = lvl, MinLevel = 20 }, new DexLevel { Species = 290, Level = lvl-1, MinLevel = 1 } }; @@ -2954,7 +2973,7 @@ private static IEnumerable getMoves(PKM pkm, int species, int minlvlG1,int return r; if (LVL) { - if(index == 386) + if (index == 386) { switch(form) {