Exemple #1
0
    public override void Verify(LegalityAnalysis data)
    {
        if (data.Entity is not IGroundTile e)
        {
            return;
        }
        var type   = data.EncounterMatch is IGroundTypeTile t ? t.GroundTile : GroundTileAllowed.None;
        var result = !type.Contains(e.GroundTile) ? GetInvalid(LEncTypeMismatch) : GetValid(LEncTypeMatch);

        data.AddLine(result);
    }
    /// <summary>
    /// Used for enforcing a fixed memory detail.
    /// </summary>
    /// <param name="data">Output storage</param>
    /// <param name="m">Memory ID</param>
    /// <param name="i">Intensity</param>
    /// <param name="t">Text Variable</param>
    /// <param name="f">Feeling</param>
    private void VerifyOTMemoryIs(LegalityAnalysis data, byte m, byte i, ushort t, byte f)
    {
        var pk = (ITrainerMemories)data.Entity;

        if (pk.OT_Memory != m)
        {
            data.AddLine(GetInvalid(string.Format(LMemoryIndexID, L_XOT, m)));
        }
        if (pk.OT_Intensity != i)
        {
            data.AddLine(GetInvalid(string.Format(LMemoryIndexIntensity, L_XOT, i)));
        }
        if (pk.OT_TextVar != t)
        {
            data.AddLine(GetInvalid(string.Format(LMemoryIndexVar, L_XOT, t)));
        }
        if (pk.OT_Feeling != f)
        {
            data.AddLine(GetInvalid(string.Format(LMemoryIndexFeel, L_XOT, f)));
        }
    }
Exemple #3
0
    private void VerifyAwakenedValues(LegalityAnalysis data, IAwakened awakened)
    {
        var pk  = data.Entity;
        int sum = pk.EVTotal;

        if (sum != 0)
        {
            data.AddLine(GetInvalid(LEffortShouldBeZero));
        }

        if (!awakened.AwakeningAllValid())
        {
            data.AddLine(GetInvalid(LAwakenedCap));
        }

        var enc = data.EncounterMatch;

        // go park transfers have 2 AVs for all stats.
        if (enc is EncounterSlot7GO)
        {
            Span <byte> avs = stackalloc byte[6];
            awakened.GetAVs(avs);
            foreach (var av in avs)
            {
                if (av >= 2)
                {
                    continue;
                }

                data.AddLine(GetInvalid(string.Format(LAwakenedShouldBeValue, 2)));
                break;
            }
            return;
        }

        if (awakened.AwakeningSum() == 0 && !enc.IsWithinEncounterRange(pk))
        {
            data.AddLine(Get(LAwakenedEXPIncreased, Severity.Fishy));
        }
    }
