@datamoc/mw_games 0.7.6 → 0.7.8

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 (367) hide show
  1. package/CHANGELOG.md +359 -0
  2. package/README.md +7 -7
  3. package/dist/actors/Inventory.d.ts +7 -1
  4. package/dist/actors/Inventory.d.ts.map +1 -1
  5. package/dist/actors/Inventory.js.map +1 -1
  6. package/dist/actors/Shop.d.ts.map +1 -1
  7. package/dist/actors/Shop.js +4 -1
  8. package/dist/actors/Shop.js.map +1 -1
  9. package/dist/actors/craft.d.ts +35 -14
  10. package/dist/actors/craft.d.ts.map +1 -1
  11. package/dist/actors/craft.js +56 -24
  12. package/dist/actors/craft.js.map +1 -1
  13. package/dist/actors/index.d.ts +1 -1
  14. package/dist/actors/index.d.ts.map +1 -1
  15. package/dist/ai/Heuristics.d.ts +240 -0
  16. package/dist/ai/Heuristics.d.ts.map +1 -0
  17. package/dist/ai/Heuristics.js +298 -0
  18. package/dist/ai/Heuristics.js.map +1 -0
  19. package/dist/ai/index.d.ts +4 -0
  20. package/dist/ai/index.d.ts.map +1 -1
  21. package/dist/ai/index.js +2 -0
  22. package/dist/ai/index.js.map +1 -1
  23. package/dist/ai/lua.d.ts +21 -0
  24. package/dist/ai/lua.d.ts.map +1 -1
  25. package/dist/ai/lua.js +21 -0
  26. package/dist/ai/lua.js.map +1 -1
  27. package/dist/ai/score.d.ts +162 -0
  28. package/dist/ai/score.d.ts.map +1 -0
  29. package/dist/ai/score.js +147 -0
  30. package/dist/ai/score.js.map +1 -0
  31. package/dist/assets/loader.d.ts +18 -4
  32. package/dist/assets/loader.d.ts.map +1 -1
  33. package/dist/assets/loader.js +40 -11
  34. package/dist/assets/loader.js.map +1 -1
  35. package/dist/audio/Positional.d.ts +125 -0
  36. package/dist/audio/Positional.d.ts.map +1 -0
  37. package/dist/audio/Positional.js +135 -0
  38. package/dist/audio/Positional.js.map +1 -0
  39. package/dist/audio/Sound.d.ts +7 -1
  40. package/dist/audio/Sound.d.ts.map +1 -1
  41. package/dist/audio/Sound.js +8 -2
  42. package/dist/audio/Sound.js.map +1 -1
  43. package/dist/audio/index.d.ts +2 -0
  44. package/dist/audio/index.d.ts.map +1 -1
  45. package/dist/audio/index.js +1 -0
  46. package/dist/audio/index.js.map +1 -1
  47. package/dist/battle/AttackDialog.d.ts +75 -0
  48. package/dist/battle/AttackDialog.d.ts.map +1 -0
  49. package/dist/battle/AttackDialog.js +115 -0
  50. package/dist/battle/AttackDialog.js.map +1 -0
  51. package/dist/battle/AttackPreview.d.ts +70 -0
  52. package/dist/battle/AttackPreview.d.ts.map +1 -0
  53. package/dist/battle/AttackPreview.js +78 -0
  54. package/dist/battle/AttackPreview.js.map +1 -0
  55. package/dist/battle/BattleStats.d.ts +45 -0
  56. package/dist/battle/BattleStats.d.ts.map +1 -0
  57. package/dist/battle/BattleStats.js +63 -0
  58. package/dist/battle/BattleStats.js.map +1 -0
  59. package/dist/battle/UnitSelector.d.ts +72 -0
  60. package/dist/battle/UnitSelector.d.ts.map +1 -0
  61. package/dist/battle/UnitSelector.js +117 -0
  62. package/dist/battle/UnitSelector.js.map +1 -0
  63. package/dist/battle/Whiteboard.d.ts +50 -0
  64. package/dist/battle/Whiteboard.d.ts.map +1 -0
  65. package/dist/battle/Whiteboard.js +94 -0
  66. package/dist/battle/Whiteboard.js.map +1 -0
  67. package/dist/battle/index.d.ts +10 -0
  68. package/dist/battle/index.d.ts.map +1 -1
  69. package/dist/battle/index.js +5 -0
  70. package/dist/battle/index.js.map +1 -1
  71. package/dist/board/Classics.d.ts.map +1 -1
  72. package/dist/board/Classics.js +51 -39
  73. package/dist/board/Classics.js.map +1 -1
  74. package/dist/board/FogOfWar.d.ts +32 -2
  75. package/dist/board/FogOfWar.d.ts.map +1 -1
  76. package/dist/board/FogOfWar.js +70 -6
  77. package/dist/board/FogOfWar.js.map +1 -1
  78. package/dist/board/HexSkirmish.d.ts.map +1 -1
  79. package/dist/board/HexSkirmish.js +69 -20
  80. package/dist/board/HexSkirmish.js.map +1 -1
  81. package/dist/board/Tactics.d.ts +7 -0
  82. package/dist/board/Tactics.d.ts.map +1 -1
  83. package/dist/board/Tactics.js +2 -2
  84. package/dist/board/Tactics.js.map +1 -1
  85. package/dist/core/Achievements.d.ts +40 -7
  86. package/dist/core/Achievements.d.ts.map +1 -1
  87. package/dist/core/Achievements.js +53 -7
  88. package/dist/core/Achievements.js.map +1 -1
  89. package/dist/core/Hex.d.ts +26 -6
  90. package/dist/core/Hex.d.ts.map +1 -1
  91. package/dist/core/Hex.js +40 -13
  92. package/dist/core/Hex.js.map +1 -1
  93. package/dist/core/Input.d.ts +36 -0
  94. package/dist/core/Input.d.ts.map +1 -1
  95. package/dist/core/Input.js +63 -1
  96. package/dist/core/Input.js.map +1 -1
  97. package/dist/core/Random.d.ts +93 -0
  98. package/dist/core/Random.d.ts.map +1 -1
  99. package/dist/core/Random.js +177 -0
  100. package/dist/core/Random.js.map +1 -1
  101. package/dist/core/SyncGuard.d.ts +57 -0
  102. package/dist/core/SyncGuard.d.ts.map +1 -0
  103. package/dist/core/SyncGuard.js +116 -0
  104. package/dist/core/SyncGuard.js.map +1 -0
  105. package/dist/core/index.d.ts +3 -1
  106. package/dist/core/index.d.ts.map +1 -1
  107. package/dist/core/index.js +2 -1
  108. package/dist/core/index.js.map +1 -1
  109. package/dist/i18n/Po.d.ts +51 -0
  110. package/dist/i18n/Po.d.ts.map +1 -0
  111. package/dist/i18n/Po.js +176 -0
  112. package/dist/i18n/Po.js.map +1 -0
  113. package/dist/i18n/index.d.ts +2 -0
  114. package/dist/i18n/index.d.ts.map +1 -1
  115. package/dist/i18n/index.js +1 -0
  116. package/dist/i18n/index.js.map +1 -1
  117. package/dist/mw_games.global.js +104 -98
  118. package/dist/mwl/MapFile.d.ts +41 -0
  119. package/dist/mwl/MapFile.d.ts.map +1 -0
  120. package/dist/mwl/MapFile.js +43 -0
  121. package/dist/mwl/MapFile.js.map +1 -0
  122. package/dist/mwl/actors.d.ts +46 -2
  123. package/dist/mwl/actors.d.ts.map +1 -1
  124. package/dist/mwl/actors.js +46 -2
  125. package/dist/mwl/actors.js.map +1 -1
  126. package/dist/mwl/campaign.d.ts +51 -0
  127. package/dist/mwl/campaign.d.ts.map +1 -0
  128. package/dist/mwl/campaign.js +58 -0
  129. package/dist/mwl/campaign.js.map +1 -0
  130. package/dist/mwl/carryover.d.ts +86 -0
  131. package/dist/mwl/carryover.d.ts.map +1 -0
  132. package/dist/mwl/carryover.js +70 -0
  133. package/dist/mwl/carryover.js.map +1 -0
  134. package/dist/mwl/catalog.d.ts +10 -1
  135. package/dist/mwl/catalog.d.ts.map +1 -1
  136. package/dist/mwl/catalog.js +10 -1
  137. package/dist/mwl/catalog.js.map +1 -1
  138. package/dist/mwl/compiler.d.ts +70 -3
  139. package/dist/mwl/compiler.d.ts.map +1 -1
  140. package/dist/mwl/compiler.js +70 -3
  141. package/dist/mwl/compiler.js.map +1 -1
  142. package/dist/mwl/conditions.d.ts +10 -1
  143. package/dist/mwl/conditions.d.ts.map +1 -1
  144. package/dist/mwl/conditions.js +10 -1
  145. package/dist/mwl/conditions.js.map +1 -1
  146. package/dist/mwl/content.d.ts +21 -0
  147. package/dist/mwl/content.d.ts.map +1 -1
  148. package/dist/mwl/content.js +19 -0
  149. package/dist/mwl/content.js.map +1 -1
  150. package/dist/mwl/expression.d.ts +21 -0
  151. package/dist/mwl/expression.d.ts.map +1 -1
  152. package/dist/mwl/expression.js +21 -0
  153. package/dist/mwl/expression.js.map +1 -1
  154. package/dist/mwl/fengari.d.ts +12 -1
  155. package/dist/mwl/fengari.d.ts.map +1 -1
  156. package/dist/mwl/fengari.js +12 -1
  157. package/dist/mwl/fengari.js.map +1 -1
  158. package/dist/mwl/grammar.d.ts +45 -1
  159. package/dist/mwl/grammar.d.ts.map +1 -1
  160. package/dist/mwl/grammar.js +45 -1
  161. package/dist/mwl/grammar.js.map +1 -1
  162. package/dist/mwl/hooks.d.ts +51 -6
  163. package/dist/mwl/hooks.d.ts.map +1 -1
  164. package/dist/mwl/hooks.js +45 -1
  165. package/dist/mwl/hooks.js.map +1 -1
  166. package/dist/mwl/index.d.ts +9 -3
  167. package/dist/mwl/index.d.ts.map +1 -1
  168. package/dist/mwl/index.js +5 -2
  169. package/dist/mwl/index.js.map +1 -1
  170. package/dist/mwl/persistence.d.ts +32 -0
  171. package/dist/mwl/persistence.d.ts.map +1 -1
  172. package/dist/mwl/persistence.js +32 -0
  173. package/dist/mwl/persistence.js.map +1 -1
  174. package/dist/mwl/readers.d.ts +24 -2
  175. package/dist/mwl/readers.d.ts.map +1 -1
  176. package/dist/mwl/readers.js +27 -4
  177. package/dist/mwl/readers.js.map +1 -1
  178. package/dist/mwl/report.d.ts +21 -2
  179. package/dist/mwl/report.d.ts.map +1 -1
  180. package/dist/mwl/report.js +21 -2
  181. package/dist/mwl/report.js.map +1 -1
  182. package/dist/mwl/runtime.d.ts +212 -7
  183. package/dist/mwl/runtime.d.ts.map +1 -1
  184. package/dist/mwl/runtime.js +682 -90
  185. package/dist/mwl/runtime.js.map +1 -1
  186. package/dist/mwl/schema.d.ts +57 -1
  187. package/dist/mwl/schema.d.ts.map +1 -1
  188. package/dist/mwl/schema.js +181 -21
  189. package/dist/mwl/schema.js.map +1 -1
  190. package/dist/mwl/scripts.d.ts +12 -1
  191. package/dist/mwl/scripts.d.ts.map +1 -1
  192. package/dist/mwl/scripts.js +12 -1
  193. package/dist/mwl/scripts.js.map +1 -1
  194. package/dist/mwl/utils.d.ts +2 -0
  195. package/dist/mwl/utils.d.ts.map +1 -1
  196. package/dist/mwl/utils.js +4 -1
  197. package/dist/mwl/utils.js.map +1 -1
  198. package/dist/roguelike/MultiTurnBeam.d.ts +50 -12
  199. package/dist/roguelike/MultiTurnBeam.d.ts.map +1 -1
  200. package/dist/roguelike/MultiTurnBeam.js +100 -53
  201. package/dist/roguelike/MultiTurnBeam.js.map +1 -1
  202. package/dist/roguelike/Targeting.d.ts +94 -0
  203. package/dist/roguelike/Targeting.d.ts.map +1 -1
  204. package/dist/roguelike/Targeting.js +123 -0
  205. package/dist/roguelike/Targeting.js.map +1 -1
  206. package/dist/roguelike/index.d.ts +3 -1
  207. package/dist/roguelike/index.d.ts.map +1 -1
  208. package/dist/roguelike/index.js +1 -0
  209. package/dist/roguelike/index.js.map +1 -1
  210. package/dist/rpg/GameState.d.ts +2 -2
  211. package/dist/rpg/GameState.js +2 -2
  212. package/dist/simulation/CampaignSave.d.ts +68 -0
  213. package/dist/simulation/CampaignSave.d.ts.map +1 -0
  214. package/dist/simulation/CampaignSave.js +54 -0
  215. package/dist/simulation/CampaignSave.js.map +1 -0
  216. package/dist/simulation/EventPresentation.d.ts +111 -0
  217. package/dist/simulation/EventPresentation.d.ts.map +1 -0
  218. package/dist/simulation/EventPresentation.js +120 -0
  219. package/dist/simulation/EventPresentation.js.map +1 -0
  220. package/dist/simulation/index.d.ts +4 -0
  221. package/dist/simulation/index.d.ts.map +1 -1
  222. package/dist/simulation/index.js +2 -0
  223. package/dist/simulation/index.js.map +1 -1
  224. package/dist/two-d/pixi-interop.d.ts +18 -6
  225. package/dist/two-d/pixi-interop.d.ts.map +1 -1
  226. package/dist/two-d/pixi-interop.js +18 -6
  227. package/dist/two-d/pixi-interop.js.map +1 -1
  228. package/dist/two-d/render/AnimatedSprite.d.ts +74 -7
  229. package/dist/two-d/render/AnimatedSprite.d.ts.map +1 -1
  230. package/dist/two-d/render/AnimatedSprite.js +88 -26
  231. package/dist/two-d/render/AnimatedSprite.js.map +1 -1
  232. package/dist/two-d/render/Camera.d.ts +64 -4
  233. package/dist/two-d/render/Camera.d.ts.map +1 -1
  234. package/dist/two-d/render/Camera.js +132 -11
  235. package/dist/two-d/render/Camera.js.map +1 -1
  236. package/dist/two-d/render/ColorTransformBatcher.d.ts +7 -1
  237. package/dist/two-d/render/ColorTransformBatcher.d.ts.map +1 -1
  238. package/dist/two-d/render/ColorTransformBatcher.js +10 -1
  239. package/dist/two-d/render/ColorTransformBatcher.js.map +1 -1
  240. package/dist/two-d/render/Halo.d.ts +58 -0
  241. package/dist/two-d/render/Halo.d.ts.map +1 -0
  242. package/dist/two-d/render/Halo.js +48 -0
  243. package/dist/two-d/render/Halo.js.map +1 -0
  244. package/dist/two-d/render/ImageModifiers.d.ts +174 -0
  245. package/dist/two-d/render/ImageModifiers.d.ts.map +1 -1
  246. package/dist/two-d/render/ImageModifiers.js +454 -1
  247. package/dist/two-d/render/ImageModifiers.js.map +1 -1
  248. package/dist/two-d/render/LightningArc.d.ts +57 -0
  249. package/dist/two-d/render/LightningArc.d.ts.map +1 -0
  250. package/dist/two-d/render/LightningArc.js +91 -0
  251. package/dist/two-d/render/LightningArc.js.map +1 -0
  252. package/dist/two-d/render/PaletteRemap.d.ts +135 -0
  253. package/dist/two-d/render/PaletteRemap.d.ts.map +1 -0
  254. package/dist/two-d/render/PaletteRemap.js +181 -0
  255. package/dist/two-d/render/PaletteRemap.js.map +1 -0
  256. package/dist/two-d/render/Particles.d.ts +26 -0
  257. package/dist/two-d/render/Particles.d.ts.map +1 -1
  258. package/dist/two-d/render/Particles.js +23 -2
  259. package/dist/two-d/render/Particles.js.map +1 -1
  260. package/dist/two-d/render/SpriteAttachment.d.ts +55 -0
  261. package/dist/two-d/render/SpriteAttachment.d.ts.map +1 -0
  262. package/dist/two-d/render/SpriteAttachment.js +60 -0
  263. package/dist/two-d/render/SpriteAttachment.js.map +1 -0
  264. package/dist/two-d/render/TerrainGraphics.d.ts +142 -0
  265. package/dist/two-d/render/TerrainGraphics.d.ts.map +1 -0
  266. package/dist/two-d/render/TerrainGraphics.js +161 -0
  267. package/dist/two-d/render/TerrainGraphics.js.map +1 -0
  268. package/dist/two-d/render/TerrainGraphicsLayer.d.ts +55 -0
  269. package/dist/two-d/render/TerrainGraphicsLayer.d.ts.map +1 -0
  270. package/dist/two-d/render/TerrainGraphicsLayer.js +54 -0
  271. package/dist/two-d/render/TerrainGraphicsLayer.js.map +1 -0
  272. package/dist/two-d/render/TintedSprite.js +2 -2
  273. package/dist/two-d/render/TintedSprite.js.map +1 -1
  274. package/dist/two-d/render/index.d.ts +15 -3
  275. package/dist/two-d/render/index.d.ts.map +1 -1
  276. package/dist/two-d/render/index.js +7 -1
  277. package/dist/two-d/render/index.js.map +1 -1
  278. package/dist/two-d/stage/StoryScreen.d.ts +110 -0
  279. package/dist/two-d/stage/StoryScreen.d.ts.map +1 -0
  280. package/dist/two-d/stage/StoryScreen.js +178 -0
  281. package/dist/two-d/stage/StoryScreen.js.map +1 -0
  282. package/dist/two-d/stage/index.d.ts +2 -0
  283. package/dist/two-d/stage/index.d.ts.map +1 -1
  284. package/dist/two-d/stage/index.js +1 -0
  285. package/dist/two-d/stage/index.js.map +1 -1
  286. package/dist/two-d/ui/Checkbox.d.ts +44 -0
  287. package/dist/two-d/ui/Checkbox.d.ts.map +1 -0
  288. package/dist/two-d/ui/Checkbox.js +89 -0
  289. package/dist/two-d/ui/Checkbox.js.map +1 -0
  290. package/dist/two-d/ui/DataTable.d.ts +79 -0
  291. package/dist/two-d/ui/DataTable.d.ts.map +1 -0
  292. package/dist/two-d/ui/DataTable.js +164 -0
  293. package/dist/two-d/ui/DataTable.js.map +1 -0
  294. package/dist/two-d/ui/Dropdown.d.ts +72 -0
  295. package/dist/two-d/ui/Dropdown.d.ts.map +1 -0
  296. package/dist/two-d/ui/Dropdown.js +126 -0
  297. package/dist/two-d/ui/Dropdown.js.map +1 -0
  298. package/dist/two-d/ui/FloatingText.d.ts +43 -4
  299. package/dist/two-d/ui/FloatingText.d.ts.map +1 -1
  300. package/dist/two-d/ui/FloatingText.js +52 -5
  301. package/dist/two-d/ui/FloatingText.js.map +1 -1
  302. package/dist/two-d/ui/FloatingTextStack.d.ts +86 -13
  303. package/dist/two-d/ui/FloatingTextStack.d.ts.map +1 -1
  304. package/dist/two-d/ui/FloatingTextStack.js +89 -18
  305. package/dist/two-d/ui/FloatingTextStack.js.map +1 -1
  306. package/dist/two-d/ui/Layout.d.ts +105 -0
  307. package/dist/two-d/ui/Layout.d.ts.map +1 -0
  308. package/dist/two-d/ui/Layout.js +174 -0
  309. package/dist/two-d/ui/Layout.js.map +1 -0
  310. package/dist/two-d/ui/MarkupText.d.ts +53 -0
  311. package/dist/two-d/ui/MarkupText.d.ts.map +1 -0
  312. package/dist/two-d/ui/MarkupText.js +122 -0
  313. package/dist/two-d/ui/MarkupText.js.map +1 -0
  314. package/dist/two-d/ui/ScrollBox.d.ts +67 -0
  315. package/dist/two-d/ui/ScrollBox.d.ts.map +1 -0
  316. package/dist/two-d/ui/ScrollBox.js +127 -0
  317. package/dist/two-d/ui/ScrollBox.js.map +1 -0
  318. package/dist/two-d/ui/Skins.d.ts +59 -0
  319. package/dist/two-d/ui/Skins.d.ts.map +1 -0
  320. package/dist/two-d/ui/Skins.js +77 -0
  321. package/dist/two-d/ui/Skins.js.map +1 -0
  322. package/dist/two-d/ui/Slider.d.ts +91 -0
  323. package/dist/two-d/ui/Slider.d.ts.map +1 -0
  324. package/dist/two-d/ui/Slider.js +170 -0
  325. package/dist/two-d/ui/Slider.js.map +1 -0
  326. package/dist/two-d/ui/Spinner.d.ts +67 -0
  327. package/dist/two-d/ui/Spinner.d.ts.map +1 -0
  328. package/dist/two-d/ui/Spinner.js +137 -0
  329. package/dist/two-d/ui/Spinner.js.map +1 -0
  330. package/dist/two-d/ui/TabbedList.d.ts +118 -0
  331. package/dist/two-d/ui/TabbedList.d.ts.map +1 -0
  332. package/dist/two-d/ui/TabbedList.js +221 -0
  333. package/dist/two-d/ui/TabbedList.js.map +1 -0
  334. package/dist/two-d/ui/TextModel.d.ts +65 -0
  335. package/dist/two-d/ui/TextModel.d.ts.map +1 -0
  336. package/dist/two-d/ui/TextModel.js +126 -0
  337. package/dist/two-d/ui/TextModel.js.map +1 -0
  338. package/dist/two-d/ui/TreeView.d.ts +68 -0
  339. package/dist/two-d/ui/TreeView.d.ts.map +1 -0
  340. package/dist/two-d/ui/TreeView.js +133 -0
  341. package/dist/two-d/ui/TreeView.js.map +1 -0
  342. package/dist/two-d/ui/Window.d.ts +13 -0
  343. package/dist/two-d/ui/Window.d.ts.map +1 -1
  344. package/dist/two-d/ui/Window.js +23 -0
  345. package/dist/two-d/ui/Window.js.map +1 -1
  346. package/dist/two-d/ui/WindowStack.d.ts.map +1 -1
  347. package/dist/two-d/ui/WindowStack.js +2 -0
  348. package/dist/two-d/ui/WindowStack.js.map +1 -1
  349. package/dist/two-d/ui/index.d.ts +29 -3
  350. package/dist/two-d/ui/index.d.ts.map +1 -1
  351. package/dist/two-d/ui/index.js +15 -2
  352. package/dist/two-d/ui/index.js.map +1 -1
  353. package/dist/two-d/ui/markup.d.ts +181 -0
  354. package/dist/two-d/ui/markup.d.ts.map +1 -0
  355. package/dist/two-d/ui/markup.js +370 -0
  356. package/dist/two-d/ui/markup.js.map +1 -0
  357. package/dist/version.d.ts +1 -1
  358. package/dist/version.js +1 -1
  359. package/dist/world/SideTurns.d.ts +115 -0
  360. package/dist/world/SideTurns.d.ts.map +1 -0
  361. package/dist/world/SideTurns.js +138 -0
  362. package/dist/world/SideTurns.js.map +1 -0
  363. package/dist/world/index.d.ts +2 -0
  364. package/dist/world/index.d.ts.map +1 -1
  365. package/dist/world/index.js +1 -0
  366. package/dist/world/index.js.map +1 -1
  367. package/package.json +2 -1
