using System; namespace PKHeX.Core; /// /// Logic to create an . /// public static class EvolutionChain { /// /// Build an for the given and . /// /// Entity to search for. /// Evolution details. public static EvolutionHistory GetEvolutionChainsAllGens(PKM pk, IEncounterTemplate enc) { var min = GetMinLevel(pk, enc); var origin = new EvolutionOrigin(pk.Species, (byte)enc.Version, (byte)enc.Generation, min, (byte)pk.CurrentLevel); if (!pk.IsEgg && enc is not EncounterInvalid) return GetEvolutionChainsSearch(pk, origin, enc.Context, enc.Species); return GetEvolutionChainsSearch(pk, origin, pk.Context, enc.Species); } /// /// Build an for the given and . /// /// Entity to search for. /// Evolution details. /// Starting (original) context of the . /// Encountered as species. If not known (search for all), set to 0. public static EvolutionHistory GetEvolutionChainsSearch(PKM pk, EvolutionOrigin enc, EntityContext context, ushort encSpecies = 0) { Span chain = stackalloc EvoCriteria[EvolutionTree.MaxEvolutions]; return EvolutionChainsSearch(pk, enc, context, encSpecies, chain); } private static byte GetMinLevel(PKM pk, IEncounterTemplate enc) => enc.Generation switch { 2 => pk is ICaughtData2 c2 ? Math.Max((byte)c2.Met_Level, enc.LevelMin) : enc.LevelMin, <= 4 when pk.Format != enc.Generation => enc.LevelMin, _ => Math.Max((byte)pk.Met_Level, enc.LevelMin), }; private static EvolutionHistory EvolutionChainsSearch(PKM pk, EvolutionOrigin enc, EntityContext context, ushort encSpecies, Span chain) { var history = new EvolutionHistory(); var length = GetOriginChain(chain, pk, enc, encSpecies, enc.IsDiscardRequired(pk.Format)); if (length == 0) return history; chain = chain[..length]; // Update the chain to only include the current species, leave future evolutions as unknown if (encSpecies != 0) EvolutionUtil.ConditionBaseChainForward(chain, encSpecies); if (context == EntityContext.Gen2) { // Handle the evolution case for Gen2->Gen1 EvolutionGroup2.Instance.Evolve(chain, pk, enc, history); EvolutionGroup1.Instance.Evolve(chain, pk, enc, history); if (pk.Format > 2) // Skip forward to Gen7 context = EntityContext.Gen7; else // no more possible contexts; done. return history; } var group = EvolutionGroupUtil.GetGroup(context); while (true) { group.Evolve(chain, pk, enc, history); var previous = group.GetNext(pk, enc); if (previous is null) break; group = previous; } return history; } /// /// Gets a list of that represent the possible original states of the . /// /// Entity to search for. /// Evolution details. /// Encountered as species. If not known (search for all), set to 0. /// Discard evolutions that are not possible for the original context. Pass false to keep all evolutions. public static EvoCriteria[] GetOriginChain(PKM pk, EvolutionOrigin enc, ushort encSpecies = 0, bool discard = true) { Span result = stackalloc EvoCriteria[EvolutionTree.MaxEvolutions]; int count = GetOriginChain(result, pk, enc, encSpecies, discard); if (count == 0) return []; var chain = result[..count]; return chain.ToArray(); } /// /// Gets a list of that represent the possible original states of the . /// /// Span to write results to. /// Entity to search for. /// Evolution details. /// Encountered as species. If not known (search for all), set to 0. /// Discard evolutions that are not possible for the original context. Pass false to keep all evolutions. /// Number of valid evolutions found. public static int GetOriginChain(Span result, PKM pk, EvolutionOrigin enc, ushort encSpecies = 0, bool discard = true) { ushort species = enc.Species; byte form = pk.Form; if (pk.IsEgg && !enc.SkipChecks) { result[0] = new EvoCriteria { Species = species, Form = form, LevelMax = enc.LevelMax, LevelMin = enc.LevelMax }; return 1; } result[0] = new EvoCriteria { Species = species, Form = form, LevelMax = enc.LevelMax }; var count = DevolveFrom(result, pk, enc, pk.Context, encSpecies, discard); var chain = result[..count]; EvolutionUtil.CleanDevolve(chain, enc.LevelMin); return count; } private static int DevolveFrom(Span result, PKM pk, EvolutionOrigin enc, EntityContext context, ushort encSpecies, bool discard) { var group = EvolutionGroupUtil.GetGroup(context); while (true) { group.Devolve(result, pk, enc); var previous = group.GetPrevious(pk, enc); if (previous is null) break; group = previous; } if (discard) group.DiscardForOrigin(result, pk, enc); if (encSpecies != 0) return EvolutionUtil.IndexOf(result, encSpecies) + 1; return GetCount(result); } /// /// Gets the count of entries that are not empty (species == 0). /// private static int GetCount(in ReadOnlySpan result) { int count = 0; foreach (ref readonly var evo in result) { if (evo.Species == 0) break; count++; } return count; } }