ShowdownSet: handling for context-locked forms

Retconned forms like Totems and Cosplay Pikachu that only exist in a specific context now get recognized again
Current-Context has shifted from Gen6->Gen7->Gen8+, so a "get form list for species" won't return these forms.

Not a perfect implementation, but better for now.
This commit is contained in:
Kurt
2025-05-04 13:19:40 -05:00
parent a77e60d8d8
commit a85f919630
5 changed files with 67 additions and 10 deletions

View File

@@ -75,7 +75,10 @@ public static string GetStringFromForm(byte form, GameStrings strings, ushort sp
return string.Empty;
var forms = FormConverter.GetFormList(species, strings.Types, strings.forms, genderForms, context);
return form >= forms.Length ? string.Empty : forms[form];
var result = form >= forms.Length ? string.Empty : forms[form];
// Showdown uses a non-standard representation for some forms, and uses interstitial dashes instead of spaces.
return GetShowdownFormName(species, result);
}
private const string MiniorFormName = "Meteor";
@@ -116,13 +119,22 @@ public static string GetShowdownFormName(ushort species, string form)
};
}
public static bool IsTotemForm(ReadOnlySpan<char> formName) =>
formName.Equals("Totem", StringComparison.OrdinalIgnoreCase) ||
formName.Equals("Alola-Totem", StringComparison.OrdinalIgnoreCase) ||
formName.Equals("Large", StringComparison.OrdinalIgnoreCase);
public static bool IsCosplayPikachu(ReadOnlySpan<char> formName, ReadOnlySpan<string> formNames)
=> FormConverter.IsCosplayPikachu(formName, formNames);
/// <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(ushort species, string form, int ability)
public static string GetFormNameFromShowdownFormName(ushort species, string form, int ability)
{
if (form.Length != 0)
form = form.Replace(' ', '-'); // inconsistencies are great

View File

@@ -77,7 +77,8 @@ private void LoadLines(IEnumerable<string> lines, BattleTemplateLocalization loc
private void SanitizeResult(BattleTemplateLocalization localization)
{
FormName = ShowdownParsing.SetShowdownFormName(Species, FormName, Ability);
ReviseContextIfPastGenForm(localization.Strings);
FormName = ShowdownParsing.GetFormNameFromShowdownFormName(Species, FormName, Ability);
Form = ShowdownParsing.GetFormFromString(FormName, localization.Strings, Species, Context);
// Handle edge case with fixed-gender forms.
@@ -85,6 +86,17 @@ private void SanitizeResult(BattleTemplateLocalization localization)
ReviseGenderedForms();
}
private void ReviseContextIfPastGenForm(GameStrings strings)
{
if (FormName.Length == 0)
return; // no form name
if (FormInfo.HasTotemForm(Species) && ShowdownParsing.IsTotemForm(FormName))
Context = EntityContext.Gen7;
else if (Species is (int)Pikachu && ShowdownParsing.IsCosplayPikachu(FormName, strings.forms))
Context = EntityContext.Gen6;
}
private void ReviseGenderedForms()
{
if (Gender == 1) // Recognized with (F)
@@ -115,7 +127,7 @@ private void ParseLines(SpanLineEnumerator lines, BattleTemplateLocalization loc
bool first = true;
foreach (var line in lines)
{
ReadOnlySpan<char> trim = line.Trim();
var trim = line.Trim();
if (IsLengthOutOfRange(trim))
{
// Try for other languages just in case.
@@ -502,8 +514,7 @@ private void PushToken(BattleTemplateToken token, List<string> result, in Battle
{
// Core
case BattleTemplateToken.FirstLine:
var form = ShowdownParsing.GetShowdownFormName(Species, FormName);
result.Add(GetStringFirstLine(form, settings));
result.Add(GetStringFirstLine(FormName, settings));
break;
case BattleTemplateToken.Ability when (uint)Ability < strings.Ability.Count:
result.Add(cfg.Push(BattleTemplateToken.Ability, strings.Ability[Ability]));

View File

@@ -24,6 +24,24 @@ public static bool IsBattleOnlyForm(ushort species, byte form, byte format)
return false;
}
/// <summary>
/// Checks if the form is a Mega form.
/// </summary>
/// <param name="species">Entity species</param>
/// <param name="form">Entity form</param>
public static bool IsMegaForm(ushort species, byte form)
{
if (BattleMegas.Contains(species))
return IsBattleMegaForm(species, form);
return false;
}
/// <summary>
/// Checks if the species has a Mega form.
/// </summary>
/// <param name="species">Entity species</param>
public static bool HasMegaForm(ushort species) => BattleMegas.Contains(species);
private static bool IsBattleForm(ushort species, byte form) => species switch
{
// Only continue checking if the species is in the list of Battle Only forms.

View File

@@ -957,7 +957,7 @@ private static void SetAlcremieFormList(IReadOnlyList<string> forms, Span<string
SetDecorations(result, 7, forms[CaramelSwirl]);
SetDecorations(result, 8, forms[RainbowSwirl]);
static void SetDecorations(Span<string> result, [ConstantExpected] byte f, string baseName)
static void SetDecorations(Span<string> result, [ConstantExpected] byte f, ReadOnlySpan<char> baseName)
{
int start = f * AlcremieCountDecoration;
var slice = result.Slice(start, AlcremieCountDecoration);
@@ -973,4 +973,20 @@ static void SetDecorations(Span<string> result, [ConstantExpected] byte f, strin
(int)Alcremie => Enum.GetNames<AlcremieDecoration>(),
_ => EMPTY,
};
/// <summary>
/// Compatibility check for past-generation form list for <see cref="Pikachu"/>.
/// </summary>
/// <param name="formName">Desired form name</param>
/// <param name="formNames">List of all form names</param>
/// <returns><c>true</c> if the form name is a cosplay Pikachu form.</returns>
public static bool IsCosplayPikachu(ReadOnlySpan<char> formName, ReadOnlySpan<string> formNames)
{
for (int i = 729; i <= 734; i++)
{
if (formName.Equals(formNames[i], StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
}

View File

@@ -12,11 +12,11 @@ public void SimulatorGetParse()
{
var settings = new BattleTemplateExportSettings(BattleTemplateConfig.CommunityStandard);
foreach (ReadOnlySpan<char> setstr in Sets)
foreach (var setstr in Sets)
{
var set = new ShowdownSet(setstr).GetSetLines(settings);
foreach (var line in set)
setstr.Contains(line, StringComparison.Ordinal).Should().BeTrue($"Line {line} should be in the set {setstr}");
setstr.Contains(line, StringComparison.Ordinal).Should().BeTrue($"`{line}` should be in the set: {setstr}");
}
}
@@ -410,7 +410,7 @@ Adamant Nature
SetMunchSnorLax,
"""
Greninja @ Choice Specs
Greninja-Ash @ Choice Specs
Ability: Battle Bond
EVs: 252 SpA / 4 SpD / 252 Spe
Timid Nature