@rpgm-tools/neo-angband-core 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (848) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/perceive.d.ts.map +1 -1
  3. package/dist/agent/perceive.js +2 -2
  4. package/dist/agent/perceive.js.map +1 -1
  5. package/dist/combat/blow-vectors.d.ts +78 -0
  6. package/dist/combat/blow-vectors.d.ts.map +1 -0
  7. package/dist/combat/blow-vectors.fixtures.d.ts +19 -0
  8. package/dist/combat/blow-vectors.fixtures.d.ts.map +1 -0
  9. package/dist/combat/blow-vectors.fixtures.js +81 -0
  10. package/dist/combat/blow-vectors.fixtures.js.map +1 -0
  11. package/dist/combat/blow-vectors.js +195 -0
  12. package/dist/combat/blow-vectors.js.map +1 -0
  13. package/dist/combat/brand-slay.d.ts +22 -9
  14. package/dist/combat/brand-slay.d.ts.map +1 -1
  15. package/dist/combat/brand-slay.js +37 -10
  16. package/dist/combat/brand-slay.js.map +1 -1
  17. package/dist/combat/index.d.ts +1 -1
  18. package/dist/combat/index.js +1 -1
  19. package/dist/combat/melee.d.ts +25 -13
  20. package/dist/combat/melee.d.ts.map +1 -1
  21. package/dist/combat/melee.js +23 -19
  22. package/dist/combat/melee.js.map +1 -1
  23. package/dist/combat/mon-melee.d.ts +166 -7
  24. package/dist/combat/mon-melee.d.ts.map +1 -1
  25. package/dist/combat/mon-melee.js +469 -338
  26. package/dist/combat/mon-melee.js.map +1 -1
  27. package/dist/combat/ranged.d.ts +15 -13
  28. package/dist/combat/ranged.d.ts.map +1 -1
  29. package/dist/combat/ranged.js +31 -29
  30. package/dist/combat/ranged.js.map +1 -1
  31. package/dist/constants.d.ts +14 -0
  32. package/dist/constants.d.ts.map +1 -1
  33. package/dist/constants.js +14 -0
  34. package/dist/constants.js.map +1 -1
  35. package/dist/datafile.d.ts +48 -0
  36. package/dist/datafile.d.ts.map +1 -0
  37. package/dist/datafile.js +102 -0
  38. package/dist/datafile.js.map +1 -0
  39. package/dist/effects/effect-info-registry.d.ts +237 -0
  40. package/dist/effects/effect-info-registry.d.ts.map +1 -0
  41. package/dist/effects/effect-info-registry.js +138 -0
  42. package/dist/effects/effect-info-registry.js.map +1 -0
  43. package/dist/effects/effect-info-vectors.d.ts +92 -0
  44. package/dist/effects/effect-info-vectors.d.ts.map +1 -0
  45. package/dist/effects/effect-info-vectors.fixtures.d.ts +18 -0
  46. package/dist/effects/effect-info-vectors.fixtures.d.ts.map +1 -0
  47. package/dist/effects/effect-info-vectors.fixtures.js +51 -0
  48. package/dist/effects/effect-info-vectors.fixtures.js.map +1 -0
  49. package/dist/effects/effect-info-vectors.js +315 -0
  50. package/dist/effects/effect-info-vectors.js.map +1 -0
  51. package/dist/effects/effect-info.d.ts +5 -1
  52. package/dist/effects/effect-info.d.ts.map +1 -1
  53. package/dist/effects/effect-info.js +245 -143
  54. package/dist/effects/effect-info.js.map +1 -1
  55. package/dist/effects/effect.d.ts +4 -3
  56. package/dist/effects/effect.d.ts.map +1 -1
  57. package/dist/effects/effect.js +88 -70
  58. package/dist/effects/effect.js.map +1 -1
  59. package/dist/effects/handlers.d.ts +1 -1
  60. package/dist/effects/handlers.d.ts.map +1 -1
  61. package/dist/effects/handlers.js +5 -2
  62. package/dist/effects/handlers.js.map +1 -1
  63. package/dist/effects/interpreter.d.ts +11 -0
  64. package/dist/effects/interpreter.d.ts.map +1 -1
  65. package/dist/effects/interpreter.js +13 -0
  66. package/dist/effects/interpreter.js.map +1 -1
  67. package/dist/game/cave-cmd.d.ts +9 -5
  68. package/dist/game/cave-cmd.d.ts.map +1 -1
  69. package/dist/game/cave-cmd.js +56 -11
  70. package/dist/game/cave-cmd.js.map +1 -1
  71. package/dist/game/cave-square.d.ts +42 -0
  72. package/dist/game/cave-square.d.ts.map +1 -0
  73. package/dist/game/cave-square.js +73 -0
  74. package/dist/game/cave-square.js.map +1 -0
  75. package/dist/game/char-sheet.d.ts +12 -8
  76. package/dist/game/char-sheet.d.ts.map +1 -1
  77. package/dist/game/char-sheet.js +41 -8
  78. package/dist/game/char-sheet.js.map +1 -1
  79. package/dist/game/chest.d.ts +2 -3
  80. package/dist/game/chest.d.ts.map +1 -1
  81. package/dist/game/chest.js +17 -6
  82. package/dist/game/chest.js.map +1 -1
  83. package/dist/game/context.d.ts +213 -22
  84. package/dist/game/context.d.ts.map +1 -1
  85. package/dist/game/context.js +109 -17
  86. package/dist/game/context.js.map +1 -1
  87. package/dist/game/describe.d.ts +13 -0
  88. package/dist/game/describe.d.ts.map +1 -1
  89. package/dist/game/describe.js +32 -4
  90. package/dist/game/describe.js.map +1 -1
  91. package/dist/game/display.d.ts +97 -8
  92. package/dist/game/display.d.ts.map +1 -1
  93. package/dist/game/display.js +157 -13
  94. package/dist/game/display.js.map +1 -1
  95. package/dist/game/dump-level.d.ts.map +1 -1
  96. package/dist/game/dump-level.js +3 -2
  97. package/dist/game/dump-level.js.map +1 -1
  98. package/dist/game/effect-attack.d.ts.map +1 -1
  99. package/dist/game/effect-attack.js +36 -27
  100. package/dist/game/effect-attack.js.map +1 -1
  101. package/dist/game/effect-detect.d.ts +4 -4
  102. package/dist/game/effect-detect.d.ts.map +1 -1
  103. package/dist/game/effect-detect.js +42 -9
  104. package/dist/game/effect-detect.js.map +1 -1
  105. package/dist/game/effect-env.d.ts +7 -1
  106. package/dist/game/effect-env.d.ts.map +1 -1
  107. package/dist/game/effect-env.js +1 -0
  108. package/dist/game/effect-env.js.map +1 -1
  109. package/dist/game/effect-general.d.ts +5 -3
  110. package/dist/game/effect-general.d.ts.map +1 -1
  111. package/dist/game/effect-general.js +94 -43
  112. package/dist/game/effect-general.js.map +1 -1
  113. package/dist/game/effect-item.d.ts +2 -1
  114. package/dist/game/effect-item.d.ts.map +1 -1
  115. package/dist/game/effect-item.js +136 -82
  116. package/dist/game/effect-item.js.map +1 -1
  117. package/dist/game/effect-melee.d.ts +10 -4
  118. package/dist/game/effect-melee.d.ts.map +1 -1
  119. package/dist/game/effect-melee.js +44 -19
  120. package/dist/game/effect-melee.js.map +1 -1
  121. package/dist/game/effect-monster.d.ts +26 -4
  122. package/dist/game/effect-monster.d.ts.map +1 -1
  123. package/dist/game/effect-monster.js +52 -9
  124. package/dist/game/effect-monster.js.map +1 -1
  125. package/dist/game/effect-teleport.d.ts +58 -19
  126. package/dist/game/effect-teleport.d.ts.map +1 -1
  127. package/dist/game/effect-teleport.js +143 -27
  128. package/dist/game/effect-teleport.js.map +1 -1
  129. package/dist/game/effect-terrain.d.ts +1 -3
  130. package/dist/game/effect-terrain.d.ts.map +1 -1
  131. package/dist/game/effect-terrain.js +38 -22
  132. package/dist/game/effect-terrain.js.map +1 -1
  133. package/dist/game/equip-cmp.d.ts.map +1 -1
  134. package/dist/game/equip-cmp.js +10 -7
  135. package/dist/game/equip-cmp.js.map +1 -1
  136. package/dist/game/floor.d.ts +16 -4
  137. package/dist/game/floor.d.ts.map +1 -1
  138. package/dist/game/floor.js +22 -4
  139. package/dist/game/floor.js.map +1 -1
  140. package/dist/game/gear.d.ts +101 -13
  141. package/dist/game/gear.d.ts.map +1 -1
  142. package/dist/game/gear.js +183 -24
  143. package/dist/game/gear.js.map +1 -1
  144. package/dist/game/harness.d.ts +2 -1
  145. package/dist/game/harness.d.ts.map +1 -1
  146. package/dist/game/harness.js +12 -1
  147. package/dist/game/harness.js.map +1 -1
  148. package/dist/game/ignore-cmd.d.ts +47 -8
  149. package/dist/game/ignore-cmd.d.ts.map +1 -1
  150. package/dist/game/ignore-cmd.js +76 -8
  151. package/dist/game/ignore-cmd.js.map +1 -1
  152. package/dist/game/known.d.ts +223 -34
  153. package/dist/game/known.d.ts.map +1 -1
  154. package/dist/game/known.js +301 -67
  155. package/dist/game/known.js.map +1 -1
  156. package/dist/game/loop.d.ts +4 -3
  157. package/dist/game/loop.d.ts.map +1 -1
  158. package/dist/game/loop.js +49 -29
  159. package/dist/game/loop.js.map +1 -1
  160. package/dist/game/lore-color.d.ts.map +1 -1
  161. package/dist/game/lore-color.js +13 -1
  162. package/dist/game/lore-color.js.map +1 -1
  163. package/dist/game/mon-cast.d.ts +3 -4
  164. package/dist/game/mon-cast.d.ts.map +1 -1
  165. package/dist/game/mon-cast.js +30 -2
  166. package/dist/game/mon-cast.js.map +1 -1
  167. package/dist/game/mon-cmd.d.ts +4 -5
  168. package/dist/game/mon-cmd.d.ts.map +1 -1
  169. package/dist/game/mon-cmd.js +80 -27
  170. package/dist/game/mon-cmd.js.map +1 -1
  171. package/dist/game/mon-death.d.ts +8 -4
  172. package/dist/game/mon-death.d.ts.map +1 -1
  173. package/dist/game/mon-death.js +6 -9
  174. package/dist/game/mon-death.js.map +1 -1
  175. package/dist/game/mon-group.d.ts +9 -5
  176. package/dist/game/mon-group.d.ts.map +1 -1
  177. package/dist/game/mon-group.js +9 -5
  178. package/dist/game/mon-group.js.map +1 -1
  179. package/dist/game/mon-message.d.ts +90 -40
  180. package/dist/game/mon-message.d.ts.map +1 -1
  181. package/dist/game/mon-message.js +267 -59
  182. package/dist/game/mon-message.js.map +1 -1
  183. package/dist/game/mon-place.d.ts +11 -1
  184. package/dist/game/mon-place.d.ts.map +1 -1
  185. package/dist/game/mon-place.js +15 -3
  186. package/dist/game/mon-place.js.map +1 -1
  187. package/dist/game/mon-ranged.d.ts +2 -1
  188. package/dist/game/mon-ranged.d.ts.map +1 -1
  189. package/dist/game/mon-ranged.js +11 -5
  190. package/dist/game/mon-ranged.js.map +1 -1
  191. package/dist/game/mon-side.d.ts +3 -2
  192. package/dist/game/mon-side.d.ts.map +1 -1
  193. package/dist/game/mon-side.js +15 -14
  194. package/dist/game/mon-side.js.map +1 -1
  195. package/dist/game/monster-turn.d.ts +20 -10
  196. package/dist/game/monster-turn.d.ts.map +1 -1
  197. package/dist/game/monster-turn.js +110 -83
  198. package/dist/game/monster-turn.js.map +1 -1
  199. package/dist/game/notice.d.ts +36 -0
  200. package/dist/game/notice.d.ts.map +1 -0
  201. package/dist/game/notice.js +79 -0
  202. package/dist/game/notice.js.map +1 -0
  203. package/dist/game/obj-cmd.d.ts +14 -3
  204. package/dist/game/obj-cmd.d.ts.map +1 -1
  205. package/dist/game/obj-cmd.js +109 -35
  206. package/dist/game/obj-cmd.js.map +1 -1
  207. package/dist/game/obj-list.d.ts +13 -2
  208. package/dist/game/obj-list.d.ts.map +1 -1
  209. package/dist/game/obj-list.js +42 -27
  210. package/dist/game/obj-list.js.map +1 -1
  211. package/dist/game/object-inspect.d.ts +9 -0
  212. package/dist/game/object-inspect.d.ts.map +1 -1
  213. package/dist/game/object-inspect.js +4 -0
  214. package/dist/game/object-inspect.js.map +1 -1
  215. package/dist/game/pickup.d.ts +0 -2
  216. package/dist/game/pickup.d.ts.map +1 -1
  217. package/dist/game/pickup.js +18 -3
  218. package/dist/game/pickup.js.map +1 -1
  219. package/dist/game/player-side-vectors.d.ts +174 -0
  220. package/dist/game/player-side-vectors.d.ts.map +1 -0
  221. package/dist/game/player-side-vectors.fixtures.d.ts +33 -0
  222. package/dist/game/player-side-vectors.fixtures.d.ts.map +1 -0
  223. package/dist/game/player-side-vectors.fixtures.js +191 -0
  224. package/dist/game/player-side-vectors.fixtures.js.map +1 -0
  225. package/dist/game/player-side-vectors.js +292 -0
  226. package/dist/game/player-side-vectors.js.map +1 -0
  227. package/dist/game/player-side.d.ts +129 -1
  228. package/dist/game/player-side.d.ts.map +1 -1
  229. package/dist/game/player-side.js +520 -338
  230. package/dist/game/player-side.js.map +1 -1
  231. package/dist/game/player-turn.d.ts +26 -7
  232. package/dist/game/player-turn.d.ts.map +1 -1
  233. package/dist/game/player-turn.js +180 -33
  234. package/dist/game/player-turn.js.map +1 -1
  235. package/dist/game/poly.d.ts +38 -0
  236. package/dist/game/poly.d.ts.map +1 -0
  237. package/dist/game/poly.js +82 -0
  238. package/dist/game/poly.js.map +1 -0
  239. package/dist/game/project-cast.d.ts +20 -3
  240. package/dist/game/project-cast.d.ts.map +1 -1
  241. package/dist/game/project-cast.js +49 -6
  242. package/dist/game/project-cast.js.map +1 -1
  243. package/dist/game/project-feat-vectors.d.ts +124 -0
  244. package/dist/game/project-feat-vectors.d.ts.map +1 -0
  245. package/dist/game/project-feat-vectors.fixtures.d.ts +13 -0
  246. package/dist/game/project-feat-vectors.fixtures.d.ts.map +1 -0
  247. package/dist/game/project-feat-vectors.fixtures.js +94 -0
  248. package/dist/game/project-feat-vectors.fixtures.js.map +1 -0
  249. package/dist/game/project-feat-vectors.js +229 -0
  250. package/dist/game/project-feat-vectors.js.map +1 -0
  251. package/dist/game/project-feat.d.ts +53 -1
  252. package/dist/game/project-feat.d.ts.map +1 -1
  253. package/dist/game/project-feat.js +374 -247
  254. package/dist/game/project-feat.js.map +1 -1
  255. package/dist/game/project-monster.d.ts +21 -8
  256. package/dist/game/project-monster.d.ts.map +1 -1
  257. package/dist/game/project-monster.js +45 -21
  258. package/dist/game/project-monster.js.map +1 -1
  259. package/dist/game/project-obj.d.ts +62 -0
  260. package/dist/game/project-obj.d.ts.map +1 -1
  261. package/dist/game/project-obj.js +72 -41
  262. package/dist/game/project-obj.js.map +1 -1
  263. package/dist/game/project-player.d.ts +8 -0
  264. package/dist/game/project-player.d.ts.map +1 -1
  265. package/dist/game/project-player.js.map +1 -1
  266. package/dist/game/projection-handlers.d.ts +81 -0
  267. package/dist/game/projection-handlers.d.ts.map +1 -0
  268. package/dist/game/projection-handlers.js +93 -0
  269. package/dist/game/projection-handlers.js.map +1 -0
  270. package/dist/game/quest.d.ts +23 -0
  271. package/dist/game/quest.d.ts.map +1 -1
  272. package/dist/game/quest.js +42 -0
  273. package/dist/game/quest.js.map +1 -1
  274. package/dist/game/ranged-cmd.d.ts +13 -2
  275. package/dist/game/ranged-cmd.d.ts.map +1 -1
  276. package/dist/game/ranged-cmd.js +32 -15
  277. package/dist/game/ranged-cmd.js.map +1 -1
  278. package/dist/game/repeat.d.ts +78 -0
  279. package/dist/game/repeat.d.ts.map +1 -0
  280. package/dist/game/repeat.js +88 -0
  281. package/dist/game/repeat.js.map +1 -0
  282. package/dist/game/shape-inspect.d.ts +74 -0
  283. package/dist/game/shape-inspect.d.ts.map +1 -0
  284. package/dist/game/shape-inspect.js +114 -0
  285. package/dist/game/shape-inspect.js.map +1 -0
  286. package/dist/game/spell-cmd.d.ts +15 -4
  287. package/dist/game/spell-cmd.d.ts.map +1 -1
  288. package/dist/game/spell-cmd.js +35 -6
  289. package/dist/game/spell-cmd.js.map +1 -1
  290. package/dist/game/spoil.d.ts.map +1 -1
  291. package/dist/game/spoil.js +89 -40
  292. package/dist/game/spoil.js.map +1 -1
  293. package/dist/game/steal.d.ts +3 -3
  294. package/dist/game/steal.d.ts.map +1 -1
  295. package/dist/game/steal.js +9 -4
  296. package/dist/game/steal.js.map +1 -1
  297. package/dist/game/target-loop.d.ts +10 -4
  298. package/dist/game/target-loop.d.ts.map +1 -1
  299. package/dist/game/target-loop.js +14 -8
  300. package/dist/game/target-loop.js.map +1 -1
  301. package/dist/game/target.d.ts +4 -4
  302. package/dist/game/target.d.ts.map +1 -1
  303. package/dist/game/target.js +19 -8
  304. package/dist/game/target.js.map +1 -1
  305. package/dist/game/ui-entry.d.ts +63 -4
  306. package/dist/game/ui-entry.d.ts.map +1 -1
  307. package/dist/game/ui-entry.js +151 -23
  308. package/dist/game/ui-entry.js.map +1 -1
  309. package/dist/game/wizard.d.ts +23 -5
  310. package/dist/game/wizard.d.ts.map +1 -1
  311. package/dist/game/wizard.js +93 -11
  312. package/dist/game/wizard.js.map +1 -1
  313. package/dist/game/world.d.ts.map +1 -1
  314. package/dist/game/world.js +6 -0
  315. package/dist/game/world.js.map +1 -1
  316. package/dist/gen/cave.d.ts +49 -8
  317. package/dist/gen/cave.d.ts.map +1 -1
  318. package/dist/gen/cave.js +200 -28
  319. package/dist/gen/cave.js.map +1 -1
  320. package/dist/gen/generate.d.ts +54 -9
  321. package/dist/gen/generate.d.ts.map +1 -1
  322. package/dist/gen/generate.js +55 -8
  323. package/dist/gen/generate.js.map +1 -1
  324. package/dist/gen/glyph-vectors.d.ts +109 -0
  325. package/dist/gen/glyph-vectors.d.ts.map +1 -0
  326. package/dist/gen/glyph-vectors.fixtures.d.ts +18 -0
  327. package/dist/gen/glyph-vectors.fixtures.d.ts.map +1 -0
  328. package/dist/gen/glyph-vectors.fixtures.js +94 -0
  329. package/dist/gen/glyph-vectors.fixtures.js.map +1 -0
  330. package/dist/gen/glyph-vectors.js +218 -0
  331. package/dist/gen/glyph-vectors.js.map +1 -0
  332. package/dist/gen/glyph.d.ts +104 -0
  333. package/dist/gen/glyph.d.ts.map +1 -0
  334. package/dist/gen/glyph.js +72 -0
  335. package/dist/gen/glyph.js.map +1 -0
  336. package/dist/gen/room.d.ts +26 -2
  337. package/dist/gen/room.d.ts.map +1 -1
  338. package/dist/gen/room.js +414 -264
  339. package/dist/gen/room.js.map +1 -1
  340. package/dist/gen/util.d.ts +11 -2
  341. package/dist/gen/util.d.ts.map +1 -1
  342. package/dist/gen/util.js +31 -9
  343. package/dist/gen/util.js.map +1 -1
  344. package/dist/i18n/i18n.d.ts +207 -0
  345. package/dist/i18n/i18n.d.ts.map +1 -0
  346. package/dist/i18n/i18n.js +405 -0
  347. package/dist/i18n/i18n.js.map +1 -0
  348. package/dist/i18n/text.d.ts +62 -0
  349. package/dist/i18n/text.d.ts.map +1 -0
  350. package/dist/i18n/text.js +120 -0
  351. package/dist/i18n/text.js.map +1 -0
  352. package/dist/index.d.ts +25 -0
  353. package/dist/index.d.ts.map +1 -1
  354. package/dist/index.js +38 -0
  355. package/dist/index.js.map +1 -1
  356. package/dist/mod/extension.d.ts +115 -0
  357. package/dist/mod/extension.d.ts.map +1 -0
  358. package/dist/mod/extension.js +85 -0
  359. package/dist/mod/extension.js.map +1 -0
  360. package/dist/mod/ids.d.ts +10 -0
  361. package/dist/mod/ids.d.ts.map +1 -1
  362. package/dist/mod/ids.js +90 -21
  363. package/dist/mod/ids.js.map +1 -1
  364. package/dist/mod/record-keys.d.ts +43 -0
  365. package/dist/mod/record-keys.d.ts.map +1 -0
  366. package/dist/mod/record-keys.js +100 -0
  367. package/dist/mod/record-keys.js.map +1 -0
  368. package/dist/mod/registry-host.d.ts +472 -0
  369. package/dist/mod/registry-host.d.ts.map +1 -1
  370. package/dist/mod/registry-host.js +313 -0
  371. package/dist/mod/registry-host.js.map +1 -1
  372. package/dist/mod/save-blocks.d.ts +1 -1
  373. package/dist/mon/bind.d.ts.map +1 -1
  374. package/dist/mon/bind.js +6 -3
  375. package/dist/mon/bind.js.map +1 -1
  376. package/dist/mon/knowledge-groups.d.ts +0 -6
  377. package/dist/mon/knowledge-groups.d.ts.map +1 -1
  378. package/dist/mon/knowledge-groups.js +12 -8
  379. package/dist/mon/knowledge-groups.js.map +1 -1
  380. package/dist/mon/lore-describe.d.ts +46 -10
  381. package/dist/mon/lore-describe.d.ts.map +1 -1
  382. package/dist/mon/lore-describe.js +74 -22
  383. package/dist/mon/lore-describe.js.map +1 -1
  384. package/dist/mon/lore-file.d.ts +126 -0
  385. package/dist/mon/lore-file.d.ts.map +1 -0
  386. package/dist/mon/lore-file.js +291 -0
  387. package/dist/mon/lore-file.js.map +1 -0
  388. package/dist/mon/lore.d.ts +9 -3
  389. package/dist/mon/lore.d.ts.map +1 -1
  390. package/dist/mon/lore.js +9 -3
  391. package/dist/mon/lore.js.map +1 -1
  392. package/dist/mon/make.d.ts +4 -2
  393. package/dist/mon/make.d.ts.map +1 -1
  394. package/dist/mon/make.js +4 -2
  395. package/dist/mon/make.js.map +1 -1
  396. package/dist/mon/monster.d.ts +1 -1
  397. package/dist/mon/monster.js +1 -1
  398. package/dist/mon/predicate.d.ts +14 -9
  399. package/dist/mon/predicate.d.ts.map +1 -1
  400. package/dist/mon/predicate.js +14 -9
  401. package/dist/mon/predicate.js.map +1 -1
  402. package/dist/mon/project-mon.d.ts +16 -3
  403. package/dist/mon/project-mon.d.ts.map +1 -1
  404. package/dist/mon/project-mon.js +14 -5
  405. package/dist/mon/project-mon.js.map +1 -1
  406. package/dist/mon/steal.d.ts +31 -4
  407. package/dist/mon/steal.d.ts.map +1 -1
  408. package/dist/mon/steal.js +20 -7
  409. package/dist/mon/steal.js.map +1 -1
  410. package/dist/mon/take-hit.d.ts +8 -3
  411. package/dist/mon/take-hit.d.ts.map +1 -1
  412. package/dist/mon/take-hit.js +8 -3
  413. package/dist/mon/take-hit.js.map +1 -1
  414. package/dist/mon/timed.d.ts +15 -9
  415. package/dist/mon/timed.d.ts.map +1 -1
  416. package/dist/mon/timed.js +13 -8
  417. package/dist/mon/timed.js.map +1 -1
  418. package/dist/mon/types.d.ts +11 -4
  419. package/dist/mon/types.d.ts.map +1 -1
  420. package/dist/mon/types.js.map +1 -1
  421. package/dist/msg.d.ts +40 -1
  422. package/dist/msg.d.ts.map +1 -1
  423. package/dist/msg.js +42 -1
  424. package/dist/msg.js.map +1 -1
  425. package/dist/obj/artifact-fake.d.ts +46 -14
  426. package/dist/obj/artifact-fake.d.ts.map +1 -1
  427. package/dist/obj/artifact-fake.js +46 -17
  428. package/dist/obj/artifact-fake.js.map +1 -1
  429. package/dist/obj/bind.d.ts +23 -0
  430. package/dist/obj/bind.d.ts.map +1 -1
  431. package/dist/obj/bind.js +49 -9
  432. package/dist/obj/bind.js.map +1 -1
  433. package/dist/obj/desc-vectors.d.ts +68 -0
  434. package/dist/obj/desc-vectors.d.ts.map +1 -0
  435. package/dist/obj/desc-vectors.fixtures.d.ts +12 -0
  436. package/dist/obj/desc-vectors.fixtures.d.ts.map +1 -0
  437. package/dist/obj/desc-vectors.fixtures.js +95 -0
  438. package/dist/obj/desc-vectors.fixtures.js.map +1 -0
  439. package/dist/obj/desc-vectors.js +86 -0
  440. package/dist/obj/desc-vectors.js.map +1 -0
  441. package/dist/obj/desc.d.ts +17 -2
  442. package/dist/obj/desc.d.ts.map +1 -1
  443. package/dist/obj/desc.js +129 -54
  444. package/dist/obj/desc.js.map +1 -1
  445. package/dist/obj/effects-info.d.ts.map +1 -1
  446. package/dist/obj/effects-info.js +87 -58
  447. package/dist/obj/effects-info.js.map +1 -1
  448. package/dist/obj/fake-object.d.ts +70 -0
  449. package/dist/obj/fake-object.d.ts.map +1 -0
  450. package/dist/obj/fake-object.js +104 -0
  451. package/dist/obj/fake-object.js.map +1 -0
  452. package/dist/obj/ignore.d.ts +55 -12
  453. package/dist/obj/ignore.d.ts.map +1 -1
  454. package/dist/obj/ignore.js +68 -31
  455. package/dist/obj/ignore.js.map +1 -1
  456. package/dist/obj/knowledge.d.ts +46 -3
  457. package/dist/obj/knowledge.d.ts.map +1 -1
  458. package/dist/obj/knowledge.js +169 -151
  459. package/dist/obj/knowledge.js.map +1 -1
  460. package/dist/obj/known-object.d.ts +68 -8
  461. package/dist/obj/known-object.d.ts.map +1 -1
  462. package/dist/obj/known-object.js +90 -10
  463. package/dist/obj/known-object.js.map +1 -1
  464. package/dist/obj/make.d.ts +15 -4
  465. package/dist/obj/make.d.ts.map +1 -1
  466. package/dist/obj/make.js +49 -27
  467. package/dist/obj/make.js.map +1 -1
  468. package/dist/obj/object-info.d.ts +10 -0
  469. package/dist/obj/object-info.d.ts.map +1 -1
  470. package/dist/obj/object-info.js +93 -7
  471. package/dist/obj/object-info.js.map +1 -1
  472. package/dist/obj/object.d.ts +42 -4
  473. package/dist/obj/object.d.ts.map +1 -1
  474. package/dist/obj/object.js +362 -147
  475. package/dist/obj/object.js.map +1 -1
  476. package/dist/obj/power.d.ts +23 -4
  477. package/dist/obj/power.d.ts.map +1 -1
  478. package/dist/obj/power.js +231 -65
  479. package/dist/obj/power.js.map +1 -1
  480. package/dist/obj/randart-build.d.ts +27 -22
  481. package/dist/obj/randart-build.d.ts.map +1 -1
  482. package/dist/obj/randart-build.js +435 -357
  483. package/dist/obj/randart-build.js.map +1 -1
  484. package/dist/obj/randart-data.d.ts +62 -24
  485. package/dist/obj/randart-data.d.ts.map +1 -1
  486. package/dist/obj/randart-data.js +333 -189
  487. package/dist/obj/randart-data.js.map +1 -1
  488. package/dist/obj/randart-file.d.ts +21 -0
  489. package/dist/obj/randart-file.d.ts.map +1 -0
  490. package/dist/obj/randart-file.js +119 -0
  491. package/dist/obj/randart-file.js.map +1 -0
  492. package/dist/obj/randart-log.d.ts +63 -0
  493. package/dist/obj/randart-log.d.ts.map +1 -0
  494. package/dist/obj/randart-log.js +72 -0
  495. package/dist/obj/randart-log.js.map +1 -0
  496. package/dist/obj/randart-registry.d.ts +178 -0
  497. package/dist/obj/randart-registry.d.ts.map +1 -0
  498. package/dist/obj/randart-registry.js +140 -0
  499. package/dist/obj/randart-registry.js.map +1 -0
  500. package/dist/obj/randart-vectors.d.ts +97 -0
  501. package/dist/obj/randart-vectors.d.ts.map +1 -0
  502. package/dist/obj/randart-vectors.fixtures.d.ts +18 -0
  503. package/dist/obj/randart-vectors.fixtures.d.ts.map +1 -0
  504. package/dist/obj/randart-vectors.fixtures.js +50 -0
  505. package/dist/obj/randart-vectors.fixtures.js.map +1 -0
  506. package/dist/obj/randart-vectors.js +311 -0
  507. package/dist/obj/randart-vectors.js.map +1 -0
  508. package/dist/obj/randart.d.ts +53 -15
  509. package/dist/obj/randart.d.ts.map +1 -1
  510. package/dist/obj/randart.js +166 -44
  511. package/dist/obj/randart.js.map +1 -1
  512. package/dist/obj/rune-registry.d.ts +180 -0
  513. package/dist/obj/rune-registry.d.ts.map +1 -0
  514. package/dist/obj/rune-registry.js +161 -0
  515. package/dist/obj/rune-registry.js.map +1 -0
  516. package/dist/obj/rune-vectors.d.ts +99 -0
  517. package/dist/obj/rune-vectors.d.ts.map +1 -0
  518. package/dist/obj/rune-vectors.fixtures.d.ts +44 -0
  519. package/dist/obj/rune-vectors.fixtures.d.ts.map +1 -0
  520. package/dist/obj/rune-vectors.fixtures.js +125 -0
  521. package/dist/obj/rune-vectors.fixtures.js.map +1 -0
  522. package/dist/obj/rune-vectors.js +159 -0
  523. package/dist/obj/rune-vectors.js.map +1 -0
  524. package/dist/obj/tval-registry.d.ts +174 -0
  525. package/dist/obj/tval-registry.d.ts.map +1 -0
  526. package/dist/obj/tval-registry.js +151 -0
  527. package/dist/obj/tval-registry.js.map +1 -0
  528. package/dist/obj/tval-vectors.d.ts +85 -0
  529. package/dist/obj/tval-vectors.d.ts.map +1 -0
  530. package/dist/obj/tval-vectors.fixtures.d.ts +24 -0
  531. package/dist/obj/tval-vectors.fixtures.d.ts.map +1 -0
  532. package/dist/obj/tval-vectors.fixtures.js +41 -0
  533. package/dist/obj/tval-vectors.fixtures.js.map +1 -0
  534. package/dist/obj/tval-vectors.js +76 -0
  535. package/dist/obj/tval-vectors.js.map +1 -0
  536. package/dist/obj/types.d.ts +18 -7
  537. package/dist/obj/types.d.ts.map +1 -1
  538. package/dist/obj/types.js.map +1 -1
  539. package/dist/obj/value.d.ts.map +1 -1
  540. package/dist/obj/value.js +23 -20
  541. package/dist/obj/value.js.map +1 -1
  542. package/dist/parser.d.ts +116 -0
  543. package/dist/parser.d.ts.map +1 -0
  544. package/dist/parser.js +205 -0
  545. package/dist/parser.js.map +1 -0
  546. package/dist/player/bind.d.ts.map +1 -1
  547. package/dist/player/bind.js +3 -2
  548. package/dist/player/bind.js.map +1 -1
  549. package/dist/player/birth.d.ts +2 -1
  550. package/dist/player/birth.d.ts.map +1 -1
  551. package/dist/player/birth.js +2 -1
  552. package/dist/player/birth.js.map +1 -1
  553. package/dist/player/calcs.d.ts +58 -23
  554. package/dist/player/calcs.d.ts.map +1 -1
  555. package/dist/player/calcs.js +93 -36
  556. package/dist/player/calcs.js.map +1 -1
  557. package/dist/player/death.d.ts +50 -0
  558. package/dist/player/death.d.ts.map +1 -0
  559. package/dist/player/death.js +46 -0
  560. package/dist/player/death.js.map +1 -0
  561. package/dist/player/options-file.d.ts +129 -0
  562. package/dist/player/options-file.d.ts.map +1 -0
  563. package/dist/player/options-file.js +309 -0
  564. package/dist/player/options-file.js.map +1 -0
  565. package/dist/player/options.d.ts +2 -1
  566. package/dist/player/options.d.ts.map +1 -1
  567. package/dist/player/options.js +2 -1
  568. package/dist/player/options.js.map +1 -1
  569. package/dist/player/player.d.ts +50 -3
  570. package/dist/player/player.d.ts.map +1 -1
  571. package/dist/player/player.js +10 -1
  572. package/dist/player/player.js.map +1 -1
  573. package/dist/player/shape-lore.d.ts +11 -7
  574. package/dist/player/shape-lore.d.ts.map +1 -1
  575. package/dist/player/shape-lore.js +24 -11
  576. package/dist/player/shape-lore.js.map +1 -1
  577. package/dist/player/timed.d.ts +5 -3
  578. package/dist/player/timed.d.ts.map +1 -1
  579. package/dist/player/timed.js +11 -2
  580. package/dist/player/timed.js.map +1 -1
  581. package/dist/player/types.d.ts +29 -4
  582. package/dist/player/types.d.ts.map +1 -1
  583. package/dist/player/types.js +24 -1
  584. package/dist/player/types.js.map +1 -1
  585. package/dist/session/boot.d.ts +26 -2
  586. package/dist/session/boot.d.ts.map +1 -1
  587. package/dist/session/boot.js +21 -2
  588. package/dist/session/boot.js.map +1 -1
  589. package/dist/session/game.d.ts +15 -0
  590. package/dist/session/game.d.ts.map +1 -1
  591. package/dist/session/game.js +616 -140
  592. package/dist/session/game.js.map +1 -1
  593. package/dist/session/save-migrate.d.ts.map +1 -1
  594. package/dist/session/save-migrate.js +76 -1
  595. package/dist/session/save-migrate.js.map +1 -1
  596. package/dist/session/save.d.ts +75 -12
  597. package/dist/session/save.d.ts.map +1 -1
  598. package/dist/session/save.js +177 -18
  599. package/dist/session/save.js.map +1 -1
  600. package/dist/store/bind.d.ts +1 -1
  601. package/dist/store/bind.d.ts.map +1 -1
  602. package/dist/store/bind.js +4 -3
  603. package/dist/store/bind.js.map +1 -1
  604. package/dist/store/mass-produce-vectors.d.ts +40 -0
  605. package/dist/store/mass-produce-vectors.d.ts.map +1 -0
  606. package/dist/store/mass-produce-vectors.fixtures.d.ts +13 -0
  607. package/dist/store/mass-produce-vectors.fixtures.d.ts.map +1 -0
  608. package/dist/store/mass-produce-vectors.fixtures.js +52 -0
  609. package/dist/store/mass-produce-vectors.fixtures.js.map +1 -0
  610. package/dist/store/mass-produce-vectors.js +44 -0
  611. package/dist/store/mass-produce-vectors.js.map +1 -0
  612. package/dist/store/store.d.ts +104 -7
  613. package/dist/store/store.d.ts.map +1 -1
  614. package/dist/store/store.js +199 -77
  615. package/dist/store/store.js.map +1 -1
  616. package/dist/store/transact.d.ts +14 -5
  617. package/dist/store/transact.d.ts.map +1 -1
  618. package/dist/store/transact.js +17 -16
  619. package/dist/store/transact.js.map +1 -1
  620. package/dist/store/types.d.ts +3 -2
  621. package/dist/store/types.d.ts.map +1 -1
  622. package/dist/store/types.js +1 -1
  623. package/dist/version.d.ts +1 -1
  624. package/dist/version.js +1 -1
  625. package/dist/visuals/index.d.ts +1 -0
  626. package/dist/visuals/index.d.ts.map +1 -1
  627. package/dist/visuals/index.js +1 -0
  628. package/dist/visuals/index.js.map +1 -1
  629. package/dist/visuals/map-text.d.ts +8 -0
  630. package/dist/visuals/map-text.d.ts.map +1 -1
  631. package/dist/visuals/map-text.js +1 -1
  632. package/dist/visuals/map-text.js.map +1 -1
  633. package/dist/visuals/object-glyph.d.ts +56 -0
  634. package/dist/visuals/object-glyph.d.ts.map +1 -0
  635. package/dist/visuals/object-glyph.js +52 -0
  636. package/dist/visuals/object-glyph.js.map +1 -0
  637. package/dist/visuals/prefs.d.ts +14 -22
  638. package/dist/visuals/prefs.d.ts.map +1 -1
  639. package/dist/visuals/prefs.js +4 -23
  640. package/dist/visuals/prefs.js.map +1 -1
  641. package/dist/visuals/tile-prefs.d.ts +26 -0
  642. package/dist/visuals/tile-prefs.d.ts.map +1 -1
  643. package/dist/visuals/tile-prefs.js +28 -0
  644. package/dist/visuals/tile-prefs.js.map +1 -1
  645. package/dist/world/feature.d.ts +2 -1
  646. package/dist/world/feature.d.ts.map +1 -1
  647. package/dist/world/feature.js +2 -0
  648. package/dist/world/feature.js.map +1 -1
  649. package/dist/world/project.d.ts +10 -2
  650. package/dist/world/project.d.ts.map +1 -1
  651. package/dist/world/project.js +18 -6
  652. package/dist/world/project.js.map +1 -1
  653. package/dist/world/projection.d.ts +37 -3
  654. package/dist/world/projection.d.ts.map +1 -1
  655. package/dist/world/projection.js +69 -7
  656. package/dist/world/projection.js.map +1 -1
  657. package/dist/world/trap.d.ts +2 -1
  658. package/dist/world/trap.d.ts.map +1 -1
  659. package/dist/world/trap.js +2 -1
  660. package/dist/world/trap.js.map +1 -1
  661. package/dist/world/view.d.ts +1 -1
  662. package/dist/world/view.d.ts.map +1 -1
  663. package/dist/world/view.js +11 -3
  664. package/dist/world/view.js.map +1 -1
  665. package/package.json +1 -1
  666. package/src/agent/perceive.ts +2 -3
  667. package/src/combat/blow-vectors.fixtures.ts +99 -0
  668. package/src/combat/blow-vectors.json +482 -0
  669. package/src/combat/blow-vectors.ts +280 -0
  670. package/src/combat/brand-slay.ts +42 -11
  671. package/src/combat/index.ts +1 -1
  672. package/src/combat/melee.ts +37 -19
  673. package/src/combat/mon-melee.ts +611 -364
  674. package/src/combat/ranged.ts +43 -26
  675. package/src/constants.ts +15 -0
  676. package/src/datafile.ts +148 -0
  677. package/src/effects/effect-info-registry.ts +313 -0
  678. package/src/effects/effect-info-vectors.fixtures.ts +61 -0
  679. package/src/effects/effect-info-vectors.json +11532 -0
  680. package/src/effects/effect-info-vectors.ts +376 -0
  681. package/src/effects/effect-info.ts +1005 -859
  682. package/src/effects/effect.ts +96 -62
  683. package/src/effects/handlers.ts +5 -2
  684. package/src/effects/interpreter.ts +14 -0
  685. package/src/game/cave-cmd.ts +54 -11
  686. package/src/game/cave-square.ts +80 -0
  687. package/src/game/char-sheet.ts +59 -16
  688. package/src/game/chest.ts +25 -8
  689. package/src/game/context.ts +293 -27
  690. package/src/game/describe.ts +34 -4
  691. package/src/game/display.ts +215 -19
  692. package/src/game/dump-level.ts +3 -2
  693. package/src/game/effect-attack.ts +41 -28
  694. package/src/game/effect-detect.ts +45 -8
  695. package/src/game/effect-env.ts +8 -0
  696. package/src/game/effect-general.ts +110 -47
  697. package/src/game/effect-item.ts +1075 -999
  698. package/src/game/effect-melee.ts +51 -18
  699. package/src/game/effect-monster.ts +55 -9
  700. package/src/game/effect-teleport.ts +173 -38
  701. package/src/game/effect-terrain.ts +38 -22
  702. package/src/game/equip-cmp.ts +483 -480
  703. package/src/game/floor.ts +22 -4
  704. package/src/game/gear.ts +210 -21
  705. package/src/game/harness.ts +12 -1
  706. package/src/game/ignore-cmd.ts +96 -8
  707. package/src/game/known.ts +392 -73
  708. package/src/game/loop.ts +52 -27
  709. package/src/game/lore-color.ts +13 -1
  710. package/src/game/mon-cast.ts +31 -6
  711. package/src/game/mon-cmd.ts +95 -30
  712. package/src/game/mon-death.ts +14 -12
  713. package/src/game/mon-group.ts +9 -5
  714. package/src/game/mon-message.ts +363 -65
  715. package/src/game/mon-place.ts +15 -3
  716. package/src/game/mon-ranged.ts +14 -5
  717. package/src/game/mon-side.ts +16 -12
  718. package/src/game/monster-turn.ts +120 -87
  719. package/src/game/notice.ts +85 -0
  720. package/src/game/obj-cmd.ts +126 -37
  721. package/src/game/obj-list.ts +44 -26
  722. package/src/game/object-inspect.ts +13 -0
  723. package/src/game/pickup.ts +29 -5
  724. package/src/game/player-side-vectors.fixtures.ts +215 -0
  725. package/src/game/player-side-vectors.json +6914 -0
  726. package/src/game/player-side-vectors.ts +420 -0
  727. package/src/game/player-side.ts +622 -340
  728. package/src/game/player-turn.ts +228 -31
  729. package/src/game/poly.ts +107 -0
  730. package/src/game/project-cast.ts +63 -6
  731. package/src/game/project-feat-vectors.fixtures.ts +106 -0
  732. package/src/game/project-feat-vectors.json +6554 -0
  733. package/src/game/project-feat-vectors.ts +323 -0
  734. package/src/game/project-feat.ts +417 -248
  735. package/src/game/project-monster.ts +69 -30
  736. package/src/game/project-obj-vectors.json +1 -0
  737. package/src/game/project-obj.ts +115 -47
  738. package/src/game/project-player.ts +8 -0
  739. package/src/game/projection-handlers.ts +110 -0
  740. package/src/game/quest.ts +48 -0
  741. package/src/game/ranged-cmd.ts +45 -11
  742. package/src/game/repeat.ts +92 -0
  743. package/src/game/shape-inspect.ts +166 -0
  744. package/src/game/spell-cmd.ts +39 -6
  745. package/src/game/spoil.ts +92 -39
  746. package/src/game/steal.ts +9 -4
  747. package/src/game/target-loop.ts +14 -7
  748. package/src/game/target.ts +18 -7
  749. package/src/game/ui-entry.ts +172 -22
  750. package/src/game/wizard.ts +97 -10
  751. package/src/game/world.ts +6 -0
  752. package/src/gen/cave.ts +225 -26
  753. package/src/gen/generate.ts +62 -9
  754. package/src/gen/glyph-vectors.fixtures.ts +122 -0
  755. package/src/gen/glyph-vectors.json +5996 -0
  756. package/src/gen/glyph-vectors.ts +280 -0
  757. package/src/gen/glyph.ts +132 -0
  758. package/src/gen/room.ts +446 -265
  759. package/src/gen/util.ts +32 -9
  760. package/src/i18n/i18n.ts +492 -0
  761. package/src/i18n/text.ts +117 -0
  762. package/src/index.ts +307 -254
  763. package/src/mod/extension.ts +136 -0
  764. package/src/mod/ids.ts +92 -21
  765. package/src/mod/record-keys.ts +102 -0
  766. package/src/mod/registry-host.ts +1063 -234
  767. package/src/mon/bind.ts +14 -8
  768. package/src/mon/knowledge-groups.ts +12 -12
  769. package/src/mon/lore-describe.ts +93 -26
  770. package/src/mon/lore-file.ts +365 -0
  771. package/src/mon/lore.ts +9 -3
  772. package/src/mon/make.ts +4 -2
  773. package/src/mon/monster.ts +1 -1
  774. package/src/mon/predicate.ts +14 -9
  775. package/src/mon/project-mon.ts +23 -5
  776. package/src/mon/steal.ts +39 -7
  777. package/src/mon/take-hit.ts +8 -3
  778. package/src/mon/timed.ts +15 -9
  779. package/src/mon/types.ts +11 -4
  780. package/src/msg.ts +55 -1
  781. package/src/obj/artifact-fake.ts +47 -17
  782. package/src/obj/bind.ts +52 -9
  783. package/src/obj/desc-vectors.fixtures.ts +119 -0
  784. package/src/obj/desc-vectors.json +2360 -0
  785. package/src/obj/desc-vectors.ts +128 -0
  786. package/src/obj/desc.ts +159 -55
  787. package/src/obj/effects-info.ts +90 -59
  788. package/src/obj/fake-object.ts +135 -0
  789. package/src/obj/ignore.ts +95 -28
  790. package/src/obj/knowledge.ts +291 -139
  791. package/src/obj/known-object.ts +126 -11
  792. package/src/obj/make.ts +53 -29
  793. package/src/obj/object-info.ts +103 -6
  794. package/src/obj/object.ts +392 -151
  795. package/src/obj/power.ts +297 -59
  796. package/src/obj/randart-build.ts +1875 -1705
  797. package/src/obj/randart-data.ts +1547 -1344
  798. package/src/obj/randart-file.ts +130 -0
  799. package/src/obj/randart-log.ts +78 -0
  800. package/src/obj/randart-registry.ts +247 -0
  801. package/src/obj/randart-vectors.fixtures.ts +61 -0
  802. package/src/obj/randart-vectors.json +836 -0
  803. package/src/obj/randart-vectors.ts +369 -0
  804. package/src/obj/randart.ts +722 -552
  805. package/src/obj/rune-registry.ts +270 -0
  806. package/src/obj/rune-vectors.fixtures.ts +164 -0
  807. package/src/obj/rune-vectors.json +121 -0
  808. package/src/obj/rune-vectors.ts +229 -0
  809. package/src/obj/tval-kind-vectors.json +391 -0
  810. package/src/obj/tval-registry.ts +243 -0
  811. package/src/obj/tval-vectors.fixtures.ts +50 -0
  812. package/src/obj/tval-vectors.json +38 -0
  813. package/src/obj/tval-vectors.ts +126 -0
  814. package/src/obj/types.ts +18 -7
  815. package/src/obj/value.ts +26 -20
  816. package/src/parser.ts +214 -0
  817. package/src/player/bind.ts +5 -2
  818. package/src/player/birth.ts +2 -1
  819. package/src/player/calcs.ts +123 -42
  820. package/src/player/death.ts +78 -0
  821. package/src/player/options-file.ts +387 -0
  822. package/src/player/options.ts +2 -1
  823. package/src/player/player.ts +60 -4
  824. package/src/player/shape-lore.ts +33 -14
  825. package/src/player/timed.ts +14 -4
  826. package/src/player/types.ts +30 -4
  827. package/src/session/boot.ts +51 -1
  828. package/src/session/game.ts +720 -163
  829. package/src/session/save-migrate.ts +81 -1
  830. package/src/session/save.ts +264 -22
  831. package/src/store/bind.ts +4 -3
  832. package/src/store/mass-produce-vectors.fixtures.ts +64 -0
  833. package/src/store/mass-produce-vectors.json +1169 -0
  834. package/src/store/mass-produce-vectors.ts +67 -0
  835. package/src/store/store.ts +266 -63
  836. package/src/store/transact.ts +39 -16
  837. package/src/store/types.ts +3 -2
  838. package/src/version.ts +1 -1
  839. package/src/visuals/index.ts +1 -0
  840. package/src/visuals/map-text.ts +1 -1
  841. package/src/visuals/object-glyph.ts +76 -0
  842. package/src/visuals/prefs.ts +22 -32
  843. package/src/visuals/tile-prefs.ts +32 -0
  844. package/src/world/feature.ts +4 -1
  845. package/src/world/project.ts +19 -4
  846. package/src/world/projection.ts +79 -8
  847. package/src/world/trap.ts +25 -21
  848. package/src/world/view.ts +11 -1
