2020-10-25 16:15:16 +00:00
|
|
|
|
using System.Collections.Generic;
|
2020-10-25 16:30:42 +00:00
|
|
|
|
using static PKHeX.Core.Species;
|
2020-10-25 16:15:16 +00:00
|
|
|
|
|
|
|
|
|
namespace PKHeX.Core
|
|
|
|
|
{
|
2020-10-25 16:30:42 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Contains logic for Alternate Form information.
|
|
|
|
|
/// </summary>
|
2020-12-11 04:42:30 +00:00
|
|
|
|
public static class FormInfo
|
2020-10-25 16:15:16 +00:00
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Checks if the form cannot exist outside of a Battle.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="species">Entity species</param>
|
|
|
|
|
/// <param name="form">Entity form</param>
|
|
|
|
|
/// <param name="format">Current generation format</param>
|
|
|
|
|
/// <returns>True if it can only exist in a battle, false if it can exist outside of battle.</returns>
|
|
|
|
|
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)
|
|
|
|
|
{
|
2020-10-25 16:30:42 +00:00
|
|
|
|
case (int)Slowbro when form == 2 && format >= 8: // Only mark Ultra Necrozma as Battle Only
|
|
|
|
|
case (int)Darmanitan when form == 2 && format >= 8: // this one is OK, Galarian Slowbro (not a Mega)
|
|
|
|
|
case (int)Zygarde when form < 4: // Zygarde Complete
|
|
|
|
|
case (int)Mimikyu when form == 2: // Totem disguise Mimikyu
|
|
|
|
|
case (int)Necrozma when form < 3: // this one is OK, Galarian non-Zen
|
2020-10-25 16:15:16 +00:00
|
|
|
|
return false;
|
2020-12-30 23:03:55 +00:00
|
|
|
|
case (int)Minior: return form < 7; // Minior Shields-Down
|
2020-10-25 16:15:16 +00:00
|
|
|
|
|
|
|
|
|
default:
|
2020-10-25 16:35:19 +00:00
|
|
|
|
return form != 0;
|
2020-10-25 16:15:16 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Reverts the Battle Form to the form it would have outside of Battle.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>Only call this if you've already checked that <see cref="IsBattleOnlyForm"/> returns true.</remarks>
|
|
|
|
|
/// <param name="species">Entity species</param>
|
|
|
|
|
/// <param name="form">Entity form</param>
|
|
|
|
|
/// <param name="format">Current generation format</param>
|
|
|
|
|
/// <returns>Suggested alt form value.</returns>
|
|
|
|
|
public static int GetOutOfBattleForm(int species, int form, int format)
|
|
|
|
|
{
|
|
|
|
|
return species switch
|
|
|
|
|
{
|
2020-10-25 16:30:42 +00:00
|
|
|
|
(int)Darmanitan => form & 2,
|
|
|
|
|
(int)Zygarde when format > 6 => 3,
|
|
|
|
|
(int)Minior => form + 7,
|
2020-10-25 16:15:16 +00:00
|
|
|
|
_ => 0
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Checks if the <see cref="form"/> is a fused form, which indicates it cannot be traded away.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="species">Entity species</param>
|
|
|
|
|
/// <param name="form">Entity form</param>
|
|
|
|
|
/// <param name="format">Current generation format</param>
|
|
|
|
|
/// <returns>True if it is a fused species-form, false if it is not fused.</returns>
|
|
|
|
|
public static bool IsFusedForm(int species, int form, int format)
|
|
|
|
|
{
|
|
|
|
|
return species switch
|
|
|
|
|
{
|
2020-10-25 16:30:42 +00:00
|
|
|
|
(int)Kyurem when form != 0 && format >= 5 => true,
|
|
|
|
|
(int)Necrozma when form != 0 && format >= 7 => true,
|
|
|
|
|
(int)Calyrex when form != 0 && format >= 8 => true,
|
2020-10-25 16:15:16 +00:00
|
|
|
|
_ => false
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-02 22:46:26 +00:00
|
|
|
|
/// <summary>Checks if the form may be different than the original encounter detail.</summary>
|
|
|
|
|
/// <param name="species">Original species</param>
|
|
|
|
|
/// <param name="oldForm">Original form</param>
|
|
|
|
|
/// <param name="newForm">Current form</param>
|
|
|
|
|
/// <param name="format">Current format</param>
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Species that can be captured normally in the wild and can change between their forms.
|
|
|
|
|
/// </summary>
|
2020-12-22 01:17:56 +00:00
|
|
|
|
public static readonly HashSet<int> WildChangeFormAfter = new()
|
2020-11-02 22:46:26 +00:00
|
|
|
|
{
|
|
|
|
|
412, // Burmy
|
|
|
|
|
479, // Rotom
|
|
|
|
|
676, // Furfrou
|
|
|
|
|
741, // Oricorio
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Species that can change between their forms, regardless of origin.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>Excludes Zygarde as it has special conditions. Check separately.</remarks>
|
2020-12-22 01:17:56 +00:00
|
|
|
|
private static readonly HashSet<int> FormChange = new(WildChangeFormAfter)
|
2020-11-02 22:46:26 +00:00
|
|
|
|
{
|
|
|
|
|
386, // Deoxys
|
|
|
|
|
487, // Giratina
|
|
|
|
|
492, // Shaymin
|
|
|
|
|
493, // Arceus
|
|
|
|
|
641, // Tornadus
|
|
|
|
|
642, // Thundurus
|
|
|
|
|
645, // Landorus
|
|
|
|
|
646, // Kyurem
|
|
|
|
|
647, // Keldeo
|
|
|
|
|
649, // Genesect
|
|
|
|
|
720, // Hoopa
|
|
|
|
|
773, // Silvally
|
|
|
|
|
800, // Necrozma
|
|
|
|
|
898, // Calyrex
|
|
|
|
|
};
|
|
|
|
|
|
2020-10-25 16:15:16 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Species that have an alternate form that cannot exist outside of battle.
|
|
|
|
|
/// </summary>
|
2020-12-22 01:17:56 +00:00
|
|
|
|
private static readonly HashSet<int> BattleForms = new()
|
2020-10-25 16:15:16 +00:00
|
|
|
|
{
|
2020-10-25 16:30:42 +00:00
|
|
|
|
(int)Castform,
|
|
|
|
|
(int)Cherrim,
|
|
|
|
|
(int)Darmanitan,
|
|
|
|
|
(int)Meloetta,
|
|
|
|
|
(int)Aegislash,
|
|
|
|
|
(int)Xerneas,
|
|
|
|
|
(int)Zygarde,
|
|
|
|
|
|
|
|
|
|
(int)Wishiwashi,
|
2020-12-30 23:03:55 +00:00
|
|
|
|
(int)Minior,
|
2020-10-25 16:30:42 +00:00
|
|
|
|
(int)Mimikyu,
|
|
|
|
|
|
|
|
|
|
(int)Cramorant,
|
|
|
|
|
(int)Morpeko,
|
|
|
|
|
(int)Eiscue,
|
|
|
|
|
|
|
|
|
|
(int)Zacian,
|
|
|
|
|
(int)Zamazenta,
|
|
|
|
|
(int)Eternatus,
|
2020-10-25 16:15:16 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Species that have a mega form that cannot exist outside of battle.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>Using a held item to change form during battle, via an in-battle transformation feature.</remarks>
|
2020-12-22 01:17:56 +00:00
|
|
|
|
private static readonly HashSet<int> BattleMegas = new()
|
2020-10-25 16:15:16 +00:00
|
|
|
|
{
|
|
|
|
|
// XY
|
2020-10-25 16:30:42 +00:00
|
|
|
|
(int)Venusaur, (int)Charizard, (int)Blastoise,
|
|
|
|
|
(int)Alakazam, (int)Gengar, (int)Kangaskhan, (int)Pinsir,
|
|
|
|
|
(int)Gyarados, (int)Aerodactyl, (int)Mewtwo,
|
2020-10-25 16:15:16 +00:00
|
|
|
|
|
2020-10-25 16:30:42 +00:00
|
|
|
|
(int)Ampharos, (int)Scizor, (int)Heracross, (int)Houndoom, (int)Tyranitar,
|
2020-10-25 16:15:16 +00:00
|
|
|
|
|
2020-10-25 16:30:42 +00:00
|
|
|
|
(int)Blaziken, (int)Gardevoir, (int)Mawile, (int)Aggron, (int)Medicham,
|
|
|
|
|
(int)Manectric, (int)Banette, (int)Absol, (int)Latios, (int)Latias,
|
2020-10-25 16:15:16 +00:00
|
|
|
|
|
2020-10-25 16:30:42 +00:00
|
|
|
|
(int)Garchomp, (int)Lucario, (int)Abomasnow,
|
2020-10-25 16:15:16 +00:00
|
|
|
|
|
|
|
|
|
// AO
|
2020-10-25 16:30:42 +00:00
|
|
|
|
(int)Beedrill, (int)Pidgeot, (int)Slowbro,
|
2020-10-25 16:15:16 +00:00
|
|
|
|
|
2020-10-25 16:30:42 +00:00
|
|
|
|
(int)Steelix,
|
2020-10-25 16:15:16 +00:00
|
|
|
|
|
2020-10-25 16:30:42 +00:00
|
|
|
|
(int)Sceptile, (int)Swampert, (int)Sableye, (int)Sharpedo, (int)Camerupt,
|
|
|
|
|
(int)Altaria, (int)Glalie, (int)Salamence, (int)Metagross, (int)Rayquaza,
|
2020-10-25 16:15:16 +00:00
|
|
|
|
|
2020-10-25 16:30:42 +00:00
|
|
|
|
(int)Lopunny, (int)Gallade,
|
|
|
|
|
(int)Audino, (int)Diancie,
|
2020-10-25 16:15:16 +00:00
|
|
|
|
|
|
|
|
|
// USUM
|
2020-10-25 16:30:42 +00:00
|
|
|
|
(int)Necrozma, // Ultra Necrozma
|
2020-10-25 16:15:16 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Species that have a primal form that cannot exist outside of battle.
|
|
|
|
|
/// </summary>
|
2020-12-22 01:17:56 +00:00
|
|
|
|
private static readonly HashSet<int> BattlePrimals = new() { (int)Kyogre, (int)Groudon };
|
2020-10-25 16:15:16 +00:00
|
|
|
|
|
|
|
|
|
private static readonly HashSet<int> BattleOnly = GetBattleFormSet();
|
|
|
|
|
|
|
|
|
|
private static HashSet<int> GetBattleFormSet()
|
|
|
|
|
{
|
|
|
|
|
var hs = new HashSet<int>(BattleForms);
|
|
|
|
|
hs.UnionWith(BattleMegas);
|
|
|
|
|
hs.UnionWith(BattlePrimals);
|
|
|
|
|
return hs;
|
|
|
|
|
}
|
2020-11-27 19:51:02 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Chhecks if the <see cref="form"/> for the <see cref="species"/> is a Totem form.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="species">Entity species</param>
|
|
|
|
|
/// <param name="form">Entity form</param>
|
|
|
|
|
/// <param name="format">Current generation format</param>
|
|
|
|
|
public static bool IsTotemForm(int species, int form, int format)
|
|
|
|
|
{
|
|
|
|
|
if (format != 7)
|
|
|
|
|
return false;
|
|
|
|
|
if (form == 0)
|
|
|
|
|
return false;
|
|
|
|
|
if (!Legal.Totem_USUM.Contains(species))
|
|
|
|
|
return false;
|
|
|
|
|
if (species == (int)Mimikyu)
|
2020-12-25 20:30:26 +00:00
|
|
|
|
return form is 2 or 3;
|
2020-11-27 19:51:02 +00:00
|
|
|
|
if (Legal.Totem_Alolan.Contains(species))
|
|
|
|
|
return form == 2;
|
|
|
|
|
return form == 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the base <see cref="form"/> for the <see cref="species"/> when the Totem form is reverted (on transfer).
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="species">Entity species</param>
|
|
|
|
|
/// <param name="form">Entity form</param>
|
|
|
|
|
public static int GetTotemBaseForm(int species, int form)
|
|
|
|
|
{
|
|
|
|
|
if (species == (int)Mimikyu)
|
|
|
|
|
return form - 2;
|
|
|
|
|
return form - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Checks if the <see cref="form"/> exists for the <see cref="species"/> without having an associated <see cref="PersonalInfo"/> index.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="species">Entity species</param>
|
|
|
|
|
/// <param name="form">Entity form</param>
|
|
|
|
|
/// <param name="format">Current generation format</param>
|
2020-12-11 04:42:30 +00:00
|
|
|
|
/// <seealso cref="HasFormValuesNotIndicatedByPersonal"/>
|
|
|
|
|
public static bool IsValidOutOfBoundsForm(int species, int form, int format)
|
2020-11-27 19:51:02 +00:00
|
|
|
|
{
|
|
|
|
|
return (Species) species switch
|
|
|
|
|
{
|
|
|
|
|
Unown => form < (format == 2 ? 26 : 28), // A-Z : A-Z?!
|
|
|
|
|
Mothim => form < 3, // Burmy base form is kept
|
|
|
|
|
|
|
|
|
|
Scatterbug => form < 18, // Vivillon Pre-evolutions
|
|
|
|
|
Spewpa => form < 18, // Vivillon Pre-evolutions
|
|
|
|
|
|
|
|
|
|
_ => false
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2020-12-11 04:42:30 +00:00
|
|
|
|
/// Checks if the <see cref="PKM"/> data should have a drop-down selection visible for the <see cref="PKM.Form"/> value.
|
2020-11-27 19:51:02 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="pi">Game specific personal info</param>
|
|
|
|
|
/// <param name="species"><see cref="Species"/> ID</param>
|
2020-12-11 04:42:30 +00:00
|
|
|
|
/// <param name="format"><see cref="PKM.Form"/> ID</param>
|
2020-11-27 19:51:02 +00:00
|
|
|
|
/// <returns>True if has formes that can be provided by <see cref="FormConverter.GetFormList"/>, otherwise false for none.</returns>
|
|
|
|
|
public static bool HasFormSelection(PersonalInfo pi, int species, int format)
|
|
|
|
|
{
|
|
|
|
|
if (format <= 3 && species != (int)Unown)
|
|
|
|
|
return false;
|
|
|
|
|
|
2020-12-11 04:42:30 +00:00
|
|
|
|
if (HasFormValuesNotIndicatedByPersonal.Contains(species))
|
2020-11-27 19:51:02 +00:00
|
|
|
|
return true;
|
|
|
|
|
|
2020-12-11 04:42:30 +00:00
|
|
|
|
int count = pi.FormCount;
|
2020-11-27 19:51:02 +00:00
|
|
|
|
return count > 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2020-12-11 04:42:30 +00:00
|
|
|
|
/// <seealso cref="IsValidOutOfBoundsForm"/>
|
2020-11-27 19:51:02 +00:00
|
|
|
|
/// </summary>
|
2020-12-22 01:17:56 +00:00
|
|
|
|
private static readonly HashSet<int> HasFormValuesNotIndicatedByPersonal = new()
|
2020-11-27 19:51:02 +00:00
|
|
|
|
{
|
|
|
|
|
(int)Unown,
|
|
|
|
|
(int)Mothim, // (Burmy forme carried over, not cleared)
|
|
|
|
|
(int)Scatterbug, (int)Spewpa, // Vivillon pre-evos
|
|
|
|
|
};
|
2020-10-25 16:15:16 +00:00
|
|
|
|
}
|
|
|
|
|
}
|