diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterTrade4PID.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterTrade4PID.cs
index 55fbd2b1b..25087596f 100644
--- a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterTrade4PID.cs
+++ b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterTrade4PID.cs
@@ -216,6 +216,14 @@ private bool IsMatchEggLocation(PKM pk)
#endregion
+ ///
+ /// Language obtained by the trainer will be of a foreign language ID.
+ ///
+ ///
+ /// Does NOT indicate for bugged D/P English origin, which is Japanese.
+ ///
+ public bool IsLanguageSwap => Species is (ushort)Core.Species.Magikarp or (ushort)Core.Species.Pikachu;
+
public int DetectOriginalLanguage(PKM pk)
{
int lang = pk.Language;
diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionOrigin.cs b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionOrigin.cs
index 869166a82..4853874f9 100644
--- a/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionOrigin.cs
+++ b/PKHeX.Core/Legality/Evolutions/EvolutionGroup/EvolutionOrigin.cs
@@ -24,12 +24,22 @@ namespace PKHeX.Core;
public bool IsDiscardRequired(byte format) => format <= 2 && Options.HasFlag(OriginOptions.ForceDiscard);
}
+///
+/// Options for to modify logic checks based on the source of the constructor input.
+///
[Flags]
public enum OriginOptions : byte
{
None = 0,
+
+ ///
SkipChecks = 1 << 0,
+
+ ///
ForceDiscard = 1 << 1,
+ ///
+ /// Options relevant when checking for an encounter template, which bypasses logic checks against an Entity.
+ ///
EncounterTemplate = SkipChecks | ForceDiscard,
}
diff --git a/PKHeX.Core/Legality/Verifiers/CXDVerifier.cs b/PKHeX.Core/Legality/Verifiers/CXDVerifier.cs
index 6f2ed36b6..95a659b34 100644
--- a/PKHeX.Core/Legality/Verifiers/CXDVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/CXDVerifier.cs
@@ -18,10 +18,13 @@ public override void Verify(LegalityAnalysis data)
if (pk.OriginalTrainerGender == 1)
data.AddLine(GetInvalid(LG3OTGender, CheckIdentifier.Trainer));
+
+ // Trainer ID is checked in another verifier. Don't duplicate it here.
}
private static void VerifyStarterXD(LegalityAnalysis data)
{
+ // The starter in XD must have the correct PIDIV type.
var info = data.Info.PIDIV;
if (info.Type is not (PIDType.CXD or PIDType.CXD_ColoStarter))
return; // already flagged as invalid
diff --git a/PKHeX.Core/Legality/Verifiers/ItemVerifier.cs b/PKHeX.Core/Legality/Verifiers/ItemVerifier.cs
index 5e57fa80c..dcf899090 100644
--- a/PKHeX.Core/Legality/Verifiers/ItemVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/ItemVerifier.cs
@@ -17,17 +17,19 @@ public override void Verify(LegalityAnalysis data)
data.AddLine(GetInvalid(LItemEgg));
if (!ItemRestrictions.IsHeldItemAllowed(item, context: pk.Context))
- {
data.AddLine(GetInvalid(LItemUnreleased));
- }
- else if (pk.Format == 3 && item == 175) // Enigma Berry
- {
- // A Pokémon holding this Berry cannot be traded to Pokémon Colosseum or Pokémon XD: Gale of Darkness, nor can it be stored in Pokémon Box Ruby & Sapphire.
- if (pk is CK3 or XK3)
- data.AddLine(GetInvalid(LItemUnreleased));
- else
- VerifyEReaderBerry(data);
- }
+ else if (item == 175 && pk is G3PKM g3) // Enigma Berry
+ VerifyEnigmaGen3(data, g3);
+ }
+
+ private void VerifyEnigmaGen3(LegalityAnalysis data, G3PKM g3)
+ {
+ // A Pokémon holding this Berry cannot be traded to Pokémon Colosseum or Pokémon XD: Gale of Darkness,
+ // nor can it be stored in Pokémon Box Ruby & Sapphire.
+ if (g3 is CK3 or XK3 || ParseSettings.ActiveTrainer is SAV3RSBox)
+ data.AddLine(GetInvalid(LItemUnreleased));
+ else
+ VerifyEReaderBerry(data);
}
private void VerifyEReaderBerry(LegalityAnalysis data)
diff --git a/PKHeX.Core/Legality/Verifiers/LanguageVerifier.cs b/PKHeX.Core/Legality/Verifiers/LanguageVerifier.cs
index a3c8e187b..9080775cc 100644
--- a/PKHeX.Core/Legality/Verifiers/LanguageVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/LanguageVerifier.cs
@@ -1,4 +1,6 @@
using static PKHeX.Core.LegalityCheckStrings;
+using static PKHeX.Core.GameVersion;
+using static PKHeX.Core.LanguageID;
namespace PKHeX.Core;
@@ -13,21 +15,21 @@ public override void Verify(LegalityAnalysis data)
{
var pk = data.Entity;
var originalGeneration = data.Info.Generation;
- var currentLanguage = pk.Language;
- var maxLanguageID = Legal.GetMaxLanguageID(originalGeneration);
+ var currentLanguage = (LanguageID)pk.Language;
+ var maxLanguageID = (LanguageID)Legal.GetMaxLanguageID(originalGeneration);
var enc = data.EncounterMatch;
if (!IsValidLanguageID(currentLanguage, maxLanguageID, pk, enc))
{
- data.AddLine(GetInvalid(string.Format(LOTLanguage, $"<={(LanguageID)maxLanguageID}", (LanguageID)currentLanguage)));
+ data.AddLine(GetInvalid(string.Format(LOTLanguage, $"<={maxLanguageID}", currentLanguage)));
return;
}
// Korean Gen4 games can not trade with other Gen4 languages, but can use Pal Park with any Gen3 game/language.
if (pk.Format == 4 && enc.Generation == 4 && !IsValidGen4Korean(currentLanguage)
- && enc is not EncounterTrade4PID {Species: (int)Species.Pikachu or (int)Species.Magikarp} // ger magikarp / eng pikachu
+ && enc is not EncounterTrade4PID { IsLanguageSwap: true } // ger magikarp / eng pikachu
)
{
- bool kor = currentLanguage == (int)LanguageID.Korean;
+ bool kor = currentLanguage == Korean;
var msgpkm = kor ? L_XKorean : L_XKoreanNon;
var msgsav = kor ? L_XKoreanNon : L_XKorean;
data.AddLine(GetInvalid(string.Format(LTransferOriginFInvalid0_1, msgpkm, msgsav)));
@@ -37,24 +39,24 @@ public override void Verify(LegalityAnalysis data)
if (originalGeneration <= 2)
{
// Korean Crystal does not exist, neither do Korean VC1
- if (pk is { Korean: true, Version: not (GameVersion.GD or GameVersion.SI) })
- data.AddLine(GetInvalid(string.Format(LOTLanguage, $"!={(LanguageID)currentLanguage}", (LanguageID)currentLanguage)));
+ if (pk is { Korean: true, Version: not (GD or SI) })
+ data.AddLine(GetInvalid(string.Format(LOTLanguage, $"!={currentLanguage}", currentLanguage)));
// Japanese VC is language locked; cannot obtain Japanese-Blue version as other languages.
- if (pk is { Japanese: false, Version: GameVersion.BU })
- data.AddLine(GetInvalid(string.Format(LOTLanguage, nameof(LanguageID.Japanese), (LanguageID)currentLanguage)));
+ if (pk is { Japanese: false, Version: BU })
+ data.AddLine(GetInvalid(string.Format(LOTLanguage, nameof(Japanese), currentLanguage)));
}
}
- public static bool IsValidLanguageID(int currentLanguage, int maxLanguageID, PKM pk, IEncounterTemplate enc)
+ public static bool IsValidLanguageID(LanguageID currentLanguage, LanguageID maxLanguageID, PKM pk, IEncounterTemplate enc)
{
- if (currentLanguage == (int)LanguageID.UNUSED_6)
+ if (currentLanguage == UNUSED_6)
return false; // Language ID 6 is unused.
if (currentLanguage > maxLanguageID)
return false; // Language not available (yet)
- if (currentLanguage <= (int)LanguageID.None && !(enc is EncounterTrade5BW && EncounterTrade5BW.IsValidMissingLanguage(pk)))
+ if (currentLanguage == 0 && !(enc is EncounterTrade5BW && EncounterTrade5BW.IsValidMissingLanguage(pk)))
return false; // Missing Language value is not obtainable
return true; // Language is possible
@@ -67,18 +69,18 @@ public static bool IsValidLanguageID(int currentLanguage, int maxLanguageID, PKM
/// Korean Gen4 games can not trade with other Gen4 languages, but can use Pal Park with any Gen3 game/language.
/// Anything with Gen4 origin cannot exist in the other language save file.
///
- public static bool IsValidGen4Korean(int pkmLanguage)
+ public static bool IsValidGen4Korean(LanguageID pkmLanguage)
{
if (ParseSettings.ActiveTrainer is not SAV4 tr)
return true; // ignore
return IsValidGen4Korean(pkmLanguage, tr);
}
- ///
- public static bool IsValidGen4Korean(int pkmLanguage, SAV4 tr)
+ ///
+ public static bool IsValidGen4Korean(LanguageID pkmLanguage, SAV4 tr)
{
- bool savKOR = tr.Language == (int)LanguageID.Korean;
- bool pkmKOR = pkmLanguage == (int)LanguageID.Korean;
+ bool savKOR = (LanguageID)tr.Language == Korean;
+ bool pkmKOR = pkmLanguage == Korean;
return savKOR == pkmKOR;
}
}
diff --git a/PKHeX.Core/Legality/Verifiers/LegendsArceusVerifier.cs b/PKHeX.Core/Legality/Verifiers/LegendsArceusVerifier.cs
index 780486772..0bc60aa40 100644
--- a/PKHeX.Core/Legality/Verifiers/LegendsArceusVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/LegendsArceusVerifier.cs
@@ -59,7 +59,7 @@ private void CheckScalars(LegalityAnalysis data, PA8 pa)
private static void CheckLearnset(LegalityAnalysis data, PA8 pa)
{
- var moveCount = GetMoveCount(pa);
+ var moveCount = pa.MoveCount;
if (moveCount == 4)
return;
@@ -166,16 +166,6 @@ private static int AddMasteredMissing(PA8 pa, Span current, int ctr, Lea
return ctr;
}
- private static int GetMoveCount(PA8 pa)
- {
- var count = 0;
- if (pa.Move1 != 0) count++;
- if (pa.Move2 != 0) count++;
- if (pa.Move3 != 0) count++;
- if (pa.Move4 != 0) count++;
- return count;
- }
-
private void CheckMastery(LegalityAnalysis data, PA8 pa)
{
var permit = pa.Permit;
diff --git a/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs b/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs
index 421262be8..e41784174 100644
--- a/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs
@@ -37,7 +37,7 @@ public override void Verify(LegalityAnalysis data)
var reqEXP = enc is EncounterStatic2 { DizzyPunchEgg: true }
? 125 // Gen2 Dizzy Punch gifts always have 125 EXP, even if it's more than the Lv5 exp required.
- : Experience.GetEXP(enc.LevelMin, pk.PersonalInfo.EXPGrowth);
+ : Experience.GetEXP(enc.LevelMin, data.PersonalInfo.EXPGrowth);
if (reqEXP != pk.EXP)
data.AddLine(GetInvalid(LEggEXP));
return;
@@ -46,14 +46,14 @@ public override void Verify(LegalityAnalysis data)
var lvl = pk.CurrentLevel;
if (lvl >= 100)
{
- var expect = Experience.GetEXP(100, pk.PersonalInfo.EXPGrowth);
+ var expect = Experience.GetEXP(100, data.PersonalInfo.EXPGrowth);
if (pk.EXP != expect)
data.AddLine(GetInvalid(LLevelEXPTooHigh));
}
if (lvl < pk.MetLevel)
data.AddLine(GetInvalid(LLevelMetBelow));
- else if (!enc.IsWithinEncounterRange(pk) && lvl != 100 && pk.EXP == Experience.GetEXP(lvl, pk.PersonalInfo.EXPGrowth))
+ else if (!enc.IsWithinEncounterRange(pk) && lvl != 100 && pk.EXP == Experience.GetEXP(lvl, data.PersonalInfo.EXPGrowth))
data.AddLine(Get(LLevelEXPThreshold, Severity.Fishy));
else
data.AddLine(GetValid(LLevelMetSane));
diff --git a/PKHeX.Core/Legality/Verifiers/SlotTypeVerifier.cs b/PKHeX.Core/Legality/Verifiers/SlotTypeVerifier.cs
index 6939a99c9..4cf38ff0d 100644
--- a/PKHeX.Core/Legality/Verifiers/SlotTypeVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/SlotTypeVerifier.cs
@@ -1,4 +1,6 @@
using static PKHeX.Core.LegalityCheckStrings;
+using static PKHeX.Core.StorageSlotType;
+using static PKHeX.Core.Species;
namespace PKHeX.Core;
@@ -27,12 +29,12 @@ public override void Verify(LegalityAnalysis data)
public static bool IsSourceValid(PKM pk, StorageSlotType source) => source switch
{
- StorageSlotType.FusedKyurem => pk.Species is (int)Species.Reshiram or (int)Species.Zekrom,
- StorageSlotType.FusedNecrozmaS => pk.Species is (int)Species.Solgaleo,
- StorageSlotType.FusedNecrozmaM => pk.Species is (int)Species.Lunala,
- StorageSlotType.FusedCalyrex => pk.Species is (int)Species.Glastrier or (int)Species.Spectrier,
+ FusedKyurem => pk.Species is (int)Reshiram or (int)Zekrom,
+ FusedNecrozmaS => pk.Species is (int)Solgaleo,
+ FusedNecrozmaM => pk.Species is (int)Lunala,
+ FusedCalyrex => pk.Species is (int)Glastrier or (int)Spectrier,
- StorageSlotType.Ride => pk.Species is (int)Species.Koraidon or (int)Species.Miraidon
+ Ride => pk.Species is (int)Koraidon or (int)Miraidon
&& pk is PK9 {FormArgument: EncounterStatic9.RideLegendFormArg },
_ => true,
};
@@ -40,9 +42,9 @@ public override void Verify(LegalityAnalysis data)
public static bool IsSourceValidEgg(PKM pk, StorageSlotType source) => source switch
{
// Eggs should normally only be in Box or Party.
- StorageSlotType.Box or StorageSlotType.Party => true,
- StorageSlotType.Resort => true, // Poké Pelago can incubate eggs
- StorageSlotType.Daycare when pk.Format == 2 => true, // ignore the "current egg" slot
+ Box or Party => true,
+ Resort => true, // Poké Pelago can incubate eggs
+ Daycare when pk.Format == 2 => true, // ignore the "current egg" slot
_ => false,
};
}
diff --git a/PKHeX.Core/Legality/Verifiers/TransferVerifier.cs b/PKHeX.Core/Legality/Verifiers/TransferVerifier.cs
index b6fde1171..bbeb031c2 100644
--- a/PKHeX.Core/Legality/Verifiers/TransferVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/TransferVerifier.cs
@@ -44,7 +44,7 @@ private void VerifyVCNatureEXP(LegalityAnalysis data)
}
if (met <= 2) // Not enough EXP to have every nature -- check for exclusions!
{
- var pi = pk.PersonalInfo;
+ var pi = data.PersonalInfo;
var growth = pi.EXPGrowth;
var nature = pk.Nature;
bool valid = VerifyVCNature(growth, nature);
diff --git a/PKHeX.Core/PKM/PKM.cs b/PKHeX.Core/PKM/PKM.cs
index cb4e76e86..2b796314b 100644
--- a/PKHeX.Core/PKM/PKM.cs
+++ b/PKHeX.Core/PKM/PKM.cs
@@ -408,6 +408,7 @@ public void SetIVs(uint iv32)
}
///
+ /// Returns the combined 30-bit representation commonly used as IV32.
public uint GetIVs()
{
uint iv32 = 0;
@@ -466,6 +467,12 @@ public ushort[] Moves
set => SetMoves(value);
}
+ ///
+ /// Tries to add a move to the moveset of the PKM.
+ ///
+ /// Move ID to add.
+ /// If the current moveset is full, whether to push out the oldest move (index 0) to add the new one.
+ ///
public bool AddMove(ushort move, bool pushOut = true)
{
if (move == 0 || move >= MaxMoveID || HasMove(move))
@@ -483,6 +490,9 @@ public bool AddMove(ushort move, bool pushOut = true)
return true;
}
+ ///
+ /// Count of non-zero moves in the moveset.
+ ///
public int MoveCount => Convert.ToInt32(Move1 != 0) + Convert.ToInt32(Move2 != 0) + Convert.ToInt32(Move3 != 0) + Convert.ToInt32(Move4 != 0);
public void GetMoves(Span value)
diff --git a/PKHeX.Core/Saves/Substructures/Gen3/CXD/ShadowInfoEntryXD.cs b/PKHeX.Core/Saves/Substructures/Gen3/CXD/ShadowInfoEntryXD.cs
index 08e87563e..0e63d95b0 100644
--- a/PKHeX.Core/Saves/Substructures/Gen3/CXD/ShadowInfoEntryXD.cs
+++ b/PKHeX.Core/Saves/Substructures/Gen3/CXD/ShadowInfoEntryXD.cs
@@ -36,24 +36,22 @@ public uint EXP
public override string ToString() => $"{(Species) Species} 0x{PID:X8} {Purification}";
}
-public sealed class ShadowInfoEntry3J : ShadowInfoEntryXD
+public sealed class ShadowInfoEntry3J(Memory data) : ShadowInfoEntryXD(data)
{
internal const int SIZE_ENTRY = 60; // -12 from U
- public ShadowInfoEntry3J() : base(new byte[SIZE_ENTRY]) { }
- public ShadowInfoEntry3J(byte[] data) : base(data) { }
+ public ShadowInfoEntry3J() : this(new byte[SIZE_ENTRY]) { }
public override byte Index { get => Data[0x35]; set => Data[0x35] = value; }
public override string ToString() => $"{(Species)Species} 0x{PID:X8} {Purification}";
}
-public sealed class ShadowInfoEntry3U : ShadowInfoEntryXD
+public sealed class ShadowInfoEntry3U(Memory data) : ShadowInfoEntryXD(data)
{
internal const int SIZE_ENTRY = 72; // -12 from U
- public ShadowInfoEntry3U() : base(new byte[SIZE_ENTRY]) { }
- public ShadowInfoEntry3U(byte[] data) : base(data) { }
+ public ShadowInfoEntry3U() : this(new byte[SIZE_ENTRY]) { }
public override byte Index { get => Data[0x3F]; set => Data[0x3F] = value; }