Refactor EvoCriteria to be a struct, reduce allocation (#3483)

* Make EvolutionCriteria struct

8 bytes per object instead of 26
Unify LevelMin/LevelMax to match EncounterTemplate
bubble up precise array type for better iteration

* Inline queue operations, less allocation

* Inline some logic

* Update EvolutionChain.cs

* Improve clarity on duplicate move check

* Search reverse

For a dual stage chain, finds it first iteration rather than second.
This commit is contained in:
Kurt 2022-04-23 21:33:17 -07:00 committed by GitHub
parent 959b9e998b
commit ef3cb34387
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
77 changed files with 349 additions and 300 deletions

View file

@ -28,6 +28,6 @@ namespace PKHeX.Core
public virtual bool IsMatchLocation(int location) => Location == location;
public bool HasSpecies(int species) => Raw.Any(z => z.Species == species);
public IEnumerable<EncounterSlot> GetSpecies(IReadOnlyList<DexLevel> chain) => Raw.Where(z => chain.Any(c => z.Species == c.Species));
public IEnumerable<EncounterSlot> GetSpecies(IReadOnlyList<EvoCriteria> chain) => Raw.Where(z => chain.Any(c => z.Species == c.Species));
}
}

View file

@ -55,7 +55,7 @@ namespace PKHeX.Core
if (slot.Species != evo.Species)
continue;
if (slot.LevelMin > evo.Level)
if (slot.LevelMin > evo.LevelMax)
break;
if (slot.Form != evo.Form)
break;

View file

@ -124,7 +124,7 @@ namespace PKHeX.Core
if (slot.Species != (int) Species.Unown || evo.Form >= 26) // Don't yield !? forms
break;
}
if (slot.LevelMin > evo.Level)
if (slot.LevelMin > evo.LevelMax)
break;
yield return slot;

View file

@ -153,7 +153,7 @@ namespace PKHeX.Core
if (slot.Form != evo.Form)
break;
if (slot.LevelMin > evo.Level)
if (slot.LevelMin > evo.LevelMax)
break;
yield return slot;

View file

@ -65,7 +65,7 @@ namespace PKHeX.Core
if (slot.Form != evo.Form)
break;
if (slot.LevelMin > evo.Level)
if (slot.LevelMin > evo.LevelMax)
break;
yield return slot;

View file

@ -158,7 +158,7 @@ namespace PKHeX.Core
if (!slot.IsRandomUnspecificForm)
break;
}
if (slot.LevelMin > evo.Level)
if (slot.LevelMin > evo.LevelMax)
break;
yield return slot;

View file

@ -79,9 +79,9 @@ namespace PKHeX.Core
// Track some metadata about how this slot was matched.
var clone = slot with
{
WhiteFlute = evo.MinLevel < slot.LevelMin,
BlackFlute = evo.MinLevel > slot.LevelMax && evo.MinLevel <= slot.LevelMax + FluteBoostMax,
DexNav = slot.CanDexNav && (evo.MinLevel != slot.LevelMax || pkm.RelearnMove1 != 0 || pkm.AbilityNumber == 4),
WhiteFlute = evo.LevelMin < slot.LevelMin,
BlackFlute = evo.LevelMin > slot.LevelMax && evo.LevelMin <= slot.LevelMax + FluteBoostMax,
DexNav = slot.CanDexNav && (evo.LevelMin != slot.LevelMax || pkm.RelearnMove1 != 0 || pkm.AbilityNumber == 4),
};
yield return clone;
break;

View file

@ -139,7 +139,7 @@ namespace PKHeX.Core
}
}
private bool ExistsPressureSlot(DexLevel evo, ref byte level)
private bool ExistsPressureSlot(IDexLevel evo, ref byte level)
{
bool existsForm = false;
foreach (var z in Slots)

View file

@ -73,11 +73,11 @@ namespace PKHeX.Core
// Since it is possible to evolve before transferring, we only need the highest evolution species possible.
// PoGoEncTool has already extrapolated the evolutions to separate encounters!
var sf = chain.FirstOrDefault(z => z.Species == Species && z.Form == Form);
if (sf == null)
if (sf == default)
yield break;
var stamp = EncounterSlotGO.GetTimeStamp(pkm.Met_Year + 2000, pkm.Met_Month, pkm.Met_Day);
var met = Math.Max(sf.MinLevel, pkm.Met_Level);
var met = Math.Max(sf.LevelMin, pkm.Met_Level);
EncounterSlot7GO? deferredIV = null;
foreach (var slot in Slots)

View file

@ -103,11 +103,11 @@ namespace PKHeX.Core
// Since it is possible to evolve before transferring, we only need the highest evolution species possible.
// PoGoEncTool has already extrapolated the evolutions to separate encounters!
var sf = chain.FirstOrDefault(z => z.Species == Species && (z.Form == Form || FormInfo.IsFormChangeable(Species, Form, z.Form, pkm.Format)));
if (sf == null)
if (sf == default)
yield break;
var ball = (Ball)pkm.Ball;
var met = Math.Max(sf.MinLevel, pkm.Met_Level);
var met = Math.Max(sf.LevelMin, pkm.Met_Level);
EncounterSlot8GO? deferredIV = null;
foreach (var slot in Slots)

View file

@ -196,7 +196,7 @@ namespace PKHeX.Core
return LegalityCheckStrings.LEncCondition;
}
public bool IsMatchExact(PKM pkm, DexLevel dl) => true; // Matched by Area
public bool IsMatchExact(PKM pkm, IDexLevel evo) => true; // Matched by Area
public virtual EncounterMatchRating GetMatchRating(PKM pkm)
{

View file

@ -214,7 +214,7 @@ namespace PKHeX.Core
}
}
public virtual bool IsMatchExact(PKM pkm, DexLevel evo)
public virtual bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (Nature != Nature.Random && pkm.Nature != (int) Nature)
return false;
@ -256,7 +256,7 @@ namespace PKHeX.Core
return Legal.GetIsFixedIVSequenceValidSkipRand((int[])IVs, pkm);
}
protected virtual bool IsMatchForm(PKM pkm, DexLevel evo)
protected virtual bool IsMatchForm(PKM pkm, IDexLevel evo)
{
if (IsRandomUnspecificForm)
return true;
@ -288,7 +288,7 @@ namespace PKHeX.Core
return Location == pkm.Met_Location;
}
protected virtual bool IsMatchLevel(PKM pkm, DexLevel evo)
protected virtual bool IsMatchLevel(PKM pkm, IDexLevel evo)
{
return pkm.Met_Level == Level;
}

View file

@ -33,11 +33,11 @@
pk1.Catch_Rate = table[Species].CatchRate;
}
protected override bool IsMatchLevel(PKM pkm, DexLevel evo)
protected override bool IsMatchLevel(PKM pkm, IDexLevel evo)
{
// Met Level is not stored in the PK1 format.
// Check if it is at or above the encounter level.
return Level <= evo.Level;
return Level <= evo.LevelMax;
}
protected override bool IsMatchLocation(PKM pkm)
@ -46,7 +46,7 @@
return true;
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (!base.IsMatchExact(pkm, evo))
return false;

View file

@ -24,7 +24,7 @@ namespace PKHeX.Core
{
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (!base.IsMatchExact(pkm, evo))
return false;

View file

@ -17,7 +17,7 @@ namespace PKHeX.Core
Level = level;
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (Shiny == Shiny.Always && !pkm.IsShiny)
return false;
@ -56,12 +56,12 @@ namespace PKHeX.Core
return true;
}
protected override bool IsMatchLevel(PKM pkm, DexLevel evo)
protected override bool IsMatchLevel(PKM pkm, IDexLevel evo)
{
if (pkm is ICaughtData2 {CaughtData: not 0})
return pkm.Met_Level == (EggEncounter ? 1 : Level);
return Level <= evo.Level;
return Level <= evo.LevelMax;
}
protected override bool IsMatchLocation(PKM pkm)
@ -104,7 +104,7 @@ namespace PKHeX.Core
EggCycles = 20;
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
// Let it get picked up as regular EncounterEgg under other conditions.
if (pkm.Format > 2)

View file

@ -28,7 +28,7 @@ namespace PKHeX.Core
{
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (!base.IsMatchExact(pkm, evo))
return false;

View file

@ -25,10 +25,10 @@ namespace PKHeX.Core
return pkm.Egg_Location == 0;
}
protected override bool IsMatchLevel(PKM pkm, DexLevel evo)
protected override bool IsMatchLevel(PKM pkm, IDexLevel evo)
{
if (pkm.Format != 3) // Met Level lost on PK3=>PK4
return Level <= evo.Level;
return Level <= evo.LevelMax;
if (EggEncounter)
return pkm.Met_Level == 0 && pkm.CurrentLevel >= 5; // met level 0, origin level 5

View file

@ -75,10 +75,10 @@ namespace PKHeX.Core
}
}
protected override bool IsMatchLevel(PKM pkm, DexLevel evo)
protected override bool IsMatchLevel(PKM pkm, IDexLevel evo)
{
if (pkm.Format != 4) // Met Level lost on PK4=>PK5
return Level <= evo.Level;
return Level <= evo.LevelMax;
return pkm.Met_Level == (EggEncounter ? 0 : Level);
}

