using Microsoft.Xna.Framework; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; namespace OpenVIII { namespace Kernel { /// /// Character Stats from Kernel /// /// /// public sealed class CharacterStats { #region Fields public const int Count = 11; public const int ID = 6; private const int MaxLevel = 100; private const int PercentMod = 100; private readonly byte[] _exp; private readonly byte[] _hp; private readonly byte _limitID; private readonly byte[] _luck; private readonly byte[] _mag; private readonly byte[] _spd; private readonly byte[] _spr; private readonly byte[] _str; private readonly byte[] _vit; #endregion Fields #region Constructors private CharacterStats(BinaryReader br, Characters charID) { CharID = charID; //Offset = br.ReadUInt16(); //0x0000; 2 bytes; Offset to character name // ReSharper disable once CommentTypo //Squall and Rinoa have name offsets of 0xFFFF because their name is in the save game data rather than kernel.bin. br.BaseStream.Seek(2, SeekOrigin.Current); Crisis = br.ReadByte(); //0x0002; 1 byte; Crisis level hp multiplier Gender = br.ReadByte() == 0 ? Gender.Male : Gender.Female; //0x0003; 1 byte; Gender; 0x00 - Male 0x01 - Female _limitID = br.ReadByte(); //0x0004; 1 byte; Limit Break ID LimitParam = br.ReadByte(); //0x0005; 1 byte; Limit Break Param used for the power of each renzokuken hit before finisher _exp = br.ReadBytes(2); //0x0006; 2 bytes; EXP modifier _hp = br.ReadBytes(4); //0x0008; 4 bytes; HP modifiers _str = br.ReadBytes(4); //0x000C; 4 bytes; STR modifiers _vit = br.ReadBytes(4); //0x0010; 4 bytes; VIT modifiers _mag = br.ReadBytes(4); //0x0014; 4 bytes; MAG modifiers _spr = br.ReadBytes(4); //0x0018; 4 bytes; SPR modifiers _spd = br.ReadBytes(4); //0x001C; 4 bytes; SPD modifiers _luck = br.ReadBytes(4); //0x0020; 4 bytes; LUCK modifiers } #endregion Constructors #region Properties /// /// Crisis level modifier /// /// public byte Crisis { get; } public Gender Gender { get; } public BattleCommand Limit => Memory.KernelBin.BattleCommands[_limitID]; public byte LimitParam { get; } public FF8String Name => Memory.Strings.GetName((Faces.ID)CharID); private Characters CharID { get; } #endregion Properties #region Methods public static IReadOnlyDictionary Read(BinaryReader br) => Enumerable.Range(0, Count).ToDictionary(i => (Characters)i, i => CreateInstance(br, (Characters)i)); public byte Eva(int lvl, int magicID = 0, int magicCount = 0, int statBonus = 0, int spd = 0, int percentMod = 0) { var value = (((Memory.KernelBin.MagicData[magicID].JVal[Stat.EVA] * magicCount) / 100 + spd / 4) * (percentMod + PercentMod)) / 100; return (byte)MathHelper.Clamp(value, 0, KernelBin.MaxStatValue); } /// /// Experience to reach level /// /// Level /// public int Exp(byte lvl) => ((lvl - 1) * (lvl - 1) * _exp[1]) / 256 + (lvl - 1) * _exp[0] * 10; public byte Hit(int magicID = 0, int magicCount = 0, int weapon = 0) { var value = Memory.KernelBin.MagicData[magicID].JVal[Stat.HIT] * magicCount + Memory.KernelBin.WeaponsData[weapon].Hit; return (byte)MathHelper.Clamp(value, 0, KernelBin.MaxStatValue); } /// /// /// Level /// Bonus Value of Junctioned Magic /// Total amount of Magic in slot /// Bonus integer based HP /// 50% = 50, 100%=100, etc /// public ushort HP(sbyte lvl, int magicID = 0, int magicCount = 0, int statBonus = 0, int percentMod = 0) { if (Memory.KernelBin == null) return 0; var value = (((Memory.KernelBin.MagicData[magicID].JVal[Stat.HP] * magicCount) + statBonus + (lvl * _hp[0]) - ((10 * lvl * lvl) / _hp[1]) + _hp[2]) * (percentMod + PercentMod)) / 100; return (ushort)MathHelper.Clamp(value, 0, KernelBin.MaxHPValue); } public byte Level(uint exp) { //by default no character has this set. Debug.Assert(_exp[1] == 0); // if set we need to update the formula. return (byte)MathHelper.Clamp(exp / (_exp[0] * 10) + 1, 0, MaxLevel); } public byte Luck(int lvl, int magicID = 0, int magicCount = 0, int statBonus = 0, int percentMod = PercentMod) => SPD_LUCK(_luck[0], _luck[1], _luck[2], _luck[3], lvl, Memory.KernelBin.MagicData[magicID].JVal[Stat.Luck], magicCount, statBonus, percentMod); public byte MAG(int lvl, int magicID = 0, int magicCount = 0, int statBonus = 0, int percentMod = PercentMod) => STR_VIT_MAG_SPR(_mag[0], _mag[1], _mag[2], _mag[3], lvl, Memory.KernelBin.MagicData[magicID].JVal[Stat.MAG], magicCount, statBonus, percentMod); public byte SPD(int lvl, int magicID = 0, int magicCount = 0, int statBonus = 0, int percentMod = PercentMod) => SPD_LUCK(_spd[0], _spd[1], _spd[2], _spd[3], lvl, Memory.KernelBin.MagicData[magicID].JVal[Stat.SPD], magicCount, statBonus, percentMod); public byte SPR(int lvl, int magicID = 0, int magicCount = 0, int statBonus = 0, int percentMod = PercentMod) => STR_VIT_MAG_SPR(_spr[0], _spr[1], _spr[2], _spr[3], lvl, Memory.KernelBin.MagicData[magicID].JVal[Stat.SPR], magicCount, statBonus, percentMod); public byte STR(int lvl, int magicID = 0, int magicCount = 0, int statBonus = 0, int percentMod = PercentMod, int weapon = 0) => STR_VIT_MAG_SPR(_str[0], _str[1], _str[2], _str[3], lvl, Memory.KernelBin.MagicData[magicID].JVal[Stat.STR], magicCount, statBonus, percentMod, weapon); public override string ToString() => Name; public byte VIT(int lvl, int magicID = 0, int magicCount = 0, int statBonus = 0, int percentMod = PercentMod) => STR_VIT_MAG_SPR(_vit[0], _vit[1], _vit[2], _vit[3], lvl, Memory.KernelBin.MagicData[magicID].JVal[Stat.VIT], magicCount, statBonus, percentMod); private static CharacterStats CreateInstance(BinaryReader br, Characters charID) => new CharacterStats(br, charID); private static byte SPD_LUCK(int a, int b, int c, int d, int lvl, int magicJVal, int magicCount, int statBonus, int percentMod = 0, int unk = 0) { var value = ((unk + (magicJVal * magicCount) / 100 + statBonus + lvl / b - lvl / d + lvl * a + c) * (percentMod + PercentMod)) / 100; return (byte)MathHelper.Clamp(value, 0, KernelBin.MaxStatValue); } private static byte STR_VIT_MAG_SPR(int a, int b, int c, int d, int lvl, int magicJVal, int magicCount, int statBonus, int percentMod = 0, int unk = 0) { var value = ((unk + (magicJVal * magicCount) / 100 + statBonus + ((lvl * a) / 10 + lvl / b - (lvl * lvl) / d / 2 + c) / 4) * (percentMod + PercentMod)) / 100; return (byte)MathHelper.Clamp(value, 0, KernelBin.MaxStatValue); } #endregion Methods } } }