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,
};
}