diff --git a/PKHeX.WinForms/MainWindow/Main.cs b/PKHeX.WinForms/MainWindow/Main.cs index 8fec9becc..1908f8b71 100644 --- a/PKHeX.WinForms/MainWindow/Main.cs +++ b/PKHeX.WinForms/MainWindow/Main.cs @@ -979,8 +979,8 @@ private void openSAV(SaveFile sav, string path) Legal.AllowGBCartEra = drVC == DialogResult.No; // physical cart selected if (Legal.AllowGBCartEra && sav.Generation == 1) { - var drTradeback = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Generation 1 Save File detected. Allow tradeback from generation 2 for legallity purpose?", - "Yes: Generation 2 tradeback allow" + Environment.NewLine + "No: Only consider legal pokemon possible without generation 2 games"); + var drTradeback = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, $"Generation {SAV.Generation} Save File detected. Allow tradebacks from Generation 2 for legality purposes?", + "Yes: Allow Generation 2 tradeback learnsets" + Environment.NewLine + "No: Don't allow Generation 2 tradeback learnsets"); Legal.AllowGen1Tradeback = drTradeback == DialogResult.Yes; } else @@ -991,7 +991,7 @@ private void openSAV(SaveFile sav, string path) if (sav.Generation == 3 && (sav.IndeterminateGame || ModifierKeys == Keys.Control)) { - WinFormsUtil.Alert("Gen3 Game Detected.", "Select version."); + WinFormsUtil.Alert($"Generation {SAV.Generation} Save File detected.", "Select version."); var g = new[] {GameVersion.R, GameVersion.S, GameVersion.E, GameVersion.FR, GameVersion.LG}; var games = g.Select(z => GameInfo.VersionDataSource.First(v => v.Value == (int)z)); var dialog = new SAV_GameSelect(games); @@ -1013,7 +1013,7 @@ private void openSAV(SaveFile sav, string path) { string fr = GameInfo.VersionDataSource.First(r => r.Value == (int)GameVersion.FR).Text; string lg = GameInfo.VersionDataSource.First(l => l.Value == (int)GameVersion.LG).Text; - const string dual = "{0}/{1} Game Detected."; + const string dual = "{0}/{1} Save File Detected."; WinFormsUtil.Alert(string.Format(dual, fr, lg), "Select version."); var g = new[] {GameVersion.FR, GameVersion.LG}; var games = g.Select(z => GameInfo.VersionDataSource.First(v => v.Value == (int)z)); diff --git a/PKHeX/Legality/Analysis.cs b/PKHeX/Legality/Analysis.cs index db7720c7c..d5e47919d 100644 --- a/PKHeX/Legality/Analysis.cs +++ b/PKHeX/Legality/Analysis.cs @@ -123,7 +123,7 @@ private void parsePK1(PKM pk) pkm = pk; if (!pkm.IsOriginValid) { AddLine(Severity.Invalid, V187, CheckIdentifier.None); return; } - UpdateTradebackG12(); + updateTradebackG12(); updateEncounterChain(); updateMoveLegality(); updateTypeInfo(); @@ -214,42 +214,44 @@ private void updateEncounterChain() Parse.Add(Encounter); EvoChainsAllGens = Legal.getEvolutionChainsAllGens(pkm, EncounterOriginalGB ?? EncounterMatch); } - private void UpdateTradebackG12() + private void updateTradebackG12() { if (pkm.Format == 1) { if (!Legal.AllowGen1Tradeback) { pkm.TradebackStatus = TradebackType.Gen1_NotTradeback; - (pkm as PK1).CatchRateIsItem = false; + ((PK1)pkm).CatchRateIsItem = false; + return; } - else - { - var catch_rate = (pkm as PK1).Catch_Rate; - // If catch rate match a species catch rate from the evolution chain of the species but do not match a generation 2 item means it was not tradeback - // If match a held item but not a species catch rate then in means it was tradeback - var HeldItemCatchRate = (catch_rate == 0 || Legal.HeldItems_GSC.Any(h => h == catch_rate)); - // For species catch rate discart species that have no valid encounters and different catch rate that their preevolutions - var Lineage = Legal.getLineage(pkm).Where(s => !Legal.Species_NotAvailable_CatchRate.Contains(s)).ToList(); - var RGBCatchRate = Lineage.Any(s => catch_rate == PersonalTable.RB[s].CatchRate); - // Dragonite Catch Rate is different than Dragonair in Yellow but there is not any Dragonite encounter - var YCatchRate = Lineage.Any(s => s != 149 && catch_rate == PersonalTable.Y[s].CatchRate); - if (HeldItemCatchRate && !RGBCatchRate && !YCatchRate) - pkm.TradebackStatus = TradebackType.WasTradeback; - else if (!HeldItemCatchRate && (RGBCatchRate || YCatchRate)) - pkm.TradebackStatus = TradebackType.Gen1_NotTradeback; - else - pkm.TradebackStatus = TradebackType.Any; - // Set CatchRateIsItem to true to keep the held item stored when changed species - // only if catch rate match a valid held item and do not match the default catch rate from the species - // If pokemon have not been traded to gen 2 then catch rate could not be a held item - (pkm as PK1).CatchRateIsItem = pkm.Gen1_NotTradeback ? false : (HeldItemCatchRate && !RGBCatchRate && !YCatchRate); - } + // Detect tradeback status by comparing the catch rate(Gen1)/held item(Gen2) to the species in the pkm's evolution chain. + var catch_rate = ((PK1)pkm).Catch_Rate; + + // For species catch rate, discard any species that has no valid encounters and a different catch rate than their pre-evolutions + var Lineage = Legal.getLineage(pkm).Where(s => !Legal.Species_NotAvailable_CatchRate.Contains(s)).ToList(); + // Dragonite's Catch Rate is different than Dragonair's in Yellow, but there is no Dragonite encounter. + var RGBCatchRate = Lineage.Any(s => catch_rate == PersonalTable.RB[s].CatchRate); + var YCatchRate = Lineage.Any(s => s != 149 && catch_rate == PersonalTable.Y[s].CatchRate); + + bool matchAny = RGBCatchRate || YCatchRate; + + // If the catch rate value has been modified, the item has either been removed or swapped in Generation 2. + var HeldItemCatchRate = catch_rate == 0 || Legal.HeldItems_GSC.Any(h => h == catch_rate); + if (HeldItemCatchRate && !matchAny) + pkm.TradebackStatus = TradebackType.WasTradeback; + else if (!HeldItemCatchRate && matchAny) + pkm.TradebackStatus = TradebackType.Gen1_NotTradeback; + else + pkm.TradebackStatus = TradebackType.Any; + + + // Update the editing settings for the PKM to acknowledge the tradeback status if the species is changed. + ((PK1)pkm).CatchRateIsItem = !pkm.Gen1_NotTradeback && HeldItemCatchRate && matchAny; } else if (pkm.Format == 2 || pkm.VC2) { - // Eggs, pokemon with non-empty crystal met location and generation 2 species without generation 1 preevolutions can not be traded to generation 1 games + // Eggs, pokemon with non-empty crystal met location, and generation 2 species without generation 1 preevolutions can't be traded to generation 1. if (pkm.IsEgg || pkm.HasOriginalMetLocation || (pkm.Species > Legal.MaxSpeciesID_1 && !Legal.FutureEvolutionsGen1.Contains(pkm.Species))) pkm.TradebackStatus = TradebackType.Gen2_NotTradeback; else @@ -257,7 +259,8 @@ private void UpdateTradebackG12() } else if (pkm.VC1) { - // Probably if VC2 is released VC1 pokemon with met date after VC2 Bank release date will be TradebackType.Any + // If VC2 is ever released, we can assume it will be TradebackType.Any. + // Met date cannot be used definitively as the player can change their system clock. pkm.TradebackStatus = TradebackType.Gen1_NotTradeback; } else diff --git a/PKHeX/Legality/Checks.cs b/PKHeX/Legality/Checks.cs index d320c3aff..cec351be4 100644 --- a/PKHeX/Legality/Checks.cs +++ b/PKHeX/Legality/Checks.cs @@ -2582,11 +2582,11 @@ private CheckResult[] verifyMoves(GameVersion game = GameVersion.Any) } private void UptateGen1LevelUpMoves(ValidEncounterMoves EncounterMoves, int defaultLvlG1, int generation) { - switch(generation) + switch (generation) { case 1: case 2: - var lvlG1 = EncounterMatch == null ? 6 : (EncounterMatch as IEncounterable).LevelMin + 1; + var lvlG1 = (EncounterMatch as IEncounterable)?.LevelMin + 1 ?? 6; if (lvlG1 != defaultLvlG1) EncounterMoves.validLevelUpMoves[1] = Legal.getValidMoves(pkm, EvoChainsAllGens[1], generation: 1, minLvLG1: lvlG1, LVL: true, Tutor: false, Machine: false, MoveReminder: false).ToList(); break; @@ -2601,7 +2601,7 @@ private ValidEncounterMoves getEncounterValidMoves(int defaultspecies, int encou var LevelMoves = Legal.getValidMovesAllGens(pkm, EvoChainsAllGens, minLvLG1: minLvLG1, Tutor: false, Machine: false, RemoveTransferHM: false); var TMHMMoves = Legal.getValidMovesAllGens(pkm, EvoChainsAllGens, LVL: false, Tutor: false, MoveReminder: false, RemoveTransferHM: false); var TutorMoves = Legal.getValidMovesAllGens(pkm, EvoChainsAllGens, LVL: false, Machine: false, MoveReminder: false, RemoveTransferHM: false); - return new ValidEncounterMoves() + return new ValidEncounterMoves { EncounterSpecies = encounterspecies, validLevelUpMoves = LevelMoves, @@ -2613,16 +2613,17 @@ private ValidEncounterMoves getEncounterValidMoves(int defaultspecies, int encou } private List getEncountersValidMoves(List encounters, DexLevel[] vs) { - var defaultspecies = Legal.getEncounterSpecies(pkm, vs, EncounterMatch); + var defaultspecies = Legal.getEncounterSpecies(EncounterMatch, vs); var r = new List(); - foreach(DexLevel evo in vs) + foreach (DexLevel evo in vs) { - // Store only one set of valid moves for species, using the minimun level encounter for that species - var encounters_evo = encounters.Where(e => Legal.getEncounterSpecies(pkm, vs, e) == evo.Species); + // Store only one set of valid moves for species; use the minimum level encounter for that species + var encounters_evo = encounters.Where(e => Legal.getEncounterSpecies(e, vs) == evo.Species).ToList(); if (!encounters_evo.Any()) continue; - // For every possible encounter species get valid moves using minimun encounter level for each species - // Generation 1 encounters will overwrite the valid level moves of gen 1 if encounter level is not the minimun + + // For every possible encounter species, get valid moves using minimum encounter level for each species + // Generation 1 encounters will overwrite the valid level moves of gen 1 if encounter level is not the minimum var minlevel = encounters_evo.Min(e => Legal.getEncounterLevel(pkm, e)); var encounter_minlevel = encounters_evo.First(e => Legal.getEncounterLevel(pkm, e) == minlevel); r.Add(getEncounterValidMoves(defaultspecies, evo.Species, encounter_minlevel, minlevel)); @@ -2644,9 +2645,7 @@ private CheckResult[] parseMovesForEncounters(GameVersion game, int[] Moves) encounters = encounters.Distinct().ToList(); if (!encounters.Any()) // There isn't any valid encounter and wasnt an egg - { - return parseMovesNoEncounters(Moves); - } + return parseMovesNoEncounters(); // Iterate over encounters bool pre3DS = pkm.GenNumber < 6; @@ -2660,7 +2659,7 @@ private CheckResult[] parseMovesForEncounters(GameVersion game, int[] Moves) if (pkm.GenNumber <= 2) EncounterOriginalGB = enc; - EncounterSpecies = Legal.getEncounterSpecies(pkm, vs, EncounterMatch); + EncounterSpecies = Legal.getEncounterSpecies(EncounterMatch, vs); var EncounterMoves = EncountersMoves.First(e => e.EncounterSpecies == EncounterSpecies); EvoChainsAllGens = EncounterMoves.EvolutionChains; @@ -2670,7 +2669,7 @@ private CheckResult[] parseMovesForEncounters(GameVersion game, int[] Moves) var EncounterMatchGen = EncounterMatch as IGeneration; var defaultG1LevelMoves = EncounterMoves.validLevelUpMoves[1]; if (EncounterMatchGen != null) - // Generation 1 can have different minimun level in different encounter of the same species, update valid level moves + // Generation 1 can have different minimum level in different encounter of the same species; update valid level moves UptateGen1LevelUpMoves(EncounterMoves, EncounterMoves.minLvlG1, EncounterMatchGen.Generation); res = pre3DS @@ -2680,13 +2679,12 @@ private CheckResult[] parseMovesForEncounters(GameVersion game, int[] Moves) if (res.All(x => x.Valid)) break; - if (EncounterMatchGen?.Generation == 1) - // If is not valid restore generation 1 moves + if (EncounterMatchGen?.Generation == 1) // not valid, restore generation 1 moves EncounterMoves.validLevelUpMoves[1] = defaultG1LevelMoves; } return res; } - private CheckResult[] parseMovesNoEncounters(int[] Moves) + private CheckResult[] parseMovesNoEncounters() { EncounterSpecies = pkm.Species; var validLevelMoves = Legal.getValidMovesAllGens(pkm, EvoChainsAllGens, minLvLG1: 1, Tutor: false, Machine: false, RemoveTransferHM: false); @@ -3022,8 +3020,9 @@ private CheckResult[] parseMoves(int[] moves, List[] learn, int[] relearn, if (res[m] == null) continue; + + // Check for incense exclusive moves; must not be special/event. if (res[m].Valid && issplitbreed && IncenseExclusiveMoves[gen].Contains(moves[m]) && !eventegg.Contains(moves[m]) && !special.Contains(moves[m])) - // Learned moves exclusive to the incense species, ignore if the move is also a special move or an event egg move IncenseMovesLearned.Add(m); if (res[m].Valid && gen == 1) Gen1MovesLearned.Add(m); @@ -3334,17 +3333,21 @@ private void ParseEvolutionLevelupMove(int[] moves, List[] EggMovesSplitLea { // Ignore if there is an invalid move or an empty move, this validtion is only for 4 non-empty moves that are all valid, but invalid as a 4 combination // Ignore Mr.Mime and Sodowodoo from generations 1 to 3, they cant be evolved from Bonsly or Munchlax - // Ignore if encounter species is the evolution species, pokemon was not evolved by the player - if (!res.All(r => r?.Valid ?? false) || moves.Any(m => m == 0) || (Legal.BabyEvolutionWithMove.Contains(pkm.Species) && pkm.GenNumber <= 3) || EncounterSpecies == pkm.Species) + // Ignore if encounter species is the evolution species, the pokemon was not evolved by the player + if (!res.All(r => r?.Valid ?? false) || moves.Any(m => m == 0) || + (Legal.BabyEvolutionWithMove.Contains(pkm.Species) && pkm.GenNumber <= 3) || + EncounterSpecies == 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, egg can be assumed to be a non-incense egg, pokemon was not evolved by the player - if (EncounterMatch == null && pkm.WasEgg && Legal.BabyEvolutionWithMove.Contains(pkm.Species) && !IncenseMovesLearned.Any() && !EggMovesSplitLearned[0].Any()) + // 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 (EncounterMatch == null && pkm.WasEgg && Legal.BabyEvolutionWithMove.Contains(pkm.Species) && + !IncenseMovesLearned.Any() && !EggMovesSplitLearned[0].Any()) return; var ValidMoves = Legal.getValidPostEvolutionMoves(pkm, pkm.Species, 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) + switch (pkm.Species) { case 122: // Mr. Mime (Mime Jr with Mimic) case 185: // Sudowoodo (Bonsly with Mimic) @@ -3366,16 +3369,15 @@ private void ParseEvolutionLevelupMove(int[] moves, List[] EggMovesSplitLea ValidMoves.AddRange(Legal.FairyMoves); break; case 763: // Tsareena (Steenee with Stomp) - ValidMoves.Add(23); + ValidMoves.Add(023); break; } - if(!moves.Any(m => ValidMoves.Contains(m))) - { - for(int m = 0; m < 4; m++) - { - res[m] = new CheckResult(Severity.Invalid, string.Format(V385, specieslist[pkm.Species]), CheckIdentifier.Move); - } - } + + if (moves.Any(m => ValidMoves.Contains(m))) + return; + + for (int m = 0; m < 4; m++) + res[m] = new CheckResult(Severity.Invalid, string.Format(V385, specieslist[pkm.Species]), CheckIdentifier.Move); } private void verifyPreRelearn() { diff --git a/PKHeX/Legality/Core.cs b/PKHeX/Legality/Core.cs index 1c6b57b19..cf22cc1e7 100644 --- a/PKHeX/Legality/Core.cs +++ b/PKHeX/Legality/Core.cs @@ -3094,7 +3094,7 @@ internal static int getEncounterLevel(PKM pkm, object encounter) (encounter as IEncounterable)?.LevelMin ?? (pkm.GenNumber <= 3 ? 5 : 1 ); //egg } - internal static int getEncounterSpecies(PKM pkm, DexLevel[] vs, object encounter) + internal static int getEncounterSpecies(object encounter, DexLevel[] vs) { if (encounter is int) return (int)encounter; @@ -3102,7 +3102,7 @@ internal static int getEncounterSpecies(PKM pkm, DexLevel[] vs, object encounter return vs.Reverse().First(s => ((IEncounterable[])encounter).Any(slot => slot.Species == s.Species)).Species; if (encounter is IEncounterable) return vs.Reverse().First(s => ((IEncounterable)encounter).Species == s.Species).Species; - // encounter is null, is an egg or invalid origin, return base species + // fallback to base species on unknown origin return vs.Last().Species; } internal static IEnumerable getValidPreEvolutions(PKM pkm, int maxspeciesorigin = -1, int lvl = -1, bool skipChecks = false) diff --git a/PKHeX/Legality/Tables1.cs b/PKHeX/Legality/Tables1.cs index 3b31230ee..967a59641 100644 --- a/PKHeX/Legality/Tables1.cs +++ b/PKHeX/Legality/Tables1.cs @@ -42,11 +42,11 @@ public static partial class Legal 015, 019, 057, 070, 148 }; - internal static readonly int[] G1CaterpieMoves = new[] { 33, 81}; - internal static readonly int[] G1WeedleMoves = new[] { 40, 81 }; + internal static readonly int[] G1CaterpieMoves = { 33, 81 }; + internal static readonly int[] G1WeedleMoves = { 40, 81 }; internal static readonly int[] G1MetapodMoves = G1CaterpieMoves.Concat(new[] { 106 }).ToArray(); internal static readonly int[] G1KakunaMoves = G1WeedleMoves.Concat(new[] { 106 }).ToArray(); - internal static readonly int[] G1Exeggcute_IncompatibleMoves = new[] { 78, 77, 79 }; + internal static readonly int[] G1Exeggcute_IncompatibleMoves = { 78, 77, 79 }; internal static readonly int[] WildPokeBalls1 = {4}; @@ -183,9 +183,9 @@ public static partial class Legal internal static readonly int[] FutureEvolutionsGen1_Gen2LevelUp = { + // Crobat Espeon Umbreon Blissey 169,196,197,242 }; - //Crobat Espeon Umbreon Blissey internal static readonly int[] SpecialMinMoveSlots = { 25, 26, 29, 30, 31, 32, 33, 34, 36, 38, 40, 59, 91, 103, 114, 121,