DeadTime.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.Linq;
  5. using Microsoft.Xna.Framework;
  6. using OpenVIII.Kernel;
  7. namespace OpenVIII
  8. {
  9. public class DeadTime : Slide<int>
  10. {
  11. private static int Lerp(int start, int end, float amount) => checked((int)MathHelper.Lerp(start, end, amount));
  12. private static TimeSpan GenerateTotalTime(int start) => TimeSpan.FromMilliseconds((1000d / 15d) * start);
  13. public DeadTime() : base(200, 0, GenerateTotalTime(200), Lerp)
  14. {
  15. Repeat = true;
  16. DoneEvent += DeadTime_DoneEvent;
  17. }
  18. protected override void CheckRepeat()
  19. {
  20. if (Done)
  21. {
  22. DoneEvent?.Invoke(this, End);
  23. }
  24. base.CheckRepeat();
  25. }
  26. public override void GotoEnd()
  27. {
  28. DoneEvent?.Invoke(this, End);
  29. base.GotoEnd();
  30. }
  31. private event EventHandler<int> DoneEvent;
  32. public event EventHandler<int> Gilgamesh;
  33. public event EventHandler<Saves.Item> AngeloSearch;
  34. public event EventHandler<Characters> AngeloReverse;
  35. public event EventHandler<Characters> AngeloRecover;
  36. /// <summary>
  37. /// Test if an ability can trigger for Angelo.
  38. /// </summary>
  39. /// <param name="ability"></param>
  40. /// <returns></returns>
  41. private static bool TestAngeloAbilityTriggers(Angelo ability)
  42. {
  43. //else if (8 >= [0..255] Angelo Recover is used (3.3 %)
  44. //else if (2 >= [0..255] Angelo Reverse is used (1 %)
  45. //else if (8 >= [0..255] Angelo Search is used (3.2 %)
  46. //Angelo_Disabled I think is set when Rinoa is in space so angelo is out of reach;
  47. //https://gamefaqs.gamespot.com/ps/197343-final-fantasy-viii/faqs/25194
  48. if (Memory.State.BattleMiscFlags.HasFlag(Saves.BattleMiscFlags.AngeloDisabled) ||
  49. !Memory.State.Party.Contains(Characters.Rinoa_Heartilly) ||
  50. !Memory.State.LimitBreakAngeloCompleted.HasFlag(ability)) return false;
  51. switch (ability)
  52. {
  53. case Angelo.Recover:
  54. //Characters.Any(x => x.Value.IsCritical && !x.Value.IsDead && Memory.State.PartyData.Contains(x.Key))
  55. return !Memory.State.AnyDeadPartyMembers && Memory.State.AnyCriticalPartyMembers && Memory.Random.Next(256) < 8;
  56. case Angelo.Reverse:
  57. return Memory.State.AnyDeadPartyMembers && Memory.Random.Next(256) < 2;
  58. case Angelo.Search:
  59. var c = Memory.State[Characters.Rinoa_Heartilly];
  60. if (!(c.IsGameOver ||
  61. c.Statuses1.HasFlag(BattleOnlyStatuses.Sleep) ||
  62. c.Statuses1.HasFlag(BattleOnlyStatuses.Stop) ||
  63. c.Statuses1.HasFlag(BattleOnlyStatuses.Confuse) ||
  64. c.Statuses0.HasFlag(PersistentStatuses.Berserk) ||
  65. c.Statuses1.HasFlag(BattleOnlyStatuses.AngelWing)))
  66. return Memory.Random.Next(256) < 8;
  67. break;
  68. case Angelo.None:
  69. break;
  70. case Angelo.Rush:
  71. break;
  72. case Angelo.Cannon:
  73. break;
  74. case Angelo.Strike:
  75. break;
  76. case Angelo.Invincible_Moon:
  77. break;
  78. case Angelo.Wishing_Star:
  79. break;
  80. case Angelo.Angel_Wing:
  81. break;
  82. default:
  83. throw new ArgumentOutOfRangeException(nameof(ability), ability, null);
  84. }
  85. return false;
  86. }
  87. /// <summary>
  88. /// if (12 >= [0..255]) Gilgamesh is summoned (5.1 %)
  89. /// </summary>
  90. /// <returns>bool</returns>
  91. private static bool TestGilgamesh() =>
  92. Memory.State.BattleMiscFlags.HasFlag(Saves.BattleMiscFlags.Gilgamesh) && Memory.Random.Next(256) <= 12;
  93. /// <summary>
  94. /// Trigger Event when DeadTime is done.
  95. /// </summary>
  96. /// <param name="sender"></param>
  97. /// <param name="e"></param>
  98. /// <see cref="https://gamefaqs.gamespot.com/ps/197343-final-fantasy-viii/faqs/58936"/>
  99. [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
  100. private void DeadTime_DoneEvent(object sender, int e)
  101. {
  102. bool save(IEnumerable<Characters> hurt, ref EventHandler<Characters> @event)
  103. {
  104. if (hurt == null || !hurt.Any()) return false;
  105. var c = hurt.Random();
  106. @event?.Invoke(this, c);
  107. return true;
  108. }
  109. //Will Gilgamesh appear?
  110. if (TestGilgamesh())
  111. {
  112. Gilgamesh?.Invoke(this, 0);
  113. }
  114. //Will Angelo Recover be used?
  115. else if (TestAngeloAbilityTriggers(Angelo.Recover) &&
  116. save(Memory.State.Party.Where(x => x != Characters.Blank && Memory.State[x].IsCritical), ref AngeloRecover))
  117. { }
  118. //Will Angelo Reverse be used?
  119. else if (TestAngeloAbilityTriggers(Angelo.Reverse) &&
  120. save(Memory.State.Party.Where(x => x != Characters.Blank && Memory.State[x].IsDead), ref AngeloReverse))
  121. {}
  122. //Will Angelo Search be used?
  123. else if (TestAngeloAbilityTriggers(Angelo.Search))
  124. {
  125. //Real game has a counter that Count to 255 and resets to 0
  126. //instead of a random number. The counter counts up every 1 tick.
  127. //60 ticks per second.
  128. var rnd = checked((byte)Memory.Random.Next(256));
  129. if (rnd < 128) AngeloSearch?.Invoke(this,Algorithm(1));
  130. else if (rnd < 160) AngeloSearch?.Invoke(this, Algorithm(2));
  131. else if (rnd < 176) AngeloSearch?.Invoke(this, Algorithm(3));
  132. else if (rnd < 192) AngeloSearch?.Invoke(this, Algorithm(4));
  133. else if (rnd < 200) AngeloSearch?.Invoke(this, Algorithm(5));
  134. else AngeloSearch?.Invoke(this, Algorithm(6));
  135. Saves.Item Algorithm(byte i)
  136. {
  137. //https://gamefaqs.gamespot.com/ps/197343-final-fantasy-viii/faqs/58936
  138. //I'm unsure where in the game files this is.
  139. //In remaster they changed this. But unsure how
  140. // they added (Ribbon, Friendship, and Mog's Amulet)
  141. // using a true random kinda breaks this.
  142. // because in game random is a set array of numbers 0-255
  143. // so the number you get previous would determine the possible number you get
  144. // so these can only select specific numbers. But because we are using a real random
  145. // more items are possible. might need to tweak this.
  146. //these are added in remaster as possible items.
  147. //const byte Ribbon = 100;
  148. //const byte Friendship = 32;
  149. //const byte Mog's_Amulet = 65;
  150. var item = new Saves.Item { QTY = 1 };
  151. rnd = checked((byte)Memory.Random.Next(256));
  152. switch (i)
  153. {
  154. case 1: // 1-8
  155. item.ID = (byte)(rnd % 8 + 1);
  156. break;
  157. case 2: // 102-199
  158. item.ID = (byte)(rnd % 98);
  159. if (item.ID == 0) item.ID = 98;
  160. item.ID += 101;
  161. break;
  162. case 3: // 102-124
  163. item.ID = (byte)(rnd % 23);
  164. if (item.ID == 0)
  165. item.ID = 23;
  166. item.ID += 101;
  167. break;
  168. case 4: // 67-100
  169. item.ID = (byte)(rnd % 34);
  170. if (item.ID == 0)
  171. item.ID = 34;
  172. item.ID += 66;
  173. break;
  174. case 5: // 33-54
  175. item.ID = (byte)(rnd % 32 + 33);
  176. break;
  177. default: // 33-40
  178. item.ID = (byte)(rnd % 7 + 33);
  179. break;
  180. }
  181. return item;
  182. }
  183. }
  184. }
  185. }
  186. }