diff --git a/.gitignore b/.gitignore index 2bd0a997b..b8fdb6e1e 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ local.properties *.suo *.user *.sln.docstates +*.vs # Build results diff --git a/PKHeX/Legality/Analysis.cs b/PKHeX/Legality/Analysis.cs index 394e08a16..6e3168e7e 100644 --- a/PKHeX/Legality/Analysis.cs +++ b/PKHeX/Legality/Analysis.cs @@ -8,6 +8,7 @@ public partial class LegalityAnalysis { private PKM pkm; private DexLevel[] EvoChain; + private DexLevel[][] EvoChainsAllGens; private readonly List Parse = new List(); private object EncounterMatch; @@ -143,6 +144,7 @@ private void updateEncounterChain() Encounter = verifyEncounter(); Parse.Add(Encounter); EvoChain = Legal.getEvolutionChain(pkm, EncounterMatch); + EvoChainsAllGens = Legal.getEvolutionChainsAllGens(pkm, EncounterMatch); } private void updateEncounterInfo() { @@ -257,7 +259,7 @@ public int[] getSuggestedMoves(bool tm, bool tutor, bool reminder) return null; if (!Parsed) return new int[4]; - return Legal.getValidMoves(pkm, EvoChain, Tutor: tutor, Machine: tm, MoveReminder: reminder).Skip(1).ToArray(); // skip move 0 + return Legal.getValidMoves(pkm, EvoChainsAllGens, Tutor: tutor, Machine: tm, MoveReminder: reminder).Skip(1).ToArray(); // skip move 0 } public EncounterStatic getSuggestedMetInfo() diff --git a/PKHeX/Legality/Checks.cs b/PKHeX/Legality/Checks.cs index d51396610..3108cb8d6 100644 --- a/PKHeX/Legality/Checks.cs +++ b/PKHeX/Legality/Checks.cs @@ -526,9 +526,9 @@ private CheckResult verifyEncounterG1() { // Since encounter matching is super weak due to limited stored data in the structure // Calculate all 3 at the same time and pick the best result (by species). - var s = Legal.getValidStaticEncounter(pkm); + var s = Legal.getValidStaticEncounter(pkm, gen1Encounter: true); var e = Legal.getValidWildEncounters(pkm); - var t = Legal.getValidIngameTrade(pkm); + var t = Legal.getValidIngameTrade(pkm, gen1Encounter: true); const byte invalid = 255; @@ -1433,8 +1433,11 @@ private CheckResult verifyCommonMemory(int handler) resultPrefix = "HT "; break; } + int[] generations = new int[1] { pkm.Format }; + if (pkm.GenNumber == 6 && pkm.Format == 7) + generations = new int[2] { 6, 7 }; int matchingMoveMemory = Array.IndexOf(Legal.MoveSpecificMemories[0], m); - if (matchingMoveMemory != -1 && pkm.Species != 235 && !Legal.getCanLearnMachineMove(pkm, Legal.MoveSpecificMemories[1][matchingMoveMemory])) + if (matchingMoveMemory != -1 && pkm.Species != 235 && !Legal.getCanLearnMachineMove(pkm, Legal.MoveSpecificMemories[1][matchingMoveMemory], generations)) { return new CheckResult(Severity.Invalid, resultPrefix + "Memory: Species cannot learn this move.", CheckIdentifier.Memory); } @@ -1444,14 +1447,14 @@ private CheckResult verifyCommonMemory(int handler) } if (m == 21) // {0} saw {2} carrying {1} on its back. {4} that {3}. { - if (!Legal.getCanLearnMachineMove(new PK6 {Species = t, EXP = PKX.getEXP(100, t)}, 19)) + if (!Legal.getCanLearnMachineMove(new PK6 {Species = t, EXP = PKX.getEXP(100, t)}, 19, generations)) return new CheckResult(Severity.Invalid, resultPrefix + "Memory: Argument Species cannot learn Fly.", CheckIdentifier.Memory); } - if ((m == 16 || m == 48) && (t == 0 || !Legal.getCanKnowMove(pkm, t, GameVersion.Any))) + if ((m == 16 || m == 48) && (t == 0 || !Legal.getCanKnowMove(pkm, t, generations, GameVersion.Any))) { return new CheckResult(Severity.Invalid, resultPrefix + "Memory: Species cannot know this move.", CheckIdentifier.Memory); } - if (m == 49 && (t == 0 || !Legal.getCanRelearnMove(pkm, t, GameVersion.Any))) // {0} was able to remember {2} at {1}'s instruction. {4} that {3}. + if (m == 49 && (t == 0 || !Legal.getCanRelearnMove(pkm, t, generations, GameVersion.Any))) // {0} was able to remember {2} at {1}'s instruction. {4} that {3}. { return new CheckResult(Severity.Invalid, resultPrefix + "Memory: Species cannot relearn this move.", CheckIdentifier.Memory); } @@ -1897,9 +1900,9 @@ private CheckResult[] verifyMoves() for (int i = 0; i < 4; i++) res[i] = new CheckResult(CheckIdentifier.Move); - var validMoves = Legal.getValidMoves(pkm, EvoChain, Tutor: false, Machine: false).ToArray(); - var validTMHM = Legal.getValidMoves(pkm, EvoChain, Tutor: false, MoveReminder: false).ToArray(); - var validTutor = Legal.getValidMoves(pkm, EvoChain, Machine: false, MoveReminder: false).ToArray(); + var validMoves = Legal.getValidMoves(pkm, EvoChainsAllGens, Tutor: false, Machine: false).ToArray(); + var validTMHM = Legal.getValidMoves(pkm, EvoChainsAllGens, Tutor: false, MoveReminder: false).ToArray(); + var validTutor = Legal.getValidMoves(pkm, EvoChainsAllGens, Machine: false, MoveReminder: false).ToArray(); if (pkm.Species == 235) // Smeargle { for (int i = 0; i < 4; i++) diff --git a/PKHeX/Legality/Core.cs b/PKHeX/Legality/Core.cs index 27fd13c3e..21afca5e9 100644 --- a/PKHeX/Legality/Core.cs +++ b/PKHeX/Legality/Core.cs @@ -192,12 +192,19 @@ private static void MarkG7SMSlots(ref EncounterArea[] Areas) } // Moves - internal static IEnumerable getValidMoves(PKM pkm, IEnumerable evoChain, bool Tutor = true, bool Machine = true, bool MoveReminder = true) + internal static IEnumerable getValidMoves(PKM pkm, DexLevel[][] evoChains, bool Tutor = true, bool Machine = true, bool MoveReminder = true) { GameVersion version = (GameVersion)pkm.Version; if (!pkm.IsUntraded) version = GameVersion.Any; - return getValidMoves(pkm, version, evoChain, LVL: true, Relearn: false, Tutor: Tutor, Machine: Machine, MoveReminder: MoveReminder); + return getValidMoves(pkm, version, evoChains, LVL: true, Relearn: false, Tutor: Tutor, Machine: Machine, MoveReminder: MoveReminder); + } + internal static IEnumerable getValidMoves(PKM pkm, IEnumerable evoChain,int generation, bool Tutor = true, bool Machine = true, bool MoveReminder = true) + { + GameVersion version = (GameVersion)pkm.Version; + if (!pkm.IsUntraded) + version = GameVersion.Any; + return getValidMoves(pkm, version, evoChain, generation,LVL: true, Relearn: false, Tutor: Tutor, Machine: Machine, MoveReminder: MoveReminder); } internal static IEnumerable getValidRelearn(PKM pkm, int skipOption) { @@ -269,10 +276,14 @@ internal static EncounterSlot[] getValidWildEncounters(PKM pkm) s.AddRange(getValidEncounterSlots(pkm, area, DexNav: pkm.AO)); return s.Any() ? s.ToArray() : null; } - internal static EncounterStatic getValidStaticEncounter(PKM pkm) + internal static EncounterStatic getValidStaticEncounter(PKM pkm, bool gen1Encounter = false) { // Get possible encounters IEnumerable poss = getStaticEncounters(pkm); + + int lvl = (pkm.HasOriginalMetLocation) ? pkm.Met_Level : getMaxLevelGeneration(pkm); + if (lvl <= 0) + return null; ; // Back Check against pkm foreach (EncounterStatic e in poss) { @@ -284,7 +295,12 @@ internal static EncounterStatic getValidStaticEncounter(PKM pkm) { if (e.Location != 0 && e.Location != pkm.Met_Location) continue; - if (e.Level != pkm.Met_Level) + if (e.Level != lvl) + continue; + } + else + { + if (e.Level > lvl) continue; } if (e.Gender != -1 && e.Gender != pkm.Gender) @@ -305,7 +321,7 @@ internal static EncounterStatic getValidStaticEncounter(PKM pkm) } return null; } - internal static EncounterTrade getValidIngameTrade(PKM pkm) + internal static EncounterTrade getValidIngameTrade(PKM pkm, bool gen1Encounter = false) { if (!pkm.WasIngameTrade) return null; @@ -313,6 +329,9 @@ internal static EncounterTrade getValidIngameTrade(PKM pkm) if (lang == 0 || lang == 6) return null; + int lvl = (pkm.HasOriginalMetLocation) ? pkm.Met_Level : getMaxLevelGeneration(pkm); + if (lvl <= 0) + return null; // Get valid pre-evolutions IEnumerable p = getValidPreEvolutions(pkm); @@ -343,7 +362,9 @@ internal static EncounterTrade getValidIngameTrade(PKM pkm) return null; if (pkm.HasOriginalMetLocation && z.Location != pkm.Met_Location) return null; - if (z.Level != pkm.Met_Level) + if (pkm.HasOriginalMetLocation && z.Level != lvl) + return null; + if (!pkm.HasOriginalMetLocation && z.Level > lvl) return null; if (z.Nature != Nature.Random && (int)z.Nature != pkm.Nature) return null; @@ -414,6 +435,19 @@ internal static int getMaxSpeciesOrigin(int generation) default: return -1; } } + + internal static int[] getFutureGenEvolutions(int generation) + { + switch (generation) + { + case 1: return FutureEvolutionsGen1; + case 2: return FutureEvolutionsGen2; + case 3: return FutureEvolutionsGen3; + case 4: return FutureEvolutionsGen4; + case 5: return FutureEvolutionsGen5; + default: return new int[0]; + } + } internal static int getMaxSpeciesOrigin(PKM pkm) { @@ -705,25 +739,52 @@ private static bool getCanBeCaptured(int species, IEnumerable are return false; } - internal static bool getCanLearnMachineMove(PKM pkm, int move, GameVersion version = GameVersion.Any) + internal static bool getCanLearnMachineMove(PKM pkm, int move, int[] generations, GameVersion version = GameVersion.Any) { - return getValidMoves(pkm, version, getValidPreEvolutions(pkm), Machine: true).Contains(move); + foreach (int generation in generations) + if (getCanLearnMachineMove(pkm, generation, move, version)) + return true; + return false; } - internal static bool getCanRelearnMove(PKM pkm, int move, GameVersion version = GameVersion.Any) + internal static bool getCanRelearnMove(PKM pkm,int move, int[] generations, GameVersion version = GameVersion.Any) { - return getValidMoves(pkm, version, getValidPreEvolutions(pkm), LVL: true, Relearn: true).Contains(move); + foreach (int generation in generations) + if (getCanRelearnMove(pkm, move, generation, version)) + return true; + return false; } - internal static bool getCanLearnMove(PKM pkm, int move, GameVersion version = GameVersion.Any) + internal static bool getCanLearnMove(PKM pkm, int move, int[] generations, GameVersion version = GameVersion.Any) { - return getValidMoves(pkm, version, getValidPreEvolutions(pkm), Tutor: true, Machine: true).Contains(move); + foreach (int generation in generations) + if (getCanLearnMove(pkm, move, generation, version)) + return true; + return false; } - internal static bool getCanKnowMove(PKM pkm, int move, GameVersion version = GameVersion.Any) + internal static bool getCanKnowMove(PKM pkm, int move, int[] generations, GameVersion version = GameVersion.Any) + { + foreach (int generation in generations) + if (getCanKnowMove(pkm, move, generation, version)) + return true; + return false; + } + internal static bool getCanLearnMachineMove(PKM pkm,int move, int generation, GameVersion version = GameVersion.Any) + { + return getValidMoves(pkm, version, getValidPreEvolutions(pkm), generation, Machine: true).Contains(move); + } + internal static bool getCanRelearnMove(PKM pkm, int move, int generation, GameVersion version = GameVersion.Any) + { + return getValidMoves(pkm, version, getValidPreEvolutions(pkm), generation, LVL: true, Relearn: true).Contains(move); + } + internal static bool getCanLearnMove(PKM pkm,int move, int generation, GameVersion version = GameVersion.Any) + { + return getValidMoves(pkm, version, getValidPreEvolutions(pkm), generation, Tutor: true, Machine: true).Contains(move); + } + internal static bool getCanKnowMove(PKM pkm,int move, int generation, GameVersion version = GameVersion.Any) { if (pkm.Species == 235 && !InvalidSketch.Contains(move)) return true; - return getValidMoves(pkm, Version: version, vs: getValidPreEvolutions(pkm), LVL: true, Relearn: true, Tutor: true, Machine: true).Contains(move); + return getValidMoves(pkm, version,getValidPreEvolutions(pkm), generation, LVL: true, Relearn: true, Tutor: true, Machine: true).Contains(move); } - internal static int getBaseSpecies(PKM pkm, int skipOption = 0) { if (pkm.Species == 292) @@ -741,10 +802,131 @@ internal static int getBaseSpecies(PKM pkm, int skipOption = 0) default: return evos.Length <= 0 ? pkm.Species : evos.Last().Species; } } + internal static int getMaxLevelGeneration(PKM pkm) + { + return getMaxLevelGeneration(pkm, pkm.GenNumber); + } + internal static int getMaxLevelGeneration(PKM pkm, int generation) + { + if (!pkm.InhabitedGeneration(generation)) + return 0; + + if (pkm.Format <= 2) + { + if (generation == 1 && FutureEvolutionsGen1_Gen2LevelUp.Contains(pkm.Species)) + return pkm.CurrentLevel - 1; + return pkm.CurrentLevel; + } + + if (pkm.Species == 700 && generation == 5) + return pkm.CurrentLevel - 1; + + if (pkm.Gen3 && pkm.Format > 4 && pkm.Met_Level == pkm.CurrentLevel && FutureEvolutionsGen3_LevelUp.Contains(pkm.Species)) + return pkm.Met_Level - 1; + + if(!pkm.HasOriginalMetLocation) + return pkm.Met_Level; + + return pkm.CurrentLevel; + } + + internal static int getMinLevelGeneration(PKM pkm) + { + return getMinLevelGeneration(pkm, pkm.GenNumber); + } + internal static int getMinLevelGeneration(PKM pkm, int generation) + { + if (!pkm.InhabitedGeneration(generation)) + return 0; + + if (pkm.Format <= 2) + return 2; + + if(!pkm.HasOriginalMetLocation) + return pkm.Met_Level; + + if (pkm.GenNumber <= 3) + return 2; + + return 1; + } + internal static DexLevel[][] getEvolutionChainsAllGens(PKM pkm, object Encounter) + { + IEnumerable CompleteEvoChain = getEvolutionChain(pkm, Encounter); + DexLevel[][] GensEvoChains = new DexLevel[8][]; + for (int gen = 1; gen <= 7; gen++) + GensEvoChains[gen] = new DexLevel[0]; + + if((pkm.Format >2 && pkm.GenU) || pkm.Species==0)//Illegal origin or empty pokemon, return only chain for current format + { + GensEvoChains[pkm.Format] = CompleteEvoChain.ToArray(); + return GensEvoChains; + } + //If is egg skip the other checks and just return the evo chain for GenNumber, that will contains only the pokemon inside the egg + //Empty list returned if is an impossible egg (like a gen 3 infernape inside an egg) + if (pkm.IsEgg && getMaxSpeciesOrigin(pkm.GenNumber) > pkm.Species) + return GensEvoChains; + else if (pkm.IsEgg) + { + GensEvoChains[pkm.GenNumber] = CompleteEvoChain.ToArray(); + return GensEvoChains; + } + + int currengenlevel = 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) + continue; + if ((pkm.Gen2 || pkm.VC2) && 3 <= gen && gen <= 6) + continue; + if (!pkm.HasOriginalMetLocation && pkm.Format >2 && gen <= 4 && currengenlevel > pkm.Met_Level) + //Met location was lost at this point but it also means the pokemon existed in generations 1 to 4 with maximun level equals to met level + currengenlevel = pkm.Met_Level; + int maxspeciesgen = getMaxSpeciesOrigin(gen); + //Remove future gen evolutions after a few special considerations, + //it the pokemon origin is illegal like a "gen 3" Infernape the list will be emptied, it didnt existed in gen 3 in any evolution phase + while (CompleteEvoChain.Any() && CompleteEvoChain.First().Species > maxspeciesgen) + { + //Eeve requieres to level one time to be Sylveon, it can be deduced in gen 5 and before it existed with maximun one level bellow current + if (CompleteEvoChain.First().Species == 700 && gen == 5) + currengenlevel--; + //This is a gen 3 pokemon in a gen 4 phase evolution that requieres level up and then transfered to gen 5,6 or 7 + //We can deduce that it existed in gen 4 until met level, + //but if current level is met level we can also deduce it existed in gen 3 until maximun met level -1 + if (gen == 3 && pkm.Format>4 && currengenlevel == pkm.CurrentLevel && CompleteEvoChain.First().Species > MaxSpeciesID_3 && CompleteEvoChain.First().RequiresLvlUp) + currengenlevel--; + //The same condition for gen2 evolution of gen 1 pokemon, level of the pokemon in gen 1 games would be CurrentLevel -1 one level bellow gen 2 level + if (gen == 1 && pkm.Format == 2 && currengenlevel == pkm.CurrentLevel && CompleteEvoChain.First().Species > MaxSpeciesID_1 && CompleteEvoChain.First().RequiresLvlUp) + currengenlevel--; + CompleteEvoChain = CompleteEvoChain.Skip(1); + }; + //Alolan form evolutions, remove from gens 1-6 chains + if (gen < 7 && pkm.Format >= 7 && CompleteEvoChain.Any() && CompleteEvoChain.First().Form > 0 && EvolveToAlolanForms.Contains(CompleteEvoChain.First().Species)) + CompleteEvoChain = CompleteEvoChain.Skip(1); + + if(CompleteEvoChain.Any()) + { + GensEvoChains[gen] = getEvolutionChain(pkm, Encounter, CompleteEvoChain.First().Species, currengenlevel); + if (!pkm.HasOriginalMetLocation && gen >= pkm.GenNumber ) + //Remove previous evolutions bellow transfer level + //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 >= currengenlevel).ToArray(); + } + } + return GensEvoChains; + } internal static DexLevel[] getEvolutionChain(PKM pkm, object Encounter) + { + return getEvolutionChain(pkm, Encounter,pkm.Species, 100); + } + internal static DexLevel[] getEvolutionChain(PKM pkm, object Encounter,int maxspec, int maxlevel) { int minspec; - var vs = getValidPreEvolutions(pkm).ToArray(); + DexLevel[] vs = getValidPreEvolutions(pkm).ToArray(); // Evolution chain is in reverse order (devolution) @@ -757,8 +939,29 @@ internal static DexLevel[] getEvolutionChain(PKM pkm, object Encounter) else minspec = vs.Last().Species; - int index = Math.Max(0, Array.FindIndex(vs, p => p.Species == minspec)); - Array.Resize(ref vs, index + 1); + int minindex = Math.Max(0, Array.FindIndex(vs, p => p.Species == minspec)); + Array.Resize(ref vs, minindex + 1); + if(vs.Last().MinLevel > 1) //Last entry from vs is removed, turn next entry into the wild/hatched pokemon + { + vs.Last().MinLevel = 1; + vs.Last().RequiresLvlUp = false; + if (vs.First().MinLevel == 2 && !vs.First().RequiresLvlUp) + { + //Example Raichu in gen 2 or later, + //because Pichu requires level up minimun level of Raichu would be 2 + //but after removing Pichu because the origin species is Pikachu, Raichu min level should be 1 + vs.First().MinLevel = 1; + vs.First().RequiresLvlUp = false; + } + } + //Maxspec is used to remove future gen evolutions, to gather evolution chain of a pokemon in previous generations + int skip = Math.Max(0, Array.FindIndex(vs, p => p.Species == maxspec)); + //Maxlevel is also used for previous generations, it removes evolutions imposible before the transfer level + //For example a fire red charizard whose current level in XY is 50 but met level is 20, it couldnt be a Charizard in gen 3 and 4 games + vs = vs.Skip(skip).Where(e=>e.MinLevel <= maxlevel).ToArray(); + //Reduce the evolution chain levels to max level, because met level is the last one when the pokemon could be and learn moves in that generation + foreach (DexLevel d in vs) + d.Level = Math.Min(d.Level, maxlevel); return vs; } internal static string getEncounterTypeName(PKM pkm, object Encounter) @@ -887,27 +1090,34 @@ private static IEnumerable getValidEncounterSlots(PKM pkm, Encoun bool ignoreSlotLevel = ignoreLevel; IEnumerable slots = loc.Slots.Where(slot => vs.Any(evo => evo.Species == slot.Species && (ignoreSlotLevel || evo.Level >= slot.LevelMin - df))); - if (pkm.Format < 3 || pkm.VC) - return slots; // no met level or special encounter considerations - - // Filter for Met Level - int lvl = pkm.Met_Level; + int lvl = (pkm.HasOriginalMetLocation) ? pkm.Met_Level: getMaxLevelGeneration(pkm); + if (lvl <= 0) + return slotdata; int gen = pkm.GenNumber; - bool ignoreMetLevel = ignoreLevel || gen <= 4 && pkm.Format != gen; - var encounterSlots = slots.Where(slot => ignoreMetLevel || slot.LevelMin - df <= lvl && lvl <= slot.LevelMax + (slot.AllowDexNav ? dn : df)).ToList(); + IEnumerable encounterSlots; + if(pkm.HasOriginalMetLocation) + encounterSlots = slots.Where(slot => ignoreLevel || slot.LevelMin - df <= lvl && lvl <= slot.LevelMax + (slot.AllowDexNav ? dn : df)).ToList(); + else + //Those encounters with level min greater that met level are not valid for this pokemon + encounterSlots = slots.Where(slot => ignoreLevel || slot.LevelMin <= lvl).ToList(); + + if(gen <= 2) + { + //For gen 1 and 2 return minimun level slot + //Minimun 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 + EncounterSlot slotMin = encounterSlots.OrderBy(slot => slot.LevelMin).FirstOrDefault(); + if (slotMin != null) + slotdata.Add(slotMin); + return slotdata; + } // Pressure Slot EncounterSlot slotMax = encounterSlots.OrderByDescending(slot => slot.LevelMax).FirstOrDefault(); if (slotMax != null) slotMax = new EncounterSlot(slotMax) { Pressure = true, Form = pkm.AltForm }; - if (gen < 4) - { - if (slotMax != null) - slotdata.Add(slotMax); - return slotdata; - } - if (!DexNav) + if (gen >= 6 && !DexNav) { // Filter for Form Specific slotdata.AddRange(WildForms.Contains(pkm.Species) @@ -919,6 +1129,11 @@ private static IEnumerable getValidEncounterSlots(PKM pkm, Encoun } List eslots = encounterSlots.Where(slot => !WildForms.Contains(pkm.Species) || slot.Form == pkm.AltForm).ToList(); + if(gen <= 5) + { + slotdata.AddRange(eslots); + return slotdata; + } if (slotMax != null) eslots.Add(slotMax); foreach (EncounterSlot s in eslots) @@ -957,13 +1172,13 @@ private static IEnumerable getValidPreEvolutions(PKM pkm, int lvl = -1 if (lvl == 1 && pkm.IsEgg) return new List { - new DexLevel { Species = pkm.Species, Level = 1 }, + new DexLevel { Species = pkm.Species, Level = 1, MinLevel = 1 }, }; - if (pkm.Species == 292 && pkm.Met_Level + 1 <= lvl && lvl >= 20) + if (pkm.Species == 292 && lvl >= 20 && (!pkm.HasOriginalMetLocation || pkm.Met_Level + 1 <= lvl)) return new List { - new DexLevel { Species = 292, Level = lvl }, - new DexLevel { Species = 290, Level = lvl-1 } + new DexLevel { Species = 292, Level = lvl, MinLevel =20 }, + new DexLevel { Species = 290, Level = lvl-1, MinLevel = 1 } }; var et = getEvolutionTable(pkm); @@ -974,11 +1189,22 @@ private static IEnumerable getStatic(PKM pkm, IEnumerable dl = getValidPreEvolutions(pkm, lvl); return table.Where(e => dl.Any(d => d.Species == e.Species)); } - private static IEnumerable getValidMoves(PKM pkm, GameVersion Version, IEnumerable vs, bool LVL = false, bool Relearn = false, bool Tutor = false, bool Machine = false, bool MoveReminder = true) + private static IEnumerable getValidMoves(PKM pkm, GameVersion Version, DexLevel[][] vs, bool LVL = false, bool Relearn = false, bool Tutor = false, bool Machine = false, bool MoveReminder = true) { List r = new List { 0 }; + for(int gen =1;gen<=7;gen++) + { + if (vs[gen].Any()) + r.AddRange(getValidMoves(pkm, Version, vs[gen], gen, LVL, Tutor, Machine, MoveReminder)); + } + return r.Distinct().ToArray(); + } + private static IEnumerable getValidMoves(PKM pkm, GameVersion Version, IEnumerable vs, int Generation, bool LVL = false, bool Relearn = false, bool Tutor = false, bool Machine = false, bool MoveReminder = true) + { + List r = new List { 0 }; + if (!vs.Any()) + return r; int species = pkm.Species; - int lvl = pkm.CurrentLevel; // Special Type Tutors Availability bool moveTutor = Tutor || MoveReminder; // Usually true, except when called for move suggestions (no tutored moves) @@ -987,33 +1213,32 @@ private static IEnumerable getValidMoves(PKM pkm, GameVersion Version, IEnu { int formcount = pkm.PersonalInfo.FormeCount; for (int i = 0; i < formcount; i++) - r.AddRange(getMoves(pkm, species, lvl, i, moveTutor, Version, LVL, Tutor, Machine, MoveReminder)); + r.AddRange(getMoves(pkm, species, vs.First().Level, i, moveTutor, Version, LVL, Tutor, Machine, Generation, MoveReminder)); if (Relearn) r.AddRange(pkm.RelearnMoves); return r.Distinct().ToArray(); } - r.AddRange(getMoves(pkm, species, lvl, pkm.AltForm, moveTutor, Version, LVL, Tutor, Machine, MoveReminder)); - foreach (DexLevel evo in vs) - r.AddRange(getMoves(pkm, evo.Species, evo.Level, pkm.AltForm, moveTutor, Version, LVL, Tutor, Machine, MoveReminder)); + r.AddRange(getMoves(pkm, evo.Species, evo.Level, pkm.AltForm, moveTutor, Version, LVL, Tutor, Machine, Generation, MoveReminder)); if (pkm.Format <= 3) return r.Distinct().ToArray(); + if (LVL) + { + if (species == 479 && Generation >= 4) // Rotom + r.Add(RotomMoves[pkm.AltForm]); + if (species == 648 && Generation >= 5) // Meloetta + r.Add(547); // Relic Song - if (species == 479) // Rotom - r.Add(RotomMoves[pkm.AltForm]); - if (species == 648) // Meloetta - r.Add(547); // Relic Song + if (species == 25 && pkm.Format == 6 && Generation == 6) // Pikachu + r.Add(PikachuMoves[pkm.AltForm]); - if (species == 25 && pkm.Format == 6 && pkm.GenNumber == 6) // Pikachu - r.Add(PikachuMoves[pkm.AltForm]); - - if (species == 718 && pkm.GenNumber == 7) // Zygarde - r.AddRange(ZygardeMoves); - if ((species == 25 || species == 26) && pkm.Format == 7) // Pikachu/Raichu Tutor + if (species == 718 && Generation == 7) // Zygarde + r.AddRange(ZygardeMoves); + } + if ((species == 25 || species == 26) && Generation == 7 && moveTutor) // Pikachu/Raichu Tutor r.Add(344); // Volt Tackle - - if (Relearn) r.AddRange(pkm.RelearnMoves); + if (Relearn && Generation >= 6) r.AddRange(pkm.RelearnMoves); return r.Distinct().ToArray(); } private static IEnumerable getMoves(PKM pkm, int species, int lvl, int form, bool moveTutor, GameVersion Version, bool LVL, bool specialTutors, bool Machine, bool MoveReminder) @@ -1043,6 +1268,8 @@ private static IEnumerable getMoves(PKM pkm, int species, int lvl, int form { case 1: { + if (species > MaxSpeciesID_1)//Sanity check + return r; var pi_rb = (PersonalInfoG1)PersonalTable.RB[species]; var pi_y = (PersonalInfoG1)PersonalTable.Y[species]; if (LVL) diff --git a/PKHeX/Legality/Structures/DexLevel.cs b/PKHeX/Legality/Structures/DexLevel.cs index cea495af3..cd3d1e475 100644 --- a/PKHeX/Legality/Structures/DexLevel.cs +++ b/PKHeX/Legality/Structures/DexLevel.cs @@ -4,12 +4,14 @@ public class DexLevel { public int Species; public int Level; + public int MinLevel; + public bool RequiresLvlUp; public int Form = -1; public int Flag = -1; public DexLevel Copy(int lvl) { - return new DexLevel {Species = Species, Level = lvl, Form = Form, Flag = -1}; + return new DexLevel {Species = Species, Level = lvl, MinLevel = MinLevel, RequiresLvlUp = RequiresLvlUp, Form = Form, Flag = -1}; } public bool Matches(int species, int form) { diff --git a/PKHeX/Legality/Structures/EvolutionTree.cs b/PKHeX/Legality/Structures/EvolutionTree.cs index 1371a2525..53a705483 100644 --- a/PKHeX/Legality/Structures/EvolutionTree.cs +++ b/PKHeX/Legality/Structures/EvolutionTree.cs @@ -153,7 +153,7 @@ private int getIndex(EvolutionMethod evo) return Personal.getFormeIndex(evolvesToSpecies, evolvesToForm); } - public IEnumerable getValidPreEvolutions(PKM pkm, int lvl, bool skipChecks = false) + public IEnumerable getValidPreEvolutions(PKM pkm, int lvl,bool skipChecks = false) { int index = getIndex(pkm); int maxSpeciesOrigin = Legal.getMaxSpeciesOrigin(pkm); @@ -354,7 +354,7 @@ public bool Valid(PKM pkm, int lvl, bool skipChecks) return false; } } - + public DexLevel GetDexLevel(int species, int lvl) { @@ -416,6 +416,25 @@ public IEnumerable getExplicitLineage(PKM pkm, int lvl, bool skipCheck continue; oneValid = true; + if (evo.Level == 0 && !evo.RequiresLevelUp) //Evolutions like elemental stones, trade, etc + { + dl.Last().MinLevel = 1; + } + else if(evo.Level ==0) //Evolutions like frienship, pichu -> pikachu, eevee -> umbreon, etc + { + dl.Last().MinLevel = 2; + if (dl.Count > 1 && !dl.First().RequiresLvlUp) + dl.First().MinLevel = 2; //Raichu from Pikachu would have minimun level 1, but with Pichu included Raichu minimun level is 2 + } + else //level up evolutions + { + dl.Last().MinLevel = evo.Level; + if (dl.Count > 1 && dl.First().MinLevel < evo.Level && !dl.First().RequiresLvlUp) + dl.First().MinLevel = evo.Level; //Pokemon like Nidoqueen, its minimun level is Nidorina minimun level + if (dl.Count > 1 && dl.First().MinLevel <= evo.Level && dl.First().RequiresLvlUp) + dl.First().MinLevel = evo.Level + 1; //Pokemon like Crobat, its minimun level is Golbat minimun level + 1 + } + dl.Last().RequiresLvlUp = evo.RequiresLevelUp; int species = evo.Species; // Gen7 Personal Formes -- unmap the forme personal entry ID to the actual species ID since species are consecutive @@ -436,6 +455,9 @@ public IEnumerable getExplicitLineage(PKM pkm, int lvl, bool skipCheck if (dl.Any(d => d.Species <= maxSpeciesOrigin) && dl.Last().Species > maxSpeciesOrigin) dl.RemoveAt(dl.Count - 1); + //Last species is the wild/hatched species, the minimun is 1 because it has not evolved from previous species + dl.Last().MinLevel = 1; + dl.Last().RequiresLvlUp = false; return dl; } } diff --git a/PKHeX/Legality/Tables1.cs b/PKHeX/Legality/Tables1.cs index 5079716b6..7770d500b 100644 --- a/PKHeX/Legality/Tables1.cs +++ b/PKHeX/Legality/Tables1.cs @@ -114,5 +114,16 @@ public static partial class Legal new EncounterSlot1 {Species = 118, LevelMin = 10, LevelMax = 10, Type = SlotType.Good_Rod, Rate = -1, }, // Goldeen new EncounterSlot1 {Species = 060, LevelMin = 10, LevelMax = 10, Type = SlotType.Good_Rod, Rate = -1, }, // Poliwag }}; + + internal static readonly int[] FutureEvolutionsGen1 = + { + 169,182,186,196,197,199,208,212,230,233,242,462,463,464,465,466,467,470,471,474,700 + }; + + internal static readonly int[] FutureEvolutionsGen1_Gen2LevelUp = new int[] + { + 169,196,197,242 + }; + //Crobat Espeon Umbreon Blissey } } diff --git a/PKHeX/Legality/Tables2.cs b/PKHeX/Legality/Tables2.cs index 27a3a89c5..1c934f4dd 100644 --- a/PKHeX/Legality/Tables2.cs +++ b/PKHeX/Legality/Tables2.cs @@ -36,5 +36,10 @@ public static partial class Legal 10, 00, 00, 00, 00 }; internal static readonly int[] WildPokeBalls2 = { 4 }; + + internal static readonly int[] FutureEvolutionsGen2 = + { + 424,429,430,461,462,463,464,465,466,467,468,469,470,471,472,473,474,700 + }; } } diff --git a/PKHeX/Legality/Tables3.cs b/PKHeX/Legality/Tables3.cs index a136845ca..91ec69374 100644 --- a/PKHeX/Legality/Tables3.cs +++ b/PKHeX/Legality/Tables3.cs @@ -82,5 +82,17 @@ public static partial class Legal 590, 591, 592, 593 }; internal static readonly int[] WildPokeBalls3 = {1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12}; + + internal static readonly int[] FutureEvolutionsGen3 = + { + 407,424,429,430,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,700 + }; + + internal static readonly int[] FutureEvolutionsGen3_LevelUp = new int[] + { + 424, 461, 462, 463, 465, 469, 470, 471, 472, 473, 476 + }; + // Ambipom Weavile Magnezone Lickilicky Tangrowth + // Yanmega Leafeon Glaceon Mamoswine Gliscor Probopass } } diff --git a/PKHeX/Legality/Tables4.cs b/PKHeX/Legality/Tables4.cs index 8dbfb116b..83f1a9f6f 100644 --- a/PKHeX/Legality/Tables4.cs +++ b/PKHeX/Legality/Tables4.cs @@ -130,5 +130,10 @@ public static partial class Legal 17, 18, 19, 20, 21, 22, // Comp Ball not usable in wild }; + + internal static readonly int[] FutureEvolutionsGen4 = + { + 700 + }; } } diff --git a/PKHeX/Legality/Tables5.cs b/PKHeX/Legality/Tables5.cs index 7c1199c20..723d65860 100644 --- a/PKHeX/Legality/Tables5.cs +++ b/PKHeX/Legality/Tables5.cs @@ -89,5 +89,10 @@ public static partial class Legal // HGSS balls not usable // Dream ball not usable in wild }; + + internal static readonly int[] FutureEvolutionsGen5 = + { + 700 + }; } } diff --git a/PKHeX/PKM/PKM.cs b/PKHeX/PKM/PKM.cs index 3299c6c67..32dd885ec 100644 --- a/PKHeX/PKM/PKM.cs +++ b/PKHeX/PKM/PKM.cs @@ -277,7 +277,7 @@ public byte[] Write() public bool Gen3 => Version >= 1 && Version <= 5 || Version == 15; public bool Gen2 => Version == (int)GameVersion.GSC; public bool Gen1 => Version == (int)GameVersion.RBY; - public bool GenU => !(Gen7 || Gen6 || Gen5 || Gen4 || Gen3 || Gen2 || Gen1); + public bool GenU => !(Gen7 || Gen6 || Gen5 || Gen4 || Gen3 || Gen2 || Gen1 || VC); public int GenNumber { get @@ -453,6 +453,13 @@ public bool InhabitedGeneration(int Generation, int species = -1) if (species < 0) species = Species; + if (Format == 1 && Generation ==2 && (Legal.MaxSpeciesID_2 >= species || Legal.FutureEvolutionsGen2.Contains(species))) + return true; + + if (Format == Generation) + //Every pokemon even those with illegal data inhabit the generation of its current format + return true; + if (Format < Generation) return false; // Future @@ -460,14 +467,14 @@ public bool InhabitedGeneration(int Generation, int species = -1) return false; // Sanity Check Species ID - if (Legal.getMaxSpeciesOrigin(GenNumber) < species) + if (Legal.getMaxSpeciesOrigin(GenNumber) < species && !Legal.getFutureGenEvolutions(GenNumber).Contains(species)) return false; int gen = GenNumber; switch (Generation) { - case 1: return VC; - case 2: return VC; + case 1: return Format == 1 || VC; + case 2: return Format <= 2 || VC2; case 3: return Gen3; case 4: return 3 <= gen && gen <= 4; case 5: return 3 <= gen && gen <= 5;