2022-06-26 06:08:28 +00:00
|
|
|
using System;
|
2018-10-10 04:07:13 +00:00
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
using static PKHeX.Core.Legal;
|
2021-01-16 20:01:40 +00:00
|
|
|
using static PKHeX.Core.GameVersion;
|
|
|
|
using static PKHeX.Core.Species;
|
2018-10-10 04:07:13 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
namespace PKHeX.Core;
|
2018-10-10 23:32:02 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Miscellaneous GB Era restriction logic for legality checking
|
|
|
|
/// </summary>
|
|
|
|
internal static class GBRestrictions
|
|
|
|
{
|
2022-08-26 17:07:24 +00:00
|
|
|
private static readonly HashSet<byte> Stadium_GiftSpecies = new()
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
|
|
|
(int)Bulbasaur,
|
|
|
|
(int)Charmander,
|
|
|
|
(int)Squirtle,
|
|
|
|
(int)Psyduck,
|
|
|
|
(int)Hitmonlee,
|
|
|
|
(int)Hitmonchan,
|
|
|
|
(int)Eevee,
|
|
|
|
(int)Omanyte,
|
|
|
|
(int)Kabuto,
|
|
|
|
};
|
2018-10-10 23:32:02 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
/// <summary>
|
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
|
|
|
/// Checks if the type matches any of the type IDs extracted from the Personal Table used for R/G/B/Y games.
|
2022-06-18 18:04:24 +00:00
|
|
|
/// </summary>
|
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
|
|
|
/// <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;
|
2018-10-10 23:32:02 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Species that have a catch rate value that is different from their pre-evolutions, and cannot be obtained directly.
|
|
|
|
/// </summary>
|
2022-08-26 17:07:24 +00:00
|
|
|
internal static readonly HashSet<byte> Species_NotAvailable_CatchRate = new()
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
|
|
|
(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,
|
|
|
|
};
|
|
|
|
|
2022-08-26 17:07:24 +00:00
|
|
|
internal static readonly HashSet<byte> Trade_Evolution1 = new()
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
|
|
|
(int)Kadabra,
|
|
|
|
(int)Machoke,
|
|
|
|
(int)Graveler,
|
|
|
|
(int)Haunter,
|
|
|
|
};
|
2021-06-10 05:54:21 +00:00
|
|
|
|
2022-08-27 06:43:36 +00:00
|
|
|
public static bool RateMatchesEncounter(ushort species, GameVersion version, byte rate)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
|
|
|
if (version.Contains(YW))
|
2018-10-10 04:07:13 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
if (rate == PersonalTable.Y[species].CatchRate)
|
|
|
|
return true;
|
|
|
|
if (version == YW) // no RB
|
|
|
|
return false;
|
2018-10-10 04:07:13 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
return rate == PersonalTable.RB[species].CatchRate;
|
|
|
|
}
|
2018-10-10 04:07:13 +00:00
|
|
|
|
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
|
|
|
private static bool GetCatchRateMatchesPreEvolution(PK1 pk, byte catch_rate)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
|
|
|
// 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;
|
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
|
|
|
var chain = table.GetValidPreEvolutions(pk, levelMax: (byte)pk.CurrentLevel);
|
2022-06-18 18:04:24 +00:00
|
|
|
foreach (var entry in chain)
|
2018-10-10 04:07:13 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
var s = entry.Species;
|
2022-08-26 17:07:24 +00:00
|
|
|
if (Species_NotAvailable_CatchRate.Contains((byte)s))
|
2022-06-18 18:04:24 +00:00
|
|
|
continue;
|
|
|
|
if (catch_rate == PersonalTable.RB[s].CatchRate || catch_rate == PersonalTable.Y[s].CatchRate)
|
2021-05-18 17:38:01 +00:00
|
|
|
return true;
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2021-05-18 17:38:01 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
// Krabby encounter trade special catch rate
|
2022-08-27 06:43:36 +00:00
|
|
|
ushort species = pk.Species;
|
2022-06-18 18:04:24 +00:00
|
|
|
if (catch_rate == 204 && (species is (int)Krabby or (int)Kingler))
|
|
|
|
return true;
|
|
|
|
|
2022-08-26 17:07:24 +00:00
|
|
|
if (catch_rate is (167 or 168) && Stadium_GiftSpecies.Contains((byte)species))
|
2022-06-18 18:04:24 +00:00
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2021-05-18 17:38:01 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
/// <summary>
|
2022-08-27 06:43:36 +00:00
|
|
|
/// Checks if the <see cref="pk"/> can inhabit <see cref="Gen1"></see>
|
2022-06-18 18:04:24 +00:00
|
|
|
/// </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)
|
2021-05-18 17:38:01 +00:00
|
|
|
return false;
|
2018-10-10 04:07:13 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
// 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;
|
2020-08-30 06:47:03 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
// Sanity check species, if it could have existed as a pre-evolution.
|
2022-08-27 06:43:36 +00:00
|
|
|
ushort species = pk.Species;
|
2022-06-18 18:04:24 +00:00
|
|
|
if (species <= MaxSpeciesID_1)
|
|
|
|
return true;
|
|
|
|
return EvolutionLegality.FutureEvolutionsGen1.Contains(species);
|
|
|
|
}
|
2020-08-30 06:47:03 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
/// <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);
|
2019-05-11 07:59:07 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (pk.Format == 2 || pk.VC2) // Check for impossible tradeback scenarios
|
|
|
|
return !pk.CanInhabitGen1() ? PotentialGBOrigin.Gen2Only : PotentialGBOrigin.Either;
|
2018-10-10 04:07:13 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
// 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;
|
|
|
|
}
|
2018-10-10 04:07:13 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
/// <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;
|
2018-10-10 04:07:13 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
// 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;
|
2018-10-10 04:07:13 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
bool matchAny = GetCatchRateMatchesPreEvolution(pk, catch_rate);
|
|
|
|
if (!matchAny)
|
|
|
|
return PotentialGBOrigin.Either;
|
2018-10-10 04:07:13 +00:00
|
|
|
|
2022-08-18 06:48:37 +00:00
|
|
|
if (IsTradebackCatchRate(catch_rate))
|
2022-06-18 18:04:24 +00:00
|
|
|
return PotentialGBOrigin.Either;
|
2018-10-10 04:07:13 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
return PotentialGBOrigin.Gen1Only;
|
|
|
|
}
|
2018-10-10 04:07:13 +00:00
|
|
|
|
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
|
|
|
public static TimeCapsuleEvaluation IsTimeCapsuleTransferred(PKM pk, ReadOnlySpan<MoveResult> moves, IEncounterTemplate enc)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
2022-06-26 06:08:28 +00:00
|
|
|
foreach (var z in moves)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
2022-06-26 06:08:28 +00:00
|
|
|
if (z.Generation == enc.Generation || z.Generation > 2)
|
|
|
|
continue;
|
2022-06-18 18:04:24 +00:00
|
|
|
if (pk is PK1 {Catch_Rate: not 0} g1 && !IsTradebackCatchRate(g1.Catch_Rate))
|
|
|
|
return TimeCapsuleEvaluation.BadCatchRate;
|
|
|
|
return enc.Generation == 2 ? TimeCapsuleEvaluation.Transferred21 : TimeCapsuleEvaluation.Transferred12;
|
2018-10-10 04:07:13 +00:00
|
|
|
}
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (pk is not GBPKM gb)
|
2018-10-10 04:07:13 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
return enc.Generation switch
|
2021-08-20 22:59:54 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
2 when pk.VC2 => TimeCapsuleEvaluation.Transferred21,
|
|
|
|
1 when pk.VC1 => TimeCapsuleEvaluation.Transferred12,
|
|
|
|
_ => TimeCapsuleEvaluation.NotTransferred,
|
|
|
|
};
|
2018-10-10 04:07:13 +00:00
|
|
|
}
|
2021-08-20 22:59:54 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (gb is ICaughtData2 pk2)
|
2021-08-20 22:59:54 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
if (enc.Generation == 1)
|
|
|
|
return TimeCapsuleEvaluation.Transferred12;
|
|
|
|
if (pk2.CaughtData != 0)
|
|
|
|
return TimeCapsuleEvaluation.NotTransferred;
|
|
|
|
if (enc.Version == C)
|
|
|
|
return TimeCapsuleEvaluation.Transferred21;
|
|
|
|
return TimeCapsuleEvaluation.Indeterminate;
|
|
|
|
}
|
2021-08-20 22:59:54 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (gb is PK1 pk1)
|
2021-08-20 22:59:54 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
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;
|
2021-08-20 22:59:54 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
return TimeCapsuleEvaluation.Indeterminate;
|
2021-08-20 22:59:54 +00:00
|
|
|
}
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private static bool IsCatchRateMatchEncounter(IEncounterTemplate enc, PK1 pk1) => enc switch
|
2021-08-20 22:59:54 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
EncounterStatic1 s when s.GetMatchRating(pk1) != EncounterMatchRating.PartialMatch => true,
|
|
|
|
EncounterTrade1 => true,
|
|
|
|
_ => RateMatchesEncounter(enc.Species, enc.Version, pk1.Catch_Rate),
|
|
|
|
};
|
2021-08-20 22:59:54 +00:00
|
|
|
|
2022-08-18 06:48:37 +00:00
|
|
|
public static bool IsTradebackCatchRate(byte rate) => Array.IndexOf(HeldItems_GSC, rate) != -1;
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2021-08-20 22:59:54 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
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);
|
2018-10-10 04:07:13 +00:00
|
|
|
}
|