using System;
using System.Collections.Generic;
using static PKHeX.Core.Species;
namespace PKHeX.Core;
///
/// Logic for parsing details for objects.
///
public static class ShowdownParsing
{
private static readonly string[] genderForms = ["", "F", ""];
///
/// Gets the Form ID from the input .
///
///
///
/// Species ID the form belongs to
/// Format the form name should appear in
/// Zero (base form) if no form matches the input string.
public static byte GetFormFromString(ReadOnlySpan name, GameStrings strings, ushort species, EntityContext context)
{
if (name.Length == 0)
return 0;
var forms = FormConverter.GetFormList(species, strings.Types, strings.forms, genderForms, context);
if (forms.Length < 1)
return 0;
// Find first matching index that matches any case, ignoring dashes interchanged with spaces.
for (byte i = 0; i < forms.Length; i++)
{
if (IsFormEquivalent(forms[i], name))
return i;
}
// No match, assume default 0 form.
return 0;
}
private static bool IsFormEquivalent(ReadOnlySpan reference, ReadOnlySpan input)
{
if (input.Length != reference.Length)
return false;
for (int i = 0; i < input.Length; i++)
{
var c1 = input[i];
var c2 = reference[i];
if (char.ToUpperInvariant(c1) == char.ToUpperInvariant(c2))
continue;
if (c1 is ' ' or '-' && c2 is ' ' or '-')
continue;
return false;
}
return true;
}
///
/// Converts a Form ID to string.
///
///
///
///
///
public static string GetStringFromForm(byte form, GameStrings strings, ushort species, EntityContext context)
{
if (form == 0)
return string.Empty;
var forms = FormConverter.GetFormList(species, strings.Types, strings.forms, genderForms, context);
return form >= forms.Length ? string.Empty : forms[form];
}
private const string MiniorFormName = "Meteor";
///
/// Converts the PKHeX standard form name to Showdown's form name.
///
/// Species ID
/// PKHeX form name
public static string GetShowdownFormName(ushort species, string form)
{
if (form.Length == 0)
{
return species switch
{
(int)Minior => MiniorFormName,
_ => form,
};
}
return species switch
{
(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.OrdinalIgnoreCase) => 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)Maushold when form is "Family of Four" => "Four",
(int)Greninja or (int)Rockruff or (int)Koraidon or (int)Miraidon => string.Empty,
_ => FormInfo.HasTotemForm(species) && form == "Large"
? species is (int)Raticate or (int)Marowak ? "Alola-Totem" : "Totem"
: form.Replace(' ', '-'),
};
}
///
/// Converts the Showdown form name to PKHeX's form name.
///
/// Species ID
/// Showdown form name
/// Showdown ability ID
public static string SetShowdownFormName(ushort species, string form, int ability)
{
if (form.Length != 0)
form = form.Replace(' ', '-'); // inconsistencies are great
return species switch
{
(int)Basculin when form is "Blue-Striped" => "Blue",
(int)Vivillon when form is "Pokeball" => "Poké Ball",
(int)Necrozma when form is "Dusk-Mane" => "Dusk",
(int)Necrozma when form is "Dawn-Wings" => "Dawn",
(int)Toxtricity when form is "Low-Key" => "Low Key",
(int)Darmanitan when form is "Galar-Zen" => "Galar Zen",
(int)Minior when form is not MiniorFormName => $"C-{form}",
(int)Zygarde when form is "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)Maushold when form is "Four" => "Family of Four",
(int)Urshifu or (int)Pikachu or (int)Alcremie => form.Replace('-', ' '), // Strike and Cosplay
_ => FormInfo.HasTotemForm(species) && form.EndsWith("Totem", StringComparison.OrdinalIgnoreCase) ? "Large" : form,
};
}
///
/// Fetches data from the input .
///
/// Raw lines containing numerous multi-line set data.
/// objects until is consumed.
public static IEnumerable GetShowdownSets(IEnumerable 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(8);
foreach (var line in lines)
{
if (!string.IsNullOrWhiteSpace(line))
{
setLines.Add(line);
continue;
}
if (setLines.Count == 0)
continue;
yield return new ShowdownSet(setLines);
setLines.Clear();
}
if (setLines.Count != 0)
yield return new ShowdownSet(setLines);
}
///
public static IEnumerable GetShowdownSets(ReadOnlyMemory text)
{
int start = 0;
do
{
var span = text.Span;
var slice = span[start..];
var set = GetShowdownSet(slice, out int length);
if (set.Species == 0)
break;
yield return set;
start += length;
}
while (start < text.Length);
}
///
public static IEnumerable GetShowdownSets(string text) => GetShowdownSets(text.AsMemory());
private static int GetLength(ReadOnlySpan text)
{
// Find the end of the Showdown Set lines.
// The end is implied when:
// - we see a complete whitespace or empty line, or
// - we witness four 'move' definition lines.
int length = 0;
int moveCount = 4;
while (true)
{
var newline = text.IndexOf('\n');
if (newline == -1)
return length + text.Length;
var slice = text[..newline];
var used = newline + 1;
length += used;
if (slice.IsEmpty || slice.IsWhiteSpace())
return length;
if (slice.TrimStart()[0] is '-' or '–' && --moveCount == 0)
return length;
text = text[used..];
}
}
public static ShowdownSet GetShowdownSet(ReadOnlySpan text, out int length)
{
length = GetLength(text);
var slice = text[..length];
var set = new ShowdownSet(slice);
while (length < text.Length && text[length] is '\r' or '\n' or ' ')
length++;
return set;
}
///
/// Converts the data into an importable set format for Pokémon Showdown.
///
/// PKM to convert to string
/// Multi line set data
public static string GetShowdownText(PKM pk)
{
if (pk.Species == 0)
return string.Empty;
return new ShowdownSet(pk).Text;
}
///
/// Fetches ShowdownSet lines from the input data.
///
/// Pokémon data to summarize.
/// Localization setting
/// Consumable list of lines.
public static IEnumerable GetShowdownText(IEnumerable data, string lang = ShowdownSet.DefaultLanguage)
{
var sets = GetShowdownSets(data);
foreach (var set in sets)
yield return set.LocalizedText(lang);
}
///
/// Fetches ShowdownSet lines from the input data.
///
/// Pokémon data to summarize.
/// Consumable list of lines.
public static IEnumerable GetShowdownSets(IEnumerable data)
{
foreach (var pk in data)
{
if (pk.Species == 0)
continue;
yield return new ShowdownSet(pk);
}
}
///
/// Fetches ShowdownSet lines from the input data, and combines it into one string.
///
/// Pokémon data to summarize.
/// Splitter between each set.
/// Single string containing all lines.
public static string GetShowdownSets(IEnumerable data, string separator) => string.Join(separator, GetShowdownText(data));
///
/// Gets a localized string preview of the provided .
///
/// Pokémon data
/// Language code
/// Multi-line string
public static string GetLocalizedPreviewText(PKM pk, string language)
{
var set = new ShowdownSet(pk);
set.InterpretAsPreview(pk);
return set.LocalizedText(language);
}
}