Revise ActiveTrainer checks if unset (unit tests)

No longer need to disable correct-handler-state check for unit tests
Adds indication for HT not matching gender (if active trainer is set)
This commit is contained in:
Kurt 2024-05-13 17:38:16 -05:00
parent 0f7d6e1b6a
commit 3dc84d6a39
9 changed files with 81 additions and 51 deletions

View file

@ -483,6 +483,7 @@ public static class LegalityCheckStrings
public static string LTransferFlagIllegal { get; set; } = "Flagged as illegal by the game (glitch abuse).";
public static string LTransferHTFlagRequired { get; set; } = "Current handler cannot be past gen OT for transferred specimen.";
public static string LTransferHTMismatchName { get; set; } = "Handling trainer does not match the expected trainer name.";
public static string LTransferHTMismatchGender { get; set; } = "Handling trainer does not match the expected trainer gender.";
public static string LTransferHTMismatchLanguage { get; set; } = "Handling trainer does not match the expected trainer language.";
public static string LTransferMet { get; set; } = "Invalid Met Location, expected Poké Transfer or Crown.";
public static string LTransferNotPossible { get; set; } = "Unable to transfer into current format from origin format.";

View file

@ -25,7 +25,7 @@ public sealed class LegalitySettings
/// <param name="allowRNG">If true, allows special encounters to be nicknamed.</param>
public void SetCheckWithoutSaveFile(bool checkHOME = true, bool allowRNG = false)
{
Handler.CheckActiveHandler = false;
ParseSettings.ClearActiveTrainer();
if (!checkHOME)
HOMETransfer.Disable();
if (allowRNG)

View file

@ -8,7 +8,20 @@ namespace PKHeX.Core;
/// <remarks><see cref="LegalityAnalysis"/></remarks>
public static class ParseSettings
{
internal static ITrainerInfo ActiveTrainer { get; set; } = new SimpleTrainerInfo(GameVersion.Any) { OT = string.Empty, Language = -1 };
/// <summary>
/// Current Trainer of the active Save Data.
/// </summary>
/// <remarks>
/// Used for legality checks to determine if the data is from the active save file.
/// Defaults to a blank trainer with no data to prevent matching unless another reference (save file) is loaded.
/// </remarks>
internal static ITrainerInfo? ActiveTrainer { get; private set; }
/// <summary>
/// Resets active trainer to null, disabling any legality checks that compare to a currently loaded trainer.
/// </summary>
/// <remarks>Shouldn't need to use this unless you want to undo any loading of save data to revert to an uninitialized state.</remarks>
public static void ClearActiveTrainer() => ActiveTrainer = null;
/// <summary>
/// Master settings configuration for legality analysis.
@ -63,8 +76,6 @@ public static class ParseSettings
public static bool AllowGBEraEvents => AllowGBCartEra;
public static bool AllowGBStadium2 => AllowGBCartEra;
internal static bool IsFromActiveTrainer(PKM pk) => ActiveTrainer.IsFromTrainer(pk);
/// <summary>
/// Initializes certain settings
/// </summary>

View file

@ -107,7 +107,7 @@ public sealed class FormArgumentVerifier : Verifier
{
// Starter Legend has '1' when present in party, to differentiate.
// Cannot be traded to other games.
EncounterStatic9 { StarterBoxLegend: true } x when !(ParseSettings.ActiveTrainer is SAV9SV sv && sv.Version == x.Version) => GetInvalid(LTradeNotAvailable),
EncounterStatic9 { StarterBoxLegend: true } x when ParseSettings.ActiveTrainer is { } tr && (tr is not SAV9SV sv || sv.Version != x.Version) => GetInvalid(LTradeNotAvailable),
EncounterStatic9 { StarterBoxLegend: true } => arg switch
{
< 1 => GetInvalid(LFormArgumentLow),

View file

@ -53,41 +53,56 @@ public sealed class HistoryVerifier : Verifier
private void VerifyHandlerState(LegalityAnalysis data, bool neverOT)
{
var pk = data.Entity;
var Info = data.Info;
var info = data.Info;
var enc = info.EncounterOriginal;
var current = pk.CurrentHandler;
// HT Flag
if (ParseSettings.Settings.Handler.CheckActiveHandler)
if (ParseSettings.Settings.Handler.CheckActiveHandler && ParseSettings.ActiveTrainer is { } tr)
{
var tr = ParseSettings.ActiveTrainer;
var withOT = tr.IsFromTrainer(pk);
var flag = pk.CurrentHandler;
var expect = withOT ? 0 : 1;
if (flag != expect)
var shouldBe0 = tr.IsFromTrainer(pk);
byte expect = shouldBe0 ? (byte)0 : (byte)1;
if (!IsHandlerStateCorrect(enc, pk, current, expect))
{
if (flag == 0 && !IsHandlerOriginalBug(Info.EncounterOriginal, pk))
{
data.AddLine(GetInvalid(LTransferCurrentHandlerInvalid));
return;
}
data.AddLine(GetInvalid(LTransferCurrentHandlerInvalid));
return;
}
if (flag == 1)
{
Span<char> ht = stackalloc char[pk.TrashCharCountTrainer];
var len = pk.LoadString(pk.HandlingTrainerTrash, ht);
ht = ht[..len];
if (!ht.SequenceEqual(tr.OT))
data.AddLine(GetInvalid(LTransferHTMismatchName));
if (pk is IHandlerLanguage h && h.HandlingTrainerLanguage != tr.Language)
data.AddLine(Get(LTransferHTMismatchLanguage, Severity.Fishy));
}
if (current == 1)
CheckHandlingTrainerEquals(data, pk, tr);
}
if (!pk.IsUntraded && IsUntradeableEncounter(Info.EncounterMatch)) // Starter, untradeable
data.AddLine(GetInvalid(LTransferCurrentHandlerInvalid));
if ((Info.Generation != pk.Format || neverOT) && pk.CurrentHandler != 1)
if (current != 1 && (enc.Context != pk.Context || neverOT))
data.AddLine(GetInvalid(LTransferHTFlagRequired));
if (!pk.IsUntraded && IsUntradeableEncounter(enc)) // Starter, untradeable
data.AddLine(GetInvalid(LTransferCurrentHandlerInvalid));
}
private static bool IsHandlerStateCorrect(IEncounterTemplate enc, PKM pk, byte current, byte expect)
{
if (current == expect)
return true;
if (current == 0)
return IsHandlerOriginalBug(enc, pk);
return false; // HT [1] should be OT [0].
}
private void CheckHandlingTrainerEquals(LegalityAnalysis data, PKM pk, ITrainerInfo tr)
{
Span<char> ht = stackalloc char[pk.TrashCharCountTrainer];
var len = pk.LoadString(pk.HandlingTrainerTrash, ht);
ht = ht[..len];
if (!ht.SequenceEqual(tr.OT))
data.AddLine(GetInvalid(LTransferHTMismatchName));
if (pk.HandlingTrainerGender != tr.Gender)
data.AddLine(GetInvalid(LTransferHTMismatchGender));
// If the format exposes a language, check if it matches.
// Can be mismatched as the game only checks OT/Gender equivalence -- if it matches, don't update everything else.
// Statistically unlikely that players will play in different languages, but it's technically possible.
if (pk is IHandlerLanguage h && h.HandlingTrainerLanguage != tr.Language)
data.AddLine(Get(LTransferHTMismatchLanguage, Severity.Fishy));
}
private static bool IsUntradeableEncounter(IEncounterTemplate enc) => enc switch

View file

@ -23,7 +23,7 @@ public sealed class LanguageVerifier : Verifier
}
// 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 && !IsValidG4Korean(currentLanguage)
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
)
{
@ -61,18 +61,24 @@ public sealed class LanguageVerifier : Verifier
}
/// <summary>
/// Check if the <see cref="currentLanguage"/> can exist in the Generation 4 savefile.
/// Check if the <see cref="pkmLanguage"/> can exist in the Generation 4 save file.
/// </summary>
/// <param name="currentLanguage"></param>
public static bool IsValidG4Korean(int currentLanguage)
/// <remarks>
/// 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)
{
var activeTr = ParseSettings.ActiveTrainer;
var activeLang = activeTr.Language;
bool savKOR = activeLang == (int) LanguageID.Korean;
bool pkmKOR = currentLanguage == (int) LanguageID.Korean;
if (savKOR == pkmKOR)
return true;
if (ParseSettings.ActiveTrainer is not SAV4 tr)
return true; // ignore
return IsValidGen4Korean(pkmLanguage, tr);
}
return activeLang < 0; // check not overriden by Legality settings
/// <inheritdoc cref="IsValidGen4Korean(int)"/>
public static bool IsValidGen4Korean(int pkmLanguage, SAV4 tr)
{
bool savKOR = tr.Language == (int)LanguageID.Korean;
bool pkmKOR = pkmLanguage == (int)LanguageID.Korean;
return savKOR == pkmKOR;
}
}

View file

@ -123,7 +123,7 @@ public sealed class LevelVerifier : Verifier
// Context check is only applicable to Gen1/2; transferring to Gen2 is a trade.
// Stadium 2 can transfer across game/generation boundaries without initiating a trade.
// Ignore this check if the environment's loaded trainer is not from Gen1/2 or is from GB Era.
if (ParseSettings.ActiveTrainer.Generation >= 3 || ParseSettings.AllowGBStadium2)
if (ParseSettings.AllowGBStadium2 || ParseSettings.ActiveTrainer is { Generation: not (1 or 2) })
return false;
var moves = data.Info.Moves;
@ -135,6 +135,9 @@ public sealed class LevelVerifier : Verifier
return true; // traded to Gen2 for special moves
if (pk.Format != 1)
return true; // traded to Gen2 (current state)
return !ParseSettings.IsFromActiveTrainer(pk); // not with OT
if (ParseSettings.ActiveTrainer is { } tr)
return !tr.IsFromTrainer(pk); // not with OT
return false;
}
}

View file

@ -7,11 +7,6 @@ namespace PKHeX.Core.Tests.Simulator;
public class ShowdownSetTests
{
static ShowdownSetTests()
{
ParseSettings.Settings.Handler.CheckActiveHandler = false;
}
[Fact]
public void SimulatorGetParse()
{

View file

@ -22,7 +22,6 @@ internal static class TestUtil
public static void InitializeLegality()
{
ParseSettings.Settings.Handler.CheckActiveHandler = false; // not checking in context of saves
lock (InitLock)
{
if (IsInitialized)