using System; using System.Collections.Generic; using static PKHeX.Core.LegalityCheckStrings; namespace PKHeX.Core; /// /// Verifies the stat details of data that has not yet left . /// public sealed class LegendsArceusVerifier : Verifier { protected override CheckIdentifier Identifier => CheckIdentifier.RelearnMove; 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 }) data.AddLine(GetInvalid(LStatAlphaInvalid)); CheckScalars(data, pa); CheckGanbaru(data, pa); } private static void CheckGanbaru(LegalityAnalysis data, PA8 pa) { for (int i = 0; i < 6; i++) { var gv = pa.GetGV(i); var max = pa.GetMaxGanbaru(i); if (gv <= max) continue; data.AddLine(GetInvalid(LGanbaruStatTooHigh, CheckIdentifier.EVs)); return; } } private void CheckScalars(LegalityAnalysis data, PA8 pa) { // Static encounters hard-match the Height & Weight; only slots are unchecked for Alpha Height/Weight. if (pa.IsAlpha && data.EncounterMatch is EncounterSlot8a) { if (pa.HeightScalar != 255) data.AddLine(GetInvalid(LStatIncorrectHeightValue)); if (pa.WeightScalar != 255) data.AddLine(GetInvalid(LStatIncorrectWeightValue)); } // No way to mutate the display height scalar value. Must match! if (pa.HeightScalar != pa.HeightScalarCopy) data.AddLine(GetInvalid(LStatIncorrectHeightCopy, CheckIdentifier.Encounter)); } private static void CheckLearnset(LegalityAnalysis data, PA8 pa) { var moveCount = GetMoveCount(pa); if (moveCount == 4) return; // Get the bare minimum moveset. Span expect = stackalloc int[4]; var minMoveCount = LoadBareMinimumMoveset(data.EncounterMatch, data.Info.EvoChainsAllGens[8], pa, expect); // Flag move slots that are empty. for (int i = moveCount; i < minMoveCount; i++) { // Expected move should never be empty, but just future-proof against any revisions. var msg = expect[i] != 0 ? string.Format(LMoveFExpect_0, ParseSettings.MoveStrings[expect[i]]) : LMoveSourceEmpty; data.Info.Moves[i] = new CheckMoveResult(data.Info.Moves[i], Severity.Invalid, msg, CheckIdentifier.CurrentMove); } } /// /// Gets the expected minimum count of moves, and modifies the input with the bare minimum move IDs. /// private static int LoadBareMinimumMoveset(ISpeciesForm enc, IReadOnlyList evos, PA8 pa, Span moves) { // Get any encounter moves var pt = PersonalTable.LA; var index = pt.GetFormIndex(enc.Species, enc.Form); var moveset = Legal.LevelUpLA[index]; moveset.SetEncounterMoves(pa.Met_Level, moves); var count = moves.IndexOf(0); if ((uint)count >= 4) return 4; var purchasedCount = pa.GetPurchasedCount(); Span purchased = stackalloc int[purchasedCount]; LoadPurchasedMoves(pa, purchased); // Level up to current level moveset.SetLevelUpMoves(pa.Met_Level, pa.CurrentLevel, moves, purchased, count); count = moves.IndexOf(0); if ((uint)count >= 4) return 4; // Evolve and try for (int i = 0; i < evos.Count - 1; i++) { var (species, form) = evos[i]; index = pt.GetFormIndex(species, form); moveset = Legal.LevelUpLA[index]; moveset.SetEvolutionMoves(moves, purchased, count); count = moves.IndexOf(0); if ((uint)count >= 4) return 4; } // Any tutored moves we don't know about?? return AddMasteredMissing(pa, moves, count); } private static void LoadPurchasedMoves(IMoveShop8 pa, Span result) { int ctr = 0; var purchased = pa.MoveShopPermitIndexes; for (int i = 0; i < purchased.Length; i++) { if (pa.GetPurchasedRecordFlag(i)) result[ctr++] = purchased[i]; } } private static int AddMasteredMissing(PA8 pa, Span current, int ctr) { for (int i = 0; i < pa.MoveShopPermitIndexes.Length; i++) { // Buying the move tutor grants access, but does not learn the move. // Mastering requires the move to be present in the movepool. if (!pa.GetMasteredRecordFlag(i)) continue; // Purchased moves can be swapped with existing moves; we're only interested in special granted moves. if (pa.GetPurchasedRecordFlag(i)) continue; var move = pa.MoveShopPermitIndexes[i]; if (current.IndexOf(move) == -1) current[ctr++] = move; if (ctr == 4) return 4; } return ctr; } private static int GetMoveCount(PKM pa) { var count = 0; for (int i = 0; i < 4; i++) { if (pa.GetMove(i) is not 0) count++; } return count; } private void CheckMastery(LegalityAnalysis data, PA8 pa) { var bits = pa.MoveShopPermitFlags; var moves = pa.MoveShopPermitIndexes; var alphaMove = pa.AlphaMove; if (alphaMove is not 0) VerifyAlphaMove(data, pa, alphaMove, moves, bits); else VerifyAlphaMoveZero(data); for (int i = 0; i < bits.Length; i++) VerifyTutorMoveIndex(data, pa, i, bits, moves); } private void VerifyTutorMoveIndex(LegalityAnalysis data, PA8 pa, int i, ReadOnlySpan bits, ReadOnlySpan moves) { bool isPurchased = pa.GetPurchasedRecordFlag(i); if (isPurchased) { // Check if the move can be purchased. if (bits[i]) return; // If it has been legally purchased, then any mastery state is legal. data.AddLine(GetInvalid(string.Format(LMoveShopPurchaseInvalid_0, ParseSettings.MoveStrings[moves[i]]))); return; } bool isMastered = pa.GetMasteredRecordFlag(i); if (!isMastered) return; // All good. // Check if the move can be purchased; using a Mastery Seed checks the permission. if (pa.AlphaMove == moves[i]) return; // Previously checked. if (data.EncounterMatch is IAlpha { IsAlpha: true } && CanMasterMoveFromMoveShop(moves[i], moves, bits)) return; // Alpha forced move. if (!bits[i]) data.AddLine(GetInvalid(string.Format(LMoveShopMasterInvalid_0, ParseSettings.MoveStrings[moves[i]]))); else if (!CanLearnMoveByLevelUp(data, pa, i, moves)) data.AddLine(GetInvalid(string.Format(LMoveShopMasterNotLearned_0, ParseSettings.MoveStrings[moves[i]]))); } private static bool CanLearnMoveByLevelUp(LegalityAnalysis data, PA8 pa, int i, ReadOnlySpan moves) { // Check if the move can be learned in the learnset... // Changing forms do not have separate tutor permissions, so we don't need to bother with form changes. // Level up movepools can grant moves for mastery at lower levels for earlier evolutions... find the minimum. int level = 101; foreach (var (species, form) in data.Info.EvoChainsAllGens[8]) { var pt = PersonalTable.LA; var index = pt.GetFormIndex(species, form); var moveset = Legal.LevelUpLA[index]; var lvl = moveset.GetLevelLearnMove(moves[i]); if (lvl == -1) continue; // cannot learn via level up level = Math.Min(lvl, level); } return pa.CurrentLevel >= level; } private void VerifyAlphaMove(LegalityAnalysis data, PA8 pa, int alphaMove, ReadOnlySpan moves, ReadOnlySpan bits) { if (!pa.IsAlpha || data.EncounterMatch is EncounterSlot8a { Type: SlotType.Landmark }) { data.AddLine(GetInvalid(LMoveShopAlphaMoveShouldBeZero)); return; } if (!CanMasterMoveFromMoveShop(alphaMove, moves, bits)) { data.AddLine(GetInvalid(LMoveShopAlphaMoveShouldBeOther)); return; } // An Alpha Move must be marked as mastered. var masteredIndex = moves.IndexOf(alphaMove); // Index is already >= 0, implicitly via the above call not returning false. if (!pa.GetMasteredRecordFlag(masteredIndex)) data.AddLine(GetInvalid(LMoveShopAlphaMoveShouldBeMastered)); } private void VerifyAlphaMoveZero(LegalityAnalysis data) { var enc = data.Info.EncounterMatch; if (enc is not IAlpha { IsAlpha: true }) return; // okay if (enc is EncounterSlot8a { Type: SlotType.Landmark }) return; // okay var pi = PersonalTable.LA.GetFormEntry(enc.Species, enc.Form); var tutors = pi.SpecialTutors[0]; bool hasAnyTutor = Array.IndexOf(tutors, true) >= 0; if (hasAnyTutor) // must have had a tutor flag data.AddLine(GetInvalid(LMoveShopAlphaMoveShouldBeOther)); } private static bool CanMasterMoveFromMoveShop(int move, ReadOnlySpan moves, ReadOnlySpan bits) { var index = moves.IndexOf(move); if (index == -1) return false; // not in the list if (!bits[index]) return false; // not a possible move return true; } }