using System;
using System.Collections.Generic;
using System.Linq;
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;
static EvolutionTree()
{
// Evolution tables need Personal Tables initialized beforehand, hence why the EvolutionTree data is initialized here.
byte[] get(string resource) => Util.GetBinaryResource($"evos_{resource}.pkl");
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);
// There's always oddballs.
Evolves7.FixEvoTreeSM();
Evolves7b.FixEvoTreeGG();
}
internal static EvolutionTree GetEvolutionTree(int generation)
{
switch (generation)
{
case 1: return Evolves1;
case 2: return Evolves2;
case 3: return Evolves3;
case 4: return Evolves4;
case 5: return Evolves5;
case 6: return Evolves6;
default:
return Evolves7;
}
}
internal static EvolutionTree GetEvolutionTree(PKM pkm, int generation)
{
switch (generation)
{
case 1: return Evolves1;
case 2: return Evolves2;
case 3: return Evolves3;
case 4: return Evolves4;
case 5: return Evolves5;
case 6: return Evolves6;
case 7 when pkm.GG: return Evolves7b;
default:
return Evolves7;
}
}
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)
{
switch (Game)
{
case GameVersion.Gen1: return EvolutionSet1.GetArray(data[0], MaxSpeciesTree);
case GameVersion.Gen2: return EvolutionSet2.GetArray(data[0], MaxSpeciesTree);
case GameVersion.Gen3: return EvolutionSet3.GetArray(data[0]);
case GameVersion.Gen4: return EvolutionSet4.GetArray(data[0]);
case GameVersion.Gen5: return EvolutionSet5.GetArray(data[0]);
case GameVersion.Gen6: return new List(data.Select(d => new EvolutionSet6(d)));
case GameVersion.Gen7: return new List(data.Select(d => new EvolutionSet7(d)));
default: 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].PossibleEvolutions)
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].Insert(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].PossibleEvolutions)
{
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].StageEntryMethods[0]
.Banlist = EvolutionMethod.BanSM;
// Ban Exeggutor Evolution on SM
Lineage[Personal.GetFormeIndex(103, 0)]
.Chain[0].StageEntryMethods[0]
.Banlist = EvolutionMethod.BanSM;
// Ban Marowak Evolution on SM
Lineage[Personal.GetFormeIndex(105, 0)]
.Chain[0].StageEntryMethods[0]
.Banlist = EvolutionMethod.BanSM;
}
private void FixEvoTreeGG()
{
// Ban Raichu Evolution on SM
Lineage[Personal.GetFormeIndex(26, 0)]
.Chain[1].StageEntryMethods[0]
.Banlist = EvolutionMethod.BanGG;
// Ban Exeggutor Evolution on SM
Lineage[Personal.GetFormeIndex(103, 0)]
.Chain[0].StageEntryMethods[0]
.Banlist = EvolutionMethod.BanGG;
// Ban Marowak Evolution on SM
Lineage[Personal.GetFormeIndex(105, 0)]
.Chain[0].StageEntryMethods[0]
.Banlist = EvolutionMethod.BanGG;
}
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);
}
}
}