Modify G1/G2 character mappings (#4154)

### 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.
This commit is contained in:
abcboy101 2024-01-08 15:27:03 +08:00 committed by GitHub
parent 018694b84f
commit 01131e59f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 15 deletions

View file

@ -157,11 +157,9 @@ public static class StringConverter12
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 class StringConverter12
return count + 1;
}
private static bool TryGetIndex(in ReadOnlySpan<char> 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 = "べぺへり";
/// <summary>
/// Tries to remap the user input to a valid character.
/// </summary>
private static bool TryGetUserFriendlyRemap(in ReadOnlySpan<char> 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 class StringConverter12
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<char> TableEN =>
[
@ -192,15 +220,15 @@ public static class StringConverter12
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<char> TableJP =>
@ -212,12 +240,12 @@ public static class StringConverter12
'パ', 'ピ', 'プ', 'ポ', 'ぱ', 'ぴ', 'ぷ', 'ペ', 'ぽ', 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, '', '', 'ォ', '♀', '', '', '', '', '', '', '', '', '', '', // F0-FF

View file

@ -151,13 +151,21 @@ public static class SpeciesName
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);
}

View file

@ -83,4 +83,27 @@ public class StringTests
var result = StringConverter12Transporter.GetString(b12[..len], true);
result.Should().Be(g7);
}
[Theory]
[InlineData(Species.MrMime, "MRMIME")]
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<byte> 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);
}
}