Enemy.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. using Microsoft.Xna.Framework;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. namespace OpenVIII
  7. {
  8. public class Enemy : Damageable, IEnemy
  9. {
  10. #region Fields
  11. private const int statusdefault = 100;
  12. private byte _fixedLevel;
  13. private bool mugged = false;
  14. #endregion Fields
  15. #region Methods
  16. private byte convert1(byte[] @in)
  17. {
  18. //from Ifrit's help file
  19. byte level = Level;
  20. int i = level * @in[0] / 10 + level / @in[1] - level * level / 2 / (@in[3] + @in[2]) / 4;
  21. //PLEASE NOTE: I'm not 100% sure on the STR/MAG formula, but it should be accurate enough to get the general idea.
  22. // wiki states something like ([3(Lv)] + [(Lv) / 5] - [(Lv)² / 260] + 12) / 4
  23. return (byte)MathHelper.Clamp(i, 0, byte.MaxValue);
  24. }
  25. private byte convert2(byte[] @in)
  26. {
  27. //from Ifrit's help file
  28. byte level = Level;
  29. int i = level / @in[1] - level / @in[3] + level * @in[0] + @in[2];
  30. return (byte)MathHelper.Clamp(i, 0, byte.MaxValue);
  31. }
  32. private int convert3(ushort @in, byte inLevel)
  33. {
  34. //from Ifrit's help file
  35. byte level = Level;
  36. if (inLevel == 0)
  37. return 0;
  38. else
  39. return @in * (5 * (level - inLevel) / inLevel + 4);
  40. }
  41. private T hml<T>(T h, T m, T l)
  42. {
  43. byte level = Level;
  44. if (level > Info.highLevelStart)
  45. return h;
  46. else if (level > Info.medLevelStart)
  47. return m;
  48. else return l;
  49. }
  50. private int levelgroup()
  51. {
  52. byte l = Level;
  53. if (l > Info.highLevelStart)
  54. return 2;
  55. if (l > Info.medLevelStart)
  56. return 1;
  57. else return 0;
  58. }
  59. #endregion Methods
  60. #region Constructors
  61. private Enemy()
  62. {
  63. }
  64. protected override void ReadData(BinaryReader br, Enum @enum) => throw new NotImplementedException("This method is not used by Enemy");
  65. public static Enemy Load(Module_battle_debug.EnemyInstanceInformation eII, byte fixedLevel = 0, ushort? startinghp = null)
  66. {
  67. Enemy r = new Enemy
  68. {
  69. EII = eII,
  70. FixedLevel = fixedLevel
  71. };
  72. r._CurrentHP = startinghp ?? r.MaxHP();
  73. if ((r.Info.bitSwitch & Debug_battleDat.Information.Flag1.Zombie) != 0)
  74. {
  75. r.Statuses0 |= Kernel_bin.Persistent_Statuses.Zombie;
  76. }
  77. if ((r.Info.bitSwitch & Debug_battleDat.Information.Flag1.Auto_Protect) != 0)
  78. {
  79. r.Statuses1 |= Kernel_bin.Battle_Only_Statuses.Protect;
  80. }
  81. if ((r.Info.bitSwitch & Debug_battleDat.Information.Flag1.Auto_Reflect) != 0)
  82. {
  83. r.Statuses1 |= Kernel_bin.Battle_Only_Statuses.Reflect;
  84. }
  85. if ((r.Info.bitSwitch & Debug_battleDat.Information.Flag1.Auto_Shell) != 0)
  86. {
  87. r.Statuses1 |= Kernel_bin.Battle_Only_Statuses.Shell;
  88. }
  89. if ((r.Info.bitSwitch & Debug_battleDat.Information.Flag1.Fly) != 0)
  90. {
  91. r.Statuses1 |= Kernel_bin.Battle_Only_Statuses.Float;
  92. }
  93. r.Init();
  94. return r;
  95. }
  96. #endregion Constructors
  97. #region Properties
  98. public IEnumerable<Kernel_bin.Enemy_Attacks_Data> Enemy_Attacks_Datas => Abilities.Where(x => x.MONSTER != null).Select(x => x.MONSTER);
  99. public IEnumerable<GFs> JunctionedGFs => Memory.State != null? DrawList.Select(x => x.GF).Where(gf => gf >= GFs.Quezacotl && gf <= GFs.Eden && !Memory.State.UnlockedGFs.Contains(gf)).Distinct() : null;
  100. public static List<Enemy> Party { get; set; }
  101. public byte AP => Info.ap;
  102. public Debug_battleDat.Magic[] DrawList => hml(Info.drawhigh, Info.drawmed, Info.drawlow);
  103. /// <summary>
  104. /// Randomly gain 1 or 0 from this list.
  105. /// </summary>
  106. public Saves.Item[] DropList => hml(Info.drophigh, Info.dropmed, Info.droplow);
  107. public Debug_battleDat.Abilities[] Abilities => hml(Info.abilitiesHigh, Info.abilitiesMed, Info.abilitiesLow);
  108. public byte DropRate => (byte)(MathHelper.Clamp(Info.dropRate * 100 / byte.MaxValue, 0, 100));
  109. public Module_battle_debug.EnemyInstanceInformation EII { get; set; }
  110. public override byte EVA => convert2(Info.eva);
  111. /// <summary>
  112. /// The EXP everyone gets.
  113. /// </summary>
  114. public override int EXP => convert3(Info.exp, Memory.State.AveragePartyLevel);
  115. public byte FixedLevel { get => _fixedLevel; set => _fixedLevel = value; }
  116. /// <summary>
  117. /// Enemy attacks determine hit%
  118. /// </summary>
  119. /// <remarks>
  120. /// LeythalknightToday at 1:53 PM Enemy attack accuracy is set per ability based on the value
  121. /// that's called "Attack param" in Doomtrain, and only when they use attack types that have
  122. /// a miss chance
  123. /// </remarks>
  124. public override byte HIT => 0;
  125. /// <summary>
  126. /// Level of enemy based on average of party or fixed value.
  127. /// </summary>
  128. /// <see cref="https://finalfantasy.fandom.com/wiki/Level#Enemy_levels"/>
  129. public override byte Level
  130. {
  131. get
  132. {
  133. if (FixedLevel != default)
  134. return FixedLevel;
  135. byte a = Memory.State.AveragePartyLevel;
  136. byte d = (byte)(a / 5);
  137. return (byte)MathHelper.Clamp(a + d, 1, 100);
  138. }
  139. }
  140. /// <summary>
  141. /// unknown luck stat. putting 0 in here so there is something.
  142. /// </summary>
  143. public override byte LUCK => 0;
  144. public override byte MAG => convert1(Info.mag);
  145. /// <summary>
  146. /// Randomly gain 1 or 0 from this list.
  147. /// </summary>
  148. public Saves.Item[] MugList => hml(Info.mughigh, Info.mugmed, Info.muglow);
  149. public byte MugRate => (byte)(MathHelper.Clamp(Info.mugRate * 100 / byte.MaxValue, 0, 100));
  150. public override FF8String Name => Info.name;
  151. public override byte SPD => convert2(Info.spd);
  152. public override byte SPR => convert2(Info.spr);
  153. public override byte STR => convert1(Info.str);
  154. public override byte VIT => convert2(Info.vit);
  155. public Kernel_bin.Devour Devour => Info.devour[levelgroup()] >= Kernel_bin.Devour_.Count ?
  156. Kernel_bin.Devour_[Kernel_bin.Devour_.Count - 1] :
  157. Kernel_bin.Devour_[Info.devour[levelgroup()]];
  158. public Debug_battleDat.Information Info => EII.Data.information;
  159. #endregion Properties
  160. public static implicit operator Enemy(Module_battle_debug.EnemyInstanceInformation @in) => Load(@in);
  161. /// <summary>
  162. /// Return card if succeed at roll
  163. /// </summary>
  164. /// <returns></returns>
  165. /// <see cref="https://gamefaqs.gamespot.com/ps/197343-final-fantasy-viii/faqs/58936"/>
  166. public Cards.ID Card()
  167. {
  168. if (Info.card.Skip(1).All(x => x == Cards.ID.Immune)) return Cards.ID.Immune;
  169. int p = (256 * MaxHP() - 255 * CurrentHP()) / MaxHP();
  170. int r = Memory.Random.Next(256);
  171. // 2 is rare card, 1 is normal card 0 per ifrit.
  172. return r < (p + 1) ? (r < 17 ? Info.card[2] : Info.card[1]) : Cards.ID.Fail;
  173. }
  174. public Cards.ID CardDrop()
  175. {
  176. //9 / 256
  177. if (Info.card[0] == Cards.ID.Immune) return Info.card[1];
  178. int r = Memory.Random.Next(256);
  179. return r < 9 ? Info.card[1] : Cards.ID.Fail;
  180. }
  181. public override Damageable Clone() => throw new NotImplementedException();
  182. public Saves.Item Drop(bool RareITEM = false)
  183. {
  184. if (mugged) return default;
  185. int percent = DropRate;
  186. Saves.Item[] list = DropList;
  187. int i = Memory.Random.Next(100 + 1);
  188. if (i < percent && list.Length > 0)
  189. {
  190. //Slot | 0 | 1 | 2 | 3
  191. //------------------| ---------| --------- | ---------| --------
  192. //Without Rare Item | 178 / 256 | 51 / 256 | 15 / 256 | 12 / 256
  193. //------------------| ---------| --------- | ---------| --------
  194. //With Rare Item | 128 / 256 | 114 / 256 | 14 / 256 | 0 / 256 <- kinda makes no sence to me
  195. int r = Memory.Random.Next(256);
  196. if (RareITEM)
  197. {
  198. if (r < 128)
  199. return list[0];
  200. else if ((r -= 128) < 114)
  201. return list[1];
  202. else if ((r -= 114) < 14)
  203. return list[2];
  204. else
  205. return list[3];
  206. }
  207. if (r < 178)
  208. return list[0];
  209. else if ((r -= 178) < 51)
  210. return list[1];
  211. else if ((r -= 51) < 15)
  212. return list[2];
  213. else
  214. return list[3];
  215. }
  216. return default;
  217. }
  218. public override short ElementalResistance(Kernel_bin.Element @in)
  219. {
  220. List<Kernel_bin.Element> l = (Enum.GetValues(typeof(Kernel_bin.Element))).Cast<Kernel_bin.Element>().ToList();
  221. if (@in == Kernel_bin.Element.Non_Elemental)
  222. return 100;
  223. // I wonder if i should average the resistances in cases of multiple elements.
  224. else
  225. return conv(Info.resistance[l.FindIndex(x => (x & @in) != 0) - 1]);
  226. short conv(byte val) => (short)MathHelper.Clamp(900 - (val * 10), -100, 400);
  227. }
  228. /// <summary>
  229. /// The character whom lands the last hit gets alittle bonus xp.
  230. /// </summary>
  231. /// <param name="lasthitlevel">Level of character whom got last hit.</param>
  232. /// <returns></returns>
  233. public int EXPExtra(byte lasthitlevel) => convert3(Info.expExtra, lasthitlevel);
  234. public override ushort MaxHP()
  235. {
  236. //from Ifrit's help file
  237. if (Info.hp == null)
  238. return 0;
  239. int i = (Info.hp[0] * Level * Level / 20) + (Info.hp[0] + Info.hp[2] * 100) * Level + Info.hp[1] * 10 + Info.hp[3] * 1000;
  240. return (ushort)MathHelper.Clamp(i, 0, ushort.MaxValue);
  241. }
  242. public Saves.Item Mug(byte spd, bool RareITEM = false)
  243. {
  244. if (mugged) return default;
  245. int percent = (MugRate + spd);
  246. Saves.Item[] list = DropList;
  247. int i = Memory.Random.Next(100 + 1);
  248. try
  249. {
  250. if (i < percent && list.Length > 0)
  251. {
  252. byte r = (byte)Memory.Random.Next(256);
  253. if (RareITEM)
  254. {
  255. if (r < 128)
  256. return list[0];
  257. else if ((r -= 128) < 114)
  258. return list[1];
  259. else if ((r -= 114) < 14)
  260. return list[2];
  261. else
  262. return list[3];
  263. }
  264. if (r < 178)
  265. return list[0];
  266. else if ((r -= 178) < 51)
  267. return list[1];
  268. else if ((r -= 51) < 15)
  269. return list[2];
  270. else
  271. return list[3];
  272. }
  273. }
  274. finally { mugged = true; }
  275. mugged = false;
  276. return default;
  277. }
  278. /// <summary>
  279. /// I notice that the resistance reported on the wiki is 100 less than the number in the data.
  280. /// </summary>
  281. /// <param name="s">status effect</param>
  282. /// <returns>percent of resistance</returns>
  283. /// <see cref="https://finalfantasy.fandom.com/wiki/G-Soldier#Stats"/>
  284. public override sbyte StatusResistance(Kernel_bin.Persistent_Statuses s)
  285. {
  286. byte r = 100;
  287. switch (s)
  288. {
  289. case Kernel_bin.Persistent_Statuses.Death:
  290. r = Info.deathResistanceMental;
  291. break;
  292. case Kernel_bin.Persistent_Statuses.Poison:
  293. r = Info.poisonResistanceMental;
  294. break;
  295. case Kernel_bin.Persistent_Statuses.Petrify:
  296. r = Info.petrifyResistanceMental;
  297. break;
  298. case Kernel_bin.Persistent_Statuses.Darkness:
  299. r = Info.darknessResistanceMental;
  300. break;
  301. case Kernel_bin.Persistent_Statuses.Silence:
  302. r = Info.silenceResistanceMental;
  303. break;
  304. case Kernel_bin.Persistent_Statuses.Berserk:
  305. r = Info.berserkResistanceMental;
  306. break;
  307. case Kernel_bin.Persistent_Statuses.Zombie:
  308. r = Info.zombieResistanceMental;
  309. break;
  310. }
  311. return (sbyte)MathHelper.Clamp(r - 100, -100, 100);
  312. }
  313. /// <summary>
  314. /// I notice that the resistance reported on the wiki is 100 less than the number in the data.
  315. /// </summary>
  316. /// <param name="s">status effect</param>
  317. /// <returns>percent of resistance</returns>
  318. /// <see cref="https://finalfantasy.fandom.com/wiki/G-Soldier#Stats"/>
  319. public override sbyte StatusResistance(Kernel_bin.Battle_Only_Statuses s)
  320. {
  321. byte r = statusdefault;
  322. switch (s)
  323. {
  324. case Kernel_bin.Battle_Only_Statuses.Sleep:
  325. r = Info.sleepResistanceMental;
  326. break;
  327. case Kernel_bin.Battle_Only_Statuses.Haste:
  328. r = Info.hasteResistanceMental;
  329. break;
  330. case Kernel_bin.Battle_Only_Statuses.Slow:
  331. r = Info.slowResistanceMental;
  332. break;
  333. case Kernel_bin.Battle_Only_Statuses.Stop:
  334. r = Info.stopResistanceMental;
  335. break;
  336. case Kernel_bin.Battle_Only_Statuses.Regen:
  337. r = Info.regenResistanceMental;
  338. break;
  339. case Kernel_bin.Battle_Only_Statuses.Protect:
  340. break;
  341. case Kernel_bin.Battle_Only_Statuses.Shell:
  342. break;
  343. case Kernel_bin.Battle_Only_Statuses.Reflect:
  344. r = Info.reflectResistanceMental;
  345. break;
  346. case Kernel_bin.Battle_Only_Statuses.Aura:
  347. break;
  348. case Kernel_bin.Battle_Only_Statuses.Curse:
  349. break;
  350. case Kernel_bin.Battle_Only_Statuses.Doom:
  351. r = Info.doomResistanceMental;
  352. break;
  353. case Kernel_bin.Battle_Only_Statuses.Invincible:
  354. break;
  355. case Kernel_bin.Battle_Only_Statuses.Petrifying:
  356. r = Info.slowPetrifyResistanceMental;
  357. break;
  358. case Kernel_bin.Battle_Only_Statuses.Float:
  359. r = Info.floatResistanceMental;
  360. break;
  361. case Kernel_bin.Battle_Only_Statuses.Confuse:
  362. r = Info.confuseResistanceMental;
  363. break;
  364. case Kernel_bin.Battle_Only_Statuses.Drain:
  365. r = Info.drainResistanceMental;
  366. break;
  367. case Kernel_bin.Battle_Only_Statuses.Eject:
  368. r = Info.explusionResistanceMental;
  369. break;
  370. }
  371. return (sbyte)MathHelper.Clamp(r - 100, -100, 100);
  372. }
  373. /// <summary>
  374. /// The wiki says some areas have forced or random levels. This lets you override the level.
  375. /// </summary>
  376. /// <see cref="https://finalfantasy.fandom.com/wiki/Level#Enemy_levels"/>
  377. public override string ToString() => Name.Value_str;
  378. public override ushort TotalStat(Kernel_bin.Stat s)
  379. {
  380. switch (s)
  381. {
  382. case Kernel_bin.Stat.HP:
  383. return CurrentHP();
  384. case Kernel_bin.Stat.EVA:
  385. //TODO confirm if there is no flat stat buff for eva. If there isn't then remove from function.
  386. return EVA;
  387. case Kernel_bin.Stat.SPD:
  388. return SPD;
  389. case Kernel_bin.Stat.HIT:
  390. return HIT;
  391. case Kernel_bin.Stat.LUCK:
  392. return LUCK;
  393. case Kernel_bin.Stat.MAG:
  394. return MAG;
  395. case Kernel_bin.Stat.SPR:
  396. return SPR;
  397. case Kernel_bin.Stat.STR:
  398. return STR;
  399. case Kernel_bin.Stat.VIT:
  400. return VIT;
  401. }
  402. return 0;
  403. }
  404. public static implicit operator Module_battle_debug.EnemyInstanceInformation(Enemy @in) => @in.EII;
  405. }
  406. }