Saves.Data.cs 36 KB

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