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