#define SUPPRESS using System; using System.Collections.Generic; using static PKHeX.Core.LegalityAnalyzers; using static PKHeX.Core.LegalityCheckStrings; namespace PKHeX.Core; /// /// Legality Check object containing the data and overview values from the parse. /// public sealed class LegalityAnalysis { /// The entity we are checking. internal readonly PKM Entity; /// The entity's , which may have been sourced from the Save File it resides on. /// We store this rather than re-fetching, as some games that use the same format have different values. internal readonly IPersonalInfo PersonalInfo; private readonly List Parse = new(8); /// /// Parse result list allowing view of the legality parse. /// public IReadOnlyList Results => Parse; /// /// Matched encounter data for the . /// public IEncounterable EncounterMatch => Info.EncounterMatch; /// /// Original encounter data for the . /// /// /// Generation 1/2 that are transferred forward to Generation 7 are restricted to new encounter details. /// By retaining their original match, more information can be provided by the parse. /// public IEncounterable EncounterOriginal => Info.EncounterOriginal; /// /// Indicates where the originated. /// public readonly SlotOrigin SlotOrigin; /// /// Indicates if all checks ran to completion. /// /// This value is false if any checks encountered an error. public readonly bool Parsed; /// /// Indicates if all checks returned a result. /// public readonly bool Valid; /// /// Contains various data reused for multiple checks. /// public readonly LegalInfo Info; /// /// Checks the input data for legality. This is the best method for checking with context, as some games do not have all Alternate Form data available. /// /// Input data to check /// specific personal data /// Details about where the originated from. public LegalityAnalysis(PKM pk, IPersonalTable table, SlotOrigin source = SlotOrigin.Party) : this(pk, table.GetFormEntry(pk.Species, pk.Form), source) { } /// /// Checks the input data for legality. /// /// Input data to check /// Details about where the originated from. public LegalityAnalysis(PKM pk, SlotOrigin source = SlotOrigin.Party) : this(pk, pk.PersonalInfo, source) { } /// /// Checks the input data for legality. /// /// Input data to check /// Personal info to parse with /// Details about where the originated from. public LegalityAnalysis(PKM pk, IPersonalInfo pi, SlotOrigin source = SlotOrigin.Party) { Entity = pk; PersonalInfo = pi; SlotOrigin = source; Info = new LegalInfo(pk, Parse); #if SUPPRESS try #endif { EncounterFinder.FindVerifiedEncounter(pk, Info); if (!pk.IsOriginValid) AddLine(Severity.Invalid, LEncConditionBadSpecies, CheckIdentifier.GameOrigin); GetParseMethod()(); Valid = Parse.TrueForAll(chk => chk.Valid) && MoveResult.AllValid(Info.Moves) && MoveResult.AllValid(Info.Relearn); if (!Valid && IsPotentiallyMysteryGift(Info, pk)) AddLine(Severity.Invalid, LFatefulGiftMissing, CheckIdentifier.Fateful); Parsed = true; } #if SUPPRESS // We want to swallow any error from malformed input data from the user. The Valid state is all that we really need. catch (Exception e) { System.Diagnostics.Debug.WriteLine(e.Message); Valid = false; // Moves and Relearn arrays can potentially be empty on error. var moves = Info.Moves; for (var i = 0; i < moves.Length; i++) { ref var p = ref moves[i]; if (!p.IsParsed) p = MoveResult.Unobtainable(); } moves = Info.Relearn; for (var i = 0; i < moves.Length; i++) { ref var p = ref moves[i]; if (!p.IsParsed) p = MoveResult.Unobtainable(); } AddLine(Severity.Invalid, L_AError, CheckIdentifier.Misc); } #endif } private static bool IsPotentiallyMysteryGift(LegalInfo info, PKM pk) { if (info.EncounterOriginal is not EncounterInvalid enc) return false; if (enc.Generation <= 3) return pk.Format <= 3; if (!pk.FatefulEncounter) return false; if (enc.Generation < 6) return true; if (!MoveResult.AllValid(info.Relearn)) return true; return false; } private Action GetParseMethod() { if (Entity.Format <= 2) // prior to storing GameVersion return ParsePK1; var gen = GetParseFormat(); return gen switch { 3 => ParsePK3, 4 => ParsePK4, 5 => ParsePK5, 6 => ParsePK6, 1 => ParsePK7, 2 => ParsePK7, 7 => ParsePK7, 8 => ParsePK8, 9 => ParsePK9, _ => throw new ArgumentOutOfRangeException(nameof(gen)), }; } private int GetParseFormat() { var gen = Entity.Generation; if (gen != 0) return gen; if (Entity is PK9 { IsUnhatchedEgg: true }) return 9; return Entity.Format; } private void ParsePK1() { Nickname.Verify(this); Level.Verify(this); Level.VerifyG1(this); Trainer.VerifyOTGB(this); MiscValues.VerifyMiscG1(this); if (Entity.Format == 2) Item.Verify(this); } private void ParsePK3() { UpdateChecks(); if (Entity.Format > 3) Transfer.VerifyTransferLegalityG3(this); if (Entity.Version == GameVersion.CXD) CXD.Verify(this); if (Info.EncounterMatch is WC3 {NotDistributed: true}) AddLine(Severity.Invalid, LEncUnreleased, CheckIdentifier.Encounter); if (Entity.Format >= 8) Transfer.VerifyTransferLegalityG8(this); } private void ParsePK4() { UpdateChecks(); if (Entity.Format > 4) Transfer.VerifyTransferLegalityG4(this); if (Entity.Format >= 8) Transfer.VerifyTransferLegalityG8(this); } private void ParsePK5() { UpdateChecks(); if (Entity.Format >= 8) Transfer.VerifyTransferLegalityG8(this); } private void ParsePK6() { UpdateChecks(); if (Entity.Format >= 8) Transfer.VerifyTransferLegalityG8(this); } private void ParsePK7() { if (Entity.VC) UpdateVCTransferInfo(); UpdateChecks(); if (Entity.Format >= 8) Transfer.VerifyTransferLegalityG8(this); else if (Entity is PB7) Awakening.Verify(this); } private void ParsePK8() { UpdateChecks(); Transfer.VerifyTransferLegalityG8(this); } private void ParsePK9() { UpdateChecks(); Transfer.VerifyTransferLegalityG9(this); } /// /// Adds a new Check parse value. /// /// Check severity /// Check comment /// Check type internal void AddLine(Severity s, string c, CheckIdentifier i) => AddLine(new CheckResult(s, i, c)); /// /// Adds a new Check parse value. /// /// Check result to add. internal void AddLine(CheckResult chk) => Parse.Add(chk); private void UpdateVCTransferInfo() { var enc = (Info.EncounterOriginalGB = EncounterMatch); if (enc is EncounterInvalid) return; var vc = EncounterGenerator7.GetVCStaticTransferEncounter(Entity, enc.Species, Info.EvoChainsAllGens.Gen7); Info.EncounterMatch = vc; Transfer.VerifyVCEncounter(Entity, enc, vc, this); Transfer.VerifyTransferLegalityG12(this); } private void UpdateChecks() { PIDEC.Verify(this); Nickname.Verify(this); LanguageIndex.Verify(this); Trainer.Verify(this); TrainerID.Verify(this); IVs.Verify(this); EVs.Verify(this); Level.Verify(this); Ribbon.Verify(this); AbilityValues.Verify(this); BallIndex.Verify(this); FormValues.Verify(this); MiscValues.Verify(this); GenderValues.Verify(this); Item.Verify(this); Contest.Verify(this); Marking.Verify(this); var format = Entity.Format; if (format is 4 or 5 or 6) // Gen 6->7 transfer removes this property. Gen4GroundTile.Verify(this); if (format < 6) return; History.Verify(this); if (format < 8) // Gen 7->8 transfer removes these properties. ConsoleRegion.Verify(this); if (Entity is ITrainerMemories) Memory.Verify(this); if (Entity is ISuperTrain) Medal.Verify(this); if (format < 7) return; HyperTraining.Verify(this); MiscValues.VerifyVersionEvolution(this); if (format < 8) return; Mark.Verify(this); Arceus.Verify(this); } }