mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-22 20:13:06 +00:00
Evotree: Evolution Traversal Enhancements (#3936)
Like move validation, evolutions are the earliest thing we wish to traverse when determining what encounters may have originated the current Pokémon. To determine the permitted species-form-levels a Pokémon could originate with, we must devolve a Pokémon by traveling down-generation to origin. Once we have an encounter, we can then evolve it to the current species, traversing upwards from origin to the current format.
This commit is contained in:
parent
e02b33ef2c
commit
dcc0e79435
175 changed files with 3663 additions and 2503 deletions
|
@ -73,7 +73,7 @@ public static class BallApplicator
|
|||
return pk.Ball = (int)next;
|
||||
}
|
||||
|
||||
private static int ApplyFirstLegalBall(PKM pk, Span<Ball> balls)
|
||||
private static int ApplyFirstLegalBall(PKM pk, ReadOnlySpan<Ball> balls)
|
||||
{
|
||||
foreach (var b in balls)
|
||||
{
|
||||
|
|
|
@ -112,7 +112,7 @@ public static class QRMessageUtil
|
|||
// Decode unicode string -- size might be pretty big (user input), so just rent instead of stackalloc
|
||||
var tmp = ArrayPool<byte>.Shared.Rent(url.Length);
|
||||
var result = Convert.TryFromBase64Chars(url, tmp, out int bytesWritten) ? tmp[..bytesWritten] : null;
|
||||
ArrayPool<byte>.Shared.Return(tmp);
|
||||
ArrayPool<byte>.Shared.Return(tmp, true);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
|
@ -106,4 +108,47 @@ public static class LocationsHOME
|
|||
SWSL when ver == (int)GameVersion.SW => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the location is (potentially) remapped based on visitation options.
|
||||
/// </summary>
|
||||
/// <remarks>Relevant when a side data yields SW/SH side data with a higher priority than the original (by version) side data.</remarks>
|
||||
/// <param name="original">Original context</param>
|
||||
/// <param name="current">Current context</param>
|
||||
public static LocationRemapState GetRemapState(EntityContext original, EntityContext current)
|
||||
{
|
||||
if (current == original)
|
||||
return LocationRemapState.Original;
|
||||
if (current == EntityContext.Gen8)
|
||||
return LocationRemapState.Remapped;
|
||||
return original.Generation() switch
|
||||
{
|
||||
< 8 => LocationRemapState.Original,
|
||||
8 => LocationRemapState.Either,
|
||||
_ => current is (EntityContext.Gen8a or EntityContext.Gen8b) // down
|
||||
? LocationRemapState.Either
|
||||
: LocationRemapState.Original,
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsMatchLocation(EntityContext original, EntityContext current, int met, int expect, int version)
|
||||
{
|
||||
var state = GetRemapState(original, current);
|
||||
return state switch
|
||||
{
|
||||
LocationRemapState.Original => met == expect,
|
||||
LocationRemapState.Remapped => met == GetMetSWSH((ushort)expect, version),
|
||||
LocationRemapState.Either => met == expect || met == GetMetSWSH((ushort)expect, version),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum LocationRemapState
|
||||
{
|
||||
None,
|
||||
Original = 1 << 0,
|
||||
Remapped = 1 << 1,
|
||||
Either = Original | Remapped,
|
||||
}
|
||||
|
|
|
@ -82,6 +82,11 @@ public sealed class ItemStorage9SV : IItemStorage
|
|||
2210, 2211, 2212, 2213, 2214, 2215, 2216, 2217, 2218, 2219,
|
||||
2220, 2221, 2222, 2223, 2224, 2225, 2226, 2227, 2228, 2229,
|
||||
2230, 2231,
|
||||
|
||||
// DLC additions
|
||||
// 2232, 2233, 2234, 2235, 2236, 2237, 2238, 2239, 2240, 2241,
|
||||
// 2242, 2243, 2244, 2245, 2246, 2247, 2248, 2249, 2250, 2251,
|
||||
// 2252, 2253, 2254, 2255, 2256, 2257, 2258, 2259, 2260, 2261,
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<ushort> Pouch_Treasure_SV => new ushort[]
|
||||
|
|
|
@ -139,20 +139,6 @@ public static class Legal
|
|||
internal static readonly ushort[] HeldItems_LA = Array.Empty<ushort>();
|
||||
internal static readonly ushort[] HeldItems_SV = ItemStorage9SV.GetAllHeld();
|
||||
|
||||
internal static int GetMaxSpeciesOrigin(int generation) => generation switch
|
||||
{
|
||||
1 => MaxSpeciesID_1,
|
||||
2 => MaxSpeciesID_2,
|
||||
3 => MaxSpeciesID_3,
|
||||
4 => MaxSpeciesID_4,
|
||||
5 => MaxSpeciesID_5,
|
||||
6 => MaxSpeciesID_6,
|
||||
7 => MaxSpeciesID_7b,
|
||||
8 => MaxSpeciesID_8a,
|
||||
9 => MaxSpeciesID_9,
|
||||
_ => -1,
|
||||
};
|
||||
|
||||
internal static int GetMaxLanguageID(int generation) => generation switch
|
||||
{
|
||||
1 => (int) LanguageID.Spanish, // 1-7 except 6
|
||||
|
@ -231,11 +217,12 @@ public static class Legal
|
|||
/// </summary>
|
||||
public static bool GetIsFixedIVSequenceValidSkipRand(ReadOnlySpan<int> IVs, PKM pk, uint max = 31)
|
||||
{
|
||||
for (int i = 0; i < 6; i++)
|
||||
for (int i = 5; i >= 0; i--)
|
||||
{
|
||||
if ((uint) IVs[i] > max) // random
|
||||
var iv = IVs[i];
|
||||
if ((uint)iv > max) // random
|
||||
continue;
|
||||
if (IVs[i] != pk.GetIV(i))
|
||||
if (iv != pk.GetIV(i))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -48,7 +48,7 @@ internal static class EncounterUtil
|
|||
return temp;
|
||||
}
|
||||
|
||||
internal static T? GetMinByLevel<T>(EvoCriteria[] chain, IEnumerable<T> possible) where T : class, IEncounterTemplate
|
||||
internal static T? GetMinByLevel<T>(ReadOnlySpan<EvoCriteria> chain, IEnumerable<T> possible) where T : class, IEncounterTemplate
|
||||
{
|
||||
// MinBy grading: prefer species-form match, select lowest min level encounter.
|
||||
// Minimum allocation :)
|
||||
|
|
|
@ -14,7 +14,7 @@ public sealed record EncounterInvalid : IEncounterable
|
|||
public byte LevelMin { get; }
|
||||
public byte LevelMax { get; }
|
||||
public bool EggEncounter { get; }
|
||||
public int Generation { get; init; }
|
||||
public int Generation { get; }
|
||||
public EntityContext Context { get; }
|
||||
public GameVersion Version { get; }
|
||||
public bool IsShiny => false;
|
||||
|
|
|
@ -53,8 +53,25 @@ public sealed record EncounterFixed9 : EncounterStatic, IGemType
|
|||
|
||||
protected override bool IsMatchLocation(PKM pk)
|
||||
{
|
||||
if (pk is PK8)
|
||||
return LocationsHOME.IsValidMetSV((ushort)pk.Met_Location, pk.Version);
|
||||
var metState = LocationsHOME.GetRemapState(Context, pk.Context);
|
||||
if (metState == LocationRemapState.Original)
|
||||
return IsMatchLocationExact(pk);
|
||||
if (metState == LocationRemapState.Remapped)
|
||||
return IsMatchLocationRemapped(pk);
|
||||
return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk);
|
||||
}
|
||||
|
||||
private bool IsMatchLocationRemapped(PKM pk)
|
||||
{
|
||||
var met = (ushort)pk.Met_Location;
|
||||
var version = pk.Version;
|
||||
if (pk.Context == EntityContext.Gen8)
|
||||
return LocationsHOME.IsValidMetSV(met, version);
|
||||
return LocationsHOME.GetMetSWSH((ushort)Location, version) == met;
|
||||
}
|
||||
|
||||
private bool IsMatchLocationExact(PKM pk)
|
||||
{
|
||||
var loc = pk.Met_Location;
|
||||
return loc == Location0 || loc == Location1 || loc == Location2 || loc == Location3;
|
||||
}
|
||||
|
|
|
@ -213,9 +213,23 @@ public sealed record EncounterMight9 : EncounterStatic, ITeraRaid9
|
|||
|
||||
protected override bool IsMatchLocation(PKM pk)
|
||||
{
|
||||
if (pk is PK8)
|
||||
return LocationsHOME.IsValidMetSV((ushort)pk.Met_Location, pk.Version);
|
||||
return base.IsMatchLocation(pk);
|
||||
var metState = LocationsHOME.GetRemapState(Context, pk.Context);
|
||||
if (metState == LocationRemapState.Original)
|
||||
return IsMatchLocationExact(pk);
|
||||
if (metState == LocationRemapState.Remapped)
|
||||
return IsMatchLocationRemapped(pk);
|
||||
return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk);
|
||||
}
|
||||
|
||||
private bool IsMatchLocationExact(PKM pk) => pk.Met_Location == Location;
|
||||
|
||||
private bool IsMatchLocationRemapped(PKM pk)
|
||||
{
|
||||
var met = (ushort)pk.Met_Location;
|
||||
var version = pk.Version;
|
||||
if (pk.Context == EntityContext.Gen8)
|
||||
return LocationsHOME.IsValidMetSV(met, version);
|
||||
return LocationsHOME.GetMetSWSH((ushort)Location, version) == met;
|
||||
}
|
||||
|
||||
protected override EncounterMatchRating IsMatchDeferred(PKM pk)
|
||||
|
|
|
@ -72,10 +72,38 @@ public sealed record EncounterStatic8a(GameVersion Version) : EncounterStatic(Ve
|
|||
|
||||
if (pk is IScaledSize s)
|
||||
{
|
||||
if (HasFixedHeight && s.HeightScalar != HeightScalar)
|
||||
return false;
|
||||
if (HasFixedWeight && s.WeightScalar != WeightScalar)
|
||||
return false;
|
||||
// 3 of the Alpha statics were mistakenly set as 127 scale. If they enter HOME on 3.0.1, they'll get bumped to 255.
|
||||
if (IsAlpha && this is { HeightScalar: 127, WeightScalar: 127 }) // Average Size Alphas
|
||||
{
|
||||
// HOME >=3.0.1 ensures 255 scales for the 127's
|
||||
// PLA and S/V could have safe-harbored them via <=3.0.0
|
||||
if (pk.Context is EntityContext.Gen8a or EntityContext.Gen9)
|
||||
{
|
||||
if (s is not { HeightScalar: 127, WeightScalar: 127 }) // Original?
|
||||
{
|
||||
// Must match the HOME updated values AND must have the Alpha ribbon (visited HOME).
|
||||
if (s is not { HeightScalar: 255, WeightScalar: 255 })
|
||||
return false;
|
||||
if (pk is IRibbonSetMark9 { RibbonMarkAlpha: false })
|
||||
return false;
|
||||
if (pk.IsUntraded)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Must match the HOME updated values
|
||||
if (s is not { HeightScalar: 255, WeightScalar: 255 })
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HasFixedHeight && s.HeightScalar != HeightScalar)
|
||||
return false;
|
||||
if (HasFixedWeight && s.WeightScalar != WeightScalar)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (pk is IAlpha a && a.IsAlpha != IsAlpha)
|
||||
|
@ -86,14 +114,16 @@ public sealed record EncounterStatic8a(GameVersion Version) : EncounterStatic(Ve
|
|||
|
||||
protected override bool IsMatchLocation(PKM pk)
|
||||
{
|
||||
if (pk is PK8)
|
||||
return pk.Met_Location == LocationsHOME.SWLA;
|
||||
if (pk is PB8 { Version: (int)GameVersion.PLA, Met_Location: LocationsHOME.SWLA })
|
||||
return true;
|
||||
|
||||
return base.IsMatchLocation(pk);
|
||||
var metState = LocationsHOME.GetRemapState(Context, pk.Context);
|
||||
if (metState == LocationRemapState.Original)
|
||||
return base.IsMatchLocation(pk);
|
||||
if (metState == LocationRemapState.Remapped)
|
||||
return IsMetRemappedSWSH(pk);
|
||||
return base.IsMatchLocation(pk) || IsMetRemappedSWSH(pk);
|
||||
}
|
||||
|
||||
private static bool IsMetRemappedSWSH(PKM pk) => pk.Met_Location == LocationsHOME.SWLA;
|
||||
|
||||
public override EncounterMatchRating GetMatchRating(PKM pk)
|
||||
{
|
||||
var result = GetMatchRatingInternal(pk);
|
||||
|
|
|
@ -19,8 +19,25 @@ public sealed record EncounterStatic8b : EncounterStatic, IStaticCorrelation8b
|
|||
|
||||
protected override bool IsMatchLocation(PKM pk)
|
||||
{
|
||||
if (pk is PK8)
|
||||
return LocationsHOME.IsValidMetBDSP((ushort)pk.Met_Location, pk.Version);
|
||||
var metState = LocationsHOME.GetRemapState(Context, pk.Context);
|
||||
if (metState == LocationRemapState.Original)
|
||||
return IsMatchLocationExact(pk);
|
||||
if (metState == LocationRemapState.Remapped)
|
||||
return IsMatchLocationRemapped(pk);
|
||||
return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk);
|
||||
}
|
||||
|
||||
private bool IsMatchLocationRemapped(PKM pk)
|
||||
{
|
||||
var met = (ushort)pk.Met_Location;
|
||||
var version = pk.Version;
|
||||
if (pk.Context == EntityContext.Gen8)
|
||||
return LocationsHOME.IsValidMetBDSP(met, version);
|
||||
return LocationsHOME.GetMetSWSH((ushort)Location, version) == met;
|
||||
}
|
||||
|
||||
private bool IsMatchLocationExact(PKM pk)
|
||||
{
|
||||
if (!Roaming)
|
||||
return base.IsMatchLocation(pk);
|
||||
return IsRoamingLocation(pk);
|
||||
|
@ -48,18 +65,24 @@ public sealed record EncounterStatic8b : EncounterStatic, IStaticCorrelation8b
|
|||
|
||||
protected override bool IsMatchEggLocation(PKM pk)
|
||||
{
|
||||
if (pk is not PB8)
|
||||
{
|
||||
if (!EggEncounter)
|
||||
return pk.Egg_Location == 0;
|
||||
var metState = LocationsHOME.GetRemapState(Context, pk.Context);
|
||||
if (metState == LocationRemapState.Original)
|
||||
return IsMatchEggLocationExact(pk);
|
||||
if (metState == LocationRemapState.Remapped)
|
||||
return IsMatchEggLocationRemapped(pk);
|
||||
// Either
|
||||
return IsMatchEggLocationExact(pk) || IsMatchEggLocationRemapped(pk);
|
||||
}
|
||||
|
||||
if (pk is PK8)
|
||||
return LocationsHOME.IsLocationSWSHEgg(pk.Version, pk.Met_Location, pk.Egg_Location, (ushort)EggLocation);
|
||||
|
||||
// Hatched
|
||||
return pk.Egg_Location == EggLocation || pk.Egg_Location == Locations.LinkTrade6NPC;
|
||||
}
|
||||
private bool IsMatchEggLocationRemapped(PKM pk)
|
||||
{
|
||||
if (!EggEncounter)
|
||||
return pk.Egg_Location == 0;
|
||||
return LocationsHOME.IsLocationSWSHEgg(pk.Version, pk.Met_Location, pk.Egg_Location, (ushort)EggLocation);
|
||||
}
|
||||
|
||||
private bool IsMatchEggLocationExact(PKM pk)
|
||||
{
|
||||
var eggloc = pk.Egg_Location;
|
||||
if (!EggEncounter)
|
||||
return eggloc == EggLocation;
|
||||
|
|
|
@ -21,9 +21,23 @@ public sealed record EncounterStatic9(GameVersion Version) : EncounterStatic(Ver
|
|||
|
||||
protected override bool IsMatchLocation(PKM pk)
|
||||
{
|
||||
if (pk is PK8)
|
||||
return LocationsHOME.IsValidMetSV((ushort)pk.Met_Location, pk.Version);
|
||||
return base.IsMatchLocation(pk);
|
||||
var metState = LocationsHOME.GetRemapState(Context, pk.Context);
|
||||
if (metState == LocationRemapState.Original)
|
||||
return IsMatchLocationExact(pk);
|
||||
if (metState == LocationRemapState.Remapped)
|
||||
return IsMatchLocationRemapped(pk);
|
||||
return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk);
|
||||
}
|
||||
|
||||
private bool IsMatchLocationExact(PKM pk) => pk.Met_Location == Location;
|
||||
|
||||
private bool IsMatchLocationRemapped(PKM pk)
|
||||
{
|
||||
var met = (ushort)pk.Met_Location;
|
||||
var version = pk.Version;
|
||||
if (pk.Context == EntityContext.Gen8)
|
||||
return LocationsHOME.IsValidMetSV(met, version);
|
||||
return LocationsHOME.GetMetSWSH((ushort)Location, version) == met;
|
||||
}
|
||||
|
||||
protected override bool IsMatchPartial(PKM pk)
|
||||
|
|
|
@ -97,9 +97,23 @@ public sealed record EncounterTera9 : EncounterStatic, ITeraRaid9
|
|||
|
||||
protected override bool IsMatchLocation(PKM pk)
|
||||
{
|
||||
if (pk is PK8)
|
||||
return LocationsHOME.IsValidMetSV((ushort)pk.Met_Location, pk.Version);
|
||||
return base.IsMatchLocation(pk);
|
||||
var metState = LocationsHOME.GetRemapState(Context, pk.Context);
|
||||
if (metState == LocationRemapState.Original)
|
||||
return IsMatchLocationExact(pk);
|
||||
if (metState == LocationRemapState.Remapped)
|
||||
return IsMatchLocationRemapped(pk);
|
||||
return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk);
|
||||
}
|
||||
|
||||
private bool IsMatchLocationExact(PKM pk) => pk.Met_Location == Location;
|
||||
|
||||
private bool IsMatchLocationRemapped(PKM pk)
|
||||
{
|
||||
var met = (ushort)pk.Met_Location;
|
||||
var version = pk.Version;
|
||||
if (pk.Context == EntityContext.Gen8)
|
||||
return LocationsHOME.IsValidMetSV(met, version);
|
||||
return LocationsHOME.GetMetSWSH((ushort)Location, version) == met;
|
||||
}
|
||||
|
||||
protected override EncounterMatchRating IsMatchDeferred(PKM pk)
|
||||
|
|
|
@ -36,17 +36,46 @@ public sealed record EncounterTrade8b : EncounterTrade, IContestStatsReadOnly, I
|
|||
return false;
|
||||
if (pk is IScaledSize w && w.WeightScalar != WeightScalar)
|
||||
return false;
|
||||
if (!IsMatchLocation(pk))
|
||||
return false;
|
||||
return base.IsMatchExact(pk, evo);
|
||||
}
|
||||
|
||||
private bool IsMatchLocation(PKM pk)
|
||||
{
|
||||
var metState = LocationsHOME.GetRemapState(Context, pk.Context);
|
||||
if (metState == LocationRemapState.Original)
|
||||
return IsMatchLocationExact(pk);
|
||||
if (metState == LocationRemapState.Remapped)
|
||||
return IsMatchLocationRemapped(pk);
|
||||
return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk);
|
||||
}
|
||||
|
||||
private bool IsMatchLocationExact(PKM pk) => pk.Met_Location == Location;
|
||||
|
||||
private bool IsMatchLocationRemapped(PKM pk)
|
||||
{
|
||||
var met = (ushort)pk.Met_Location;
|
||||
var version = pk.Version;
|
||||
if (pk.Context == EntityContext.Gen8)
|
||||
return LocationsHOME.IsValidMetBDSP(met, version);
|
||||
return LocationsHOME.GetMetSWSH((ushort)Location, version) == met;
|
||||
}
|
||||
|
||||
protected override bool IsMatchEggLocation(PKM pk)
|
||||
{
|
||||
var expect = EggLocation;
|
||||
if (pk is not PB8 && expect == Locations.Default8bNone)
|
||||
expect = 0;
|
||||
return pk.Egg_Location == expect;
|
||||
var metState = LocationsHOME.GetRemapState(Context, pk.Context);
|
||||
if (metState == LocationRemapState.Original)
|
||||
return IsMatchEggLocationExact(pk);
|
||||
if (metState == LocationRemapState.Remapped)
|
||||
return IsMatchEggLocationRemapped(pk);
|
||||
// Either
|
||||
return IsMatchEggLocationExact(pk) || IsMatchEggLocationRemapped(pk);
|
||||
}
|
||||
|
||||
private static bool IsMatchEggLocationRemapped(PKM pk) => pk.Egg_Location == 0;
|
||||
private bool IsMatchEggLocationExact(PKM pk) => pk.Egg_Location == EggLocation;
|
||||
|
||||
protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk)
|
||||
{
|
||||
base.ApplyDetails(sav, criteria, pk);
|
||||
|
|
|
@ -41,6 +41,32 @@ public sealed record EncounterTrade9 : EncounterTrade, IGemType
|
|||
{
|
||||
if (TeraType != GemType.Random && pk is ITeraType t && !Tera9RNG.IsMatchTeraType(TeraType, Species, Form, (byte)t.TeraTypeOriginal))
|
||||
return false;
|
||||
return base.IsMatchExact(pk, evo);
|
||||
if (!base.IsMatchExact(pk, evo))
|
||||
return false;
|
||||
if (!IsMatchLocation(pk))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private bool IsMatchLocation(PKM pk)
|
||||
{
|
||||
var metState = LocationsHOME.GetRemapState(Context, pk.Context);
|
||||
if (metState == LocationRemapState.Original)
|
||||
return IsMatchLocationExact(pk);
|
||||
if (metState == LocationRemapState.Remapped)
|
||||
return IsMatchLocationRemapped(pk);
|
||||
return IsMatchLocationExact(pk) || IsMatchLocationRemapped(pk);
|
||||
}
|
||||
|
||||
private bool IsMatchLocationExact(PKM pk) => pk.Met_Location == Location;
|
||||
|
||||
private bool IsMatchLocationRemapped(PKM pk)
|
||||
{
|
||||
var met = (ushort)pk.Met_Location;
|
||||
var version = pk.Version;
|
||||
if (pk.Context == EntityContext.Gen8)
|
||||
return LocationsHOME.IsValidMetSV(met, version);
|
||||
return LocationsHOME.GetMetSWSH((ushort)Location, version) == met;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,9 @@ public sealed class EncounterGenerator1 : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
if (groups.HasFlag(Mystery))
|
||||
{
|
||||
var table = GetGifts();
|
||||
|
@ -109,6 +112,8 @@ public sealed class EncounterGenerator1 : IEncounterGenerator
|
|||
// Calculate all 3 at the same time and pick the best result (by species).
|
||||
// Favor special event move gifts as Static Encounters when applicable
|
||||
var chain = EncounterOrigin.GetOriginChain12(pk, game);
|
||||
if (chain.Length == 0)
|
||||
return Array.Empty<IEncounterable>();
|
||||
return GetEncounters(pk, chain, game);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,9 @@ public sealed class EncounterGenerator2 : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
bool korean = pk.Korean;
|
||||
if (groups.HasFlag(Mystery))
|
||||
{
|
||||
|
@ -116,6 +119,8 @@ public sealed class EncounterGenerator2 : IEncounterGenerator
|
|||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, GameVersion game)
|
||||
{
|
||||
var chain = EncounterOrigin.GetOriginChain12(pk, game);
|
||||
if (chain.Length == 0)
|
||||
return Array.Empty<IEncounterable>();
|
||||
return GetEncounters(pk, chain, game);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,9 @@ public sealed class EncounterGenerator3 : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetPossible(PKM _, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
if (groups.HasFlag(Mystery))
|
||||
{
|
||||
var table = EncountersWC3.Encounter_WC3;
|
||||
|
@ -117,12 +120,15 @@ public sealed class EncounterGenerator3 : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, LegalInfo info)
|
||||
{
|
||||
var chain = EncounterOrigin.GetOriginChain(pk);
|
||||
var chain = EncounterOrigin.GetOriginChain(pk, 3);
|
||||
return GetEncounters(pk, chain, info);
|
||||
}
|
||||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
info.PIDIV = MethodFinder.Analyze(pk);
|
||||
IEncounterable? partial = null;
|
||||
|
||||
|
|
|
@ -13,6 +13,9 @@ public sealed class EncounterGenerator3GC : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
if (groups.HasFlag(Mystery))
|
||||
{
|
||||
var table = EncountersWC3.Encounter_WC3CXD;
|
||||
|
@ -67,12 +70,14 @@ public sealed class EncounterGenerator3GC : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, LegalInfo info)
|
||||
{
|
||||
var chain = EncounterOrigin.GetOriginChain(pk);
|
||||
var chain = EncounterOrigin.GetOriginChain(pk, 3);
|
||||
return GetEncounters(pk, chain, info);
|
||||
}
|
||||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
IEncounterable? partial = null;
|
||||
info.PIDIV = MethodFinder.Analyze(pk);
|
||||
foreach (var z in IterateInner(pk, chain))
|
||||
|
|
|
@ -17,12 +17,17 @@ public sealed class EncounterGenerator4 : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, LegalInfo info)
|
||||
{
|
||||
var chain = EncounterOrigin.GetOriginChain(pk);
|
||||
return GetEncounters(pk, chain, info);
|
||||
var chain = EncounterOrigin.GetOriginChain(pk, 4);
|
||||
if (chain.Length == 0)
|
||||
return Array.Empty<IEncounterable>();
|
||||
return GetEncounters(pk, chain, info);
|
||||
}
|
||||
|
||||
public IEnumerable<IEncounterable> GetPossible(PKM _, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
if (groups.HasFlag(Mystery))
|
||||
{
|
||||
if (chain[^1].Species == (int)Species.Manaphy)
|
||||
|
|
|
@ -13,12 +13,17 @@ public sealed class EncounterGenerator5 : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, LegalInfo info)
|
||||
{
|
||||
var chain = EncounterOrigin.GetOriginChain(pk);
|
||||
var chain = EncounterOrigin.GetOriginChain(pk, 5);
|
||||
if (chain.Length == 0)
|
||||
return Array.Empty<IEncounterable>();
|
||||
return GetEncounters(pk, chain, info);
|
||||
}
|
||||
|
||||
public IEnumerable<IEncounterable> GetPossible(PKM _, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
if (groups.HasFlag(Mystery))
|
||||
{
|
||||
var table = EncounterEvent.MGDB_G5;
|
||||
|
|
|
@ -13,6 +13,9 @@ public sealed class EncounterGenerator6 : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
if (groups.HasFlag(Mystery))
|
||||
{
|
||||
var table = EncounterEvent.MGDB_G6;
|
||||
|
@ -134,12 +137,14 @@ public sealed class EncounterGenerator6 : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, LegalInfo info)
|
||||
{
|
||||
var chain = EncounterOrigin.GetOriginChain(pk);
|
||||
var chain = EncounterOrigin.GetOriginChain(pk, 6);
|
||||
return GetEncounters(pk, chain, info);
|
||||
}
|
||||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
var game = (GameVersion)pk.Version;
|
||||
|
||||
IEncounterable? deferred = null;
|
||||
|
|
|
@ -13,6 +13,9 @@ public sealed class EncounterGenerator7 : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
if (groups.HasFlag(Mystery))
|
||||
{
|
||||
var table = EncounterEvent.MGDB_G7;
|
||||
|
@ -108,6 +111,8 @@ public sealed class EncounterGenerator7 : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
var game = (GameVersion)pk.Version;
|
||||
|
||||
bool yielded = false;
|
||||
|
|
|
@ -15,6 +15,9 @@ public sealed class EncounterGenerator7GG : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetPossible(PKM _, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
if (groups.HasFlag(Mystery))
|
||||
{
|
||||
var table = EncounterEvent.MGDB_G7GG;
|
||||
|
@ -102,6 +105,8 @@ public sealed class EncounterGenerator7GG : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
bool yielded = false;
|
||||
if (pk.FatefulEncounter)
|
||||
{
|
||||
|
|
|
@ -12,6 +12,9 @@ public sealed class EncounterGenerator7GO : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
if (groups.HasFlag(Slot))
|
||||
{
|
||||
var areas = EncountersGO.SlotsGO_GG;
|
||||
|
@ -38,6 +41,8 @@ public sealed class EncounterGenerator7GO : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
if (!CanBeWildEncounter(pk))
|
||||
yield break;
|
||||
|
||||
|
|
|
@ -12,6 +12,9 @@ public sealed class EncounterGenerator8 : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetPossible(PKM _, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
if (groups.HasFlag(Mystery))
|
||||
{
|
||||
var table = EncounterEvent.MGDB_G8;
|
||||
|
@ -108,6 +111,8 @@ public sealed class EncounterGenerator8 : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
if (pk.FatefulEncounter)
|
||||
{
|
||||
bool yielded = false;
|
||||
|
|
|
@ -12,6 +12,9 @@ public sealed class EncounterGenerator8GO : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
if (groups.HasFlag(Slot))
|
||||
{
|
||||
var table = EncountersGO.SlotsGO;
|
||||
|
@ -38,6 +41,8 @@ public sealed class EncounterGenerator8GO : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
if (!CanBeWildEncounter(pk))
|
||||
yield break;
|
||||
|
||||
|
|
|
@ -12,6 +12,9 @@ public sealed class EncounterGenerator8a : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
if (groups.HasFlag(Mystery))
|
||||
{
|
||||
var table = EncounterEvent.MGDB_G8A;
|
||||
|
@ -80,6 +83,8 @@ public sealed class EncounterGenerator8a : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
if (pk is PK8 { SWSH: false })
|
||||
yield break;
|
||||
if (pk.IsEgg)
|
||||
|
@ -139,8 +144,11 @@ public sealed class EncounterGenerator8a : IEncounterGenerator
|
|||
// Encounter Slots
|
||||
if (CanBeWildEncounter(pk))
|
||||
{
|
||||
bool hasOriginalLocation = pk is not (PK8 or PB8 { Met_Location: LocationsHOME.SWLA });
|
||||
var location = pk.Met_Location;
|
||||
var remap = LocationsHOME.GetRemapState(EntityContext.Gen8a, pk.Context);
|
||||
bool hasOriginalLocation = true;
|
||||
if (remap.HasFlag(LocationRemapState.Remapped))
|
||||
hasOriginalLocation = location != LocationsHOME.SWLA;
|
||||
foreach (var area in Encounters8a.SlotsLA)
|
||||
{
|
||||
if (hasOriginalLocation && !area.IsMatchLocation(location))
|
||||
|
|
|
@ -12,6 +12,9 @@ public sealed class EncounterGenerator8b : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetPossible(PKM _, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
if (groups.HasFlag(Mystery))
|
||||
{
|
||||
var table = EncounterEvent.MGDB_G8B;
|
||||
|
@ -107,6 +110,8 @@ public sealed class EncounterGenerator8b : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
if (pk is PK8)
|
||||
yield break;
|
||||
|
||||
|
@ -182,8 +187,11 @@ public sealed class EncounterGenerator8b : IEncounterGenerator
|
|||
|
||||
if (CanBeWildEncounter(pk))
|
||||
{
|
||||
bool hasOriginalLocation = pk is not PK8;
|
||||
var location = pk.Met_Location;
|
||||
var remap = LocationsHOME.GetRemapState(EntityContext.Gen8b, pk.Context);
|
||||
bool hasOriginalLocation = true;
|
||||
if (remap.HasFlag(LocationRemapState.Remapped))
|
||||
hasOriginalLocation = location != LocationsHOME.GetMetSWSH((ushort)location, (int)game);
|
||||
var encWild = game == GameVersion.BD ? Encounters8b.SlotsBD : Encounters8b.SlotsSP;
|
||||
foreach (var area in encWild)
|
||||
{
|
||||
|
|
|
@ -13,7 +13,10 @@ public sealed class EncounterGenerator9 : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, LegalInfo info)
|
||||
{
|
||||
var chain = EncounterOrigin.GetOriginChain(pk);
|
||||
var chain = EncounterOrigin.GetOriginChain(pk, 9);
|
||||
if (chain.Length == 0)
|
||||
return System.Array.Empty<IEncounterable>();
|
||||
|
||||
return (GameVersion)pk.Version switch
|
||||
{
|
||||
SW when pk.Met_Location == LocationsHOME.SWSL => Instance.GetEncountersSWSH(pk, chain, SL),
|
||||
|
@ -24,6 +27,9 @@ public sealed class EncounterGenerator9 : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game, EncounterTypeGroup groups)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
if (groups.HasFlag(Mystery))
|
||||
{
|
||||
var table = EncounterEvent.MGDB_G9;
|
||||
|
|
|
@ -15,7 +15,7 @@ public sealed class EncounterGenerator7X : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, LegalInfo info)
|
||||
{
|
||||
var chain = EncounterOrigin.GetOriginChain(pk);
|
||||
var chain = EncounterOrigin.GetOriginChain(pk, 7);
|
||||
return GetEncounters(pk, chain, info);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ public sealed class EncounterGenerator8X : IEncounterGenerator
|
|||
|
||||
public IEnumerable<IEncounterable> GetEncounters(PKM pk, LegalInfo info)
|
||||
{
|
||||
var chain = EncounterOrigin.GetOriginChain(pk);
|
||||
var chain = EncounterOrigin.GetOriginChain(pk, 8);
|
||||
return GetEncounters(pk, chain, info);
|
||||
}
|
||||
|
||||
|
|
|
@ -31,10 +31,10 @@ public static class EncounterMovesetGenerator
|
|||
/// <param name="moves">Moves that the resulting <see cref="IEncounterable"/> must be able to learn.</param>
|
||||
/// <param name="versions">Any specific version(s) to iterate for. If left blank, all will be checked.</param>
|
||||
/// <returns>A consumable <see cref="PKM"/> list of possible results.</returns>
|
||||
/// <remarks>When updating, update the sister <see cref="GenerateEncounters(PKM,ITrainerInfo,ushort[],GameVersion[])"/> method.</remarks>
|
||||
public static IEnumerable<PKM> GeneratePKMs(PKM pk, ITrainerInfo info, ushort[] moves, params GameVersion[] versions)
|
||||
/// <remarks>When updating, update the sister <see cref="GenerateEncounters(PKM,ITrainerInfo,ReadOnlyMemory{ushort},GameVersion[])"/> method.</remarks>
|
||||
public static IEnumerable<PKM> GeneratePKMs(PKM pk, ITrainerInfo info, ReadOnlyMemory<ushort> moves, params GameVersion[] versions)
|
||||
{
|
||||
if (!IsSane(pk, moves))
|
||||
if (!IsSane(pk, moves.Span))
|
||||
yield break;
|
||||
|
||||
OptimizeCriteria(pk, info);
|
||||
|
@ -63,10 +63,10 @@ public static class EncounterMovesetGenerator
|
|||
/// <param name="moves">Moves that the resulting <see cref="IEncounterable"/> must be able to learn.</param>
|
||||
/// <param name="versions">Any specific version(s) to iterate for. If left blank, all will be checked.</param>
|
||||
/// <returns>A consumable <see cref="IEncounterable"/> list of possible results.</returns>
|
||||
/// <remarks>When updating, update the sister <see cref="GeneratePKMs(PKM,ITrainerInfo,ushort[],GameVersion[])"/> method.</remarks>
|
||||
public static IEnumerable<IEncounterable> GenerateEncounters(PKM pk, ITrainerInfo info, ushort[] moves, params GameVersion[] versions)
|
||||
/// <remarks>When updating, update the sister <see cref="GeneratePKMs(PKM,ITrainerInfo,ReadOnlyMemory{ushort},GameVersion[])"/> method.</remarks>
|
||||
public static IEnumerable<IEncounterable> GenerateEncounters(PKM pk, ITrainerInfo info, ReadOnlyMemory<ushort> moves, params GameVersion[] versions)
|
||||
{
|
||||
if (!IsSane(pk, moves))
|
||||
if (!IsSane(pk, moves.Span))
|
||||
yield break;
|
||||
|
||||
OptimizeCriteria(pk, info);
|
||||
|
@ -99,7 +99,7 @@ public static class EncounterMovesetGenerator
|
|||
/// <param name="info">Trainer information of the receiver.</param>
|
||||
/// <param name="generation">Specific generation to iterate versions for.</param>
|
||||
/// <param name="moves">Moves that the resulting <see cref="IEncounterable"/> must be able to learn.</param>
|
||||
public static IEnumerable<PKM> GeneratePKMs(PKM pk, ITrainerInfo info, int generation, ushort[] moves)
|
||||
public static IEnumerable<PKM> GeneratePKMs(PKM pk, ITrainerInfo info, int generation, ReadOnlyMemory<ushort> moves)
|
||||
{
|
||||
var vers = GameUtil.GetVersionsInGeneration(generation, pk.Version);
|
||||
return GeneratePKMs(pk, info, moves, vers);
|
||||
|
@ -112,7 +112,7 @@ public static class EncounterMovesetGenerator
|
|||
/// <param name="generation">Specific generation to iterate versions for.</param>
|
||||
/// <param name="moves">Moves that the resulting <see cref="IEncounterable"/> must be able to learn.</param>
|
||||
/// <returns>A consumable <see cref="IEncounterable"/> list of possible encounters.</returns>
|
||||
public static IEnumerable<IEncounterable> GenerateEncounter(PKM pk, int generation, ushort[] moves)
|
||||
public static IEnumerable<IEncounterable> GenerateEncounter(PKM pk, int generation, ReadOnlyMemory<ushort> moves)
|
||||
{
|
||||
var vers = GameUtil.GetVersionsInGeneration(generation, pk.Version);
|
||||
return GenerateEncounters(pk, moves, vers);
|
||||
|
@ -125,9 +125,9 @@ public static class EncounterMovesetGenerator
|
|||
/// <param name="moves">Moves that the resulting <see cref="IEncounterable"/> must be able to learn.</param>
|
||||
/// <param name="versions">Any specific version(s) to iterate for. If left blank, all will be checked.</param>
|
||||
/// <returns>A consumable <see cref="IEncounterable"/> list of possible encounters.</returns>
|
||||
public static IEnumerable<IEncounterable> GenerateEncounters(PKM pk, ushort[] moves, params GameVersion[] versions)
|
||||
public static IEnumerable<IEncounterable> GenerateEncounters(PKM pk, ReadOnlyMemory<ushort> moves, params GameVersion[] versions)
|
||||
{
|
||||
if (!IsSane(pk, moves))
|
||||
if (!IsSane(pk, moves.Span))
|
||||
yield break;
|
||||
|
||||
if (versions.Length > 0)
|
||||
|
@ -152,9 +152,9 @@ public static class EncounterMovesetGenerator
|
|||
/// <param name="moves">Moves that the resulting <see cref="IEncounterable"/> must be able to learn.</param>
|
||||
/// <param name="vers">Any specific version(s) to iterate for. If left blank, all will be checked.</param>
|
||||
/// <returns>A consumable <see cref="IEncounterable"/> list of possible encounters.</returns>
|
||||
public static IEnumerable<IEncounterable> GenerateEncounters(PKM pk, ushort[] moves, IReadOnlyList<GameVersion> vers)
|
||||
public static IEnumerable<IEncounterable> GenerateEncounters(PKM pk, ReadOnlyMemory<ushort> moves, IReadOnlyList<GameVersion> vers)
|
||||
{
|
||||
if (!IsSane(pk, moves))
|
||||
if (!IsSane(pk, moves.Span))
|
||||
yield break;
|
||||
|
||||
foreach (var ver in vers)
|
||||
|
@ -171,17 +171,22 @@ public static class EncounterMovesetGenerator
|
|||
/// <param name="moves">Moves that the resulting <see cref="IEncounterable"/> must be able to learn.</param>
|
||||
/// <param name="version">Specific version to iterate for.</param>
|
||||
/// <returns>A consumable <see cref="IEncounterable"/> list of possible encounters.</returns>
|
||||
private static IEnumerable<IEncounterable> GenerateVersionEncounters(PKM pk, ushort[] moves, GameVersion version)
|
||||
private static IEnumerable<IEncounterable> GenerateVersionEncounters(PKM pk, ReadOnlyMemory<ushort> moves, GameVersion version)
|
||||
{
|
||||
pk.Version = (int)version;
|
||||
|
||||
var context = pk.Context;
|
||||
if (context is EntityContext.Gen2 && version is GameVersion.RD or GameVersion.GN or GameVersion.BU or GameVersion.YW)
|
||||
context = EntityContext.Gen1; // try excluding baby pokemon from our evolution chain, for move learning purposes.
|
||||
var et = EvolutionTree.GetEvolutionTree(context);
|
||||
var chain = et.GetValidPreEvolutions(pk, levelMax: 100, skipChecks: true);
|
||||
var generation = (byte)version.GetGeneration();
|
||||
if (version is GameVersion.GO)
|
||||
generation = (byte)pk.Format;
|
||||
if (pk.Context is EntityContext.Gen2 && version is GameVersion.RD or GameVersion.GN or GameVersion.BU or GameVersion.YW)
|
||||
generation = 1; // try excluding baby pokemon from our evolution chain, for move learning purposes.
|
||||
|
||||
var needs = GetNeededMoves(pk, moves, version);
|
||||
var origin = new EvolutionOrigin(pk.Species, (byte)version, generation, 1, 100, true);
|
||||
var chain = EvolutionChain.GetOriginChain(pk, origin);
|
||||
if (chain.Length == 0)
|
||||
yield break;
|
||||
|
||||
ReadOnlyMemory<ushort> needs = GetNeededMoves(pk, moves.Span, version, generation);
|
||||
var generator = EncounterGenerator.GetGenerator(version);
|
||||
|
||||
foreach (var type in PriorityList)
|
||||
|
@ -224,27 +229,17 @@ public static class EncounterMovesetGenerator
|
|||
return true;
|
||||
}
|
||||
|
||||
private static ushort[] GetNeededMoves(PKM pk, ReadOnlySpan<ushort> moves, GameVersion ver)
|
||||
private static ushort[] GetNeededMoves(PKM pk, ReadOnlySpan<ushort> moves, GameVersion ver, int generation)
|
||||
{
|
||||
if (pk.Species == (int)Species.Smeargle)
|
||||
return Array.Empty<ushort>();
|
||||
|
||||
// Roughly determine the generation the PKM is originating from
|
||||
int origin = pk.Generation;
|
||||
if (origin < 0)
|
||||
origin = ver.GetGeneration();
|
||||
|
||||
// Temporarily replace the Version for VC1 transfers, so that they can have VC2 moves if needed.
|
||||
bool vcBump = origin == 1 && pk.Format >= 7;
|
||||
if (vcBump)
|
||||
pk.Version = (int)GameVersion.C;
|
||||
|
||||
var length = pk.MaxMoveID + 1;
|
||||
var rent = ArrayPool<bool>.Shared.Rent(length);
|
||||
var permitted = rent.AsSpan(0, length);
|
||||
var enc = new EvolutionOrigin(0, (byte)ver, (byte)origin, 1, 100, true);
|
||||
var history = EvolutionChain.GetEvolutionChainsSearch(pk, enc);
|
||||
var e = EncounterInvalid.Default with { Generation = origin };
|
||||
var enc = new EvolutionOrigin(pk.Species, (byte)ver, (byte)generation, 1, 100, true);
|
||||
var history = EvolutionChain.GetEvolutionChainsSearch(pk, enc, ver.GetContext(), 0);
|
||||
var e = EncounterInvalid.Default; // default empty
|
||||
LearnPossible.Get(pk, e, history, permitted);
|
||||
|
||||
int ctr = 0; // count of moves that can be learned
|
||||
|
@ -260,15 +255,12 @@ public static class EncounterMovesetGenerator
|
|||
permitted.Clear();
|
||||
ArrayPool<bool>.Shared.Return(rent);
|
||||
|
||||
if (vcBump)
|
||||
pk.Version = (int)ver;
|
||||
|
||||
if (ctr == 0)
|
||||
return Array.Empty<ushort>();
|
||||
return result[..ctr].ToArray();
|
||||
}
|
||||
|
||||
private static IEnumerable<IEncounterable> GetPossibleOfType(PKM pk, ushort[] needs, GameVersion version, EncounterTypeGroup type, EvoCriteria[] chain, IEncounterGenerator generator)
|
||||
private static IEnumerable<IEncounterable> GetPossibleOfType(PKM pk, ReadOnlyMemory<ushort> needs, GameVersion version, EncounterTypeGroup type, EvoCriteria[] chain, IEncounterGenerator generator)
|
||||
=> type switch
|
||||
{
|
||||
Egg => GetEggs(pk, needs, chain, version, generator),
|
||||
|
@ -288,7 +280,7 @@ public static class EncounterMovesetGenerator
|
|||
/// <param name="version">Specific version to iterate for. Necessary for retrieving possible Egg Moves.</param>
|
||||
/// <param name="generator"></param>
|
||||
/// <returns>A consumable <see cref="IEncounterable"/> list of possible encounters.</returns>
|
||||
private static IEnumerable<IEncounterable> GetEggs(PKM pk, ushort[] needs, EvoCriteria[] chain, GameVersion version, IEncounterGenerator generator)
|
||||
private static IEnumerable<IEncounterable> GetEggs(PKM pk, ReadOnlyMemory<ushort> needs, EvoCriteria[] chain, GameVersion version, IEncounterGenerator generator)
|
||||
{
|
||||
if (!Breeding.CanGameGenerateEggs(version))
|
||||
yield break; // no eggs from these games
|
||||
|
@ -296,7 +288,7 @@ public static class EncounterMovesetGenerator
|
|||
var eggs = generator.GetPossible(pk, chain, version, Egg);
|
||||
foreach (var egg in eggs)
|
||||
{
|
||||
if (needs.Length == 0 || HasAllNeededMovesEgg(needs, egg))
|
||||
if (needs.Length == 0 || HasAllNeededMovesEgg(needs.Span, egg))
|
||||
yield return egg;
|
||||
}
|
||||
}
|
||||
|
@ -310,7 +302,7 @@ public static class EncounterMovesetGenerator
|
|||
/// <param name="version">Specific version to iterate for.</param>
|
||||
/// <param name="generator">Generator</param>
|
||||
/// <returns>A consumable <see cref="IEncounterable"/> list of possible encounters.</returns>
|
||||
private static IEnumerable<IEncounterable> GetGifts(PKM pk, ushort[] needs, EvoCriteria[] chain, GameVersion version, IEncounterGenerator generator)
|
||||
private static IEnumerable<IEncounterable> GetGifts(PKM pk, ReadOnlyMemory<ushort> needs, EvoCriteria[] chain, GameVersion version, IEncounterGenerator generator)
|
||||
{
|
||||
var context = pk.Context;
|
||||
var gifts = generator.GetPossible(pk, chain, version, Mystery);
|
||||
|
@ -318,7 +310,7 @@ public static class EncounterMovesetGenerator
|
|||
{
|
||||
if (!IsSane(chain, enc, context))
|
||||
continue;
|
||||
if (needs.Length == 0 || GetHasAllNeededMoves(needs, enc))
|
||||
if (needs.Length == 0 || GetHasAllNeededMoves(needs.Span, enc))
|
||||
yield return enc;
|
||||
}
|
||||
}
|
||||
|
@ -332,7 +324,7 @@ public static class EncounterMovesetGenerator
|
|||
/// <param name="version">Specific version to iterate for.</param>
|
||||
/// <param name="generator"></param>
|
||||
/// <returns>A consumable <see cref="IEncounterable"/> list of possible encounters.</returns>
|
||||
private static IEnumerable<IEncounterable> GetStatic(PKM pk, ushort[] needs, EvoCriteria[] chain, GameVersion version, IEncounterGenerator generator)
|
||||
private static IEnumerable<IEncounterable> GetStatic(PKM pk, ReadOnlyMemory<ushort> needs, EvoCriteria[] chain, GameVersion version, IEncounterGenerator generator)
|
||||
{
|
||||
var context = pk.Context;
|
||||
var encounters = generator.GetPossible(pk, chain, version, Static);
|
||||
|
@ -340,7 +332,7 @@ public static class EncounterMovesetGenerator
|
|||
{
|
||||
if (!IsSane(chain, enc, context))
|
||||
continue;
|
||||
if (needs.Length == 0 || GetHasAllNeededMovesConsiderGen2(needs, enc))
|
||||
if (needs.Length == 0 || GetHasAllNeededMovesConsiderGen2(needs.Span, enc))
|
||||
yield return enc;
|
||||
}
|
||||
}
|
||||
|
@ -354,7 +346,7 @@ public static class EncounterMovesetGenerator
|
|||
/// <param name="version">Specific version to iterate for.</param>
|
||||
/// <param name="generator"></param>
|
||||
/// <returns>A consumable <see cref="IEncounterable"/> list of possible encounters.</returns>
|
||||
private static IEnumerable<IEncounterable> GetTrades(PKM pk, ushort[] needs, EvoCriteria[] chain, GameVersion version, IEncounterGenerator generator)
|
||||
private static IEnumerable<IEncounterable> GetTrades(PKM pk, ReadOnlyMemory<ushort> needs, EvoCriteria[] chain, GameVersion version, IEncounterGenerator generator)
|
||||
{
|
||||
var context = pk.Context;
|
||||
var trades = generator.GetPossible(pk, chain, version, Trade);
|
||||
|
@ -362,7 +354,7 @@ public static class EncounterMovesetGenerator
|
|||
{
|
||||
if (!IsSane(chain, enc, context))
|
||||
continue;
|
||||
if (needs.Length == 0 || GetHasAllNeededMovesConsiderGen2(needs, enc))
|
||||
if (needs.Length == 0 || GetHasAllNeededMovesConsiderGen2(needs.Span, enc))
|
||||
yield return enc;
|
||||
}
|
||||
}
|
||||
|
@ -376,7 +368,7 @@ public static class EncounterMovesetGenerator
|
|||
/// <param name="version">Origin version</param>
|
||||
/// <param name="generator"></param>
|
||||
/// <returns>A consumable <see cref="IEncounterable"/> list of possible encounters.</returns>
|
||||
private static IEnumerable<IEncounterable> GetSlots(PKM pk, ushort[] needs, EvoCriteria[] chain, GameVersion version, IEncounterGenerator generator)
|
||||
private static IEnumerable<IEncounterable> GetSlots(PKM pk, ReadOnlyMemory<ushort> needs, EvoCriteria[] chain, GameVersion version, IEncounterGenerator generator)
|
||||
{
|
||||
var context = pk.Context;
|
||||
var slots = generator.GetPossible(pk, chain, version, Slot);
|
||||
|
@ -384,7 +376,7 @@ public static class EncounterMovesetGenerator
|
|||
{
|
||||
if (!IsSane(chain, slot, context))
|
||||
continue;
|
||||
if (needs.Length == 0 || HasAllNeededMovesSlot(needs, slot))
|
||||
if (needs.Length == 0 || HasAllNeededMovesSlot(needs.Span, slot))
|
||||
yield return slot;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,11 +18,15 @@ public static class EncounterSuggestion
|
|||
if (pk.WasEgg)
|
||||
return GetSuggestedEncounterEgg(pk, loc);
|
||||
|
||||
var chain = EvolutionChain.GetValidPreEvolutions(pk, maxLevel: 100, skipChecks: true);
|
||||
Span<EvoCriteria> chain = stackalloc EvoCriteria[EvolutionTree.MaxEvolutions];
|
||||
var origin = new EvolutionOrigin(pk.Species, (byte)pk.Version, (byte)pk.Generation, (byte)pk.CurrentLevel, (byte)pk.CurrentLevel, SkipChecks: true);
|
||||
var count = EvolutionChain.GetOriginChain(chain, pk, origin);
|
||||
var ver = (GameVersion)pk.Version;
|
||||
var generator = EncounterGenerator.GetGenerator(ver);
|
||||
var w = EncounterUtil.GetMinByLevel(chain, generator.GetPossible(pk, chain, ver, EncounterTypeGroup.Slot));
|
||||
var s = EncounterUtil.GetMinByLevel(chain, generator.GetPossible(pk, chain, ver, EncounterTypeGroup.Static));
|
||||
|
||||
var evos = chain[..count].ToArray();
|
||||
var w = EncounterUtil.GetMinByLevel(evos, generator.GetPossible(pk, evos, ver, EncounterTypeGroup.Slot));
|
||||
var s = EncounterUtil.GetMinByLevel(evos, generator.GetPossible(pk, evos, ver, EncounterTypeGroup.Static));
|
||||
|
||||
if (w is null)
|
||||
return s is null ? null : GetSuggestedEncounter(pk, s, loc);
|
||||
|
@ -116,22 +120,22 @@ public static class EncounterSuggestion
|
|||
if (startLevel >= 100)
|
||||
startLevel = 100;
|
||||
|
||||
var table = EvolutionTree.GetEvolutionTree(pk.Context);
|
||||
int count = 1;
|
||||
byte i = 100;
|
||||
int most = 1;
|
||||
Span<EvoCriteria> chain = stackalloc EvoCriteria[EvolutionTree.MaxEvolutions];
|
||||
var origin = new EvolutionOrigin(pk.Species, (byte)pk.Version, (byte)pk.Generation, startLevel, 100, SkipChecks: true);
|
||||
while (true)
|
||||
{
|
||||
var evos = table.GetValidPreEvolutions(pk, levelMax: i, skipChecks: true, levelMin: startLevel);
|
||||
if (evos.Length < count) // lost an evolution, prior level was minimum current level
|
||||
return GetMaxLevelMax(evos) + 1;
|
||||
count = evos.Length;
|
||||
if (i == startLevel)
|
||||
var count = EvolutionChain.GetOriginChain(chain, pk, origin);
|
||||
if (count < most) // lost an evolution, prior level was minimum current level
|
||||
return GetMaxLevelMax(chain) + 1;
|
||||
most = count;
|
||||
if (origin.LevelMax == origin.LevelMin)
|
||||
return startLevel;
|
||||
--i;
|
||||
origin = origin with { LevelMax = (byte)(origin.LevelMax - 1) };
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetMaxLevelMax(EvoCriteria[] evos)
|
||||
private static int GetMaxLevelMax(ReadOnlySpan<EvoCriteria> evos)
|
||||
{
|
||||
int max = 0;
|
||||
foreach (var evo in evos)
|
||||
|
|
|
@ -8,6 +8,8 @@ namespace PKHeX.Core;
|
|||
/// </summary>
|
||||
public static class EvolutionVerifier
|
||||
{
|
||||
private static readonly CheckResult VALID = new(CheckIdentifier.Evolution);
|
||||
|
||||
/// <summary>
|
||||
/// Verifies Evolution scenarios of an <see cref="IEncounterable"/> for an input <see cref="PKM"/> and relevant <see cref="LegalInfo"/>.
|
||||
/// </summary>
|
||||
|
@ -16,7 +18,7 @@ public static class EvolutionVerifier
|
|||
public static CheckResult VerifyEvolution(PKM pk, LegalInfo info)
|
||||
{
|
||||
// Check if basic evolution methods are satisfiable with this encounter.
|
||||
if (!IsValidEvolution(pk, info))
|
||||
if (!IsValidEvolution(pk, info.EvoChainsAllGens, info.EncounterOriginal))
|
||||
return new CheckResult(Severity.Invalid, CheckIdentifier.Evolution, LEvoInvalid);
|
||||
|
||||
// Check if complex evolution methods are satisfiable with this encounter.
|
||||
|
@ -26,36 +28,34 @@ public static class EvolutionVerifier
|
|||
return VALID;
|
||||
}
|
||||
|
||||
private static readonly CheckResult VALID = new(CheckIdentifier.Evolution);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the Evolution from the source <see cref="IEncounterable"/> is valid.
|
||||
/// </summary>
|
||||
/// <param name="pk">Source data to verify</param>
|
||||
/// <param name="info">Source supporting information to verify with</param>
|
||||
/// <param name="history">Source supporting information to verify with</param>
|
||||
/// <param name="enc">Matched encounter</param>
|
||||
/// <returns>Evolution is valid or not</returns>
|
||||
private static bool IsValidEvolution(PKM pk, LegalInfo info)
|
||||
private static bool IsValidEvolution(PKM pk, EvolutionHistory history, IEncounterTemplate enc)
|
||||
{
|
||||
var chains = info.EvoChainsAllGens;
|
||||
if (chains.Get(pk.Context).Length == 0)
|
||||
// OK if un-evolved from original encounter
|
||||
var encSpecies = enc.Species;
|
||||
var curSpecies = pk.Species;
|
||||
if (curSpecies == encSpecies)
|
||||
return true; // never evolved
|
||||
|
||||
var current = history.Get(pk.Context);
|
||||
if (!EvolutionUtil.Contains(current, curSpecies))
|
||||
return false; // Can't exist as current species
|
||||
|
||||
// OK if un-evolved from original encounter
|
||||
ushort species = pk.Species;
|
||||
var enc = info.EncounterMatch;
|
||||
if (species == enc.Species) // never evolved
|
||||
return true;
|
||||
|
||||
// Bigender->Fixed (non-Genderless) destination species, accounting for PID-Gender relationship
|
||||
if (species == (int)Species.Vespiquen && enc.Generation < 6 && (pk.EncryptionConstant & 0xFF) >= 0x1F) // Combee->Vespiquen Invalid Evolution
|
||||
// Double check that our encounter was able to exist as the encounter species.
|
||||
var original = history.Get(enc.Context);
|
||||
if (!EvolutionUtil.Contains(original, encSpecies))
|
||||
return false;
|
||||
|
||||
// Double check that our encounter was able to exist as the encounter species.
|
||||
foreach (var z in chains.Get(enc.Context))
|
||||
{
|
||||
if (z.Species == enc.Species)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
// Bigender->Fixed (non-Genderless) destination species, accounting for PID-Gender relationship
|
||||
if (curSpecies == (int)Species.Vespiquen && enc.Generation < 6 && (pk.EncryptionConstant & 0xFF) >= 0x1F) // Combee->Vespiquen Invalid Evolution
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static PKHeX.Core.Species;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
@ -13,14 +11,14 @@ public static class EncounterOrigin
|
|||
/// Gets possible evolution details for the input <see cref="pk"/>
|
||||
/// </summary>
|
||||
/// <param name="pk">Current state of the Pokémon</param>
|
||||
/// <param name="generation">Original Generation</param>
|
||||
/// <returns>Possible origin species-form-levels to match against encounter data.</returns>
|
||||
/// <remarks>Use <see cref="GetOriginChain12"/> if the <see cref="pk"/> originated from Generation 1 or 2.</remarks>
|
||||
public static EvoCriteria[] GetOriginChain(PKM pk)
|
||||
public static EvoCriteria[] GetOriginChain(PKM pk, byte generation)
|
||||
{
|
||||
bool hasOriginMet = pk.HasOriginalMetLocation;
|
||||
var maxLevel = GetLevelOriginMax(pk, hasOriginMet);
|
||||
var minLevel = GetLevelOriginMin(pk, hasOriginMet);
|
||||
return GetOriginChain(pk, -1, (byte)maxLevel, (byte)minLevel, hasOriginMet);
|
||||
var (minLevel, maxLevel) = GetMinMax(pk, generation);
|
||||
var origin = new EvolutionOrigin(pk.Species, (byte)pk.Version, generation, minLevel, maxLevel);
|
||||
return EvolutionChain.GetOriginChain(pk, origin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -31,150 +29,67 @@ public static class EncounterOrigin
|
|||
/// <returns>Possible origin species-form-levels to match against encounter data.</returns>
|
||||
public static EvoCriteria[] GetOriginChain12(PKM pk, GameVersion gameSource)
|
||||
{
|
||||
var (minLevel, maxLevel) = GetMinMaxGB(pk);
|
||||
bool rby = gameSource == GameVersion.RBY;
|
||||
var maxSpecies = rby ? Legal.MaxSpeciesID_1 : Legal.MaxSpeciesID_2;
|
||||
|
||||
bool hasOriginMet;
|
||||
int maxLevel, minLevel;
|
||||
if (pk is ICaughtData2 pk2)
|
||||
{
|
||||
hasOriginMet = pk2.CaughtData != 0;
|
||||
maxLevel = rby && Future_LevelUp2.Contains(pk.Species) ? pk.CurrentLevel - 1 : pk.CurrentLevel;
|
||||
minLevel = !hasOriginMet ? 2 : pk.IsEgg ? 5 : pk2.Met_Level;
|
||||
}
|
||||
else if (pk is PK1 pk1)
|
||||
{
|
||||
hasOriginMet = false;
|
||||
maxLevel = pk1.CurrentLevel;
|
||||
minLevel = 2;
|
||||
}
|
||||
else if (rby)
|
||||
{
|
||||
hasOriginMet = false;
|
||||
maxLevel = Future_LevelUp2.Contains(pk.Species) ? pk.CurrentLevel - 1 : GetLevelOriginMaxTransfer(pk, pk.Met_Level, 1);
|
||||
minLevel = 2;
|
||||
}
|
||||
else // GSC
|
||||
{
|
||||
hasOriginMet = false;
|
||||
maxLevel = GetLevelOriginMaxTransfer(pk, pk.Met_Level, 2);
|
||||
minLevel = 2;
|
||||
}
|
||||
|
||||
return GetOriginChain(pk, maxSpecies, (byte)maxLevel, (byte)minLevel, hasOriginMet);
|
||||
byte ver = rby ? (byte)GameVersion.RBY : (byte)GameVersion.GSC;
|
||||
byte gen = rby ? (byte)1 : (byte)2;
|
||||
var origin = new EvolutionOrigin(pk.Species, ver, gen, minLevel, maxLevel);
|
||||
return EvolutionChain.GetOriginChain(pk, origin);
|
||||
}
|
||||
|
||||
private static EvoCriteria[] GetOriginChain(PKM pk, int maxSpecies, byte maxLevel, byte minLevel, bool hasOriginMet)
|
||||
private static (byte minLevel, byte maxLevel) GetMinMax(PKM pk, byte generation)
|
||||
{
|
||||
if (maxLevel < minLevel)
|
||||
return Array.Empty<EvoCriteria>();
|
||||
|
||||
if (hasOriginMet)
|
||||
return EvolutionChain.GetValidPreEvolutions(pk, maxSpecies, maxLevel, minLevel);
|
||||
|
||||
// Permit the maximum to be all the way up to Current Level; we'll trim these impossible evolutions out later.
|
||||
var tempMax = pk.CurrentLevel;
|
||||
var chain = EvolutionChain.GetValidPreEvolutions(pk, maxSpecies, tempMax, minLevel);
|
||||
|
||||
for (var i = 0; i < chain.Length; i++)
|
||||
chain[i] = chain[i] with { LevelMax = maxLevel, LevelMin = minLevel };
|
||||
|
||||
return chain;
|
||||
byte maxLevel = (byte)pk.CurrentLevel;
|
||||
byte minLevel = GetLevelOriginMin(pk, generation);
|
||||
return (minLevel, maxLevel);
|
||||
}
|
||||
|
||||
private static int GetLevelOriginMin(PKM pk, bool hasMet)
|
||||
private static (byte minLevel, byte maxLevel) GetMinMaxGB(PKM pk)
|
||||
{
|
||||
if (pk.Format <= 3)
|
||||
{
|
||||
if (pk.IsEgg)
|
||||
return 5;
|
||||
return Math.Max(2, pk.Met_Level);
|
||||
}
|
||||
if (!hasMet)
|
||||
return 1;
|
||||
return Math.Max(1, pk.Met_Level);
|
||||
byte maxLevel = (byte)pk.CurrentLevel;
|
||||
byte minLevel = GetLevelOriginMinGB(pk);
|
||||
return (minLevel, maxLevel);
|
||||
}
|
||||
|
||||
private static int GetLevelOriginMax(PKM pk, bool hasMet)
|
||||
private static byte GetLevelOriginMin(PKM pk, byte generation) => generation switch
|
||||
{
|
||||
var met = pk.Met_Level;
|
||||
if (hasMet)
|
||||
return pk.CurrentLevel;
|
||||
|
||||
int generation = pk.Generation;
|
||||
if (generation >= 4)
|
||||
return met;
|
||||
|
||||
var downLevel = GetLevelOriginMaxTransfer(pk, pk.CurrentLevel, generation);
|
||||
return Math.Min(met, downLevel);
|
||||
}
|
||||
|
||||
private static int GetLevelOriginMaxTransfer(PKM pk, int met, int generation)
|
||||
{
|
||||
var species = pk.Species;
|
||||
|
||||
if (Future_LevelUp.TryGetValue((ushort)(species | (pk.Form << 11)), out var delta))
|
||||
return met - delta;
|
||||
|
||||
if (generation < 4 && Future_LevelUp4.Contains(species) && (pk.Format <= 7 || !Future_LevelUp4_Not8.Contains(species)))
|
||||
return met - 1;
|
||||
|
||||
return met;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Species introduced in Generation 2 that require a level up to evolve into from a specimen that originated in a previous generation.
|
||||
/// </summary>
|
||||
private static readonly HashSet<ushort> Future_LevelUp2 = new()
|
||||
{
|
||||
(int)Crobat,
|
||||
(int)Espeon,
|
||||
(int)Umbreon,
|
||||
(int)Blissey,
|
||||
3 => GetLevelOriginMin3(pk),
|
||||
4 => GetLevelOriginMin4(pk),
|
||||
_ => Math.Max((byte)1, (byte)pk.Met_Level),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Species introduced in Generation 4 that require a level up to evolve into from a specimen that originated in a previous generation.
|
||||
/// </summary>
|
||||
private static readonly HashSet<ushort> Future_LevelUp4 = new()
|
||||
private static bool IsEggLocationNonZero(PKM pk) => pk.Egg_Location != LocationEdits.GetNoneLocation(pk.Context);
|
||||
|
||||
private static byte GetLevelOriginMinGB(PKM pk)
|
||||
{
|
||||
(int)Ambipom,
|
||||
(int)Weavile,
|
||||
(int)Magnezone,
|
||||
(int)Lickilicky,
|
||||
(int)Tangrowth,
|
||||
(int)Yanmega,
|
||||
(int)Leafeon,
|
||||
(int)Glaceon,
|
||||
(int)Mamoswine,
|
||||
(int)Gliscor,
|
||||
(int)Probopass,
|
||||
};
|
||||
const byte EggLevel = 5;
|
||||
const byte MinWildLevel = 2;
|
||||
if (pk.IsEgg)
|
||||
return EggLevel;
|
||||
if (pk is not ICaughtData2 { CaughtData: not 0 } pk2)
|
||||
return MinWildLevel;
|
||||
return (byte)pk2.Met_Level;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Species introduced in Generation 4 that used to require a level up to evolve prior to Generation 8.
|
||||
/// </summary>
|
||||
private static readonly HashSet<ushort> Future_LevelUp4_Not8 = new()
|
||||
private static byte GetLevelOriginMin3(PKM pk)
|
||||
{
|
||||
(int)Magnezone, // Thunder Stone
|
||||
(int)Leafeon, // Leaf Stone
|
||||
(int)Glaceon, // Ice Stone
|
||||
};
|
||||
const byte EggLevel = 5;
|
||||
const byte MinWildLevel = 2;
|
||||
if (pk.Format != 3)
|
||||
return MinWildLevel;
|
||||
if (pk.IsEgg)
|
||||
return EggLevel;
|
||||
return (byte)pk.Met_Level;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Species introduced in Generation 6+ that require a level up to evolve into from a specimen that originated in a previous generation.
|
||||
/// </summary>
|
||||
private static readonly Dictionary<ushort, byte> Future_LevelUp = new()
|
||||
private static byte GetLevelOriginMin4(PKM pk)
|
||||
{
|
||||
// Gen6
|
||||
{(int)Sylveon, 1},
|
||||
|
||||
// Gen7
|
||||
{(int)Marowak | (1 << 11), 1},
|
||||
|
||||
// Gen8
|
||||
{(int)Weezing | (1 << 11), 1},
|
||||
{(int)MrMime | (1 << 11), 1},
|
||||
{(int)MrRime, 2},
|
||||
};
|
||||
const byte EggLevel = 1;
|
||||
const byte MinWildLevel = 2;
|
||||
if (pk.Format != 4)
|
||||
return IsEggLocationNonZero(pk) ? EggLevel : MinWildLevel;
|
||||
if (pk.IsEgg || IsEggLocationNonZero(pk))
|
||||
return EggLevel;
|
||||
return (byte)pk.Met_Level;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
|
||||
using static PKHeX.Core.Legal;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
|
@ -11,29 +9,53 @@ public static class EvolutionChain
|
|||
{
|
||||
public static EvolutionHistory GetEvolutionChainsAllGens(PKM pk, IEncounterTemplate enc)
|
||||
{
|
||||
var origin = new EvolutionOrigin(enc.Species, (byte)enc.Version, (byte)enc.Generation, enc.LevelMin, (byte)pk.CurrentLevel);
|
||||
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);
|
||||
return GetEvolutionChainsSearch(pk, origin, enc.Context, enc.Species);
|
||||
|
||||
var history = new EvolutionHistory();
|
||||
var group = EvolutionGroupUtil.GetCurrentGroup(pk);
|
||||
var chain = group.GetInitialChain(pk, origin, pk.Species, pk.Form);
|
||||
history.Set(pk.Context, chain);
|
||||
return history;
|
||||
return GetEvolutionChainsSearch(pk, origin, pk.Context, enc.Species);
|
||||
}
|
||||
|
||||
public static EvolutionHistory GetEvolutionChainsSearch(PKM pk, EvolutionOrigin enc)
|
||||
private static byte GetMinLevel(PKM pk, IEncounterTemplate enc) => enc.Generation switch
|
||||
{
|
||||
var group = EvolutionGroupUtil.GetCurrentGroup(pk);
|
||||
ReadOnlySpan<EvoCriteria> chain = group.GetInitialChain(pk, enc, pk.Species, pk.Form);
|
||||
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),
|
||||
};
|
||||
|
||||
public static EvolutionHistory GetEvolutionChainsSearch(PKM pk, EvolutionOrigin enc, EntityContext context, ushort encSpecies)
|
||||
{
|
||||
Span<EvoCriteria> chain = stackalloc EvoCriteria[EvolutionTree.MaxEvolutions];
|
||||
return EvolutionChainsSearch(pk, enc, context, encSpecies, chain);
|
||||
}
|
||||
|
||||
private static EvolutionHistory EvolutionChainsSearch(PKM pk, EvolutionOrigin enc, EntityContext context, ushort encSpecies, Span<EvoCriteria> chain)
|
||||
{
|
||||
var history = new EvolutionHistory();
|
||||
var length = GetOriginChain(chain, pk, enc, encSpecies, false);
|
||||
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)
|
||||
{
|
||||
EvolutionGroup2.Instance.Evolve(chain, pk, enc, history);
|
||||
EvolutionGroup1.Instance.Evolve(chain, pk, enc, history);
|
||||
if (pk.Format > 2)
|
||||
context = EntityContext.Gen7;
|
||||
else
|
||||
return history;
|
||||
}
|
||||
|
||||
var group = EvolutionGroupUtil.GetGroup(context);
|
||||
while (true)
|
||||
{
|
||||
var any = group.Append(pk, history, ref chain, enc);
|
||||
if (!any)
|
||||
break;
|
||||
var previous = group.GetPrevious(pk, enc);
|
||||
group.Evolve(chain, pk, enc, history);
|
||||
var previous = group.GetNext(pk, enc);
|
||||
if (previous is null)
|
||||
break;
|
||||
group = previous;
|
||||
|
@ -41,18 +63,65 @@ public static class EvolutionChain
|
|||
return history;
|
||||
}
|
||||
|
||||
public static EvoCriteria[] GetValidPreEvolutions(PKM pk, int maxspeciesorigin = -1, int maxLevel = -1, int minLevel = 1, bool skipChecks = false)
|
||||
public static EvoCriteria[] GetOriginChain(PKM pk, EvolutionOrigin enc, ushort encSpecies = 0, bool discard = true)
|
||||
{
|
||||
if (maxLevel < 0)
|
||||
maxLevel = pk.CurrentLevel;
|
||||
Span<EvoCriteria> result = stackalloc EvoCriteria[EvolutionTree.MaxEvolutions];
|
||||
int count = GetOriginChain(result, pk, enc, encSpecies, discard);
|
||||
if (count == 0)
|
||||
return Array.Empty<EvoCriteria>();
|
||||
|
||||
if (maxspeciesorigin == -1 && ParseSettings.AllowGen1Tradeback && pk is { Format: <= 2, Generation: 1 })
|
||||
maxspeciesorigin = MaxSpeciesID_2;
|
||||
var chain = result[..count];
|
||||
return chain.ToArray();
|
||||
}
|
||||
|
||||
var context = pk.Context;
|
||||
if (context < EntityContext.Gen2)
|
||||
context = EntityContext.Gen2;
|
||||
var et = EvolutionTree.GetEvolutionTree(context);
|
||||
return et.GetValidPreEvolutions(pk, levelMax: (byte)maxLevel, maxSpeciesOrigin: maxspeciesorigin, skipChecks: skipChecks, levelMin: (byte)minLevel);
|
||||
public static int GetOriginChain(Span<EvoCriteria> 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<EvoCriteria> 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);
|
||||
if (encSpecies != 0)
|
||||
return EvolutionUtil.IndexOf(result, encSpecies) + 1;
|
||||
return GetCount(result);
|
||||
}
|
||||
|
||||
private static int GetCount(Span<EvoCriteria> result)
|
||||
{
|
||||
// return the count of species != 0
|
||||
int count = 0;
|
||||
foreach (var evo in result)
|
||||
{
|
||||
if (evo.Species == 0)
|
||||
break;
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,60 +2,76 @@ using System;
|
|||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class EvolutionGroup1 : IEvolutionGroup
|
||||
public sealed class EvolutionGroup1 : IEvolutionGroup, IEvolutionEnvironment
|
||||
{
|
||||
public static readonly EvolutionGroup1 Instance = new();
|
||||
private static readonly EvolutionTree Tree = EvolutionTree.Evolves1;
|
||||
private const int MaxSpecies = Legal.MaxSpeciesID_1;
|
||||
private static PersonalTable1 Personal => PersonalTable.RB;
|
||||
|
||||
public IEvolutionGroup GetNext(PKM pk, EvolutionOrigin enc) => EvolutionGroup2.Instance;
|
||||
public IEvolutionGroup? GetPrevious(PKM pk, EvolutionOrigin enc) => pk.Format == 1 ? EvolutionGroup2.Instance : null;
|
||||
public IEvolutionGroup? GetPrevious(PKM pk, EvolutionOrigin enc) => pk.Format == 1 && ParseSettings.AllowGen1Tradeback ? EvolutionGroup2.Instance : null;
|
||||
|
||||
public bool Append(PKM pk, EvolutionHistory history, ref ReadOnlySpan<EvoCriteria> chain, EvolutionOrigin enc)
|
||||
public void DiscardForOrigin(Span<EvoCriteria> result, PKM pk)
|
||||
{
|
||||
// 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;
|
||||
if (!ParseSettings.AllowGen1Tradeback)
|
||||
return; // no other groups were iterated, so no need to discard
|
||||
|
||||
// Get the evolution tree from this group and get the new chain from it.
|
||||
var criteria = enc with { LevelMax = evo.LevelMax, LevelMin = enc.LevelMin };
|
||||
var local = GetInitialChain(pk, criteria, evo.Species, evo.Form);
|
||||
|
||||
// Revise the tree
|
||||
var revised = Prune(local);
|
||||
|
||||
// Set the tree to the history field
|
||||
history.Gen1 = revised;
|
||||
|
||||
// Retain a reference to the current chain for future appending as we step backwards.
|
||||
chain = revised;
|
||||
|
||||
return revised.Length != 0;
|
||||
EvolutionUtil.Discard(result, PersonalTable.C);
|
||||
}
|
||||
|
||||
public EvoCriteria[] GetInitialChain(PKM pk, EvolutionOrigin enc, ushort species, byte form)
|
||||
public int Devolve(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc)
|
||||
{
|
||||
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 (pk.Format >= 7 && !enc.SkipChecks)
|
||||
{
|
||||
// If the evo can't exist in the game, it must be a future evolution.
|
||||
if (!pt.IsPresentInGame(evo.Species, evo.Form))
|
||||
var max = pk.Met_Level;
|
||||
enc = enc with { LevelMin = 2, LevelMax = (byte)max };
|
||||
}
|
||||
int present = 1;
|
||||
for (int i = 1; i < result.Length; i++)
|
||||
{
|
||||
var prev = result[i - 1];
|
||||
if (!TryDevolve(prev, pk, prev.LevelMax, enc.LevelMin, enc.SkipChecks, out var evo))
|
||||
continue;
|
||||
|
||||
result = evo;
|
||||
return true;
|
||||
ref var reference = ref result[i];
|
||||
if (evo.IsBetterDevolution(reference))
|
||||
reference = evo;
|
||||
present++;
|
||||
}
|
||||
return present;
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
|
||||
public int Evolve(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc, EvolutionHistory history)
|
||||
{
|
||||
if (pk.Format > 2)
|
||||
enc = enc with { LevelMax = (byte)pk.Met_Level };
|
||||
|
||||
int present = 1;
|
||||
for (int i = result.Length - 1; i >= 1; i--)
|
||||
{
|
||||
ref var dest = ref result[i - 1];
|
||||
var devolved = result[i];
|
||||
if (!TryEvolve(devolved, dest, pk, enc.LevelMax, devolved.LevelMin, enc.SkipChecks, out var evo))
|
||||
{
|
||||
if (dest.Method == EvoCriteria.SentinelNotReached)
|
||||
break; // Don't continue for higher evolutions.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evo.IsBetterEvolution(dest))
|
||||
dest = evo;
|
||||
present++;
|
||||
}
|
||||
history.Gen1 = EvolutionUtil.SetHistory(result, PersonalTable.RB);
|
||||
return present;
|
||||
}
|
||||
|
||||
public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
return Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,64 +6,69 @@ public sealed class EvolutionGroup2 : IEvolutionGroup
|
|||
{
|
||||
public static readonly EvolutionGroup2 Instance = new();
|
||||
private static readonly EvolutionTree Tree = EvolutionTree.Evolves2;
|
||||
private const int MaxSpecies = Legal.MaxSpeciesID_2;
|
||||
private const int Generation = 2;
|
||||
private static PersonalTable2 Personal => PersonalTable.C;
|
||||
|
||||
public IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc) => pk.Format > Generation ? EvolutionGroup7.Instance : null;
|
||||
public IEvolutionGroup? GetPrevious(PKM pk, EvolutionOrigin enc) => pk.Format != 1 ? EvolutionGroup1.Instance : null;
|
||||
public void DiscardForOrigin(Span<EvoCriteria> result, PKM pk) => EvolutionUtil.Discard(result, Personal);
|
||||
|
||||
public bool Append(PKM pk, EvolutionHistory history, ref ReadOnlySpan<EvoCriteria> chain, EvolutionOrigin enc)
|
||||
public int Devolve(Span<EvoCriteria> result, PKM pk, 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.
|
||||
byte min;
|
||||
if (pk.Format > Generation)
|
||||
min = enc.LevelMin;
|
||||
else if (pk is ICaughtData2 { CaughtData: not 0 } c)
|
||||
min = (byte)c.Met_Level;
|
||||
else
|
||||
min = enc.LevelMin;
|
||||
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.Gen2 = 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 (pk.Format > Generation && !enc.SkipChecks)
|
||||
{
|
||||
// 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;
|
||||
var max = pk.Met_Level;
|
||||
EvolutionUtil.UpdateCeiling(result, max);
|
||||
enc = enc with { LevelMin = 2, LevelMax = (byte)max };
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
int present = 1;
|
||||
for (int i = 1; i < result.Length; i++)
|
||||
{
|
||||
var prev = result[i - 1];
|
||||
if (!TryDevolve(prev, pk, prev.LevelMax, enc.LevelMin, enc.SkipChecks, out var evo))
|
||||
continue;
|
||||
|
||||
ref var reference = ref result[i];
|
||||
if (evo.IsBetterDevolution(reference))
|
||||
reference = evo;
|
||||
present++;
|
||||
}
|
||||
return present;
|
||||
}
|
||||
|
||||
public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
|
||||
public int Evolve(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc, EvolutionHistory history)
|
||||
{
|
||||
if (pk.Format > Generation)
|
||||
enc = enc with { LevelMax = (byte)pk.Met_Level };
|
||||
|
||||
int present = 1;
|
||||
for (int i = result.Length - 1; i >= 1; i--)
|
||||
{
|
||||
ref var dest = ref result[i - 1];
|
||||
var devolved = result[i];
|
||||
if (!TryEvolve(devolved, dest, pk, enc.LevelMax, devolved.LevelMin, enc.SkipChecks, out var evo))
|
||||
{
|
||||
if (dest.Method == EvoCriteria.SentinelNotReached)
|
||||
break; // Don't continue for higher evolutions.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evo.IsBetterEvolution(dest))
|
||||
dest = evo;
|
||||
present++;
|
||||
}
|
||||
history.Gen2 = EvolutionUtil.SetHistory(result, Personal);
|
||||
return present;
|
||||
}
|
||||
|
||||
public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
return Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,58 +6,69 @@ 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 void DiscardForOrigin(Span<EvoCriteria> result, PKM pk) => EvolutionUtil.Discard(result, Personal);
|
||||
|
||||
public bool Append(PKM pk, EvolutionHistory history, ref ReadOnlySpan<EvoCriteria> chain, EvolutionOrigin enc)
|
||||
public int Devolve(Span<EvoCriteria> result, PKM pk, 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 (pk.Format == 4 && !enc.SkipChecks) // 5+ already have been revised
|
||||
{
|
||||
// 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;
|
||||
var max = pk.Met_Level;
|
||||
EvolutionUtil.UpdateCeiling(result, max);
|
||||
enc = enc with { LevelMin = 1, LevelMax = (byte)max };
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
int present = 1;
|
||||
for (int i = 1; i < result.Length; i++)
|
||||
{
|
||||
var prev = result[i - 1];
|
||||
if (!TryDevolve(prev, pk, prev.LevelMax, enc.LevelMin, enc.SkipChecks, out var evo))
|
||||
continue;
|
||||
|
||||
ref var reference = ref result[i];
|
||||
if (evo.IsBetterDevolution(reference))
|
||||
reference = evo;
|
||||
present++;
|
||||
}
|
||||
return present;
|
||||
}
|
||||
|
||||
public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
|
||||
public int Evolve(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc, EvolutionHistory history)
|
||||
{
|
||||
if (pk.Format > Generation)
|
||||
enc = enc with { LevelMax = (byte)pk.Met_Level };
|
||||
|
||||
int present = 1;
|
||||
for (int i = result.Length - 1; i >= 1; i--)
|
||||
{
|
||||
ref var dest = ref result[i - 1];
|
||||
var devolved = result[i];
|
||||
if (!TryEvolve(devolved, dest, pk, enc.LevelMax, devolved.LevelMin, enc.SkipChecks, out var evo))
|
||||
{
|
||||
if (dest.Method == EvoCriteria.SentinelNotReached)
|
||||
break; // Don't continue for higher evolutions.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evo.IsBetterEvolution(dest))
|
||||
dest = evo;
|
||||
present++;
|
||||
}
|
||||
history.Gen3 = EvolutionUtil.SetHistory(result, Personal);
|
||||
return present;
|
||||
}
|
||||
|
||||
public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
return Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,58 +6,71 @@ public sealed class EvolutionGroup4 : IEvolutionGroup
|
|||
{
|
||||
public static readonly EvolutionGroup4 Instance = new();
|
||||
private static readonly EvolutionTree Tree = EvolutionTree.Evolves4;
|
||||
private const int MaxSpecies = Legal.MaxSpeciesID_4;
|
||||
private const int Generation = 4;
|
||||
private static PersonalTable4 Personal => PersonalTable.HGSS;
|
||||
|
||||
public IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc) => pk.Format > Generation ? EvolutionGroup5.Instance : null;
|
||||
public IEvolutionGroup? GetPrevious(PKM pk, EvolutionOrigin enc) => enc.Generation == 3 ? EvolutionGroup3.Instance : null;
|
||||
public void DiscardForOrigin(Span<EvoCriteria> result, PKM pk) => EvolutionUtil.Discard(result, Personal);
|
||||
|
||||
public bool Append(PKM pk, EvolutionHistory history, ref ReadOnlySpan<EvoCriteria> chain, EvolutionOrigin enc)
|
||||
public int Devolve(Span<EvoCriteria> result, PKM pk, 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.Gen4 = 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 (pk.Format > Generation && !enc.SkipChecks)
|
||||
{
|
||||
// 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;
|
||||
var max = pk.Met_Level;
|
||||
EvolutionUtil.UpdateCeiling(result, max);
|
||||
enc = enc with { LevelMin = 1, LevelMax = (byte)max };
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
int present = 1;
|
||||
for (int i = 1; i < result.Length; i++)
|
||||
{
|
||||
var prev = result[i - 1];
|
||||
if (!TryDevolve(prev, pk, prev.LevelMax, enc.LevelMin, enc.SkipChecks, out var evo))
|
||||
continue;
|
||||
|
||||
ref var reference = ref result[i];
|
||||
if (evo.IsBetterDevolution(reference))
|
||||
reference = evo;
|
||||
present++;
|
||||
}
|
||||
return present;
|
||||
}
|
||||
|
||||
public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
|
||||
public int Evolve(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc, EvolutionHistory history)
|
||||
{
|
||||
if (pk.Format > Generation)
|
||||
enc = enc with { LevelMax = (byte)pk.Met_Level };
|
||||
else if (enc.Generation < Generation)
|
||||
EvolutionUtil.UpdateFloor(result, pk.Met_Level);
|
||||
|
||||
int present = 1;
|
||||
for (int i = result.Length - 1; i >= 1; i--)
|
||||
{
|
||||
ref var dest = ref result[i - 1];
|
||||
var devolved = result[i];
|
||||
if (!TryEvolve(devolved, dest, pk, enc.LevelMax, devolved.LevelMin, enc.SkipChecks, out var evo))
|
||||
{
|
||||
if (dest.Method == EvoCriteria.SentinelNotReached)
|
||||
break; // Don't continue for higher evolutions.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evo.IsBetterEvolution(dest))
|
||||
dest = evo;
|
||||
present++;
|
||||
}
|
||||
history.Gen4 = EvolutionUtil.SetHistory(result, Personal);
|
||||
return present;
|
||||
}
|
||||
|
||||
public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
return Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,57 +6,62 @@ public sealed class EvolutionGroup5 : IEvolutionGroup
|
|||
{
|
||||
public static readonly EvolutionGroup5 Instance = new();
|
||||
private static readonly EvolutionTree Tree = EvolutionTree.Evolves5;
|
||||
private const int MaxSpecies = Legal.MaxSpeciesID_5;
|
||||
private const int Generation = 5;
|
||||
private static PersonalTable5B2W2 Personal => PersonalTable.B2W2;
|
||||
|
||||
public IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc) => pk.Format > Generation ? EvolutionGroup6.Instance : null;
|
||||
public IEvolutionGroup? GetPrevious(PKM pk, EvolutionOrigin enc) => enc.Generation < Generation ? EvolutionGroup4.Instance : null;
|
||||
public void DiscardForOrigin(Span<EvoCriteria> result, PKM pk) => EvolutionUtil.Discard(result, Personal);
|
||||
|
||||
public bool Append(PKM pk, EvolutionHistory history, ref ReadOnlySpan<EvoCriteria> chain, EvolutionOrigin enc)
|
||||
public int Devolve(Span<EvoCriteria> result, PKM pk, 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 criteria = enc with { LevelMax = evo.LevelMax, LevelMin = (byte)pk.Met_Level };
|
||||
var local = GetInitialChain(pk, criteria, evo.Species, evo.Form);
|
||||
|
||||
// Revise the tree
|
||||
var revised = Prune(local);
|
||||
|
||||
// Set the tree to the history field
|
||||
history.Gen5 = 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)
|
||||
int present = 1;
|
||||
for (int i = 1; i < result.Length; i++)
|
||||
{
|
||||
// If the evo can't exist in the game, it must be a future evolution.
|
||||
if (!pt.IsPresentInGame(evo.Species, evo.Form))
|
||||
var prev = result[i - 1];
|
||||
if (!TryDevolve(prev, pk, prev.LevelMax, enc.LevelMin, enc.SkipChecks, out var evo))
|
||||
continue;
|
||||
|
||||
result = evo;
|
||||
return true;
|
||||
ref var reference = ref result[i];
|
||||
if (evo.IsBetterDevolution(reference))
|
||||
reference = evo;
|
||||
present++;
|
||||
}
|
||||
return present;
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
|
||||
public int Evolve(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc, EvolutionHistory history)
|
||||
{
|
||||
if (enc.Generation < Generation)
|
||||
EvolutionUtil.UpdateFloor(result, pk.Met_Level);
|
||||
|
||||
int present = 1;
|
||||
for (int i = result.Length - 1; i >= 1; i--)
|
||||
{
|
||||
ref var dest = ref result[i - 1];
|
||||
var devolved = result[i];
|
||||
if (!TryEvolve(devolved, dest, pk, enc.LevelMax, devolved.LevelMin, enc.SkipChecks, out var evo))
|
||||
{
|
||||
if (dest.Method == EvoCriteria.SentinelNotReached)
|
||||
break; // Don't continue for higher evolutions.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evo.IsBetterEvolution(dest))
|
||||
dest = evo;
|
||||
present++;
|
||||
}
|
||||
history.Gen5 = EvolutionUtil.SetHistory(result, Personal);
|
||||
return present;
|
||||
}
|
||||
|
||||
public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
return Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,57 +6,59 @@ public sealed class EvolutionGroup6 : IEvolutionGroup
|
|||
{
|
||||
public static readonly EvolutionGroup6 Instance = new();
|
||||
private static readonly EvolutionTree Tree = EvolutionTree.Evolves6;
|
||||
private const int MaxSpecies = Legal.MaxSpeciesID_6;
|
||||
private const int Generation = 6;
|
||||
private static PersonalTable6AO Personal => PersonalTable.AO;
|
||||
|
||||
public IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc) => pk.Format > Generation ? EvolutionGroup7.Instance : null;
|
||||
public IEvolutionGroup? GetPrevious(PKM pk, EvolutionOrigin enc) => enc.Generation < Generation ? EvolutionGroup5.Instance : null;
|
||||
public void DiscardForOrigin(Span<EvoCriteria> result, PKM pk) => EvolutionUtil.Discard(result, Personal);
|
||||
|
||||
public bool Append(PKM pk, EvolutionHistory history, ref ReadOnlySpan<EvoCriteria> chain, EvolutionOrigin enc)
|
||||
public int Devolve(Span<EvoCriteria> result, PKM pk, 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 criteria = enc with { LevelMax = evo.LevelMax, LevelMin = (byte)pk.Met_Level };
|
||||
var local = GetInitialChain(pk, criteria, evo.Species, evo.Form);
|
||||
|
||||
// Revise the tree
|
||||
var revised = Prune(local);
|
||||
|
||||
// Set the tree to the history field
|
||||
history.Gen6 = 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)
|
||||
int present = 1;
|
||||
for (int i = 1; i < result.Length; i++)
|
||||
{
|
||||
// If the evo can't exist in the game, it must be a future evolution.
|
||||
if (!pt.IsPresentInGame(evo.Species, evo.Form))
|
||||
var prev = result[i - 1];
|
||||
if (!TryDevolve(prev, pk, prev.LevelMax, enc.LevelMin, enc.SkipChecks, out var evo))
|
||||
continue;
|
||||
|
||||
result = evo;
|
||||
return true;
|
||||
ref var reference = ref result[i];
|
||||
if (evo.IsBetterDevolution(reference))
|
||||
reference = evo;
|
||||
present++;
|
||||
}
|
||||
return present;
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
|
||||
public int Evolve(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc, EvolutionHistory history)
|
||||
{
|
||||
int present = 1;
|
||||
for (int i = result.Length - 1; i >= 1; i--)
|
||||
{
|
||||
ref var dest = ref result[i - 1];
|
||||
var devolved = result[i];
|
||||
if (!TryEvolve(devolved, dest, pk, enc.LevelMax, devolved.LevelMin, enc.SkipChecks, out var evo))
|
||||
{
|
||||
if (dest.Method == EvoCriteria.SentinelNotReached)
|
||||
break; // Don't continue for higher evolutions.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evo.IsBetterEvolution(dest))
|
||||
dest = evo;
|
||||
present++;
|
||||
}
|
||||
history.Gen6 = EvolutionUtil.SetHistory(result, Personal);
|
||||
return present;
|
||||
}
|
||||
|
||||
public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
return Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,64 +6,90 @@ public sealed class EvolutionGroup7 : IEvolutionGroup
|
|||
{
|
||||
public static readonly EvolutionGroup7 Instance = new();
|
||||
private static readonly EvolutionTree Tree = EvolutionTree.Evolves7;
|
||||
private const int MaxSpecies = Legal.MaxSpeciesID_7_USUM;
|
||||
private const int Generation = 7;
|
||||
private static PersonalTable7 Personal => PersonalTable.USUM;
|
||||
|
||||
public IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc) => pk.Format > Generation ? EvolutionGroup8.Instance : null;
|
||||
public IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc) => pk.Format > Generation ? EvolutionGroupHOME.Instance : null;
|
||||
public IEvolutionGroup? GetPrevious(PKM pk, EvolutionOrigin enc)
|
||||
{
|
||||
if (enc.Generation <= 2)
|
||||
if (enc.Generation is 1 or 2)
|
||||
return EvolutionGroup2.Instance;
|
||||
if (enc.Generation < Generation)
|
||||
return EvolutionGroup6.Instance;
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool Append(PKM pk, EvolutionHistory history, ref ReadOnlySpan<EvoCriteria> chain, EvolutionOrigin enc)
|
||||
public void DiscardForOrigin(Span<EvoCriteria> result, PKM pk) => EvolutionUtil.Discard(result, Personal);
|
||||
|
||||
public int Devolve(Span<EvoCriteria> result, PKM pk, 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 criteria = enc with { LevelMax = evo.LevelMax, LevelMin = (byte)pk.Met_Level };
|
||||
var local = GetInitialChain(pk, criteria, evo.Species, evo.Form);
|
||||
|
||||
// Revise the tree
|
||||
var revised = Prune(local);
|
||||
|
||||
// Set the tree to the history field
|
||||
history.Gen7 = 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)
|
||||
int present = 1;
|
||||
for (int i = 1; i < result.Length; i++)
|
||||
{
|
||||
// If the evo can't exist in the game, it must be a future evolution.
|
||||
if (!pt.IsPresentInGame(evo.Species, evo.Form))
|
||||
ref var prev = ref result[i - 1];
|
||||
RevertMutatedForms(ref prev);
|
||||
if (!TryDevolve(prev, pk, prev.LevelMax, enc.LevelMin, enc.SkipChecks, out var evo))
|
||||
continue;
|
||||
|
||||
result = evo;
|
||||
return true;
|
||||
ref var reference = ref result[i];
|
||||
if (evo.IsBetterDevolution(reference))
|
||||
reference = evo;
|
||||
present++;
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
return present;
|
||||
}
|
||||
|
||||
private static void RevertMutatedForms(ref EvoCriteria evo)
|
||||
{
|
||||
// Zygarde's 10% Form and 50% Form can be changed with the help of external tools: the Reassembly Unit and the Zygarde Cube.
|
||||
if (evo is { Species: (ushort)Species.Zygarde, Form: not (0 or 1) })
|
||||
evo = evo with { Form = evo.LevelMax == 63 ? (byte)1 : (byte)0 }; // 50% Forme
|
||||
else if (evo is { Species: (ushort)Species.Silvally, Form: not 0 })
|
||||
evo = evo with { Form = 0 }; // Normal
|
||||
}
|
||||
|
||||
public int Evolve(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc, EvolutionHistory history)
|
||||
{
|
||||
if (enc.Generation <= 2) // VC Transfer
|
||||
EvolutionUtil.UpdateFloor(result, pk.Met_Level);
|
||||
|
||||
int present = 1;
|
||||
for (int i = result.Length - 1; i >= 1; i--)
|
||||
{
|
||||
ref var dest = ref result[i - 1];
|
||||
var devolved = result[i];
|
||||
if (!TryEvolve(devolved, dest, pk, enc.LevelMax, devolved.LevelMin, enc.SkipChecks, out var evo))
|
||||
{
|
||||
if (dest.Method == EvoCriteria.SentinelNotReached)
|
||||
break; // Don't continue for higher evolutions.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evo.IsBetterEvolution(dest))
|
||||
dest = evo;
|
||||
present++;
|
||||
}
|
||||
history.Gen7 = EvolutionUtil.SetHistory(result, Personal);
|
||||
return present;
|
||||
}
|
||||
|
||||
public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
|
||||
public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
var b = Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
return b && !IsEvolutionBanned(pk, result);
|
||||
}
|
||||
|
||||
// Kanto Evolutions are not accessible unless it visits US/UM.
|
||||
private static bool IsEvolutionBanned(PKM pk, in ISpeciesForm dest) => pk is PK7 { SM: true, IsUntraded: true } && dest switch
|
||||
{
|
||||
{ Species: (ushort)Species.Raichu, Form: 0 } => true,
|
||||
{ Species: (ushort)Species.Marowak, Form: 0 } => true,
|
||||
{ Species: (ushort)Species.Exeggutor, Form: 0 } => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,57 +6,59 @@ public sealed class EvolutionGroup7b : IEvolutionGroup
|
|||
{
|
||||
public static readonly EvolutionGroup7b Instance = new();
|
||||
private static readonly EvolutionTree Tree = EvolutionTree.Evolves7b;
|
||||
private const int MaxSpecies = Legal.MaxSpeciesID_7b;
|
||||
private const int Generation = 7;
|
||||
private static PersonalTable7GG Personal => PersonalTable.GG;
|
||||
|
||||
public IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc) => pk.Format > Generation ? EvolutionGroup8.Instance : null;
|
||||
public IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc) => pk.Format > Generation ? EvolutionGroupHOME.Instance : null;
|
||||
public IEvolutionGroup? GetPrevious(PKM pk, EvolutionOrigin enc) => null;
|
||||
public void DiscardForOrigin(Span<EvoCriteria> result, PKM pk) => EvolutionUtil.Discard(result, Personal);
|
||||
|
||||
public bool Append(PKM pk, EvolutionHistory history, ref ReadOnlySpan<EvoCriteria> chain, EvolutionOrigin enc)
|
||||
public int Devolve(Span<EvoCriteria> result, PKM pk, 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 criteria = enc with { LevelMax = evo.LevelMax, LevelMin = (byte)pk.Met_Level };
|
||||
var local = GetInitialChain(pk, criteria, evo.Species, evo.Form);
|
||||
|
||||
// Revise the tree
|
||||
var revised = Prune(local);
|
||||
|
||||
// Set the tree to the history field
|
||||
history.Gen7b = 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)
|
||||
int present = 1;
|
||||
for (int i = 1; i < result.Length; i++)
|
||||
{
|
||||
// If the evo can't exist in the game, it must be a future evolution.
|
||||
if (!pt.IsPresentInGame(evo.Species, evo.Form))
|
||||
var prev = result[i - 1];
|
||||
if (!TryDevolve(prev, pk, prev.LevelMax, enc.LevelMin, enc.SkipChecks, out var evo))
|
||||
continue;
|
||||
|
||||
result = evo;
|
||||
return true;
|
||||
ref var reference = ref result[i];
|
||||
if (evo.IsBetterDevolution(reference))
|
||||
reference = evo;
|
||||
present++;
|
||||
}
|
||||
return present;
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
|
||||
public int Evolve(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc, EvolutionHistory history)
|
||||
{
|
||||
int present = 1;
|
||||
for (int i = result.Length - 1; i >= 1; i--)
|
||||
{
|
||||
ref var dest = ref result[i - 1];
|
||||
var devolved = result[i];
|
||||
if (!TryEvolve(devolved, dest, pk, enc.LevelMax, devolved.LevelMin, enc.SkipChecks, out var evo))
|
||||
{
|
||||
if (dest.Method == EvoCriteria.SentinelNotReached)
|
||||
break; // Don't continue for higher evolutions.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evo.IsBetterEvolution(dest))
|
||||
dest = evo;
|
||||
present++;
|
||||
}
|
||||
history.Gen7b = EvolutionUtil.SetHistory(result, Personal);
|
||||
return present;
|
||||
}
|
||||
|
||||
public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
return Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,232 +0,0 @@
|
|||
using System;
|
||||
using static PKHeX.Core.GameVersion;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class EvolutionGroup8 : IEvolutionGroup
|
||||
{
|
||||
public static readonly EvolutionGroup8 Instance = new();
|
||||
private static readonly EvolutionTree Tree8 = EvolutionTree.Evolves8;
|
||||
private static readonly EvolutionTree Tree8a = EvolutionTree.Evolves8a;
|
||||
private static readonly EvolutionTree Tree8b = EvolutionTree.Evolves8b;
|
||||
private const int MaxSpecies = Legal.MaxSpeciesID_8a;
|
||||
private const int Generation = 8;
|
||||
|
||||
public IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc) => EvolutionGroup9.Instance;
|
||||
public IEvolutionGroup? GetPrevious(PKM pk, EvolutionOrigin enc)
|
||||
{
|
||||
if ((GameVersion)enc.Version is GP or GE or GG or GO)
|
||||
return EvolutionGroup7b.Instance;
|
||||
if (enc.Generation >= Generation)
|
||||
return null;
|
||||
return EvolutionGroup7.Instance;
|
||||
}
|
||||
|
||||
public bool Append(PKM pk, EvolutionHistory history, ref ReadOnlySpan<EvoCriteria> chain, EvolutionOrigin enc)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
return false;
|
||||
|
||||
var swsh = Append(pk, chain, enc, PersonalTable.SWSH, Tree8 , ref history.Gen8 );
|
||||
var pla = Append(pk, chain, enc, PersonalTable.LA , Tree8a, ref history.Gen8a);
|
||||
var bdsp = Append(pk, chain, enc, PersonalTable.BDSP, Tree8b, ref history.Gen8b);
|
||||
|
||||
if (!(swsh || pla || bdsp))
|
||||
return false;
|
||||
|
||||
// Block BD/SP transfers that are impossible
|
||||
BlockBDSP(history, enc);
|
||||
|
||||
if (!pk.IsUntraded && !(ParseSettings.IgnoreTransferIfNoTracker && pk is IHomeTrack { HasTracker: false }))
|
||||
{
|
||||
CrossPropagate(history);
|
||||
}
|
||||
else
|
||||
{
|
||||
DeleteAdjacent(pk, history);
|
||||
|
||||
if (!HasVisited(history))
|
||||
return false;
|
||||
}
|
||||
|
||||
chain = GetMaxChain(history);
|
||||
|
||||
return chain.Length != 0;
|
||||
}
|
||||
|
||||
private static bool HasVisited(EvolutionHistory history)
|
||||
{
|
||||
return history.Gen8.Length != 0 || history.Gen8a.Length != 0 || history.Gen8b.Length != 0;
|
||||
}
|
||||
|
||||
private static void DeleteAdjacent(PKM pk, EvolutionHistory history)
|
||||
{
|
||||
if (pk is not PK8)
|
||||
history.Gen8 = Array.Empty<EvoCriteria>();
|
||||
if (pk is not PA8)
|
||||
history.Gen8a = Array.Empty<EvoCriteria>();
|
||||
if (pk is not PB8)
|
||||
history.Gen8b = Array.Empty<EvoCriteria>();
|
||||
}
|
||||
|
||||
private static void BlockBDSP(EvolutionHistory history, EvolutionOrigin enc)
|
||||
{
|
||||
var bdsp = history.Gen8b;
|
||||
if (bdsp.Length == 0)
|
||||
return;
|
||||
|
||||
// Spinda and Nincada cannot transfer in or out as the current species.
|
||||
// Remove them from their non-origin game evolution chains.
|
||||
var last = bdsp[^1];
|
||||
if (last.Species == (int)Species.Nincada)
|
||||
RemoveIfSpecies(history, enc);
|
||||
else if (last.Species == (int)Species.Spinda)
|
||||
RemoveIfSpecies(history, enc);
|
||||
|
||||
static void RemoveIfSpecies(EvolutionHistory history, EvolutionOrigin enc)
|
||||
{
|
||||
var wasBDSP = BDSP.Contains(enc.Version);
|
||||
ref var evos = ref wasBDSP ? ref history.Gen8 : ref history.Gen8b;
|
||||
evos = evos.Length < 2 ? Array.Empty<EvoCriteria>() : evos.AsSpan(0, evos.Length - 1).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<EvoCriteria> GetMaxChain(EvolutionHistory history)
|
||||
{
|
||||
var arr0 = history.Gen8;
|
||||
var arr1 = history.Gen8a;
|
||||
var arr2 = history.Gen8b;
|
||||
if (arr0.Length >= arr1.Length && arr0.Length >= arr2.Length)
|
||||
return arr0;
|
||||
if (arr1.Length >= arr2.Length)
|
||||
return arr1;
|
||||
return arr2;
|
||||
}
|
||||
|
||||
private static void CrossPropagate(EvolutionHistory history)
|
||||
{
|
||||
var arr0 = history.Gen8;
|
||||
var arr1 = history.Gen8a;
|
||||
var arr2 = history.Gen8b;
|
||||
|
||||
ReplaceIfBetter(arr0, arr1, arr2);
|
||||
ReplaceIfBetter(arr1, arr0, arr2);
|
||||
ReplaceIfBetter(arr2, arr0, arr1);
|
||||
}
|
||||
|
||||
private static void ReplaceIfBetter(Span<EvoCriteria> local, ReadOnlySpan<EvoCriteria> other1, ReadOnlySpan<EvoCriteria> other2)
|
||||
{
|
||||
for (int i = 0; i < local.Length; i++)
|
||||
{
|
||||
ReplaceIfBetter(local, other1, i);
|
||||
ReplaceIfBetter(local, other2, i);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReplaceIfBetter(Span<EvoCriteria> local, ReadOnlySpan<EvoCriteria> other, int parentIndex)
|
||||
{
|
||||
// Replace the evolution entry if another connected game has a better evolution method (different min/max).
|
||||
var native = local[parentIndex];
|
||||
|
||||
// Check if the evo is in the other game; if not, we're done here.
|
||||
var index = IndexOfSpecies(other, native.Species);
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
var alt = other[index];
|
||||
if (alt.LevelMin < native.LevelMin || alt.LevelMax > native.LevelMax)
|
||||
local[parentIndex] = alt;
|
||||
}
|
||||
|
||||
private static int IndexOfSpecies(ReadOnlySpan<EvoCriteria> evos, ushort species)
|
||||
{
|
||||
// Returns the index of the first evo that matches the species
|
||||
for (int i = 0; i < evos.Length; i++)
|
||||
{
|
||||
if (evos[i].Species == species)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static bool Append<T>(PKM pk, ReadOnlySpan<EvoCriteria> chain, EvolutionOrigin enc, T pt, EvolutionTree tree, ref EvoCriteria[] dest) where T : IPersonalTable
|
||||
{
|
||||
// Get the first evolution in the chain that can be present in this group
|
||||
var any = GetFirstEvolution(pt, chain, out var evo);
|
||||
if (!any)
|
||||
return false;
|
||||
|
||||
// Get the evolution tree from this group and get the new chain from it.
|
||||
var criteria = enc with { LevelMax = evo.LevelMax, LevelMin = (byte)pk.Met_Level };
|
||||
var local = GetInitialChain(pk, criteria, evo.Species, evo.Form, tree);
|
||||
|
||||
// Revise the tree
|
||||
var revised = Prune(local);
|
||||
|
||||
// Set the tree to the history field
|
||||
dest = revised;
|
||||
|
||||
return revised.Length != 0;
|
||||
}
|
||||
|
||||
public EvoCriteria[] GetInitialChain(PKM pk, EvolutionOrigin enc, ushort species, byte form)
|
||||
{
|
||||
if (!GetPreferredGroup(species, form, out var group))
|
||||
return Array.Empty<EvoCriteria>();
|
||||
var tree = GetTree(group);
|
||||
return GetInitialChain(pk, enc, species, form, tree);
|
||||
}
|
||||
|
||||
private static EvoCriteria[] GetInitialChain(PKM pk, EvolutionOrigin enc, ushort species, byte form, EvolutionTree tree)
|
||||
{
|
||||
if (species is (int)Species.Dialga or (int)Species.Palkia or (int)Species.Arceus)
|
||||
form = 0; // PLA forms; play nice for yielding SW/SH context
|
||||
return tree.GetExplicitLineage(species, form, pk, enc.LevelMin, enc.LevelMax, MaxSpecies, enc.SkipChecks, enc.Species);
|
||||
}
|
||||
|
||||
private static EvolutionTree GetTree(PreferredGroup group) => group switch
|
||||
{
|
||||
PreferredGroup.BDSP => Tree8b,
|
||||
PreferredGroup.LA => Tree8a,
|
||||
_ => Tree8,
|
||||
};
|
||||
|
||||
private static bool GetPreferredGroup(ushort species, byte form, out PreferredGroup result)
|
||||
{
|
||||
if (PersonalTable.LA.IsPresentInGame(species, form))
|
||||
result = PreferredGroup.LA;
|
||||
else if (PersonalTable.SWSH.IsPresentInGame(species, form))
|
||||
result = PreferredGroup.SWSH;
|
||||
else if (PersonalTable.BDSP.IsPresentInGame(species, form))
|
||||
result = PreferredGroup.BDSP;
|
||||
else
|
||||
result = PreferredGroup.None;
|
||||
return result != 0;
|
||||
}
|
||||
|
||||
private static EvoCriteria[] Prune(EvoCriteria[] chain) => chain;
|
||||
|
||||
private enum PreferredGroup
|
||||
{
|
||||
None,
|
||||
LA,
|
||||
SWSH,
|
||||
BDSP,
|
||||
}
|
||||
|
||||
private static bool GetFirstEvolution<T>(T pt, ReadOnlySpan<EvoCriteria> chain, out EvoCriteria result) where T : IPersonalTable
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class EvolutionGroup9 : IEvolutionGroup
|
||||
{
|
||||
public static readonly EvolutionGroup9 Instance = new();
|
||||
private static readonly EvolutionTree Tree9 = EvolutionTree.Evolves9;
|
||||
private const int MaxSpecies = Legal.MaxSpeciesID_9;
|
||||
|
||||
public IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc) => null;
|
||||
public IEvolutionGroup GetPrevious(PKM pk, EvolutionOrigin enc) => EvolutionGroup8.Instance;
|
||||
|
||||
public bool Append(PKM pk, EvolutionHistory history, ref ReadOnlySpan<EvoCriteria> chain, EvolutionOrigin enc)
|
||||
{
|
||||
if (chain.Length == 0)
|
||||
return false;
|
||||
|
||||
var sv = Append(pk, chain, enc, PersonalTable.SV, Tree9, ref history.Gen9);
|
||||
|
||||
if (!sv)
|
||||
return false;
|
||||
|
||||
chain = history.Gen9;
|
||||
return chain.Length != 0;
|
||||
}
|
||||
|
||||
private static bool Append<T>(PKM pk, ReadOnlySpan<EvoCriteria> chain, EvolutionOrigin enc, T pt, EvolutionTree tree, ref EvoCriteria[] dest) where T : IPersonalTable
|
||||
{
|
||||
// Get the first evolution in the chain that can be present in this group
|
||||
var any = GetFirstEvolution(pt, chain, out var evo);
|
||||
if (!any)
|
||||
return false;
|
||||
|
||||
// Get the evolution tree from this group and get the new chain from it.
|
||||
var criteria = enc with { LevelMax = evo.LevelMax, LevelMin = (byte)pk.Met_Level };
|
||||
var local = GetInitialChain(pk, criteria, evo.Species, evo.Form, tree);
|
||||
|
||||
// Revise the tree
|
||||
var revised = Prune(local);
|
||||
|
||||
// Set the tree to the history field
|
||||
dest = revised;
|
||||
|
||||
return revised.Length != 0;
|
||||
}
|
||||
|
||||
public EvoCriteria[] GetInitialChain(PKM pk, EvolutionOrigin enc, ushort species, byte form)
|
||||
{
|
||||
if (!GetPreferredGroup(species, form, out var group))
|
||||
return Array.Empty<EvoCriteria>();
|
||||
var tree = GetTree(group);
|
||||
return GetInitialChain(pk, enc, species, form, tree);
|
||||
}
|
||||
|
||||
private static EvoCriteria[] GetInitialChain(PKM pk, EvolutionOrigin enc, ushort species, byte form, EvolutionTree tree)
|
||||
{
|
||||
return tree.GetExplicitLineage(species, form, pk, enc.LevelMin, enc.LevelMax, MaxSpecies, enc.SkipChecks, enc.Species);
|
||||
}
|
||||
|
||||
private static EvolutionTree GetTree(PreferredGroup group) => group switch
|
||||
{
|
||||
_ => Tree9,
|
||||
};
|
||||
|
||||
private static bool GetPreferredGroup(ushort species, byte form, out PreferredGroup result)
|
||||
{
|
||||
if (PersonalTable.SV.IsPresentInGame(species, form))
|
||||
result = PreferredGroup.SV;
|
||||
else
|
||||
result = PreferredGroup.None;
|
||||
return result != 0;
|
||||
}
|
||||
|
||||
private static EvoCriteria[] Prune(EvoCriteria[] chain) => chain;
|
||||
|
||||
private enum PreferredGroup
|
||||
{
|
||||
None,
|
||||
SV,
|
||||
}
|
||||
|
||||
private static bool GetFirstEvolution<T>(T pt, ReadOnlySpan<EvoCriteria> chain, out EvoCriteria result) where T : IPersonalTable
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,258 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using static PKHeX.Core.GameVersion;
|
||||
using static PKHeX.Core.EvolutionUtil;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class EvolutionGroupHOME : IEvolutionGroup
|
||||
{
|
||||
public static readonly EvolutionGroupHOME Instance = new();
|
||||
|
||||
private static readonly EvolutionEnvironment8 SWSH = new();
|
||||
private static readonly EvolutionEnvironment8a LA = new();
|
||||
private static readonly EvolutionEnvironment8b BDSP = new();
|
||||
private static readonly EvolutionEnvironment9 SV = new();
|
||||
|
||||
public IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc) => null;
|
||||
|
||||
public IEvolutionGroup? GetPrevious(PKM pk, EvolutionOrigin enc)
|
||||
{
|
||||
if (enc.Generation >= 8)
|
||||
return null;
|
||||
if ((GameVersion)enc.Version is GP or GE or GG or GO)
|
||||
return EvolutionGroup7b.Instance;
|
||||
return EvolutionGroup7.Instance;
|
||||
}
|
||||
|
||||
public void DiscardForOrigin(Span<EvoCriteria> result, PKM pk)
|
||||
{
|
||||
if (pk.SV)
|
||||
Discard(result, PersonalTable.SV);
|
||||
else if (pk.LA)
|
||||
Discard(result, PersonalTable.LA);
|
||||
else if (pk.BDSP)
|
||||
Discard(result, PersonalTable.BDSP);
|
||||
else if (pk.SWSH)
|
||||
Discard(result, PersonalTable.SWSH);
|
||||
// GO can be otherwise, don't discard any.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if we should check all adjacent evolution sources in addition to the current one.
|
||||
/// </summary>
|
||||
/// <returns>True if we should check all adjacent evolution sources.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool CheckAllAdjacent(PKM pk, EvolutionOrigin enc) => enc.SkipChecks || pk is IHomeTrack { HasTracker: true } || !ParseSettings.IgnoreTransferIfNoTracker;
|
||||
|
||||
public int Devolve(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc)
|
||||
{
|
||||
if (CheckAllAdjacent(pk, enc))
|
||||
return DevolveMulti(result, pk, enc);
|
||||
return DevolveSingle(result, pk, enc);
|
||||
}
|
||||
|
||||
public int Evolve(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc, EvolutionHistory history)
|
||||
{
|
||||
if (CheckAllAdjacent(pk, enc))
|
||||
return EvolveMulti(result, pk, enc, history);
|
||||
return EvolveSingle(result, pk, enc, history);
|
||||
}
|
||||
|
||||
private int DevolveMulti(Span<EvoCriteria> result, PKM pk, in EvolutionOrigin enc)
|
||||
{
|
||||
int present = 1;
|
||||
for (int i = 1; i < result.Length; i++)
|
||||
{
|
||||
ref var prev = ref result[i - 1];
|
||||
RevertMutatedForms(ref prev);
|
||||
ref var reference = ref result[i];
|
||||
|
||||
bool devolvedAny = false;
|
||||
if (SWSH.TryDevolve(prev, pk, prev.LevelMax, enc.LevelMin, enc.SkipChecks, out var evo))
|
||||
devolvedAny = UpdateIfBetter(ref reference, evo);
|
||||
if (LA .TryDevolve(prev, pk, prev.LevelMax, enc.LevelMin, enc.SkipChecks, out evo))
|
||||
devolvedAny = UpdateIfBetter(ref reference, evo);
|
||||
if (BDSP.TryDevolve(prev, pk, prev.LevelMax, enc.LevelMin, enc.SkipChecks, out evo))
|
||||
devolvedAny = UpdateIfBetter(ref reference, evo);
|
||||
if (SV .TryDevolve(prev, pk, prev.LevelMax, enc.LevelMin, enc.SkipChecks, out evo))
|
||||
devolvedAny = UpdateIfBetter(ref reference, evo);
|
||||
|
||||
if (devolvedAny)
|
||||
present++;
|
||||
|
||||
static bool UpdateIfBetter(ref EvoCriteria reference, in EvoCriteria evo)
|
||||
{
|
||||
if (evo.IsBetterDevolution(reference))
|
||||
reference = evo;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return present;
|
||||
}
|
||||
|
||||
private int EvolveMulti(Span<EvoCriteria> result, PKM pk, in EvolutionOrigin enc, EvolutionHistory history)
|
||||
{
|
||||
int present = 1;
|
||||
for (int i = result.Length - 1; i >= 1; i--)
|
||||
{
|
||||
ref var dest = ref result[i - 1];
|
||||
var devolved = result[i];
|
||||
|
||||
bool devolvedAny = false;
|
||||
if (SWSH.TryEvolve(devolved, dest, pk, enc.LevelMax, devolved.LevelMin, enc.SkipChecks, out var evo))
|
||||
devolvedAny = UpdateIfBetter(ref dest, evo);
|
||||
if (LA .TryEvolve(devolved, dest, pk, enc.LevelMax, devolved.LevelMin, enc.SkipChecks, out evo))
|
||||
devolvedAny = UpdateIfBetter(ref dest, evo);
|
||||
if (BDSP.TryEvolve(devolved, dest, pk, enc.LevelMax, devolved.LevelMin, enc.SkipChecks, out evo))
|
||||
devolvedAny = UpdateIfBetter(ref dest, evo);
|
||||
if (SV .TryEvolve(devolved, dest, pk, enc.LevelMax, devolved.LevelMin, enc.SkipChecks, out evo))
|
||||
devolvedAny = UpdateIfBetter(ref dest, evo);
|
||||
|
||||
if (devolvedAny)
|
||||
present++;
|
||||
else if (dest.Method == EvoCriteria.SentinelNotReached)
|
||||
break; // Don't continue for higher evolutions.
|
||||
|
||||
static bool UpdateIfBetter(ref EvoCriteria reference, in EvoCriteria evo)
|
||||
{
|
||||
if (evo.IsBetterEvolution(reference))
|
||||
reference = evo;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
history.Gen8 = SetHistory(result, PersonalTable.SWSH);
|
||||
history.Gen8a = SetHistory(result, PersonalTable.LA);
|
||||
history.Gen8b = SetHistory(result, PersonalTable.BDSP);
|
||||
history.Gen9 = SetHistory(result, PersonalTable.SV);
|
||||
|
||||
return present;
|
||||
}
|
||||
|
||||
private static int DevolveSingle(Span<EvoCriteria> result, PKM pk, in EvolutionOrigin enc)
|
||||
{
|
||||
int present = 1;
|
||||
var env = GetSingleEnv(pk);
|
||||
for (int i = 1; i < result.Length; i++)
|
||||
{
|
||||
ref var prev = ref result[i - 1];
|
||||
RevertMutatedForms(ref prev);
|
||||
if (!env.TryDevolve(prev, pk, prev.LevelMax, enc.LevelMin, enc.SkipChecks, out var evo))
|
||||
continue;
|
||||
|
||||
ref var reference = ref result[i];
|
||||
if (evo.IsBetterDevolution(reference))
|
||||
reference = evo;
|
||||
present++;
|
||||
}
|
||||
|
||||
return present;
|
||||
}
|
||||
|
||||
private static void RevertMutatedForms(ref EvoCriteria evo)
|
||||
{
|
||||
if (evo is { Species: (ushort)Species.Dialga, Form: not 0 })
|
||||
evo = evo with { Form = 0 }; // Normal
|
||||
else if (evo is { Species: (ushort)Species.Palkia, Form: not 0 })
|
||||
evo = evo with { Form = 0 }; // Normal
|
||||
else if (evo is { Species: (ushort)Species.Silvally, Form: not 0 })
|
||||
evo = evo with { Form = 0 }; // Normal
|
||||
}
|
||||
|
||||
private int EvolveSingle(Span<EvoCriteria> result, PKM pk, in EvolutionOrigin enc, EvolutionHistory history)
|
||||
{
|
||||
int present = 1;
|
||||
var env = GetSingleEnv(pk);
|
||||
for (int i = result.Length - 1; i >= 1; i--)
|
||||
{
|
||||
ref var dest = ref result[i - 1];
|
||||
var devolved = result[i];
|
||||
if (!env.TryEvolve(devolved, dest, pk, enc.LevelMax, devolved.LevelMin, enc.SkipChecks, out var evo))
|
||||
{
|
||||
if (dest.Method == EvoCriteria.SentinelNotReached)
|
||||
break; // Don't continue for higher evolutions.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evo.IsBetterEvolution(dest))
|
||||
dest = evo;
|
||||
present++;
|
||||
}
|
||||
|
||||
if (pk is PK8) history.Gen8 = SetHistory(result, PersonalTable.SWSH);
|
||||
else if (pk is PA8) history.Gen8a = SetHistory(result, PersonalTable.LA);
|
||||
else if (pk is PB8) history.Gen8b = SetHistory(result, PersonalTable.BDSP);
|
||||
else if (pk is PK9) history.Gen9 = SetHistory(result, PersonalTable.SV);
|
||||
|
||||
return present;
|
||||
}
|
||||
|
||||
private static IEvolutionEnvironment GetSingleEnv(PKM pk) => pk switch
|
||||
{
|
||||
PK8 => SWSH,
|
||||
PA8 => LA,
|
||||
PB8 => BDSP,
|
||||
PK9 => SV,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(pk), pk, null),
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class EvolutionEnvironment8 : IEvolutionEnvironment
|
||||
{
|
||||
private static readonly EvolutionTree Tree = EvolutionTree.Evolves8;
|
||||
|
||||
public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
return Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
|
||||
public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
var b = Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
return b && !IsEvolutionBanned(pk, head);
|
||||
}
|
||||
|
||||
// Gigantamax Pikachu, Meowth, and Eevee are prevented from evolving.
|
||||
private static bool IsEvolutionBanned(PKM pk, in ISpeciesForm head) => head.Species switch
|
||||
{
|
||||
(int)Species.Pikachu => pk is PK8 { CanGigantamax: true },
|
||||
(int)Species.Meowth => pk is PK8 { CanGigantamax: true },
|
||||
(int)Species.Eevee => pk is PK8 { CanGigantamax: true },
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class EvolutionEnvironment8a : IEvolutionEnvironment
|
||||
{
|
||||
private static readonly EvolutionTree Tree = EvolutionTree.Evolves8a;
|
||||
|
||||
public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
=> Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
|
||||
public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
=> Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
|
||||
public sealed class EvolutionEnvironment8b : IEvolutionEnvironment
|
||||
{
|
||||
private static readonly EvolutionTree Tree = EvolutionTree.Evolves8b;
|
||||
|
||||
public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
=> Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
|
||||
public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
=> Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
|
||||
public sealed class EvolutionEnvironment9 : IEvolutionEnvironment
|
||||
{
|
||||
private static readonly EvolutionTree Tree = EvolutionTree.Evolves9;
|
||||
|
||||
public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
=> Tree.Reverse.TryDevolve(head, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
|
||||
public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
=> Tree.Forward.TryEvolve(head, next, pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using static PKHeX.Core.EntityContext;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
@ -8,15 +7,10 @@ namespace PKHeX.Core;
|
|||
/// </summary>
|
||||
public static class EvolutionGroupUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IEvolutionGroup"/> for the <see cref="pk"/>.
|
||||
/// </summary>
|
||||
public static IEvolutionGroup GetCurrentGroup(PKM pk) => GetCurrentGroup(pk.Context);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IEvolutionGroup"/> for the <see cref="EntityContext"/>.
|
||||
/// </summary>
|
||||
public static IEvolutionGroup GetCurrentGroup(EntityContext context) => context switch
|
||||
public static IEvolutionGroup GetGroup(EntityContext context) => context switch
|
||||
{
|
||||
Gen1 => EvolutionGroup1.Instance,
|
||||
Gen2 => EvolutionGroup2.Instance,
|
||||
|
@ -25,13 +19,8 @@ public static class EvolutionGroupUtil
|
|||
Gen5 => EvolutionGroup5.Instance,
|
||||
Gen6 => EvolutionGroup6.Instance,
|
||||
Gen7 => EvolutionGroup7.Instance,
|
||||
Gen8 => EvolutionGroup8.Instance,
|
||||
Gen9 => EvolutionGroup9.Instance,
|
||||
|
||||
Gen7b => EvolutionGroup7b.Instance,
|
||||
Gen8a => EvolutionGroup8.Instance,
|
||||
Gen8b => EvolutionGroup8.Instance,
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(context), context, null),
|
||||
_ => EvolutionGroupHOME.Instance,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Details about the original encounter.
|
||||
/// </summary>
|
||||
/// <param name="Species">Species the encounter originated as</param>
|
||||
/// <param name="Version">Version the encounter originated on</param>
|
||||
/// <param name="Generation">Generation the encounter originated in</param>
|
||||
/// <param name="LevelMin">Minimum level the encounter originated at</param>
|
||||
/// <param name="LevelMax">Maximum level in final state</param>
|
||||
/// <param name="SkipChecks">Skip enforcement of legality for evolution criteria</param>
|
||||
public readonly record struct EvolutionOrigin(ushort Species, byte Version, byte Generation, byte LevelMin, byte LevelMax, bool SkipChecks = false);
|
172
PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionUtil.cs
Normal file
172
PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionUtil.cs
Normal file
|
@ -0,0 +1,172 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
internal static class EvolutionUtil
|
||||
{
|
||||
public static EvoCriteria[] SetHistory<T>(Span<EvoCriteria> result, T pt) where T : IPersonalTable
|
||||
{
|
||||
// Get the range of {result} that is present within pt.
|
||||
int start = 0;
|
||||
int count = 0;
|
||||
|
||||
// Find first index that exists in the game, and take while they do.
|
||||
foreach (ref readonly var evo in result)
|
||||
{
|
||||
if (evo.Method == EvoCriteria.SentinelNotReached)
|
||||
{
|
||||
// Unable to evolve to this stage, skip everything before it
|
||||
start += count + 1;
|
||||
count = 0;
|
||||
}
|
||||
else if (pt.IsPresentInGame(evo.Species, evo.Form))
|
||||
{
|
||||
// Found a valid entry, increment count.
|
||||
count++;
|
||||
}
|
||||
else if (count == 0)
|
||||
{
|
||||
// No valid entries found yet, increment start.
|
||||
start++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Found an invalid entry, stop.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return GetLocalEvolutionArray(result.Slice(start, count));
|
||||
}
|
||||
|
||||
private static EvoCriteria[] GetLocalEvolutionArray(Span<EvoCriteria> result)
|
||||
{
|
||||
if (result.Length == 0)
|
||||
return Array.Empty<EvoCriteria>();
|
||||
|
||||
var array = result.ToArray();
|
||||
var length = CleanEvolve(array);
|
||||
if (length != array.Length)
|
||||
Array.Resize(ref array, length);
|
||||
return array;
|
||||
}
|
||||
|
||||
public static void Discard<T>(Span<EvoCriteria> result, T pt) where T : IPersonalTable
|
||||
{
|
||||
// Iterate through the result, and if an entry is not present in the game, shift other entries down and zero out the last entry.
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
var evo = result[i];
|
||||
if (evo.Species == 0)
|
||||
break;
|
||||
if (pt.IsPresentInGame(evo.Species, evo.Form))
|
||||
continue;
|
||||
|
||||
ShiftDown(result[i..]);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ShiftDown(Span<EvoCriteria> result)
|
||||
{
|
||||
for (int i = 1; i < result.Length; i++)
|
||||
result[i - 1] = result[i];
|
||||
result[^1] = default; // zero out the last entry
|
||||
}
|
||||
|
||||
public static int IndexOf(Span<EvoCriteria> result, ushort species)
|
||||
{
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
if (result[i].Species == species)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static bool Contains(ReadOnlySpan<EvoCriteria> result, ushort species)
|
||||
{
|
||||
foreach (ref readonly var z in result)
|
||||
{
|
||||
if (z.Species == species)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Revises the <see cref="result"/> to account for a new maximum <see cref="level"/>.
|
||||
/// </summary>
|
||||
public static void UpdateCeiling(Span<EvoCriteria> result, int level)
|
||||
{
|
||||
foreach (ref var evo in result)
|
||||
{
|
||||
if (evo.Species == 0)
|
||||
break;
|
||||
evo = evo with { LevelMax = (byte)Math.Min(evo.LevelMax, level) };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Revises the <see cref="result"/> to account for a new minimum <see cref="level"/>.
|
||||
/// </summary>
|
||||
public static void UpdateFloor(Span<EvoCriteria> result, int level)
|
||||
{
|
||||
foreach (ref var evo in result)
|
||||
{
|
||||
if (evo.Species == 0)
|
||||
break;
|
||||
evo = evo with { LevelMin = (byte)Math.Max(evo.LevelMin, level) };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mutates the result to leave placeholder data for the species that the <see cref="encSpecies"/> evolves into.
|
||||
/// </summary>
|
||||
/// <param name="result">All evolutions for the species.</param>
|
||||
/// <param name="encSpecies">Species encountered as.</param>
|
||||
public static void ConditionBaseChainForward(Span<EvoCriteria> result, ushort encSpecies)
|
||||
{
|
||||
foreach (ref var evo in result)
|
||||
{
|
||||
if (evo.Species == encSpecies)
|
||||
break;
|
||||
evo = evo with { Method = EvoCriteria.SentinelNotReached };
|
||||
}
|
||||
}
|
||||
|
||||
public static void CleanDevolve(Span<EvoCriteria> result, byte levelMin)
|
||||
{
|
||||
// Rectify minimum levels.
|
||||
// trickle our two temp variables up the chain (essentially evolving from min).
|
||||
byte req = 0;
|
||||
EvolutionType method = EvolutionType.None;
|
||||
for (int i = result.Length - 1; i >= 0; i--)
|
||||
{
|
||||
ref var evo = ref result[i];
|
||||
var nextMin = evo.LevelMin; // to evolve
|
||||
var nextReq = evo.LevelUpRequired;
|
||||
var nextMethod = evo.Method;
|
||||
evo = evo with { LevelMin = Math.Min(evo.LevelMax, levelMin), LevelUpRequired = req, Method = method };
|
||||
levelMin = Math.Max(nextMin, levelMin);
|
||||
req = nextReq;
|
||||
method = nextMethod;
|
||||
}
|
||||
}
|
||||
|
||||
private static int CleanEvolve(Span<EvoCriteria> result)
|
||||
{
|
||||
// Rectify maximum levels.
|
||||
int i = 1;
|
||||
for (; i < result.Length; i++)
|
||||
{
|
||||
var next = result[i - 1];
|
||||
// Ignore LevelUp byte as it can learn moves prior to evolving.
|
||||
var newMax = next.LevelMax;
|
||||
ref var evo = ref result[i];
|
||||
if (evo.LevelMin > newMax - next.LevelUpRequired)
|
||||
break;
|
||||
evo = evo with { LevelMax = newMax };
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
|
@ -17,18 +17,45 @@ public interface IEvolutionGroup
|
|||
/// </summary>
|
||||
IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc);
|
||||
|
||||
bool Append(PKM pk, EvolutionHistory history, ref ReadOnlySpan<EvoCriteria> chain, EvolutionOrigin enc);
|
||||
/// <summary>
|
||||
/// Walks down the evolution chain to find previous evolutions.
|
||||
/// </summary>
|
||||
/// <param name="result">Array to store results in.</param>
|
||||
/// <param name="pk">PKM to check.</param>
|
||||
/// <param name="enc">Cached metadata about the PKM.</param>
|
||||
/// <returns>Number of results found.</returns>
|
||||
int Devolve(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc);
|
||||
|
||||
EvoCriteria[] GetInitialChain(PKM pk, EvolutionOrigin enc, ushort species, byte form);
|
||||
/// <summary>
|
||||
/// Walks up the evolution chain to find the evolution path.
|
||||
/// </summary>
|
||||
/// <param name="result">Array to store best results in.</param>
|
||||
/// <param name="pk">PKM to check.</param>
|
||||
/// <param name="enc">Cached metadata about the PKM.</param>
|
||||
/// <param name="history">History of evolutions to cache arrays for individual contexts.</param>
|
||||
/// <returns>Number of results found.</returns>
|
||||
int Evolve(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc, EvolutionHistory history);
|
||||
|
||||
/// <summary>
|
||||
/// Discards all entries that do not exist in the group.
|
||||
/// </summary>
|
||||
void DiscardForOrigin(Span<EvoCriteria> result, PKM pk);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Details about the original encounter.
|
||||
/// Provides information about how to evolve to the next evolution stage.
|
||||
/// </summary>
|
||||
/// <param name="Species">Species the encounter originated as</param>
|
||||
/// <param name="Version">Version the encounter originated on</param>
|
||||
/// <param name="Generation">Generation the encounter originated in</param>
|
||||
/// <param name="LevelMin">Minimum level the encounter originated at</param>
|
||||
/// <param name="LevelMax">Maximum level in final state</param>
|
||||
/// <param name="SkipChecks">Skip enforcement of legality for evolution criteria</param>
|
||||
public readonly record struct EvolutionOrigin(ushort Species, byte Version, byte Generation, byte LevelMin, byte LevelMax, bool SkipChecks = false);
|
||||
public interface IEvolutionEnvironment
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to devolve the provided <see cref="head"/> to the previous evolution.
|
||||
/// </summary>
|
||||
/// <returns>True if the de-evolution was successful and <see cref="result"/> should be used.</returns>
|
||||
bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to evolve the provided <see cref="head"/> to the next evolution.
|
||||
/// </summary>
|
||||
/// <returns>True if the evolution was successful and <see cref="result"/> should be used.</returns>
|
||||
bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result);
|
||||
}
|
||||
|
|
|
@ -37,28 +37,26 @@ public sealed class EvolutionHistory
|
|||
public bool HasVisitedPLA => Gen8a.Length != 0;
|
||||
public bool HasVisitedBDSP => Gen8b.Length != 0;
|
||||
|
||||
public ref EvoCriteria[] Get(EntityContext context)
|
||||
public ReadOnlySpan<EvoCriteria> Get(EntityContext context) => context switch
|
||||
{
|
||||
if (context == EntityContext.Gen1) return ref Gen1;
|
||||
if (context == EntityContext.Gen2) return ref Gen2;
|
||||
if (context == EntityContext.Gen3) return ref Gen3;
|
||||
if (context == EntityContext.Gen4) return ref Gen4;
|
||||
if (context == EntityContext.Gen5) return ref Gen5;
|
||||
if (context == EntityContext.Gen6) return ref Gen6;
|
||||
if (context == EntityContext.Gen7) return ref Gen7;
|
||||
if (context == EntityContext.Gen8) return ref Gen8;
|
||||
if (context == EntityContext.Gen9) return ref Gen9;
|
||||
EntityContext.Gen1 => Gen1,
|
||||
EntityContext.Gen2 => Gen2,
|
||||
EntityContext.Gen3 => Gen3,
|
||||
EntityContext.Gen4 => Gen4,
|
||||
EntityContext.Gen5 => Gen5,
|
||||
EntityContext.Gen6 => Gen6,
|
||||
EntityContext.Gen7 => Gen7,
|
||||
EntityContext.Gen8 => Gen8,
|
||||
EntityContext.Gen9 => Gen9,
|
||||
|
||||
if (context == EntityContext.Gen7b) return ref Gen7b;
|
||||
if (context == EntityContext.Gen8a) return ref Gen8a;
|
||||
if (context == EntityContext.Gen8b) return ref Gen8b;
|
||||
EntityContext.Gen7b => Gen7b,
|
||||
EntityContext.Gen8a => Gen8a,
|
||||
EntityContext.Gen8b => Gen8b,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(context), context, null),
|
||||
};
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(context));
|
||||
}
|
||||
|
||||
public bool HasVisited(EntityContext context, ushort species)
|
||||
public static bool HasVisited(ReadOnlySpan<EvoCriteria> evos, ushort species)
|
||||
{
|
||||
var evos = Get(context);
|
||||
foreach (var evo in evos)
|
||||
{
|
||||
if (evo.Species == species)
|
||||
|
@ -66,10 +64,4 @@ public sealed class EvolutionHistory
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Set(EntityContext context, EvoCriteria[] chain)
|
||||
{
|
||||
ref var arr = ref Get(context);
|
||||
arr = chain;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
internal static class EvolutionLegality
|
||||
{
|
||||
internal static readonly HashSet<ushort> FutureEvolutionsGen1 = new()
|
||||
{
|
||||
(int)Species.Crobat,
|
||||
(int)Species.Bellossom,
|
||||
(int)Species.Politoed,
|
||||
(int)Species.Espeon,
|
||||
(int)Species.Umbreon,
|
||||
(int)Species.Slowking,
|
||||
(int)Species.Steelix,
|
||||
(int)Species.Scizor,
|
||||
(int)Species.Kingdra,
|
||||
(int)Species.Porygon2,
|
||||
(int)Species.Blissey,
|
||||
|
||||
(int)Species.Magnezone,
|
||||
(int)Species.Lickilicky,
|
||||
(int)Species.Rhyperior,
|
||||
(int)Species.Tangrowth,
|
||||
(int)Species.Electivire,
|
||||
(int)Species.Magmortar,
|
||||
(int)Species.Leafeon,
|
||||
(int)Species.Glaceon,
|
||||
(int)Species.PorygonZ,
|
||||
|
||||
(int)Species.Sylveon,
|
||||
|
||||
(int)Species.Kleavor,
|
||||
};
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Evolution Reversal logic
|
||||
/// </summary>
|
||||
public static class EvolutionReversal
|
||||
{
|
||||
/// <summary>
|
||||
/// Reverses from current state to see what evolutions the <see cref="pk"/> may have existed as.
|
||||
/// </summary>
|
||||
/// <param name="lineage">Evolution Method lineage reversal object</param>
|
||||
/// <param name="species">Species to devolve from</param>
|
||||
/// <param name="form">Form to devolve from</param>
|
||||
/// <param name="pk">Entity reference to sanity check evolutions with</param>
|
||||
/// <param name="levelMin">Minimum level the entity may exist as</param>
|
||||
/// <param name="levelMax">Maximum the entity may exist as</param>
|
||||
/// <param name="maxSpeciesID">Maximum species ID that may exist</param>
|
||||
/// <param name="skipChecks">Option to bypass some evolution criteria</param>
|
||||
/// <param name="stopSpecies">Species ID that should be the last node, if at all.</param>
|
||||
/// <returns>Reversed evolution lineage, with the lowest index being the current species-form.</returns>
|
||||
public static EvoCriteria[] Reverse(this IEvolutionLookup lineage, ushort species, byte form, PKM pk, byte levelMin, byte levelMax, int maxSpeciesID, bool skipChecks, int stopSpecies)
|
||||
{
|
||||
// Sometimes we have to sanitize the inputs.
|
||||
switch (species)
|
||||
{
|
||||
case (int)Species.Silvally:
|
||||
form = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Store our results -- trim at the end when we place it on the heap.
|
||||
const int maxEvolutions = 3;
|
||||
Span<EvoCriteria> evos = stackalloc EvoCriteria[maxEvolutions];
|
||||
|
||||
var lvl = levelMax; // highest level, any level-up method will decrease.
|
||||
evos[0] = new EvoCriteria { Species = species, Form = form, LevelMax = lvl }; // current species-form
|
||||
|
||||
// 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; // count in the 'evos' span
|
||||
while (species != stopSpecies)
|
||||
{
|
||||
var key = EvolutionTree.GetLookupKey(species, form);
|
||||
var node = lineage[key];
|
||||
|
||||
bool oneValid = false;
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
ref var link = ref i == 0 ? ref node.First : ref node.Second;
|
||||
if (link.IsEmpty)
|
||||
break;
|
||||
|
||||
if (link.IsEvolutionBanned(pk) && !skipChecks)
|
||||
continue;
|
||||
|
||||
var evo = link.Method;
|
||||
if (!evo.Valid(pk, lvl, skipChecks))
|
||||
continue;
|
||||
|
||||
if (evo.RequiresLevelUp && levelMin >= lvl)
|
||||
break; // impossible evolution
|
||||
|
||||
UpdateMinValues(evos[..ctr], evo, levelMin);
|
||||
|
||||
species = link.Species;
|
||||
form = link.Form;
|
||||
evos[ctr++] = evo.GetEvoCriteria(species, form, lvl);
|
||||
if (evo.RequiresLevelUp)
|
||||
lvl--;
|
||||
|
||||
oneValid = true;
|
||||
break;
|
||||
}
|
||||
if (!oneValid)
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove future gen pre-evolutions; no Munchlax from a Gen3 Snorlax, no Pichu from a Gen1-only Raichu, etc
|
||||
ref var last = ref evos[ctr - 1];
|
||||
if (last.Species > maxSpeciesID)
|
||||
{
|
||||
for (int i = 0; i < ctr; i++)
|
||||
{
|
||||
if (evos[i].Species > maxSpeciesID)
|
||||
continue;
|
||||
ctr--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Last species is the wild/hatched species, the minimum level is because it has not evolved from previous species
|
||||
var result = evos[..ctr];
|
||||
last = ref result[^1];
|
||||
last = last with { LevelMin = levelMin, LevelUpRequired = 0 };
|
||||
|
||||
// Rectify minimum levels
|
||||
RectifyMinimumLevels(result);
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private static void RectifyMinimumLevels(Span<EvoCriteria> result)
|
||||
{
|
||||
for (int i = result.Length - 2; i >= 0; i--)
|
||||
{
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateMinValues(Span<EvoCriteria> evos, EvolutionMethod evo, byte minLevel)
|
||||
{
|
||||
ref var last = ref evos[^1];
|
||||
if (!evo.RequiresLevelUp)
|
||||
{
|
||||
// Evolutions like elemental stones, trade, etc
|
||||
last = last with { LevelMin = minLevel };
|
||||
return;
|
||||
}
|
||||
if (evo.Level == 0)
|
||||
{
|
||||
// Friendship based Level Up Evolutions, Pichu -> Pikachu, Eevee -> Umbreon, etc
|
||||
last = last with { LevelMin = (byte)(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
|
||||
if (evos.Length > 1)
|
||||
{
|
||||
ref var first = ref evos[0];
|
||||
if (!first.RequiresLvlUp)
|
||||
first = first with { LevelMin = (byte)(minLevel + 1) };
|
||||
}
|
||||
}
|
||||
else // level up evolutions
|
||||
{
|
||||
last = last with { LevelMin = evo.Level };
|
||||
|
||||
if (evos.Length > 1)
|
||||
{
|
||||
ref var first = ref evos[0];
|
||||
if (first.RequiresLvlUp)
|
||||
{
|
||||
// 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
|
||||
{
|
||||
// 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 = last with { LevelUpRequired = evo.RequiresLevelUp ? (byte)1 : (byte)0 };
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
@ -19,28 +18,24 @@ public static class EvolutionSet1
|
|||
: new EvolutionMethod((EvolutionType)method, species, Argument: arg);
|
||||
}
|
||||
|
||||
public static IReadOnlyList<EvolutionMethod[]> GetArray(ReadOnlySpan<byte> data, int maxSpecies)
|
||||
public static EvolutionMethod[][] GetArray(ReadOnlySpan<byte> data, int maxSpecies)
|
||||
{
|
||||
var evos = new EvolutionMethod[maxSpecies + 1][];
|
||||
int ofs = 0;
|
||||
var result = new EvolutionMethod[maxSpecies + 1][];
|
||||
for (int i = 0, offset = 0; i < result.Length; i++)
|
||||
result[i] = GetEntry(data, ref offset);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static EvolutionMethod[] GetEntry(ReadOnlySpan<byte> data, ref int offset)
|
||||
{
|
||||
var count = data[offset++];
|
||||
if (count == 0)
|
||||
return Array.Empty<EvolutionMethod>();
|
||||
|
||||
const int bpe = 3;
|
||||
for (int i = 0; i < evos.Length; i++)
|
||||
{
|
||||
int count = data[ofs];
|
||||
ofs++;
|
||||
if (count == 0)
|
||||
{
|
||||
evos[i] = Array.Empty<EvolutionMethod>();
|
||||
continue;
|
||||
}
|
||||
var m = new EvolutionMethod[count];
|
||||
for (int j = 0; j < m.Length; j++)
|
||||
{
|
||||
m[j] = GetMethod(data.Slice(ofs, bpe));
|
||||
ofs += bpe;
|
||||
}
|
||||
evos[i] = m;
|
||||
}
|
||||
return evos;
|
||||
var result = new EvolutionMethod[count];
|
||||
for (int i = 0; i < result.Length; i++, offset += bpe)
|
||||
result[i] = GetMethod(data.Slice(offset, bpe));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
@ -45,37 +44,43 @@ public static class EvolutionSet3
|
|||
}
|
||||
}
|
||||
|
||||
public static IReadOnlyList<EvolutionMethod[]> GetArray(ReadOnlySpan<byte> data)
|
||||
public static EvolutionMethod[][] GetArray(ReadOnlySpan<byte> data)
|
||||
{
|
||||
var evos = new EvolutionMethod[Legal.MaxSpeciesID_3 + 1][];
|
||||
evos[0] = Array.Empty<EvolutionMethod>();
|
||||
var result = new EvolutionMethod[Legal.MaxSpeciesID_3 + 1][];
|
||||
result[0] = Array.Empty<EvolutionMethod>();
|
||||
for (ushort i = 1; i <= Legal.MaxSpeciesIndex_3; i++)
|
||||
{
|
||||
int g4species = SpeciesConverter.GetNational3(i);
|
||||
if (g4species == 0)
|
||||
continue;
|
||||
|
||||
const int maxCount = 5;
|
||||
const int size = 8;
|
||||
|
||||
int offset = i * (maxCount * size);
|
||||
int count = 0;
|
||||
for (; count < maxCount; count++)
|
||||
{
|
||||
if (data[offset + (count * size)] == 0)
|
||||
break;
|
||||
}
|
||||
if (count == 0)
|
||||
{
|
||||
evos[g4species] = Array.Empty<EvolutionMethod>();
|
||||
continue;
|
||||
}
|
||||
|
||||
var set = new EvolutionMethod[count];
|
||||
for (int j = 0; j < set.Length; j++)
|
||||
set[j] = GetMethod(data.Slice(offset + (j * size), size));
|
||||
evos[g4species] = set;
|
||||
if (g4species != 0)
|
||||
result[g4species] = GetEntry(data, i);
|
||||
}
|
||||
return evos;
|
||||
return result;
|
||||
}
|
||||
|
||||
private const int maxCount = 5;
|
||||
private const int size = 8;
|
||||
|
||||
private static EvolutionMethod[] GetEntry(ReadOnlySpan<byte> data, ushort index)
|
||||
{
|
||||
var span = data.Slice(index * (maxCount * size), maxCount * size);
|
||||
int count = ScanCountEvolutions(span);
|
||||
|
||||
if (count == 0)
|
||||
return Array.Empty<EvolutionMethod>();
|
||||
|
||||
var result = new EvolutionMethod[count];
|
||||
for (int i = 0, offset = 0; i < result.Length; i++, offset += size)
|
||||
result[i] = GetMethod(span.Slice(offset, size));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int ScanCountEvolutions(ReadOnlySpan<byte> span)
|
||||
{
|
||||
for (int count = 0; count < maxCount; count++)
|
||||
{
|
||||
if (span[count * size] == 0)
|
||||
return count;
|
||||
}
|
||||
return maxCount;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
@ -29,35 +28,39 @@ public static class EvolutionSet4
|
|||
return new EvolutionMethod(type, species, Argument: arg, Level: (byte)lvl, LevelUp: lvlup);
|
||||
}
|
||||
|
||||
public static IReadOnlyList<EvolutionMethod[]> GetArray(ReadOnlySpan<byte> data)
|
||||
private const int bpe = 6; // bytes per evolution entry
|
||||
private const int entries = 7; // amount of entries per species
|
||||
private const int size = (entries * bpe) + 2; // bytes per species entry, + 2 alignment bytes
|
||||
|
||||
public static EvolutionMethod[][] GetArray(ReadOnlySpan<byte> data)
|
||||
{
|
||||
const int bpe = 6; // bytes per evolution entry
|
||||
const int entries = 7; // amount of entries per species
|
||||
const int size = (entries * bpe) + 2; // bytes per species entry, + 2 alignment bytes
|
||||
var result = new EvolutionMethod[data.Length / size][];
|
||||
for (int i = 0, offset = 0; i < result.Length; i++, offset += size)
|
||||
result[i] = GetEntry(data.Slice(offset, size));
|
||||
return result;
|
||||
}
|
||||
|
||||
var evos = new EvolutionMethod[data.Length / size][];
|
||||
for (int i = 0; i < evos.Length; i++)
|
||||
private static EvolutionMethod[] GetEntry(ReadOnlySpan<byte> data)
|
||||
{
|
||||
int count = ScanCountEvolutions(data);
|
||||
if (count == 0)
|
||||
return Array.Empty<EvolutionMethod>();
|
||||
|
||||
var result = new EvolutionMethod[count];
|
||||
for (int i = 0, offset = 0; i < result.Length; i++, offset += bpe)
|
||||
result[i] = GetMethod(data.Slice(offset, bpe));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int ScanCountEvolutions(ReadOnlySpan<byte> data)
|
||||
{
|
||||
for (int count = 0; count < entries; count++)
|
||||
{
|
||||
int offset = i * size;
|
||||
int count = 0;
|
||||
for (; count < entries; count++)
|
||||
{
|
||||
var methodOffset = offset + (count * bpe);
|
||||
var method = data[methodOffset];
|
||||
if (method == 0)
|
||||
break;
|
||||
}
|
||||
if (count == 0)
|
||||
{
|
||||
evos[i] = Array.Empty<EvolutionMethod>();
|
||||
continue;
|
||||
}
|
||||
|
||||
var set = new EvolutionMethod[count];
|
||||
for (int j = 0; j < set.Length; j++)
|
||||
set[j] = GetMethod(data.Slice(offset + (j * bpe), bpe));
|
||||
evos[i] = set;
|
||||
var methodOffset = count * bpe;
|
||||
var method = data[methodOffset];
|
||||
if (method == 0)
|
||||
return count;
|
||||
}
|
||||
return evos;
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
@ -28,26 +27,24 @@ public static class EvolutionSet5
|
|||
private const int entries = 7; // amount of entries per species
|
||||
private const int size = entries * bpe; // bytes per species entry
|
||||
|
||||
public static IReadOnlyList<EvolutionMethod[]> GetArray(ReadOnlySpan<byte> data)
|
||||
public static EvolutionMethod[][] GetArray(ReadOnlySpan<byte> data)
|
||||
{
|
||||
var evos = new EvolutionMethod[data.Length / size][];
|
||||
for (int i = 0; i < evos.Length; i++)
|
||||
{
|
||||
int offset = i * size;
|
||||
var rawEntries = data.Slice(offset, size);
|
||||
var count = ScanCountEvolutions(rawEntries);
|
||||
if (count == 0)
|
||||
{
|
||||
evos[i] = Array.Empty<EvolutionMethod>();
|
||||
continue;
|
||||
}
|
||||
var result = new EvolutionMethod[data.Length / size][];
|
||||
for (int i = 0, offset = 0; i < result.Length; i++, offset += size)
|
||||
result[i] = GetEntry(data.Slice(offset, size));
|
||||
return result;
|
||||
}
|
||||
|
||||
var set = new EvolutionMethod[count];
|
||||
for (int j = 0; j < set.Length; j++)
|
||||
set[j] = GetMethod(rawEntries.Slice(j * bpe, bpe));
|
||||
evos[i] = set;
|
||||
}
|
||||
return evos;
|
||||
private static EvolutionMethod[] GetEntry(ReadOnlySpan<byte> data)
|
||||
{
|
||||
var count = ScanCountEvolutions(data);
|
||||
if (count == 0)
|
||||
return Array.Empty<EvolutionMethod>();
|
||||
|
||||
var result = new EvolutionMethod[count];
|
||||
for (int i = 0, offset = 0; i < result.Length; i++, offset += bpe)
|
||||
result[i] = GetMethod(data.Slice(offset, bpe));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int ScanCountEvolutions(ReadOnlySpan<byte> data)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
@ -13,15 +12,20 @@ public static class EvolutionSet6
|
|||
internal static bool IsMethodWithArg(byte method) => (0b100000011111110000000101000000 & (1 << method)) != 0;
|
||||
private const int SIZE = 6;
|
||||
|
||||
private static EvolutionMethod[] GetMethods(ReadOnlySpan<byte> data)
|
||||
public static EvolutionMethod[][] GetArray(BinLinkerAccessor data)
|
||||
{
|
||||
var evos = new EvolutionMethod[data.Length / SIZE];
|
||||
for (int i = 0; i < data.Length; i += SIZE)
|
||||
{
|
||||
var entry = data.Slice(i, SIZE);
|
||||
evos[i/SIZE] = GetMethod(entry);
|
||||
}
|
||||
return evos;
|
||||
var result = new EvolutionMethod[data.Length][];
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
result[i] = GetEntry(data[i]);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static EvolutionMethod[] GetEntry(ReadOnlySpan<byte> data)
|
||||
{
|
||||
var result = new EvolutionMethod[data.Length / SIZE];
|
||||
for (int i = 0, offset = 0; i < result.Length; i++, offset += SIZE)
|
||||
result[i] = GetMethod(data.Slice(offset, SIZE));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static EvolutionMethod GetMethod(ReadOnlySpan<byte> entry)
|
||||
|
@ -36,12 +40,4 @@ public static class EvolutionSet6
|
|||
var lvlup = type.IsLevelUpRequired() ? (byte)1 : (byte)0;
|
||||
return new EvolutionMethod(type, species, Argument: arg, Level: (byte)lvl, LevelUp: lvlup);
|
||||
}
|
||||
|
||||
public static IReadOnlyList<EvolutionMethod[]> GetArray(BinLinkerAccessor data)
|
||||
{
|
||||
var evos = new EvolutionMethod[data.Length][];
|
||||
for (int i = 0; i < evos.Length; i++)
|
||||
evos[i] = GetMethods(data[i]);
|
||||
return evos;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
@ -11,40 +10,33 @@ public static class EvolutionSet7
|
|||
{
|
||||
private const int SIZE = 8;
|
||||
|
||||
private static EvolutionMethod[] GetMethods(ReadOnlySpan<byte> data, bool LevelUpBypass)
|
||||
public static EvolutionMethod[][] GetArray(BinLinkerAccessor data, byte levelUp = 1)
|
||||
{
|
||||
var result = new EvolutionMethod[data.Length][];
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
result[i] = GetEntry(data[i], levelUp);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static EvolutionMethod[] GetEntry(ReadOnlySpan<byte> data, byte levelUp)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
return Array.Empty<EvolutionMethod>();
|
||||
|
||||
var result = new EvolutionMethod[data.Length / SIZE];
|
||||
int i = 0, offset = 0;
|
||||
while (true)
|
||||
{
|
||||
var entry = data.Slice(offset, SIZE);
|
||||
result[i] = ReadEvolution(entry, LevelUpBypass);
|
||||
offset += SIZE;
|
||||
if (offset >= data.Length)
|
||||
return result;
|
||||
i++;
|
||||
}
|
||||
for (int i = 0, offset = 0; i < result.Length; i++, offset += SIZE)
|
||||
result[i] = GetMethod(data.Slice(offset, SIZE), levelUp);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static EvolutionMethod ReadEvolution(ReadOnlySpan<byte> entry, bool levelUpBypass)
|
||||
private static EvolutionMethod GetMethod(ReadOnlySpan<byte> entry, byte levelUp)
|
||||
{
|
||||
var type = (EvolutionType)entry[0];
|
||||
var arg = ReadUInt16LittleEndian(entry[2..]);
|
||||
var species = ReadUInt16LittleEndian(entry[4..]);
|
||||
var form = entry[6];
|
||||
var level = entry[7];
|
||||
var lvlup = !levelUpBypass && type.IsLevelUpRequired() ? (byte)1 : (byte)0;
|
||||
var lvlup = type.IsLevelUpRequired() ? levelUp : (byte)0;
|
||||
return new EvolutionMethod(type, species, form, arg, level, lvlup);
|
||||
}
|
||||
|
||||
public static IReadOnlyList<EvolutionMethod[]> GetArray(BinLinkerAccessor data, bool LevelUpBypass)
|
||||
{
|
||||
var evos = new EvolutionMethod[data.Length][];
|
||||
for (int i = 0; i < evos.Length; i++)
|
||||
evos[i] = GetMethods(data[i], LevelUpBypass);
|
||||
return evos;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static PKHeX.Core.GameVersion;
|
||||
using static PKHeX.Core.Legal;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
@ -11,30 +8,38 @@ namespace PKHeX.Core;
|
|||
/// <remarks>
|
||||
/// Used to determine if a <see cref="PKM.Species"/> can evolve from prior steps in its evolution branch.
|
||||
/// </remarks>
|
||||
public sealed class EvolutionTree
|
||||
public sealed class EvolutionTree : EvolutionNetwork
|
||||
{
|
||||
public static readonly EvolutionTree Evolves1 = new(GetResource("rby"), Gen1, PersonalTable.Y, MaxSpeciesID_1);
|
||||
public static readonly EvolutionTree Evolves2 = new(GetResource("gsc"), Gen2, PersonalTable.C, MaxSpeciesID_2);
|
||||
public static readonly EvolutionTree Evolves3 = new(GetResource("g3"), Gen3, PersonalTable.RS, MaxSpeciesID_3);
|
||||
public static readonly EvolutionTree Evolves4 = new(GetResource("g4"), Gen4, PersonalTable.DP, MaxSpeciesID_4);
|
||||
public static readonly EvolutionTree Evolves5 = new(GetResource("g5"), Gen5, PersonalTable.BW, MaxSpeciesID_5);
|
||||
public static readonly EvolutionTree Evolves6 = new(GetReader("ao"), Gen6, PersonalTable.AO, MaxSpeciesID_6);
|
||||
public static readonly EvolutionTree Evolves7 = new(GetReader("uu"), Gen7, PersonalTable.USUM, MaxSpeciesID_7_USUM);
|
||||
public static readonly EvolutionTree Evolves7b = new(GetReader("gg"), Gen7, PersonalTable.GG, MaxSpeciesID_7b);
|
||||
public static readonly EvolutionTree Evolves8 = new(GetReader("ss"), Gen8, PersonalTable.SWSH, MaxSpeciesID_8);
|
||||
public static readonly EvolutionTree Evolves8a = new(GetReader("la"), PLA, PersonalTable.LA, MaxSpeciesID_8a);
|
||||
public static readonly EvolutionTree Evolves8b = new(GetReader("bs"), BDSP, PersonalTable.BDSP, MaxSpeciesID_8b);
|
||||
public static readonly EvolutionTree Evolves9 = new(GetReader("sv"), Gen9, PersonalTable.SV, MaxSpeciesID_9);
|
||||
public const int MaxEvolutions = 3;
|
||||
public static readonly EvolutionTree Evolves1 = GetViaSpecies (PersonalTable.Y, EvolutionSet1.GetArray(GetResource("rby"), 151));
|
||||
public static readonly EvolutionTree Evolves2 = GetViaSpecies (PersonalTable.C, EvolutionSet1.GetArray(GetResource("gsc"), 251));
|
||||
public static readonly EvolutionTree Evolves3 = GetViaSpecies (PersonalTable.RS, EvolutionSet3.GetArray(GetResource("g3")));
|
||||
public static readonly EvolutionTree Evolves4 = GetViaSpecies (PersonalTable.DP, EvolutionSet4.GetArray(GetResource("g4")));
|
||||
public static readonly EvolutionTree Evolves5 = GetViaSpecies (PersonalTable.BW, EvolutionSet5.GetArray(GetResource("g5")));
|
||||
public static readonly EvolutionTree Evolves6 = GetViaSpecies (PersonalTable.AO, EvolutionSet6.GetArray(GetReader("ao")));
|
||||
public static readonly EvolutionTree Evolves7 = GetViaPersonal(PersonalTable.USUM, EvolutionSet7.GetArray(GetReader("uu")));
|
||||
public static readonly EvolutionTree Evolves7b = GetViaPersonal(PersonalTable.GG, EvolutionSet7.GetArray(GetReader("gg")));
|
||||
public static readonly EvolutionTree Evolves8 = GetViaPersonal(PersonalTable.SWSH, EvolutionSet7.GetArray(GetReader("ss")));
|
||||
public static readonly EvolutionTree Evolves8a = GetViaPersonal(PersonalTable.LA, EvolutionSet7.GetArray(GetReader("la"), 0));
|
||||
public static readonly EvolutionTree Evolves8b = GetViaPersonal(PersonalTable.BDSP, EvolutionSet7.GetArray(GetReader("bs")));
|
||||
public static readonly EvolutionTree Evolves9 = GetViaPersonal(PersonalTable.SV, EvolutionSet7.GetArray(GetReader("sv")));
|
||||
|
||||
private static ReadOnlySpan<byte> GetResource(string resource) => Util.GetBinaryResource($"evos_{resource}.pkl");
|
||||
private static BinLinkerAccessor GetReader(string resource) => BinLinkerAccessor.Get(GetResource(resource), resource);
|
||||
private EvolutionTree(IEvolutionForward forward, IEvolutionReverse reverse) : base(forward, reverse) { }
|
||||
|
||||
static EvolutionTree()
|
||||
private static EvolutionTree GetViaSpecies(IPersonalTable t, EvolutionMethod[][] entries)
|
||||
{
|
||||
// Add in banned evolution data!
|
||||
Evolves7.FixEvoTreeSM();
|
||||
Evolves8.FixEvoTreeSS();
|
||||
Evolves8b.FixEvoTreeBS();
|
||||
var forward = new EvolutionForwardSpecies(entries);
|
||||
var reverse = new EvolutionReverseSpecies(entries, t);
|
||||
return new EvolutionTree(forward, reverse);
|
||||
}
|
||||
|
||||
private static EvolutionTree GetViaPersonal(IPersonalTable t, EvolutionMethod[][] entries)
|
||||
{
|
||||
var forward = new EvolutionForwardPersonal(entries, t);
|
||||
var reverse = new EvolutionReversePersonal(entries, t);
|
||||
return new EvolutionTree(forward, reverse);
|
||||
}
|
||||
|
||||
public static EvolutionTree GetEvolutionTree(EntityContext context) => context switch
|
||||
|
@ -53,297 +58,4 @@ public sealed class EvolutionTree
|
|||
EntityContext.Gen8b => Evolves8b,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(context), context, null),
|
||||
};
|
||||
|
||||
private readonly IReadOnlyList<EvolutionMethod[]> Entries;
|
||||
private readonly IPersonalTable Personal;
|
||||
private readonly int MaxSpeciesTree;
|
||||
private readonly EvolutionReverseLookup Lineage;
|
||||
private bool FormSeparateEvoData => MaxSpeciesTree > MaxSpeciesID_6;
|
||||
|
||||
internal static int GetLookupKey(ushort species, byte form) => species | (form << 11);
|
||||
|
||||
#region Constructor
|
||||
|
||||
private EvolutionTree(ReadOnlySpan<byte> data, GameVersion game, IPersonalTable personal, int maxSpeciesTree)
|
||||
{
|
||||
Personal = personal;
|
||||
MaxSpeciesTree = maxSpeciesTree;
|
||||
Entries = GetEntries(data, game);
|
||||
var connections = CreateTreeOld();
|
||||
Lineage = new(connections, maxSpeciesTree);
|
||||
}
|
||||
|
||||
private EvolutionTree(BinLinkerAccessor data, GameVersion game, IPersonalTable personal, int maxSpeciesTree)
|
||||
{
|
||||
Personal = personal;
|
||||
MaxSpeciesTree = maxSpeciesTree;
|
||||
Entries = GetEntries(data, game);
|
||||
|
||||
// Starting in Generation 7, forms have separate evolution data.
|
||||
var oldStyle = !FormSeparateEvoData;
|
||||
var connections = oldStyle ? CreateTreeOld() : CreateTree();
|
||||
Lineage = new(connections, maxSpeciesTree);
|
||||
}
|
||||
|
||||
private IEnumerable<(int Key, EvolutionLink Value)> CreateTreeOld()
|
||||
{
|
||||
for (ushort sSpecies = 1; sSpecies <= MaxSpeciesTree; sSpecies++)
|
||||
{
|
||||
var fc = Personal[sSpecies].FormCount;
|
||||
for (byte sForm = 0; sForm < fc; sForm++)
|
||||
{
|
||||
var index = sSpecies;
|
||||
var evos = Entries[index];
|
||||
foreach (var evo in evos)
|
||||
{
|
||||
var dSpecies = evo.Species;
|
||||
if (dSpecies == 0)
|
||||
continue;
|
||||
|
||||
var dForm = sSpecies == (int)Species.Espurr && evo.Method == EvolutionType.LevelUpFormFemale1 ? (byte)1 : sForm;
|
||||
var key = GetLookupKey(dSpecies, dForm);
|
||||
|
||||
var link = new EvolutionLink(sSpecies, sForm, evo);
|
||||
yield return (key, link);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<(int Key, EvolutionLink Value)> CreateTree()
|
||||
{
|
||||
for (ushort sSpecies = 1; sSpecies <= MaxSpeciesTree; sSpecies++)
|
||||
{
|
||||
var fc = Personal[sSpecies].FormCount;
|
||||
for (byte sForm = 0; sForm < fc; sForm++)
|
||||
{
|
||||
var index = Personal.GetFormIndex(sSpecies, sForm);
|
||||
var evos = Entries[index];
|
||||
foreach (var evo in evos)
|
||||
{
|
||||
var dSpecies = evo.Species;
|
||||
if (dSpecies == 0)
|
||||
break;
|
||||
|
||||
var dForm = evo.GetDestinationForm(sForm);
|
||||
var key = GetLookupKey(dSpecies, dForm);
|
||||
|
||||
var link = new EvolutionLink(sSpecies, sForm, evo);
|
||||
yield return (key, link);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyList<EvolutionMethod[]> GetEntries(ReadOnlySpan<byte> data, GameVersion game) => game switch
|
||||
{
|
||||
Gen1 => EvolutionSet1.GetArray(data, 151),
|
||||
Gen2 => EvolutionSet1.GetArray(data, 251),
|
||||
Gen3 => EvolutionSet3.GetArray(data),
|
||||
Gen4 => EvolutionSet4.GetArray(data),
|
||||
Gen5 => EvolutionSet5.GetArray(data),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(game)),
|
||||
};
|
||||
|
||||
private static IReadOnlyList<EvolutionMethod[]> GetEntries(BinLinkerAccessor data, GameVersion game) => game switch
|
||||
{
|
||||
Gen6 => EvolutionSet6.GetArray(data),
|
||||
Gen7 or Gen8 or BDSP or Gen9 => EvolutionSet7.GetArray(data, false),
|
||||
PLA => EvolutionSet7.GetArray(data, true),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(game)),
|
||||
};
|
||||
|
||||
private void FixEvoTreeSM()
|
||||
{
|
||||
// Sun/Moon lack Ultra's Kantonian evolution methods.
|
||||
static bool BanOnlySM(PKM pk) => pk is { IsUntraded: true, SM: true };
|
||||
BanEvo((int)Species.Raichu, 0, BanOnlySM);
|
||||
BanEvo((int)Species.Marowak, 0, BanOnlySM);
|
||||
BanEvo((int)Species.Exeggutor, 0, BanOnlySM);
|
||||
}
|
||||
|
||||
private void FixEvoTreeSS()
|
||||
{
|
||||
// Gigantamax Pikachu, Meowth-0, and Eevee are prevented from evolving.
|
||||
// Raichu cannot be evolved to the Alolan variant at this time.
|
||||
static bool BanGmax(PKM pk) => pk is IGigantamax { CanGigantamax: true };
|
||||
BanEvo((int)Species.Raichu, 0, BanGmax);
|
||||
BanEvo((int)Species.Raichu, 1, pk => (pk is IGigantamax {CanGigantamax: true}) || pk.Version is (int)GO or >= (int)GP);
|
||||
BanEvo((int)Species.Persian, 0, BanGmax);
|
||||
BanEvo((int)Species.Persian, 1, BanGmax);
|
||||
BanEvo((int)Species.Perrserker, 0, BanGmax);
|
||||
|
||||
BanEvo((int)Species.Exeggutor, 1, pk => pk.Version is (int)GO or >= (int)GP);
|
||||
BanEvo((int)Species.Marowak, 1, pk => pk.Version is (int)GO or >= (int)GP);
|
||||
BanEvo((int)Species.Weezing, 0, pk => pk.Version >= (int)SW);
|
||||
BanEvo((int)Species.MrMime, 0, pk => pk.Version >= (int)SW);
|
||||
|
||||
foreach (var s in GetEvolutions((int)Species.Eevee, 0)) // Eeveelutions
|
||||
BanEvo(s.Species, s.Form, BanGmax);
|
||||
}
|
||||
|
||||
private void FixEvoTreeBS()
|
||||
{
|
||||
BanEvo((int)Species.Glaceon, 0, pk => pk.CurrentLevel == pk.Met_Level); // Ice Stone is unreleased, requires Route 217 Ice Rock Level Up instead
|
||||
BanEvo((int)Species.Milotic, 0, pk => pk is IContestStatsReadOnly { CNT_Beauty: < 170 } || pk.CurrentLevel == pk.Met_Level); // Prism Scale is unreleased, requires 170 Beauty Level Up instead
|
||||
}
|
||||
|
||||
private void BanEvo(ushort species, byte form, Func<PKM, bool> func)
|
||||
{
|
||||
var key = GetLookupKey(species, form);
|
||||
ref var node = ref Lineage[key];
|
||||
node.Ban(func);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of evolutions for the input <see cref="PKM"/> by checking each evolution in the chain.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon data to check with.</param>
|
||||
/// <param name="levelMax">Maximum level to permit before the chain breaks.</param>
|
||||
/// <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="levelMin">Minimum level to permit before the chain breaks.</param>
|
||||
/// <param name="stopSpecies">Final species to stop at, if known</param>
|
||||
public EvoCriteria[] GetValidPreEvolutions(PKM pk, byte levelMax, int maxSpeciesOrigin = -1, bool skipChecks = false, byte levelMin = 1, int stopSpecies = 0)
|
||||
{
|
||||
if (maxSpeciesOrigin <= 0)
|
||||
maxSpeciesOrigin = GetMaxSpeciesOrigin(pk);
|
||||
|
||||
ushort species = pk.Species;
|
||||
byte form = pk.Form;
|
||||
|
||||
return GetExplicitLineage(species, form, pk, levelMin, levelMax, maxSpeciesOrigin, skipChecks, stopSpecies);
|
||||
}
|
||||
|
||||
private static int GetMaxSpeciesOrigin(PKM pk)
|
||||
{
|
||||
if (pk.Format == 1)
|
||||
return MaxSpeciesID_1;
|
||||
if (pk.Format == 2 || pk.VC)
|
||||
return MaxSpeciesID_2;
|
||||
return Legal.GetMaxSpeciesOrigin(pk.Generation);
|
||||
}
|
||||
|
||||
public bool IsSpeciesDerivedFrom(ushort species, byte form, int otherSpecies, int otherForm, bool ignoreForm = true)
|
||||
{
|
||||
var evos = GetEvolutionsAndPreEvolutions(species, form);
|
||||
foreach (var (s, f) in evos)
|
||||
{
|
||||
if (s != otherSpecies)
|
||||
continue;
|
||||
if (ignoreForm)
|
||||
return true;
|
||||
return f == otherForm;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all species the <see cref="species"/>-<see cref="form"/> can evolve to & from, yielded in order of increasing evolution stage.
|
||||
/// </summary>
|
||||
/// <param name="species">Species ID</param>
|
||||
/// <param name="form">Form ID</param>
|
||||
/// <returns>Enumerable of species IDs (with the Form IDs included, left shifted by 11).</returns>
|
||||
public IEnumerable<(ushort Species, byte Form)> GetEvolutionsAndPreEvolutions(ushort species, byte form)
|
||||
{
|
||||
foreach (var s in GetPreEvolutions(species, form))
|
||||
yield return s;
|
||||
yield return (species, form);
|
||||
foreach (var s in GetEvolutions(species, form))
|
||||
yield return s;
|
||||
}
|
||||
|
||||
public (ushort Species, byte Form) GetBaseSpeciesForm(ushort species, byte form, int skip = 0)
|
||||
{
|
||||
var chain = GetEvolutionsAndPreEvolutions(species, form);
|
||||
foreach (var c in chain)
|
||||
{
|
||||
if (skip == 0)
|
||||
return c;
|
||||
skip--;
|
||||
}
|
||||
return (species, form);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all species the <see cref="species"/>-<see cref="form"/> can evolve from, yielded in order of increasing evolution stage.
|
||||
/// </summary>
|
||||
/// <param name="species">Species ID</param>
|
||||
/// <param name="form">Form ID</param>
|
||||
/// <returns>Enumerable of species IDs (with the Form IDs included, left shifted by 11).</returns>
|
||||
public IEnumerable<(ushort Species, byte Form)> GetPreEvolutions(ushort species, byte form)
|
||||
{
|
||||
int index = GetLookupKey(species, form);
|
||||
var node = Lineage[index];
|
||||
{
|
||||
// No convergent evolutions; first method is enough.
|
||||
var s = node.First.Tuple;
|
||||
if (s.Species == 0)
|
||||
yield break;
|
||||
|
||||
var preEvolutions = GetPreEvolutions(s.Species, s.Form);
|
||||
foreach (var preEvo in preEvolutions)
|
||||
yield return preEvo;
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all species the <see cref="species"/>-<see cref="form"/> can evolve to, yielded in order of increasing evolution stage.
|
||||
/// </summary>
|
||||
/// <param name="species">Species ID</param>
|
||||
/// <param name="form">Form ID</param>
|
||||
/// <returns>Enumerable of species IDs (with the Form IDs included, left shifted by 11).</returns>
|
||||
public IEnumerable<(ushort Species, byte Form)> GetEvolutions(ushort species, byte form)
|
||||
{
|
||||
int index = !FormSeparateEvoData ? species : Personal.GetFormIndex(species, form);
|
||||
var evos = Entries[index];
|
||||
foreach (var method in evos)
|
||||
{
|
||||
var s = method.Species;
|
||||
if (s == 0)
|
||||
continue;
|
||||
var f = method.GetDestinationForm(form);
|
||||
yield return (s, f);
|
||||
var nextEvolutions = GetEvolutions(s, f);
|
||||
foreach (var nextEvo in nextEvolutions)
|
||||
yield return nextEvo;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the reverse evolution path for the input <see cref="pk"/>.
|
||||
/// </summary>
|
||||
/// <param name="species">Entity Species to begin the chain</param>
|
||||
/// <param name="form">Entity Form to begin the chain</param>
|
||||
/// <param name="pk">Entity data</param>
|
||||
/// <param name="levelMin">Minimum level</param>
|
||||
/// <param name="levelMax">Maximum level</param>
|
||||
/// <param name="maxSpeciesID">Clamp for maximum species ID</param>
|
||||
/// <param name="skipChecks">Skip the secondary checks that validate the evolution</param>
|
||||
/// <param name="stopSpecies">Final species to stop at, if known</param>
|
||||
public EvoCriteria[] GetExplicitLineage(ushort species, byte form, PKM pk, byte levelMin, byte levelMax, int maxSpeciesID, bool skipChecks, int stopSpecies)
|
||||
{
|
||||
if (pk.IsEgg && !skipChecks)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new EvoCriteria{ Species = species, Form = form, LevelMax = levelMax, LevelMin = levelMax },
|
||||
};
|
||||
}
|
||||
|
||||
// Shedinja's evolution case can be a little tricky; hard-code handling.
|
||||
if (species == (int)Species.Shedinja && levelMax >= 20 && (!pk.HasOriginalMetLocation || levelMin < levelMax))
|
||||
{
|
||||
var min = Math.Max(levelMin, (byte)20);
|
||||
return new[]
|
||||
{
|
||||
new EvoCriteria { Species = (ushort)Species.Shedinja, LevelMax = levelMax, LevelMin = min, Method = EvolutionType.LevelUp },
|
||||
new EvoCriteria { Species = (ushort)Species.Nincada, LevelMax = levelMax, LevelMin = levelMin },
|
||||
};
|
||||
}
|
||||
return Lineage.Reverse(species, form, pk, levelMin, levelMax, maxSpeciesID, skipChecks, stopSpecies);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class EvolutionForwardPersonal : IEvolutionForward
|
||||
{
|
||||
private readonly IPersonalTable Personal;
|
||||
private readonly EvolutionMethod[][] Entries;
|
||||
|
||||
public EvolutionForwardPersonal(EvolutionMethod[][] entries, IPersonalTable personal)
|
||||
{
|
||||
Entries = entries;
|
||||
Personal = personal;
|
||||
}
|
||||
|
||||
public ReadOnlyMemory<EvolutionMethod> GetForward(ushort species, byte form)
|
||||
{
|
||||
int index = Personal.GetFormIndex(species, form);
|
||||
return Entries[index];
|
||||
}
|
||||
|
||||
public IEnumerable<(ushort Species, byte Form)> GetEvolutions(ushort species, byte form)
|
||||
{
|
||||
var methods = GetForward(species, form);
|
||||
return GetEvolutions(methods, form);
|
||||
}
|
||||
|
||||
private IEnumerable<(ushort Species, byte Form)> GetEvolutions(ReadOnlyMemory<EvolutionMethod> evos, byte form)
|
||||
{
|
||||
for (int i = 0; i < evos.Length; i++)
|
||||
{
|
||||
var method = evos.Span[i];
|
||||
var s = method.Species;
|
||||
if (s == 0)
|
||||
continue;
|
||||
var f = method.GetDestinationForm(form);
|
||||
yield return (s, f);
|
||||
var nextEvolutions = GetEvolutions(s, f);
|
||||
foreach (var nextEvo in nextEvolutions)
|
||||
yield return nextEvo;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
var methods = GetForward(head.Species, head.Form);
|
||||
foreach (var method in methods.Span)
|
||||
{
|
||||
if (method.Species != next.Species)
|
||||
continue;
|
||||
var expectForm = method.GetDestinationForm(head.Form);
|
||||
if (next.Form != expectForm)
|
||||
continue;
|
||||
|
||||
var chk = method.Check(pk, currentMaxLevel, levelMin, skipChecks);
|
||||
if (chk != EvolutionCheckResult.Valid)
|
||||
continue;
|
||||
|
||||
result = Create(next.Species, next.Form, method, currentMaxLevel, levelMin);
|
||||
return true;
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static EvoCriteria Create(ushort species, byte form, EvolutionMethod method, byte currentMaxLevel, byte min) => new()
|
||||
{
|
||||
Species = species,
|
||||
Form = form,
|
||||
LevelMax = currentMaxLevel,
|
||||
Method = method.Method,
|
||||
|
||||
// Temporarily store these and overwrite them when we clean the list.
|
||||
LevelMin = Math.Max(min, method.Level),
|
||||
LevelUpRequired = method.RequiresLevelUp ? (byte)1 : (byte)0,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class EvolutionForwardSpecies : IEvolutionForward
|
||||
{
|
||||
private readonly EvolutionMethod[][] Entries;
|
||||
|
||||
public EvolutionForwardSpecies(EvolutionMethod[][] entries) => Entries = entries;
|
||||
|
||||
public IEnumerable<(ushort Species, byte Form)> GetEvolutions(ushort species, byte form)
|
||||
{
|
||||
var methods = GetForward(species, form);
|
||||
return GetEvolutions(methods, form);
|
||||
}
|
||||
|
||||
public ReadOnlyMemory<EvolutionMethod> GetForward(ushort species, byte form)
|
||||
{
|
||||
var arr = Entries;
|
||||
if (species >= arr.Length)
|
||||
return Array.Empty<EvolutionMethod>();
|
||||
return arr[species];
|
||||
}
|
||||
|
||||
private IEnumerable<(ushort Species, byte Form)> GetEvolutions(ReadOnlyMemory<EvolutionMethod> evos, byte form)
|
||||
{
|
||||
for (int i = 0; i < evos.Length; i++)
|
||||
{
|
||||
var method = evos.Span[i];
|
||||
var s = method.Species;
|
||||
if (s == 0)
|
||||
continue;
|
||||
var f = method.GetDestinationForm(form);
|
||||
yield return (s, f);
|
||||
var nextEvolutions = GetEvolutions(s, f);
|
||||
foreach (var nextEvo in nextEvolutions)
|
||||
yield return nextEvo;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
var methods = GetForward(head.Species, head.Form);
|
||||
foreach (var method in methods.Span)
|
||||
{
|
||||
if (method.Species != next.Species)
|
||||
continue;
|
||||
var expectForm = method.GetDestinationForm(head.Form);
|
||||
if (next.Form != expectForm)
|
||||
continue;
|
||||
|
||||
var chk = method.Check(pk, currentMaxLevel, levelMin, skipChecks);
|
||||
if (chk != EvolutionCheckResult.Valid)
|
||||
continue;
|
||||
|
||||
result = Create(next.Species, next.Form, method, currentMaxLevel, levelMin);
|
||||
return true;
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static EvoCriteria Create(ushort species, byte form, EvolutionMethod method, byte currentMaxLevel, byte min) => new()
|
||||
{
|
||||
Species = species,
|
||||
Form = form,
|
||||
LevelMax = currentMaxLevel,
|
||||
Method = method.Method,
|
||||
|
||||
// Temporarily store these and overwrite them when we clean the list.
|
||||
LevelMin = Math.Max(min, method.Level),
|
||||
LevelUpRequired = method.RequiresLevelUp ? (byte)1 : (byte)0,
|
||||
};
|
||||
}
|
19
PKHeX.Core/Legality/Evolutions/Forward/IEvolutionForward.cs
Normal file
19
PKHeX.Core/Legality/Evolutions/Forward/IEvolutionForward.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public interface IEvolutionForward
|
||||
{
|
||||
ReadOnlyMemory<EvolutionMethod> GetForward(ushort species, byte form);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all species the <see cref="species"/>-<see cref="form"/> can evolve to, yielded in order of increasing evolution stage.
|
||||
/// </summary>
|
||||
/// <param name="species">Species ID</param>
|
||||
/// <param name="form">Form ID</param>
|
||||
/// <returns>Enumerable of species IDs (with the Form IDs included, left shifted by 11).</returns>
|
||||
IEnumerable<(ushort Species, byte Form)> GetEvolutions(ushort species, byte form);
|
||||
|
||||
bool TryEvolve(ISpeciesForm head, ISpeciesForm next, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result);
|
||||
}
|
68
PKHeX.Core/Legality/Evolutions/IEvolutionNetwork.cs
Normal file
68
PKHeX.Core/Legality/Evolutions/IEvolutionNetwork.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public interface IEvolutionNetwork
|
||||
{
|
||||
IEvolutionForward Forward { get; }
|
||||
IEvolutionReverse Reverse { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base abstraction for <see cref="EvolutionTree"/>
|
||||
/// </summary>
|
||||
public abstract class EvolutionNetwork : IEvolutionNetwork
|
||||
{
|
||||
public IEvolutionForward Forward { get; }
|
||||
public IEvolutionReverse Reverse { get; }
|
||||
|
||||
protected EvolutionNetwork(IEvolutionForward forward, IEvolutionReverse reverse)
|
||||
{
|
||||
Forward = forward;
|
||||
Reverse = reverse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all species the <see cref="species"/>-<see cref="form"/> can evolve to & from, yielded in order of increasing evolution stage.
|
||||
/// </summary>
|
||||
/// <param name="species">Species ID</param>
|
||||
/// <param name="form">Form ID</param>
|
||||
/// <returns>Enumerable of species IDs (with the Form IDs included, left shifted by 11).</returns>
|
||||
public IEnumerable<(ushort Species, byte Form)> GetEvolutionsAndPreEvolutions(ushort species, byte form)
|
||||
{
|
||||
foreach (var s in Reverse.GetPreEvolutions(species, form))
|
||||
yield return s;
|
||||
yield return (species, form);
|
||||
foreach (var s in Forward.GetEvolutions(species, form))
|
||||
yield return s;
|
||||
}
|
||||
|
||||
public bool IsSpeciesDerivedFrom(ushort species, byte form, int otherSpecies, int otherForm, bool ignoreForm = true)
|
||||
{
|
||||
var evos = GetEvolutionsAndPreEvolutions(species, form);
|
||||
foreach (var (s, f) in evos)
|
||||
{
|
||||
if (s != otherSpecies)
|
||||
continue;
|
||||
if (ignoreForm)
|
||||
return true;
|
||||
return f == otherForm;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public (ushort Species, byte Form) GetBaseSpeciesForm(ushort species, byte form)
|
||||
{
|
||||
var chain = Reverse.GetPreEvolutions(species, form);
|
||||
foreach (var evo in chain)
|
||||
return evo;
|
||||
return (species, form);
|
||||
}
|
||||
|
||||
public int Devolve(Span<EvoCriteria> result, ushort species, byte form, PKM pk, byte levelMin, byte levelMax, ushort stopSpecies,
|
||||
bool skipChecks)
|
||||
{
|
||||
return Reverse.Devolve(result, species, form, pk, levelMin, levelMax, stopSpecies, skipChecks);
|
||||
}
|
||||
}
|
|
@ -19,4 +19,25 @@ public readonly record struct EvoCriteria : ISpeciesForm
|
|||
public bool InsideLevelRange(int level) => LevelMin <= level && level <= LevelMax;
|
||||
|
||||
public override string ToString() => $"{(Species) Species}{(Form != 0 ? $"-{Form}" : "")}}} [{LevelMin},{LevelMax}] via {Method}";
|
||||
|
||||
internal const EvolutionType SentinelNotReached = EvolutionType.Invalid;
|
||||
|
||||
public bool IsBetterDevolution(EvoCriteria reference)
|
||||
{
|
||||
if (reference.Species == 0)
|
||||
return true;
|
||||
|
||||
return LevelMin + LevelUpRequired < reference.LevelMin + reference.LevelUpRequired;
|
||||
}
|
||||
|
||||
public bool IsBetterEvolution(EvoCriteria reference)
|
||||
{
|
||||
if (reference.Method == SentinelNotReached)
|
||||
return true;
|
||||
|
||||
if (LevelMin + LevelUpRequired > reference.LevelMin + reference.LevelUpRequired)
|
||||
return false;
|
||||
|
||||
return LevelMax > reference.LevelMax;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
public enum EvolutionCheckResult
|
||||
{
|
||||
Valid = 0,
|
||||
InsufficientLevel,
|
||||
BadGender,
|
||||
BadForm,
|
||||
WrongEC,
|
||||
VisitVersion,
|
||||
LowContestStat,
|
||||
Untraded,
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using static PKHeX.Core.EvolutionType;
|
||||
using static PKHeX.Core.EvolutionCheckResult;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
@ -11,7 +12,7 @@ namespace PKHeX.Core;
|
|||
/// <param name="Argument">Conditional Argument (different from <see cref="Level"/>)</param>
|
||||
/// <param name="Level">Conditional Argument (different from <see cref="Argument"/>)</param>
|
||||
/// <param name="LevelUp">Indicates if a level up is required to trigger evolution.</param>
|
||||
public readonly record struct EvolutionMethod(EvolutionType Method, ushort Species, byte Form = 0, ushort Argument = 0, byte Level = 0, byte LevelUp = 0) : ISpeciesForm
|
||||
public readonly record struct EvolutionMethod(EvolutionType Method, ushort Species, byte Form = EvolutionMethod.AnyForm, ushort Argument = 0, byte Level = 0, byte LevelUp = 0) : ISpeciesForm
|
||||
{
|
||||
/// <summary>Evolve to Species</summary>
|
||||
public ushort Species { get; } = Species;
|
||||
|
@ -56,88 +57,61 @@ public readonly record struct EvolutionMethod(EvolutionType Method, ushort Speci
|
|||
/// </summary>
|
||||
/// <param name="pk">Entity to check</param>
|
||||
/// <param name="lvl">Current level</param>
|
||||
/// <param name="levelMin">Minimum level to sanity check with</param>
|
||||
/// <param name="skipChecks">Option to skip some comparisons to return a 'possible' evolution.</param>
|
||||
/// <returns>True if a evolution criteria is valid.</returns>
|
||||
public bool Valid(PKM pk, byte lvl, bool skipChecks)
|
||||
public EvolutionCheckResult Check(PKM pk, byte lvl, byte levelMin, bool skipChecks)
|
||||
{
|
||||
if (!Method.IsLevelUpRequired())
|
||||
return ValidNotLevelUp(pk, skipChecks);
|
||||
|
||||
if (!IsLevelUpMethodSecondarySatisfied(pk, skipChecks))
|
||||
return false;
|
||||
var chk = IsLevelUpMethodSecondarySatisfied(pk, skipChecks);
|
||||
if (chk != Valid)
|
||||
return chk;
|
||||
|
||||
// Level Up (any); the above Level Up (with condition) cases will reach here if they were valid
|
||||
if (!RequiresLevelUp)
|
||||
return lvl >= Level;
|
||||
|
||||
if (Level == 0 && lvl < 2)
|
||||
return false;
|
||||
if (lvl < Level)
|
||||
return false;
|
||||
return InsufficientLevel;
|
||||
if (!RequiresLevelUp)
|
||||
return Valid;
|
||||
if (Level == 0 && lvl < 2)
|
||||
return InsufficientLevel;
|
||||
if (lvl < levelMin + LevelUp && !skipChecks)
|
||||
return InsufficientLevel;
|
||||
|
||||
if (skipChecks)
|
||||
return lvl >= Level;
|
||||
|
||||
// Check Met Level for extra validity
|
||||
return HasMetLevelIncreased(pk, lvl);
|
||||
return Valid;
|
||||
}
|
||||
|
||||
private bool IsLevelUpMethodSecondarySatisfied(PKM pk, bool skipChecks) => Method switch
|
||||
private EvolutionCheckResult IsLevelUpMethodSecondarySatisfied(PKM pk, bool skipChecks) => Method switch
|
||||
{
|
||||
// Special Level Up Cases -- return false if invalid
|
||||
LevelUpMale when pk.Gender != 0 => false,
|
||||
LevelUpFemale when pk.Gender != 1 => false,
|
||||
LevelUpFormFemale1 when pk.Gender != 1 || pk.Form != 1 => false,
|
||||
LevelUpMale when pk.Gender != 0 => BadGender,
|
||||
LevelUpFemale when pk.Gender != 1 => BadGender,
|
||||
LevelUpFormFemale1 when pk.Gender != 1 => BadGender,
|
||||
LevelUpFormFemale1 when pk.Form != 1 => BadForm,
|
||||
|
||||
// Permit the evolution if we're exploring for mistakes.
|
||||
LevelUpBeauty when pk is IContestStatsReadOnly s && s.CNT_Beauty < Argument => skipChecks,
|
||||
LevelUpNatureAmped or LevelUpNatureLowKey when GetAmpLowKeyResult(pk.Nature) != pk.Form => skipChecks,
|
||||
LevelUpBeauty when pk is IContestStatsReadOnly s && s.CNT_Beauty < Argument => skipChecks ? Valid : LowContestStat,
|
||||
LevelUpNatureAmped or LevelUpNatureLowKey when GetAmpLowKeyResult(pk.Nature) != pk.Form => skipChecks ? Valid : BadForm,
|
||||
|
||||
// Version checks come in pairs, check for any pair match
|
||||
LevelUpVersion or LevelUpVersionDay or LevelUpVersionNight when ((pk.Version & 1) != (Argument & 1) && pk.IsUntraded) => skipChecks,
|
||||
LevelUpVersion or LevelUpVersionDay or LevelUpVersionNight when ((pk.Version & 1) != (Argument & 1) && pk.IsUntraded) => skipChecks ? Valid : VisitVersion,
|
||||
|
||||
LevelUpKnowMoveEC100 when pk.EncryptionConstant % 100 != 0 => skipChecks,
|
||||
LevelUpKnowMoveECElse when pk.EncryptionConstant % 100 == 0 => skipChecks,
|
||||
LevelUpInBattleEC100 when pk.EncryptionConstant % 100 != 0 => skipChecks,
|
||||
LevelUpInBattleECElse when pk.EncryptionConstant % 100 == 0 => skipChecks,
|
||||
LevelUpKnowMoveEC100 when pk.EncryptionConstant % 100 != 0 => skipChecks ? Valid : WrongEC,
|
||||
LevelUpKnowMoveECElse when pk.EncryptionConstant % 100 == 0 => skipChecks ? Valid : WrongEC,
|
||||
LevelUpInBattleEC100 when pk.EncryptionConstant % 100 != 0 => skipChecks ? Valid : WrongEC,
|
||||
LevelUpInBattleECElse when pk.EncryptionConstant % 100 == 0 => skipChecks ? Valid : WrongEC,
|
||||
|
||||
_ => true,
|
||||
_ => Valid,
|
||||
};
|
||||
|
||||
private bool ValidNotLevelUp(PKM pk, bool skipChecks) => Method switch
|
||||
private EvolutionCheckResult ValidNotLevelUp(PKM pk, bool skipChecks) => Method switch
|
||||
{
|
||||
UseItemMale or LevelUpRecoilDamageMale => pk.Gender == 0,
|
||||
UseItemFemale or LevelUpRecoilDamageFemale => pk.Gender == 1,
|
||||
UseItemMale or LevelUpRecoilDamageMale => pk.Gender == 0 ? Valid : BadGender,
|
||||
UseItemFemale or LevelUpRecoilDamageFemale => pk.Gender == 1 ? Valid : BadGender,
|
||||
|
||||
Trade or TradeHeldItem or TradeShelmetKarrablast => !pk.IsUntraded || skipChecks,
|
||||
_ => true, // no conditions
|
||||
};
|
||||
|
||||
private bool HasMetLevelIncreased(PKM pk, int lvl)
|
||||
{
|
||||
int origin = pk.Generation;
|
||||
return origin switch
|
||||
{
|
||||
// No met data in RBY; No met data in GS, Crystal met data can be reset
|
||||
1 or 2 => true,
|
||||
|
||||
// Pal Park / PokeTransfer updates Met Level
|
||||
3 or 4 => pk.Format > origin || lvl > pk.Met_Level,
|
||||
|
||||
// 5=>6 and later transfers keep current level
|
||||
>=5 => lvl >= Level && (!pk.IsNative || lvl > pk.Met_Level),
|
||||
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public EvoCriteria GetEvoCriteria(ushort species, byte form, byte lvl) => new()
|
||||
{
|
||||
Species = species,
|
||||
Form = form,
|
||||
LevelMax = lvl,
|
||||
LevelMin = 0,
|
||||
Method = Method,
|
||||
Trade or TradeHeldItem or TradeShelmetKarrablast => !pk.IsUntraded || skipChecks ? Valid : Untraded,
|
||||
_ => Valid, // no conditions
|
||||
};
|
||||
|
||||
public static int GetAmpLowKeyResult(int n)
|
|
@ -78,6 +78,11 @@ public enum EvolutionType : byte
|
|||
UseItemFullMoon = 90, // Ursaluna
|
||||
UseMoveAgileStyle = 91, // Wyrdeer
|
||||
UseMoveStrongStyle = 92, // Overqwil
|
||||
|
||||
/// <summary>
|
||||
/// Used as a placeholder sentinel for internal logic.
|
||||
/// </summary>
|
||||
Invalid = unchecked((byte)-1),
|
||||
}
|
||||
|
||||
/// <summary>
|
|
@ -1,34 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Links a <see cref="EvolutionMethod"/> to the source <see cref="Species"/> and <see cref="Form"/> that the method can be triggered from.
|
||||
/// </summary>
|
||||
public struct EvolutionLink
|
||||
public readonly record struct EvolutionLink(EvolutionMethod Method, ushort Species, byte Form)
|
||||
{
|
||||
private Func<PKM, bool>? IsBanned = null;
|
||||
public readonly EvolutionMethod Method;
|
||||
public readonly ushort Species;
|
||||
public readonly byte Form;
|
||||
|
||||
public EvolutionLink(ushort species, byte form, EvolutionMethod method)
|
||||
{
|
||||
Species = species;
|
||||
Form = form;
|
||||
Method = method;
|
||||
}
|
||||
|
||||
public bool IsEmpty => Species == 0;
|
||||
|
||||
public (ushort Species, byte Form) Tuple => (Species, Form);
|
||||
|
||||
public void Ban(Func<PKM, bool> check) => IsBanned = check;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the <see cref="Method"/> is allowed.
|
||||
/// </summary>
|
||||
/// <param name="pk">Entity to check</param>
|
||||
/// <returns>True if banned, false if allowed.</returns>
|
||||
public bool IsEvolutionBanned(PKM pk) => IsBanned != null && IsBanned(pk);
|
||||
}
|
||||
|
|
|
@ -26,20 +26,4 @@ public struct EvolutionNode
|
|||
else
|
||||
throw new InvalidOperationException($"{nameof(EvolutionNode)} already has two links.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a function that disallows the reverse evolution link from being valid if the <see cref="func"/> is not satisfied.
|
||||
/// </summary>
|
||||
/// <param name="func">Function that checks if the link should be allowed as an evolution path.</param>
|
||||
public void Ban(Func<PKM, bool> func)
|
||||
{
|
||||
ref var first = ref First;
|
||||
if (first.IsEmpty)
|
||||
return;
|
||||
first.Ban(func);
|
||||
ref var second = ref Second;
|
||||
if (second.IsEmpty)
|
||||
return;
|
||||
second.Ban(func);
|
||||
}
|
||||
}
|
||||
|
|
84
PKHeX.Core/Legality/Evolutions/Reversal/EvolutionReversal.cs
Normal file
84
PKHeX.Core/Legality/Evolutions/Reversal/EvolutionReversal.cs
Normal file
|
@ -0,0 +1,84 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Evolution Reversal logic
|
||||
/// </summary>
|
||||
public static class EvolutionReversal
|
||||
{
|
||||
/// <summary>
|
||||
/// Reverses from current state to see what evolutions the <see cref="pk"/> may have existed as.
|
||||
/// </summary>
|
||||
/// <param name="lineage">Evolution Method lineage reversal object</param>
|
||||
/// <param name="result">Result storage</param>
|
||||
/// <param name="species">Species to devolve from</param>
|
||||
/// <param name="form">Form to devolve from</param>
|
||||
/// <param name="pk">Entity reference to sanity check evolutions with</param>
|
||||
/// <param name="levelMin">Minimum level the entity may exist as</param>
|
||||
/// <param name="levelMax">Maximum the entity may exist as</param>
|
||||
/// <param name="stopSpecies">Species ID that should be the last node, if at all. Provide 0 to fully devolve.</param>
|
||||
/// <param name="skipChecks">Option to bypass some evolution criteria</param>
|
||||
/// <returns>Reversed evolution lineage, with the lowest index being the current species-form.</returns>
|
||||
public static int Devolve(this IEvolutionLookup lineage, Span<EvoCriteria> result, ushort species, byte form,
|
||||
PKM pk, byte levelMin, byte levelMax, ushort stopSpecies, bool skipChecks)
|
||||
{
|
||||
// Store our results -- trim at the end when we place it on the heap.
|
||||
var head = result[0] = new EvoCriteria { Species = species, Form = form, LevelMax = levelMax };
|
||||
int ctr = Devolve(lineage, result, head, pk, levelMax, levelMin, skipChecks, stopSpecies);
|
||||
EvolutionUtil.CleanDevolve(result[..ctr], levelMin);
|
||||
return ctr;
|
||||
}
|
||||
|
||||
private static int Devolve(IEvolutionLookup lineage, Span<EvoCriteria> result, EvoCriteria head,
|
||||
PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, ushort stopSpecies)
|
||||
{
|
||||
// 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; // count in the 'evos' span
|
||||
while (head.Species != stopSpecies)
|
||||
{
|
||||
var node = lineage[head.Species, head.Form];
|
||||
if (!node.TryDevolve(pk, currentMaxLevel, levelMin, skipChecks, out var x))
|
||||
return ctr;
|
||||
|
||||
result[ctr++] = x;
|
||||
currentMaxLevel -= x.LevelUpRequired;
|
||||
}
|
||||
return ctr;
|
||||
}
|
||||
|
||||
public static bool TryDevolve(this EvolutionNode node, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
// Multiple methods can exist to devolve to the same species-form.
|
||||
// The first method is less restrictive (no LevelUp req), if two {level/other} methods exist.
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
ref var link = ref i == 0 ? ref node.First : ref node.Second;
|
||||
if (link.IsEmpty)
|
||||
break;
|
||||
|
||||
var chk = link.Method.Check(pk, currentMaxLevel, levelMin, skipChecks);
|
||||
if (chk != EvolutionCheckResult.Valid)
|
||||
continue;
|
||||
|
||||
result = Create(link, currentMaxLevel);
|
||||
return true;
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static EvoCriteria Create(EvolutionLink link, byte currentMaxLevel) => new()
|
||||
{
|
||||
Species = link.Species,
|
||||
Form = link.Form,
|
||||
LevelMax = currentMaxLevel,
|
||||
Method = link.Method.Method,
|
||||
|
||||
// Temporarily store these and overwrite them when we clean the list.
|
||||
LevelMin = link.Method.Level,
|
||||
LevelUpRequired = link.Method.RequiresLevelUp ? (byte)1 : (byte)0,
|
||||
};
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
@ -9,36 +8,56 @@ namespace PKHeX.Core;
|
|||
public sealed class EvolutionReverseLookup : IEvolutionLookup
|
||||
{
|
||||
private readonly EvolutionNode[] Nodes;
|
||||
private readonly int MaxSpecies;
|
||||
private readonly Dictionary<int, int> KeyLookup;
|
||||
private readonly ushort MaxSpecies;
|
||||
|
||||
public EvolutionReverseLookup(IEnumerable<(int Key, EvolutionLink Value)> links, int maxSpecies)
|
||||
public EvolutionReverseLookup(ushort maxSpecies)
|
||||
{
|
||||
MaxSpecies = maxSpecies;
|
||||
Nodes = new EvolutionNode[maxSpecies * 2];
|
||||
KeyLookup = new Dictionary<int, int>(maxSpecies);
|
||||
var nodes = new EvolutionNode[maxSpecies * 2];
|
||||
int ctr = maxSpecies + 1;
|
||||
foreach (var (key, value) in links)
|
||||
{
|
||||
var index = key <= MaxSpecies ? key : KeyLookup.TryGetValue(key, out var x) ? x : KeyLookup[key] = ctr++;
|
||||
ref var node = ref nodes[index];
|
||||
node.Add(value);
|
||||
}
|
||||
Nodes = nodes;
|
||||
Debug.Assert(KeyLookup.Count < maxSpecies);
|
||||
MaxSpecies = maxSpecies;
|
||||
}
|
||||
|
||||
private int GetIndex(int key)
|
||||
private void Register(EvolutionLink link, ushort species)
|
||||
{
|
||||
if (key <= MaxSpecies)
|
||||
return key;
|
||||
ref var node = ref Nodes[species];
|
||||
node.Add(link);
|
||||
}
|
||||
|
||||
public void Register(EvolutionLink link, ushort species, byte form)
|
||||
{
|
||||
if (form == 0)
|
||||
{
|
||||
Register(link, species);
|
||||
return;
|
||||
}
|
||||
|
||||
int key = GetKey(species, form);
|
||||
if (!KeyLookup.TryGetValue(key, out var index))
|
||||
{
|
||||
index = Nodes.Length - KeyLookup.Count - 1;
|
||||
KeyLookup.Add(key, index);
|
||||
}
|
||||
|
||||
ref var node = ref Nodes[index];
|
||||
node.Add(link);
|
||||
}
|
||||
|
||||
private int GetIndex(ushort species, byte form)
|
||||
{
|
||||
if (species > MaxSpecies)
|
||||
return 0;
|
||||
if (form == 0)
|
||||
return species;
|
||||
int key = GetKey(species, form);
|
||||
return KeyLookup.TryGetValue(key, out var index) ? index : 0;
|
||||
}
|
||||
|
||||
public ref EvolutionNode this[int key] => ref Nodes[GetIndex(key)];
|
||||
private static int GetKey(ushort species, byte form) => species | form << 11;
|
||||
public ref EvolutionNode this[ushort species, byte form] => ref Nodes[GetIndex(species, form)];
|
||||
}
|
||||
|
||||
public interface IEvolutionLookup
|
||||
{
|
||||
ref EvolutionNode this[int key] { get; }
|
||||
ref EvolutionNode this[ushort species, byte form] { get; }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class EvolutionReversePersonal : IEvolutionReverse
|
||||
{
|
||||
public IEvolutionLookup Lineage { get; }
|
||||
|
||||
public EvolutionReversePersonal(EvolutionMethod[][] entries, IPersonalTable t)
|
||||
{
|
||||
Lineage = GetLineage(t, entries);
|
||||
}
|
||||
|
||||
private static EvolutionReverseLookup GetLineage(IPersonalTable t, EvolutionMethod[][] entries)
|
||||
{
|
||||
var maxSpecies = t.MaxSpeciesID;
|
||||
var lineage = new EvolutionReverseLookup(maxSpecies);
|
||||
for (ushort sSpecies = 1; sSpecies <= maxSpecies; sSpecies++)
|
||||
{
|
||||
var fc = t[sSpecies].FormCount;
|
||||
for (byte sForm = 0; sForm < fc; sForm++)
|
||||
{
|
||||
var index = t.GetFormIndex(sSpecies, sForm);
|
||||
foreach (var evo in entries[index])
|
||||
{
|
||||
var dSpecies = evo.Species;
|
||||
if (dSpecies == 0)
|
||||
break;
|
||||
|
||||
var dForm = evo.GetDestinationForm(sForm);
|
||||
var link = new EvolutionLink(evo, sSpecies, sForm);
|
||||
lineage.Register(link, dSpecies, dForm);
|
||||
}
|
||||
}
|
||||
}
|
||||
return lineage;
|
||||
}
|
||||
|
||||
public EvolutionNode GetReverse(ushort species, byte form) => Lineage[species, form];
|
||||
|
||||
public IEnumerable<(ushort Species, byte Form)> GetPreEvolutions(ushort species, byte form)
|
||||
{
|
||||
var node = Lineage[species, form];
|
||||
|
||||
// No convergent evolutions; first method is enough.
|
||||
var s = node.First;
|
||||
if (s.Species == 0)
|
||||
yield break;
|
||||
|
||||
var preEvolutions = GetPreEvolutions(s.Species, s.Form);
|
||||
foreach (var preEvo in preEvolutions)
|
||||
yield return preEvo;
|
||||
yield return (s.Species, s.Form);
|
||||
}
|
||||
|
||||
public int Devolve(Span<EvoCriteria> result, ushort species, byte form, PKM pk, byte levelMin, byte levelMax, ushort stopSpecies,
|
||||
bool skipChecks)
|
||||
{
|
||||
return Lineage.Devolve(result, species, form, pk, levelMin, levelMax, stopSpecies, skipChecks);
|
||||
}
|
||||
|
||||
public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
var node = Lineage[head.Species, head.Form];
|
||||
return node.TryDevolve(pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class EvolutionReverseSpecies : IEvolutionReverse
|
||||
{
|
||||
public EvolutionReverseLookup Lineage { get; }
|
||||
|
||||
public EvolutionReverseSpecies(EvolutionMethod[][] entries, IPersonalTable t)
|
||||
{
|
||||
Lineage = GetLineage(t, entries);
|
||||
}
|
||||
|
||||
private static EvolutionReverseLookup GetLineage(IPersonalTable t, EvolutionMethod[][] entries)
|
||||
{
|
||||
var maxSpecies = t.MaxSpeciesID;
|
||||
var lineage = new EvolutionReverseLookup(maxSpecies);
|
||||
for (ushort sSpecies = 1; sSpecies <= maxSpecies; sSpecies++)
|
||||
{
|
||||
var fc = t[sSpecies].FormCount;
|
||||
for (byte sForm = 0; sForm < fc; sForm++)
|
||||
{
|
||||
foreach (var evo in entries[sSpecies])
|
||||
{
|
||||
var dSpecies = evo.Species;
|
||||
if (dSpecies == 0)
|
||||
break;
|
||||
|
||||
var dForm = sSpecies == (int)Species.Espurr && evo.Method == EvolutionType.LevelUpFormFemale1
|
||||
? (byte)1
|
||||
: sForm;
|
||||
var link = new EvolutionLink(evo, sSpecies, sForm);
|
||||
lineage.Register(link, dSpecies, dForm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lineage;
|
||||
}
|
||||
|
||||
public EvolutionNode GetReverse(ushort species, byte form) => Lineage[species, form];
|
||||
|
||||
public IEnumerable<(ushort Species, byte Form)> GetPreEvolutions(ushort species, byte form)
|
||||
{
|
||||
var node = Lineage[species, form];
|
||||
|
||||
// No convergent evolutions; first method is enough.
|
||||
var s = node.First;
|
||||
if (s.Species == 0)
|
||||
yield break;
|
||||
|
||||
var preEvolutions = GetPreEvolutions(s.Species, s.Form);
|
||||
foreach (var preEvo in preEvolutions)
|
||||
yield return preEvo;
|
||||
yield return (s.Species, s.Form);
|
||||
}
|
||||
|
||||
public int Devolve(Span<EvoCriteria> result, ushort species, byte form, PKM pk, byte levelMin, byte levelMax, ushort stopSpecies,
|
||||
bool skipChecks)
|
||||
{
|
||||
return Lineage.Devolve(result, species, form, pk, levelMin, levelMax, stopSpecies, skipChecks);
|
||||
}
|
||||
|
||||
public bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result)
|
||||
{
|
||||
var node = Lineage[head.Species, head.Form];
|
||||
return node.TryDevolve(pk, currentMaxLevel, levelMin, skipChecks, out result);
|
||||
}
|
||||
}
|
21
PKHeX.Core/Legality/Evolutions/Reversal/IEvolutionReverse.cs
Normal file
21
PKHeX.Core/Legality/Evolutions/Reversal/IEvolutionReverse.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public interface IEvolutionReverse
|
||||
{
|
||||
EvolutionNode GetReverse(ushort species, byte form);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all species the <see cref="species"/>-<see cref="form"/> can evolve from, yielded in order of increasing evolution stage.
|
||||
/// </summary>
|
||||
/// <param name="species">Species ID</param>
|
||||
/// <param name="form">Form ID</param>
|
||||
/// <returns>Enumerable of species IDs (with the Form IDs included, left shifted by 11).</returns>
|
||||
IEnumerable<(ushort Species, byte Form)> GetPreEvolutions(ushort species, byte form);
|
||||
|
||||
int Devolve(Span<EvoCriteria> result, ushort species, byte form, PKM pk, byte levelMin, byte levelMax, ushort stopSpecies, bool skipChecks);
|
||||
|
||||
bool TryDevolve(ISpeciesForm head, PKM pk, byte currentMaxLevel, byte levelMin, bool skipChecks, out EvoCriteria result);
|
||||
}
|
|
@ -7,6 +7,11 @@ namespace PKHeX.Core;
|
|||
/// </summary>
|
||||
public interface ILearnGroup
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the maximum move ID that this group can learn.
|
||||
/// </summary>
|
||||
ushort MaxMoveID { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next group to traverse to continue checking moves.
|
||||
/// </summary>
|
||||
|
|
|
@ -9,6 +9,7 @@ public sealed class LearnGroup1 : ILearnGroup
|
|||
{
|
||||
public static readonly LearnGroup1 Instance = new();
|
||||
private const int Generation = 1;
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_1;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => pk.Context switch
|
||||
{
|
||||
|
|
|
@ -9,6 +9,7 @@ public sealed class LearnGroup2 : ILearnGroup
|
|||
{
|
||||
public static readonly LearnGroup2 Instance = new();
|
||||
private const int Generation = 2;
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_2;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => pk.Context switch
|
||||
{
|
||||
|
|
|
@ -9,6 +9,7 @@ public sealed class LearnGroup3 : ILearnGroup
|
|||
{
|
||||
public static readonly LearnGroup3 Instance = new();
|
||||
private const int Generation = 3;
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_3;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => null; // Gen3 is the end of the line!
|
||||
public bool HasVisited(PKM pk, EvolutionHistory history) => history.HasVisitedGen3;
|
||||
|
|
|
@ -9,6 +9,7 @@ public sealed class LearnGroup4 : ILearnGroup
|
|||
{
|
||||
public static readonly LearnGroup4 Instance = new();
|
||||
private const int Generation = 4;
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_4;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => enc.Generation is Generation ? null : LearnGroup3.Instance;
|
||||
public bool HasVisited(PKM pk, EvolutionHistory history) => history.HasVisitedGen4;
|
||||
|
|
|
@ -9,6 +9,7 @@ public sealed class LearnGroup5 : ILearnGroup
|
|||
{
|
||||
public static readonly LearnGroup5 Instance = new();
|
||||
private const int Generation = 5;
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_5;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => enc.Generation is Generation ? null : LearnGroup4.Instance;
|
||||
public bool HasVisited(PKM pk, EvolutionHistory history) => history.HasVisitedGen5;
|
||||
|
|
|
@ -9,6 +9,7 @@ public sealed class LearnGroup6 : ILearnGroup
|
|||
{
|
||||
public static readonly LearnGroup6 Instance = new();
|
||||
private const int Generation = 6;
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_6_AO;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => enc.Generation is Generation ? null : LearnGroup5.Instance;
|
||||
public bool HasVisited(PKM pk, EvolutionHistory history) => history.HasVisitedGen6;
|
||||
|
|
|
@ -9,6 +9,7 @@ public sealed class LearnGroup7 : ILearnGroup
|
|||
{
|
||||
public static readonly LearnGroup7 Instance = new();
|
||||
private const int Generation = 7;
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_7;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => enc.Generation switch
|
||||
{
|
||||
|
@ -167,7 +168,7 @@ public sealed class LearnGroup7 : ILearnGroup
|
|||
}
|
||||
|
||||
// Check all forms
|
||||
var inst = LearnSource6AO.Instance;
|
||||
var inst = LearnSource7USUM.Instance;
|
||||
if (!inst.TryGetPersonal(evo.Species, evo.Form, out var pi))
|
||||
return;
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ public sealed class LearnGroup7b : ILearnGroup
|
|||
{
|
||||
public static readonly LearnGroup7b Instance = new();
|
||||
private const int Generation = 7;
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_7b;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => null;
|
||||
public bool HasVisited(PKM pk, EvolutionHistory history) => history.HasVisitedLGPE;
|
||||
|
|
|
@ -10,6 +10,7 @@ public sealed class LearnGroup8 : ILearnGroup
|
|||
public static readonly LearnGroup8 Instance = new();
|
||||
private const int Generation = 8;
|
||||
private const EntityContext Context = EntityContext.Gen8;
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_8;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option)
|
||||
{
|
||||
|
@ -48,7 +49,13 @@ public sealed class LearnGroup8 : ILearnGroup
|
|||
if (option is not LearnOption.Current && types.HasFlag(MoveSourceType.Encounter) && pk.IsOriginalMovesetDeleted() && enc is EncounterEgg { Generation: Generation } egg)
|
||||
CheckEncounterMoves(result, current, egg);
|
||||
|
||||
return MoveResult.AllParsed(result);
|
||||
if (MoveResult.AllParsed(result))
|
||||
return true;
|
||||
|
||||
var home = LearnGroupHOME.Instance;
|
||||
if (option != LearnOption.HOME && home.HasVisited(pk, history))
|
||||
return home.Check(result, current, pk, history, enc, types);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void CheckSharedMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EvoCriteria evo)
|
||||
|
|
|
@ -10,6 +10,7 @@ public sealed class LearnGroup8a : ILearnGroup
|
|||
public static readonly LearnGroup8a Instance = new();
|
||||
private const int Generation = 8;
|
||||
private const EntityContext Context = EntityContext.Gen8a;
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_8a;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => null;
|
||||
public bool HasVisited(PKM pk, EvolutionHistory history) => history.HasVisitedPLA;
|
||||
|
@ -21,7 +22,13 @@ public sealed class LearnGroup8a : ILearnGroup
|
|||
for (var i = 0; i < evos.Length; i++)
|
||||
Check(result, current, pk, evos[i], i);
|
||||
|
||||
return MoveResult.AllParsed(result);
|
||||
if (MoveResult.AllParsed(result))
|
||||
return true;
|
||||
|
||||
var home = LearnGroupHOME.Instance;
|
||||
if (option != LearnOption.HOME && home.HasVisited(pk, history))
|
||||
return home.Check(result, current, pk, history, enc, types);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void Check(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, EvoCriteria evo, int stage)
|
||||
|
|
|
@ -10,6 +10,7 @@ public sealed class LearnGroup8b : ILearnGroup
|
|||
public static readonly LearnGroup8b Instance = new();
|
||||
private const int Generation = 8;
|
||||
private const EntityContext Context = EntityContext.Gen8b;
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_8b;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => null;
|
||||
public bool HasVisited(PKM pk, EvolutionHistory history) => history.HasVisitedBDSP;
|
||||
|
@ -26,7 +27,13 @@ public sealed class LearnGroup8b : ILearnGroup
|
|||
|
||||
CheckSharedMoves(result, current, evos[0]);
|
||||
|
||||
return MoveResult.AllParsed(result);
|
||||
if (MoveResult.AllParsed(result))
|
||||
return true;
|
||||
|
||||
var home = LearnGroupHOME.Instance;
|
||||
if (option != LearnOption.HOME && home.HasVisited(pk, history))
|
||||
return home.Check(result, current, pk, history, enc, types);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void CheckSharedMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EvoCriteria evo)
|
||||
|
|
|
@ -10,12 +10,9 @@ public sealed class LearnGroup9 : ILearnGroup
|
|||
public static readonly LearnGroup9 Instance = new();
|
||||
private const int Generation = 9;
|
||||
private const EntityContext Context = EntityContext.Gen9;
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_9;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => null;
|
||||
public bool HasVisited(PKM pk, EvolutionHistory history) => history.HasVisitedGen9;
|
||||
|
||||
public bool Check(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, EvolutionHistory history,
|
||||
|
@ -33,7 +30,13 @@ public sealed class LearnGroup9 : ILearnGroup
|
|||
if (option is not LearnOption.Current && types.HasFlag(MoveSourceType.Encounter) && pk.IsOriginalMovesetDeleted() && enc is EncounterEgg { Generation: Generation } egg)
|
||||
CheckEncounterMoves(result, current, egg);
|
||||
|
||||
return MoveResult.AllParsed(result);
|
||||
if (MoveResult.AllParsed(result))
|
||||
return true;
|
||||
|
||||
var home = LearnGroupHOME.Instance;
|
||||
if (option != LearnOption.HOME && home.HasVisited(pk, history))
|
||||
return home.Check(result, current, pk, history, enc, types);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void CheckSharedMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EvoCriteria evo)
|
||||
|
@ -129,7 +132,7 @@ public sealed class LearnGroup9 : ILearnGroup
|
|||
}
|
||||
|
||||
// Check all forms
|
||||
var inst = LearnSource6AO.Instance;
|
||||
var inst = LearnSource9SV.Instance;
|
||||
if (!inst.TryGetPersonal(evo.Species, evo.Form, out var pi))
|
||||
return;
|
||||
|
||||
|
|
239
PKHeX.Core/Legality/LearnSource/Group/LearnGroupHOME.cs
Normal file
239
PKHeX.Core/Legality/LearnSource/Group/LearnGroupHOME.cs
Normal file
|
@ -0,0 +1,239 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulates logic for HOME's Move Relearner feature.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the Entity knew a move at any point in its history, it can be relearned if the current format can learn it.
|
||||
/// </remarks>
|
||||
public class LearnGroupHOME : ILearnGroup
|
||||
{
|
||||
public static readonly LearnGroupHOME Instance = new();
|
||||
public ushort MaxMoveID => 0;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => null;
|
||||
public bool HasVisited(PKM pk, EvolutionHistory history) => pk is IHomeTrack { HasTracker: true } || !ParseSettings.IgnoreTransferIfNoTracker;
|
||||
|
||||
public bool Check(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, EvolutionHistory history,
|
||||
IEncounterTemplate enc, MoveSourceType types = MoveSourceType.All, LearnOption option = LearnOption.HOME)
|
||||
{
|
||||
var context = pk.Context;
|
||||
if (context == EntityContext.None)
|
||||
return false;
|
||||
|
||||
var local = GetCurrent(context);
|
||||
var evos = history.Get(context);
|
||||
if (history.HasVisitedGen9 && pk is not PK9)
|
||||
{
|
||||
var instance = LearnGroup9.Instance;
|
||||
instance.Check(result, current, pk, history, enc, types, option);
|
||||
if (CleanPurge(result, current, pk, types, local, evos))
|
||||
return true;
|
||||
}
|
||||
if (history.HasVisitedSWSH && pk is not PK8)
|
||||
{
|
||||
var instance = LearnGroup8.Instance;
|
||||
instance.Check(result, current, pk, history, enc, types, option);
|
||||
if (CleanPurge(result, current, pk, types, local, evos))
|
||||
return true;
|
||||
}
|
||||
if (history.HasVisitedPLA && pk is not PA8)
|
||||
{
|
||||
var instance = LearnGroup8a.Instance;
|
||||
instance.Check(result, current, pk, history, enc, types, option);
|
||||
if (CleanPurge(result, current, pk, types, local, evos))
|
||||
return true;
|
||||
}
|
||||
if (history.HasVisitedBDSP && pk is not PB8)
|
||||
{
|
||||
var instance = LearnGroup8b.Instance;
|
||||
instance.Check(result, current, pk, history, enc, types, option);
|
||||
if (CleanPurge(result, current, pk, types, local, evos))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryAddSpecialCaseMoves(pk.Species, result, current))
|
||||
return true;
|
||||
|
||||
if (history.HasVisitedLGPE)
|
||||
{
|
||||
var instance = LearnGroup7b.Instance;
|
||||
instance.Check(result, current, pk, history, enc, types, option);
|
||||
if (CleanPurge(result, current, pk, types, local, evos))
|
||||
return true;
|
||||
}
|
||||
else if (history.HasVisitedGen7)
|
||||
{
|
||||
ILearnGroup instance = LearnGroup7.Instance;
|
||||
while (true)
|
||||
{
|
||||
instance.Check(result, current, pk, history, enc, types, option);
|
||||
if (CleanPurge(result, current, pk, types, local, evos))
|
||||
return true;
|
||||
var prev = instance.GetPrevious(pk, history, enc, option);
|
||||
if (prev is null)
|
||||
break;
|
||||
instance = prev;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scan the results and remove any that are not valid for the game <see cref="local"/> game.
|
||||
/// </summary>
|
||||
/// <returns>True if all results are valid.</returns>
|
||||
private static bool CleanPurge(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, MoveSourceType types, IHomeSource local, ReadOnlySpan<EvoCriteria> evos)
|
||||
{
|
||||
// The logic used to update the results did not check if the move was actually able to be learned in the local game.
|
||||
// Double check the results and remove any that are not valid for the local game.
|
||||
// SW/SH will continue to iterate downwards to previous groups after HOME is checked, so we can exactly check via Environment.
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
ref var r = ref result[i];
|
||||
if (!r.Valid || r.Generation == 0)
|
||||
continue;
|
||||
|
||||
if (r.Info.Environment == local.Environment)
|
||||
continue;
|
||||
|
||||
// Check if any evolution in the local context can learn the move via HOME instruction. If none can, the move is invalid.
|
||||
var move = current[i];
|
||||
if (move == 0)
|
||||
continue;
|
||||
|
||||
bool valid = false;
|
||||
foreach (var evo in evos)
|
||||
{
|
||||
var chk = local.GetCanLearnHOME(pk, evo, move, types);
|
||||
if (chk.Method != LearnMethod.None)
|
||||
valid = true;
|
||||
}
|
||||
if (!valid)
|
||||
r = default;
|
||||
}
|
||||
|
||||
return MoveResult.AllParsed(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current HOME source for the given context.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
private static IHomeSource GetCurrent(EntityContext context) => context switch
|
||||
{
|
||||
EntityContext.Gen8 => LearnSource8SWSH.Instance,
|
||||
EntityContext.Gen8a => LearnSource8LA.Instance,
|
||||
EntityContext.Gen8b => LearnSource8BDSP.Instance,
|
||||
EntityContext.Gen9 => LearnSource9SV.Instance,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(context), context, null),
|
||||
};
|
||||
|
||||
public void GetAllMoves(Span<bool> result, PKM pk, EvolutionHistory history, IEncounterTemplate enc,
|
||||
MoveSourceType types = MoveSourceType.All, LearnOption option = LearnOption.HOME)
|
||||
{
|
||||
option = LearnOption.HOME;
|
||||
var local = GetCurrent(pk.Context);
|
||||
var evos = history.Get(pk.Context);
|
||||
|
||||
// Check all adjacent games
|
||||
if (history.HasVisitedGen9 && pk is not PK9)
|
||||
RentLoopGetAll(LearnGroup9. Instance, result, pk, history, enc, types, option, evos, local);
|
||||
if (history.HasVisitedSWSH && pk is not PK8)
|
||||
RentLoopGetAll(LearnGroup8. Instance, result, pk, history, enc, types, option, evos, local);
|
||||
if (history.HasVisitedPLA && pk is not PA8)
|
||||
RentLoopGetAll(LearnGroup8a.Instance, result, pk, history, enc, types, option, evos, local);
|
||||
if (history.HasVisitedBDSP && pk is not PB8)
|
||||
RentLoopGetAll(LearnGroup8b.Instance, result, pk, history, enc, types, option, evos, local);
|
||||
|
||||
AddSpecialCaseMoves(pk.Species, result);
|
||||
|
||||
// Looking backwards before HOME
|
||||
if (history.HasVisitedLGPE)
|
||||
{
|
||||
RentLoopGetAll(LearnGroup7b.Instance, result, pk, history, enc, types, option, evos, local);
|
||||
}
|
||||
else if (history.HasVisitedGen7)
|
||||
{
|
||||
ILearnGroup instance = LearnGroup7.Instance;
|
||||
while (true)
|
||||
{
|
||||
RentLoopGetAll(instance, result, pk, history, enc, types, option, evos, local);
|
||||
var prev = instance.GetPrevious(pk, history, enc, option);
|
||||
if (prev is null)
|
||||
break;
|
||||
instance = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void RentLoopGetAll<T>(T instance, Span<bool> result, PKM pk, EvolutionHistory history,
|
||||
IEncounterTemplate enc,
|
||||
MoveSourceType types, LearnOption option, ReadOnlySpan<EvoCriteria> evos, IHomeSource local) where T : ILearnGroup
|
||||
{
|
||||
var length = instance.MaxMoveID;
|
||||
var rent = ArrayPool<bool>.Shared.Rent(length);
|
||||
var temp = rent.AsSpan(0, length);
|
||||
instance.GetAllMoves(temp, pk, history, enc, types, option);
|
||||
LoopMerge(result, pk, evos, types, local, temp);
|
||||
temp.Clear();
|
||||
ArrayPool<bool>.Shared.Return(rent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For each move that is possible to learn in another game, check if it is possible to learn in the current game.
|
||||
/// </summary>
|
||||
/// <param name="result">Resulting array of moves that are possible to learn in the current game.</param>
|
||||
/// <param name="pk">Entity to check.</param>
|
||||
/// <param name="evos">Evolutions to check.</param>
|
||||
/// <param name="types">Move source types to check.</param>
|
||||
/// <param name="dest">Destination game to check.</param>
|
||||
/// <param name="temp">Temporary array of moves that are possible to learn in the checked game.</param>
|
||||
private static void LoopMerge(Span<bool> result, PKM pk, ReadOnlySpan<EvoCriteria> evos, MoveSourceType types, IHomeSource dest, Span<bool> temp)
|
||||
{
|
||||
var length = Math.Min(result.Length, temp.Length);
|
||||
for (ushort move = 0; move < length; move++)
|
||||
{
|
||||
if (!temp[move])
|
||||
continue; // not possible to learn in other game
|
||||
if (result[move])
|
||||
continue; // already possible to learn in current game
|
||||
|
||||
foreach (var evo in evos)
|
||||
{
|
||||
var chk = dest.GetCanLearnHOME(pk, evo, move, types);
|
||||
if (chk.Method == LearnMethod.None)
|
||||
continue;
|
||||
result[move] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryAddSpecialCaseMoves(ushort species, Span<MoveResult> result, ReadOnlySpan<ushort> current)
|
||||
{
|
||||
if (IsPikachuLine(species))
|
||||
{
|
||||
var index = current.IndexOf((ushort)Move.VoltTackle);
|
||||
if (index == -1)
|
||||
return false;
|
||||
ref var move = ref result[index];
|
||||
if (move.Valid)
|
||||
return false;
|
||||
move = new MoveResult(LearnMethod.Shared, LearnEnvironment.HOME);
|
||||
return MoveResult.AllValid(result);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void AddSpecialCaseMoves(ushort species, Span<bool> result)
|
||||
{
|
||||
if (IsPikachuLine(species))
|
||||
result[(int)Move.VoltTackle] = true;
|
||||
}
|
||||
|
||||
private static bool IsPikachuLine(ushort species) => species is (int)Species.Raichu or (int)Species.Pikachu or (int)Species.Pichu;
|
||||
}
|
|
@ -19,6 +19,7 @@ public enum LearnEnvironment : byte
|
|||
/* Gen7 */ SM, USUM, GG,
|
||||
/* Gen8 */ SWSH, BDSP, PLA,
|
||||
/* Gen9 */ SV,
|
||||
HOME,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
7
PKHeX.Core/Legality/LearnSource/Sources/IHomeSource.cs
Normal file
7
PKHeX.Core/Legality/LearnSource/Sources/IHomeSource.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
public interface IHomeSource
|
||||
{
|
||||
LearnEnvironment Environment { get; }
|
||||
MoveLearnInfo GetCanLearnHOME(PKM pk, EvoCriteria evo, ushort move, MoveSourceType types = MoveSourceType.All);
|
||||
}
|
|
@ -77,11 +77,11 @@ public sealed class LearnSource4HGSS : ILearnSource<PersonalInfo4>, IEggSource
|
|||
|
||||
private static bool GetIsEnhancedTutor(EvoCriteria evo, ISpeciesForm current, ushort move, LearnOption option) => evo.Species is (int)Species.Rotom && move switch
|
||||
{
|
||||
(int)Move.Overheat => option == LearnOption.AtAnyTime || current.Form == 1,
|
||||
(int)Move.HydroPump => option == LearnOption.AtAnyTime || current.Form == 2,
|
||||
(int)Move.Blizzard => option == LearnOption.AtAnyTime || current.Form == 3,
|
||||
(int)Move.AirSlash => option == LearnOption.AtAnyTime || current.Form == 4,
|
||||
(int)Move.LeafStorm => option == LearnOption.AtAnyTime || current.Form == 5,
|
||||
(int)Move.Overheat => option.IsPast() || current.Form == 1,
|
||||
(int)Move.HydroPump => option.IsPast() || current.Form == 2,
|
||||
(int)Move.Blizzard => option.IsPast() || current.Form == 3,
|
||||
(int)Move.AirSlash => option.IsPast() || current.Form == 4,
|
||||
(int)Move.LeafStorm => option.IsPast() || current.Form == 5,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
|
|
|
@ -75,11 +75,11 @@ public sealed class LearnSource4Pt : LearnSource4, ILearnSource<PersonalInfo4>,
|
|||
|
||||
private static bool GetIsEnhancedTutor(EvoCriteria evo, ISpeciesForm current, ushort move, LearnOption option) => evo.Species is (int)Species.Rotom && move switch
|
||||
{
|
||||
(int)Move.Overheat => option == LearnOption.AtAnyTime || current.Form == 1,
|
||||
(int)Move.HydroPump => option == LearnOption.AtAnyTime || current.Form == 2,
|
||||
(int)Move.Blizzard => option == LearnOption.AtAnyTime || current.Form == 3,
|
||||
(int)Move.AirSlash => option == LearnOption.AtAnyTime || current.Form == 4,
|
||||
(int)Move.LeafStorm => option == LearnOption.AtAnyTime || current.Form == 5,
|
||||
(int)Move.Overheat => option.IsPast() || current.Form == 1,
|
||||
(int)Move.HydroPump => option.IsPast() || current.Form == 2,
|
||||
(int)Move.Blizzard => option.IsPast() || current.Form == 3,
|
||||
(int)Move.AirSlash => option.IsPast() || current.Form == 4,
|
||||
(int)Move.LeafStorm => option.IsPast() || current.Form == 5,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
|
|
|
@ -73,11 +73,11 @@ public sealed class LearnSource5B2W2 : LearnSource5, ILearnSource<PersonalInfo5B
|
|||
(int)Species.Meloetta => move is (int)Move.RelicSong,
|
||||
(int)Species.Rotom => move switch
|
||||
{
|
||||
(int)Move.Overheat => option == LearnOption.AtAnyTime || current.Form == 1,
|
||||
(int)Move.HydroPump => option == LearnOption.AtAnyTime || current.Form == 2,
|
||||
(int)Move.Blizzard => option == LearnOption.AtAnyTime || current.Form == 3,
|
||||
(int)Move.AirSlash => option == LearnOption.AtAnyTime || current.Form == 4,
|
||||
(int)Move.LeafStorm => option == LearnOption.AtAnyTime || current.Form == 5,
|
||||
(int)Move.Overheat => option.IsPast() || current.Form == 1,
|
||||
(int)Move.HydroPump => option.IsPast() || current.Form == 2,
|
||||
(int)Move.Blizzard => option.IsPast() || current.Form == 3,
|
||||
(int)Move.AirSlash => option.IsPast() || current.Form == 4,
|
||||
(int)Move.LeafStorm => option.IsPast() || current.Form == 5,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
|
|
|
@ -70,11 +70,11 @@ public sealed class LearnSource5BW : LearnSource5, ILearnSource<PersonalInfo5BW>
|
|||
(int)Species.Meloetta => move is (int)Move.RelicSong,
|
||||
(int)Species.Rotom => move switch
|
||||
{
|
||||
(int)Move.Overheat => option == LearnOption.AtAnyTime || current.Form == 1,
|
||||
(int)Move.HydroPump => option == LearnOption.AtAnyTime || current.Form == 2,
|
||||
(int)Move.Blizzard => option == LearnOption.AtAnyTime || current.Form == 3,
|
||||
(int)Move.AirSlash => option == LearnOption.AtAnyTime || current.Form == 4,
|
||||
(int)Move.LeafStorm => option == LearnOption.AtAnyTime || current.Form == 5,
|
||||
(int)Move.Overheat => option.IsPast() || current.Form == 1,
|
||||
(int)Move.HydroPump => option.IsPast() || current.Form == 2,
|
||||
(int)Move.Blizzard => option.IsPast() || current.Form == 3,
|
||||
(int)Move.AirSlash => option.IsPast() || current.Form == 4,
|
||||
(int)Move.LeafStorm => option.IsPast() || current.Form == 5,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
|
|
|
@ -74,11 +74,11 @@ public sealed class LearnSource6AO : ILearnSource<PersonalInfo6AO>, IEggSource
|
|||
(int)Species.Meloetta => move is (int)Move.RelicSong,
|
||||
(int)Species.Rotom => move switch
|
||||
{
|
||||
(int)Move.Overheat => option == LearnOption.AtAnyTime || current.Form == 1,
|
||||
(int)Move.HydroPump => option == LearnOption.AtAnyTime || current.Form == 2,
|
||||
(int)Move.Blizzard => option == LearnOption.AtAnyTime || current.Form == 3,
|
||||
(int)Move.AirSlash => option == LearnOption.AtAnyTime || current.Form == 4,
|
||||
(int)Move.LeafStorm => option == LearnOption.AtAnyTime || current.Form == 5,
|
||||
(int)Move.Overheat => option.IsPast() || current.Form == 1,
|
||||
(int)Move.HydroPump => option.IsPast() || current.Form == 2,
|
||||
(int)Move.Blizzard => option.IsPast() || current.Form == 3,
|
||||
(int)Move.AirSlash => option.IsPast() || current.Form == 4,
|
||||
(int)Move.LeafStorm => option.IsPast() || current.Form == 5,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
|
|
|
@ -71,11 +71,11 @@ public sealed class LearnSource6XY : ILearnSource<PersonalInfo6XY>, IEggSource
|
|||
(int)Species.Meloetta => move is (int)Move.RelicSong,
|
||||
(int)Species.Rotom => move switch
|
||||
{
|
||||
(int)Move.Overheat => option == LearnOption.AtAnyTime || current.Form == 1,
|
||||
(int)Move.HydroPump => option == LearnOption.AtAnyTime || current.Form == 2,
|
||||
(int)Move.Blizzard => option == LearnOption.AtAnyTime || current.Form == 3,
|
||||
(int)Move.AirSlash => option == LearnOption.AtAnyTime || current.Form == 4,
|
||||
(int)Move.LeafStorm => option == LearnOption.AtAnyTime || current.Form == 5,
|
||||
(int)Move.Overheat => option.IsPast() || current.Form == 1,
|
||||
(int)Move.HydroPump => option.IsPast() || current.Form == 2,
|
||||
(int)Move.Blizzard => option.IsPast() || current.Form == 3,
|
||||
(int)Move.AirSlash => option.IsPast() || current.Form == 4,
|
||||
(int)Move.LeafStorm => option.IsPast() || current.Form == 5,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
|
|
|
@ -72,19 +72,19 @@ public sealed class LearnSource7SM : ILearnSource<PersonalInfo7>, IEggSource
|
|||
(int)Species.Pikachu or (int)Species.Raichu => move is (int)Move.VoltTackle,
|
||||
(int)Species.Necrozma => move switch
|
||||
{
|
||||
(int)Move.SunsteelStrike => (option == LearnOption.AtAnyTime || current.Form == 1), // Sun w/ Solgaleo
|
||||
(int)Move.MoongeistBeam => (option == LearnOption.AtAnyTime || current.Form == 2), // Moon w/ Lunala
|
||||
(int)Move.SunsteelStrike => option.IsPast() || current.Form == 1, // Sun w/ Solgaleo
|
||||
(int)Move.MoongeistBeam => option.IsPast() || current.Form == 2, // Moon w/ Lunala
|
||||
_ => false,
|
||||
},
|
||||
(int)Species.Keldeo => move is (int)Move.SecretSword,
|
||||
(int)Species.Meloetta => move is (int)Move.RelicSong,
|
||||
(int)Species.Rotom => move switch
|
||||
{
|
||||
(int)Move.Overheat => option == LearnOption.AtAnyTime || current.Form == 1,
|
||||
(int)Move.HydroPump => option == LearnOption.AtAnyTime || current.Form == 2,
|
||||
(int)Move.Blizzard => option == LearnOption.AtAnyTime || current.Form == 3,
|
||||
(int)Move.AirSlash => option == LearnOption.AtAnyTime || current.Form == 4,
|
||||
(int)Move.LeafStorm => option == LearnOption.AtAnyTime || current.Form == 5,
|
||||
(int)Move.Overheat => option.IsPast() || current.Form == 1,
|
||||
(int)Move.HydroPump => option.IsPast() || current.Form == 2,
|
||||
(int)Move.Blizzard => option.IsPast() || current.Form == 3,
|
||||
(int)Move.AirSlash => option.IsPast() || current.Form == 4,
|
||||
(int)Move.LeafStorm => option.IsPast() || current.Form == 5,
|
||||
_ => false,
|
||||
},
|
||||
(int)Species.Zygarde => move is (int)Move.ExtremeSpeed or (int)Move.DragonDance or (int)Move.ThousandArrows or (int)Move.ThousandWaves or (int)Move.CoreEnforcer,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue