mirror of
https://github.com/kwsch/PKHeX
synced 2024-12-25 11:53:11 +00:00
3c232505e5
In this pull request I've changed a ton of method signatures to reflect the more-narrow types of Species, Move# and Form; additionally, I've narrowed other large collections that stored lists of species / permitted values, and reworked them to be more performant with the latest API spaghetti that PKHeX provides. Roamer met locations, usually in a range of [max-min]<64, can be quickly checked using a bitflag operation on a UInt64. Other collections (like "Is this from Colosseum or XD") were eliminated -- shadow state is not transferred COLO<->XD, so having a Shadow ID or matching the met location from a gift/wild encounter is a sufficient check for "originated in XD".
245 lines
8.7 KiB
C#
245 lines
8.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
using static PKHeX.Core.Legal;
|
|
using static PKHeX.Core.GameVersion;
|
|
using static PKHeX.Core.Species;
|
|
|
|
namespace PKHeX.Core;
|
|
|
|
/// <summary>
|
|
/// Miscellaneous GB Era restriction logic for legality checking
|
|
/// </summary>
|
|
internal static class GBRestrictions
|
|
{
|
|
private static readonly HashSet<byte> Stadium_GiftSpecies = new()
|
|
{
|
|
(int)Bulbasaur,
|
|
(int)Charmander,
|
|
(int)Squirtle,
|
|
(int)Psyduck,
|
|
(int)Hitmonlee,
|
|
(int)Hitmonchan,
|
|
(int)Eevee,
|
|
(int)Omanyte,
|
|
(int)Kabuto,
|
|
};
|
|
|
|
/// <summary>
|
|
/// Checks if the type matches any of the type IDs extracted from the Personal Table used for R/G/B/Y games.
|
|
/// </summary>
|
|
/// <remarks>Valid values: 0, 1, 2, 3, 4, 5, 7, 8, 20, 21, 22, 23, 24, 25, 26</remarks>
|
|
internal static bool TypeIDExists(int type) => (uint)type < 32 && (0b111111100000000000110111111 & (1 << type)) != 0;
|
|
|
|
/// <summary>
|
|
/// Species that have a catch rate value that is different from their pre-evolutions, and cannot be obtained directly.
|
|
/// </summary>
|
|
internal static readonly HashSet<byte> Species_NotAvailable_CatchRate = new()
|
|
{
|
|
(int)Butterfree,
|
|
(int)Pidgeot,
|
|
(int)Nidoqueen,
|
|
(int)Nidoking,
|
|
(int)Ninetales,
|
|
(int)Vileplume,
|
|
(int)Persian,
|
|
(int)Arcanine,
|
|
(int)Poliwrath,
|
|
(int)Alakazam,
|
|
(int)Machamp,
|
|
(int)Victreebel,
|
|
(int)Rapidash,
|
|
(int)Cloyster,
|
|
(int)Exeggutor,
|
|
(int)Starmie,
|
|
(int)Dragonite,
|
|
};
|
|
|
|
internal static readonly HashSet<byte> Trade_Evolution1 = new()
|
|
{
|
|
(int)Kadabra,
|
|
(int)Machoke,
|
|
(int)Graveler,
|
|
(int)Haunter,
|
|
};
|
|
|
|
public static bool RateMatchesEncounter(ushort species, GameVersion version, byte rate)
|
|
{
|
|
if (version.Contains(YW))
|
|
{
|
|
if (rate == PersonalTable.Y[species].CatchRate)
|
|
return true;
|
|
if (version == YW) // no RB
|
|
return false;
|
|
}
|
|
return rate == PersonalTable.RB[species].CatchRate;
|
|
}
|
|
|
|
private static bool GetCatchRateMatchesPreEvolution(PK1 pk, byte catch_rate)
|
|
{
|
|
// For species catch rate, discard any species that has no valid encounters and a different catch rate than their pre-evolutions
|
|
var table = EvolutionTree.Evolves1;
|
|
var chain = table.GetValidPreEvolutions(pk, levelMax: (byte)pk.CurrentLevel);
|
|
foreach (var entry in chain)
|
|
{
|
|
var s = entry.Species;
|
|
if (Species_NotAvailable_CatchRate.Contains((byte)s))
|
|
continue;
|
|
if (catch_rate == PersonalTable.RB[s].CatchRate || catch_rate == PersonalTable.Y[s].CatchRate)
|
|
return true;
|
|
}
|
|
|
|
// Krabby encounter trade special catch rate
|
|
ushort species = pk.Species;
|
|
if (catch_rate == 204 && (species is (int)Krabby or (int)Kingler))
|
|
return true;
|
|
|
|
if (catch_rate is (167 or 168) && Stadium_GiftSpecies.Contains((byte)species))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the <see cref="pk"/> can inhabit <see cref="Gen1"></see>
|
|
/// </summary>
|
|
/// <param name="pk">Data to check</param>
|
|
/// <returns>true if can inhabit, false if not.</returns>
|
|
private static bool CanInhabitGen1(this PKM pk)
|
|
{
|
|
// Korean Gen2 games can't trade-back because there are no Gen1 Korean games released
|
|
if (pk.Korean || pk.IsEgg)
|
|
return false;
|
|
|
|
// Gen2 format with met data can't receive Gen1 moves, unless Stadium 2 is used (Oak's PC).
|
|
// If you put a Pokemon in the N64 box, the met info is retained, even if you switch over to a Gen I game to teach it TMs
|
|
// You can use rare candies from within the lab, so level-up moves from RBY context can be learned this way as well
|
|
// Stadium 2 is GB Cart Era only (not 3DS Virtual Console).
|
|
if (pk is ICaughtData2 {CaughtData: not 0} && !ParseSettings.AllowGBCartEra)
|
|
return false;
|
|
|
|
// Sanity check species, if it could have existed as a pre-evolution.
|
|
ushort species = pk.Species;
|
|
if (species <= MaxSpeciesID_1)
|
|
return true;
|
|
return EvolutionLegality.FutureEvolutionsGen1.Contains(species);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the Tradeback status depending on various values.
|
|
/// </summary>
|
|
/// <param name="pk">Pokémon to guess the tradeback status from.</param>
|
|
internal static PotentialGBOrigin GetTradebackStatusInitial(PKM pk)
|
|
{
|
|
if (pk is PK1 pk1)
|
|
return GetTradebackStatusRBY(pk1);
|
|
|
|
if (pk.Format == 2 || pk.VC2) // Check for impossible tradeback scenarios
|
|
return !pk.CanInhabitGen1() ? PotentialGBOrigin.Gen2Only : PotentialGBOrigin.Either;
|
|
|
|
// VC2 is released, we can assume it will be TradebackType.Any.
|
|
// Is impossible to differentiate a VC1 pokemon traded to Gen7 after VC2 is available.
|
|
// Met Date cannot be used definitively as the player can change their system clock.
|
|
return PotentialGBOrigin.Either;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the Tradeback status depending on the <see cref="PK1.Catch_Rate"/>
|
|
/// </summary>
|
|
/// <param name="pk">Pokémon to guess the tradeback status from.</param>
|
|
private static PotentialGBOrigin GetTradebackStatusRBY(PK1 pk)
|
|
{
|
|
if (!ParseSettings.AllowGen1Tradeback)
|
|
return PotentialGBOrigin.Gen1Only;
|
|
|
|
// Detect tradeback status by comparing the catch rate(Gen1)/held item(Gen2) to the species in the pk's evolution chain.
|
|
var catch_rate = pk.Catch_Rate;
|
|
if (catch_rate == 0)
|
|
return PotentialGBOrigin.Either;
|
|
|
|
bool matchAny = GetCatchRateMatchesPreEvolution(pk, catch_rate);
|
|
if (!matchAny)
|
|
return PotentialGBOrigin.Either;
|
|
|
|
if (IsTradebackCatchRate(catch_rate))
|
|
return PotentialGBOrigin.Either;
|
|
|
|
return PotentialGBOrigin.Gen1Only;
|
|
}
|
|
|
|
public static TimeCapsuleEvaluation IsTimeCapsuleTransferred(PKM pk, ReadOnlySpan<MoveResult> moves, IEncounterTemplate enc)
|
|
{
|
|
foreach (var z in moves)
|
|
{
|
|
if (z.Generation == enc.Generation || z.Generation > 2)
|
|
continue;
|
|
if (pk is PK1 {Catch_Rate: not 0} g1 && !IsTradebackCatchRate(g1.Catch_Rate))
|
|
return TimeCapsuleEvaluation.BadCatchRate;
|
|
return enc.Generation == 2 ? TimeCapsuleEvaluation.Transferred21 : TimeCapsuleEvaluation.Transferred12;
|
|
}
|
|
|
|
if (pk is not GBPKM gb)
|
|
{
|
|
return enc.Generation switch
|
|
{
|
|
2 when pk.VC2 => TimeCapsuleEvaluation.Transferred21,
|
|
1 when pk.VC1 => TimeCapsuleEvaluation.Transferred12,
|
|
_ => TimeCapsuleEvaluation.NotTransferred,
|
|
};
|
|
}
|
|
|
|
if (gb is ICaughtData2 pk2)
|
|
{
|
|
if (enc.Generation == 1)
|
|
return TimeCapsuleEvaluation.Transferred12;
|
|
if (pk2.CaughtData != 0)
|
|
return TimeCapsuleEvaluation.NotTransferred;
|
|
if (enc.Version == C)
|
|
return TimeCapsuleEvaluation.Transferred21;
|
|
return TimeCapsuleEvaluation.Indeterminate;
|
|
}
|
|
|
|
if (gb is PK1 pk1)
|
|
{
|
|
var rate = pk1.Catch_Rate;
|
|
if (rate == 0)
|
|
return TimeCapsuleEvaluation.Transferred12;
|
|
|
|
bool isTradebackItem = IsTradebackCatchRate(rate);
|
|
if (IsCatchRateMatchEncounter(enc, pk1))
|
|
return isTradebackItem ? TimeCapsuleEvaluation.Indeterminate : TimeCapsuleEvaluation.NotTransferred;
|
|
return isTradebackItem ? TimeCapsuleEvaluation.Transferred12 : TimeCapsuleEvaluation.BadCatchRate;
|
|
}
|
|
return TimeCapsuleEvaluation.Indeterminate;
|
|
}
|
|
|
|
private static bool IsCatchRateMatchEncounter(IEncounterTemplate enc, PK1 pk1) => enc switch
|
|
{
|
|
EncounterStatic1 s when s.GetMatchRating(pk1) != EncounterMatchRating.PartialMatch => true,
|
|
EncounterTrade1 => true,
|
|
_ => RateMatchesEncounter(enc.Species, enc.Version, pk1.Catch_Rate),
|
|
};
|
|
|
|
public static bool IsTradebackCatchRate(byte rate) => Array.IndexOf(HeldItems_GSC, rate) != -1;
|
|
}
|
|
|
|
public enum PotentialGBOrigin
|
|
{
|
|
Either,
|
|
Gen1Only,
|
|
Gen2Only,
|
|
}
|
|
|
|
public enum TimeCapsuleEvaluation
|
|
{
|
|
Indeterminate,
|
|
Transferred21,
|
|
Transferred12,
|
|
NotTransferred,
|
|
BadCatchRate,
|
|
}
|
|
|
|
public static class TimeCapsuleEvlautationExtensions
|
|
{
|
|
public static bool WasTimeCapsuleTransferred(this TimeCapsuleEvaluation eval) => eval is not (TimeCapsuleEvaluation.Indeterminate or TimeCapsuleEvaluation.NotTransferred or TimeCapsuleEvaluation.BadCatchRate);
|
|
}
|