mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-08-25 05:45:10 -05:00
128 lines
11 KiB
C#
128 lines
11 KiB
C#
using System;
|
|
using static PKHeX.Core.LanguageID;
|
|
|
|
namespace PKHeX.Core;
|
|
|
|
/// <summary>
|
|
/// Transfers from Gen1/2=>Gen7 don't set PP correctly (ignoring PP Ups and bad range fetch). Transporter app language determines which table (not ROM of source or destination).
|
|
/// This class contains the expected PP values for each move in each language, as well as methods to check if a given set of moves and PP values match the expected values.
|
|
/// </summary>
|
|
public static class VirtualConsolePP
|
|
{
|
|
// Skip move IDs 0-109, no need to duplicate them since we have the valid table elsewhere.
|
|
private const byte MoveFirst = 110; // Withdraw
|
|
|
|
private static ReadOnlySpan<byte> TableENG => [000, 000, 109, 000, 000, 165, 255, 000, 000, 017, 023, 047, 213, 201, 207, 232, 236, 037, 070, 205, 100, 118, 211, 238, 006, 016, 089, 086, 172, 032, 151, 000, 089, 000, 022, 000, 023, 000, 024, 000, 025, 000, 025, 000, 026, 000, 027, 000, 027, 000, 028, 000, 029, 000, 030, 000, 030, 000, 031, 000, 032, 000, 032, 000, 033, 000, 034, 000, 034, 000, 035, 000, 036, 000, 036, 000, 037, 000, 038, 000, 039, 000, 039, 000, 040, 000, 041, 000, 042, 000, 042, 000, 043, 000, 044, 000, 044, 000, 045, 000, 046, 000, 047, 000, 048, 000, 048, 000, 049, 000, 050, 000, 051, 000, 052, 000, 052, 000, 053, 000, 054, 000, 055, 000, 055, 000, 056, 000, 057, 000, 058, 000, 059, 000, 060, 000, 060, 000, 061, 000, 062, 000];
|
|
private static ReadOnlySpan<byte> TableJPN => [000, 000, 123, 000, 000, 164, 255, 000, 000, 022, 030, 059, 224, 028, 037, 065, 069, 142, 180, 065, 015, 039, 147, 178, 206, 219, 032, 087, 082, 003, 045, 000, 105, 000, 023, 000, 024, 000, 024, 000, 025, 000, 026, 000, 027, 000, 028, 000, 029, 000, 030, 000, 031, 000, 031, 000, 032, 000, 033, 000, 034, 000, 035, 000, 036, 000, 037, 000, 038, 000, 039, 000, 039, 000, 040, 000, 041, 000, 042, 000, 043, 000, 044, 000, 045, 000, 046, 000, 046, 000, 047, 000, 048, 000, 049, 000, 050, 000, 051, 000, 052, 000, 053, 000, 054, 000, 055, 000, 055, 000, 056, 000, 057, 000, 058, 000, 059, 000, 060, 000, 061, 000, 062, 000, 063, 000, 064, 000, 065, 000, 066, 000, 067, 000, 068, 000, 069, 000, 070, 000, 062, 000];
|
|
private static ReadOnlySpan<byte> TableSPA => [000, 000, 109, 000, 000, 161, 255, 000, 000, 017, 023, 048, 147, 147, 153, 179, 182, 242, 022, 161, 054, 072, 170, 197, 221, 232, 226, 231, 064, 219, 105, 000, 093, 000, 023, 000, 023, 000, 024, 000, 025, 000, 026, 000, 026, 000, 027, 000, 028, 000, 029, 000, 030, 000, 030, 000, 031, 000, 032, 000, 033, 000, 033, 000, 034, 000, 035, 000, 036, 000, 037, 000, 037, 000, 038, 000, 039, 000, 040, 000, 040, 000, 041, 000, 042, 000, 043, 000, 044, 000, 045, 000, 045, 000, 046, 000, 046, 000, 047, 000, 048, 000, 049, 000, 050, 000, 050, 000, 051, 000, 052, 000, 053, 000, 054, 000, 054, 000, 055, 000, 056, 000, 057, 000, 058, 000, 058, 000, 059, 000, 060, 000, 061, 000, 062, 000, 063, 000, 064, 000, 064, 000];
|
|
private static ReadOnlySpan<byte> TableFRE => [000, 000, 113, 000, 000, 165, 255, 000, 000, 017, 023, 049, 173, 169, 176, 201, 204, 007, 042, 199, 093, 111, 206, 236, 003, 014, 054, 059, 154, 091, 016, 000, 092, 000, 023, 000, 023, 000, 024, 000, 025, 000, 026, 000, 026, 000, 027, 000, 028, 000, 029, 000, 029, 000, 030, 000, 031, 000, 031, 000, 032, 000, 033, 000, 034, 000, 034, 000, 035, 000, 036, 000, 036, 000, 037, 000, 038, 000, 039, 000, 039, 000, 040, 000, 041, 000, 042, 000, 043, 000, 043, 000, 044, 000, 045, 000, 046, 000, 046, 000, 047, 000, 048, 000, 049, 000, 050, 000, 050, 000, 051, 000, 052, 000, 053, 000, 054, 000, 054, 000, 055, 000, 056, 000, 057, 000, 058, 000, 058, 000, 059, 000, 060, 000, 061, 000, 062, 000, 063, 000, 063, 000];
|
|
private static ReadOnlySpan<byte> TableDEU => [000, 000, 110, 000, 000, 166, 255, 000, 000, 017, 023, 049, 191, 177, 184, 208, 212, 022, 057, 207, 099, 118, 222, 251, 017, 028, 065, 065, 114, 036, 251, 000, 093, 000, 023, 000, 023, 000, 024, 000, 025, 000, 026, 000, 026, 000, 027, 000, 028, 000, 028, 000, 029, 000, 030, 000, 031, 000, 032, 000, 032, 000, 033, 000, 034, 000, 035, 000, 035, 000, 036, 000, 037, 000, 038, 000, 038, 000, 039, 000, 040, 000, 041, 000, 041, 000, 042, 000, 043, 000, 044, 000, 045, 000, 045, 000, 046, 000, 047, 000, 048, 000, 049, 000, 049, 000, 050, 000, 051, 000, 052, 000, 053, 000, 053, 000, 054, 000, 055, 000, 056, 000, 057, 000, 057, 000, 058, 000, 059, 000, 060, 000, 061, 000, 062, 000, 063, 000, 063, 000, 064, 000];
|
|
private static ReadOnlySpan<byte> TableKOR => [000, 000, 082, 000, 000, 162, 255, 000, 000, 017, 022, 039, 237, 154, 160, 178, 180, 219, 239, 050, 186, 203, 253, 014, 029, 036, 039, 247, 228, 151, 138, 000, 056, 000, 022, 000, 023, 000, 023, 000, 024, 000, 024, 000, 024, 000, 025, 000, 025, 000, 025, 000, 026, 000, 026, 000, 027, 000, 027, 000, 027, 000, 028, 000, 028, 000, 028, 000, 029, 000, 029, 000, 030, 000, 030, 000, 030, 000, 031, 000, 031, 000, 031, 000, 032, 000, 032, 000, 033, 000, 033, 000, 033, 000, 034, 000, 034, 000, 034, 000, 035, 000, 035, 000, 036, 000, 036, 000, 036, 000, 037, 000, 037, 000, 038, 000, 038, 000, 038, 000, 039, 000, 039, 000, 040, 000, 040, 000, 040, 000, 041, 000, 041, 000, 041, 000, 042, 000, 042, 000, 043, 000];
|
|
private static ReadOnlySpan<byte> TableITA => [000, 000, 109, 000, 000, 166, 255, 000, 000, 017, 023, 048, 185, 173, 180, 206, 209, 012, 046, 195, 090, 108, 197, 225, 248, 003, 074, 078, 163, 046, 202, 000, 093, 000, 022, 000, 023, 000, 024, 000, 025, 000, 025, 000, 026, 000, 027, 000, 028, 000, 029, 000, 029, 000, 030, 000, 031, 000, 032, 000, 032, 000, 033, 000, 034, 000, 035, 000, 035, 000, 036, 000, 037, 000, 037, 000, 038, 000, 039, 000, 040, 000, 041, 000, 041, 000, 042, 000, 043, 000, 044, 000, 045, 000, 045, 000, 046, 000, 047, 000, 048, 000, 048, 000, 049, 000, 050, 000, 051, 000, 052, 000, 052, 000, 053, 000, 054, 000, 055, 000, 056, 000, 057, 000, 057, 000, 058, 000, 059, 000, 060, 000, 061, 000, 062, 000, 062, 000, 063, 000, 064, 000];
|
|
private static ReadOnlySpan<byte> TableZHS => [000, 000, 082, 000, 000, 162, 255, 000, 000, 017, 022, 038, 154, 068, 074, 092, 094, 126, 146, 198, 075, 092, 132, 147, 160, 167, 106, 051, 200, 227, 050, 000, 051, 000, 022, 000, 023, 000, 023, 000, 023, 000, 024, 000, 024, 000, 024, 000, 025, 000, 025, 000, 025, 000, 026, 000, 026, 000, 026, 000, 027, 000, 027, 000, 027, 000, 028, 000, 028, 000, 028, 000, 028, 000, 029, 000, 029, 000, 029, 000, 030, 000, 030, 000, 030, 000, 031, 000, 031, 000, 031, 000, 032, 000, 032, 000, 032, 000, 033, 000, 033, 000, 033, 000, 034, 000, 034, 000, 034, 000, 035, 000, 035, 000, 035, 000, 036, 000, 036, 000, 036, 000, 037, 000, 037, 000, 037, 000, 038, 000, 038, 000, 038, 000, 039, 000, 039, 000, 040, 000, 040, 000];
|
|
private static ReadOnlySpan<byte> TableZHT => [000, 000, 082, 000, 000, 163, 255, 000, 000, 017, 022, 039, 151, 065, 072, 089, 091, 124, 143, 195, 073, 089, 130, 145, 158, 165, 107, 052, 199, 227, 060, 000, 051, 000, 022, 000, 023, 000, 023, 000, 023, 000, 024, 000, 024, 000, 024, 000, 025, 000, 025, 000, 025, 000, 026, 000, 026, 000, 026, 000, 027, 000, 027, 000, 027, 000, 028, 000, 028, 000, 028, 000, 028, 000, 029, 000, 029, 000, 029, 000, 030, 000, 030, 000, 030, 000, 031, 000, 031, 000, 031, 000, 032, 000, 032, 000, 032, 000, 033, 000, 033, 000, 033, 000, 034, 000, 034, 000, 034, 000, 035, 000, 035, 000, 035, 000, 036, 000, 036, 000, 036, 000, 037, 000, 037, 000, 037, 000, 038, 000, 038, 000, 038, 000, 039, 000, 039, 000, 040, 000, 040, 000];
|
|
|
|
/// <summary>
|
|
/// Sanity check the languages possible.
|
|
/// </summary>
|
|
/// <param name="language">Language ID of the Transporter application to check.</param>
|
|
/// <returns>True if the language is supported for VC PP table; false otherwise.</returns>
|
|
public static bool IsSupportedLanguage(LanguageID language) => language is (>= Japanese and <= ChineseT) and not UNUSED_6;
|
|
|
|
/// <summary>
|
|
/// Gets the VC PP table for the specified Transporter application language.
|
|
/// </summary>
|
|
public static ReadOnlySpan<byte> GetTable(LanguageID language) => language switch
|
|
{
|
|
English => TableENG,
|
|
Japanese => TableJPN,
|
|
Spanish => TableSPA,
|
|
French => TableFRE,
|
|
German => TableDEU,
|
|
Korean => TableKOR,
|
|
Italian => TableITA,
|
|
ChineseS => TableZHS,
|
|
ChineseT => TableZHT,
|
|
_ => throw new NotSupportedException($"Language {language} is not supported for VC PP table."),
|
|
};
|
|
|
|
/// <summary>
|
|
/// Fetches the expected PP for the given move from the VC PP table. Returns false if the move is not in the VC range.
|
|
/// </summary>
|
|
/// <param name="move">Move ID.</param>
|
|
/// <param name="arr">Glitched PP table for the Pokémon's language.</param>
|
|
/// <param name="pp">Output expected PP for the move.</param>
|
|
/// <returns><see langword="true"/> if the move is in the VC range and the expected PP was fetched; <see langword="false"/> otherwise.</returns>
|
|
public static bool IsGlitched(ushort move, ReadOnlySpan<byte> arr, out byte pp)
|
|
{
|
|
var index = move - MoveFirst;
|
|
if (index < 0)
|
|
{
|
|
pp = MoveInfo7.PP[move]; // fetch from the normal table for moves below the glitched range
|
|
return true;
|
|
}
|
|
if ((uint)index < arr.Length)
|
|
{
|
|
pp = arr[index]; // fetch from the glitched table for moves in the glitched range
|
|
return true;
|
|
}
|
|
pp = 0; // unexpected move, ignore.
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if all moves are possible from R/B/Y only.
|
|
/// </summary>
|
|
public static bool IsPossibleVC1(ReadOnlySpan<ushort> moves)
|
|
=> !moves.ContainsAnyExceptInRange<ushort>(0, Legal.MaxMoveID_1);
|
|
|
|
/// <summary>
|
|
/// Checks if all moves are possible from R/B/Y/G/S/C only.
|
|
/// </summary>
|
|
public static bool IsPossibleVC2(ReadOnlySpan<ushort> moves)
|
|
=> !moves.ContainsAnyExceptInRange<ushort>(0, Legal.MaxMoveID_2);
|
|
|
|
/// <summary>
|
|
/// Checks the moves against the table to see if all match their stored PP value.
|
|
/// </summary>
|
|
public static bool IsMatch(LanguageID language, ReadOnlySpan<ushort> moves, ReadOnlySpan<int> pp)
|
|
{
|
|
var arr = GetTable(language);
|
|
for (int i = 0; i < moves.Length; i++)
|
|
{
|
|
var move = moves[i];
|
|
if (!IsGlitched(move, arr, out var expected))
|
|
return false;
|
|
if (pp[i] != expected)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks the moves against all supported Transfer application languages to see if any match their stored PP values.
|
|
/// </summary>
|
|
public static bool IsMatchAnyLanguage(ReadOnlySpan<byte> trash, ReadOnlySpan<ushort> moves, ReadOnlySpan<int> pp, ushort species)
|
|
{
|
|
for (var language = Japanese; language <= ChineseT; language++)
|
|
{
|
|
if (language == UNUSED_6)
|
|
continue;
|
|
if (!IsMatch(language, moves, pp))
|
|
continue;
|
|
if (!IsTrashMatch(trash, species, language))
|
|
continue;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static bool IsTrashMatch(ReadOnlySpan<byte> trash, ushort species, LanguageID language)
|
|
{
|
|
var expectName = SpeciesName.GetSpeciesName(species, (int)language);
|
|
var currentLength = TrashBytesUTF16.GetStringLength(trash) + 1; // terminator
|
|
var match = TrashBytesUTF16.IsUnderlayerPresent(expectName, trash, currentLength);
|
|
return !match.IsInvalid;
|
|
}
|
|
}
|