diff --git a/PKHeX/Legality/Analysis.cs b/PKHeX/Legality/Analysis.cs index bd6cbeb0d..49e4daa07 100644 --- a/PKHeX/Legality/Analysis.cs +++ b/PKHeX/Legality/Analysis.cs @@ -385,5 +385,23 @@ private static int getSuggestedTransferLocation(PKM pkm) return 30001; return -1; } + private static int[] getGenMovesCheckOrder(PKM pkm) + { + if (pkm.Format == 1) + return new [] { 1, 2 }; + else if (pkm.Format == 2) + return new [] { 2, 1 }; + else if (pkm.Format == 7 && pkm.VC1) + return new [] { 7, 1 }; + else if (pkm.Format == 7 && pkm.VC2) + return new [] { 7, 2, 1 }; + else + { + var order = new int[pkm.Format - pkm.GenNumber + 1]; + for (int i = 0; i < order.Length; i++) + order[i] = pkm.Format - i; + return order; + } + } } } diff --git a/PKHeX/Legality/CheckStrings.cs b/PKHeX/Legality/CheckStrings.cs index 761b8ae31..a6a4e9870 100644 --- a/PKHeX/Legality/CheckStrings.cs +++ b/PKHeX/Legality/CheckStrings.cs @@ -90,6 +90,10 @@ public static string[] getLocalization(string[] existingLines = null) public static string V174 { get; set; } = "Learned by Move Tutor."; public static string V175 { get; set; } = "Special Non-Relearn Move."; public static string V177 { get; set; } = "Learned by Level-up."; + public static string V330 { get; set; } = "Learned by Level-up in generation {0}."; + public static string V331 { get; set; } = "Learned by TM/HM in generation {0}."; + public static string V332 { get; set; } = "Learned by Move Tutor in generation {0}."; + public static string V333 { get; set; } = "Event Egg Move."; #endregion @@ -353,6 +357,12 @@ public static string[] getLocalization(string[] existingLines = null) public static string V327 {get; set;} = "Special ingame N's Sparkle flag should not be checked."; 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 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."; + public static string V339 {get; set;} = "Generation {0} HM. Should have been removed before transfered to generation {1}."; #endregion } diff --git a/PKHeX/Legality/Checks.cs b/PKHeX/Legality/Checks.cs index abaa71c07..88cf3c1c2 100644 --- a/PKHeX/Legality/Checks.cs +++ b/PKHeX/Legality/Checks.cs @@ -2021,10 +2021,10 @@ private void verifyVersionEvolution() private CheckResult[] verifyMoves(GameVersion game = GameVersion.Any) { - var validLevelMoves = Legal.getValidMoves(pkm, EvoChainsAllGens, Tutor: false, Machine: false).ToArray(); - var validTMHM = Legal.getValidMoves(pkm, EvoChainsAllGens, LVL: false, Tutor: false, MoveReminder: false).ToArray(); - var validTutor = Legal.getValidMoves(pkm, EvoChainsAllGens, LVL: false, Machine: false, MoveReminder: false).ToArray(); - + var validLevelMoves = Legal.getValidMovesAllGens(pkm, EvoChainsAllGens, Tutor: false, Machine: false, RemoveTransferHM:false); + var validTMHM = Legal.getValidMovesAllGens(pkm, EvoChainsAllGens, LVL: false, Tutor: false, MoveReminder: false, RemoveTransferHM: false); + var validTutor = Legal.getValidMovesAllGens(pkm, EvoChainsAllGens, LVL: false, Machine: false, MoveReminder: false, RemoveTransferHM: false); + Legal.RemoveFutureMoves(pkm, EvoChainsAllGens, ref validLevelMoves, ref validTMHM, ref validTutor); CheckResult[] res; int[] Moves = pkm.Moves; if (pkm.Species == 235) // Smeargle can have any move except a few @@ -2050,13 +2050,13 @@ private CheckResult[] parseMovesSketch(int[] Moves) : new CheckResult(CheckIdentifier.Move); return res; } - private CheckResult[] parseMovesGetGift(int[] Moves, int[] validLevelMoves, int[] validTMHM, int[] validTutor) + private CheckResult[] parseMovesGetGift(int[] Moves, int[][] validLevelMoves, int[][] validTMHM, int[][] validTutor) { int[] RelearnMoves = pkm.RelearnMoves; foreach (MysteryGift mg in EventGiftMatch) { int[] SpecialMoves = mg.Moves; - CheckResult[] res = parseMoves(Moves, validLevelMoves, RelearnMoves, validTMHM, validTutor, SpecialMoves, new int[0]); + CheckResult[] res = parseMoves(Moves, validLevelMoves, RelearnMoves, validTMHM, validTutor, SpecialMoves, new int[0], new int[0]); if (res.Any(r => !r.Valid)) continue; @@ -2067,18 +2067,19 @@ private CheckResult[] parseMovesGetGift(int[] Moves, int[] validLevelMoves, int[ } // no Mystery Gifts matched - return parseMoves(Moves, validLevelMoves, RelearnMoves, validTMHM, validTutor, new int[0], new int[0]); + return parseMoves(Moves, validLevelMoves, RelearnMoves, validTMHM, validTutor, new int[0], new int[0], new int[0]); } - private CheckResult[] parseMovesRegular(int[] Moves, int[] validLevelMoves, int[] validTMHM, int[] validTutor, GameVersion game) + private CheckResult[] parseMovesRegular(int[] Moves, int[][] validLevelMoves, int[][] validTMHM, int[][] validTutor, GameVersion game) { int[] EggMoves = pkm.WasEgg ? Legal.getEggMoves(pkm, game).ToArray() : new int[0]; + int[] EventEggMoves = new int[0]; int[] RelearnMoves = pkm.RelearnMoves; int[] SpecialMoves = (EncounterMatch as MysteryGift)?.Moves ?? (EncounterMatch as EncounterStatic)?.Moves ?? (EncounterMatch as EncounterTrade)?.Moves ?? new int[0]; - CheckResult[] res = parseMoves(Moves, validLevelMoves, RelearnMoves, validTMHM, validTutor, SpecialMoves, EggMoves); + CheckResult[] res = parseMoves(Moves, validLevelMoves, RelearnMoves, validTMHM, validTutor, SpecialMoves, EggMoves, EventEggMoves); if (pkm.GenNumber < 6) return res; @@ -2089,31 +2090,170 @@ private CheckResult[] parseMovesRegular(int[] Moves, int[] validLevelMoves, int[ return res; } - private static CheckResult[] parseMoves(int[] moves, int[] learn, int[] relearn, int[] tmhm, int[] tutor, int[] special, int[] egg) + private CheckResult[] parseMoves(int[] moves, int[][] learn, int[] relearn, int[][] tmhm, int[][] tutor, int[] special, int[] egg, int[] eventegg) { CheckResult[] res = new CheckResult[4]; - for (int i = 0; i < 4; i++) + var Gen1MovesLearned = new List(); + var EggMovesLearned = new List(); + var EventEggMovesLearned = new List(); + // Check none moves and relearn moves before generation moves + for (int m = 0; m < 4; m++) { - if (moves[i] == 0) - res[i] = new CheckResult(Severity.Valid, V167, CheckIdentifier.Move); - else if (learn.Contains(moves[i])) - res[i] = new CheckResult(Severity.Valid, V177, CheckIdentifier.Move); - else if (egg.Contains(moves[i])) - res[i] = new CheckResult(Severity.Valid, V171, CheckIdentifier.Move) { Flag = true }; - else if (relearn.Contains(moves[i])) - res[i] = new CheckResult(Severity.Valid, V172, CheckIdentifier.Move) { Flag = true }; - else if (tmhm.Contains(moves[i])) - res[i] = new CheckResult(Severity.Valid, V173, CheckIdentifier.Move); - else if (tutor.Contains(moves[i])) - res[i] = new CheckResult(Severity.Valid, V174, CheckIdentifier.Move); - else if (special.Contains(moves[i])) - res[i] = new CheckResult(Severity.Valid, V175, CheckIdentifier.Move); - else - res[i] = new CheckResult(Severity.Invalid, V176, CheckIdentifier.Move); + if (moves[m] == 0) + res[m] = new CheckResult(Severity.Valid, V167, CheckIdentifier.Move); + else if (relearn.Contains(moves[m])) + res[m] = new CheckResult(Severity.Valid, V172, CheckIdentifier.Move) { Flag = true }; + } + + if (res.All(r => r.Valid)) + return res; + + bool MixedGen1NonTradebackGen2= false; + // Check moves going backwards, marking the move valid in the most current generation when it can be learned + int[] generations = getGenMovesCheckOrder(pkm); + for (int g = 0; g < generations.Length; g++) + { + var gen = generations[g]; + if (!pkm.InhabitedGeneration(gen)) + continue; + + IEnumerable HMLearned = new List(); + // Check if pokemon knows HM moves from generation 3 and 4 but are not valid yet, that means it cant learn the HMs in future generations + bool KnowDefogWhirlpool = false; + if (gen == 4 && pkm.Format > 4) + { + HMLearned = moves.Where((m,i) => !(res[i]?.Valid ?? false) && Legal.HM_4_RemovePokeTransfer.Any(l => l == m)).Select((m, i) => i); + // Defog and Whirlpool at the same time, also both can't be learned in future generations or else they will be valid + KnowDefogWhirlpool = moves.Where((m, i) => (m == 250 && m == 432) && !(res[i]?.Valid ?? false)).Count() == 2; + } + else if (gen == 3 && pkm.Format > 3) + HMLearned = moves.Where((m, i) => !(res[i]?.Valid ?? false) && Legal.HM_3.Any(l => l == m)).Select((m, i) => i); + + for (int m = 0; m < 4; m++) + { + if (res[m]?.Valid ?? false) + continue; + + if (learn[m].Contains(moves[m])) + { + res[m] = new CheckResult(Severity.Valid, (gen == pkm.Format)? V177 : String.Format(V330, gen), CheckIdentifier.Move); + if (gen == 1) Gen1MovesLearned.Add(m); + } + else if (tmhm[m].Contains(moves[m])) + { + res[m] = new CheckResult(Severity.Valid, (gen == pkm.Format) ? V173 : String.Format(V331, gen), CheckIdentifier.Move); + if (gen == 1) Gen1MovesLearned.Add(m); + } + else if (tutor[m].Contains(moves[m])) + { + res[m] = new CheckResult(Severity.Valid, (gen == pkm.Format) ? V173 : String.Format(V332, gen), CheckIdentifier.Move); + if (gen == 1) Gen1MovesLearned.Add(m); + } + else if (gen == pkm.GenNumber && special.Contains(moves[m])) + { + res[m] = new CheckResult(Severity.Valid, V175, CheckIdentifier.Move); + if (gen == 1) Gen1MovesLearned.Add(m); + } + } + + if (gen == generations.Length - 1) + { + // Check egg moves after all the generations and all the moves, every move that can be learned in another source should have preference + // the moves that can only be learned from egg moves should in the future check if the move combinations can be breed in gens 2 to 5 + for (int m = 0; m < 4; m++) + { + if (res[m]?.Valid ?? false) + continue; + if (egg.Contains(moves[m])) + { + if (Gen1MovesLearned.Any() && moves[m] > Legal.MaxMoveID_1) + { + // To learn exclusive generation 1 moves the pokemon was tradeback, but it can 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 }; + MixedGen1NonTradebackGen2 = true; + } + else + res[m] = new CheckResult(Severity.Valid, V171, CheckIdentifier.Move) { Flag = true }; + EggMovesLearned.Add(m); + } + if (eventegg.Contains(moves[m])) + { + if(!egg.Contains(moves[m])) + { + if (Gen1MovesLearned.Any() && moves[m] > Legal.MaxMoveID_1) + { + res[m] = new CheckResult(Severity.Invalid, V334, CheckIdentifier.Move) { Flag = true }; + MixedGen1NonTradebackGen2 = true; + } + else + res[m] = new CheckResult(Severity.Valid, V33, CheckIdentifier.Move) { Flag = true }; + } + EventEggMovesLearned.Add(m); + } + } + + // A pokemon could have normal egg moves and regular egg moves only if all the event egg moves are also regular egg moves + if (!EggMovesLearned.All(e=> EventEggMovesLearned.Contains(e))) + { + for (int m = 0; m < 4; m++) + { + if (EventEggMovesLearned.Contains(m) && !EggMovesLearned.Contains(m)) + res[m] = new CheckResult(Severity.Invalid, V337, CheckIdentifier.Move); + else if (!EventEggMovesLearned.Contains(m) && EggMovesLearned.Contains(m)) + res[m] = new CheckResult(Severity.Invalid, V336, CheckIdentifier.Move); + } + } + + } + + if (3 <= gen && gen <= 4 && gen > pkm.Format) + { + // After all the moves from the generations 3 and 4, + // including egg moves if is the origin generation because some hidden moves are also special egg moves in gen 3 + // Check if the marked hidden moves that were invalid at the start are now marked as valid, that means + // the hidden move was learned in gen 3 or 4 but was not removed when transfer to 4 or 5 + if (KnowDefogWhirlpool) + KnowDefogWhirlpool = moves.Where((m,i) => (m == 250 && m == 432) && (res[i]?.Valid ?? false)).Count() == 2; + + if (KnowDefogWhirlpool) + { + KnowDefogWhirlpool = moves.Where((m, i) => (m == 250 && m == 432) && (res[i]?.Valid ?? false)).Count() == 2; + foreach (int index in moves.Where(m => (m == 250 && m == 432)).Select((move, index) => index)) + { + res[index] = new CheckResult(Severity.Invalid, V338, CheckIdentifier.Move); + } + } + + foreach (int hm in HMLearned.Where(l => res[l]?.Valid ?? false)) + { + res[hm] = new CheckResult(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); + } + } + + if (res.All(r => r.Valid)) + return res; + } + + if (res.All(r => r.Valid)) + return res; + + for (int m = 0; m < 4; m++) + { + if (res[m] == null) + res[m] = new CheckResult(Severity.Invalid, V176, CheckIdentifier.Move); } return res; } - private CheckResult[] verifyRelearn() { RelearnBase = null; diff --git a/PKHeX/Legality/Core.cs b/PKHeX/Legality/Core.cs index 942e15acc..7cd95e979 100644 --- a/PKHeX/Legality/Core.cs +++ b/PKHeX/Legality/Core.cs @@ -548,6 +548,28 @@ private static EncounterArea[] getTables2(GameVersion Version) } // Moves + internal static void RemoveFutureMoves(PKM pkm, DexLevel[][] evoChains, ref int[][] validLevelMoves, ref int[][] validTMHM, ref int[][] validTutor) + { + IEnumerable AllMoves = new List(); + if (pkm.Format > 3) + { + for (int i = evoChains.Length - 1; i > 0; i--) + { + validLevelMoves[i] = validLevelMoves[i].Except(AllMoves).ToArray(); + validTMHM[i] = validTMHM[i].Except(AllMoves).ToArray(); + validTutor[i] = validTutor[i].Except(AllMoves).ToArray(); + AllMoves = AllMoves.Concat(validLevelMoves[i]).Concat(validTMHM[i]).Concat(validTutor[i]); + } + } + else + { + int tradeback = (pkm.Format == 2) ? 1 : 2; + var formatmoves = validLevelMoves[pkm.Format].Concat(validTMHM[pkm.Format]).Concat(validTutor[pkm.Format]); + validLevelMoves[tradeback] = validLevelMoves[tradeback].Except(formatmoves).ToArray(); + validTMHM[tradeback] = validTMHM[tradeback].Except(formatmoves).ToArray(); + validTutor[tradeback] = validTutor[tradeback].Except(formatmoves).ToArray(); + } + } internal static int[][] getValidMovesAllGens(PKM pkm, DexLevel[][] evoChains, bool LVL = true, bool Tutor = true, bool Machine = true, bool MoveReminder = true, bool RemoveTransferHM = true) { int[][] Moves = new int[evoChains.Length][]; diff --git a/PKHeX/Resources/text/en/legality_en.txt b/PKHeX/Resources/text/en/legality_en.txt index 74ac36425..d931a0c9c 100644 --- a/PKHeX/Resources/text/en/legality_en.txt +++ b/PKHeX/Resources/text/en/legality_en.txt @@ -15,6 +15,9 @@ V173 = Learned by TM/HM. V174 = Learned by Move Tutor. V175 = Special Non-Relearn Move. V177 = Learned by Level-up. +V330 = Learned by Level-up in generation {0}. +V332 = Learned by Move Tutor in generation {0}. +V333 = Event Egg Move. V203 = Genderless Pokémon should not have a gender. V201 = Encryption Constant is not set. V204 = Held item is unreleased. @@ -245,4 +248,10 @@ V324 = Special ingame Fateful Encounter flag missing. V325 = Fateful Encounter should not be checked. 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. \ No newline at end of file +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. +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. +V339 = Generation {0} HM. Should have been removed before transfered to generation {1}. \ No newline at end of file