using System; using System.Collections.Generic; using static PKHeX.Core.Species; namespace PKHeX.Core { /// /// Logic for various related requests. /// public static class FormConverter { /// /// Gets a list of formes that the species can have. /// /// of the Pokémon. /// List of type names /// List of form names /// List of genders names /// Generation number for exclusive formes /// A list of strings corresponding to the formes that a Pokémon can have. public static string[] GetFormList(int species, IReadOnlyList types, IReadOnlyList forms, IReadOnlyList genders, int generation) { // Mega List if (generation < 8 && IsFormListSingleMega(species)) return GetMegaSingle(types, forms); if (generation == 7 && Legal.Totem_USUM.Contains(species)) return GetFormsTotem(species, types, forms); if (species <= Legal.MaxSpeciesID_1) return GetFormsGen1(species, types, forms, generation); if (species <= Legal.MaxSpeciesID_2) return GetFormsGen2(species, types, forms, generation); if (species <= Legal.MaxSpeciesID_3) return GetFormsGen3(species, types, forms, generation); if (species <= Legal.MaxSpeciesID_4) return GetFormsGen4(species, types, forms, generation); if (species <= Legal.MaxSpeciesID_5) return GetFormsGen5(species, types, forms, generation); if (species <= Legal.MaxSpeciesID_6) return GetFormsGen6(species, types, forms, genders); if (species <= Legal.MaxSpeciesID_7_USUM) return GetFormsGen7(species, types, forms); return GetFormsGen8(species, types, forms, genders); } // this is a hack; depends on currently loaded SaveFile's Game ID private static bool IsGG() => PKMConverter.Game == (int)GameVersion.GP || PKMConverter.Game == (int)GameVersion.GE; public static bool IsTotemForm(int species, int form, int generation) { if (generation != 7) return false; if (form == 0) return false; if (!Legal.Totem_USUM.Contains(species)) return false; if (species == (int)Mimikyu) return form == 2 || form == 3; if (Legal.Totem_Alolan.Contains(species)) return form == 2; return form == 1; } public static int GetTotemBaseForm(int species, int form) { if (species == (int)Mimikyu) return form - 2; return form - 1; } public static bool IsValidOutOfBoundsForme(int species, int form, int generation) { switch ((Species)species) { case Unown: return form < (generation == 2 ? 26 : 28); // A-Z : A-Z?! case Mothim: // Burmy base form is kept return form < 3; case Scatterbug: case Spewpa: // Vivillon Pre-evolutions return form < 18; default: return false; } } private static readonly string[] EMPTY = { string.Empty }; private const string Starter = nameof(Starter); private static string[] GetFormsGen1(int species, IReadOnlyList types, IReadOnlyList forms, int generation) { switch ((Species)species) { case Charizard when generation < 8: case Mewtwo when generation < 8: return GetMegaXY(types, forms); case Eevee when IsGG(): return new[] { types[000], // Normal Starter, }; case Pikachu: return GetFormsPikachu(generation, types, forms); case Weezing when generation >= 8: case Ponyta when generation >= 8: case Rapidash when generation >= 8: case Slowpoke when generation >= 8: case MrMime when generation >= 8: case Farfetchd when generation >= 8: case Articuno when generation >= 8: case Zapdos when generation >= 8: case Moltres when generation >= 8: return GetFormsGalar(types, forms); case Slowbro when generation >= 8: return GetFormsGalarSlowbro(types, forms); default: return GetFormsAlolan(generation, types, forms, species); } } private static string[] GetFormsGen2(int species, IReadOnlyList types, IReadOnlyList forms, int generation) { return species switch { (int)Pichu when generation == 4 => GetFormsPichu(types, forms), (int)Slowking when generation >= 8 => GetFormsGalar(types, forms), (int)Unown => GetFormsUnown(generation), (int)Corsola when generation >= 8 => GetFormsGalar(types, forms), _ => EMPTY }; } private static string[] GetFormsGen3(int species, IReadOnlyList types, IReadOnlyList forms, int generation) { switch ((Species)species) { default: return EMPTY; case Zigzagoon when generation >= 8: case Linoone when generation >= 8: return GetFormsGalar(types, forms); case Castform: // Castform return new[] { types[000], // Normal forms[889], // Sunny forms[890], // Rainy forms[891], // Snowy }; case Kyogre: // Kyogre case Groudon: // Groudon return new[] { types[000], // Normal forms[899], // Primal }; case Deoxys: // Deoxys return new[] { types[000], // Normal forms[902], // Attack forms[903], // Defense forms[904], // Speed }; } } private static string[] GetFormsGen4(int species, IReadOnlyList types, IReadOnlyList forms, int generation) { switch ((Species)species) { default: return EMPTY; case Burmy: case Wormadam: case Mothim: return new[] { forms[412], // Plant forms[905], // Sandy forms[906], // Trash }; case Cherrim: return new[] { forms[421], // Overcast forms[909], // Sunshine }; case Shellos: case Gastrodon: return new[] { forms[422], // West forms[911], // East }; case Rotom: return new[] { types[000], // Normal forms[917], // Heat forms[918], // Wash forms[919], // Frost forms[920], // Fan forms[921], // Mow }; case Giratina: return new[] { forms[487], // Altered forms[922], // Origin }; case Shaymin: return new[] { forms[492], // Land forms[923], // Sky }; case Arceus: case Silvally: return GetFormsArceus(generation, types); } } private static string[] GetFormsGen5(int species, IReadOnlyList types, IReadOnlyList forms, int generation) { switch ((Species)species) { default: return EMPTY; case Basculin: return new[] { forms[550], // Red forms[942], // Blue }; case Darumaka when generation >= 8: return GetFormsGalar(types, forms); case Darmanitan: { if (generation <= 7) { return new[] { forms[555], // Standard forms[943], // Zen }; } return new[] { forms[555], // Standard forms[943], // Zen forms[Galarian], // Standard forms[Galarian] + " " + forms[943], // Zen }; } case Yamask when generation >= 8: return GetFormsGalar(types, forms); case Deerling: case Sawsbuck: return new[] { forms[585], // Spring forms[947], // Summer forms[948], // Autumn forms[949], // Winter }; case Stunfisk when generation >= 8: return GetFormsGalar(types, forms); case Tornadus: case Thundurus: case Landorus: return new[] { forms[641], // Incarnate forms[952], // Therian }; case Kyurem: return new[] { types[000], // Normal forms[953], // White forms[954], // Black }; case Keldeo: return new[] { forms[647], // Ordinary forms[955], // Resolute }; case Meloetta: return new[] { forms[648], // Aria forms[956], // Pirouette }; case Genesect: return new[] { types[000], // Normal types[010], // Douse (Water) types[012], // Shock (Electric) types[009], // Burn (Fire) types[014], // Chill (Ice) }; } } private static string[] GetFormsGen6(int species, IReadOnlyList types, IReadOnlyList forms, IReadOnlyList genders) { switch ((Species)species) { default: return EMPTY; case Greninja: return new[] { types[000], // Normal forms[962], // "Ash", forms[1012], // "Bonded" - Active }; case Scatterbug: case Spewpa: case Vivillon: return new[] { forms[666], // Icy Snow forms[963], // Polar forms[964], // Tundra forms[965], // Continental forms[966], // Garden forms[967], // Elegant forms[968], // Meadow forms[969], // Modern forms[970], // Marine forms[971], // Archipelago forms[972], // High-Plains forms[973], // Sandstorm forms[974], // River forms[975], // Monsoon forms[976], // Savanna forms[977], // Sun forms[978], // Ocean forms[979], // Jungle forms[980], // Fancy forms[981], // Poké Ball }; case Flabébé: case Florges: return new[] { forms[669], // Red forms[986], // Yellow forms[987], // Orange forms[988], // Blue forms[989], // White }; case Floette: return new[] { forms[669], // Red forms[986], // Yellow forms[987], // Orange forms[988], // Blue forms[989], // White forms[990], // Eternal }; case Furfrou: return new[] { forms[676], // Natural forms[995], // Heart forms[996], // Star forms[997], // Diamond forms[998], // Debutante forms[999], // Matron forms[1000], // Dandy forms[1001], // La Reine forms[1002], // Kabuki forms[1003], // Pharaoh }; case Meowstic: return new[] { genders[000], // Male genders[001], // Female }; case Aegislash: return new[] { forms[681], // Shield forms[1005], // Blade }; case Pumpkaboo: case Gourgeist: return new[] { forms[710], // Average forms[1006], // Small forms[1007], // Large forms[1008], // Super }; case Xerneas: return new[] { forms[716], // Neutral forms[1012], // Active }; case Hoopa: return new[] { forms[720], // Confined forms[1018], // Unbound }; case Zygarde: return new[] { forms[718], // 50% (Aura Break) forms[1013], // 10% (Aura Break) forms[1014] + "-C", // 10% Cell (Power Construct) forms[1015] + "-C", // 50% Cell (Power Construct) forms[1016], // 100% Cell (Power Construct) }; } } private static string[] GetFormsGen7(int species, IReadOnlyList types, IReadOnlyList forms) { switch ((Species)species) { default: return EMPTY; case Oricorio: return new[] { forms[741], // "RED" - Baile forms[1021], // "YLW" - Pom-Pom forms[1022], // "PNK" - Pa'u forms[1023], // "BLU" - Sensu }; case Rockruff: return new[] { types[0], // Normal forms[1064], // Dusk }; case Lycanroc: return new[] { forms[745], // Midday forms[1024], // Midnight forms[1064], // Dusk }; case Wishiwashi: return new[] { forms[746], forms[1025], // School }; case Silvally: return GetFormsArceus(7, types); case Minior: return new[] { forms[774], // "R-Meteor", // Meteor Red forms[1045], // "O-Meteor", // Meteor Orange forms[1046], // "Y-Meteor", // Meteor Yellow forms[1047], // "G-Meteor", // Meteor Green forms[1048], // "B-Meteor", // Meteor Blue forms[1049], // "I-Meteor", // Meteor Indigo forms[1050], // "V-Meteor", // Meteor Violet forms[1051], // "R-Core", // Core Red forms[1052], // "O-Core", // Core Orange forms[1053], // "Y-Core", // Core Yellow forms[1054], // "G-Core", // Core Green forms[1055], // "B-Core", // Core Blue forms[1056], // "I-Core", // Core Indigo forms[1057], // "V-Core", // Core Violet }; case Mimikyu: return new[] { forms[778], // Disguised forms[1058], // Busted }; case Necrozma: return new[] { types[000], // Normal forms[1065], // Dusk Mane forms[1066], // Dawn Wings forms[1067], // Ultra Necrozma }; case Magearna: return new[] { types[000], forms[1062], // Original }; } } private static string[] GetFormsGen8(int species, IReadOnlyList types, IReadOnlyList forms, IReadOnlyList genders) { switch ((Species)species) { default: return EMPTY; case Cramorant: return new[] { types[0], // Normal forms[Gulping], forms[Gorging], }; case Toxtricity: return new[] { forms[(int)Toxtricity], // Amped forms[LowKey], }; case Indeedee: return new[] { genders[000], // Male genders[001], // Female }; case Sinistea: case Polteageist: return new[] { "Phony", "Antique", }; case Alcremie: return new[] { forms[(int)Alcremie], // Vanilla Cream forms[RubyCream], forms[MatchaCream], forms[MintCream], forms[LemonCream], forms[SaltedCream], forms[RubySwirl], forms[CaramelSwirl], forms[RainbowSwirl], }; case Morpeko: return new[] { types[0], // Normal forms[HangryMode], }; case Eiscue: return new[] { types[0], // Normal forms[NoiceFace], }; case Zacian: case Zamazenta: return new[] { types[0], // Normal forms[Crowned], }; case Eternatus: return new[] { types[0], // Normal forms[Eternamax], }; case Urshifu: return new[] { forms[SingleStrike], forms[RapidStrike], }; case Zarude: return new[] { types[0], // Normal forms[Dada], }; } } private static string[] GetFormsAlolan (int generation, IReadOnlyList types, IReadOnlyList forms, int species) { if (generation < 7) return EMPTY; switch ((Species)species) { default: return EMPTY; case Meowth when generation >= 8: return new[] { types[000], forms[810], // Alolan forms[Galarian], // Alolan }; case Rattata: case Raichu: case Sandshrew: case Sandslash: case Vulpix: case Ninetales: case Diglett: case Dugtrio: case Meowth: case Persian: case Geodude: case Graveler: case Golem: case Grimer: case Muk: case Exeggutor: case Marowak: // Only reached when Gen8+, as Totem logic picks up Gen7 earlier. return new[] { types[000], forms[810] // Alolan }; } } private static string[] GetFormsPikachu(int generation, IReadOnlyList types, IReadOnlyList forms) { switch (generation) { default: return EMPTY; case 6: return new[] { types[000], // Normal forms[729], // Rockstar forms[730], // Belle forms[731], // Pop forms[732], // PhD forms[733], // Libre forms[734], // Cosplay }; case 7 when IsGG(): return new[] { types[000], // Normal forms[813], // Original forms[814], // Hoenn forms[815], // Sinnoh forms[816], // Unova forms[817], // Kalos forms[818], // Alola forms[1063], // Partner Starter, }; case 7: return new[] { types[000], // Normal forms[813], // Original forms[814], // Hoenn forms[815], // Sinnoh forms[816], // Unova forms[817], // Kalos forms[818], // Alola forms[1063], // Partner Starter, }; case 8: return new[] { types[000], // Normal forms[813], // Original forms[814], // Hoenn forms[815], // Sinnoh forms[816], // Unova forms[817], // Kalos forms[818], // Alola forms[1063], // Partner Starter, forms[1085], // World }; } } private static string[] GetFormsPichu (IReadOnlyList types, IReadOnlyList forms) { return new[] { types[000], // Normal forms[000], // Spiky }; } private static string[] GetFormsArceus (int generation, IReadOnlyList types) { switch (generation) { case 4: return new[] { types[00], // Normal types[01], // Fighting types[02], // Flying types[03], // Poison types[04], // etc types[05], types[06], types[07], types[08], "???", // ???-type arceus types[09], types[10], types[11], types[12], types[13], types[14], types[15], types[16] // No Fairy Type }; case 5: return new[] { types[00], // Normal types[01], // Fighting types[02], // Flying types[03], // Poison types[04], // etc types[05], types[06], types[07], types[08], types[09], types[10], types[11], types[12], types[13], types[14], types[15], types[16] // No Fairy type }; default: return new[] { types[00], // Normal types[01], // Fighting types[02], // Flying types[03], // Poison types[04], // etc types[05], types[06], types[07], types[08], types[09], types[10], types[11], types[12], types[13], types[14], types[15], types[16], types[17], }; } } private static string[] GetFormsTotem (int species, IReadOnlyList types, IReadOnlyList forms) { if ((Species)species == Mimikyu) // Mimikyu { return new[] { forms[778], // Disguised forms[1058], // Busted forms[1007], // Large "*" + forms[1058], // Busted }; } if (Legal.Totem_Alolan.Contains(species)) { return new[] { types[0], // Normal forms[810], // Alolan forms[1007], // Large }; } return new[] { types[0], // Normal forms[1007], // Large }; } private static string[] GetFormsUnown(int generation) { return generation switch { 2 => new[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", // "!", "?", not in Gen II }, _ => new[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "!", "?", } }; } private static bool IsFormListSingleMega(int species) => Mega_6_Single.Contains(species); private static readonly HashSet Mega_6_Single = new HashSet { // XY 003, 009, 065, 094, 115, 127, 130, 142, 181, 212, 214, 229, 248, 257, 282, 303, 306, 308, 310, 354, 359, 380, 381, 445, 448, 460, // AO 015, 018, 080, 208, 254, 260, 302, 319, 323, 334, 362, 373, 376, 384, 428, 475, 531, 719, }; /// /// Checks if the data should have a drop-down selection visible for the value. /// /// Game specific personal info /// ID /// ID /// True if has formes that can be provided by , otherwise false for none. public static bool HasFormSelection(PersonalInfo pi, int species, int format) { if (format <= 3 && species != (int)Unown) return false; if (HasFormeValuesNotIndicatedByPersonal.Contains(species)) return true; int count = pi.FormeCount; return count > 1; } private static readonly HashSet HasFormeValuesNotIndicatedByPersonal = new HashSet { (int)Unown, (int)Mothim, // (Burmy forme carried over, not cleared) (int)Scatterbug, (int)Spewpa, // Vivillon pre-evos }; private static string[] GetMegaSingle(IReadOnlyList types, IReadOnlyList forms) { return new[] { types[000], // Normal forms[804], // Mega }; } private static string[] GetMegaXY(IReadOnlyList types, IReadOnlyList forms) { return new[] { types[000], // Normal forms[805], // Mega X forms[806], // Mega Y }; } private static string[] GetFormsGalar(IReadOnlyList types, IReadOnlyList forms) { return new[] { types[000], // Normal forms[Galarian], // Galarian }; } private static string[] GetFormsGalarSlowbro(IReadOnlyList types, IReadOnlyList forms) { return new[] { types[000], // Normal forms[804], // Mega forms[Galarian], // Galarian }; } private const int Galarian = 1068; // private const int Gigantamax = 1069; private const int Gulping = 1070; private const int Gorging = 1071; private const int LowKey = 1072; private const int RubyCream = 1073; private const int MatchaCream = 1074; private const int MintCream = 1075; private const int LemonCream = 1076; private const int SaltedCream = 1077; private const int RubySwirl = 1078; private const int CaramelSwirl = 1079; private const int RainbowSwirl = 1080; private const int NoiceFace = 1081; private const int HangryMode = 1082; private const int Crowned = 1083; private const int Eternamax = 1084; private const int SingleStrike = 1086; private const int RapidStrike = 1087; private const int Dada = 1088; public static string[] GetAlcremieFormList(IReadOnlyList forms) { var result = new string[63]; // seed form0 with the pattern result[0 * 7] = forms[(int) Alcremie]; // Vanilla Cream result[1 * 7] = forms[RubyCream]; result[2 * 7] = forms[MatchaCream]; result[3 * 7] = forms[MintCream]; result[4 * 7] = forms[LemonCream]; result[5 * 7] = forms[SaltedCream]; result[6 * 7] = forms[RubySwirl]; result[7 * 7] = forms[CaramelSwirl]; result[8 * 7] = forms[RainbowSwirl]; const int deco = 7; const int fc = 9; for (int f = 0; f < fc; f++) { int start = f * deco; // iterate downwards using form0 as pattern ref, replacing on final loop for (int i = deco - 1; i >= 0; i--) { result[start + i] = $"{result[start]} ({(AlcremieDecoration)i})"; } } return result; } public static string[] GetFormArgumentStrings(int species, int form, int generation) { if (generation <= 5) return EMPTY; static string[] GetBlank(int count) { var result = new string[count]; for (int i = 0; i < result.Length; i++) result[i] = i.ToString(); return result; } return species switch { (int) Furfrou when form != 0 => new[] {"0", "1", "2", "3", "4", "5"}, (int) Hoopa when form == 1 => new[] {"0", "1", "2", "3"}, (int) Yamask when form == 1 => GetBlank(320), (int) Runerigus when form == 0 => GetBlank(320), // max Runerigus HP (int) Alcremie => Enum.GetNames(typeof(AlcremieDecoration)), _ => EMPTY }; } } }