@@ -0,0 +1,135 @@
1
+ /**
2
+ * The gain a listener at `distance` hears a source at, clamped to [0, 1]: 1 at or inside
3
+ * `refDistance`, 0 at or beyond `maxDistance`, an inverse-distance curve between them.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { audioGain } from '@datamoc/mw_games/audio';
8
+ *
9
+ * audioGain(0, { refDistance: 2 }); // 1 - inside the full-volume radius
10
+ * audioGain(2, { refDistance: 2 }); // 1 - the reference distance itself
11
+ * audioGain(4, { refDistance: 2 }); // 0.5 - one doubling away
12
+ * audioGain(4, { refDistance: 2, maxDistance: 4 }); // 0 - at the edge of hearing
13
+ * ```
14
+ */
15
+ export function audioGain(distance, falloff = {}) {
16
+ const refDistance = falloff.refDistance ?? 1;
17
+ const maxDistance = falloff.maxDistance ?? Infinity;
18
+ const rolloff = falloff.rolloff ?? 1;
19
+ if (!(distance > refDistance))
20
+ return 1;
21
+ if (!(distance < maxDistance))
22
+ return 0;
23
+ const gain = Math.pow(refDistance / distance, rolloff);
24
+ return Math.max(0, Math.min(1, gain));
25
+ }
26
+ /**
27
+ * Stereo pan in [-1, 1] for a source: -1 is hard left of the listener, 1 hard right, 0
28
+ * directly ahead or behind. The listener's `facing` is its heading in radians (0 faces +x,
29
+ * increasing clockwise on a screen where y grows downward), so turning the listener turns
30
+ * what counts as "right" with it - a sound to the world's east is centred when the listener
31
+ * faces east and hard right when the listener faces north.
32
+ *
33
+ * A game with a stereo backend applies this itself; the framework has no panner node of its
34
+ * own, so it reports the number rather than pretending to pan.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * import { audioPan } from '@datamoc/mw_games/audio';
39
+ *
40
+ * const listener = { x: 0, y: 0, facing: 0 }; // facing east
41
+ * audioPan(listener, { x: 10, y: 0 }); // 0 - straight ahead
42
+ * audioPan(listener, { x: 0, y: 10 }); // 1 - to the listener's right
43
+ * audioPan(listener, { x: 0, y: -10 }); // -1 - to its left
44
+ * ```
45
+ */
46
+ export function audioPan(listener, source) {
47
+ const dx = source.x - listener.x;
48
+ const dy = source.y - listener.y;
49
+ const distance = Math.hypot(dx, dy);
50
+ if (distance === 0)
51
+ return 0;
52
+ //the listener's right-hand direction at heading `facing`, in screen coordinates
53
+ const rightX = Math.sin(listener.facing);
54
+ const rightY = Math.cos(listener.facing);
55
+ const pan = (dx * rightX + dy * rightY) / distance;
56
+ return Math.max(-1, Math.min(1, pan));
57
+ }
58
+ /**
59
+ * Where sounds are heard from: a game moves one listener to its camera or its party leader
60
+ * each frame, and every `SoundSource` is mixed against it.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * import { AudioListener } from '@datamoc/mw_games/audio';
65
+ *
66
+ * const listener = new AudioListener({ x: 40, y: 12 });
67
+ * listener.moveTo(41, 12); // following the camera
68
+ * listener.face(Math.PI / 2); // now facing the world's south
69
+ * ```
70
+ */
71
+ export class AudioListener {
72
+ constructor(options = {}) {
73
+ this.x = options.x ?? 0;
74
+ this.y = options.y ?? 0;
75
+ this.facing = options.facing ?? 0;
76
+ }
77
+ moveTo(x, y) {
78
+ this.x = x;
79
+ this.y = y;
80
+ }
81
+ face(radians) {
82
+ this.facing = radians;
83
+ }
84
+ }
85
+ /**
86
+ * One positional one-shot: a `Sound` attached to a point in the world, heard from a
87
+ * listener with the distance gain `audioGain` computes. `playFor` applies that gain through
88
+ * `Sound.play(gain)`, and stays silent past `maxDistance` rather than playing at volume 0.
89
+ *
90
+ * Long-running position (a waterfall, an engine) is the game's own loop calling `playFor` on
91
+ * a cadence it chooses; this is the placement and the mix, not a scheduler.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * import { AudioListener, Sound, SoundSource } from '@datamoc/mw_games/audio';
96
+ *
97
+ * const listener = new AudioListener({ x: 0, y: 0 });
98
+ * const arrow = new Sound('sounds/arrow.mp3');
99
+ * const source = new SoundSource(arrow, { x: 6, y: 8, refDistance: 4, maxDistance: 20 });
100
+ *
101
+ * const gain = source.gainTo(listener); // 0.4 at distance 10
102
+ * source.playFor(listener); // plays at that gain, or not at all when silent
103
+ * ```
104
+ */
105
+ export class SoundSource {
106
+ constructor(sound, options = {}) {
107
+ this.sound = sound;
108
+ this.x = options.x ?? 0;
109
+ this.y = options.y ?? 0;
110
+ this.falloff = {
111
+ refDistance: options.refDistance,
112
+ maxDistance: options.maxDistance,
113
+ rolloff: options.rolloff,
114
+ };
115
+ }
116
+ moveTo(x, y) {
117
+ this.x = x;
118
+ this.y = y;
119
+ }
120
+ gainTo(listener) {
121
+ return audioGain(Math.hypot(this.x - listener.x, this.y - listener.y), this.falloff);
122
+ }
123
+ panTo(listener) {
124
+ return audioPan(listener, this);
125
+ }
126
+ /** Plays the source at the gain the listener hears it; returns the gain actually used. */
127
+ playFor(listener) {
128
+ const gain = this.gainTo(listener);
129
+ if (gain <= 0)
130
+ return 0;
131
+ this.sound.play(gain);
132
+ return gain;
133
+ }
134
+ }
135
+ //# sourceMappingURL=Positional.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Positional.js","sourceRoot":"","sources":["../../src/audio/Positional.ts"],"names":[],"mappings":"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"}
@@ -28,7 +28,13 @@ export declare class Sound {
28
28
  private caption?;
29
29
  volume: number;
30
30
  constructor(path: string, options?: SoundOptions);
31
- play(): void;
31
+ /**
32
+ * Plays one instance from the pool.
33
+ *
34
+ * @param gain multiplied into the sound's own volume, for a distance-attenuated one-shot
35
+ * (`Positional.SoundSource.playFor`); 1 leaves it at `volume`.
36
+ */
37
+ play(gain?: number): void;
32
38
  stopAll(): void;
33
39
  }
