Misc tweaks

No functional change
This commit is contained in:
Kurt
2025-05-29 10:03:17 -05:00
parent 53ddcfb0e4
commit c9b8a5b893
11 changed files with 81 additions and 56 deletions

View File

@@ -216,6 +216,14 @@ private bool IsMatchEggLocation(PKM pk)
#endregion
/// <summary>
/// Language obtained by the trainer will be of a foreign language ID.
/// </summary>
/// <remarks>
/// Does NOT indicate for bugged D/P English origin, which is Japanese.
/// </remarks>
public bool IsLanguageSwap => Species is (ushort)Core.Species.Magikarp or (ushort)Core.Species.Pikachu;
public int DetectOriginalLanguage(PKM pk)
{
int lang = pk.Language;

View File

@@ -24,12 +24,22 @@ namespace PKHeX.Core;
public bool IsDiscardRequired(byte format) => format <= 2 && Options.HasFlag(OriginOptions.ForceDiscard);
}
/// <summary>
/// Options for <see cref="EvolutionOrigin"/> to modify logic checks based on the source of the <see cref="EvolutionOrigin"/> constructor input.
/// </summary>
[Flags]
public enum OriginOptions : byte
{
None = 0,
/// <inheritdoc cref="EvolutionOrigin.SkipChecks"/>
SkipChecks = 1 << 0,
/// <inheritdoc cref="EvolutionOrigin.IsDiscardRequired"/>
ForceDiscard = 1 << 1,
/// <summary>
/// Options relevant when checking for an encounter template, which bypasses logic checks against an Entity.
/// </summary>
EncounterTemplate = SkipChecks | ForceDiscard,
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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.
/// </remarks>
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);
}
/// <inheritdoc cref="IsValidGen4Korean(int)"/>
public static bool IsValidGen4Korean(int pkmLanguage, SAV4 tr)
/// <inheritdoc cref="IsValidGen4Korean(LanguageID)"/>
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;
}
}

View File

@@ -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<ushort> 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;

View File

@@ -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));

View File

@@ -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,
};
}

View File

@@ -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);

View File

@@ -408,6 +408,7 @@ public void SetIVs(uint iv32)
}
/// <inheritdoc cref="GetIVs(Span{int})"/>
/// <remarks>Returns the combined 30-bit representation commonly used as IV32.</remarks>
public uint GetIVs()
{
uint iv32 = 0;
@@ -466,6 +467,12 @@ public ushort[] Moves
set => SetMoves(value);
}
/// <summary>
/// Tries to add a move to the moveset of the PKM.
/// </summary>
/// <param name="move">Move ID to add.</param>
/// <param name="pushOut">If the current moveset is full, whether to push out the oldest move (index 0) to add the new one.</param>
/// <returns></returns>
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;
}
/// <summary>
/// Count of non-zero moves in the moveset.
/// </summary>
public int MoveCount => Convert.ToInt32(Move1 != 0) + Convert.ToInt32(Move2 != 0) + Convert.ToInt32(Move3 != 0) + Convert.ToInt32(Move4 != 0);
public void GetMoves(Span<ushort> value)

View File

@@ -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<byte> 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<byte> 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; }