using System; using System.Collections.Generic; using System.Linq; using static PKHeX.Core.LegalityCheckStrings; namespace PKHeX.Core { public static class EncounterVerifier { /// /// Gets the method to verify the data. /// /// Source data to verify /// Returns the verification method appropriate for the input PKM public static Func GetEncounterVerifierMethod(PKM pkm) { switch (pkm.GenNumber) { case 1: case 2: return VerifyEncounterG12; default: return VerifyEncounter; } } private static CheckResult VerifyEncounter(PKM pkm, LegalInfo info) { switch (info.EncounterMatch) { case EncounterEgg e: return VerifyEncounterEgg(pkm, e); case EncounterLink l: return VerifyEncounterLink(pkm, l); case EncounterTrade t: return VerifyEncounterTrade(pkm, t); case EncounterSlot w: return VerifyEncounterWild(pkm, w); case EncounterStatic s: return VerifyEncounterStatic(pkm, s); case MysteryGift g: return VerifyEncounterEvent(pkm, g); default: return new CheckResult(Severity.Invalid, V80, CheckIdentifier.Encounter); } } private static CheckResult VerifyEncounterG12(PKM pkm, LegalInfo info) { var EncounterMatch = info.EncounterMatch; if (EncounterMatch.EggEncounter) { pkm.WasEgg = true; return VerifyEncounterEgg(pkm, EncounterMatch); } if (EncounterMatch is EncounterSlot1 l) { if (info.Generation == 2) return VerifyWildEncounterGen2(pkm, l); return new CheckResult(Severity.Valid, V68, CheckIdentifier.Encounter); } if (EncounterMatch is EncounterStatic s) return VerifyEncounterStatic(pkm, s); if (EncounterMatch is EncounterTrade t) return VerifyEncounterTrade(pkm, t); return new CheckResult(Severity.Invalid, V80, CheckIdentifier.Encounter); } // Gen2 Wild Encounters private static CheckResult VerifyWildEncounterGen2(PKM pkm, EncounterSlot1 encounter) { switch (encounter.Type) { // Fishing in the beta gen 2 Safari Zone case SlotType.Old_Rod_Safari: case SlotType.Good_Rod_Safari: case SlotType.Super_Rod_Safari: return new CheckResult(Severity.Invalid, V609, CheckIdentifier.Encounter); } if (encounter.Version == GameVersion.C) return VerifyWildEncounterCrystal(pkm, encounter); return new CheckResult(Severity.Valid, V68, CheckIdentifier.Encounter); } private static CheckResult VerifyWildEncounterCrystal(PKM pkm, EncounterSlot encounter) { switch (encounter.Type) { case SlotType.Headbutt: case SlotType.Headbutt_Special: return VerifyWildEncounterCrystalHeadbutt(pkm, encounter); case SlotType.Old_Rod: case SlotType.Good_Rod: case SlotType.Super_Rod: switch (encounter.Location) { case 19: // National Park return new CheckResult(Severity.Invalid, V608, CheckIdentifier.Encounter); case 76: // Route 14 return new CheckResult(Severity.Invalid, V607, CheckIdentifier.Encounter); } break; } return new CheckResult(Severity.Valid, V68, CheckIdentifier.Encounter); } private static CheckResult VerifyWildEncounterCrystalHeadbutt(PKM pkm, EncounterSlot encounter) { var Area = Legal.GetCrystalTreeArea(encounter); if (Area == null) // Failsafe, every area with headbutt encounters has a tree area return new CheckResult(Severity.Invalid, V605, CheckIdentifier.Encounter); var table = Area.GetTrees(encounter.Type); var trainerpivot = pkm.TID % 10; switch (table[trainerpivot]) { case TreeEncounterAvailable.ValidTree: return new CheckResult(Severity.Valid, V604, CheckIdentifier.Encounter); case TreeEncounterAvailable.InvalidTree: return new CheckResult(Severity.Invalid, V605, CheckIdentifier.Encounter); default: // Impossible return new CheckResult(Severity.Invalid, V606, CheckIdentifier.Encounter); } } // Eggs private static CheckResult VerifyEncounterEgg(PKM pkm, IEncounterable egg) { pkm.WasEgg = true; // Check Species if (Legal.NoHatchFromEgg.Contains(pkm.Species)) return new CheckResult(Severity.Invalid, V50, CheckIdentifier.Encounter); switch (pkm.GenNumber) { case 1: case 2: return new CheckResult(CheckIdentifier.Encounter); // no met location info case 3: return pkm.Format != 3 ? VerifyEncounterEgg3Transfer(pkm) : VerifyEncounterEgg3(pkm); case 4: return pkm.IsEgg ? VerifyUnhatchedEgg(pkm, 02002) : VerifyEncounterEgg4(pkm); case 5: return pkm.IsEgg ? VerifyUnhatchedEgg(pkm, 30003) : VerifyEncounterEgg5(pkm); case 6: return pkm.IsEgg ? VerifyUnhatchedEgg(pkm, 30002) : VerifyEncounterEgg6(pkm); case 7: return pkm.IsEgg ? VerifyUnhatchedEgg(pkm, 30002) : VerifyEncounterEgg7(pkm); default: // none of the above return new CheckResult(Severity.Invalid, V51, CheckIdentifier.Encounter); } } private static CheckResult VerifyEncounterEgg3(PKM pkm) { return pkm.Format == 3 ? VerifyEncounterEgg3Native(pkm) : VerifyEncounterEgg3Transfer(pkm); } private static CheckResult VerifyEncounterEgg3Native(PKM pkm) { if (pkm.Met_Level != 0) return new CheckResult(Severity.Invalid, string.Format(V52, 0), CheckIdentifier.Encounter); if (pkm.IsEgg) { var loc = pkm.FRLG ? Legal.ValidEggMet_FRLG : Legal.ValidEggMet_RSE; if (!loc.Contains(pkm.Met_Location)) return new CheckResult(Severity.Invalid, V55, CheckIdentifier.Encounter); } else { var locs = pkm.FRLG ? Legal.ValidMet_FRLG : pkm.E ? Legal.ValidMet_E : Legal.ValidMet_RS; if (locs.Contains(pkm.Met_Location)) return new CheckResult(Severity.Valid, V53, CheckIdentifier.Encounter); if (Legal.ValidMet_FRLG.Contains(pkm.Met_Location) || Legal.ValidMet_E.Contains(pkm.Met_Location) || Legal.ValidMet_RS.Contains(pkm.Met_Location)) return new CheckResult(Severity.Valid, V56, CheckIdentifier.Encounter); return new CheckResult(Severity.Invalid, V54, CheckIdentifier.Encounter); } return new CheckResult(Severity.Valid, V53, CheckIdentifier.Encounter); } private static CheckResult VerifyEncounterEgg3Transfer(PKM pkm) { if (pkm.IsEgg) return new CheckResult(Severity.Invalid, V57, CheckIdentifier.Encounter); if (pkm.Met_Level < 5) return new CheckResult(Severity.Invalid, V58, CheckIdentifier.Encounter); if (pkm.Egg_Location != 0) return new CheckResult(Severity.Invalid, V59, CheckIdentifier.Encounter); if (pkm.Format == 4 && pkm.Met_Location != 0x37) // Pal Park return new CheckResult(Severity.Invalid, V60, CheckIdentifier.Encounter); if (pkm.Format != 4 && pkm.Met_Location != 30001) return new CheckResult(Severity.Invalid, V61, CheckIdentifier.Encounter); return new CheckResult(Severity.Valid, V53, CheckIdentifier.Encounter); } private static CheckResult VerifyEncounterEgg4(PKM pkm) { if (pkm.Format == 4) return VerifyEncounterEggLevelLoc(pkm, 0, Legal.Met_HGSS_Hatch); if (pkm.IsEgg) return new CheckResult(Severity.Invalid, V57, CheckIdentifier.Encounter); // transferred if (pkm.Met_Level < 1) return new CheckResult(Severity.Invalid, V58, CheckIdentifier.Encounter); if (pkm.Met_Location != 30001) return new CheckResult(Severity.Invalid, V61, CheckIdentifier.Encounter); return new CheckResult(Severity.Valid, V53, CheckIdentifier.Encounter); } private static CheckResult VerifyEncounterEgg5(PKM pkm) { return VerifyEncounterEggLevelLoc(pkm, 1, pkm.B2W2 ? Legal.ValidMet_B2W2 : Legal.ValidMet_BW); } private static CheckResult VerifyEncounterEgg6(PKM pkm) { if (pkm.AO) return VerifyEncounterEggLevelLoc(pkm, 1, Legal.ValidMet_AO); if (pkm.Egg_Location == 318) return new CheckResult(Severity.Invalid, V55, CheckIdentifier.Encounter); return VerifyEncounterEggLevelLoc(pkm, 1, Legal.ValidMet_XY); } private static CheckResult VerifyEncounterEgg7(PKM pkm) { if (pkm.SM) return VerifyEncounterEggLevelLoc(pkm, 1, Legal.ValidMet_SM); if (pkm.USUM) return VerifyEncounterEggLevelLoc(pkm, 1, Legal.ValidMet_USUM); // no other games return new CheckResult(Severity.Invalid, V51, CheckIdentifier.Encounter); } private static CheckResult VerifyEncounterEggLevelLoc(PKM pkm, int eggLevel, ICollection MetLocations) { if (pkm.Met_Level != eggLevel) return new CheckResult(Severity.Invalid, string.Format(V52, eggLevel), CheckIdentifier.Encounter); return MetLocations.Contains(pkm.Met_Location) ? new CheckResult(Severity.Valid, V53, CheckIdentifier.Encounter) : new CheckResult(Severity.Invalid, V54, CheckIdentifier.Encounter); } private static CheckResult VerifyUnhatchedEgg(PKM pkm, int tradeLoc) { var eggLevel = pkm.Format < 5 ? 0 : 1; if (pkm.Met_Level != eggLevel) return new CheckResult(Severity.Invalid, string.Format(V52, eggLevel), CheckIdentifier.Encounter); if (pkm.Egg_Location == tradeLoc) return new CheckResult(Severity.Invalid, V62, CheckIdentifier.Encounter); if (pkm.Met_Location == tradeLoc) return new CheckResult(Severity.Valid, V56, CheckIdentifier.Encounter); return pkm.Met_Location == 0 ? new CheckResult(Severity.Valid, V63, CheckIdentifier.Encounter) : new CheckResult(Severity.Invalid, V59, CheckIdentifier.Encounter); } // Other private static CheckResult VerifyEncounterWild(PKM pkm, EncounterSlot slot) { // Check for Unreleased Encounters / Collisions switch (pkm.GenNumber) { case 4: if (slot.Location == 193 && slot.Type == SlotType.Surf) // surfing in Johto Route 45 return new CheckResult(Severity.Invalid, V384, CheckIdentifier.Encounter); break; } if (slot.Permissions.IsNormalLead) return slot.Permissions.Pressure ? new CheckResult(Severity.Valid, V67, CheckIdentifier.Encounter) : new CheckResult(Severity.Valid, V68, CheckIdentifier.Encounter); // Decreased Level Encounters if (slot.Permissions.WhiteFlute) return slot.Permissions.Pressure ? new CheckResult(Severity.Valid, V69, CheckIdentifier.Encounter) : new CheckResult(Severity.Valid, V70, CheckIdentifier.Encounter); // Increased Level Encounters if (slot.Permissions.BlackFlute) return slot.Permissions.Pressure ? new CheckResult(Severity.Valid, V71, CheckIdentifier.Encounter) : new CheckResult(Severity.Valid, V72, CheckIdentifier.Encounter); if (slot.Permissions.Pressure) return new CheckResult(Severity.Valid, V67, CheckIdentifier.Encounter); return new CheckResult(Severity.Valid, V73, CheckIdentifier.Encounter); } private static CheckResult VerifyEncounterStatic(PKM pkm, EncounterStatic s) { // Check for Unreleased Encounters / Collisions switch (pkm.GenNumber) { case 3: if (s is EncounterStaticShadow w && w.EReader && pkm.Language != (int)LanguageID.Japanese) // Non-JP E-reader Pokemon return new CheckResult(Severity.Invalid, V406, CheckIdentifier.Encounter); if (pkm.Species == 151 && s.Location == 201 && pkm.Language != (int)LanguageID.Japanese) // Non-JP Mew (Old Sea Map) return new CheckResult(Severity.Invalid, V353, CheckIdentifier.Encounter); break; case 4: switch (pkm.Species) { case 491 when s.Location == 079 && !pkm.Pt: // DP Darkrai return new CheckResult(Severity.Invalid, V383, CheckIdentifier.Encounter); case 492 when s.Location == 063 && !pkm.Pt:// DP Shaymin return new CheckResult(Severity.Invalid, V354, CheckIdentifier.Encounter); case 493 when s.Location == 086: // Azure Flute Arceus return new CheckResult(Severity.Invalid, V352, CheckIdentifier.Encounter); } if (s.Location == 193 && s is EncounterStaticTyped t && t.TypeEncounter == EncounterType.Surfing_Fishing) // Roaming pokemon surfing in Johto Route 45 return new CheckResult(Severity.Invalid, V384, CheckIdentifier.Encounter); break; case 7: if (s.EggLocation == 60002 && pkm.RelearnMoves.Any(m => m != 0)) return new CheckResult(Severity.Invalid, V74, CheckIdentifier.RelearnMove); // not gift egg break; } if (s.EggEncounter && !pkm.IsEgg) // hatched { var hatchCheck = VerifyEncounterEgg(pkm, null); if (!hatchCheck.Valid) return hatchCheck; } return new CheckResult(Severity.Valid, V75, CheckIdentifier.Encounter); } private static CheckResult VerifyEncounterTrade(PKM pkm, EncounterTrade trade) { if (trade.Species == pkm.Species && trade.EvolveOnTrade) { // Pokemon that evolve on trade can not be in the phase evolution after the trade // If the trade holds an everstone EvolveOnTrade will be false for the encounter var species = LegalityAnalysis.SpeciesStrings; var unevolved = species[pkm.Species]; var evolved = species[pkm.Species + 1]; return new CheckResult(Severity.Invalid, string.Format(V401, unevolved, evolved), CheckIdentifier.Encounter); } return new CheckResult(Severity.Valid, V76, CheckIdentifier.Encounter); } private static CheckResult VerifyEncounterLink(PKM pkm, EncounterLink enc) { // Should NOT be Fateful, and should be in Database if (enc == null) return new CheckResult(Severity.Invalid, V43, CheckIdentifier.Encounter); if (pkm.XY && !enc.XY) return new CheckResult(Severity.Invalid, V44, CheckIdentifier.Encounter); if (pkm.AO && !enc.ORAS) return new CheckResult(Severity.Invalid, V45, CheckIdentifier.Encounter); if (pkm.IsShiny) return new CheckResult(Severity.Invalid, V47, CheckIdentifier.Encounter); return pkm.FatefulEncounter ? new CheckResult(Severity.Invalid, V48, CheckIdentifier.Encounter) : new CheckResult(Severity.Valid, V49, CheckIdentifier.Encounter); } private static CheckResult VerifyEncounterEvent(PKM pkm, MysteryGift MatchedGift) { switch (MatchedGift) { case PCD pcd: if (!pcd.CanBeReceivedBy(pkm.Version)) return new CheckResult(Severity.Invalid, string.Format(V21, MatchedGift.CardHeader, $"-- {V416}"), CheckIdentifier.Encounter); break; } if (!pkm.IsEgg && MatchedGift.IsEgg) // hatched { var hatchCheck = VerifyEncounterEgg(pkm, null); if (!hatchCheck.Valid) return hatchCheck; } // Strict matching already performed by EncounterGenerator. May be worth moving some checks here to better flag invalid gifts. return new CheckResult(Severity.Valid, string.Format(V21, MatchedGift.CardHeader, string.Empty), CheckIdentifier.Encounter); } } }