2
0

Saves.CharacterData.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.ComponentModel;
  5. using System.Diagnostics.CodeAnalysis;
  6. using System.IO;
  7. using System.Linq;
  8. using Microsoft.Xna.Framework;
  9. using OpenVIII.Battle;
  10. using OpenVIII.Kernel;
  11. namespace OpenVIII
  12. {
  13. public static partial class Saves
  14. {
  15. #region Enums
  16. /// <summary>
  17. /// </summary>
  18. /// <remarks>Hyne has switch locked 0 and 1</remarks>
  19. [Flags]
  20. [SuppressMessage("ReSharper", "UnusedMember.Global")]
  21. public enum Exists : byte
  22. {
  23. Unavailable = 0x0,
  24. /// <summary>
  25. /// Shows in menu.
  26. /// </summary>
  27. Available = 0x1,
  28. /// <summary>
  29. /// Party members that cannot leave or be added
  30. /// </summary>
  31. SwitchLocked0 = 0x2,
  32. /// <summary>
  33. /// Party members that cannot leave or be added
  34. /// </summary>
  35. SwitchLocked1 = 0x4,
  36. /// <summary>
  37. /// Many have this set. I don't know what it does.
  38. /// </summary>
  39. Unk8 = 0x8,
  40. /// <summary>
  41. /// Many have this set. I don't know what it does.
  42. /// </summary>
  43. Unk10 = 0x10,
  44. /// <summary>
  45. /// Many have this set. I don't know what it does.
  46. /// </summary>
  47. Unk20 = 0x20,
  48. /// <summary>
  49. /// Many have this set. I don't know what it does.
  50. /// </summary>
  51. Unk30 = 0x40,
  52. /// <summary>
  53. /// Many have this set. I don't know what it does.
  54. /// </summary>
  55. Unk40 = 0x80
  56. }
  57. #endregion Enums
  58. #region Classes
  59. /// <summary>
  60. /// Data for each Character
  61. /// </summary>
  62. /// <see cref="http://wiki.ffrtt.ru/index.php/FF8/GameSaveFormat#Characters"/>
  63. public class CharacterData : Damageable, ICharacterData
  64. {
  65. #region Fields
  66. /// <summary>
  67. /// Raw HP buff from items.
  68. /// </summary>
  69. public ushort RawHP;
  70. /// <summary>
  71. /// Junctioned Abilities
  72. /// </summary>
  73. public List<Abilities> Abilities;
  74. /// <summary>
  75. /// <para>Alt Models/different costumes</para>
  76. /// <para>(Normal, SeeD, Soldier...)</para>
  77. /// </summary>
  78. public byte AlternativeModel;
  79. /// <summary>
  80. /// Junctioned Commands
  81. /// </summary>
  82. public List<Abilities> Commands;
  83. /// <summary>
  84. /// <para>Compatibility With GFs</para>
  85. /// <para>Effects Summon speed and such.</para>
  86. /// </summary>
  87. public Dictionary<GFs, CompatibilitywithGF> CompatibilityWithGFs;
  88. /// <summary>
  89. /// Value determines if a character shows in menu and can be added to party.
  90. /// <para>
  91. /// 15,9,7,4,1 shows on menu, 0 locked, 6 hidden // I think I wonder if this is a flags value.
  92. /// </para>
  93. /// </summary>
  94. public Exists Exists;
  95. public OrderedDictionary<byte, byte> Magics;
  96. /// <summary>
  97. /// Character Model
  98. /// </summary>
  99. public byte ModelID;
  100. /// <summary>
  101. /// Number of Kills
  102. /// </summary>
  103. public ushort NumberOfKills;
  104. /// <summary>
  105. /// Number of KOs
  106. /// </summary>
  107. public ushort NumberOfKOs;
  108. public byte PaddingOrUnusedCommand;
  109. /// <summary>
  110. /// Stats that can be increased via items. Except for HP because it's a ushort not a byte.
  111. /// </summary>
  112. public Dictionary<Stat, byte> RawStats;
  113. /// <summary>
  114. /// Junctioned Magic per stat.
  115. /// </summary>
  116. public Dictionary<Stat, byte> StatJ;
  117. public byte Unknown1;
  118. public byte Unknown2;
  119. public byte Unknown3;
  120. public byte Unknown4;
  121. /// <summary>
  122. /// Weapon
  123. /// </summary>
  124. public byte WeaponID;
  125. /// <summary>
  126. /// Total amount of spells the will be loaded/saved.
  127. /// </summary>
  128. private const int MagicCapacity = 32;
  129. /// <summary>
  130. /// Total Exp
  131. /// </summary>
  132. private uint _experience;
  133. private Characters _id;
  134. /// <summary>
  135. /// Junctioned GFs - raw value
  136. /// </summary>
  137. private GFflags _rawJunctionedGFs;
  138. #endregion Fields
  139. #region Properties
  140. /// <summary>
  141. /// Visible
  142. /// </summary>
  143. public bool Available => (Exists & Exists.Available) != 0;
  144. /// <summary>
  145. /// 25.4% chance to cast automatically on game over, if used once in battle
  146. /// </summary>
  147. /// <remarks>
  148. /// Memory.State.FieldVars. has a value that tracks if PhoenixPinion is used just need to
  149. /// find it
  150. /// </remarks>
  151. public bool CanPhoenixPinion => IsDead && !(IsPetrify || (Statuses1 & (BattleOnlyStatuses.Eject)) != 0) && Memory.State.Items.Any(m => m.ID == 31 && m.QTY >= 1);
  152. /// <summary>
  153. /// Kernel Stats
  154. /// </summary>
  155. public CharacterStats CharacterStats
  156. {
  157. get
  158. {
  159. if (Memory.KernelBin?.CharacterStats != null && Memory.KernelBin.CharacterStats.TryGetValue(ID, out var value))
  160. return value;
  161. return null;
  162. }
  163. }
  164. //public CharacterData(BinaryReader br, Characters c) => Read(br, c);
  165. public CharacterInstanceInformation CII { get; private set; }
  166. /// <summary>
  167. /// Set by GenerateCrisisLevel(), -1 means no limit break. &gt;=0 has a limit break.
  168. /// </summary>
  169. /// <returns>-1 - 4</returns>
  170. /// <remarks>https://finalfantasy.fandom.com/wiki/Crisis_Level</remarks>
  171. public sbyte CurrentCrisisLevel { get; private set; }
  172. public override byte EVA => checked((byte)TotalStat(Stat.EVA));
  173. public override int EXP => checked((int)Experience);
  174. public uint Experience
  175. {
  176. get => _experience; set
  177. {
  178. if (_experience == 0)
  179. _experience = value;
  180. }
  181. }
  182. public ushort ExperienceToNextLevel => (ushort)(Level == 100 ? 0 : MathHelper.Clamp(CharacterStats.Exp((byte)(Level + 1)) - Experience, 0, CharacterStats.Exp(2)));
  183. public override byte HIT => checked((byte)TotalStat(Stat.HIT));
  184. /// <summary>
  185. /// If TeamLaguna the ID will change to a Laguna Party member
  186. /// </summary>
  187. public Characters ID
  188. {
  189. get
  190. {
  191. int ind;
  192. if (Memory.State != null && Memory.State.TeamLaguna && (ind = Memory.State.PartyData.FindIndex(x => x.Equals(_id))) >= 0 && !Memory.State.Party.Contains(_id))
  193. return Memory.State.Party[ind];
  194. return _id;
  195. }
  196. }
  197. public override bool IsCritical => CurrentHP() <= CriticalHP();
  198. /// <summary>
  199. /// Junctioned GFs
  200. /// </summary>
  201. public IEnumerable<GFs> JunctionedGFs => Enum.GetValues(_rawJunctionedGFs.GetType()).Cast<GFflags>().Where(x => _rawJunctionedGFs.HasFlag(x) && ConvertGFEnum.ContainsKey(x)).Distinct().Select(x => ConvertGFEnum[x]);
  202. public override byte Level => CharacterStats?.Level(Experience) ?? 0;
  203. public override byte LUCK => checked((byte)TotalStat(Stat.Luck));
  204. public override byte MAG => checked((byte)TotalStat(Stat.MAG));
  205. /// <summary>
  206. /// If TeamLaguna the name will change to a Laguna Party member
  207. /// </summary>
  208. public override FF8String Name
  209. {
  210. get
  211. {
  212. if (Memory.State != null && Memory.State.TeamLaguna)
  213. {
  214. return Memory.Strings.GetName(ID);
  215. }
  216. return base.Name;
  217. }
  218. set => base.Name = value;
  219. }
  220. public override byte SPD => checked((byte)TotalStat(Stat.SPD));
  221. public override byte SPR => checked((byte)TotalStat(Stat.SPR));
  222. public override byte STR => checked((byte)TotalStat(Stat.STR));
  223. /// <summary>
  224. /// Cannot remove from party or add to party.
  225. /// </summary>
  226. public bool SwitchLocked => (Exists & (Exists.SwitchLocked0 | Exists.SwitchLocked1)) != 0;
  227. public List<Abilities> UnlockedGFAbilities
  228. {
  229. get
  230. {
  231. var total = new BitArray(16 * 8);
  232. var abilities = new List<Abilities>();
  233. foreach (var gf in JunctionedGFs)
  234. {
  235. total.Or(Memory.State.GFs[gf].Complete);
  236. }
  237. for (var i = 1; i < total.Length; i++)//0 is none so skipping it.
  238. {
  239. if (total[i])
  240. abilities.Add((Abilities)i);
  241. }
  242. return abilities;
  243. }
  244. }
  245. public override byte VIT => checked((byte)TotalStat(Stat.VIT));
  246. #endregion Properties
  247. #region Methods
  248. public static CharacterData Load(BinaryReader br, Characters @enum, Data data) => Load<CharacterData>(br, @enum, data);
  249. public void AutoATK() => Auto(KernelBin.AutoAtk);
  250. public void AutoDEF() => Auto(KernelBin.AutoDef);
  251. public void AutoMAG() => Auto(KernelBin.AutoMAG);
  252. public void BattleStart(CharacterInstanceInformation cii)
  253. {
  254. CII = cii;
  255. Statuses1 = BattleOnlyStatuses.None;
  256. if (Abilities.Contains(Kernel.Abilities.AutoHaste))
  257. Statuses1 |= BattleOnlyStatuses.Haste;
  258. if (Abilities.Contains(Kernel.Abilities.AutoProtect))
  259. Statuses1 |= BattleOnlyStatuses.Protect;
  260. if (Abilities.Contains(Kernel.Abilities.AutoReflect))
  261. Statuses1 |= BattleOnlyStatuses.Reflect;
  262. if (Abilities.Contains(Kernel.Abilities.AutoShell))
  263. Statuses1 |= BattleOnlyStatuses.Shell;
  264. //reset the ATB timer.
  265. ATBTimer.FirstTurn();
  266. }
  267. public override Damageable Clone()
  268. {
  269. //Shadow copy
  270. var c = (CharacterData)MemberwiseClone();
  271. //Deep copy
  272. c.Name = Name?.Clone();
  273. c.CompatibilityWithGFs = CompatibilityWithGFs?.ToDictionary(e => e.Key, e => e.Value);
  274. c.StatJ = StatJ?.ToDictionary(e => e.Key, e => e.Value);
  275. c.Magics = new OrderedDictionary<byte, byte>(Magics?.Count ?? 0);
  276. if (Magics != null)
  277. foreach (KeyValuePair<byte, byte> magic in Magics)
  278. c.Magics.Add(magic.Key, magic.Value);
  279. c.RawStats = RawStats?.ToDictionary(e => e.Key, e => e.Value);
  280. c.Commands = Commands?.ConvertAll(item => item);
  281. c.Abilities = Abilities?.ConvertAll(item => item);
  282. return c;
  283. }
  284. public OrderedDictionary<byte, byte> CloneMagic() => Magics.Clone();
  285. public Dictionary<Stat, byte> CloneMagicJunction() => new Dictionary<Stat, byte>(StatJ);
  286. public int CriticalHP(Characters value) => MaxHP(value) / 4 - 1;
  287. public override ushort CurrentHP() => CurrentHP(ID);
  288. public ushort CurrentHP(Characters c)
  289. {
  290. var max = MaxHP(c);
  291. if (max < _CurrentHP) _CurrentHP = max;
  292. return _CurrentHP;
  293. }
  294. public override short ElementalResistance(Element @in) => throw new NotImplementedException();
  295. /// <summary>
  296. /// <para>
  297. /// This Generates a Crisis Level. Run this each turn in battle. Though in real game it
  298. /// runs when the menu pops up.
  299. /// </para>
  300. /// <para>-1 means no limit break. &gt;=0 has a limit break.</para>
  301. /// </summary>
  302. /// <returns>-1 - 4</returns>
  303. /// <remarks>https://finalfantasy.fandom.com/wiki/Crisis_Level</remarks>
  304. /// <remarks>TODO: Need to confirm the formula is correct via reverse</remarks>
  305. public sbyte GenerateCrisisLevel()
  306. {
  307. var current = CurrentHP();
  308. var max = MaxHP();
  309. //if ((ID == Characters.Seifer_Almasy && CurrentHP() < (max * 84 / 100)))
  310. //{
  311. var hpMod = CharacterStats.Crisis * 10 * current / max;
  312. var deathBonus = Memory.State.DeadPartyMembers() * 200 + 1600;
  313. var statusBonus = (int)(Statuses0.Count() * 10); // I think this is status of all party members
  314. var randomMod = Memory.Random.Next(byte.MaxValue + 1) + 160;
  315. var crisisLevel = (statusBonus + deathBonus - hpMod) / randomMod; // better random number?
  316. switch (crisisLevel)
  317. {
  318. case 5:
  319. CurrentCrisisLevel = 0;
  320. return CurrentCrisisLevel;
  321. case 6:
  322. CurrentCrisisLevel = 1;
  323. return CurrentCrisisLevel;
  324. case 7:
  325. CurrentCrisisLevel = 2;
  326. return CurrentCrisisLevel;
  327. default:
  328. {
  329. if (crisisLevel < 8) return CurrentCrisisLevel = -1;
  330. CurrentCrisisLevel = 3;
  331. return CurrentCrisisLevel;
  332. }
  333. }
  334. }
  335. // ReSharper disable once UnusedMember.Global
  336. public void JunctionGF(GFs gf) =>
  337. _rawJunctionedGFs |= ConvertGFEnum.FirstOrDefault(x => x.Value == gf).Key;
  338. public void JunctionSpell(Stat stat, byte spell)
  339. {
  340. //see if magic is in use, if so remove it
  341. if (StatJ.ContainsValue(spell))
  342. {
  343. var key = StatJ.FirstOrDefault(x => x.Value == spell).Key;
  344. StatJ[key] = 0;
  345. }
  346. //junction magic
  347. StatJ[stat] = spell;
  348. }
  349. /// <summary>
  350. /// Max HP
  351. /// </summary>
  352. /// <param name="c">Force another character's HP calculation</param>
  353. /// <returns></returns>
  354. public ushort MaxHP(Characters c) => TotalStat(Stat.HP, c);
  355. public override ushort MaxHP() => MaxHP(ID);
  356. public override float PercentFullHP() => PercentFullHP(ID);
  357. public float PercentFullHP(Characters c) => (float)CurrentHP(c) / MaxHP(c);
  358. public void RemoveAll()
  359. {
  360. StatJ = StatJ.ToDictionary(e => e.Key, e => (byte)0);
  361. Commands = Commands.ConvertAll(item => Kernel.Abilities.None);
  362. Abilities = Abilities.ConvertAll(item => Kernel.Abilities.None);
  363. _rawJunctionedGFs = GFflags.None;
  364. }
  365. public void RemoveJunctionedGF(GFs gf) =>
  366. _rawJunctionedGFs ^= ConvertGFEnum.FirstOrDefault(x => x.Value == gf).Key;
  367. public void RemoveMagic() => StatJ = StatJ.ToDictionary(e => e.Key, e => (byte)0);
  368. /// <summary>
  369. /// Sorted Enumerable based on best to worst for Stat. Uses character's total magic and
  370. /// kernel bin's stat value.
  371. /// </summary>
  372. /// <param name="stat">Stat sorting by.</param>
  373. /// <returns>Ordered Enumerable</returns>
  374. public IOrderedEnumerable<MagicData> SortedMagic(Stat stat) => Memory.KernelBin.MagicData.OrderBy(x => (-x.TotalStatVal(stat) * (Magics.ContainsKey(x.MagicDataID) ? Magics[x.MagicDataID] : 0)) / 100);
  375. public override sbyte StatusResistance(BattleOnlyStatuses s) => throw new NotImplementedException();
  376. public override sbyte StatusResistance(PersistentStatuses s) => throw new NotImplementedException();
  377. public override string ToString() => Name.Length > 0 ? Name.ToString() : base.ToString();
  378. // ReSharper disable once MethodOverloadWithOptionalParameter
  379. public ushort TotalStat(Stat s, Characters c = Characters.Blank)
  380. {
  381. if (!Enum.IsDefined(typeof(Characters), c))
  382. throw new InvalidEnumArgumentException(nameof(c), (int)c, typeof(Characters));
  383. if (c == Characters.Blank)
  384. c = _id; // this might need to be ID, which would mean the next if is wrong.
  385. if (c != _id && c < Characters.Laguna_Loire)
  386. throw new ArgumentException($"{this}::Wrong visible character value({c}). Must match ({_id}) unless Laguna, Kiros or Ward!");
  387. var total = 0;
  388. if (Memory.KernelBin.StatPercentAbilities != null)
  389. foreach (var i in Abilities)
  390. {
  391. if (Memory.KernelBin.StatPercentAbilities.TryGetValue(i, out var ability) && ability.Stat == s)
  392. total += ability.Value;
  393. }
  394. if (CharacterStats == null) return 0;
  395. // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
  396. switch (s)
  397. {
  398. case Stat.HP:
  399. return CharacterStats.HP((sbyte)Level, StatJ[s], StatJ[s] == 0 ? 0 : Magics[StatJ[s]], RawHP, total);
  400. case Stat.EVA:
  401. //TODO confirm if there is no flat stat buff for eva. If there isn't then remove from function.
  402. return CharacterStats.Eva((sbyte)Level, StatJ[s], StatJ[s] == 0 ? 0 : Magics[StatJ[s]], 0, TotalStat(Stat.SPD, c), total);
  403. case Stat.SPD:
  404. return CharacterStats.SPD((sbyte)Level, StatJ[s], StatJ[s] == 0 ? 0 : Magics[StatJ[s]], RawStats[s], total);
  405. case Stat.HIT:
  406. return CharacterStats.Hit(StatJ[s], StatJ[s] == 0 ? 0 : Magics[StatJ[s]], WeaponID);
  407. case Stat.Luck:
  408. return CharacterStats.Luck((sbyte)Level, StatJ[s], StatJ[s] == 0 ? 0 : Magics[StatJ[s]], RawStats[s], total);
  409. case Stat.MAG:
  410. return CharacterStats.MAG((sbyte)Level, StatJ[s], StatJ[s] == 0 ? 0 : Magics[StatJ[s]], RawStats[s], total);
  411. case Stat.SPR:
  412. return CharacterStats.SPR((sbyte)Level, StatJ[s], StatJ[s] == 0 ? 0 : Magics[StatJ[s]], RawStats[s], total);
  413. case Stat.STR:
  414. return CharacterStats.STR((sbyte)Level, StatJ[s], StatJ[s] == 0 ? 0 : Magics[StatJ[s]], RawStats[s], total, WeaponID);
  415. case Stat.VIT:
  416. return CharacterStats.VIT((sbyte)Level, StatJ[s], StatJ[s] == 0 ? 0 : Magics[StatJ[s]], RawStats[s], total);
  417. default:
  418. throw new ArgumentOutOfRangeException(nameof(s), s, null);
  419. }
  420. }
  421. public override ushort TotalStat(Stat s) => TotalStat(s, ID);
  422. public bool Unlocked(Stat stat) => Unlocked(UnlockedGFAbilities, stat);
  423. public bool Unlocked(List<Abilities> unlocked, Stat stat)
  424. {
  425. switch (stat)
  426. {
  427. case Stat.HP:
  428. case Stat.STR:
  429. case Stat.VIT:
  430. case Stat.MAG:
  431. case Stat.SPR:
  432. case Stat.SPD:
  433. case Stat.EVA:
  434. case Stat.HIT:
  435. case Stat.Luck:
  436. case Stat.None:
  437. throw new ArgumentOutOfRangeException(nameof(stat), stat, null);
  438. default:
  439. return unlocked.Contains(KernelBin.Stat2Ability[stat]);
  440. case Stat.ElAtk:
  441. return unlocked.Contains(Kernel.Abilities.ElAtkJ);
  442. case Stat.ElDef1:
  443. return unlocked.Contains(Kernel.Abilities.ElDefJ) ||
  444. unlocked.Contains(Kernel.Abilities.ElDefJ2) ||
  445. unlocked.Contains(Kernel.Abilities.ElDefJ4);
  446. case Stat.ElDef2:
  447. return unlocked.Contains(Kernel.Abilities.ElDefJ2) ||
  448. unlocked.Contains(Kernel.Abilities.ElDefJ4);
  449. case Stat.ElDef3:
  450. case Stat.ElDef4:
  451. return unlocked.Contains(Kernel.Abilities.ElDefJ4);
  452. case Stat.StAtk:
  453. return unlocked.Contains(Kernel.Abilities.StAtkJ);
  454. case Stat.StDef1:
  455. return unlocked.Contains(Kernel.Abilities.StDefJ) ||
  456. unlocked.Contains(Kernel.Abilities.StDefJ2) ||
  457. unlocked.Contains(Kernel.Abilities.StDefJ4);
  458. case Stat.StDef2:
  459. return unlocked.Contains(Kernel.Abilities.StDefJ2) ||
  460. unlocked.Contains(Kernel.Abilities.StDefJ4);
  461. case Stat.StDef3:
  462. case Stat.StDef4:
  463. return unlocked.Contains(Kernel.Abilities.StDefJ4);
  464. }
  465. }
  466. protected override void ReadData(BinaryReader br, Enum c)
  467. {
  468. _id = c as Characters? ?? throw new ArgumentException($"Enum {c} is not Characters");
  469. Name = Memory.Strings.GetName(_id, Data ?? Memory.State);
  470. _CurrentHP = br.ReadUInt16();//0x00
  471. RawHP = br.ReadUInt16();//0x02
  472. Experience = br.ReadUInt32();//0x04
  473. ModelID = br.ReadByte();//0x08
  474. WeaponID = br.ReadByte();//0x09
  475. RawStats = new Dictionary<Stat, byte>(6)
  476. {
  477. [Stat.STR] = br.ReadByte(),//0x0A
  478. [Stat.VIT] = br.ReadByte(),//0x0B
  479. [Stat.MAG] = br.ReadByte(),//0x0C
  480. [Stat.SPR] = br.ReadByte(),//0x0D
  481. [Stat.SPD] = br.ReadByte(),//0x0E
  482. [Stat.Luck] = br.ReadByte()//0x0F
  483. };
  484. Magics = new OrderedDictionary<byte, byte>(MagicCapacity);
  485. for (var i = 0; i < MagicCapacity; i++)
  486. {
  487. var key = br.ReadByte();
  488. var val = br.ReadByte();
  489. if (!Magics.ContainsKey(key))
  490. Magics.Add(key, val);//0x10
  491. }
  492. Commands = Array.ConvertAll(br.ReadBytes(3), item => (Abilities)item).ToList();//0x50
  493. PaddingOrUnusedCommand = br.ReadByte();//0x53
  494. Abilities = Array.ConvertAll(br.ReadBytes(4), item => (Abilities)item).ToList();//0x54
  495. _rawJunctionedGFs = (GFflags)br.ReadUInt16();//0x58 each bit is one gf.
  496. Unknown1 = br.ReadByte();//0x5A
  497. AlternativeModel = br.ReadByte();//0x5B (Normal, SeeD, Soldier...)
  498. StatJ = new Dictionary<Stat, byte>(9);
  499. for (var i = 0; i < 19; i++)
  500. {
  501. var key = (Stat)i;
  502. var val = br.ReadByte();
  503. if (!StatJ.ContainsKey(key))
  504. StatJ.Add(key, val);
  505. }
  506. Unknown2 = br.ReadByte();//0x6F (padding?)
  507. CompatibilityWithGFs = new Dictionary<GFs, CompatibilitywithGF>(16);
  508. for (var i = 0; i < 16; i++)
  509. CompatibilityWithGFs.Add((GFs)i, br.ReadUInt16());//0x70
  510. NumberOfKills = br.ReadUInt16();//0x90
  511. NumberOfKOs = br.ReadUInt16();//0x92
  512. Exists = (Exists)br.ReadByte();//0x94
  513. Unknown3 = br.ReadByte();//0x95
  514. Statuses0 = (PersistentStatuses)br.ReadByte();//0x96
  515. Unknown4 = br.ReadByte();//0x97
  516. }
  517. private void Auto(IEnumerable<Stat> list)
  518. {
  519. RemoveMagic();
  520. var unlockedList = UnlockedGFAbilities;
  521. foreach (var stat in list)
  522. {
  523. if (!Unlocked(unlockedList, stat)) continue;
  524. foreach (var spell in SortedMagic(stat))
  525. {
  526. if (StatJ.ContainsValue(spell.MagicDataID)) continue;
  527. //TODO make smarter.
  528. //example if you can get max stat with a weaker spell use that first.
  529. // if stat is max with out spell skip
  530. if (stat != Stat.HP && TotalStat(stat) == KernelBin.MaxStatValue) break;
  531. // if hp is max without spell skip
  532. if (stat == Stat.HP && TotalStat(stat) == KernelBin.MaxHPValue) break;
  533. // junction spell
  534. StatJ[stat] = spell.MagicDataID;
  535. break;
  536. }
  537. }
  538. }
  539. #endregion Methods
  540. }
  541. #endregion Classes
  542. }
  543. }