using System; using System.Collections.Generic; namespace PKHeX.Core; /// /// Logic related to the name of a . /// public static class SpeciesName { private const int LatestGeneration = PKX.Generation; /// /// Species name lists indexed by the value. /// public static readonly IReadOnlyList> SpeciesLang = new[] { Util.GetSpeciesList("ja"), // 0 (unused, invalid) Util.GetSpeciesList("ja"), // 1 Util.GetSpeciesList("en"), // 2 Util.GetSpeciesList("fr"), // 3 Util.GetSpeciesList("it"), // 4 Util.GetSpeciesList("de"), // 5 Util.GetSpeciesList("es"), // 6 (reserved for Gen3 KO?, unused) Util.GetSpeciesList("es"), // 7 Util.GetSpeciesList("ko"), // 8 Util.GetSpeciesList("zh"), // 9 Simplified Util.GetSpeciesList("zh2"), // 10 Traditional }; /// /// Egg name list indexed by the value. /// /// Indexing matches . private static readonly string[] EggNames = { "タマゴ", "タマゴ", "Egg", "Œuf", "Uovo", "Ei", "Huevo", "Huevo", "알", "蛋", "蛋", }; /// /// to table for all values. /// public static readonly IReadOnlyList> SpeciesDict = Util.GetMultiDictionary(SpeciesLang, 1); /// /// Gets a Pokémon's default name for the desired language ID. /// /// National Dex number of the Pokémon. Should be 0 if an egg. /// Language ID of the Pokémon /// The Species name if within expected range, else an empty string. /// Should only be used externally for message displays; for accurate in-game names use . public static string GetSpeciesName(ushort species, int language) { if ((uint)language >= SpeciesLang.Count) return string.Empty; if (species == 0) return EggNames[language]; var arr = SpeciesLang[language]; if (species >= arr.Count) return string.Empty; return arr[species]; } /// /// Gets a Pokémon's default name for the desired language ID and generation. /// /// National Dex number of the Pokémon. Should be 0 if an egg. /// Language ID of the Pokémon /// Generation specific formatting option /// Generation specific default species name public static string GetSpeciesNameGeneration(ushort species, int language, int generation) => generation switch { <= 4 => GetSpeciesName1234(species, language, generation), 7 when language == (int) LanguageID.ChineseS => GetSpeciesName7ZH(species, language), _ => GetSpeciesName(species, language), }; /// /// Gets a Pokémon's egg name for the desired language ID and generation. /// /// Language ID of the Pokémon /// Generation specific formatting option public static string GetEggName(int language, int generation = LatestGeneration) => generation switch { <= 4 => GetEggName1234(0, language, generation), _ => (uint)language >= EggNames.Length ? string.Empty : EggNames[language], }; private static string GetSpeciesName1234(ushort species, int language, int generation) { if (species == 0) return GetEggName1234(species, language, generation); var nick = GetSpeciesName(species, language); switch (language) { case (int)LanguageID.Korean when generation == 2: return StringConverter2KOR.LocalizeKOR2(nick); case (int)LanguageID.Korean: case (int)LanguageID.Japanese: return nick; // No further processing } Span result = stackalloc char[nick.Length]; nick.CopyTo(result); // All names are uppercase. for (int i = 0; i < result.Length; i++) result[i] = char.ToUpperInvariant(result[i]); if (language == (int)LanguageID.French) StringConverter4Util.StripDiacriticsFR4(result); // strips accents on E and I // Gen1/2 species names do not have spaces. if (generation >= 3) return new string(result); int indexSpace = result.IndexOf(' '); if (indexSpace != -1) { // Shift down. Strings have at most 1 occurrence of a space. result[(indexSpace+1)..].CopyTo(result[indexSpace..]); result = result[..^1]; } return new string(result); } private static string GetEggName1234(ushort species, int language, int generation) { if (generation == 3) return "タマゴ"; // All Gen3 eggs are treated as JPN eggs. // Gen2 & Gen4 don't use Œuf like in future games if (language == (int)LanguageID.French) return generation == 2 ? "OEUF" : "Oeuf"; var nick = GetSpeciesName(species, language); // All Gen4 egg names are Title cased. if (generation == 4) return nick; // Gen2: All Caps return nick.ToUpperInvariant(); } /// /// Gets the Generation 7 species name for Chinese games. /// /// /// Species Names for Chinese (Simplified) were revised during Generation 8 Crown Tundra DLC (#2). /// For a Gen7 species name request, return the old species name (hardcoded... yay). /// In an updated Gen8 game, the species nickname will automatically reset to the correct localization (on save/load ?), fixing existing entries. /// We don't differentiate patch revisions, just generation; Gen8 will return the latest localization. /// Gen8 did revise CHT species names, but only for Barraskewda, Urshifu, and Zarude. These species are new (Gen8); we can just use the latest. /// private static string GetSpeciesName7ZH(ushort species, int language) => species switch { // Revised in DLC1 - Isle of Armor // https://cn.portal-pokemon.com/topics/event/200323190120_post_19.html (int)Species.Porygon2 => "多边兽Ⅱ", // Later changed to 多边兽2型 (int)Species.PorygonZ => "多边兽Z", // Later changed to 多边兽乙型 (int)Species.Mimikyu => "谜拟Q", // Later changed to 谜拟丘 // Revised in DLC2 - Crown Tundra // https://cn.portal-pokemon.com/topics/event/201020170000_post_21.html (int)Species.Cofagrigus => "死神棺", // Later changed to 迭失棺 (int)Species.Pangoro => "流氓熊猫", // Later changed to 霸道熊猫 //(int)Species.Nickit => "偷儿狐", // Later changed to 狡小狐 //(int)Species.Thievul => "狐大盗", // Later changed to 猾大狐 //(int)Species.Toxel => "毒电婴", // Later changed to 电音婴 //(int)Species.Runerigus => "死神板", // Later changed to 迭失板 _ => GetSpeciesName(species, language), }; /// /// Checks if the input is not the species name for all languages. /// /// National Dex number of the Pokémon. Should be 0 if an egg. /// Current name /// Generation specific formatting option /// True if it does not match any language name, False if not nicknamed public static bool IsNicknamedAnyLanguage(ushort species, ReadOnlySpan nickname, int generation = LatestGeneration) { var langs = Language.GetAvailableGameLanguages(generation); foreach (var language in langs) { if (!IsNicknamed(species, nickname, language, generation)) return false; } return true; } /// /// Checks if the input is not the species name. /// /// National Dex number of the Pokémon. Should be 0 if an egg. /// Current name /// Language ID of the Pokémon /// Generation specific formatting option /// True if it does not match the language name, False if not nicknamed (matches). public static bool IsNicknamed(ushort species, ReadOnlySpan nickname, int language, int generation = LatestGeneration) { var expect = GetSpeciesNameGeneration(species, language, generation); return !nickname.SequenceEqual(expect); } /// /// Gets the Species name Language ID for the current name and generation. /// /// National Dex number of the Pokémon. Should be 0 if an egg. /// Language ID with a higher priority /// Current name /// Generation specific formatting option /// Language ID if it does not match any language name, -1 if no matches public static int GetSpeciesNameLanguage(ushort species, int priorityLanguage, ReadOnlySpan nickname, int generation = LatestGeneration) { var langs = Language.GetAvailableGameLanguages(generation); var priorityIndex = langs.IndexOf((byte)priorityLanguage); if (priorityIndex != -1) { var expect = GetSpeciesNameGeneration(species, priorityLanguage, generation); if (nickname.SequenceEqual(expect)) return priorityLanguage; } return GetSpeciesNameLanguage(species, nickname, generation, langs); } /// /// Gets the Species name Language ID for the current name and generation. /// /// National Dex number of the Pokémon. Should be 0 if an egg. /// Current name /// Generation specific formatting option /// Language ID if it does not match any language name, -1 if no matches public static int GetSpeciesNameLanguage(ushort species, ReadOnlySpan nickname, int generation = LatestGeneration) { var langs = Language.GetAvailableGameLanguages(generation); return GetSpeciesNameLanguage(species, nickname, generation, langs); } private static int GetSpeciesNameLanguage(ushort species, ReadOnlySpan nickname, int generation, ReadOnlySpan langs) { foreach (var lang in langs) { var expect = GetSpeciesNameGeneration(species, lang, generation); if (nickname.SequenceEqual(expect)) return lang; } return -1; } /// /// Gets the Species ID for the specified and . /// /// Species Name /// Language the name is from /// Species ID /// Only use this for modern era name -> ID fetching. public static int GetSpeciesID(string speciesName, int language = (int)LanguageID.English) { if (SpeciesDict[language].TryGetValue(speciesName, out var value)) return value; // stupid ’, ignore language if we match these. return speciesName switch { "Farfetch'd" => (int)Species.Farfetchd, "Sirfetch'd" => (int)Species.Sirfetchd, _ => -1, }; } }