using System.Collections.Generic; using static PKHeX.Core.Species; namespace PKHeX.Core; /// /// Contains logic for Alternate Form information. /// public static class FormInfo { /// /// Checks if the form cannot exist outside of a Battle. /// /// Entity species /// Entity form /// Current generation format /// True if it can only exist in a battle, false if it can exist outside of battle. public static bool IsBattleOnlyForm(int species, int form, int format) { if (!BattleOnly.Contains(species)) return false; // Some species have battle only forms as well as out-of-battle forms (other than base form). switch (species) { case (int)Slowbro when form == 2 && format >= 8: // this one is OK, Galarian Slowbro (not a Mega) case (int)Darmanitan when form == 2 && format >= 8: // this one is OK, Galarian non-Zen case (int)Zygarde when form < 4: // Zygarde Complete case (int)Mimikyu when form == 2: // Totem disguise Mimikyu case (int)Necrozma when form < 3: // Only mark Ultra Necrozma as Battle Only return false; case (int)Minior: return form < 7; // Minior Shields-Down default: return form != 0; } } /// /// Reverts the Battle Form to the form it would have outside of Battle. /// /// Only call this if you've already checked that returns true. /// Entity species /// Entity form /// Current generation format /// Suggested alt form value. public static int GetOutOfBattleForm(int species, int form, int format) => species switch { (int)Darmanitan => form & 2, (int)Zygarde when format > 6 => 3, (int)Minior => form + 7, _ => 0, }; /// /// Checks if the is a fused form, which indicates it cannot be traded away. /// /// Entity species /// Entity form /// Current generation format /// True if it is a fused species-form, false if it is not fused. public static bool IsFusedForm(int species, int form, int format) => species switch { (int)Kyurem when form != 0 && format >= 5 => true, (int)Necrozma when form != 0 && format >= 7 => true, (int)Calyrex when form != 0 && format >= 8 => true, _ => false, }; /// Checks if the form may be different than the original encounter detail. /// Original species /// Original form /// Current form /// Current format public static bool IsFormChangeable(int species, int oldForm, int newForm, int format) { if (FormChange.Contains(species)) return true; // Zygarde Form Changing // Gen6: Introduced; no form changing. // Gen7: Form changing introduced; can only change to Form 2/3 (Power Construct), never to 0/1 (Aura Break). A form-1 can be boosted to form-0. // Gen8: Form changing improved; can pick any Form & Ability combination. if (species == (int)Zygarde) { return format switch { 6 => false, 7 => newForm >= 2 || (oldForm == 1 && newForm == 0), _ => true, }; } return false; } /// /// Species that can change between their forms, regardless of origin. /// /// Excludes Zygarde as it has special conditions. Check separately. private static readonly HashSet FormChange = new() { // Sometimes considered for wild encounters (int)Burmy, (int)Rotom, (int)Furfrou, (int)Oricorio, (int)Deoxys, (int)Dialga, (int)Palkia, (int)Giratina, (int)Shaymin, (int)Arceus, (int)Tornadus, (int)Thundurus, (int)Landorus, (int)Kyurem, (int)Keldeo, (int)Genesect, (int)Hoopa, (int)Silvally, (int)Necrozma, (int)Calyrex, (int)Enamorus, }; /// /// Species that have an alternate form that cannot exist outside of battle. /// private static readonly HashSet BattleForms = new() { (int)Castform, (int)Cherrim, (int)Darmanitan, (int)Meloetta, (int)Aegislash, (int)Xerneas, (int)Zygarde, (int)Wishiwashi, (int)Minior, (int)Mimikyu, (int)Cramorant, (int)Morpeko, (int)Eiscue, (int)Zacian, (int)Zamazenta, (int)Eternatus, }; /// /// Species that have a mega form that cannot exist outside of battle. /// /// Using a held item to change form during battle, via an in-battle transformation feature. private static readonly HashSet BattleMegas = new() { // XY (int)Venusaur, (int)Charizard, (int)Blastoise, (int)Alakazam, (int)Gengar, (int)Kangaskhan, (int)Pinsir, (int)Gyarados, (int)Aerodactyl, (int)Mewtwo, (int)Ampharos, (int)Scizor, (int)Heracross, (int)Houndoom, (int)Tyranitar, (int)Blaziken, (int)Gardevoir, (int)Mawile, (int)Aggron, (int)Medicham, (int)Manectric, (int)Banette, (int)Absol, (int)Latios, (int)Latias, (int)Garchomp, (int)Lucario, (int)Abomasnow, // AO (int)Beedrill, (int)Pidgeot, (int)Slowbro, (int)Steelix, (int)Sceptile, (int)Swampert, (int)Sableye, (int)Sharpedo, (int)Camerupt, (int)Altaria, (int)Glalie, (int)Salamence, (int)Metagross, (int)Rayquaza, (int)Lopunny, (int)Gallade, (int)Audino, (int)Diancie, // USUM (int)Necrozma, // Ultra Necrozma }; /// /// Species that have a primal form that cannot exist outside of battle. /// private static readonly HashSet BattlePrimals = new() { (int)Kyogre, (int)Groudon }; private static readonly HashSet BattleOnly = GetBattleFormSet(); private static HashSet GetBattleFormSet() { var hs = new HashSet(BattleForms); hs.UnionWith(BattleMegas); hs.UnionWith(BattlePrimals); return hs; } /// /// Checks if the for the is a Totem form. /// /// Entity species /// Entity form /// Current generation format public static bool IsTotemForm(int species, int form, int format) => format == 7 && IsTotemForm(species, form); /// /// Checks if the for the is a Totem form. /// /// Use if you aren't 100% sure the format is 7. /// Entity species /// Entity form public static bool IsTotemForm(int species, int form) { if (form == 0) return false; if (!Legal.Totem_USUM.Contains(species)) return false; if (species == (int)Mimikyu) return form is 2 or 3; if (Legal.Totem_Alolan.Contains(species)) return form == 2; return form == 1; } /// /// Gets the base for the when the Totem form is reverted (on transfer). /// /// Entity species /// Entity form public static int GetTotemBaseForm(int species, int form) { if (species == (int)Mimikyu) return form - 2; return form - 1; } public static bool IsLordForm(int species, int form, int generation) { if (generation != 8) return false; return IsLordForm(species, form); } private static bool IsLordForm(int species, int form) => form != 0 && species switch { (int)Arcanine when form == 2 => true, (int)Electrode when form == 2 => true, (int)Lilligant when form == 2 => true, (int)Avalugg when form == 2 => true, (int)Kleavor when form == 1 => true, _ => false, }; /// /// Checks if the exists for the without having an associated index. /// /// Entity species /// Entity form /// Current generation format /// public static bool IsValidOutOfBoundsForm(int species, int form, int format) => (Species) species switch { Unown => form < (format == 2 ? 26 : 28), // A-Z : A-Z?! Mothim => form < 3, // Burmy base form is kept Scatterbug => form <= Vivillon3DS.MaxWildFormID, // Vivillon Pre-evolutions Spewpa => form <= Vivillon3DS.MaxWildFormID, // Vivillon Pre-evolutions _ => false, }; /// /// Checks if the data should have a drop-down selection visible for the value. /// /// Game specific personal info /// ID /// ID /// True if has forms that can be provided by , otherwise false for none. public static bool HasFormSelection(IPersonalFormInfo pi, int species, int format) { if (format <= 3 && species != (int)Unown) return false; if (HasFormValuesNotIndicatedByPersonal.Contains(species)) return true; int count = pi.FormCount; return count > 1; } /// /// /// private static readonly HashSet HasFormValuesNotIndicatedByPersonal = new() { (int)Unown, (int)Mothim, // (Burmy form is not cleared on evolution) (int)Scatterbug, (int)Spewpa, // Vivillon pre-evos }; }