@datamoc/mw_games 0.1.1 → 0.2.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 (360) hide show
  1. package/README.md +57 -32
  2. package/dist/actors/Appearances.js +1 -1
  3. package/dist/actors/Aura.d.ts +29 -0
  4. package/dist/actors/Aura.d.ts.map +1 -0
  5. package/dist/actors/Aura.js +69 -0
  6. package/dist/actors/Aura.js.map +1 -0
  7. package/dist/actors/ItemStatusEffect.d.ts +27 -0
  8. package/dist/actors/ItemStatusEffect.d.ts.map +1 -0
  9. package/dist/actors/ItemStatusEffect.js +35 -0
  10. package/dist/actors/ItemStatusEffect.js.map +1 -0
  11. package/dist/actors/Resource.d.ts.map +1 -1
  12. package/dist/actors/Resource.js +13 -5
  13. package/dist/actors/Resource.js.map +1 -1
  14. package/dist/actors/Support.d.ts +32 -0
  15. package/dist/actors/Support.d.ts.map +1 -0
  16. package/dist/actors/Support.js +60 -0
  17. package/dist/actors/Support.js.map +1 -0
  18. package/dist/actors/Traits.d.ts +29 -0
  19. package/dist/actors/Traits.d.ts.map +1 -0
  20. package/dist/actors/Traits.js +21 -0
  21. package/dist/actors/Traits.js.map +1 -0
  22. package/dist/actors/craft.d.ts.map +1 -1
  23. package/dist/actors/craft.js +6 -2
  24. package/dist/actors/craft.js.map +1 -1
  25. package/dist/actors/index.d.ts +8 -0
  26. package/dist/actors/index.d.ts.map +1 -1
  27. package/dist/actors/index.js +4 -0
  28. package/dist/actors/index.js.map +1 -1
  29. package/dist/assets/Streaming.d.ts +40 -0
  30. package/dist/assets/Streaming.d.ts.map +1 -0
  31. package/dist/assets/Streaming.js +79 -0
  32. package/dist/assets/Streaming.js.map +1 -0
  33. package/dist/assets/index.d.ts +14 -1
  34. package/dist/assets/index.d.ts.map +1 -1
  35. package/dist/assets/index.js +19 -0
  36. package/dist/assets/index.js.map +1 -1
  37. package/dist/audio/Captions.d.ts +15 -0
  38. package/dist/audio/Captions.d.ts.map +1 -0
  39. package/dist/audio/Captions.js +4 -0
  40. package/dist/audio/Captions.js.map +1 -0
  41. package/dist/audio/Midi.d.ts +75 -0
  42. package/dist/audio/Midi.d.ts.map +1 -0
  43. package/dist/audio/Midi.js +186 -0
  44. package/dist/audio/Midi.js.map +1 -0
  45. package/dist/audio/Orchestrator.d.ts +40 -0
  46. package/dist/audio/Orchestrator.d.ts.map +1 -0
  47. package/dist/audio/Orchestrator.js +48 -0
  48. package/dist/audio/Orchestrator.js.map +1 -0
  49. package/dist/audio/Sound.d.ts +3 -0
  50. package/dist/audio/Sound.d.ts.map +1 -1
  51. package/dist/audio/Sound.js +4 -0
  52. package/dist/audio/Sound.js.map +1 -1
  53. package/dist/audio/Synth.d.ts +35 -0
  54. package/dist/audio/Synth.d.ts.map +1 -0
  55. package/dist/audio/Synth.js +96 -0
  56. package/dist/audio/Synth.js.map +1 -0
  57. package/dist/audio/index.d.ts +8 -0
  58. package/dist/audio/index.d.ts.map +1 -1
  59. package/dist/audio/index.js +4 -0
  60. package/dist/audio/index.js.map +1 -1
  61. package/dist/battle/Party.d.ts.map +1 -1
  62. package/dist/battle/Party.js +2 -0
  63. package/dist/battle/Party.js.map +1 -1
  64. package/dist/board/Army.d.ts +17 -0
  65. package/dist/board/Army.d.ts.map +1 -0
  66. package/dist/board/Army.js +48 -0
  67. package/dist/board/Army.js.map +1 -0
  68. package/dist/board/Classics.d.ts.map +1 -1
  69. package/dist/board/Classics.js +14 -6
  70. package/dist/board/Classics.js.map +1 -1
  71. package/dist/board/FogOfWar.d.ts +23 -0
  72. package/dist/board/FogOfWar.d.ts.map +1 -0
  73. package/dist/board/FogOfWar.js +40 -0
  74. package/dist/board/FogOfWar.js.map +1 -0
  75. package/dist/board/Tactics.d.ts +2 -0
  76. package/dist/board/Tactics.d.ts.map +1 -1
  77. package/dist/board/Tactics.js +36 -8
  78. package/dist/board/Tactics.js.map +1 -1
  79. package/dist/board/index.d.ts +5 -1
  80. package/dist/board/index.d.ts.map +1 -1
  81. package/dist/board/index.js +3 -1
  82. package/dist/board/index.js.map +1 -1
  83. package/dist/core/Feedback.d.ts +23 -0
  84. package/dist/core/Feedback.d.ts.map +1 -0
  85. package/dist/core/Feedback.js +35 -0
  86. package/dist/core/Feedback.js.map +1 -0
  87. package/dist/core/Game.d.ts +27 -1
  88. package/dist/core/Game.d.ts.map +1 -1
  89. package/dist/core/Game.js +61 -5
  90. package/dist/core/Game.js.map +1 -1
  91. package/dist/core/Input.d.ts +59 -1
  92. package/dist/core/Input.d.ts.map +1 -1
  93. package/dist/core/Input.js +135 -0
  94. package/dist/core/Input.js.map +1 -1
  95. package/dist/core/Loading.d.ts +48 -0
  96. package/dist/core/Loading.d.ts.map +1 -0
  97. package/dist/core/Loading.js +102 -0
  98. package/dist/core/Loading.js.map +1 -0
  99. package/dist/core/News.d.ts +46 -0
  100. package/dist/core/News.d.ts.map +1 -0
  101. package/dist/core/News.js +79 -0
  102. package/dist/core/News.js.map +1 -0
  103. package/dist/core/PlayerInput.d.ts +38 -0
  104. package/dist/core/PlayerInput.d.ts.map +1 -0
  105. package/dist/core/PlayerInput.js +57 -0
  106. package/dist/core/PlayerInput.js.map +1 -0
  107. package/dist/core/Random.d.ts +2 -2
  108. package/dist/core/Random.js +2 -2
  109. package/dist/core/RunHistory.d.ts +43 -0
  110. package/dist/core/RunHistory.d.ts.map +1 -0
  111. package/dist/core/RunHistory.js +41 -0
  112. package/dist/core/RunHistory.js.map +1 -0
  113. package/dist/core/Sanitize.d.ts +56 -0
  114. package/dist/core/Sanitize.d.ts.map +1 -0
  115. package/dist/core/Sanitize.js +87 -0
  116. package/dist/core/Sanitize.js.map +1 -0
  117. package/dist/core/Save.d.ts +31 -7
  118. package/dist/core/Save.d.ts.map +1 -1
  119. package/dist/core/Save.js +56 -0
  120. package/dist/core/Save.js.map +1 -1
  121. package/dist/core/SaveSync.d.ts +26 -0
  122. package/dist/core/SaveSync.d.ts.map +1 -0
  123. package/dist/core/SaveSync.js +52 -0
  124. package/dist/core/SaveSync.js.map +1 -0
  125. package/dist/core/Scramble.d.ts +13 -0
  126. package/dist/core/Scramble.d.ts.map +1 -0
  127. package/dist/core/Scramble.js +44 -0
  128. package/dist/core/Scramble.js.map +1 -0
  129. package/dist/core/Session.d.ts +21 -0
  130. package/dist/core/Session.d.ts.map +1 -0
  131. package/dist/core/Session.js +11 -0
  132. package/dist/core/Session.js.map +1 -0
  133. package/dist/core/Spawner.d.ts +48 -0
  134. package/dist/core/Spawner.d.ts.map +1 -0
  135. package/dist/core/Spawner.js +65 -0
  136. package/dist/core/Spawner.js.map +1 -0
  137. package/dist/core/Tween.d.ts +25 -0
  138. package/dist/core/Tween.d.ts.map +1 -0
  139. package/dist/core/Tween.js +60 -0
  140. package/dist/core/Tween.js.map +1 -0
  141. package/dist/core/UndoHistory.d.ts +37 -0
  142. package/dist/core/UndoHistory.d.ts.map +1 -0
  143. package/dist/core/UndoHistory.js +49 -0
  144. package/dist/core/UndoHistory.js.map +1 -0
  145. package/dist/core/index.d.ts +22 -0
  146. package/dist/core/index.d.ts.map +1 -1
  147. package/dist/core/index.js +12 -0
  148. package/dist/core/index.js.map +1 -1
  149. package/dist/i18n/Fluent.d.ts +7 -0
  150. package/dist/i18n/Fluent.d.ts.map +1 -0
  151. package/dist/i18n/Fluent.js +86 -0
  152. package/dist/i18n/Fluent.js.map +1 -0
  153. package/dist/i18n/index.d.ts +15 -1
  154. package/dist/i18n/index.d.ts.map +1 -1
  155. package/dist/i18n/index.js +17 -2
  156. package/dist/i18n/index.js.map +1 -1
  157. package/dist/index.d.ts +1 -0
  158. package/dist/index.d.ts.map +1 -1
  159. package/dist/index.js +1 -0
  160. package/dist/index.js.map +1 -1
  161. package/dist/mw_games.global.js +351 -79
  162. package/dist/render/Camera.d.ts +32 -4
  163. package/dist/render/Camera.d.ts.map +1 -1
  164. package/dist/render/Camera.js +58 -23
  165. package/dist/render/Camera.js.map +1 -1
  166. package/dist/render/Capabilities.d.ts +51 -0
  167. package/dist/render/Capabilities.d.ts.map +1 -0
  168. package/dist/render/Capabilities.js +56 -0
  169. package/dist/render/Capabilities.js.map +1 -0
  170. package/dist/render/ColorBlindness.d.ts +16 -0
  171. package/dist/render/ColorBlindness.d.ts.map +1 -0
  172. package/dist/render/ColorBlindness.js +41 -0
  173. package/dist/render/ColorBlindness.js.map +1 -0
  174. package/dist/render/ColorTransformBatcher.d.ts +9 -0
  175. package/dist/render/ColorTransformBatcher.d.ts.map +1 -1
  176. package/dist/render/ColorTransformBatcher.js +131 -19
  177. package/dist/render/ColorTransformBatcher.js.map +1 -1
  178. package/dist/render/Minimap.d.ts +52 -0
  179. package/dist/render/Minimap.d.ts.map +1 -0
  180. package/dist/render/Minimap.js +95 -0
  181. package/dist/render/Minimap.js.map +1 -0
  182. package/dist/render/TileMap.d.ts +2 -2
  183. package/dist/render/TileMap.d.ts.map +1 -1
  184. package/dist/render/TileMap.js +12 -12
  185. package/dist/render/TileMap.js.map +1 -1
  186. package/dist/render/TintedSprite.d.ts +1 -8
  187. package/dist/render/TintedSprite.d.ts.map +1 -1
  188. package/dist/render/TintedSprite.js +3 -111
  189. package/dist/render/TintedSprite.js.map +1 -1
  190. package/dist/render/Viewport.d.ts +46 -0
  191. package/dist/render/Viewport.d.ts.map +1 -0
  192. package/dist/render/Viewport.js +52 -0
  193. package/dist/render/Viewport.js.map +1 -0
  194. package/dist/render/index.d.ts +9 -1
  195. package/dist/render/index.d.ts.map +1 -1
  196. package/dist/render/index.js +5 -1
  197. package/dist/render/index.js.map +1 -1
  198. package/dist/roguelike/FieldOfView.d.ts +1 -1
  199. package/dist/roguelike/Level.d.ts +2 -2
  200. package/dist/roguelike/Level.d.ts.map +1 -1
  201. package/dist/roguelike/Level.js +1 -1
  202. package/dist/roguelike/Level.js.map +1 -1
  203. package/dist/roguelike/Pathfinder.d.ts +3 -3
  204. package/dist/roguelike/Pathfinder.js +2 -2
  205. package/dist/roguelike/Scheduler.d.ts +1 -1
  206. package/dist/roguelike/Scheduler.js +1 -1
  207. package/dist/roguelike/Stealth.d.ts +34 -0
  208. package/dist/roguelike/Stealth.d.ts.map +1 -0
  209. package/dist/roguelike/Stealth.js +35 -0
  210. package/dist/roguelike/Stealth.js.map +1 -0
  211. package/dist/roguelike/generate.d.ts +3 -3
  212. package/dist/roguelike/generate.js +1 -1
  213. package/dist/roguelike/generate.js.map +1 -1
  214. package/dist/roguelike/index.d.ts +2 -0
  215. package/dist/roguelike/index.d.ts.map +1 -1
  216. package/dist/roguelike/index.js +1 -0
  217. package/dist/roguelike/index.js.map +1 -1
  218. package/dist/rpg/Collision.d.ts +42 -0
  219. package/dist/rpg/Collision.d.ts.map +1 -0
  220. package/dist/rpg/Collision.js +77 -0
  221. package/dist/rpg/Collision.js.map +1 -0
  222. package/dist/rpg/FreeMover.d.ts +45 -0
  223. package/dist/rpg/FreeMover.d.ts.map +1 -0
  224. package/dist/rpg/FreeMover.js +68 -0
  225. package/dist/rpg/FreeMover.js.map +1 -0
  226. package/dist/rpg/Marshal.d.ts +65 -0
  227. package/dist/rpg/Marshal.d.ts.map +1 -0
  228. package/dist/rpg/Marshal.js +393 -0
  229. package/dist/rpg/Marshal.js.map +1 -0
  230. package/dist/rpg/Quest.d.ts +41 -0
  231. package/dist/rpg/Quest.d.ts.map +1 -1
  232. package/dist/rpg/Quest.js +39 -6
  233. package/dist/rpg/Quest.js.map +1 -1
  234. package/dist/rpg/index.d.ts +7 -1
  235. package/dist/rpg/index.d.ts.map +1 -1
  236. package/dist/rpg/index.js +3 -0
  237. package/dist/rpg/index.js.map +1 -1
  238. package/dist/stage/DialogueStage.d.ts +2 -3
  239. package/dist/stage/DialogueStage.d.ts.map +1 -1
  240. package/dist/stage/DialogueStage.js +21 -30
  241. package/dist/stage/DialogueStage.js.map +1 -1
  242. package/dist/stage/index.d.ts +1 -1
  243. package/dist/stage/index.d.ts.map +1 -1
  244. package/dist/stage/script.d.ts +32 -3
  245. package/dist/stage/script.d.ts.map +1 -1
  246. package/dist/stage/script.js +0 -0
  247. package/dist/stage/script.js.map +1 -1
  248. package/dist/three-d/Character3D.d.ts +48 -0
  249. package/dist/three-d/Character3D.d.ts.map +1 -0
  250. package/dist/three-d/Character3D.js +96 -0
  251. package/dist/three-d/Character3D.js.map +1 -0
  252. package/dist/three-d/Engine3D.d.ts +23 -0
  253. package/dist/three-d/Engine3D.d.ts.map +1 -0
  254. package/dist/three-d/Engine3D.js +45 -0
  255. package/dist/three-d/Engine3D.js.map +1 -0
  256. package/dist/three-d/Grid.d.ts +14 -0
  257. package/dist/three-d/Grid.d.ts.map +1 -0
  258. package/dist/three-d/Grid.js +13 -0
  259. package/dist/three-d/Grid.js.map +1 -0
  260. package/dist/three-d/Heightmap.d.ts +35 -0
  261. package/dist/three-d/Heightmap.d.ts.map +1 -0
  262. package/dist/three-d/Heightmap.js +29 -0
  263. package/dist/three-d/Heightmap.js.map +1 -0
  264. package/dist/three-d/Models.d.ts +12 -0
  265. package/dist/three-d/Models.d.ts.map +1 -0
  266. package/dist/three-d/Models.js +14 -0
  267. package/dist/three-d/Models.js.map +1 -0
  268. package/dist/three-d/TileGrid3D.d.ts +21 -0
  269. package/dist/three-d/TileGrid3D.d.ts.map +1 -0
  270. package/dist/three-d/TileGrid3D.js +65 -0
  271. package/dist/three-d/TileGrid3D.js.map +1 -0
  272. package/dist/three-d/Vox.d.ts +24 -0
  273. package/dist/three-d/Vox.d.ts.map +1 -0
  274. package/dist/three-d/Vox.js +93 -0
  275. package/dist/three-d/Vox.js.map +1 -0
  276. package/dist/three-d/index.d.ts +13 -0
  277. package/dist/three-d/index.d.ts.map +1 -0
  278. package/dist/three-d/index.js +7 -0
  279. package/dist/three-d/index.js.map +1 -0
  280. package/dist/ui/Bar.d.ts +49 -0
  281. package/dist/ui/Bar.d.ts.map +1 -0
  282. package/dist/ui/Bar.js +70 -0
  283. package/dist/ui/Bar.js.map +1 -0
  284. package/dist/ui/BitmapLabel.d.ts +42 -0
  285. package/dist/ui/BitmapLabel.d.ts.map +1 -0
  286. package/dist/ui/BitmapLabel.js +59 -0
  287. package/dist/ui/BitmapLabel.js.map +1 -0
  288. package/dist/ui/Button.d.ts +52 -0
  289. package/dist/ui/Button.d.ts.map +1 -0
  290. package/dist/ui/Button.js +147 -0
  291. package/dist/ui/Button.js.map +1 -0
  292. package/dist/ui/FloatingText.d.ts +28 -0
  293. package/dist/ui/FloatingText.d.ts.map +1 -0
  294. package/dist/ui/FloatingText.js +39 -0
  295. package/dist/ui/FloatingText.js.map +1 -0
  296. package/dist/ui/HelpScreen.d.ts +29 -0
  297. package/dist/ui/HelpScreen.d.ts.map +1 -0
  298. package/dist/ui/HelpScreen.js +39 -0
  299. package/dist/ui/HelpScreen.js.map +1 -0
  300. package/dist/ui/IconGrid.d.ts +8 -0
  301. package/dist/ui/IconGrid.d.ts.map +1 -1
  302. package/dist/ui/IconGrid.js +27 -1
  303. package/dist/ui/IconGrid.js.map +1 -1
  304. package/dist/ui/Label.d.ts +10 -1
  305. package/dist/ui/Label.d.ts.map +1 -1
  306. package/dist/ui/Label.js +25 -2
  307. package/dist/ui/Label.js.map +1 -1
  308. package/dist/ui/ListView.d.ts +12 -1
  309. package/dist/ui/ListView.d.ts.map +1 -1
  310. package/dist/ui/ListView.js +56 -2
  311. package/dist/ui/ListView.js.map +1 -1
  312. package/dist/ui/LoadingScreen.d.ts +31 -0
  313. package/dist/ui/LoadingScreen.d.ts.map +1 -0
  314. package/dist/ui/LoadingScreen.js +60 -0
  315. package/dist/ui/LoadingScreen.js.map +1 -0
  316. package/dist/ui/MessageBox.d.ts +27 -1
  317. package/dist/ui/MessageBox.d.ts.map +1 -1
  318. package/dist/ui/MessageBox.js +63 -14
  319. package/dist/ui/MessageBox.js.map +1 -1
  320. package/dist/ui/RebindScreen.d.ts +55 -0
  321. package/dist/ui/RebindScreen.d.ts.map +1 -0
  322. package/dist/ui/RebindScreen.js +114 -0
  323. package/dist/ui/RebindScreen.js.map +1 -0
  324. package/dist/ui/Toast.d.ts +44 -0
  325. package/dist/ui/Toast.d.ts.map +1 -0
  326. package/dist/ui/Toast.js +89 -0
  327. package/dist/ui/Toast.js.map +1 -0
  328. package/dist/ui/VerticalLabel.d.ts +5 -0
  329. package/dist/ui/VerticalLabel.d.ts.map +1 -1
  330. package/dist/ui/VerticalLabel.js +18 -7
  331. package/dist/ui/VerticalLabel.js.map +1 -1
  332. package/dist/ui/Window.d.ts +6 -0
  333. package/dist/ui/Window.d.ts.map +1 -1
  334. package/dist/ui/Window.js +16 -1
  335. package/dist/ui/Window.js.map +1 -1
  336. package/dist/ui/WindowStack.d.ts +1 -0
  337. package/dist/ui/WindowStack.d.ts.map +1 -1
  338. package/dist/ui/WindowStack.js +4 -1
  339. package/dist/ui/WindowStack.js.map +1 -1
  340. package/dist/ui/index.d.ts +17 -1
  341. package/dist/ui/index.d.ts.map +1 -1
  342. package/dist/ui/index.js +9 -1
  343. package/dist/ui/index.js.map +1 -1
  344. package/dist/ui/theme.d.ts +21 -0
  345. package/dist/ui/theme.d.ts.map +1 -1
  346. package/dist/ui/theme.js +42 -0
  347. package/dist/ui/theme.js.map +1 -1
  348. package/dist/version.d.ts +13 -0
  349. package/dist/version.d.ts.map +1 -0
  350. package/dist/version.js +13 -0
  351. package/dist/version.js.map +1 -0
  352. package/dist/world/Environment.d.ts +35 -0
  353. package/dist/world/Environment.d.ts.map +1 -0
  354. package/dist/world/Environment.js +55 -0
  355. package/dist/world/Environment.js.map +1 -0
  356. package/dist/world/index.d.ts +2 -0
  357. package/dist/world/index.d.ts.map +1 -1
  358. package/dist/world/index.js +1 -0
  359. package/dist/world/index.js.map +1 -1
  360. package/package.json +40 -4
