diff --git a/PKHeX.WinForms/MainWindow/Main.cs b/PKHeX.WinForms/MainWindow/Main.cs index b42b6f4fd..8fec9becc 100644 --- a/PKHeX.WinForms/MainWindow/Main.cs +++ b/PKHeX.WinForms/MainWindow/Main.cs @@ -977,9 +977,17 @@ namespace PKHeX.WinForms if (drVC == DialogResult.Cancel) return; 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"); + Legal.AllowGen1Tradeback = drTradeback == DialogResult.Yes; + } + else + Legal.AllowGen1Tradeback = false; } else - Legal.AllowGBCartEra = sav.Generation == 2; + Legal.AllowGBCartEra = Legal.AllowGen1Tradeback = sav.Generation == 2; if (sav.Generation == 3 && (sav.IndeterminateGame || ModifierKeys == Keys.Control)) { diff --git a/PKHeX/Legality/Analysis.cs b/PKHeX/Legality/Analysis.cs index d5b949f58..db7720c7c 100644 --- a/PKHeX/Legality/Analysis.cs +++ b/PKHeX/Legality/Analysis.cs @@ -12,8 +12,9 @@ namespace PKHeX.Core private readonly List Parse = new List(); private List EncountersGBMatch; - private object EncounterOriginalGB => EncountersGBMatch?.FirstOrDefault()?.Encounter; + private object EncounterOriginalGB; private object EncounterMatch; + private int EncounterSpecies; private Type Type; // Parent class when applicable (EncounterStatic / MysteryGift) private Type MatchedType; // Child class if applicable (WC6, PGF, etc) private string EncounterName => Legal.getEncounterTypeName(pkm, EncounterOriginalGB ?? EncounterMatch); @@ -122,13 +123,14 @@ namespace PKHeX.Core pkm = pk; if (!pkm.IsOriginValid) { AddLine(Severity.Invalid, V187, CheckIdentifier.None); return; } - + UpdateTradebackG12(); updateEncounterChain(); updateMoveLegality(); updateTypeInfo(); verifyNickname(); verifyDVs(); verifyG1OT(); + verifyMiscG1(); } private void parsePK3(PKM pk) { @@ -212,6 +214,57 @@ namespace PKHeX.Core Parse.Add(Encounter); EvoChainsAllGens = Legal.getEvolutionChainsAllGens(pkm, EncounterOriginalGB ?? EncounterMatch); } + private void UpdateTradebackG12() + { + if (pkm.Format == 1) + { + if (!Legal.AllowGen1Tradeback) + { + pkm.TradebackStatus = TradebackType.Gen1_NotTradeback; + (pkm as PK1).CatchRateIsItem = false; + } + 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); + } + } + 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 + if (pkm.IsEgg || pkm.HasOriginalMetLocation || (pkm.Species > Legal.MaxSpeciesID_1 && !Legal.FutureEvolutionsGen1.Contains(pkm.Species))) + pkm.TradebackStatus = TradebackType.Gen2_NotTradeback; + else + pkm.TradebackStatus = TradebackType.Any; + } + else if (pkm.VC1) + { + // Probably if VC2 is released VC1 pokemon with met date after VC2 Bank release date will be TradebackType.Any + pkm.TradebackStatus = TradebackType.Gen1_NotTradeback; + } + else + { + pkm.TradebackStatus = TradebackType.Any; + } + } private void updateTypeInfo() { if (pkm.VC && pkm.Format == 7) diff --git a/PKHeX/Legality/Checks.cs b/PKHeX/Legality/Checks.cs index 2c19b3f94..d320c3aff 100644 --- a/PKHeX/Legality/Checks.cs +++ b/PKHeX/Legality/Checks.cs @@ -773,7 +773,7 @@ namespace PKHeX.Core } private CheckResult verifyEncounterG12() { - EncountersGBMatch = Legal.getEncounter12(pkm, Legal.AllowGBCartEra && pkm.Format < 3); + EncountersGBMatch = Legal.getEncounter12(pkm); if (EncountersGBMatch == null) return new CheckResult(Severity.Invalid, V80, CheckIdentifier.Encounter); @@ -782,7 +782,7 @@ namespace PKHeX.Core pkm.WasEgg = true; return verifyEncounterEgg(); } - EncounterMatch = EncounterOriginalGB; + EncounterMatch = EncounterOriginalGB = EncountersGBMatch?.FirstOrDefault()?.Encounter; if (EncounterMatch is EncounterSlot) return new CheckResult(Severity.Valid, V68, CheckIdentifier.Encounter); if (EncounterMatch is EncounterStatic) @@ -2339,6 +2339,64 @@ namespace PKHeX.Core AddLine(Severity.Valid, V318, CheckIdentifier.Form); } + private void verifyMiscG1() + { + if (pkm.Format > 1) + return; + + var Type_A = (pkm as PK1).Type_A; + var Type_B = (pkm as PK1).Type_B; + if (pkm.Species == 137) + { + // Porygon can have any type combination of any generation 1 species because of the move Conversion, + // that change Porygon type to match the oponent types + var Type_A_Match = Legal.Types_Gen1.Any(t => t == Type_A); + var Type_B_Match = Legal.Types_Gen1.Any(t => t == Type_B); + if (!Type_A_Match) + AddLine(Severity.Invalid, V386, CheckIdentifier.Misc); + if (!Type_B_Match) + AddLine(Severity.Invalid, V387, CheckIdentifier.Misc); + if (Type_A_Match && Type_B_Match) + { + var TypesAB_Match = PersonalTable.RB.IsValidTypeCombination(Type_A, Type_B); + if (TypesAB_Match) + AddLine(Severity.Valid, V391, CheckIdentifier.Misc); + else + AddLine(Severity.Invalid, V388, CheckIdentifier.Misc); + } + } + else // Types must match species types + { + var Type_A_Match = Type_A == PersonalTable.RB[pkm.Species].Types[0]; + var Type_B_Match = Type_B == PersonalTable.RB[pkm.Species].Types[1]; + + AddLine(Type_A_Match ? Severity.Valid : Severity.Invalid, Type_A_Match ? V392 : V389, CheckIdentifier.Misc); + + AddLine(Type_B_Match ? Severity.Valid : Severity.Invalid, Type_B_Match ? V393 : V390, CheckIdentifier.Misc); + } + var catch_rate =(pkm as PK1).Catch_Rate; + switch (pkm.TradebackStatus) + { + case TradebackType.Any: + case TradebackType.WasTradeback: + if (catch_rate == 0 || Legal.HeldItems_GSC.Any(h => h == catch_rate)) + { AddLine(Severity.Valid, V394, CheckIdentifier.Misc); } + else if (pkm.TradebackStatus == TradebackType.WasTradeback) + { AddLine(Severity.Invalid, V395, CheckIdentifier.Misc); } + else + goto case TradebackType.Gen1_NotTradeback; + break; + case TradebackType.Gen1_NotTradeback: + if ( ((pkm.Species == 149) && (catch_rate == PersonalTable.Y[149].CatchRate)) || + (Legal.Species_NotAvailable_CatchRate.Contains(pkm.Species) && (catch_rate == PersonalTable.RB[pkm.Species].CatchRate))) + { AddLine(Severity.Invalid, V396, CheckIdentifier.Misc); } + else if (!EvoChainsAllGens[1].Any(e => catch_rate == PersonalTable.RB[e.Species].CatchRate || catch_rate == PersonalTable.Y[e.Species].CatchRate)) + { AddLine(Severity.Invalid, pkm.Gen1_NotTradeback? V397: V399, CheckIdentifier.Misc); } + else + { AddLine(Severity.Valid, V398, CheckIdentifier.Misc); } + break; + } + } private void verifyMisc() { if (pkm.Format == 7 && ((PK7)pkm).PelagoEventStatus != 0) @@ -2374,7 +2432,11 @@ namespace PKHeX.Core if (Type == typeof(EncounterStatic)) { var enc = EncounterMatch as EncounterStatic; - if (enc.Fateful) + var fateful = enc.Fateful; + if (pkm.Gen3 && pkm.WasEgg && !pkm.IsEgg) + // Fatefull generation 3 eggs lost fatefull mark after hatch + fateful = false; + if (fateful) { if (pkm.FatefulEncounter) AddLine(Severity.Valid, V323, CheckIdentifier.Fateful); @@ -2508,15 +2570,8 @@ namespace PKHeX.Core } private CheckResult[] verifyMoves(GameVersion game = GameVersion.Any) { - int minLvLG1 = 0; - if (pkm.GenNumber <= 2) - minLvLG1 = pkm.WasEgg ? 6 : (EncounterMatch as IEncounterable)?.LevelMin + 1 ?? 0; - var validLevelMoves = Legal.getValidMovesAllGens(pkm, EvoChainsAllGens, minLvLG1: minLvLG1, 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, ref validLevelMoves, ref validTMHM, ref validTutor); int[] Moves = pkm.Moves; - var res = parseMovesForEncounters(game, validLevelMoves, validTMHM, validTutor, Moves); + var res = parseMovesForEncounters(game, Moves); // Duplicate Moves Check verifyNoEmptyDuplicates(Moves, res); @@ -2525,63 +2580,129 @@ namespace PKHeX.Core return res; } - private void UptateGen1LevelUpMoves(ref List[] validLevelMoves, List DefaultMoves, int generation) + private void UptateGen1LevelUpMoves(ValidEncounterMoves EncounterMoves, int defaultLvlG1, int generation) { - switch (generation) + switch(generation) { case 1: - validLevelMoves[1] = Legal.getValidMoves(pkm, EvoChainsAllGens[1], generation: 1, minLvLG1: (EncounterMatch as IEncounterable).LevelMin, LVL: true, Tutor: false, Machine: false, MoveReminder: false).ToList(); - break; case 2: - validLevelMoves[1] = DefaultMoves; + var lvlG1 = EncounterMatch == null ? 6 : (EncounterMatch as IEncounterable).LevelMin + 1; + 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; } + } - private CheckResult[] parseMovesForEncounters(GameVersion game, List[] validLevelMoves, List[] validTMHM, List[] validTutor, int[] Moves) + private ValidEncounterMoves getEncounterValidMoves(int defaultspecies, int encounterspecies, object encounter, int encounterlevel) + { + var minLvLG1 = pkm.GenNumber <= 2 ? encounterlevel + 1 : 0; + // If encounter species is the same species from the first match, the one in variable EncounterMatch, its evolution chains is already in EvoChainsAllGens + var EvolutionChains = defaultspecies == EncounterSpecies ? EvoChainsAllGens : Legal.getEvolutionChainsAllGens(pkm, encounter); + 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() + { + EncounterSpecies = encounterspecies, + validLevelUpMoves = LevelMoves, + validTMHMMoves = TMHMMoves, + validTutorMoves = TutorMoves, + EvolutionChains = EvolutionChains, + minLvlG1 = minLvLG1 + }; + } + private List getEncountersValidMoves(List encounters, DexLevel[] vs) + { + var defaultspecies = Legal.getEncounterSpecies(pkm, vs, EncounterMatch); + var r = new List(); + 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); + 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 + 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)); + } + return r; + } + private CheckResult[] parseMovesForEncounters(GameVersion game, int[] Moves) { if (pkm.Species == 235) // special handling for Smeargle - return parseMovesForSmeargle(Moves, validLevelMoves); // Smeargle can have any moves except a few + return parseMovesForSmeargle(Moves); // Smeargle can have any moves except a few // Gather Encounters var encounters = pkm.GenNumber <= 2 ? GetEncounterMovesGBEra() : pkm.GenNumber == 3 && pkm.WasEgg ? GetEncounterMovesGen3Egg() : GetEncounterMoves(); + // it could be duplicated between EncounterMatch and EncounterStaticMatch or EncounterMatch and EventGiftMatch encounters = encounters.Distinct().ToList(); if (!encounters.Any()) // There isn't any valid encounter and wasnt an egg { - var empty = Legal.GetEmptyMovesList(EvoChainsAllGens); - var emptyegg = Legal.GetEmptyEggMovesList(); - return parseMoves(pkm.Moves, validLevelMoves, pkm.RelearnMoves, validTMHM, validTutor, new int[0], emptyegg, emptyegg, empty, new int[0], new int[0], false); + return parseMovesNoEncounters(Moves); } // Iterate over encounters bool pre3DS = pkm.GenNumber < 6; + var vs = Legal.getValidPreEvolutions(pkm).ToArray(); + // gather valid moves for encounter species + var EncountersMoves = getEncountersValidMoves(encounters, vs); CheckResult[] res = new CheckResult[4]; - var defaultGen1Moves = validLevelMoves[1]; foreach (var enc in encounters) { EncounterMatch = enc; - var EncounterMatchGen = EncounterMatch as IGeneration; - if (EncounterMatchGen != null) - UptateGen1LevelUpMoves(ref validLevelMoves, defaultGen1Moves, EncounterMatchGen.Generation); + if (pkm.GenNumber <= 2) + EncounterOriginalGB = enc; + + EncounterSpecies = Legal.getEncounterSpecies(pkm, vs, EncounterMatch); + var EncounterMoves = EncountersMoves.First(e => e.EncounterSpecies == EncounterSpecies); + EvoChainsAllGens = EncounterMoves.EvolutionChains; + if (pkm.GenNumber <= 3) - pkm.WasEgg = EncounterMatch == null || ((EncounterMatch as IEncounterable)?.EggEncounter ?? false); + pkm.WasEgg = (EncounterMatch == null) || ((EncounterMatch as IEncounterable)?.EggEncounter ?? false); + + 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 + UptateGen1LevelUpMoves(EncounterMoves, EncounterMoves.minLvlG1, EncounterMatchGen.Generation); + res = pre3DS - ? parseMovesPre3DS(game, validLevelMoves, validTMHM, validTutor, Moves) - : parseMoves3DS(game, validLevelMoves, validTMHM, validTutor, Moves); + ? parseMovesPre3DS(game, EncounterMoves.validLevelUpMoves, EncounterMoves.validTMHMMoves, EncounterMoves.validTutorMoves, Moves) + : parseMoves3DS(game, EncounterMoves.validLevelUpMoves, EncounterMoves.validTMHMMoves, EncounterMoves.validTutorMoves, Moves); if (res.All(x => x.Valid)) break; + + if (EncounterMatchGen?.Generation == 1) + // If is not valid restore generation 1 moves + EncounterMoves.validLevelUpMoves[1] = defaultG1LevelMoves; } return res; } - private CheckResult[] parseMovesForSmeargle(int[] Moves, List[] validLevelMoves) + private CheckResult[] parseMovesNoEncounters(int[] Moves) + { + EncounterSpecies = pkm.Species; + var validLevelMoves = Legal.getValidMovesAllGens(pkm, EvoChainsAllGens, minLvLG1: 1, 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, ref validLevelMoves, ref validTMHM, ref validTutor); + + var empty = Legal.GetEmptyMovesList(EvoChainsAllGens); + var emptyegg = Legal.GetEmptyEggMovesList(); + return parseMoves(pkm.Moves, validLevelMoves, pkm.RelearnMoves, validTMHM, validTutor, new int[0], emptyegg, emptyegg, empty, new int[0], new int[0], false); + } + private CheckResult[] parseMovesForSmeargle(int[] Moves) { if (!pkm.IsEgg) return parseMovesSketch(Moves); + var validLevelMoves = Legal.getValidMovesAllGens(pkm, EvoChainsAllGens, minLvLG1: 1, Tutor: false, Machine: false, RemoveTransferHM: false); // can only know sketch as egg var empty = Legal.GetEmptyMovesList(EvoChainsAllGens); var emptyegg = Legal.GetEmptyEggMovesList(); @@ -2716,7 +2837,7 @@ namespace PKHeX.Core Games = new[] { GameVersion.B2W2 }; break; } - var issplitbreed = Legal.SplitBreed.Contains(pkm.Species); + var issplitbreed = Legal.getSplitBreedGeneration(pkm).Contains(pkm.Species); foreach (var ver in Games) { var EventEggMoves = (EncounterMatch as IMoveset)?.Moves ?? new int[0]; @@ -3034,6 +3155,11 @@ namespace PKHeX.Core // Example: Azurill levelup/tmhm/tutor move, incompatible with Marill egg moves res[m] = new CheckResult(Severity.Invalid, string.Format(V376, splitbreedspecies0, splitbreedspecies1), CheckIdentifier.Move); } + else if (EncounterMatch == null && !EggMovesSplitLearned[0].Any() && !IncenseMovesLearned.Any()) + { + // Day care egg with no incense species exclusive moves, we can assume non-incense egg, that means there is no need to check if evolution from incense species is valid + EncounterSpecies = Legal.getBaseEggSpecies(pkm, 1); + } } } @@ -3062,7 +3188,7 @@ namespace PKHeX.Core foreach (int m in Gen1MovesLearned) res[m] = new CheckResult(Severity.Invalid, V335, CheckIdentifier.Move); - if (gen == 1 && pkm.Format == 1 && !Legal.AllowGBCartEra) + if (gen == 1 && pkm.Format == 1 && pkm.Gen1_NotTradeback) { // Check moves learned at the same level in red/blue and yellow, illegal because there is no move reminder // Only two incompatibilites and only there are no illegal combination if generation 2 or 7 are included in the analysis @@ -3083,7 +3209,7 @@ namespace PKHeX.Core return res; } - if (pkm.Species == 292 && (EncounterMatch as IEncounterable)?.Species != 292) + if (pkm.Species == 292 && EncounterSpecies != 292) { // Ignore Shedinja if the Encounter was also a Shedinja, assume null Encounter as a Nincada egg // Check Shedinja evolved moves from Ninjask after egg moves @@ -3209,7 +3335,7 @@ namespace PKHeX.Core // 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) || (EncounterMatch as IEncounterable)?.Species == pkm.Species) + 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 diff --git a/PKHeX/Legality/Core.cs b/PKHeX/Legality/Core.cs index 60876b5a0..1c6b57b19 100644 --- a/PKHeX/Legality/Core.cs +++ b/PKHeX/Legality/Core.cs @@ -14,6 +14,7 @@ namespace PKHeX.Core /// Setting to specify if an analysis should permit data sourced from the physical cartridge era of GameBoy games. public static bool AllowGBCartEra = false; + public static bool AllowGen1Tradeback = false; /// Setting to specify if the e-berry index item is an eningma berry or a e-reader berry and the name of the e-reader berry public static bool EReaderBerryIsEnigma = true; @@ -554,6 +555,7 @@ namespace PKHeX.Core MarkEncountersGeneration(ref SlotsRBY, 1); MarkEncountersGeneration(ref StaticRBY, 1); Evolves1 = new EvolutionTree(new[] { Resources.evos_rby }, GameVersion.RBY, PersonalTable.Y, MaxSpeciesID_1); + FixPersonalTableY(); } // Gen 2 { @@ -758,6 +760,13 @@ namespace PKHeX.Core } } + private static void FixPersonalTableY() + { + // Personal Table from Yellow do not have yellow catch rate for Pikachu and Kadabra, have RedBlue instead + PersonalTable.Y[25].CatchRate = 163; // Pikachu + PersonalTable.Y[64].CatchRate = 96; // Kadabra + } + // Moves internal static int[] getMinLevelLearnMove(int species, int Generation, List moves) { @@ -994,7 +1003,7 @@ namespace PKHeX.Core } } } - else + else if (validLevelMoves.Length >= 3) { int tradeback = pkm.Format == 2 ? 1 : 2; validLevelMoves[tradeback]?.RemoveAll(x => FutureMoves.Contains(x)); @@ -1227,7 +1236,7 @@ namespace PKHeX.Core } internal static List[] getBaseEggMoves(PKM pkm, GameVersion gameSource, int lvl) { - if (SplitBreed.Contains(pkm.Species)) + if (getSplitBreedGeneration(pkm.GenNumber).Contains(pkm.Species)) return new[] { getBaseEggMoves(pkm, 0, gameSource,lvl).ToList(), @@ -1237,7 +1246,7 @@ namespace PKHeX.Core } internal static List[] getEggMoves(PKM pkm, GameVersion Version) { - if (SplitBreed.Contains(pkm.Species)) + if (getSplitBreedGeneration(pkm.GenNumber).Contains(pkm.Species)) return new[] { getEggMoves(pkm, getBaseEggSpecies(pkm, 0), 0, Version).ToList(), @@ -1388,6 +1397,18 @@ namespace PKHeX.Core if (e.Form != pkm.AltForm && !e.SkipFormCheck && !getCanFormChange(pkm, e.Species)) continue; + if (pkm.Format == 1 && pkm.Gen1_NotTradeback) + { + var catch_rate = (pkm as PK1).Catch_Rate; + // Pure gen 1, trades can be filter by catch rate + if ((pkm.Species == 25 || pkm.Species == 26) && catch_rate == 190) + // Red Blue Pikachu, is not a static encounter + continue; + + if (catch_rate != PersonalTable.RB[e.Species].CatchRate && catch_rate != PersonalTable.Y[e.Species].CatchRate) + continue; + } + // Defer to EC/PID check // if (e.Shiny != null && e.Shiny != pkm.IsShiny) // continue; @@ -1464,14 +1485,17 @@ namespace PKHeX.Core } internal static GameVersion[] getGen1GameEncounter(PKM pk) { - if (pk.Format != 2 || AllowGBCartEra) + if (pk.Format != 1 || !pk.Gen1_NotTradeback) return new[] { GameVersion.RD, GameVersion.YW }; if (25 <= pk.Species && pk.Species <= 26) // Yellow Pikachu detected by its special catch rate - return new[] { (((PK1)pk).Catch_Rate == 163) ? GameVersion.YW : GameVersion.RD }; + return new[] { ((pk as PK1).Catch_Rate == 163) ? GameVersion.YW : GameVersion.RD }; if (64 <= pk.Species && pk.Species <= 65) // Yellow Kadabra detected by its special catch rate - return new[] { (((PK1)pk).Catch_Rate == 96) ? GameVersion.YW : GameVersion.RD }; + return new[] { ((pk as PK1).Catch_Rate == 96) ? GameVersion.YW : GameVersion.RD }; + if (148 <= pk.Species && pk.Species <= 149 && ((pk as PK1).Catch_Rate == 27)) + // Yellow Dragonair detected by its special catch rate, is have another catch rate could be red/blue dratini or yellow dratini + return new[] { GameVersion.YW }; return new[] { GameVersion.RD, GameVersion.YW }; } internal static IEnumerable getInitialMovesGBEncounter(int species, int lvl, GameVersion ver) @@ -1592,7 +1616,7 @@ namespace PKHeX.Core } internal static int getRequiredMoveCount(PKM pk, int[] moves, List[] learn, List[] tmhm, List[] tutor, int[] initialmoves) { - if (pk.Format != 1 || AllowGBCartEra) // No MoveDeleter + if (pk.Format != 1 || !pk.Gen1_NotTradeback) // No MoveDeleter return 1; // Move deleter exits, slots from 2 onwards can allways be empty int required = getRequiredMoveCount(pk, moves, learn, initialmoves); @@ -1618,20 +1642,22 @@ namespace PKHeX.Core private static int getRequiredMoveSlotsRegular(PKM pk, int[] moves, List[] learn, int[] initialmoves) { int species = pk.Species; + int catch_rate = (pk as PK1).Catch_Rate; // 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 == 011 || species == 012) && catch_rate == 120) { - if (!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)); + // Captured as Metapod without Caterpie moves + return initialmoves.Union(learn[1]).Distinct().Count(lm => lm != 0 && !G1CaterpieMoves.Contains(lm)); + // There is no valid Butterfree encounter in generation 1 games } - if (species == 014 || species == 015) + if ((species == 014 || species == 015) && (catch_rate == 45 || catch_rate == 120)) { - if (species == 15 && !moves.Any(m => G1KakunaMoves.Contains(m))) // Captured as Beedril without Weedle and Kakuna moves + if (species == 15 && catch_rate == 45) // 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)); + // 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 @@ -1656,6 +1682,7 @@ namespace PKHeX.Core } private static int getRequiredMoveCountDecrement(PKM pk, int[] moves, List[] learn, int[] initialmoves) { + int catch_rate = (pk as PK1).Catch_Rate; 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 @@ -1672,9 +1699,9 @@ namespace PKHeX.Core if (064 == pk.Species || pk.Species == 065) { - if (!moves.Contains(134))// Initial Yellow Kadabra Kinesis + if (catch_rate != 100)// Initial Yellow Kadabra Kinesis (move 134) usedslots--; - if (pk.CurrentLevel < 10 && !moves.Contains(50)) // Kadabra Disable, not learned until 20 if captured as Abra + if (catch_rate == 200 && pk.CurrentLevel < 20) // Kadabra Disable, not learned until 20 if captured as Abra (move 50) usedslots--; } if (104 == pk.Species || pk.Species == 105) // Cubone and Marowak @@ -1741,7 +1768,8 @@ namespace PKHeX.Core switch (gameSource) { case GameVersion.RBY: - return getValidEncounterTradeVC1(pkm, p, TradeGift_RBY); + var table = !AllowGen1Tradeback ? TradeGift_RBY_NoTradeback : TradeGift_RBY_Tradeback; + return getValidEncounterTradeVC1(pkm, p, table); case GameVersion.GSC: return getValidEncounterTradeVC2(pkm, p); default: @@ -1750,13 +1778,8 @@ namespace PKHeX.Core } private static EncounterTrade getValidEncounterTradeVC2(PKM pkm, DexLevel[] p) { - // Check RBY trades with loosened level criteria. - var z = getValidEncounterTradeVC1(pkm, p, TradeGift_RBY_2); - if (z != null) - return z; - // Check GSC trades. Reuse generic table fetch-match - z = getValidEncounterTradeVC1(pkm, p, TradeGift_GSC); + var z = getValidEncounterTradeVC1(pkm, p, TradeGift_GSC); // Filter Criteria if (z?.Gender != pkm.Gender) @@ -1781,12 +1804,19 @@ namespace PKHeX.Core return null; if (z.Level > pkm.CurrentLevel) // minimum required level return null; + if(pkm.Format == 1 && pkm.Gen1_NotTradeback) + { + // Even if the in game trade use the tables with source pokemon allowing generaion 2 games, the traded pokemon could be a non-tradeback pokemon + var catch_rate = (pkm as PK1).Catch_Rate; + if (catch_rate != PersonalTable.RB[z.Species].CatchRate && catch_rate != PersonalTable.Y[z.Species].CatchRate) + return null; + } return z; } private static GBEncounterData getEncounter12(PKM pkm, GameVersion game) { var gen = game == GameVersion.GSC ? 2 : 1; - bool WasEgg = game == GameVersion.GSC && getWasEgg23(pkm) && !NoHatchFromEgg.Contains(pkm.Species); + bool WasEgg = !pkm.Gen1_NotTradeback && game == GameVersion.GSC && getWasEgg23(pkm) && !NoHatchFromEgg.Contains(pkm.Species); if (WasEgg) { // Further Filtering @@ -1835,10 +1865,10 @@ namespace PKHeX.Core return new GBEncounterData(pkm, gen, t); return null; } - internal static List getEncounter12(PKM pkm, bool gen2) + internal static List getEncounter12(PKM pkm) { - var g1 = pkm.IsEgg || pkm.HasOriginalMetLocation ? null : getEncounter12(pkm, GameVersion.RBY); - var g2 = gen2 ? getEncounter12(pkm, GameVersion.GSC) : null; + var g1 = pkm.Gen2_NotTradeback ? null : getEncounter12(pkm, GameVersion.RBY); + var g2 = pkm.Gen1_NotTradeback ? null : getEncounter12(pkm, GameVersion.GSC); if (g1 == null && g2 == null) return null; if (g1 == null || g2 == null) @@ -2591,9 +2621,10 @@ namespace PKHeX.Core internal static DexLevel[][] getEvolutionChainsAllGens(PKM pkm, object Encounter) { var CompleteEvoChain = getEvolutionChain(pkm, Encounter).ToArray(); - int size = Math.Max(pkm.Format, 2); - DexLevel[][] GensEvoChains = new DexLevel[size + 1][]; - for (int i = 0; i <= size; i++) + int maxgen = pkm.Format == 1 && !pkm.Gen1_NotTradeback ? 2 : pkm.Format; + int mingen = pkm.Format == 2 && !pkm.Gen2_NotTradeback ? 1 : pkm.GenNumber; + DexLevel[][] GensEvoChains = new DexLevel[maxgen + 1][]; + for (int i = 0; i <= maxgen; i++) GensEvoChains[i] = new DexLevel[0]; if (pkm.Species == 0 || pkm.Format > 2 && pkm.GenU) // Illegal origin or empty pokemon, return only chain for current format @@ -2611,15 +2642,13 @@ namespace PKHeX.Core } int lvl = pkm.CurrentLevel; - int maxgen = pkm.Format <= 2 ? 2 : pkm.Format; - int mingen = pkm.VC2 || pkm.Format <= 2 ? 1 : pkm.GenNumber; // Iterate generations backwards because level will be decreased from current level in each generation for (int gen = maxgen; gen >= mingen; gen--) { - if ((pkm.Gen1 || pkm.VC1) && pkm.Format > 2 && 2 <= gen && gen <= 6) + if (pkm.GenNumber == 1 && pkm.Gen1_NotTradeback && gen == 2) continue; - if ((pkm.Gen2 || pkm.VC2) && 3 <= gen && gen <= 6) + if (pkm.GenNumber <= 2 && 3 <= gen && gen <= 6) continue; if (!pkm.HasOriginalMetLocation && pkm.Format > 2 && gen < pkm.Format && gen <= 4 && lvl > pkm.Met_Level) { @@ -2660,6 +2689,10 @@ namespace PKHeX.Core //For example a gen3 charizar in format 7 with current level 36 and met level 36 //chain level for charmander is 35, is bellow met level GensEvoChains[gen] = GensEvoChains[gen].Where(e => e.Level >= getMinLevelGeneration(pkm,gen)).ToArray(); + + if (gen == 1 && GensEvoChains[gen].LastOrDefault()?.Species > MaxSpeciesID_1) + // Remove generation 2 pre-evolutions + GensEvoChains[gen] = GensEvoChains[gen].Take(GensEvoChains[gen].Length - 1).ToArray(); } return GensEvoChains; } @@ -2913,6 +2946,26 @@ namespace PKHeX.Core // Get Valid levels IEnumerable vs = getValidPreEvolutions(pkm, maxspeciesorigin: maxspeciesorigin, lvl: ignoreLevel ? 100 : -1, skipChecks:ignoreLevel); + bool IsRGBKadabra = false; + if (pkm.Format == 1 && pkm.Gen1_NotTradeback) + { + // Pure gen 1, slots can be filter by catch rate + if ((pkm.Species == 25 || pkm.Species == 26) && (pkm as PK1).Catch_Rate == 163) + // Yellow Pikachu, is not a wild encounter + return slotdata; + if ((pkm.Species == 64 || pkm.Species == 65) && (pkm as PK1).Catch_Rate == 96) + // Yellow Kadabra, ignore Abra encounters + vs = vs.Where(s => s.Species == 64); + if ((pkm.Species == 148 || pkm.Species == 149) && (pkm as PK1).Catch_Rate == 27) + // Yellow Dragonair, ignore Dratini encounters + vs = vs.Where(s => s.Species == 148); + else + { + IsRGBKadabra = (pkm.Species == 64 || pkm.Species == 65) && (pkm as PK1).Catch_Rate == 100; + vs = vs.Where(s => (pkm as PK1).Catch_Rate == PersonalTable.RB[s.Species].CatchRate); + } + } + // Get slots where pokemon can exist bool ignoreSlotLevel = ignoreLevel; IEnumerable slots = loc.Slots.Where(slot => vs.Any(evo => evo.Species == slot.Species && (ignoreSlotLevel || evo.Level >= slot.LevelMin - df))); @@ -2931,7 +2984,11 @@ namespace PKHeX.Core encounterSlots = slots.Where(slot => slot.LevelMin <= lvl).ToList(); if (gen <= 2) - { + { + if (IsRGBKadabra) + //Red Kadabra slots : Level 49 and 51 in RGB, but level 20 and 27 in Yellow + encounterSlots = encounterSlots.Where(slot => slot.LevelMin >= 49).ToList(); + // For gen 1 and 2 return Minimum level slot // Minimum level is needed to check available moves, because there is no move reminder in gen 1, // There are moves in the level up table that cant be legally obtained @@ -3031,7 +3088,24 @@ namespace PKHeX.Core } return slotLocations; } - private static IEnumerable getValidPreEvolutions(PKM pkm, int maxspeciesorigin = -1, int lvl = -1, bool skipChecks = false) + internal static int getEncounterLevel(PKM pkm, object encounter) + { + return (encounter as IEncounterable[])?.Min(e => e.LevelMin) ?? + (encounter as IEncounterable)?.LevelMin ?? + (pkm.GenNumber <= 3 ? 5 : 1 ); //egg + } + internal static int getEncounterSpecies(PKM pkm, DexLevel[] vs, object encounter) + { + if (encounter is int) + return (int)encounter; + if (encounter is IEncounterable[]) + 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 + return vs.Last().Species; + } + internal static IEnumerable getValidPreEvolutions(PKM pkm, int maxspeciesorigin = -1, int lvl = -1, bool skipChecks = false) { if (lvl < 0) lvl = pkm.CurrentLevel; @@ -3046,8 +3120,10 @@ namespace PKHeX.Core new DexLevel { Species = 292, Level = lvl, MinLevel = 20 }, new DexLevel { Species = 290, Level = lvl-1, MinLevel = 1 } }; + if (maxspeciesorigin == -1 && pkm.InhabitedGeneration(2) && pkm.GenNumber == 1) + maxspeciesorigin = MaxSpeciesID_2; - var et = getEvolutionTable(pkm); + var et = maxspeciesorigin == MaxSpeciesID_2 ? getEvolutionTable(2) : getEvolutionTable(pkm); return et.getValidPreEvolutions(pkm, lvl: lvl, maxSpeciesOrigin: maxspeciesorigin, skipChecks: skipChecks); } private static IEnumerable getStatic(PKM pkm, IEnumerable table, int maxspeciesorigin =-1, int lvl = -1) diff --git a/PKHeX/Legality/LegalityCheckStrings.cs b/PKHeX/Legality/LegalityCheckStrings.cs index 7f4d04993..db49da2fb 100644 --- a/PKHeX/Legality/LegalityCheckStrings.cs +++ b/PKHeX/Legality/LegalityCheckStrings.cs @@ -63,6 +63,11 @@ namespace PKHeX.Core public static string V372 { get; set; } = "{0} Berry"; public static string V380 { get; set; } = "Encounter Type match encounter."; public static string V382 { get; set; } = "Encounter Type not implemented for pokemon encounter."; + public static string V391 { get; set; } = "Porygon with valid Type A and B values."; + public static string V392 { get; set; } = "Valid Type A, match species type."; + public static string V393 { get; set; } = "Valid Type B, match species type."; + public static string V394 { get; set; } = "Catch rate match a valid held item from generation 2"; + public static string V398 { get; set; } = "Catch Rate match a species from pokemon evolution chain."; #endregion #region Legality Check Result Strings @@ -374,6 +379,15 @@ namespace PKHeX.Core public static string V379 {get; set;} = "{0} Inherited Move. Incompatible with {1} inherited moves."; public static string V381 {get; set;} = "Encounter Type does not match encounter."; public static string V385 {get; set;} = "Moves combinations is not compatible with {0} evolution."; + public static string V386 {get; set;} = "Porygon with invalid Type A value."; + public static string V387 {get; set;} = "Porygon with invalid Type B value."; + public static string V388 {get; set;} = "Porygon with invalid Type A and B values. Does not a match a valid type combination."; + public static string V389 {get; set;} = "Invalid Type A, does not match species type."; + public static string V390 {get; set;} = "Invalid Type B, does not match species type."; + public static string V395 {get; set;} = "Catch rate does not match a valid held item from generation 2"; + public static string V396 {get; set;} = "Catch rate match species without encounters. Expected a preevolution catch rate."; + public static string V397 {get; set;} = "Catch rate does not match any species from pokemon evolution chain."; + public static string V399 {get; set;} = "Catch rate does not match any species from pokemon evolution chain or any generation 2 held items."; #endregion } diff --git a/PKHeX/Legality/Structures/TradebackType.cs b/PKHeX/Legality/Structures/TradebackType.cs new file mode 100644 index 000000000..02d28c12f --- /dev/null +++ b/PKHeX/Legality/Structures/TradebackType.cs @@ -0,0 +1,10 @@ +namespace PKHeX.Core +{ + public enum TradebackType + { + Any, + Gen1_NotTradeback, + Gen2_NotTradeback, + WasTradeback + } +} diff --git a/PKHeX/Legality/Structures/ValidEncounterMoves.cs b/PKHeX/Legality/Structures/ValidEncounterMoves.cs new file mode 100644 index 000000000..2eb6e9655 --- /dev/null +++ b/PKHeX/Legality/Structures/ValidEncounterMoves.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Linq; + +namespace PKHeX.Core +{ + public class ValidEncounterMoves + { + public int EncounterSpecies { get; set; } + public DexLevel[][] EvolutionChains { get; set; } + public List[] validLevelUpMoves { get; set; } + public List[] validTMHMMoves { get; set; } + public List[] validTutorMoves { get; set; } + public int minLvlG1 { get; set; } + } +} diff --git a/PKHeX/Legality/Tables1.cs b/PKHeX/Legality/Tables1.cs index 9eab37f8e..3b31230ee 100644 --- a/PKHeX/Legality/Tables1.cs +++ b/PKHeX/Legality/Tables1.cs @@ -112,70 +112,63 @@ namespace PKHeX.Core new EncounterStatic { Species = 054, Level = 15, Moves = new [] { 133, 10 }, Version = GameVersion.Stadium }, // Stadium Psyduck (Amnesia) new EncounterStatic { Species = 151, Level = 5, IVs = new [] {15,15,15,15,15,15}, Version = GameVersion.VCEvents }, // Event Mew }; - internal static readonly EncounterTrade[] TradeGift_RBY = - { + internal static readonly EncounterTrade[] TradeGift_RBY_Common = + { // Species & Minimum level (legal) possible to acquire at. //new EncounterTrade { Species = 122, Generation = 1, Level = 06 }, // Mr. Mime - Game Corner Abra new EncounterTrade { Species = 032, Generation = 1, Level = 02 }, // Nidoran♂ - Wild Nidoran♀ new EncounterTrade { Species = 029, Generation = 1, Level = 02 }, // Nidoran♀ - Wild Nidoran♂ new EncounterTrade { Species = 030, Generation = 1, Level = 16 }, // Nidorina - Evolve Nidorino new EncounterTrade { Species = 108, Generation = 1, Level = 15 }, // Lickitung - Surf Slowbro - new EncounterTrade { Species = 124, Generation = 1, Level = 15 }, // Jynx - Fish Poliwhirl (GSC: 10) new EncounterTrade { Species = 083, Generation = 1, Level = 02 }, // Farfetch’d - Wild Spearow new EncounterTrade { Species = 101, Generation = 1, Level = 03 }, // Electrode - Wild Raichu - new EncounterTrade { Species = 114, Generation = 1, Level = 13 }, // Tangela - Wild Venonat (GSC: 5) - new EncounterTrade { Species = 086, Generation = 1, Level = 28 }, // Seel - Wild Ponyta (GSC: 6) new EncounterTrade { Species = 122, Generation = 1, Level = 03 }, // Mr. Mime - Wild Jigglypuff new EncounterTrade { Species = 060, Generation = 1, Level = 02 }, // Poliwag - Wild Rattata + //new EncounterTrade { Species = 083, Generation = 1, Level = 02 }, // Farfetch’d - Wild Pidgey + new EncounterTrade { Species = 079, Generation = 1, Level = 22 }, // Slowpoke - Wild Seel + + new EncounterTrade { Species = 051, Generation = 1, Level = 15 }, // Dugtrio - Trade Lickitung + new EncounterTrade { Species = 047, Generation = 1, Level = 13 }, // Parasect - Trade Tangela + }; + internal static readonly EncounterTrade[] TradeGift_RBY_NoTradeback = TradeGift_RBY_Common.Concat(new[] + { + // Species & Minimum level (legal) possible to acquire at. + new EncounterTrade { Species = 124, Generation = 1, Level = 15 }, // Jynx - Fish Poliwhirl (GSC: 10) + new EncounterTrade { Species = 114, Generation = 1, Level = 13 }, // Tangela - Wild Venonat (GSC: 5) + new EncounterTrade { Species = 086, Generation = 1, Level = 28 }, // Seel - Wild Ponyta (GSC: 6) + new EncounterTrade { Species = 115, Generation = 1, Level = 42 }, // Kangaskhan - Evolve Rhydon (GSC: 30) new EncounterTrade { Species = 128, Generation = 1, Level = 28 }, // Tauros - Evolve Persian (GSC: 18) new EncounterTrade { Species = 093, Generation = 1, Level = 20 }, // Haunter - Trade Machoke (GSC: 10) - //new EncounterTrade { Species = 083, Generation = 1, Level = 02 }, // Farfetch’d - Wild Pidgey new EncounterTrade { Species = 075, Generation = 1, Level = 16 }, // Graveler - Evolve Kadabra (GSC: 15) - new EncounterTrade { Species = 079, Generation = 1, Level = 22 }, // Slowpoke - Wild Seel new EncounterTrade { Species = 098, Generation = 1, Level = 15 }, // Krabby - Wild Growlithe (GSC: 5) //new EncounterTrade { Species = 122, Generation = 1, Level = 08 }, // Mr. Mime - Wild Clefairy (GSC: 6) new EncounterTrade { Species = 067, Generation = 1, Level = 20 }, // Machoke - Wild Cubone (GSC: 10) - new EncounterTrade { Species = 051, Generation = 1, Level = 15 }, // Dugtrio - Trade Lickitung - new EncounterTrade { Species = 047, Generation = 1, Level = 13 }, // Parasect - Trade Tangela new EncounterTrade { Species = 112, Generation = 1, Level = 15 }, // Rhydon - Surf Golduck (GSC: 10) new EncounterTrade { Species = 087, Generation = 1, Level = 15 }, // Dewgong - Wild Growlithe (GSC: 5) new EncounterTrade { Species = 089, Generation = 1, Level = 25 }, // Muk - Wild Kangaskhan (GSC: 14) - }; - internal static readonly EncounterTrade[] TradeGift_RBY_2 = + }).ToArray(); + internal static readonly EncounterTrade[] TradeGift_RBY_Tradeback = TradeGift_RBY_Common.Concat(new[] { // Trade gifts that can be obtained at a lower level due to the requested Pokémon being a lower level in GSC - //new EncounterTrade { Species = 122, Generation = 1, Level = 06 }, // Mr. Mime - Game Corner Abra - //new EncounterTrade { Species = 032, Generation = 1, Level = 02 }, // Nidoran♂ - Wild Nidoran♀ - //new EncounterTrade { Species = 029, Generation = 1, Level = 02 }, // Nidoran♀ - Wild Nidoran♂ - //new EncounterTrade { Species = 030, Generation = 1, Level = 16 }, // Nidorina - Evolve Nidorino - //new EncounterTrade { Species = 108, Generation = 1, Level = 15 }, // Lickitung - Surf Slowbro new EncounterTrade { Species = 124, Generation = 1, Level = 10 }, // Jynx - Fish Poliwhirl (RBY: 15) - //new EncounterTrade { Species = 083, Generation = 1, Level = 02 }, // Farfetch’d - Wild Spearow - //new EncounterTrade { Species = 101, Generation = 1, Level = 03 }, // Electrode - Wild Raichu new EncounterTrade { Species = 114, Generation = 1, Level = 05 }, // Tangela - Wild Venonat (RBY: 13) new EncounterTrade { Species = 086, Generation = 1, Level = 05 }, // Seel - Egg Ponyta (RBY: 28) - //new EncounterTrade { Species = 122, Generation = 1, Level = 03 }, // Mr. Mime - Wild Jigglypuff - //new EncounterTrade { Species = 060, Generation = 1, Level = 02 }, // Poliwag - Wild Rattata new EncounterTrade { Species = 115, Generation = 1, Level = 30 }, // Kangaskhan - Evolve Rhydon (RBY: 42) new EncounterTrade { Species = 128, Generation = 1, Level = 18 }, // Tauros - Evolve Persian (RBY: 28) new EncounterTrade { Species = 093, Generation = 1, Level = 10 }, // Haunter - Trade Machoke (RBY: 20) - //new EncounterTrade { Species = 083, Generation = 1, Level = 02 }, // Farfetch’d - Wild Pidgey new EncounterTrade { Species = 075, Generation = 1, Level = 15 }, // Graveler - Evolve Kadabra (RBY: 16) - //new EncounterTrade { Species = 079, Generation = 1, Level = 22 }, // Slowpoke - Wild Seel new EncounterTrade { Species = 098, Generation = 1, Level = 05 }, // Krabby - Egg Growlithe (RBY: 15) //new EncounterTrade { Species = 122, Generation = 1, Level = 08 }, // Mr. Mime - Wild Clefairy (RBY: 6) new EncounterTrade { Species = 067, Generation = 1, Level = 05 }, // Machoke - Egg Cubone (RBY: 20) - //new EncounterTrade { Species = 051, Generation = 1, Level = 15 }, // Dugtrio - Trade Lickitung - //new EncounterTrade { Species = 047, Generation = 1, Level = 13 }, // Parasect - Trade Tangela new EncounterTrade { Species = 112, Generation = 1, Level = 10 }, // Rhydon - Surf Golduck (RBY: 15) new EncounterTrade { Species = 087, Generation = 1, Level = 05 }, // Dewgong - Egg Growlithe (RBY: 15) new EncounterTrade { Species = 089, Generation = 1, Level = 05 }, // Muk - Egg Kangaskhan (RBY: 25) - }; + }).ToArray(); internal static readonly EncounterArea FishOldGood_RBY = new EncounterArea { Location = -1, Slots = new EncounterSlot[] { new EncounterSlot1 {Species = 129, LevelMin = 05, LevelMax = 05, Type = SlotType.Old_Rod, Rate = -1, }, // Magikarp @@ -197,5 +190,13 @@ namespace PKHeX.Core { 25, 26, 29, 30, 31, 32, 33, 34, 36, 38, 40, 59, 91, 103, 114, 121, }; + internal static readonly int[] Types_Gen1 = + { + 0, 1, 2, 3, 4, 5, 7, 8, 20, 21, 22, 23, 24, 25, 26 + }; + internal static readonly int[] Species_NotAvailable_CatchRate = + { + 12, 18, 31, 34, 36, 38, 45, 53, 59, 62, 65, 68, 71, 76, 78, 91, 94, 103, 121 + }; } } diff --git a/PKHeX/PKHeX.Core.csproj b/PKHeX/PKHeX.Core.csproj index 9c463e72a..60b211d6f 100644 --- a/PKHeX/PKHeX.Core.csproj +++ b/PKHeX/PKHeX.Core.csproj @@ -175,6 +175,8 @@ + + diff --git a/PKHeX/PKM/PK1.cs b/PKHeX/PKM/PK1.cs index d62c40727..7282a4592 100644 --- a/PKHeX/PKM/PK1.cs +++ b/PKHeX/PKM/PK1.cs @@ -141,12 +141,11 @@ namespace PKHeX.Core get { return PKX.getG1Species(Data[0]); } set { - int currentRate = PersonalInfo.CatchRate; Data[0] = (byte)PKX.setG1Species(value); // Before updating catch rate, check if non-standard - if (Catch_Rate == currentRate) - Catch_Rate = PersonalInfo.CatchRate; + if (!CatchRateIsItem) + Catch_Rate = PersonalTable.RB[Legal.getBaseSpecies(this)].CatchRate; Type_A = PersonalInfo.Types[0]; Type_B = PersonalInfo.Types[1]; } @@ -273,6 +272,7 @@ namespace PKHeX.Core public override int CNT_Tough { get { return 0; } set { } } public override int CNT_Sheen { get { return 0; } set { } } #endregion + public bool CatchRateIsItem = false; public PK2 convertToPK2() { diff --git a/PKHeX/PKM/PKM.cs b/PKHeX/PKM/PKM.cs index b8ca0d8dd..d33782662 100644 --- a/PKHeX/PKM/PKM.cs +++ b/PKHeX/PKM/PKM.cs @@ -310,7 +310,8 @@ namespace PKHeX.Core if (Gen3) return 3; if (Gen2) return Format; // 2 if (Gen1) return Format; // 1 - if (VC) return 1; + if (VC1) return 1; + if (VC2) return 2; return -1; } } @@ -435,6 +436,9 @@ namespace PKHeX.Core } // Legality Extensions + public TradebackType TradebackStatus { get; set; } = TradebackType.Any; + public bool Gen1_NotTradeback => TradebackStatus == TradebackType.Gen1_NotTradeback; + public bool Gen2_NotTradeback => TradebackStatus == TradebackType.Gen2_NotTradeback; public virtual bool WasLink => false; private bool _WasEgg; public virtual bool WasEgg @@ -517,9 +521,6 @@ namespace PKHeX.Core if (Format == Generation) return true; - if (Format < Generation) - return false; // Future - if (!IsOriginValid) return false; @@ -527,11 +528,22 @@ namespace PKHeX.Core if (Legal.getMaxSpeciesOrigin(GenNumber) < species && !Legal.getFutureGenEvolutions(GenNumber).Contains(species)) return false; + // Trade generation 1 -> 2 + if (Format == 2 && Generation == 1 && !Gen2_NotTradeback) + return true; + + // Trade generation 2 -> 1 + if (Format == 1 && Generation == 2 && !Gen1_NotTradeback) + return true; + + if (Format < Generation) + return false; // Future + int gen = GenNumber; switch (Generation) { - case 1: - case 2: return Format <= 2 || VC; + case 1: return Format == 1 || VC1; + case 2: return Format == 2 || VC2; case 3: return Gen3; case 4: return 3 <= gen && gen <= 4; case 5: return 3 <= gen && gen <= 5; diff --git a/PKHeX/PersonalInfo/PersonalTable.cs b/PKHeX/PersonalInfo/PersonalTable.cs index 9aec81298..3883242cd 100644 --- a/PKHeX/PersonalInfo/PersonalTable.cs +++ b/PKHeX/PersonalInfo/PersonalTable.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; namespace PKHeX.Core { @@ -180,5 +181,9 @@ namespace PKHeX.Core } return result; } + public bool IsValidTypeCombination(int Type1, int Type2) + { + return Table.Any(p => p.Types[0] == Type1 && p.Types[1] == Type2); + } } } diff --git a/PKHeX/Resources/text/en/LegalityCheckStrings_en.txt b/PKHeX/Resources/text/en/LegalityCheckStrings_en.txt index ca3bc4830..759da4af2 100644 --- a/PKHeX/Resources/text/en/LegalityCheckStrings_en.txt +++ b/PKHeX/Resources/text/en/LegalityCheckStrings_en.txt @@ -35,6 +35,11 @@ V362 = Default move in generation {0}. V372 = {0} Berry V380 = Encounter Type match encounter. V382 = Encounter Type not implemented for pokemon encounter. +V391 = Porygon with valid Type A and B values. +V392 = Valid Type A, match species type. +V393 = Valid Type B, match species type. +V394 = Catch rate match a valid held item from generation 2. +V398 = Catch Rate match any species from pokemon evolution chain. V203 = Genderless Pokémon should not have a gender. V201 = Encryption Constant is not set. V204 = Held item is unreleased. @@ -315,4 +320,13 @@ V377 = Egg Move. Not expected in a gift egg. V378 = Inherited move learned by Level-up. Not expected in a gift egg. V379 = {0} Inherited Move. Incompatible with {1} inherited moves. V381 = Encounter Type does not match encounter. -V385 = Moves combinations is not compatible with {0} evolution. \ No newline at end of file +V385 = Moves combinations is not compatible with {0} evolution. +V386 = Porygon with invalid Type A value. +V387 = Porygon with invalid Type B value. +V388 = Porygon with invalid Type A and B values. Does not a match a valid type combination. +V389 = Invalid Type A, does not match species type. +V390 = Invalid Type B, does not match species type. +V395 = Catch rate does not match a valid held item from generation 2. +V396 = Catch rate match species without encounters. Expected a preevolution catch rate. +V397 = Catch rate does not match any species from pokemon evolution chain. +V399 = Catch rate does not match any species from pokemon evolution chain or any generation 2 held items. \ No newline at end of file diff --git a/PKHeX/Resources/text/ko/LegalityCheckStrings_ko.txt b/PKHeX/Resources/text/ko/LegalityCheckStrings_ko.txt index 5e4344db5..ebed5ff74 100644 --- a/PKHeX/Resources/text/ko/LegalityCheckStrings_ko.txt +++ b/PKHeX/Resources/text/ko/LegalityCheckStrings_ko.txt @@ -35,6 +35,12 @@ V362 = {0}세대 기본 기술입니다. V372 = {0}열매 V380 = Encounter Type match encounter. V382 = Encounter Type not implemented for pokemon encounter. +V382 = Encounter Type not implemented for pokemon encounter. +V391 = Porygon with valid Type A and B values. +V392 = Valid Type A, match species type. +V393 = Valid Type B, match species type. +V394 = Catch rate match a valid held item from generation 2. +V398 = Catch rate match any species from pokemon evolution chain. V203 = 무성 포켓몬은 성별을 가질 수 없습니다. V201 = 암호화 상수가 설정되어 있지 않습니다. V204 = 지닌 물건이 배포되지 않은 물건입니다. @@ -314,4 +320,13 @@ V377 = 자력기입니다. 이벤트 알에서 예상된 기술이 아닙니다. V378 = 유전받은 레벨업 기술입니다. 이벤트 알에서 예상된 기술이 아닙니다. V379 = 유전받은 {0} 기술입니다. 유전받은 {1} 기술과 함께 존재할 수 없습니다. V381 = Encounter Type does not match encounter. -V385 = Moves combinations is not compatible with {0} evolution. \ No newline at end of file +V385 = Moves combinations is not compatible with {0} evolution. +V386 = Porygon with invalid Type A value. +V387 = Porygon with invalid Type B value. +V388 = Porygon with invalid Type A and B values. Does not a match a valid type combination. +V389 = Invalid Type A, does not match species type. +V390 = Invalid Type B, does not match species type. +V395 = Catch rate does not match a valid held item from generation 2. +V396 = Catch rate match species without encounters. Expected a preevolution catch rate. +V397 = Catch rate does not match any species from pokemon evolution chain. +V399 = Catch rate does not match any species from pokemon evolution chain or any generation 2 held items. \ No newline at end of file diff --git a/PKHeX/Resources/text/zh/LegalityCheckStrings_zh.txt b/PKHeX/Resources/text/zh/LegalityCheckStrings_zh.txt index 7fab0bbde..c483ec371 100644 --- a/PKHeX/Resources/text/zh/LegalityCheckStrings_zh.txt +++ b/PKHeX/Resources/text/zh/LegalityCheckStrings_zh.txt @@ -35,6 +35,12 @@ V362 = 在第{0}世代的默认招式。 V372 = {0}树果 V380 = Encounter Type match encounter. V382 = Encounter Type not implemented for pokemon encounter. +V382 = Encounter Type not implemented for pokemon encounter. +V391 = Porygon with valid Type A and B values. +V392 = Valid Type A, match species type. +V393 = Valid Type B, match species type. +V394 = Catch rate match a valid held item from generation 2 +V398 = Catch rate match any species from pokemon evolution chain. V203 = 无性别宝可梦不能有性别。 V201 = 未设置加密常数。 V204 = 持有物未解禁。 @@ -314,4 +320,13 @@ V377 = 遗传招式。礼物蛋不应有。 V378 = 遗传升级招式。礼物蛋不应有。 V379 = {0}的遗传招式。与遗传的招式{1}不共存。 V381 = Encounter Type does not match encounter. -V385 = Moves combinations is not compatible with {0} evolution. \ No newline at end of file +V385 = Moves combinations is not compatible with {0} evolution. +V386 = Porygon with invalid Type A value. +V387 = Porygon with invalid Type B value. +V388 = Porygon with invalid Type A and B values. Does not a match a valid type combination. +V389 = Invalid Type A, does not match species type. +V390 = Invalid Type B, does not match species type. +V395 = Catch rate does not match a valid held item from generation 2. +V396 = Catch rate match species without encounters. Expected a preevolution catch rate. +V397 = Catch rate does not match any species from pokemon evolution chain. +V399 = Catch rate does not match any species from pokemon evolution chain or any generation 2 held items. \ No newline at end of file