@@ -1,1344 +1,1547 @@
1
- /**
2
- * Random artifact generation: the data model and power-measurement half,
3
- * ported from reference/src/obj-randart.c (Angband 4.2.6) lines 1-1332 plus
4
- * the struct artifact_set_data allocator (obj-randart.c L2993) and the
5
- * struct definition in obj-randart.h.
6
- *
7
- * This module ports the parts of the randart generator that measure the
8
- * standard artifact set: it evaluates each artifact's power (artifactPower,
9
- * upstream artifact_power), records the baseline power statistics
10
- * (storeBasePower), tallies how often each ability appears (collectArtifactData
11
- * and the count_* helpers), and rescales those tallies into generation
12
- * frequencies (parseFrequencies / rescaleFreqs / adjustFreqs). The actual
13
- * generation of new artifacts (do_randart and its helpers, obj-randart.c
14
- * L1332+) is a separate, later port.
15
- *
16
- * Faithful notes / approximations:
17
- * - The upstream file_putf(log_file, ...) logging is dropped throughout; it
18
- * never affects any returned value (same convention as power.ts).
19
- * - artifact_power (obj-randart.c L186) builds a "fake" object with
20
- * make_fake_artifact (obj-make.c L728), which is object_prep(kind, 0,
21
- * MAXIMISE) (obj-make.c L817) followed by copy_artifact_data (obj-make.c
22
- * L520). artifactPower reproduces that field mapping directly into a
23
- * PowerObject; the MAXIMISE and MINIMISE aspects it uses consume no RNG, so
24
- * the two are inlined as pure rvMaximise/rvMinimise helpers and artifactPower
25
- * needs no Rng.
26
- * - The fake object's curse timeouts (copy_curses, obj-curse.c) are set from a
27
- * RANDOMISE dice roll upstream. Timeouts never enter object_power (cursePower
28
- * reads only curse power), so artifactPower maps curse powers with timeout 0
29
- * and consumes no RNG. Standard artifacts carry no curses, so RNG order in
30
- * storeBasePower is unaffected either way.
31
- * - Upstream mean()/variance() (z-util.c L1389/L1516) are exact-rational
32
- * multi-precision routines; store_base_power calls them with a non-NULL frac,
33
- * which is the "round the result down" path. Both are now ported for real in
34
- * ../rational (saturation and all) and called through meanFloored /
35
- * varianceFloored below, rather than reimplemented here.
36
- */
37
-
38
- import { ELEM, KF, OBJ_MOD, OF, TV } from "../generated/index.js";
39
- import { ART_IDX } from "../generated/randart-properties.js";
40
- import type { Rng } from "../rng.js";
41
- import type { ObjRegistry } from "./bind.js";
42
- import type { CurseData, CurseTimedFoil } from "./object.js";
43
- import type { ActivationSummarizer } from "./randart-build.js";
44
- import {
45
- copyBrands,
46
- copySlays,
47
- tvalCanHaveCharges,
48
- tvalIsEdible,
49
- tvalIsFuel,
50
- tvalIsLauncher,
51
- tvalIsPotion,
52
- } from "./object.js";
53
- import { mean, variance } from "../rational.js";
54
- import { objectPower } from "./power.js";
55
- import type { PowerObject } from "./power.js";
56
- import type { Artifact, ElementInfo } from "./types.js";
57
- import { ELEM_MAX, newOfFlags, OBJ_MOD_MAX, TV_MAX } from "./types.js";
58
- import type { RandomValue } from "../rng.js";
59
-
60
- /** ART_IDX_TOTAL: number of learned-probability slots (obj-randart.h). */
61
- const ART_IDX_TOTAL = ART_IDX.TOTAL;
62
-
63
- /* ------------------------------------------------------------------ */
64
- /* struct artifact_set_data (obj-randart.h L59) */
65
- /* ------------------------------------------------------------------ */
66
-
67
- /**
68
- * struct artifact_set_data: everything the generator learns from the standard
69
- * artifact set. Field names mirror the upstream struct (snake_case -> camelCase).
70
- */
71
- export interface ArtifactSetData {
72
- /* Mean start and increment values for to_hit, to_dam and AC. */
73
- hitIncrement: number;
74
- damIncrement: number;
75
- hitStartval: number;
76
- damStartval: number;
77
- acStartval: number;
78
- acIncrement: number;
79
-
80
- /* Data structures for learned probabilities. */
81
- artProbs: number[];
82
- tvProbs: number[];
83
- tvNum: number[];
84
- bowTotal: number;
85
- meleeTotal: number;
86
- bootTotal: number;
87
- gloveTotal: number;
88
- headgearTotal: number;
89
- shieldTotal: number;
90
- cloakTotal: number;
91
- armorTotal: number;
92
- otherTotal: number;
93
- total: number;
94
- negPowerTotal: number;
95
-
96
- /* Tval frequency values. */
97
- tvFreq: number[];
98
-
99
- /* Artifact power ratings. */
100
- basePower: number[];
101
- maxPower: number;
102
- minPower: number;
103
- avgPower: number;
104
- varPower: number;
105
- avgTvPower: number[];
106
- minTvPower: number[];
107
- maxTvPower: number[];
108
-
109
- /* Base item levels. */
110
- baseItemLevel: number[];
111
-
112
- /* Base item rarities. */
113
- baseItemProb: number[];
114
-
115
- /* Artifact rarities. */
116
- baseArtAlloc: number[];
117
-
118
- /**
119
- * The player-timed failure tables (timed effect NAME -> fail directives) for
120
- * artifact_curse_conflicts' TIMED_INC foil rejection (obj-curse.c L267-296).
121
- * Built by the game from the bound TimedEffect[]; absent => the foil branch
122
- * is skipped (curses still filtered by explicit conflict flags). See gap 3.3.
123
- */
124
- timedFoil?: CurseTimedFoil | undefined;
125
- /**
126
- * effect_summarize_properties (effects-info.c) for
127
- * remove_contradictory_activation's redundancy check (obj-randart.c L2420,
128
- * gap 3.8). Lives in the effects domain; injected here. Absent => a redundant
129
- * activation is never stripped (conservative). Wired from the effects layer.
130
- */
131
- activationSummarize?: ActivationSummarizer | undefined;
132
- }
133
-
134
- /* ------------------------------------------------------------------ */
135
- /* Arrays of indices by item type, used in frequency generation */
136
- /* (obj-randart.c L52) */
137
- /* ------------------------------------------------------------------ */
138
-
139
- const artIdxBow: readonly number[] = [
140
- ART_IDX.BOW_SHOTS,
141
- ART_IDX.BOW_MIGHT,
142
- ART_IDX.BOW_BRAND,
143
- ART_IDX.BOW_SLAY,
144
- ];
145
- const artIdxWeapon: readonly number[] = [
146
- ART_IDX.WEAPON_HIT,
147
- ART_IDX.WEAPON_DAM,
148
- ART_IDX.WEAPON_AGGR,
149
- ];
150
- const artIdxNonweapon: readonly number[] = [
151
- ART_IDX.NONWEAPON_HIT,
152
- ART_IDX.NONWEAPON_DAM,
153
- ART_IDX.NONWEAPON_HIT_DAM,
154
- ART_IDX.NONWEAPON_AGGR,
155
- ART_IDX.NONWEAPON_BRAND,
156
- ART_IDX.NONWEAPON_SLAY,
157
- ART_IDX.NONWEAPON_BLOWS,
158
- ART_IDX.NONWEAPON_SHOTS,
159
- ];
160
- const artIdxMelee: readonly number[] = [
161
- ART_IDX.MELEE_BLESS,
162
- ART_IDX.MELEE_SINV,
163
- ART_IDX.MELEE_BRAND,
164
- ART_IDX.MELEE_SLAY,
165
- ART_IDX.MELEE_BLOWS,
166
- ART_IDX.MELEE_AC,
167
- ART_IDX.MELEE_DICE,
168
- ART_IDX.MELEE_WEIGHT,
169
- ART_IDX.MELEE_TUNN,
170
- ];
171
- const artIdxAllarmor: readonly number[] = [ART_IDX.ALLARMOR_WEIGHT];
172
- const artIdxBoot: readonly number[] = [
173
- ART_IDX.BOOT_AC,
174
- ART_IDX.BOOT_FEATHER,
175
- ART_IDX.BOOT_STEALTH,
176
- ART_IDX.BOOT_TRAP_IMM,
177
- ART_IDX.BOOT_SPEED,
178
- ART_IDX.BOOT_MOVES,
179
- ];
180
- const artIdxGlove: readonly number[] = [
181
- ART_IDX.GLOVE_AC,
182
- ART_IDX.GLOVE_HIT_DAM,
183
- ART_IDX.GLOVE_FA,
184
- ART_IDX.GLOVE_DEX,
185
- ];
186
- const artIdxHeadgear: readonly number[] = [
187
- ART_IDX.HELM_AC,
188
- ART_IDX.HELM_RBLIND,
189
- ART_IDX.HELM_ESP,
190
- ART_IDX.HELM_SINV,
191
- ART_IDX.HELM_WIS,
192
- ART_IDX.HELM_INT,
193
- ];
194
- const artIdxShield: readonly number[] = [
195
- ART_IDX.SHIELD_AC,
196
- ART_IDX.SHIELD_LRES,
197
- ];
198
- const artIdxCloak: readonly number[] = [
199
- ART_IDX.CLOAK_AC,
200
- ART_IDX.CLOAK_STEALTH,
201
- ];
202
- const artIdxArmor: readonly number[] = [
203
- ART_IDX.ARMOR_AC,
204
- ART_IDX.ARMOR_STEALTH,
205
- ART_IDX.ARMOR_HLIFE,
206
- ART_IDX.ARMOR_CON,
207
- ART_IDX.ARMOR_LRES,
208
- ART_IDX.ARMOR_ALLRES,
209
- ART_IDX.ARMOR_HRES,
210
- ];
211
-
212
- /* ------------------------------------------------------------------ */
213
- /* Aspect helpers (pure; MAXIMISE/MINIMISE consume no RNG) */
214
- /* ------------------------------------------------------------------ */
215
-
216
- /** randcalc(v, 0, MINIMISE): base + dice (see rng.ts damcalc/mBonusCalc). */
217
- function rvMinimise(v: RandomValue): number {
218
- return v.base + v.dice;
219
- }
220
-
221
- /** randcalc(v, 0, MAXIMISE): base + dice*sides + mBonus. */
222
- function rvMaximise(v: RandomValue): number {
223
- return v.base + v.dice * v.sides + v.mBonus;
224
- }
225
-
226
- /** Number of set entries in a 1-based boolean slay/brand array, or 0. */
227
- function countTrue(arr: readonly boolean[] | null): number {
228
- if (!arr) return 0;
229
- let n = 0;
230
- for (let i = 1; i < arr.length; i++) {
231
- if (arr[i]) n++;
232
- }
233
- return n;
234
- }
235
-
236
- /**
237
- * store_base_power's two statistics, at the call shape obj-randart.c L278-L282
238
- * uses: mean(a, n, &frac) and variance(a, n, false, false, &frac), i.e. the
239
- * FLOORED variants. The scratch fraction upstream passes is written and then
240
- * ignored, so one shared throwaway stands in for it.
241
- *
242
- * These used to be local reimplementations here (plain floor division and
243
- * (sum(x^2)*size - sum(x)^2) / size^2). They agreed with the C for artifact
244
- * powers, but not at the rails: upstream's variance saturates at INT_MAX and
245
- * upstream's mean floors toward negative infinity via a magnitude-and-fraction
246
- * dance. ../rational is the real port, so the statistics now go through it.
247
- */
248
- const SCRATCH_FRAC = { n: 0, d: 1 };
249
-
250
- function meanFloored(nums: readonly number[]): number {
251
- return mean(nums, nums.length, SCRATCH_FRAC);
252
- }
253
-
254
- function varianceFloored(nums: readonly number[]): number {
255
- return variance(nums, nums.length, false, false, SCRATCH_FRAC);
256
- }
257
-
258
- /* ------------------------------------------------------------------ */
259
- /* artifact_power (obj-randart.c L186) */
260
- /* ------------------------------------------------------------------ */
261
-
262
- /**
263
- * Build the "fake" object object_power evaluates for an artifact, reproducing
264
- * make_fake_artifact (obj-make.c L728) = object_prep(kind, 0, MAXIMISE)
265
- * followed by copy_artifact_data (obj-make.c L520). Returns null when the
266
- * artifact has no tval or no base kind (make_fake_artifact would return false).
267
- */
268
- function makeFakeArtifactPower(
269
- reg: ObjRegistry,
270
- art: Artifact,
271
- ): PowerObject | null {
272
- if (!art.tval) return null;
273
- const kind = reg.lookupKind(art.tval, art.sval);
274
- if (!kind) return null;
275
-
276
- /* Flags: object_prep copies base then kind flags (the second copy
277
- * overwrites the first, upstream quirk kept in objectPrep), then
278
- * copy_artifact_data clears the light-fuel flags and unions art flags. */
279
- const flags = newOfFlags();
280
- flags.copy(kind.base.flags);
281
- flags.copy(kind.flags);
282
- flags.off(OF.TAKES_FUEL);
283
- flags.off(OF.BURNS_OUT);
284
- flags.union(art.flags);
285
-
286
- /* Modifiers: object_prep fills from the kind (MAXIMISE), then
287
- * copy_artifact_data overwrites every entry with the artifact's. */
288
- const modifiers: number[] = new Array<number>(OBJ_MOD_MAX).fill(0);
289
- for (let i = 0; i < OBJ_MOD_MAX; i++) {
290
- modifiers[i] = art.modifiers[i] ?? 0;
291
- }
292
-
293
- /* pval: object_prep assigns charges then food/oil/launcher pval; the
294
- * artifact copy does not touch pval. */
295
- let pval = 0;
296
- if (tvalCanHaveCharges(art.tval)) {
297
- pval = rvMaximise(kind.charge);
298
- }
299
- if (
300
- tvalIsEdible(art.tval) ||
301
- tvalIsPotion(art.tval) ||
302
- tvalIsFuel(art.tval) ||
303
- tvalIsLauncher(art.tval)
304
- ) {
305
- pval = rvMaximise(kind.pval);
306
- }
307
-
308
- /* Slays/brands: object_prep copies the kind's, copy_artifact_data unions
309
- * the artifact's on top. */
310
- let slays = copySlays(null, kind.slays, reg.slays);
311
- slays = copySlays(slays, art.slays, reg.slays);
312
- let brands = copyBrands(null, kind.brands, reg.brands);
313
- brands = copyBrands(brands, art.brands, reg.brands);
314
-
315
- /* Element info: object_prep sets res_level and flags from kind (+ base
316
- * flags); copy_artifact_data overrides res_level with any non-zero
317
- * artifact level and unions the artifact ignore flags. */
318
- const elInfo: ElementInfo[] = [];
319
- for (let i = 0; i < ELEM_MAX; i++) {
320
- const ke = kind.elInfo[i] as ElementInfo;
321
- const be = kind.base.elInfo[i] as ElementInfo;
322
- const ae = art.elInfo[i] as ElementInfo;
323
- let resLevel = ke.resLevel;
324
- let elFlags = ke.flags | be.flags;
325
- if (ae.resLevel !== 0) resLevel = ae.resLevel;
326
- elFlags |= ae.flags;
327
- elInfo.push({ resLevel, flags: elFlags });
328
- }
329
-
330
- /* Curses: copy_curses copies the kind's then the artifact's powers.
331
- * Timeouts (a RANDOMISE roll upstream) never affect power, so they are
332
- * left at 0 and no RNG is consumed. */
333
- let curses: CurseData[] | null = null;
334
- if (kind.curses || art.curses) {
335
- curses = [];
336
- for (let i = 0; i < reg.curses.length; i++) {
337
- curses.push({ power: 0, timeout: 0 });
338
- }
339
- if (kind.curses) {
340
- for (let i = 0; i < reg.curses.length; i++) {
341
- const power = kind.curses[i] ?? 0;
342
- if (power) (curses[i] as CurseData).power = power;
343
- }
344
- }
345
- if (art.curses) {
346
- for (let i = 0; i < reg.curses.length; i++) {
347
- const power = art.curses[i] ?? 0;
348
- if (power) (curses[i] as CurseData).power = power;
349
- }
350
- }
351
- }
352
-
353
- /* Activation comes from the artifact or, failing that, the kind. */
354
- const act = art.activation ?? kind.activation;
355
- const activation = act ? { power: act.power } : null;
356
-
357
- return {
358
- tval: art.tval,
359
- toH: art.toH,
360
- toD: art.toD,
361
- toA: art.toA,
362
- ac: art.ac,
363
- dd: art.dd,
364
- ds: art.ds,
365
- weight: art.weight,
366
- pval,
367
- modifiers,
368
- brands,
369
- slays,
370
- flags,
371
- elInfo,
372
- curses,
373
- activation,
374
- kind: { power: kind.power, kindFlags: kind.kindFlags },
375
- ego: null,
376
- };
377
- }
378
-
379
- /**
380
- * artifact_power (obj-randart.c L186): the artifact's power, by generating a
381
- * fake object from the artifact and calling the common object_power.
382
- */
383
- export function artifactPower(reg: ObjRegistry, art: Artifact): number {
384
- const obj = makeFakeArtifactPower(reg, art);
385
- if (!obj) return 0;
386
- return objectPower(reg, obj);
387
- }
388
-
389
- /* ------------------------------------------------------------------ */
390
- /* store_base_power (obj-randart.c L219) */
391
- /* ------------------------------------------------------------------ */
392
-
393
- /**
394
- * store_base_power (obj-randart.c L219): store the original artifact power
395
- * ratings as a baseline, and capture the per-set and per-tval power statistics
396
- * and base-item info.
397
- */
398
- export function storeBasePower(reg: ObjRegistry, data: ArtifactSetData): void {
399
- const aMax = reg.artifacts.length;
400
-
401
- data.maxPower = 0;
402
- data.minPower = INHIBIT_POWER + 1;
403
- data.varPower = 0;
404
-
405
- const fakeTotalPower: number[] = [];
406
- const fakeTvPower: number[][] = [];
407
- for (let i = 0; i < TV_MAX; i++) {
408
- fakeTvPower.push([]);
409
- data.minTvPower[i] = INHIBIT_POWER + 1;
410
- data.maxTvPower[i] = 0;
411
- }
412
-
413
- for (let i = 0; i < aMax; i++) {
414
- const art = reg.artifacts[i] ?? null;
415
- const power = art ? artifactPower(reg, art) : 0;
416
- data.basePower[i] = power;
417
-
418
- /* Capture power stats, ignoring cursed and uber arts. */
419
- if (power > data.maxPower && power < INHIBIT_POWER) {
420
- data.maxPower = power;
421
- }
422
- if (power < data.minPower && power > 0) {
423
- data.minPower = power;
424
- }
425
- if (power > 0 && power < INHIBIT_POWER && art) {
426
- const tval = art.tval;
427
- fakeTotalPower.push(power);
428
- fakeTvPower[tval]!.push(power);
429
- data.tvNum[tval] = (data.tvNum[tval] ?? 0) + 1;
430
- if (power < (data.minTvPower[tval] ?? 0)) data.minTvPower[tval] = power;
431
- if (power > (data.maxTvPower[tval] ?? 0)) data.maxTvPower[tval] = power;
432
- }
433
- if (power < 0) {
434
- data.negPowerTotal++;
435
- }
436
-
437
- if (!power || !art) continue;
438
- const kind = reg.lookupKind(art.tval, art.sval);
439
- if (!kind) continue;
440
- data.baseItemLevel[i] = kind.level;
441
- data.baseItemProb[i] = kind.allocProb;
442
- data.baseArtAlloc[i] = art.allocProb;
443
- }
444
-
445
- /* Round the result down (upstream passes frac but ignores it). */
446
- data.avgPower = meanFloored(fakeTotalPower);
447
- data.varPower = varianceFloored(fakeTotalPower);
448
- for (let i = 0; i < TV_MAX; i++) {
449
- if (data.tvNum[i]) {
450
- data.avgTvPower[i] = meanFloored(fakeTvPower[i]!);
451
- }
452
- }
453
-
454
- /* Store the number of different types, for use later. */
455
- for (let i = 0; i < aMax; i++) {
456
- const art = reg.artifacts[i] ?? null;
457
- const tval = art ? art.tval : TV.NULL;
458
- switch (tval) {
459
- case TV.SWORD:
460
- case TV.POLEARM:
461
- case TV.HAFTED:
462
- data.meleeTotal++;
463
- break;
464
- case TV.BOW:
465
- data.bowTotal++;
466
- break;
467
- case TV.SOFT_ARMOR:
468
- case TV.HARD_ARMOR:
469
- case TV.DRAG_ARMOR:
470
- data.armorTotal++;
471
- break;
472
- case TV.SHIELD:
473
- data.shieldTotal++;
474
- break;
475
- case TV.CLOAK:
476
- data.cloakTotal++;
477
- break;
478
- case TV.HELM:
479
- case TV.CROWN:
480
- data.headgearTotal++;
481
- break;
482
- case TV.GLOVES:
483
- data.gloveTotal++;
484
- break;
485
- case TV.BOOTS:
486
- data.bootTotal++;
487
- break;
488
- case TV.NULL:
489
- break;
490
- default:
491
- data.otherTotal++;
492
- }
493
- data.total++;
494
- }
495
- }
496
-
497
- /* ------------------------------------------------------------------ */
498
- /* Ability counters (obj-randart.c L342-L1053) */
499
- /* ------------------------------------------------------------------ */
500
-
501
- /**
502
- * count_weapon_abilities (obj-randart.c L342): handle weapon combat abilities.
503
- */
504
- export function countWeaponAbilities(
505
- reg: ObjRegistry,
506
- art: Artifact,
507
- data: ArtifactSetData,
508
- ): void {
509
- const kind = reg.lookupKind(art.tval, art.sval);
510
- if (!kind) return;
511
- const minToH = rvMinimise(kind.toH);
512
- const minToD = rvMinimise(kind.toD);
513
- const minToA = rvMinimise(kind.toA);
514
-
515
- /* To-hit and to-dam. */
516
- let bonus = Math.trunc(
517
- (art.toH - minToH - data.hitStartval) / data.hitIncrement,
518
- );
519
- data.artProbs[ART_IDX.WEAPON_HIT]! += bonus;
520
-
521
- bonus = Math.trunc((art.toD - minToD - data.damStartval) / data.damIncrement);
522
- data.artProbs[ART_IDX.WEAPON_DAM]! += bonus;
523
-
524
- /* Does this weapon have an unusual bonus to AC? */
525
- bonus = Math.trunc((art.toA - minToA) / data.acIncrement);
526
- if (art.toA > 20) {
527
- data.artProbs[ART_IDX.MELEE_AC_SUPER]!++;
528
- } else if (bonus > 0) {
529
- data.artProbs[ART_IDX.MELEE_AC]! += bonus;
530
- }
531
-
532
- /* Check damage dice - are they more than normal? */
533
- if (art.dd > kind.dd) {
534
- if (art.dd - kind.dd > 2) {
535
- data.artProbs[ART_IDX.MELEE_DICE_SUPER]!++;
536
- } else {
537
- data.artProbs[ART_IDX.MELEE_DICE]!++;
538
- }
539
- }
540
-
541
- /* Check weight - is it different from normal? */
542
- if (art.weight !== kind.weight) {
543
- data.artProbs[ART_IDX.MELEE_WEIGHT]!++;
544
- }
545
-
546
- /* Do we have 3 or more extra blows? */
547
- const blows = art.modifiers[OBJ_MOD.BLOWS] ?? 0;
548
- if (blows > 2) {
549
- data.artProbs[ART_IDX.MELEE_BLOWS_SUPER]!++;
550
- } else if (blows > 0) {
551
- data.artProbs[ART_IDX.MELEE_BLOWS]!++;
552
- }
553
-
554
- /* Aggravation. */
555
- if (art.flags.has(OF.AGGRAVATE)) {
556
- data.artProbs[ART_IDX.WEAPON_AGGR]!++;
557
- }
558
-
559
- /* Blessed weapon? */
560
- if (art.flags.has(OF.BLESSED)) {
561
- data.artProbs[ART_IDX.MELEE_BLESS]!++;
562
- }
563
-
564
- /* See invisible? */
565
- if (art.flags.has(OF.SEE_INVIS)) {
566
- data.artProbs[ART_IDX.MELEE_SINV]!++;
567
- }
568
-
569
- /* Tunnelling ability. */
570
- if ((art.modifiers[OBJ_MOD.TUNNEL] ?? 0) > 0) {
571
- data.artProbs[ART_IDX.MELEE_TUNN]!++;
572
- }
573
-
574
- /* Count brands and slays. */
575
- if (art.slays) {
576
- data.artProbs[ART_IDX.MELEE_SLAY]! += countTrue(art.slays);
577
- }
578
- if (art.brands) {
579
- data.artProbs[ART_IDX.MELEE_BRAND]! += countTrue(art.brands);
580
- }
581
- }
582
-
583
- /**
584
- * count_bow_abilities (obj-randart.c L444): count combat abilities on bows.
585
- */
586
- export function countBowAbilities(
587
- reg: ObjRegistry,
588
- art: Artifact,
589
- data: ArtifactSetData,
590
- ): void {
591
- const kind = reg.lookupKind(art.tval, art.sval);
592
- if (!kind) return;
593
- const minToH = rvMinimise(kind.toH);
594
- const minToD = rvMinimise(kind.toD);
595
- const minToA = rvMinimise(kind.toA);
596
-
597
- /* To-hit. */
598
- let bonus = Math.trunc(
599
- (art.toH - minToH - data.hitStartval) / data.hitIncrement,
600
- );
601
- data.artProbs[ART_IDX.WEAPON_HIT]! += bonus;
602
-
603
- /* To-dam. */
604
- bonus = Math.trunc((art.toD - minToD - data.damStartval) / data.damIncrement);
605
- data.artProbs[ART_IDX.WEAPON_DAM]! += bonus;
606
-
607
- /* Armor class. */
608
- bonus = Math.trunc((art.toA - minToA - data.acStartval) / data.acIncrement);
609
- if (bonus > 0) {
610
- data.artProbs[ART_IDX.GEN_AC]! += bonus;
611
- }
612
-
613
- /* Aggravation. */
614
- if (art.flags.has(OF.AGGRAVATE)) {
615
- data.artProbs[ART_IDX.WEAPON_AGGR]!++;
616
- }
617
-
618
- /* Do we have more than 1 extra shot? */
619
- const shots = art.modifiers[OBJ_MOD.SHOTS] ?? 0;
620
- if (shots > 10) {
621
- data.artProbs[ART_IDX.BOW_SHOTS_SUPER]!++;
622
- } else if (shots > 0) {
623
- data.artProbs[ART_IDX.BOW_SHOTS]!++;
624
- }
625
-
626
- /* Do we have 3 or more extra might? */
627
- const might = art.modifiers[OBJ_MOD.MIGHT] ?? 0;
628
- if (might > 2) {
629
- data.artProbs[ART_IDX.BOW_MIGHT_SUPER]!++;
630
- } else if (might > 0) {
631
- data.artProbs[ART_IDX.BOW_MIGHT]!++;
632
- }
633
-
634
- /* Count brands and slays. */
635
- if (art.slays) {
636
- data.artProbs[ART_IDX.BOW_SLAY]! += countTrue(art.slays);
637
- }
638
- if (art.brands) {
639
- data.artProbs[ART_IDX.BOW_BRAND]! += countTrue(art.brands);
640
- }
641
- }
642
-
643
- /**
644
- * count_nonweapon_abilities (obj-randart.c L516): handle nonweapon combat
645
- * abilities.
646
- */
647
- export function countNonweaponAbilities(
648
- reg: ObjRegistry,
649
- art: Artifact,
650
- data: ArtifactSetData,
651
- ): void {
652
- const kind = reg.lookupKind(art.tval, art.sval);
653
- if (!kind) return;
654
- const toHit = art.toH - rvMinimise(kind.toH);
655
- const toDam = art.toD - rvMinimise(kind.toD);
656
- const toA = art.toA - rvMinimise(kind.toA) - data.acStartval;
657
- let bonus = Math.trunc(toA / data.acIncrement);
658
-
659
- /* Armor class. */
660
- if (bonus > 0) {
661
- if (art.toA > 20) {
662
- data.artProbs[ART_IDX.GEN_AC_SUPER]!++;
663
- } else if (art.tval === TV.BOOTS) {
664
- data.artProbs[ART_IDX.BOOT_AC]! += bonus;
665
- } else if (art.tval === TV.GLOVES) {
666
- data.artProbs[ART_IDX.GLOVE_AC]! += bonus;
667
- } else if (art.tval === TV.HELM || art.tval === TV.CROWN) {
668
- data.artProbs[ART_IDX.HELM_AC]! += bonus;
669
- } else if (art.tval === TV.SHIELD) {
670
- data.artProbs[ART_IDX.SHIELD_AC]! += bonus;
671
- } else if (art.tval === TV.CLOAK) {
672
- data.artProbs[ART_IDX.CLOAK_AC]! += bonus;
673
- } else if (
674
- art.tval === TV.SOFT_ARMOR ||
675
- art.tval === TV.HARD_ARMOR ||
676
- art.tval === TV.DRAG_ARMOR
677
- ) {
678
- data.artProbs[ART_IDX.ARMOR_AC]! += bonus;
679
- } else {
680
- data.artProbs[ART_IDX.GEN_AC]! += bonus;
681
- }
682
- }
683
-
684
- /* To hit and dam bonuses. */
685
- if (toHit > 0 && toDam > 0) {
686
- bonus = Math.trunc(
687
- (toHit + toDam) / (data.hitIncrement + data.damIncrement),
688
- );
689
- if (bonus > 0) {
690
- if (art.tval === TV.GLOVES) {
691
- data.artProbs[ART_IDX.GLOVE_HIT_DAM]! += bonus;
692
- } else {
693
- data.artProbs[ART_IDX.NONWEAPON_HIT_DAM]! += bonus;
694
- }
695
- }
696
- } else if (toHit > 0) {
697
- bonus = Math.trunc(toHit / data.hitIncrement);
698
- if (bonus > 0) {
699
- data.artProbs[ART_IDX.NONWEAPON_HIT]! += bonus;
700
- }
701
- } else if (toDam > 0) {
702
- bonus = Math.trunc(toDam / data.damIncrement);
703
- if (bonus > 0) {
704
- data.artProbs[ART_IDX.NONWEAPON_DAM]! += bonus;
705
- }
706
- }
707
-
708
- /* Check weight - is it different from normal? */
709
- if (art.weight !== kind.weight) {
710
- data.artProbs[ART_IDX.ALLARMOR_WEIGHT]!++;
711
- }
712
-
713
- /* Aggravation. */
714
- if (art.flags.has(OF.AGGRAVATE)) {
715
- data.artProbs[ART_IDX.NONWEAPON_AGGR]!++;
716
- }
717
-
718
- /* Count brands and slays. */
719
- if (art.slays) {
720
- data.artProbs[ART_IDX.NONWEAPON_SLAY]! += countTrue(art.slays);
721
- }
722
- if (art.brands) {
723
- data.artProbs[ART_IDX.NONWEAPON_BRAND]! += countTrue(art.brands);
724
- }
725
-
726
- /* Blows. */
727
- if ((art.modifiers[OBJ_MOD.BLOWS] ?? 0) > 0) {
728
- data.artProbs[ART_IDX.NONWEAPON_BLOWS]!++;
729
- }
730
-
731
- /* Shots. */
732
- if ((art.modifiers[OBJ_MOD.SHOTS] ?? 0) > 0) {
733
- data.artProbs[ART_IDX.NONWEAPON_SHOTS]!++;
734
- }
735
-
736
- /* Tunnelling ability. */
737
- if ((art.modifiers[OBJ_MOD.TUNNEL] ?? 0) > 0) {
738
- data.artProbs[ART_IDX.GEN_TUNN]!++;
739
- }
740
- }
741
-
742
- /**
743
- * count_modifiers (obj-randart.c L630): count stat and other modifier bonuses.
744
- */
745
- export function countModifiers(art: Artifact, data: ArtifactSetData): void {
746
- let num = 0;
747
-
748
- /* Stat bonuses. Add up the number of individual bonuses. */
749
- if ((art.modifiers[OBJ_MOD.STR] ?? 0) > 0) num++;
750
- if ((art.modifiers[OBJ_MOD.INT] ?? 0) > 0) num++;
751
- if ((art.modifiers[OBJ_MOD.WIS] ?? 0) > 0) num++;
752
- if ((art.modifiers[OBJ_MOD.DEX] ?? 0) > 0) num++;
753
- if ((art.modifiers[OBJ_MOD.CON] ?? 0) > 0) num++;
754
-
755
- /* Handle a few special cases separately. */
756
- if (
757
- (art.tval === TV.HELM || art.tval === TV.CROWN) &&
758
- ((art.modifiers[OBJ_MOD.WIS] ?? 0) > 0 ||
759
- (art.modifiers[OBJ_MOD.INT] ?? 0) > 0)
760
- ) {
761
- if ((art.modifiers[OBJ_MOD.WIS] ?? 0) > 0) {
762
- data.artProbs[ART_IDX.HELM_WIS]!++;
763
- num--;
764
- }
765
- if ((art.modifiers[OBJ_MOD.INT] ?? 0) > 0) {
766
- data.artProbs[ART_IDX.HELM_INT]!++;
767
- num--;
768
- }
769
- } else if (
770
- (art.tval === TV.SOFT_ARMOR ||
771
- art.tval === TV.HARD_ARMOR ||
772
- art.tval === TV.DRAG_ARMOR) &&
773
- (art.modifiers[OBJ_MOD.CON] ?? 0) > 0
774
- ) {
775
- data.artProbs[ART_IDX.ARMOR_CON]!++;
776
- num--;
777
- } else if (art.tval === TV.GLOVES && (art.modifiers[OBJ_MOD.DEX] ?? 0) > 0) {
778
- data.artProbs[ART_IDX.GLOVE_DEX]!++;
779
- num--;
780
- }
781
-
782
- /* Now the general case. */
783
- if (num > 0) {
784
- data.artProbs[ART_IDX.GEN_STAT]! += num;
785
- }
786
-
787
- /* Handle stealth, including a couple of special cases. */
788
- if ((art.modifiers[OBJ_MOD.STEALTH] ?? 0) > 0) {
789
- if (art.tval === TV.BOOTS) {
790
- data.artProbs[ART_IDX.BOOT_STEALTH]!++;
791
- } else if (art.tval === TV.CLOAK) {
792
- data.artProbs[ART_IDX.CLOAK_STEALTH]!++;
793
- } else if (
794
- art.tval === TV.SOFT_ARMOR ||
795
- art.tval === TV.HARD_ARMOR ||
796
- art.tval === TV.DRAG_ARMOR
797
- ) {
798
- data.artProbs[ART_IDX.ARMOR_STEALTH]!++;
799
- } else {
800
- data.artProbs[ART_IDX.GEN_STEALTH]!++;
801
- }
802
- }
803
-
804
- /* Searching bonus - fully generic. */
805
- if ((art.modifiers[OBJ_MOD.SEARCH] ?? 0) > 0) {
806
- data.artProbs[ART_IDX.GEN_SEARCH]!++;
807
- }
808
-
809
- /* Infravision bonus - fully generic. */
810
- if ((art.modifiers[OBJ_MOD.INFRA] ?? 0) > 0) {
811
- data.artProbs[ART_IDX.GEN_INFRA]!++;
812
- }
813
-
814
- /* Damage reduction bonus - fully generic. */
815
- if ((art.modifiers[OBJ_MOD.DAM_RED] ?? 0) > 0) {
816
- data.artProbs[ART_IDX.GEN_DAM_RED]!++;
817
- }
818
-
819
- /* Moves bonus. */
820
- if ((art.modifiers[OBJ_MOD.MOVES] ?? 0) > 0) {
821
- if (art.tval === TV.BOOTS) {
822
- data.artProbs[ART_IDX.BOOT_MOVES]!++;
823
- } else {
824
- data.artProbs[ART_IDX.GEN_MOVES]!++;
825
- }
826
- }
827
-
828
- /* Speed - boots handled separately, supercharge shares its frequency. */
829
- const speed = art.modifiers[OBJ_MOD.SPEED] ?? 0;
830
- if (speed > 0) {
831
- if (speed > 7) {
832
- data.artProbs[ART_IDX.GEN_SPEED_SUPER]!++;
833
- } else if (art.tval === TV.BOOTS) {
834
- data.artProbs[ART_IDX.BOOT_SPEED]!++;
835
- } else {
836
- data.artProbs[ART_IDX.GEN_SPEED]!++;
837
- }
838
- }
839
-
840
- /* Permanent light. */
841
- if ((art.modifiers[OBJ_MOD.LIGHT] ?? 0) > 0) {
842
- data.artProbs[ART_IDX.GEN_LIGHT]!++;
843
- }
844
- }
845
-
846
- /**
847
- * count_low_resists (obj-randart.c L764): count low resists and immunities.
848
- */
849
- export function countLowResists(art: Artifact, data: ArtifactSetData): void {
850
- let num = 0;
851
-
852
- /* Count up immunities for this item, if any. */
853
- if ((art.elInfo[ELEM.ACID] as ElementInfo).resLevel === 3) num++;
854
- if ((art.elInfo[ELEM.ELEC] as ElementInfo).resLevel === 3) num++;
855
- if ((art.elInfo[ELEM.FIRE] as ElementInfo).resLevel === 3) num++;
856
- if ((art.elInfo[ELEM.COLD] as ElementInfo).resLevel === 3) num++;
857
-
858
- data.artProbs[ART_IDX.GEN_IMMUNE]! += num;
859
-
860
- /* Count up low resists (not the type, just the number). */
861
- num = 0;
862
- if ((art.elInfo[ELEM.ACID] as ElementInfo).resLevel === 1) num++;
863
- if ((art.elInfo[ELEM.ELEC] as ElementInfo).resLevel === 1) num++;
864
- if ((art.elInfo[ELEM.FIRE] as ElementInfo).resLevel === 1) num++;
865
- if ((art.elInfo[ELEM.COLD] as ElementInfo).resLevel === 1) num++;
866
-
867
- if (num) {
868
- if (art.tval === TV.SHIELD) {
869
- data.artProbs[ART_IDX.SHIELD_LRES]! += num;
870
- } else if (
871
- art.tval === TV.SOFT_ARMOR ||
872
- art.tval === TV.HARD_ARMOR ||
873
- art.tval === TV.DRAG_ARMOR
874
- ) {
875
- if (num === 4) {
876
- data.artProbs[ART_IDX.ARMOR_ALLRES]!++;
877
- } else {
878
- data.artProbs[ART_IDX.ARMOR_LRES]! += num;
879
- }
880
- } else {
881
- data.artProbs[ART_IDX.GEN_LRES]! += num;
882
- }
883
- }
884
- }
885
-
886
- /**
887
- * count_high_resists (obj-randart.c L816): count high resists and protections.
888
- */
889
- export function countHighResists(art: Artifact, data: ArtifactSetData): void {
890
- let num = 0;
891
-
892
- /* Body armor: count all high resists as an aggregate first. */
893
- if (
894
- art.tval === TV.SOFT_ARMOR ||
895
- art.tval === TV.HARD_ARMOR ||
896
- art.tval === TV.DRAG_ARMOR
897
- ) {
898
- if ((art.elInfo[ELEM.POIS] as ElementInfo).resLevel === 1) num++;
899
- if (art.flags.has(OF.PROT_FEAR)) num++;
900
- if ((art.elInfo[ELEM.LIGHT] as ElementInfo).resLevel === 1) num++;
901
- if ((art.elInfo[ELEM.DARK] as ElementInfo).resLevel === 1) num++;
902
- if (art.flags.has(OF.PROT_BLIND)) num++;
903
- if (art.flags.has(OF.PROT_CONF)) num++;
904
- if ((art.elInfo[ELEM.SOUND] as ElementInfo).resLevel === 1) num++;
905
- if ((art.elInfo[ELEM.SHARD] as ElementInfo).resLevel === 1) num++;
906
- if ((art.elInfo[ELEM.NEXUS] as ElementInfo).resLevel === 1) num++;
907
- if ((art.elInfo[ELEM.NETHER] as ElementInfo).resLevel === 1) num++;
908
- if ((art.elInfo[ELEM.CHAOS] as ElementInfo).resLevel === 1) num++;
909
- if ((art.elInfo[ELEM.DISEN] as ElementInfo).resLevel === 1) num++;
910
- if (art.flags.has(OF.PROT_STUN)) num++;
911
- data.artProbs[ART_IDX.ARMOR_HRES]! += num;
912
- }
913
-
914
- /* Now do the high resists individually. */
915
- if ((art.elInfo[ELEM.POIS] as ElementInfo).resLevel === 1) {
916
- data.artProbs[ART_IDX.GEN_RPOIS]!++;
917
- }
918
- if (art.flags.has(OF.PROT_FEAR)) {
919
- data.artProbs[ART_IDX.GEN_RFEAR]!++;
920
- }
921
- if ((art.elInfo[ELEM.LIGHT] as ElementInfo).resLevel === 1) {
922
- data.artProbs[ART_IDX.GEN_RLIGHT]!++;
923
- }
924
- if ((art.elInfo[ELEM.DARK] as ElementInfo).resLevel === 1) {
925
- data.artProbs[ART_IDX.GEN_RDARK]!++;
926
- }
927
- if (art.flags.has(OF.PROT_BLIND)) {
928
- if (art.tval === TV.HELM || art.tval === TV.CROWN) {
929
- data.artProbs[ART_IDX.HELM_RBLIND]!++;
930
- } else {
931
- data.artProbs[ART_IDX.GEN_RBLIND]!++;
932
- }
933
- }
934
- if (art.flags.has(OF.PROT_CONF)) {
935
- data.artProbs[ART_IDX.GEN_RCONF]!++;
936
- }
937
- if ((art.elInfo[ELEM.SOUND] as ElementInfo).resLevel === 1) {
938
- data.artProbs[ART_IDX.GEN_RSOUND]!++;
939
- }
940
- if ((art.elInfo[ELEM.SHARD] as ElementInfo).resLevel === 1) {
941
- data.artProbs[ART_IDX.GEN_RSHARD]!++;
942
- }
943
- if ((art.elInfo[ELEM.NEXUS] as ElementInfo).resLevel === 1) {
944
- data.artProbs[ART_IDX.GEN_RNEXUS]!++;
945
- }
946
- if ((art.elInfo[ELEM.NETHER] as ElementInfo).resLevel === 1) {
947
- data.artProbs[ART_IDX.GEN_RNETHER]!++;
948
- }
949
- if ((art.elInfo[ELEM.CHAOS] as ElementInfo).resLevel === 1) {
950
- data.artProbs[ART_IDX.GEN_RCHAOS]!++;
951
- }
952
- if ((art.elInfo[ELEM.DISEN] as ElementInfo).resLevel === 1) {
953
- data.artProbs[ART_IDX.GEN_RDISEN]!++;
954
- }
955
- if (art.flags.has(OF.PROT_STUN)) {
956
- data.artProbs[ART_IDX.GEN_PSTUN]!++;
957
- }
958
- }
959
-
960
- /**
961
- * count_abilities (obj-randart.c L943): general abilities, adding some to a
962
- * specific item-type tally depending on the base item.
963
- */
964
- export function countAbilities(
965
- reg: ObjRegistry,
966
- art: Artifact,
967
- data: ArtifactSetData,
968
- ): void {
969
- const kind = reg.lookupKind(art.tval, art.sval);
970
-
971
- /* Sustains. */
972
- if (
973
- art.flags.has(OF.SUST_STR) ||
974
- art.flags.has(OF.SUST_INT) ||
975
- art.flags.has(OF.SUST_WIS) ||
976
- art.flags.has(OF.SUST_DEX) ||
977
- art.flags.has(OF.SUST_CON)
978
- ) {
979
- let num = 0;
980
- if (art.flags.has(OF.SUST_STR)) num++;
981
- if (art.flags.has(OF.SUST_INT)) num++;
982
- if (art.flags.has(OF.SUST_WIS)) num++;
983
- if (art.flags.has(OF.SUST_DEX)) num++;
984
- if (art.flags.has(OF.SUST_CON)) num++;
985
- data.artProbs[ART_IDX.GEN_SUST]! += num;
986
- }
987
-
988
- /* Free action - handle gloves separately. */
989
- if (art.flags.has(OF.FREE_ACT)) {
990
- if (art.tval === TV.GLOVES) {
991
- data.artProbs[ART_IDX.GLOVE_FA]!++;
992
- } else {
993
- data.artProbs[ART_IDX.GEN_FA]!++;
994
- }
995
- }
996
-
997
- /* Hold life - do body armor separately. */
998
- if (art.flags.has(OF.HOLD_LIFE)) {
999
- if (
1000
- art.tval === TV.SOFT_ARMOR ||
1001
- art.tval === TV.HARD_ARMOR ||
1002
- art.tval === TV.DRAG_ARMOR
1003
- ) {
1004
- data.artProbs[ART_IDX.ARMOR_HLIFE]!++;
1005
- } else {
1006
- data.artProbs[ART_IDX.GEN_HLIFE]!++;
1007
- }
1008
- }
1009
-
1010
- /* Feather fall - handle boots separately. */
1011
- if (art.flags.has(OF.FEATHER)) {
1012
- if (art.tval === TV.BOOTS) {
1013
- data.artProbs[ART_IDX.BOOT_FEATHER]!++;
1014
- } else {
1015
- data.artProbs[ART_IDX.GEN_FEATHER]!++;
1016
- }
1017
- }
1018
-
1019
- /* See invisible - do helms/crowns separately (weapons already handled). */
1020
- if (art.flags.has(OF.SEE_INVIS)) {
1021
- if (
1022
- !(
1023
- art.tval === TV.DIGGING ||
1024
- art.tval === TV.HAFTED ||
1025
- art.tval === TV.POLEARM ||
1026
- art.tval === TV.SWORD
1027
- )
1028
- ) {
1029
- if (art.tval === TV.HELM || art.tval === TV.CROWN) {
1030
- data.artProbs[ART_IDX.HELM_SINV]!++;
1031
- } else {
1032
- data.artProbs[ART_IDX.GEN_SINV]!++;
1033
- }
1034
- }
1035
- }
1036
-
1037
- /* ESP - handle helms/crowns separately. */
1038
- if (art.flags.has(OF.TELEPATHY)) {
1039
- if (art.tval === TV.HELM || art.tval === TV.CROWN) {
1040
- data.artProbs[ART_IDX.HELM_ESP]!++;
1041
- } else {
1042
- data.artProbs[ART_IDX.GEN_ESP]!++;
1043
- }
1044
- }
1045
-
1046
- /* Slow digestion - generic. */
1047
- if (art.flags.has(OF.SLOW_DIGEST)) {
1048
- data.artProbs[ART_IDX.GEN_SDIG]!++;
1049
- }
1050
-
1051
- /* Regeneration - generic. */
1052
- if (art.flags.has(OF.REGEN)) {
1053
- data.artProbs[ART_IDX.GEN_REGEN]!++;
1054
- }
1055
-
1056
- /* Trap immunity - handle boots separately. */
1057
- if (art.flags.has(OF.TRAP_IMMUNE)) {
1058
- if (art.tval === TV.BOOTS) {
1059
- data.artProbs[ART_IDX.BOOT_TRAP_IMM]!++;
1060
- } else {
1061
- data.artProbs[ART_IDX.GEN_TRAP_IMM]!++;
1062
- }
1063
- }
1064
-
1065
- /* Activation. */
1066
- if (art.activation || kind?.activation) {
1067
- data.artProbs[ART_IDX.GEN_ACTIV]!++;
1068
- }
1069
- }
1070
-
1071
- /* ------------------------------------------------------------------ */
1072
- /* collect_artifact_data (obj-randart.c L1059) */
1073
- /* ------------------------------------------------------------------ */
1074
-
1075
- /**
1076
- * collect_artifact_data (obj-randart.c L1059): parse the standard artifacts and
1077
- * count up the frequencies of the various abilities.
1078
- */
1079
- export function collectArtifactCounts(
1080
- reg: ObjRegistry,
1081
- data: ArtifactSetData,
1082
- ): void {
1083
- const aMax = reg.artifacts.length;
1084
-
1085
- for (let i = 0; i < aMax; i++) {
1086
- const art = reg.artifacts[i] ?? null;
1087
- if (!art) continue;
1088
-
1089
- /* Don't parse cursed or null items. */
1090
- if ((data.basePower[i] ?? 0) < 0 || art.tval === 0) continue;
1091
-
1092
- const kind = reg.lookupKind(art.tval, art.sval);
1093
- if (!kind) continue;
1094
-
1095
- /* Special cases -- don't parse these! */
1096
- if (art.name.includes("The One Ring") || kind.kindFlags.has(KF.QUEST_ART)) {
1097
- continue;
1098
- }
1099
-
1100
- /* Add the base item tval to the tv_probs array. */
1101
- data.tvProbs[kind.tval] = (data.tvProbs[kind.tval] ?? 0) + 1;
1102
-
1103
- /* Count combat abilities broken up by type. */
1104
- if (
1105
- art.tval === TV.DIGGING ||
1106
- art.tval === TV.HAFTED ||
1107
- art.tval === TV.POLEARM ||
1108
- art.tval === TV.SWORD
1109
- ) {
1110
- countWeaponAbilities(reg, art, data);
1111
- } else if (art.tval === TV.BOW) {
1112
- countBowAbilities(reg, art, data);
1113
- } else {
1114
- countNonweaponAbilities(reg, art, data);
1115
- }
1116
-
1117
- /* Count other properties. */
1118
- countModifiers(art, data);
1119
- countLowResists(art, data);
1120
- countHighResists(art, data);
1121
- countAbilities(reg, art, data);
1122
- }
1123
- }
1124
-
1125
- /* ------------------------------------------------------------------ */
1126
- /* Frequency rescaling (obj-randart.c L1124-L1265) */
1127
- /* ------------------------------------------------------------------ */
1128
-
1129
- /** Rescale one index group by total / denom (integer division, in place). */
1130
- function rescaleGroup(
1131
- data: ArtifactSetData,
1132
- group: readonly number[],
1133
- denom: number,
1134
- ): void {
1135
- for (const idx of group) {
1136
- data.artProbs[idx] = Math.trunc((data.artProbs[idx]! * data.total) / denom);
1137
- }
1138
- }
1139
-
1140
- /**
1141
- * rescale_freqs (obj-randart.c L1124): rescale item-dependent ability
1142
- * frequencies as though the whole set were made of that item type, so
1143
- * dependent and independent abilities become comparable.
1144
- */
1145
- export function rescaleFreqs(data: ArtifactSetData): void {
1146
- /* Bow-only abilities. */
1147
- rescaleGroup(data, artIdxBow, data.bowTotal);
1148
-
1149
- /* All weapon abilities. */
1150
- rescaleGroup(data, artIdxWeapon, data.bowTotal + data.meleeTotal);
1151
-
1152
- /* Corresponding non-weapon abilities. */
1153
- const nonweaponDenom = data.total - data.meleeTotal - data.bowTotal;
1154
- rescaleGroup(data, artIdxNonweapon, nonweaponDenom);
1155
-
1156
- /* All melee weapon abilities. */
1157
- rescaleGroup(data, artIdxMelee, data.meleeTotal);
1158
-
1159
- /* All general armor abilities. */
1160
- const allArmorDenom =
1161
- data.armorTotal +
1162
- data.bootTotal +
1163
- data.shieldTotal +
1164
- data.headgearTotal +
1165
- data.cloakTotal +
1166
- data.gloveTotal;
1167
- rescaleGroup(data, artIdxAllarmor, allArmorDenom);
1168
-
1169
- /* Boots. */
1170
- rescaleGroup(data, artIdxBoot, data.bootTotal);
1171
- /* Gloves. */
1172
- rescaleGroup(data, artIdxGlove, data.gloveTotal);
1173
- /* Headgear. */
1174
- rescaleGroup(data, artIdxHeadgear, data.headgearTotal);
1175
- /* Shields. */
1176
- rescaleGroup(data, artIdxShield, data.shieldTotal);
1177
- /* Cloaks. */
1178
- rescaleGroup(data, artIdxCloak, data.cloakTotal);
1179
- /* Body armor. */
1180
- rescaleGroup(data, artIdxArmor, data.armorTotal);
1181
- }
1182
-
1183
- /**
1184
- * adjust_freqs (obj-randart.c L1214): enforce minimum frequencies for
1185
- * abilities that might be missing from the standard set, and halve the
1186
- * aggravation frequencies (which are counted twice).
1187
- */
1188
- export function adjustFreqs(data: ArtifactSetData): void {
1189
- const atLeast = (idx: number, min: number): void => {
1190
- if (data.artProbs[idx]! < min) data.artProbs[idx] = min;
1191
- };
1192
-
1193
- atLeast(ART_IDX.GEN_RFEAR, 5);
1194
- atLeast(ART_IDX.MELEE_DICE_SUPER, 5);
1195
- atLeast(ART_IDX.BOW_SHOTS_SUPER, 5);
1196
- atLeast(ART_IDX.BOW_MIGHT_SUPER, 5);
1197
- atLeast(ART_IDX.MELEE_BLOWS_SUPER, 5);
1198
- atLeast(ART_IDX.GEN_SPEED_SUPER, 5);
1199
- atLeast(ART_IDX.GEN_AC, 5);
1200
- atLeast(ART_IDX.GEN_TUNN, 5);
1201
- atLeast(ART_IDX.NONWEAPON_BRAND, 2);
1202
- atLeast(ART_IDX.NONWEAPON_SLAY, 1);
1203
- atLeast(ART_IDX.BOW_BRAND, 2);
1204
- atLeast(ART_IDX.BOW_SLAY, 2);
1205
- atLeast(ART_IDX.NONWEAPON_BLOWS, 1);
1206
- atLeast(ART_IDX.NONWEAPON_SHOTS, 1);
1207
- atLeast(ART_IDX.GEN_AC_SUPER, 5);
1208
- atLeast(ART_IDX.MELEE_AC, 5);
1209
- atLeast(ART_IDX.GEN_PSTUN, 3);
1210
-
1211
- /* Cut aggravation frequencies in half since they're used twice. */
1212
- data.artProbs[ART_IDX.NONWEAPON_AGGR] = Math.trunc(
1213
- data.artProbs[ART_IDX.NONWEAPON_AGGR]! / 2,
1214
- );
1215
- data.artProbs[ART_IDX.WEAPON_AGGR] = Math.trunc(
1216
- data.artProbs[ART_IDX.WEAPON_AGGR]! / 2,
1217
- );
1218
- }
1219
-
1220
- /**
1221
- * parse_frequencies (obj-randart.c L1273): parse the artifacts and write
1222
- * frequencies of their abilities and base object kinds, building the dynamic
1223
- * generation probabilities.
1224
- */
1225
- export function parseFrequencies(reg: ObjRegistry, data: ArtifactSetData): void {
1226
- /* Zero the frequencies for artifact attributes. */
1227
- for (let i = 0; i < ART_IDX_TOTAL; i++) data.artProbs[i] = 0;
1228
-
1229
- collectArtifactCounts(reg, data);
1230
-
1231
- /* Big hack, reduce frequencies of sharp weapons. */
1232
- for (let i = 0; i < TV_MAX; i++) {
1233
- if (i === TV.SWORD || i === TV.POLEARM) {
1234
- data.tvProbs[i] = Math.trunc((data.tvProbs[i]! * 2) / 3);
1235
- }
1236
- }
1237
-
1238
- /* Rescale frequencies. */
1239
- rescaleFreqs(data);
1240
-
1241
- /* Perform any additional rescaling and adjustment. */
1242
- adjustFreqs(data);
1243
-
1244
- /* Build a cumulative frequency table for tvals. */
1245
- for (let i = 0; i < TV_MAX; i++) {
1246
- for (let j = i; j < TV_MAX; j++) {
1247
- data.tvFreq[j] = (data.tvFreq[j] ?? 0) + (data.tvProbs[i] ?? 0);
1248
- }
1249
- }
1250
- }
1251
-
1252
- /* ------------------------------------------------------------------ */
1253
- /* Generation of a random artifact (header) */
1254
- /* ------------------------------------------------------------------ */
1255
-
1256
- /**
1257
- * get_base_item_tval (obj-randart.c L1332): pick a random base item tval from
1258
- * the original artifact tval frequencies.
1259
- */
1260
- export function getBaseItemTval(data: ArtifactSetData, rng: Rng): number {
1261
- let tval = 0;
1262
- const r = rng.randint1(data.tvFreq[TV_MAX - 1]!);
1263
- while (r > (data.tvFreq[tval] ?? 0)) {
1264
- tval++;
1265
- }
1266
- return tval;
1267
- }
1268
-
1269
- /* ------------------------------------------------------------------ */
1270
- /* artifact_set_data allocation (obj-randart.c L2993) */
1271
- /* ------------------------------------------------------------------ */
1272
-
1273
- /** INHIBIT_POWER: the power ceiling above which arts are treated as uber. */
1274
- const INHIBIT_POWER = 20000;
1275
-
1276
- /**
1277
- * artifact_set_data_new (obj-randart.c L2993): allocate and initialize a new
1278
- * artifact set data structure. Power/level/prob arrays are sized by the number
1279
- * of artifact slots (a_max, i.e. reg.artifacts.length); the tval arrays by
1280
- * TV_MAX and the ability array by ART_IDX_TOTAL.
1281
- */
1282
- export function artifactSetDataNew(reg: ObjRegistry): ArtifactSetData {
1283
- const aMax = reg.artifacts.length;
1284
- const zeros = (n: number): number[] => new Array<number>(n).fill(0);
1285
-
1286
- return {
1287
- /* Mean start and increment values for to_hit, to_dam and AC. */
1288
- hitIncrement: 4,
1289
- damIncrement: 4,
1290
- hitStartval: 10,
1291
- damStartval: 10,
1292
- acStartval: 15,
1293
- acIncrement: 5,
1294
-
1295
- artProbs: zeros(ART_IDX_TOTAL),
1296
- tvProbs: zeros(TV_MAX),
1297
- tvNum: zeros(TV_MAX),
1298
- bowTotal: 0,
1299
- meleeTotal: 0,
1300
- bootTotal: 0,
1301
- gloveTotal: 0,
1302
- headgearTotal: 0,
1303
- shieldTotal: 0,
1304
- cloakTotal: 0,
1305
- armorTotal: 0,
1306
- otherTotal: 0,
1307
- total: 0,
1308
- negPowerTotal: 0,
1309
-
1310
- tvFreq: zeros(TV_MAX),
1311
-
1312
- basePower: zeros(aMax),
1313
- maxPower: 0,
1314
- minPower: 0,
1315
- avgPower: 0,
1316
- varPower: 0,
1317
- avgTvPower: zeros(TV_MAX),
1318
- minTvPower: zeros(TV_MAX),
1319
- maxTvPower: zeros(TV_MAX),
1320
-
1321
- baseItemLevel: zeros(aMax),
1322
- baseItemProb: zeros(aMax),
1323
- baseArtAlloc: zeros(aMax),
1324
- };
1325
- }
1326
-
1327
- /**
1328
- * Allocate an ArtifactSetData and run the measurement pipeline over the
1329
- * registry's standard artifacts: store_base_power, then parse_frequencies
1330
- * (which runs collect_artifact_data, rescale_freqs and adjust_freqs). Returns
1331
- * the populated data. `rng` is threaded through for parity with upstream
1332
- * do_randart (store_base_power's fake-object build consumes no RNG for the
1333
- * standard, curse-free artifact set).
1334
- */
1335
- export function collectArtifactData(
1336
- reg: ObjRegistry,
1337
- rng: Rng,
1338
- ): ArtifactSetData {
1339
- void rng;
1340
- const data = artifactSetDataNew(reg);
1341
- storeBasePower(reg, data);
1342
- parseFrequencies(reg, data);
1343
- return data;
1344
- }
1
+ /**
2
+ * Random artifact generation: the data model and power-measurement half,
3
+ * ported from reference/src/obj-randart.c (Angband 4.2.6) lines 1-1332 plus
4
+ * the struct artifact_set_data allocator (obj-randart.c L2993) and the
5
+ * struct definition in obj-randart.h.
6
+ *
7
+ * This module ports the parts of the randart generator that measure the
8
+ * standard artifact set: it evaluates each artifact's power (artifactPower,
9
+ * upstream artifact_power), records the baseline power statistics
10
+ * (storeBasePower), tallies how often each ability appears (collectArtifactData
11
+ * and the count_* helpers), and rescales those tallies into generation
12
+ * frequencies (parseFrequencies / rescaleFreqs / adjustFreqs). The actual
13
+ * generation of new artifacts (do_randart and its helpers, obj-randart.c
14
+ * L1332+) is a separate, later port.
15
+ *
16
+ * Faithful notes / approximations:
17
+ * - The upstream file_putf(log_file, ...) logging is PORTED (PORT_TODO 5.5),
18
+ * through the randartLog sink in ./randart-log.js. It never affects any
19
+ * returned value, so a run with no log open behaves identically.
20
+ * - artifact_power (obj-randart.c L186) builds a "fake" object with
21
+ * make_fake_artifact (obj-make.c L728) and calls object_power on it. It calls
22
+ * the REAL one: obj/artifact-fake.ts, the same builder the knowledge browser
23
+ * and the spoiler dump use, which is object_prep(kind, 0, MAXIMISE)
24
+ * (obj-make.c L817) followed by copy_artifact_data (obj-make.c L520).
25
+ * This file used to carry its own hand-written copy of that field mapping,
26
+ * flattened into a PowerObject because object_power reads only a few fields.
27
+ * Two hand-written copies of one C function agree until they do not, and the
28
+ * divergence would have been silent and total (artifact_power drives the
29
+ * whole design loop). It also cost the object_desc log line, which needs the
30
+ * whole object. Both are gone: one builder, one field mapping.
31
+ * - The fake object's curse timeouts (copy_curses, obj-curse.c) are set from a
32
+ * RANDOMISE roll upstream, and that roll DRAWS. The timeout never enters
33
+ * object_power, so this file used to skip it and say no RNG was consumed;
34
+ * that was measured on the standard artifact set (one curse in the whole
35
+ * file) and was wrong during generation, where design_artifact re-powers an
36
+ * artifact make_bad has just cursed. The rolls are made, in upstream's order,
37
+ * by copyArtifactData itself now that the real builder does the work.
38
+ * - Upstream mean()/variance() (z-util.c L1389/L1516) are exact-rational
39
+ * multi-precision routines; store_base_power calls them with a non-NULL frac,
40
+ * which is the "round the result down" path. Both are now ported for real in
41
+ * ../rational (saturation and all) and called through meanFloored /
42
+ * varianceFloored below, rather than reimplemented here.
43
+ */
44
+
45
+ import { randartLog, randartLogf } from "./randart-log.js";
46
+ import { randartRegistry, seedRandart } from "./randart-registry.js";
47
+ import { ELEM, KF, OBJ_MOD, OF, TV } from "../generated/index.js";
48
+ import { ART_IDX } from "../generated/randart-properties.js";
49
+ import type { Rng } from "../rng.js";
50
+ import type { Constants } from "../constants.js";
51
+ import type { ObjRegistry } from "./bind.js";
52
+ import { tvalFindName } from "./bind.js";
53
+ import type { CurseTimedFoil } from "./object.js";
54
+ import { makeFakeArtifact } from "./artifact-fake.js";
55
+ import { ODESC, objectDesc } from "./desc.js";
56
+ import type { KnownDesc } from "./known-object.js";
57
+ import { makeRuneEnv } from "./knowledge.js";
58
+ import type { ActivationSummarizer } from "./randart-build.js";
59
+ import { mean, variance } from "../rational.js";
60
+ import { objectPower } from "./power.js";
61
+ import type { Artifact, ElementInfo } from "./types.js";
62
+ import { TV_MAX } from "./types.js";
63
+ import type { RandomValue } from "../rng.js";
64
+
65
+ /** ART_IDX_TOTAL: number of learned-probability slots (obj-randart.h). */
66
+ const ART_IDX_TOTAL = ART_IDX.TOTAL;
67
+
68
+ /* ------------------------------------------------------------------ */
69
+ /* struct artifact_set_data (obj-randart.h L59) */
70
+ /* ------------------------------------------------------------------ */
71
+
72
+ /**
73
+ * struct artifact_set_data: everything the generator learns from the standard
74
+ * artifact set. Field names mirror the upstream struct (snake_case -> camelCase).
75
+ */
76
+ export interface ArtifactSetData {
77
+ /* Mean start and increment values for to_hit, to_dam and AC. */
78
+ hitIncrement: number;
79
+ damIncrement: number;
80
+ hitStartval: number;
81
+ damStartval: number;
82
+ acStartval: number;
83
+ acIncrement: number;
84
+
85
+ /* Data structures for learned probabilities. */
86
+ artProbs: number[];
87
+ tvProbs: number[];
88
+ tvNum: number[];
89
+ bowTotal: number;
90
+ meleeTotal: number;
91
+ bootTotal: number;
92
+ gloveTotal: number;
93
+ headgearTotal: number;
94
+ shieldTotal: number;
95
+ cloakTotal: number;
96
+ armorTotal: number;
97
+ otherTotal: number;
98
+ total: number;
99
+ negPowerTotal: number;
100
+
101
+ /* Tval frequency values. */
102
+ tvFreq: number[];
103
+
104
+ /* Artifact power ratings. */
105
+ basePower: number[];
106
+ maxPower: number;
107
+ minPower: number;
108
+ avgPower: number;
109
+ varPower: number;
110
+ avgTvPower: number[];
111
+ minTvPower: number[];
112
+ maxTvPower: number[];
113
+
114
+ /* Base item levels. */
115
+ baseItemLevel: number[];
116
+
117
+ /* Base item rarities. */
118
+ baseItemProb: number[];
119
+
120
+ /* Artifact rarities. */
121
+ baseArtAlloc: number[];
122
+
123
+ /**
124
+ * The player-timed failure tables (timed effect NAME -> fail directives) for
125
+ * artifact_curse_conflicts' TIMED_INC foil rejection (obj-curse.c L267-296).
126
+ * Built by the game from the bound TimedEffect[]; absent => the foil branch
127
+ * is skipped (curses still filtered by explicit conflict flags). See gap 3.3.
128
+ */
129
+ timedFoil?: CurseTimedFoil | undefined;
130
+ /**
131
+ * effect_summarize_properties (effects-info.c) for
132
+ * remove_contradictory_activation's redundancy check (obj-randart.c L2420,
133
+ * gap 3.8). Lives in the effects domain; injected here. Absent => a redundant
134
+ * activation is never stripped (conservative). Wired from the effects layer.
135
+ */
136
+ activationSummarize?: ActivationSummarizer | undefined;
137
+ }
138
+
139
+ /* ------------------------------------------------------------------ */
140
+ /* Arrays of indices by item type, used in frequency generation */
141
+ /* (obj-randart.c L52) */
142
+ /* ------------------------------------------------------------------ */
143
+
144
+ const artIdxBow: readonly number[] = [
145
+ ART_IDX.BOW_SHOTS,
146
+ ART_IDX.BOW_MIGHT,
147
+ ART_IDX.BOW_BRAND,
148
+ ART_IDX.BOW_SLAY,
149
+ ];
150
+ const artIdxWeapon: readonly number[] = [
151
+ ART_IDX.WEAPON_HIT,
152
+ ART_IDX.WEAPON_DAM,
153
+ ART_IDX.WEAPON_AGGR,
154
+ ];
155
+ const artIdxNonweapon: readonly number[] = [
156
+ ART_IDX.NONWEAPON_HIT,
157
+ ART_IDX.NONWEAPON_DAM,
158
+ ART_IDX.NONWEAPON_HIT_DAM,
159
+ ART_IDX.NONWEAPON_AGGR,
160
+ ART_IDX.NONWEAPON_BRAND,
161
+ ART_IDX.NONWEAPON_SLAY,
162
+ ART_IDX.NONWEAPON_BLOWS,
163
+ ART_IDX.NONWEAPON_SHOTS,
164
+ ];
165
+ const artIdxMelee: readonly number[] = [
166
+ ART_IDX.MELEE_BLESS,
167
+ ART_IDX.MELEE_SINV,
168
+ ART_IDX.MELEE_BRAND,
169
+ ART_IDX.MELEE_SLAY,
170
+ ART_IDX.MELEE_BLOWS,
171
+ ART_IDX.MELEE_AC,
172
+ ART_IDX.MELEE_DICE,
173
+ ART_IDX.MELEE_WEIGHT,
174
+ ART_IDX.MELEE_TUNN,
175
+ ];
176
+ const artIdxAllarmor: readonly number[] = [ART_IDX.ALLARMOR_WEIGHT];
177
+ const artIdxBoot: readonly number[] = [
178
+ ART_IDX.BOOT_AC,
179
+ ART_IDX.BOOT_FEATHER,
180
+ ART_IDX.BOOT_STEALTH,
181
+ ART_IDX.BOOT_TRAP_IMM,
182
+ ART_IDX.BOOT_SPEED,
183
+ ART_IDX.BOOT_MOVES,
184
+ ];
185
+ const artIdxGlove: readonly number[] = [
186
+ ART_IDX.GLOVE_AC,
187
+ ART_IDX.GLOVE_HIT_DAM,
188
+ ART_IDX.GLOVE_FA,
189
+ ART_IDX.GLOVE_DEX,
190
+ ];
191
+ const artIdxHeadgear: readonly number[] = [
192
+ ART_IDX.HELM_AC,
193
+ ART_IDX.HELM_RBLIND,
194
+ ART_IDX.HELM_ESP,
195
+ ART_IDX.HELM_SINV,
196
+ ART_IDX.HELM_WIS,
197
+ ART_IDX.HELM_INT,
198
+ ];
199
+ const artIdxShield: readonly number[] = [
200
+ ART_IDX.SHIELD_AC,
201
+ ART_IDX.SHIELD_LRES,
202
+ ];
203
+ const artIdxCloak: readonly number[] = [
204
+ ART_IDX.CLOAK_AC,
205
+ ART_IDX.CLOAK_STEALTH,
206
+ ];
207
+ const artIdxArmor: readonly number[] = [
208
+ ART_IDX.ARMOR_AC,
209
+ ART_IDX.ARMOR_STEALTH,
210
+ ART_IDX.ARMOR_HLIFE,
211
+ ART_IDX.ARMOR_CON,
212
+ ART_IDX.ARMOR_LRES,
213
+ ART_IDX.ARMOR_ALLRES,
214
+ ART_IDX.ARMOR_HRES,
215
+ ];
216
+
217
+ /* ------------------------------------------------------------------ */
218
+ /* Aspect helpers (pure; MAXIMISE/MINIMISE consume no RNG) */
219
+ /* ------------------------------------------------------------------ */
220
+
221
+ /** randcalc(v, 0, MINIMISE): base + dice (see rng.ts damcalc/mBonusCalc). */
222
+ function rvMinimise(v: RandomValue): number {
223
+ return v.base + v.dice;
224
+ }
225
+
226
+ /** Number of set entries in a 1-based boolean slay/brand array, or 0. */
227
+ function countTrue(arr: readonly boolean[] | null): number {
228
+ if (!arr) return 0;
229
+ let n = 0;
230
+ for (let i = 1; i < arr.length; i++) {
231
+ if (arr[i]) n++;
232
+ }
233
+ return n;
234
+ }
235
+
236
+ /**
237
+ * store_base_power's two statistics, at the call shape obj-randart.c L278-L282
238
+ * uses: mean(a, n, &frac) and variance(a, n, false, false, &frac), i.e. the
239
+ * FLOORED variants. The scratch fraction upstream passes is written and then
240
+ * ignored, so one shared throwaway stands in for it.
241
+ *
242
+ * These used to be local reimplementations here (plain floor division and
243
+ * (sum(x^2)*size - sum(x)^2) / size^2). They agreed with the C for artifact
244
+ * powers, but not at the rails: upstream's variance saturates at INT_MAX and
245
+ * upstream's mean floors toward negative infinity via a magnitude-and-fraction
246
+ * dance. ../rational is the real port, so the statistics now go through it.
247
+ */
248
+ const SCRATCH_FRAC = { n: 0, d: 1 };
249
+
250
+ function meanFloored(nums: readonly number[]): number {
251
+ return mean(nums, nums.length, SCRATCH_FRAC);
252
+ }
253
+
254
+ function varianceFloored(nums: readonly number[]): number {
255
+ return variance(nums, nums.length, false, false, SCRATCH_FRAC);
256
+ }
257
+
258
+ /* ------------------------------------------------------------------ */
259
+ /* artifact_power (obj-randart.c L186) */
260
+ /* ------------------------------------------------------------------ */
261
+ /**
262
+ * artifact_power (obj-randart.c L186): the artifact's power, by generating a
263
+ * fake object from the artifact and calling the common object_power.
264
+ */
265
+ export function artifactPower(
266
+ reg: ObjRegistry,
267
+ /**
268
+ * object_prep's z_info / player constants, reached upstream as globals. Only
269
+ * the real make_fake_artifact needs them, which is why this argument appears
270
+ * here at all: the flattened builder that used to live in this file could
271
+ * skip object_prep, and skipping object_prep is what let the two copies of
272
+ * one C function drift.
273
+ */
274
+ constants: Constants,
275
+ art: Artifact,
276
+ /**
277
+ * artifact_power's `reason` (obj-randart.c:186), which exists only to head
278
+ * the log block. REQUIRED rather than optional: upstream writes it out at
279
+ * every one of its four call sites, and a default here would silently label
280
+ * one evaluation as another in the log the argument exists to produce.
281
+ */
282
+ reason: string,
283
+ /**
284
+ * make_fake_artifact's copy_curses rolls a timeout per cursed slot and those
285
+ * rolls DRAW. Required, because a default would hand back a private Rng and
286
+ * silently restore the divergence this argument exists to close.
287
+ */
288
+ rng: Rng,
289
+ ): number {
290
+ randartLogf(() => `********** Evaluating ${reason} ********\n`);
291
+ randartLogf(() => `Artifact index is ${art.aidx}\n`);
292
+
293
+ const obj = makeFakeArtifact(reg, constants, art, rng);
294
+ if (!obj) return 0;
295
+
296
+ /*
297
+ * object_desc of the fake artifact (obj-randart.c L205-L206), with
298
+ * ODESC_PREFIX | ODESC_FULL | ODESC_SPOIL and a NULL player - upstream's own
299
+ * argument, and the reason this needs no knowledge state. A null player is
300
+ * an omniscient observer: objectDesc uses the object as its own known shadow
301
+ * and every gate opens, so the RuneEnv below is never asked a knowledge
302
+ * question. It supplies the registries and nothing else, and slot_object is
303
+ * null for every slot because there is no player to have equipment.
304
+ *
305
+ * The KnownDesc is equally thin, and deliberately says the honest thing
306
+ * rather than the convenient one: isAware/isTried are FALSE, exactly as
307
+ * upstream's kind->aware at this point in birth. It does not matter -- SPOIL
308
+ * forces `aware` true a line later, and obj_desc_get_basename returns
309
+ * kind->name for an artifact regardless -- but a KnownDesc that lied here
310
+ * would be a claim about the player, and this code has no player.
311
+ *
312
+ * Its format string is "%s\n", which has no literal span for the census in
313
+ * randart-log.census.test.ts to match, so this line was carried there as
314
+ * UNWRITTEN_SPANLESS = 1 for as long as it went unwritten. That count is now
315
+ * zero, which is the only reason the reassuring EXPECTED_MISSING_RANDART = 0
316
+ * can be read as "nothing left".
317
+ */
318
+ randartLogf(() => {
319
+ const env = makeRuneEnv(
320
+ () => null,
321
+ (v) => rng.randcalcVaries(v),
322
+ {
323
+ brands: reg.brands,
324
+ slays: reg.slays,
325
+ curses: reg.curses,
326
+ properties: reg.properties,
327
+ elementNames: (reg.projections ?? []).map((p) => p.name),
328
+ },
329
+ );
330
+ const deps: KnownDesc = { isAware: () => false, isTried: () => false };
331
+ const name = objectDesc(
332
+ obj,
333
+ ODESC.PREFIX | ODESC.FULL | ODESC.SPOIL,
334
+ null,
335
+ env,
336
+ deps,
337
+ );
338
+ return `${name}
339
+ `;
340
+ });
341
+
342
+ return objectPower(reg, obj);
343
+ }
344
+
345
+ /* ------------------------------------------------------------------ */
346
+ /* store_base_power (obj-randart.c L219) */
347
+ /* ------------------------------------------------------------------ */
348
+
349
+ /**
350
+ * store_base_power (obj-randart.c L219): store the original artifact power
351
+ * ratings as a baseline, and capture the per-set and per-tval power statistics
352
+ * and base-item info.
353
+ */
354
+ export function storeBasePower(
355
+ reg: ObjRegistry,
356
+ constants: Constants,
357
+ arts: readonly (Artifact | null)[],
358
+ data: ArtifactSetData,
359
+ rng: Rng,
360
+ ): void {
361
+ const aMax = arts.length;
362
+
363
+ data.maxPower = 0;
364
+ data.minPower = INHIBIT_POWER + 1;
365
+ data.varPower = 0;
366
+
367
+ const fakeTotalPower: number[] = [];
368
+ const fakeTvPower: number[][] = [];
369
+ for (let i = 0; i < TV_MAX; i++) {
370
+ fakeTvPower.push([]);
371
+ data.minTvPower[i] = INHIBIT_POWER + 1;
372
+ data.maxTvPower[i] = 0;
373
+ }
374
+
375
+ for (let i = 0; i < aMax; i++) {
376
+ const art = arts[i] ?? null;
377
+ const power = art
378
+ ? artifactPower(reg, constants, art, "for original power", rng)
379
+ : 0;
380
+ data.basePower[i] = power;
381
+
382
+ /* Capture power stats, ignoring cursed and uber arts. */
383
+ if (power > data.maxPower && power < INHIBIT_POWER) {
384
+ data.maxPower = power;
385
+ }
386
+ if (power < data.minPower && power > 0) {
387
+ data.minPower = power;
388
+ }
389
+ if (power > 0 && power < INHIBIT_POWER && art) {
390
+ const tval = art.tval;
391
+ fakeTotalPower.push(power);
392
+ fakeTvPower[tval]!.push(power);
393
+ data.tvNum[tval] = (data.tvNum[tval] ?? 0) + 1;
394
+ if (power < (data.minTvPower[tval] ?? 0)) data.minTvPower[tval] = power;
395
+ if (power > (data.maxTvPower[tval] ?? 0)) data.maxTvPower[tval] = power;
396
+ }
397
+ if (power < 0) {
398
+ data.negPowerTotal++;
399
+ }
400
+
401
+ if (!power || !art) continue;
402
+ const kind = reg.lookupKind(art.tval, art.sval);
403
+ if (!kind) continue;
404
+ data.baseItemLevel[i] = kind.level;
405
+ data.baseItemProb[i] = kind.allocProb;
406
+ data.baseArtAlloc[i] = art.allocProb;
407
+ }
408
+
409
+ /* Round the result down (upstream passes frac but ignores it). */
410
+ data.avgPower = meanFloored(fakeTotalPower);
411
+ data.varPower = varianceFloored(fakeTotalPower);
412
+ for (let i = 0; i < TV_MAX; i++) {
413
+ if (data.tvNum[i]) {
414
+ data.avgTvPower[i] = meanFloored(fakeTvPower[i]!);
415
+ }
416
+ }
417
+
418
+ randartLogf(
419
+ () =>
420
+ `Max power is ${String(data.maxPower)}, min is ${String(data.minPower)}\n`,
421
+ );
422
+ randartLogf(
423
+ () =>
424
+ `Mean is ${String(data.avgPower)}, variance is ${String(data.varPower)}\n`,
425
+ );
426
+ for (let i = 0; i < TV_MAX; i++) {
427
+ if (data.avgTvPower[i]) {
428
+ randartLogf(
429
+ () =>
430
+ `Power for tval ${tvalFindName(i)}: min ${String(data.minTvPower[i])}, max ${String(data.maxTvPower[i])}, avg ${String(data.avgTvPower[i])}\n`,
431
+ );
432
+ }
433
+ }
434
+
435
+ /* Store the number of different types, for use later. */
436
+ for (let i = 0; i < aMax; i++) {
437
+ const art = arts[i] ?? null;
438
+ const tval = art ? art.tval : TV.NULL;
439
+ /* The upstream switch on tval is now the randart registry's `census`
440
+ * table. This is not bookkeeping: the totals feed the frequency table the
441
+ * design loop SPENDS, so a mod's item class landing in `otherTotal` skews
442
+ * what every randart in the game becomes. An unregistered tval counts
443
+ * toward `otherTotal`, which is upstream's own default arm. */
444
+ const bucket = randartRegistry().census.handlerFor(tval);
445
+ if (bucket) bucket(data);
446
+ else data.otherTotal++;
447
+ data.total++;
448
+ }
449
+ }
450
+
451
+ /* ------------------------------------------------------------------ *
452
+ * Core's item-class census buckets, lifted case by case.
453
+ * ------------------------------------------------------------------ */
454
+
455
+ seedRandart((registry) => {
456
+ const bucket = (
457
+ tvals: readonly number[],
458
+ count: (data: ArtifactSetData) => void,
459
+ ): void => {
460
+ for (const tval of tvals) registry.census.set(tval, count);
461
+ };
462
+
463
+ bucket([TV.SWORD, TV.POLEARM, TV.HAFTED], (d) => {
464
+ d.meleeTotal++;
465
+ });
466
+ bucket([TV.BOW], (d) => {
467
+ d.bowTotal++;
468
+ });
469
+ bucket([TV.SOFT_ARMOR, TV.HARD_ARMOR, TV.DRAG_ARMOR], (d) => {
470
+ d.armorTotal++;
471
+ });
472
+ bucket([TV.SHIELD], (d) => {
473
+ d.shieldTotal++;
474
+ });
475
+ bucket([TV.CLOAK], (d) => {
476
+ d.cloakTotal++;
477
+ });
478
+ bucket([TV.HELM, TV.CROWN], (d) => {
479
+ d.headgearTotal++;
480
+ });
481
+ bucket([TV.GLOVES], (d) => {
482
+ d.gloveTotal++;
483
+ });
484
+ bucket([TV.BOOTS], (d) => {
485
+ d.bootTotal++;
486
+ });
487
+ /* An EMPTY artifact slot, registered explicitly rather than left to the
488
+ * default: upstream distinguishes "no artifact here" (counts toward nothing)
489
+ * from "an item class I have no bucket for" (counts toward otherTotal), and
490
+ * the default arm can only express the second. */
491
+ bucket([TV.NULL], () => {
492
+ /* deliberately nothing */
493
+ });
494
+ });
495
+
496
+ /* ------------------------------------------------------------------ */
497
+ /* Ability counters (obj-randart.c L342-L1053) */
498
+ /* ------------------------------------------------------------------ */
499
+
500
+ /**
501
+ * count_weapon_abilities (obj-randart.c L342): handle weapon combat abilities.
502
+ */
503
+ export function countWeaponAbilities(
504
+ reg: ObjRegistry,
505
+ art: Artifact,
506
+ data: ArtifactSetData,
507
+ ): void {
508
+ const kind = reg.lookupKind(art.tval, art.sval);
509
+ if (!kind) return;
510
+ const minToH = rvMinimise(kind.toH);
511
+ const minToD = rvMinimise(kind.toD);
512
+ const minToA = rvMinimise(kind.toA);
513
+
514
+ /* To-hit and to-dam. */
515
+ let bonus = Math.trunc(
516
+ (art.toH - minToH - data.hitStartval) / data.hitIncrement,
517
+ );
518
+ if (bonus > 0) {
519
+ randartLogf(
520
+ () => `Adding ${bonus} instances of extra to-hit bonus for weapon\n`,
521
+ );
522
+ } else if (bonus < 0) {
523
+ randartLogf(
524
+ () => `Subtracting ${bonus} instances of extra to-hit bonus for weapon\n`,
525
+ );
526
+ }
527
+ data.artProbs[ART_IDX.WEAPON_HIT]! += bonus;
528
+
529
+ bonus = Math.trunc((art.toD - minToD - data.damStartval) / data.damIncrement);
530
+ /* Asymmetric with to-hit ON PURPOSE: the to-dam arm has no `< 0` test, so a
531
+ * bonus of exactly 0 logs "Subtracting 0" (obj-randart.c L359-363). */
532
+ if (bonus > 0) {
533
+ randartLogf(
534
+ () => `Adding ${bonus} instances of extra to-dam bonus for weapon\n`,
535
+ );
536
+ } else {
537
+ randartLogf(
538
+ () => `Subtracting ${bonus} instances of extra to-dam bonus for weapon\n`,
539
+ );
540
+ }
541
+ data.artProbs[ART_IDX.WEAPON_DAM]! += bonus;
542
+
543
+ /* Does this weapon have an unusual bonus to AC? */
544
+ bonus = Math.trunc((art.toA - minToA) / data.acIncrement);
545
+ if (art.toA > 20) {
546
+ randartLogf(() => `Adding ${bonus} for supercharged AC\n`);
547
+ data.artProbs[ART_IDX.MELEE_AC_SUPER]!++;
548
+ } else if (bonus > 0) {
549
+ randartLogf(
550
+ () => `Adding ${bonus} instances of extra AC bonus for weapon\n`,
551
+ );
552
+ data.artProbs[ART_IDX.MELEE_AC]! += bonus;
553
+ }
554
+
555
+ /* Check damage dice - are they more than normal? */
556
+ if (art.dd > kind.dd) {
557
+ if (art.dd - kind.dd > 2) {
558
+ randartLog(`Adding 1 for super-charged damage dice!\n`);
559
+ data.artProbs[ART_IDX.MELEE_DICE_SUPER]!++;
560
+ } else {
561
+ randartLog(`Adding 1 for extra damage dice.\n`);
562
+ data.artProbs[ART_IDX.MELEE_DICE]!++;
563
+ }
564
+ }
565
+
566
+ /* Check weight - is it different from normal? */
567
+ if (art.weight !== kind.weight) {
568
+ randartLog(`Adding 1 for unusual weight.\n`);
569
+ data.artProbs[ART_IDX.MELEE_WEIGHT]!++;
570
+ }
571
+
572
+ /* Do we have 3 or more extra blows? */
573
+ const blows = art.modifiers[OBJ_MOD.BLOWS] ?? 0;
574
+ if (blows > 2) {
575
+ randartLog(`Adding 1 for supercharged blows (3 or more!)\n`);
576
+ data.artProbs[ART_IDX.MELEE_BLOWS_SUPER]!++;
577
+ } else if (blows > 0) {
578
+ randartLog(`Adding 1 for extra blows\n`);
579
+ data.artProbs[ART_IDX.MELEE_BLOWS]!++;
580
+ }
581
+
582
+ /* Aggravation. */
583
+ if (art.flags.has(OF.AGGRAVATE)) {
584
+ randartLog(`Adding 1 for aggravation - weapon\n`);
585
+ data.artProbs[ART_IDX.WEAPON_AGGR]!++;
586
+ }
587
+
588
+ /* Blessed weapon? */
589
+ if (art.flags.has(OF.BLESSED)) {
590
+ randartLog(`Adding 1 for blessed weapon\n`);
591
+ data.artProbs[ART_IDX.MELEE_BLESS]!++;
592
+ }
593
+
594
+ /* See invisible? */
595
+ if (art.flags.has(OF.SEE_INVIS)) {
596
+ randartLog(`Adding 1 for see invisible (weapon case)\n`);
597
+ data.artProbs[ART_IDX.MELEE_SINV]!++;
598
+ }
599
+
600
+ /* Tunnelling ability. */
601
+ if ((art.modifiers[OBJ_MOD.TUNNEL] ?? 0) > 0) {
602
+ randartLog(`Adding 1 for tunnelling bonus.\n`);
603
+ data.artProbs[ART_IDX.MELEE_TUNN]!++;
604
+ }
605
+
606
+ /* Count brands and slays. */
607
+ if (art.slays) {
608
+ const n = countTrue(art.slays);
609
+ data.artProbs[ART_IDX.MELEE_SLAY]! += n;
610
+ randartLogf(() => `Adding ${n} for slays\n`);
611
+ }
612
+ if (art.brands) {
613
+ const n = countTrue(art.brands);
614
+ data.artProbs[ART_IDX.MELEE_BRAND]! += n;
615
+ randartLogf(() => `Adding ${n} for brands\n`);
616
+ }
617
+ }
618
+
619
+ /**
620
+ * count_bow_abilities (obj-randart.c L444): count combat abilities on bows.
621
+ */
622
+ export function countBowAbilities(
623
+ reg: ObjRegistry,
624
+ art: Artifact,
625
+ data: ArtifactSetData,
626
+ ): void {
627
+ const kind = reg.lookupKind(art.tval, art.sval);
628
+ if (!kind) return;
629
+ const minToH = rvMinimise(kind.toH);
630
+ const minToD = rvMinimise(kind.toD);
631
+ const minToA = rvMinimise(kind.toA);
632
+
633
+ /* To-hit. */
634
+ let bonus = Math.trunc(
635
+ (art.toH - minToH - data.hitStartval) / data.hitIncrement,
636
+ );
637
+ if (bonus > 0) {
638
+ randartLogf(
639
+ () => `Adding ${bonus} instances of extra to-hit bonus for weapon\n`,
640
+ );
641
+ } else if (bonus < 0) {
642
+ randartLogf(
643
+ () => `Subtracting ${bonus} instances of extra to-hit bonus for weapon\n`,
644
+ );
645
+ }
646
+ data.artProbs[ART_IDX.WEAPON_HIT]! += bonus;
647
+
648
+ /* To-dam. */
649
+ bonus = Math.trunc((art.toD - minToD - data.damStartval) / data.damIncrement);
650
+ /* Plain `else`, as in count_weapon_abilities: the to-dam arm has no `< 0`
651
+ * test either here (obj-randart.c L461-466), so a bonus of exactly 0 logs
652
+ * "Subtracting 0". The to-hit arm above DOES have one. */
653
+ if (bonus > 0) {
654
+ randartLogf(
655
+ () => `Adding ${bonus} instances of extra to-dam bonus for weapon\n`,
656
+ );
657
+ } else {
658
+ randartLogf(
659
+ () => `Subtracting ${bonus} instances of extra to-dam bonus for weapon\n`,
660
+ );
661
+ }
662
+ data.artProbs[ART_IDX.WEAPON_DAM]! += bonus;
663
+
664
+ /* Armor class. */
665
+ bonus = Math.trunc((art.toA - minToA - data.acStartval) / data.acIncrement);
666
+ if (bonus > 0) {
667
+ randartLogf(() => `Adding ${bonus} for AC bonus - general\n`);
668
+ data.artProbs[ART_IDX.GEN_AC]! += bonus;
669
+ }
670
+
671
+ /* Aggravation. */
672
+ if (art.flags.has(OF.AGGRAVATE)) {
673
+ randartLog(`Adding 1 for aggravation - weapon\n`);
674
+ data.artProbs[ART_IDX.WEAPON_AGGR]!++;
675
+ }
676
+
677
+ /* Do we have more than 1 extra shot? */
678
+ const shots = art.modifiers[OBJ_MOD.SHOTS] ?? 0;
679
+ if (shots > 10) {
680
+ randartLog(`Adding 1 for supercharged shots (more than 1!)\n`);
681
+ data.artProbs[ART_IDX.BOW_SHOTS_SUPER]!++;
682
+ } else if (shots > 0) {
683
+ randartLog(`Adding 1 for extra shots\n`);
684
+ data.artProbs[ART_IDX.BOW_SHOTS]!++;
685
+ }
686
+
687
+ /* Do we have 3 or more extra might? */
688
+ const might = art.modifiers[OBJ_MOD.MIGHT] ?? 0;
689
+ if (might > 2) {
690
+ randartLog(`Adding 1 for supercharged might (3 or more!)\n`);
691
+ data.artProbs[ART_IDX.BOW_MIGHT_SUPER]!++;
692
+ } else if (might > 0) {
693
+ randartLog(`Adding 1 for extra might\n`);
694
+ data.artProbs[ART_IDX.BOW_MIGHT]!++;
695
+ }
696
+
697
+ /* Count brands and slays. */
698
+ if (art.slays) {
699
+ const n = countTrue(art.slays);
700
+ data.artProbs[ART_IDX.BOW_SLAY]! += n;
701
+ randartLogf(() => `Adding ${n} for slays\n`);
702
+ }
703
+ if (art.brands) {
704
+ const n = countTrue(art.brands);
705
+ data.artProbs[ART_IDX.BOW_BRAND]! += n;
706
+ randartLogf(() => `Adding ${n} for brands\n`);
707
+ }
708
+ }
709
+
710
+ /**
711
+ * count_nonweapon_abilities (obj-randart.c L516): handle nonweapon combat
712
+ * abilities.
713
+ */
714
+ export function countNonweaponAbilities(
715
+ reg: ObjRegistry,
716
+ art: Artifact,
717
+ data: ArtifactSetData,
718
+ ): void {
719
+ const kind = reg.lookupKind(art.tval, art.sval);
720
+ if (!kind) return;
721
+ const toHit = art.toH - rvMinimise(kind.toH);
722
+ const toDam = art.toD - rvMinimise(kind.toD);
723
+ const toA = art.toA - rvMinimise(kind.toA) - data.acStartval;
724
+ let bonus = Math.trunc(toA / data.acIncrement);
725
+
726
+ /* Armor class. */
727
+ if (bonus > 0) {
728
+ if (art.toA > 20) {
729
+ randartLogf(() => `Adding ${bonus} for supercharged AC\n`);
730
+ data.artProbs[ART_IDX.GEN_AC_SUPER]!++;
731
+ } else if (art.tval === TV.BOOTS) {
732
+ randartLogf(() => `Adding ${bonus} for AC bonus - boots\n`);
733
+ data.artProbs[ART_IDX.BOOT_AC]! += bonus;
734
+ } else if (art.tval === TV.GLOVES) {
735
+ randartLogf(() => `Adding ${bonus} for AC bonus - gloves\n`);
736
+ data.artProbs[ART_IDX.GLOVE_AC]! += bonus;
737
+ } else if (art.tval === TV.HELM || art.tval === TV.CROWN) {
738
+ randartLogf(() => `Adding ${bonus} for AC bonus - hat\n`);
739
+ data.artProbs[ART_IDX.HELM_AC]! += bonus;
740
+ } else if (art.tval === TV.SHIELD) {
741
+ randartLogf(() => `Adding ${bonus} for AC bonus - shield\n`);
742
+ data.artProbs[ART_IDX.SHIELD_AC]! += bonus;
743
+ } else if (art.tval === TV.CLOAK) {
744
+ randartLogf(() => `Adding ${bonus} for AC bonus - cloak\n`);
745
+ data.artProbs[ART_IDX.CLOAK_AC]! += bonus;
746
+ } else if (
747
+ art.tval === TV.SOFT_ARMOR ||
748
+ art.tval === TV.HARD_ARMOR ||
749
+ art.tval === TV.DRAG_ARMOR
750
+ ) {
751
+ randartLogf(() => `Adding ${bonus} for AC bonus - body armor\n`);
752
+ data.artProbs[ART_IDX.ARMOR_AC]! += bonus;
753
+ } else {
754
+ randartLogf(() => `Adding ${bonus} for AC bonus - general\n`);
755
+ data.artProbs[ART_IDX.GEN_AC]! += bonus;
756
+ }
757
+ }
758
+
759
+ /* To hit and dam bonuses. */
760
+ if (toHit > 0 && toDam > 0) {
761
+ bonus = Math.trunc(
762
+ (toHit + toDam) / (data.hitIncrement + data.damIncrement),
763
+ );
764
+ if (bonus > 0) {
765
+ if (art.tval === TV.GLOVES) {
766
+ randartLogf(
767
+ () =>
768
+ `Adding ${bonus} instances of extra to-hit and to-dam bonus for gloves\n`,
769
+ );
770
+ data.artProbs[ART_IDX.GLOVE_HIT_DAM]! += bonus;
771
+ } else {
772
+ randartLogf(
773
+ () =>
774
+ `Adding ${bonus} instances of extra to-hit and to-dam bonus for non-weapon\n`,
775
+ );
776
+ data.artProbs[ART_IDX.NONWEAPON_HIT_DAM]! += bonus;
777
+ }
778
+ }
779
+ } else if (toHit > 0) {
780
+ bonus = Math.trunc(toHit / data.hitIncrement);
781
+ if (bonus > 0) {
782
+ randartLogf(
783
+ () => `Adding ${bonus} instances of extra to-hit bonus for non-weapon\n`,
784
+ );
785
+ data.artProbs[ART_IDX.NONWEAPON_HIT]! += bonus;
786
+ }
787
+ } else if (toDam > 0) {
788
+ bonus = Math.trunc(toDam / data.damIncrement);
789
+ if (bonus > 0) {
790
+ randartLogf(
791
+ () => `Adding ${bonus} instances of extra to-dam bonus for non-weapon\n`,
792
+ );
793
+ data.artProbs[ART_IDX.NONWEAPON_DAM]! += bonus;
794
+ }
795
+ }
796
+
797
+ /* Check weight - is it different from normal? */
798
+ if (art.weight !== kind.weight) {
799
+ randartLog(`Adding 1 for unusual weight.\n`);
800
+ data.artProbs[ART_IDX.ALLARMOR_WEIGHT]!++;
801
+ }
802
+
803
+ /* Aggravation. */
804
+ if (art.flags.has(OF.AGGRAVATE)) {
805
+ randartLog(`Adding 1 for aggravation - nonweapon\n`);
806
+ data.artProbs[ART_IDX.NONWEAPON_AGGR]!++;
807
+ }
808
+
809
+ /* Count brands and slays. */
810
+ if (art.slays) {
811
+ const n = countTrue(art.slays);
812
+ data.artProbs[ART_IDX.NONWEAPON_SLAY]! += n;
813
+ randartLogf(() => `Adding ${n} for slays\n`);
814
+ }
815
+ if (art.brands) {
816
+ const n = countTrue(art.brands);
817
+ data.artProbs[ART_IDX.NONWEAPON_BRAND]! += n;
818
+ randartLogf(() => `Adding ${n} for brands\n`);
819
+ }
820
+
821
+ /* Blows. */
822
+ if ((art.modifiers[OBJ_MOD.BLOWS] ?? 0) > 0) {
823
+ randartLog(`Adding 1 for extra blows on nonweapon\n`);
824
+ data.artProbs[ART_IDX.NONWEAPON_BLOWS]!++;
825
+ }
826
+
827
+ /* Shots. */
828
+ if ((art.modifiers[OBJ_MOD.SHOTS] ?? 0) > 0) {
829
+ randartLog(`Adding 1 for extra shots on nonweapon\n`);
830
+ data.artProbs[ART_IDX.NONWEAPON_SHOTS]!++;
831
+ }
832
+
833
+ /* Tunnelling ability. */
834
+ if ((art.modifiers[OBJ_MOD.TUNNEL] ?? 0) > 0) {
835
+ randartLog(`Adding 1 for tunnelling bonus - general.\n`);
836
+ data.artProbs[ART_IDX.GEN_TUNN]!++;
837
+ }
838
+ }
839
+
840
+ /**
841
+ * count_modifiers (obj-randart.c L630): count stat and other modifier bonuses.
842
+ */
843
+ export function countModifiers(art: Artifact, data: ArtifactSetData): void {
844
+ let num = 0;
845
+
846
+ /* Stat bonuses. Add up the number of individual bonuses. */
847
+ if ((art.modifiers[OBJ_MOD.STR] ?? 0) > 0) num++;
848
+ if ((art.modifiers[OBJ_MOD.INT] ?? 0) > 0) num++;
849
+ if ((art.modifiers[OBJ_MOD.WIS] ?? 0) > 0) num++;
850
+ if ((art.modifiers[OBJ_MOD.DEX] ?? 0) > 0) num++;
851
+ if ((art.modifiers[OBJ_MOD.CON] ?? 0) > 0) num++;
852
+
853
+ /* Handle a few special cases separately. */
854
+ if (
855
+ (art.tval === TV.HELM || art.tval === TV.CROWN) &&
856
+ ((art.modifiers[OBJ_MOD.WIS] ?? 0) > 0 ||
857
+ (art.modifiers[OBJ_MOD.INT] ?? 0) > 0)
858
+ ) {
859
+ if ((art.modifiers[OBJ_MOD.WIS] ?? 0) > 0) {
860
+ randartLog("Adding 1 for WIS bonus on headgear.\n");
861
+ data.artProbs[ART_IDX.HELM_WIS]!++;
862
+ num--;
863
+ }
864
+ if ((art.modifiers[OBJ_MOD.INT] ?? 0) > 0) {
865
+ randartLog("Adding 1 for INT bonus on headgear.\n");
866
+ data.artProbs[ART_IDX.HELM_INT]!++;
867
+ num--;
868
+ }
869
+ } else if (
870
+ (art.tval === TV.SOFT_ARMOR ||
871
+ art.tval === TV.HARD_ARMOR ||
872
+ art.tval === TV.DRAG_ARMOR) &&
873
+ (art.modifiers[OBJ_MOD.CON] ?? 0) > 0
874
+ ) {
875
+ randartLog("Adding 1 for CON bonus on body armor.\n");
876
+ data.artProbs[ART_IDX.ARMOR_CON]!++;
877
+ num--;
878
+ } else if (art.tval === TV.GLOVES && (art.modifiers[OBJ_MOD.DEX] ?? 0) > 0) {
879
+ randartLog("Adding 1 for DEX bonus on gloves.\n");
880
+ data.artProbs[ART_IDX.GLOVE_DEX]!++;
881
+ num--;
882
+ }
883
+
884
+ /* Now the general case. */
885
+ if (num > 0) {
886
+ randartLogf(() => `Adding ${num} for stat bonuses - general.\n`);
887
+ data.artProbs[ART_IDX.GEN_STAT]! += num;
888
+ }
889
+
890
+ /* Handle stealth, including a couple of special cases. */
891
+ if ((art.modifiers[OBJ_MOD.STEALTH] ?? 0) > 0) {
892
+ if (art.tval === TV.BOOTS) {
893
+ randartLog("Adding 1 for stealth bonus on boots.\n");
894
+ data.artProbs[ART_IDX.BOOT_STEALTH]!++;
895
+ } else if (art.tval === TV.CLOAK) {
896
+ randartLog("Adding 1 for stealth bonus on cloak.\n");
897
+ data.artProbs[ART_IDX.CLOAK_STEALTH]!++;
898
+ } else if (
899
+ art.tval === TV.SOFT_ARMOR ||
900
+ art.tval === TV.HARD_ARMOR ||
901
+ art.tval === TV.DRAG_ARMOR
902
+ ) {
903
+ randartLog("Adding 1 for stealth bonus on armor.\n");
904
+ data.artProbs[ART_IDX.ARMOR_STEALTH]!++;
905
+ } else {
906
+ randartLog("Adding 1 for stealth bonus - general.\n");
907
+ data.artProbs[ART_IDX.GEN_STEALTH]!++;
908
+ }
909
+ }
910
+
911
+ /* Searching bonus - fully generic. */
912
+ if ((art.modifiers[OBJ_MOD.SEARCH] ?? 0) > 0) {
913
+ randartLog("Adding 1 for search bonus - general.\n");
914
+ data.artProbs[ART_IDX.GEN_SEARCH]!++;
915
+ }
916
+
917
+ /* Infravision bonus - fully generic. */
918
+ if ((art.modifiers[OBJ_MOD.INFRA] ?? 0) > 0) {
919
+ randartLog("Adding 1 for infravision bonus - general.\n");
920
+ data.artProbs[ART_IDX.GEN_INFRA]!++;
921
+ }
922
+
923
+ /* Damage reduction bonus - fully generic. */
924
+ if ((art.modifiers[OBJ_MOD.DAM_RED] ?? 0) > 0) {
925
+ randartLog("Adding 1 for damage reduction bonus - general.\n");
926
+ data.artProbs[ART_IDX.GEN_DAM_RED]!++;
927
+ }
928
+
929
+ /* Moves bonus. */
930
+ if ((art.modifiers[OBJ_MOD.MOVES] ?? 0) > 0) {
931
+ if (art.tval === TV.BOOTS) {
932
+ randartLog("Adding 1 for moves bonus on boots.\n");
933
+ data.artProbs[ART_IDX.BOOT_MOVES]!++;
934
+ } else {
935
+ randartLog("Adding 1 for moves bonus - general.\n");
936
+ data.artProbs[ART_IDX.GEN_MOVES]!++;
937
+ }
938
+ }
939
+
940
+ /* Speed - boots handled separately, supercharge shares its frequency. */
941
+ const speed = art.modifiers[OBJ_MOD.SPEED] ?? 0;
942
+ if (speed > 0) {
943
+ if (speed > 7) {
944
+ randartLog("Adding 1 for supercharged speed bonus!\n");
945
+ data.artProbs[ART_IDX.GEN_SPEED_SUPER]!++;
946
+ } else if (art.tval === TV.BOOTS) {
947
+ randartLog("Adding 1 for normal speed bonus on boots.\n");
948
+ data.artProbs[ART_IDX.BOOT_SPEED]!++;
949
+ } else {
950
+ randartLog("Adding 1 for normal speed bonus - general.\n");
951
+ data.artProbs[ART_IDX.GEN_SPEED]!++;
952
+ }
953
+ }
954
+
955
+ /* Permanent light. */
956
+ if ((art.modifiers[OBJ_MOD.LIGHT] ?? 0) > 0) {
957
+ randartLog("Adding 1 for light radius - general.\n");
958
+ data.artProbs[ART_IDX.GEN_LIGHT]!++;
959
+ }
960
+ }
961
+
962
+ /**
963
+ * count_low_resists (obj-randart.c L764): count low resists and immunities.
964
+ */
965
+ export function countLowResists(art: Artifact, data: ArtifactSetData): void {
966
+ let num = 0;
967
+
968
+ /* Count up immunities for this item, if any. */
969
+ if ((art.elInfo[ELEM.ACID] as ElementInfo).resLevel === 3) num++;
970
+ if ((art.elInfo[ELEM.ELEC] as ElementInfo).resLevel === 3) num++;
971
+ if ((art.elInfo[ELEM.FIRE] as ElementInfo).resLevel === 3) num++;
972
+ if ((art.elInfo[ELEM.COLD] as ElementInfo).resLevel === 3) num++;
973
+
974
+ randartLogf(() => `Adding ${num} for immunities.\n`);
975
+ data.artProbs[ART_IDX.GEN_IMMUNE]! += num;
976
+
977
+ /* Count up low resists (not the type, just the number). */
978
+ num = 0;
979
+ if ((art.elInfo[ELEM.ACID] as ElementInfo).resLevel === 1) num++;
980
+ if ((art.elInfo[ELEM.ELEC] as ElementInfo).resLevel === 1) num++;
981
+ if ((art.elInfo[ELEM.FIRE] as ElementInfo).resLevel === 1) num++;
982
+ if ((art.elInfo[ELEM.COLD] as ElementInfo).resLevel === 1) num++;
983
+
984
+ if (num) {
985
+ if (art.tval === TV.SHIELD) {
986
+ randartLogf(() => `Adding ${num} for low resists on shield.\n`);
987
+ data.artProbs[ART_IDX.SHIELD_LRES]! += num;
988
+ } else if (
989
+ art.tval === TV.SOFT_ARMOR ||
990
+ art.tval === TV.HARD_ARMOR ||
991
+ art.tval === TV.DRAG_ARMOR
992
+ ) {
993
+ if (num === 4) {
994
+ randartLog("Adding 1 for ALL LOW RESISTS on body armor.\n");
995
+ data.artProbs[ART_IDX.ARMOR_ALLRES]!++;
996
+ } else {
997
+ randartLogf(() => `Adding ${num} for low resists on body armor.\n`);
998
+ data.artProbs[ART_IDX.ARMOR_LRES]! += num;
999
+ }
1000
+ } else {
1001
+ randartLogf(() => `Adding ${num} for low resists - general.\n`);
1002
+ data.artProbs[ART_IDX.GEN_LRES]! += num;
1003
+ }
1004
+ }
1005
+ }
1006
+
1007
+ /**
1008
+ * count_high_resists (obj-randart.c L816): count high resists and protections.
1009
+ */
1010
+ export function countHighResists(art: Artifact, data: ArtifactSetData): void {
1011
+ let num = 0;
1012
+
1013
+ /* Body armor: count all high resists as an aggregate first. */
1014
+ if (
1015
+ art.tval === TV.SOFT_ARMOR ||
1016
+ art.tval === TV.HARD_ARMOR ||
1017
+ art.tval === TV.DRAG_ARMOR
1018
+ ) {
1019
+ if ((art.elInfo[ELEM.POIS] as ElementInfo).resLevel === 1) num++;
1020
+ if (art.flags.has(OF.PROT_FEAR)) num++;
1021
+ if ((art.elInfo[ELEM.LIGHT] as ElementInfo).resLevel === 1) num++;
1022
+ if ((art.elInfo[ELEM.DARK] as ElementInfo).resLevel === 1) num++;
1023
+ if (art.flags.has(OF.PROT_BLIND)) num++;
1024
+ if (art.flags.has(OF.PROT_CONF)) num++;
1025
+ if ((art.elInfo[ELEM.SOUND] as ElementInfo).resLevel === 1) num++;
1026
+ if ((art.elInfo[ELEM.SHARD] as ElementInfo).resLevel === 1) num++;
1027
+ if ((art.elInfo[ELEM.NEXUS] as ElementInfo).resLevel === 1) num++;
1028
+ if ((art.elInfo[ELEM.NETHER] as ElementInfo).resLevel === 1) num++;
1029
+ if ((art.elInfo[ELEM.CHAOS] as ElementInfo).resLevel === 1) num++;
1030
+ if ((art.elInfo[ELEM.DISEN] as ElementInfo).resLevel === 1) num++;
1031
+ if (art.flags.has(OF.PROT_STUN)) num++;
1032
+ randartLogf(() => `Adding ${num} for high resists on body armor.\n`);
1033
+ data.artProbs[ART_IDX.ARMOR_HRES]! += num;
1034
+ }
1035
+
1036
+ /* Now do the high resists individually. */
1037
+ if ((art.elInfo[ELEM.POIS] as ElementInfo).resLevel === 1) {
1038
+ randartLog("Adding 1 for resist poison - general.\n");
1039
+ data.artProbs[ART_IDX.GEN_RPOIS]!++;
1040
+ }
1041
+ if (art.flags.has(OF.PROT_FEAR)) {
1042
+ randartLog("Adding 1 for resist fear - general.\n");
1043
+ data.artProbs[ART_IDX.GEN_RFEAR]!++;
1044
+ }
1045
+ if ((art.elInfo[ELEM.LIGHT] as ElementInfo).resLevel === 1) {
1046
+ randartLog("Adding 1 for resist light - general.\n");
1047
+ data.artProbs[ART_IDX.GEN_RLIGHT]!++;
1048
+ }
1049
+ if ((art.elInfo[ELEM.DARK] as ElementInfo).resLevel === 1) {
1050
+ randartLog("Adding 1 for resist dark - general.\n");
1051
+ data.artProbs[ART_IDX.GEN_RDARK]!++;
1052
+ }
1053
+ if (art.flags.has(OF.PROT_BLIND)) {
1054
+ if (art.tval === TV.HELM || art.tval === TV.CROWN) {
1055
+ randartLog("Adding 1 for resist blindness - headgear.\n");
1056
+ data.artProbs[ART_IDX.HELM_RBLIND]!++;
1057
+ } else {
1058
+ randartLog("Adding 1 for resist blindness - general.\n");
1059
+ data.artProbs[ART_IDX.GEN_RBLIND]!++;
1060
+ }
1061
+ }
1062
+ if (art.flags.has(OF.PROT_CONF)) {
1063
+ randartLog("Adding 1 for resist confusion - general.\n");
1064
+ data.artProbs[ART_IDX.GEN_RCONF]!++;
1065
+ }
1066
+ if ((art.elInfo[ELEM.SOUND] as ElementInfo).resLevel === 1) {
1067
+ randartLog("Adding 1 for resist sound - general.\n");
1068
+ data.artProbs[ART_IDX.GEN_RSOUND]!++;
1069
+ }
1070
+ if ((art.elInfo[ELEM.SHARD] as ElementInfo).resLevel === 1) {
1071
+ randartLog("Adding 1 for resist shards - general.\n");
1072
+ data.artProbs[ART_IDX.GEN_RSHARD]!++;
1073
+ }
1074
+ if ((art.elInfo[ELEM.NEXUS] as ElementInfo).resLevel === 1) {
1075
+ randartLog("Adding 1 for resist nexus - general.\n");
1076
+ data.artProbs[ART_IDX.GEN_RNEXUS]!++;
1077
+ }
1078
+ if ((art.elInfo[ELEM.NETHER] as ElementInfo).resLevel === 1) {
1079
+ randartLog("Adding 1 for resist nether - general.\n");
1080
+ data.artProbs[ART_IDX.GEN_RNETHER]!++;
1081
+ }
1082
+ if ((art.elInfo[ELEM.CHAOS] as ElementInfo).resLevel === 1) {
1083
+ randartLog("Adding 1 for resist chaos - general.\n");
1084
+ data.artProbs[ART_IDX.GEN_RCHAOS]!++;
1085
+ }
1086
+ if ((art.elInfo[ELEM.DISEN] as ElementInfo).resLevel === 1) {
1087
+ randartLog("Adding 1 for resist disenchantment - general.\n");
1088
+ data.artProbs[ART_IDX.GEN_RDISEN]!++;
1089
+ }
1090
+ if (art.flags.has(OF.PROT_STUN)) {
1091
+ randartLog("Adding 1 for res_stun - general.\n");
1092
+ data.artProbs[ART_IDX.GEN_PSTUN]!++;
1093
+ }
1094
+ }
1095
+
1096
+ /**
1097
+ * count_abilities (obj-randart.c L943): general abilities, adding some to a
1098
+ * specific item-type tally depending on the base item.
1099
+ */
1100
+ export function countAbilities(
1101
+ reg: ObjRegistry,
1102
+ art: Artifact,
1103
+ data: ArtifactSetData,
1104
+ ): void {
1105
+ const kind = reg.lookupKind(art.tval, art.sval);
1106
+
1107
+ /* Sustains. */
1108
+ if (
1109
+ art.flags.has(OF.SUST_STR) ||
1110
+ art.flags.has(OF.SUST_INT) ||
1111
+ art.flags.has(OF.SUST_WIS) ||
1112
+ art.flags.has(OF.SUST_DEX) ||
1113
+ art.flags.has(OF.SUST_CON)
1114
+ ) {
1115
+ let num = 0;
1116
+ if (art.flags.has(OF.SUST_STR)) num++;
1117
+ if (art.flags.has(OF.SUST_INT)) num++;
1118
+ if (art.flags.has(OF.SUST_WIS)) num++;
1119
+ if (art.flags.has(OF.SUST_DEX)) num++;
1120
+ if (art.flags.has(OF.SUST_CON)) num++;
1121
+ randartLogf(() => `Adding ${num} for stat sustains.\n`);
1122
+ data.artProbs[ART_IDX.GEN_SUST]! += num;
1123
+ }
1124
+
1125
+ /* Free action - handle gloves separately. */
1126
+ if (art.flags.has(OF.FREE_ACT)) {
1127
+ if (art.tval === TV.GLOVES) {
1128
+ randartLog("Adding 1 for free action on gloves.\n");
1129
+ data.artProbs[ART_IDX.GLOVE_FA]!++;
1130
+ } else {
1131
+ randartLog("Adding 1 for free action - general.\n");
1132
+ data.artProbs[ART_IDX.GEN_FA]!++;
1133
+ }
1134
+ }
1135
+
1136
+ /* Hold life - do body armor separately. */
1137
+ if (art.flags.has(OF.HOLD_LIFE)) {
1138
+ if (
1139
+ art.tval === TV.SOFT_ARMOR ||
1140
+ art.tval === TV.HARD_ARMOR ||
1141
+ art.tval === TV.DRAG_ARMOR
1142
+ ) {
1143
+ randartLog("Adding 1 for hold life on armor.\n");
1144
+ data.artProbs[ART_IDX.ARMOR_HLIFE]!++;
1145
+ } else {
1146
+ randartLog("Adding 1 for hold life - general.\n");
1147
+ data.artProbs[ART_IDX.GEN_HLIFE]!++;
1148
+ }
1149
+ }
1150
+
1151
+ /* Feather fall - handle boots separately. */
1152
+ if (art.flags.has(OF.FEATHER)) {
1153
+ if (art.tval === TV.BOOTS) {
1154
+ randartLog("Adding 1 for feather fall on boots.\n");
1155
+ data.artProbs[ART_IDX.BOOT_FEATHER]!++;
1156
+ } else {
1157
+ randartLog("Adding 1 for feather fall - general.\n");
1158
+ data.artProbs[ART_IDX.GEN_FEATHER]!++;
1159
+ }
1160
+ }
1161
+
1162
+ /* See invisible - do helms/crowns separately (weapons already handled). */
1163
+ if (art.flags.has(OF.SEE_INVIS)) {
1164
+ if (
1165
+ !(
1166
+ art.tval === TV.DIGGING ||
1167
+ art.tval === TV.HAFTED ||
1168
+ art.tval === TV.POLEARM ||
1169
+ art.tval === TV.SWORD
1170
+ )
1171
+ ) {
1172
+ if (art.tval === TV.HELM || art.tval === TV.CROWN) {
1173
+ randartLog("Adding 1 for see invisible - headgear.\n");
1174
+ data.artProbs[ART_IDX.HELM_SINV]!++;
1175
+ } else {
1176
+ randartLog("Adding 1 for see invisible - general.\n");
1177
+ data.artProbs[ART_IDX.GEN_SINV]!++;
1178
+ }
1179
+ }
1180
+ }
1181
+
1182
+ /* ESP - handle helms/crowns separately. */
1183
+ if (art.flags.has(OF.TELEPATHY)) {
1184
+ if (art.tval === TV.HELM || art.tval === TV.CROWN) {
1185
+ randartLog("Adding 1 for ESP on headgear.\n");
1186
+ data.artProbs[ART_IDX.HELM_ESP]!++;
1187
+ } else {
1188
+ randartLog("Adding 1 for ESP - general.\n");
1189
+ data.artProbs[ART_IDX.GEN_ESP]!++;
1190
+ }
1191
+ }
1192
+
1193
+ /* Slow digestion - generic. */
1194
+ if (art.flags.has(OF.SLOW_DIGEST)) {
1195
+ randartLog("Adding 1 for slow digestion - general.\n");
1196
+ data.artProbs[ART_IDX.GEN_SDIG]!++;
1197
+ }
1198
+
1199
+ /* Regeneration - generic. */
1200
+ if (art.flags.has(OF.REGEN)) {
1201
+ randartLog("Adding 1 for regeneration - general.\n");
1202
+ data.artProbs[ART_IDX.GEN_REGEN]!++;
1203
+ }
1204
+
1205
+ /* Trap immunity - handle boots separately. */
1206
+ if (art.flags.has(OF.TRAP_IMMUNE)) {
1207
+ if (art.tval === TV.BOOTS) {
1208
+ randartLog("Adding 1 for trap immunity on boots.\n");
1209
+ data.artProbs[ART_IDX.BOOT_TRAP_IMM]!++;
1210
+ } else {
1211
+ randartLog("Adding 1 for trap immunity - general.\n");
1212
+ data.artProbs[ART_IDX.GEN_TRAP_IMM]!++;
1213
+ }
1214
+ }
1215
+
1216
+ /* Activation. */
1217
+ if (art.activation || kind?.activation) {
1218
+ randartLog("Adding 1 for activation.\n");
1219
+ data.artProbs[ART_IDX.GEN_ACTIV]!++;
1220
+ }
1221
+ }
1222
+
1223
+ /* ------------------------------------------------------------------ */
1224
+ /* collect_artifact_data (obj-randart.c L1059) */
1225
+ /* ------------------------------------------------------------------ */
1226
+
1227
+ /**
1228
+ * collect_artifact_data (obj-randart.c L1059): parse the standard artifacts and
1229
+ * count up the frequencies of the various abilities.
1230
+ */
1231
+ export function collectArtifactCounts(
1232
+ reg: ObjRegistry,
1233
+ arts: readonly (Artifact | null)[],
1234
+ data: ArtifactSetData,
1235
+ ): void {
1236
+ const aMax = arts.length;
1237
+
1238
+ for (let i = 0; i < aMax; i++) {
1239
+ const art = arts[i] ?? null;
1240
+
1241
+ /* obj-randart.c:1066-1067 logs the index BEFORE any skip, so a cursed,
1242
+ * tval-0 or absent entry still prints its line. Upstream reads a_info[i]
1243
+ * out of a dense array and never has a null to guard, so the port's own
1244
+ * null check has to come after this to keep the counts aligned. */
1245
+ randartLogf(() => `Current artifact index is ${i}\n`);
1246
+ if (!art) continue;
1247
+
1248
+ /* Don't parse cursed or null items. */
1249
+ if ((data.basePower[i] ?? 0) < 0 || art.tval === 0) continue;
1250
+
1251
+ const kind = reg.lookupKind(art.tval, art.sval);
1252
+ if (!kind) continue;
1253
+
1254
+ /* Special cases -- don't parse these! */
1255
+ if (art.name.includes("The One Ring") || kind.kindFlags.has(KF.QUEST_ART)) {
1256
+ continue;
1257
+ }
1258
+
1259
+ /* Add the base item tval to the tv_probs array. */
1260
+ data.tvProbs[kind.tval] = (data.tvProbs[kind.tval] ?? 0) + 1;
1261
+ randartLogf(() => `Base item is ${kind.kidx}\n`);
1262
+
1263
+ /* Count combat abilities broken up by type. */
1264
+ if (
1265
+ art.tval === TV.DIGGING ||
1266
+ art.tval === TV.HAFTED ||
1267
+ art.tval === TV.POLEARM ||
1268
+ art.tval === TV.SWORD
1269
+ ) {
1270
+ countWeaponAbilities(reg, art, data);
1271
+ } else if (art.tval === TV.BOW) {
1272
+ countBowAbilities(reg, art, data);
1273
+ } else {
1274
+ countNonweaponAbilities(reg, art, data);
1275
+ }
1276
+
1277
+ /* Count other properties. */
1278
+ countModifiers(art, data);
1279
+ countLowResists(art, data);
1280
+ countHighResists(art, data);
1281
+ countAbilities(reg, art, data);
1282
+ }
1283
+ }
1284
+
1285
+ /* ------------------------------------------------------------------ */
1286
+ /* Frequency rescaling (obj-randart.c L1124-L1265) */
1287
+ /* ------------------------------------------------------------------ */
1288
+
1289
+ /** Rescale one index group by total / denom (integer division, in place). */
1290
+ function rescaleGroup(
1291
+ data: ArtifactSetData,
1292
+ group: readonly number[],
1293
+ denom: number,
1294
+ ): void {
1295
+ for (const idx of group) {
1296
+ data.artProbs[idx] = Math.trunc((data.artProbs[idx]! * data.total) / denom);
1297
+ }
1298
+ }
1299
+
1300
+ /**
1301
+ * rescale_freqs (obj-randart.c L1124): rescale item-dependent ability
1302
+ * frequencies as though the whole set were made of that item type, so
1303
+ * dependent and independent abilities become comparable.
1304
+ */
1305
+ export function rescaleFreqs(data: ArtifactSetData): void {
1306
+ /* Bow-only abilities. */
1307
+ rescaleGroup(data, artIdxBow, data.bowTotal);
1308
+
1309
+ /* All weapon abilities. */
1310
+ rescaleGroup(data, artIdxWeapon, data.bowTotal + data.meleeTotal);
1311
+
1312
+ /* Corresponding non-weapon abilities. */
1313
+ const nonweaponDenom = data.total - data.meleeTotal - data.bowTotal;
1314
+ rescaleGroup(data, artIdxNonweapon, nonweaponDenom);
1315
+
1316
+ /* All melee weapon abilities. */
1317
+ rescaleGroup(data, artIdxMelee, data.meleeTotal);
1318
+
1319
+ /* All general armor abilities. */
1320
+ const allArmorDenom =
1321
+ data.armorTotal +
1322
+ data.bootTotal +
1323
+ data.shieldTotal +
1324
+ data.headgearTotal +
1325
+ data.cloakTotal +
1326
+ data.gloveTotal;
1327
+ rescaleGroup(data, artIdxAllarmor, allArmorDenom);
1328
+
1329
+ /* Boots. */
1330
+ rescaleGroup(data, artIdxBoot, data.bootTotal);
1331
+ /* Gloves. */
1332
+ rescaleGroup(data, artIdxGlove, data.gloveTotal);
1333
+ /* Headgear. */
1334
+ rescaleGroup(data, artIdxHeadgear, data.headgearTotal);
1335
+ /* Shields. */
1336
+ rescaleGroup(data, artIdxShield, data.shieldTotal);
1337
+ /* Cloaks. */
1338
+ rescaleGroup(data, artIdxCloak, data.cloakTotal);
1339
+ /* Body armor. */
1340
+ rescaleGroup(data, artIdxArmor, data.armorTotal);
1341
+ }
1342
+
1343
+ /**
1344
+ * adjust_freqs (obj-randart.c L1214): enforce minimum frequencies for
1345
+ * abilities that might be missing from the standard set, and halve the
1346
+ * aggravation frequencies (which are counted twice).
1347
+ */
1348
+ export function adjustFreqs(data: ArtifactSetData): void {
1349
+ const atLeast = (idx: number, min: number): void => {
1350
+ if (data.artProbs[idx]! < min) data.artProbs[idx] = min;
1351
+ };
1352
+
1353
+ atLeast(ART_IDX.GEN_RFEAR, 5);
1354
+ atLeast(ART_IDX.MELEE_DICE_SUPER, 5);
1355
+ atLeast(ART_IDX.BOW_SHOTS_SUPER, 5);
1356
+ atLeast(ART_IDX.BOW_MIGHT_SUPER, 5);
1357
+ atLeast(ART_IDX.MELEE_BLOWS_SUPER, 5);
1358
+ atLeast(ART_IDX.GEN_SPEED_SUPER, 5);
1359
+ atLeast(ART_IDX.GEN_AC, 5);
1360
+ atLeast(ART_IDX.GEN_TUNN, 5);
1361
+ atLeast(ART_IDX.NONWEAPON_BRAND, 2);
1362
+ atLeast(ART_IDX.NONWEAPON_SLAY, 1);
1363
+ atLeast(ART_IDX.BOW_BRAND, 2);
1364
+ atLeast(ART_IDX.BOW_SLAY, 2);
1365
+ atLeast(ART_IDX.NONWEAPON_BLOWS, 1);
1366
+ atLeast(ART_IDX.NONWEAPON_SHOTS, 1);
1367
+ atLeast(ART_IDX.GEN_AC_SUPER, 5);
1368
+ atLeast(ART_IDX.MELEE_AC, 5);
1369
+ atLeast(ART_IDX.GEN_PSTUN, 3);
1370
+
1371
+ /* Cut aggravation frequencies in half since they're used twice. */
1372
+ data.artProbs[ART_IDX.NONWEAPON_AGGR] = Math.trunc(
1373
+ data.artProbs[ART_IDX.NONWEAPON_AGGR]! / 2,
1374
+ );
1375
+ data.artProbs[ART_IDX.WEAPON_AGGR] = Math.trunc(
1376
+ data.artProbs[ART_IDX.WEAPON_AGGR]! / 2,
1377
+ );
1378
+ }
1379
+
1380
+ /**
1381
+ * parse_frequencies (obj-randart.c L1273): parse the artifacts and write
1382
+ * frequencies of their abilities and base object kinds, building the dynamic
1383
+ * generation probabilities.
1384
+ */
1385
+ export function parseFrequencies(
1386
+ reg: ObjRegistry,
1387
+ arts: readonly (Artifact | null)[],
1388
+ data: ArtifactSetData,
1389
+ ): void {
1390
+ randartLog("\n****** BEGINNING GENERATION OF FREQUENCIES\n\n");
1391
+
1392
+ /* Zero the frequencies for artifact attributes. */
1393
+ for (let i = 0; i < ART_IDX_TOTAL; i++) data.artProbs[i] = 0;
1394
+
1395
+ collectArtifactCounts(reg, arts, data);
1396
+
1397
+ /* Big hack, reduce frequencies of sharp weapons. */
1398
+ for (let i = 0; i < TV_MAX; i++) {
1399
+ if (i === TV.SWORD || i === TV.POLEARM) {
1400
+ data.tvProbs[i] = Math.trunc((data.tvProbs[i]! * 2) / 3);
1401
+ }
1402
+ }
1403
+
1404
+ /* "Print out some of the abilities, to make sure that everything's fine"
1405
+ * (obj-randart.c:1295-1301). Both loops run to their FULL extent, before any
1406
+ * rescaling - the pre- and post-rescale dumps are what make the log usable
1407
+ * for comparing two runs. */
1408
+ for (let i = 0; i < ART_IDX_TOTAL; i++) {
1409
+ randartLogf(() => `Frequency of ability ${i}: ${data.artProbs[i]}\n`);
1410
+ }
1411
+ for (let i = 0; i < TV_MAX; i++) {
1412
+ randartLogf(
1413
+ () => `Frequency of ${tvalFindName(i)}: ${data.tvProbs[i]}\n`,
1414
+ );
1415
+ }
1416
+
1417
+ /* Rescale frequencies. */
1418
+ rescaleFreqs(data);
1419
+
1420
+ /* Perform any additional rescaling and adjustment. */
1421
+ adjustFreqs(data);
1422
+
1423
+ /* "Log the final frequencies to check that everything's correct" (:1309). */
1424
+ for (let i = 0; i < ART_IDX_TOTAL; i++) {
1425
+ randartLogf(
1426
+ () => `Rescaled frequency of ability ${i}: ${data.artProbs[i]}\n`,
1427
+ );
1428
+ }
1429
+
1430
+ /* Build a cumulative frequency table for tvals. */
1431
+ for (let i = 0; i < TV_MAX; i++) {
1432
+ for (let j = i; j < TV_MAX; j++) {
1433
+ data.tvFreq[j] = (data.tvFreq[j] ?? 0) + (data.tvProbs[i] ?? 0);
1434
+ }
1435
+ }
1436
+
1437
+ /* "Print out the frequency table, for verification" (:1319). */
1438
+ for (let i = 0; i < TV_MAX; i++) {
1439
+ randartLogf(
1440
+ () => `Cumulative frequency of ${tvalFindName(i)} is: ${data.tvFreq[i]}\n`,
1441
+ );
1442
+ }
1443
+ }
1444
+
1445
+ /* ------------------------------------------------------------------ */
1446
+ /* Generation of a random artifact (header) */
1447
+ /* ------------------------------------------------------------------ */
1448
+
1449
+ /**
1450
+ * get_base_item_tval (obj-randart.c L1332): pick a random base item tval from
1451
+ * the original artifact tval frequencies.
1452
+ */
1453
+ export function getBaseItemTval(data: ArtifactSetData, rng: Rng): number {
1454
+ let tval = 0;
1455
+ const r = rng.randint1(data.tvFreq[TV_MAX - 1]!);
1456
+ while (r > (data.tvFreq[tval] ?? 0)) {
1457
+ tval++;
1458
+ }
1459
+ return tval;
1460
+ }
1461
+
1462
+ /* ------------------------------------------------------------------ */
1463
+ /* artifact_set_data allocation (obj-randart.c L2993) */
1464
+ /* ------------------------------------------------------------------ */
1465
+
1466
+ /** INHIBIT_POWER: the power ceiling above which arts are treated as uber. */
1467
+ const INHIBIT_POWER = 20000;
1468
+
1469
+ /**
1470
+ * artifact_set_data_new (obj-randart.c L2993): allocate and initialize a new
1471
+ * artifact set data structure. Power/level/prob arrays are sized by the number
1472
+ * of artifact slots (a_max, i.e. reg.artifacts.length); the tval arrays by
1473
+ * TV_MAX and the ability array by ART_IDX_TOTAL.
1474
+ */
1475
+ export function artifactSetDataNew(reg: ObjRegistry): ArtifactSetData {
1476
+ const aMax = reg.artifacts.length;
1477
+ const zeros = (n: number): number[] => new Array<number>(n).fill(0);
1478
+
1479
+ return {
1480
+ /* Mean start and increment values for to_hit, to_dam and AC. */
1481
+ hitIncrement: 4,
1482
+ damIncrement: 4,
1483
+ hitStartval: 10,
1484
+ damStartval: 10,
1485
+ acStartval: 15,
1486
+ acIncrement: 5,
1487
+
1488
+ artProbs: zeros(ART_IDX_TOTAL),
1489
+ tvProbs: zeros(TV_MAX),
1490
+ tvNum: zeros(TV_MAX),
1491
+ bowTotal: 0,
1492
+ meleeTotal: 0,
1493
+ bootTotal: 0,
1494
+ gloveTotal: 0,
1495
+ headgearTotal: 0,
1496
+ shieldTotal: 0,
1497
+ cloakTotal: 0,
1498
+ armorTotal: 0,
1499
+ otherTotal: 0,
1500
+ total: 0,
1501
+ negPowerTotal: 0,
1502
+
1503
+ tvFreq: zeros(TV_MAX),
1504
+
1505
+ basePower: zeros(aMax),
1506
+ maxPower: 0,
1507
+ minPower: 0,
1508
+ avgPower: 0,
1509
+ varPower: 0,
1510
+ avgTvPower: zeros(TV_MAX),
1511
+ minTvPower: zeros(TV_MAX),
1512
+ maxTvPower: zeros(TV_MAX),
1513
+
1514
+ baseItemLevel: zeros(aMax),
1515
+ baseItemProb: zeros(aMax),
1516
+ baseArtAlloc: zeros(aMax),
1517
+ };
1518
+ }
1519
+
1520
+ /**
1521
+ * Allocate an ArtifactSetData and run the measurement pipeline over `arts`:
1522
+ * store_base_power, then parse_frequencies (which runs collect_artifact_data,
1523
+ * rescale_freqs and adjust_freqs). Returns the populated data. `rng` is drawn
1524
+ * from, not merely threaded: store_base_power powers every artifact, and
1525
+ * make_fake_artifact rolls a timeout per cursed slot (see
1526
+ * makeFakeArtifactPower). Over the standard set that is a handful of draws;
1527
+ * over a generated set it is one per curse the design loop added.
1528
+ *
1529
+ * WHICH SET is a parameter because do_randart measures TWICE (obj-randart.c
1530
+ * L3175-L3186): once over the standard artifacts, and once more over the
1531
+ * finished ones. Upstream can leave it implicit because create_artifact_set
1532
+ * overwrites the a_info global in place, so "the artifacts" means a different
1533
+ * set the second time round without anything being passed. This port generates
1534
+ * into a fresh array and leaves reg.artifacts alone, so the set being measured
1535
+ * has to be handed in.
1536
+ */
1537
+ export function collectArtifactData(
1538
+ reg: ObjRegistry,
1539
+ constants: Constants,
1540
+ arts: readonly (Artifact | null)[],
1541
+ rng: Rng,
1542
+ ): ArtifactSetData {
1543
+ const data = artifactSetDataNew(reg);
1544
+ storeBasePower(reg, constants, arts, data, rng);
1545
+ parseFrequencies(reg, arts, data);
1546
+ return data;
1547
+ }