main.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. /*
  2. * @licstart The following is the entire license notice for the JavaScript
  3. * code in this page.
  4. *
  5. * This file is part of gish-ap-calc.
  6. *
  7. * gish-ap-calc is free software: you can redistribute it and/or modify it
  8. * under the terms of the GNU Affero General Public License as published by the
  9. * Free Software Foundation, either version 3 of the License, or (at your
  10. * option) any later version.
  11. *
  12. * gish-ap-calc is distributed in the hope that it will be useful, but WITHOUT
  13. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  14. * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
  15. * for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public License
  18. * along with gish-ap-calc. If not, see <https://www.gnu.org/licenses/>.
  19. *
  20. * @licend The above is the entire license notice for the JavaScript code in
  21. * this page.
  22. */
  23. import { allocateAp } from "./lib.js";
  24. import {
  25. mDps,
  26. meleePeriod,
  27. mHitRate,
  28. psm,
  29. spellPeriod,
  30. wacc,
  31. wDps,
  32. wHitRate,
  33. } from "./mechanics.js";
  34. import {
  35. Monster,
  36. Speed,
  37. Spell,
  38. SpellType,
  39. Stats,
  40. Weapon,
  41. WeaponType,
  42. } from "./types.js";
  43. document.addEventListener("readystatechange", () => {
  44. if (document.readyState === "complete") {
  45. main();
  46. }
  47. });
  48. function main(): void {
  49. /**************** INPUTS ****************/
  50. // Base stats
  51. const strBaseInput = document.getElementById(
  52. "str-base",
  53. ) as HTMLInputElement;
  54. const dexBaseInput = document.getElementById(
  55. "dex-base",
  56. ) as HTMLInputElement;
  57. const intBaseInput = document.getElementById(
  58. "int-base",
  59. ) as HTMLInputElement;
  60. const lukBaseInput = document.getElementById(
  61. "luk-base",
  62. ) as HTMLInputElement;
  63. // Additional stats
  64. const strAdditionalInput = document.getElementById(
  65. "str-additional",
  66. ) as HTMLInputElement;
  67. const dexAdditionalInput = document.getElementById(
  68. "dex-additional",
  69. ) as HTMLInputElement;
  70. const intAdditionalInput = document.getElementById(
  71. "int-additional",
  72. ) as HTMLInputElement;
  73. const lukAdditionalInput = document.getElementById(
  74. "luk-additional",
  75. ) as HTMLInputElement;
  76. const waccAdditionalInput = document.getElementById(
  77. "wacc-additional",
  78. ) as HTMLInputElement;
  79. const matkAdditionalInput = document.getElementById(
  80. "matk-additional",
  81. ) as HTMLInputElement;
  82. // Total WATK
  83. const totalWatkInput = document.getElementById(
  84. "total-watk",
  85. ) as HTMLInputElement;
  86. // AP available
  87. const apAvailableInput = document.getElementById(
  88. "ap-available",
  89. ) as HTMLInputElement;
  90. // Character
  91. const levelInput = document.getElementById("level") as HTMLInputElement;
  92. // Weapon/spell
  93. const weaponTypeInput = document.getElementById(
  94. "weapon-type",
  95. ) as HTMLSelectElement;
  96. const speedInput = document.getElementById("speed") as HTMLSelectElement;
  97. const spellInput = document.getElementById("spell") as HTMLSelectElement;
  98. const spellBasicAtkInput = document.getElementById(
  99. "spell-basic-atk",
  100. ) as HTMLInputElement;
  101. const spellLinesInput = document.getElementById(
  102. "spell-lines",
  103. ) as HTMLSelectElement;
  104. const masteryInput = document.getElementById(
  105. "mastery",
  106. ) as HTMLInputElement;
  107. const spellBoosterInput = document.getElementById(
  108. "spell-booster",
  109. ) as HTMLInputElement;
  110. // Elemental stuff
  111. const eleAmpInput = document.getElementById("ele-amp") as HTMLInputElement;
  112. const eleWepInput = document.getElementById("ele-wep") as HTMLInputElement;
  113. // Enemy
  114. const wdefInput = document.getElementById(
  115. "enemy-wdef",
  116. ) as HTMLInputElement;
  117. const mdefInput = document.getElementById(
  118. "enemy-mdef",
  119. ) as HTMLInputElement;
  120. const avoidInput = document.getElementById(
  121. "enemy-avoid",
  122. ) as HTMLInputElement;
  123. const eleSusInput = document.getElementById(
  124. "ele-sus",
  125. ) as HTMLSelectElement;
  126. const enemyLevelInput = document.getElementById(
  127. "enemy-level",
  128. ) as HTMLInputElement;
  129. const enemyCountInput = document.getElementById(
  130. "enemy-count",
  131. ) as HTMLInputElement;
  132. // wDominanceFactor
  133. const wDominanceFactorInput = document.getElementById(
  134. "w-dominance-factor",
  135. ) as HTMLInputElement;
  136. /**************** OUTPUTS ****************/
  137. // Stats
  138. const strOutput = document.getElementById("str-output") as HTMLSpanElement;
  139. const strBaseOutput = document.getElementById(
  140. "str-base-output",
  141. ) as HTMLSpanElement;
  142. const strAdditionalOutput = document.getElementById(
  143. "str-additional-output",
  144. ) as HTMLSpanElement;
  145. const dexOutput = document.getElementById("dex-output") as HTMLSpanElement;
  146. const dexBaseOutput = document.getElementById(
  147. "dex-base-output",
  148. ) as HTMLSpanElement;
  149. const dexAdditionalOutput = document.getElementById(
  150. "dex-additional-output",
  151. ) as HTMLSpanElement;
  152. const intOutput = document.getElementById("int-output") as HTMLSpanElement;
  153. const intBaseOutput = document.getElementById(
  154. "int-base-output",
  155. ) as HTMLSpanElement;
  156. const intAdditionalOutput = document.getElementById(
  157. "int-additional-output",
  158. ) as HTMLSpanElement;
  159. const lukOutput = document.getElementById("luk-output") as HTMLSpanElement;
  160. const lukBaseOutput = document.getElementById(
  161. "luk-base-output",
  162. ) as HTMLSpanElement;
  163. const lukAdditionalOutput = document.getElementById(
  164. "luk-additional-output",
  165. ) as HTMLSpanElement;
  166. // Combat
  167. const meleeDpsOutput = document.getElementById(
  168. "melee-dps",
  169. ) as HTMLSpanElement;
  170. const spellDpsOutput = document.getElementById(
  171. "spell-dps",
  172. ) as HTMLSpanElement;
  173. const meleeHitRateOutput = document.getElementById(
  174. "melee-hit-rate",
  175. ) as HTMLSpanElement;
  176. const spellHitRateOutput = document.getElementById(
  177. "spell-hit-rate",
  178. ) as HTMLSpanElement;
  179. function recalculate(): void {
  180. const initialBaseStats = new Stats(
  181. handleIntInput(strBaseInput, 4, 4),
  182. handleIntInput(dexBaseInput, 4, 4),
  183. handleIntInput(intBaseInput, 4, 4),
  184. handleIntInput(lukBaseInput, 4, 4),
  185. );
  186. const level = handleIntInput(levelInput, 8, 30, 200);
  187. const wepTypeInt = handleIntInput(weaponTypeInput, 30, 30, 44);
  188. const wepType = (() => {
  189. if (!(wepTypeInt in WeaponType)) {
  190. weaponTypeInput.value = "30";
  191. return WeaponType.OneHandedSword;
  192. }
  193. return wepTypeInt as WeaponType;
  194. })();
  195. const wepSpeed = handleIntInput(speedInput, 6, 2, 9) as Speed;
  196. const weapon = new Weapon(
  197. psm(wepType),
  198. meleePeriod(wepType, wepSpeed),
  199. );
  200. const spellTypeInt = handleIntInput(spellInput, 0, 0, 2321008);
  201. const spellType = (() => {
  202. if (!(spellTypeInt in SpellType)) {
  203. spellInput.value = "0";
  204. return SpellType.Other;
  205. }
  206. return spellTypeInt as SpellType;
  207. })();
  208. const spellT = (() => {
  209. let t = spellPeriod(
  210. handleIntInput(spellBoosterInput, 0, -2, 0),
  211. spellType,
  212. wepSpeed,
  213. );
  214. if (t === undefined) {
  215. console.error(
  216. `spellPeriod(${handleIntInput(
  217. spellBoosterInput,
  218. 0,
  219. -2,
  220. 0,
  221. )}, ${spellType}, ${wepSpeed}) is undefined`,
  222. );
  223. t = 0.81;
  224. }
  225. return t;
  226. })();
  227. const spell = new Spell(
  228. handleIntInput(spellBasicAtkInput, 10, 1),
  229. handleIntInput(masteryInput, 15, 10, 90) / 100,
  230. spellT,
  231. handleIntInput(spellLinesInput, 1, 1),
  232. );
  233. const totalWatk = handleIntInput(totalWatkInput, 1, 0);
  234. const rawMatk = handleIntInput(matkAdditionalInput, 0, 0);
  235. const rawWacc = handleIntInput(waccAdditionalInput, 0, 0);
  236. const monster = new Monster(
  237. handleIntInput(enemyLevelInput, 1, 1),
  238. handleIntInput(avoidInput, 1, 1),
  239. handleIntInput(wdefInput, 0),
  240. handleIntInput(mdefInput, 0),
  241. );
  242. const [baseStats, totalStats] = allocateAp(
  243. handleIntInput(apAvailableInput, 1, 1),
  244. initialBaseStats,
  245. new Stats(
  246. handleIntInput(strAdditionalInput, 0, 0),
  247. handleIntInput(dexAdditionalInput, 0, 0),
  248. handleIntInput(intAdditionalInput, 0, 0),
  249. handleIntInput(lukAdditionalInput, 0, 0),
  250. ).add(initialBaseStats),
  251. level,
  252. weapon,
  253. spell,
  254. totalWatk,
  255. rawMatk,
  256. rawWacc,
  257. monster,
  258. handleFloatInput(wDominanceFactorInput, 2, 1),
  259. );
  260. const additionalStats = totalStats.clone().sub(baseStats);
  261. strOutput.textContent = "" + totalStats.str;
  262. dexOutput.textContent = "" + totalStats.dex;
  263. intOutput.textContent = "" + totalStats.int;
  264. lukOutput.textContent = "" + totalStats.luk;
  265. strBaseOutput.textContent = "" + baseStats.str;
  266. dexBaseOutput.textContent = "" + baseStats.dex;
  267. intBaseOutput.textContent = "" + baseStats.int;
  268. lukBaseOutput.textContent = "" + baseStats.luk;
  269. strAdditionalOutput.textContent = "" + additionalStats.str;
  270. dexAdditionalOutput.textContent = "" + additionalStats.dex;
  271. intAdditionalOutput.textContent = "" + additionalStats.int;
  272. lukAdditionalOutput.textContent = "" + additionalStats.luk;
  273. const d = Math.max(monster.level - level, 0);
  274. meleeDpsOutput.textContent = wDps(
  275. totalStats,
  276. rawWacc,
  277. totalWatk,
  278. weapon,
  279. monster,
  280. d,
  281. ).toFixed(3);
  282. spellDpsOutput.textContent = mDps(
  283. totalStats,
  284. rawMatk,
  285. spell,
  286. monster,
  287. d,
  288. ).toFixed(3);
  289. meleeHitRateOutput.textContent = (
  290. 100 *
  291. wHitRate(
  292. wacc(totalStats.dex, totalStats.luk, rawWacc),
  293. monster.avoid,
  294. d,
  295. )
  296. ).toFixed(2);
  297. spellHitRateOutput.textContent = (
  298. 100 * mHitRate(totalStats.int, totalStats.luk, monster.avoid, d)
  299. ).toFixed(2);
  300. }
  301. for (const input of [
  302. strBaseInput,
  303. dexBaseInput,
  304. intBaseInput,
  305. lukBaseInput,
  306. strAdditionalInput,
  307. dexAdditionalInput,
  308. intAdditionalInput,
  309. lukAdditionalInput,
  310. waccAdditionalInput,
  311. matkAdditionalInput,
  312. totalWatkInput,
  313. apAvailableInput,
  314. levelInput,
  315. weaponTypeInput,
  316. speedInput,
  317. spellInput,
  318. spellBasicAtkInput,
  319. spellLinesInput,
  320. masteryInput,
  321. spellBoosterInput,
  322. eleAmpInput,
  323. eleWepInput,
  324. wdefInput,
  325. mdefInput,
  326. avoidInput,
  327. eleSusInput,
  328. enemyLevelInput,
  329. enemyCountInput,
  330. wDominanceFactorInput,
  331. ]) {
  332. input.addEventListener("change", recalculate);
  333. }
  334. recalculate();
  335. }
  336. function handleIntInput(
  337. input: HTMLInputElement | HTMLSelectElement,
  338. def: number,
  339. min: number = Number.NEGATIVE_INFINITY,
  340. max: number = Number.POSITIVE_INFINITY,
  341. ): number {
  342. let x = Math.min(Math.max(parseInt(input.value, 10), min), max);
  343. if (!Number.isFinite(x)) {
  344. x = def;
  345. }
  346. input.value = "" + x;
  347. return x;
  348. }
  349. function handleFloatInput(
  350. input: HTMLInputElement | HTMLSelectElement,
  351. def: number,
  352. min: number = Number.NEGATIVE_INFINITY,
  353. max: number = Number.POSITIVE_INFINITY,
  354. ): number {
  355. let x = Math.min(Math.max(parseFloat(input.value), min), max);
  356. if (!Number.isFinite(x)) {
  357. x = def;
  358. }
  359. input.value = "" + x;
  360. return x;
  361. }