@maka/maka-cli 5.3.17 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/typescript/package.json +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.d.ts +43 -0
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +131 -0
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/assense.d.ts +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js +79 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.d.ts +42 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +224 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.d.ts +28 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js +101 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.d.ts +19 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +149 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.d.ts +58 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +378 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.d.ts +11 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.js +8 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/command.d.ts +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/command.js +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/command.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/drone.d.ts +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js +60 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/edge.d.ts +13 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js +31 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.d.ts +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js +27 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/equip.d.ts +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js +40 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.d.ts +13 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js +43 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/give.js +7 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/give.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.d.ts +11 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +115 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.d.ts +25 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +176 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/inv.d.ts +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/inv.js +39 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/inv.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/jack.d.ts +17 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/jack.js +64 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/jack.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/kill.d.ts +10 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/kill.js +11 -33
- package/bundle/typescript/src/commands/game/sideQuest/commands/kill.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +35 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/map.d.ts +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/map.js +46 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/map.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pan.d.ts +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pan.js +55 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pan.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.d.ts +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js +18 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/project.d.ts +19 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/project.js +54 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/project.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/read.js +6 -5
- package/bundle/typescript/src/commands/game/sideQuest/commands/read.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.d.ts +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js +52 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/reload.d.ts +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/reload.js +40 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/reload.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.d.ts +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +59 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js +23 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +20 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.d.ts +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +49 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/silent.d.ts +13 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js +50 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/solve.js +32 -11
- package/bundle/typescript/src/commands/game/sideQuest/commands/solve.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/spells.d.ts +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js +65 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/stance.d.ts +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js +41 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.d.ts +21 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.js +43 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.d.ts +20 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.js +45 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.d.ts +8 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +80 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.d.ts +24 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.js +101 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/item-factory.js +2 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/item-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/npc-factory.js +7 -2
- package/bundle/typescript/src/commands/game/sideQuest/factories/npc-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/puzzle-factory.js +2 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/puzzle-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.d.ts +8 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +138 -21
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.d.ts +119 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js +816 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +70 -9
- package/bundle/typescript/src/commands/game/sideQuest/game.js +403 -164
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.js +101 -0
- package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.d.ts +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.js +95 -0
- package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/door.d.ts +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/door.js +15 -2
- package/bundle/typescript/src/commands/game/sideQuest/models/door.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/inventory.d.ts +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/inventory.js +54 -4
- package/bundle/typescript/src/commands/game/sideQuest/models/inventory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/item.d.ts +41 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/item.js +106 -3
- package/bundle/typescript/src/commands/game/sideQuest/models/item.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +25 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +220 -47
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +213 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +525 -9
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.d.ts +75 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js +174 -6
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/room.d.ts +20 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js +39 -18
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.d.ts +69 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +175 -2
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene1.json +80 -1
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +7 -2
- package/bundle/typescript/src/commands/game/sideQuest/types/player-equipment.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/exit-seed.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/item-seed.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/npc-seed.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/player-seed.d.ts +34 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/puzzle-seed.d.ts +15 -1
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/room-seed.d.ts +3 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/scene-seed.d.ts +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.d.ts +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.js +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.js +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/ui.d.ts +3 -0
- package/bundle/typescript/src/commands/game/sideQuest/ui.js +95 -5
- package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.d.ts +60 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js +138 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.d.ts +143 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +328 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.d.ts +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.d.ts +22 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js +34 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.d.ts +36 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.js +92 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.d.ts +44 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +193 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.js +36 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +54 -7
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/commands/set/global.sub.cmd.js +59 -5
- package/bundle/typescript/src/commands/set/global.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/tools/ai/ai.class.d.ts +35 -0
- package/bundle/typescript/src/tools/ai/ai.class.js +112 -0
- package/bundle/typescript/src/tools/ai/ai.class.js.map +1 -0
- package/bundle/typescript/src/tools/fsi/fsi.interfaces.d.ts +1 -0
- package/bundle/typescript/src/tools/fsi/fsi.interfaces.js.map +1 -1
- package/bundle/typescript/src/tools/https/https.class.d.ts +2 -1
- package/bundle/typescript/src/tools/https/https.class.js +43 -0
- package/bundle/typescript/src/tools/https/https.class.js.map +1 -1
- package/bundle/typescript/src/tools/https/https.interface.d.ts +8 -0
- package/package.json +1 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { Player } from '../models/player.js';
|
|
2
|
+
import { NPC } from '../models/npc.js';
|
|
3
|
+
import { Scene } from '../models/scene.js';
|
|
4
|
+
import { Logger } from './logger.js';
|
|
5
|
+
/**
|
|
6
|
+
* What one directed strike looks like mechanically -- defaults come from
|
|
7
|
+
* the attacker's equipped weapon (see weaponProfile()), but a spell attack
|
|
8
|
+
* supplies its own: a casting pool instead of Agility+skill, and a soak
|
|
9
|
+
* pool that ignores armor (magic's whole niche -- see commands/cast.ts).
|
|
10
|
+
*/
|
|
11
|
+
export interface IStrikeProfile {
|
|
12
|
+
/** e.g. "with Ares Light Fire 75", "with a manabolt" */
|
|
13
|
+
label: string;
|
|
14
|
+
pool: number;
|
|
15
|
+
damageBase: number;
|
|
16
|
+
soakPool: number;
|
|
17
|
+
/** The physical weapon behind the strike, when there is one -- lets
|
|
18
|
+
* resolveStrike() spend a round of its ammunition. */
|
|
19
|
+
weaponItem?: import('../models/item.js').Item;
|
|
20
|
+
/** Subdual (see subdue.ts): damage lands on the STUN track instead of
|
|
21
|
+
* physical -- knockouts, not kills (though stun overflow still bleeds
|
|
22
|
+
* into the body). */
|
|
23
|
+
dealStun?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The shared combat-exchange engine, extracted from AttackCommand so spell
|
|
27
|
+
* combat (cast.ts) rides the exact same rails: progressive block-by-block
|
|
28
|
+
* narration with beats, opposed strike resolution, death handling through
|
|
29
|
+
* the flows that already exist (removeActor for NPCs, endGame for the
|
|
30
|
+
* player), and the NPC follow-up pause that gives the player a window to
|
|
31
|
+
* act between an NPC's exchanges. See AttackCommand's class doc for the
|
|
32
|
+
* full rationale on pacing and self-announcement -- all of it lives here
|
|
33
|
+
* now, unchanged in behavior.
|
|
34
|
+
*/
|
|
35
|
+
export declare class CombatExchange {
|
|
36
|
+
private scene;
|
|
37
|
+
private logger;
|
|
38
|
+
private actor;
|
|
39
|
+
/** Pause between narration blocks so a fight reads as a fight. */
|
|
40
|
+
static readonly BEAT_MS = 900;
|
|
41
|
+
/**
|
|
42
|
+
* Extra breathing room tacked onto the END of an NPC-initiated exchange,
|
|
43
|
+
* before the command returns to the NPC's AI reflect loop (which awaits
|
|
44
|
+
* it, so delaying the return directly throttles its next swing). This is
|
|
45
|
+
* the player's window to heal or flee. Player-typed actions skip it.
|
|
46
|
+
*/
|
|
47
|
+
static readonly NPC_FOLLOWUP_PAUSE_MS = 2500;
|
|
48
|
+
constructor(scene: Scene, logger: Logger, actor: Player | NPC);
|
|
49
|
+
/**
|
|
50
|
+
* Call once combat is actually joined (gates passed, target resolved).
|
|
51
|
+
* While an exchange involving the player is resolving, the player can't
|
|
52
|
+
* "go" (see go.ts) -- you don't get to walk out from between an
|
|
53
|
+
* attacker's narration beats mid-exchange. finishExchange() releases the
|
|
54
|
+
* lock BEFORE the between-exchanges pause, which remains the legitimate
|
|
55
|
+
* window to flee or heal. Safe to call for NPC-vs-NPC fights too: the
|
|
56
|
+
* lock only engages when the player is one of the combatants.
|
|
57
|
+
*/
|
|
58
|
+
private _combatants;
|
|
59
|
+
beginExchange(target: Player | NPC): void;
|
|
60
|
+
/** Clears the per-combatant serialization flags. Safe to call twice. */
|
|
61
|
+
releaseExchange(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Parks an NPC-driven action until every listed combatant's in-flight
|
|
64
|
+
* exchange settles, so the action fires in the lull instead of being
|
|
65
|
+
* discarded. Without this, an NPC's whole turn evaporated: its AI reacts
|
|
66
|
+
* to the player's attack within a second or two -- always mid-exchange --
|
|
67
|
+
* so every counterattack AND every wounded-NPC flee attempt was refused
|
|
68
|
+
* into its private history. Two real sessions of Krow talking trash while
|
|
69
|
+
* never once shooting back or making the door he was trying to flee
|
|
70
|
+
* through, which read to the player as the game stalling out.
|
|
71
|
+
*
|
|
72
|
+
* Resolves true when the flags clear (caller must revalidate everything:
|
|
73
|
+
* the waiter or its target may have died in the very exchange it sat
|
|
74
|
+
* out); false on timeout, meaning something is stuck and the caller
|
|
75
|
+
* should fall back to a plain refusal.
|
|
76
|
+
*/
|
|
77
|
+
static waitForLull(combatants: (Player | NPC)[], timeoutMs?: number): Promise<boolean>;
|
|
78
|
+
/**
|
|
79
|
+
* The initiative roll-off that opens an exchange: Reaction + Intuition
|
|
80
|
+
* (+ live commlink AR bonus, see Player.getInitiativePool). The winner
|
|
81
|
+
* acts first -- attacking someone faster than you means eating their
|
|
82
|
+
* strike before yours lands -- with ties going to the aggressor. Winning
|
|
83
|
+
* by 3+ net hits earns an INITIATIVE PASS: one extra strike at the end
|
|
84
|
+
* of the exchange (the wired-reflexes speed fantasy, without a full
|
|
85
|
+
* action-economy system).
|
|
86
|
+
*/
|
|
87
|
+
rollInitiative(aggressor: Player | NPC, defender: Player | NPC): {
|
|
88
|
+
first: Player | NPC;
|
|
89
|
+
second: Player | NPC;
|
|
90
|
+
extraStriker: Player | NPC | null;
|
|
91
|
+
lines: string[];
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* The player should see a fight's blow-by-blow only when they're actually
|
|
95
|
+
* there for it -- as a combatant or as a bystander in the same room. A
|
|
96
|
+
* brawl between two NPCs elsewhere in the scene stays off-screen, and
|
|
97
|
+
* skips the narration beats entirely.
|
|
98
|
+
*/
|
|
99
|
+
playerWitnesses(): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Pushes one narration block to the player's screen (when they're present
|
|
102
|
+
* for it) and holds a beat before whatever happens next. Only results of
|
|
103
|
+
* commands the PLAYER typed flow through ui.ts's response pipeline; an
|
|
104
|
+
* NPC's command result goes solely into that NPC's own AI history, so
|
|
105
|
+
* without self-announcing here the player would never see the dice of a
|
|
106
|
+
* fight an NPC started. The status box refreshes with every block so the
|
|
107
|
+
* condition monitor visibly ticks as damage lands.
|
|
108
|
+
*/
|
|
109
|
+
announce(lines: string[]): Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* The default, weapon-based strike profile. A firearm with a dry
|
|
112
|
+
* magazine automatically falls back to bare fists -- this covers the
|
|
113
|
+
* involuntary strikes (retaliation) where refusing to swing at all would
|
|
114
|
+
* make no sense; a deliberately INITIATED attack with a dry gun is
|
|
115
|
+
* blocked with a reload prompt before ever reaching here (attack.ts).
|
|
116
|
+
*/
|
|
117
|
+
weaponProfile(attacker: Player | NPC, defender: Player | NPC): IStrikeProfile;
|
|
118
|
+
/** One directed strike: attack vs defense, then soak. Mutates the defender. */
|
|
119
|
+
resolveStrike(attacker: Player | NPC, defender: Player | NPC, profile?: IStrikeProfile): string[];
|
|
120
|
+
/**
|
|
121
|
+
* Death/knockout narration is announced BEFORE the removal side effects
|
|
122
|
+
* run -- removeActor() fires "on death drops" observer lines straight to
|
|
123
|
+
* the screen, so running it first would put the gear hitting the floor
|
|
124
|
+
* before the kill itself. `fallLine`/`gameOverLine` let a caller replace
|
|
125
|
+
* the default gunned-down flavor; `fatal: false` marks a knockout (a
|
|
126
|
+
* full STUN track -- see cast.ts drain) rather than a death: same
|
|
127
|
+
* out-of-the-run consequences, honestly narrated as unconsciousness.
|
|
128
|
+
*/
|
|
129
|
+
resolveDeath(fallen: Player | NPC, killer: Player | NPC, opts?: {
|
|
130
|
+
fallLine?: string;
|
|
131
|
+
gameOverLine?: string;
|
|
132
|
+
fatal?: boolean;
|
|
133
|
+
}): Promise<string[]>;
|
|
134
|
+
/**
|
|
135
|
+
* The screen already got everything block-by-block, so the player-typed
|
|
136
|
+
* path returns '' (ui.ts prints the return value -- a second copy would
|
|
137
|
+
* double-display). An NPC actor gets the full report back instead: that
|
|
138
|
+
* return value is what lands in the NPC's AI history, which is how it
|
|
139
|
+
* knows how its own fight went -- but only after the follow-up pause,
|
|
140
|
+
* which is the player's window to act before the NPC's next exchange.
|
|
141
|
+
*/
|
|
142
|
+
finishExchange(report: string[]): Promise<string>;
|
|
143
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { rollPool, formatRoll } from './dice.js';
|
|
2
|
+
import { leaveMatrix } from './planes.js';
|
|
3
|
+
/**
|
|
4
|
+
* The shared combat-exchange engine, extracted from AttackCommand so spell
|
|
5
|
+
* combat (cast.ts) rides the exact same rails: progressive block-by-block
|
|
6
|
+
* narration with beats, opposed strike resolution, death handling through
|
|
7
|
+
* the flows that already exist (removeActor for NPCs, endGame for the
|
|
8
|
+
* player), and the NPC follow-up pause that gives the player a window to
|
|
9
|
+
* act between an NPC's exchanges. See AttackCommand's class doc for the
|
|
10
|
+
* full rationale on pacing and self-announcement -- all of it lives here
|
|
11
|
+
* now, unchanged in behavior.
|
|
12
|
+
*/
|
|
13
|
+
export class CombatExchange {
|
|
14
|
+
scene;
|
|
15
|
+
logger;
|
|
16
|
+
actor;
|
|
17
|
+
/** Pause between narration blocks so a fight reads as a fight. */
|
|
18
|
+
static BEAT_MS = 900;
|
|
19
|
+
/**
|
|
20
|
+
* Extra breathing room tacked onto the END of an NPC-initiated exchange,
|
|
21
|
+
* before the command returns to the NPC's AI reflect loop (which awaits
|
|
22
|
+
* it, so delaying the return directly throttles its next swing). This is
|
|
23
|
+
* the player's window to heal or flee. Player-typed actions skip it.
|
|
24
|
+
*/
|
|
25
|
+
static NPC_FOLLOWUP_PAUSE_MS = 2500;
|
|
26
|
+
constructor(scene, logger, actor) {
|
|
27
|
+
this.scene = scene;
|
|
28
|
+
this.logger = logger;
|
|
29
|
+
this.actor = actor;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Call once combat is actually joined (gates passed, target resolved).
|
|
33
|
+
* While an exchange involving the player is resolving, the player can't
|
|
34
|
+
* "go" (see go.ts) -- you don't get to walk out from between an
|
|
35
|
+
* attacker's narration beats mid-exchange. finishExchange() releases the
|
|
36
|
+
* lock BEFORE the between-exchanges pause, which remains the legitimate
|
|
37
|
+
* window to flee or heal. Safe to call for NPC-vs-NPC fights too: the
|
|
38
|
+
* lock only engages when the player is one of the combatants.
|
|
39
|
+
*/
|
|
40
|
+
_combatants = [];
|
|
41
|
+
beginExchange(target) {
|
|
42
|
+
const player = this.scene.getPlayer();
|
|
43
|
+
if (this.actor === player || target === player) {
|
|
44
|
+
this.scene.beginPlayerExchange();
|
|
45
|
+
}
|
|
46
|
+
// Serialize combat per combatant (see Player.inExchange) -- callers
|
|
47
|
+
// gate on these flags BEFORE calling beginExchange.
|
|
48
|
+
this._combatants = [this.actor, target];
|
|
49
|
+
this.actor.inExchange = true;
|
|
50
|
+
target.inExchange = true;
|
|
51
|
+
// Initiating violence revokes your own surrender -- hands up means
|
|
52
|
+
// hands up (see commands/surrender.ts). The TARGET's surrender flag
|
|
53
|
+
// stays: being attacked while surrendered doesn't un-surrender you.
|
|
54
|
+
this.actor.surrendered = false;
|
|
55
|
+
// Both sides remember the fight -- this is what a parting shot keys
|
|
56
|
+
// off when either of them tries to walk away (see go.ts).
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
this.actor.combatOpponent = target;
|
|
59
|
+
this.actor.lastExchangeAt = now;
|
|
60
|
+
target.combatOpponent = this.actor;
|
|
61
|
+
target.lastExchangeAt = now;
|
|
62
|
+
}
|
|
63
|
+
/** Clears the per-combatant serialization flags. Safe to call twice. */
|
|
64
|
+
releaseExchange() {
|
|
65
|
+
for (const combatant of this._combatants) {
|
|
66
|
+
combatant.inExchange = false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Parks an NPC-driven action until every listed combatant's in-flight
|
|
71
|
+
* exchange settles, so the action fires in the lull instead of being
|
|
72
|
+
* discarded. Without this, an NPC's whole turn evaporated: its AI reacts
|
|
73
|
+
* to the player's attack within a second or two -- always mid-exchange --
|
|
74
|
+
* so every counterattack AND every wounded-NPC flee attempt was refused
|
|
75
|
+
* into its private history. Two real sessions of Krow talking trash while
|
|
76
|
+
* never once shooting back or making the door he was trying to flee
|
|
77
|
+
* through, which read to the player as the game stalling out.
|
|
78
|
+
*
|
|
79
|
+
* Resolves true when the flags clear (caller must revalidate everything:
|
|
80
|
+
* the waiter or its target may have died in the very exchange it sat
|
|
81
|
+
* out); false on timeout, meaning something is stuck and the caller
|
|
82
|
+
* should fall back to a plain refusal.
|
|
83
|
+
*/
|
|
84
|
+
static async waitForLull(combatants, timeoutMs = 15000) {
|
|
85
|
+
const busy = () => combatants.some(c => c.inExchange);
|
|
86
|
+
const deadline = Date.now() + timeoutMs;
|
|
87
|
+
while (busy()) {
|
|
88
|
+
if (Date.now() >= deadline)
|
|
89
|
+
return false;
|
|
90
|
+
await new Promise(resolve => setTimeout(resolve, 250));
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The initiative roll-off that opens an exchange: Reaction + Intuition
|
|
96
|
+
* (+ live commlink AR bonus, see Player.getInitiativePool). The winner
|
|
97
|
+
* acts first -- attacking someone faster than you means eating their
|
|
98
|
+
* strike before yours lands -- with ties going to the aggressor. Winning
|
|
99
|
+
* by 3+ net hits earns an INITIATIVE PASS: one extra strike at the end
|
|
100
|
+
* of the exchange (the wired-reflexes speed fantasy, without a full
|
|
101
|
+
* action-economy system).
|
|
102
|
+
*/
|
|
103
|
+
rollInitiative(aggressor, defender) {
|
|
104
|
+
const aggRoll = rollPool(aggressor.getInitiativePool());
|
|
105
|
+
const defRoll = rollPool(defender.getInitiativePool());
|
|
106
|
+
const aggressorFirst = aggRoll.hits >= defRoll.hits;
|
|
107
|
+
const first = aggressorFirst ? aggressor : defender;
|
|
108
|
+
const second = aggressorFirst ? defender : aggressor;
|
|
109
|
+
const net = Math.abs(aggRoll.hits - defRoll.hits);
|
|
110
|
+
const extraStriker = net >= 3 ? first : null;
|
|
111
|
+
this.logger.write(`Initiative: ${aggressor.name} ${aggRoll.hits} vs ${defender.name} ${defRoll.hits} -- ${first.name} first${extraStriker ? ' with an extra strike' : ''}.`);
|
|
112
|
+
const lines = [
|
|
113
|
+
`Initiative: ${aggressor.name} ${formatRoll(aggRoll)} vs ${defender.name} ${formatRoll(defRoll)}`,
|
|
114
|
+
` ${first.name} moves first.${extraStriker ? ` And they're FAST -- an extra strike this exchange.` : ''}`,
|
|
115
|
+
];
|
|
116
|
+
return { first, second, extraStriker, lines };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The player should see a fight's blow-by-blow only when they're actually
|
|
120
|
+
* there for it -- as a combatant or as a bystander in the same room. A
|
|
121
|
+
* brawl between two NPCs elsewhere in the scene stays off-screen, and
|
|
122
|
+
* skips the narration beats entirely.
|
|
123
|
+
*/
|
|
124
|
+
playerWitnesses() {
|
|
125
|
+
// Same room AND perceivable plane (see Player.canPerceive): a Matrix
|
|
126
|
+
// brawl is invisible static to a meat bystander in the same room; an
|
|
127
|
+
// astral observer sees a meat fight below, dimly.
|
|
128
|
+
const player = this.scene.getPlayer();
|
|
129
|
+
return player.currentLocation === this.actor.currentLocation && player.canPerceive(this.actor);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Pushes one narration block to the player's screen (when they're present
|
|
133
|
+
* for it) and holds a beat before whatever happens next. Only results of
|
|
134
|
+
* commands the PLAYER typed flow through ui.ts's response pipeline; an
|
|
135
|
+
* NPC's command result goes solely into that NPC's own AI history, so
|
|
136
|
+
* without self-announcing here the player would never see the dice of a
|
|
137
|
+
* fight an NPC started. The status box refreshes with every block so the
|
|
138
|
+
* condition monitor visibly ticks as damage lands.
|
|
139
|
+
*/
|
|
140
|
+
async announce(lines) {
|
|
141
|
+
if (lines.length === 0 || !this.playerWitnesses())
|
|
142
|
+
return;
|
|
143
|
+
this.logger.log(lines.join('\n'));
|
|
144
|
+
this.scene.updateStatus();
|
|
145
|
+
await new Promise(resolve => setTimeout(resolve, CombatExchange.BEAT_MS));
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The default, weapon-based strike profile. A firearm with a dry
|
|
149
|
+
* magazine automatically falls back to bare fists -- this covers the
|
|
150
|
+
* involuntary strikes (retaliation) where refusing to swing at all would
|
|
151
|
+
* make no sense; a deliberately INITIATED attack with a dry gun is
|
|
152
|
+
* blocked with a reload prompt before ever reaching here (attack.ts).
|
|
153
|
+
*/
|
|
154
|
+
weaponProfile(attacker, defender) {
|
|
155
|
+
const weapon = attacker.getWeaponProfile();
|
|
156
|
+
const weaponItem = attacker.equipment.rightHand?.weapon ?? attacker.equipment.leftHand?.weapon ?? undefined;
|
|
157
|
+
// An armed edge boost (see edge.ts) lands on whichever strike this
|
|
158
|
+
// builds -- the attacker's next swing.
|
|
159
|
+
const edgeBoost = attacker.consumeEdgeBoost();
|
|
160
|
+
if (weaponItem?.isFirearm() && (weaponItem.ammo <= 0 || weaponItem.jammed)) {
|
|
161
|
+
return {
|
|
162
|
+
label: `with bare fists -- ${weaponItem.name} is ${weaponItem.jammed ? 'locked (smartlink hacked)' : 'dry'}`,
|
|
163
|
+
pool: Math.max(0, attacker.getAttackPool() - weapon.poolBonus + edgeBoost),
|
|
164
|
+
damageBase: 2,
|
|
165
|
+
soakPool: defender.getSoakPool(),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
label: `with ${weapon.name}`,
|
|
170
|
+
pool: attacker.getAttackPool() + edgeBoost,
|
|
171
|
+
damageBase: weapon.damage,
|
|
172
|
+
soakPool: defender.getSoakPool(),
|
|
173
|
+
weaponItem: weaponItem ?? undefined,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/** One directed strike: attack vs defense, then soak. Mutates the defender. */
|
|
177
|
+
resolveStrike(attacker, defender, profile) {
|
|
178
|
+
const strike = profile ?? this.weaponProfile(attacker, defender);
|
|
179
|
+
const lines = [];
|
|
180
|
+
// Firing costs a round; the last one gets a warning line.
|
|
181
|
+
if (strike.weaponItem?.isFirearm()) {
|
|
182
|
+
strike.weaponItem.fireRound();
|
|
183
|
+
this.logger.write(`${attacker.name} fired ${strike.weaponItem.name} (${strike.weaponItem.ammo} rounds left).`);
|
|
184
|
+
}
|
|
185
|
+
const attack = rollPool(strike.pool);
|
|
186
|
+
const defense = rollPool(defender.getDefensePool());
|
|
187
|
+
lines.push(`${attacker.name} attacks ${defender.name} ${strike.label}:`);
|
|
188
|
+
lines.push(` Attack: ${formatRoll(attack)}`);
|
|
189
|
+
lines.push(` Defense: ${formatRoll(defense)}`);
|
|
190
|
+
const dryWarning = strike.weaponItem?.isFirearm() && strike.weaponItem.ammo === 0
|
|
191
|
+
? [` ${attacker.name}'s ${strike.weaponItem.name} clicks empty -- that was the last round.`]
|
|
192
|
+
: [];
|
|
193
|
+
const netHits = attack.hits - defense.hits;
|
|
194
|
+
if (attack.criticalGlitch) {
|
|
195
|
+
lines.push(` ${attacker.name}'s attack goes catastrophically wrong -- a clean miss, and everyone nearby heard it.`);
|
|
196
|
+
return [...lines, ...dryWarning];
|
|
197
|
+
}
|
|
198
|
+
if (netHits <= 0) {
|
|
199
|
+
lines.push(` ${defender.name} evades the attack.`);
|
|
200
|
+
return [...lines, ...dryWarning];
|
|
201
|
+
}
|
|
202
|
+
const incoming = strike.damageBase + netHits;
|
|
203
|
+
const soak = rollPool(strike.soakPool);
|
|
204
|
+
const dealt = Math.max(0, incoming - soak.hits);
|
|
205
|
+
lines.push(` Hit! ${netHits} net -- ${incoming} damage incoming.`);
|
|
206
|
+
lines.push(` Soak: ${formatRoll(soak)}`);
|
|
207
|
+
if (dealt === 0) {
|
|
208
|
+
lines.push(` ${defender.name} shrugs it off -- no damage gets through.`);
|
|
209
|
+
return [...lines, ...dryWarning];
|
|
210
|
+
}
|
|
211
|
+
// A jacked-in visitor takes MATRIX damage (only visitors have a
|
|
212
|
+
// simMode -- native ice's normal track IS its integrity, so it takes
|
|
213
|
+
// damage the ordinary way below). Matrix damage is HARDWARE damage:
|
|
214
|
+
// deck rider -- every box lands on the deck's device monitor; a
|
|
215
|
+
// full track BRICKS it: forced dump, dumpshock, fight
|
|
216
|
+
// over. Hot-sim ADDITIONALLY bleeds half the hit
|
|
217
|
+
// (rounded up) into the rider's real stun track.
|
|
218
|
+
// technomancer (no deck) -- a living persona has no hardware to
|
|
219
|
+
// sacrifice: EVERY box is real stun, both sim modes.
|
|
220
|
+
if (defender.plane === 'matrix' && defender.simMode) {
|
|
221
|
+
const deck = defender.activeDeck;
|
|
222
|
+
if (deck) {
|
|
223
|
+
deck.takeDeckDamage(dealt);
|
|
224
|
+
this.logger.write(`${attacker.name} dealt ${dealt} MATRIX damage to ${defender.name}'s ${deck.name} (${deck.deckDamage}/${deck.maxDeckBoxes}).`);
|
|
225
|
+
lines.push(` ${defender.name}'s ${deck.name} takes ${dealt} box${dealt === 1 ? '' : 'es'}: ${deck.deckSummary()}`);
|
|
226
|
+
if (defender.simMode === 'hot') {
|
|
227
|
+
const bio = Math.ceil(dealt / 2);
|
|
228
|
+
const { stun, overflow } = defender.takeStun(bio);
|
|
229
|
+
lines.push(` Biofeedback claws through the hot-sim link -- ${stun} stun: ${defender.stunSummary()}`);
|
|
230
|
+
if (overflow > 0) {
|
|
231
|
+
lines.push(` It's more than a mind can hold -- ${overflow} box${overflow === 1 ? '' : 'es'} spill physical: ${defender.conditionSummary()}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (deck.isBricked) {
|
|
235
|
+
lines.push(` ${deck.name} BRICKS -- the persona collapses into static --`);
|
|
236
|
+
lines.push(...leaveMatrix(this.scene, defender, { forced: true, reason: `The ice burns your deck out from under you.` }));
|
|
237
|
+
}
|
|
238
|
+
return [...lines, ...dryWarning];
|
|
239
|
+
}
|
|
240
|
+
// Deckless: the living persona. Fall through to the stun branch.
|
|
241
|
+
}
|
|
242
|
+
const asStun = strike.dealStun || (defender.plane === 'matrix' && !!defender.simMode);
|
|
243
|
+
if (asStun) {
|
|
244
|
+
const { stun, overflow } = defender.takeStun(dealt);
|
|
245
|
+
this.logger.write(`${attacker.name} dealt ${dealt} STUN to ${defender.name} (stun ${defender.stunTaken}/${defender.maxStunBoxes}, overflow ${overflow}).`);
|
|
246
|
+
const bioNote = !strike.dealStun ? ' (biofeedback)' : '';
|
|
247
|
+
lines.push(` ${defender.name} takes ${stun} stun${bioNote}: ${defender.stunSummary()}`);
|
|
248
|
+
if (overflow > 0) {
|
|
249
|
+
lines.push(` The beating goes past unconsciousness -- ${overflow} box${overflow === 1 ? '' : 'es'} spill into their body: ${defender.conditionSummary()}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
defender.takeDamage(dealt);
|
|
254
|
+
this.logger.write(`${attacker.name} dealt ${dealt} boxes to ${defender.name} (${defender.damageTaken}/${defender.maxConditionBoxes}).`);
|
|
255
|
+
lines.push(` ${defender.name} takes ${dealt} box${dealt === 1 ? '' : 'es'}: ${defender.conditionSummary()}`);
|
|
256
|
+
}
|
|
257
|
+
return [...lines, ...dryWarning];
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Death/knockout narration is announced BEFORE the removal side effects
|
|
261
|
+
* run -- removeActor() fires "on death drops" observer lines straight to
|
|
262
|
+
* the screen, so running it first would put the gear hitting the floor
|
|
263
|
+
* before the kill itself. `fallLine`/`gameOverLine` let a caller replace
|
|
264
|
+
* the default gunned-down flavor; `fatal: false` marks a knockout (a
|
|
265
|
+
* full STUN track -- see cast.ts drain) rather than a death: same
|
|
266
|
+
* out-of-the-run consequences, honestly narrated as unconsciousness.
|
|
267
|
+
*/
|
|
268
|
+
async resolveDeath(fallen, killer, opts) {
|
|
269
|
+
const room = fallen.currentLocation;
|
|
270
|
+
const isPlayer = fallen === this.scene.getPlayer();
|
|
271
|
+
// Idempotence: if this NPC was already removed (a concurrent exchange
|
|
272
|
+
// beat us to the kill), don't narrate a second death or re-drop gear.
|
|
273
|
+
if (!isPlayer && this.scene.getActor(fallen.name) !== fallen) {
|
|
274
|
+
this.logger.write(`CombatExchange: ${fallen.name} already resolved as down -- skipping duplicate death.`);
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
const verb = (opts?.fatal ?? true) ? 'killed' : 'knocked out';
|
|
278
|
+
this.logger.write(`CombatExchange: ${killer.name} ${verb} ${fallen.name} in ${room.name}.`);
|
|
279
|
+
this.scene.addWorldEvent(`${killer.name} ${verb} ${fallen.name} in ${room.name}.`);
|
|
280
|
+
if (isPlayer) {
|
|
281
|
+
const lines = [opts?.fallLine ?? `You collapse. The last thing you see is ${killer.name} standing over you.`];
|
|
282
|
+
await this.announce(lines);
|
|
283
|
+
// Same flow as kill.ts and a fatal bypass: freeze input, self-announce
|
|
284
|
+
// so the reason stays on screen, and wait for ctrl-c ("You died!").
|
|
285
|
+
const gameOverLine = opts?.gameOverLine ?? `💀 GAME OVER: ${fallen.name} was gunned down by ${killer.name}.`;
|
|
286
|
+
void this.scene.endGame().then(() => {
|
|
287
|
+
this.logger.logWithColor(gameOverLine, 'red');
|
|
288
|
+
this.logger.logWithColor(`Press ctrl-c to quit.`, 'red');
|
|
289
|
+
});
|
|
290
|
+
return ['', ...lines];
|
|
291
|
+
}
|
|
292
|
+
const npcFall = (opts?.fatal ?? true)
|
|
293
|
+
? `${fallen.name} goes down -- dead before they hit the floor. Their gear scatters across ${room.name}.`
|
|
294
|
+
: `${fallen.name} collapses, out cold -- down for the count. Their gear scatters across ${room.name}.`;
|
|
295
|
+
const lines = [opts?.fallLine ?? npcFall];
|
|
296
|
+
await this.announce(lines);
|
|
297
|
+
// scene.removeActor drops everything they carried AND wore where they
|
|
298
|
+
// fell -- its observer lines now correctly land after the kill above.
|
|
299
|
+
this.scene.removeActor(fallen.name);
|
|
300
|
+
return ['', ...lines];
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* The screen already got everything block-by-block, so the player-typed
|
|
304
|
+
* path returns '' (ui.ts prints the return value -- a second copy would
|
|
305
|
+
* double-display). An NPC actor gets the full report back instead: that
|
|
306
|
+
* return value is what lands in the NPC's AI history, which is how it
|
|
307
|
+
* knows how its own fight went -- but only after the follow-up pause,
|
|
308
|
+
* which is the player's window to act before the NPC's next exchange.
|
|
309
|
+
*/
|
|
310
|
+
async finishExchange(report) {
|
|
311
|
+
// The exchange is resolved -- movement unlocks BEFORE the follow-up
|
|
312
|
+
// pause below, so the pause stays the window to flee or heal.
|
|
313
|
+
this.scene.endPlayerExchange();
|
|
314
|
+
this.releaseExchange();
|
|
315
|
+
this.scene.updateStatus();
|
|
316
|
+
// The fight autopilot's trigger: a settled exchange involving the
|
|
317
|
+
// player schedules their next auto action (see utilities/auto-fight.ts).
|
|
318
|
+
this.scene.notifyExchangeSettled(this._combatants);
|
|
319
|
+
if (this.actor === this.scene.getPlayer()) {
|
|
320
|
+
return '';
|
|
321
|
+
}
|
|
322
|
+
if (this.playerWitnesses()) {
|
|
323
|
+
await new Promise(resolve => setTimeout(resolve, CombatExchange.NPC_FOLLOWUP_PAUSE_MS));
|
|
324
|
+
}
|
|
325
|
+
return report.join('\n');
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
//# sourceMappingURL=combat-exchange.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"combat-exchange.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/utilities/combat-exchange.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Visual styling for commlink/AR-context messages. A call stays in the main
|
|
2
|
+
// log window (it's not a separate view -- it's augmented reality laid over
|
|
3
|
+
// wherever the player already is), but is tagged with a distinct icon and
|
|
4
|
+
// color so it visibly reads as a different "space" than in-person dialogue.
|
|
5
|
+
//
|
|
6
|
+
// Future contexts (the Astral Plane, VR hot-sim/cold-sim) are expected to
|
|
7
|
+
// follow the same shape: their own icon/color pair, applied the same way --
|
|
8
|
+
// not a bigger context system, just more constants like this one, until
|
|
9
|
+
// there's an actual second context to justify one.
|
|
10
|
+
export const CALL_ICON = '📞';
|
|
11
|
+
export const CALL_COLOR = 'cyan';
|
|
12
|
+
//# sourceMappingURL=comm-style.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comm-style.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/utilities/comm-style.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,2EAA2E;AAC3E,0EAA0E;AAC1E,4EAA4E;AAC5E,EAAE;AACF,0EAA0E;AAC1E,4EAA4E;AAC5E,wEAAwE;AACxE,mDAAmD;AACnD,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,CAAC;AAC9B,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shadowrun-style d6 dice pools: roll N six-siders, every 5 or 6 is a hit.
|
|
3
|
+
* A "glitch" is rolling 1s on more than half the pool (something went ugly
|
|
4
|
+
* even if you succeeded); a "critical glitch" is glitching with zero hits
|
|
5
|
+
* (it went ugly AND you failed). Pure functions, no game-state knowledge --
|
|
6
|
+
* combat resolution lives in commands/attack.ts.
|
|
7
|
+
*/
|
|
8
|
+
export interface IDiceRoll {
|
|
9
|
+
pool: number;
|
|
10
|
+
rolls: number[];
|
|
11
|
+
hits: number;
|
|
12
|
+
glitch: boolean;
|
|
13
|
+
criticalGlitch: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function rollPool(pool: number): IDiceRoll;
|
|
16
|
+
/**
|
|
17
|
+
* One-line render of a roll for the game log, e.g.:
|
|
18
|
+
* "8d6 [6 5 5 3 2 1 4 6] -> 4 hits"
|
|
19
|
+
* The raw dice faces are shown on purpose -- watching the pool land is
|
|
20
|
+
* most of the fun of dice-pool combat.
|
|
21
|
+
*/
|
|
22
|
+
export declare function formatRoll(roll: IDiceRoll): string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shadowrun-style d6 dice pools: roll N six-siders, every 5 or 6 is a hit.
|
|
3
|
+
* A "glitch" is rolling 1s on more than half the pool (something went ugly
|
|
4
|
+
* even if you succeeded); a "critical glitch" is glitching with zero hits
|
|
5
|
+
* (it went ugly AND you failed). Pure functions, no game-state knowledge --
|
|
6
|
+
* combat resolution lives in commands/attack.ts.
|
|
7
|
+
*/
|
|
8
|
+
export function rollPool(pool) {
|
|
9
|
+
const size = Math.max(0, Math.floor(pool));
|
|
10
|
+
const rolls = Array.from({ length: size }, () => 1 + Math.floor(Math.random() * 6));
|
|
11
|
+
const hits = rolls.filter(r => r >= 5).length;
|
|
12
|
+
const ones = rolls.filter(r => r === 1).length;
|
|
13
|
+
const glitch = size > 0 && ones > size / 2;
|
|
14
|
+
return {
|
|
15
|
+
pool: size,
|
|
16
|
+
rolls,
|
|
17
|
+
hits,
|
|
18
|
+
glitch,
|
|
19
|
+
criticalGlitch: glitch && hits === 0,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* One-line render of a roll for the game log, e.g.:
|
|
24
|
+
* "8d6 [6 5 5 3 2 1 4 6] -> 4 hits"
|
|
25
|
+
* The raw dice faces are shown on purpose -- watching the pool land is
|
|
26
|
+
* most of the fun of dice-pool combat.
|
|
27
|
+
*/
|
|
28
|
+
export function formatRoll(roll) {
|
|
29
|
+
if (roll.pool === 0)
|
|
30
|
+
return '0d6 -> no dice to roll';
|
|
31
|
+
const glitchTag = roll.criticalGlitch ? ' -- CRITICAL GLITCH' : roll.glitch ? ' -- glitch' : '';
|
|
32
|
+
return `${roll.pool}d6 [${roll.rolls.join(' ')}] -> ${roll.hits} hit${roll.hits === 1 ? '' : 's'}${glitchTag}`;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=dice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dice.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/utilities/dice.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IACpF,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IAC/C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC;IAE3C,OAAO;QACL,IAAI,EAAE,IAAI;QACV,KAAK;QACL,IAAI;QACJ,MAAM;QACN,cAAc,EAAE,MAAM,IAAI,IAAI,KAAK,CAAC;KACrC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,IAAe;IACxC,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,wBAAwB,CAAC;IACrD,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAChG,OAAO,GAAG,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,SAAS,EAAE,CAAC;AACjH,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared fuzzy name-matching, extracted from Inventory's item resolution so
|
|
3
|
+
* actor targeting (attack.ts, cast.ts) behaves the same way "take" does --
|
|
4
|
+
* found via a real session where "cast manabolt at mr krow" failed against
|
|
5
|
+
* "Mr. Krow" over a missing period. Three tiers, in order of confidence:
|
|
6
|
+
*
|
|
7
|
+
* 1. Exact match on the loosely-normalized strings (case, punctuation,
|
|
8
|
+
* and extra whitespace ignored -- "mr krow" == "Mr. Krow").
|
|
9
|
+
* 2. Substring containment, either direction -- short aliases ("krow"),
|
|
10
|
+
* or extra/missing words at the edges.
|
|
11
|
+
* 3. Word-overlap with light per-word typo tolerance (Levenshtein <= 1
|
|
12
|
+
* on words longer than 3 chars, stopwords ignored) -- misspellings
|
|
13
|
+
* and reordered/inserted words.
|
|
14
|
+
*
|
|
15
|
+
* A tier only matches when it produces exactly ONE candidate (or a clear
|
|
16
|
+
* best score) -- an ambiguous guess deliberately misses rather than
|
|
17
|
+
* picking someone at random.
|
|
18
|
+
*/
|
|
19
|
+
export declare const STOPWORDS: Set<string>;
|
|
20
|
+
/** Lowercase, strip punctuation, collapse whitespace. */
|
|
21
|
+
export declare function normalizeLoose(s: string): string;
|
|
22
|
+
export declare function significantWords(s: string): string[];
|
|
23
|
+
export declare function wordsAreCloseEnough(a: string, b: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Damerau-Levenshtein (OSA variant): like plain Levenshtein but a swap of
|
|
26
|
+
* two adjacent letters counts as ONE edit, not two -- transpositions
|
|
27
|
+
* ("jhonson" for "johnson", "teh" for "the") are the single most common
|
|
28
|
+
* typo class, and plain Levenshtein prices them out of the <=1 tolerance.
|
|
29
|
+
*/
|
|
30
|
+
export declare function levenshteinDistance(a: string, b: string): number;
|
|
31
|
+
/**
|
|
32
|
+
* Picks the single name from `names` that `input` most plausibly refers
|
|
33
|
+
* to, or undefined when nothing (or more than one thing) qualifies.
|
|
34
|
+
* Returns the ORIGINAL name string, not the normalized form.
|
|
35
|
+
*/
|
|
36
|
+
export declare function fuzzyPickName(input: string, names: string[]): string | undefined;
|