PKHeX/PKHeX.Core/PKM/PB7.cs
Kurt 02420d3e93
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-16 18:47:31 -07:00

629 lines
No EOL
30 KiB
C#

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace PKHeX.Core
{
/// <summary> Generation 7 <see cref="PKM"/> format used for <see cref="GameVersion.GG"/>. </summary>
public sealed class PB7 : _K6, IHyperTrain, IAwakened
{
public static readonly byte[] Unused =
{
0x2A, // Old Marking Value (PelagoEventStatus)
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, // Unused Ribbons
0x58, 0x59, // Nickname Terminator
0x73,
0x90, 0x91, // HT Terminator
0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, // Old Geolocation/memories
0xA7, 0xAA, 0xAB,
0xAC, 0xAD, // Fatigue, no GUI editing
0xC8, 0xC9, // OT Terminator
};
public override IReadOnlyList<byte> ExtraBytes => Unused;
public override int SIZE_PARTY => SIZE;
public override int SIZE_STORED => SIZE;
private const int SIZE = 260;
public override int Format => 7;
public override PersonalInfo PersonalInfo => PersonalTable.GG.GetFormeEntry(Species, AltForm);
public override byte[] Data { get; }
public PB7() => Data = new byte[SIZE];
public PB7(byte[] data)
{
PKX.CheckEncrypted(ref data, 7);
if (data.Length != SIZE)
Array.Resize(ref data, SIZE);
Data = data;
}
public override PKM Clone() => new PB7((byte[])Data.Clone()){Identifier = Identifier};
private string GetString(int Offset, int Count) => StringConverter.GetString7(Data, Offset, Count);
private byte[] SetString(string value, int maxLength, bool chinese = false) => StringConverter.SetString7b(value, maxLength, Language, chinese: chinese);
// Structure
#region Block A
public override uint EncryptionConstant
{
get => BitConverter.ToUInt32(Data, 0x00);
set => BitConverter.GetBytes(value).CopyTo(Data, 0x00);
}
public override ushort Sanity
{
get => BitConverter.ToUInt16(Data, 0x04);
set => BitConverter.GetBytes(value).CopyTo(Data, 0x04);
}
public override ushort Checksum
{
get => BitConverter.ToUInt16(Data, 0x06);
set => BitConverter.GetBytes(value).CopyTo(Data, 0x06);
}
public override int Species
{
get => BitConverter.ToUInt16(Data, 0x08);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x08);
}
public override int HeldItem
{
get => BitConverter.ToUInt16(Data, 0x0A);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0A);
}
public override int TID
{
get => BitConverter.ToUInt16(Data, 0x0C);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0C);
}
public override int SID
{
get => BitConverter.ToUInt16(Data, 0x0E);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0E);
}
public override uint EXP
{
get => BitConverter.ToUInt32(Data, 0x10);
set => BitConverter.GetBytes(value).CopyTo(Data, 0x10);
}
public override int Ability { get => Data[0x14]; set => Data[0x14] = (byte)value; }
public override int AbilityNumber { get => Data[0x15] & 7; set => Data[0x15] = (byte)((Data[0x15] & ~7) | (value & 7)); }
public bool Favorite { get => (Data[0x15] & 8) != 0; set => Data[0x15] = (byte)((Data[0x15] & ~8) | ((value ? 1 : 0) << 3)); }
public override int MarkValue { get => BitConverter.ToUInt16(Data, 0x16); protected set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x16); }
public override uint PID
{
get => BitConverter.ToUInt32(Data, 0x18);
set => BitConverter.GetBytes(value).CopyTo(Data, 0x18);
}
public override int Nature { get => Data[0x1C]; set => Data[0x1C] = (byte)value; }
public override bool FatefulEncounter { get => (Data[0x1D] & 1) == 1; set => Data[0x1D] = (byte)((Data[0x1D] & ~0x01) | (value ? 1 : 0)); }
public override int Gender { get => (Data[0x1D] >> 1) & 0x3; set => Data[0x1D] = (byte)((Data[0x1D] & ~0x06) | (value << 1)); }
public override int AltForm { get => Data[0x1D] >> 3; set => Data[0x1D] = (byte)((Data[0x1D] & 0x07) | (value << 3)); }
public override int EV_HP { get => Data[0x1E]; set => Data[0x1E] = (byte)value; }
public override int EV_ATK { get => Data[0x1F]; set => Data[0x1F] = (byte)value; }
public override int EV_DEF { get => Data[0x20]; set => Data[0x20] = (byte)value; }
public override int EV_SPE { get => Data[0x21]; set => Data[0x21] = (byte)value; }
public override int EV_SPA { get => Data[0x22]; set => Data[0x22] = (byte)value; }
public override int EV_SPD { get => Data[0x23]; set => Data[0x23] = (byte)value; }
public int AV_HP { get => Data[0x24]; set => Data[0x24] = (byte)value; }
public int AV_ATK { get => Data[0x25]; set => Data[0x25] = (byte)value; }
public int AV_DEF { get => Data[0x26]; set => Data[0x26] = (byte)value; }
public int AV_SPE { get => Data[0x27]; set => Data[0x27] = (byte)value; }
public int AV_SPA { get => Data[0x28]; set => Data[0x28] = (byte)value; }
public int AV_SPD { get => Data[0x29]; set => Data[0x29] = (byte)value; }
public byte _0x2A { get => Data[0x2A]; set => Data[0x2A] = value; }
private byte PKRS { get => Data[0x2B]; set => Data[0x2B] = value; }
public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); }
public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | value << 4); }
public float HeightAbsolute { get => BitConverter.ToSingle(Data, 0x2C); set => BitConverter.GetBytes(value).CopyTo(Data, 0x2C); }
public byte _0x38 { get => Data[0x38]; set => Data[0x38] = value; }
public byte _0x39 { get => Data[0x39]; set => Data[0x39] = value; }
public int HeightScalar { get => Data[0x3A]; set => Data[0x3A] = (byte)value; }
public int WeightScalar { get => Data[0x3B]; set => Data[0x3B] = (byte)value; }
public uint FormDuration { get => BitConverter.ToUInt32(Data, 0x3C); set => BitConverter.GetBytes(value).CopyTo(Data, 0x3C); }
#endregion
#region Block B
public override string Nickname
{
get => GetString(0x40, 24);
set => SetString(value, 12).CopyTo(Data, 0x40);
}
public override int Move1
{
get => BitConverter.ToUInt16(Data, 0x5A);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x5A);
}
public override int Move2
{
get => BitConverter.ToUInt16(Data, 0x5C);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x5C);
}
public override int Move3
{
get => BitConverter.ToUInt16(Data, 0x5E);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x5E);
}
public override int Move4
{
get => BitConverter.ToUInt16(Data, 0x60);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x60);
}
public override int Move1_PP { get => Data[0x62]; set => Data[0x62] = (byte)value; }
public override int Move2_PP { get => Data[0x63]; set => Data[0x63] = (byte)value; }
public override int Move3_PP { get => Data[0x64]; set => Data[0x64] = (byte)value; }
public override int Move4_PP { get => Data[0x65]; set => Data[0x65] = (byte)value; }
public override int Move1_PPUps { get => Data[0x66]; set => Data[0x66] = (byte)value; }
public override int Move2_PPUps { get => Data[0x67]; set => Data[0x67] = (byte)value; }
public override int Move3_PPUps { get => Data[0x68]; set => Data[0x68] = (byte)value; }
public override int Move4_PPUps { get => Data[0x69]; set => Data[0x69] = (byte)value; }
public override int RelearnMove1
{
get => BitConverter.ToUInt16(Data, 0x6A);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x6A);
}
public override int RelearnMove2
{
get => BitConverter.ToUInt16(Data, 0x6C);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x6C);
}
public override int RelearnMove3
{
get => BitConverter.ToUInt16(Data, 0x6E);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x6E);
}
public override int RelearnMove4
{
get => BitConverter.ToUInt16(Data, 0x70);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x70);
}
public byte _0x72 { get => Data[0x72]; set => Data[0x72] = value; }
public byte _0x73 { get => Data[0x73]; set => Data[0x73] = value; }
private uint IV32 { get => BitConverter.ToUInt32(Data, 0x74); set => BitConverter.GetBytes(value).CopyTo(Data, 0x74); }
public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); }
public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); }
public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); }
public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); }
public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); }
public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); }
public override bool IsEgg { get => ((IV32 >> 30) & 1) == 1; set => IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0u); }
public override bool IsNicknamed { get => ((IV32 >> 31) & 1) == 1; set => IV32 = (IV32 & 0x7FFFFFFFu) | (value ? 0x80000000u : 0u); }
#endregion
#region Block C
public override string HT_Name { get => GetString(0x78, 24); set => SetString(value, 12).CopyTo(Data, 0x78); }
public override int HT_Gender { get => Data[0x92]; set => Data[0x92] = (byte)value; }
public override int CurrentHandler { get => Data[0x93]; set => Data[0x93] = (byte)value; }
public byte _0x94 { get => Data[0x94]; set => Data[0x94] = value; }
public byte _0x95 { get => Data[0x95]; set => Data[0x95] = value; }
public byte _0x96 { get => Data[0x96]; set => Data[0x96] = value; }
public byte _0x97 { get => Data[0x97]; set => Data[0x97] = value; }
public byte _0x98 { get => Data[0x98]; set => Data[0x98] = value; }
public byte _0x99 { get => Data[0x99]; set => Data[0x99] = value; }
public byte _0x9A { get => Data[0x9A]; set => Data[0x9A] = value; }
public byte _0x9B { get => Data[0x9B]; set => Data[0x9B] = value; }
public byte _0x9C { get => Data[0x9C]; set => Data[0x9C] = value; }
public byte _0x9D { get => Data[0x9D]; set => Data[0x9D] = value; }
public byte _0x9E { get => Data[0x9E]; set => Data[0x9E] = value; }
public byte _0x9F { get => Data[0x9F]; set => Data[0x9F] = value; }
public byte _0xA0 { get => Data[0xA0]; set => Data[0xA0] = value; }
public byte _0xA1 { get => Data[0xA1]; set => Data[0xA1] = value; }
public override int HT_Friendship { get => Data[0xA2]; set => Data[0xA2] = (byte)value; }
public override int HT_Affection { get => Data[0xA3]; set => Data[0xA3] = (byte)value; }
public override int HT_Intensity { get => Data[0xA4]; set => Data[0xA4] = (byte)value; }
public override int HT_Memory { get => Data[0xA5]; set => Data[0xA5] = (byte)value; }
public override int HT_Feeling { get => Data[0xA6]; set => Data[0xA6] = (byte)value; }
public byte _0xA7 { get => Data[0xA7]; set => Data[0xA7] = value; }
public override int HT_TextVar { get => BitConverter.ToUInt16(Data, 0xA8); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0xA8); }
public byte _0xAA { get => Data[0xAA]; set => Data[0xAA] = value; }
public byte _0xAB { get => Data[0xAB]; set => Data[0xAB] = value; }
public byte FieldEventFatigue1 { get => Data[0xAC]; set => Data[0xAC] = value; }
public byte FieldEventFatigue2 { get => Data[0xAD]; set => Data[0xAD] = value; }
public override byte Fullness { get => Data[0xAE]; set => Data[0xAE] = value; }
public override byte Enjoyment { get => Data[0xAF]; set => Data[0xAF] = value; }
#endregion
#region Block D
public override string OT_Name { get => GetString(0xB0, 24); set => SetString(value, 12).CopyTo(Data, 0xB0); }
public override int OT_Friendship { get => Data[0xCA]; set => Data[0xCA] = (byte)value; }
public int _0xCB { get => Data[0xCB]; set => Data[0xCB] = (byte)value; }
public int _0xCC { get => Data[0xCC]; set => Data[0xCC] = (byte)value; }
public int _0xCD { get => Data[0xCD]; set => Data[0xCD] = (byte)value; }
public int _0xCE { get => Data[0xCE]; set => Data[0xCE] = (byte)value; }
public int _0xCF { get => Data[0xCF]; set => Data[0xCF] = (byte)value; }
public int _0xD0 { get => Data[0xD0]; set => Data[0xD0] = (byte)value; }
public override int Egg_Year { get => Data[0xD1]; set => Data[0xD1] = (byte)value; }
public override int Egg_Month { get => Data[0xD2]; set => Data[0xD2] = (byte)value; }
public override int Egg_Day { get => Data[0xD3]; set => Data[0xD3] = (byte)value; }
public override int Met_Year { get => Data[0xD4]; set => Data[0xD4] = (byte)value; }
public override int Met_Month { get => Data[0xD5]; set => Data[0xD5] = (byte)value; }
public override int Met_Day { get => Data[0xD6]; set => Data[0xD6] = (byte)value; }
public int Rank { get => Data[0xD7]; set => Data[0xD7] = (byte)value; } // unused but fetched for stat calcs, and set for trpoke data?
public override int Egg_Location { get => BitConverter.ToUInt16(Data, 0xD8); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0xD8); }
public override int Met_Location { get => BitConverter.ToUInt16(Data, 0xDA); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0xDA); }
public override int Ball { get => Data[0xDC]; set => Data[0xDC] = (byte)value; }
public override int Met_Level { get => Data[0xDD] & ~0x80; set => Data[0xDD] = (byte)((Data[0xDD] & 0x80) | value); }
public override int OT_Gender { get => Data[0xDD] >> 7; set => Data[0xDD] = (byte)((Data[0xDD] & ~0x80) | (value << 7)); }
public int HyperTrainFlags { get => Data[0xDE]; set => Data[0xDE] = (byte)value; }
public bool HT_HP { get => ((HyperTrainFlags >> 0) & 1) == 1; set => HyperTrainFlags = (HyperTrainFlags & ~(1 << 0)) | ((value ? 1 : 0) << 0); }
public bool HT_ATK { get => ((HyperTrainFlags >> 1) & 1) == 1; set => HyperTrainFlags = (HyperTrainFlags & ~(1 << 1)) | ((value ? 1 : 0) << 1); }
public bool HT_DEF { get => ((HyperTrainFlags >> 2) & 1) == 1; set => HyperTrainFlags = (HyperTrainFlags & ~(1 << 2)) | ((value ? 1 : 0) << 2); }
public bool HT_SPA { get => ((HyperTrainFlags >> 3) & 1) == 1; set => HyperTrainFlags = (HyperTrainFlags & ~(1 << 3)) | ((value ? 1 : 0) << 3); }
public bool HT_SPD { get => ((HyperTrainFlags >> 4) & 1) == 1; set => HyperTrainFlags = (HyperTrainFlags & ~(1 << 4)) | ((value ? 1 : 0) << 4); }
public bool HT_SPE { get => ((HyperTrainFlags >> 5) & 1) == 1; set => HyperTrainFlags = (HyperTrainFlags & ~(1 << 5)) | ((value ? 1 : 0) << 5); }
public override int Version { get => Data[0xDF]; set => Data[0xDF] = (byte)value; }
public int _0xE0 { get => Data[0xE0]; set => Data[0xE0] = (byte)value; }
public int _0xE1 { get => Data[0xE1]; set => Data[0xE1] = (byte)value; }
public int _0xE2 { get => Data[0xE2]; set => Data[0xE2] = (byte)value; }
public override int Language { get => Data[0xE3]; set => Data[0xE3] = (byte)value; }
public float WeightAbsolute { get => BitConverter.ToSingle(Data, 0xE4); set => BitConverter.GetBytes(value).CopyTo(Data, 0xE4); }
#endregion
#region Battle Stats
public override int Status_Condition { get => BitConverter.ToInt32(Data, 0xE8); set => BitConverter.GetBytes(value).CopyTo(Data, 0xE8); }
public override int Stat_Level { get => Data[0xEC]; set => Data[0xEC] = (byte)value; }
public byte DirtType { get => Data[0xED]; set => Data[0xED] = value; }
public byte DirtLocation { get => Data[0xEE]; set => Data[0xEE] = value; }
// 0xEF unused
public override int Stat_HPCurrent { get => BitConverter.ToUInt16(Data, 0xF0); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0xF0); }
public override int Stat_HPMax { get => BitConverter.ToUInt16(Data, 0xF2); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0xF2); }
public override int Stat_ATK { get => BitConverter.ToUInt16(Data, 0xF4); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0xF4); }
public override int Stat_DEF { get => BitConverter.ToUInt16(Data, 0xF6); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0xF6); }
public override int Stat_SPE { get => BitConverter.ToUInt16(Data, 0xF8); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0xF8); }
public override int Stat_SPA { get => BitConverter.ToUInt16(Data, 0xFA); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0xFA); }
public override int Stat_SPD { get => BitConverter.ToUInt16(Data, 0xFC); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0xFC); }
public int Stat_CP { get => BitConverter.ToUInt16(Data, 0xFE); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0xFE); }
public bool Stat_Mega { get => Data[0x100] != 0; set => Data[0x100] = (byte)(value ? 1 : 0); }
public int Stat_MegaForm { get => Data[0x101]; set => Data[0x101] = (byte)value; }
// 102/103 unused
#endregion
public override int[] Markings
{
get
{
int[] marks = new int[8];
int val = MarkValue;
for (int i = 0; i < marks.Length; i++)
marks[i] = ((val >> (i * 2)) & 3) % 3;
return marks;
}
set
{
if (value.Length > 8)
return;
int v = 0;
for (int i = 0; i < value.Length; i++)
v |= (value[i] % 3) << (i * 2);
MarkValue = v;
}
}
protected override bool TradeOT(ITrainerInfo tr)
{
// Check to see if the OT matches the SAV's OT info.
if (!(tr.OT == OT_Name && tr.TID == TID && tr.SID == SID && tr.Gender == OT_Gender))
return false;
CurrentHandler = 0;
return true;
}
protected override void TradeHT(ITrainerInfo tr)
{
if (HT_Name != tr.OT)
{
HT_Friendship = CurrentFriendship; // copy friendship instead of resetting (don't alter CP)
HT_Affection = 0;
HT_Name = tr.OT;
}
CurrentHandler = 1;
HT_Gender = tr.Gender;
}
public void FixMemories()
{
if (IsUntraded)
HT_Friendship = HT_Affection = HT_TextVar = HT_Memory = HT_Intensity = HT_Feeling = 0;
}
// Maximums
public override int MaxMoveID => Legal.MaxMoveID_7b;
public override int MaxSpeciesID => Legal.MaxSpeciesID_7b;
public override int MaxAbilityID => Legal.MaxAbilityID_7_USUM;
public override int MaxItemID => Legal.MaxItemID_7_USUM;
public override int MaxBallID => Legal.MaxBallID_7;
public override int MaxGameID => Legal.MaxGameID_7b;
public override ushort[] GetStats(PersonalInfo p) => CalculateStatsBeluga(p);
public ushort[] CalculateStatsBeluga(PersonalInfo p)
{
int level = CurrentLevel;
int nature = Nature;
int friend = CurrentFriendship; // stats +10% depending on friendship!
int scalar = (int)(((friend / 255.0f / 10.0f) + 1.0f) * 100.0f);
ushort[] stats =
{
(ushort)(AV_HP + GetStat(p.HP, HT_HP ? 31 : IV_HP, level) + 10 + level),
(ushort)(AV_ATK + (scalar * GetStat(p.ATK, HT_ATK ? 31 : IV_ATK, level, nature, 0) / 100)),
(ushort)(AV_DEF + (scalar * GetStat(p.DEF, HT_DEF ? 31 : IV_DEF, level, nature, 1) / 100)),
(ushort)(AV_SPE + (scalar * GetStat(p.SPE, HT_SPE ? 31 : IV_SPE, level, nature, 4) / 100)),
(ushort)(AV_SPA + (scalar * GetStat(p.SPA, HT_SPA ? 31 : IV_SPA, level, nature, 2) / 100)),
(ushort)(AV_SPD + (scalar * GetStat(p.SPD, HT_SPD ? 31 : IV_SPD, level, nature, 3) / 100)),
};
if (Species == 292)
stats[0] = 1;
return stats;
}
/// <summary>
/// Gets the initial stat value based on the base stat value, IV, and current level.
/// </summary>
/// <param name="baseStat"><see cref="PersonalInfo"/> stat.</param>
/// <param name="iv">Current IV, already accounted for Hyper Training</param>
/// <param name="level">Current Level</param>
/// <returns>Initial Stat</returns>
private static int GetStat(int baseStat, int iv, int level) => (iv + (2 * baseStat)) * level / 100;
/// <summary>
/// Gets the initial stat value with nature amplification applied. Used for all stats except HP.
/// </summary>
/// <param name="baseStat"><see cref="PersonalInfo"/> stat.</param>
/// <param name="iv">Current IV, already accounted for Hyper Training</param>
/// <param name="level">Current Level</param>
/// <param name="nature"><see cref="PKM.Nature"/></param>
/// <param name="statIndex">Stat amp index in the nature amp table</param>
/// <returns>Initial Stat with nature amplification applied.</returns>
private static int GetStat(int baseStat, int iv, int level, int nature, int statIndex)
{
int initial = GetStat(baseStat, iv, level) + 5;
return AmplifyStat(nature, statIndex, initial);
}
private static int AmplifyStat(int nature, int index, int initial)
{
return GetNatureAmp(nature, index) switch
{
1 => (110 * initial / 100), // 110%
-1 => (90 * initial / 100), // 90%
_ => initial
};
}
private static sbyte GetNatureAmp(int nature, int index)
{
if ((uint)nature >= 25)
return -1;
return NatureAmpTable[(5 * nature) + index];
}
private static readonly sbyte[] NatureAmpTable =
{
0, 0, 0, 0, 0, // Hardy
1,-1, 0, 0, 0, // Lonely
1, 0, 0, 0,-1, // Brave
1, 0,-1, 0, 0, // Adamant
1, 0, 0,-1, 0, // Naughty
-1, 1, 0, 0, 0, // Bold
0, 0, 0, 0, 0, // Docile
0, 1, 0, 0,-1, // Relaxed
0, 1,-1, 0, 0, // Impish
0, 1, 0,-1, 0, // Lax
-1, 0, 0, 0, 1, // Timid
0,-1, 0, 0, 1, // Hasty
0, 0, 0, 0, 0, // Serious
0, 0,-1, 0, 1, // Jolly
0, 0, 0,-1, 1, // Naive
-1, 0, 1, 0, 0, // Modest
0,-1, 1, 0, 0, // Mild
0, 0, 1, 0,-1, // Quiet
0, 0, 0, 0, 0, // Bashful
0, 0, 1,-1, 0, // Rash
-1, 0, 0, 1, 0, // Calm
0,-1, 0, 1, 0, // Gentle
0, 0, 0, 1,-1, // Sassy
0, 0,-1, 1, 0, // Careful
0, 0, 0, 0, 0, // Quirky
};
public int CalcCP => Math.Min(10000, AwakeCP + BaseCP);
public int BaseCP
{
get
{
var p = PersonalInfo;
int level = CurrentLevel;
int nature = Nature;
int scalar = CPScalar;
// Calculate stats for all, then sum together.
// HP is not overriden to 1 like a regular stat calc for Shedinja.
var statSum =
(ushort)GetStat(p.HP, HT_HP ? 31 : IV_HP, level) + 10 + level
+ (ushort)(GetStat(p.ATK, HT_ATK ? 31 : IV_ATK, level, nature, 0) * scalar / 100)
+ (ushort)(GetStat(p.DEF, HT_DEF ? 31 : IV_DEF, level, nature, 1) * scalar / 100)
+ (ushort)(GetStat(p.SPE, HT_SPE ? 31 : IV_SPE, level, nature, 4) * scalar / 100)
+ (ushort)(GetStat(p.SPA, HT_SPA ? 31 : IV_SPA, level, nature, 2) * scalar / 100)
+ (ushort)(GetStat(p.SPD, HT_SPD ? 31 : IV_SPD, level, nature, 3) * scalar / 100);
float result = statSum * 6f;
result *= level;
result /= 100f;
return (int)result;
}
}
public int CPScalar
{
get
{
int friend = CurrentFriendship; // stats +10% depending on friendship!
float scalar = friend / 255f;
scalar /= 10f;
scalar++;
scalar *= 100f;
return (int)scalar;
}
}
public int AwakeCP
{
get
{
var sum = this.AwakeningSum();
if (sum == 0)
return 0;
var lvl = CurrentLevel;
float scalar = lvl * 4f;
scalar /= 100f;
scalar += 2f;
float result = sum * scalar;
return (int)result;
}
}
public void ResetCP() => Stat_CP = CalcCP;
public void ResetCalculatedValues()
{
ResetCP();
ResetHeight();
ResetWeight();
}
// Casts are as per the game code; they may seem redundant but every bit of precision matters?
// This still doesn't precisely match :( -- just use a tolerance check when updating.
// If anyone can figure out how to get all precision to match, feel free to update :)
public float HeightRatio => GetHeightRatio(HeightScalar);
public float WeightRatio => GetWeightRatio(WeightScalar);
public float CalcHeightAbsolute => GetHeightAbsolute(PersonalInfo, HeightScalar);
public float CalcWeightAbsolute => GetWeightAbsolute(PersonalInfo, HeightScalar, WeightScalar);
public void ResetHeight()
{
var current = HeightAbsolute;
var updated = CalcHeightAbsolute;
if (Math.Abs(current - updated) > 0.0001f)
HeightAbsolute = updated;
}
public void ResetWeight()
{
var current = WeightAbsolute;
var updated = CalcWeightAbsolute;
if (Math.Abs(current - updated) > 0.0001f)
WeightAbsolute = updated;
}
public static int GetSizeRating(int scalar)
{
if (scalar < 0x10)
return 0; // 1/16 = XS
if (scalar < 0x30u)
return 1; // 2/16 = S
if (scalar < 0xD0u)
return 2; // average (10/16)
if (scalar < 0xF0u)
return 3; // 2/16 = L
return 4; // 1/16 = XL
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
private static float GetHeightRatio(int heightScalar)
{
// +/- 40%
float result = (byte)heightScalar / 255f;
result *= 0.8f;
result += 0.6f;
return result;
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
private static float GetWeightRatio(int weightScalar)
{
// +/- 20%
float result = (byte)weightScalar / 255f;
result *= 0.4f;
result += 0.8f;
return result;
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public static float GetHeightAbsolute(PersonalInfo p, int heightScalar)
{
float HeightRatio = GetHeightRatio(heightScalar);
return HeightRatio * p.Height;
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public static float GetWeightAbsolute(PersonalInfo p, int heightScalar, int weightScalar)
{
float HeightRatio = GetHeightRatio(heightScalar);
float WeightRatio = GetWeightRatio(weightScalar);
float weight = WeightRatio * p.Weight;
return HeightRatio * weight;
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public static byte GetHeightScalar(float height, int avgHeight)
{
// height is already *100
float biasH = avgHeight * -0.6f;
float biasL = avgHeight * 0.8f;
float numerator = biasH + height;
float result = numerator / biasL;
result *= 255f;
int value = (int)result;
int unsigned = value & ~(value >> 31);
return (byte)Math.Min(255, unsigned);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public static byte GetWeightScalar(float height, float weight, int avgHeight, int avgWeight)
{
// height is already *100
// weight is already *10
float heightRatio = height / avgHeight;
float weightComponent = heightRatio * weight;
float top = avgWeight * -0.8f;
top += weightComponent;
float bot = avgWeight * 0.4f;
float result = top / bot;
result *= 255f;
int value = (int)result;
int unsigned = value & ~(value >> 31);
return (byte)Math.Min(255, unsigned);
}
public PK8 ConvertToPK8()
{
var pk8 = new PK8();
// todo: take from PK7.ConvertToPK8()
// Fix Checksum
pk8.RefreshChecksum();
return pk8; // Done!
}
}
}