using System; using System.Collections.Generic; namespace PKHeX.Core { /// /// Generation specific Evolution Tree data. /// /// /// Used to determine if a can evolve from prior steps in its evolution branch. /// public sealed class EvolutionTree { private static readonly EvolutionTree Evolves1; private static readonly EvolutionTree Evolves2; private static readonly EvolutionTree Evolves3; private static readonly EvolutionTree Evolves4; private static readonly EvolutionTree Evolves5; private static readonly EvolutionTree Evolves6; private static readonly EvolutionTree Evolves7; private static readonly EvolutionTree Evolves7b; private static readonly EvolutionTree Evolves8; static EvolutionTree() { // Evolution tables need Personal Tables initialized beforehand, hence why the EvolutionTree data is initialized here. static byte[] get(string resource) => Util.GetBinaryResource($"evos_{resource}.pkl"); static byte[][] unpack(string resource) => Data.UnpackMini(get(resource), resource); Evolves1 = new EvolutionTree(new[] { get("rby") }, GameVersion.Gen1, PersonalTable.Y, Legal.MaxSpeciesID_1); Evolves2 = new EvolutionTree(new[] { get("gsc") }, GameVersion.Gen2, PersonalTable.C, Legal.MaxSpeciesID_2); Evolves3 = new EvolutionTree(new[] { get("g3") }, GameVersion.Gen3, PersonalTable.RS, Legal.MaxSpeciesID_3); Evolves4 = new EvolutionTree(new[] { get("g4") }, GameVersion.Gen4, PersonalTable.DP, Legal.MaxSpeciesID_4); Evolves5 = new EvolutionTree(new[] { get("g5") }, GameVersion.Gen5, PersonalTable.BW, Legal.MaxSpeciesID_5); Evolves6 = new EvolutionTree(unpack("ao"), GameVersion.Gen6, PersonalTable.AO, Legal.MaxSpeciesID_6); Evolves7 = new EvolutionTree(unpack("uu"), GameVersion.Gen7, PersonalTable.USUM, Legal.MaxSpeciesID_7_USUM); Evolves7b = new EvolutionTree(unpack("gg"), GameVersion.Gen7, PersonalTable.GG, Legal.MaxSpeciesID_7b); Evolves8 = new EvolutionTree(unpack("ss"), GameVersion.Gen8, PersonalTable.SWSH, Legal.MaxSpeciesID_8); // There's always oddballs. Evolves7.FixEvoTreeSM(); } internal static EvolutionTree GetEvolutionTree(int generation) { return generation switch { 1 => Evolves1, 2 => Evolves2, 3 => Evolves3, 4 => Evolves4, 5 => Evolves5, 6 => Evolves6, 7 => Evolves7, _ => Evolves8 }; } internal static EvolutionTree GetEvolutionTree(PKM pkm, int generation) { return generation switch { 1 => Evolves1, 2 => Evolves2, 3 => Evolves3, 4 => Evolves4, 5 => Evolves5, 6 => Evolves6, 7 => (pkm.GG ? Evolves7b : Evolves7), _ => Evolves8 }; } private readonly IReadOnlyList Entries; private readonly EvolutionLineage[] Lineage; private readonly GameVersion Game; private readonly PersonalTable Personal; private readonly int MaxSpeciesTree; private EvolutionTree(IReadOnlyList data, GameVersion game, PersonalTable personal, int maxSpeciesTree) { Game = game; Personal = personal; MaxSpeciesTree = maxSpeciesTree; Entries = GetEntries(data); Lineage = CreateTree(); } private IReadOnlyList GetEntries(IReadOnlyList data) { return Game switch { GameVersion.Gen1 => EvolutionSet1.GetArray(data[0], MaxSpeciesTree), GameVersion.Gen2 => EvolutionSet1.GetArray(data[0], MaxSpeciesTree), GameVersion.Gen3 => EvolutionSet3.GetArray(data[0]), GameVersion.Gen4 => EvolutionSet4.GetArray(data[0]), GameVersion.Gen5 => EvolutionSet5.GetArray(data[0]), GameVersion.Gen6 => EvolutionSet6.GetArray(data), GameVersion.Gen7 => EvolutionSet7.GetArray(data), GameVersion.Gen8 => EvolutionSet7.GetArray(data), _ => throw new Exception() }; } private EvolutionLineage[] CreateTree() { var lineage = new EvolutionLineage[Entries.Count]; for (int i = 0; i < Entries.Count; i++) lineage[i] = new EvolutionLineage(); if (Game == GameVersion.Gen6) Array.Resize(ref lineage, MaxSpeciesTree + 1); // Populate Lineages for (int i = 1; i < lineage.Length; i++) CreateBranch(lineage, i); return lineage; } private void CreateBranch(IReadOnlyList lineage, int i) { // Iterate over all possible evolutions foreach (var evo in Entries[i]) CreateLeaf(lineage, i, evo); } private void CreateLeaf(IReadOnlyList lineage, int i, EvolutionMethod evo) { int index = GetIndex(evo); if (index < 0) return; var sourceEvo = evo.Copy(i); lineage[index].Insert(sourceEvo); // If current entries has a pre-evolution, propagate to evolution as well var current = lineage[i].Chain; if (current.Count > 0) lineage[index].Chain.Insert(0, current[0]); if (index >= i) return; // If destination species evolves into something (ie a 'baby' Pokemon like Cleffa) // Add it to the corresponding parent chains foreach (var method in Entries[index]) { int newIndex = GetIndex(method); if (newIndex < 0) continue; lineage[newIndex].Insert(sourceEvo); } } private void FixEvoTreeSM() { // Wormadam -- Copy Burmy 0 to Wormadam-1/2 Lineage[Personal.GetFormeIndex(413, 1)].Chain.Insert(0, Lineage[413].Chain[0]); Lineage[Personal.GetFormeIndex(413, 2)].Chain.Insert(0, Lineage[413].Chain[0]); // Shellos -- Move Shellos-1 evo from Gastrodon-0 to Gastrodon-1 Lineage[Personal.GetFormeIndex(422 + 1, 1)].Chain.Insert(0, Lineage[422 + 1].Chain[0]); Lineage[422+1].Chain.RemoveAt(0); // Meowstic -- Meowstic-1 (F) should point back to Espurr, copy Meowstic-0 (M) Lineage[Personal.GetFormeIndex(678, 1)].Chain.Insert(0, Lineage[678].Chain[0]); // Floette doesn't contain evo info for forms 1-4, copy. Florges points to form 0, no fix needed. var fbb = Lineage[669+1].Chain[0]; for (int i = 1; i <= 4; i++) // NOT AZ Lineage[Personal.GetFormeIndex(669+1, i)].Chain.Insert(0, fbb); // Clear forme chains from Florges Lineage[671].Chain.RemoveRange(0, Lineage[671].Chain.Count - 2); // Gourgeist -- Sizes are still relevant. Formes are in reverse order. for (int i = 1; i <= 3; i++) { Lineage[Personal.GetFormeIndex(711, i)].Chain.Clear(); Lineage[Personal.GetFormeIndex(711, i)].Chain.Add(Lineage[711].Chain[3-i]); } Lineage[711].Chain.RemoveRange(0, 3); // Ban Raichu Evolution on SM Lineage[Personal.GetFormeIndex(26, 0)] .Chain[1][0] .Banlist = EvolutionMethod.BanSM; // Ban Exeggutor Evolution on SM Lineage[Personal.GetFormeIndex(103, 0)] .Chain[0][0] .Banlist = EvolutionMethod.BanSM; // Ban Marowak Evolution on SM Lineage[Personal.GetFormeIndex(105, 0)] .Chain[0][0] .Banlist = EvolutionMethod.BanSM; } private int GetIndex(PKM pkm) { if (pkm.Format < 7) return pkm.Species; return Personal.GetFormeIndex(pkm.Species, pkm.AltForm); } private int GetIndex(EvolutionMethod evo) { int evolvesToSpecies = evo.Species; if (evolvesToSpecies == 0) return -1; if (Personal == null) return evolvesToSpecies; int evolvesToForm = evo.Form; if (evolvesToForm < 0) evolvesToForm = 0; return Personal.GetFormeIndex(evolvesToSpecies, evolvesToForm); } /// /// Gets a list of evolutions for the input by checking each evolution in the chain. /// /// Pokémon data to check with. /// Maximum level to permit before the chain breaks. /// Maximum species ID to permit within the chain. /// Ignores an evolution's criteria, causing the returned list to have all possible evolutions. /// Minimum level to permit before the chain breaks. /// public List GetValidPreEvolutions(PKM pkm, int maxLevel, int maxSpeciesOrigin = -1, bool skipChecks = false, int minLevel = 1) { int index = GetIndex(pkm); if (maxSpeciesOrigin <= 0) maxSpeciesOrigin = Legal.GetMaxSpeciesOrigin(pkm); return Lineage[index].GetExplicitLineage(pkm, maxLevel, skipChecks, MaxSpeciesTree, maxSpeciesOrigin, minLevel); } public IEnumerable GetEvolutionsAndPreEvolutions(int species, int form) { foreach (var s in GetPreEvolutions(species, form)) yield return s; yield return species; foreach (var s in GetEvolutions(species, form)) yield return s; } private IEnumerable GetPreEvolutions(int species, int form) { int index = Personal.GetFormeIndex(species, form); var node = Lineage[index]; foreach (var methods in node.Chain) { foreach (var prevo in methods) yield return prevo.Species; } } private IEnumerable GetEvolutions(int species, int form) { int index = Personal.GetFormeIndex(species, form); var node = Entries[index]; foreach (var z in node) { var s = z.Species; if (s == 0) continue; yield return s; foreach (var next in GetEvolutions(s, form)) yield return next; } } } }