#define SUPPRESS using System; using System.Collections.Generic; using System.Linq; 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 pkm; /// 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 PersonalInfo PersonalInfo; private readonly List Parse = new(8); /// /// Parse result list allowing view of the legality parse. /// public IReadOnlyList Results => Parse; /// /// Only use this when trying to mutate the legality. Not for use when checking legality. /// public void ResetParse() => Parse.Clear(); /// /// 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; 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, PersonalTable 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, PersonalInfo pi, SlotOrigin source = SlotOrigin.Party) { pkm = pk; PersonalInfo = pi; SlotOrigin = source; if (pkm.Format <= 2) // prior to storing GameVersion pkm.TradebackStatus = GBRestrictions.GetTradebackStatusInitial(pkm); Info = new LegalInfo(pkm, Parse); #if SUPPRESS try #endif { EncounterFinder.FindVerifiedEncounter(pkm, Info); if (!pkm.IsOriginValid) AddLine(Severity.Invalid, LEncConditionBadSpecies, CheckIdentifier.GameOrigin); GetParseMethod()(); Valid = Parse.All(chk => chk.Valid) && Info.Moves.All(m => m.Valid) && Info.Relearn.All(m => m.Valid); if (!Valid && IsPotentiallyMysteryGift(Info, pkm)) AddLine(Severity.Indeterminate, 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. #pragma warning disable CA1031 // Do not catch general exception types catch (Exception e) #pragma warning restore CA1031 // Do not catch general exception types { System.Diagnostics.Debug.WriteLine(e.Message); Valid = false; var moves = Info.Moves; // Moves and Relearn arrays can potentially be empty on error. // ReSharper disable once ConstantNullCoalescingCondition for (int i = 0; i < moves.Length; i++) moves[i] ??= new CheckMoveResult(MoveSource.None, pkm.Format, Severity.Indeterminate, L_AError, CheckIdentifier.CurrentMove); var relearn = Info.Relearn; // ReSharper disable once ConstantNullCoalescingCondition for (int i = 0; i < relearn.Length; i++) relearn[i] ??= new CheckResult(Severity.Indeterminate, L_AError, CheckIdentifier.RelearnMove); 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 true; if (!pk.FatefulEncounter) return false; if (enc.Generation < 6) return true; if (info.Relearn.Any(chk => !chk.Valid)) return true; return false; } private Action GetParseMethod() { if (pkm.Format <= 2) // prior to storing GameVersion return ParsePK1; int gen = pkm.Generation; if (gen <= 0) gen = pkm.Format; return gen switch { 3 => ParsePK3, 4 => ParsePK4, 5 => ParsePK5, 6 => ParsePK6, 1 => ParsePK7, 2 => ParsePK7, 7 => ParsePK7, 8 => ParsePK8, _ => throw new Exception() }; } private void ParsePK1() { if (pkm.TradebackStatus == TradebackType.Any && Info.Generation != pkm.Format) pkm.TradebackStatus = TradebackType.WasTradeback; // Example: GSC Pokemon with only possible encounters in RBY, like the legendary birds Nickname.Verify(this); Level.Verify(this); Level.VerifyG1(this); Trainer.VerifyOTG1(this); MiscValues.VerifyMiscG1(this); if (pkm.Format == 2) Item.Verify(this); } private void ParsePK3() { UpdateChecks(); if (pkm.Format > 3) Transfer.VerifyTransferLegalityG3(this); if (pkm.Version == (int)GameVersion.CXD) CXD.Verify(this); if (Info.EncounterMatch is WC3 {NotDistributed: true}) AddLine(Severity.Invalid, LEncUnreleased, CheckIdentifier.Encounter); if (pkm.Format >= 8) Transfer.VerifyTransferLegalityG8(this); } private void ParsePK4() { UpdateChecks(); if (pkm.Format > 4) Transfer.VerifyTransferLegalityG4(this); if (pkm.Format >= 8) Transfer.VerifyTransferLegalityG8(this); } private void ParsePK5() { UpdateChecks(); NHarmonia.Verify(this); if (pkm.Format >= 8) Transfer.VerifyTransferLegalityG8(this); } private void ParsePK6() { UpdateChecks(); if (pkm.Format >= 8) Transfer.VerifyTransferLegalityG8(this); } private void ParsePK7() { if (pkm.VC) UpdateVCTransferInfo(); UpdateChecks(); if (pkm.Format >= 8) Transfer.VerifyTransferLegalityG8(this); } private void ParsePK8() { UpdateChecks(); Transfer.VerifyTransferLegalityG8(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, c, i)); /// /// 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 = EncounterStaticGenerator.GetVCStaticTransferEncounter(pkm, enc, Info.EvoChainsAllGens[7]); Info.EncounterMatch = vc; foreach (var z in Transfer.VerifyVCEncounter(pkm, enc, vc, Info.Moves)) AddLine(z); Transfer.VerifyTransferLegalityG12(this); } private void UpdateChecks() { PIDEC.Verify(this); Nickname.Verify(this); LanguageIndex.Verify(this); Trainer.Verify(this); IndividualValues.Verify(this); EffortValues.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); var format = pkm.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 (pkm is ITrainerMemories) Memory.Verify(this); if (pkm is ISuperTrain) Medal.Verify(this); if (format < 7) return; HyperTraining.Verify(this); MiscValues.VerifyVersionEvolution(this); if (format < 8) return; Mark.Verify(this); } } }