PKHeX/PKHeX.Core/Legality/Encounters/EncounterGenerator.cs
Kurt 68aa9aeec9 Relocate encounter data loading to separate files
shifts encounter data from Legal's split tables to individual files for
easier maintenance and initialization. Legal Core's init is so much
simpler now.

fix resource name typo
2017-07-25 00:28:43 -07:00

1337 lines
56 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using static PKHeX.Core.Legal;
namespace PKHeX.Core
{
public static class EncounterGenerator
{
public static IEnumerable<IEncounterable> 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<IEncounterable> 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<IEncounterable> GetEncounters3(PKM pkm, LegalInfo info)
{
info.PIDIV = MethodFinder.Analyze(pkm);
var deferred = new List<IEncounterable>();
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);
}
info.PIDIVMatches = false;
foreach (var z in deferred)
yield return z;
}
private static IEnumerable<IEncounterable> GetEncounters4(PKM pkm, LegalInfo info)
{
info.PIDIV = MethodFinder.Analyze(pkm);
var deferred = new List<IEncounterable>();
foreach (var z in GenerateRawEncounters4(pkm))
{
if (info.PIDIV.Type.IsCompatible4(z, pkm))
yield return z;
else
deferred.Add(z);
}
info.PIDIVMatches = false;
foreach (var z in deferred)
yield return z;
}
private static IEnumerable<GBEncounterData> 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<int> species = new HashSet<int>(vs.Select(p => p.Species).ToList());
var deferred = new List<IEncounterable>();
foreach (var t in GetValidEncounterTrades(pkm, game))
{
yield return new GBEncounterData(pkm, gen, t, game);
}
foreach (var s in GetValidStaticEncounter(pkm, game))
{
// Valid stadium and non-stadium encounters, return only non-stadium encounters, they are less restrictive
if (!species.Contains(s.Species))
continue;
if (game == GameVersion.RBY && s.Species != 54 && s.Version == GameVersion.Stadium)
{
deferred.Add(s);
continue;
}
if (game == GameVersion.GSC && !s.EggEncounter && s.Version == GameVersion.C && !pkm.HasOriginalMetLocation)
continue;
yield return new GBEncounterData(pkm, gen, s, game);
}
foreach (var e in GetValidWildEncounters(pkm, game))
{
if (!species.Contains(e.Species))
continue;
yield return new GBEncounterData(pkm, gen, e, game);
}
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)
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<GBEncounterData> GenerateFilteredEncounters(PKM pkm)
{
bool crystal = pkm.Format == 2 && pkm.Met_Location != 0;
var g1i = new PeekEnumerator<GBEncounterData>(get1().GetEnumerator());
var g2i = new PeekEnumerator<GBEncounterData>(get2().GetEnumerator());
var deferred = new List<GBEncounterData>();
while (g2i.PeekIsNext() || g1i.PeekIsNext())
{
PeekEnumerator<GBEncounterData> move;
if (g1i.PeekIsNext())
{
if (g2i.PeekIsNext())
move = g1i.Peek().Type > g2i.Peek().Type ? g1i : g2i;
else
move = g1i;
}
else
move = 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<GBEncounterData> get1()
{
if (!pkm.Gen2_NotTradeback && !crystal)
foreach (var z in GenerateRawEncounters12(pkm, GameVersion.RBY))
yield return z;
}
IEnumerable<GBEncounterData> get2()
{
if (!pkm.Gen1_NotTradeback && AllowGen2VCTransfer)
foreach (var z in GenerateRawEncounters12(pkm, crystal ? GameVersion.C : GameVersion.GSC))
yield return z;
}
}
private static IEnumerable<IEncounterable> 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<IEncounterable> GenerateRawEncounters4(PKM pkm)
{
int ctr = 0;
bool wasEvent = pkm.WasEvent || pkm.WasEventEgg; // egg events?
if (wasEvent)
{
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; }
}
bool safariSport = pkm.Ball == 0x05 || pkm.Ball == 0x18; // never static encounters
if (!safariSport)
foreach (var z in GetValidStaticEncounter(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;
// 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;
}
private static IEnumerable<IEncounterable> GenerateRawEncounters3(PKM pkm)
{
foreach (var z in GetValidGifts(pkm))
yield return z;
bool safari = pkm.Ball == 0x05; // never static encounters
if (!safari)
foreach (var z in GetValidStaticEncounter(pkm))
yield return z;
foreach (var z in GetValidWildEncounters(pkm))
yield return z;
foreach (var z in GetValidEncounterTrades(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;
if (pkm.Version == 15)
yield break; // no eggs in C/XD
foreach (var z in GenerateEggs(pkm))
yield return z;
}
// EncounterStatic
private static bool IsEncounterTypeMatch(IEncounterable e, int type)
{
return type == 0 && !(e is EncounterStaticTyped)
|| e is EncounterStaticTyped t && t.TypeEncounter.Contains(type);
}
private static IEnumerable<EncounterStatic> GetValidStaticEncounter(PKM pkm, GameVersion gameSource = GameVersion.Any)
{
if (gameSource == GameVersion.Any)
gameSource = (GameVersion)pkm.Version;
// Get possible encounters
IEnumerable<EncounterStatic> poss = GetStaticEncounters(pkm, gameSource: gameSource);
int lvl = GetMinLevelEncounter(pkm);
if (lvl < 0)
yield break;
// Back Check against pkm
var enc = GetMatchingStaticEncounters(pkm, poss, lvl).ToList();
// Filter for encounter types; type is cleared on 6->7 transfer
if (!pkm.Gen4 || pkm.Format >= 7)
{
foreach (var e in enc)
yield return e;
yield break;
}
// Yield out if type matches, else defer to end if no matches were yielded
int ctr = 0;
int type = pkm.EncounterType;
var pass = new List<EncounterStatic>();
foreach (var e in enc)
{
if (IsEncounterTypeMatch(e, type))
{ yield return e; ++ctr; }
else pass.Add(e);
}
if (ctr != 0)
yield break;
foreach (var e in pass)
yield return e;
}
private static IEnumerable<EncounterStatic> GetMatchingStaticEncounters(PKM pkm, IEnumerable<EncounterStatic> poss, int lvl)
{
// check for petty rejection scenarios that will be flagged by other legality checks
var deferred = new List<EncounterStatic>();
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)
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 || 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;
if (pkm is PK1 pk1 && pkm.Gen1_NotTradeback)
{
var catch_rate = pk1.Catch_Rate;
var japanese = pk1.Japanese;
// Pure gen 1, trades can be filter by catch rate
if ((pkm.Species == 25 || pkm.Species == 26) && catch_rate == 190)
// Red Blue Pikachu, is not a static encounter
continue;
if (e.Version == GameVersion.Stadium)
{
if (e.Species != 054 && !Stadium_CatchRate.Contains(catch_rate))
continue;
// Amnesia Psyduck have different catch rate in japanese stadium and international stadium
if (e.Species == 054 && japanese && catch_rate != 167)
continue;
if (e.Species == 054 && !japanese && catch_rate != 168)
continue;
}
// Encounters with different catch rates in yellow and redblue are duplicated with different gameverion
else if (e.Version == GameVersion.YW && catch_rate != PersonalTable.Y[e.Species].CatchRate)
continue;
else if (e.Version != GameVersion.YW && catch_rate != PersonalTable.RB[e.Species].CatchRate)
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 (!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<EncounterStatic> GetStaticEncounters(PKM pkm, int lvl = -1, 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, lvl: lvl);
case 2:
return GetStatic(pkm, table, maxspeciesorigin: MaxSpeciesID_2, lvl: lvl);
default:
return GetStatic(pkm, table, lvl);
}
}
private static IEnumerable<EncounterStatic> GetStatic(PKM pkm, IEnumerable<EncounterStatic> table, int maxspeciesorigin = -1, int lvl = -1)
{
IEnumerable<DexLevel> dl = GetValidPreEvolutions(pkm, maxspeciesorigin: maxspeciesorigin, lvl: lvl);
return table.Where(e => dl.Any(d => d.Species == e.Species));
}
// EncounterSlot
private static IEnumerable<EncounterSlot> 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<EncounterSlot> 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 gen = pkm.GenNumber;
int species = pkm.Species;
bool CheckEncounterType = gen == 4 && pkm.Format != 7;
var deferred = new List<EncounterSlot>();
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 if (CheckEncounterType && !slot.TypeEncounter.Contains(pkm.EncounterType)) { } // incorrect encounter type
else
{
yield return slot;
continue;
}
deferred.Add(slot);
}
foreach (var d in deferred)
yield return d;
}
private static IEnumerable<EncounterSlot> 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<DexLevel> vs = GetValidPreEvolutions(pkm);
foreach (DexLevel d in vs.Where(d => FriendSafari.Contains(d.Species) && d.Level >= 30))
{
yield return new EncounterSlot
{
Species = d.Species,
LevelMin = 30,
LevelMax = 30,
Form = 0,
Type = SlotType.FriendSafari,
};
}
}
private static IEnumerable<EncounterSlot> 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<DexLevel> 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<EncounterSlot> slots = loc.Slots.Where(slot => vs.Any(evo => evo.Species == slot.Species && (ignoreLevel || evo.Level >= slot.LevelMin - df)));
List<EncounterSlot> 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<EncounterSlot> 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<DexLevel> 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
switch (pkm.Species)
{
// Pikachu
case 25 when pk1.Catch_Rate == 163:
case 26 when pk1.Catch_Rate == 163:
return false; // Yellow Pikachu is not a wild encounter
// Kadabra (YW)
case 64 when pk1.Catch_Rate == 96:
case 65 when pk1.Catch_Rate == 96:
vs = vs.Where(s => s.Species == 64);
Gen1Version = GameVersion.YW;
return true;
// Kadabra (RB)
case 64 when pk1.Catch_Rate == 100:
case 65 when pk1.Catch_Rate == 100:
vs = vs.Where(s => s.Species == 64);
Gen1Version = GameVersion.RB;
return true;
// Dragonair (YW)
case 148 when pk1.Catch_Rate == 27:
case 149 when pk1.Catch_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 => pk1.Catch_Rate == PersonalTable.RB[s.Species].CatchRate);
RBDragonair = true;
return true;
default:
vs = vs.Where(s => pk1.Catch_Rate == PersonalTable.RB[s.Species].CatchRate);
return true;
}
}
private static IEnumerable<EncounterSlot> FilterGBSlots(PKM pkm, int gen, GameVersion Gen1Version, IEnumerable<EncounterSlot> 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<EncounterArea> 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<EncounterArea> 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<EncounterArea> GetSlots(PKM pkm, IEnumerable<EncounterArea> tables, int lvl = -1)
{
IEnumerable<DexLevel> vs = GetValidPreEvolutions(pkm, lvl: lvl);
foreach (var loc in tables)
{
IEnumerable<EncounterSlot> 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<EncounterLink> GetValidLinkGifts(PKM pkm)
{
switch (pkm.GenNumber)
{
case 6:
return Encounters6.LinkGifts6.Where(g => g.Species == pkm.Species && g.Level == pkm.Met_Level);
default:
return new EncounterLink[0];
}
}
// 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 : null;
}
return null;
}
private static IEnumerable<EncounterTrade> 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<EncounterTrade> 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<EncounterTrade> GetValidEncounterTradesVC1(PKM pkm, DexLevel[] p, IEnumerable<EncounterTrade> 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<EncounterTrade> 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 == 0 || lang == 6)
yield break;
int lvl = GetMinLevelEncounter(pkm);
if (lvl <= 0)
yield break;
// Get valid pre-evolutions
IEnumerable<DexLevel> 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.Shiny ^ pkm.IsShiny) // Are PIDs static?
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.Nature != Nature.Random && (int)z.Nature != pkm.Nature)
return false;
if (z.Gender != -1 && z.Gender != pkm.Gender)
return false;
if (z.OTGender != -1 && z.OTGender != pkm.OT_Gender)
return false;
// if (z.Ability == 4 ^ pkm.AbilityNumber == 4) // defer to Ability
// countinue;
return true;
}
// MysteryGift
private static IEnumerable<MysteryGift> 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 new List<MysteryGift>();
}
}
private static IEnumerable<MysteryGift> GetMatchingWC3(PKM pkm, IEnumerable<MysteryGift> DB)
{
if (DB == null)
yield break;
var validWC3 = new List<MysteryGift>();
var vs = GetValidPreEvolutions(pkm, MaxSpeciesID_3).ToArray();
foreach (WC3 wc in DB.OfType<WC3>().Where(wc => vs.Any(dl => dl.Species == wc.Species)))
{
// Gen3 Version MUST match.
if (wc.Version != 0 && !((GameVersion)wc.Version).Contains((GameVersion)pkm.Version))
continue;
bool hatchedEgg = wc.IsEgg && !pkm.IsEgg;
if (!hatchedEgg)
{
if (wc.SID != -1 && wc.SID != pkm.SID) continue;
if (wc.TID != -1 && wc.TID != pkm.TID) continue;
if (wc.OT_Name != null && wc.OT_Name != pkm.OT_Name) continue;
if (wc.OT_Gender < 3 && wc.OT_Gender != pkm.OT_Gender) continue;
}
if (wc.Language != -1 && wc.Language != pkm.Language) continue;
if (wc.Ball != pkm.Ball) continue;
if (wc.Fateful != pkm.FatefulEncounter) continue;
if (pkm.IsNative)
{
if (wc.Met_Level != pkm.Met_Level)
continue;
if (wc.Met_Location != pkm.Met_Location && (!wc.IsEgg || pkm.IsEgg))
continue;
}
else
{
if (pkm.IsEgg)
break;
if (wc.Level > pkm.Met_Level)
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<MysteryGift> GetMatchingPCD(PKM pkm, IEnumerable<MysteryGift> DB)
{
if (DB == null || pkm.IsEgg && pkm.Format != 4) // transferred
yield break;
if (IsRangerManaphy(pkm))
{
yield return new PGT { Data = { [0] = 7, [8] = 1 } };
yield break;
}
var validPCD = new List<MysteryGift>();
var vs = GetValidPreEvolutions(pkm).ToArray();
foreach (PCD mg in DB.OfType<PCD>().Where(wc => vs.Any(dl => dl.Species == wc.Species)))
{
var wc = mg.Gift.PK;
if (!wc.IsEgg)
{
if (wc.TID != pkm.TID) continue;
if (wc.SID != pkm.SID) continue;
if (wc.OT_Name != pkm.OT_Name) continue;
if (wc.OT_Gender != pkm.OT_Gender) continue;
if (wc.Language != 0 && wc.Language != pkm.Language) continue;
if (pkm.Format != 4) // transferred
{
// met location: deferred to general transfer check
if (wc.CurrentLevel > pkm.Met_Level) continue;
}
else
{
if (wc.Egg_Location + 3000 != pkm.Met_Location) continue;
if (wc.CurrentLevel != pkm.Met_Level) continue;
}
}
else // Egg
{
if (wc.Egg_Location + 3000 != pkm.Egg_Location && pkm.Egg_Location != 2002) // traded
continue;
if (wc.CurrentLevel != pkm.Met_Level)
continue;
if (pkm.IsEgg && !pkm.IsNative)
continue;
}
if (wc.AltForm != pkm.AltForm && vs.All(dl => !IsFormChangeable(pkm, dl.Species))) continue;
if (wc.Ball != pkm.Ball) continue;
if (wc.OT_Gender < 3 && wc.OT_Gender != pkm.OT_Gender) continue;
if (wc.PID == 1 && pkm.IsShiny) continue;
if (wc.Gender != 3 && wc.Gender != pkm.Gender) continue;
if (wc.CNT_Cool > pkm.CNT_Cool) continue;
if (wc.CNT_Beauty > pkm.CNT_Beauty) continue;
if (wc.CNT_Cute > pkm.CNT_Cute) continue;
if (wc.CNT_Smart > pkm.CNT_Smart) continue;
if (wc.CNT_Tough > pkm.CNT_Tough) continue;
if (wc.CNT_Sheen > pkm.CNT_Sheen) continue;
bool receivable = mg.CanBeReceivedBy(pkm.Version);
if (wc.Species == pkm.Species && receivable) // best match
yield return mg;
else
validPCD.Add(mg);
}
foreach (var z in validPCD)
yield return z;
}
private static IEnumerable<MysteryGift> GetMatchingPGF(PKM pkm, IEnumerable<MysteryGift> DB)
{
if (DB == null)
yield break;
var validPGF = new List<MysteryGift>();
var vs = GetValidPreEvolutions(pkm).ToArray();
foreach (PGF wc in DB.OfType<PGF>().Where(wc => vs.Any(dl => dl.Species == wc.Species)))
{
if (!wc.IsEgg)
{
if (wc.SID != pkm.SID) continue;
if (wc.TID != pkm.TID) continue;
if (wc.OT != pkm.OT_Name) continue;
if (wc.OTGender < 3 && wc.OTGender != pkm.OT_Gender) continue;
if (wc.PID != 0 && pkm.PID != wc.PID) continue;
if (wc.PIDType == 0 && pkm.IsShiny) continue;
if (wc.PIDType == 2 && !pkm.IsShiny) continue;
if (wc.OriginGame != 0 && wc.OriginGame != pkm.Version) continue;
if (wc.Language != 0 && wc.Language != pkm.Language) continue;
if (wc.EggLocation != pkm.Egg_Location) continue;
if (wc.MetLocation != pkm.Met_Location) continue;
}
else
{
if (wc.EggLocation != pkm.Egg_Location && pkm.Egg_Location != 30002) // traded
continue;
if (pkm.IsEgg && !pkm.IsNative)
continue;
}
if (wc.Form != pkm.AltForm && vs.All(dl => !IsFormChangeable(pkm, dl.Species))) continue;
if (wc.Level != pkm.Met_Level) continue;
if (wc.Ball != pkm.Ball) continue;
if (wc.Nature != 0xFF && wc.Nature != pkm.Nature) continue;
if (wc.Gender != 2 && wc.Gender != pkm.Gender) continue;
if (wc.CNT_Cool > pkm.CNT_Cool) continue;
if (wc.CNT_Beauty > pkm.CNT_Beauty) continue;
if (wc.CNT_Cute > pkm.CNT_Cute) continue;
if (wc.CNT_Smart > pkm.CNT_Smart) continue;
if (wc.CNT_Tough > pkm.CNT_Tough) continue;
if (wc.CNT_Sheen > pkm.CNT_Sheen) continue;
if (wc.Species == pkm.Species) // best match
yield return wc;
else
validPGF.Add(wc);
}
foreach (var z in validPGF)
yield return z;
}
private static IEnumerable<MysteryGift> GetMatchingWC6(PKM pkm, IEnumerable<MysteryGift> DB)
{
if (DB == null)
yield break;
List<MysteryGift> validWC6 = new List<MysteryGift>();
var vs = GetValidPreEvolutions(pkm).ToArray();
foreach (WC6 wc in DB.OfType<WC6>().Where(wc => vs.Any(dl => dl.Species == wc.Species)))
{
if (pkm.Egg_Location == 0) // Not Egg
{
if (wc.CardID != pkm.SID) continue;
if (wc.TID != pkm.TID) continue;
if (wc.OT != pkm.OT_Name) continue;
if (wc.OTGender != pkm.OT_Gender) continue;
if (wc.PIDType == 0 && pkm.PID != wc.PID) continue;
if (wc.PIDType == 2 && !pkm.IsShiny) continue;
if (wc.PIDType == 3 && pkm.IsShiny) continue;
if (wc.OriginGame != 0 && wc.OriginGame != pkm.Version) continue;
if (wc.EncryptionConstant != 0 && wc.EncryptionConstant != pkm.EncryptionConstant) continue;
if (wc.Language != 0 && wc.Language != pkm.Language) continue;
}
if (wc.Form != pkm.AltForm && vs.All(dl => !IsFormChangeable(pkm, dl.Species))) continue;
if (wc.IsEgg)
{
if (wc.EggLocation != pkm.Egg_Location && pkm.Egg_Location != 30002) // traded
continue;
if (pkm.IsEgg && !pkm.IsNative)
continue;
}
else
{
if (wc.EggLocation != pkm.Egg_Location) continue;
if (wc.MetLocation != pkm.Met_Location) continue;
}
if (wc.Level != pkm.Met_Level) continue;
if (wc.Ball != pkm.Ball) continue;
if (wc.OTGender < 3 && wc.OTGender != pkm.OT_Gender) continue;
if (wc.Nature != 0xFF && wc.Nature != pkm.Nature) continue;
if (wc.Gender != 3 && wc.Gender != pkm.Gender) continue;
if (wc.CNT_Cool > pkm.CNT_Cool) continue;
if (wc.CNT_Beauty > pkm.CNT_Beauty) continue;
if (wc.CNT_Cute > pkm.CNT_Cute) continue;
if (wc.CNT_Smart > pkm.CNT_Smart) continue;
if (wc.CNT_Tough > pkm.CNT_Tough) continue;
if (wc.CNT_Sheen > pkm.CNT_Sheen) continue;
if (wc.Species == pkm.Species) // best match
yield return wc;
else
validWC6.Add(wc);
}
foreach (var z in validWC6)
yield return z;
}
private static IEnumerable<MysteryGift> GetMatchingWC7(PKM pkm, IEnumerable<MysteryGift> DB)
{
if (DB == null)
yield break;
List<MysteryGift> validWC7 = new List<MysteryGift>();
var vs = GetValidPreEvolutions(pkm).ToArray();
foreach (WC7 wc in DB.OfType<WC7>().Where(wc => vs.Any(dl => dl.Species == wc.Species)))
{
if (pkm.Egg_Location == 0) // Not Egg
{
if (wc.OTGender != 3)
{
if (wc.SID != pkm.SID) continue;
if (wc.TID != pkm.TID) continue;
if (wc.OTGender != pkm.OT_Gender) continue;
}
if (!string.IsNullOrEmpty(wc.OT) && wc.OT != pkm.OT_Name) continue;
if (wc.OriginGame != 0 && wc.OriginGame != pkm.Version) continue;
if (wc.EncryptionConstant != 0 && wc.EncryptionConstant != pkm.EncryptionConstant) continue;
if (wc.Language != 0 && wc.Language != pkm.Language) continue;
}
if (wc.Form != pkm.AltForm && vs.All(dl => !IsFormChangeable(pkm, dl.Species))) continue;
if (wc.IsEgg)
{
if (wc.EggLocation != pkm.Egg_Location && pkm.Egg_Location != 30002) // traded
continue;
if (pkm.IsEgg && !pkm.IsNative)
continue;
}
else
{
if (wc.EggLocation != pkm.Egg_Location) continue;
if (wc.MetLocation != pkm.Met_Location) continue;
}
if (wc.MetLevel != pkm.Met_Level) continue;
if (wc.Ball != pkm.Ball) continue;
if (wc.OTGender < 3 && wc.OTGender != pkm.OT_Gender) continue;
if (wc.Nature != 0xFF && wc.Nature != pkm.Nature) continue;
if (wc.Gender != 3 && wc.Gender != pkm.Gender) continue;
if (wc.CNT_Cool > pkm.CNT_Cool) continue;
if (wc.CNT_Beauty > pkm.CNT_Beauty) continue;
if (wc.CNT_Cute > pkm.CNT_Cute) continue;
if (wc.CNT_Smart > pkm.CNT_Smart) continue;
if (wc.CNT_Tough > pkm.CNT_Tough) continue;
if (wc.CNT_Sheen > pkm.CNT_Sheen) continue;
if (wc.PIDType == 2 && !pkm.IsShiny) continue;
if (wc.PIDType == 3 && pkm.IsShiny) continue;
if ((pkm.SID << 16 | pkm.TID) == 0x79F57B49) // Greninja WC has variant PID and can arrive @ 36 or 37
{
if (!pkm.IsShiny)
validWC7.Add(wc);
continue;
}
if (wc.PIDType == 0 && pkm.PID != wc.PID) continue;
if (wc.Species == pkm.Species) // best match
yield return wc;
else
validWC7.Add(wc);
}
foreach (var z in validWC7)
yield return z;
}
// EncounterEgg
private static IEnumerable<EncounterEgg> GenerateEggs(PKM pkm)
{
if (NoHatchFromEgg.Contains(pkm.Species))
yield break;
int lvl = pkm.GenNumber < 4 ? 5 : 1;
var ver = (GameVersion) pkm.Version; // version is a true indicator for all generation 3+ origins
int max = GetMaxSpeciesOrigin(pkm.GenNumber);
var baseSpecies = GetBaseSpecies(pkm, 0);
if (baseSpecies <= max)
yield return new EncounterEgg { Game = ver, Level = lvl, Species = baseSpecies };
if (GetSplitBreedGeneration(pkm).Contains(pkm.Species) && (baseSpecies = GetBaseSpecies(pkm, 1)) <= max)
yield return new EncounterEgg { Game = ver, Level = lvl, Species = baseSpecies, SplitBreed = true };
}
// 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<EncounterArea> locs = GetDexNavAreas(pkm);
return locs.Select(loc => GetValidEncounterSlots(pkm, loc, DexNav: true)).Any(slots => slots.Any(slot => slot.Permissions.AllowDexNav && slot.Permissions.DexNav));
}
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:
return GetStaticEncounters(pkm, 100).OrderBy(s => s.Level).FirstOrDefault();
}
}
internal 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
};
}
internal 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;
}
}
}