using System; 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(ushort species, byte 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 byte GetOutOfBattleForm(ushort species, byte form, int format) => species switch { (int)Darmanitan => (byte)(form & 2), (int)Zygarde when format > 6 => 3, (int)Minior => (byte)(form + 7), _ => 0, }; /// /// Indicates if the entity should be prevented from being traded away. /// /// Entity species /// Entity form /// Entity form argument /// Current generation format /// True if it trading should be disallowed. public static bool IsUntradable(ushort species, byte form, uint formArg, int format) => species switch { (int)Koraidon or (int)Miraidon when formArg == 1 => true, // Ride-able Box Legend (int)Pikachu when form == 8 && format == 7 => true, // Let's Go Pikachu Starter (int)Eevee when form == 1 && format == 7 => true, // Let's Go Eevee Starter _ => IsFusedForm(species, form, format), }; /// /// 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(ushort species, byte 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 /// Encounter context /// Current context public static bool IsFormChangeable(ushort species, byte oldForm, byte newForm, EntityContext origin, EntityContext current) { 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 current switch { EntityContext.Gen6 => false, EntityContext.Gen7 => newForm >= 2 || (oldForm == 1 && newForm == 0), _ => true, }; } if (species is (int)Deerling or (int)Sawsbuck) { if (origin == EntityContext.Gen5) return true; // B/W if (current.Generation() >= 8) return true; // Via S/V } return false; } public static bool IsFormChangeEgg(ushort species) => FormChangeEgg.Contains(species); private static ReadOnlySpan FormChangeEgg => new ushort[] { (int)Burmy, (int)Furfrou, (int)Oricorio, }; /// /// 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() { (int)Burmy, (int)Furfrou, (int)Oricorio, // Sometimes considered for wild encounters (int)Rotom, (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, (int)Palafin, }; /// /// 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; } /// /// Species has a Totem form in Gen7 (S/M & US/UM) that can be captured and owned. /// /// /// True if the species exists as a Totem. /// Excludes because it cannot be captured. public static bool HasTotemForm(ushort species) => species switch { (ushort)Raticate => true, (ushort)Marowak => true, (ushort)Gumshoos => true, (ushort)Vikavolt => true, (ushort)Ribombee => true, (ushort)Araquanid => true, (ushort)Lurantis => true, (ushort)Salazzle => true, (ushort)Mimikyu => true, (ushort)Kommoo => true, (ushort)Togedemaru => true, _ => false, }; /// /// Checks if the for the is a Totem form. /// /// Entity species /// Entity form /// Current generation format public static bool IsTotemForm(ushort species, byte form, EntityContext context) => context == EntityContext.Gen7 && 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(ushort species, byte form) { if (form == 0) return false; if (!HasTotemForm(species)) return false; if (species == (int)Mimikyu) return form is 2 or 3; if (species is (int)Raticate or (int)Marowak) 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 byte GetTotemBaseForm(ushort species, byte form) { if (species == (int)Mimikyu) return 0; return --form; } public static bool IsLordForm(ushort species, byte form, EntityContext context) { if (context != EntityContext.Gen8a) return false; return IsLordForm(species, form); } private static bool IsLordForm(ushort species, byte 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(ushort species, byte 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, ushort species, int format) { if (format <= 3 && species != (int)Unown) return false; if (HasFormValuesNotIndicatedByPersonal(species)) return true; int count = pi.FormCount; return count > 1; } /// /// /// private static bool HasFormValuesNotIndicatedByPersonal(ushort species) => species switch { (int)Unown => true, (int)Mothim => true, // (Burmy form is not cleared on evolution) (int)Scatterbug or (int)Spewpa => true, // Vivillon pre-evos _ => false, }; }