Saves.Data.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Concurrent;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. using Microsoft.Xna.Framework;
  8. using OpenVIII.Kernel;
  9. // ReSharper disable UnusedMember.Global
  10. namespace OpenVIII
  11. {
  12. public static partial class Saves
  13. {
  14. [Flags]
  15. public enum BattleMiscFlags : byte
  16. {
  17. Dream = 0x1,
  18. Odin = 0x2,
  19. Phoenix = 0x4,
  20. Gilgamesh = 0x8,
  21. AngeloDisabled = 0x10,
  22. AngelWingEnabled = 0x20,
  23. Unk40 = 0x40,
  24. Unk80 = 0x80
  25. }
  26. #region Classes
  27. /// <summary>
  28. /// Save Data
  29. /// </summary>
  30. /// <see cref="http://wiki.ffrtt.ru/index.php/FF8/GameSaveFormat"/>
  31. public class Data
  32. {
  33. #region Fields
  34. private Dictionary<Characters, CharacterData> _characters;
  35. private Dictionary<GFs, GFData> _gfs;
  36. private List<Shop> _shops;
  37. #endregion Fields
  38. #region Constructors
  39. public Data()
  40. {
  41. //Define Containers
  42. _gfs = new Dictionary<GFs, GFData>(16);
  43. _characters = new Dictionary<Characters, CharacterData>(8);
  44. _shops = new List<Shop>(20);
  45. Items = new List<Item>(198);
  46. TimePlayed = new TimeSpan();
  47. CoordinateX = new short[3];
  48. CoordinateY = new short[3];
  49. TriangleID = new ushort[3];
  50. FieldVars = new FieldVars(); //0x0D70 http://wiki.ffrtt.ru/index.php/FF8/Variables
  51. WorldMap = new Worldmap(); //br.ReadBytes(128);//0x1270
  52. TripleTriad = new TripleTriad(); //br.ReadBytes(128);//0x12F0
  53. ChocoboWorld = new ChocoboWorld(); //br.ReadBytes(64);//0x1370
  54. }
  55. #endregion Constructors
  56. #region Properties
  57. /// <summary>
  58. /// 0x000C 4 bytes Preview: Amount of Gil
  59. /// </summary>
  60. private uint _amountOfGilPreview;
  61. /// <summary>
  62. /// 0x0B20 4 bytes Amount of Gil (Laguna)
  63. /// </summary>
  64. public uint AmountOfGilLaguna => _amountOfGilLaguna;
  65. /// <summary>
  66. /// 0x0B1C 4 bytes Amount of Gil
  67. /// </summary>
  68. private uint _amountOfGil;
  69. private uint _amountOfGilLaguna;
  70. public uint AmountOfGil => TeamLaguna ? AmountOfGilLaguna : _amountOfGil;
  71. public void ChangeGil(int amount)
  72. {
  73. void proc(ref uint gil)
  74. {
  75. gil = (uint) MathHelper.Clamp(gil + amount, uint.MinValue, uint.MaxValue);
  76. }
  77. if (TeamLaguna)
  78. {
  79. proc(ref _amountOfGilLaguna);
  80. }
  81. proc(ref _amountOfGil);
  82. _amountOfGilPreview = _amountOfGil;
  83. }
  84. /// <summary>
  85. /// 0x0040 12 bytes Preview: Angelo's name (0x00 terminated)
  86. /// </summary>
  87. public FF8String AngeloName { get; set; }
  88. public byte AveragePartyLevel
  89. {
  90. get
  91. {
  92. var level = 0;
  93. var cnt = 0;
  94. for (var p = 0; p < 3; p++)
  95. {
  96. var c = Party?[p] ?? OpenVIII.Characters.Squall_Leonhart;
  97. if (c == OpenVIII.Characters.Blank) continue;
  98. level += this[c]?.Level ?? 0;
  99. cnt++;
  100. }
  101. if(cnt>0)
  102. return (byte)MathHelper.Clamp(level / cnt, 0, 100);
  103. return 0;
  104. }
  105. }
  106. /// <summary>
  107. /// 0x0D08 4 bytes Battle: First "Bomb" battle (Elemental tip)
  108. /// </summary>
  109. public uint BattleElemental { get; set; }
  110. /// <summary>
  111. /// 0x0CF2 2 bytes Battle: battle escaped
  112. /// </summary>
  113. public ushort BattleEscapeCount { get; set; }
  114. /// <summary>
  115. /// 0x0D10 4 bytes Battle: First "Irvine" battle (Irvine's limit break tip)
  116. /// </summary>
  117. public uint BattleIrvine { get; set; }
  118. /// <summary>
  119. /// 0x0D14 8 bytes Battle: Magic drawn once
  120. /// </summary>
  121. public BitArray BattleMagic { get; set; }
  122. /// <summary>
  123. /// 0x0D0C 4 bytes Battle: First "T-Rex" battle (Mental tip)
  124. /// </summary>
  125. public uint BattleMental { get; set; }
  126. /// <summary>
  127. /// 0x0D04 4 bytes Battle: First "Bug" battle (R1 tip)
  128. /// </summary>
  129. public uint BattleR1 { get; set; }
  130. /// <summary>
  131. /// 0x0D30 1 byte Battle: Renzokuken auto
  132. /// </summary>
  133. public bool BattleRenzokukenAuto { get; set; }
  134. /// <summary>
  135. /// 0x0D31 1 byte Battle: Renzokuken indicator
  136. /// </summary>
  137. public bool BattleRenzokukenIndicator { get; set; }
  138. /// <summary>
  139. /// 0x0D1C 20 bytes Battle: Enemy scanned once
  140. /// </summary>
  141. public BitArray BattleScan { get; set; }
  142. /// <summary>
  143. /// 0x0CF4 4 bytes Unknown
  144. /// </summary>
  145. public uint BattleTonberryKilledCount { get; set; }
  146. /// <summary>
  147. /// 0x0CFC 4 bytes Battle: Tonberry King killed (yeah, this is a boolean)
  148. /// </summary>
  149. public bool BattleTonberryKingKilled { get; set; }
  150. /// <summary>
  151. /// Battle: dream/Odin/Phoenix/Gilgamesh/Angelo disabled/Angel Wing enabled/???/???
  152. /// </summary>
  153. public BattleMiscFlags BattleMiscFlags { get; set; }
  154. /// <summary>
  155. /// 0x0CEC 4 bytes Battle: victory Count
  156. /// </summary>
  157. public uint BattleVictoryCount { get; set; }
  158. /// <summary>
  159. /// 0x004C 12 bytes Preview: Boko's name (0x00 terminated)
  160. /// </summary>
  161. public FF8String BokoName { get; set; }
  162. /// <summary>
  163. /// 0x04A0-0x08C7 152 bytes each 8 of them. Characters: Squall-Edea
  164. /// </summary>
  165. public bool Characters => _characters != null && _characters.Count > 0;
  166. public int CharactersCount => _characters.Count;
  167. /// <summary>
  168. /// 0x1370 64 bytes Chocobo World (TODO)
  169. /// </summary>
  170. public ChocoboWorld ChocoboWorld { get; set; }
  171. /// <summary>
  172. /// 0x0AF0 20 bytes Configuration
  173. /// </summary>
  174. public Configuration Configuration { get; set; }
  175. /// <summary>
  176. /// 0x0D56 3*2 bytes (signed) coordinate X (party1, party2, party3)
  177. /// </summary>
  178. public short[] CoordinateX { get; set; }
  179. /// <summary>
  180. /// 0x0D5C 3*2 bytes (signed) coordinate Y (party1, party2, party3)
  181. /// </summary>
  182. public short[] CoordinateY { get; set; }
  183. /// <summary>
  184. /// 0x0CE4 4 bytes Countdown
  185. /// </summary>
  186. public uint Countdown { get; set; }
  187. /// <summary>
  188. /// 0x0058 4 bytes Preview: Current Disk (0 based)
  189. /// </summary>
  190. public uint CurrentDisk { get; set; }
  191. /// <summary>
  192. /// 0x0D52 2 bytes Current field
  193. /// </summary>
  194. public ushort CurrentField { get; set; }
  195. /// <summary>
  196. /// 0x005C 4 bytes Preview: Current save (last saved game)
  197. /// </summary>
  198. public uint CurrentSave { get; set; }
  199. /// <summary>
  200. /// 0x0D68 3*1 bytes Direction (party1, party2, party3)
  201. /// </summary>
  202. public byte[] Direction { get; set; }
  203. /// <summary>
  204. /// Time since loaded
  205. /// </summary>
  206. public TimeSpan ElapsedTimeSinceLoad => (Memory.TotalGameTime - LoadTime);
  207. /// <summary>
  208. /// 0x0D70 256 + 1024 bytes Field vars
  209. /// </summary>
  210. /// <see cref="http://wiki.ffrtt.ru/index.php/FF8/Variables"/>
  211. public FieldVars FieldVars { get; set; }
  212. /// <summary>
  213. /// 0x0006 2 bytes Preview: 1st character's current HP
  214. /// </summary>
  215. public ushort FirstCharactersCurrentHp { get; set; }
  216. /// <summary>
  217. /// 0x0024 1 byte Preview: 1st character's level
  218. /// </summary>
  219. public byte FirstCharactersLevel { get; set; }
  220. /// <summary>
  221. /// 0x0008 2 bytes Preview: 1st character's max HP
  222. /// </summary>
  223. public ushort FirstCharactersMaxHp { get; set; }
  224. /// <summary>
  225. /// <para>0x0CE0 4 bytes Game time</para>
  226. /// <para>unsure if this is a duplicate of Time played or something.</para>
  227. /// </summary>
  228. public TimeSpan GameTime { get; private set; }
  229. /// <summary>
  230. /// 0x0060 - 0x049F 68 bytes each 16 of them. Guardian Forces: Quetzalcoatl-Eden
  231. /// </summary>
  232. public IReadOnlyDictionary<GFs, GFData> GFs => _gfs;
  233. /// <summary>
  234. /// 0x0B0C 12 bytes Griever name (FF8 text format)
  235. /// </summary>
  236. public FF8String GrieverName { get; set; }
  237. /// <summary>
  238. /// <para>0x0B54 396 bytes Items 198 items (Item ID and Quantity)</para>
  239. /// <para>
  240. /// The order of items out of battle and Each item uses 2 bytes 1 for ID and 1 for Quantity
  241. /// </para>
  242. /// </summary>
  243. public List<Item> Items { get; private set; }
  244. /// <summary>
  245. /// <para>0x0B34 32 bytes Items battle order</para>
  246. /// <para>Only the items that can be used in battle</para>
  247. /// <para>The order they appear in the battle item menu.</para>
  248. /// </summary>
  249. public byte[] ItemsBattleOrder { get; set; }
  250. /// <summary>
  251. /// 0x0B08 4 bytes Known weapons
  252. /// </summary>
  253. public BitArray KnownWeapons { get; set; }
  254. /// <summary>
  255. /// <para>0x0B2A 1 byte Limit Break Angelo completed</para>
  256. /// <para>Each bit is a completely learned ability</para>
  257. /// </summary>
  258. public Angelo LimitBreakAngeloCompleted { get; set; }
  259. /// <summary>
  260. /// <para>0x0B2B 1 byte Limit Break Angelo known</para>
  261. /// <para>Each bit is an ability is known about/able to be learned</para>
  262. /// </summary>
  263. public Angelo LimitBreakAngeloKnown { get; set; }
  264. /// <summary>
  265. /// <para>0x0B2C 8 bytes Limit Break Angelo points</para>
  266. /// <para>Each byte is progress to learning an ability</para>
  267. /// </summary>
  268. public Dictionary<Angelo, byte> LimitBreakAngeloPoints { get; set; }
  269. public BitArray LimitBreakIrvineUnlockedShot { get; set; }
  270. /// <summary>
  271. /// <para>0x0B24 2 bytes Limit Break Quistis</para>
  272. /// <para>Each bit is an unlocked blue magic spell</para>
  273. /// </summary>
  274. public BitArray LimitBreakQuistisUnlockedBlueMagic { get; set; }
  275. /// <summary>
  276. /// <para>0x0B29 1 byte Limit Break Selphie</para>
  277. /// <para>I think this sets bits when a rare spell is used.</para>
  278. /// </summary>
  279. public BitArray LimitBreakSelphieUsedRareSpells { get; set; }
  280. /// <summary>
  281. /// <para>0x0B26 2 bytes Limit Break Zell</para>
  282. /// <para>Each bit is a combo/attack unlocked. So it shows on screen.</para>
  283. /// <para>You can use the combo if you know it.</para>
  284. /// </summary>
  285. public BitArray LimitBreakZellUnlockedDuel { get; set; }
  286. /// <summary>
  287. /// xna GameTime when loaded
  288. /// </summary>
  289. public TimeSpan LoadTime { get; set; }
  290. /// <summary>
  291. /// 0x0004 2 bytes Preview: Location ID
  292. /// </summary>
  293. public ushort LocationID { get; set; }
  294. /// <summary>
  295. /// 0x0D50 2 bytes Module (1= field, 2= world map, 3= battle)
  296. /// </summary>
  297. public ushort Module { get; set; }
  298. public IEnumerable<CharacterData> NonPartyMembers => _characters == null
  299. ? null
  300. : (from i in _characters where !PartyData.Contains(i.Key) && i.Value.Available select i.Value);
  301. /// <summary>
  302. /// 0x0D6B 1 byte Padding
  303. /// </summary>
  304. public byte Padding { get; set; }
  305. /// <summary>
  306. /// 0x0025-0x0027 1 byte Preview: 1st-3rd character's portrait; 0xFF = blank;
  307. /// </summary>
  308. public List<Characters> Party { get; set; }
  309. /// <summary>
  310. /// 0x0D48 4 bytes Party (last byte always = 255)
  311. /// </summary>
  312. public Characters[] Party2 { get; set; }
  313. /// <summary>
  314. /// 0x0B04 4 bytes Party (0xFF terminated and/or blank)
  315. /// </summary>
  316. public List<Characters> PartyData { get; set; }
  317. /// <summary>
  318. /// 0x0D54 2 bytes Previous field
  319. /// </summary>
  320. public ushort PreviousField { get; set; }
  321. /// <summary>
  322. /// 0x0034 12 bytes Preview: Rinoa's name (0x00 terminated)
  323. /// </summary>
  324. public FF8String RinoaName { get; set; }
  325. /// <summary>
  326. /// 0x000A 2 bytes Preview: save Count
  327. /// </summary>
  328. public ushort SaveCount { get; set; }
  329. /// <summary>
  330. /// 0x0D43 1 byte SeeD test level
  331. /// </summary>
  332. public byte SeeDTestLevel { get; set; }
  333. /// <summary>
  334. /// 0x0960-0x0AEF 400 total bytes 20 bytes each 20 of them. Shops
  335. /// </summary>
  336. public IReadOnlyList<Shop> Shops => _shops;
  337. public bool SmallTeam =>
  338. _characters == null ||
  339. _characters.All(i => PartyData.Contains(i.Key) || !i.Value.Available);
  340. /// <summary>
  341. /// 0x0028 12 bytes Preview: Squall's name (0x00 terminated)
  342. /// </summary>
  343. public FF8String SquallName { get; set; }
  344. public bool TeamLaguna => Party != null && (Party[0] == OpenVIII.Characters.Laguna_Loire || Party[1] == OpenVIII.Characters.Laguna_Loire || Party[2] == OpenVIII.Characters.Laguna_Loire);
  345. /// <summary>
  346. /// Stored playtime in seconds. Made into timespan for easy parsing.
  347. /// </summary>
  348. /// <remarks>0x0020 4 bytes Preview: Total number of seconds played</remarks>
  349. public TimeSpan TimePlayed { get; set; }
  350. /// <summary>
  351. /// 0x0D62 3*2 bytes Triangle ID (party1, party2, party3)
  352. /// </summary>
  353. public ushort[] TriangleID { get; set; }
  354. /// <summary>
  355. /// 0x12F0 128 bytes Triple Triad (TODO flush this out)
  356. /// </summary>
  357. public TripleTriad TripleTriad { get; set; }
  358. /// <summary>
  359. /// 0x0D33 16 bytes Tutorial infos
  360. /// </summary>
  361. public BitArray TutorialInfos { get; set; }
  362. /// <summary>
  363. /// 0x0B18 2 bytes Unknown (always 7966?)
  364. /// </summary>
  365. public ushort Unknown1 { get; set; }
  366. /// <summary>
  367. /// 0x0B1A 2 bytes Unknown
  368. /// </summary>
  369. public ushort Unknown2 { get; set; }
  370. /// <summary>
  371. /// 0x0CE8 4 bytes Unknown
  372. /// </summary>
  373. public uint Unknown3 { get; set; }
  374. /// <summary>
  375. /// 0x0CF0 2 bytes Unknown
  376. /// </summary>
  377. public ushort Unknown4 { get; set; }
  378. public uint Unknown5 { get; set; }
  379. /// <summary>
  380. /// 0x0D00 4 bytes Unknown
  381. /// </summary>
  382. public uint Unknown6 { get; set; }
  383. /// <summary>
  384. /// 0x0D44 4 bytes Unknown
  385. /// </summary>
  386. public uint Unknown7 { get; set; }
  387. /// <summary>
  388. /// 0x0D4C 4 bytes Unknown
  389. /// </summary>
  390. public uint Unknown8 { get; set; }
  391. /// <summary>
  392. /// 0x0D6C 4 bytes Unknown
  393. /// </summary>
  394. public uint Unknown9 { get; set; }
  395. /// <summary>
  396. /// 0x1270 128 bytes World map
  397. /// </summary>
  398. public Worldmap WorldMap { get; set; }
  399. #endregion Properties
  400. #region Indexers
  401. public GFData this[GFs id] => GetDamageable(id);
  402. public CharacterData this[Characters id] => GetDamageable(id);
  403. public Damageable this[Faces.ID id] => GetDamageable(id);
  404. #endregion Indexers
  405. #region Methods
  406. public static Data LoadInitOut()
  407. {
  408. Memory.Log.WriteLine($"{nameof(Saves)} :: {nameof(Data)} :: {nameof(LoadInitOut)} ");
  409. var aw = ArchiveWorker.Load(Memory.Archives.A_MAIN, true);
  410. var buffer = aw.GetBinaryFile("init.out");
  411. if (buffer != null && buffer.Length >0)
  412. {
  413. using (var br = new BinaryReader(new MemoryStream(buffer)))
  414. {
  415. var data = new Data();
  416. data.ReadInitOut(br);
  417. return data;
  418. }
  419. }
  420. return null;
  421. }
  422. /// <summary>
  423. /// preforms a Shadow Copy. Then does deep copy on any required objects.
  424. /// </summary>
  425. /// <returns></returns>
  426. public Data Clone()
  427. {
  428. Memory.Log.WriteLine($"{nameof(Saves)} :: {nameof(Data)} :: {nameof(Clone)} ");
  429. //shadow copy
  430. var d = (Data)MemberwiseClone();
  431. //deep copy anything that needs it here.
  432. d._characters = _characters.ToDictionary(x => x.Key, x => (CharacterData)x.Value.Clone());
  433. d._gfs = _gfs.ToDictionary(x => x.Key, x => (GFData)x.Value.Clone());
  434. d.ChocoboWorld = ChocoboWorld.Clone();
  435. d.FieldVars = FieldVars.Clone();
  436. d.WorldMap = WorldMap.Clone();
  437. d.TripleTriad = TripleTriad.Clone();
  438. d.LimitBreakAngeloPoints = LimitBreakAngeloPoints.ToDictionary(x => x.Key, x => x.Value);
  439. d._shops = _shops.Select(x => x.Clone()).ToList();
  440. d.Items = Items.Select(x => x.Clone()).ToList();
  441. return d;
  442. }
  443. //public Damageable this[Damageable damageable]
  444. /// <summary>
  445. /// return -1 on error
  446. /// </summary>
  447. /// <param name="id"></param>
  448. /// <param name="character"></param>
  449. /// <param name="gf"></param>
  450. /// <returns></returns>
  451. public int CurrentHP(Faces.ID id = Faces.ID.Blank, Characters character = OpenVIII.Characters.Blank, GFs gf = OpenVIII.GFs.Blank)
  452. {
  453. if (character == OpenVIII.Characters.Blank)
  454. character = id.ToCharacters();
  455. if (gf == OpenVIII.GFs.Blank)
  456. gf = id.ToGFs();
  457. var hp = (this[character]?.CurrentHP() ?? -1);
  458. hp = (hp < 0 && GFs.ContainsKey(gf) ? GFs[gf].CurrentHP() : hp);
  459. return hp;
  460. }
  461. /// <summary>
  462. /// How many dead characters there are.
  463. /// </summary>
  464. /// <returns>&gt;=0</returns>
  465. public int DeadCharacters() =>
  466. _characters?.Count(m =>
  467. m.Value.Available && m.Value.IsDead) ?? 0;
  468. /// <summary>
  469. /// How many dead party members there are.
  470. /// </summary>
  471. /// <returns>&gt;=0</returns>
  472. public int DeadPartyMembers() =>
  473. PartyData?.Count(m => m != OpenVIII.Characters.Blank && this[m].IsDead) ?? 0;
  474. public ConcurrentQueue<GFs> EarnAP(uint ap, out ConcurrentQueue<KeyValuePair<GFs, Abilities>> abilities)
  475. {
  476. var ret = new ConcurrentQueue<GFs>();
  477. abilities = null;
  478. if (GFs == null) return ret;
  479. abilities = new ConcurrentQueue<KeyValuePair<GFs, Abilities>>();
  480. foreach (var g in GFs.Where(i => i.Value != null && i.Value.Exists))
  481. {
  482. if (!g.Value.EarnExp(ap, out var ability)) continue;
  483. if (ability != Abilities.None)
  484. {
  485. abilities.Enqueue(new KeyValuePair<GFs, Abilities>(g.Key, ability));
  486. }
  487. ret.Enqueue(g.Key);
  488. }
  489. return ret;
  490. }
  491. public bool EarnItem(Cards.ID card, byte qty, byte location = 0)
  492. {
  493. var i = new TTCardInfo { Unlocked = true, Qty = qty, Location = location };
  494. if (!TripleTriad.cards.TryAdd(card, i))
  495. {
  496. TripleTriad.cards[card].Unlocked = i.Unlocked;
  497. TripleTriad.cards[card].Qty += i.Qty;
  498. TripleTriad.cards[card].Location = i.Location;
  499. return true;
  500. }
  501. return false;
  502. }
  503. public bool EarnItem(KeyValuePair<Cards.ID, byte> keyValuePair, byte location = 0) => EarnItem(keyValuePair.Key, keyValuePair.Value, location);
  504. public Item EarnItem(byte iD, int qty)
  505. {
  506. if (Items.Any(i => i.ID == iD) && qty != 0)
  507. {
  508. var k = Items.FindIndex(item => item.ID == iD);
  509. if (qty < 0)
  510. return Items[k] = Items[k].Add(checked((sbyte)qty));
  511. if (qty > 0)
  512. return Items[k] = Items[k].Add(checked((byte)qty));
  513. }
  514. else if (Items.Any(i => i.ID == 0) && qty > 0)
  515. {
  516. var k = Items.FindIndex(item => item.ID == 0);
  517. return Items[k] = Items[k].Add(checked((byte)qty), iD);
  518. }
  519. return default;
  520. }
  521. public Item EarnItem(Item item) => EarnItem(item.ID, item.QTY);
  522. public Dictionary<GFs, Characters> JunctionedGFs()
  523. {
  524. var r = new Dictionary<GFs, Characters>();
  525. if (_characters == null) return r;
  526. foreach (var c in _characters)
  527. {
  528. foreach (var gf in c.Value.JunctionedGFs)
  529. if(!r.ContainsKey(gf) && gf >= OpenVIII.GFs.Quezacotl && gf <= OpenVIII.GFs.Eden )
  530. r.Add(gf, c.Value.ID);
  531. }
  532. return r;
  533. }
  534. public bool MaxGFAbilities(GFs gf) => GFs.ContainsKey(gf) && GFs[gf].MaxGFAbilities;
  535. public bool PartyHasAbility(Abilities a)
  536. {
  537. if (PartyData == null) return false;
  538. foreach (var c in PartyData)
  539. {
  540. if (_characters.TryGetValue(c, out var cd) && cd.Abilities.Contains(a))
  541. return true;
  542. }
  543. return false;
  544. }
  545. public void Read(BinaryReader br)
  546. {
  547. //Read Data
  548. LocationID = br.ReadUInt16();//0x0004
  549. FirstCharactersCurrentHp = br.ReadUInt16();//0x0006
  550. FirstCharactersMaxHp = br.ReadUInt16();//0x0008
  551. SaveCount = br.ReadUInt16();//0x000A
  552. _amountOfGilPreview = br.ReadUInt32();//0x000C
  553. TimePlayed = new TimeSpan(0, 0, checked((int)br.ReadUInt32()));//0x0020
  554. FirstCharactersLevel = br.ReadByte();//0x0024
  555. Party = Array.ConvertAll(br.ReadBytes(3), item => (Characters)item).ToList();//0x0025//0x0026//0x0027 0xFF = blank.
  556. SquallName = br.ReadBytes(12);//0x0028
  557. RinoaName = br.ReadBytes(12);//0x0034
  558. AngeloName = br.ReadBytes(12);//0x0040
  559. BokoName = br.ReadBytes(12);//0x004C
  560. CurrentDisk = br.ReadUInt32();//0x0058
  561. CurrentSave = br.ReadUInt32();//0x005C
  562. ReadInitOut(br);
  563. GameTime = new TimeSpan(0, 0, (int)br.ReadUInt32());//0x0CE0
  564. Countdown = br.ReadUInt32();//0x0CE4
  565. Unknown3 = br.ReadUInt32();//0x0CE8
  566. BattleVictoryCount = br.ReadUInt32();//0x0CEC
  567. Unknown4 = br.ReadUInt16();//0x0CF0
  568. BattleEscapeCount = br.ReadUInt16();//0x0CF2
  569. Unknown5 = br.ReadUInt32();//0x0CF4
  570. BattleTonberryKilledCount = br.ReadUInt32();//0x0CF8
  571. BattleTonberryKingKilled = br.ReadUInt32() > 0;//0x0CFC (yeah, this is a boolean)
  572. Unknown6 = br.ReadUInt32();//0x0D00
  573. BattleR1 = br.ReadUInt32();//0x0D04 First bug battle (R1 tip)
  574. BattleElemental = br.ReadUInt32();//0x0D08 First "Bomb" battle (Elemental tip)
  575. BattleMental = br.ReadUInt32();//0x0D0C  First "T-Rex" battle (Mental tip)
  576. BattleIrvine = br.ReadUInt32();//0x0D10 First "Irvine" battle (Irvine's limit break tip)
  577. BattleMagic = new BitArray(br.ReadBytes(8));//0x0D14 Magic drawn once
  578. BattleScan = new BitArray(br.ReadBytes(20));//0x0D1C Enemy scanned once
  579. BattleRenzokukenAuto = br.ReadByte() > 0;//0x0D30 Renzokuken auto
  580. BattleRenzokukenIndicator = br.ReadByte() > 0;//0x0D31 Renzokuken indicator
  581. BattleMiscFlags = (BattleMiscFlags)br.ReadByte();//0x0D32 dream/Odin/Phoenix/Gilgamesh/Angelo disabled/Angel Wing enabled/???/???
  582. TutorialInfos = new BitArray(br.ReadBytes(16));//0x0D33
  583. SeeDTestLevel = br.ReadByte();//0x0D43
  584. Unknown7 = br.ReadUInt32();//0x0D44
  585. Party2 = Array.ConvertAll(br.ReadBytes(4), item => (Characters)item); //0x0D48 (last byte always = 255) //duplicate?
  586. Unknown8 = br.ReadUInt32();//0x0D4C
  587. Module = br.ReadUInt16();//0x0D50 (1= field, 2= world map, 3= battle)
  588. CurrentField = br.ReadUInt16();//0x0D52
  589. PreviousField = br.ReadUInt16();//0x0D54
  590. for (var i = 0; i < 3; i++)
  591. CoordinateX[i] = br.ReadInt16();//0x0D56 signed (party1, party2, party3)
  592. for (var i = 0; i < 3; i++)
  593. CoordinateY[i] = br.ReadInt16();//0x0D5C signed (party1, party2, party3)
  594. for (var i = 0; i < 3; i++)
  595. TriangleID[i] = br.ReadUInt16();//0x0D62 (party1, party2, party3)
  596. Direction = br.ReadBytes(3 * 1);//0x0D68 (party1, party2, party3)
  597. Padding = br.ReadByte();//0x0D6B
  598. Unknown9 = br.ReadUInt32();//0x0D6C
  599. FieldVars.Read(br); //0x0D70 http://wiki.ffrtt.ru/index.php/FF8/Variables
  600. WorldMap.Read(br);//br.ReadBytes(128);//0x1270
  601. TripleTriad.Read(br); //br.ReadBytes(128);//0x12F0
  602. ChocoboWorld.Read(br); //br.ReadBytes(64);//0x1370
  603. }
  604. /// <summary>
  605. /// Read Init.Out
  606. /// </summary>
  607. /// <see cref="https://github.com/alexfilth/quezacotl/blob/master/Quezacotl/InitWorker.cs"/>
  608. public void ReadInitOut(BinaryReader br)
  609. {
  610. GetNames();
  611. //init offset 0;
  612. for (byte i = 0; i <= (int)OpenVIII.GFs.Eden; i++)
  613. {
  614. _gfs.Add((GFs)i, GFData.Load(br, (GFs)i, this));
  615. }
  616. //init offset 1088;
  617. for (byte i = 0; i <= (int)OpenVIII.Characters.Edea_Kramer; i++)
  618. {
  619. var tmp = CharacterData.Load(br, (Characters)i, this);
  620. tmp.Name = Memory.Strings.GetName((Characters)i, this);
  621. _characters.Add((Characters)i, tmp);// 0x04A0 -> 0x08C8 //152 bytes per 8 total
  622. }
  623. //init offset 2304
  624. for (var i = 0; i < _shops.Capacity; i++)
  625. _shops.Add(new Shop(br));//0x0960 //400 bytes
  626. //init offset 2704
  627. //Configuration = br.ReadBytes(20); //0x0AF0 //20 bytes TODO break this up into a structure or class.
  628. Configuration = new Configuration(br);
  629. PartyData = Array.ConvertAll(br.ReadBytes(4), item => (Characters)item).ToList(); //0x0B04 // 4 bytes 0xFF terminated.
  630. if (Party == null) Party = PartyData.Take(3).ToList();
  631. KnownWeapons = new BitArray(br.ReadBytes(4)); //0x0B08 // 4 bytes
  632. GrieverName = br.ReadBytes(12); //0x0B0C // 12 bytes
  633. if (string.IsNullOrWhiteSpace(GrieverName)) GrieverName = Memory.Strings.GetName(Faces.ID.Griever);
  634. Unknown1 = br.ReadUInt16();//0x0B18 (always 7966?)
  635. Unknown2 = br.ReadUInt16();//0x0B1A
  636. _amountOfGil = br.ReadUInt32();//0x0B1C //dupilicate
  637. // ReSharper disable once RedundantCheckBeforeAssignment //sometimes this is set sometimes it isn't.
  638. if (_amountOfGilPreview != _amountOfGil) _amountOfGilPreview = _amountOfGil;
  639. _amountOfGilLaguna = br.ReadUInt32();//0x0B20
  640. LimitBreakQuistisUnlockedBlueMagic = new BitArray(br.ReadBytes(2));//0x0B24
  641. LimitBreakZellUnlockedDuel = new BitArray(br.ReadBytes(2));//0x0B26
  642. LimitBreakIrvineUnlockedShot = new BitArray(br.ReadBytes(1));//0x0B28
  643. LimitBreakSelphieUsedRareSpells = new BitArray(br.ReadBytes(1));//0x0B29
  644. LimitBreakAngeloCompleted = (Angelo)br.ReadByte();//0x0B2A
  645. LimitBreakAngeloKnown = (Angelo)br.ReadByte();//0x0B2B
  646. LimitBreakAngeloPoints = br.ReadBytes(8).Select((value, key) => new { key, value }).ToDictionary(x => (Angelo)(x.key + 1), x => x.value);//0x0B2C
  647. ItemsBattleOrder = br.ReadBytes(32);//0x0B34
  648. //Init offset 2804
  649. for (var i = 0; br.BaseStream.Position + 2 <= br.BaseStream.Length && i < Items.Capacity; i++)
  650. Items.Add(new Item(br.ReadByte(), br.ReadByte())); //0x0B54 198 items (Item ID and Quantity)
  651. }
  652. /// <summary>
  653. /// List of all Unlocked GFs
  654. /// </summary>
  655. public IEnumerable<GFs> UnlockedGFs => GFs.Where(x => x.Value.Exists).Select(x => x.Key);
  656. public bool AnyDeadPartyMembers => PartyData.Select(x => this[x]).Any(x => x != null && x.IsDead);
  657. public bool AnyCriticalPartyMembers => PartyData.Select(x => this[x]).Any(x => x != null && x.IsCritical);
  658. private CharacterData GetDamageable(Characters id)
  659. {
  660. if (!Enum.IsDefined(typeof(Characters), id) || id == OpenVIII.Characters.Blank)
  661. return null;
  662. CharacterData c = null;
  663. if (_characters == null || CharactersCount <= 0 || Party == null || PartyData == null ||
  664. _characters.TryGetValue(id, out c)) return c;
  665. var ind = Party.FindIndex(x => x.Equals(id));
  666. if (ind == -1 || !_characters.TryGetValue(PartyData[ind], out c))
  667. throw new ArgumentException($"{this}::Cannot find {id} in CharacterData or Party");
  668. return c;
  669. }
  670. private GFData GetDamageable(GFs id) => GFs.ContainsKey(id) ? GFs[id] : null;
  671. private Damageable GetDamageable(Faces.ID id)
  672. {
  673. var gf = id.ToGFs();
  674. var c = id.ToCharacters();
  675. if (c == OpenVIII.Characters.Blank)
  676. return GetDamageable(gf);
  677. return GetDamageable(c);
  678. }
  679. private void GetNames()
  680. {
  681. if (string.IsNullOrWhiteSpace(SquallName)) SquallName = Memory.Strings.GetName(Faces.ID.Squall_Leonhart);
  682. if (string.IsNullOrWhiteSpace(RinoaName)) RinoaName = Memory.Strings.GetName(Faces.ID.Rinoa_Heartilly);
  683. if (string.IsNullOrWhiteSpace(AngeloName)) AngeloName = Memory.Strings.GetName(Faces.ID.Angelo);
  684. if (string.IsNullOrWhiteSpace(BokoName)) BokoName = Memory.Strings.GetName(Faces.ID.Boko);
  685. }
  686. #endregion Methods
  687. }
  688. #endregion Classes
  689. }
  690. }