34
40
  //# sourceMappingURL=Sound.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Sound.d.ts","sourceRoot":"","sources":["../../src/audio/Sound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG3D,MAAM,WAAW,YAAY;IAC5B,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC;IAEpC,2FAA2F;IAC3F,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,KAAK;IACjB,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,MAAM,EAAE,MAAM,CAAC;IAEf,YAAY,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,EAMnD;IAED,IAAI,IAAI,IAAI,CAYX;IAED,OAAO,IAAI,IAAI,CAKd;CACD"}
1
+ {"version":3,"file":"Sound.d.ts","sourceRoot":"","sources":["../../src/audio/Sound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG3D,MAAM,WAAW,YAAY;IAC5B,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC;IAEpC,2FAA2F;IAC3F,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,KAAK;IACjB,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,MAAM,EAAE,MAAM,CAAC;IAEf,YAAY,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,EAMnD;IAED;;;;;OAKG;IACH,IAAI,CAAC,IAAI,SAAI,GAAG,IAAI,CAYnB;IAED,OAAO,IAAI,IAAI,CAKd;CACD"}
@@ -23,10 +23,16 @@ export class Sound {
23
23
  this.caption = options.caption;
24
24
  this.pool = Array.from({ length: size }, () => create(path));
25
25
  }
26
- play() {
26
+ /**
27
+ * Plays one instance from the pool.
28
+ *
29
+ * @param gain multiplied into the sound's own volume, for a distance-attenuated one-shot
30
+ * (`Positional.SoundSource.playFor`); 1 leaves it at `volume`.
31
+ */
32
+ play(gain = 1) {
27
33
  const audio = this.pool[this.next];
28
34
  this.next = (this.next + 1) % this.pool.length;
29
- audio.volume = this.volume;
35
+ audio.volume = this.volume * gain;
30
36
  audio.currentTime = 0;
31
37
  // play() can return a Promise that rejects if this reuse interrupts its own prior,
32
38
  // still-loading play() (a real browser, not the fakes tests supply) - swallow it the
@@ -1 +1 @@
1
- {"version":3,"file":"Sound.js","sourceRoot":"","sources":["../../src/audio/Sound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAiB,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAc1C;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,KAAK;IAMjB,YAAY,IAAY,EAAE,OAAO,GAAiB,EAAE;QAJ5C,SAAI,GAAG,CAAC,CAAC;QAKhB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,WAAW,CAAC;QAC7C,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;QAClC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED,IAAI;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAE/C,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC;QACtB,mFAAmF;QACnF,qFAAqF;QACrF,wFAAwF;QACxF,KAAK,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAEnD,IAAI,IAAI,CAAC,OAAO;YAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO;QACN,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAC/B,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC;QACvB,CAAC;IACF,CAAC;CACD"}
1
+ {"version":3,"file":"Sound.js","sourceRoot":"","sources":["../../src/audio/Sound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAiB,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAc1C;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,KAAK;IAMjB,YAAY,IAAY,EAAE,OAAO,GAAiB,EAAE;QAJ5C,SAAI,GAAG,CAAC,CAAC;QAKhB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,WAAW,CAAC;QAC7C,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;QAClC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED;;;;;OAKG;IACH,IAAI,CAAC,IAAI,GAAG,CAAC;QACZ,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAE/C,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QAClC,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC;QACtB,mFAAmF;QACnF,qFAAqF;QACrF,wFAAwF;QACxF,KAAK,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAEnD,IAAI,IAAI,CAAC,OAAO;YAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO;QACN,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAC/B,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC;QACvB,CAAC;IACF,CAAC;CACD"}
@@ -12,4 +12,6 @@ export { synthesizeTone, playTone } from './Synth.ts';
12
12
  export type { Waveform, ToneOptions } from './Synth.ts';
13
13
  export { parseMidi, scheduleMidi, noteToFrequency, MidiPlayer } from './Midi.ts';
14
14
  export type { MidiFile, MidiEvent, MidiNoteEvent, MidiTempoEvent, ScheduledNote, MidiPlayerOptions } from './Midi.ts';
15
+ export { AudioListener, SoundSource, audioGain, audioPan } from './Positional.ts';
16
+ export type { AudioPoint, AudioFalloff, SoundSourceOptions } from './Positional.ts';
15
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/audio/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtD,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACjF,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/audio/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtD,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACjF,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACtH,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAClF,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -5,4 +5,5 @@ export { createAudio } from './Playable.js';
5
5
  export { Orchestrator } from './Orchestrator.js';
6
6
  export { synthesizeTone, playTone } from './Synth.js';
7
7
  export { parseMidi, scheduleMidi, noteToFrequency, MidiPlayer } from './Midi.js';
8
+ export { AudioListener, SoundSource, audioGain, audioPan } from './Positional.js';
8
9
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/audio/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/audio/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEjF,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,75 @@
1
+ import { Signal } from '../core/Signal.ts';
2
+ import { AttackPreview, type AttackFrame } from './AttackPreview.ts';
3
+ import { UnitSelector, type SelectableUnit, type SelectorStage } from './UnitSelector.ts';
4
+ /** The per-strike numbers a game's own rules produce for one attacker/target pair. */
5
+ export interface StrikeNumbers {
6
+ damage: number;
7
+ strikes: number;
8
+ chanceToHit?: number;
9
+ hits?: readonly boolean[];
10
+ }
11
+ export interface AttackDialogOptions {
12
+ units: readonly SelectableUnit[];
13
+ side?: string;
14
+ disabled?: (unit: SelectableUnit) => boolean;
15
+ canTarget?: (attacker: SelectableUnit, target: SelectableUnit) => boolean;
16
+ /** the game's damage rules, called once the attacker and target are both chosen */
17
+ damageFor: (attacker: SelectableUnit, target: SelectableUnit) => StrikeNumbers;
18
+ /** seconds between strikes in the preview animation */
19
+ strikeDuration?: number;
20
+ }
21
+ /**
22
+ * The attack dialog's whole state, with no renderer: the unit selector, the damage preview the
23
+ * game's numbers build, and the animation clock the preview is sampled on.
24
+ *
25
+ * The order of business is the one a player sees - choose an attacker, choose a target, and the
26
+ * dialog shows a blow that lands over time - so the clock is here rather than in a widget: `update`
27
+ * takes the frame's `dt` and returns the preview frame to draw, and `finished` says when the
28
+ * animation has run out and the caller may apply the result. `damageFor` is where the game's own
29
+ * rules live; this model never invents damage.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * import { AttackDialog } from '@datamoc/mw_games/battle';
34
+ *
35
+ * const dialog = new AttackDialog({
36
+ * units: [{ id: 'hero', side: '1' }, { id: 'rat', side: '2' }],
37
+ * side: '1',
38
+ * damageFor: () => ({ damage: 4, strikes: 2 }),
39
+ * strikeDuration: 0.25,
40
+ * });
41
+ * dialog.select();
42
+ * dialog.select();
43
+ * console.log(dialog.update(0.3)?.defenderHp); // the defender after the second strike
44
+ * ```
45
+ */
46
+ export declare class AttackDialog {
47
+ readonly onChange: Signal<void>;
48
+ readonly selector: UnitSelector;
49
+ private readonly damageFor;
50
+ private readonly strikeDuration;
51
+ private built;
52
+ private elapsed_;
53
+ constructor(options: AttackDialogOptions);
54
+ get stage(): SelectorStage;
55
+ get attacker(): SelectableUnit | null;
56
+ get target(): SelectableUnit | null;
57
+ /** the damage preview, built once both choices are made */
58
+ get preview(): AttackPreview | null;
59
+ /** how long the animation has been playing, in seconds */
60
+ get elapsed(): number;
61
+ /** whether the animation has reached the end of the preview */
62
+ get finished(): boolean;
63
+ /** the frame to draw right now, or `null` before both choices are made */
64
+ get frame(): AttackFrame | null;
65
+ move(delta: number): void;
66
+ select(): boolean;
67
+ back(): void;
68
+ /** advances the animation, returning the frame to draw */
69
+ update(dt: number): AttackFrame | null;
70
+ /** skips the animation to its end, the click-through a player makes */
71
+ finish(): void;
72
+ reset(): void;
73
+ private readonly handleSelectorChange;
74
+ }
75
+ //# sourceMappingURL=AttackDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AttackDialog.d.ts","sourceRoot":"","sources":["../../src/battle/AttackDialog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAE1F,sFAAsF;AACtF,MAAM,WAAW,aAAa;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IACnC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,OAAO,CAAC;IAC7C,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC;IAC1E,mFAAmF;IACnF,SAAS,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc,KAAK,aAAa,CAAC;IAC/E,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,YAAY;IACxB,QAAQ,CAAC,QAAQ,eAAsB;IACvC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAEhC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsE;IAChG,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,KAAK,CAA8B;IAC3C,OAAO,CAAC,QAAQ,CAAK;IAErB,YAAY,OAAO,EAAE,mBAAmB,EAUvC;IAED,IAAI,KAAK,IAAI,aAAa,CAEzB;IAED,IAAI,QAAQ,IAAI,cAAc,GAAG,IAAI,CAEpC;IAED,IAAI,MAAM,IAAI,cAAc,GAAG,IAAI,CAElC;IAED,2DAA2D;IAC3D,IAAI,OAAO,IAAI,aAAa,GAAG,IAAI,CAElC;IAED,0DAA0D;IAC1D,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,+DAA+D;IAC/D,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,0EAA0E;IAC1E,IAAI,KAAK,IAAI,WAAW,GAAG,IAAI,CAE9B;IAED,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAExB;IAED,MAAM,IAAI,OAAO,CAEhB;IAED,IAAI,IAAI,IAAI,CAEX;IAED,0DAA0D;IAC1D,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAIrC;IAED,uEAAuE;IACvE,MAAM,IAAI,IAAI,CAEb;IAED,KAAK,IAAI,IAAI,CAIZ;IAED,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAiBnC;CACF"}
@@ -0,0 +1,115 @@
1
+ import { Signal } from '../core/Signal.js';
2
+ import { AttackPreview } from './AttackPreview.js';
3
+ import { UnitSelector } from './UnitSelector.js';
4
+ /**
5
+ * The attack dialog's whole state, with no renderer: the unit selector, the damage preview the
6
+ * game's numbers build, and the animation clock the preview is sampled on.
7
+ *
8
+ * The order of business is the one a player sees - choose an attacker, choose a target, and the
9
+ * dialog shows a blow that lands over time - so the clock is here rather than in a widget: `update`
10
+ * takes the frame's `dt` and returns the preview frame to draw, and `finished` says when the
11
+ * animation has run out and the caller may apply the result. `damageFor` is where the game's own
12
+ * rules live; this model never invents damage.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { AttackDialog } from '@datamoc/mw_games/battle';
17
+ *
18
+ * const dialog = new AttackDialog({
19
+ * units: [{ id: 'hero', side: '1' }, { id: 'rat', side: '2' }],
20
+ * side: '1',
21
+ * damageFor: () => ({ damage: 4, strikes: 2 }),
22
+ * strikeDuration: 0.25,
23
+ * });
24
+ * dialog.select();
25
+ * dialog.select();
26
+ * console.log(dialog.update(0.3)?.defenderHp); // the defender after the second strike
27
+ * ```
28
+ */
29
+ export class AttackDialog {
30
+ constructor(options) {
31
+ this.onChange = new Signal();
32
+ this.built = null;
33
+ this.elapsed_ = 0;
34
+ this.handleSelectorChange = () => {
35
+ if (this.selector.done && this.attacker && this.target) {
36
+ const numbers = this.damageFor(this.attacker, this.target);
37
+ this.built = new AttackPreview({
38
+ attacker: { hp: this.attacker.hp ?? 0, name: this.attacker.id },
39
+ defender: { hp: this.target.hp ?? 0, name: this.target.id },
40
+ damage: numbers.damage,
41
+ strikes: numbers.strikes,
42
+ ...(numbers.chanceToHit === undefined ? {} : { chanceToHit: numbers.chanceToHit }),
43
+ ...(numbers.hits === undefined ? {} : { hits: numbers.hits }),
44
+ strikeDuration: this.strikeDuration,
45
+ });
46
+ this.elapsed_ = 0;
47
+ }
48
+ else {
49
+ this.built = null;
50
+ }
51
+ this.onChange.dispatch();
52
+ };
53
+ this.damageFor = options.damageFor;
54
+ this.strikeDuration = options.strikeDuration ?? 0;
55
+ this.selector = new UnitSelector({
56
+ units: options.units,
57
+ ...(options.side === undefined ? {} : { side: options.side }),
58
+ ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
59
+ ...(options.canTarget === undefined ? {} : { canTarget: options.canTarget }),
60
+ });
61
+ this.selector.onChange.add(this.handleSelectorChange);
62
+ }
63
+ get stage() {
64
+ return this.selector.stage;
65
+ }
66
+ get attacker() {
67
+ return this.selector.attacker;
68
+ }
69
+ get target() {
70
+ return this.selector.target;
71
+ }
72
+ /** the damage preview, built once both choices are made */
73
+ get preview() {
74
+ return this.built;
75
+ }
76
+ /** how long the animation has been playing, in seconds */
77
+ get elapsed() {
78
+ return this.elapsed_;
79
+ }
80
+ /** whether the animation has reached the end of the preview */
81
+ get finished() {
82
+ return this.built !== null && this.elapsed_ >= this.built.duration;
83
+ }
84
+ /** the frame to draw right now, or `null` before both choices are made */
85
+ get frame() {
86
+ return this.built ? this.built.sampleAt(this.elapsed_) : null;
87
+ }
88
+ move(delta) {
89
+ this.selector.move(delta);
90
+ }
91
+ select() {
92
+ return this.selector.select();
93
+ }
94
+ back() {
95
+ this.selector.back();
96
+ }
97
+ /** advances the animation, returning the frame to draw */
98
+ update(dt) {
99
+ if (!this.built)
100
+ return null;
101
+ this.elapsed_ = Math.min(this.built.duration, this.elapsed_ + Math.max(0, dt));
102
+ return this.frame;
103
+ }
104
+ /** skips the animation to its end, the click-through a player makes */
105
+ finish() {
106
+ if (this.built)
107
+ this.elapsed_ = this.built.duration;
108
+ }
109
+ reset() {
110
+ this.selector.reset();
111
+ this.built = null;
112
+ this.elapsed_ = 0;
113
+ }
114
+ }
115
+ //# sourceMappingURL=AttackDialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AttackDialog.js","sourceRoot":"","sources":["../../src/battle/AttackDialog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ export interface PreviewCombatant {
2
+ hp: number;
3
+ maxHp?: number;
4
+ name?: string;
5
+ }
6
+ export interface AttackPreviewOptions {
7
+ attacker: PreviewCombatant;
8
+ defender: PreviewCombatant;
9
+ /** damage per strike, from the game's own formula - `mwg` supplies none */
10
+ damage: number;
11
+ strikes: number;
12
+ /** chance to hit, 0 to 1; 1 (always hits) when omitted */
13
+ chanceToHit?: number;
14
+ /** which strikes land; every strike when omitted, for the maximum-damage preview */
15
+ hits?: readonly boolean[];
16
+ /** seconds between strikes in the animation; 0 when there is no animation */
17
+ strikeDuration?: number;
18
+ }
19
+ /** One moment of the attack animation: the board after `strike` strikes. */
20
+ export interface AttackFrame {
21
+ strike: number;
22
+ time: number;
23
+ attackerHp: number;
24
+ defenderHp: number;
25
+ /** hp taken off the defender by the strikes so far */
26
+ defenderDamage: number;
27
+ }
28
+ /**
29
+ * The numbers an attack dialog shows and animates: the attacker and defender's hp over the course
30
+ * of a blow, plus the totals the labels print.
31
+ *
32
+ * It deliberately computes no damage of its own - this framework supplies no damage formula, so a
33
+ * game hands in the per-strike `damage` exactly as its own rules worked it out. What the preview
34
+ * adds is the part a dialog needs and a formula does not: the *timeline* (one frame per strike,
35
+ * `sampleAt` the animation's own clock) and the two totals (the maximum, `damage * strikes`, and
36
+ * the expected value once `chanceToHit` is weighed). Which strikes actually land is `hits`, so a
37
+ * preview can show the maximum by default or a known outcome a replay already rolled.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * import { AttackPreview } from '@datamoc/mw_games/battle';
42
+ *
43
+ * const preview = new AttackPreview({
44
+ * attacker: { hp: 20 },
45
+ * defender: { hp: 12 },
46
+ * damage: 4,
47
+ * strikes: 3,
48
+ * chanceToHit: 0.6,
49
+ * strikeDuration: 0.2,
50
+ * });
51
+ * console.log(preview.totalDamage); // 12
52
+ * console.log(preview.sampleAt(0.25).defenderHp); // 8, after the second strike
53
+ * ```
54
+ */
55
+ export declare class AttackPreview {
56
+ readonly frames: readonly AttackFrame[];
57
+ readonly totalDamage: number;
58
+ readonly expectedDamage: number;
59
+ readonly chanceToHit: number;
60
+ private readonly strikeDuration;
61
+ private readonly defenderHp0;
62
+ constructor(options: AttackPreviewOptions);
63
+ /** how long the animation runs, in seconds */
64
+ get duration(): number;
65
+ /** whether the defender would be at 0 hp once every landing strike has been shown */
66
+ get defenderKilled(): boolean;
67
+ /** the frame the animation shows at `time`, clamped to its own start and end */
68
+ sampleAt(time: number): AttackFrame;
69
+ }
70
+ //# sourceMappingURL=AttackPreview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AttackPreview.d.ts","sourceRoot":"","sources":["../../src/battle/AttackPreview.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oFAAoF;IACpF,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAC1B,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,cAAc,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,aAAa;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IACxC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC,YAAY,OAAO,EAAE,oBAAoB,EAoBxC;IAED,8CAA8C;IAC9C,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,qFAAqF;IACrF,IAAI,cAAc,IAAI,OAAO,CAE5B;IAED,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAWlC;CACD"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The numbers an attack dialog shows and animates: the attacker and defender's hp over the course
3
+ * of a blow, plus the totals the labels print.
4
+ *
5
+ * It deliberately computes no damage of its own - this framework supplies no damage formula, so a
6
+ * game hands in the per-strike `damage` exactly as its own rules worked it out. What the preview
7
+ * adds is the part a dialog needs and a formula does not: the *timeline* (one frame per strike,
8
+ * `sampleAt` the animation's own clock) and the two totals (the maximum, `damage * strikes`, and
9
+ * the expected value once `chanceToHit` is weighed). Which strikes actually land is `hits`, so a
10
+ * preview can show the maximum by default or a known outcome a replay already rolled.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { AttackPreview } from '@datamoc/mw_games/battle';
15
+ *
16
+ * const preview = new AttackPreview({
17
+ * attacker: { hp: 20 },
18
+ * defender: { hp: 12 },
19
+ * damage: 4,
20
+ * strikes: 3,
21
+ * chanceToHit: 0.6,
22
+ * strikeDuration: 0.2,
23
+ * });
24
+ * console.log(preview.totalDamage); // 12
25
+ * console.log(preview.sampleAt(0.25).defenderHp); // 8, after the second strike
26
+ * ```
27
+ */
28
+ export class AttackPreview {
29
+ constructor(options) {
30
+ this.chanceToHit = clamp01(options.chanceToHit ?? 1);
31
+ this.strikeDuration = Math.max(0, options.strikeDuration ?? 0);
32
+ this.totalDamage = Math.max(0, options.damage) * Math.max(0, options.strikes);
33
+ this.expectedDamage = this.totalDamage * this.chanceToHit;
34
+ this.defenderHp0 = options.defender.hp;
35
+ const frames = [];
36
+ let defenderHp = options.defender.hp;
37
+ for (let strike = 0; strike <= options.strikes; strike++) {
38
+ if (strike > 0 && (options.hits?.[strike - 1] ?? true))
39
+ defenderHp -= Math.max(0, options.damage);
40
+ frames.push({
41
+ strike,
42
+ time: strike * this.strikeDuration,
43
+ attackerHp: options.attacker.hp,
44
+ defenderHp: Math.max(0, defenderHp),
45
+ defenderDamage: this.defenderHp0 - Math.max(0, defenderHp),
46
+ });
47
+ }
48
+ this.frames = frames;
49
+ }
50
+ /** how long the animation runs, in seconds */
51
+ get duration() {
52
+ return this.strikeDuration * (this.frames.length - 1);
53
+ }
54
+ /** whether the defender would be at 0 hp once every landing strike has been shown */
55
+ get defenderKilled() {
56
+ return this.frames[this.frames.length - 1].defenderHp === 0;
57
+ }
58
+ /** the frame the animation shows at `time`, clamped to its own start and end */
59
+ sampleAt(time) {
60
+ const last = this.frames.length - 1;
61
+ if (this.strikeDuration <= 0)
62
+ return this.frames[last];
63
+ //compare frame times rather than dividing, so a float strike duration does not land on the
64
+ //frame before the one its time names (0.4 / 0.2 can read just under 2)
65
+ let index = 0;
66
+ for (let frame = 0; frame <= last; frame++) {
67
+ if (this.frames[frame].time <= time + 1e-9)
68
+ index = frame;
69
+ else
70
+ break;
71
+ }
72
+ return this.frames[index];
73
+ }
74
+ }
75
+ function clamp01(value) {
76
+ return Math.max(0, Math.min(1, value));
77
+ }
78
+ //# sourceMappingURL=AttackPreview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AttackPreview.js","sourceRoot":"","sources":["../../src/battle/AttackPreview.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * A per-run battle statistics breakdown by category and unit type - recruits, recalls,
3
+ * advances, kills, deaths, damage dealt, damage taken - the shape Wesnoth's own
4
+ * statistics dialog shows and a bare `core.PlayerStats<T>` cannot give a game for free,
5
+ * since `T` there is an opaque summary the game must already know how to combine. Here
6
+ * the categories and the per-unit-type breakdown are the primitive: a game records one
7
+ * event at a time as it happens and reads either a category's total or its breakdown by
8
+ * unit type, then folds `toJSON()` into a `core.RunHistory` entry or a `core.PlayerStats`
9
+ * total the way any other run summary would.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { BattleStats } from '@datamoc/mw_games/battle';
14
+ *
15
+ * const stats = new BattleStats();
16
+ * stats.record('kills', 'orc-grunt');
17
+ * stats.record('kills', 'orc-grunt');
18
+ * stats.record('damageDealt', 'elvish-archer', 12);
19
+ *
20
+ * console.log(stats.total('kills')); // 2
21
+ * console.log(stats.breakdown('kills')); // [{ unitType: 'orc-grunt', count: 2 }]
22
+ * ```
23
+ */
24
+ export type BattleStatCategory = 'recruits' | 'recalls' | 'advances' | 'kills' | 'deaths' | 'damageDealt' | 'damageTaken';
25
+ export declare class BattleStats {
26
+ private counts;
27
+ /** adds `amount` (default 1) to a category's count for one unit type */
28
+ record(category: BattleStatCategory, unitType: string, amount?: number): void;
29
+ /** one category's count for one unit type; 0 for a combination nothing has recorded yet */
30
+ forType(category: BattleStatCategory, unitType: string): number;
31
+ /** a category's total across every unit type */
32
+ total(category: BattleStatCategory): number;
33
+ /** every unit type a category has recorded, with its count - the rows a stats screen breaks a category into */
34
+ breakdown(category: BattleStatCategory): readonly {
35
+ unitType: string;
36
+ count: number;
37
+ }[];
38
+ toJSON(): {
39
+ counts: [BattleStatCategory, [string, number][]][];
40
+ };
41
+ static fromJSON(data: {
42
+ counts: [BattleStatCategory, [string, number][]][];
43
+ }): BattleStats;
44
+ }
45
+ //# sourceMappingURL=BattleStats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BattleStats.d.ts","sourceRoot":"","sources":["../../src/battle/BattleStats.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,MAAM,kBAAkB,GAC7B,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,aAAa,GAAG,aAAa,CAAC;AAE1F,qBAAa,WAAW;IACvB,OAAO,CAAC,MAAM,CAAsD;IAEpE,wEAAwE;IACxE,MAAM,CAAC,QAAQ,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,SAAI,GAAG,IAAI,CAIvE;IAED,2FAA2F;IAC3F,OAAO,CAAC,QAAQ,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE9D;IAED,gDAAgD;IAChD,KAAK,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,CAM1C;IAED,+GAA+G;IAC/G,SAAS,CAAC,QAAQ,EAAE,kBAAkB,GAAG,SAAS;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAGtF;IAED,MAAM,IAAI;QAAE,MAAM,EAAE,CAAC,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAA;KAAE,CAE/D;IAED,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,CAAC,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAA;KAAE,GAAG,WAAW,CAIzF;CACD"}