Commands.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. using Microsoft.Xna.Framework;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Linq;
  6. namespace OpenVIII.IGMData
  7. {
  8. public class Commands : Base, IDisposable
  9. {
  10. #region Fields
  11. private bool disposedValue = false;
  12. private bool EventAdded;
  13. private Kernel.BattleCommand[] commands;
  14. private Battle.Dat.Abilities[] enemycommands;
  15. private int nonbattleWidth;
  16. private sbyte page = 0;
  17. private bool skipRefresh;
  18. private static int s_cidoff = 0;
  19. #endregion Fields
  20. #region Properties
  21. //Selphie_Slots
  22. private SelphieSlots Selphie_Slots { get => (SelphieSlots)ITEM[Offsets.Selphie_Slots, 0]; set => ITEM[Offsets.Selphie_Slots, 0] = value; }
  23. private Pool.GF GFPool { get => (Pool.GF)ITEM[Offsets.GF_Pool, 0]; set => ITEM[Offsets.GF_Pool, 0] = value; }
  24. private Pool.Item ItemPool { get => (Pool.Item)ITEM[Offsets.Item_Pool, 0]; set => ITEM[Offsets.Item_Pool, 0] = value; }
  25. private Pool.Magic MagPool { get => (Pool.Magic)ITEM[Offsets.Mag_Pool, 0]; set => ITEM[Offsets.Mag_Pool, 0] = value; }
  26. private Pool.BlueMagic BluePool { get => (Pool.BlueMagic)ITEM[Offsets.Blue_Pool, 0]; set => ITEM[Offsets.Blue_Pool, 0] = value; }
  27. private Pool.Combine CombinePool { get => (Pool.Combine)ITEM[Offsets.Combine_Pool, 0]; set => ITEM[Offsets.Combine_Pool, 0] = value; }
  28. private Pool.Bullet BulletPool { get => (Pool.Bullet)ITEM[Offsets.Bullet_Pool, 0]; set => ITEM[Offsets.Bullet_Pool, 0] = value; }
  29. private IGMDataItem.Icon LimitArrow { get => (IGMDataItem.Icon)ITEM[Offsets.Limit_Arrow, 0]; set => ITEM[Offsets.Limit_Arrow, 0] = value; }
  30. public IGMData.Target.Group TargetGroup
  31. {
  32. get => (IGMData.Target.Group)ITEM[Offsets.Targets_Window, 0];
  33. protected set => ITEM[Offsets.Targets_Window, 0] = value;
  34. }
  35. public IGMData.Pool.EnemyAttacks EnemyAttacks
  36. {
  37. get => ((IGMData.Pool.EnemyAttacks)ITEM[Offsets.Enemy_Attacks_Pool, 0]);
  38. protected set => ITEM[Offsets.Enemy_Attacks_Pool, 0] = value;
  39. }
  40. private static class Offsets
  41. {
  42. public const int
  43. Limit_Arrow = 4,
  44. Start = 5,
  45. Blue_Pool = Start,
  46. Mag_Pool = 6,
  47. GF_Pool = 7,
  48. Enemy_Attacks_Pool = 8,
  49. Item_Pool = 9,
  50. Targets_Window = 10,
  51. Combine_Pool = 11,
  52. Selphie_Slots = 12,
  53. Bullet_Pool = 13,
  54. Count = 14;
  55. }
  56. //base.Inputs_CANCEL();
  57. private static int Cidoff
  58. {
  59. get => s_cidoff; set
  60. {
  61. if (value >= Memory.KernelBin.BattleCommands.Count)
  62. value = 0;
  63. else if (value < 0)
  64. value = Memory.KernelBin.BattleCommands.Count - 1;
  65. s_cidoff = value;
  66. }
  67. }
  68. #endregion Properties
  69. #region Methods
  70. // To detect redundant calls
  71. protected virtual void Dispose(bool disposing)
  72. {
  73. if (!disposedValue)
  74. {
  75. if (disposing)
  76. {
  77. // TODO: dispose managed state (managed objects).
  78. }
  79. // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
  80. // TODO: set large fields to null.
  81. disposedValue = true;
  82. UnsubscribeEvents();
  83. GC.SuppressFinalize(this);
  84. }
  85. }
  86. // This code added to correctly implement the disposable pattern.
  87. public void Dispose() =>
  88. // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  89. Dispose(true);
  90. public static Commands Create(Rectangle pos, Damageable damageable = null, bool battle = false)
  91. {
  92. var r = new Commands
  93. {
  94. skipRefresh = damageable == null,
  95. Battle = battle
  96. };
  97. r.SetDamageable(damageable, null);
  98. r.Init(Offsets.Count, 1, new IGMDataItem.Box { Pos = pos, Title = Icons.ID.COMMAND }, 1, 4);
  99. return r;
  100. }
  101. public override bool Inputs()
  102. {
  103. var ret = false;
  104. var found = false;
  105. //loop through Start to Count
  106. //This is to only check for input from the dialogs that popup.
  107. for (var i = Offsets.Start; i < Offsets.Count; i++)
  108. {
  109. if (InputITEM(ITEM[i, 0], ref ret))
  110. {
  111. found = true;
  112. break;
  113. }
  114. }
  115. if (!found)
  116. {
  117. Cursor_Status |= Cursor_Status.Enabled;
  118. Cursor_Status &= ~Cursor_Status.Blinking;
  119. ret = base.Inputs();
  120. }
  121. return ret;
  122. }
  123. public override bool Inputs_CANCEL() => false;
  124. public override void Inputs_Left()
  125. {
  126. if (Battle && CURSOR_SELECT == 0 && CrisisLevel > -1)
  127. {
  128. if (page == 1)
  129. {
  130. Refresh();
  131. skipsnd = true;
  132. base.Inputs_Left();
  133. }
  134. }
  135. }
  136. public override bool Inputs_OKAY()
  137. {
  138. var c = commands[CURSOR_SELECT];
  139. if (c == null) return false;
  140. base.Inputs_OKAY();
  141. TargetGroup.SelectTargetWindows(c);
  142. if (c.BattleID == 1 && Damageable != null && Damageable.GetEnemy(out var e))
  143. {
  144. var ecattacks = e.Abilities.Where(x => x.Monster != null);
  145. if (ecattacks.Count() == 1)
  146. {
  147. var monster = ecattacks.First().Monster;
  148. TargetGroup.SelectTargetWindows(monster);
  149. TargetGroup.ShowTargetWindows();
  150. }
  151. else
  152. {
  153. EnemyAttacks.Refresh();
  154. EnemyAttacks.Show();
  155. }
  156. return true;
  157. }
  158. else
  159. switch (c.BattleID)
  160. {
  161. default:
  162. // ITEM[Targets_Window, 0].Show();
  163. throw new ArgumentOutOfRangeException($"{this}::Command ({c.Name}, {c.BattleID}) doesn't have explict operation defined!");
  164. case 1: //ATTACK
  165. case 5: //Renzokuken
  166. case 12: //MUG
  167. case 6: //DRAW
  168. case 29: //CARD
  169. case 32: //ABSORB
  170. case 28: //DARKSIDE
  171. case 30: //DOOM
  172. case 26: //RECOVER
  173. case 27: //REVIVE
  174. case 33: //LVL DOWN
  175. case 34: //LVL UP
  176. case 31: //Kamikaze
  177. case 38: //MiniMog
  178. case 24: //MADRUSH
  179. case 25: //Treatment
  180. case 20: //Desperado
  181. case 21: //Blood Pain
  182. case 22: //Massive Anchor
  183. case 7: //DEVOUR
  184. case 11: //DUEL
  185. case 17: //FIRE CROSS / NO MERCY
  186. case 18: //SORCERY / ICE STRIKE
  187. TargetGroup.ShowTargetWindows();
  188. return true;
  189. case 16: // SLOT
  190. //TODO add slot menu to randomly choose spell to cast.
  191. Selphie_Slots.Show();
  192. Selphie_Slots.Refresh();
  193. return true;
  194. case 14: //SHOT
  195. BulletPool.Show();
  196. BulletPool.Refresh();
  197. return true;
  198. case 19: //COMBINE (ANGELO or ANGEL WING)
  199. //TODO see if ANGEL WING unlock if so show menu to choose angelo or angel wing.
  200. //TargetGroup.ShowTargetWindows();
  201. CombinePool.Show();
  202. CombinePool.Refresh();
  203. return true;
  204. case 0: //null
  205. case 9: //CAST is part of DRAW menu.
  206. case 10: // Stock is part of DRAW menu.
  207. case 8: // NOMSG
  208. case 13: // NOMSG
  209. return false;
  210. case 36: //DOUBLE
  211. case 37: //TRIPLE
  212. //TODO 2 casts 2 targets
  213. //TODO 3 casts 3 targets
  214. MagPool.Show();
  215. MagPool.Refresh();
  216. return true;
  217. case 2: //magic
  218. //TODO ADD a menu for DOUBLE and TRIPLE to choose SINGLE DOUBLE OR TRIPLE
  219. case 35: //SINGLE
  220. MagPool.Show();
  221. MagPool.Refresh();
  222. return true;
  223. case 3: //GF
  224. GFPool.Show();
  225. GFPool.Refresh();
  226. return true;
  227. case 4: //items
  228. ItemPool.Show();
  229. ItemPool.Refresh();
  230. return true;
  231. case 15: //Blue magic
  232. BluePool.Show();
  233. BluePool.Refresh();
  234. return true;
  235. case 23: //Defend
  236. Debug.WriteLine($"{Damageable.Name} is using {c.Name}({c.BattleID})");
  237. Damageable.EndTurn();
  238. return true;
  239. }
  240. }
  241. public override void Inputs_Right()
  242. {
  243. if (Battle && CURSOR_SELECT == 0 && CrisisLevel > -1)
  244. {
  245. if (page == 0 && Damageable.GetCharacterData(out var c))
  246. {
  247. commands[CURSOR_SELECT] = c.CharacterStats.Limit;
  248. ((IGMDataItem.Text)ITEM[0, 0]).Data = commands[CURSOR_SELECT].Name;
  249. skipsnd = true;
  250. base.Inputs_Right();
  251. page++;
  252. LimitArrow.Hide();
  253. }
  254. }
  255. }
  256. /// <summary>
  257. /// Things that may of changed before screen loads or junction is changed.
  258. /// </summary>
  259. public override void Refresh()
  260. {
  261. if (Damageable != null)
  262. {
  263. if (!skipRefresh)
  264. {
  265. if (Battle)
  266. {
  267. if ((Damageable.IsGameOver || !Damageable.GetBattleMode().Equals(Damageable.BattleMode.YourTurn)))
  268. {
  269. Hide();
  270. goto end;
  271. }
  272. else
  273. Show();
  274. }
  275. if (Damageable.GetEnemy(out var e))
  276. {
  277. enemycommands = e.Abilities;
  278. var pos = 0;
  279. bool Item, Magic, Attack;
  280. Item = Magic = Attack = false;
  281. foreach (var a in enemycommands)
  282. {
  283. if (pos >= Rows) break;
  284. ((IGMDataItem.Text)ITEM[pos, 0]).Hide();
  285. BLANKS[pos] = true;
  286. if (a.Item != null)
  287. {
  288. Item = true;
  289. //((IGMDataItem.Text)ITEM[pos, 0]).Data = a.ITEM.Value.Name;
  290. //((IGMDataItem.Text)ITEM[pos, 0]).Show();
  291. //BLANKS[pos] = false;
  292. }
  293. else if (a.Magic != null)
  294. {
  295. Magic = true;
  296. //((IGMDataItem.Text)ITEM[pos, 0]).Data = a.MAGIC.Name;
  297. //((IGMDataItem.Text)ITEM[pos, 0]).Show();
  298. //BLANKS[pos] = false;
  299. }
  300. else if (a.Monster != null)
  301. {
  302. Attack = true;
  303. //((IGMDataItem.Text)ITEM[pos, 0]).Data = a.MONSTER.Name;
  304. //((IGMDataItem.Text)ITEM[pos, 0]).Show();
  305. //BLANKS[pos] = false;
  306. }
  307. //((IGMDataItem.Text)ITEM[pos, 0]).Pos = SIZE[pos];
  308. //pos++;
  309. }
  310. if (Attack)
  311. {
  312. var ecattacks = enemycommands.Where(x => x.Monster != null);
  313. AddCommand(Memory.KernelBin.BattleCommands[1], (ecattacks.Count() == 1 ? ecattacks.First().Monster.Name : null));
  314. }
  315. if (Magic || e.DrawList.Any(x => x.Data != null))
  316. AddCommand(Memory.KernelBin.BattleCommands[2]);
  317. if (Item || e.DropList.Any(x => x.Data?.Battle != null) || e.MugList.Any(x => x.Data?.Battle != null))
  318. AddCommand(Memory.KernelBin.BattleCommands[4]);
  319. if (e.JunctionedGFs?.Any() ?? false)
  320. AddCommand(Memory.KernelBin.BattleCommands[3]);
  321. void AddCommand(Kernel.BattleCommand c, FF8String alt = null)
  322. {
  323. commands[pos] = c;
  324. ((IGMDataItem.Text)ITEM[pos, 0]).Data = alt ?? c.Name;
  325. ((IGMDataItem.Text)ITEM[pos, 0]).Pos = SIZE[pos];
  326. ITEM[pos, 0].Show();
  327. BLANKS[pos] = false;
  328. pos++;
  329. }
  330. for (; pos < Rows; pos++)
  331. {
  332. ITEM[pos, 0].Hide();
  333. BLANKS[pos] = true;
  334. }
  335. }
  336. else if (Memory.State.Characters && Damageable.GetCharacterData(out var c))
  337. {
  338. if (Battle)
  339. c.GenerateCrisisLevel();
  340. var DataSize = Rectangle.Empty;
  341. page = 0;
  342. Cursor_Status &= ~Cursor_Status.Horizontal;
  343. commands[0] = Memory.KernelBin.BattleCommands[(c.Abilities.Contains(Kernel.Abilities.Mug) ? 12 : 1)];
  344. ITEM[0, 0] = new IGMDataItem.Text
  345. {
  346. Data = commands[0].Name,
  347. Pos = SIZE[0]
  348. };
  349. for (var pos = 1; pos < Rows; pos++)
  350. {
  351. var cmd = c.Commands[pos - 1];
  352. if (cmd != Kernel.Abilities.None)
  353. {
  354. if (!Memory.KernelBin.CommandAbilities.TryGetValue(cmd, out var cmdval))
  355. {
  356. continue;
  357. }
  358. #if DEBUG
  359. if (!Battle) commands[pos] = cmdval.BattleCommand;
  360. else commands[pos] = Memory.KernelBin.BattleCommands[Cidoff++];
  361. #else
  362. commands[pos] = cmdval.BattleCommand;
  363. #endif
  364. ((IGMDataItem.Text)ITEM[pos, 0]).Data = commands[pos].Name;
  365. ((IGMDataItem.Text)ITEM[pos, 0]).Pos = SIZE[pos];
  366. ITEM[pos, 0].Show();
  367. CheckBounds(ref DataSize, pos);
  368. BLANKS[pos] = false;
  369. }
  370. else
  371. {
  372. ITEM[pos, 0]?.Hide();
  373. BLANKS[pos] = true;
  374. }
  375. }
  376. const int crisisWidth = 294;
  377. if (Battle && CrisisLevel > -1)
  378. {
  379. CONTAINER.Width = crisisWidth;
  380. LimitArrow.Show();
  381. }
  382. else
  383. {
  384. CONTAINER.Width = nonbattleWidth;
  385. LimitArrow.Hide();
  386. }
  387. AutoAdjustContainerWidth(DataSize);
  388. }
  389. }
  390. end:
  391. skipRefresh = false;
  392. base.Refresh();
  393. }
  394. }
  395. public override void Refresh(Damageable damageable)
  396. {
  397. if (Damageable != damageable)
  398. skipRefresh = false;
  399. base.Refresh(damageable);
  400. }
  401. /// <summary>
  402. /// Things fixed at startup.
  403. /// </summary>
  404. protected override void Init()
  405. {
  406. base.Init();
  407. BLANKS[Offsets.Limit_Arrow] = true;
  408. for (var pos = 0; pos < Rows; pos++)
  409. ITEM[pos, 0] = new IGMDataItem.Text
  410. {
  411. Pos = SIZE[pos]
  412. };
  413. GFPool = Pool.GF.Create(new Rectangle(X + 50, Y - 20, 320, 192), Damageable, true);
  414. GFPool.Hide();
  415. BluePool = Pool.BlueMagic.Create(new Rectangle(X + 50, Y - 20, 300, 192), Damageable, true);
  416. BluePool.Hide();
  417. MagPool = Pool.Magic.Create(new Rectangle(X + 50, Y - 20, 300, 192), Damageable, true);
  418. MagPool.Hide();
  419. ItemPool = Pool.Item.Create(new Rectangle(X + 50, Y - 22, 400, 194), Damageable, true);
  420. ItemPool.Hide();
  421. EnemyAttacks = Pool.EnemyAttacks.Create(new Rectangle(X + 50, Y - 22, 400, 194), Damageable, true);
  422. EnemyAttacks.Hide();
  423. CombinePool = Pool.Combine.Create(new Rectangle(X + 50, Y - 22, 300, 112), Damageable, true);
  424. CombinePool.Hide();
  425. BulletPool = Pool.Bullet.Create(new Rectangle(X + 50, Y - 22, 400, 168), Damageable, true);
  426. BulletPool.Hide();
  427. Selphie_Slots = SelphieSlots.Create(new Rectangle(X + 50, Y - 22, 300, 168), Damageable, true);
  428. Selphie_Slots.Hide();
  429. LimitArrow = new IGMDataItem.Icon { Data = Icons.ID.Arrow_Right, Pos = new Rectangle(SIZE[0].X + Width - 55, SIZE[0].Y, 0, 0), Palette = 2, Faded_Palette = 7, Blink = true };
  430. LimitArrow.Hide();
  431. TargetGroup = Target.Group.Create(Damageable);
  432. TargetGroup.Hide();
  433. commands = new Kernel.BattleCommand[Rows];
  434. enemycommands = null;
  435. PointerZIndex = Offsets.Limit_Arrow;
  436. nonbattleWidth = Width;
  437. }
  438. protected override void InitShift(int i, int col, int row)
  439. {
  440. base.InitShift(i, col, row);
  441. SIZE[i].Inflate(-22, -8);
  442. SIZE[i].Offset(0, 12 + (-8 * row));
  443. }
  444. private Damageable.BattleMode BattleMode = Damageable.BattleMode.EndTurn;
  445. public override void ModeChangeEvent(object sender, Enum e)
  446. {
  447. base.ModeChangeEvent(sender, e);
  448. if (e.GetType() == typeof(Damageable.BattleMode) && !BattleMode.Equals(e))
  449. {
  450. BattleMode = (Damageable.BattleMode)e;
  451. Refresh();
  452. }
  453. }
  454. public override Damageable Damageable
  455. {
  456. get => base.Damageable;
  457. protected set
  458. {
  459. if (value != base.Damageable)
  460. {
  461. if (Battle)
  462. {
  463. UnsubscribeEvents();
  464. base.Damageable = value;
  465. SubscribeEvents();
  466. }
  467. else
  468. base.Damageable = value;
  469. }
  470. }
  471. }
  472. public sbyte CrisisLevel => Damageable != null && Damageable.GetCharacterData(out var c) ? c.CurrentCrisisLevel : (sbyte)-1;
  473. private void SubscribeEvents()
  474. {
  475. if (Damageable != null && !EventAdded)
  476. {
  477. Damageable.BattleModeChangeEventHandler += ModeChangeEvent;
  478. Damageable.BattleModeChangeEventHandler += ItemPool.ModeChangeEvent;
  479. Damageable.BattleModeChangeEventHandler += MagPool.ModeChangeEvent;
  480. EventAdded = true;
  481. }
  482. }
  483. private void UnsubscribeEvents()
  484. {
  485. if (Damageable != null && EventAdded)
  486. {
  487. Damageable.BattleModeChangeEventHandler -= ModeChangeEvent;
  488. Damageable.BattleModeChangeEventHandler -= ItemPool.ModeChangeEvent;
  489. Damageable.BattleModeChangeEventHandler -= MagPool.ModeChangeEvent;
  490. EventAdded = false;
  491. }
  492. }
  493. #endregion Methods
  494. ~Commands()
  495. {
  496. Dispose(false);
  497. }
  498. }
  499. }