PKHeX/PKHeX.Core/PKM/PKM.cs

1172 lines
46 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
/// <summary>
/// Object representing a <see cref="PKM"/>'s data and derived properties.
/// </summary>
2019-11-16 01:34:18 +00:00
public abstract class PKM : ITrainerID, ILangNick, IGameValueLimit, INature
{
public static readonly string[] Extensions = PKX.GetPKMExtensions();
public abstract int SIZE_PARTY { get; }
public abstract int SIZE_STORED { get; }
public string Extension => GetType().Name.ToLower();
public abstract PersonalInfo PersonalInfo { get; }
2019-11-16 01:34:18 +00:00
public virtual IReadOnlyList<ushort> ExtraBytes => Array.Empty<ushort>();
// Internal Attributes set on creation
public readonly byte[] Data; // Raw Storage
PKHeX.Core Nullable cleanup (#2401) * Handle some nullable cases Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data) Make some classes have explicit constructors instead of { } initialization * Handle bits more obviously without null * Make SaveFile.BAK explicitly readonly again * merge constructor methods to have readonly fields * Inline some properties * More nullable handling * Rearrange box actions define straightforward classes to not have any null properties * Make extrabyte reference array immutable * Move tooltip creation to designer * Rearrange some logic to reduce nesting * Cache generated fonts * Split mystery gift album purpose * Handle more tooltips * Disallow null setters * Don't capture RNG object, only type enum * Unify learnset objects Now have readonly properties which are never null don't new() empty learnsets (>800 Learnset objects no longer created, total of 2400 objects since we also new() a move & level array) optimize g1/2 reader for early abort case * Access rewrite Initialize blocks in a separate object, and get via that object removes a couple hundred "might be null" warnings since blocks are now readonly getters some block references have been relocated, but interfaces should expose all that's needed put HoF6 controls in a groupbox, and disable * Readonly personal data * IVs non nullable for mystery gift * Explicitly initialize forced encounter moves * Make shadow objects readonly & non-null Put murkrow fix in binary data resource, instead of on startup * Assign dex form fetch on constructor Fixes legality parsing edge cases also handle cxd parse for valid; exit before exception is thrown in FrameGenerator * Remove unnecessary null checks * Keep empty value until init SetPouch sets the value to an actual one during load, but whatever * Readonly team lock data * Readonly locks Put locked encounters at bottom (favor unlocked) * Mail readonly data / offset Rearrange some call flow and pass defaults Add fake classes for SaveDataEditor mocking Always party size, no need to check twice in stat editor use a fake save file as initial data for savedata editor, and for gamedata (wow i found a usage) constrain eventwork editor to struct variable types (uint, int, etc), thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
public string? Identifier; // User or Form Custom Attribute
public int Box { get; set; } = -1; // Batch Editor
public int Slot { get; set; } = -1; // Batch Editor
protected PKM(byte[] data) => Data = data;
protected PKM(int size) => Data = new byte[size];
public virtual byte[] EncryptedPartyData => ArrayUtil.Truncate(Encrypt(), SIZE_PARTY);
public virtual byte[] EncryptedBoxData => ArrayUtil.Truncate(Encrypt(), SIZE_STORED);
public virtual byte[] DecryptedPartyData => ArrayUtil.Truncate(Write(), SIZE_PARTY);
public virtual byte[] DecryptedBoxData => ArrayUtil.Truncate(Write(), SIZE_STORED);
public virtual bool Valid { get => ChecksumValid && Sanity == 0; set { if (!value) return; Sanity = 0; RefreshChecksum(); } }
// Trash Bytes
public abstract byte[] Nickname_Trash { get; set; }
public abstract byte[] OT_Trash { get; set; }
public virtual byte[] HT_Trash { get => Array.Empty<byte>(); set { } }
2018-09-15 05:37:47 +00:00
protected byte[] GetData(int Offset, int Length) => Data.Slice(Offset, Length);
protected virtual ushort CalculateChecksum() => PokeCrypto.GetCHK(Data, SIZE_STORED);
2018-09-15 05:37:47 +00:00
protected abstract byte[] Encrypt();
public abstract int Format { get; }
2018-09-15 05:37:47 +00:00
private byte[] Write()
{
RefreshChecksum();
return Data;
}
// Surface Properties
public abstract int Species { get; set; }
public abstract string Nickname { get; set; }
public abstract int HeldItem { get; set; }
public abstract int Gender { get; set; }
public abstract int Nature { get; set; }
2019-11-16 01:34:18 +00:00
public virtual int StatNature { get => Nature; set => Nature = value; }
public abstract int Ability { get; set; }
public abstract int CurrentFriendship { get; set; }
public abstract int AltForm { get; set; }
public abstract bool IsEgg { get; set; }
public abstract bool IsNicknamed { get; set; }
public abstract uint EXP { get; set; }
public abstract int TID { get; set; }
public abstract string OT_Name { get; set; }
public abstract int OT_Gender { get; set; }
public abstract int Ball { get; set; }
public abstract int Met_Level { get; set; }
// Battle
public abstract int Move1 { get; set; }
public abstract int Move2 { get; set; }
public abstract int Move3 { get; set; }
public abstract int Move4 { get; set; }
public abstract int Move1_PP { get; set; }
public abstract int Move2_PP { get; set; }
public abstract int Move3_PP { get; set; }
public abstract int Move4_PP { get; set; }
public abstract int Move1_PPUps { get; set; }
public abstract int Move2_PPUps { get; set; }
public abstract int Move3_PPUps { get; set; }
public abstract int Move4_PPUps { get; set; }
public abstract int EV_HP { get; set; }
public abstract int EV_ATK { get; set; }
public abstract int EV_DEF { get; set; }
public abstract int EV_SPE { get; set; }
public abstract int EV_SPA { get; set; }
public abstract int EV_SPD { get; set; }
public abstract int IV_HP { get; set; }
public abstract int IV_ATK { get; set; }
public abstract int IV_DEF { get; set; }
public abstract int IV_SPE { get; set; }
public abstract int IV_SPA { get; set; }
public abstract int IV_SPD { get; set; }
public abstract int Status_Condition { get; set; }
public abstract int Stat_Level { get; set; }
public abstract int Stat_HPMax { get; set; }
public abstract int Stat_HPCurrent { get; set; }
public abstract int Stat_ATK { get; set; }
public abstract int Stat_DEF { get; set; }
public abstract int Stat_SPE { get; set; }
public abstract int Stat_SPA { get; set; }
public abstract int Stat_SPD { get; set; }
// Hidden Properties
public abstract int Version { get; set; }
public abstract int SID { get; set; }
public abstract int PKRS_Strain { get; set; }
public abstract int PKRS_Days { get; set; }
public abstract uint EncryptionConstant { get; set; }
public abstract uint PID { get; set; }
public abstract ushort Sanity { get; set; }
public abstract ushort Checksum { get; set; }
// Misc Properties
public abstract int Language { get; set; }
public abstract bool FatefulEncounter { get; set; }
public abstract int TSV { get; }
public abstract int PSV { get; }
public abstract int Characteristic { get; }
public abstract int MarkValue { get; protected set; }
public abstract int Met_Location { get; set; }
public abstract int Egg_Location { get; set; }
public abstract int OT_Friendship { get; set; }
public virtual bool Japanese => Language == (int)LanguageID.Japanese;
public virtual bool Korean => Language == (int)LanguageID.Korean;
// Future Properties
public virtual int Met_Year { get => 0; set { } }
public virtual int Met_Month { get => 0; set { } }
public virtual int Met_Day { get => 0; set { } }
public virtual string HT_Name { get => string.Empty; set { } }
public virtual int HT_Gender { get => 0; set { } }
public virtual int HT_Friendship { get => 0; set { } }
public virtual byte Enjoyment { get => 0; set { } }
public virtual byte Fullness { get => 0; set { } }
public virtual int AbilityNumber { get => 0; set { } }
/// <summary>
/// The date the Pokémon was met.
/// </summary>
/// <returns>
/// A DateTime representing the date the Pokémon was met.
/// Returns null if either the <see cref="PKM"/> format does not support dates or the stored date is invalid.</returns>
/// <remarks>
/// Not all <see cref="PKM"/> types support the <see cref="MetDate"/> property. In these cases, this property will return null.
/// If null is assigned to this property, it will be cleared.
/// </remarks>
public DateTime? MetDate
{
get
{
// Check to see if date is valid
2016-08-11 00:54:12 +00:00
if (!Util.IsDateValid(2000 + Met_Year, Met_Month, Met_Day))
return null;
return new DateTime(2000 + Met_Year, Met_Month, Met_Day);
}
set
{
if (value.HasValue)
{
// Only update the properties if a value is provided.
2016-08-10 14:07:16 +00:00
Met_Year = value.Value.Year - 2000;
Met_Month = value.Value.Month;
Met_Day = value.Value.Day;
}
else
{
// Clear the Met Date.
// If code tries to access MetDate again, null will be returned.
Met_Year = 0;
Met_Month = 0;
Met_Day = 0;
}
}
}
public virtual int Egg_Year { get => 0; set { } }
public virtual int Egg_Month { get => 0; set { } }
public virtual int Egg_Day { get => 0; set { } }
/// <summary>
/// The date a Pokémon was met as an egg.
/// </summary>
/// <returns>
/// A DateTime representing the date the Pokémon was met as an egg.
/// Returns null if either the <see cref="PKM"/> format does not support dates or the stored date is invalid.</returns>
/// <remarks>
/// Not all <see cref="PKM"/> types support the <see cref="EggMetDate"/> property. In these cases, this property will return null.
/// If null is assigned to this property, it will be cleared.
/// </remarks>
public DateTime? EggMetDate
{
get
{
// Check to see if date is valid
2016-08-11 00:54:12 +00:00
if (!Util.IsDateValid(2000 + Egg_Year, Egg_Month, Egg_Day))
return null;
return new DateTime(2000 + Egg_Year, Egg_Month, Egg_Day);
}
set
{
if (value.HasValue)
{
// Only update the properties if a value is provided.
2016-08-10 14:07:16 +00:00
Egg_Year = value.Value.Year - 2000;
Egg_Month = value.Value.Month;
Egg_Day = value.Value.Day;
}
else
{
// Clear the Met Date.
// If code tries to access MetDate again, null will be returned.
Egg_Year = 0;
Egg_Month = 0;
Egg_Day = 0;
}
}
}
public virtual int RelearnMove1 { get => 0; set { } }
public virtual int RelearnMove2 { get => 0; set { } }
public virtual int RelearnMove3 { get => 0; set { } }
public virtual int RelearnMove4 { get => 0; set { } }
public virtual int EncounterType { get => 0; set { } }
// Exposed but not Present in all
public abstract int CurrentHandler { get; set; }
// Maximums
public abstract int MaxMoveID { get; }
public abstract int MaxSpeciesID { get; }
public abstract int MaxItemID { get; }
public abstract int MaxAbilityID { get; }
public abstract int MaxBallID { get; }
public abstract int MaxGameID { get; }
public virtual int MinGameID => 0;
public abstract int MaxIV { get; }
public abstract int MaxEV { get; }
public abstract int OTLength { get; }
public abstract int NickLength { get; }
// Derived
public int SpecForm { get => Species + (AltForm << 11); set { Species = value & 0x7FF; AltForm = value >> 11; } }
public virtual int SpriteItem => HeldItem;
public virtual bool IsShiny => TSV == PSV;
public StorageSlotFlag StorageFlags { get; internal set; }
public bool Locked => StorageFlags.HasFlagFast(StorageSlotFlag.Locked);
public int TrainerID7 { get => (int)((uint)(TID | (SID << 16)) % 1000000); set => SetID7(TrainerSID7, value); }
public int TrainerSID7 { get => (int)((uint)(TID | (SID << 16)) / 1000000); set => SetID7(value, TrainerID7); }
2018-09-15 05:37:47 +00:00
public uint ShinyXor
{
get
{
var pid = PID;
var upper = (pid >> 16) ^ (uint)SID;
return (pid & 0xFFFF) ^ (uint)TID ^ upper;
}
}
2019-02-10 18:31:20 +00:00
public int DisplayTID
{
get => GenNumber >= 7 ? TrainerID7 : TID;
set { if (GenNumber >= 7) TrainerID7 = value; else TID = value; }
2019-02-10 18:31:20 +00:00
}
public int DisplaySID
{
get => GenNumber >= 7 ? TrainerSID7 : SID;
set { if (GenNumber >= 7) TrainerSID7 = value; else SID = value; }
2019-02-10 18:31:20 +00:00
}
private void SetID7(int sid7, int tid7)
{
var oid = (sid7 * 1_000_000) + (tid7 % 1_000_000);
TID = (ushort)oid;
SID = oid >> 16;
}
public bool E => Version == (int)GameVersion.E;
public bool FRLG => Version == (int)GameVersion.FR || Version == (int)GameVersion.LG;
public bool Pt => (int)GameVersion.Pt == Version;
public bool HGSS => Version == (int)GameVersion.HG || Version == (int)GameVersion.SS;
public bool BW => Version == (int)GameVersion.B || Version == (int)GameVersion.W;
public bool B2W2 => Version == (int)GameVersion.B2 || Version == (int)GameVersion.W2;
public bool XY => Version == (int)GameVersion.X || Version == (int)GameVersion.Y;
public bool AO => Version == (int)GameVersion.AS || Version == (int)GameVersion.OR;
public bool SM => Version == (int)GameVersion.SN || Version == (int)GameVersion.MN;
public bool USUM => Version == (int)GameVersion.US || Version == (int)GameVersion.UM;
public bool GO => Version == (int)GameVersion.GO;
public bool VC1 => Version >= (int)GameVersion.RD && Version <= (int)GameVersion.YW;
public bool VC2 => Version >= (int)GameVersion.GD && Version <= (int)GameVersion.C;
public bool LGPE => Version == (int)GameVersion.GP || Version == (int)GameVersion.GE;
public bool SWSH => Version == (int)GameVersion.SW || Version == (int)GameVersion.SH;
protected bool PtHGSS => Pt || HGSS;
public bool GO_LGPE => GO && Met_Location == Locations.GO7;
public bool GO_HOME => GO && Met_Location == Locations.GO8;
public bool VC => VC1 || VC2;
public bool GG => LGPE || GO_LGPE;
public bool Gen8 => (Version >= 44 && Version <= 45) || GO_HOME;
public bool Gen7 => (Version >= 30 && Version <= 33) || GG;
public bool Gen6 => Version >= 24 && Version <= 29;
public bool Gen5 => Version >= 20 && Version <= 23;
public bool Gen4 => Version >= 7 && Version <= 12 && Version != 9;
2018-09-15 05:37:47 +00:00
public bool Gen3 => (Version >= 1 && Version <= 5) || Version == 15;
public bool Gen2 => Version == (int)GameVersion.GSC;
public bool Gen1 => Version == (int)GameVersion.RBY;
public bool GenU => GenNumber <= 0;
2018-09-15 05:37:47 +00:00
2016-08-09 01:42:42 +00:00
public int GenNumber
{
get
{
2019-11-16 01:34:18 +00:00
if (Gen8) return 8;
if (Gen7) return 7;
2016-08-09 01:42:42 +00:00
if (Gen6) return 6;
if (Gen5) return 5;
if (Gen4) return 4;
if (Gen3) return 3;
if (Gen2) return Format; // 2
if (Gen1) return Format; // 1
Generation 1 and 2 legal Improvements (#1099) * Refactor parseMovesForEncounter to gather valid moves for species encounter, some Pokemon can have valid encounters with different source species from the encounter, the valid moves change if the encounter species change because some preevolutions moves are illegal if pokemon caught already evolved. Example, generation 1 Pikachu that can have a RBY Pikachu encounter and GSC Pichu encounter, the valid moves for the first encounters should not have any Pichu exclusive evolution moves Also assign the encounter match from gb when parsing moves like the variable Encounter Match, to store the encounter that is valid for the pokemon moves instead the first encounter. Store the species encounter, this will be needed to check if the evolution is valid for species that evolve leveling with a given learned move * Add Tradeback Status to the pokemon, this variable for generations 1 and 2 use data like the catch rate to determine if trade between generations 1 and 2 was possible. If analysis is for VC games tradeback have value NotTradeback for every gen 1 pokemon, but for cart saves some pokemon can be determine that have not been tradeback, if catch rate match species catch rate but do not match a valid generation 2 held item that means the pokemon habe been never traded to generation 2 games, that allow to discart encounters and moves from generation 2. Also if is not tradeback catch rate is used to filter encounters, catch rate determine in what species was captured the pokemon discarting some preevolutions moves Also add option for generation 1 cart save analysis to check legal status not allowing generation 2 games, like VC games but with Stadium allowed, like the generation 1 non tradeback rules from Smogon Also change evolution chains to included generation 2 preevolutions for gen 1 pokemon if tradeback was possible, it is needed to avoid parsemoves to check illegal pokemon like Hitmonchan with Tyrogue level up moves * Check legal values of generation 1 type and catch rate Replace pokemon catch rate after changind pokemon species always if pokemon was not tradeback from generation 2, the catch rate will keep unchanged only if it can be a held item and do not match species catch rate (default item) Also if catch rate is changed use base species catch rate to avoid legal errors if the catch rate of the evolution species if is not possible with the current moves * Filter ingame trades and static encounters with catch rate for generation 1 non tradeback * Fix min moves for generation 1 metapod encounter * Clean up * Fix encounter level for generation 1, valid moves are those with one level after the encounter level, pokemon can not learn a new move until level up Clean up type validation Fix generation 3 fatefull encounter eggs, the pokemon lost the fatefull mark when it hatch * Clean-up * Use new variable EncounterSpecies when it is needed to detect the species of the encounter, the old code wont work if the encounter is a wild slots array * Fix generation 1 evolution chains and catch rate as default held item * Fix Generation 1 Yellow Pikachu and Kadabra catch rates
2017-04-27 04:27:59 +00:00
if (VC1) return 1;
if (VC2) return 2;
2016-08-09 01:42:42 +00:00
return -1;
}
2016-08-09 01:42:42 +00:00
}
2018-09-15 05:37:47 +00:00
public int DebutGeneration => Legal.GetDebutGeneration(Species);
2019-03-21 23:48:17 +00:00
public bool PKRS_Infected { get => PKRS_Strain > 0; set => PKRS_Strain = value ? Math.Max(PKRS_Strain, 1) : 0; }
public bool PKRS_Cured
{
get => PKRS_Days == 0 && PKRS_Strain > 0;
set
{
PKRS_Days = value ? 0 : 1;
PKRS_Infected = true;
}
}
public virtual bool ChecksumValid => Checksum == CalculateChecksum();
2019-11-16 01:34:18 +00:00
public int CurrentLevel { get => Experience.GetLevel(EXP, PersonalInfo.EXPGrowth); set => EXP = Experience.GetEXP(Stat_Level = value, PersonalInfo.EXPGrowth); }
public int MarkCircle { get => Markings[0]; set { var marks = Markings; marks[0] = value; Markings = marks; } }
public int MarkTriangle { get => Markings[1]; set { var marks = Markings; marks[1] = value; Markings = marks; } }
public int MarkSquare { get => Markings[2]; set { var marks = Markings; marks[2] = value; Markings = marks; } }
public int MarkHeart { get => Markings[3]; set { var marks = Markings; marks[3] = value; Markings = marks; } }
public int MarkStar { get => Markings[4]; set { var marks = Markings; marks[4] = value; Markings = marks; } }
public int MarkDiamond { get => Markings[5]; set { var marks = Markings; marks[5] = value; Markings = marks; } }
public int IVTotal => IV_HP + IV_ATK + IV_DEF + IV_SPA + IV_SPD + IV_SPE;
public int EVTotal => EV_HP + EV_ATK + EV_DEF + EV_SPA + EV_SPD + EV_SPE;
public int MaximumIV => Math.Max(Math.Max(Math.Max(Math.Max(Math.Max(IV_HP, IV_ATK), IV_DEF), IV_SPA), IV_SPD), IV_SPE);
public int FlawlessIVCount
{
get
{
int max = MaxIV;
int ctr = 0;
if (IV_HP == max) ++ctr;
if (IV_ATK == max) ++ctr;
if (IV_DEF == max) ++ctr;
if (IV_SPA == max) ++ctr;
if (IV_SPD == max) ++ctr;
if (IV_SPE == max) ++ctr;
return ctr;
}
}
2016-11-24 04:24:01 +00:00
public string FileName => $"{FileNameWithoutExtension}.{Extension}";
public virtual string FileNameWithoutExtension
2016-11-24 04:24:01 +00:00
{
get
{
string form = AltForm > 0 ? $"-{AltForm:00}" : string.Empty;
string star = IsShiny ? " ★" : string.Empty;
return $"{Species:000}{form}{star} - {Nickname} - {Checksum:X4}{EncryptionConstant:X8}";
2016-11-24 04:24:01 +00:00
}
}
2018-09-15 05:37:47 +00:00
public int[] IVs
{
get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD };
set
{
PKHeX.Core Nullable cleanup (#2401) * Handle some nullable cases Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data) Make some classes have explicit constructors instead of { } initialization * Handle bits more obviously without null * Make SaveFile.BAK explicitly readonly again * merge constructor methods to have readonly fields * Inline some properties * More nullable handling * Rearrange box actions define straightforward classes to not have any null properties * Make extrabyte reference array immutable * Move tooltip creation to designer * Rearrange some logic to reduce nesting * Cache generated fonts * Split mystery gift album purpose * Handle more tooltips * Disallow null setters * Don't capture RNG object, only type enum * Unify learnset objects Now have readonly properties which are never null don't new() empty learnsets (>800 Learnset objects no longer created, total of 2400 objects since we also new() a move & level array) optimize g1/2 reader for early abort case * Access rewrite Initialize blocks in a separate object, and get via that object removes a couple hundred "might be null" warnings since blocks are now readonly getters some block references have been relocated, but interfaces should expose all that's needed put HoF6 controls in a groupbox, and disable * Readonly personal data * IVs non nullable for mystery gift * Explicitly initialize forced encounter moves * Make shadow objects readonly & non-null Put murkrow fix in binary data resource, instead of on startup * Assign dex form fetch on constructor Fixes legality parsing edge cases also handle cxd parse for valid; exit before exception is thrown in FrameGenerator * Remove unnecessary null checks * Keep empty value until init SetPouch sets the value to an actual one during load, but whatever * Readonly team lock data * Readonly locks Put locked encounters at bottom (favor unlocked) * Mail readonly data / offset Rearrange some call flow and pass defaults Add fake classes for SaveDataEditor mocking Always party size, no need to check twice in stat editor use a fake save file as initial data for savedata editor, and for gamedata (wow i found a usage) constrain eventwork editor to struct variable types (uint, int, etc), thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
if (value.Length != 6)
return;
IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2];
IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5];
}
}
2018-09-15 05:37:47 +00:00
public int[] EVs
{
get => new[] { EV_HP, EV_ATK, EV_DEF, EV_SPE, EV_SPA, EV_SPD };
set
{
PKHeX.Core Nullable cleanup (#2401) * Handle some nullable cases Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data) Make some classes have explicit constructors instead of { } initialization * Handle bits more obviously without null * Make SaveFile.BAK explicitly readonly again * merge constructor methods to have readonly fields * Inline some properties * More nullable handling * Rearrange box actions define straightforward classes to not have any null properties * Make extrabyte reference array immutable * Move tooltip creation to designer * Rearrange some logic to reduce nesting * Cache generated fonts * Split mystery gift album purpose * Handle more tooltips * Disallow null setters * Don't capture RNG object, only type enum * Unify learnset objects Now have readonly properties which are never null don't new() empty learnsets (>800 Learnset objects no longer created, total of 2400 objects since we also new() a move & level array) optimize g1/2 reader for early abort case * Access rewrite Initialize blocks in a separate object, and get via that object removes a couple hundred "might be null" warnings since blocks are now readonly getters some block references have been relocated, but interfaces should expose all that's needed put HoF6 controls in a groupbox, and disable * Readonly personal data * IVs non nullable for mystery gift * Explicitly initialize forced encounter moves * Make shadow objects readonly & non-null Put murkrow fix in binary data resource, instead of on startup * Assign dex form fetch on constructor Fixes legality parsing edge cases also handle cxd parse for valid; exit before exception is thrown in FrameGenerator * Remove unnecessary null checks * Keep empty value until init SetPouch sets the value to an actual one during load, but whatever * Readonly team lock data * Readonly locks Put locked encounters at bottom (favor unlocked) * Mail readonly data / offset Rearrange some call flow and pass defaults Add fake classes for SaveDataEditor mocking Always party size, no need to check twice in stat editor use a fake save file as initial data for savedata editor, and for gamedata (wow i found a usage) constrain eventwork editor to struct variable types (uint, int, etc), thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
if (value.Length != 6)
return;
EV_HP = value[0]; EV_ATK = value[1]; EV_DEF = value[2];
EV_SPE = value[3]; EV_SPA = value[4]; EV_SPD = value[5];
}
}
2018-09-15 05:37:47 +00:00
public int[] Stats
{
get => new[] { Stat_HPCurrent, Stat_ATK, Stat_DEF, Stat_SPE, Stat_SPA, Stat_SPD };
set
{
PKHeX.Core Nullable cleanup (#2401) * Handle some nullable cases Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data) Make some classes have explicit constructors instead of { } initialization * Handle bits more obviously without null * Make SaveFile.BAK explicitly readonly again * merge constructor methods to have readonly fields * Inline some properties * More nullable handling * Rearrange box actions define straightforward classes to not have any null properties * Make extrabyte reference array immutable * Move tooltip creation to designer * Rearrange some logic to reduce nesting * Cache generated fonts * Split mystery gift album purpose * Handle more tooltips * Disallow null setters * Don't capture RNG object, only type enum * Unify learnset objects Now have readonly properties which are never null don't new() empty learnsets (>800 Learnset objects no longer created, total of 2400 objects since we also new() a move & level array) optimize g1/2 reader for early abort case * Access rewrite Initialize blocks in a separate object, and get via that object removes a couple hundred "might be null" warnings since blocks are now readonly getters some block references have been relocated, but interfaces should expose all that's needed put HoF6 controls in a groupbox, and disable * Readonly personal data * IVs non nullable for mystery gift * Explicitly initialize forced encounter moves * Make shadow objects readonly & non-null Put murkrow fix in binary data resource, instead of on startup * Assign dex form fetch on constructor Fixes legality parsing edge cases also handle cxd parse for valid; exit before exception is thrown in FrameGenerator * Remove unnecessary null checks * Keep empty value until init SetPouch sets the value to an actual one during load, but whatever * Readonly team lock data * Readonly locks Put locked encounters at bottom (favor unlocked) * Mail readonly data / offset Rearrange some call flow and pass defaults Add fake classes for SaveDataEditor mocking Always party size, no need to check twice in stat editor use a fake save file as initial data for savedata editor, and for gamedata (wow i found a usage) constrain eventwork editor to struct variable types (uint, int, etc), thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
if (value.Length != 6)
return;
Stat_HPCurrent = value[0]; Stat_ATK = value[1]; Stat_DEF = value[2];
Stat_SPE = value[3]; Stat_SPA = value[4]; Stat_SPD = value[5];
}
}
public int[] Moves
{
get => new[] { Move1, Move2, Move3, Move4 };
set => SetMoves(value);
}
2018-09-15 05:37:47 +00:00
public void SetMoves(IReadOnlyList<int> value)
{
Move1 = value.Count > 0 ? value[0] : 0;
Move2 = value.Count > 1 ? value[1] : 0;
Move3 = value.Count > 2 ? value[2] : 0;
Move4 = value.Count > 3 ? value[3] : 0;
}
2016-08-27 15:43:36 +00:00
public int[] RelearnMoves
{
get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 };
set => SetRelearnMoves(value);
}
public void SetRelearnMoves(IReadOnlyList<int> value)
{
RelearnMove1 = value.Count > 0 ? value[0] : 0;
RelearnMove2 = value.Count > 1 ? value[1] : 0;
RelearnMove3 = value.Count > 2 ? value[2] : 0;
RelearnMove4 = value.Count > 3 ? value[3] : 0;
2016-08-27 15:43:36 +00:00
}
2018-09-15 05:37:47 +00:00
public int PIDAbility
{
get
{
if (GenNumber > 5 || Format > 5)
return -1;
2018-05-12 15:13:39 +00:00
if (Version == (int) GameVersion.CXD)
return PersonalInfo.GetAbilityIndex(Ability); // Can mismatch; not tied to PID
return (int)((Gen5 ? PID >> 16 : PID) & 1);
}
}
public virtual int[] Markings
{
get
{
int[] mark = new int[8];
for (int i = 0; i < 8; i++)
mark[i] = (MarkValue >> i) & 1;
return mark;
}
set
{
if (value.Length > 8)
return;
byte b = 0;
for (int i = 0; i < value.Length; i++)
b |= (byte)(Math.Min(value[i], 1) << i);
MarkValue = b;
}
}
private int HPBitValPower => ((IV_HP & 2) >> 1) | ((IV_ATK & 2) >> 0) | ((IV_DEF & 2) << 1) | ((IV_SPE & 2) << 2) | ((IV_SPA & 2) << 3) | ((IV_SPD & 2) << 4);
public virtual int HPPower => Format < 6 ? ((40 * HPBitValPower) / 63) + 30 : 60;
2018-09-15 05:37:47 +00:00
private int HPBitValType => ((IV_HP & 1) >> 0) | ((IV_ATK & 1) << 1) | ((IV_DEF & 1) << 2) | ((IV_SPE & 1) << 3) | ((IV_SPA & 1) << 4) | ((IV_SPD & 1) << 5);
2018-09-15 05:37:47 +00:00
public virtual int HPType
{
get => 15 * HPBitValType / 63;
set
{
IV_HP = (IV_HP & ~1) + HiddenPower.DefaultLowBits[value, 0];
IV_ATK = (IV_ATK & ~1) + HiddenPower.DefaultLowBits[value, 1];
IV_DEF = (IV_DEF & ~1) + HiddenPower.DefaultLowBits[value, 2];
IV_SPE = (IV_SPE & ~1) + HiddenPower.DefaultLowBits[value, 3];
IV_SPA = (IV_SPA & ~1) + HiddenPower.DefaultLowBits[value, 4];
IV_SPD = (IV_SPD & ~1) + HiddenPower.DefaultLowBits[value, 5];
}
}
// Legality Extensions
Generation 1 and 2 legal Improvements (#1099) * Refactor parseMovesForEncounter to gather valid moves for species encounter, some Pokemon can have valid encounters with different source species from the encounter, the valid moves change if the encounter species change because some preevolutions moves are illegal if pokemon caught already evolved. Example, generation 1 Pikachu that can have a RBY Pikachu encounter and GSC Pichu encounter, the valid moves for the first encounters should not have any Pichu exclusive evolution moves Also assign the encounter match from gb when parsing moves like the variable Encounter Match, to store the encounter that is valid for the pokemon moves instead the first encounter. Store the species encounter, this will be needed to check if the evolution is valid for species that evolve leveling with a given learned move * Add Tradeback Status to the pokemon, this variable for generations 1 and 2 use data like the catch rate to determine if trade between generations 1 and 2 was possible. If analysis is for VC games tradeback have value NotTradeback for every gen 1 pokemon, but for cart saves some pokemon can be determine that have not been tradeback, if catch rate match species catch rate but do not match a valid generation 2 held item that means the pokemon habe been never traded to generation 2 games, that allow to discart encounters and moves from generation 2. Also if is not tradeback catch rate is used to filter encounters, catch rate determine in what species was captured the pokemon discarting some preevolutions moves Also add option for generation 1 cart save analysis to check legal status not allowing generation 2 games, like VC games but with Stadium allowed, like the generation 1 non tradeback rules from Smogon Also change evolution chains to included generation 2 preevolutions for gen 1 pokemon if tradeback was possible, it is needed to avoid parsemoves to check illegal pokemon like Hitmonchan with Tyrogue level up moves * Check legal values of generation 1 type and catch rate Replace pokemon catch rate after changind pokemon species always if pokemon was not tradeback from generation 2, the catch rate will keep unchanged only if it can be a held item and do not match species catch rate (default item) Also if catch rate is changed use base species catch rate to avoid legal errors if the catch rate of the evolution species if is not possible with the current moves * Filter ingame trades and static encounters with catch rate for generation 1 non tradeback * Fix min moves for generation 1 metapod encounter * Clean up * Fix encounter level for generation 1, valid moves are those with one level after the encounter level, pokemon can not learn a new move until level up Clean up type validation Fix generation 3 fatefull encounter eggs, the pokemon lost the fatefull mark when it hatch * Clean-up * Use new variable EncounterSpecies when it is needed to detect the species of the encounter, the old code wont work if the encounter is a wild slots array * Fix generation 1 evolution chains and catch rate as default held item * Fix Generation 1 Yellow Pikachu and Kadabra catch rates
2017-04-27 04:27:59 +00:00
public TradebackType TradebackStatus { get; set; } = TradebackType.Any;
public bool Gen1_NotTradeback => TradebackStatus == TradebackType.Gen1_NotTradeback;
public bool Gen2_NotTradeback => TradebackStatus == TradebackType.Gen2_NotTradeback;
public virtual bool WasLink => false;
2018-09-15 05:37:47 +00:00
public bool WasEgg
{
get
{
int loc = Egg_Location;
return GenNumber switch
{
4 => (Legal.EggLocations4.Contains(loc) || (Species == (int) Core.Species.Manaphy && loc == Locations.Ranger4) || (loc == Locations.Faraway4 && PtHGSS)), // faraway
5 => Legal.EggLocations5.Contains(loc),
6 => Legal.EggLocations6.Contains(loc),
7 => Legal.EggLocations7.Contains(loc),
8 => Legal.EggLocations8.Contains(loc),
// Gen 1/2 and pal park Gen 3
_ => false
};
}
}
2018-09-15 05:37:47 +00:00
public bool WasBredEgg
{
get
{
int loc = Egg_Location;
switch (GenNumber)
{
case 4: return loc == Locations.Daycare4 || loc == Locations.LinkTrade4 || (loc == Locations.Faraway4 && PtHGSS); // faraway
case 5: return loc == Locations.Daycare5 || loc == Locations.LinkTrade5;
case 6:
case 7:
case 8:
return loc == Locations.Daycare5 || loc == Locations.LinkTrade6;
default: return false; // Gen 1/2 and pal park Gen 3
}
}
}
2018-09-15 05:37:47 +00:00
public virtual bool WasGiftEgg
{
get
{
if (!WasEgg)
return false;
int loc = Egg_Location;
switch (GenNumber)
{
case 4: return Legal.GiftEggLocation4.Contains(loc) || (loc == Locations.Faraway4 && HGSS); // faraway
case 5: return loc == 60003;
case 6:
case 7:
case 8:
return loc == 60004;
}
return false;
}
}
2018-09-15 05:37:47 +00:00
public virtual bool WasEvent => Locations.IsEventLocation5(Met_Location) || FatefulEncounter;
public virtual bool WasEventEgg
{
get
{
if (Gen4)
return WasEgg && Species == (int) Core.Species.Manaphy;
// Gen5+
if (Met_Level != 1)
return false;
int loc = Egg_Location;
return Locations.IsEventLocation5(loc) || (FatefulEncounter && loc != 0);
}
}
2018-09-15 05:37:47 +00:00
public bool WasTradedEgg => Egg_Location == GetTradedEggLocation();
public bool IsTradedEgg => Met_Location == GetTradedEggLocation();
private int GetTradedEggLocation() => Locations.TradedEggLocation(GenNumber);
public virtual bool IsUntraded => false;
public virtual bool IsNative => GenNumber == Format;
public virtual bool IsOriginValid => Species <= Legal.GetMaxSpeciesOrigin(Format);
/// <summary>
/// Checks if the <see cref="PKM"/> could inhabit a set of games.
/// </summary>
/// <param name="generation">Set of games.</param>
/// <param name="species"></param>
/// <returns>True if could inhabit, False if not.</returns>
public bool InhabitedGeneration(int generation, int species = -1)
{
if (species < 0)
species = Species;
if (Format == generation)
return true;
if (!IsOriginValid)
return false;
// Sanity Check Species ID
if (species > Legal.GetMaxSpeciesOrigin(generation) && !Legal.GetFutureGenEvolutions(generation).Contains(species))
return false;
2018-05-12 15:13:39 +00:00
// Trade generation 1 -> 2
if (Format == 2 && generation == 1 && !Gen2_NotTradeback)
Generation 1 and 2 legal Improvements (#1099) * Refactor parseMovesForEncounter to gather valid moves for species encounter, some Pokemon can have valid encounters with different source species from the encounter, the valid moves change if the encounter species change because some preevolutions moves are illegal if pokemon caught already evolved. Example, generation 1 Pikachu that can have a RBY Pikachu encounter and GSC Pichu encounter, the valid moves for the first encounters should not have any Pichu exclusive evolution moves Also assign the encounter match from gb when parsing moves like the variable Encounter Match, to store the encounter that is valid for the pokemon moves instead the first encounter. Store the species encounter, this will be needed to check if the evolution is valid for species that evolve leveling with a given learned move * Add Tradeback Status to the pokemon, this variable for generations 1 and 2 use data like the catch rate to determine if trade between generations 1 and 2 was possible. If analysis is for VC games tradeback have value NotTradeback for every gen 1 pokemon, but for cart saves some pokemon can be determine that have not been tradeback, if catch rate match species catch rate but do not match a valid generation 2 held item that means the pokemon habe been never traded to generation 2 games, that allow to discart encounters and moves from generation 2. Also if is not tradeback catch rate is used to filter encounters, catch rate determine in what species was captured the pokemon discarting some preevolutions moves Also add option for generation 1 cart save analysis to check legal status not allowing generation 2 games, like VC games but with Stadium allowed, like the generation 1 non tradeback rules from Smogon Also change evolution chains to included generation 2 preevolutions for gen 1 pokemon if tradeback was possible, it is needed to avoid parsemoves to check illegal pokemon like Hitmonchan with Tyrogue level up moves * Check legal values of generation 1 type and catch rate Replace pokemon catch rate after changind pokemon species always if pokemon was not tradeback from generation 2, the catch rate will keep unchanged only if it can be a held item and do not match species catch rate (default item) Also if catch rate is changed use base species catch rate to avoid legal errors if the catch rate of the evolution species if is not possible with the current moves * Filter ingame trades and static encounters with catch rate for generation 1 non tradeback * Fix min moves for generation 1 metapod encounter * Clean up * Fix encounter level for generation 1, valid moves are those with one level after the encounter level, pokemon can not learn a new move until level up Clean up type validation Fix generation 3 fatefull encounter eggs, the pokemon lost the fatefull mark when it hatch * Clean-up * Use new variable EncounterSpecies when it is needed to detect the species of the encounter, the old code wont work if the encounter is a wild slots array * Fix generation 1 evolution chains and catch rate as default held item * Fix Generation 1 Yellow Pikachu and Kadabra catch rates
2017-04-27 04:27:59 +00:00
return true;
2018-05-12 15:13:39 +00:00
// Trade generation 2 -> 1
if (Format == 1 && generation == 2 && !Gen1_NotTradeback)
Generation 1 and 2 legal Improvements (#1099) * Refactor parseMovesForEncounter to gather valid moves for species encounter, some Pokemon can have valid encounters with different source species from the encounter, the valid moves change if the encounter species change because some preevolutions moves are illegal if pokemon caught already evolved. Example, generation 1 Pikachu that can have a RBY Pikachu encounter and GSC Pichu encounter, the valid moves for the first encounters should not have any Pichu exclusive evolution moves Also assign the encounter match from gb when parsing moves like the variable Encounter Match, to store the encounter that is valid for the pokemon moves instead the first encounter. Store the species encounter, this will be needed to check if the evolution is valid for species that evolve leveling with a given learned move * Add Tradeback Status to the pokemon, this variable for generations 1 and 2 use data like the catch rate to determine if trade between generations 1 and 2 was possible. If analysis is for VC games tradeback have value NotTradeback for every gen 1 pokemon, but for cart saves some pokemon can be determine that have not been tradeback, if catch rate match species catch rate but do not match a valid generation 2 held item that means the pokemon habe been never traded to generation 2 games, that allow to discart encounters and moves from generation 2. Also if is not tradeback catch rate is used to filter encounters, catch rate determine in what species was captured the pokemon discarting some preevolutions moves Also add option for generation 1 cart save analysis to check legal status not allowing generation 2 games, like VC games but with Stadium allowed, like the generation 1 non tradeback rules from Smogon Also change evolution chains to included generation 2 preevolutions for gen 1 pokemon if tradeback was possible, it is needed to avoid parsemoves to check illegal pokemon like Hitmonchan with Tyrogue level up moves * Check legal values of generation 1 type and catch rate Replace pokemon catch rate after changind pokemon species always if pokemon was not tradeback from generation 2, the catch rate will keep unchanged only if it can be a held item and do not match species catch rate (default item) Also if catch rate is changed use base species catch rate to avoid legal errors if the catch rate of the evolution species if is not possible with the current moves * Filter ingame trades and static encounters with catch rate for generation 1 non tradeback * Fix min moves for generation 1 metapod encounter * Clean up * Fix encounter level for generation 1, valid moves are those with one level after the encounter level, pokemon can not learn a new move until level up Clean up type validation Fix generation 3 fatefull encounter eggs, the pokemon lost the fatefull mark when it hatch * Clean-up * Use new variable EncounterSpecies when it is needed to detect the species of the encounter, the old code wont work if the encounter is a wild slots array * Fix generation 1 evolution chains and catch rate as default held item * Fix Generation 1 Yellow Pikachu and Kadabra catch rates
2017-04-27 04:27:59 +00:00
return true;
if (Format < generation)
Generation 1 and 2 legal Improvements (#1099) * Refactor parseMovesForEncounter to gather valid moves for species encounter, some Pokemon can have valid encounters with different source species from the encounter, the valid moves change if the encounter species change because some preevolutions moves are illegal if pokemon caught already evolved. Example, generation 1 Pikachu that can have a RBY Pikachu encounter and GSC Pichu encounter, the valid moves for the first encounters should not have any Pichu exclusive evolution moves Also assign the encounter match from gb when parsing moves like the variable Encounter Match, to store the encounter that is valid for the pokemon moves instead the first encounter. Store the species encounter, this will be needed to check if the evolution is valid for species that evolve leveling with a given learned move * Add Tradeback Status to the pokemon, this variable for generations 1 and 2 use data like the catch rate to determine if trade between generations 1 and 2 was possible. If analysis is for VC games tradeback have value NotTradeback for every gen 1 pokemon, but for cart saves some pokemon can be determine that have not been tradeback, if catch rate match species catch rate but do not match a valid generation 2 held item that means the pokemon habe been never traded to generation 2 games, that allow to discart encounters and moves from generation 2. Also if is not tradeback catch rate is used to filter encounters, catch rate determine in what species was captured the pokemon discarting some preevolutions moves Also add option for generation 1 cart save analysis to check legal status not allowing generation 2 games, like VC games but with Stadium allowed, like the generation 1 non tradeback rules from Smogon Also change evolution chains to included generation 2 preevolutions for gen 1 pokemon if tradeback was possible, it is needed to avoid parsemoves to check illegal pokemon like Hitmonchan with Tyrogue level up moves * Check legal values of generation 1 type and catch rate Replace pokemon catch rate after changind pokemon species always if pokemon was not tradeback from generation 2, the catch rate will keep unchanged only if it can be a held item and do not match species catch rate (default item) Also if catch rate is changed use base species catch rate to avoid legal errors if the catch rate of the evolution species if is not possible with the current moves * Filter ingame trades and static encounters with catch rate for generation 1 non tradeback * Fix min moves for generation 1 metapod encounter * Clean up * Fix encounter level for generation 1, valid moves are those with one level after the encounter level, pokemon can not learn a new move until level up Clean up type validation Fix generation 3 fatefull encounter eggs, the pokemon lost the fatefull mark when it hatch * Clean-up * Use new variable EncounterSpecies when it is needed to detect the species of the encounter, the old code wont work if the encounter is a wild slots array * Fix generation 1 evolution chains and catch rate as default held item * Fix Generation 1 Yellow Pikachu and Kadabra catch rates
2017-04-27 04:27:59 +00:00
return false; // Future
int gen = GenNumber;
return generation switch
{
1 => (Format == 1 || VC), // species compat checked via sanity above
2 => (Format == 2 || VC),
3 => Gen3,
4 => (3 <= gen && gen <= 4),
5 => (3 <= gen && gen <= 5),
6 => (3 <= gen && gen <= 6),
7 => ((3 <= gen && gen <= 7) || VC),
8 => ((3 <= gen && gen <= 8) || VC),
_ => false
};
}
/// <summary>
/// Checks if the PKM has its original met location.
/// </summary>
/// <returns>Returns false if the Met Location has been overwritten via generational transfer.</returns>
2018-09-15 05:37:47 +00:00
public virtual bool HasOriginalMetLocation => !(Format < 3 || VC || (GenNumber <= 4 && Format != GenNumber));
/// <summary>
/// Checks if the current <see cref="Gender"/> is valid.
/// </summary>
/// <returns>True if valid, False if invalid.</returns>
public virtual bool IsGenderValid()
{
int gender = Gender;
int gv = PersonalInfo.Gender;
if (gv == 255)
return gender == 2;
if (gv == 254)
return gender == 1;
if (gv == 0)
return gender == 0;
int gen = GenNumber;
if (gen <= 2 || gen >= 6)
return gender == (gender & 1);
return gender == PKX.GetGenderFromPIDAndRatio(PID, gv);
}
/// <summary>
/// Updates the checksum of the <see cref="PKM"/>.
/// </summary>
public virtual void RefreshChecksum() => Checksum = CalculateChecksum();
/// <summary>
/// Reorders moves and fixes PP if necessary.
/// </summary>
public void FixMoves()
{
ReorderMoves();
if (Move1 == 0) Move1_PP = Move1_PPUps = 0;
if (Move2 == 0) Move2_PP = Move2_PPUps = 0;
if (Move3 == 0) Move3_PP = Move3_PPUps = 0;
if (Move4 == 0) Move4_PP = Move4_PPUps = 0;
}
/// <summary>
/// Reorders moves to put Empty entries last.
/// </summary>
private void ReorderMoves()
{
if (Move1 == 0 && Move2 != 0)
{
Move1 = Move2;
Move1_PP = Move2_PP;
Move1_PPUps = Move2_PPUps;
Move2 = 0;
}
if (Move2 == 0 && Move3 != 0)
{
Move2 = Move3;
Move2_PP = Move3_PP;
Move2_PPUps = Move3_PPUps;
Move3 = 0;
}
if (Move3 == 0 && Move4 != 0)
{
Move3 = Move4;
Move3_PP = Move4_PP;
Move3_PPUps = Move4_PPUps;
Move4 = 0;
}
}
/// <summary>
/// Applies the desired Ability option.
/// </summary>
/// <param name="n">Ability Number (0/1/2)</param>
public void RefreshAbility(int n)
{
AbilityNumber = 1 << n;
var abilities = PersonalInfo.Abilities;
if ((uint)n < abilities.Count)
Ability = abilities[n];
if (this is PK5 pk5)
pk5.HiddenAbility = n == 2;
}
/// <summary>
/// Gets the IV Judge Rating value.
/// </summary>
/// <remarks>IV Judge scales his response 0 (worst) to 3 (best).</remarks>
public int PotentialRating
{
get
{
int ivTotal = IVTotal;
if (ivTotal <= 90)
return 0;
if (ivTotal <= 120)
return 1;
return ivTotal <= 150 ? 2 : 3;
}
}
/// <summary>
/// Gets the current Battle Stats.
/// </summary>
/// <param name="p"><see cref="PersonalInfo"/> entry containing Base Stat Info</param>
/// <returns>Battle Stats (H/A/B/S/C/D)</returns>
public virtual ushort[] GetStats(PersonalInfo p)
{
int level = CurrentLevel;
ushort[] stats = this is IHyperTrain t ? GetStats(p, t, level) : GetStats(p, level);
// Account for nature
2019-11-16 01:34:18 +00:00
PKX.ModifyStatsForNature(stats, StatNature);
return stats;
}
2018-09-15 05:37:47 +00:00
private ushort[] GetStats(PersonalInfo p, IHyperTrain t, int level)
{
ushort[] stats = new ushort[6];
stats[0] = (ushort)(p.HP == 1 ? 1 : (((t.HT_HP ? 31 : IV_HP) + (2 * p.HP) + (EV_HP / 4) + 100) * level / 100) + 10);
stats[1] = (ushort)((((t.HT_ATK ? 31 : IV_ATK) + (2 * p.ATK) + (EV_ATK / 4)) * level / 100) + 5);
stats[2] = (ushort)((((t.HT_DEF ? 31 : IV_DEF) + (2 * p.DEF) + (EV_DEF / 4)) * level / 100) + 5);
stats[4] = (ushort)((((t.HT_SPA ? 31 : IV_SPA) + (2 * p.SPA) + (EV_SPA / 4)) * level / 100) + 5);
stats[5] = (ushort)((((t.HT_SPD ? 31 : IV_SPD) + (2 * p.SPD) + (EV_SPD / 4)) * level / 100) + 5);
stats[3] = (ushort)((((t.HT_SPE ? 31 : IV_SPE) + (2 * p.SPE) + (EV_SPE / 4)) * level / 100) + 5);
return stats;
}
2018-09-15 05:37:47 +00:00
private ushort[] GetStats(PersonalInfo p, int level)
{
ushort[] stats = new ushort[6];
stats[0] = (ushort)(p.HP == 1 ? 1 : ((IV_HP + (2 * p.HP) + (EV_HP / 4) + 100) * level / 100) + 10);
stats[1] = (ushort)(((IV_ATK + (2 * p.ATK) + (EV_ATK / 4)) * level / 100) + 5);
stats[2] = (ushort)(((IV_DEF + (2 * p.DEF) + (EV_DEF / 4)) * level / 100) + 5);
stats[4] = (ushort)(((IV_SPA + (2 * p.SPA) + (EV_SPA / 4)) * level / 100) + 5);
stats[5] = (ushort)(((IV_SPD + (2 * p.SPD) + (EV_SPD / 4)) * level / 100) + 5);
stats[3] = (ushort)(((IV_SPE + (2 * p.SPE) + (EV_SPE / 4)) * level / 100) + 5);
return stats;
}
/// <summary>
/// Applies the specified stats to the <see cref="PKM"/>.
/// </summary>
/// <param name="stats">Battle Stats (H/A/B/S/C/D)</param>
public void SetStats(ushort[] stats)
{
Stat_HPMax = Stat_HPCurrent = stats[0];
Stat_ATK = stats[1];
Stat_DEF = stats[2];
Stat_SPE = stats[3];
Stat_SPA = stats[4];
Stat_SPD = stats[5];
}
/// <summary>
/// Indicates if Party Stats are present. False if not initialized (from stored format).
/// </summary>
public bool PartyStatsPresent => Stat_HPMax != 0;
/// <summary>
/// Clears any status condition and refreshes the stats.
/// </summary>
public void ResetPartyStats()
{
SetStats(GetStats(PersonalInfo));
Stat_Level = CurrentLevel;
Status_Condition = 0;
}
public void Heal()
{
ResetPartyStats();
2019-02-21 06:08:28 +00:00
HealPP();
}
/// <summary>
/// Restores PP to maximum based on the current PP Ups for each move.
/// </summary>
public void HealPP()
{
Move1_PP = GetMovePP(Move1, Move1_PPUps);
Move2_PP = GetMovePP(Move2, Move2_PPUps);
Move3_PP = GetMovePP(Move3, Move3_PPUps);
Move4_PP = GetMovePP(Move4, Move4_PPUps);
}
/// <summary>
/// Enforces that Party Stat values are present.
/// </summary>
/// <returns>True if stats were refreshed, false if stats were already present.</returns>
public bool ForcePartyData()
{
if (PartyStatsPresent)
return false;
ResetPartyStats();
return true;
}
/// <summary>
/// Checks if the <see cref="PKM"/> can hold its <see cref="HeldItem"/>.
/// </summary>
/// <param name="valid">Items that the <see cref="PKM"/> can hold.</param>
/// <returns>True/False if the <see cref="PKM"/> can hold its <see cref="HeldItem"/>.</returns>
public virtual bool CanHoldItem(IReadOnlyList<ushort> valid) => valid.Contains((ushort)HeldItem);
/// <summary>
/// Deep clones the <see cref="PKM"/> object. The clone will not have any shared resources with the source.
/// </summary>
/// <returns>Cloned <see cref="PKM"/> object</returns>
public abstract PKM Clone();
/// <summary>
/// Sets Link Trade data for an <see cref="IsEgg"/>.
/// </summary>
/// <param name="day">Day the <see cref="PKM"/> was traded.</param>
/// <param name="month">Month the <see cref="PKM"/> was traded.</param>
/// <param name="y">Day the <see cref="PKM"/> was traded.</param>
/// <param name="location">Link Trade location value.</param>
protected void SetLinkTradeEgg(int day, int month, int y, int location)
{
Met_Day = day;
Met_Month = month;
Met_Year = y - 2000;
Met_Location = location;
}
/// <summary>
/// Gets the PP of a Move ID with consideration of the amount of PP Ups applied.
/// </summary>
/// <param name="move">Move ID</param>
/// <param name="ppUpCount">PP Ups count</param>
/// <returns>Current PP for the move.</returns>
public virtual int GetMovePP(int move, int ppUpCount) => GetBasePP(move) * (5 + ppUpCount) / 5;
/// <summary>
/// Gets the base PP of a move ID depending on the <see cref="PKM"/>'s format.
/// </summary>
/// <param name="move">Move ID</param>
/// <returns>Amount of PP the move has by default (no PP Ups).</returns>
private int GetBasePP(int move)
{
var table = Legal.GetPPTable(this, Format);
if (move >= table.Count)
move = 0;
return table[move];
}
2016-07-29 05:33:16 +00:00
/// <summary>
/// Applies a shiny <see cref="PID"/> to the <see cref="PKM"/>.
/// </summary>
/// <remarks>
/// If a <see cref="PKM"/> originated in a generation prior to Generation 6, the <see cref="EncryptionConstant"/> is updated.
/// If a <see cref="PKM"/> is in the <see cref="GBPKM"/> format, it will update the <see cref="IVs"/> instead.
/// </remarks>
public virtual void SetShiny()
2016-07-29 05:33:16 +00:00
{
var rnd = Util.Rand;
do { PID = PKX.GetRandomPID(rnd, Species, Gender, Version, Nature, AltForm, PID); }
while (!IsShiny);
if (Format >= 6 && (Gen3 || Gen4 || Gen5))
EncryptionConstant = PID;
2016-07-29 05:33:16 +00:00
}
2018-09-15 05:37:47 +00:00
/// <summary>
/// Applies a shiny <see cref="SID"/> to the <see cref="PKM"/>.
/// </summary>
public void SetShinySID(Shiny shiny = Shiny.Random)
{
if (IsShiny && shiny.IsValid(this))
return;
var xor = TID ^ (PID >> 16) ^ (PID & 0xFFFF);
var bits = shiny switch
{
Shiny.AlwaysSquare => 0,
Shiny.AlwaysStar => 1,
_ => Util.Rand.Next(8)
};
SID = (int)xor ^ bits;
}
2018-09-15 05:37:47 +00:00
/// <summary>
/// Applies a <see cref="PID"/> to the <see cref="PKM"/> according to the specified <see cref="Gender"/>.
/// </summary>
/// <param name="gender"><see cref="Gender"/> to apply</param>
/// <remarks>
/// If a <see cref="PKM"/> originated in a generation prior to Generation 6, the <see cref="EncryptionConstant"/> is updated.
/// </remarks>
public void SetPIDGender(int gender)
2016-07-29 05:54:29 +00:00
{
var rnd = Util.Rand;
do PID = PKX.GetRandomPID(rnd, Species, gender, Version, Nature, AltForm, PID);
while (IsShiny);
if (Format >= 6 && (Gen3 || Gen4 || Gen5))
EncryptionConstant = PID;
}
2018-09-15 05:37:47 +00:00
/// <summary>
/// Applies a <see cref="PID"/> to the <see cref="PKM"/> according to the specified <see cref="Gender"/>.
/// </summary>
/// <param name="nature"><see cref="Nature"/> to apply</param>
/// <remarks>
/// If a <see cref="PKM"/> originated in a generation prior to Generation 6, the <see cref="EncryptionConstant"/> is updated.
/// </remarks>
public void SetPIDNature(int nature)
{
var rnd = Util.Rand;
do PID = PKX.GetRandomPID(rnd, Species, Gender, Version, nature, AltForm, PID);
while (IsShiny);
if (Format >= 6 && (Gen3 || Gen4 || Gen5))
EncryptionConstant = PID;
2016-07-29 05:54:29 +00:00
}
2018-09-15 05:37:47 +00:00
/// <summary>
/// Applies a <see cref="PID"/> to the <see cref="PKM"/> according to the specified <see cref="AltForm"/>.
/// </summary>
/// <param name="form"><see cref="AltForm"/> to apply</param>
/// <remarks>
/// This method should only be used for Unown originating in Generation 3 games.
/// If a <see cref="PKM"/> originated in a generation prior to Generation 6, the <see cref="EncryptionConstant"/> is updated.
/// </remarks>
public void SetPIDUnown3(int form)
{
do PID = Util.Rand32(); while (PKX.GetUnownForm(PID) != form);
if (Format >= 6 && (Gen3 || Gen4 || Gen5))
EncryptionConstant = PID;
}
/// <summary>
/// Randomizes the IVs within game constraints.
/// </summary>
/// <param name="flawless">Count of flawless IVs to set. If none provided, a count will be detected.</param>
/// <returns>Randomized IVs if desired.</returns>
public int[] SetRandomIVs(int? flawless = null)
{
if (Version == (int)GameVersion.GO && flawless != 6)
return SetRandomIVsGO();
int[] ivs = new int[6];
var rnd = Util.Rand;
for (int i = 0; i < 6; i++)
ivs[i] = rnd.Next(MaxIV + 1);
int count = flawless ?? GetFlawlessIVCount();
if (count != 0)
{
for (int i = 0; i < count; i++)
ivs[i] = MaxIV;
Util.Shuffle(ivs); // Randomize IV order
}
return IVs = ivs;
}
public int[] SetRandomIVsGO(int minIV = 0)
{
int[] ivs = new int[6];
var rnd = Util.Rand;
ivs[0] = (rnd.Next(minIV, 16) << 1) | 1; // hp
ivs[1] = ivs[4] = (rnd.Next(minIV, 16) << 1) | 1; // attack
ivs[2] = ivs[5] = (rnd.Next(minIV, 16) << 1) | 1; // defense
ivs[3] = rnd.Next(MaxIV + 1); // speed
return IVs = ivs;
}
/// <summary>
/// Randomizes the IVs within game constraints.
/// </summary>
/// <param name="template">IV template to generate from</param>
/// <param name="flawless">Count of flawless IVs to set. If none provided, a count will be detected.</param>
/// <returns>Randomized IVs if desired.</returns>
public int[] SetRandomIVs(IReadOnlyList<int> template, int? flawless = null)
{
int count = flawless ?? GetFlawlessIVCount();
int[] ivs = new int[6];
var rnd = Util.Rand;
do
{
for (int i = 0; i < 6; i++)
ivs[i] = template[i] < 0 ? rnd.Next(MaxIV + 1) : template[i];
} while (ivs.Count(z => z == MaxIV) < count);
IVs = ivs;
return ivs;
}
/// <summary>
/// Gets the amount of flawless IVs that the <see cref="PKM"/> should have.
/// </summary>
/// <returns>Count of IVs that should be max.</returns>
public int GetFlawlessIVCount()
{
if (GenNumber >= 6 && (Legal.Legends.Contains(Species) || Legal.SubLegends.Contains(Species)))
return 3;
if (XY)
{
if (PersonalInfo.EggGroup1 == 15) // Undiscovered
return 3;
if (Met_Location == 148 && Met_Level == 30) // Friend Safari
return 2;
}
if (VC)
return Species == (int)Core.Species.Mew || Species == (int)Core.Species.Celebi ? 5 : 3;
return 0;
}
/// <summary>
/// Applies all shared properties from the current <see cref="PKM"/> to <see cref="Destination"/> <see cref="PKM"/>.
/// </summary>
/// <param name="Destination"><see cref="PKM"/> that receives property values.</param>
public void TransferPropertiesWithReflection(PKM Destination)
{
// Only transfer declared properties not defined in PKM.cs but in the actual type
var srcType = GetType();
var destType = Destination.GetType();
var srcProperties = ReflectUtil.GetAllPropertyInfoPublic(srcType).Select(z => z.Name);
var destProperties = ReflectUtil.GetAllPropertyInfoPublic(destType).Where(z => z.SetMethod != null).Select(z => z.Name);
// Transfer properties in the order they are defined in the destination PKM format for best conversion
var shared = destProperties.Intersect(srcProperties);
foreach (string property in shared)
{
BatchEditing.TryGetHasProperty(this, property, out var src);
var prop = src.GetValue(this);
if (prop != null && !(prop is byte[]) && BatchEditing.TryGetHasProperty(Destination, property, out var pi))
ReflectUtil.SetValue(pi, Destination, prop);
}
}
2020-08-18 22:39:45 +00:00
/// <summary>
/// Checks if the <see cref="PKM"/> has the <see cref="move"/> in its current move list.
/// </summary>
public bool HasMove(int move) => Move1 == move || Move2 == move || Move3 == move || Move4 == move;
public int GetMoveIndex(int move) => Move1 == move ? 0 : Move2 == move ? 1 : Move3 == move ? 2 : Move4 == move ? 3 : -1;
public int GetMove(int index)
{
return index switch
{
0 => Move1,
1 => Move2,
2 => Move3,
3 => Move4,
_ => throw new IndexOutOfRangeException(nameof(index)),
};
}
public void SetMove(int index, int value)
{
switch (index)
{
case 0:
Move1 = value;
return;
case 1:
Move2 = value;
return;
case 2:
Move3 = value;
return;
case 3:
Move4 = value;
return;
default:
throw new IndexOutOfRangeException(nameof(index));
}
}
/// <summary>
/// Clears moves that a <see cref="PKM"/> may have, possibly from a future generation.
/// </summary>
public void ClearInvalidMoves()
{
uint invalid = 0;
var moves = Moves;
for (var i = 0; i < moves.Length; i++)
{
if (moves[i] <= MaxMoveID)
continue;
invalid++;
moves[i] = 0;
}
if (invalid == 0)
return;
if (invalid == 4) // no moves remain
{
moves[0] = 1; // Pound
Move1_PP = GetMovePP(1, Move1_PPUps);
}
Moves = moves;
FixMoves();
}
/// <summary>
/// Gets one of the <see cref="EVs"/> based on its index within the array.
/// </summary>
/// <param name="index">Index to get</param>
public int GetEV(int index)
{
return index switch
{
0 => EV_HP,
1 => EV_ATK,
2 => EV_DEF,
3 => EV_SPE,
4 => EV_SPA,
5 => EV_SPD,
_ => throw new ArgumentOutOfRangeException(nameof(index))
};
}
/// <summary>
/// Gets one of the <see cref="IVs"/> based on its index within the array.
/// </summary>
/// <param name="index">Index to get</param>
public int GetIV(int index)
{
return index switch
{
0 => IV_HP,
1 => IV_ATK,
2 => IV_DEF,
3 => IV_SPE,
4 => IV_SPA,
5 => IV_SPD,
_ => throw new ArgumentOutOfRangeException(nameof(index))
};
}
}
}