package/README.md CHANGED
@@ -1,16 +1,18 @@
1
1
  # mwg
2
2
 
3
- A framework for building 2D top-down games that run **from a local file** — no server, no
4
- install, no runtime to download.
3
+ A framework for building primarily 2D top-down games that run **from a local file**: no
4
+ server, no install, no runtime to download. An optional Babylon.js module supports 3D maps.
5
5
 
6
6
  Licensed under the [MPL-2.0](LICENSE): games built with `mwg` carry no licence obligation
7
7
  of their own; improvements to `mwg`'s own files are shared back.
8
8
 
9
- **[Live examples and API docs](https://datamoc.github.io/mwg/)** — every example below,
9
+ **[Live examples and API docs](https://datamoc.github.io/mwg/)**: every example below,
10
10
  playable in the browser with no download, plus the generated API reference.
11
11
 
12
- > **Status: early.** The scaffolding, licence and capability spec are in place. The
13
- > modules below are being built in the order given in [ROADMAP.md](ROADMAP.md).
12
+ > **Status: early release (v0.2.0).** Every module in the shared floor below, plus optional
13
+ > 3D, mobile (Capacitor) and desktop (WebView2) packaging, is built and tested - see
14
+ > [ROADMAP.md](ROADMAP.md) for the full, numbered history. Still `0.y.z`: the "public API"
15
+ > can still change between minor versions, and the wider genre-specific modules keep growing.
14
16
 
15
17
  ## What it is for
16
18
 
@@ -31,7 +33,7 @@ what that has to cover:
31
33
 
32
34
  Underneath they share almost everything: a grid, a camera following someone across it,
33
35
  sprites that need tinting, turn-taking, characters with stats and equipment, an inventory,
34
- and a great many windows. The differences are narrower than the genre labels suggest — one
36
+ and a great many windows. The differences are narrower than the genre labels suggest: one
35
37
  generates its maps and another authors them; one schedules monsters and another schedules
36
38
  events; one rolls encounters from the terrain and another places them by hand; one puts its
37
39
  conversations in a corner of the map and another gives them the whole screen; one moves in
@@ -39,10 +41,11 @@ four directions on a square grid and another in six on a hex grid.
39
41
 
40
42
  `mwg` provides that shared floor plus each specialism, on top of
41
43
  [PixiJS](https://pixijs.com) for rendering and [rot.js](https://ondras.github.io/rot.js/)
42
- for the classic roguelike algorithms. See [Capability spec](#capability-spec).
44
+ for the classic roguelike algorithms. Optional 3D scenes use Babylon.js. See
45
+ [Capability spec](#capability-spec).
43
46
 
44
47
  These are references for *what a finished game of each kind has to do*, and nothing more.
45
- No code, asset, data table or file format is taken from any of them — see
48
+ No code, asset, data table or file format is taken from any of them: see
46
49
  [Licence and provenance](#licence-and-provenance).
47
50
 
48
51
  ### On being "like RPG Maker"
@@ -50,13 +53,13 @@ No code, asset, data table or file format is taken from any of them — see
50
53
  Worth being precise, because RPG Maker is two things and `mwg` is only aiming at one of
51
54
  them for now.
52
55
 
53
- It is a **runtime** — a tile engine, an event interpreter, a battle system, a message box.
56
+ It is a **runtime**: a tile engine, an event interpreter, a battle system, a message box.
54
57
  That is what the capability spec below commits to.
55
58
 
56
59
  It is also an **editor**, and that is what most people actually mean by RPG Maker: drawing
57
60
  maps and wiring events without writing code. `mwg` does not have one. What it does instead
58
61
  is keep maps, events and dialogue as **plain data** rather than code, so an editor can be
59
- built against that data later without reworking the runtime — and so that in the meantime
62
+ built against that data later without reworking the runtime, and so that in the meantime
60
63
  the data can be written by hand, generated by a script, or exported from
61
64
  [Tiled](https://www.mapeditor.org/), which is free and already good at this.
62
65
 
@@ -83,7 +86,7 @@ instead of by name:
83
86
  npm install ./datamoc-mw_games-0.1.1.tgz
84
87
  ```
85
88
 
86
- **Without either.** Drop one file next to your page and open it — this path exists because
89
+ **Without either.** Drop one file next to your page and open it: this path exists because
87
90
  the framework's own target is a game you can run by double-clicking it. Download
88
91
  `mw_games.global.js` from a [release](https://github.com/datamoc/mwg/releases) (no `npm`,
89
92
  no `node_modules`, nothing to install):
@@ -139,6 +142,7 @@ only some do.
139
142
  | | pointer, keyboard and gamepad input with rebinding | `mwg/core` |
140
143
  | **text** | message tables per language, compiled at build time | `mwg/i18n` |
141
144
  | | plurals, gendered forms and interpolation | `mwg/i18n` |
145
+ | | Fluent-style FTL catalogs with locale-aware fallback and variants | `mwg/i18n` |
142
146
  | | left-to-right, right-to-left and vertical writing | `mwg/i18n` |
143
147
  | | interface mirrored for right-to-left languages | `mwg/ui` |
144
148
  | | a missing translation falls back rather than showing a key | `mwg/i18n` |
@@ -173,14 +177,15 @@ The Wesnoth-shaped half: a grid whose shape is a parameter, not a parallel imple
173
177
  | --- | --- |
174
178
  | hexagonal tile maps, addressed by axial or offset coordinates | `mwg/render` |
175
179
  | movement cost and defense bonus by terrain, hex or square alike | `mwg/roguelike` |
176
- | zone of control: a hex a unit cannot move through, only into | `mwg/roguelike` |
177
180
  | field of view and pathfinding over a hex grid, not just square | `mwg/roguelike` |
178
181
  | isometric (and staggered) projection, alongside orthogonal and hex | `mwg/render` |
182
+ | zone of control: a hex a unit can only move into, not through, unless already engaged | `mwg/board` |
183
+ | an army economy: recruiting against a currency, recalling from a pool, per-turn upkeep | `mwg/board` |
179
184
 
180
185
  A game picks a grid shape once; the camera, field of view, pathfinding and turn scheduler
181
186
  work the same way over either, rather than a hex game needing its own copy of each. Between
182
187
  them, orthogonal, isometric and hex cover every map orientation
183
- [Tiled](https://www.mapeditor.org/) itself supports — a Tiled map loads and renders through
188
+ [Tiled](https://www.mapeditor.org/) itself supports: a Tiled map loads and renders through
184
189
  the same `TileMap`, whichever orientation it was drawn in, rather than mwg reading only the
185
190
  one shape it happens to render natively. Flare's Empyrean Campaign is the isometric-shaped
186
191
  reference, the same way Wesnoth is the hex-shaped one.
@@ -219,7 +224,7 @@ rather than classes.
219
224
 
220
225
  That last row is why `tools/make-example-assets.mjs` draws its character in parts. A figure
221
226
  assembled from skin, eyes, hair and garments is one palette away from being a second
222
- character — and one *layer* away from showing the armour it just put on. A single flat
227
+ character, and one *layer* away from showing the armour it just put on. A single flat
223
228
  sprite per character can do neither.
224
229
 
225
230
  ### Creatures and battles
@@ -237,7 +242,7 @@ The Pokémon-shaped half, and the part `mwg` deliberately keeps generic.
237
242
  | capture, experience, levelling and evolution as replaceable rules | `mwg/battle` |
238
243
 
239
244
  `mwg` supplies the *shape*: a stat block, a type matrix, a turn queue. It supplies no
240
- formulas, no type chart and no species — those are a game's own design, and copying any
245
+ formulas, no type chart and no species: those are a game's own design, and copying any
241
246
  particular game's would be both wrong and useless to anyone else.
242
247
 
243
248
  All of these specialisms share the tile map, the camera, the windows, the turn clock and
@@ -247,7 +252,7 @@ the save system, which is why they sit in one framework rather than four.
247
252
 
248
253
  The single most load-bearing rendering feature in either genre is the colour transform
249
254
  `texel × M + A`. Darkness outside the field of view, tinting a poisoned enemy, flashing
250
- white on a hit, a room lit by a fireplace, a screen-wide tint for a night scene — all of it
255
+ white on a hit, a room lit by a fireplace, a screen-wide tint for a night scene: all of it
251
256
  is that one operation.
252
257
 
253
258
  Pixi's built-in `tint` is multiply-only, which can only ever darken. The additive term is
@@ -259,7 +264,7 @@ nothing per sprite. See `examples/colour-transform`.
259
264
  An RPG's content is enormous and repetitive, and almost none of it deserves to be written
260
265
  by hand in TypeScript. Keeping maps, events, dialogue and item tables as data means they
261
266
  can be authored in a spreadsheet, generated by a script, exported from Tiled, or eventually
262
- edited in a proper editor — without any of those options requiring changes to the runtime.
267
+ edited in a proper editor, without any of those options requiring changes to the runtime.
263
268
 
264
269
  It also keeps the content out of the bundle: `tools/compile-resources` treats a map file
265
270
  exactly like a sprite sheet, so adding a hundred maps does not touch the build at all.
@@ -270,14 +275,14 @@ Supporting more than one language is mostly bookkeeping. Supporting more than on
270
275
  *direction* is not, and the three cases are genuinely different work:
271
276
 
272
277
  - **Left-to-right** is what everything assumes by default.
273
- - **Right-to-left** — Arabic, Hebrew — is two problems at once. The text itself the browser
278
+ - **Right-to-left** (Arabic, Hebrew) is two problems at once. The text itself the browser
274
279
  can handle: canvas applies the Unicode bidirectional algorithm and shapes the glyphs, so
275
280
  a label mostly comes out right once its direction is declared. The *interface* is the
276
281
  harder half, because a right-to-left reader expects it mirrored: lists indent from the
277
282
  right, a back arrow points the other way, a progress bar fills the other way. `mwg/ui`
278
283
  lays widgets out against a direction rather than against "left" so this can be flipped,
279
284
  and a game only has to mark which of its own icons must not be mirrored.
280
- - **Vertical**, top to bottom — traditional Chinese, Japanese, Mongolian — gets no help at
285
+ - **Vertical**, top to bottom (traditional Chinese, Japanese, Mongolian), gets no help at
281
286
  all. Canvas has no vertical writing mode, so the text has to be laid out glyph by glyph,
282
287
  with its own rules for which characters rotate and which stand upright. This is real work
283
288
  and it is scheduled as such, not assumed to fall out of the other two.
@@ -286,37 +291,57 @@ Message tables are compiled at build time, the same way every other resource is,
286
291
  a language costs nothing at runtime and a missing string falls back to the base language
287
292
  rather than showing a raw key to the player.
288
293
 
294
+ ## Optional Babylon.js 3D
295
+
296
+ `@babylonjs/core`/`@babylonjs/loaders` are peer dependencies, not installed automatically -
297
+ a game that never imports `mwg/3d` never downloads Babylon.js. Add them yourself
298
+ (`npm install @babylonjs/core @babylonjs/loaders`) once a game imports 3D support:
299
+
300
+ ```ts
301
+ import { Engine3D, createTileGrid3D } from '@datamoc/mw_games/3d';
302
+ import { loadModel3D } from '@datamoc/mw_games/3d/models';
303
+ ```
304
+
305
+ `Engine3D` owns a WebGL scene, orbit camera, light, resize handling, and render loop.
306
+ `createTileGrid3D` batches square or flat-top hex tiles and elevation columns with thin
307
+ instances; `createHeightmapTerrain3D` builds continuous terrain from decoded RGBA heightmap
308
+ pixels. `Character3D` moves imported meshes or camera-facing sprites continuously, and can
309
+ play imported animation clips. `parseVox` and `createVoxModel3D` load one MagicaVoxel model;
310
+ `loadModel3D` handles glTF/GLB.
311
+ Run `npm run example:3d` for the live example or `npm run benchmark:3d` for its FPS gate.
312
+
289
313
  ## Roadmap
290
314
 
291
- See [ROADMAP.md](ROADMAP.md) for the full, numbered build order — 85 items, 74 shipped and
292
- eleven open (3D rendering broken into a nine-step build-up plus an engine choice, all
293
- logged at low priority; see that file for why). Each module ships in the order given there;
294
- later ones build on the
295
- modules before them.
315
+ See [ROADMAP.md](ROADMAP.md) for the full, numbered build order and implementation notes.
316
+ The optional 3D group is now implemented without changing the existing 2D entry point.
296
317
 
297
318
  ## Licence and provenance
298
319
 
299
- `mwg` is MPL-2.0. Its dependencies are permissive and compatible: PixiJS is MIT, rot.js is
300
- BSD-3-Clause.
320
+ `mwg` is MPL-2.0. Its dependencies are permissive and compatible: PixiJS and Babylon.js are
321
+ MIT, and rot.js is BSD-3-Clause.
301
322
 
302
323
  `tools/vendored/rgssad-wasm` is a build of the `rgssad-wasm` crate from
303
324
  <https://github.com/nathaniel-daniel/rgssad-rs>, used under its MIT licence
304
- option (© 2022 Nathaniel Daniel) — see `tools/vendored/rgssad-wasm/README.md`.
325
+ option (© 2022 Nathaniel Daniel); see `tools/vendored/rgssad-wasm/README.md`.
305
326
  It is a developer-side archive tool only, not part of the framework or any game.
306
327
 
307
328
  Shattered Pixel Dungeon (GPL-3, © Oleg Dolya and Evan Debenham), The Battle for Wesnoth
308
329
  (GPL-2.0-or-later, © the Battle for Wesnoth contributors), Flare's Empyrean Campaign
309
330
  (GPL-3, © the Flare project), and Dungeon Crawl Stone Soup (GPL-2.0, © the Dungeon Crawl
310
- Stone Soup development team) are referenced only as design studies — a list of what a
331
+ Stone Soup development team) are referenced only as design studies: a list of what a
311
332
  finished roguelike, a finished hex-based tactical game, a finished isometric one, and a
312
333
  finished branching dungeon each have to do. None of their code, assets, maps, unit
313
334
  definitions or text is included here, and `mwg` is not a derivative work of any of them.
314
335
 
315
336
  ADOM, RPG Maker, Pokémon, Ren'Py and Twine are likewise references for a *shape of game*, not
316
- sources of anything. `mwg` implements none of their file formats, reads none of their projects or save
317
- data, and includes none of their runtimes, assets, text or data tables — no type chart, no
318
- species list, no stat formula. Those are each game's own design work. Where a file format
319
- is needed, `mwg` uses Tiled's, which is open and documented.
337
+ sources of anything. `mwg` includes none of their runtimes, assets, text or data tables: no
338
+ type chart, no species list, no stat formula, no map, no line of dialogue. Those are each
339
+ game's own design work. A loader or writer for one of their file formats is a different
340
+ matter and is not off limits on its own: a format is a container, understood the same way
341
+ any published format is, by reading real files in it to learn its shape; what stays out is
342
+ the media that container holds, someone else's actual assets, text or data tables. Where a
343
+ format already exists and needs no reverse-engineering, `mwg` uses Tiled's, which is open
344
+ and documented.
320
345
 
321
346
  Game mechanics are not themselves protected, which is why a framework may offer "a turn
322
347
  queue ordered by speed" or "an effectiveness matrix". Particular numbers, names and
@@ -10,7 +10,7 @@ export function assignAppearances(table) {
10
10
  const pool = [...table.labels];
11
11
  for (let i = pool.length - 1; i > 0; i--) {
12
12
  const j = Random.int(0, i + 1);
13
- [pool[i], pool[j]] = [pool[i], pool[j]];
13
+ [pool[i], pool[j]] = [pool[j], pool[i]];
14
14
  }
15
15
  const out = new Map();
16
16
  table.kinds.forEach((kind, index) => out.set(kind, pool[index]));
@@ -0,0 +1,29 @@
1
+ import { StatBlock, type Modifier } from './StatBlock.ts';
2
+ /** "adjacent allies fight better while this unit is nearby" - the modifiers, not the radius */
3
+ export interface AuraDef {
4
+ name: string;
5
+ modifiers: readonly Omit<Modifier, 'source'>[];
6
+ }
7
+ /** anyone who might carry or receive an aura; `aura` absent or `null` means "carries none" */
8
+ export interface AuraParticipant {
9
+ stats: StatBlock;
10
+ aura?: AuraDef | null;
11
+ }
12
+ /**
13
+ * Continuously reapplies auras as who is adjacent to whom changes, unlike `battle.BattleHooks`
14
+ * (triggered once) or `actors.StatusEffect` (expires on a timer) - neither re-checks "is a
15
+ * qualifying unit still adjacent" on its own, which is exactly what a positional aura needs
16
+ * every time anyone moves.
17
+ *
18
+ * Call `update` once per relevant tick with the full roster and an adjacency test; "adjacent"
19
+ * is deliberately left to the caller (square grid, hex grid, board-tactics neighbours all mean
20
+ * something different) rather than assumed here. Each call diffs against who was affected by
21
+ * each carrier last time: a unit that just became adjacent gets the carrier's modifiers added,
22
+ * one that just left loses exactly those modifiers, and one that stays adjacent is left alone
23
+ * rather than having its modifiers reapplied on every tick.
24
+ */
25
+ export declare class AuraField {
26
+ private affected;
27
+ update(participants: readonly AuraParticipant[], isAdjacent: (a: AuraParticipant, b: AuraParticipant) => boolean): void;
28
+ }
29
+ //# sourceMappingURL=Aura.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Aura.d.ts","sourceRoot":"","sources":["../../src/actors/Aura.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1D,+FAA+F;AAC/F,MAAM,WAAW,OAAO;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,SAAS,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;CAC/C;AAED,8FAA8F;AAC9F,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CACtB;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,SAAS;IAGrB,OAAO,CAAC,QAAQ,CAA4D;IAE5E,MAAM,CAAC,YAAY,EAAE,SAAS,eAAe,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,eAAe,KAAK,OAAO,GAAG,IAAI,CA6CtH;CACD"}
@@ -0,0 +1,69 @@
1
+ import { StatBlock } from './StatBlock.js';
2
+ /**
3
+ * Continuously reapplies auras as who is adjacent to whom changes, unlike `battle.BattleHooks`
4
+ * (triggered once) or `actors.StatusEffect` (expires on a timer) - neither re-checks "is a
5
+ * qualifying unit still adjacent" on its own, which is exactly what a positional aura needs
6
+ * every time anyone moves.
7
+ *
8
+ * Call `update` once per relevant tick with the full roster and an adjacency test; "adjacent"
9
+ * is deliberately left to the caller (square grid, hex grid, board-tactics neighbours all mean
10
+ * something different) rather than assumed here. Each call diffs against who was affected by
11
+ * each carrier last time: a unit that just became adjacent gets the carrier's modifiers added,
12
+ * one that just left loses exactly those modifiers, and one that stays adjacent is left alone
13
+ * rather than having its modifiers reapplied on every tick.
14
+ */
15
+ export class AuraField {
16
+ constructor() {
17
+ //per carrier, which participants currently carry that carrier's modifiers and under
18
+ //what source symbol - a unit inside two carriers' auras at once tracks both independently
19
+ this.affected = new Map();
20
+ }
21
+ update(participants, isAdjacent) {
22
+ const roster = new Set(participants);
23
+ const carriers = participants.filter((p) => !!p.aura);
24
+ const carrierSet = new Set(carriers);
25
+ for (const carrier of carriers) {
26
+ let currentlyAffected = this.affected.get(carrier);
27
+ if (!currentlyAffected) {
28
+ currentlyAffected = new Map();
29
+ this.affected.set(carrier, currentlyAffected);
30
+ }
31
+ for (const other of participants) {
32
+ if (other === carrier)
33
+ continue;
34
+ const adjacent = isAdjacent(carrier, other);
35
+ const source = currentlyAffected.get(other);
36
+ if (adjacent && !source) {
37
+ const newSource = Symbol(`aura:${carrier.aura.name}`);
38
+ for (const modifier of carrier.aura.modifiers)
39
+ other.stats.addModifier({ ...modifier, source: newSource });
40
+ currentlyAffected.set(other, newSource);
41
+ }
42
+ else if (!adjacent && source) {
43
+ other.stats.removeModifiersFrom(source);
44
+ currentlyAffected.delete(other);
45
+ }
46
+ }
47
+ //a target affected last call but absent from this call's roster entirely (died,
48
+ //was removed) is never visited by the loop above, since that only walks
49
+ //`participants` - left alone, its modifiers and its own strong reference here
50
+ //would both persist forever
51
+ for (const [other, source] of [...currentlyAffected]) {
52
+ if (roster.has(other))
53
+ continue;
54
+ other.stats.removeModifiersFrom(source);
55
+ currentlyAffected.delete(other);
56
+ }
57
+ }
58
+ //a carrier that died, lost its aura, or simply is not in this call's roster any more
59
+ //stops affecting anyone it was still affecting, rather than leaving stale modifiers
60
+ for (const [carrier, currentlyAffected] of [...this.affected]) {
61
+ if (carrierSet.has(carrier))
62
+ continue;
63
+ for (const [other, source] of currentlyAffected)
64
+ other.stats.removeModifiersFrom(source);
65
+ this.affected.delete(carrier);
66
+ }
67
+ }
68
+ }
69
+ //# sourceMappingURL=Aura.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Aura.js","sourceRoot":"","sources":["../../src/actors/Aura.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAiB,MAAM,gBAAgB,CAAC;AAc1D;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,SAAS;IAAtB;QACC,oFAAoF;QACpF,0FAA0F;QAClF,aAAQ,GAAG,IAAI,GAAG,EAAiD,CAAC;IAgD7E,CAAC;IA9CA,MAAM,CAAC,YAAwC,EAAE,UAA+D;QAC/G,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;QACrC,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAA4C,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAChG,MAAM,UAAU,GAAG,IAAI,GAAG,CAAkB,QAAQ,CAAC,CAAC;QAEtD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAChC,IAAI,iBAAiB,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACnD,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBACxB,iBAAiB,GAAG,IAAI,GAAG,EAAE,CAAC;gBAC9B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;YAC/C,CAAC;YAED,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;gBAClC,IAAI,KAAK,KAAK,OAAO;oBAAE,SAAS;gBAChC,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;gBAC5C,MAAM,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBAE5C,IAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;oBACzB,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;oBACtD,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,SAAS;wBAAE,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,GAAG,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;oBAC3G,iBAAiB,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;gBACzC,CAAC;qBAAM,IAAI,CAAC,QAAQ,IAAI,MAAM,EAAE,CAAC;oBAChC,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;oBACxC,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACjC,CAAC;YACF,CAAC;YAED,gFAAgF;YAChF,wEAAwE;YACxE,8EAA8E;YAC9E,4BAA4B;YAC5B,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,EAAE,CAAC;gBACtD,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;oBAAE,SAAS;gBAChC,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;gBACxC,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACjC,CAAC;QACF,CAAC;QAED,qFAAqF;QACrF,oFAAoF;QACpF,KAAK,MAAM,CAAC,OAAO,EAAE,iBAAiB,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/D,IAAI,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,SAAS;YACtC,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,iBAAiB;gBAAE,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;YACzF,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;IACF,CAAC;CACD"}
@@ -0,0 +1,27 @@
1
+ import type { EffectClock } from './StatusEffect.ts';
2
+ export interface ItemStatusEffectOptions<T> {
3
+ /** fields applied to the item for the duration, restored to their prior value on expiry */
4
+ fields: Partial<T>;
5
+ /** turns until the fields are restored */
6
+ duration: number;
7
+ /** called once per turn the effect is active, such as a poison coating's own tick */
8
+ tick?: (turn: number) => void;
9
+ }
10
+ /** ends an `ItemStatusEffect` early, restoring the item's fields immediately rather than at expiry */
11
+ export interface ItemStatusEffectHandle {
12
+ cancel(): void;
13
+ }
14
+ /**
15
+ * A temporary status on an item itself - a weapon coated in poison for a fixed number of
16
+ * turns, a shield reinforced until it wears off, a ring blessed only "for this floor" - the
17
+ * item-targeted counterpart to `applyStatusEffect`, which only ever targets a `StatBlock`.
18
+ * Today's item-level fields (`cursed`, `blessed`, `level`, `affix`) are all permanent until
19
+ * a game explicitly changes them; this is what expires one instead.
20
+ *
21
+ * Applies `fields` onto `item`, remembering each field's prior value (not merely deleting
22
+ * it), and restores exactly that value once `clock` expires the effect or `cancel()` is
23
+ * called early - the same "never has to remember to remove what it added" guarantee
24
+ * `applyStatusEffect` already gives a `StatBlock`.
25
+ */
26
+ export declare function applyItemStatusEffect<T extends object>(item: T, clock: EffectClock, options: ItemStatusEffectOptions<T>): ItemStatusEffectHandle;
27
+ //# sourceMappingURL=ItemStatusEffect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ItemStatusEffect.d.ts","sourceRoot":"","sources":["../../src/actors/ItemStatusEffect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,uBAAuB,CAAC,CAAC;IACzC,2FAA2F;IAC3F,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAEnB,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IAEjB,qFAAqF;IACrF,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,sGAAsG;AACtG,MAAM,WAAW,sBAAsB;IACtC,MAAM,IAAI,IAAI,CAAC;CACf;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,MAAM,EACrD,IAAI,EAAE,CAAC,EACP,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,GACjC,sBAAsB,CAuBxB"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * A temporary status on an item itself - a weapon coated in poison for a fixed number of
3
+ * turns, a shield reinforced until it wears off, a ring blessed only "for this floor" - the
4
+ * item-targeted counterpart to `applyStatusEffect`, which only ever targets a `StatBlock`.
5
+ * Today's item-level fields (`cursed`, `blessed`, `level`, `affix`) are all permanent until
6
+ * a game explicitly changes them; this is what expires one instead.
7
+ *
8
+ * Applies `fields` onto `item`, remembering each field's prior value (not merely deleting
9
+ * it), and restores exactly that value once `clock` expires the effect or `cancel()` is
10
+ * called early - the same "never has to remember to remove what it added" guarantee
11
+ * `applyStatusEffect` already gives a `StatBlock`.
12
+ */
13
+ export function applyItemStatusEffect(item, clock, options) {
14
+ const previous = new Map();
15
+ for (const key of Object.keys(options.fields)) {
16
+ previous.set(key, item[key]);
17
+ item[key] = options.fields[key];
18
+ }
19
+ const restore = () => {
20
+ for (const [key, value] of previous)
21
+ item[key] = value;
22
+ };
23
+ const id = clock.add({
24
+ duration: options.duration,
25
+ tick: (turn) => options.tick?.(turn),
26
+ onExpire: restore,
27
+ });
28
+ return {
29
+ cancel: () => {
30
+ clock.remove(id);
31
+ restore();
32
+ },
33
+ };
34
+ }
35
+ //# sourceMappingURL=ItemStatusEffect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ItemStatusEffect.js","sourceRoot":"","sources":["../../src/actors/ItemStatusEffect.ts"],"names":[],"mappings":"AAkBA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qBAAqB,CACpC,IAAO,EACP,KAAkB,EAClB,OAAmC;IAEnC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;IAChD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAgB,EAAE,CAAC;QAC9D,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAe,CAAC;IAC/C,CAAC;IAED,MAAM,OAAO,GAAG,GAAS,EAAE;QAC1B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,QAAQ;YAAE,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACxD,CAAC,CAAC;IAEF,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC;QACpB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC;QACpC,QAAQ,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,OAAO;QACN,MAAM,EAAE,GAAG,EAAE;YACZ,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACjB,OAAO,EAAE,CAAC;QACX,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"Resource.d.ts","sourceRoot":"","sources":["../../src/actors/Resource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CACf;AAYD,yFAAyF;AACzF,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,GAAG,SAAS,YAAY,EAAE,GAAG,OAAO,CAEjG;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,GAAG,SAAS,YAAY,EAAE,GAAG,OAAO,CAK7F"}
1
+ {"version":3,"file":"Resource.d.ts","sourceRoot":"","sources":["../../src/actors/Resource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CACf;AAoBD,yFAAyF;AACzF,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,GAAG,SAAS,YAAY,EAAE,GAAG,OAAO,CAEjG;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,GAAG,SAAS,YAAY,EAAE,GAAG,OAAO,CAK7F"}
@@ -7,9 +7,17 @@ function all(cost) {
7
7
  }
8
8
  return list;
9
9
  }
10
+ //several costs on the same stat (two effects both drawing mana, say) must be checked
11
+ //against their combined total, not each independently against the same starting pool
12
+ function totals(cost) {
13
+ const out = new Map();
14
+ for (const c of cost)
15
+ out.set(c.stat, (out.get(c.stat) ?? 0) + c.amount);
16
+ return out;
17
+ }
10
18
  /** True when every cost in `cost` is currently affordable, without spending anything. */
11
19
  export function canAfford(stats, cost) {
12
- return all(cost).every((c) => stats.base(c.stat) >= c.amount);
20
+ return [...totals(all(cost))].every(([stat, amount]) => stats.base(stat) >= amount);
13
21
  }
14
22
  /**
15
23
  * Spends every cost in `cost` at once, the way `craft` resolves a recipe.
@@ -18,11 +26,11 @@ export function canAfford(stats, cost) {
18
26
  * this returns false. A zero cost is always affordable and deducts nothing.
19
27
  */
20
28
  export function spend(stats, cost) {
21
- const list = all(cost);
22
- if (!list.every((c) => stats.base(c.stat) >= c.amount))
29
+ const combined = totals(all(cost));
30
+ if (![...combined].every(([stat, amount]) => stats.base(stat) >= amount))
23
31
  return false;
24
- for (const c of list)
25
- stats.setBase(c.stat, stats.base(c.stat) - c.amount);
32
+ for (const [stat, amount] of combined)
33
+ stats.setBase(stat, stats.base(stat) - amount);
26
34
  return true;
27
35
  }
28
36
  //# sourceMappingURL=Resource.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Resource.js","sourceRoot":"","sources":["../../src/actors/Resource.ts"],"names":[],"mappings":"AAeA,SAAS,GAAG,CAAC,IAA4C;IACxD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACjD,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACtB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACjG,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,SAAS,CAAC,KAAgB,EAAE,IAA4C;IACvF,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,KAAK,CAAC,KAAgB,EAAE,IAA4C;IACnF,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IACrE,KAAK,MAAM,CAAC,IAAI,IAAI;QAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;IAC3E,OAAO,IAAI,CAAC;AACb,CAAC"}
1
+ {"version":3,"file":"Resource.js","sourceRoot":"","sources":["../../src/actors/Resource.ts"],"names":[],"mappings":"AAeA,SAAS,GAAG,CAAC,IAA4C;IACxD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACjD,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACtB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACjG,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED,qFAAqF;AACrF,qFAAqF;AACrF,SAAS,MAAM,CAAC,IAA6B;IAC5C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,KAAK,MAAM,CAAC,IAAI,IAAI;QAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;IACzE,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,SAAS,CAAC,KAAgB,EAAE,IAA4C;IACvF,OAAO,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC;AACrF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,KAAK,CAAC,KAAgB,EAAE,IAA4C;IACnF,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACnC,IAAI,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IACvF,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,QAAQ;QAAE,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;IACtF,OAAO,IAAI,CAAC;AACb,CAAC"}
@@ -0,0 +1,32 @@
1
+ /** A thresholded bond level between two named units. */
2
+ export interface SupportLevel {
3
+ id: string;
4
+ threshold: number;
5
+ /** Optional game-facing bonus or dialogue key unlocked at this level. */
6
+ bonus?: string;
7
+ }
8
+ export interface SupportChange {
9
+ a: string;
10
+ b: string;
11
+ points: number;
12
+ previousLevel: SupportLevel | null;
13
+ level: SupportLevel | null;
14
+ }
15
+ export interface SupportSave {
16
+ pairs: Array<[string, string, number]>;
17
+ }
18
+ /**
19
+ * Stores persistent progress for a relationship between a specific pair of units.
20
+ * Pair order is ignored, so `add('alice', 'bob', 1)` and the reverse address one bond.
21
+ */
22
+ export declare class SupportLedger {
23
+ private levels;
24
+ private points;
25
+ constructor(levels: readonly SupportLevel[]);
26
+ get(a: string, b: string): number;
27
+ level(a: string, b: string): SupportLevel | null;
28
+ add(a: string, b: string, amount: number): SupportChange;
29
+ save(): SupportSave;
30
+ static restore(levels: readonly SupportLevel[], data: SupportSave): SupportLedger;
31
+ }
32
+ //# sourceMappingURL=Support.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Support.d.ts","sourceRoot":"","sources":["../../src/actors/Support.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC7B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACvC;AAED;;;GAGG;AACH,qBAAa,aAAa;IACzB,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,MAAM,CAA6B;IAE3C,YAAY,MAAM,EAAE,SAAS,YAAY,EAAE,EAK1C;IAED,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhC;IAED,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAQ/C;IAED,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAQvD;IAED,IAAI,IAAI,WAAW,CAMlB;IAED,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,EAAE,IAAI,EAAE,WAAW,GAAG,aAAa,CAShF;CACD"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Stores persistent progress for a relationship between a specific pair of units.
3
+ * Pair order is ignored, so `add('alice', 'bob', 1)` and the reverse address one bond.
4
+ */
5
+ export class SupportLedger {
6
+ constructor(levels) {
7
+ this.points = new Map();
8
+ if (levels.some((level) => !level.id || !Number.isFinite(level.threshold) || level.threshold < 0)) {
9
+ throw new Error('support levels need an id and a non-negative threshold');
10
+ }
11
+ this.levels = [...levels].sort((a, b) => a.threshold - b.threshold);
12
+ }
13
+ get(a, b) {
14
+ return this.points.get(pairKey(a, b)) ?? 0;
15
+ }
16
+ level(a, b) {
17
+ const points = this.get(a, b);
18
+ let found = null;
19
+ for (const level of this.levels) {
20
+ if (level.threshold > points)
21
+ break;
22
+ found = level;
23
+ }
24
+ return found;
25
+ }
26
+ add(a, b, amount) {
27
+ if (!a || !b || a === b)
28
+ throw new Error('support needs two different named units');
29
+ if (!Number.isFinite(amount))
30
+ throw new Error(`support amount must be finite, got ${amount}`);
31
+ const previousLevel = this.level(a, b);
32
+ const key = pairKey(a, b);
33
+ const points = Math.max(0, (this.points.get(key) ?? 0) + amount);
34
+ this.points.set(key, points);
35
+ return { a, b, points, previousLevel, level: this.level(a, b) };
36
+ }
37
+ save() {
38
+ const pairs = [...this.points].map(([key, points]) => {
39
+ const [a, b] = key.split('\u0000');
40
+ return [a, b, points];
41
+ });
42
+ return { pairs };
43
+ }
44
+ static restore(levels, data) {
45
+ const ledger = new SupportLedger(levels);
46
+ for (const [a, b, points] of data.pairs) {
47
+ if (!a || !b || a === b || !Number.isFinite(points) || points < 0)
48
+ throw new Error('invalid support save data');
49
+ const key = pairKey(a, b);
50
+ if (ledger.points.has(key))
51
+ throw new Error(`invalid support save data: duplicate pair "${a}" / "${b}"`);
52
+ ledger.points.set(key, points);
53
+ }
54
+ return ledger;
55
+ }
56
+ }
57
+ function pairKey(a, b) {
58
+ return [a, b].sort().join('\u0000');
59
+ }
60
+ //# sourceMappingURL=Support.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Support.js","sourceRoot":"","sources":["../../src/actors/Support.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { StatBlock, type Modifier } from './StatBlock.ts';
2
+ /**
3
+ * One entry in a shared pool a creature or unit can draw from at creation - "born strong",
4
+ * "nocturnal", "unlucky". Distinct from `actors.Affix` (item-side, one per item, weight-picked
5
+ * at generation) and `StatusEffect` (temporary, expires): a trait is permanent once assigned,
6
+ * never chosen by the player, and belongs to the creature rather than anything it carries.
7
+ */
8
+ export interface TraitDef {
9
+ name: string;
10
+ modifiers: readonly Omit<Modifier, 'source'>[];
11
+ /** shown in a character sheet; `mwg` never reads this itself */
12
+ description?: string;
13
+ }
14
+ /** an assigned trait's modifiers can be found again later via `removeModifiersFrom` */
15
+ export interface AssignedTrait {
16
+ trait: TraitDef;
17
+ source: symbol;
18
+ }
19
+ /**
20
+ * Draws `count` distinct traits at random from `pool` and applies their modifiers to `stats`
21
+ * permanently - no clock, no trigger, nothing removes them automatically. Each trait gets its
22
+ * own `source` symbol so a game that genuinely needs to strip one later still can, via
23
+ * `StatBlock.removeModifiersFrom`, even though nothing here ever calls that itself.
24
+ *
25
+ * @param count clamped to `pool.length` - asking for more traits than exist in the pool
26
+ * assigns every one of them rather than throwing
27
+ */
28
+ export declare function assignTraits(stats: StatBlock, pool: readonly TraitDef[], count: number): AssignedTrait[];
29
+ //# sourceMappingURL=Traits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Traits.d.ts","sourceRoot":"","sources":["../../src/actors/Traits.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1D;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,SAAS,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;IAE/C,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,uFAAuF;AACvF,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,QAAQ,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,EAAE,CAOxG"}
@@ -0,0 +1,21 @@
1
+ import { StatBlock } from './StatBlock.js';
2
+ import * as Random from '../core/Random.js';
3
+ /**
4
+ * Draws `count` distinct traits at random from `pool` and applies their modifiers to `stats`
5
+ * permanently - no clock, no trigger, nothing removes them automatically. Each trait gets its
6
+ * own `source` symbol so a game that genuinely needs to strip one later still can, via
7
+ * `StatBlock.removeModifiersFrom`, even though nothing here ever calls that itself.
8
+ *
9
+ * @param count clamped to `pool.length` - asking for more traits than exist in the pool
10
+ * assigns every one of them rather than throwing
11
+ */
12
+ export function assignTraits(stats, pool, count) {
13
+ const picked = Random.shuffle([...pool]).slice(0, Math.min(count, pool.length));
14
+ return picked.map((trait) => {
15
+ const source = Symbol(`trait:${trait.name}`);
16
+ for (const modifier of trait.modifiers)
17
+ stats.addModifier({ ...modifier, source });
18
+ return { trait, source };
19
+ });
20
+ }
21
+ //# sourceMappingURL=Traits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Traits.js","sourceRoot":"","sources":["../../src/actors/Traits.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAiB,MAAM,gBAAgB,CAAC;AAC1D,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAsB5C;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,KAAgB,EAAE,IAAyB,EAAE,KAAa;IACtF,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAChF,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC3B,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7C,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,SAAS;YAAE,KAAK,CAAC,WAAW,CAAC,EAAE,GAAG,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QACnF,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC,CAAC,CAAC;AACJ,CAAC"}