2022-06-18 18:04:24 +00:00
|
|
|
using System;
|
2020-12-29 05:42:54 +00:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using static PKHeX.Core.Species;
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
namespace PKHeX.Core;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Logic for parsing details for <see cref="ShowdownSet"/> objects.
|
|
|
|
/// </summary>
|
|
|
|
public static class ShowdownParsing
|
2020-12-29 05:42:54 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
private static readonly string[] genderForms = { "", "F", "" };
|
|
|
|
|
2020-12-29 05:42:54 +00:00
|
|
|
/// <summary>
|
2022-06-18 18:04:24 +00:00
|
|
|
/// Gets the Form ID from the input <see cref="name"/>.
|
2020-12-29 05:42:54 +00:00
|
|
|
/// </summary>
|
2022-06-18 18:04:24 +00:00
|
|
|
/// <param name="name"></param>
|
|
|
|
/// <param name="strings"></param>
|
|
|
|
/// <param name="species">Species ID the form belongs to</param>
|
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
|
|
|
/// <param name="context">Format the form name should appear in</param>
|
2022-06-18 18:04:24 +00:00
|
|
|
/// <returns>Zero (base form) if no form matches the input string.</returns>
|
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 int GetFormFromString(string name, GameStrings strings, int species, EntityContext context)
|
2020-12-29 05:42:54 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
if (name.Length == 0)
|
|
|
|
return 0;
|
2020-12-29 05:42:54 +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
|
|
|
var formStrings = FormConverter.GetFormList(species, strings.Types, strings.forms, genderForms, context);
|
2022-06-18 18:04:24 +00:00
|
|
|
return Math.Max(0, Array.FindIndex(formStrings, z => z.Contains(name)));
|
|
|
|
}
|
2020-12-29 05:42:54 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Converts a Form ID to string.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="form"></param>
|
|
|
|
/// <param name="strings"></param>
|
|
|
|
/// <param name="species"></param>
|
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
|
|
|
/// <param name="context"></param>
|
|
|
|
public static string GetStringFromForm(int form, GameStrings strings, int species, EntityContext context)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
|
|
|
if (form <= 0)
|
|
|
|
return string.Empty;
|
2020-12-29 05:42:54 +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
|
|
|
var forms = FormConverter.GetFormList(species, strings.Types, strings.forms, genderForms, context);
|
2022-06-18 18:04:24 +00:00
|
|
|
return form >= forms.Length ? string.Empty : forms[form];
|
|
|
|
}
|
2020-12-29 05:42:54 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private const string MiniorFormName = "Meteor";
|
2020-12-29 05:42:54 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Converts the PKHeX standard form name to Showdown's form name.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="species">Species ID</param>
|
|
|
|
/// <param name="form">PKHeX form name</param>
|
|
|
|
public static string GetShowdownFormName(int species, string form)
|
|
|
|
{
|
|
|
|
if (form.Length == 0)
|
2020-12-29 05:42:54 +00:00
|
|
|
{
|
|
|
|
return species switch
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
(int)Minior => MiniorFormName,
|
|
|
|
_ => form,
|
2020-12-29 05:42:54 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
return species switch
|
2020-12-29 05:42:54 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
(int)Basculin when form is "Blue" => "Blue-Striped",
|
|
|
|
(int)Vivillon when form is "Poké Ball" => "Pokeball",
|
|
|
|
(int)Zygarde => form.Replace("-C", string.Empty).Replace("50%", string.Empty),
|
|
|
|
(int)Minior when form.StartsWith("M-", StringComparison.Ordinal) => MiniorFormName,
|
|
|
|
(int)Minior => form.Replace("C-", string.Empty),
|
|
|
|
(int)Necrozma when form is "Dusk" => $"{form}-Mane",
|
|
|
|
(int)Necrozma when form is "Dawn" => $"{form}-Wings",
|
|
|
|
(int)Polteageist or (int)Sinistea => form == "Antique" ? form : string.Empty,
|
|
|
|
|
|
|
|
(int)Furfrou or (int)Greninja or (int)Rockruff => string.Empty,
|
|
|
|
|
|
|
|
_ => Legal.Totem_USUM.Contains(species) && form == "Large"
|
|
|
|
? Legal.Totem_Alolan.Contains(species) && species != (int)Mimikyu ? "Alola-Totem" : "Totem"
|
|
|
|
: form.Replace(' ', '-'),
|
|
|
|
};
|
|
|
|
}
|
2020-12-29 05:42:54 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Converts the Showdown form name to PKHeX's form name.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="species">Species ID</param>
|
|
|
|
/// <param name="form">Showdown form name</param>
|
|
|
|
/// <param name="ability">Showdown ability ID</param>
|
|
|
|
public static string SetShowdownFormName(int species, string form, int ability)
|
|
|
|
{
|
|
|
|
if (form.Length != 0)
|
|
|
|
form = form.Replace(' ', '-'); // inconsistencies are great
|
2020-12-29 05:42:54 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
return species switch
|
2020-12-29 05:42:54 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
(int)Basculin when form == "Blue-Striped" => "Blue",
|
|
|
|
(int)Vivillon when form == "Pokeball" => "Poké Ball",
|
|
|
|
(int)Necrozma when form == "Dusk-Mane" => "Dusk",
|
|
|
|
(int)Necrozma when form == "Dawn-Wings" => "Dawn",
|
|
|
|
(int)Toxtricity when form == "Low-Key" => "Low Key",
|
|
|
|
(int)Darmanitan when form == "Galar-Zen" => "Galar Zen",
|
|
|
|
(int)Minior when form != MiniorFormName => $"C-{form}",
|
|
|
|
(int)Zygarde when form == "Complete" => form,
|
|
|
|
(int)Zygarde when ability == 211 => $"{(string.IsNullOrWhiteSpace(form) ? "50%" : "10%")}-C",
|
|
|
|
(int)Greninja when ability == 210 => "Ash", // Battle Bond
|
|
|
|
(int)Rockruff when ability == 020 => "Dusk", // Rockruff-1
|
|
|
|
(int)Urshifu or (int)Pikachu or (int)Alcremie => form.Replace('-', ' '), // Strike and Cosplay
|
|
|
|
|
|
|
|
_ => Legal.Totem_USUM.Contains(species) && form.EndsWith("Totem", StringComparison.Ordinal) ? "Large" : form,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Fetches <see cref="ShowdownSet"/> data from the input <see cref="lines"/>.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="lines">Raw lines containing numerous multi-line set data.</param>
|
|
|
|
/// <returns><see cref="ShowdownSet"/> objects until <see cref="lines"/> is consumed.</returns>
|
|
|
|
public static IEnumerable<ShowdownSet> GetShowdownSets(IEnumerable<string> lines)
|
|
|
|
{
|
|
|
|
// exported sets always have >4 moves; new List will always require 1 resizing, allocate 2x to save 1 reallocation.
|
|
|
|
// intro, nature, ability, (ivs, evs, shiny, level) 4*moves
|
|
|
|
var setLines = new List<string>(8);
|
|
|
|
foreach (var line in lines)
|
|
|
|
{
|
|
|
|
if (!string.IsNullOrWhiteSpace(line))
|
2020-12-29 05:42:54 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
setLines.Add(line);
|
|
|
|
continue;
|
2020-12-29 05:42:54 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
if (setLines.Count == 0)
|
|
|
|
continue;
|
|
|
|
yield return new ShowdownSet(setLines);
|
|
|
|
setLines.Clear();
|
2020-12-29 05:42:54 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
if (setLines.Count != 0)
|
|
|
|
yield return new ShowdownSet(setLines);
|
|
|
|
}
|
2020-12-29 05:42:54 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Converts the <see cref="PKM"/> data into an importable set format for Pokémon Showdown.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="pk">PKM to convert to string</param>
|
|
|
|
/// <returns>Multi line set data</returns>
|
|
|
|
public static string GetShowdownText(PKM pk)
|
|
|
|
{
|
|
|
|
if (pk.Species == 0)
|
|
|
|
return string.Empty;
|
|
|
|
return new ShowdownSet(pk).Text;
|
|
|
|
}
|
2020-12-29 05:42:54 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Fetches ShowdownSet lines from the input <see cref="PKM"/> data.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="data">Pokémon data to summarize.</param>
|
2022-08-18 06:50:14 +00:00
|
|
|
/// <param name="lang">Localization setting</param>
|
2022-06-18 18:04:24 +00:00
|
|
|
/// <returns>Consumable list of <see cref="ShowdownSet.Text"/> lines.</returns>
|
2022-08-18 06:50:14 +00:00
|
|
|
public static IEnumerable<string> GetShowdownText(IEnumerable<PKM> data, string lang = ShowdownSet.DefaultLanguage)
|
|
|
|
{
|
|
|
|
var sets = GetShowdownSets(data);
|
|
|
|
foreach (var set in sets)
|
|
|
|
yield return set.LocalizedText(lang);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Fetches ShowdownSet lines from the input <see cref="PKM"/> data.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="data">Pokémon data to summarize.</param>
|
|
|
|
/// <returns>Consumable list of <see cref="ShowdownSet.Text"/> lines.</returns>
|
|
|
|
public static IEnumerable<ShowdownSet> GetShowdownSets(IEnumerable<PKM> data)
|
|
|
|
{
|
|
|
|
foreach (var pk in data)
|
|
|
|
{
|
|
|
|
if (pk.Species == 0)
|
|
|
|
continue;
|
|
|
|
yield return new ShowdownSet(pk);
|
|
|
|
}
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Fetches ShowdownSet lines from the input <see cref="PKM"/> data, and combines it into one string.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="data">Pokémon data to summarize.</param>
|
|
|
|
/// <param name="separator">Splitter between each set.</param>
|
|
|
|
/// <returns>Single string containing all <see cref="ShowdownSet.Text"/> lines.</returns>
|
2022-08-18 06:50:14 +00:00
|
|
|
public static string GetShowdownSets(IEnumerable<PKM> data, string separator) => string.Join(separator, GetShowdownText(data));
|
2022-06-18 18:04:24 +00:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets a localized string preview of the provided <see cref="pk"/>.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="pk">Pokémon data</param>
|
|
|
|
/// <param name="language">Language code</param>
|
|
|
|
/// <returns>Multi-line string</returns>
|
|
|
|
public static string GetLocalizedPreviewText(PKM pk, string language)
|
|
|
|
{
|
|
|
|
var set = new ShowdownSet(pk);
|
|
|
|
if (pk.Format <= 2) // Nature preview from IVs
|
|
|
|
set.Nature = Experience.GetNatureVC(pk.EXP);
|
|
|
|
return set.LocalizedText(language);
|
2020-12-29 05:42:54 +00:00
|
|
|
}
|
|
|
|
}
|