CharacterStats.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. using Microsoft.Xna.Framework;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. namespace OpenVIII
  7. {
  8. namespace Kernel
  9. {
  10. /// <summary>
  11. /// Character Stats from Kernel
  12. /// </summary>
  13. /// <see cref="https://github.com/alexfilth/doomtrain/wiki/Characters"/>
  14. /// <seealso cref="http://forums.qhimm.com/index.php?topic=16923.msg240609#msg240609"/>
  15. public sealed class CharacterStats
  16. {
  17. #region Fields
  18. public const int Count = 11;
  19. public const int ID = 6;
  20. private const int MaxLevel = 100;
  21. private const int PercentMod = 100;
  22. private readonly byte[] _exp;
  23. private readonly byte[] _hp;
  24. private readonly byte _limitID;
  25. private readonly byte[] _luck;
  26. private readonly byte[] _mag;
  27. private readonly byte[] _spd;
  28. private readonly byte[] _spr;
  29. private readonly byte[] _str;
  30. private readonly byte[] _vit;
  31. #endregion Fields
  32. #region Constructors
  33. private CharacterStats(BinaryReader br, Characters charID)
  34. {
  35. CharID = charID;
  36. //Offset = br.ReadUInt16(); //0x0000; 2 bytes; Offset to character name
  37. // ReSharper disable once CommentTypo
  38. //Squall and Rinoa have name offsets of 0xFFFF because their name is in the save game data rather than kernel.bin.
  39. br.BaseStream.Seek(2, SeekOrigin.Current);
  40. Crisis = br.ReadByte(); //0x0002; 1 byte; Crisis level hp multiplier
  41. Gender = br.ReadByte() == 0 ? Gender.Male : Gender.Female; //0x0003; 1 byte; Gender; 0x00 - Male 0x01 - Female
  42. _limitID = br.ReadByte(); //0x0004; 1 byte; Limit Break ID
  43. LimitParam = br.ReadByte(); //0x0005; 1 byte; Limit Break Param used for the power of each renzokuken hit before finisher
  44. _exp = br.ReadBytes(2); //0x0006; 2 bytes; EXP modifier
  45. _hp = br.ReadBytes(4); //0x0008; 4 bytes; HP modifiers
  46. _str = br.ReadBytes(4); //0x000C; 4 bytes; STR modifiers
  47. _vit = br.ReadBytes(4); //0x0010; 4 bytes; VIT modifiers
  48. _mag = br.ReadBytes(4); //0x0014; 4 bytes; MAG modifiers
  49. _spr = br.ReadBytes(4); //0x0018; 4 bytes; SPR modifiers
  50. _spd = br.ReadBytes(4); //0x001C; 4 bytes; SPD modifiers
  51. _luck = br.ReadBytes(4); //0x0020; 4 bytes; LUCK modifiers
  52. }
  53. #endregion Constructors
  54. #region Properties
  55. /// <summary>
  56. /// Crisis level modifier
  57. /// </summary>
  58. /// <see cref="https://finalfantasy.fandom.com/wiki/Crisis_Level#Crisis_Level"/>
  59. public byte Crisis { get; }
  60. public Gender Gender { get; }
  61. public BattleCommand Limit => Memory.KernelBin.BattleCommands[_limitID];
  62. public byte LimitParam { get; }
  63. public FF8String Name => Memory.Strings.GetName((Faces.ID)CharID);
  64. private Characters CharID { get; }
  65. #endregion Properties
  66. #region Methods
  67. public static IReadOnlyDictionary<Characters, CharacterStats> Read(BinaryReader br)
  68. => Enumerable.Range(0, Count).ToDictionary(i => (Characters)i, i => CreateInstance(br, (Characters)i));
  69. public byte Eva(int lvl, int magicID = 0, int magicCount = 0, int statBonus = 0, int spd = 0, int percentMod = 0)
  70. {
  71. var value = (((Memory.KernelBin.MagicData[magicID].JVal[Stat.EVA] * magicCount) / 100 + spd / 4) *
  72. (percentMod + PercentMod)) / 100;
  73. return (byte)MathHelper.Clamp(value, 0, KernelBin.MaxStatValue);
  74. }
  75. /// <summary>
  76. /// Experience to reach level
  77. /// </summary>
  78. /// <param name="lvl">Level</param>
  79. /// <returns></returns>
  80. public int Exp(byte lvl) => ((lvl - 1) * (lvl - 1) * _exp[1]) / 256 + (lvl - 1) * _exp[0] * 10;
  81. public byte Hit(int magicID = 0, int magicCount = 0, int weapon = 0)
  82. {
  83. var value = Memory.KernelBin.MagicData[magicID].JVal[Stat.HIT] * magicCount + Memory.KernelBin.WeaponsData[weapon].Hit;
  84. return (byte)MathHelper.Clamp(value, 0, KernelBin.MaxStatValue);
  85. }
  86. /// <summary>
  87. /// </summary>
  88. /// <param name="lvl">Level</param>
  89. /// <param name="magicID">Bonus Value of Junctioned Magic</param>
  90. /// <param name="magicCount">Total amount of Magic in slot</param>
  91. /// <param name="statBonus">Bonus integer based HP</param>
  92. /// <param name="percentMod">50% = 50, 100%=100, etc</param>
  93. /// <returns></returns>
  94. public ushort HP(sbyte lvl, int magicID = 0, int magicCount = 0, int statBonus = 0, int percentMod = 0)
  95. {
  96. if (Memory.KernelBin == null) return 0;
  97. var value = (((Memory.KernelBin.MagicData[magicID].JVal[Stat.HP] * magicCount) + statBonus + (lvl * _hp[0]) - ((10 * lvl * lvl) / _hp[1]) + _hp[2]) * (percentMod + PercentMod)) / 100;
  98. return (ushort)MathHelper.Clamp(value, 0, KernelBin.MaxHPValue);
  99. }
  100. public byte Level(uint exp)
  101. {
  102. //by default no character has this set.
  103. Debug.Assert(_exp[1] == 0); // if set we need to update the formula.
  104. return (byte)MathHelper.Clamp(exp / (_exp[0] * 10) + 1, 0, MaxLevel);
  105. }
  106. public byte Luck(int lvl, int magicID = 0, int magicCount = 0, int statBonus = 0, int percentMod = PercentMod)
  107. => SPD_LUCK(_luck[0], _luck[1], _luck[2], _luck[3], lvl, Memory.KernelBin.MagicData[magicID].JVal[Stat.Luck], magicCount, statBonus, percentMod);
  108. public byte MAG(int lvl, int magicID = 0, int magicCount = 0, int statBonus = 0, int percentMod = PercentMod)
  109. => STR_VIT_MAG_SPR(_mag[0], _mag[1], _mag[2], _mag[3], lvl, Memory.KernelBin.MagicData[magicID].JVal[Stat.MAG], magicCount, statBonus, percentMod);
  110. public byte SPD(int lvl, int magicID = 0, int magicCount = 0, int statBonus = 0, int percentMod = PercentMod)
  111. => SPD_LUCK(_spd[0], _spd[1], _spd[2], _spd[3], lvl, Memory.KernelBin.MagicData[magicID].JVal[Stat.SPD], magicCount, statBonus, percentMod);
  112. public byte SPR(int lvl, int magicID = 0, int magicCount = 0, int statBonus = 0, int percentMod = PercentMod)
  113. => STR_VIT_MAG_SPR(_spr[0], _spr[1], _spr[2], _spr[3], lvl, Memory.KernelBin.MagicData[magicID].JVal[Stat.SPR], magicCount, statBonus, percentMod);
  114. public byte STR(int lvl, int magicID = 0, int magicCount = 0, int statBonus = 0, int percentMod = PercentMod, int weapon = 0)
  115. => STR_VIT_MAG_SPR(_str[0], _str[1], _str[2], _str[3], lvl, Memory.KernelBin.MagicData[magicID].JVal[Stat.STR], magicCount, statBonus, percentMod, weapon);
  116. public override string ToString() => Name;
  117. public byte VIT(int lvl, int magicID = 0, int magicCount = 0, int statBonus = 0, int percentMod = PercentMod)
  118. => STR_VIT_MAG_SPR(_vit[0], _vit[1], _vit[2], _vit[3], lvl, Memory.KernelBin.MagicData[magicID].JVal[Stat.VIT], magicCount, statBonus, percentMod);
  119. private static CharacterStats CreateInstance(BinaryReader br, Characters charID)
  120. => new CharacterStats(br, charID);
  121. 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)
  122. {
  123. var value = ((unk + (magicJVal * magicCount) / 100 + statBonus + lvl / b - lvl / d + lvl * a + c) * (percentMod + PercentMod)) / 100;
  124. return (byte)MathHelper.Clamp(value, 0, KernelBin.MaxStatValue);
  125. }
  126. 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)
  127. {
  128. var value = ((unk + (magicJVal * magicCount) / 100 + statBonus + ((lvl * a) / 10 + lvl / b - (lvl * lvl) / d / 2 + c) / 4) * (percentMod + PercentMod)) / 100;
  129. return (byte)MathHelper.Clamp(value, 0, KernelBin.MaxStatValue);
  130. }
  131. #endregion Methods
  132. }
  133. }
  134. }