2022-06-18 18:04:24 +00:00
using System ;
2018-03-11 02:03:09 +00:00
2022-06-18 18:04:24 +00:00
namespace PKHeX.Core ;
/// <summary>
/// Contains extension logic for modifying <see cref="PKM"/> data.
/// </summary>
public static class CommonEdits
2018-03-11 02:03:09 +00:00
{
/// <summary>
2022-06-18 18:04:24 +00:00
/// Setting which enables/disables automatic manipulation of <see cref="PKM.MarkValue"/> when importing from a <see cref="IBattleTemplate"/>.
/// </summary>
public static bool ShowdownSetIVMarkings { get ; set ; } = true ;
/// <summary>
/// Setting which causes the <see cref="PKM.StatNature"/> to the <see cref="PKM.Nature"/> in Gen8+ formats.
2018-03-11 02:03:09 +00:00
/// </summary>
2022-06-18 18:04:24 +00:00
public static bool ShowdownSetBehaviorNature { get ; set ; }
/// <summary>
/// Sets the <see cref="PKM.Nickname"/> to the provided value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="nick"><see cref="PKM.Nickname"/> to set. If no nickname is provided, the <see cref="PKM.Nickname"/> is set to the default value for its current language and format.</param>
public static void SetNickname ( this PKM pk , string nick )
2018-03-11 02:03:09 +00:00
{
2022-06-26 06:08:28 +00:00
if ( nick . Length = = 0 )
2018-03-11 02:03:09 +00:00
{
2022-06-18 18:04:24 +00:00
pk . ClearNickname ( ) ;
return ;
2019-02-12 05:49:05 +00:00
}
2022-06-18 18:04:24 +00:00
pk . IsNicknamed = true ;
pk . Nickname = nick ;
}
2019-02-12 05:49:05 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Clears the <see cref="PKM.Nickname"/> to the default value.
/// </summary>
/// <param name="pk"></param>
public static string ClearNickname ( this PKM pk )
{
pk . IsNicknamed = false ;
string nick = SpeciesName . GetSpeciesNameGeneration ( pk . Species , pk . Language , pk . Format ) ;
pk . Nickname = nick ;
if ( pk is GBPKM pk12 )
pk12 . SetNotNicknamed ( ) ;
return nick ;
}
2018-03-11 02:03:09 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Sets the <see cref="PKM.Ability"/> value by sanity checking the provided <see cref="PKM.Ability"/> against the possible pool of abilities.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="abil">Desired <see cref="PKM.Ability"/> to set.</param>
public static void SetAbility ( this PKM pk , int abil )
{
if ( abil < 0 )
return ;
var index = pk . PersonalInfo . GetAbilityIndex ( abil ) ;
index = Math . Max ( 0 , index ) ;
pk . SetAbilityIndex ( index ) ;
}
/// <summary>
/// Sets the <see cref="PKM.Ability"/> value based on the provided ability index (0-2)
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Desired <see cref="PKM.AbilityNumber"/> (shifted by 1) to set.</param>
public static void SetAbilityIndex ( this PKM pk , int index )
{
if ( pk is PK5 pk5 & & index = = 2 )
pk5 . HiddenAbility = true ;
else if ( pk . Format < = 5 )
pk . PID = EntityPID . GetRandomPID ( Util . Rand , pk . Species , pk . Gender , pk . Version , pk . Nature , pk . Form , ( uint ) ( index * 0x10001 ) ) ;
pk . RefreshAbility ( index ) ;
}
/// <summary>
/// Sets a Random <see cref="PKM.EncryptionConstant"/> value. The <see cref="PKM.EncryptionConstant"/> is not updated if the value should match the <see cref="PKM.PID"/> instead.
/// </summary>
/// <remarks>Accounts for Wurmple evolutions.</remarks>
/// <param name="pk">Pokémon to modify.</param>
public static void SetRandomEC ( this PKM pk )
{
int gen = pk . Generation ;
if ( gen is 3 or 4 or 5 )
2018-03-11 02:03:09 +00:00
{
2022-06-18 18:04:24 +00:00
pk . EncryptionConstant = pk . PID ;
return ;
2018-09-03 17:30:35 +00:00
}
2018-03-11 02:03:09 +00:00
2022-06-18 18:04:24 +00:00
int wIndex = WurmpleUtil . GetWurmpleEvoGroup ( pk . Species ) ;
if ( wIndex ! = - 1 )
2018-09-03 17:30:35 +00:00
{
2022-06-18 18:04:24 +00:00
pk . EncryptionConstant = WurmpleUtil . GetWurmpleEncryptionConstant ( wIndex ) ;
return ;
2018-03-11 02:03:09 +00:00
}
2022-06-18 18:04:24 +00:00
pk . EncryptionConstant = Util . Rand32 ( ) ;
}
2018-03-11 02:03:09 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Sets the <see cref="PKM.IsShiny"/> derived value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="shiny">Desired <see cref="PKM.IsShiny"/> state to set.</param>
public static bool SetIsShiny ( this PKM pk , bool shiny ) = > shiny ? SetShiny ( pk ) : pk . SetUnshiny ( ) ;
2019-05-11 21:25:58 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Makes a <see cref="PKM"/> shiny.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="type">Shiny type to force. Only use Always* or Random</param>
/// <returns>Returns true if the <see cref="PKM"/> data was modified.</returns>
public static bool SetShiny ( PKM pk , Shiny type = Shiny . Random )
{
if ( pk . IsShiny & & type . IsValid ( pk ) )
return false ;
2018-03-11 02:03:09 +00:00
2022-06-18 18:04:24 +00:00
if ( type = = Shiny . Random | | pk . FatefulEncounter | | pk . Version = = ( int ) GameVersion . GO | | pk . Format < = 2 )
2018-03-11 02:03:09 +00:00
{
2022-06-18 18:04:24 +00:00
pk . SetShiny ( ) ;
return true ;
}
2018-03-11 02:03:09 +00:00
2022-06-18 18:04:24 +00:00
do { pk . SetShiny ( ) ; }
while ( ! type . IsValid ( pk ) ) ;
2019-11-21 04:38:05 +00:00
2022-06-18 18:04:24 +00:00
return true ;
}
2020-02-21 23:02:16 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Makes a <see cref="PKM"/> not-shiny.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <returns>Returns true if the <see cref="PKM"/> data was modified.</returns>
public static bool SetUnshiny ( this PKM pk )
{
if ( ! pk . IsShiny )
return false ;
2018-03-11 02:03:09 +00:00
2022-06-18 18:04:24 +00:00
pk . SetPIDGender ( pk . Gender ) ;
return true ;
}
2018-03-11 02:03:09 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Sets the <see cref="PKM.Nature"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Nature"/> value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="nature">Desired <see cref="PKM.Nature"/> value to set.</param>
public static void SetNature ( this PKM pk , int nature )
{
var value = Math . Min ( ( int ) Nature . Quirky , Math . Max ( ( int ) Nature . Hardy , nature ) ) ;
var format = pk . Format ;
if ( format > = 8 )
pk . StatNature = value ;
else if ( format is 3 or 4 )
pk . SetPIDNature ( value ) ;
else
pk . Nature = value ;
}
2018-03-11 02:03:09 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Copies <see cref="IBattleTemplate"/> details to the <see cref="PKM"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="Set"><see cref="IBattleTemplate"/> details to copy from.</param>
public static void ApplySetDetails ( this PKM pk , IBattleTemplate Set )
{
pk . Species = Math . Min ( pk . MaxSpeciesID , Set . Species ) ;
pk . Form = Set . Form ;
2022-07-01 00:24:01 +00:00
if ( Set . Moves [ 0 ] ! = 0 )
pk . SetMoves ( Set . Moves , true ) ;
2022-06-18 18:04:24 +00:00
pk . ApplyHeldItem ( Set . HeldItem , Set . Format ) ;
pk . CurrentLevel = Set . Level ;
pk . CurrentFriendship = Set . Friendship ;
pk . SetIVs ( Set . IVs ) ;
if ( pk is GBPKM gb )
2018-03-11 02:03:09 +00:00
{
2022-06-18 18:04:24 +00:00
// In Generation 1/2 Format sets, when IVs are not specified with a Hidden Power set, we might not have the hidden power type.
// Under this scenario, just force the Hidden Power type.
2022-06-26 06:08:28 +00:00
if ( Array . IndexOf ( Set . Moves , ( int ) Move . HiddenPower ) ! = - 1 & & pk . HPType ! = Set . HiddenPowerType )
{
if ( Array . FindIndex ( Set . IVs , static z = > z > = 30 ) ! = - 1 )
pk . SetHiddenPower ( Set . HiddenPowerType ) ;
}
2022-06-18 18:04:24 +00:00
// In Generation 1/2 Format sets, when EVs are not specified at all, it implies maximum EVs instead!
// Under this scenario, just apply maximum EVs (65535).
2022-06-26 06:08:28 +00:00
if ( Array . FindIndex ( Set . EVs , static z = > z ! = 0 ) = = - 1 )
2022-06-18 18:04:24 +00:00
gb . MaxEVs ( ) ;
2018-03-11 02:03:09 +00:00
else
2022-06-26 06:08:28 +00:00
pk . SetEVs ( Set . EVs ) ;
2018-03-11 02:03:09 +00:00
}
2022-06-18 18:04:24 +00:00
else
2018-03-11 02:03:09 +00:00
{
2022-06-26 06:08:28 +00:00
pk . SetEVs ( Set . EVs ) ;
2022-06-18 18:04:24 +00:00
}
2018-03-11 02:03:09 +00:00
2022-06-18 18:04:24 +00:00
// IVs have no side effects such as hidden power type in gen 8
// therefore all specified IVs are deliberate and should not be Hyper Trained for pokemon met in gen 8
if ( ! pk . Gen8 )
pk . SetSuggestedHyperTrainingData ( Set . IVs ) ;
2020-08-15 04:49:01 +00:00
2022-06-18 18:04:24 +00:00
if ( ShowdownSetIVMarkings )
pk . SetMarkings ( ) ;
2018-03-11 02:03:09 +00:00
2022-06-18 18:04:24 +00:00
pk . SetNickname ( Set . Nickname ) ;
pk . SetSaneGender ( Set . Gender ) ;
2022-02-07 23:40:02 +00:00
2022-06-18 18:04:24 +00:00
if ( Legal . IsPPUpAvailable ( pk ) )
pk . SetMaximumPPUps ( Set . Moves ) ;
2021-01-03 03:58:25 +00:00
2022-06-18 18:04:24 +00:00
if ( pk . Format > = 3 )
{
pk . SetAbility ( Set . Ability ) ;
pk . SetNature ( Set . Nature ) ;
}
2021-01-03 03:58:25 +00:00
2022-06-18 18:04:24 +00:00
pk . SetIsShiny ( Set . Shiny ) ;
pk . SetRandomEC ( ) ;
2018-03-11 02:03:09 +00:00
2022-06-18 18:04:24 +00:00
if ( pk is IAwakened a )
{
a . SetSuggestedAwakenedValues ( pk ) ;
if ( pk is PB7 b )
2018-11-20 02:26:46 +00:00
{
2022-06-18 18:04:24 +00:00
for ( int i = 0 ; i < 6 ; i + + )
b . SetEV ( i , 0 ) ;
b . ResetCalculatedValues ( ) ;
2018-11-20 02:26:46 +00:00
}
2022-06-18 18:04:24 +00:00
}
if ( pk is IGanbaru g )
g . SetSuggestedGanbaruValues ( pk ) ;
2019-11-26 01:32:10 +00:00
2022-06-18 18:04:24 +00:00
if ( pk is IGigantamax c )
c . CanGigantamax = Set . CanGigantamax ;
if ( pk is IDynamaxLevel d )
d . DynamaxLevel = d . GetSuggestedDynamaxLevel ( pk ) ;
2018-11-11 04:21:36 +00:00
2022-06-18 18:04:24 +00:00
if ( pk is ITechRecord8 t )
{
t . ClearRecordFlags ( ) ;
t . SetRecordFlags ( Set . Moves ) ;
}
if ( pk is IMoveShop8Mastery s )
s . SetMoveShopFlags ( Set . Moves , pk ) ;
2019-11-24 14:06:08 +00:00
2022-06-18 18:04:24 +00:00
if ( ShowdownSetBehaviorNature & & pk . Format > = 8 )
pk . Nature = pk . StatNature ;
2020-03-19 20:47:25 +00:00
2022-06-18 18:04:24 +00:00
var legal = new LegalityAnalysis ( pk ) ;
2022-06-26 06:08:28 +00:00
if ( legal . Parsed & & Array . FindIndex ( legal . Info . Relearn , static z = > ! z . Valid ) ! = - 1 )
2022-06-18 18:04:24 +00:00
pk . SetRelearnMoves ( legal . GetSuggestedRelearnMoves ( ) ) ;
pk . ResetPartyStats ( ) ;
pk . RefreshChecksum ( ) ;
}
2018-03-11 02:03:09 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Sets the <see cref="PKM.HeldItem"/> value depending on the current format and the provided item index & format.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="item">Held Item to apply</param>
/// <param name="format">Format required for importing</param>
public static void ApplyHeldItem ( this PKM pk , int item , int format )
{
item = ItemConverter . GetItemForFormat ( item , format , pk . Format ) ;
pk . HeldItem = ( ( uint ) item > pk . MaxItemID ) ? 0 : item ;
}
2018-04-26 01:45:31 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
2022-06-26 06:08:28 +00:00
/// Sets one of the <see cref="EffortValues"/> based on its index within the array.
2022-06-18 18:04:24 +00:00
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Index to set to</param>
/// <param name="value">Value to set</param>
public static int SetEV ( this PKM pk , int index , int value ) = > index switch
{
0 = > pk . EV_HP = value ,
1 = > pk . EV_ATK = value ,
2 = > pk . EV_DEF = value ,
3 = > pk . EV_SPE = value ,
4 = > pk . EV_SPA = value ,
5 = > pk . EV_SPD = value ,
_ = > throw new ArgumentOutOfRangeException ( nameof ( index ) ) ,
} ;
2018-03-11 02:03:09 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Sets one of the <see cref="PKM.IVs"/> based on its index within the array.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Index to set to</param>
/// <param name="value">Value to set</param>
public static int SetIV ( this PKM pk , int index , int value ) = > index switch
{
0 = > pk . IV_HP = value ,
1 = > pk . IV_ATK = value ,
2 = > pk . IV_DEF = value ,
3 = > pk . IV_SPE = value ,
4 = > pk . IV_SPA = value ,
5 = > pk . IV_SPD = value ,
_ = > throw new ArgumentOutOfRangeException ( nameof ( index ) ) ,
} ;
2018-03-11 02:03:09 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
2022-06-26 06:08:28 +00:00
/// Fetches the highest value the provided <see cref="EffortValues"/> index can be while considering others.
2022-06-18 18:04:24 +00:00
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Index to fetch for</param>
/// <returns>Highest value the value can be.</returns>
public static int GetMaximumEV ( this PKM pk , int index )
{
if ( pk . Format < 3 )
return ushort . MaxValue ;
2018-03-11 02:03:09 +00:00
2022-06-18 18:04:24 +00:00
var sum = pk . EVTotal - pk . GetEV ( index ) ;
int remaining = 510 - sum ;
return Math . Min ( Math . Max ( remaining , 0 ) , 252 ) ;
}
2018-04-22 19:43:18 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Fetches the highest value the provided <see cref="PKM.IVs"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Index to fetch for</param>
/// <param name="allow30">Causes the returned value to be dropped down -1 if the value is already at a maximum.</param>
/// <returns>Highest value the value can be.</returns>
public static int GetMaximumIV ( this PKM pk , int index , bool allow30 = false )
{
if ( pk . GetIV ( index ) = = pk . MaxIV & & allow30 )
return pk . MaxIV - 1 ;
return pk . MaxIV ;
}
2018-12-30 06:19:44 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game.
/// </summary>
/// <param name="pk">PKM to apply hatch details to</param>
/// <param name="reHatch">Re-hatch already hatched <see cref="PKM"/> inputs</param>
public static void ForceHatchPKM ( this PKM pk , bool reHatch = false )
{
if ( ! pk . IsEgg & & ! reHatch )
return ;
pk . IsEgg = false ;
pk . ClearNickname ( ) ;
pk . CurrentFriendship = pk . PersonalInfo . BaseFriendship ;
if ( pk . IsTradedEgg )
pk . Egg_Location = pk . Met_Location ;
var loc = EncounterSuggestion . GetSuggestedEggMetLocation ( pk ) ;
if ( loc > = 0 )
pk . Met_Location = loc ;
pk . MetDate = DateTime . Today ;
if ( pk . Gen6 )
pk . SetHatchMemory6 ( ) ;
}
2018-05-02 02:24:47 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game.
/// </summary>
/// <param name="pk">PKM to apply hatch details to</param>
/// <param name="origin">Game the egg originated from</param>
/// <param name="dest">Game the egg is currently present on</param>
public static void SetEggMetData ( this PKM pk , GameVersion origin , GameVersion dest )
{
bool traded = origin ! = dest ;
var today = pk . MetDate = DateTime . Today ;
pk . Egg_Location = EncounterSuggestion . GetSuggestedEncounterEggLocationEgg ( pk . Generation , origin , traded ) ;
pk . EggMetDate = today ;
}
2018-05-02 02:24:47 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Maximizes the <see cref="PKM.CurrentFriendship"/>. If the <see cref="PKM.IsEgg"/>, the hatch counter is set to 1.
/// </summary>
/// <param name="pk">PKM to apply hatch details to</param>
public static void MaximizeFriendship ( this PKM pk )
{
if ( pk . IsEgg )
pk . OT_Friendship = 1 ;
else
pk . CurrentFriendship = byte . MaxValue ;
if ( pk is ICombatPower pb )
pb . ResetCP ( ) ;
}
2018-07-14 22:08:14 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Maximizes the <see cref="PKM.CurrentLevel"/>. If the <see cref="PKM.IsEgg"/>, the <see cref="PKM"/> is ignored.
/// </summary>
/// <param name="pk">PKM to apply hatch details to</param>
public static void MaximizeLevel ( this PKM pk )
{
if ( pk . IsEgg )
return ;
pk . CurrentLevel = 100 ;
if ( pk is ICombatPower pb )
pb . ResetCP ( ) ;
}
2019-04-30 00:36:29 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Sets the <see cref="PKM.Nickname"/> to its default value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="la">Precomputed optional</param>
public static void SetDefaultNickname ( this PKM pk , LegalityAnalysis la )
{
if ( la . Parsed & & la . EncounterOriginal is EncounterTrade { HasNickname : true } t )
pk . SetNickname ( t . GetNickname ( pk . Language ) ) ;
else
pk . ClearNickname ( ) ;
}
2019-04-30 00:36:29 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Sets the <see cref="PKM.Nickname"/> to its default value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetDefaultNickname ( this PKM pk ) = > pk . SetDefaultNickname ( new LegalityAnalysis ( pk ) ) ;
private static readonly string [ ] PotentialUnicode = { "★☆☆☆" , "★★☆☆" , "★★★☆" , "★★★★" } ;
private static readonly string [ ] PotentialNoUnicode = { "+" , "++" , "+++" , "++++" } ;
/// <summary>
/// Gets the Potential evaluation of the input <see cref="pk"/>.
/// </summary>
/// <param name="pk">Pokémon to analyze.</param>
/// <param name="unicode">Returned value is unicode or not</param>
/// <returns>Potential string</returns>
public static string GetPotentialString ( this PKM pk , bool unicode = true )
{
var arr = unicode ? PotentialUnicode : PotentialNoUnicode ;
return arr [ pk . PotentialRating ] ;
}
// Extensions
/// <summary>
/// Gets the Location Name for the <see cref="PKM"/>
/// </summary>
/// <param name="pk">PKM to fetch data for</param>
/// <param name="eggmet">Location requested is the egg obtained location, not met location.</param>
/// <returns>Location string</returns>
public static string GetLocationString ( this PKM pk , bool eggmet )
{
if ( pk . Format < 2 )
return string . Empty ;
int location = eggmet ? pk . Egg_Location : pk . Met_Location ;
return GameInfo . GetLocationName ( eggmet , location , pk . Format , pk . Generation , ( GameVersion ) pk . Version ) ;
2018-03-11 02:03:09 +00:00
}
}