View file

@ -24,10 +24,10 @@
return true; // transfer location verified later
}
protected override bool IsMatchLevel(PKM pkm, DexLevel evo)
protected override bool IsMatchLevel(PKM pkm, IDexLevel evo)
{
if (pkm.Format != 4) // Met Level lost on PK4=>PK5
return Level <= evo.Level;
return Level <= evo.LevelMax;
return pkm.Met_Level == Level;
}

View file

@ -18,7 +18,7 @@
Shiny = Shiny.Never;
}
protected override bool IsMatchLevel(PKM pkm, DexLevel evo)
protected override bool IsMatchLevel(PKM pkm, IDexLevel evo)
{
// Level from 5->40 depends on the number of badges
var met = pkm.Met_Level;

View file

@ -29,7 +29,7 @@
pk.RefreshAbility(ability);
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (PID != pkm.PID)
return false;

View file

@ -38,7 +38,7 @@ namespace PKHeX.Core
return true;
}
protected override bool IsMatchForm(PKM pkm, DexLevel evo)
protected override bool IsMatchForm(PKM pkm, IDexLevel evo)
{
if (IsTotem)
{

View file

@ -18,7 +18,7 @@ namespace PKHeX.Core
public AreaWeather8 Weather {get; init; } = AreaWeather8.Normal;
protected override bool IsMatchLevel(PKM pkm, DexLevel evo)
protected override bool IsMatchLevel(PKM pkm, IDexLevel evo)
{
var met = pkm.Met_Level;
var lvl = Level;
@ -29,7 +29,7 @@ namespace PKHeX.Core
return false;
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (pkm is IDynamaxLevel d && d.DynamaxLevel < DynamaxLevel)
return false;

View file

@ -38,7 +38,7 @@ namespace PKHeX.Core
55, 60, // 4
};
protected override bool IsMatchLevel(PKM pkm, DexLevel evo)
protected override bool IsMatchLevel(PKM pkm, IDexLevel evo)
{
var met = pkm.Met_Level;
var metLevel = met - 15;
@ -87,7 +87,7 @@ namespace PKHeX.Core
return loc == SharedNest || (loc <= 255 && NestLocations.Contains((byte)loc));
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (pkm.FlawlessIVCount < FlawlessIVCount)
return false;

View file

@ -14,7 +14,7 @@ namespace PKHeX.Core
return loc is SharedNest or Watchtower;
}
protected override bool IsMatchLevel(PKM pkm, DexLevel evo)
protected override bool IsMatchLevel(PKM pkm, IDexLevel evo)
{
var lvl = pkm.Met_Level;
if (lvl == Level)

View file

@ -20,7 +20,7 @@ namespace PKHeX.Core
FlawlessIVCount = flawless;
}
protected override bool IsMatchLevel(PKM pkm, DexLevel evo)
protected override bool IsMatchLevel(PKM pkm, IDexLevel evo)
{
var lvl = pkm.Met_Level;
@ -62,7 +62,7 @@ namespace PKHeX.Core
};
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (pkm.FlawlessIVCount < FlawlessIVCount)
return false;

View file

@ -18,7 +18,7 @@ namespace PKHeX.Core
public byte DynamaxLevel { get; set; }
public override int Location { get => SharedNest; init { } }
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (pkm is IDynamaxLevel d && d.DynamaxLevel < DynamaxLevel)
return false;

View file

@ -19,7 +19,7 @@ namespace PKHeX.Core
FlawlessIVCount = 4;
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (pkm.FlawlessIVCount < FlawlessIVCount)
return false;
@ -28,6 +28,6 @@ namespace PKHeX.Core
}
// no downleveling, unlike all other raids
protected override bool IsMatchLevel(PKM pkm, DexLevel evo) => pkm.Met_Level == Level;
protected override bool IsMatchLevel(PKM pkm, IDexLevel evo) => pkm.Met_Level == Level;
}
}

View file

@ -74,7 +74,7 @@ public sealed record EncounterStatic8a(GameVersion Version) : EncounterStatic(Ve
protected override void ApplyDetailsBall(PKM pk) => pk.Ball = Gift ? Ball : (int)Core.Ball.LAPoke;
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (!base.IsMatchExact(pkm, evo))
return false;

View file

@ -36,10 +36,10 @@ namespace PKHeX.Core
return Version == GameVersion.XD && met is (59 or 90 or 91 or 92 or 113);
}
protected override bool IsMatchLevel(PKM pkm, DexLevel evo)
protected override bool IsMatchLevel(PKM pkm, IDexLevel evo)
{
if (pkm.Format != 3) // Met Level lost on PK3=>PK4
return Level <= evo.Level;
return Level <= evo.LevelMax;
return pkm.Met_Level == Level;
}

View file

@ -178,7 +178,7 @@ namespace PKHeX.Core
pk.MetDate = time;
}
public virtual bool IsMatchExact(PKM pkm, DexLevel evo)
public virtual bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (IVs.Count != 0)
{
@ -213,16 +213,16 @@ namespace PKHeX.Core
return true;
}
private bool IsMatchLevel(PKM pkm, DexLevel evo)
private bool IsMatchLevel(PKM pkm, IDexLevel evo)
{
if (!pkm.HasOriginalMetLocation)
return evo.Level >= Level;
return evo.LevelMax >= Level;
if (Location != pkm.Met_Location)
return false;
if (pkm.Format < 5)
return evo.Level >= Level;
return evo.LevelMax >= Level;
return pkm.Met_Level == Level;
}

View file

@ -101,7 +101,7 @@ namespace PKHeX.Core
return lvl >= LevelMin;
}
public override bool IsMatchExact(PKM pkm, DexLevel dl)
public override bool IsMatchExact(PKM pkm, IDexLevel dl)
{
if (!IsMatchLevel(pkm, pkm.CurrentLevel)) // minimum required level
return false;

View file

@ -16,7 +16,7 @@ namespace PKHeX.Core
TID = tid;
}
public override bool IsMatchExact(PKM pkm, DexLevel dl)
public override bool IsMatchExact(PKM pkm, IDexLevel dl)
{
if (Level > pkm.CurrentLevel) // minimum required level
return false;

View file

@ -45,7 +45,7 @@ namespace PKHeX.Core
Level = level;
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (!base.IsMatchExact(pkm, evo))
return false;

View file

@ -54,7 +54,7 @@
public int MetLocation { get; init; }
public override int Location => MetLocation == default ? Locations.LinkTrade4NPC : MetLocation;
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (!base.IsMatchExact(pkm, evo))
return false;

View file

@ -33,7 +33,7 @@ namespace PKHeX.Core
OT_Intensity = intensity;
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (pkm is IDynamaxLevel d && d.DynamaxLevel < DynamaxLevel)
return false;

View file

@ -23,7 +23,7 @@
public uint PID { get; init; }
public uint EncryptionConstant { get; init; }
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (pkm.EncryptionConstant != EncryptionConstant)
return false;

View file

@ -9,6 +9,6 @@
Level = level;
}
public abstract override bool IsMatchExact(PKM pkm, DexLevel dl);
public abstract override bool IsMatchExact(PKM pkm, IDexLevel dl);
}
}

View file

@ -100,7 +100,7 @@ namespace PKHeX.Core
{
var curr = EvolutionChain.GetValidPreEvolutions(pkm, minLevel: 5);
var poss = EvolutionChain.GetValidPreEvolutions(pkm, maxLevel: 100, minLevel: 5, skipChecks: true);
return curr.Count >= poss.Count;
return curr.Length >= poss.Length;
}
}
}

View file

