Refactoring +docs

Add some documentation for the high-level encounter finding
Fix encounterstatics not being filtered for the associated game version
(closes #1372)
was a side effect of refactoring this week
This commit is contained in:
Kurt 2017-07-30 12:31:17 -07:00
parent 1f7a098751
commit d3a30ebf35
12 changed files with 193 additions and 64 deletions

View File

@ -17,8 +17,7 @@ public partial class LegalityAnalysis
private IEncounterable EncounterOriginalGB;
private IEncounterable EncounterMatch => Info.EncounterMatch;
private Type Type; // Parent class when applicable (EncounterStatic / MysteryGift)
private Type MatchedType; // Child class if applicable (WC6, PGF, etc)
private string EncounterName => (EncounterOriginalGB ?? EncounterMatch).GetEncounterTypeName() + $" ({SpeciesStrings[EncounterMatch.Species]})";
private string EncounterName => $"{(EncounterOriginalGB ?? EncounterMatch).GetEncounterTypeName()} ({SpeciesStrings[EncounterMatch.Species]})";
private CheckResult Encounter, History;
public bool Parsed { get; }
@ -213,43 +212,14 @@ private void UpdateTradebackG12()
{
if (pkm.Format == 1)
{
if (!Legal.AllowGen1Tradeback)
{
pkm.TradebackStatus = TradebackType.Gen1_NotTradeback;
((PK1)pkm).CatchRateIsItem = false;
return;
}
// Detect tradeback status by comparing the catch rate(Gen1)/held item(Gen2) to the species in the pkm's evolution chain.
var catch_rate = ((PK1)pkm).Catch_Rate;
// For species catch rate, discard any species that has no valid encounters and a different catch rate than their pre-evolutions
var Lineage = Legal.GetLineage(pkm).Where(s => !Legal.Species_NotAvailable_CatchRate.Contains(s)).ToList();
// Dragonite's Catch Rate is different than Dragonair's in Yellow, but there is no Dragonite encounter.
var RGBCatchRate = Lineage.Any(s => catch_rate == PersonalTable.RB[s].CatchRate);
var YCatchRate = Lineage.Any(s => s != 149 && catch_rate == PersonalTable.Y[s].CatchRate);
// Krabby encounter trade special catch rate
var TradeCatchRate = ((pkm.Species == 098 || pkm.Species == 099) && catch_rate == 204);
var StadiumCatchRate = Legal.Stadium_GiftSpecies.Contains(pkm.Species) && Legal.Stadium_CatchRate.Contains(catch_rate);
bool matchAny = RGBCatchRate || YCatchRate || TradeCatchRate || StadiumCatchRate;
// If the catch rate value has been modified, the item has either been removed or swapped in Generation 2.
var HeldItemCatchRate = catch_rate == 0 || Legal.HeldItems_GSC.Any(h => h == catch_rate);
if (HeldItemCatchRate && !matchAny)
pkm.TradebackStatus = TradebackType.WasTradeback;
else if (!HeldItemCatchRate && matchAny)
pkm.TradebackStatus = TradebackType.Gen1_NotTradeback;
else
pkm.TradebackStatus = TradebackType.Any;
// Update the editing settings for the PKM to acknowledge the tradeback status if the species is changed.
((PK1)pkm).CatchRateIsItem = !pkm.Gen1_NotTradeback && HeldItemCatchRate && !matchAny;
Legal.GetTradebackStatusRBY(pkm);
return;
}
else if (pkm.Format == 2 || pkm.VC2)
if (pkm.Format == 2 || pkm.VC2)
{
// Eggs, pokemon with non-empty Crystal met location, and generation 2 species without generation 1 preevolutions can't be traded to generation 1.
if (pkm.IsEgg || pkm.HasOriginalMetLocation || (pkm.Species > Legal.MaxSpeciesID_1 && !Legal.FutureEvolutionsGen1.Contains(pkm.Species)))
// check for impossible tradeback scenarios
if (pkm.IsEgg || pkm.HasOriginalMetLocation || pkm.Species > Legal.MaxSpeciesID_1 && !Legal.FutureEvolutionsGen1.Contains(pkm.Species))
pkm.TradebackStatus = TradebackType.Gen2_NotTradeback;
else
pkm.TradebackStatus = TradebackType.Any;
@ -279,7 +249,7 @@ private void UpdateTypeInfo()
// Example: GSC Pokemon with only possible encounters in RBY, like the legendary birds
pkm.TradebackStatus = TradebackType.WasTradeback;
MatchedType = Type = (EncounterOriginalGB ?? EncounterMatch)?.GetType();
Type = (EncounterOriginalGB ?? EncounterMatch)?.GetType();
var bt = Type.GetTypeInfo().BaseType;
if (bt != null && !(bt == typeof(Array) || bt == typeof(object) || bt.GetTypeInfo().IsPrimitive)) // a parent exists
Type = bt; // use base type

View File

@ -1748,7 +1748,7 @@ private CheckResult VerifyHistory()
if (pkm.OT_Memory != 0)
return new CheckResult(Severity.Invalid, V151, CheckIdentifier.History);
}
else if (MatchedType != typeof(WC6))
else if (!(EncounterMatch is WC6))
{
if (pkm.OT_Memory == 0 ^ !pkm.Gen6)
return new CheckResult(Severity.Invalid, V152, CheckIdentifier.History);

View File

@ -1340,6 +1340,44 @@ private static int GetMinLevelGeneration(PKM pkm, int generation)
return 1;
}
private static bool GetCatchRateMatchesPreEvolution(PKM pkm, int catch_rate)
{
// For species catch rate, discard any species that has no valid encounters and a different catch rate than their pre-evolutions
var Lineage = GetLineage(pkm).Where(s => !Species_NotAvailable_CatchRate.Contains(s)).ToList();
return IsCatchRateRB(Lineage) || IsCatchRateY(Lineage) || IsCatchRateTrade() || IsCatchRateStadium();
// Dragonite's Catch Rate is different than Dragonair's in Yellow, but there is no Dragonite encounter.
bool IsCatchRateRB(List<int> ds) => ds.Any(s => catch_rate == PersonalTable.RB[s].CatchRate);
bool IsCatchRateY(List<int> ds) => ds.Any(s => s != 149 && catch_rate == PersonalTable.Y[s].CatchRate);
// Krabby encounter trade special catch rate
bool IsCatchRateTrade() => (pkm.Species == 098 || pkm.Species == 099) && catch_rate == 204;
bool IsCatchRateStadium() => Stadium_GiftSpecies.Contains(pkm.Species) && Stadium_CatchRate.Contains(catch_rate);
}
internal static void GetTradebackStatusRBY(PKM pkm)
{
if (!AllowGen1Tradeback)
{
pkm.TradebackStatus = TradebackType.Gen1_NotTradeback;
((PK1)pkm).CatchRateIsItem = false;
return;
}
// Detect tradeback status by comparing the catch rate(Gen1)/held item(Gen2) to the species in the pkm's evolution chain.
var catch_rate = ((PK1)pkm).Catch_Rate;
bool matchAny = GetCatchRateMatchesPreEvolution(pkm, catch_rate);
// If the catch rate value has been modified, the item has either been removed or swapped in Generation 2.
var HeldItemCatchRate = catch_rate == 0 || HeldItems_GSC.Any(h => h == catch_rate);
if (HeldItemCatchRate && !matchAny)
pkm.TradebackStatus = TradebackType.WasTradeback;
else if (!HeldItemCatchRate && matchAny)
pkm.TradebackStatus = TradebackType.Gen1_NotTradeback;
else
pkm.TradebackStatus = TradebackType.Any;
// Update the editing settings for the PKM to acknowledge the tradeback status if the species is changed.
((PK1)pkm).CatchRateIsItem = !pkm.Gen1_NotTradeback && HeldItemCatchRate && !matchAny;
}
internal static DexLevel[][] GetEvolutionChainsAllGens(PKM pkm, IEncounterable Encounter)
{

View File

@ -3,11 +3,31 @@
namespace PKHeX.Core
{
/// <summary>
/// Miscellaneous setup utility for legality checking <see cref="IEncounterable"/> data sources.
/// </summary>
internal static class EncounterUtil
{
internal static EncounterArea[] GetEncounterTables(GameVersion Game)
/// <summary>
/// Gets the relevant <see cref="EncounterStatic"/> objects that appear in the relevant game.
/// </summary>
/// <param name="source">Table of valid encounters that appear for the game pairing</param>
/// <param name="game">Game to filter for</param>
/// <returns>Array of encounter objects that are encounterable on the input game</returns>
internal static EncounterStatic[] GetStaticEncounters(IEnumerable<EncounterStatic> source, GameVersion game)
{
switch (Game)
return source.Where(s => s.Version.Contains(game)).ToArray();
}
/// <summary>
/// Gets the <see cref="EncounterArea"/> data for the input game via the program's resource streams.
/// </summary>
/// <param name="game">Game to fetch for</param>
/// <remarks> <see cref="EncounterSlot.SlotNumber"/> data is not marked, as the RNG seed is 64 bits (permitting sufficient randomness).</remarks>
/// <returns>Array of areas that are encounterable on the input game.</returns>
internal static EncounterArea[] GetEncounterTables(GameVersion game)
{
switch (game)
{
case GameVersion.B: return GetEncounterTables("51", "b");
case GameVersion.W: return GetEncounterTables("51", "w");
@ -23,12 +43,23 @@ internal static EncounterArea[] GetEncounterTables(GameVersion Game)
return null; // bad request
}
/// <summary>
/// Direct fetch for <see cref="EncounterArea"/> data; can also be used to fetch supplementary encounter streams.
/// </summary>
/// <param name="ident">Unpacking identification ASCII characters (first two bytes of binary)</param>
/// <param name="resource">Resource name (will be prefixed with "encounter_"</param>
/// <returns>Array of encounter areas</returns>
internal static EncounterArea[] GetEncounterTables(string ident, string resource)
{
byte[] mini = Util.GetBinaryResource($"encounter_{resource}.pkl");
return EncounterArea.GetArray(Data.UnpackMini(mini, ident));
}
/// <summary>
/// Combines <see cref="EncounterArea"/> slot arrays with the same <see cref="EncounterArea.Location"/>.
/// </summary>
/// <param name="tables">Input encounter areas to combine</param>
/// <returns>Combined Array of encounter areas. No duplicate location IDs will be present.</returns>
internal static EncounterArea[] AddExtraTableSlots(params EncounterArea[][] tables)
{
return tables.SelectMany(s => s).GroupBy(l => l.Location)
@ -38,6 +69,12 @@ internal static EncounterArea[] AddExtraTableSlots(params EncounterArea[][] tabl
.ToArray();
}
/// <summary>
/// Marks Encounter Slots for party lead's ability slot influencing.
/// </summary>
/// <remarks>Magnet Pull attracts Steel type slots, and Static attracts Electric</remarks>
/// <param name="Areas">Encounter Area array for game</param>
/// <param name="t">Personal data for use with a given species' type</param>
internal static void MarkEncountersStaticMagnetPull(ref EncounterArea[] Areas, PersonalTable t)
{
const int steel = 8;
@ -67,19 +104,37 @@ internal static void MarkEncountersStaticMagnetPull(ref EncounterArea[] Areas, P
}
}
internal static void MarkEncountersGeneration(EncounterStatic[] Encounters, int Generation)
/// <summary>
/// Sets the <see cref="EncounterStatic.Generation"/> value, for use in determining split-generation origins.
/// </summary>
/// <remarks>Only used for Gen 1 & 2, as <see cref="PKM.Version"/> data is not present.</remarks>
/// <param name="Encounters">Ingame encounter data</param>
/// <param name="Generation">Generation number to set</param>
internal static void MarkEncountersGeneration(IEnumerable<EncounterStatic> Encounters, int Generation)
{
foreach (EncounterStatic Encounter in Encounters)
Encounter.Generation = Generation;
}
internal static void MarkEncountersVersion(EncounterArea[] Areas, GameVersion Version)
/// <summary>
/// Sets the <see cref="EncounterSlot1.Version"/> value, for use in determining split-generation origins.
/// </summary>
/// <remarks>Only used for Gen 1 & 2, as <see cref="PKM.Version"/> data is not present.</remarks>
/// <param name="Areas">Ingame encounter data</param>
/// <param name="Version">Version ID to set</param>
internal static void MarkEncountersVersion(IEnumerable<EncounterArea> Areas, GameVersion Version)
{
foreach (EncounterArea Area in Areas)
foreach (var Slot in Area.Slots.OfType<EncounterSlot1>())
Slot.Version = Version;
}
/// <summary>
/// Sets the <see cref="EncounterStatic.Generation"/> value, for use in determining split-generation origins.
/// </summary>
/// <remarks>Only used for Gen 1 & 2, as <see cref="PKM.Version"/> data is not present.</remarks>
/// <param name="Areas">Ingame encounter data</param>
/// <param name="Generation">Generation number to set</param>
internal static void MarkEncountersGeneration(IEnumerable<EncounterArea> Areas, int Generation)
{
foreach (EncounterArea Area in Areas)
@ -87,9 +142,13 @@ internal static void MarkEncountersGeneration(IEnumerable<EncounterArea> Areas,
Slot.Generation = Generation;
}
/// <summary>
/// Groups areas by location id, raw data has areas with different slots but the same location id.
/// </summary>
/// <remarks>Similar to <see cref="AddExtraTableSlots"/>, this method combines a single array.</remarks>
/// <param name="Areas">Ingame encounter data</param>
internal static void ReduceAreasSize(ref EncounterArea[] Areas)
{
// Group areas by location id, the raw data have areas with different slots but the same location id
Areas = Areas.GroupBy(a => a.Location).Select(a => new EncounterArea
{
Location = a.First().Location,
@ -97,15 +156,16 @@ internal static void ReduceAreasSize(ref EncounterArea[] Areas)
}).ToArray();
}
/// <summary>
/// Sets the <see cref="EncounterArea.Location"/> to the <see cref="EncounterSlot.Location"/> for identifying where the slot is encountered.
/// </summary>
/// <remarks>Some games / transferred <see cref="PKM"/> data do not contain original encounter location IDs; is mainly for info purposes.</remarks>
/// <param name="Areas">Ingame encounter data</param>
internal static void MarkSlotLocation(ref EncounterArea[] Areas)
{
foreach (EncounterArea Area in Areas)
{
foreach (EncounterSlot Slot in Area.Slots)
{
Slot.Location = Area.Location;
}
}
foreach (EncounterSlot Slot in Area.Slots)
Slot.Location = Area.Location;
}
}
}

View File

@ -15,11 +15,11 @@ internal static class Encounters3
static Encounters3()
{
StaticR = Encounter_RSE;
StaticS = Encounter_RSE;
StaticE = Encounter_RSE;
StaticFR = Encounter_FRLG;
StaticLG = Encounter_FRLG;
StaticR = GetStaticEncounters(Encounter_RSE, GameVersion.R);
StaticS = GetStaticEncounters(Encounter_RSE, GameVersion.S);
StaticE = GetStaticEncounters(Encounter_RSE, GameVersion.E);
StaticFR = GetStaticEncounters(Encounter_FRLG, GameVersion.FR);
StaticLG = GetStaticEncounters(Encounter_FRLG, GameVersion.LG);
EncounterArea[] get(string resource, string ident)
=> EncounterArea.GetArray3(Data.UnpackMini(Util.GetBinaryResource($"encounter_{resource}.pkl"), ident));

View File

@ -12,8 +12,12 @@ internal static class Encounters4
static Encounters4()
{
MarkG4PokeWalker(Encounter_PokeWalker);
StaticD = StaticP = StaticPt = Encounter_DPPt;
StaticHG = StaticSS = Encounter_HGSS.Concat(Encounter_PokeWalker).ToArray();
StaticD = GetStaticEncounters(Encounter_DPPt, GameVersion.B);
StaticP = GetStaticEncounters(Encounter_DPPt, GameVersion.W);
StaticPt = GetStaticEncounters(Encounter_DPPt, GameVersion.Pt);
var staticHGSS = Encounter_HGSS.Concat(Encounter_PokeWalker).ToArray();
StaticHG = GetStaticEncounters(staticHGSS, GameVersion.HG);
StaticSS = GetStaticEncounters(staticHGSS, GameVersion.SS);
byte[][] get(string resource, string ident)
=> Data.UnpackMini(Util.GetBinaryResource($"encounter_{resource}.pkl"), ident);

View File

@ -13,8 +13,12 @@ static Encounters5()
{
MarkG5DreamWorld(BW_DreamWorld);
MarkG5DreamWorld(B2W2_DreamWorld);
StaticB = StaticW = Encounter_BW.Concat(BW_DreamWorld).ToArray();
StaticB2 = StaticW2 = Encounter_B2W2.Concat(B2W2_DreamWorld).ToArray();
var staticbw = Encounter_BW.Concat(BW_DreamWorld).ToArray();
var staticb2w2 = Encounter_BW.Concat(BW_DreamWorld).ToArray();
StaticB = GetStaticEncounters(staticbw, GameVersion.B);
StaticW = GetStaticEncounters(staticbw, GameVersion.W);
StaticB2 = GetStaticEncounters(staticb2w2, GameVersion.B2);
StaticW2 = GetStaticEncounters(staticb2w2, GameVersion.W2);
var BSlots = GetEncounterTables(GameVersion.B);
var WSlots = GetEncounterTables(GameVersion.W);

View File

@ -9,8 +9,10 @@ internal static class Encounters6
static Encounters6()
{
StaticX = StaticY = Encounter_XY;
StaticA = StaticO = Encounter_AO;
StaticX = GetStaticEncounters(Encounter_XY, GameVersion.X);
StaticY = GetStaticEncounters(Encounter_XY, GameVersion.Y);
StaticA = GetStaticEncounters(Encounter_AO, GameVersion.AS);
StaticO = GetStaticEncounters(Encounter_AO, GameVersion.OR);
var XSlots = GetEncounterTables(GameVersion.X);
var YSlots = GetEncounterTables(GameVersion.Y);

View File

@ -10,7 +10,9 @@ internal static class Encounters7
static Encounters7()
{
StaticSN = StaticMN = Encounter_SM;
StaticSN = GetStaticEncounters(Encounter_SM, GameVersion.SN);
StaticMN = GetStaticEncounters(Encounter_SM, GameVersion.MN);
var REG_SN = GetEncounterTables(GameVersion.SN);
var REG_MN = GetEncounterTables(GameVersion.MN);
var SOS_SN = GetEncounterTables("sm", "sn_sos");

View File

@ -3,8 +3,23 @@
namespace PKHeX.Core
{
/// <summary>
/// Finds matching <see cref="IEncounterable"/> data and relevant <see cref="LegalInfo"/> for a <see cref="PKM"/>.
/// </summary>
public static class EncounterFinder
{
/// <summary>
/// Iterates through all possible encounters until a sufficient match is found
/// </summary>
/// <remarks>
/// The iterator lazily finds matching encounters, then verifies secondary checks to weed out any nonexact matches.
/// </remarks>
/// <param name="pkm">Source data to find a match for</param>
/// <returns>
/// Information containing the matched encounter and any parsed checks.
/// If no clean match is found, the last checked match is returned.
/// If no match is found, an invalid encounter object is returned.
/// </returns>
public static LegalInfo FindVerifiedEncounter(PKM pkm)
{
LegalInfo info = new LegalInfo(pkm);
@ -34,6 +49,18 @@ public static LegalInfo FindVerifiedEncounter(PKM pkm)
}
}
/// <summary>
/// Checks supplementary info to see if the encounter is still valid.
/// </summary>
/// <remarks>
/// When an encounter is initially validated, only encounter-related checks are performed.
/// By checking Moves, Evolution, and <see cref="PIDIV"/> data, a best match encounter can be found.
/// If the encounter is not valid, the method will not reject it unless another encounter is available to check.
/// </remarks>
/// <param name="pkm">Source data to check the match for</param>
/// <param name="info">Information containing the matched encounter</param>
/// <param name="iterator">Peekable iterator </param>
/// <returns>Indication whether or not the encounter passes secondary checks</returns>
private static bool VerifySecondaryChecks(PKM pkm, LegalInfo info, PeekEnumerator<IEncounterable> iterator)
{
if (pkm.Format >= 6)
@ -63,6 +90,13 @@ private static bool VerifySecondaryChecks(PKM pkm, LegalInfo info, PeekEnumerato
}
return true;
}
/// <summary>
/// Returns legality info for an unmatched encounter scenario, including a hint as to what the actual match could be.
/// </summary>
/// <param name="pkm">Source data to check the match for</param>
/// <param name="info">Information containing the unmatched encounter</param>
/// <returns>Updated information pertaining to the unmatched encounter</returns>
private static LegalInfo VerifyWithoutEncounter(PKM pkm, LegalInfo info)
{
info.EncounterMatch = new EncounterInvalid(pkm);

View File

@ -3,15 +3,30 @@
namespace PKHeX.Core
{
/// <summary>
/// Verify Evolution Information for a matched <see cref="IEncounterable"/>
/// </summary>
public static class EvolutionVerifier
{
// Evolutions
/// <summary>
/// Verifies Evolution scenarios of an <see cref="IEncounterable"/> for an input <see cref="PKM"/> and relevant <see cref="LegalInfo"/>.
/// </summary>
/// <param name="pkm">Source data to verify</param>
/// <param name="info">Source supporting information to verify with</param>
/// <returns></returns>
public static CheckResult VerifyEvolution(PKM pkm, LegalInfo info)
{
return IsValidEvolution(pkm, info)
? new CheckResult(CheckIdentifier.Evolution)
: new CheckResult(Severity.Invalid, V86, CheckIdentifier.Evolution);
}
/// <summary>
/// Checks if the Evolution from the source <see cref="IEncounterable"/> is valid.
/// </summary>
/// <param name="pkm">Source data to verify</param>
/// <param name="info">Source supporting information to verify with</param>
/// <returns>Evolution is valid or not</returns>
private static bool IsValidEvolution(PKM pkm, LegalInfo info)
{
int species = pkm.Species;

View File

@ -11,7 +11,7 @@ public class LegalInfo
/// <summary>The generation of games the PKM originated from.</summary>
public int Generation { get; set; }
/// <summary> The Game the PPKM originated from.</summary>
/// <summary> The Game the PKM originated from.</summary>
public GameVersion Game { get; set; }
/// <summary>The matched Encounter details for the <see cref="PKM"/>. </summary>