Exemple #4
0
    public override void Verify(LegalityAnalysis data)
    {
        var pk = data.Entity;

        // If the Pokémon is not nicknamed, it should match one of the language strings.
        var nickname = pk.Nickname;

        if (nickname.Length == 0)
        {
            data.AddLine(GetInvalid(LNickLengthShort));
            return;
        }
        if (pk.Species > SpeciesName.SpeciesLang[0].Count)
        {
            data.AddLine(Get(LNickLengthShort, Severity.Indeterminate));
            return;
        }

        var enc = data.EncounterOriginal;

        if (enc is ILangNicknamedTemplate n)
        {
            VerifyFixedNicknameEncounter(data, n, enc, pk, nickname);
            if (pk.IsEgg)
            {
                VerifyNicknameEgg(data);
            }
            return;
        }

        if (pk.Format <= 7 && pk.IsNicknamed) // can nickname afterwards
        {
            if (pk.VC)
            {
                VerifyG1NicknameWithinBounds(data, nickname.AsSpan());
            }
            else if (enc is MysteryGift {
                IsEgg : false
            })
    private void VerifyHTMemoryTransferTo7(LegalityAnalysis data, PKM pk, LegalInfo Info)
    {
        var mem = (ITrainerMemories)pk;

        // Bank Transfer adds in the Link Trade Memory.
        // Trading 7<->7 between games (not Bank) clears this data.
        if (mem.HT_Memory == 0)
        {
            VerifyHTMemoryNone(data, mem);
            return;
        }

        // Transfer 6->7 & withdraw to same HT => keeps past gen memory
        // Don't require link trade memory for these past gen cases
        int gen = Info.Generation;

        if (gen is >= 3 and < 7 && pk.CurrentHandler == 1)
        {
            return;
        }

        if (mem.HT_Memory != 4)
        {
            data.AddLine(Severity.Invalid, LMemoryIndexLinkHT, CheckIdentifier.Memory);
        }
        if (mem.HT_TextVar != 0)
        {
            data.AddLine(Severity.Invalid, LMemoryIndexArgHT, CheckIdentifier.Memory);
        }
        if (mem.HT_Intensity != 1)
        {
            data.AddLine(Severity.Invalid, LMemoryIndexIntensityHT1, CheckIdentifier.Memory);
        }
        if (mem.HT_Feeling > 10)
        {
            data.AddLine(Severity.Invalid, LMemoryIndexFeelHT09, CheckIdentifier.Memory);
        }
    }
    public override void Verify(LegalityAnalysis data)
    {
        var pk = data.Entity;
        var pi = pk.PersonalInfo;

        if (pi.Genderless != (pk.Gender == 2))
        {
            // DP/HGSS shedinja glitch -- only generation 4 spawns
            bool ignore = pk.Format == 4 && pk.Species == (int)Species.Shedinja && pk.Met_Level != pk.CurrentLevel;
            if (!ignore)
            {
                data.AddLine(GetInvalid(LGenderInvalidNone));
            }
            return;
        }

        // Check for PID relationship to Gender & Nature if applicable
        int gen = data.Info.Generation;

        if (gen is 3 or 4 or 5)
        {
            // Gender-PID & Nature-PID relationship check
            var result = IsValidGenderPID(data) ? GetValid(LPIDGenderMatch) : GetInvalid(LPIDGenderMismatch);
            data.AddLine(result);

            if (gen != 5)
            {
                VerifyNaturePID(data);
            }
            return;
        }

        // Check fixed gender cases
        if ((pi.OnlyFemale && pk.Gender != 1) || (pi.OnlyMale && pk.Gender != 0))
        {
            data.AddLine(GetInvalid(LGenderInvalidNone));
        }
    }
Exemple #7
0
    public override void Verify(LegalityAnalysis data)
    {
        var pk = data.Entity;

        if (!TrainerNameVerifier.IsPlayerOriginalTrainer(data.EncounterMatch))
        {
            return; // already verified
        }
        if (pk.BDSP)
        {
            if (pk.TID == 0 && pk.SID == 0) // Game loops to ensure a nonzero full-ID
            {
                data.AddLine(GetInvalid(LOT_IDInvalid));
                return;
            }
            if (pk.TID == 0xFFFF && pk.SID == 0x7FFF) // int.MaxValue cannot be yielded by Unity's Random.Range[min, max)
            {
                data.AddLine(GetInvalid(LOT_IDInvalid));
                return;
            }
        }
        else if (pk.VC && pk.SID != 0)
        {
            data.AddLine(GetInvalid(LOT_SID0Invalid));
            return;
        }

        if (pk.TID == 0 && pk.SID == 0)
        {
            data.AddLine(Get(LOT_IDs0, Severity.Fishy));
        }
        else if (pk.TID == pk.SID)
        {
            data.AddLine(Get(LOT_IDEqual, Severity.Fishy));
        }
        else if (pk.TID == 0)
        {
            data.AddLine(Get(LOT_TID0, Severity.Fishy));
        }
        else if (pk.SID == 0)
        {
            data.AddLine(Get(LOT_SID0, Severity.Fishy));
        }
        else if (IsOTIDSuspicious(pk.TID, pk.SID))
        {
            data.AddLine(Get(LOTSuspicious, Severity.Fishy));
        }
    }
Exemple #8
0
    public override void Verify(LegalityAnalysis data)
    {
        if (data.Entity is not PA8 pa)
        {
            return;
        }

        if (pa.IsNoble)
        {
            data.AddLine(GetInvalid(LStatNobleInvalid));
        }
        if (pa.IsAlpha != data.EncounterMatch is IAlpha {
            IsAlpha : true
        })
Exemple #9
0
        public override void Verify(LegalityAnalysis data)
        {
            var pkm = data.pkm;

            if (pkm.Format <= 4)
            {
                return; // legal || not present
            }
            if (pkm is IContestStats s && s.HasContestStats() && !CanHaveContestStats(pkm, s, data.Info.Generation))
            {
                data.AddLine(GetInvalid(LegalityCheckStrings.LContestZero));
            }

            // some encounters have contest stats built in. they're already checked by the initial encounter match.
        }
    public override void Verify(LegalityAnalysis data)
    {
        var pk = data.Entity;
        int originalGeneration = data.Info.Generation;
        int currentLanguage    = pk.Language;
        int maxLanguageID      = Legal.GetMaxLanguageID(originalGeneration);
        var enc = data.EncounterMatch;

        if (!IsValidLanguageID(currentLanguage, maxLanguageID, pk, enc))
        {
            data.AddLine(GetInvalid(string.Format(LOTLanguage, $"<={(LanguageID)maxLanguageID}", (LanguageID)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 && !IsValidG4Korean(currentLanguage) &&
            enc is not EncounterTrade4PID {
            Species : (int)Species.Pikachu or(int) Species.Magikarp
        }                                                                                             // ger magikarp / eng pikachu
Exemple #11
0
    public override void Verify(LegalityAnalysis data)
    {
        var pk = data.pkm;

        if (!pk.LA || pk is not PA8 pa)
        {
            return;
        }

        CheckLearnset(data, pa);
        CheckMastery(data, pa);

        if (pa.IsNoble)
        {
            data.AddLine(GetInvalid(LStatNobleInvalid));
        }
        if (pa.IsAlpha != data.EncounterMatch is IAlpha {
            IsAlpha : true
        })
    public override void Verify(LegalityAnalysis data)
    {
        var pk  = data.Entity;
        var enc = data.EncounterMatch;

        if (!IsPlayerOriginalTrainer(enc))
        {
            return; // already verified
        }
        var ot = pk.OT_Name;

        if (ot.Length == 0)
        {
            data.AddLine(GetInvalid(LOTShort));
        }

        if (IsOTNameSuspicious(ot))
        {
            data.AddLine(Get(LOTSuspicious, Severity.Fishy));
        }

        if (pk.VC)
        {
            VerifyOTG1(data);
        }
        else if (ot.Length > Legal.GetMaxLengthOT(data.Info.Generation, (LanguageID)pk.Language))
        {
            if (!IsEdgeCaseLength(pk, data.EncounterOriginal, ot))
            {
                data.AddLine(Get(LOTLong, Severity.Invalid));
            }
        }

        if (ParseSettings.CheckWordFilter)
        {
            if (WordFilter.IsFiltered(ot, out string bad))
            {
                data.AddLine(GetInvalid($"Wordfilter: {bad}"));
            }
            if (ContainsTooManyNumbers(ot, data.Info.Generation))
            {
                data.AddLine(GetInvalid("Wordfilter: Too many numbers."));
            }

            if (WordFilter.IsFiltered(pk.HT_Name, out bad))
            {
                data.AddLine(GetInvalid($"Wordfilter: {bad}"));
            }
        }
    }
    public override void Verify(LegalityAnalysis data)
    {
        var  pk             = data.Entity;
        bool checksRequired = data.EncounterMatch is EncounterStatic5N;

        if (pk is PK5 pk5)
        {
            bool has = pk5.NSparkle;
            if (checksRequired && !has)
            {
                data.AddLine(GetInvalid(LG5SparkleRequired, CheckIdentifier.Fateful));
            }
            if (!checksRequired && has)
            {
                data.AddLine(GetInvalid(LG5SparkleInvalid, CheckIdentifier.Fateful));
            }
        }

        if (!checksRequired)
        {
            return;
        }

        if (pk.OT_Gender != 0)
        {
            data.AddLine(GetInvalid(LG5OTGenderN, CheckIdentifier.Trainer));
        }
        if (pk.IVTotal != 30 * 6)
        {
            data.AddLine(GetInvalid(LG5IVAll30, CheckIdentifier.IVs));
        }
        if (!VerifyNsPKMOTValid(pk))
        {
            data.AddLine(GetInvalid(LG5ID_N, CheckIdentifier.Trainer));
        }
        if (pk.IsShiny)
        {
            data.AddLine(GetInvalid(LG5PIDShinyN, CheckIdentifier.Shiny));
        }
    }
Exemple #14
0
    public override void Verify(LegalityAnalysis data)
    {
        var pk = data.Entity;

        if (pk is IAwakened a)
        {
            VerifyAwakenedValues(data, a);
            return;
        }
        var enc = data.EncounterMatch;

        if (pk.IsEgg)
        {
            if (pk.EVTotal is not 0)
            {
                data.AddLine(GetInvalid(LEffortEgg));
            }
            return;
        }

        // In Generations I and II, when a Pokémon is taken out of the Day Care, its experience will lower to the minimum value for its current level.
        int format = pk.Format;

        if (format < 3) // can abuse daycare for EV training without EXP gain
        {
            return;
        }

        int sum = pk.EVTotal;

        if (sum > 510) // format >= 3
        {
            data.AddLine(GetInvalid(LEffortAbove510));
        }
        Span <int> evs = stackalloc int[6];

        pk.GetEVs(evs);
        if (format >= 6 && evs.Find(ev => ev > 252) != default)
        {
            data.AddLine(GetInvalid(LEffortAbove252));
        }

        const int vitaMax = 100;     // Vitamin Max

        if (format < 5)              // 3/4
        {
            if (enc.LevelMin == 100) // only true for Gen4 and Format=4
            {
                // Cannot EV train at level 100 -- Certain events are distributed at level 100.
                if (evs.Find(ev => ev > vitaMax) != default) // EVs can only be increased by vitamins to a max of 100.
                {
                    data.AddLine(GetInvalid(LEffortCap100));
                }
            }
            else // check for gained EVs without gaining EXP -- don't check gen5+ which have wings to boost above 100.
            {
                var growth  = PersonalTable.HGSS[enc.Species].EXPGrowth;
                var baseEXP = Experience.GetEXP(enc.LevelMin, growth);
                if (baseEXP == pk.EXP && evs.Find(ev => ev > vitaMax) != default)
                {
                    data.AddLine(GetInvalid(string.Format(LEffortUntrainedCap, vitaMax)));
                }
            }
        }

        // Only one of the following can be true: 0, 508, and x%6!=0
        if (sum == 0 && !enc.IsWithinEncounterRange(pk))
        {
            data.AddLine(Get(LEffortEXPIncreased, Severity.Fishy));
        }
        else if (sum == 508)
        {
            data.AddLine(Get(LEffort2Remaining, Severity.Fishy));
        }
        else if (evs[0] != 0 && evs.Count(evs[0]) == evs.Length)
        {
            data.AddLine(Get(LEffortAllEqual, Severity.Fishy));
        }
    }
    public override void Verify(LegalityAnalysis data)
    {
        var pk  = data.Entity;
        var enc = data.EncounterOriginal;

        if (enc is MysteryGift gift)
        {
            if (gift.Level != pk.Met_Level && pk.HasOriginalMetLocation)
            {
                switch (gift)
                {
                case WC3 wc3 when wc3.Met_Level == pk.Met_Level || wc3.IsEgg:
                    break;

                case WC7 wc7 when wc7.MetLevel == pk.Met_Level:
                    break;

                case PGT {
                        IsManaphyEgg: true
                } when pk.Met_Level == 0 :
                    break;

                default:
                    data.AddLine(GetInvalid(LLevelMetGift));
                    return;
                }
            }
            if (gift.Level > pk.CurrentLevel)
            {
                data.AddLine(GetInvalid(LLevelMetGiftFail));
                return;
            }
        }

        if (pk.IsEgg)
        {
            int elvl = enc.LevelMin;
            if (elvl != pk.CurrentLevel)
            {
                data.AddLine(GetInvalid(string.Format(LEggFMetLevel_0, elvl)));
                return;
            }

            var reqEXP = enc is EncounterStatic2Odd
                ? 125 // Gen2 Dizzy Punch gifts always have 125 EXP, even if it's more than the Lv5 exp required.
                : Experience.GetEXP(elvl, pk.PersonalInfo.EXPGrowth);
            if (reqEXP != pk.EXP)
            {
                data.AddLine(GetInvalid(LEggEXP));
            }
            return;
        }

        int lvl = pk.CurrentLevel;

        if (lvl >= 100)
        {
            var expect = Experience.GetEXP(100, pk.PersonalInfo.EXPGrowth);
            if (pk.EXP != expect)
            {
                data.AddLine(GetInvalid(LLevelEXPTooHigh));
            }
        }

        if (lvl < pk.Met_Level)
        {
            data.AddLine(GetInvalid(LLevelMetBelow));
        }
        else if (!enc.IsWithinEncounterRange(pk) && lvl != 100 && pk.EXP == Experience.GetEXP(lvl, pk.PersonalInfo.EXPGrowth))
        {
            data.AddLine(Get(LLevelEXPThreshold, Severity.Fishy));
        }
        else
        {
            data.AddLine(GetValid(LLevelMetSane));
        }
    }
Exemple #16
0
    private void VerifyMedalsRegular(LegalityAnalysis data)
    {
        var  pk    = data.Entity;
        var  train = (ISuperTrain)pk;
        var  Info  = data.Info;
        uint value = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(data.Entity.Data.AsSpan(0x2C));

        if ((value & 3) != 0) // 2 unused flags
        {
            data.AddLine(GetInvalid(LSuperUnused));
        }
        int TrainCount = train.SuperTrainingMedalCount();

        if (pk.IsEgg)
        {
            // Can't have any super training data as an egg.
            if (TrainCount > 0)
            {
                data.AddLine(GetInvalid(LSuperEgg));
            }
            if (train.SecretSuperTrainingUnlocked)
            {
                data.AddLine(GetInvalid(LSuperNoUnlocked));
            }
            if (train.SecretSuperTrainingComplete)
            {
                data.AddLine(GetInvalid(LSuperNoComplete));
            }
            return;
        }

        if (Info.Generation is >= 7 or <= 2)
        {
            // Can't have any super training data if it never visited Gen6.
            if (TrainCount > 0)
            {
                data.AddLine(GetInvalid(LSuperUnavailable));
            }
            if (train.SecretSuperTrainingUnlocked)
            {
                data.AddLine(GetInvalid(LSuperNoUnlocked));
            }
            if (train.SecretSuperTrainingComplete)
            {
                data.AddLine(GetInvalid(LSuperNoComplete));
            }
            return;
        }

        if (pk.Format >= 7)
        {
            // Gen6->Gen7 transfer wipes the two Secret flags.
            if (train.SecretSuperTrainingUnlocked)
            {
                data.AddLine(GetInvalid(LSuperNoUnlocked));
            }
            if (train.SecretSuperTrainingComplete)
            {
                data.AddLine(GetInvalid(LSuperNoComplete));
            }
            return;
        }

        // Only reach here if Format==6.
        if (TrainCount == 30 ^ train.SecretSuperTrainingComplete)
        {
            data.AddLine(GetInvalid(LSuperComplete));
        }
    }
    private void VerifyOTMemory(LegalityAnalysis data)
    {
        var pk   = data.Entity;
        var mem  = (ITrainerMemories)pk;
        var Info = data.Info;

        // If the encounter has a memory from the OT that could never have it replaced, ensure it was not modified.
        switch (data.EncounterMatch)
        {
        case WC6 {
                IsEgg: false
        } g when g.OTGender != 3 :
            VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling);
            return;

        case WC7 {
                IsEgg: false
        } g when g.OTGender != 3 :
            VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling);
            return;

        case WC8 {
                IsEgg: false
        } g when g.OTGender != 3 :
            VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling);
            return;

        case IMemoryOT t and not MysteryGift:     // Ignore Mystery Gift cases (covered above)
            VerifyOTMemoryIs(data, t.OT_Memory, t.OT_Intensity, t.OT_TextVar, t.OT_Feeling);
            return;
        }

        int memoryGen = Info.Generation;
        var memory    = mem.OT_Memory;

        if (pk.IsEgg)
        {
            // Traded unhatched eggs in Gen8 have OT link trade memory applied erroneously.
            // They can also have the box-inspect memory!
            if (memoryGen != 8 || !((pk.Met_Location == Locations.LinkTrade6 && memory == 4) || memory == 85))
            {
                VerifyOTMemoryIs(data, 0, 0, 0, 0); // empty
                return;
            }
        }
        else if (!CanHaveMemoryForOT(pk, memoryGen, memory, Info.EvoChainsAllGens))
        {
            VerifyOTMemoryIs(data, 0, 0, 0, 0); // empty
            return;
        }

        // Bounds checking
        var context = Memories.GetContext(memoryGen);

        if (!context.CanObtainMemoryOT((GameVersion)pk.Version, memory))
        {
            data.AddLine(GetInvalid(string.Format(LMemoryArgBadID, L_XOT)));
        }

        // Verify memory if specific to OT
        switch (memory)
        {
        // No Memory
        case 0:     // SWSH trades don't set HT memories immediately, which is hilarious.
            data.AddLine(Get(LMemoryMissingOT, memoryGen == 8 ? Severity.Fishy : Severity.Invalid));
            VerifyOTMemoryIs(data, 0, 0, 0, 0);
            return;

        // {0} hatched from an Egg and saw {1} for the first time at... {2}. {4} that {3}.
        case 2 when !Info.EncounterMatch.EggEncounter:
            data.AddLine(GetInvalid(string.Format(LMemoryArgBadHatch, L_XOT)));
            break;

        // {0} became {1}’s friend when it arrived via Link Trade at... {2}. {4} that {3}.
        case 4 when Info.Generation == 6:     // gen8 applies this memory erroneously
            data.AddLine(GetInvalid(string.Format(LMemoryArgBadOTEgg, L_XOT)));
            return;

        // {0} went to the Pokémon Center in {2} with {1} and had its tired body healed there. {4} that {3}.
        case 6 when !context.HasPokeCenter((GameVersion)pk.Version, mem.OT_TextVar):
            data.AddLine(GetInvalid(string.Format(LMemoryArgBadLocation, L_XOT)));
            return;

        // {0} was with {1} when {1} caught {2}. {4} that {3}.
        case 14:
            var result = GetCanBeCaptured(mem.OT_TextVar, Info.Generation, (GameVersion)pk.Version)     // Any Game in the Handling Trainer's generation
                    ? GetValid(string.Format(LMemoryArgSpecies, L_XOT))
                    : GetInvalid(string.Format(LMemoryArgBadSpecies, L_XOT));
            data.AddLine(result);
            return;
        }

        data.AddLine(VerifyCommonMemory(pk, 0, Info.Generation, Info, context));
    }
    private void VerifyHTMemory(LegalityAnalysis data)
    {
        var pk   = data.Entity;
        var mem  = (ITrainerMemories)pk;
        var Info = data.Info;

        var memory = mem.HT_Memory;

        if (pk.IsUntraded)
        {
            if (memory == 4 && WasTradedSWSHEgg(pk))
            {
                // Untraded link trade eggs in Gen8 have HT link trade memory applied erroneously.
                // Verify the link trade memory later.
            }
            else
            {
                VerifyHTMemoryNone(data, mem);
                return;
            }
        }

        if (pk.Format == 7)
        {
            VerifyHTMemoryTransferTo7(data, pk, Info);
            return;
        }

        var memoryGen = pk.Format >= 8 ? 8 : 6;

        // Bounds checking
        var context = Memories.GetContext(memoryGen);

        if (!context.CanObtainMemoryHT((GameVersion)pk.Version, memory))
        {
            data.AddLine(GetInvalid(string.Format(LMemoryArgBadID, L_XHT)));
        }

        // Verify memory if specific to HT
        switch (memory)
        {
        // No Memory
        case 0:     // SWSH memory application has an off-by-one error: [0,99] + 1 <= chance --> don't apply
            data.AddLine(Get(LMemoryMissingHT, memoryGen == 8 ? ParseSettings.Gen8MemoryMissingHT : Severity.Invalid));
            VerifyHTMemoryNone(data, mem);
            return;

        // {0} met {1} at... {2}. {1} threw a Poké Ball at it, and they started to travel together. {4} that {3}.
        case 1:
            data.AddLine(GetInvalid(string.Format(LMemoryArgBadCatch, L_XHT)));
            return;

        // {0} hatched from an Egg and saw {1} for the first time at... {2}. {4} that {3}.
        case 2:
            data.AddLine(GetInvalid(string.Format(LMemoryArgBadHatch, L_XHT)));
            return;

        // {0} went to the Pokémon Center in {2} with {1} and had its tired body healed there. {4} that {3}.
        case 6 when !context.HasPokeCenter(GameVersion.Any, mem.HT_TextVar):
            data.AddLine(GetInvalid(string.Format(LMemoryArgBadLocation, L_XHT)));
            return;

        // {0} was with {1} when {1} caught {2}. {4} that {3}.
        case 14:
            var result = GetCanBeCaptured(mem.HT_TextVar, memoryGen, GameVersion.Any)     // Any Game in the Handling Trainer's generation
                    ? GetValid(string.Format(LMemoryArgSpecies, L_XHT))
                    : GetInvalid(string.Format(LMemoryArgBadSpecies, L_XHT));
            data.AddLine(result);
            return;
        }

        var commonResult = VerifyCommonMemory(pk, 1, memoryGen, Info, context);

        data.AddLine(commonResult);
    }
    public override void Verify(LegalityAnalysis data)
    {
        var pk = data.Entity;

        if (pk is not IContestStats s)
        {
            return;
        }

        // If no stats have been increased from the initial amount, then we're done here.
        // some encounters have contest stats built in. they're already checked by the initial encounter match.
        if (!s.HasContestStats())
        {
            return;
        }

        // Check the correlation of Stats & Sheen!
        // In generations 3,4 and BDSP, blocks/poffins have a feel(sheen) equal to sheen=sum(stats)/5, with +/- 10% for a favored stat.
        // In generation 6 (ORAS), they don't award any sheen, so any value is legal.

        var correlation = GetContestStatRestriction(pk, data.Info.Generation, data.Info.EvoChainsAllGens);

        if (correlation == None)
        {
            // We're only here because we have contest stat values. We aren't permitted to have any, so flag it.
            data.AddLine(GetInvalid(LContestZero));
        }
        else if (correlation == NoSheen)
        {
            // We can get contest stat values, but we can't get any for Sheen.
            // Any combination of non-sheen is ok, but nonzero sheen is illegal.
            if (s.CNT_Sheen != 0)
            {
                data.AddLine(GetInvalid(LContestZeroSheen));
            }
        }
        else if (correlation == CorrelateSheen)
        {
            bool gen3   = data.Info.Generation == 3;
            bool bdsp   = pk.HasVisitedBDSP(data.Info.EvoChainsAllGens.Gen8b);
            var  method = gen3 ? ContestStatGrantingSheen.Gen3 :
                          bdsp ? ContestStatGrantingSheen.Gen8b : ContestStatGrantingSheen.Gen4;

            // Check for stat values that exceed a valid sheen value.
            var initial  = GetReferenceTemplate(data.Info.EncounterMatch);
            var minSheen = CalculateMinimumSheen(s, initial, pk, method);

            if (s.CNT_Sheen < minSheen)
            {
                data.AddLine(GetInvalid(string.Format(LContestSheenTooLow_0, minSheen)));
            }

            // Check for sheen values that are too high.
            var maxSheen = CalculateMaximumSheen(s, pk.Nature, initial, gen3);
            if (s.CNT_Sheen > maxSheen)
            {
                data.AddLine(GetInvalid(string.Format(LContestSheenTooHigh_0, maxSheen)));
            }
        }
        else if (correlation == Mixed)
        {
            bool gen3 = data.Info.Generation == 3;

            // Check for sheen values that are too high.
            var initial  = GetReferenceTemplate(data.Info.EncounterMatch);
            var maxSheen = CalculateMaximumSheen(s, pk.Nature, initial, gen3);
            if (s.CNT_Sheen > maxSheen)
            {
                data.AddLine(GetInvalid(string.Format(LContestSheenTooHigh_0, maxSheen)));
            }
        }
    }
    public override void Verify(LegalityAnalysis data)
    {
        var result = VerifyAbility(data);

        data.AddLine(result);
    }