@ -23,7 +23,7 @@ namespace PKHeX.Core
{
public static class EncounterSlotGenerator
{
public static IEnumerable<EncounterSlot> GetPossible(PKM pkm, IReadOnlyList<DexLevel> chain, GameVersion gameSource = Any)
public static IEnumerable<EncounterSlot> GetPossible(PKM pkm, IReadOnlyList<EvoCriteria> chain, GameVersion gameSource = Any)
{
var possibleAreas = GetAreasByGame(pkm, gameSource);
return possibleAreas.SelectMany(z => z.GetSpecies(chain));

View file

@ -22,7 +22,7 @@ namespace PKHeX.Core
{
public static class EncounterStaticGenerator
{
public static IEnumerable<EncounterStatic> GetPossible(PKM pkm, IReadOnlyList<DexLevel> chain, GameVersion gameSource = Any)
public static IEnumerable<EncounterStatic> GetPossible(PKM pkm, IReadOnlyList<EvoCriteria> chain, GameVersion gameSource = Any)
{
if (gameSource == Any)
gameSource = (GameVersion)pkm.Version;
@ -37,7 +37,7 @@ namespace PKHeX.Core
return table.Where(e => chain.Any(d => d.Species == e.Species));
}
public static IEnumerable<EncounterStatic> GetPossibleGBGifts(IReadOnlyList<DexLevel> chain, GameVersion gameSource)
public static IEnumerable<EncounterStatic> GetPossibleGBGifts(IReadOnlyList<EvoCriteria> chain, GameVersion gameSource)
{
static IEnumerable<EncounterStatic> GetEvents(GameVersion g)
{
@ -51,7 +51,7 @@ namespace PKHeX.Core
return table.Where(e => chain.Any(d => d.Species == e.Species));
}
public static IEnumerable<EncounterStatic> GetValidStaticEncounter(PKM pkm, IReadOnlyList<DexLevel> chain, GameVersion gameSource = Any)
public static IEnumerable<EncounterStatic> GetValidStaticEncounter(PKM pkm, IReadOnlyList<EvoCriteria> chain, GameVersion gameSource = Any)
{
if (gameSource == Any)
gameSource = (GameVersion)pkm.Version;
@ -63,7 +63,7 @@ namespace PKHeX.Core
return GetMatchingStaticEncounters(pkm, poss, chain);
}
public static IEnumerable<EncounterStatic> GetValidGBGifts(PKM pkm, IReadOnlyList<DexLevel> chain, GameVersion gameSource)
public static IEnumerable<EncounterStatic> GetValidGBGifts(PKM pkm, IReadOnlyList<EvoCriteria> chain, GameVersion gameSource)
{
var poss = GetPossibleGBGifts(chain, gameSource: gameSource);
foreach (EncounterStatic e in poss)
@ -80,7 +80,7 @@ namespace PKHeX.Core
}
}
private static IEnumerable<EncounterStatic> GetMatchingStaticEncounters(PKM pkm, IEnumerable<EncounterStatic> poss, IReadOnlyList<DexLevel> evos)
private static IEnumerable<EncounterStatic> GetMatchingStaticEncounters(PKM pkm, IEnumerable<EncounterStatic> poss, IReadOnlyList<EvoCriteria> evos)
{
// check for petty rejection scenarios that will be flagged by other legality checks
foreach (var e in poss)
@ -105,19 +105,34 @@ namespace PKHeX.Core
{
// Only yield a VC1 template if it could originate in VC1.
// Catch anything that can only exist in VC2 (Entei) even if it was "transferred" from VC1.
var species = chain.Where(z => z.Species < MaxSpeciesID_1 && z.Form == 0)
.LastOrDefault(z => PersonalTable.SM.GetFormEntry(z.Species, z.Form).BaseFriendship == pkm.OT_Friendship)?.Species ?? pkm.Species;
var species = GetVCSpecies(chain, pkm, MaxSpeciesID_1);
var vc1Species = species > MaxSpeciesID_1 ? enc.Species : species;
if (vc1Species <= MaxSpeciesID_1)
return EncounterStatic7.GetVC1(vc1Species, met);
}
// fall through else
{
var species = chain.LastOrDefault(z => PersonalTable.SM.GetFormEntry(z.Species, z.Form).BaseFriendship == pkm.OT_Friendship)?.Species ?? pkm.Species;
var species = GetVCSpecies(chain, pkm, MaxSpeciesID_2);
return EncounterStatic7.GetVC2(species > MaxSpeciesID_2 ? enc.Species : species, met);
}
}
private static int GetVCSpecies(IReadOnlyList<EvoCriteria> chain, PKM pkm, int max)
{
int species = pkm.Species;
foreach (var z in chain)
{
if (z.Species > max)
continue;
if (z.Form != 0)
continue;
if (PersonalTable.SM.GetFormEntry(z.Species, z.Form).BaseFriendship != pkm.OT_Friendship)
continue;
species = z.Species;
}
return species;
}
internal static EncounterStatic? GetStaticLocation(PKM pkm, IReadOnlyList<EvoCriteria> chain)
{
switch (pkm.Generation)

View file

@ -7,26 +7,26 @@ namespace PKHeX.Core
{
public static class EncounterTradeGenerator
{
public static IEnumerable<EncounterTrade> GetPossible(PKM pkm, IReadOnlyList<DexLevel> chain, GameVersion gameSource)
public static IEnumerable<EncounterTrade> GetPossible(PKM pkm, IReadOnlyList<EvoCriteria> chain, GameVersion gameSource)
{
if (pkm.Format <= 2 || pkm.VC)
return GetPossibleVC(chain, gameSource);
return GetPossible(chain, gameSource);
}
private static IEnumerable<EncounterTradeGB> GetPossibleVC(IReadOnlyList<DexLevel> chain, GameVersion game)
private static IEnumerable<EncounterTradeGB> GetPossibleVC(IReadOnlyList<EvoCriteria> chain, GameVersion game)
{
var table = GetTableVC(game);
return table.Where(e => chain.Any(c => c.Species == e.Species && c.Form == 0));
}
private static IEnumerable<EncounterTrade> GetPossible(IReadOnlyList<DexLevel> chain, GameVersion game)
private static IEnumerable<EncounterTrade> GetPossible(IReadOnlyList<EvoCriteria> chain, GameVersion game)
{
var table = GetTable(game);
return table.Where(e => chain.Any(c => c.Species == e.Species));
}
public static IEnumerable<EncounterTradeGB> GetValidEncounterTradesVC(PKM pkm, IReadOnlyList<DexLevel> chain, GameVersion game)
public static IEnumerable<EncounterTradeGB> GetValidEncounterTradesVC(PKM pkm, IReadOnlyList<EvoCriteria> chain, GameVersion game)
{
var table = GetTableVC(game);
foreach (var p in table)
@ -42,7 +42,7 @@ namespace PKHeX.Core
}
}
public static IEnumerable<EncounterTrade> GetValidEncounterTrades(PKM pkm, IReadOnlyList<DexLevel> chain, GameVersion game = Any)
public static IEnumerable<EncounterTrade> GetValidEncounterTrades(PKM pkm, IReadOnlyList<EvoCriteria> chain, GameVersion game = Any)
{
if (game == Any)
game = (GameVersion)pkm.Version;
@ -57,7 +57,7 @@ namespace PKHeX.Core
return GetValidEncounterTrades(pkm, chain, poss);
}
private static IEnumerable<EncounterTrade> GetValidEncounterTrades(PKM pkm, IReadOnlyList<DexLevel> chain, IEnumerable<EncounterTrade> poss)
private static IEnumerable<EncounterTrade> GetValidEncounterTrades(PKM pkm, IReadOnlyList<EvoCriteria> chain, IEnumerable<EncounterTrade> poss)
{
foreach (var p in poss)
{

View file

@ -7,7 +7,7 @@ namespace PKHeX.Core
{
public static class MysteryGiftGenerator
{
public static IEnumerable<MysteryGift> GetPossible(PKM pkm, IReadOnlyList<DexLevel> chain)
public static IEnumerable<MysteryGift> GetPossible(PKM pkm, IReadOnlyList<EvoCriteria> chain)
{
// Ranger Manaphy is a PGT and is not in the PCD[] for gen4. Check manually.
int gen = pkm.Generation;
@ -20,7 +20,7 @@ namespace PKHeX.Core
yield return enc;
}
public static IEnumerable<MysteryGift> GetValidGifts(PKM pkm, IReadOnlyList<DexLevel> chain)
public static IEnumerable<MysteryGift> GetValidGifts(PKM pkm, IReadOnlyList<EvoCriteria> chain)
{
int gen = pkm.Generation;
if (pkm.IsEgg && pkm.Format != gen) // transferred
@ -43,7 +43,7 @@ namespace PKHeX.Core
_ => Array.Empty<MysteryGift>(),
};
private static IEnumerable<MysteryGift> GetMatchingPCD(PKM pkm, IReadOnlyCollection<PCD> DB, IReadOnlyList<DexLevel> chain)
private static IEnumerable<MysteryGift> GetMatchingPCD(PKM pkm, IReadOnlyCollection<PCD> DB, IReadOnlyList<EvoCriteria> chain)
{
if (PGT.IsRangerManaphy(pkm))
{
@ -55,7 +55,7 @@ namespace PKHeX.Core
yield return g;
}
private static IEnumerable<MysteryGift> GetMatchingGifts(PKM pkm, IReadOnlyCollection<MysteryGift> DB, IReadOnlyList<DexLevel> chain)
private static IEnumerable<MysteryGift> GetMatchingGifts(PKM pkm, IReadOnlyCollection<MysteryGift> DB, IReadOnlyList<EvoCriteria> chain)
{
foreach (var mg in DB)
{

View file

@ -10,7 +10,7 @@ namespace PKHeX.Core
/// <summary>
/// Checks if the implementing object's details might have been the originator of the current <see cref="pkm"/> data.
/// </summary>
bool IsMatchExact(PKM pkm, DexLevel dl);
bool IsMatchExact(PKM pkm, IDexLevel dl);
/// <summary>
/// Checks if the potential match may not be a perfect match (might be a better match later during iteration).

View file

@ -39,7 +39,7 @@ namespace PKHeX.Core
{
int lvl = GetSuggestedEncounterEggMetLevel(pkm);
var met = loc != -1 ? loc : GetSuggestedEggMetLocation(pkm);
return new EncounterSuggestionData(pkm, met, lvl);
return new EncounterSuggestionData(pkm, met, (byte)lvl);
}
public static int GetSuggestedEncounterEggMetLevel(PKM pkm)
@ -98,19 +98,19 @@ namespace PKHeX.Core
return -1;
}
public static int GetLowestLevel(PKM pkm, int startLevel)
public static int GetLowestLevel(PKM pkm, byte startLevel)
{
if (startLevel == -1)
if (startLevel >= 100)
startLevel = 100;
var table = EvolutionTree.GetEvolutionTree(pkm, pkm.Format);
int count = 1;
for (int i = 100; i >= startLevel; i--)
for (byte i = 100; i >= startLevel; i--)
{
var evos = table.GetValidPreEvolutions(pkm, maxLevel: i, minLevel: startLevel, skipChecks: true);
if (evos.Count < count) // lost an evolution, prior level was minimum current level
return evos.Max(evo => evo.Level) + 1;
count = evos.Count;
if (evos.Length < count) // lost an evolution, prior level was minimum current level
return evos.Max(evo => evo.LevelMax) + 1;
count = evos.Length;
}
return startLevel;
}
@ -188,7 +188,7 @@ namespace PKHeX.Core
LevelMax = enc.LevelMax;
}
public EncounterSuggestionData(PKM pkm, int met, int lvl)
public EncounterSuggestionData(PKM pkm, int met, byte lvl)
{
Species = pkm.Species;
Form = pkm.Form;
@ -202,8 +202,8 @@ namespace PKHeX.Core
public int Form { get; }
public int Location { get; }
public int LevelMin { get; }
public int LevelMax { get; }
public byte LevelMin { get; }
public byte LevelMax { get; }
public int GetSuggestedMetLevel(PKM pkm) => EncounterSuggestion.GetSuggestedMetLevel(pkm, LevelMin);
public GroundTileType GetSuggestedGroundTile() => Encounter is IGroundTypeTile t ? t.GroundTile.GetIndex() : 0;

View file

@ -38,7 +38,7 @@ namespace PKHeX.Core
private static bool IsValidEvolution(PKM pkm, LegalInfo info)
{
var chains = info.EvoChainsAllGens;
if (chains[pkm.Format].Count == 0)
if (chains[pkm.Format].Length == 0)
return false; // Can't exist as current species
// OK if un-evolved from original encounter

View file

@ -146,20 +146,18 @@ namespace PKHeX.Core
{
var enc = info.EncounterMatch;
var evos = info.EvoChainsAllGens[enc.Generation];
var level = evos.Count > 0 ? evos[^1].MinLevel : enc.LevelMin;
var level = evos.Length > 0 ? evos[^1].LevelMin : enc.LevelMin;
var InitialMoves = Array.Empty<int>();
var SpecialMoves = GetSpecialMoves(enc);
var games = enc.Generation == 1 ? GBRestrictions.GetGen1Versions(enc) : GBRestrictions.GetGen2Versions(enc, pkm.Korean);
foreach (var ver in games)
{
var VerInitialMoves = enc is IMoveset {Moves.Count: not 0 } x ? (int[])x.Moves : MoveLevelUp.GetEncounterMoves(enc.Species, 0, level, ver);
if (VerInitialMoves.Intersect(InitialMoves).Count() == VerInitialMoves.Length)
return;
if (VerInitialMoves.SequenceEqual(InitialMoves))
return; // Already checked this combination, and it wasn't valid. Don't bother repeating.
var source = new MoveParseSource
{
CurrentMoves = currentMoves,
SpecialSource = SpecialMoves,
Base = VerInitialMoves,
};
ParseMoves(pkm, source, info, parse);

View file

@ -1,14 +0,0 @@
namespace PKHeX.Core;
/// <summary>
/// Small general purpose value passing object with misc data pertaining to an encountered Species.
/// </summary>
public record DexLevel(int Species, int Form) : ISpeciesForm
{
/// <summary>
/// Maximum Level
/// </summary>
public int Level { get; set; }
public override string ToString() => $"{(Species)Species}{(Form == 0 ? "" : $"-{Form}")} [{Level}]";
}

View file

@ -15,12 +15,12 @@ namespace PKHeX.Core
/// <param name="pkm">Current state of the Pokémon</param>
/// <returns>Possible origin species-form-levels to match against encounter data.</returns>
/// <remarks>Use <see cref="GetOriginChain12"/> if the <see cref="pkm"/> originated from Generation 1 or 2.</remarks>
public static IReadOnlyList<EvoCriteria> GetOriginChain(PKM pkm)
public static EvoCriteria[] GetOriginChain(PKM pkm)
{
bool hasOriginMet = pkm.HasOriginalMetLocation;
var maxLevel = GetLevelOriginMax(pkm, hasOriginMet);
var minLevel = GetLevelOriginMin(pkm, hasOriginMet);
return GetOriginChain(pkm, -1, maxLevel, minLevel, hasOriginMet);
return GetOriginChain(pkm, -1, (byte)maxLevel, (byte)minLevel, hasOriginMet);
}
/// <summary>
@ -29,7 +29,7 @@ namespace PKHeX.Core
/// <param name="pkm">Current state of the Pokémon</param>
/// <param name="gameSource">Game/group the <see cref="pkm"/> originated from. If <see cref="GameVersion.RBY"/>, it assumes Gen 1, otherwise Gen 2.</param>
/// <returns>Possible origin species-form-levels to match against encounter data.</returns>
public static IReadOnlyList<EvoCriteria> GetOriginChain12(PKM pkm, GameVersion gameSource)
public static EvoCriteria[] GetOriginChain12(PKM pkm, GameVersion gameSource)
{
bool rby = gameSource == GameVersion.RBY;
var maxSpecies = rby ? Legal.MaxSpeciesID_1 : Legal.MaxSpeciesID_2;
@ -61,10 +61,10 @@ namespace PKHeX.Core
minLevel = 2;
}
return GetOriginChain(pkm, maxSpecies, maxLevel, minLevel, hasOriginMet);
return GetOriginChain(pkm, maxSpecies, (byte)maxLevel, (byte)minLevel, hasOriginMet);
}
private static IReadOnlyList<EvoCriteria> GetOriginChain(PKM pkm, int maxSpecies, int maxLevel, int minLevel, bool hasOriginMet)
private static EvoCriteria[] GetOriginChain(PKM pkm, int maxSpecies, byte maxLevel, byte minLevel, bool hasOriginMet)
{
if (maxLevel < minLevel)
return Array.Empty<EvoCriteria>();
@ -76,11 +76,9 @@ namespace PKHeX.Core
var tempMax = pkm.CurrentLevel;
var chain = EvolutionChain.GetValidPreEvolutions(pkm, maxSpecies, tempMax, minLevel);
foreach (var evo in chain)
{
evo.Level = maxLevel;
evo.MinLevel = minLevel;
}
for (var i = 0; i < chain.Length; i++)
chain[i] = chain[i] with { LevelMax = maxLevel, LevelMin = minLevel };
return chain;
}

View file

@ -1,12 +1,16 @@
namespace PKHeX.Core;
public sealed record EvoCriteria(int Species, int Form) : DexLevel(Species, Form)
public readonly record struct EvoCriteria : IDexLevel
{
public int MinLevel { get; set; }
public bool RequiresLvlUp { get; set; }
public int Method { get; init; } = -1;
public ushort Species { get; init; }
public byte Form { get; init; }
public byte LevelUpRequired { get; init; }
public byte LevelMax { get; init; }
public byte LevelMin { get; init; }
public bool IsTradeRequired => ((EvolutionType) Method).IsTrade();
public EvolutionType Method { get; init; }
public override string ToString() => $"{(Species) Species}{(Form != 0 ? $"-{Form}" : "")}}} [{MinLevel},{Level}] via {(EvolutionType) Method}";
public bool RequiresLvlUp => LevelUpRequired != 0;
public override string ToString() => $"{(Species) Species}{(Form != 0 ? $"-{Form}" : "")}}} [{LevelMin},{LevelMax}] via {Method}";
}

View file

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static PKHeX.Core.Legal;
@ -8,42 +6,42 @@ namespace PKHeX.Core
{
public static class EvolutionChain
{
private static readonly List<EvoCriteria> NONE = new(0);
private static readonly EvoCriteria[] NONE = Array.Empty<EvoCriteria>();
internal static IReadOnlyList<EvoCriteria>[] GetEvolutionChainsAllGens(PKM pkm, IEncounterTemplate enc)
internal static EvoCriteria[][] GetEvolutionChainsAllGens(PKM pkm, IEncounterTemplate enc)
{
var chain = GetEvolutionChain(pkm, enc, pkm.Species, pkm.CurrentLevel);
if (enc is EncounterInvalid || pkm.IsEgg || chain.Count == 0)
var chain = GetEvolutionChain(pkm, enc, pkm.Species, (byte)pkm.CurrentLevel);
if (chain.Length == 0 || pkm.IsEgg || enc is EncounterInvalid)
return GetChainSingle(pkm, chain);
return GetChainAll(pkm, enc, chain);
}
private static List<EvoCriteria>[] GetChainBase(int maxgen)
private static EvoCriteria[][] GetAllEmpty(int count)
{
var GensEvoChains = new List<EvoCriteria>[maxgen + 1];
for (int i = 0; i <= maxgen; i++)
GensEvoChains[i] = NONE; // default no-evolutions
return GensEvoChains;
var result = new EvoCriteria[count][];
for (int i = 0; i < result.Length; i++)
result[i] = NONE; // default no-evolutions
return result;
}
private static List<EvoCriteria>[] GetChainSingle(PKM pkm, List<EvoCriteria> fullChain)
private static EvoCriteria[][] GetChainSingle(PKM pkm, EvoCriteria[] fullChain)
{
var chain = GetChainBase(Math.Max(2, pkm.Format));
var chain = GetAllEmpty(Math.Max(2, pkm.Format) + 1);
chain[pkm.Format] = fullChain;
return chain;
}
private static List<EvoCriteria>[] GetChainAll(PKM pkm, IEncounterTemplate enc, IReadOnlyList<EvoCriteria> fullChain)
private static EvoCriteria[][] GetChainAll(PKM pkm, IEncounterTemplate enc, EvoCriteria[] fullChain)
{
int maxgen = ParseSettings.AllowGen1Tradeback && pkm is PK1 ? 2 : pkm.Format;
var GensEvoChains = GetChainBase(maxgen);
var GensEvoChains = GetAllEmpty(maxgen + 1);
var queue = new Queue<EvoCriteria>(fullChain);
var mostEvolved = queue.Dequeue();
var head = 0; // inlined FIFO queue indexing
var mostEvolved = fullChain[head++];
int lvl = pkm.CurrentLevel;
int maxLevel = lvl;
var lvl = (byte)pkm.CurrentLevel;
var maxLevel = lvl;
int pkGen = enc.Generation;
// Iterate generations backwards
@ -53,12 +51,14 @@ namespace PKHeX.Core
for (int g = GensEvoChains.Length - 1; g >= mingen; g--)
{
if (pkGen <= 2 && g == 6)
g = 2;
g = 2; // skip over 6543 as it never existed in these.
if (g <= 4 && pkm.Format > 2 && pkm.Format > g && !pkm.HasOriginalMetLocation && lvl > pkm.Met_Level)
if (g <= 4 && pkm.Format > 2 && pkm.Format > g && !pkm.HasOriginalMetLocation)
{
// Met location was lost at this point but it also means the pokemon existed in generations 1 to 4 with maximum level equals to met level
lvl = pkm.Met_Level;
var met = pkm.Met_Level;
if (lvl > pkm.Met_Level)
lvl = (byte)met;
}
int maxspeciesgen = g == 2 && pkm.VC1 ? MaxSpeciesID_1 : GetMaxSpeciesOrigin(g);
@ -67,7 +67,7 @@ namespace PKHeX.Core
// If the pokemon origin is illegal (e.g. Gen3 Infernape) the list will be emptied -- species lineage did not exist at any evolution stage.
while (mostEvolved.Species > maxspeciesgen)
{
if (queue.Count == 0)
if (head >= fullChain.Length)
{
if (g <= 2 && pkm.VC1)
GensEvoChains[pkm.Format] = NONE; // invalidate here since we haven't reached the regular invalidation
@ -85,7 +85,7 @@ namespace PKHeX.Core
else if (g == 1 && pkm.Format == 2 && lvl == maxLevel)
lvl--;
}
mostEvolved = queue.Dequeue();
mostEvolved = fullChain[head++];
}
// Alolan form evolutions, remove from gens 1-6 chains
@ -93,14 +93,15 @@ namespace PKHeX.Core
{
if (g < 7 && pkm.Format >= 7 && mostEvolved.Form > 0)
{
if (queue.Count == 0)
if (head >= fullChain.Length)
break;
mostEvolved = queue.Dequeue();
mostEvolved = fullChain[head++];
}
}
var genChain = GensEvoChains[g] = GetEvolutionChain(pkm, enc, mostEvolved.Species, lvl);
if (genChain.Count == 0)
GensEvoChains[g] = GetEvolutionChain(pkm, enc, mostEvolved.Species, lvl);
ref var genChain = ref GensEvoChains[g];
if (genChain.Length == 0)
continue;
if (g > 2 && !pkm.HasOriginalMetLocation && g >= pkGen && noxfrDecremented)
@ -115,26 +116,48 @@ namespace PKHeX.Core
// For example a gen3 Charizard in format 7 with current level 36 and met level 36, thus could never be Charmander / Charmeleon in Gen5+.
// chain level for Charmander is 35, is below met level.
int minlvl = GetMinLevelGeneration(pkm, g);
genChain.RemoveAll(e => e.Level < minlvl);
int minIndex = Array.FindIndex(genChain, e => e.LevelMax >= minlvl);
if (minIndex != -1)
genChain = genChain.AsSpan(minIndex).ToArray();
}
else if (g == 1)
{
var g1 = GensEvoChains[1];
// Remove Gen2 post-evolutions (Scizor, Blissey...)
if (g1[0].Species > MaxSpeciesID_1)
g1.RemoveAt(0);
// Remove Gen2 pre-evolutions (Pichu, Cleffa...)
int lastIndex = g1.Count - 1;
if (lastIndex >= 0 && g1[lastIndex].Species > MaxSpeciesID_1)
g1.RemoveAt(lastIndex);
// Remove Gen7 pre-evolutions and chain break scenarios
if (pkm.VC1)
TrimVC1Transfer(pkm, GensEvoChains);
ref var lastGen = ref GensEvoChains[1];
var g1 = lastGen.AsSpan();
// Remove Gen2 post-evolutions (Scizor, Blissey...)
if (g1[0].Species > MaxSpeciesID_1)
{
if (g1.Length == 1)
{
lastGen = Array.Empty<EvoCriteria>();
continue; // done
}
g1 = g1[1..];
}
// Remove Gen2 pre-evolutions (Pichu, Cleffa...)
if (g1[^1].Species > MaxSpeciesID_1)
{
if (g1.Length == 1)
{
lastGen = Array.Empty<EvoCriteria>();
continue; // done
}
g1 = g1[..^1];
}
if (g1.Length != lastGen.Length)
lastGen = g1.ToArray();
// Update min level for the encounter to prevent certain level up moves.
if (g1.Count != 0)
g1[^1].MinLevel = enc.LevelMin;
if (g1.Length != 0)
{
ref var last = ref g1[^1];
last = last with { LevelMin = enc.LevelMin };
}
}
}
return GensEvoChains;
@ -147,23 +170,15 @@ namespace PKHeX.Core
_ => false,
};
private static void TrimVC1Transfer(PKM pkm, IList<List<EvoCriteria>> allChains)
private static void TrimVC1Transfer(PKM pkm, EvoCriteria[][] allChains)
{
if (allChains[7].All(z => z.Species > MaxSpeciesID_1))
var vc7 = allChains[7];
var gen1Index = Array.FindLastIndex(vc7, z => z.Species <= MaxSpeciesID_1);
if (gen1Index == -1)
allChains[pkm.Format] = NONE; // needed a Gen1 species present; invalidate the chain.
}
internal static int GetEvoChainSpeciesIndex(IReadOnlyList<EvoCriteria> chain, int species)
{
for (int i = 0; i < chain.Count; i++)
{
if (chain[i].Species == species)
return i;
}
return -1;
}
private static List<EvoCriteria> GetEvolutionChain(PKM pkm, IEncounterTemplate enc, int mostEvolvedSpecies, int maxlevel)
private static EvoCriteria[] GetEvolutionChain(PKM pkm, IEncounterTemplate enc, int mostEvolvedSpecies, byte maxlevel)
{
int min = enc.LevelMin;
if (pkm.HasOriginalMetLocation && pkm.Met_Level != 0)
@ -171,72 +186,76 @@ namespace PKHeX.Core
var chain = GetValidPreEvolutions(pkm, minLevel: min);
if (enc.Species == mostEvolvedSpecies)
{
if (chain.Count != 1)
chain.RemoveAll(z => z.Species != enc.Species);
if (chain.Length == 1)
return chain;
var index = Array.FindLastIndex(chain, z => z.Species == enc.Species);
if (index == -1)
return Array.Empty<EvoCriteria>();
return new[] { chain[index] };
}
// Evolution chain is in reverse order (devolution)
// Find the index of the minimum species to determine the end of the chain
int minIndex = GetEvoChainSpeciesIndex(chain, enc.Species);
bool last = minIndex < 0 || minIndex == chain.Count - 1;
int minIndex = Array.FindLastIndex(chain, z => z.Species == enc.Species);
bool last = minIndex < 0 || minIndex == chain.Length - 1;
// If we remove a pre-evolution, update the chain if appropriate.
if (!last)
{
// Remove chain species after the encounter
int count = chain.Count;
for (int i = minIndex + 1; i < count; i++)
chain.RemoveAt(i);
if (minIndex + 1 == chain.Length)
return Array.Empty<EvoCriteria>(); // no species left in chain
if (chain.Count == 0)
return chain; // no species left in chain
chain = chain.AsSpan(0, minIndex + 1).ToArray();
CheckLastEncounterRemoval(enc, chain);
}
// maxspec is used to remove future geneneration evolutions, to gather evolution chain of a pokemon in previous generations
int skip = Math.Max(0, GetEvoChainSpeciesIndex(chain, mostEvolvedSpecies));
for (int i = 0; i < skip; i++)
chain.RemoveAt(0);
var maxSpeciesIndex = Array.FindIndex(chain, z => z.Species == mostEvolvedSpecies);
if (maxSpeciesIndex > 0)
chain = chain.AsSpan(maxSpeciesIndex).ToArray();
// Gen3->4 and Gen4->5 transfer sets the Met Level property to the Pokémon's current level.
// Removes evolutions impossible before the transfer level.
// For example a FireRed Charizard with a current level (in XY) is 50 but Met Level is 20; it couldn't be a Charizard in Gen3 and Gen4 games
chain.RemoveAll(e => e.MinLevel > maxlevel);
var clampIndex = Array.FindIndex(chain, z => z.LevelMin > maxlevel);
if (clampIndex != -1)
chain = Array.FindAll(chain, z => z.LevelMin <= maxlevel);
// Reduce the evolution chain levels to max level to limit any later analysis/results.
foreach (var d in chain)
d.Level = Math.Min(d.Level, maxlevel);
for (var i = 0; i < chain.Length; i++)
{
ref var c = ref chain[i];
c = c with { LevelMax = Math.Min(c.LevelMax, maxlevel) };
}
return chain;
}
private static void CheckLastEncounterRemoval(IEncounterTemplate enc, IReadOnlyList<EvoCriteria> chain)
private static void CheckLastEncounterRemoval(IEncounterTemplate enc, EvoCriteria[] chain)
{
// Last entry from chain is removed, turn next entry into the encountered Pokémon
var last = chain[^1];
last.MinLevel = enc.LevelMin;
last.RequiresLvlUp = false;
ref var last = ref chain[^1];
last = last with { LevelMin = enc.LevelMin, LevelUpRequired = 1 };
var first = chain[0];
ref var first = ref chain[0];
if (first.RequiresLvlUp)
return;
if (first.MinLevel == 2)
if (first.LevelMin == 2)
{
// Example: Raichu in Gen2 or later
// Because Pichu requires a level up, the minimum level of the resulting Raichu must be be >2
// But after removing Pichu (because the origin species is Pikachu), the Raichu minimum level should be 1.
first.MinLevel = 1;
first.RequiresLvlUp = false;
first = first with { LevelMin = 1, LevelUpRequired = 0 };
}
else // in-game trade or evolution stone can evolve immediately
{
first.MinLevel = last.MinLevel;
first = first with { LevelMin = enc.LevelMin };
}
}
internal static List<EvoCriteria> GetValidPreEvolutions(PKM pkm, int maxspeciesorigin = -1, int maxLevel = -1, int minLevel = 1, bool skipChecks = false)
internal static EvoCriteria[] GetValidPreEvolutions(PKM pkm, int maxspeciesorigin = -1, int maxLevel = -1, int minLevel = 1, bool skipChecks = false)
{
if (maxLevel < 0)
maxLevel = pkm.CurrentLevel;
@ -246,7 +265,7 @@ namespace PKHeX.Core
int tree = Math.Max(2, pkm.Format);
var et = EvolutionTree.GetEvolutionTree(pkm, tree);
return et.GetValidPreEvolutions(pkm, maxLevel: maxLevel, maxSpeciesOrigin: maxspeciesorigin, skipChecks: skipChecks, minLevel: minLevel);
return et.GetValidPreEvolutions(pkm, maxLevel: (byte)maxLevel, maxSpeciesOrigin: maxspeciesorigin, skipChecks: skipChecks, minLevel: (byte)minLevel);
}
private static int GetMinLevelGeneration(PKM pkm, int generation)

View file

@ -25,7 +25,7 @@ namespace PKHeX.Core
/// <summary>
/// Conditional Argument (different from <see cref="Argument"/>)
/// </summary>
public readonly int Level;
public readonly byte Level;
/// <summary>
/// Destination Form
@ -38,7 +38,7 @@ namespace PKHeX.Core
// Not stored in binary data
public bool RequiresLevelUp; // tracks if this method requires a Level Up, lazily set
public EvolutionMethod(int method, int species, int argument = 0, int level = 0, int form = AnyForm)
public EvolutionMethod(int method, int species, int argument = 0, byte level = 0, int form = AnyForm)
{
Method = method;
Species = species;
@ -147,10 +147,13 @@ namespace PKHeX.Core
};
}
public EvoCriteria GetEvoCriteria(int species, int form, int lvl) => new(species, form)
public EvoCriteria GetEvoCriteria(ushort species, byte form, byte lvl) => new()
{
Level = lvl,
Method = Method,
Species = species,
Form = form,
LevelMax = lvl,
LevelMin = 0,
Method = (EvolutionType)Method,
};
public static int GetAmpLowKeyResult(int n)

View file

@ -12,7 +12,7 @@ namespace PKHeX.Core
{
int method = data[0];
int species = data[1];
int arg = data[2];
var arg = data[2];
return (method == 1)
? new EvolutionMethod(method, species, level: arg)

View file

@ -25,7 +25,7 @@ namespace PKHeX.Core
case 6: /* Trade while holding */
return new EvolutionMethod(method, species, argument: arg);
case 4: /* Level Up */
return new EvolutionMethod(4, species, argument: arg, level:arg);
return new EvolutionMethod(4, species, argument: arg, level: (byte)arg);
case 7: /* Use item */
case 15: /* Beauty evolution*/
return new EvolutionMethod(method + 1, species, argument: arg);
@ -36,7 +36,7 @@ namespace PKHeX.Core
case 12: /* Wurmple -> Cascoon evolution */
case 13: /* Nincada -> Ninjask evolution */
case 14: /* Shedinja spawn in Nincada -> Ninjask evolution */
return new EvolutionMethod(method + 1, species, argument: arg, level: arg);
return new EvolutionMethod(method + 1, species, argument: arg, level: (byte)arg);
default:
throw new ArgumentOutOfRangeException(nameof(method));

View file

@ -24,7 +24,7 @@ namespace PKHeX.Core
method++;
var lvl = EvolutionSet6.EvosWithArg.Contains(method) ? 0 : arg;
return new EvolutionMethod(method, species, argument: arg, level: lvl);
return new EvolutionMethod(method, species, argument: arg, level: (byte)lvl);
}
public static IReadOnlyList<EvolutionMethod[]> GetArray(ReadOnlySpan<byte> data)

View file

@ -19,7 +19,7 @@ namespace PKHeX.Core
throw new ArgumentOutOfRangeException(nameof(method));
var lvl = EvolutionSet6.EvosWithArg.Contains(method) ? 0 : arg;
return new EvolutionMethod(method, species, argument: arg, level: lvl);
return new EvolutionMethod(method, species, argument: arg, level: (byte)lvl);
}
private const int bpe = 6; // bytes per evolution entry
@ -42,7 +42,7 @@ namespace PKHeX.Core
var set = new EvolutionMethod[count];
for (int j = 0; j < set.Length; j++)
set[j] = GetMethod(rawEntries.Slice((j * bpe), bpe));
set[j] = GetMethod(rawEntries.Slice(j * bpe, bpe));
evos[i] = set;
}
return evos;

View file

@ -32,7 +32,7 @@ namespace PKHeX.Core
// Argument is used by both Level argument and Item/Move/etc. Clear if appropriate.
var lvl = EvosWithArg.Contains(method) ? 0 : arg;
return new EvolutionMethod(method, species, argument: arg, level: lvl);
return new EvolutionMethod(method, species, argument: arg, level: (byte)lvl);
}
public static IReadOnlyList<EvolutionMethod[]> GetArray(BinLinkerAccessor data)

View file

@ -229,26 +229,26 @@ namespace PKHeX.Core
/// <param name="maxSpeciesOrigin">Maximum species ID to permit within the chain.</param>
/// <param name="skipChecks">Ignores an evolution's criteria, causing the returned list to have all possible evolutions.</param>
/// <param name="minLevel">Minimum level to permit before the chain breaks.</param>
/// <returns></returns>
public List<EvoCriteria> GetValidPreEvolutions(PKM pkm, int maxLevel, int maxSpeciesOrigin = -1, bool skipChecks = false, int minLevel = 1)
public EvoCriteria[] GetValidPreEvolutions(PKM pkm, byte maxLevel, int maxSpeciesOrigin = -1, bool skipChecks = false, byte minLevel = 1)
{
if (maxSpeciesOrigin <= 0)
maxSpeciesOrigin = GetMaxSpeciesOrigin(pkm);
if (pkm.IsEgg && !skipChecks)
{
return new List<EvoCriteria>(1)
return new[]
{
new(pkm.Species, pkm.Form) { Level = maxLevel, MinLevel = maxLevel },
new EvoCriteria{ Species = (ushort)pkm.Species, Form = (byte)pkm.Form, LevelMax = maxLevel, LevelMin = maxLevel },
};
}
// Shedinja's evolution case can be a little tricky; hard-code handling.
if (pkm.Species == (int)Species.Shedinja && maxLevel >= 20 && (!pkm.HasOriginalMetLocation || minLevel < maxLevel))
{
return new List<EvoCriteria>(2)
var min = Math.Max(minLevel, (byte)20);
return new[]
{
new((int)Species.Shedinja, 0) { Level = maxLevel, MinLevel = Math.Max(minLevel, 20) },
new((int)Species.Nincada, 0) { Level = maxLevel, MinLevel = minLevel },
new EvoCriteria { Species = (ushort)Species.Shedinja, LevelMax = maxLevel, LevelMin = min, Method = EvolutionType.LevelUp },
new EvoCriteria { Species = (ushort)Species.Nincada, LevelMax = maxLevel, LevelMin = minLevel },
};
}
@ -353,16 +353,16 @@ namespace PKHeX.Core
/// <param name="skipChecks">Skip the secondary checks that validate the evolution</param>
/// <param name="maxSpeciesOrigin">Clamp for maximum species ID</param>
/// <param name="minLevel">Minimum level</param>
/// <returns></returns>
private List<EvoCriteria> GetExplicitLineage(PKM pkm, int maxLevel, bool skipChecks, int maxSpeciesOrigin, int minLevel)
private EvoCriteria[] GetExplicitLineage(PKM pkm, byte maxLevel, bool skipChecks, int maxSpeciesOrigin, byte minLevel)
{
int species = pkm.Species;
int form = pkm.Form;
int lvl = maxLevel;
var first = new EvoCriteria(species, form) { Level = lvl };
var species = pkm.Species;
var form = pkm.Form;
var lvl = maxLevel;
var first = new EvoCriteria { Species = (ushort)species, Form = (byte)form, LevelMax = lvl };
const int maxEvolutions = 3;
var dl = new List<EvoCriteria>(maxEvolutions) { first };
Span<EvoCriteria> dl = stackalloc EvoCriteria[maxEvolutions];
dl[0] = first;
switch (species)
{
@ -372,6 +372,7 @@ namespace PKHeX.Core
// There aren't any circular evolution paths, and all lineages have at most 3 evolutions total.
// There aren't any convergent evolution paths, so only yield the first connection.
int ctr = 1;
while (true)
{
var key = GetLookupKey(species, form);
@ -391,12 +392,11 @@ namespace PKHeX.Core
break; // impossible evolution
oneValid = true;
UpdateMinValues(dl, evo, minLevel);
UpdateMinValues(dl[..ctr], evo, minLevel);
species = link.Species;
form = link.Form;
var detail = evo.GetEvoCriteria(species, form, lvl);
dl.Add(detail);
dl[ctr++] = evo.GetEvoCriteria((ushort)species, (byte)form, lvl);
if (evo.RequiresLevelUp)
lvl--;
break;
@ -406,64 +406,79 @@ namespace PKHeX.Core
}
// Remove future gen pre-evolutions; no Munchlax from a Gen3 Snorlax, no Pichu from a Gen1-only Raichu, etc
var last = dl[^1];
if (last.Species > maxSpeciesOrigin && dl.Any(d => d.Species <= maxSpeciesOrigin))
dl.RemoveAt(dl.Count - 1);
ref var last = ref dl[ctr - 1];
if (last.Species > maxSpeciesOrigin)
{
for (int i = 0; i < ctr; i++)
{
if (dl[i].Species > maxSpeciesOrigin)
continue;
ctr--;
break;
}
}
// Last species is the wild/hatched species, the minimum level is because it has not evolved from previous species
last = dl[^1];
last.MinLevel = minLevel;
last.RequiresLvlUp = false;
var result = dl[..ctr];
last = ref result[^1];
last = last with { LevelMin = minLevel, LevelUpRequired = 0 };
// Rectify minimum levels
for (int i = dl.Count - 2; i >= 0; i--)
for (int i = result.Length - 2; i >= 0; i--)
{
var evo = dl[i];
var prev = dl[i + 1];
evo.MinLevel = Math.Max(prev.MinLevel + (evo.RequiresLvlUp ? 1 : 0), evo.MinLevel);
ref var evo = ref result[i];
var prev = result[i + 1];
var min = (byte)Math.Max(prev.LevelMin + evo.LevelUpRequired, evo.LevelMin);
evo = evo with { LevelMin = min };
}
return dl;
return result.ToArray();
}
private static void UpdateMinValues(IReadOnlyList<EvoCriteria> dl, EvolutionMethod evo, int minLevel)
private static void UpdateMinValues(Span<EvoCriteria> dl, EvolutionMethod evo, byte minLevel)
{
var last = dl[^1];
ref var last = ref dl[^1];
if (!evo.RequiresLevelUp)
{
// Evolutions like elemental stones, trade, etc
last.MinLevel = minLevel;
last = last with { LevelMin = minLevel };
return;
}
if (evo.Level == 0)
{
// Friendship based Level Up Evolutions, Pichu -> Pikachu, Eevee -> Umbreon, etc
last.MinLevel = minLevel + 1;
last = last with { LevelMin = (byte)(minLevel + 1) };
var first = dl[0];
if (dl.Count > 1 && !first.RequiresLvlUp)
first.MinLevel = minLevel + 1; // Raichu from Pikachu would have a minimum level of 1; accounting for Pichu (level up required) results in a minimum level of 2
// Raichu from Pikachu would have a minimum level of 1; accounting for Pichu (level up required) results in a minimum level of 2
if (dl.Length > 1)
{
ref var first = ref dl[0];
if (!first.RequiresLvlUp)
first = first with { LevelMin = (byte)(minLevel + 1) };
}
}
else // level up evolutions
{
last.MinLevel = evo.Level;
last = last with { LevelMin = evo.Level };
var first = dl[0];
if (dl.Count > 1)
if (dl.Length > 1)
{
ref var first = ref dl[0];
if (first.RequiresLvlUp)
{
if (first.MinLevel <= evo.Level)
first.MinLevel = evo.Level + 1; // Pokemon like Crobat, its minimum level is Golbat minimum level + 1
// Pokemon like Crobat, its minimum level is Golbat minimum level + 1
if (first.LevelMin <= evo.Level)
first = first with {LevelMin = (byte)(evo.Level + 1) };
}
else
{
if (first.MinLevel < evo.Level)
first.MinLevel = evo.Level; // Pokemon like Nidoqueen who evolve with an evolution stone, minimum level is prior evolution minimum level
// Pokemon like Nidoqueen who evolve with an evolution stone, minimum level is prior evolution minimum level
if (first.LevelMin < evo.Level)
first = first with { LevelMin = evo.Level };
}
}
}
last.RequiresLvlUp = evo.RequiresLevelUp;
last = last with { LevelUpRequired = evo.RequiresLevelUp ? (byte)1 : (byte)0 };
}
/// <summary>

View file

@ -0,0 +1,11 @@
namespace PKHeX.Core;
/// <summary>
/// Small general purpose value passing object with misc data pertaining to an encountered Species.
/// </summary>
public interface IDexLevel
{
ushort Species { get; }
byte Form { get; }
byte LevelMax { get; }
}

View file

@ -195,25 +195,25 @@ namespace PKHeX.Core
/// <summary>
/// ONLY CALL FOR GEN2 EGGS
/// </summary>
internal static IEnumerable<int> GetExclusivePreEvolutionMoves(PKM pkm, int Species, IReadOnlyList<EvoCriteria> evoChain, int generation, GameVersion Version)
internal static IEnumerable<int> GetExclusivePreEvolutionMoves(PKM pkm, int Species, EvoCriteria[] evoChain, int generation, GameVersion Version)
{
var preevomoves = new List<int>();
var evomoves = new List<int>();
var index = EvolutionChain.GetEvoChainSpeciesIndex(evoChain, Species);
for (int i = 0; i < evoChain.Count; i++)
var index = Array.FindIndex(evoChain, z => z.Species == Species);
for (int i = 0; i < evoChain.Length; i++)
{
int minLvLG2;
var evo = evoChain[i];
if (ParseSettings.AllowGen2MoveReminder(pkm))
minLvLG2 = 0;
else if (i == evoChain.Count - 1) // minimum level, otherwise next learnable level
else if (i == evoChain.Length - 1) // minimum level, otherwise next learnable level
minLvLG2 = 5;
else if (evo.RequiresLvlUp)
minLvLG2 = evo.Level + 1;
minLvLG2 = evo.LevelMax + 1;
else
minLvLG2 = evo.Level;
minLvLG2 = evo.LevelMax;
var moves = GetMoves(pkm, evo.Species, evo.Form, evo.Level, 0, minLvLG2, Version: Version, types: MoveSourceType.ExternalSources, RemoveTransferHM: false, generation: generation);
var moves = GetMoves(pkm, evo.Species, evo.Form, evo.LevelMax, 0, minLvLG2, Version: Version, types: MoveSourceType.ExternalSources, RemoveTransferHM: false, generation: generation);
var list = i >= index ? preevomoves : evomoves;
list.AddRange(moves);
}
@ -239,15 +239,15 @@ namespace PKHeX.Core
if (generation <= 2)
{
if (encounteredEvo) // minimum level, otherwise next learnable level
minLvLG1 = evo.MinLevel + 1;
minLvLG1 = evo.LevelMin + 1;
else // learns level up moves immediately after evolving
minLvLG1 = evo.MinLevel;
minLvLG1 = evo.LevelMin;
if (!ParseSettings.AllowGen2MoveReminder(pkm))
minLvLG2 = minLvLG1;
}
var maxLevel = evo.Level;
var maxLevel = evo.LevelMax;
if (!encounteredEvo) // evolution
++maxLevel; // allow lvlmoves from the level it evolved to the next species
var moves = GetMoves(pkm, evo.Species, evo.Form, maxLevel, minLvLG1, minLvLG2, version, types, RemoveTransferHM, generation);
@ -278,7 +278,7 @@ namespace PKHeX.Core
formCount = pkm.PersonalInfo.FormCount;
for (int form = 0; form < formCount; form++)
r.AddRange(GetMoves(pkm, species, form, chain[0].Level, 0, 0, version, types, RemoveTransferHM, generation));
r.AddRange(GetMoves(pkm, species, form, chain[0].LevelMax, 0, 0, version, types, RemoveTransferHM, generation));
if (types.HasFlagFast(MoveSourceType.RelearnMoves))
r.AddRange(pkm.RelearnMoves);
return r.Distinct();

View file

@ -397,7 +397,7 @@ namespace PKHeX.Core
{
// For species catch rate, discard any species that has no valid encounters and a different catch rate than their pre-evolutions
var table = EvolutionTree.GetEvolutionTree(1);
var chain = table.GetValidPreEvolutions(pkm, maxLevel: pkm.CurrentLevel);
var chain = table.GetValidPreEvolutions(pkm, maxLevel: (byte)pkm.CurrentLevel);
foreach (var entry in chain)
{
var s = entry.Species;

View file

@ -55,8 +55,8 @@ namespace PKHeX.Core
private static readonly ValidEncounterMoves NONE = new();
public ValidEncounterMoves EncounterMoves { get; internal set; } = NONE;
public IReadOnlyList<EvoCriteria>[] EvoChainsAllGens => _evochains ??= EvolutionChain.GetEvolutionChainsAllGens(pkm, EncounterMatch);
private IReadOnlyList<EvoCriteria>[]? _evochains;
public EvoCriteria[][] EvoChainsAllGens => _evochains ??= EvolutionChain.GetEvolutionChainsAllGens(pkm, EncounterMatch);
private EvoCriteria[][]? _evochains;
/// <summary><see cref="RNG"/> related information that generated the <see cref="PKM.PID"/>/<see cref="PKM.IVs"/> value(s).</summary>
public PIDIV PIDIV

View file

@ -232,7 +232,7 @@ namespace PKHeX.Core
// If the species could not exist in Gen3, must match.
var g3 = info.EvoChainsAllGens[3];
if (g3.Count == 0)
if (g3.Length == 0)
return AbilityState.MustMatch;
// Fall through when gen3 pkm transferred to gen4/5
@ -251,7 +251,7 @@ namespace PKHeX.Core
return AbilityState.MustMatch;
var chain = data.Info.EvoChainsAllGens;
bool evolved45 = chain[4].Count > 1 || (pkm.Format == 5 && chain[5].Count > 1);
bool evolved45 = chain[4].Length > 1 || (pkm.Format == 5 && chain[5].Length > 1);
if (evolved45)
{
if (pkm.Ability == pers.Ability1) // Could evolve in Gen4/5 and have a Gen3 only ability

View file

@ -49,7 +49,7 @@ namespace PKHeX.Core
// The special move verifier has a similar check!
if (pkm.HGSS && pkm.Ball == (int)Sport) // Can evolve in DP to retain the HG/SS ball -- not able to be captured in any other ball
return VerifyBallEquals(data, (int)Sport);
if (Info.Generation != 3 || Info.EvoChainsAllGens[3].Count != 2)
if (Info.Generation != 3 || Info.EvoChainsAllGens[3].Length != 2)
return VerifyBallEquals(data, (int)Poke); // Pokeball Only
}

View file

@ -105,8 +105,8 @@ public sealed class LegendsArceusVerifier : Verifier
// Evolve and try
for (int i = 0; i < evos.Count - 1; i++)
{
var (species, form) = evos[i];
index = pt.GetFormIndex(species, form);
var evo = evos[i];
index = pt.GetFormIndex(evo.Species, evo.Form);
moveset = Legal.LevelUpLA[index];
moveset.SetEvolutionMoves(moves, purchased, count);
count = moves.IndexOf(0);
@ -210,10 +210,10 @@ public sealed class LegendsArceusVerifier : Verifier
// Changing forms do not have separate tutor permissions, so we don't need to bother with form changes.
// Level up movepools can grant moves for mastery at lower levels for earlier evolutions... find the minimum.
int level = 101;
foreach (var (species, form) in data.Info.EvoChainsAllGens[8])
foreach (var evo in data.Info.EvoChainsAllGens[8])
{
var pt = PersonalTable.LA;
var index = pt.GetFormIndex(species, form);
var index = pt.GetFormIndex(evo.Species, evo.Form);
var moveset = Legal.LevelUpLA[index];
var lvl = moveset.GetLevelLearnMove(moves[i]);
if (lvl == -1)

View file

@ -74,7 +74,7 @@ namespace PKHeX.Core
public PKM ConvertToPKM(ITrainerInfo sav) => ConvertToPKM(sav, EncounterCriteria.Unrestricted);
public abstract PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria);
public abstract bool IsMatchExact(PKM pkm, DexLevel evo);
public abstract bool IsMatchExact(PKM pkm, IDexLevel evo);
protected abstract bool IsMatchDeferred(PKM pkm);
protected abstract bool IsMatchPartial(PKM pkm);

View file

@ -124,7 +124,7 @@ namespace PKHeX.Core
public bool CanBeReceivedBy(int pkmVersion) => (CardCompatibility >> pkmVersion & 1) == 1;
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
var wc = Gift.PK;
if (!wc.IsEgg)

View file

@ -358,7 +358,7 @@ namespace PKHeX.Core
pk.SetIVs(finalIVs);
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (!IsEgg)
{

View file

@ -277,7 +277,7 @@ namespace PKHeX.Core
}
// Nothing is stored as a PGT besides Ranger Manaphy. Nothing should trigger these.
public override bool IsMatchExact(PKM pkm, DexLevel evo) => false;
public override bool IsMatchExact(PKM pkm, IDexLevel evo) => false;
protected override bool IsMatchDeferred(PKM pkm) => false;
protected override bool IsMatchPartial(PKM pkm) => false;

View file

@ -583,7 +583,7 @@ namespace PKHeX.Core
pk.SetIVs(finalIVs);
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (pkm.Egg_Location == 0) // Not Egg
{

View file

@ -529,7 +529,7 @@ namespace PKHeX.Core
public bool CanHandleOT(int language) => string.IsNullOrEmpty(GetOT(language));
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (pkm.Egg_Location == 0) // Not Egg
{

View file

@ -579,7 +579,7 @@ namespace PKHeX.Core
pk.SetIVs(finalIVs);
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if ((short)pkm.Egg_Location == Locations.Default8bNone) // Not Egg
{

View file

@ -216,7 +216,7 @@ namespace PKHeX.Core
};
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
// Gen3 Version MUST match.
if (Version != 0 && !Version.Contains((GameVersion)pkm.Version))

View file

@ -518,7 +518,7 @@ namespace PKHeX.Core
pk.SetIVs(finalIVs);
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (pkm.Egg_Location == 0) // Not Egg
{

View file

@ -554,7 +554,7 @@ namespace PKHeX.Core
return CardID == 2046 && (pkm.SID << 16 | pkm.TID) == 0x79F57B49;
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (pkm.Egg_Location == 0) // Not Egg
{

View file

@ -582,7 +582,7 @@ namespace PKHeX.Core
pk.SetIVs(finalIVs);
}
public override bool IsMatchExact(PKM pkm, DexLevel evo)
public override bool IsMatchExact(PKM pkm, IDexLevel evo)
{
if (pkm.Egg_Location == 0) // Not Egg
{

View file

@ -101,7 +101,7 @@ namespace PKHeX.Core
}
// Mystery Gift implementation, unused.
public override bool IsMatchExact(PKM pkm, DexLevel evo) => false;
public override bool IsMatchExact(PKM pkm, IDexLevel evo) => false;
protected override bool IsMatchDeferred(PKM pkm) => false;
protected override bool IsMatchPartial(PKM pkm) => false;
public override Shiny Shiny => Shiny.Never;