diff --git a/PKHeX.Core/Editing/ShowdownParsing.cs b/PKHeX.Core/Editing/ShowdownParsing.cs new file mode 100644 index 000000000..26b063359 --- /dev/null +++ b/PKHeX.Core/Editing/ShowdownParsing.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Linq; +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 int GetFormFromString(string name, GameStrings strings, int species, int format) + { + if (name.Length == 0) + return 0; + + string[] formStrings = FormConverter.GetFormList(species, strings.Types, strings.forms, genderForms, format); + return Math.Max(0, Array.FindIndex(formStrings, z => z.Contains(name))); + } + + /// + /// Converts a Form ID to string. + /// + /// + /// + /// + /// + /// + public static string GetStringFromForm(int form, GameStrings strings, int species, int format) + { + if (form <= 0) + return string.Empty; + + var forms = FormConverter.GetFormList(species, strings.Types, strings.forms, genderForms, format); + 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(int 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-") => 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(' ', '-') + }; + } + + /// + /// Converts the Showdown form name to PKHeX's form name. + /// + /// Species ID + /// Showdown form name + /// Showdown ability ID + public static string SetShowdownFormName(int species, string form, int ability) + { + if (form.Length != 0) + form = form.Replace(' ', '-'); // inconsistencies are great + + return species switch + { + (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 => form.Replace('-', ' '), + + _ => Legal.Totem_USUM.Contains(species) && form.EndsWith("Totem") ? "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); + } + + /// + /// 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 pkm) + { + if (pkm.Species == 0) + return string.Empty; + return new ShowdownSet(pkm).Text; + } + + /// + /// Fetches ShowdownSet lines from the input data. + /// + /// Pokémon data to summarize. + /// Consumable list of lines. + public static IEnumerable GetShowdownSets(IEnumerable data) => data.Where(p => p.Species != 0).Select(GetShowdownText); + + /// + /// 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, GetShowdownSets(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); + if (pk.Format <= 2) // Nature preview from IVs + set.Nature = Experience.GetNatureVC(pk.EXP); + return set.LocalizedText(language); + } + } +} diff --git a/PKHeX.Core/Editing/ShowdownSet.cs b/PKHeX.Core/Editing/ShowdownSet.cs index 04497c3e4..ec45c7d78 100644 --- a/PKHeX.Core/Editing/ShowdownSet.cs +++ b/PKHeX.Core/Editing/ShowdownSet.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using static PKHeX.Core.Species; namespace PKHeX.Core { @@ -10,7 +11,6 @@ namespace PKHeX.Core public sealed class ShowdownSet : IBattleTemplate { private static readonly string[] genders = {"M", "F", ""}; - private static readonly string[] genderForms = {"", "F", ""}; private static readonly string[] StatNames = { "HP", "Atk", "Def", "SpA", "SpD", "Spe" }; private static readonly string[] Splitters = {"\r\n", "\n"}; private static readonly string[] StatSplitters = { " / ", " " }; @@ -18,7 +18,7 @@ public sealed class ShowdownSet : IBattleTemplate private static readonly string[] ItemSplit = {" @ "}; private static readonly char[] ParenJunk = { '[', ']', '(', ')' }; private static readonly ushort[] DashedSpecies = {782, 783, 784, 250, 032, 029}; // Kommo-o, Ho-Oh, Nidoran-M, Nidoran-F - private const int MAX_SPECIES = (int)Core.Species.MAX_COUNT - 1; + private const int MAX_SPECIES = (int)MAX_COUNT - 1; private static readonly GameStrings DefaultStrings = GameInfo.GetStrings(GameLanguage.DefaultLanguage); /// @@ -103,15 +103,8 @@ private void LoadLines(IEnumerable lines) ParseLines(lines); - FormName = ConvertFormFromShowdown(FormName, Species, Ability); - // Set Form - if (FormName.Length == 0) - { - Form = 0; - return; - } - string[] formStrings = FormConverter.GetFormList(Species, Strings.Types, Strings.forms, genderForms, Format); - Form = Math.Max(0, Array.FindIndex(formStrings, z => z.Contains(FormName))); + FormName = ShowdownParsing.SetShowdownFormName(Species, FormName, Ability); + Form = ShowdownParsing.GetFormFromString(FormName, Strings, Species, Format); } private const int MaxMoveCount = 4; @@ -233,7 +226,7 @@ public List GetSetLines() var result = new List(); // First Line: Name, Nickname, Gender, Item - var form = ConvertFormToShowdown(FormName, Species); + var form = ShowdownParsing.GetShowdownFormName(Species, FormName); result.Add(GetStringFirstLine(form)); // IVs @@ -269,9 +262,9 @@ private string GetStringFirstLine(string form) string specForm = Strings.Species[Species]; if (form.Length != 0) specForm += $"-{form.Replace("Mega ", "Mega-")}"; - else if (Species == (int)Core.Species.NidoranM) + else if (Species == (int)NidoranM) specForm = specForm.Replace("♂", "-M"); - else if (Species == (int)Core.Species.NidoranF) + else if (Species == (int)NidoranF) specForm = specForm.Replace("♀", "-F"); string result = GetSpeciesNickname(specForm); @@ -322,18 +315,6 @@ private IEnumerable GetStringMoves() } } - /// - /// 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 pkm) - { - if (pkm.Species == 0) - return string.Empty; - return new ShowdownSet(pkm).Text; - } - /// /// Converts the data into an importable set format for Pokémon Showdown. /// @@ -372,19 +353,7 @@ public ShowdownSet(PKM pkm) } } - SetFormString(pkm.Form); - } - - private void SetFormString(int index) - { - Form = index; - if (index <= 0) - { - FormName = string.Empty; - return; - } - var forms = FormConverter.GetFormList(Species, Strings.Types, Strings.forms, genderForms, Format); - FormName = Form >= forms.Length ? string.Empty : forms[index]; + FormName = ShowdownParsing.GetStringFromForm(Form = pkm.Form, Strings, Species, Format); } private void ParseFirstLine(string first) @@ -428,15 +397,16 @@ bool TrySetItem(int format) private void ParseFirstLineNoItem(string line) { // Gender Detection - string last3 = line.Substring(line.Length - 3); - if (last3 == "(M)" || last3 == "(F)") + if (line.EndsWith("(M)") || line.EndsWith("(F)")) { - Gender = last3.Substring(1, 1); + Gender = line[line.Length - 2].ToString(); line = line.Substring(0, line.Length - 3); } - else if (line.Contains(Strings.Species[(int)Core.Species.Meowstic]) || line.Contains(Strings.Species[(int)Core.Species.Indeedee])) // Meowstic Edge Case with no gender provided + else // Meowstic Edge Case with no gender provided { - Gender = "M"; + var s = Strings.Species; + if (line.Contains(s[(int)Meowstic]) || line.Contains(s[(int)Indeedee])) + Gender = "M"; } // Nickname Detection @@ -446,25 +416,27 @@ private void ParseFirstLineNoItem(string line) ParseSpeciesForm(line); } - private bool ParseSpeciesForm(string spec) + private const string Gmax = "-Gmax"; + + private bool ParseSpeciesForm(string speciesLine) { - spec = spec.Trim(); - if (spec.EndsWith(Gmax)) + speciesLine = speciesLine.Trim(); + if (speciesLine.EndsWith(Gmax)) { CanGigantamax = true; - spec = spec.Substring(0, spec.Length - Gmax.Length); + speciesLine = speciesLine.Substring(0, speciesLine.Length - Gmax.Length); } - if ((Species = StringUtil.FindIndexIgnoreCase(Strings.specieslist, spec)) >= 0) // success, nothing else! + if ((Species = StringUtil.FindIndexIgnoreCase(Strings.specieslist, speciesLine)) >= 0) // success, nothing else! return true; // Form string present. - int end = spec.LastIndexOf('-'); + int end = speciesLine.LastIndexOf('-'); if (end < 0) return false; - Species = StringUtil.FindIndexIgnoreCase(Strings.specieslist, spec.Substring(0, end)); - FormName = spec.Substring(end + 1); + Species = StringUtil.FindIndexIgnoreCase(Strings.specieslist, speciesLine.Substring(0, end)); + FormName = speciesLine.Substring(end + 1); if (Species >= 0) return true; @@ -473,19 +445,19 @@ private bool ParseSpeciesForm(string spec) foreach (var e in DashedSpecies) { var sn = Strings.Species[e]; - if (!spec.StartsWith(sn.Replace("♂", "-M").Replace("♀", "-F"))) + if (!speciesLine.StartsWith(sn.Replace("♂", "-M").Replace("♀", "-F"))) continue; Species = e; - FormName = spec.Substring(sn.Length); + FormName = speciesLine.Substring(sn.Length); return true; } // Version Megas - end = spec.LastIndexOf('-', Math.Max(0, end - 1)); + end = speciesLine.LastIndexOf('-', Math.Max(0, end - 1)); if (end < 0) return false; - Species = StringUtil.FindIndexIgnoreCase(Strings.specieslist, spec.Substring(0, end)); - FormName = spec.Substring(end + 1); + Species = StringUtil.FindIndexIgnoreCase(Strings.specieslist, speciesLine.Substring(0, end)); + FormName = speciesLine.Substring(end + 1); return Species >= 0; } @@ -584,110 +556,6 @@ private void ParseLineIVs(string line) IVs = IVsSpeedFirst; } - private const string Minior = "Meteor"; - private const string Gmax = "-Gmax"; - - private static string ConvertFormToShowdown(string form, int spec) - { - if (form.Length == 0) - { - return spec switch - { - (int)Core.Species.Minior => Minior, - _ => form - }; - } - - switch (spec) - { - case (int)Core.Species.Basculin when form == "Blue": - return "Blue-Striped"; - case (int)Core.Species.Vivillon when form == "Poké Ball": - return "Pokeball"; - case (int)Core.Species.Zygarde: - form = form.Replace("-C", string.Empty); - return form.Replace("50%", string.Empty); - case (int)Core.Species.Minior: - if (form.StartsWith("M-")) - return Minior; - return form.Replace("C-", string.Empty); - case (int)Core.Species.Necrozma when form == "Dusk": - return $"{form}-Mane"; - case (int)Core.Species.Necrozma when form == "Dawn": - return $"{form}-Wings"; - - case (int)Core.Species.Furfrou: - case (int)Core.Species.Greninja: - case (int)Core.Species.Rockruff: - return string.Empty; - - case (int)Core.Species.Polteageist: - case (int)Core.Species.Sinistea: - return form == "Antique" ? form : string.Empty; - - default: - if (Legal.Totem_USUM.Contains(spec) && form == "Large") - return Legal.Totem_Alolan.Contains(spec) && spec != (int)Core.Species.Mimikyu ? "Alola-Totem" : "Totem"; - return form.Replace(' ', '-'); - } - } - - private static string ConvertFormFromShowdown(string form, int spec, int ability) - { - if (form.Length != 0) - form = form.Replace(' ', '-'); // inconsistencies are great - - switch (spec) - { - case (int)Core.Species.Basculin when form is "Blue-Striped": - return "Blue"; - case (int)Core.Species.Greninja when ability == 210: - return "Ash"; // Battle Bond - case (int)Core.Species.Vivillon when form is "Pokeball": - return "Poké Ball"; - - // Zygarde - case (int)Core.Species.Zygarde when form.Length == 0: - return ability == 211 ? "50%-C" : "50%"; - case (int)Core.Species.Zygarde when form is "Complete": - return form; - case (int)Core.Species.Zygarde when ability == 211: - return "-C"; // Power Construct - - case (int)Core.Species.Rockruff when ability == 020: // Rockruff-1 - return "Dusk"; - - // Minior - case (int)Core.Species.Minior when form.Length != 0 && form != Minior: - return $"C-{form}"; - - // Necrozma - case (int)Core.Species.Necrozma when form is "Dusk-Mane" or "Dusk Mane": - return "Dusk"; - case (int)Core.Species.Necrozma when form is "Dawn-Wings" or "Dawn Wings": - return "Dawn"; - - // Toxtricity - case (int)Core.Species.Toxtricity when form is "Low-Key": - return "Low Key"; - - // Darmanitan - case (int)Core.Species.Darmanitan: - if (form is "Galar-Zen") - return "Galar Zen"; - return form; - - // Urshifu - case (int)Core.Species.Urshifu: - return form.Replace('-', ' '); - - default: - if (Legal.Totem_USUM.Contains(spec) && form.EndsWith("Totem")) - return "Large"; - return form; - } - } - private static string RemoveAll(string original, char[] remove) => string.Concat(original.Where(z => !remove.Contains(z))); private static string[] SplitLineStats(string line) @@ -698,60 +566,5 @@ private static string[] SplitLineStats(string line) .Replace("SDef", "SpD").Replace("Sp Def", "SpD") .Replace("Spd", "Spe").Replace("Speed", "Spe").Split(StatSplitters, StringSplitOptions.None); } - - /// - /// 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); - } - - /// - /// Fetches ShowdownSet lines from the input data. - /// - /// Pokémon data to summarize. - /// Consumable list of lines. - public static IEnumerable GetShowdownSets(IEnumerable data) => data.Where(p => p.Species != 0).Select(GetShowdownText); - - /// - /// 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, GetShowdownSets(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); - if (pk.Format <= 2) // Nature preview from IVs - set.Nature = Experience.GetNatureVC(pk.EXP); - return set.LocalizedText(language); - } } } diff --git a/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs b/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs index 6542e170f..2de1f0277 100644 --- a/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs +++ b/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs @@ -1165,7 +1165,7 @@ public void ClickShowdownExportCurrentBox(object sender, EventArgs e) private static void ExportShowdownText(SaveFile sav, string success, Func> fetch) { var list = fetch(sav); - var result = ShowdownSet.GetShowdownSets(list, Environment.NewLine + Environment.NewLine); + var result = ShowdownParsing.GetShowdownSets(list, Environment.NewLine + Environment.NewLine); if (string.IsNullOrWhiteSpace(result)) return; if (WinFormsUtil.SetClipboardText(result)) diff --git a/PKHeX.WinForms/Controls/Slots/SummaryPreviewer.cs b/PKHeX.WinForms/Controls/Slots/SummaryPreviewer.cs index c953fb743..098b1ac6b 100644 --- a/PKHeX.WinForms/Controls/Slots/SummaryPreviewer.cs +++ b/PKHeX.WinForms/Controls/Slots/SummaryPreviewer.cs @@ -15,7 +15,7 @@ public void Show(Control pb, PKM pk) Clear(); return; } - var text = ShowdownSet.GetLocalizedPreviewText(pk, Settings.Default.Language); + var text = ShowdownParsing.GetLocalizedPreviewText(pk, Settings.Default.Language); ShowSet.SetToolTip(pb, text); } diff --git a/PKHeX.WinForms/MainWindow/Main.cs b/PKHeX.WinForms/MainWindow/Main.cs index 4363dc199..4327f947f 100644 --- a/PKHeX.WinForms/MainWindow/Main.cs +++ b/PKHeX.WinForms/MainWindow/Main.cs @@ -524,7 +524,7 @@ private void ClickShowdownExportPKM(object sender, EventArgs e) } var pk = PreparePKM(); - var text = ShowdownSet.GetShowdownText(pk); + var text = ShowdownParsing.GetShowdownText(pk); bool success = WinFormsUtil.SetClipboardText(text); if (!success || !Clipboard.GetText().Equals(text)) WinFormsUtil.Alert(MsgClipboardFailWrite, MsgSimulatorExportFail); diff --git a/Tests/PKHeX.Core.Tests/Simulator/ShowdownSetTests.cs b/Tests/PKHeX.Core.Tests/Simulator/ShowdownSetTests.cs index ca2865b8c..13c609744 100644 --- a/Tests/PKHeX.Core.Tests/Simulator/ShowdownSetTests.cs +++ b/Tests/PKHeX.Core.Tests/Simulator/ShowdownSetTests.cs @@ -149,13 +149,13 @@ public void SimulatorParseMultiple() { var text = string.Join("\r\n\r\n", Sets); var lines = text.Split(new[] {"\r\n", "\n"}, StringSplitOptions.None); - var sets = ShowdownSet.GetShowdownSets(lines); + var sets = ShowdownParsing.GetShowdownSets(lines); Assert.True(sets.Count() == Sets.Length); - sets = ShowdownSet.GetShowdownSets(Enumerable.Empty()); + sets = ShowdownParsing.GetShowdownSets(Enumerable.Empty()); Assert.True(!sets.Any()); - sets = ShowdownSet.GetShowdownSets(new [] {"", " ", " "}); + sets = ShowdownParsing.GetShowdownSets(new [] {"", " ", " "}); Assert.True(!sets.Any()); }