@datamoc/mw_games 0.7.6 → 0.7.7
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 +270 -0
- 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/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 +232 -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/score.d.ts +126 -0
- package/dist/ai/score.d.ts.map +1 -0
- package/dist/ai/score.js +121 -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/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/core/Achievements.d.ts +40 -7
- package/dist/core/Achievements.d.ts.map +1 -1
- package/dist/core/Achievements.js +51 -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/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 +79 -0
- package/dist/mwl/carryover.d.ts.map +1 -0
- package/dist/mwl/carryover.js +63 -0
- package/dist/mwl/carryover.js.map +1 -0
- package/dist/mwl/content.d.ts +10 -0
- package/dist/mwl/content.d.ts.map +1 -1
- package/dist/mwl/content.js +8 -0
- package/dist/mwl/content.js.map +1 -1
- package/dist/mwl/hooks.d.ts +6 -5
- package/dist/mwl/hooks.d.ts.map +1 -1
- package/dist/mwl/hooks.js.map +1 -1
- package/dist/mwl/index.d.ts +8 -2
- package/dist/mwl/index.d.ts.map +1 -1
- package/dist/mwl/index.js +4 -1
- package/dist/mwl/index.js.map +1 -1
- package/dist/mwl/runtime.d.ts +140 -7
- package/dist/mwl/runtime.d.ts.map +1 -1
- package/dist/mwl/runtime.js +427 -33
- package/dist/mwl/runtime.js.map +1 -1
- package/dist/mwl/schema.d.ts.map +1 -1
- package/dist/mwl/schema.js +97 -18
- package/dist/mwl/schema.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/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/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 +438 -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/index.d.ts +13 -3
- package/dist/two-d/render/index.d.ts.map +1 -1
- package/dist/two-d/render/index.js +6 -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/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 +27 -3
- package/dist/two-d/ui/index.d.ts.map +1 -1
- package/dist/two-d/ui/index.js +14 -2
- package/dist/two-d/ui/index.js.map +1 -1
- package/dist/two-d/ui/markup.d.ts +127 -0
- package/dist/two-d/ui/markup.d.ts.map +1 -0
- package/dist/two-d/ui/markup.js +322 -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
|
@@ -3,9 +3,17 @@ import type { Step } from './Pathfinder.ts';
|
|
|
3
3
|
export interface BeamDamageContext {
|
|
4
4
|
cell: Step;
|
|
5
5
|
step: number;
|
|
6
|
-
/** the number of cells remaining after this one */
|
|
6
|
+
/** the number of cells remaining after this one, counting only fronts already resolved */
|
|
7
7
|
remaining: number;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* What ends a beam cell, as one explicit choice rather than a combination of flags:
|
|
11
|
+
*
|
|
12
|
+
* - `'terrain'` (the default): outside the map or opaque terrain stops it, the ordinary beam.
|
|
13
|
+
* - `'none'`: it passes through terrain; only `isBlocked` (when given) can stop it.
|
|
14
|
+
* - a function: the game decides, terrain ignored.
|
|
15
|
+
*/
|
|
16
|
+
export type BeamBlocker = 'terrain' | 'none' | ((cell: Step, context: BeamDamageContext) => boolean);
|
|
9
17
|
export interface MultiTurnBeamOptions<T> {
|
|
10
18
|
level: Level;
|
|
11
19
|
from: Step;
|
|
@@ -15,31 +23,55 @@ export interface MultiTurnBeamOptions<T> {
|
|
|
15
23
|
targetsAt?: (cell: Step) => readonly T[];
|
|
16
24
|
/** Applies the resolved damage. The game owns health, resistances and effects. */
|
|
17
25
|
applyDamage?: (target: T, amount: number, context: BeamDamageContext) => void;
|
|
18
|
-
/**
|
|
26
|
+
/** An extra game rule on top of `blocker`, e.g. "a wall of fire melts the beam". */
|
|
19
27
|
isBlocked?: (cell: Step, context: BeamDamageContext) => boolean;
|
|
20
|
-
/**
|
|
28
|
+
/** The explicit blocker policy; defaults to `'terrain'`, matching a plain beam. */
|
|
29
|
+
blocker?: BeamBlocker;
|
|
30
|
+
/** @deprecated use `blocker: 'none'`; kept so existing content keeps its meaning */
|
|
21
31
|
stopAtOpaque?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Opt-in per-turn fronts instead of a straight line. Given the cells the previous turn
|
|
34
|
+
* actually reached (the origin alone at turn 0) and the turn number, it returns the cells
|
|
35
|
+
* this turn affects; an empty list ends the beam. A cone widens, a burst stops moving, a
|
|
36
|
+
* forked front splits, and a moving front is whatever the game returns each turn - none of
|
|
37
|
+
* which the framework has to understand, only sequence and save.
|
|
38
|
+
*/
|
|
39
|
+
fronts?: (previous: readonly Step[], turn: number) => readonly Step[];
|
|
40
|
+
/** Names the shape this beam is, saved so a reload resumes the same one; defaults to `'line'`/`'fronts'`. */
|
|
41
|
+
shape?: string;
|
|
42
|
+
/** Called for every cell the front actually reaches, before targets are resolved. */
|
|
43
|
+
onCell?: (cell: Step, context: BeamDamageContext) => void;
|
|
22
44
|
}
|
|
23
45
|
export interface BeamStep<T> {
|
|
24
46
|
status: 'idle' | 'active' | 'done' | 'blocked' | 'cancelled';
|
|
47
|
+
/** the first cell this turn reached, or null when the front was empty or fully blocked */
|
|
25
48
|
cell: Step | null;
|
|
49
|
+
/** every cell this turn reached, in front order */
|
|
50
|
+
cells: readonly Step[];
|
|
26
51
|
step: number;
|
|
27
52
|
remaining: number;
|
|
28
53
|
targets: readonly T[];
|
|
29
54
|
damage: number;
|
|
30
55
|
}
|
|
31
56
|
export interface MultiTurnBeamSave {
|
|
32
|
-
path: Step[];
|
|
33
|
-
index: number;
|
|
34
57
|
state: 'idle' | 'active' | 'done' | 'blocked' | 'cancelled';
|
|
58
|
+
/** every front resolved so far, oldest first; `fronts[index]` is the next to resolve */
|
|
59
|
+
fronts: Step[][];
|
|
60
|
+
index: number;
|
|
61
|
+
/** the shape the beam was built with, so a reload resumes the same one */
|
|
62
|
+
shape: string;
|
|
63
|
+
/** @deprecated a save written before fronts landed: one cell per turn */
|
|
64
|
+
path?: Step[];
|
|
35
65
|
}
|
|
36
66
|
/**
|
|
37
|
-
* Advances a
|
|
67
|
+
* Advances a beam one front per turn.
|
|
38
68
|
*
|
|
39
|
-
* The framework owns traversal and timing. A game supplies target lookup and damage
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
69
|
+
* The framework owns traversal and timing. A game supplies target lookup and damage application,
|
|
70
|
+
* so this stays useful for hit points, shields, status effects and any other combat model. The
|
|
71
|
+
* default shape is a straight line captured at construction time; `fronts` opts into a per-turn
|
|
72
|
+
* front resolver, which is what expresses a cone, a burst, a fork or a moving front without any
|
|
73
|
+
* of those rules living here. Blockers and targets are checked when each cell is reached, so
|
|
74
|
+
* `advance` is deterministic when those callbacks are.
|
|
43
75
|
*
|
|
44
76
|
* @example
|
|
45
77
|
* ```ts
|
|
@@ -53,18 +85,24 @@ export interface MultiTurnBeamSave {
|
|
|
53
85
|
*/
|
|
54
86
|
export declare class MultiTurnBeam<T = unknown> {
|
|
55
87
|
private options;
|
|
56
|
-
private
|
|
88
|
+
private fronts;
|
|
57
89
|
private index;
|
|
58
90
|
private state;
|
|
91
|
+
private shape;
|
|
59
92
|
constructor(options: MultiTurnBeamOptions<T>);
|
|
60
93
|
get active(): boolean;
|
|
61
94
|
get done(): boolean;
|
|
95
|
+
/** The shape this beam was built with, for a save or a UI label. */
|
|
96
|
+
get beamShape(): string;
|
|
97
|
+
/** Every cell of every resolved front, in order - the straight path of a line beam. */
|
|
62
98
|
get currentPath(): readonly Step[];
|
|
63
99
|
start(): boolean;
|
|
64
|
-
/** Resolves and applies one beam
|
|
100
|
+
/** Resolves and applies one beam front. Call once per game turn. */
|
|
65
101
|
advance(): BeamStep<T>;
|
|
66
102
|
cancel(): void;
|
|
67
103
|
toJSON(): MultiTurnBeamSave;
|
|
68
104
|
static fromJSON<T>(options: Omit<MultiTurnBeamOptions<T>, 'from' | 'target'>, data: MultiTurnBeamSave): MultiTurnBeam<T>;
|
|
105
|
+
private blocked;
|
|
106
|
+
private inactiveStep;
|
|
69
107
|
}
|
|
70
108
|
//# sourceMappingURL=MultiTurnBeam.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultiTurnBeam.d.ts","sourceRoot":"","sources":["../../src/roguelike/MultiTurnBeam.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,
|
|
1
|
+
{"version":3,"file":"MultiTurnBeam.d.ts","sourceRoot":"","sources":["../../src/roguelike/MultiTurnBeam.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { resolveAreaOnLevel } from './Targeting.js';
|
|
2
2
|
/**
|
|
3
|
-
* Advances a
|
|
3
|
+
* Advances a beam one front per turn.
|
|
4
4
|
*
|
|
5
|
-
* The framework owns traversal and timing. A game supplies target lookup and damage
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* The framework owns traversal and timing. A game supplies target lookup and damage application,
|
|
6
|
+
* so this stays useful for hit points, shields, status effects and any other combat model. The
|
|
7
|
+
* default shape is a straight line captured at construction time; `fronts` opts into a per-turn
|
|
8
|
+
* front resolver, which is what expresses a cone, a burst, a fork or a moving front without any
|
|
9
|
+
* of those rules living here. Blockers and targets are checked when each cell is reached, so
|
|
10
|
+
* `advance` is deterministic when those callbacks are.
|
|
9
11
|
*
|
|
10
12
|
* @example
|
|
11
13
|
* ```ts
|
|
@@ -28,7 +30,16 @@ export class MultiTurnBeam {
|
|
|
28
30
|
throw new Error('beam damage must be non-negative');
|
|
29
31
|
}
|
|
30
32
|
this.options = options;
|
|
31
|
-
this.
|
|
33
|
+
this.shape = options.shape ?? (options.fronts ? 'fronts' : 'line');
|
|
34
|
+
if (options.fronts) {
|
|
35
|
+
const first = options.fronts([{ ...options.from }], 0);
|
|
36
|
+
this.fronts = first.length ? [first.map((cell) => ({ ...cell }))] : [];
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
this.fronts = resolveAreaOnLevel(options.level, options.from, options.target, { kind: 'line' })
|
|
40
|
+
.slice(1)
|
|
41
|
+
.map((cell) => [{ ...cell }]);
|
|
42
|
+
}
|
|
32
43
|
}
|
|
33
44
|
get active() {
|
|
34
45
|
return this.state === 'active';
|
|
@@ -36,64 +47,65 @@ export class MultiTurnBeam {
|
|
|
36
47
|
get done() {
|
|
37
48
|
return this.state === 'done' || this.state === 'blocked' || this.state === 'cancelled';
|
|
38
49
|
}
|
|
50
|
+
/** The shape this beam was built with, for a save or a UI label. */
|
|
51
|
+
get beamShape() {
|
|
52
|
+
return this.shape;
|
|
53
|
+
}
|
|
54
|
+
/** Every cell of every resolved front, in order - the straight path of a line beam. */
|
|
39
55
|
get currentPath() {
|
|
40
|
-
return this.
|
|
56
|
+
return this.fronts.flat().map((cell) => ({ ...cell }));
|
|
41
57
|
}
|
|
42
58
|
start() {
|
|
43
59
|
if (this.state !== 'idle')
|
|
44
60
|
return false;
|
|
45
|
-
this.state = this.
|
|
61
|
+
this.state = this.fronts.length === 0 ? 'done' : 'active';
|
|
46
62
|
return true;
|
|
47
63
|
}
|
|
48
|
-
/** Resolves and applies one beam
|
|
64
|
+
/** Resolves and applies one beam front. Call once per game turn. */
|
|
49
65
|
advance() {
|
|
50
|
-
if (this.state !== 'active')
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
targets: [],
|
|
57
|
-
damage: 0,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
const cell = this.path[this.index];
|
|
61
|
-
const context = {
|
|
62
|
-
cell: { ...cell },
|
|
63
|
-
step: this.index,
|
|
64
|
-
remaining: this.path.length - this.index - 1,
|
|
65
|
-
};
|
|
66
|
-
const blocked = !this.options.level.inside(cell.x, cell.y) ||
|
|
67
|
-
(this.options.stopAtOpaque !== false && !this.options.level.transparent(cell.x, cell.y)) ||
|
|
68
|
-
this.options.isBlocked?.(cell, context) === true;
|
|
69
|
-
if (blocked) {
|
|
70
|
-
this.state = 'blocked';
|
|
71
|
-
return {
|
|
72
|
-
status: 'blocked',
|
|
73
|
-
cell: { ...cell },
|
|
74
|
-
step: this.index,
|
|
75
|
-
remaining: context.remaining,
|
|
76
|
-
targets: [],
|
|
77
|
-
damage: 0,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
const targets = this.options.targetsAt?.(cell) ?? [];
|
|
66
|
+
if (this.state !== 'active')
|
|
67
|
+
return this.inactiveStep();
|
|
68
|
+
const front = this.fronts[this.index] ?? [];
|
|
69
|
+
const remaining = Math.max(0, this.fronts.length - this.index - 1);
|
|
70
|
+
const cells = [];
|
|
71
|
+
const targets = [];
|
|
81
72
|
let damage = 0;
|
|
82
|
-
for (const
|
|
83
|
-
const
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
this.options.
|
|
73
|
+
for (const raw of front) {
|
|
74
|
+
const context = { cell: { ...raw }, step: this.index, remaining };
|
|
75
|
+
if (this.blocked(raw, context))
|
|
76
|
+
continue;
|
|
77
|
+
cells.push({ ...raw });
|
|
78
|
+
this.options.onCell?.({ ...raw }, context);
|
|
79
|
+
for (const target of this.options.targetsAt?.(raw) ?? []) {
|
|
80
|
+
const amount = typeof this.options.damage === 'function'
|
|
81
|
+
? this.options.damage(target, context)
|
|
82
|
+
: this.options.damage;
|
|
83
|
+
if (!Number.isFinite(amount) || amount < 0)
|
|
84
|
+
throw new Error('beam damage callback must return a non-negative finite number');
|
|
85
|
+
damage += amount;
|
|
86
|
+
targets.push(target);
|
|
87
|
+
this.options.applyDamage?.(target, amount, context);
|
|
88
|
+
}
|
|
88
89
|
}
|
|
90
|
+
const step = this.index;
|
|
89
91
|
this.index++;
|
|
90
|
-
|
|
92
|
+
//a per-turn resolver extends the beam from what this front actually reached; a blocked
|
|
93
|
+
//cell does not propagate, and no cells at all ends the show
|
|
94
|
+
if (this.options.fronts && cells.length > 0) {
|
|
95
|
+
const next = this.options.fronts(cells.map((cell) => ({ ...cell })), this.index);
|
|
96
|
+
if (next.length)
|
|
97
|
+
this.fronts.push(next.map((cell) => ({ ...cell })));
|
|
98
|
+
}
|
|
99
|
+
if (cells.length === 0)
|
|
100
|
+
this.state = 'blocked';
|
|
101
|
+
else if (this.index >= this.fronts.length)
|
|
91
102
|
this.state = 'done';
|
|
92
103
|
return {
|
|
93
|
-
status:
|
|
94
|
-
cell: { ...
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
status: cells.length === 0 ? 'blocked' : this.state,
|
|
105
|
+
cell: cells[0] ? { ...cells[0] } : null,
|
|
106
|
+
cells: cells.map((cell) => ({ ...cell })),
|
|
107
|
+
step,
|
|
108
|
+
remaining,
|
|
97
109
|
targets: [...targets],
|
|
98
110
|
damage,
|
|
99
111
|
};
|
|
@@ -103,15 +115,50 @@ export class MultiTurnBeam {
|
|
|
103
115
|
this.state = 'cancelled';
|
|
104
116
|
}
|
|
105
117
|
toJSON() {
|
|
106
|
-
return {
|
|
118
|
+
return {
|
|
119
|
+
state: this.state,
|
|
120
|
+
fronts: this.fronts.map((front) => front.map((cell) => ({ ...cell }))),
|
|
121
|
+
index: this.index,
|
|
122
|
+
shape: this.shape,
|
|
123
|
+
};
|
|
107
124
|
}
|
|
108
125
|
static fromJSON(options, data) {
|
|
126
|
+
const shape = data.shape ?? (options.fronts ? 'fronts' : 'line');
|
|
127
|
+
if (options.shape !== undefined && options.shape !== shape)
|
|
128
|
+
throw new Error(`beam save is shape "${shape}", not "${options.shape}"`);
|
|
109
129
|
const beam = Object.create(MultiTurnBeam.prototype);
|
|
110
130
|
beam.options = options;
|
|
111
|
-
beam.
|
|
131
|
+
beam.shape = shape;
|
|
132
|
+
//a save written before fronts landed stored one cell per turn as `path`
|
|
133
|
+
beam.fronts = data.fronts
|
|
134
|
+
? data.fronts.map((front) => front.map((cell) => ({ ...cell })))
|
|
135
|
+
: (data.path ?? []).map((cell) => [{ ...cell }]);
|
|
112
136
|
beam.index = data.index;
|
|
113
137
|
beam.state = data.state;
|
|
114
138
|
return beam;
|
|
115
139
|
}
|
|
140
|
+
blocked(cell, context) {
|
|
141
|
+
const blocker = this.options.blocker ?? (this.options.stopAtOpaque === false ? 'none' : 'terrain');
|
|
142
|
+
let blockedByPolicy = false;
|
|
143
|
+
if (blocker === 'terrain') {
|
|
144
|
+
blockedByPolicy =
|
|
145
|
+
!this.options.level.inside(cell.x, cell.y) || !this.options.level.transparent(cell.x, cell.y);
|
|
146
|
+
}
|
|
147
|
+
else if (blocker !== 'none') {
|
|
148
|
+
blockedByPolicy = blocker({ ...cell }, context) === true;
|
|
149
|
+
}
|
|
150
|
+
return blockedByPolicy || this.options.isBlocked?.({ ...cell }, context) === true;
|
|
151
|
+
}
|
|
152
|
+
inactiveStep() {
|
|
153
|
+
return {
|
|
154
|
+
status: this.state,
|
|
155
|
+
cell: null,
|
|
156
|
+
cells: [],
|
|
157
|
+
step: this.index,
|
|
158
|
+
remaining: Math.max(0, this.fronts.length - this.index),
|
|
159
|
+
targets: [],
|
|
160
|
+
damage: 0,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
116
163
|
}
|
|
117
164
|
//# sourceMappingURL=MultiTurnBeam.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultiTurnBeam.js","sourceRoot":"","sources":["../../src/roguelike/MultiTurnBeam.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"MultiTurnBeam.js","sourceRoot":"","sources":["../../src/roguelike/MultiTurnBeam.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Level } from './Level.ts';
|
|
2
2
|
import type { Step } from './Pathfinder.ts';
|
|
3
|
+
import { Signal } from '../core/Signal.ts';
|
|
3
4
|
/**
|
|
4
5
|
* How a target resolves into the cells it actually affects.
|
|
5
6
|
*
|
|
@@ -180,4 +181,97 @@ export declare function areaFalloffMultiplier(index: number, steps: readonly num
|
|
|
180
181
|
* very first cell already blocks, in which case the shove lands against the wall instead.
|
|
181
182
|
*/
|
|
182
183
|
export declare function knockbackPath(level: Level, from: Step, direction: Step, distance: number): Step[];
|
|
184
|
+
export interface TargetingControllerOptions {
|
|
185
|
+
/** where the aim starts: the thrower's own cell */
|
|
186
|
+
origin: Step;
|
|
187
|
+
/** how far the aim reaches, in the owning shape's own ruler (`canTarget`'s) */
|
|
188
|
+
range: number;
|
|
189
|
+
/** whether a wall between origin and cursor blocks the aim; on by default */
|
|
190
|
+
requireLineOfSight?: boolean;
|
|
191
|
+
/** how a confirmed aim resolves into cells; a single cell by default */
|
|
192
|
+
shape?: AreaShape;
|
|
193
|
+
/** the cursor's first cell; defaults to `origin` */
|
|
194
|
+
cursor?: Step;
|
|
195
|
+
/**
|
|
196
|
+
* A game's own rule on top of range and sight (not through an ally, not an empty floor
|
|
197
|
+
* tile). The controller never guesses what is in a cell: only the game knows.
|
|
198
|
+
*/
|
|
199
|
+
validate?: (target: Step) => boolean;
|
|
200
|
+
}
|
|
201
|
+
/** What `TargetingController.confirm` returns, and what its `onConfirm` carries. Cells only. */
|
|
202
|
+
export interface TargetResult {
|
|
203
|
+
readonly origin: Step;
|
|
204
|
+
readonly target: Step;
|
|
205
|
+
readonly shape: AreaShape;
|
|
206
|
+
/** the cells the shape affects, resolved through the level's own topology */
|
|
207
|
+
readonly cells: readonly Step[];
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The input-facing half of targeting: a cursor a player moves over cells, the range and
|
|
211
|
+
* line-of-sight rule that decides whether the current aim is legal, an optional preview of
|
|
212
|
+
* the shape, and a confirm/cancel result. It carries no renderer: `moveTo` takes a cell, so
|
|
213
|
+
* a game converts its own pointer position through its own camera, and the controller hands
|
|
214
|
+
* back cells for the game to draw and resolve. Legality beyond range and sight is the
|
|
215
|
+
* optional `validate` hook's job, and damage is never the controller's business.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```ts
|
|
219
|
+
* import { Level, FLOOR, WALL, TargetingController } from '@datamoc/mw_games/roguelike';
|
|
220
|
+
*
|
|
221
|
+
* const level = new Level(12, 12, [WALL, FLOOR], 1);
|
|
222
|
+
* const aiming = new TargetingController(level, {
|
|
223
|
+
* origin: { x: 2, y: 2 },
|
|
224
|
+
* range: 6,
|
|
225
|
+
* shape: { kind: 'burst', radius: 1 },
|
|
226
|
+
* });
|
|
227
|
+
*
|
|
228
|
+
* aiming.move(1, 0); // keyboard: one cell east
|
|
229
|
+
* aiming.moveTo({ x: 5, y: 2 }); // pointer: the caller resolved the screen point first
|
|
230
|
+
* console.log(aiming.valid); // in range and in sight
|
|
231
|
+
* console.log(aiming.preview()); // the burst's cells, or [] while the aim is illegal
|
|
232
|
+
*
|
|
233
|
+
* const shot = aiming.confirm(); // null when illegal, the target cells when not
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
export declare class TargetingController {
|
|
237
|
+
readonly onMove: Signal<Step>;
|
|
238
|
+
readonly onConfirm: Signal<TargetResult>;
|
|
239
|
+
readonly onCancel: Signal<void>;
|
|
240
|
+
private readonly level;
|
|
241
|
+
private readonly origin;
|
|
242
|
+
private readonly range;
|
|
243
|
+
private readonly requireLineOfSight;
|
|
244
|
+
private readonly validate?;
|
|
245
|
+
private shape;
|
|
246
|
+
private cursor;
|
|
247
|
+
constructor(level: Level, options: TargetingControllerOptions);
|
|
248
|
+
/** the cell the cursor is on now, as a copy - mutating it does not move the aim */
|
|
249
|
+
get target(): Step;
|
|
250
|
+
/** how many cells the cursor is from the origin, in the level's own ruler */
|
|
251
|
+
get distance(): number;
|
|
252
|
+
get inRange(): boolean;
|
|
253
|
+
get inSight(): boolean;
|
|
254
|
+
/** whether the current aim is legal: range, sight unless waived, and the game's own rule */
|
|
255
|
+
get valid(): boolean;
|
|
256
|
+
/**
|
|
257
|
+
* Moves the cursor one cell, for keyboard and gamepad navigation. On a square level the
|
|
258
|
+
* offset is added directly; on a hex level it is looked up among the cursor's six
|
|
259
|
+
* neighbours, since only the level knows which offset means which direction there. A move
|
|
260
|
+
* that would leave the map is ignored rather than clamped to its edge.
|
|
261
|
+
*/
|
|
262
|
+
move(dx: number, dy: number): void;
|
|
263
|
+
/**
|
|
264
|
+
* Moves the cursor to a specific cell, for pointer navigation: the caller has already
|
|
265
|
+
* turned a screen point into a cell through its own camera. Off-map cells are ignored.
|
|
266
|
+
*/
|
|
267
|
+
moveTo(cell: Step): void;
|
|
268
|
+
/** Replaces the shape a confirm resolves to, keeping the cursor where it is. */
|
|
269
|
+
setShape(shape: AreaShape): void;
|
|
270
|
+
/** The cells the current aim would affect, for a preview overlay; empty while illegal. */
|
|
271
|
+
preview(): Step[];
|
|
272
|
+
/** Returns the confirmed target, or null when the current aim is illegal. */
|
|
273
|
+
confirm(): TargetResult | null;
|
|
274
|
+
/** Abandons the aim without confirming; the game closes its own overlay on `onCancel`. */
|
|
275
|
+
cancel(): void;
|
|
276
|
+
}
|
|
183
277
|
//# sourceMappingURL=Targeting.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Targeting.d.ts","sourceRoot":"","sources":["../../src/roguelike/Targeting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"Targeting.d.ts","sourceRoot":"","sources":["../../src/roguelike/Targeting.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hexDistance, hexLine, hexRange } from '../core/Hex.js';
|
|
2
|
+
import { Signal } from '../core/Signal.js';
|
|
2
3
|
/**
|
|
3
4
|
* The straight-line ruler distance a roguelike uses: diagonals cost the same as a step.
|
|
4
5
|
*
|
|
@@ -284,4 +285,126 @@ export function knockbackPath(level, from, direction, distance) {
|
|
|
284
285
|
}
|
|
285
286
|
return cells;
|
|
286
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* The input-facing half of targeting: a cursor a player moves over cells, the range and
|
|
290
|
+
* line-of-sight rule that decides whether the current aim is legal, an optional preview of
|
|
291
|
+
* the shape, and a confirm/cancel result. It carries no renderer: `moveTo` takes a cell, so
|
|
292
|
+
* a game converts its own pointer position through its own camera, and the controller hands
|
|
293
|
+
* back cells for the game to draw and resolve. Legality beyond range and sight is the
|
|
294
|
+
* optional `validate` hook's job, and damage is never the controller's business.
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```ts
|
|
298
|
+
* import { Level, FLOOR, WALL, TargetingController } from '@datamoc/mw_games/roguelike';
|
|
299
|
+
*
|
|
300
|
+
* const level = new Level(12, 12, [WALL, FLOOR], 1);
|
|
301
|
+
* const aiming = new TargetingController(level, {
|
|
302
|
+
* origin: { x: 2, y: 2 },
|
|
303
|
+
* range: 6,
|
|
304
|
+
* shape: { kind: 'burst', radius: 1 },
|
|
305
|
+
* });
|
|
306
|
+
*
|
|
307
|
+
* aiming.move(1, 0); // keyboard: one cell east
|
|
308
|
+
* aiming.moveTo({ x: 5, y: 2 }); // pointer: the caller resolved the screen point first
|
|
309
|
+
* console.log(aiming.valid); // in range and in sight
|
|
310
|
+
* console.log(aiming.preview()); // the burst's cells, or [] while the aim is illegal
|
|
311
|
+
*
|
|
312
|
+
* const shot = aiming.confirm(); // null when illegal, the target cells when not
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
export class TargetingController {
|
|
316
|
+
constructor(level, options) {
|
|
317
|
+
this.onMove = new Signal();
|
|
318
|
+
this.onConfirm = new Signal();
|
|
319
|
+
this.onCancel = new Signal();
|
|
320
|
+
if (!Number.isFinite(options.range) || options.range < 0) {
|
|
321
|
+
throw new Error('targeting range must be a finite non-negative number');
|
|
322
|
+
}
|
|
323
|
+
this.level = level;
|
|
324
|
+
this.origin = { ...options.origin };
|
|
325
|
+
this.range = options.range;
|
|
326
|
+
this.requireLineOfSight = options.requireLineOfSight ?? true;
|
|
327
|
+
this.validate = options.validate;
|
|
328
|
+
this.shape = options.shape ?? { kind: 'single' };
|
|
329
|
+
this.cursor = { ...(options.cursor ?? options.origin) };
|
|
330
|
+
}
|
|
331
|
+
/** the cell the cursor is on now, as a copy - mutating it does not move the aim */
|
|
332
|
+
get target() {
|
|
333
|
+
return { ...this.cursor };
|
|
334
|
+
}
|
|
335
|
+
/** how many cells the cursor is from the origin, in the level's own ruler */
|
|
336
|
+
get distance() {
|
|
337
|
+
return this.level.shape === 'hex'
|
|
338
|
+
? hexDistance(this.origin, this.cursor)
|
|
339
|
+
: chebyshevDistance(this.origin, this.cursor);
|
|
340
|
+
}
|
|
341
|
+
get inRange() {
|
|
342
|
+
return this.distance <= this.range;
|
|
343
|
+
}
|
|
344
|
+
get inSight() {
|
|
345
|
+
return hasLineOfSight(this.level, this.origin, this.cursor);
|
|
346
|
+
}
|
|
347
|
+
/** whether the current aim is legal: range, sight unless waived, and the game's own rule */
|
|
348
|
+
get valid() {
|
|
349
|
+
if (!this.inRange)
|
|
350
|
+
return false;
|
|
351
|
+
if (this.requireLineOfSight && !this.inSight)
|
|
352
|
+
return false;
|
|
353
|
+
return this.validate ? this.validate(this.cursor) : true;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Moves the cursor one cell, for keyboard and gamepad navigation. On a square level the
|
|
357
|
+
* offset is added directly; on a hex level it is looked up among the cursor's six
|
|
358
|
+
* neighbours, since only the level knows which offset means which direction there. A move
|
|
359
|
+
* that would leave the map is ignored rather than clamped to its edge.
|
|
360
|
+
*/
|
|
361
|
+
move(dx, dy) {
|
|
362
|
+
if (this.level.shape === 'hex') {
|
|
363
|
+
const next = this.level
|
|
364
|
+
.neighbors(this.cursor.x, this.cursor.y)
|
|
365
|
+
.find((neighbor) => neighbor.x - this.cursor.x === dx && neighbor.y - this.cursor.y === dy);
|
|
366
|
+
if (next)
|
|
367
|
+
this.moveTo(next);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
this.moveTo({ x: this.cursor.x + dx, y: this.cursor.y + dy });
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Moves the cursor to a specific cell, for pointer navigation: the caller has already
|
|
374
|
+
* turned a screen point into a cell through its own camera. Off-map cells are ignored.
|
|
375
|
+
*/
|
|
376
|
+
moveTo(cell) {
|
|
377
|
+
if (!this.level.inside(cell.x, cell.y))
|
|
378
|
+
return;
|
|
379
|
+
this.cursor = { x: cell.x, y: cell.y };
|
|
380
|
+
this.onMove.dispatch({ ...this.cursor });
|
|
381
|
+
}
|
|
382
|
+
/** Replaces the shape a confirm resolves to, keeping the cursor where it is. */
|
|
383
|
+
setShape(shape) {
|
|
384
|
+
this.shape = shape;
|
|
385
|
+
}
|
|
386
|
+
/** The cells the current aim would affect, for a preview overlay; empty while illegal. */
|
|
387
|
+
preview() {
|
|
388
|
+
if (!this.valid)
|
|
389
|
+
return [];
|
|
390
|
+
return resolveAreaOnLevel(this.level, this.origin, this.cursor, this.shape);
|
|
391
|
+
}
|
|
392
|
+
/** Returns the confirmed target, or null when the current aim is illegal. */
|
|
393
|
+
confirm() {
|
|
394
|
+
if (!this.valid)
|
|
395
|
+
return null;
|
|
396
|
+
const result = {
|
|
397
|
+
origin: { ...this.origin },
|
|
398
|
+
target: { ...this.cursor },
|
|
399
|
+
shape: this.shape,
|
|
400
|
+
cells: this.preview(),
|
|
401
|
+
};
|
|
402
|
+
this.onConfirm.dispatch(result);
|
|
403
|
+
return result;
|
|
404
|
+
}
|
|
405
|
+
/** Abandons the aim without confirming; the game closes its own overlay on `onCancel`. */
|
|
406
|
+
cancel() {
|
|
407
|
+
this.onCancel.dispatch();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
287
410
|
//# sourceMappingURL=Targeting.js.map
|