From 01131e59f0d3aa06c12432fea4ac11862fb96ac7 Mon Sep 17 00:00:00 2001 From: abcboy101 <16735361+abcboy101@users.noreply.github.com> Date: Mon, 8 Jan 2024 15:27:03 +0800 Subject: [PATCH] Modify G1/G2 character mappings (#4154) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Treat 'ヘ' as katakana - For consistency with ベ/ペ/リ which are treated as katakana here and in StringConverter12Transporter.cs, map 0xCD to katakana ヘ instead of hiragana へ. - Allow input of hiragana べ/ぺ/へ/り, mapping them to katakana ベ/ペ/ヘ/リ. Previously, they would be treated as invalid and mapped to the string terminator. ### Swap mappings for 0xE8/0xF2 - Previously, 0xE8 was mapped to U+002E `.` FULL STOP, while 0xF2 was mapped to the special character U+2024 `․` ONE DOT LEADER. Since 0xF2 is used in user input while 0xE8 is only used in `MR.MIME`, swap these mappings so that normal keyboard input maps to the user-enterable character. - Modifies SpeciesName.cs to produce U+2024 for Mr. Mime in G1/G2. --- PKHeX.Core/PKM/Strings/StringConverter12.cs | 48 ++++++++++++++++----- PKHeX.Core/PKM/Util/SpeciesName.cs | 18 +++++--- Tests/PKHeX.Core.Tests/PKM/StringTests.cs | 23 ++++++++++ 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/PKHeX.Core/PKM/Strings/StringConverter12.cs b/PKHeX.Core/PKM/Strings/StringConverter12.cs index 1ed0193a8..1fa5dc865 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter12.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter12.cs @@ -157,11 +157,9 @@ public static int LoadString(ReadOnlySpan data, Span result, bool jp int i = 0; for (; i < value.Length; i++) { - char c = value[i]; - var index = dict.IndexOf(c); - if (index is -1 or G1TerminatorCode) + if (!TryGetIndex(dict, value[i], out var index)) break; - destBuffer[i] = (byte)index; + destBuffer[i] = index; } int count = i; @@ -171,6 +169,34 @@ public static int LoadString(ReadOnlySpan data, Span result, bool jp return count + 1; } + private static bool TryGetIndex(in ReadOnlySpan dict, char c, out byte result) + { + var index = dict.IndexOf(c); + if (index == -1) + return TryGetUserFriendlyRemap(dict, c, out result); + // \0 shouldn't really be user-entered, but just in case + result = (byte)index; + return index != default; + } + + // べ (U+3079), ぺ (U+307A), へ (U+3078), and り (U+308A) + private const string Hiragana = "べぺへり"; + + /// + /// Tries to remap the user input to a valid character. + /// + private static bool TryGetUserFriendlyRemap(in ReadOnlySpan dict, char c, out byte result) + { + if (Hiragana.Contains(c)) + { + int index = dict.IndexOf((char)(c + (char)0x60)); + result = (byte)index; + return true; // Valid Hiragana will always be found if it's in the table + } + result = default; + return false; + } + #region Gen 1/2 Character Tables private const char NUL = G1Terminator; @@ -181,7 +207,9 @@ public static int LoadString(ReadOnlySpan data, Span result, bool jp private const char LPO = '@'; // Po private const char LKE = '#'; // Ke private const char LEA = '%'; // é for Box - private const char DOT = '․'; // Not . + public const char DOT = '․'; // . for MR.MIME (U+2024, not U+002E) + private const char SPF = ' '; // Full-width space (U+3000) + public const char SPH = ' '; // Half-width space public static ReadOnlySpan TableEN => [ @@ -192,15 +220,15 @@ public static int LoadString(ReadOnlySpan data, Span result, bool jp NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 40-4F NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, TOT, NUL, NUL, // 50-5F NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 60-6F - LPO, LKE, '“', '”', NUL, '…', NUL, NUL, NUL, '┌', '─', '┐', '│', '└', '┘', ' ', // 70-7F + LPO, LKE, '“', '”', NUL, '…', NUL, NUL, NUL, '┌', '─', '┐', '│', '└', '┘', SPH, // 70-7F 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 80-8F 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '(', ')', ':', ';', '[', ']', // 90-9F 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', // A0-AF 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'à', 'è', 'é', 'ù', 'À', 'Á', // B0-BF 'Ä', 'Ö', 'Ü', 'ä', 'ö', 'ü', 'È', 'É', 'Ì', 'Í', 'Ñ', 'Ò', 'Ó', 'Ù', 'Ú', 'á', // C0-CF 'ì', 'í', 'ñ', 'ò', 'ó', 'ú', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, '←', '\'', // D0-DF - '’', LPK, LMN, '-', NUL, NUL, '?', '!', '.', '&', LEA, '→', '▷', '▶', '▼', '♂', // E0-EF - MNY, '×', DOT, '/', ',', '♀', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', // F0-FF + '’', LPK, LMN, '-', NUL, NUL, '?', '!', DOT, '&', LEA, '→', '▷', '▶', '▼', '♂', // E0-EF + MNY, '×', '.', '/', ',', '♀', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', // F0-FF ]; public static ReadOnlySpan TableJP => @@ -212,12 +240,12 @@ public static int LoadString(ReadOnlySpan data, Span result, bool jp 'パ', 'ピ', 'プ', 'ポ', 'ぱ', 'ぴ', 'ぷ', 'ペ', 'ぽ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 40-4F NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, TOT, NUL, NUL, // 50-5F NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 60-6F - '「', '」', '『', '』', '・', '⋯', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, ' ', // 70-7F + '「', '」', '『', '』', '・', '⋯', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, SPF, // 70-7F 'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', 'ス', 'セ', 'ソ', 'タ', // 80-8F 'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ', 'ヌ', 'ネ', 'ノ', 'ハ', 'ヒ', 'フ', 'ホ', 'マ', 'ミ', 'ム', // 90-9F 'メ', 'モ', 'ヤ', 'ユ', 'ヨ', 'ラ', 'ル', 'レ', 'ロ', 'ワ', 'ヲ', 'ン', 'ッ', 'ャ', 'ュ', 'ョ', // A0-AF 'ィ', 'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', 'し', 'す', 'せ', 'そ', // B0-BF - 'た', 'ち', 'つ', 'て', 'と', 'な', 'に', 'ぬ', 'ね', 'の', 'は', 'ひ', 'ふ', 'へ', 'ほ', 'ま', // C0-CF + 'た', 'ち', 'つ', 'て', 'と', 'な', 'に', 'ぬ', 'ね', 'の', 'は', 'ひ', 'ふ', 'ヘ', 'ほ', 'ま', // C0-CF 'み', 'む', 'め', 'も', 'や', 'ゆ', 'よ', 'ら', 'リ', 'る', 'れ', 'ろ', 'わ', 'を', 'ん', 'っ', // D0-DF 'ゃ', 'ゅ', 'ょ', 'ー', '゚', '゙', '?', '!', '。', 'ァ', 'ゥ', 'ェ', NUL, NUL, NUL, '♂', // E0-EF MNY, NUL, '.', '/', 'ォ', '♀', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', // F0-FF diff --git a/PKHeX.Core/PKM/Util/SpeciesName.cs b/PKHeX.Core/PKM/Util/SpeciesName.cs index b814f7be3..e310b1ee7 100644 --- a/PKHeX.Core/PKM/Util/SpeciesName.cs +++ b/PKHeX.Core/PKM/Util/SpeciesName.cs @@ -151,13 +151,21 @@ private static string GetSpeciesName1234(ushort species, int language, int gener if (generation >= 3) return new string(result); - int indexSpace = result.IndexOf(' '); - if (indexSpace != -1) + // The only Gen1/2 species with a space is Mr. Mime; different period and no space. + if (species == (int)Species.MrMime) { - // Shift down. Strings have at most 1 occurrence of a space. - result[(indexSpace+1)..].CopyTo(result[indexSpace..]); - result = result[..^1]; + int indexSpace = result.IndexOf(StringConverter12.SPH); + if (indexSpace > 0) + { + // Gen1/2 uses a different period for MR.MIME than user input. + result[indexSpace - 1] = StringConverter12.DOT; + + // Shift down. Strings have at most 1 occurrence of a space. + result[(indexSpace + 1)..].CopyTo(result[indexSpace..]); + result = result[..^1]; + } } + return new string(result); } diff --git a/Tests/PKHeX.Core.Tests/PKM/StringTests.cs b/Tests/PKHeX.Core.Tests/PKM/StringTests.cs index da0e206d0..2d708b433 100644 --- a/Tests/PKHeX.Core.Tests/PKM/StringTests.cs +++ b/Tests/PKHeX.Core.Tests/PKM/StringTests.cs @@ -83,4 +83,27 @@ public static void ConvertStringVC(string g12, string g7) var result = StringConverter12Transporter.GetString(b12[..len], true); result.Should().Be(g7); } + + [Theory] + [InlineData(Species.MrMime, "MR․MIME")] + public static void ConvertStringG1(Species species, string expect) + { + const bool jp = false; + const int lang = (int)LanguageID.English; + // Ensure the API returns the correct Generation 1 name string. + var name = SpeciesName.GetSpeciesNameGeneration((ushort)species, lang, 1); + name.Should().Be(expect); + + // Ensure the API converts it back and forth correctly. + Span convert = stackalloc byte[expect.Length + 1]; + var len = StringConverter12.SetString(convert, name, name.Length, jp); + len.Should().Be(expect.Length + 1); + var gen1Name = StringConverter12.GetString(convert, jp); + gen1Name.Should().Be(expect); + + // Truncated name transferred with Virtual Console rules isn't the same as the Generation 7 name. + var vcName = StringConverter12Transporter.GetString(convert[..len], jp); + var gen7Name = SpeciesName.GetSpeciesNameGeneration((ushort)species, lang, 7); + vcName.Should().NotBe(gen7Name); + } }