mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-04-25 08:10:48 -05:00
Changed: Inventory editor no longer needs to clone the save file on GUI open Changed: some method signatures have moved from SAV3* to the specific block Allows the block structures to be used without a SAV3 object Allows the Inventory editor to open from a blank save file.
344 lines
15 KiB
C#
344 lines
15 KiB
C#
using System;
|
||
using System.Diagnostics.CodeAnalysis;
|
||
|
||
namespace PKHeX.Core;
|
||
|
||
/// <summary>
|
||
/// Loosely aggregated legality logic.
|
||
/// </summary>
|
||
public static class Legal
|
||
{
|
||
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 MaxSpeciesID_3 = 386;
|
||
internal const int MaxMoveID_3 = 354;
|
||
internal const int MaxItemID_3_RS = 346;
|
||
internal const int MaxItemID_3_FRLG = 374;
|
||
internal const int MaxItemID_3_E = 376;
|
||
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 GameVersion MaxGameID_3 = GameVersion.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 GameVersion MaxGameID_4 = GameVersion.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 GameVersion MaxGameID_5 = GameVersion.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 GameVersion MaxGameID_6 = GameVersion.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 GameVersion MaxGameID_7 = GameVersion.C;
|
||
|
||
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 GameVersion MaxGameID_7b = 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 ushort MaxSpeciesID_8_O0 = 890; // Eternatus
|
||
internal const ushort MaxMoveID_8_O0 = 796; // Steel Beam
|
||
internal const ushort MaxItemID_8_O0 = 1278; // Rotom Catalog, ignore all catalog parts
|
||
internal const ushort MaxAbilityID_8_O0 = 258; // Hunger Switch
|
||
|
||
// Rigel 1 (DLC 1: Isle of Armor)
|
||
internal const ushort MaxSpeciesID_8_R1 = 893; // Zarude
|
||
internal const ushort MaxMoveID_8_R1 = 818; // Surging Strikes
|
||
internal const ushort MaxItemID_8_R1 = 1589; // Mark Charm
|
||
internal const ushort MaxAbilityID_8_R1 = 260; // Unseen Fist
|
||
|
||
// Rigel 2 (DLC 2: Crown Tundra)
|
||
internal const ushort MaxSpeciesID_8_R2 = 898; // Calyrex
|
||
internal const ushort MaxMoveID_8_R2 = 826; // Eerie Spell
|
||
internal const ushort MaxItemID_8_R2 = 1607; // Reins of Unity
|
||
internal const ushort MaxAbilityID_8_R2 = 267; // As One (Glastrier)
|
||
|
||
internal const int MaxBallID_8 = 0x1A; // 26 Beast
|
||
internal const GameVersion MaxGameID_8 = GameVersion.SH;
|
||
|
||
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;
|
||
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;
|
||
internal const int MaxAbilityID_8b = MaxAbilityID_8_R2;
|
||
|
||
internal const ushort MaxSpeciesID_9 = MaxSpeciesID_9_T2;
|
||
internal const ushort MaxMoveID_9 = MaxMoveID_9_T2;
|
||
internal const ushort MaxItemID_9 = MaxItemID_9_T2;
|
||
internal const ushort MaxAbilityID_9 = MaxAbilityID_9_T2;
|
||
|
||
internal const ushort MaxSpeciesID_9_T0 = (int)Species.IronLeaves;
|
||
internal const ushort MaxMoveID_9_T0 = (int)Move.MagicalTorque;
|
||
internal const ushort MaxItemID_9_T0 = 2400; // Yellow Dish
|
||
internal const ushort MaxAbilityID_9_T0 = (int)Ability.MyceliumMight;
|
||
|
||
internal const ushort MaxSpeciesID_9_T1 = (int)Species.Ogerpon;
|
||
internal const ushort MaxMoveID_9_T1 = (int)Move.IvyCudgel;
|
||
internal const ushort MaxItemID_9_T1 = 2481; // Glimmering Charm
|
||
internal const ushort MaxAbilityID_9_T1 = (int)Ability.SupersweetSyrup;
|
||
|
||
internal const ushort MaxSpeciesID_9_T2 = (int)Species.Pecharunt;
|
||
internal const ushort MaxMoveID_9_T2 = (int)Move.MalignantChain;
|
||
internal const ushort MaxItemID_9_T2 = 2557; // Briar’s Book
|
||
internal const ushort MaxAbilityID_9_T2 = (int)Ability.PoisonPuppeteer;
|
||
|
||
internal const int MaxSpeciesID_9a = MaxSpeciesID_9a_MD;
|
||
internal const int MaxMoveID_9a = MaxMoveID_9a_IK;
|
||
internal const int MaxItemID_9a = MaxItemID_9a_MD;
|
||
internal const int MaxAbilityID_9a = MaxAbilityID_9a_IK;
|
||
internal const int MaxBallID_9a = MaxBallID_9a_IK;
|
||
|
||
internal const int MaxSpeciesID_9a_IK = (int)Species.Falinks;
|
||
internal const int MaxMoveID_9a_IK = (int)Move.NihilLight;
|
||
internal const int MaxItemID_9a_IK = 2634; // Blue Canari Plush
|
||
internal const int MaxAbilityID_9a_IK = (int)Ability.PoisonPuppeteer;
|
||
internal const int MaxBallID_9a_IK = (int)Ball.LAOrigin;
|
||
|
||
internal const int MaxSpeciesID_9a_MD = (int)Species.Gholdengo;
|
||
internal const int MaxMoveID_9a_MD = MaxMoveID_9a_IK;
|
||
internal const int MaxItemID_9a_MD = 2684; // Canari Bread
|
||
internal const int MaxAbilityID_9a_MD = MaxAbilityID_9a_IK;
|
||
|
||
internal const int MaxBallID_9 = (int)Ball.LAOrigin;
|
||
internal const GameVersion MaxGameID_HOME = GameVersion.VL; // TODO HOME ZA - Replace with ZA when HOME; if backwards transfer is allowed. If prevented, rename epoch as HOME1.
|
||
internal const GameVersion MaxGameID_HOME2 = GameVersion.ZA;
|
||
|
||
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();
|
||
internal static readonly ushort[] HeldItems_XY = ItemStorage6XY.GetAllHeld();
|
||
internal static readonly ushort[] HeldItems_AO = ItemStorage6AO.GetAllHeld();
|
||
internal static readonly ushort[] HeldItems_SM = ItemStorage7SM.GetAllHeld();
|
||
internal static readonly ushort[] HeldItems_USUM = ItemStorage7USUM.GetAllHeld();
|
||
internal static readonly ushort[] HeldItems_GG = [];
|
||
internal static readonly ushort[] HeldItems_SWSH = ItemStorage8SWSH.GetAllHeld();
|
||
internal static readonly ushort[] HeldItems_BS = ItemStorage8BDSP.GetAllHeld();
|
||
internal static readonly ushort[] HeldItems_LA = [];
|
||
internal static readonly ushort[] HeldItems_SV = ItemStorage9SV.GetAllHeld();
|
||
internal static readonly ushort[] HeldItems_ZA = ItemStorage9ZA.GetAllHeld();
|
||
|
||
internal static int GetMaxLanguageID(byte generation, EntityContext context) => 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,
|
||
9 => (int) (context == EntityContext.Gen9a ? LanguageID.SpanishL : LanguageID.ChineseT),
|
||
_ => -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>
|
||
internal static bool IsOriginalMovesetDeleted(this PKM pk) => pk switch
|
||
{
|
||
PA8 pa8 => !pa8.LA,
|
||
PB8 pb8 => !pb8.BDSP,
|
||
PK8 pk8 => pk8.IsSideTransfer || pk8.BattleVersion != 0,
|
||
PK9 pk9 => !(pk9.SV || pk9 is { IsEgg: true, Version: 0 }),
|
||
PA9 pa9 => !pa9.ZA,
|
||
_ => false,
|
||
};
|
||
|
||
/// <summary>
|
||
/// Indicates if PP Ups are available for use.
|
||
/// </summary>
|
||
/// <param name="pk">Entity to check</param>
|
||
public static bool IsPPUpAvailable(PKM pk) => pk is not (PA8 or PA9);
|
||
|
||
/// <summary>
|
||
/// Indicates if PP is never used in-game and should always be default values.
|
||
/// </summary>
|
||
/// <param name="pk">Entity to check</param>
|
||
/// <returns><see langword="true"/> if PP is never used; otherwise, <see langword="false"/>.</returns>
|
||
public static bool IsPPUnused(PKM pk) => pk is (PA9);
|
||
|
||
/// <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,
|
||
(int)Move.Sketch => false, // BD/SP v1.0 could use PP Ups on Sketch, but not in later versions. Disallow anyway.
|
||
(int)Move.RevivalBlessing => false,
|
||
_ => true,
|
||
};
|
||
|
||
public const int MaxLengthTrainerAsian = 6; // Chinese/Japanese/Korean
|
||
public const int MaxLengthTrainerWestern = 12;
|
||
public const int MaxLengthTrainerAsian15 = 6; // Chinese/Japanese/Korean
|
||
public const int MaxLengthTrainerWestern15 = 12;
|
||
|
||
public const int MaxLengthNicknameAsian = 6; // Chinese/Japanese/Korean
|
||
public const int MaxLengthNicknameWestern = 12;
|
||
public const int MaxLengthNicknameAsian15 = 5; // Chinese/Japanese/Korean
|
||
public const int MaxLengthNicknameWestern15 = 10;
|
||
|
||
/// <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>
|
||
public static int GetMaxLengthOT(byte generation, LanguageID language) => language switch
|
||
{
|
||
LanguageID.ChineseS or LanguageID.ChineseT => MaxLengthTrainerAsian,
|
||
LanguageID.Japanese or LanguageID.Korean => generation >= 6 ? MaxLengthTrainerAsian : MaxLengthTrainerAsian15,
|
||
_ => generation >= 6 ? MaxLengthTrainerWestern : MaxLengthTrainerWestern15,
|
||
};
|
||
|
||
/// <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>
|
||
public static int GetMaxLengthNickname(byte generation, LanguageID language) => language switch
|
||
{
|
||
LanguageID.ChineseS or LanguageID.ChineseT => MaxLengthNicknameAsian,
|
||
LanguageID.Japanese or LanguageID.Korean => generation >= 6 ? MaxLengthNicknameAsian : MaxLengthNicknameAsian15,
|
||
_ => generation >= 6 ? MaxLengthNicknameWestern : MaxLengthNicknameWestern15,
|
||
};
|
||
|
||
/// <summary>
|
||
/// Checks if the input <see cref="pk"/> has IVs that match the template <see cref="IVs"/>.
|
||
/// </summary>
|
||
public static bool GetIsFixedIVSequenceValidSkipRand(ReadOnlySpan<int> IVs, PKM pk, uint max = 31)
|
||
{
|
||
for (int i = 5; i >= 0; i--)
|
||
{
|
||
var iv = IVs[i];
|
||
if ((uint)iv > max) // random
|
||
continue;
|
||
if (iv != pk.GetIV(i))
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks if the input <see cref="pk"/> has IVs that match the template <see cref="IVs"/>.
|
||
/// </summary>
|
||
public static bool GetIsFixedIVSequenceValidSkipRand(in IndividualValueSet IVs, PKM pk, int max = 31)
|
||
{
|
||
// 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks if the input <see cref="pk"/> has IVs that match the template <see cref="IVs"/>.
|
||
/// </summary>
|
||
public static bool GetIsFixedIVSequenceValidNoRand(in 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;
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// For a species with a potentially valid FR/LG origin encounter, flag if not permitted.
|
||
/// </summary>
|
||
public static bool IsForeignFRLG(ushort species) => IsForeign(ForeignFRLG, species, ShiftFRLG);
|
||
|
||
private static bool IsForeign(ReadOnlySpan<byte> bitSet, int species, [ConstantExpected] int shift)
|
||
{
|
||
species -= shift;
|
||
var offset = species >> 3;
|
||
if ((uint)offset >= bitSet.Length)
|
||
return false;
|
||
|
||
var bit = species & 7;
|
||
if ((bitSet[offset] & (1 << bit)) != 0)
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
private const ushort ShiftFRLG = 151; // First unavailable Species (Mew)
|
||
|
||
/// <summary>
|
||
/// Bitset representing species that are considered unobtainable in FR/LG.
|
||
/// Includes foreign transfers and time-of-day evolutions.
|
||
/// First species is Mew (151), last is Deoxys (386).
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Source: https://www.serebii.net/fireredleafgreen/unobtainable.shtml
|
||
/// </remarks>
|
||
private static ReadOnlySpan<byte> ForeignFRLG =>
|
||
[
|
||
0xFF, 0x33, 0x18, 0x60, 0x04, 0x63, 0x50, 0x0D,
|
||
0x84, 0x40, 0x00, 0x04, 0xF0, 0xFF, 0xFF, 0xFF,
|
||
0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||
0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0x07,
|
||
];
|
||
}
|