Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
|
|
|
using System;
|
2016-02-23 06:52:48 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
namespace PKHeX.Core;
|
|
|
|
|
2022-08-21 08:39:16 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Loosely aggregated legality logic.
|
|
|
|
/// </summary>
|
2023-04-23 00:51:32 +00:00
|
|
|
public static class Legal
|
2016-02-23 06:52:48 +00:00
|
|
|
{
|
2023-04-21 04:23:15 +00:00
|
|
|
internal const int MaxSpeciesID_1 = 151;
|
|
|
|
internal const int MaxMoveID_1 = 165;
|
|
|
|
internal const int MaxItemID_1 = 255;
|
|
|
|
internal const int MaxAbilityID_1 = 0;
|
|
|
|
|
|
|
|
internal const int MaxSpeciesID_2 = 251;
|
|
|
|
internal const int MaxMoveID_2 = 251;
|
|
|
|
internal const int MaxItemID_2 = 255;
|
|
|
|
internal const int MaxAbilityID_2 = 0;
|
|
|
|
|
|
|
|
internal const int MaxSpeciesIndex_3 = 412;
|
|
|
|
internal const int MaxSpeciesID_3 = 386;
|
|
|
|
internal const int MaxMoveID_3 = 354;
|
|
|
|
internal const int MaxItemID_3 = 374;
|
|
|
|
internal const int MaxItemID_3_COLO = 547;
|
|
|
|
internal const int MaxItemID_3_XD = 593;
|
|
|
|
internal const int MaxAbilityID_3 = 77;
|
|
|
|
internal const int MaxBallID_3 = 0xC;
|
|
|
|
internal const int MaxGameID_3 = 15; // CXD
|
|
|
|
|
|
|
|
internal const int MaxSpeciesID_4 = 493;
|
|
|
|
internal const int MaxMoveID_4 = 467;
|
|
|
|
internal const int MaxItemID_4_DP = 464;
|
|
|
|
internal const int MaxItemID_4_Pt = 467;
|
|
|
|
internal const int MaxItemID_4_HGSS = 536;
|
|
|
|
internal const int MaxAbilityID_4 = 123;
|
|
|
|
internal const int MaxBallID_4 = 0x18;
|
|
|
|
internal const int MaxGameID_4 = 15; // CXD
|
|
|
|
|
|
|
|
internal const int MaxSpeciesID_5 = 649;
|
|
|
|
internal const int MaxMoveID_5 = 559;
|
|
|
|
internal const int MaxItemID_5_BW = 632;
|
|
|
|
internal const int MaxItemID_5_B2W2 = 638;
|
|
|
|
internal const int MaxAbilityID_5 = 164;
|
|
|
|
internal const int MaxBallID_5 = 0x19;
|
|
|
|
internal const int MaxGameID_5 = 23; // B2
|
|
|
|
|
|
|
|
internal const int MaxSpeciesID_6 = 721;
|
|
|
|
internal const int MaxMoveID_6_XY = 617;
|
|
|
|
internal const int MaxMoveID_6_AO = 621;
|
|
|
|
internal const int MaxItemID_6_XY = 717;
|
|
|
|
internal const int MaxItemID_6_AO = 775;
|
|
|
|
internal const int MaxAbilityID_6_XY = 188;
|
|
|
|
internal const int MaxAbilityID_6_AO = 191;
|
|
|
|
internal const int MaxBallID_6 = 0x19;
|
|
|
|
internal const int MaxGameID_6 = 27; // OR
|
|
|
|
|
|
|
|
internal const int MaxSpeciesID_7 = 802;
|
|
|
|
internal const int MaxMoveID_7 = 719;
|
|
|
|
internal const int MaxItemID_7 = 920;
|
|
|
|
internal const int MaxAbilityID_7 = 232;
|
|
|
|
internal const int MaxBallID_7 = 0x1A; // 26
|
|
|
|
internal const int MaxGameID_7 = 41; // Crystal (VC?)
|
|
|
|
|
|
|
|
internal const int MaxSpeciesID_7_USUM = 807;
|
|
|
|
internal const int MaxMoveID_7_USUM = 728;
|
|
|
|
internal const int MaxItemID_7_USUM = 959;
|
|
|
|
internal const int MaxAbilityID_7_USUM = 233;
|
|
|
|
|
|
|
|
internal const int MaxSpeciesID_7b = 809; // Melmetal
|
|
|
|
internal const int MaxMoveID_7b = 742; // Double Iron Bash
|
|
|
|
internal const int MaxItemID_7b = 1057; // Magmar Candy
|
|
|
|
internal const int MaxBallID_7b = (int)Ball.Beast;
|
|
|
|
internal const int MaxGameID_7b = (int)GameVersion.GE;
|
|
|
|
internal const int MaxAbilityID_7b = MaxAbilityID_7_USUM;
|
|
|
|
|
|
|
|
// Current Binaries
|
|
|
|
internal const int MaxSpeciesID_8 = MaxSpeciesID_8_R2;
|
|
|
|
internal const int MaxMoveID_8 = MaxMoveID_8_R2;
|
|
|
|
internal const int MaxItemID_8 = MaxItemID_8_R2;
|
|
|
|
internal const int MaxAbilityID_8 = MaxAbilityID_8_R2;
|
|
|
|
|
|
|
|
// Orion (No DLC)
|
|
|
|
internal const int MaxSpeciesID_8_O0 = 890; // Eternatus
|
|
|
|
internal const int MaxMoveID_8_O0 = 796; // Steel Beam
|
|
|
|
internal const int MaxItemID_8_O0 = 1278; // Rotom Catalog, ignore all catalog parts
|
|
|
|
internal const int MaxAbilityID_8_O0 = 258; // Hunger Switch
|
|
|
|
|
|
|
|
// Rigel 1 (DLC 1: Isle of Armor)
|
|
|
|
internal const int MaxSpeciesID_8_R1 = 893; // Zarude
|
|
|
|
internal const int MaxMoveID_8_R1 = 818; // Surging Strikes
|
|
|
|
internal const int MaxItemID_8_R1 = 1589; // Mark Charm
|
|
|
|
internal const int MaxAbilityID_8_R1 = 260; // Unseen Fist
|
|
|
|
|
|
|
|
// Rigel 2 (DLC 2: Crown Tundra)
|
|
|
|
internal const int MaxSpeciesID_8_R2 = 898; // Calyrex
|
|
|
|
internal const int MaxMoveID_8_R2 = 826; // Eerie Spell
|
|
|
|
internal const int MaxItemID_8_R2 = 1607; // Reins of Unity
|
|
|
|
internal const int MaxAbilityID_8_R2 = 267; // As One (Glastrier)
|
|
|
|
|
|
|
|
internal const int MaxBallID_8 = 0x1A; // 26 Beast
|
|
|
|
internal const int MaxGameID_8 = 45; // Shield
|
|
|
|
|
|
|
|
internal const int MaxSpeciesID_8a = (int)Species.Enamorus;
|
|
|
|
internal const int MaxMoveID_8a = (int)Move.TakeHeart;
|
|
|
|
internal const int MaxItemID_8a = 1828; // Legend Plate
|
|
|
|
internal const int MaxBallID_8a = (int)Ball.LAOrigin;
|
2023-06-04 01:19:16 +00:00
|
|
|
//internal const int MaxGameID_8a = (int)GameVersion.SP;
|
2023-04-21 04:23:15 +00:00
|
|
|
internal const int MaxAbilityID_8a = MaxAbilityID_8_R2;
|
|
|
|
|
|
|
|
internal const int MaxSpeciesID_8b = MaxSpeciesID_4; // Arceus-493
|
|
|
|
internal const int MaxMoveID_8b = MaxMoveID_8_R2;
|
|
|
|
internal const int MaxItemID_8b = 1822; // DS Sounds
|
|
|
|
internal const int MaxBallID_8b = (int)Ball.LAOrigin;
|
2023-06-04 01:19:16 +00:00
|
|
|
//internal const int MaxGameID_8b = (int)GameVersion.SP;
|
2023-04-21 04:23:15 +00:00
|
|
|
internal const int MaxAbilityID_8b = MaxAbilityID_8_R2;
|
|
|
|
|
|
|
|
internal const int MaxSpeciesID_9 = (int)Species.IronLeaves;
|
|
|
|
internal const int MaxMoveID_9 = (int)Move.MagicalTorque;
|
|
|
|
internal const int MaxItemID_9 = 2400; // Yellow Dish
|
|
|
|
internal const int MaxAbilityID_9 = (int)Ability.MyceliumMight;
|
|
|
|
|
|
|
|
internal const int MaxBallID_9 = (int)Ball.LAOrigin;
|
|
|
|
internal const int MaxGameID_9 = (int)GameVersion.VL;
|
2023-06-04 01:19:16 +00:00
|
|
|
internal const int MaxGameID_HOME = MaxGameID_9;
|
2023-04-21 04:23:15 +00:00
|
|
|
|
|
|
|
internal static readonly ushort[] HeldItems_GSC = ItemStorage2.GetAllHeld();
|
|
|
|
internal static readonly ushort[] HeldItems_RS = ItemStorage3RS.GetAllHeld();
|
|
|
|
internal static readonly ushort[] HeldItems_DP = ItemStorage4DP.GetAllHeld();
|
|
|
|
internal static readonly ushort[] HeldItems_Pt = ItemStorage4Pt.GetAllHeld(); // Griseous Orb Added
|
|
|
|
internal static readonly ushort[] HeldItems_HGSS = HeldItems_Pt;
|
|
|
|
internal static readonly ushort[] HeldItems_BW = ItemStorage5.GetAllHeld();
|
2023-04-23 08:35:26 +00:00
|
|
|
internal static readonly ushort[] HeldItems_XY = ItemStorage6XY.GetAllHeld();
|
|
|
|
internal static readonly ushort[] HeldItems_AO = ItemStorage6AO.GetAllHeld();
|
2023-04-21 04:23:15 +00:00
|
|
|
internal static readonly ushort[] HeldItems_SM = ItemStorage7SM.GetAllHeld();
|
|
|
|
internal static readonly ushort[] HeldItems_USUM = ItemStorage7USUM.GetAllHeld();
|
|
|
|
internal static readonly ushort[] HeldItems_GG = Array.Empty<ushort>();
|
|
|
|
internal static readonly ushort[] HeldItems_SWSH = ItemStorage8SWSH.GetAllHeld();
|
|
|
|
internal static readonly ushort[] HeldItems_BS = ItemStorage8BDSP.GetAll();
|
|
|
|
internal static readonly ushort[] HeldItems_LA = Array.Empty<ushort>();
|
|
|
|
internal static readonly ushort[] HeldItems_SV = ItemStorage9SV.GetAllHeld();
|
2022-11-25 01:42:17 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
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,
|
2022-11-25 01:42:17 +00:00
|
|
|
9 => MaxSpeciesID_9,
|
2022-06-18 18:04:24 +00:00
|
|
|
_ => -1,
|
|
|
|
};
|
|
|
|
|
|
|
|
internal static int GetMaxLanguageID(int generation) => generation switch
|
|
|
|
{
|
|
|
|
1 => (int) LanguageID.Spanish, // 1-7 except 6
|
|
|
|
3 => (int) LanguageID.Spanish, // 1-7 except 6
|
|
|
|
2 => (int) LanguageID.Korean,
|
|
|
|
4 => (int) LanguageID.Korean,
|
|
|
|
5 => (int) LanguageID.Korean,
|
|
|
|
6 => (int) LanguageID.Korean,
|
|
|
|
7 => (int) LanguageID.ChineseT,
|
|
|
|
8 => (int) LanguageID.ChineseT,
|
2022-11-25 01:42:17 +00:00
|
|
|
9 => (int) LanguageID.ChineseT,
|
2022-06-18 18:04:24 +00:00
|
|
|
_ => -1,
|
|
|
|
};
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Checks if the relearn moves should be wiped.
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>Already checked for generations < 8.</remarks>
|
|
|
|
/// <param name="pk">Entity to check</param>
|
2022-09-02 03:57:39 +00:00
|
|
|
internal static bool IsOriginalMovesetDeleted(this PKM pk) => pk switch
|
2022-08-25 06:49:00 +00:00
|
|
|
{
|
|
|
|
PA8 pa8 => !pa8.LA,
|
|
|
|
PB8 pb8 => !pb8.BDSP,
|
2022-09-02 03:57:39 +00:00
|
|
|
PK8 pk8 => pk8.IsSideTransfer || pk8.BattleVersion != 0,
|
2023-01-22 04:02:33 +00:00
|
|
|
PK9 pk9 => !(pk9.SV || pk9 is { IsEgg: true, Version: 0 }),
|
2022-08-25 06:49:00 +00:00
|
|
|
_ => false,
|
|
|
|
};
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Indicates if PP Ups are available for use.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="pk">Entity to check</param>
|
|
|
|
public static bool IsPPUpAvailable(PKM pk)
|
|
|
|
{
|
|
|
|
return pk is not PA8;
|
|
|
|
}
|
2019-01-05 18:51:41 +00:00
|
|
|
|
2022-12-17 21:29:06 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Indicate if PP Ups are available for use.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="moveID">Move ID</param>
|
|
|
|
public static bool IsPPUpAvailable(ushort moveID) => moveID switch
|
|
|
|
{
|
|
|
|
0 => false,
|
2022-12-17 22:04:22 +00:00
|
|
|
(int)Move.Sketch => false, // BD/SP v1.0 could use PP Ups on Sketch, but not in later versions. Disallow anyways.
|
2022-12-17 21:29:06 +00:00
|
|
|
(int)Move.RevivalBlessing => false,
|
|
|
|
_ => true,
|
|
|
|
};
|
|
|
|
|
2023-03-31 20:00:34 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Gets the maximum length of a Trainer Name for the input <see cref="generation"/> and <see cref="language"/>.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="generation">Generation of the Trainer</param>
|
|
|
|
/// <param name="language">Language of the Trainer</param>
|
2022-06-18 18:04:24 +00:00
|
|
|
public static int GetMaxLengthOT(int generation, LanguageID language) => language switch
|
|
|
|
{
|
|
|
|
LanguageID.ChineseS or LanguageID.ChineseT => 6,
|
|
|
|
LanguageID.Japanese or LanguageID.Korean => generation >= 6 ? 6 : 5,
|
|
|
|
_ => generation >= 6 ? 12 : 7,
|
|
|
|
};
|
|
|
|
|
2023-03-31 20:00:34 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Gets the maximum length of a Nickname for the input <see cref="generation"/> and <see cref="language"/>.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="generation">Generation of the Trainer</param>
|
|
|
|
/// <param name="language">Language of the Trainer</param>
|
2022-06-18 18:04:24 +00:00
|
|
|
public static int GetMaxLengthNickname(int generation, LanguageID language) => language switch
|
|
|
|
{
|
|
|
|
LanguageID.ChineseS or LanguageID.ChineseT => 6,
|
|
|
|
LanguageID.Japanese or LanguageID.Korean => generation >= 6 ? 6 : 5,
|
|
|
|
_ => generation >= 6 ? 12 : 10,
|
|
|
|
};
|
2019-01-05 18:51:41 +00:00
|
|
|
|
2023-03-31 20:00:34 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Checks if the input <see cref="pk"/> has IVs that match the template <see cref="IVs"/>.
|
|
|
|
/// </summary>
|
2023-01-22 04:02:33 +00:00
|
|
|
public static bool GetIsFixedIVSequenceValidSkipRand(ReadOnlySpan<int> IVs, PKM pk, uint max = 31)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
|
|
|
for (int i = 0; i < 6; i++)
|
2019-01-05 18:51:41 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
if ((uint) IVs[i] > max) // random
|
|
|
|
continue;
|
|
|
|
if (IVs[i] != pk.GetIV(i))
|
|
|
|
return false;
|
2019-01-05 18:51:41 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
return true;
|
|
|
|
}
|
2022-05-31 04:43:52 +00:00
|
|
|
|
2023-03-31 20:00:34 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Checks if the input <see cref="pk"/> has IVs that match the template <see cref="IVs"/>.
|
|
|
|
/// </summary>
|
2022-08-22 00:34:32 +00:00
|
|
|
public static bool GetIsFixedIVSequenceValidSkipRand(IndividualValueSet IVs, PKM pk, int max = 31)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
2022-08-22 00:34:32 +00:00
|
|
|
// Template IVs not in the [0,max] range are random. Only check for IVs within the "specified" range.
|
|
|
|
if ((uint)IVs.HP <= max && IVs.HP != pk.IV_HP ) return false;
|
|
|
|
if ((uint)IVs.ATK <= max && IVs.ATK != pk.IV_ATK) return false;
|
|
|
|
if ((uint)IVs.DEF <= max && IVs.DEF != pk.IV_DEF) return false;
|
|
|
|
if ((uint)IVs.SPE <= max && IVs.SPE != pk.IV_SPE) return false;
|
|
|
|
if ((uint)IVs.SPA <= max && IVs.SPA != pk.IV_SPA) return false;
|
|
|
|
if ((uint)IVs.SPD <= max && IVs.SPD != pk.IV_SPD) return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-03-31 20:00:34 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Checks if the input <see cref="pk"/> has IVs that match the template <see cref="IVs"/>.
|
|
|
|
/// </summary>
|
2022-08-22 00:34:32 +00:00
|
|
|
public static bool GetIsFixedIVSequenceValidNoRand(IndividualValueSet IVs, PKM pk)
|
|
|
|
{
|
|
|
|
if (IVs.HP != pk.IV_HP ) return false;
|
|
|
|
if (IVs.ATK != pk.IV_ATK) return false;
|
|
|
|
if (IVs.DEF != pk.IV_DEF) return false;
|
|
|
|
if (IVs.SPE != pk.IV_SPE) return false;
|
|
|
|
if (IVs.SPA != pk.IV_SPA) return false;
|
|
|
|
if (IVs.SPD != pk.IV_SPD) return false;
|
2022-06-18 18:04:24 +00:00
|
|
|
return true;
|
2016-02-23 06:52:48 +00:00
|
|
|
}
|
|
|
|
}
|