@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.
- package/README.md +57 -32
- package/dist/actors/Appearances.js +1 -1
- package/dist/actors/Aura.d.ts +29 -0
- package/dist/actors/Aura.d.ts.map +1 -0
- package/dist/actors/Aura.js +69 -0
- package/dist/actors/Aura.js.map +1 -0
- package/dist/actors/ItemStatusEffect.d.ts +27 -0
- package/dist/actors/ItemStatusEffect.d.ts.map +1 -0
- package/dist/actors/ItemStatusEffect.js +35 -0
- package/dist/actors/ItemStatusEffect.js.map +1 -0
- package/dist/actors/Resource.d.ts.map +1 -1
- package/dist/actors/Resource.js +13 -5
- package/dist/actors/Resource.js.map +1 -1
- package/dist/actors/Support.d.ts +32 -0
- package/dist/actors/Support.d.ts.map +1 -0
- package/dist/actors/Support.js +60 -0
- package/dist/actors/Support.js.map +1 -0
- package/dist/actors/Traits.d.ts +29 -0
- package/dist/actors/Traits.d.ts.map +1 -0
- package/dist/actors/Traits.js +21 -0
- package/dist/actors/Traits.js.map +1 -0
- package/dist/actors/craft.d.ts.map +1 -1
- package/dist/actors/craft.js +6 -2
- package/dist/actors/craft.js.map +1 -1
- package/dist/actors/index.d.ts +8 -0
- package/dist/actors/index.d.ts.map +1 -1
- package/dist/actors/index.js +4 -0
- package/dist/actors/index.js.map +1 -1
- package/dist/assets/Streaming.d.ts +40 -0
- package/dist/assets/Streaming.d.ts.map +1 -0
- package/dist/assets/Streaming.js +79 -0
- package/dist/assets/Streaming.js.map +1 -0
- package/dist/assets/index.d.ts +14 -1
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/assets/index.js +19 -0
- package/dist/assets/index.js.map +1 -1
- package/dist/audio/Captions.d.ts +15 -0
- package/dist/audio/Captions.d.ts.map +1 -0
- package/dist/audio/Captions.js +4 -0
- package/dist/audio/Captions.js.map +1 -0
- package/dist/audio/Midi.d.ts +75 -0
- package/dist/audio/Midi.d.ts.map +1 -0
- package/dist/audio/Midi.js +186 -0
- package/dist/audio/Midi.js.map +1 -0
- package/dist/audio/Orchestrator.d.ts +40 -0
- package/dist/audio/Orchestrator.d.ts.map +1 -0
- package/dist/audio/Orchestrator.js +48 -0
- package/dist/audio/Orchestrator.js.map +1 -0
- package/dist/audio/Sound.d.ts +3 -0
- package/dist/audio/Sound.d.ts.map +1 -1
- package/dist/audio/Sound.js +4 -0
- package/dist/audio/Sound.js.map +1 -1
- package/dist/audio/Synth.d.ts +35 -0
- package/dist/audio/Synth.d.ts.map +1 -0
- package/dist/audio/Synth.js +96 -0
- package/dist/audio/Synth.js.map +1 -0
- package/dist/audio/index.d.ts +8 -0
- package/dist/audio/index.d.ts.map +1 -1
- package/dist/audio/index.js +4 -0
- package/dist/audio/index.js.map +1 -1
- package/dist/battle/Party.d.ts.map +1 -1
- package/dist/battle/Party.js +2 -0
- package/dist/battle/Party.js.map +1 -1
- package/dist/board/Army.d.ts +17 -0
- package/dist/board/Army.d.ts.map +1 -0
- package/dist/board/Army.js +48 -0
- package/dist/board/Army.js.map +1 -0
- package/dist/board/Classics.d.ts.map +1 -1
- package/dist/board/Classics.js +14 -6
- package/dist/board/Classics.js.map +1 -1
- package/dist/board/FogOfWar.d.ts +23 -0
- package/dist/board/FogOfWar.d.ts.map +1 -0
- package/dist/board/FogOfWar.js +40 -0
- package/dist/board/FogOfWar.js.map +1 -0
- package/dist/board/Tactics.d.ts +2 -0
- package/dist/board/Tactics.d.ts.map +1 -1
- package/dist/board/Tactics.js +36 -8
- package/dist/board/Tactics.js.map +1 -1
- package/dist/board/index.d.ts +5 -1
- package/dist/board/index.d.ts.map +1 -1
- package/dist/board/index.js +3 -1
- package/dist/board/index.js.map +1 -1
- package/dist/core/Feedback.d.ts +23 -0
- package/dist/core/Feedback.d.ts.map +1 -0
- package/dist/core/Feedback.js +35 -0
- package/dist/core/Feedback.js.map +1 -0
- package/dist/core/Game.d.ts +27 -1
- package/dist/core/Game.d.ts.map +1 -1
- package/dist/core/Game.js +61 -5
- package/dist/core/Game.js.map +1 -1
- package/dist/core/Input.d.ts +59 -1
- package/dist/core/Input.d.ts.map +1 -1
- package/dist/core/Input.js +135 -0
- package/dist/core/Input.js.map +1 -1
- package/dist/core/Loading.d.ts +48 -0
- package/dist/core/Loading.d.ts.map +1 -0
- package/dist/core/Loading.js +102 -0
- package/dist/core/Loading.js.map +1 -0
- package/dist/core/News.d.ts +46 -0
- package/dist/core/News.d.ts.map +1 -0
- package/dist/core/News.js +79 -0
- package/dist/core/News.js.map +1 -0
- package/dist/core/PlayerInput.d.ts +38 -0
- package/dist/core/PlayerInput.d.ts.map +1 -0
- package/dist/core/PlayerInput.js +57 -0
- package/dist/core/PlayerInput.js.map +1 -0
- package/dist/core/Random.d.ts +2 -2
- package/dist/core/Random.js +2 -2
- package/dist/core/RunHistory.d.ts +43 -0
- package/dist/core/RunHistory.d.ts.map +1 -0
- package/dist/core/RunHistory.js +41 -0
- package/dist/core/RunHistory.js.map +1 -0
- package/dist/core/Sanitize.d.ts +56 -0
- package/dist/core/Sanitize.d.ts.map +1 -0
- package/dist/core/Sanitize.js +87 -0
- package/dist/core/Sanitize.js.map +1 -0
- package/dist/core/Save.d.ts +31 -7
- package/dist/core/Save.d.ts.map +1 -1
- package/dist/core/Save.js +56 -0
- package/dist/core/Save.js.map +1 -1
- package/dist/core/SaveSync.d.ts +26 -0
- package/dist/core/SaveSync.d.ts.map +1 -0
- package/dist/core/SaveSync.js +52 -0
- package/dist/core/SaveSync.js.map +1 -0
- package/dist/core/Scramble.d.ts +13 -0
- package/dist/core/Scramble.d.ts.map +1 -0
- package/dist/core/Scramble.js +44 -0
- package/dist/core/Scramble.js.map +1 -0
- package/dist/core/Session.d.ts +21 -0
- package/dist/core/Session.d.ts.map +1 -0
- package/dist/core/Session.js +11 -0
- package/dist/core/Session.js.map +1 -0
- package/dist/core/Spawner.d.ts +48 -0
- package/dist/core/Spawner.d.ts.map +1 -0
- package/dist/core/Spawner.js +65 -0
- package/dist/core/Spawner.js.map +1 -0
- package/dist/core/Tween.d.ts +25 -0
- package/dist/core/Tween.d.ts.map +1 -0
- package/dist/core/Tween.js +60 -0
- package/dist/core/Tween.js.map +1 -0
- package/dist/core/UndoHistory.d.ts +37 -0
- package/dist/core/UndoHistory.d.ts.map +1 -0
- package/dist/core/UndoHistory.js +49 -0
- package/dist/core/UndoHistory.js.map +1 -0
- package/dist/core/index.d.ts +22 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +12 -0
- package/dist/core/index.js.map +1 -1
- package/dist/i18n/Fluent.d.ts +7 -0
- package/dist/i18n/Fluent.d.ts.map +1 -0
- package/dist/i18n/Fluent.js +86 -0
- package/dist/i18n/Fluent.js.map +1 -0
- package/dist/i18n/index.d.ts +15 -1
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +17 -2
- package/dist/i18n/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mw_games.global.js +351 -79
- package/dist/render/Camera.d.ts +32 -4
- package/dist/render/Camera.d.ts.map +1 -1
- package/dist/render/Camera.js +58 -23
- package/dist/render/Camera.js.map +1 -1
- package/dist/render/Capabilities.d.ts +51 -0
- package/dist/render/Capabilities.d.ts.map +1 -0
- package/dist/render/Capabilities.js +56 -0
- package/dist/render/Capabilities.js.map +1 -0
- package/dist/render/ColorBlindness.d.ts +16 -0
- package/dist/render/ColorBlindness.d.ts.map +1 -0
- package/dist/render/ColorBlindness.js +41 -0
- package/dist/render/ColorBlindness.js.map +1 -0
- package/dist/render/ColorTransformBatcher.d.ts +9 -0
- package/dist/render/ColorTransformBatcher.d.ts.map +1 -1
- package/dist/render/ColorTransformBatcher.js +131 -19
- package/dist/render/ColorTransformBatcher.js.map +1 -1
- package/dist/render/Minimap.d.ts +52 -0
- package/dist/render/Minimap.d.ts.map +1 -0
- package/dist/render/Minimap.js +95 -0
- package/dist/render/Minimap.js.map +1 -0
- package/dist/render/TileMap.d.ts +2 -2
- package/dist/render/TileMap.d.ts.map +1 -1
- package/dist/render/TileMap.js +12 -12
- package/dist/render/TileMap.js.map +1 -1
- package/dist/render/TintedSprite.d.ts +1 -8
- package/dist/render/TintedSprite.d.ts.map +1 -1
- package/dist/render/TintedSprite.js +3 -111
- package/dist/render/TintedSprite.js.map +1 -1
- package/dist/render/Viewport.d.ts +46 -0
- package/dist/render/Viewport.d.ts.map +1 -0
- package/dist/render/Viewport.js +52 -0
- package/dist/render/Viewport.js.map +1 -0
- package/dist/render/index.d.ts +9 -1
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/index.js +5 -1
- package/dist/render/index.js.map +1 -1
- package/dist/roguelike/FieldOfView.d.ts +1 -1
- package/dist/roguelike/Level.d.ts +2 -2
- package/dist/roguelike/Level.d.ts.map +1 -1
- package/dist/roguelike/Level.js +1 -1
- package/dist/roguelike/Level.js.map +1 -1
- package/dist/roguelike/Pathfinder.d.ts +3 -3
- package/dist/roguelike/Pathfinder.js +2 -2
- package/dist/roguelike/Scheduler.d.ts +1 -1
- package/dist/roguelike/Scheduler.js +1 -1
- package/dist/roguelike/Stealth.d.ts +34 -0
- package/dist/roguelike/Stealth.d.ts.map +1 -0
- package/dist/roguelike/Stealth.js +35 -0
- package/dist/roguelike/Stealth.js.map +1 -0
- package/dist/roguelike/generate.d.ts +3 -3
- package/dist/roguelike/generate.js +1 -1
- package/dist/roguelike/generate.js.map +1 -1
- package/dist/roguelike/index.d.ts +2 -0
- package/dist/roguelike/index.d.ts.map +1 -1
- package/dist/roguelike/index.js +1 -0
- package/dist/roguelike/index.js.map +1 -1
- package/dist/rpg/Collision.d.ts +42 -0
- package/dist/rpg/Collision.d.ts.map +1 -0
- package/dist/rpg/Collision.js +77 -0
- package/dist/rpg/Collision.js.map +1 -0
- package/dist/rpg/FreeMover.d.ts +45 -0
- package/dist/rpg/FreeMover.d.ts.map +1 -0
- package/dist/rpg/FreeMover.js +68 -0
- package/dist/rpg/FreeMover.js.map +1 -0
- package/dist/rpg/Marshal.d.ts +65 -0
- package/dist/rpg/Marshal.d.ts.map +1 -0
- package/dist/rpg/Marshal.js +393 -0
- package/dist/rpg/Marshal.js.map +1 -0
- package/dist/rpg/Quest.d.ts +41 -0
- package/dist/rpg/Quest.d.ts.map +1 -1
- package/dist/rpg/Quest.js +39 -6
- package/dist/rpg/Quest.js.map +1 -1
- package/dist/rpg/index.d.ts +7 -1
- package/dist/rpg/index.d.ts.map +1 -1
- package/dist/rpg/index.js +3 -0
- package/dist/rpg/index.js.map +1 -1
- package/dist/stage/DialogueStage.d.ts +2 -3
- package/dist/stage/DialogueStage.d.ts.map +1 -1
- package/dist/stage/DialogueStage.js +21 -30
- package/dist/stage/DialogueStage.js.map +1 -1
- package/dist/stage/index.d.ts +1 -1
- package/dist/stage/index.d.ts.map +1 -1
- package/dist/stage/script.d.ts +32 -3
- package/dist/stage/script.d.ts.map +1 -1
- package/dist/stage/script.js +0 -0
- package/dist/stage/script.js.map +1 -1
- package/dist/three-d/Character3D.d.ts +48 -0
- package/dist/three-d/Character3D.d.ts.map +1 -0
- package/dist/three-d/Character3D.js +96 -0
- package/dist/three-d/Character3D.js.map +1 -0
- package/dist/three-d/Engine3D.d.ts +23 -0
- package/dist/three-d/Engine3D.d.ts.map +1 -0
- package/dist/three-d/Engine3D.js +45 -0
- package/dist/three-d/Engine3D.js.map +1 -0
- package/dist/three-d/Grid.d.ts +14 -0
- package/dist/three-d/Grid.d.ts.map +1 -0
- package/dist/three-d/Grid.js +13 -0
- package/dist/three-d/Grid.js.map +1 -0
- package/dist/three-d/Heightmap.d.ts +35 -0
- package/dist/three-d/Heightmap.d.ts.map +1 -0
- package/dist/three-d/Heightmap.js +29 -0
- package/dist/three-d/Heightmap.js.map +1 -0
- package/dist/three-d/Models.d.ts +12 -0
- package/dist/three-d/Models.d.ts.map +1 -0
- package/dist/three-d/Models.js +14 -0
- package/dist/three-d/Models.js.map +1 -0
- package/dist/three-d/TileGrid3D.d.ts +21 -0
- package/dist/three-d/TileGrid3D.d.ts.map +1 -0
- package/dist/three-d/TileGrid3D.js +65 -0
- package/dist/three-d/TileGrid3D.js.map +1 -0
- package/dist/three-d/Vox.d.ts +24 -0
- package/dist/three-d/Vox.d.ts.map +1 -0
- package/dist/three-d/Vox.js +93 -0
- package/dist/three-d/Vox.js.map +1 -0
- package/dist/three-d/index.d.ts +13 -0
- package/dist/three-d/index.d.ts.map +1 -0
- package/dist/three-d/index.js +7 -0
- package/dist/three-d/index.js.map +1 -0
- package/dist/ui/Bar.d.ts +49 -0
- package/dist/ui/Bar.d.ts.map +1 -0
- package/dist/ui/Bar.js +70 -0
- package/dist/ui/Bar.js.map +1 -0
- package/dist/ui/BitmapLabel.d.ts +42 -0
- package/dist/ui/BitmapLabel.d.ts.map +1 -0
- package/dist/ui/BitmapLabel.js +59 -0
- package/dist/ui/BitmapLabel.js.map +1 -0
- package/dist/ui/Button.d.ts +52 -0
- package/dist/ui/Button.d.ts.map +1 -0
- package/dist/ui/Button.js +147 -0
- package/dist/ui/Button.js.map +1 -0
- package/dist/ui/FloatingText.d.ts +28 -0
- package/dist/ui/FloatingText.d.ts.map +1 -0
- package/dist/ui/FloatingText.js +39 -0
- package/dist/ui/FloatingText.js.map +1 -0
- package/dist/ui/HelpScreen.d.ts +29 -0
- package/dist/ui/HelpScreen.d.ts.map +1 -0
- package/dist/ui/HelpScreen.js +39 -0
- package/dist/ui/HelpScreen.js.map +1 -0
- package/dist/ui/IconGrid.d.ts +8 -0
- package/dist/ui/IconGrid.d.ts.map +1 -1
- package/dist/ui/IconGrid.js +27 -1
- package/dist/ui/IconGrid.js.map +1 -1
- package/dist/ui/Label.d.ts +10 -1
- package/dist/ui/Label.d.ts.map +1 -1
- package/dist/ui/Label.js +25 -2
- package/dist/ui/Label.js.map +1 -1
- package/dist/ui/ListView.d.ts +12 -1
- package/dist/ui/ListView.d.ts.map +1 -1
- package/dist/ui/ListView.js +56 -2
- package/dist/ui/ListView.js.map +1 -1
- package/dist/ui/LoadingScreen.d.ts +31 -0
- package/dist/ui/LoadingScreen.d.ts.map +1 -0
- package/dist/ui/LoadingScreen.js +60 -0
- package/dist/ui/LoadingScreen.js.map +1 -0
- package/dist/ui/MessageBox.d.ts +27 -1
- package/dist/ui/MessageBox.d.ts.map +1 -1
- package/dist/ui/MessageBox.js +63 -14
- package/dist/ui/MessageBox.js.map +1 -1
- package/dist/ui/RebindScreen.d.ts +55 -0
- package/dist/ui/RebindScreen.d.ts.map +1 -0
- package/dist/ui/RebindScreen.js +114 -0
- package/dist/ui/RebindScreen.js.map +1 -0
- package/dist/ui/Toast.d.ts +44 -0
- package/dist/ui/Toast.d.ts.map +1 -0
- package/dist/ui/Toast.js +89 -0
- package/dist/ui/Toast.js.map +1 -0
- package/dist/ui/VerticalLabel.d.ts +5 -0
- package/dist/ui/VerticalLabel.d.ts.map +1 -1
- package/dist/ui/VerticalLabel.js +18 -7
- package/dist/ui/VerticalLabel.js.map +1 -1
- package/dist/ui/Window.d.ts +6 -0
- package/dist/ui/Window.d.ts.map +1 -1
- package/dist/ui/Window.js +16 -1
- package/dist/ui/Window.js.map +1 -1
- package/dist/ui/WindowStack.d.ts +1 -0
- package/dist/ui/WindowStack.d.ts.map +1 -1
- package/dist/ui/WindowStack.js +4 -1
- package/dist/ui/WindowStack.js.map +1 -1
- package/dist/ui/index.d.ts +17 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +9 -1
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/theme.d.ts +21 -0
- package/dist/ui/theme.d.ts.map +1 -1
- package/dist/ui/theme.js +42 -0
- package/dist/ui/theme.js.map +1 -1
- package/dist/version.d.ts +13 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +13 -0
- package/dist/version.js.map +1 -0
- package/dist/world/Environment.d.ts +35 -0
- package/dist/world/Environment.d.ts.map +1 -0
- package/dist/world/Environment.js +55 -0
- package/dist/world/Environment.js.map +1 -0
- package/dist/world/index.d.ts +2 -0
- package/dist/world/index.d.ts.map +1 -1
- package/dist/world/index.js +1 -0
- package/dist/world/index.js.map +1 -1
- 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
|
|
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/)
|
|
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.**
|
|
13
|
-
>
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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**
|
|
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
|
|
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
|
|
292
|
-
|
|
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
|
|
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)
|
|
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
|
|
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`
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
is
|
|
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[
|
|
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;
|
|
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"}
|
package/dist/actors/Resource.js
CHANGED
|
@@ -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((
|
|
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
|
|
22
|
-
if (!
|
|
29
|
+
const combined = totals(all(cost));
|
|
30
|
+
if (![...combined].every(([stat, amount]) => stats.base(stat) >= amount))
|
|
23
31
|
return false;
|
|
24
|
-
for (const
|
|
25
|
-
stats.setBase(
|
|
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,
|
|
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"}
|