using System; using System.Collections.Generic; using System.Linq; using static PKHeX.Core.LegalityCheckStrings; using static PKHeX.Core.Encounters6; using static PKHeX.Core.Encounters8; namespace PKHeX.Core { /// /// Verifies the , , and associated values. /// public sealed class MemoryVerifier : Verifier { protected override CheckIdentifier Identifier => CheckIdentifier.Memory; public override void Verify(LegalityAnalysis data) { VerifyOTMemory(data); VerifyHTMemory(data); } private CheckResult VerifyCommonMemory(PKM pkm, int handler, int gen) { var memory = MemoryVariableSet.Read(pkm, handler); // Actionable HM moves int matchingMoveMemory = Array.IndexOf(Memories.MoveSpecificMemories[0], memory.MemoryID); if (matchingMoveMemory != -1) { // Gen8 has no HMs, so this memory can never exist. if (gen != 6 || (pkm.Species != (int)Species.Smeargle && !Legal.GetCanLearnMachineMove(pkm, Memories.MoveSpecificMemories[1][matchingMoveMemory], 6))) return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); } switch (memory.MemoryID) { // {0} saw {2} carrying {1} on its back. {4} that {3}. case 21 when gen != 6 || !Legal.GetCanLearnMachineMove(new PK6 {Species = memory.Variable, EXP = Experience.GetEXP(100, PersonalTable.XY.GetFormeIndex(memory.Variable, 0))}, 19, 6): return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); case 16 when memory.Variable == 0 && !GetIsMoveKnowable(pkm, gen, memory.Variable): case 48 when memory.Variable == 0 && !GetIsMoveKnowable(pkm, gen, memory.Variable): return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); // {0} was able to remember {2} at {1}'s instruction. {4} that {3}. case 49 when memory.Variable == 0 && !GetIsMoveLearnable(pkm, gen, memory.Variable): return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); } if (!Memories.CanHaveIntensity(memory.MemoryID, memory.Intensity)) { if (pkm.Gen6 || (pkm.Gen7 && memory.MemoryID != 0)) // todo: memory intensity checks for gen8 return GetInvalid(string.Format(LMemoryIndexIntensityMin, memory.Handler, Memories.GetMinimumIntensity(memory.MemoryID))); } if (memory.MemoryID != 4 && !Memories.CanHaveFeeling(memory.MemoryID, memory.Feeling)) { if (pkm.Gen6 || (pkm.Gen7 && memory.MemoryID != 0)) // todo: memory feeling checks for gen8 return GetInvalid(string.Format(LMemoryFeelInvalid, memory.Handler)); } return GetValid(string.Format(LMemoryF_0_Valid, memory.Handler)); } private static bool GetIsMoveKnowable(PKM pkm, int gen, int move) => Legal.GetCanKnowMove(pkm, move, gen); private static bool GetIsMoveLearnable(PKM pkm, int gen, int move) => Legal.GetCanRelearnMove(pkm, move, gen); /// /// Used for enforcing a fixed memory detail. /// /// Output storage /// Memory ID /// Intensity /// Text Variable /// Feeling private void VerifyOTMemoryIs(LegalityAnalysis data, int m, int i, int t, int f) { var pkm = data.pkm; if (pkm.OT_Memory != m) data.AddLine(GetInvalid(string.Format(LMemoryIndexID, L_XOT, m))); if (pkm.OT_Intensity != i) data.AddLine(GetInvalid(string.Format(LMemoryIndexIntensity, L_XOT, i))); if (pkm.OT_TextVar != t) data.AddLine(GetInvalid(string.Format(LMemoryIndexVar, L_XOT, t))); if (pkm.OT_Feeling != f) data.AddLine(GetInvalid(string.Format(LMemoryIndexFeel, L_XOT, f))); } private void VerifyHTMemoryNone(LegalityAnalysis data, PKM pkm) { if (pkm.HT_Memory != 0 || pkm.HT_TextVar != 0 || pkm.HT_Intensity != 0 || pkm.HT_Feeling != 0) data.AddLine(GetInvalid(string.Format(LMemoryCleared, L_XHT))); } private void VerifyOTMemory(LegalityAnalysis data) { var pkm = data.pkm; var Info = data.Info; switch (data.EncounterMatch) { case WC6 g when !g.IsEgg && g.OTGender != 3: VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling); return; case WC7 g when !g.IsEgg && g.OTGender != 3: VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling); return; case WC8 g when !g.IsEgg && g.OTGender != 3: VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling); return; case IMemoryOT t when !(t is 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; int memory = pkm.OT_Memory; if (!CanHaveMemory(pkm, memoryGen, memory)) { VerifyOTMemoryIs(data, 0, 0, 0, 0); // empty return; } // Bounds checking switch (memoryGen) { case 6 when pkm.XY && (memory > Memories.MAX_MEMORY_ID_XY || Memories.Memory_NotXY.Contains(memory)): case 6 when pkm.AO && (memory > Memories.MAX_MEMORY_ID_AO || Memories.Memory_NotAO.Contains(memory)): case 8 when pkm.SWSH && (memory > Memories.MAX_MEMORY_ID_SWSH || Memories.Memory_NotSWSH.Contains(memory)): data.AddLine(GetInvalid(string.Format(LMemoryArgBadID, L_XOT))); break; } // 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 pkm.Gen6: 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 memoryGen == 6 && !Memories.GetHasPokeCenterLocation((GameVersion)pkm.Version, pkm.OT_TextVar): data.AddLine(GetInvalid(string.Format(LMemoryArgBadLocation, L_XOT))); return; case 6 when memoryGen == 8 && pkm.OT_TextVar != 0: data.AddLine(Get(string.Format(LMemoryArgBadLocation, L_XOT), ParseSettings.Gen8MemoryLocationTextVariable)); return; // {0} was with {1} when {1} caught {2}. {4} that {3}. case 14: var result = GetCanBeCaptured(pkm.OT_TextVar, Info.Generation, (GameVersion)pkm.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(pkm, 0, Info.Generation)); } private static bool CanHaveMemory(PKM pkm, int origin, int memory) { if (pkm.GG) // LGPE never assigns memories return false; if ((pkm.VC || pkm.Gen7) && memory != 4) // Generation 7 - Trade memory or nothing return memory == 4; if (origin < 6) // NDS/3DS return false; if (pkm.IsEgg) // Eggs should not have memories return false; return true; } private void VerifyHTMemory(LegalityAnalysis data) { var pkm = data.pkm; var Info = data.Info; var memory = pkm.HT_Memory; if (pkm.IsUntraded) { if (memory == 4 && WasTradedSWSHEgg(pkm)) { // Untraded link trade eggs in Gen8 have HT link trade memory applied erroneously. // Verify the link trade memory later. } else { VerifyHTMemoryNone(data, pkm); return; } } if (pkm.Format == 7) { VerifyHTMemoryTransferTo7(data, pkm, Info); return; } var memoryGen = pkm.Format >= 8 ? 8 : 6; // Bounds checking switch (memoryGen) { case 6 when memory > Memories.MAX_MEMORY_ID_AO: case 8 when memory > Memories.MAX_MEMORY_ID_SWSH || Memories.Memory_NotSWSH.Contains(memory): data.AddLine(GetInvalid(string.Format(LMemoryArgBadID, L_XHT))); break; } // Verify memory if specific to HT switch (memory) { // No Memory case 0: // SWSH trades don't set HT memories immediately, which is hilarious. data.AddLine(Get(LMemoryMissingHT, memoryGen == 8 ? Severity.Fishy : Severity.Invalid)); VerifyHTMemoryNone(data, pkm); 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 memoryGen == 6 && !Memories.GetHasPokeCenterLocation(GameVersion.Gen6, pkm.HT_TextVar): data.AddLine(GetInvalid(string.Format(LMemoryArgBadLocation, L_XOT))); return; case 6 when memoryGen == 8 && pkm.HT_TextVar != 0: data.AddLine(Get(string.Format(LMemoryArgBadLocation, L_XOT), ParseSettings.Gen8MemoryLocationTextVariable)); return; // {0} was with {1} when {1} caught {2}. {4} that {3}. case 14: var result = GetCanBeCaptured(pkm.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(pkm, 1, memoryGen); data.AddLine(commonResult); } private static bool WasTradedSWSHEgg(PKM pkm) => pkm.Gen8 && (pkm.WasTradedEgg || pkm.WasBredEgg); private void VerifyHTMemoryTransferTo7(LegalityAnalysis data, PKM pkm, LegalInfo Info) { // Bank Transfer adds in the Link Trade Memory. // Trading 7<->7 between games (not Bank) clears this data. if (pkm.HT_Memory == 0) { VerifyHTMemoryNone(data, pkm); 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 (3 <= gen && gen < 7 && pkm.CurrentHandler == 1) return; if (pkm.HT_Memory != 4) data.AddLine(Severity.Invalid, LMemoryIndexLinkHT, CheckIdentifier.Memory); if (pkm.HT_TextVar != 0) data.AddLine(Severity.Invalid, LMemoryIndexArgHT, CheckIdentifier.Memory); if (pkm.HT_Intensity != 1) data.AddLine(Severity.Invalid, LMemoryIndexIntensityHT1, CheckIdentifier.Memory); if (pkm.HT_Feeling > 10) data.AddLine(Severity.Invalid, LMemoryIndexFeelHT09, CheckIdentifier.Memory); } private static bool GetCanBeCaptured(int species, int gen, GameVersion version) { switch (gen) { // Capture Memory only obtainable via Gen 6. case 6: switch (version) { case GameVersion.Any: return Legal.FriendSafari.Contains(species) || GetCanBeCaptured(species, SlotsX, StaticX) || GetCanBeCaptured(species, SlotsY, StaticY) || GetCanBeCaptured(species, SlotsA, StaticA) || GetCanBeCaptured(species, SlotsO, StaticO); case GameVersion.X: return Legal.FriendSafari.Contains(species) || GetCanBeCaptured(species, SlotsX, StaticX); case GameVersion.Y: return Legal.FriendSafari.Contains(species) || GetCanBeCaptured(species, SlotsY, StaticY); case GameVersion.AS: return GetCanBeCaptured(species, SlotsA, StaticA); case GameVersion.OR: return GetCanBeCaptured(species, SlotsO, StaticO); } break; case 8: { switch (version) { case GameVersion.Any: return GetCanBeCaptured(species, SlotsSW.Concat(SlotsSH), StaticSW.Concat(StaticSH)); case GameVersion.SW: return GetCanBeCaptured(species, SlotsSW, StaticSW); case GameVersion.SH: return GetCanBeCaptured(species, SlotsSH, StaticSH); } break; } } return false; } private static bool GetCanBeCaptured(int species, IEnumerable area, IEnumerable statics) { if (area.Any(loc => loc.Slots.Any(slot => slot.Species == species))) return true; if (statics.Any(enc => enc.Species == species && !enc.Gift)) return true; return false; } } }