PKHeX/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionGroup3.cs
Kurt 9166d0eb64
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves

In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.

The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.

The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.

* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 16:15:27 -07:00

63 lines
2.2 KiB
C#

using System;
namespace PKHeX.Core;
public sealed class EvolutionGroup3 : IEvolutionGroup
{
public static readonly EvolutionGroup3 Instance = new();
private static readonly EvolutionTree Tree = EvolutionTree.Evolves3;
private const int MaxSpecies = Legal.MaxSpeciesID_3;
private const int Generation = 3;
private static PersonalTable3 Personal => PersonalTable.E;
public IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc) => pk.Format > Generation ? EvolutionGroup4.Instance : null;
public IEvolutionGroup? GetPrevious(PKM pk, EvolutionOrigin enc) => null;
public bool Append(PKM pk, EvolutionHistory history, ref ReadOnlySpan<EvoCriteria> chain, EvolutionOrigin enc)
{
// Get the first evolution in the chain that can be present in this group
var any = GetFirstEvolution(chain, out var evo);
if (!any)
return false;
// Get the evolution tree from this group and get the new chain from it.
var min = pk.Format > Generation ? enc.LevelMin : (byte)pk.Met_Level;
var criteria = enc with { LevelMax = evo.LevelMax, LevelMin = min };
var local = GetInitialChain(pk, criteria, evo.Species, evo.Form);
// Revise the tree
var revised = Prune(local);
// Set the tree to the history field
history.Gen3 = revised;
// Retain a reference to the current chain for future appending as we step backwards.
chain = revised;
return revised.Length != 0;
}
public EvoCriteria[] GetInitialChain(PKM pk, EvolutionOrigin enc, ushort species, byte form)
{
return Tree.GetExplicitLineage(species, form, pk, enc.LevelMin, enc.LevelMax, MaxSpecies, enc.SkipChecks, enc.Species);
}
private static EvoCriteria[] Prune(EvoCriteria[] chain) => chain;
private static bool GetFirstEvolution(ReadOnlySpan<EvoCriteria> chain, out EvoCriteria result)
{
var pt = Personal;
foreach (var evo in chain)
{
// If the evo can't exist in the game, it must be a future evolution.
if (!pt.IsPresentInGame(evo.Species, evo.Form))
continue;
result = evo;
return true;
}
result = default;
return false;
}
}