using System; using System.Collections.Generic; using System.Linq; using static PKHeX.Core.Legal; namespace PKHeX.Core { /// /// Generates matching data and relevant for a . /// Logic for generating possible in-game encounter data. /// public static class EncounterGenerator { /// /// Generates possible data according to the input PKM data and legality info. /// /// PKM data /// Legality information /// Possible encounters /// /// The iterator lazily finds possible encounters. If no encounters are possible, the enumerable will be empty. /// public static IEnumerable GetEncounters(PKM pkm, LegalInfo info) { switch (info.Generation) { case 1: case 2: foreach (var enc in GetEncounters12(pkm, info)) yield return enc; yield break; case 3: // info.PIDIV = MethodFinder.Analyze(pkm); foreach (var enc in GetEncounters3(pkm, info)) yield return enc; yield break; case 4: // info.PIDIV = MethodFinder.Analyze(pkm); foreach (var enc in GetEncounters4(pkm, info)) yield return enc; yield break; default: foreach (var enc in GenerateRawEncounters(pkm)) yield return enc; yield break; } } private static IEnumerable GetEncounters12(PKM pkm, LegalInfo info) { int baseSpecies = GetBaseSpecies(pkm); bool g1 = pkm.VC1 || pkm.Format == 1; if (g1 && baseSpecies > MaxSpeciesID_1 || baseSpecies > MaxSpeciesID_2) yield break; foreach (var z in GenerateFilteredEncounters(pkm)) { info.Generation = z.Generation; info.Game = z.Game; yield return z.Encounter; } } private static IEnumerable GetEncounters3(PKM pkm, LegalInfo info) { info.PIDIV = MethodFinder.Analyze(pkm); var deferred = new List(); foreach (var z in GenerateRawEncounters3(pkm)) { if (z is EncounterSlot w && pkm.Version == 15) info.PIDIV = MethodFinder.GetPokeSpotSeeds(pkm, w.SlotNumber).FirstOrDefault() ?? info.PIDIV; if (info.PIDIV.Type.IsCompatible3(z, pkm)) yield return z; else deferred.Add(z); } if (deferred.Count == 0) yield break; info.PIDIVMatches = false; foreach (var z in deferred) yield return z; } private static IEnumerable GetEncounters4(PKM pkm, LegalInfo info) { info.PIDIV = MethodFinder.Analyze(pkm); var deferredPIDIV = new List(); var deferredEType = new List(); foreach (var z in GenerateRawEncounters4(pkm)) { if (!info.PIDIV.Type.IsCompatible4(z, pkm)) deferredPIDIV.Add(z); else if (!IsEncounterTypeMatch(z, pkm.EncounterType)) deferredEType.Add(z); else yield return z; } foreach (var z in deferredEType) yield return z; if (deferredPIDIV.Count == 0) yield break; info.PIDIVMatches = false; foreach (var z in deferredPIDIV) yield return z; } private static IEnumerable GenerateRawEncounters12(PKM pkm, GameVersion game) { var gen = game == GameVersion.RBY ? 1 : 2; // Since encounter matching is super weak due to limited stored data in the structure // Calculate all 3 at the same time and pick the best result (by species). // Favor special event move gifts as Static Encounters when applicable var maxspeciesorigin = game == GameVersion.GSC ? MaxSpeciesID_2 : MaxSpeciesID_1; DexLevel[] vs = GetValidPreEvolutions(pkm, maxspeciesorigin: maxspeciesorigin).ToArray(); HashSet species = new HashSet(vs.Select(p => p.Species).ToList()); var deferred = new List(); foreach (var t in GetValidEncounterTrades(pkm, game)) { if (pkm.Format >= 7) { deferred.Add(t); continue; } yield return new GBEncounterData(pkm, gen, t, t.Version); } foreach (var s in GetValidStaticEncounter(pkm, game).Where(z => species.Contains(z.Species))) { // Valid stadium and non-stadium encounters, return only non-stadium encounters, they are less restrictive if (s.Version == GameVersion.Stadium || s.Version == GameVersion.Stadium2) { deferred.Add(s); continue; } if (s.Version == GameVersion.EventsGBGen2 && s.Species != 251) { // no Gen2 events outside of Japan besides Celebi if (pkm.Japanese) deferred.Add(s); continue; } if (game == GameVersion.GSC && !s.EggEncounter && s.Version == GameVersion.C && !pkm.HasOriginalMetLocation) continue; yield return new GBEncounterData(pkm, gen, s, s.Version); } foreach (var e in GetValidWildEncounters(pkm, game).OfType()) { if (!species.Contains(e.Species)) continue; yield return new GBEncounterData(pkm, gen, e, e.Version); } if (game == GameVersion.GSC || game == GameVersion.C) { bool WasEgg = !pkm.Gen1_NotTradeback && GetWasEgg23(pkm) && !NoHatchFromEgg.Contains(pkm.Species); if (WasEgg) { // Further Filtering if (pkm.Format < 3) { WasEgg &= pkm.Met_Location == 0 || pkm.Met_Level == 1; // 2->1->2 clears met info WasEgg &= pkm.CurrentLevel >= 5; } } if (WasEgg) { int eggspec = GetBaseEggSpecies(pkm); if (AllowGen2Crystal(pkm)) yield return new GBEncounterData(eggspec, GameVersion.C); // gen2 egg yield return new GBEncounterData(eggspec, GameVersion.GS); // gen2 egg } } foreach (var d in deferred) yield return new GBEncounterData(pkm, gen, d, game); } private static IEnumerable GenerateFilteredEncounters(PKM pkm) { bool crystal = pkm.Format == 2 && pkm.Met_Location != 0; var g1i = new PeekEnumerator(get1().GetEnumerator()); var g2i = new PeekEnumerator(get2().GetEnumerator()); var deferred = new List(); while (g2i.PeekIsNext() || g1i.PeekIsNext()) { var move = GetPreferredGBIterator(g1i, g2i); var obj = move.Peek(); if (obj.Generation == 1 && obj.Encounter is EncounterTrade && !IsEncounterTrade1Valid(pkm)) deferred.Add(obj); else yield return obj; move.MoveNext(); } foreach (var z in deferred) yield return z; IEnumerable get1() { if (!pkm.Gen2_NotTradeback && !crystal) foreach (var z in GenerateRawEncounters12(pkm, GameVersion.RBY)) yield return z; } IEnumerable get2() { if (!pkm.Gen1_NotTradeback && AllowGen2VCTransfer) foreach (var z in GenerateRawEncounters12(pkm, crystal ? GameVersion.C : GameVersion.GSC)) yield return z; } } /// /// Gets the preferred iterator from a pair of iterators based on the highest value . /// /// Generation 1 Iterator /// Generation 2 Iterator /// Preferred iterator private static PeekEnumerator GetPreferredGBIterator(PeekEnumerator g1i, PeekEnumerator g2i) { if (!g1i.PeekIsNext()) return g2i; if (!g2i.PeekIsNext()) return g1i; return g1i.Peek().Type > g2i.Peek().Type ? g1i : g2i; } private static IEnumerable GenerateRawEncounters(PKM pkm) { int ctr = 0; if (pkm.WasLink) { foreach (var z in GetValidLinkGifts(pkm)) { yield return z; ++ctr; } if (ctr != 0) yield break; } if (pkm.WasEvent || pkm.WasEventEgg) { foreach (var z in GetValidGifts(pkm)) { yield return z; ++ctr; } if (ctr != 0) yield break; } if (pkm.WasEgg) { foreach (var z in GenerateEggs(pkm)) { yield return z; ++ctr; } } foreach (var z in GetValidStaticEncounter(pkm)) { yield return z; ++ctr; } if (ctr != 0) yield break; foreach (var z in GetValidFriendSafari(pkm)) { yield return z; ++ctr; } if (ctr != 0) yield break; foreach (var z in GetValidWildEncounters(pkm)) { yield return z; ++ctr; } if (ctr != 0) yield break; foreach (var z in GetValidEncounterTrades(pkm)) { yield return z; ++ctr; } // if (ctr != 0) yield break; } private static IEnumerable GenerateRawEncounters4(PKM pkm) { bool wasEvent = pkm.WasEvent || pkm.WasEventEgg; // egg events? if (wasEvent) { int ctr = 0; foreach (var z in GetValidGifts(pkm)) { yield return z; ++ctr; } if (ctr != 0) yield break; } if (pkm.WasEgg) { foreach (var z in GenerateEggs(pkm)) yield return z; } var deferred = new List(); bool safariSport = pkm.Ball == 0x05 || pkm.Ball == 0x18; // never static encounters if (!safariSport) foreach (var z in GetValidStaticEncounter(pkm)) { if (z.Gift && pkm.Ball != 4) deferred.Add(z); else yield return z; } foreach (var z in GetValidEncounterTrades(pkm)) yield return z; foreach (var z in GetValidWildEncounters(pkm)) yield return z; // do static encounters if they were deferred to end, spit out any possible encounters for invalid pkm if (safariSport) foreach (var z in GetValidStaticEncounter(pkm)) yield return z; foreach (var z in deferred) yield return z; } private static IEnumerable GenerateRawEncounters3(PKM pkm) { foreach (var z in GetValidGifts(pkm)) yield return z; var deferred = new List(); bool safari = pkm.Ball == 0x05; // never static encounters if (!safari) foreach (var z in GetValidStaticEncounter(pkm)) { if (z.Gift && pkm.Ball != 4) deferred.Add(z); else yield return z; } foreach (var z in GetValidWildEncounters(pkm)) yield return z; foreach (var z in GetValidEncounterTrades(pkm)) yield return z; if (pkm.Version != 15) // no eggs in C/XD foreach (var z in GenerateEggs(pkm)) yield return z; // do static encounters if they were deferred to end, spit out any possible encounters for invalid pkm if (safari) foreach (var z in GetValidStaticEncounter(pkm)) yield return z; foreach (var z in deferred) yield return z; } // EncounterStatic private static bool IsValidCatchRatePK1(EncounterStatic e, PK1 pk1) { var catch_rate = pk1.Catch_Rate; // Pure gen 1, trades can be filter by catch rate if (pk1.Species == 25 || pk1.Species == 26) { if (catch_rate == 190) // Red Blue Pikachu, is not a static encounter return false; if (catch_rate == 163 && e.Level == 5) // Light Ball (Yellow) starter return true; } if (e.Version == GameVersion.Stadium) { switch (e.Species) { default: return Stadium_CatchRate.Contains(catch_rate); case 054: // Psyduck // Amnesia Psyduck has different catch rates depending on language return catch_rate == (pk1.Japanese ? 167 : 168); } } // Encounters can have different Catch Rates (RBG vs Y) var rate = e.Version == GameVersion.Y ? PersonalTable.Y[e.Species].CatchRate : PersonalTable.RB[e.Species].CatchRate; return catch_rate == rate; } private static IEnumerable GetValidStaticEncounter(PKM pkm, GameVersion gameSource = GameVersion.Any) { if (gameSource == GameVersion.Any) gameSource = (GameVersion)pkm.Version; // Get possible encounters IEnumerable poss = GetStaticEncounters(pkm, gameSource: gameSource); int lvl = GetMinLevelEncounter(pkm); if (lvl < 0) yield break; // Back Check against pkm var enc = GetMatchingStaticEncounters(pkm, poss, lvl); foreach (var z in enc) yield return z; } private static IEnumerable GetMatchingStaticEncounters(PKM pkm, IEnumerable poss, int lvl) { // check for petty rejection scenarios that will be flagged by other legality checks var deferred = new List(); foreach (EncounterStatic e in poss) { if (e.Nature != Nature.Random && pkm.Nature != (int)e.Nature) continue; if (pkm.WasEgg ^ e.EggEncounter && pkm.Egg_Location == 0 && pkm.Format > 3 && pkm.GenNumber > 3) { if (!pkm.IsEgg) continue; } if (pkm.Gen3 && e.EggLocation != 0) // Gen3 Egg { if (pkm.Format == 3 && pkm.IsEgg && e.EggLocation != pkm.Met_Location) continue; } else if (pkm.VC || pkm.GenNumber <= 2 && e.EggLocation != 0) // Gen2 Egg { if (pkm.Format <= 2) { if (pkm.IsEgg) { if (pkm.Met_Location != 0 && pkm.Met_Level != 0) continue; } else { switch (pkm.Met_Level) { case 0: if (pkm.Met_Location != 0) continue; break; case 1: if (pkm.Met_Location == 0) continue; break; default: if (pkm.Met_Location == 0) continue; break; } } lvl = 5; // met @ 1, hatch @ 5. } } else if (e.EggLocation != pkm.Egg_Location) { switch (pkm.GenNumber) { case 4: if (pkm.Egg_Location != 2002) // Link Trade continue; break; default: if (pkm.Egg_Location != 30002) // Link Trade continue; break; } } if (pkm.HasOriginalMetLocation) { if (!e.EggEncounter && e.Location != 0 && e.Location != pkm.Met_Location) continue; if (e.Level != lvl) { if (!(pkm.Format == 3 && e.EggEncounter && lvl == 0)) continue; } } else if (e.Level > lvl) continue; if (e.Gender != -1 && e.Gender != pkm.Gender) continue; if (e.Form != pkm.AltForm && !e.SkipFormCheck && !IsFormChangeable(pkm, e.Species)) continue; if (e.EggLocation == 60002 && e.Relearn[0] == 0 && pkm.RelearnMoves.Any(z => z != 0)) // gen7 eevee edge case continue; // Defer to EC/PID check // if (e.Shiny != null && e.Shiny != pkm.IsShiny) // continue; // Defer ball check to later // if (e.Gift && pkm.Ball != 4) // PokéBall // continue; if (pkm is PK1 pk1 && pk1.Gen1_NotTradeback) if (!IsValidCatchRatePK1(e, pk1)) continue; if (!AllowGBCartEra && GameVersion.GBCartEraOnly.Contains(e.Version)) continue; // disallow gb cart era encounters (as they aren't obtainable by Main/VC series) if (pkm.FatefulEncounter ^ e.Fateful) deferred.Add(e); else yield return e; } foreach (var e in deferred) yield return e; } private static IEnumerable GetStaticEncounters(PKM pkm, GameVersion gameSource = GameVersion.Any) { if (gameSource == GameVersion.Any) gameSource = (GameVersion)pkm.Version; var table = GetEncounterStaticTable(pkm, gameSource); switch (pkm.GenNumber) { case 1: return GetStatic(pkm, table, maxspeciesorigin: MaxSpeciesID_1); case 2: return GetStatic(pkm, table, maxspeciesorigin: MaxSpeciesID_2); default: return GetStatic(pkm, table); } } private static IEnumerable GetStatic(PKM pkm, IEnumerable table, int maxspeciesorigin = -1, int lvl = -1, bool skip = false) { IEnumerable dl = GetValidPreEvolutions(pkm, maxspeciesorigin: maxspeciesorigin, lvl: lvl, skipChecks: skip); return table.Where(e => dl.Any(d => d.Species == e.Species)); } // EncounterSlot private static IEnumerable GetRawEncounterSlots(PKM pkm, int lvl, GameVersion gameSource = GameVersion.Any) { return GetEncounterAreas(pkm, gameSource).SelectMany(area => GetValidEncounterSlots(pkm, area, DexNav: pkm.AO, lvl: lvl, gameSource: gameSource)); } private static IEnumerable GetValidWildEncounters(PKM pkm, GameVersion gameSource = GameVersion.Any) { if (gameSource == GameVersion.Any) gameSource = (GameVersion)pkm.Version; int lvl = GetMinLevelEncounter(pkm); if (lvl <= 0) yield break; var s = GetRawEncounterSlots(pkm, lvl, gameSource); bool IsSafariBall = pkm.Ball == 5; bool IsSportsBall = pkm.Ball == 0x18; bool IsHidden = pkm.AbilityNumber == 4; // hidden Ability int species = pkm.Species; var deferred = new List(); foreach (EncounterSlot slot in s) { // check for petty rejection scenarios that will be flagged by other legality checks // defer these edge case scenarios in the event that a later encounter ends up passing if (slot.Species == 265 && species != 265 && !IsWurmpleEvoValid(pkm)) { } // bad wurmple evolution else if (IsHidden ^ IsHiddenAbilitySlot(slot)) { } // ability mismatch else if (IsSafariBall ^ IsSafariSlot(slot.Type)) { } // Safari Zone only ball else if (IsSportsBall ^ slot.Type == SlotType.BugContest) { } // BCC only ball else { yield return slot; continue; } deferred.Add(slot); } foreach (var d in deferred) yield return d; } private static IEnumerable GetValidFriendSafari(PKM pkm) { if (!pkm.XY) yield break; if (pkm.Met_Location != 148) // Friend Safari yield break; if (pkm.Met_Level != 30) yield break; IEnumerable vs = GetValidPreEvolutions(pkm); foreach (DexLevel d in vs.Where(d => d.Level >= 30 && FriendSafari.Contains(d.Species))) { yield return new EncounterSlot { Species = d.Species, LevelMin = 30, LevelMax = 30, Form = 0, Type = SlotType.FriendSafari, }; } } private static IEnumerable GetValidEncounterSlots(PKM pkm, EncounterArea loc, bool DexNav, int lvl = -1, bool ignoreLevel = false, GameVersion gameSource = GameVersion.Any) { if (lvl < 0) lvl = GetMinLevelEncounter(pkm); if (lvl <= 0) yield break; int gen = pkm.GenNumber; int fluteBoost = gen < 3 ? 0 : 4; const int dexnavBoost = 30; int df = DexNav ? fluteBoost : 0; int dn = DexNav ? fluteBoost + dexnavBoost : 0; var maxspeciesorigin = -1; if (gameSource == GameVersion.RBY) maxspeciesorigin = MaxSpeciesID_1; else if (GameVersion.GSC.Contains(gameSource)) maxspeciesorigin = MaxSpeciesID_2; // Get Valid levels IEnumerable vs = GetValidPreEvolutions(pkm, maxspeciesorigin: maxspeciesorigin, lvl: ignoreLevel ? 100 : -1, skipChecks: ignoreLevel); if (!FilterGBSlotsCatchRate(pkm, ref vs, out GameVersion Gen1Version, out bool RBDragonair)) yield break; // Get slots where pokemon can exist with respect to the evolution chain IEnumerable slots = loc.Slots.Where(slot => vs.Any(evo => evo.Species == slot.Species && (ignoreLevel || evo.Level >= slot.LevelMin - df))); List encounterSlots; if (ignoreLevel) encounterSlots = slots.ToList(); else if (pkm.HasOriginalMetLocation) encounterSlots = slots.Where(slot => slot.LevelMin - df <= lvl && lvl <= slot.LevelMax + (slot.Permissions.AllowDexNav ? dn : df)).ToList(); else // check for any less than current level encounterSlots = slots.Where(slot => slot.LevelMin <= lvl).ToList(); if (gen <= 2) { var gbslots = FilterGBSlots(pkm, gen, Gen1Version, encounterSlots, RBDragonair); foreach (var s in gbslots.OrderBy(slot => slot.LevelMin)) yield return s; yield break; } // Pressure Slot EncounterSlot slotMax = encounterSlots.OrderByDescending(slot => slot.LevelMax).FirstOrDefault(); if (gen >= 6 && !DexNav) { var slotdata = WildForms.Contains(pkm.Species) ? encounterSlots.Where(slot => slot.Form == pkm.AltForm) : encounterSlots; foreach (var z in slotdata) yield return z; // Filter for Form Specific if (slotMax != null) yield return getPressureSlot(slotMax); yield break; } IEnumerable formMatchSlots = encounterSlots.Where(slot => !WildForms.Contains(pkm.Species) || slot.Form == pkm.AltForm); if (gen <= 5) { foreach (var z in formMatchSlots) yield return z; yield break; } foreach (EncounterSlot s in formMatchSlots) { bool nav = s.Permissions.AllowDexNav && (pkm.RelearnMove1 != 0 || pkm.AbilityNumber == 4); EncounterSlot slot = s.Clone(); slot.Permissions.DexNav = nav; if (slot.LevelMin > lvl) slot.Permissions.WhiteFlute = true; if (slot.LevelMax + 1 <= lvl && lvl <= slot.LevelMax + fluteBoost) slot.Permissions.BlackFlute = true; if (slot.LevelMax != lvl && slot.Permissions.AllowDexNav) slot.Permissions.DexNav = true; yield return slot; } if (slotMax != null) yield return getPressureSlot(slotMax); EncounterSlot getPressureSlot(EncounterSlot s) { var max = s.Clone(); max.Permissions.Pressure = true; max.Form = pkm.AltForm; return max; } } private static bool FilterGBSlotsCatchRate(PKM pkm, ref IEnumerable vs, out GameVersion Gen1Version, out bool RBDragonair) { RBDragonair = false; Gen1Version = GameVersion.RBY; if (!(pkm is PK1 pk1) || !pkm.Gen1_NotTradeback) return true; // Pure gen 1, slots can be filter by catch rate var rate = pk1.Catch_Rate; switch (pkm.Species) { // Pikachu case 25 when rate == 163: case 26 when rate == 163: return false; // Yellow Pikachu is not a wild encounter // Kadabra (YW) case 64 when rate == 96: case 65 when rate == 96: vs = vs.Where(s => s.Species == 64); Gen1Version = GameVersion.YW; return true; // Kadabra (RB) case 64 when rate == 100: case 65 when rate == 100: vs = vs.Where(s => s.Species == 64); Gen1Version = GameVersion.RB; return true; // Dragonair (YW) case 148 when rate == 27: case 149 when rate == 27: vs = vs.Where(s => s.Species == 148); // Yellow Dragonair, ignore Dratini encounters Gen1Version = GameVersion.YW; return true; // Dragonair (RB) case 148: case 149: // Red blue dragonair have the same catch rate as dratini, it could also be a dratini from any game vs = vs.Where(s => rate == PersonalTable.RB[s.Species].CatchRate); RBDragonair = true; return true; default: vs = vs.Where(s => rate == PersonalTable.RB[s.Species].CatchRate); return true; } } private static IEnumerable FilterGBSlots(PKM pkm, int gen, GameVersion Gen1Version, IEnumerable slots, bool RBDragonair) { switch (gen) { case 1: if (Gen1Version != GameVersion.RBY) slots = slots.Where(slot => Gen1Version.Contains(((EncounterSlot1)slot).Version)); // Red Blue dragonair or dratini from any gen 1 games if (RBDragonair) return slots.Where(slot => GameVersion.RB.Contains(((EncounterSlot1)slot).Version) || slot.Species == 147); return slots; case 2: if (pkm is PK2 pk2 && pk2.Met_Day != 0) slots = slots.Where(slot => ((EncounterSlot1)slot).Time.Contains(pk2.Met_Day)); return slots; default: return slots; } } private static IEnumerable GetEncounterSlots(PKM pkm, int lvl = -1, GameVersion gameSource = GameVersion.Any) { if (gameSource == GameVersion.Any) gameSource = (GameVersion)pkm.Version; return GetSlots(pkm, GetEncounterTable(pkm, gameSource), lvl); } private static IEnumerable GetEncounterAreas(PKM pkm, GameVersion gameSource = GameVersion.Any) { if (gameSource == GameVersion.Any) gameSource = (GameVersion)pkm.Version; var slots = GetEncounterSlots(pkm, gameSource: gameSource); bool noMet = !pkm.HasOriginalMetLocation || pkm.Format == 2 && gameSource != GameVersion.C; return noMet ? slots : slots.Where(area => area.Location == pkm.Met_Location); } private static IEnumerable GetSlots(PKM pkm, IEnumerable tables, int lvl = -1) { IEnumerable vs = GetValidPreEvolutions(pkm, lvl: lvl); foreach (var loc in tables) { IEnumerable slots = loc.Slots.Where(slot => vs.Any(evo => evo.Species == slot.Species)); EncounterSlot[] es = slots.ToArray(); if (es.Length > 0) yield return new EncounterArea { Location = loc.Location, Slots = es }; } } // EncounterLink private static IEnumerable GetValidLinkGifts(PKM pkm) { switch (pkm.GenNumber) { case 6: return Encounters6.LinkGifts6.Where(g => g.Species == pkm.Species && g.Level == pkm.Met_Level); default: return Enumerable.Empty(); } } // EncounterTrade private static EncounterTrade[] GetEncounterTradeTable(PKM pkm) { switch (pkm.GenNumber) { case 3: return pkm.FRLG ? Encounters3.TradeGift_FRLG : Encounters3.TradeGift_RSE; case 4: return pkm.HGSS ? Encounters4.TradeGift_HGSS : Encounters4.TradeGift_DPPt; case 5: return pkm.B2W2 ? Encounters5.TradeGift_B2W2 : Encounters5.TradeGift_BW; case 6: return pkm.XY ? Encounters6.TradeGift_XY : Encounters6.TradeGift_AO; case 7: return pkm.SM ? Encounters7.TradeGift_SM : Encounters7.TradeGift_USUM; } return null; } private static IEnumerable GetValidEncounterTradesVC(PKM pkm, GameVersion gameSource) { var p = GetValidPreEvolutions(pkm).ToArray(); switch (gameSource) { case GameVersion.RBY: var table = !AllowGen1Tradeback ? Encounters1.TradeGift_RBY_NoTradeback : Encounters1.TradeGift_RBY_Tradeback; return GetValidEncounterTradesVC1(pkm, p, table); case GameVersion.GSC: case GameVersion.C: return GetValidEncounterTradesVC2(pkm, p); default: return null; } } private static IEnumerable GetValidEncounterTradesVC2(PKM pkm, DexLevel[] p) { // Check GSC trades. Reuse generic table fetch-match var possible = GetValidEncounterTradesVC1(pkm, p, Encounters2.TradeGift_GSC); foreach (var z in possible) { // Filter Criteria if (z.TID != pkm.TID) continue; if (z.Gender >= 0 && z.Gender != pkm.Gender && pkm.Format <= 2) continue; if (z.IVs[0] >= 0 && !z.IVs.SequenceEqual(pkm.IVs) && pkm.Format <= 2) continue; if (pkm.Met_Location != 0 && pkm.Format == 2 && pkm.Met_Location != 126) continue; int index = Array.IndexOf(Encounters2.TradeGift_GSC, z); if (Encounters2.TradeGift_GSC_OTs[index].All(ot => ot != pkm.OT_Name)) continue; yield return z; } } private static IEnumerable GetValidEncounterTradesVC1(PKM pkm, DexLevel[] p, IEnumerable table) { var possible = table.Where(f => p.Any(r => r.Species == f.Species)); foreach (var z in possible) { if (z == null) continue; if (z.Level > pkm.CurrentLevel) // minimum required level continue; if (pkm.Format != 1 || !pkm.Gen1_NotTradeback) yield return z; // Even if the in game trade uses the tables with source pokemon allowing generation 2 games, the traded pokemon could be a non-tradeback pokemon var rate = (pkm as PK1)?.Catch_Rate; if (z is EncounterTradeCatchRate r) { if (rate != r.Catch_Rate) continue; } else { if (z.Version == GameVersion.YW && rate != PersonalTable.Y[z.Species].CatchRate) continue; if (z.Version != GameVersion.YW && rate != PersonalTable.RB[z.Species].CatchRate) continue; } yield return z; } } private static IEnumerable GetValidEncounterTrades(PKM pkm, GameVersion gameSource = GameVersion.Any) { if (gameSource == GameVersion.Any) gameSource = (GameVersion)pkm.Version; if (pkm.VC || pkm.Format <= 2) { foreach (var z in GetValidEncounterTradesVC(pkm, gameSource)) yield return z; yield break; } int lang = pkm.Language; if (lang == (int)LanguageID.UNUSED_6) // invalid language yield break; if (lang == (int)LanguageID.Hacked && (pkm.Format != 5 || !pkm.BW)) // Japanese trades in BW have no language ID yield break; int lvl = GetMinLevelEncounter(pkm); if (lvl <= 0) yield break; // Get valid pre-evolutions IEnumerable p = GetValidPreEvolutions(pkm); EncounterTrade[] table = GetEncounterTradeTable(pkm); if (table == null) yield break; var poss = table.Where(f => p.Any(r => r.Species == f.Species) && f.Version.Contains((GameVersion)pkm.Version)); foreach (var z in poss) { if (IsEncounterTradeValid(pkm, z, lvl)) yield return z; } } private static bool IsEncounterTradeValid(PKM pkm, EncounterTrade z, int lvl) { for (int i = 0; i < 6; i++) if (z.IVs[i] != -1 && z.IVs[i] != pkm.IVs[i]) return false; if (z is EncounterTradePID p) { if (p.PID != pkm.EncryptionConstant) return false; } else { if (z.Shiny ^ pkm.IsShiny) return false; if (z.Nature != Nature.Random && (int)z.Nature != pkm.Nature) return false; if (z.Gender != -1 && z.Gender != pkm.Gender) return false; } if (z.TID != pkm.TID) return false; if (z.SID != pkm.SID) return false; if (pkm.HasOriginalMetLocation) { var loc = z.Location > 0 ? z.Location : EncounterTrade.DefaultMetLocation[pkm.GenNumber - 1]; if (loc != pkm.Met_Location) return false; if (pkm.Format < 5) { if (z.Level > lvl) return false; } else if (z.Level != lvl) return false; } else if (z.Level > lvl) return false; if (z.CurrentLevel != -1 && z.CurrentLevel > pkm.CurrentLevel) return false; if (z.OTGender != -1 && z.OTGender != pkm.OT_Gender) return false; if (z.Egg_Location != pkm.Egg_Location) return false; // if (z.Ability == 4 ^ pkm.AbilityNumber == 4) // defer to Ability // countinue; return true; } // MysteryGift private static IEnumerable GetValidGifts(PKM pkm) { switch (pkm.GenNumber) { case 3: return GetMatchingWC3(pkm, MGDB_G3); case 4: return GetMatchingPCD(pkm, MGDB_G4); case 5: return GetMatchingPGF(pkm, MGDB_G5); case 6: return GetMatchingWC6(pkm, MGDB_G6); case 7: return GetMatchingWC7(pkm, MGDB_G7); default: return Enumerable.Empty(); } } private static IEnumerable GetMatchingWC3(PKM pkm, IEnumerable DB) { if (DB == null) yield break; var validWC3 = new List(); var vs = GetValidPreEvolutions(pkm, MaxSpeciesID_3).ToArray(); var enumerable = DB.OfType().Where(wc => vs.Any(dl => dl.Species == wc.Species)); foreach (WC3 wc in enumerable) { if (!GetIsMatchWC3(pkm, wc)) continue; if (wc.Species == pkm.Species) // best match yield return wc; else validWC3.Add(wc); } foreach (var z in validWC3) yield return z; } private static IEnumerable GetMatchingPCD(PKM pkm, IEnumerable DB) { if (DB == null || pkm.IsEgg && pkm.Format != 4) // transferred yield break; if (IsRangerManaphy(pkm)) { if (pkm.Language != (int)LanguageID.Korean) // never korean yield return new PGT { Data = { [0] = 7, [8] = 1 } }; yield break; } var deferred = new List(); var vs = GetValidPreEvolutions(pkm).ToArray(); var enumerable = DB.OfType().Where(wc => vs.Any(dl => dl.Species == wc.Species)); foreach (PCD mg in enumerable) { var wc = mg.Gift.PK; if (!GetIsMatchPCD(pkm, wc, vs)) continue; bool receivable = mg.CanBeReceivedBy(pkm.Version); if (wc.Species == pkm.Species && receivable) // best match yield return mg; else deferred.Add(mg); } foreach (var z in deferred) yield return z; } private static IEnumerable GetMatchingPGF(PKM pkm, IEnumerable DB) { if (DB == null) yield break; var deferred = new List(); var vs = GetValidPreEvolutions(pkm).ToArray(); var enumerable = DB.OfType().Where(wc => vs.Any(dl => dl.Species == wc.Species)); foreach (PGF wc in enumerable) { if (!GetIsMatchPGF(pkm, wc, vs)) continue; if (wc.Species == pkm.Species) // best match yield return wc; else deferred.Add(wc); } foreach (var z in deferred) yield return z; } private static IEnumerable GetMatchingWC6(PKM pkm, IEnumerable DB) { if (DB == null) yield break; var deferred = new List(); var vs = GetValidPreEvolutions(pkm).ToArray(); var enumerable = DB.OfType().Where(wc => vs.Any(dl => dl.Species == wc.Species)); foreach (WC6 wc in enumerable) { if (!GetIsMatchWC6(pkm, wc, vs)) continue; switch (wc.CardID) { case 0525 when wc.IV_HP == 0xFE: // Diancie was distributed with no IV enforcement & 3IVs case 0504 when wc.RibbonClassic != ((IRibbonSetEvent4)pkm).RibbonClassic: // magmar with/without classic deferred.Add(wc); continue; } if (wc.Species == pkm.Species) // best match yield return wc; else deferred.Add(wc); } foreach (var z in deferred) yield return z; } private static IEnumerable GetMatchingWC7(PKM pkm, IEnumerable DB) { if (DB == null) yield break; var deferred = new List(); var vs = GetValidPreEvolutions(pkm).ToArray(); var enumerable = DB.OfType().Where(wc => vs.Any(dl => dl.Species == wc.Species)); foreach (WC7 wc in enumerable) { if (!GetIsMatchWC7(pkm, wc, vs)) continue; if ((pkm.SID << 16 | pkm.TID) == 0x79F57B49) // Greninja WC has variant PID and can arrive @ 36 or 37 { if (!pkm.IsShiny) deferred.Add(wc); continue; } if (wc.PIDType == 0 && pkm.PID != wc.PID) continue; if (wc.Species == pkm.Species) // best match yield return wc; else deferred.Add(wc); } foreach (var z in deferred) yield return z; } private static bool GetIsMatchWC3(PKM pkm, WC3 wc) { // Gen3 Version MUST match. if (wc.Version != 0 && !((GameVersion)wc.Version).Contains((GameVersion)pkm.Version)) return false; bool hatchedEgg = wc.IsEgg && !pkm.IsEgg; if (!hatchedEgg) { if (wc.SID != -1 && wc.SID != pkm.SID) return false; if (wc.TID != -1 && wc.TID != pkm.TID) return false; if (wc.OT_Name != null && wc.OT_Name != pkm.OT_Name) return false; if (wc.OT_Gender < 3 && wc.OT_Gender != pkm.OT_Gender) return false; } if (wc.Language != -1 && wc.Language != pkm.Language) return false; if (wc.Ball != pkm.Ball) return false; if (wc.Fateful != pkm.FatefulEncounter) { // XD Gifts only at level 20 get flagged after transfer bool valid = wc.Level == 20 && pkm is XK3; if (!valid) return false; } if (pkm.IsNative) { if (wc.Met_Level != pkm.Met_Level) return false; if (wc.Met_Location != pkm.Met_Location && (!wc.IsEgg || pkm.IsEgg)) return false; } else { if (pkm.IsEgg) return false; if (wc.Level > pkm.Met_Level) return false; } return true; } private static bool GetIsMatchPCD(PKM pkm, PKM wc, IEnumerable vs) { if (!wc.IsEgg) { if (wc.TID != pkm.TID) return false; if (wc.SID != pkm.SID) return false; if (wc.OT_Name != pkm.OT_Name) return false; if (wc.OT_Gender != pkm.OT_Gender) return false; if (wc.Language != 0 && wc.Language != pkm.Language) return false; if (pkm.Format != 4) // transferred { // met location: deferred to general transfer check if (wc.CurrentLevel > pkm.Met_Level) return false; } else { if (wc.Egg_Location + 3000 != pkm.Met_Location) return false; if (wc.CurrentLevel != pkm.Met_Level) return false; } } else // Egg { if (wc.Egg_Location + 3000 != pkm.Egg_Location && pkm.Egg_Location != 2002) // traded return false; if (wc.CurrentLevel != pkm.Met_Level) return false; if (pkm.IsEgg && !pkm.IsNative) return false; } if (wc.AltForm != pkm.AltForm && vs.All(dl => !IsFormChangeable(pkm, dl.Species))) return false; if (wc.Ball != pkm.Ball) return false; if (wc.OT_Gender < 3 && wc.OT_Gender != pkm.OT_Gender) return false; if (wc.PID == 1 && pkm.IsShiny) return false; if (wc.Gender != 3 && wc.Gender != pkm.Gender) return false; if (wc.CNT_Cool > pkm.CNT_Cool) return false; if (wc.CNT_Beauty > pkm.CNT_Beauty) return false; if (wc.CNT_Cute > pkm.CNT_Cute) return false; if (wc.CNT_Smart > pkm.CNT_Smart) return false; if (wc.CNT_Tough > pkm.CNT_Tough) return false; if (wc.CNT_Sheen > pkm.CNT_Sheen) return false; return true; } private static bool GetIsMatchPGF(PKM pkm, PGF wc, IEnumerable vs) { if (!wc.IsEgg) { if (wc.SID != pkm.SID) return false; if (wc.TID != pkm.TID) return false; if (wc.OT != pkm.OT_Name) return false; if (wc.OTGender < 3 && wc.OTGender != pkm.OT_Gender) return false; if (wc.PID != 0 && pkm.PID != wc.PID) return false; if (wc.PIDType == 0 && pkm.IsShiny) return false; if (wc.PIDType == 2 && !pkm.IsShiny) return false; if (wc.OriginGame != 0 && wc.OriginGame != pkm.Version) return false; if (wc.Language != 0 && wc.Language != pkm.Language) return false; if (wc.EggLocation != pkm.Egg_Location) return false; if (wc.MetLocation != pkm.Met_Location) return false; } else { if (wc.EggLocation != pkm.Egg_Location && pkm.Egg_Location != 30003) // traded return false; if (pkm.IsEgg && !pkm.IsNative) return false; } if (wc.Form != pkm.AltForm && vs.All(dl => !IsFormChangeable(pkm, dl.Species))) return false; if (wc.Level != pkm.Met_Level) return false; if (wc.Ball != pkm.Ball) return false; if (wc.Nature != 0xFF && wc.Nature != pkm.Nature) return false; if (wc.Gender != 2 && wc.Gender != pkm.Gender) return false; if (wc.CNT_Cool > pkm.CNT_Cool) return false; if (wc.CNT_Beauty > pkm.CNT_Beauty) return false; if (wc.CNT_Cute > pkm.CNT_Cute) return false; if (wc.CNT_Smart > pkm.CNT_Smart) return false; if (wc.CNT_Tough > pkm.CNT_Tough) return false; if (wc.CNT_Sheen > pkm.CNT_Sheen) return false; return true; } private static bool GetIsMatchWC6(PKM pkm, WC6 wc, IEnumerable vs) { if (pkm.Egg_Location == 0) // Not Egg { if (wc.CardID != pkm.SID) return false; if (wc.TID != pkm.TID) return false; if (wc.OT != pkm.OT_Name) return false; if (wc.OTGender != pkm.OT_Gender) return false; if (wc.PIDType == 0 && pkm.PID != wc.PID) return false; if (wc.PIDType == 2 && !pkm.IsShiny) return false; if (wc.PIDType == 3 && pkm.IsShiny) return false; if (wc.OriginGame != 0 && wc.OriginGame != pkm.Version) return false; if (wc.EncryptionConstant != 0 && wc.EncryptionConstant != pkm.EncryptionConstant) return false; if (wc.Language != 0 && wc.Language != pkm.Language) return false; } if (wc.Form != pkm.AltForm && vs.All(dl => !IsFormChangeable(pkm, dl.Species))) return false; if (wc.IsEgg) { if (wc.EggLocation != pkm.Egg_Location && pkm.Egg_Location != 30002) // traded return false; if (pkm.IsEgg && !pkm.IsNative) return false; } else { if (wc.EggLocation != pkm.Egg_Location) return false; if (wc.MetLocation != pkm.Met_Location) return false; } if (wc.Level != pkm.Met_Level) return false; if (wc.Ball != pkm.Ball) return false; if (wc.OTGender < 3 && wc.OTGender != pkm.OT_Gender) return false; if (wc.Nature != 0xFF && wc.Nature != pkm.Nature) return false; if (wc.Gender != 3 && wc.Gender != pkm.Gender) return false; if (wc.CNT_Cool > pkm.CNT_Cool) return false; if (wc.CNT_Beauty > pkm.CNT_Beauty) return false; if (wc.CNT_Cute > pkm.CNT_Cute) return false; if (wc.CNT_Smart > pkm.CNT_Smart) return false; if (wc.CNT_Tough > pkm.CNT_Tough) return false; if (wc.CNT_Sheen > pkm.CNT_Sheen) return false; return true; } private static bool GetIsMatchWC7(PKM pkm, WC7 wc, IEnumerable vs) { if (pkm.Egg_Location == 0) // Not Egg { if (wc.OTGender != 3) { if (wc.SID != pkm.SID) return false; if (wc.TID != pkm.TID) return false; if (wc.OTGender != pkm.OT_Gender) return false; } if (!string.IsNullOrEmpty(wc.OT) && wc.OT != pkm.OT_Name) return false; if (wc.OriginGame != 0 && wc.OriginGame != pkm.Version) return false; if (wc.EncryptionConstant != 0 && wc.EncryptionConstant != pkm.EncryptionConstant) return false; if (wc.Language != 0 && wc.Language != pkm.Language) return false; } if (wc.Form != pkm.AltForm && vs.All(dl => !IsFormChangeable(pkm, dl.Species))) return false; if (wc.IsEgg) { if (wc.EggLocation != pkm.Egg_Location && pkm.Egg_Location != 30002) // traded return false; if (pkm.IsEgg && !pkm.IsNative) return false; } else { if (wc.EggLocation != pkm.Egg_Location) return false; if (wc.MetLocation != pkm.Met_Location) return false; } if (wc.MetLevel != pkm.Met_Level) return false; if (wc.Ball != pkm.Ball) return false; if (wc.OTGender < 3 && wc.OTGender != pkm.OT_Gender) return false; if (wc.Nature != 0xFF && wc.Nature != pkm.Nature) return false; if (wc.Gender != 3 && wc.Gender != pkm.Gender) return false; if (wc.CNT_Cool > pkm.CNT_Cool) return false; if (wc.CNT_Beauty > pkm.CNT_Beauty) return false; if (wc.CNT_Cute > pkm.CNT_Cute) return false; if (wc.CNT_Smart > pkm.CNT_Smart) return false; if (wc.CNT_Tough > pkm.CNT_Tough) return false; if (wc.CNT_Sheen > pkm.CNT_Sheen) return false; if (wc.PIDType == 2 && !pkm.IsShiny) return false; if (wc.PIDType == 3 && pkm.IsShiny) return false; return true; } // EncounterEgg private static IEnumerable GenerateEggs(PKM pkm) { if (NoHatchFromEgg.Contains(pkm.Species)) yield break; int gen = pkm.GenNumber; // version is a true indicator for all generation 3-5 origins var ver = (GameVersion) pkm.Version; int max = GetMaxSpeciesOrigin(gen); var baseSpecies = GetBaseSpecies(pkm, 0); int lvl = gen < 4 ? 5 : 1; if (baseSpecies <= max) { yield return new EncounterEgg { Game = ver, Level = lvl, Species = baseSpecies }; if (gen > 5 && pkm.WasTradedEgg) yield return new EncounterEgg { Game = tradePair(), Level = lvl, Species = baseSpecies }; } if (!GetSplitBreedGeneration(pkm).Contains(pkm.Species)) yield break; // no other possible species baseSpecies = GetBaseSpecies(pkm, 1); if (baseSpecies <= max) { yield return new EncounterEgg { Game = ver, Level = lvl, Species = baseSpecies, SplitBreed = true }; if (gen > 5 && pkm.WasTradedEgg) yield return new EncounterEgg { Game = tradePair(), Level = lvl, Species = baseSpecies, SplitBreed = true }; } // Gen6+ update the origin game when hatched. Quick manip for X.Y<->A.O | S.M<->US.UM, ie X->A GameVersion tradePair() => (GameVersion) (((int) ver - 4 * gen) ^ 2 + 4 * gen); } // Utility private static bool IsRangerManaphy(PKM pkm) { var egg = pkm.Egg_Location; const int ranger = 3001; const int linkegg = 2002; if (!pkm.IsEgg) // Link Trade Egg or Ranger return egg == linkegg || egg == ranger; if (egg != ranger) return false; var met = pkm.Met_Location; return met == linkegg || met == 0; } private static bool IsHiddenAbilitySlot(EncounterSlot slot) { return slot.Permissions.DexNav || slot.Type == SlotType.FriendSafari || slot.Type == SlotType.Horde || slot.Type == SlotType.SOS; } internal static bool IsSafariSlot(SlotType t) { return t == SlotType.Grass_Safari || t == SlotType.Surf_Safari || t == SlotType.Rock_Smash_Safari || t == SlotType.Pokeradar_Safari || t == SlotType.Old_Rod_Safari || t == SlotType.Good_Rod_Safari || t == SlotType.Super_Rod_Safari; } internal static bool IsDexNavValid(PKM pkm) { if (!pkm.AO || !pkm.InhabitedGeneration(6)) return false; IEnumerable locs = GetDexNavAreas(pkm); var d_areas = locs.Select(loc => GetValidEncounterSlots(pkm, loc, DexNav: true)); return d_areas.Any(slots => slots.Any(slot => slot.Permissions.AllowDexNav && slot.Permissions.DexNav)); } private static bool IsEncounterTypeMatch(IEncounterable e, int type) { switch (e) { case EncounterStaticTyped t: return t.TypeEncounter.Contains(type); case EncounterSlot w: return w.TypeEncounter.Contains(type); default: return type == 0; } } internal static EncounterArea GetCaptureLocation(PKM pkm) { return (from area in GetEncounterSlots(pkm, 100) let slots = GetValidEncounterSlots(pkm, area, pkm.AO, ignoreLevel: true).ToArray() where slots.Any() select new EncounterArea { Location = area.Location, Slots = slots, }).OrderBy(area => area.Slots.Min(x => x.LevelMin)).FirstOrDefault(); } internal static EncounterStatic GetStaticLocation(PKM pkm, int species = -1) { switch (pkm.GenNumber) { case 1: return GetRBYStaticTransfer(species, pkm.Met_Level); case 2: return GetGSStaticTransfer(species, pkm.Met_Level); default: var table = GetEncounterStaticTable(pkm, (GameVersion)pkm.Version); return GetStatic(pkm, table, lvl: 100, skip: true).FirstOrDefault(); } } internal static bool IsVCStaticTransferEncounterValid(PKM pkm, EncounterStatic e) { return pkm.Met_Location == e.Location && pkm.Egg_Location == e.EggLocation; } internal static IEncounterable GetVCStaticTransferEncounter(PKM pkm) { if (pkm.VC1) return GetRBYStaticTransfer(pkm.Species, pkm.Met_Level); if (pkm.VC2) return GetGSStaticTransfer(pkm.Species, pkm.Met_Level); return new EncounterInvalid(pkm); } private static EncounterStatic GetRBYStaticTransfer(int species, int pkmMetLevel) { return new EncounterStatic { Species = species, Gift = true, // Forces Poké Ball Ability = TransferSpeciesDefaultAbility_1.Contains(species) ? 1 : 4, // Hidden by default, else first Shiny = species == 151 ? (bool?)false : null, Fateful = species == 151, Location = 30013, EggLocation = 0, IV3 = true, Level = pkmMetLevel, Version = GameVersion.RBY }; } private static EncounterStatic GetGSStaticTransfer(int species, int pkmMetLevel) { return new EncounterStatic { Species = species, Gift = true, // Forces Poké Ball Ability = TransferSpeciesDefaultAbility_2.Contains(species) ? 1 : 4, // Hidden by default, else first Shiny = species == 151 || species == 251 ? (bool?)false : null, Fateful = species == 151 || species == 251, Location = 30004, // todo EggLocation = 0, IV3 = true, Level = pkmMetLevel, Version = GameVersion.GS }; } internal static bool IsEncounterTrade1Valid(PKM pkm) { string ot = pkm.OT_Name; string tr = pkm.Format <= 2 ? "TRAINER" : "Trainer"; // decaps on transfer return ot == "トレーナー" || ot == tr; } private static bool IsWurmpleEvoValid(PKM pkm) { uint evoVal = PKX.GetWurmpleEvoVal(pkm.EncryptionConstant); int wIndex = Array.IndexOf(WurmpleEvolutions, pkm.Species) / 2; return evoVal == wIndex; } } }