@maka/maka-cli 5.3.16 → 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/create.js +1 -1
- package/bundle/typescript/src/commands/create.js.map +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
|
@@ -2,6 +2,7 @@ import { Category } from '../types/shared/item-enum.js';
|
|
|
2
2
|
import { Logger } from '../utilities/logger.js';
|
|
3
3
|
import { Inventory } from './inventory.js';
|
|
4
4
|
import { AbstractPlayer } from '../types/shared/abstracts.js';
|
|
5
|
+
export const STANCE_CYCLE = ['manual', 'guarded', 'aggressive'];
|
|
5
6
|
export class Player extends AbstractPlayer {
|
|
6
7
|
_playerName;
|
|
7
8
|
_currentLocation;
|
|
@@ -11,6 +12,167 @@ export class Player extends AbstractPlayer {
|
|
|
11
12
|
_lifestyleChoices = [];
|
|
12
13
|
logger;
|
|
13
14
|
_currency = 0; // for seeding/debug only
|
|
15
|
+
// Whoever this actor most recently placed/received a commlink call with.
|
|
16
|
+
// No explicit "hang up" -- placing a new call just replaces it. Lets a
|
|
17
|
+
// reply via the ordinary "say" command reach a call partner in a
|
|
18
|
+
// different room, the same way a real call would, without the NPC (or
|
|
19
|
+
// player) needing to learn a separate reply verb. See say.ts and call.ts.
|
|
20
|
+
activeCallPartner;
|
|
21
|
+
// Running silent (see commands/silent.ts): the commlink stays equipped
|
|
22
|
+
// but stops broadcasting -- no calls out, and nobody can raise this
|
|
23
|
+
// actor (call.ts checks both ends). Toggling it drops any active call.
|
|
24
|
+
runningSilent = false;
|
|
25
|
+
// Whoever this actor most recently traded combat exchanges with, and
|
|
26
|
+
// when -- set for BOTH sides by CombatExchange.beginExchange(). Walking
|
|
27
|
+
// out on a live, recent opponent invites a parting shot (see go.ts);
|
|
28
|
+
// firing one (or a death) clears it.
|
|
29
|
+
combatOpponent;
|
|
30
|
+
lastExchangeAt = 0;
|
|
31
|
+
// True while this actor is a combatant in an exchange that's still
|
|
32
|
+
// resolving. Serializes combat per actor: a second attack involving a
|
|
33
|
+
// busy combatant is refused instead of interleaving -- found via a real
|
|
34
|
+
// session where an NPC's overlapping AI loops fired two exchanges at
|
|
35
|
+
// once and the target died TWICE (two kill events, two narrations).
|
|
36
|
+
inExchange = false;
|
|
37
|
+
// Fight-or-flight autopilot (see utilities/auto-fight.ts): the FIGHT
|
|
38
|
+
// half runs automatically at the set temperature, the flight stays
|
|
39
|
+
// manual -- always. Tab cycles it in the UI; "stance" sets it by name.
|
|
40
|
+
// manual -- today's behavior, you type every swing
|
|
41
|
+
// guarded -- auto-fights, but finishes non-lethally (switches to
|
|
42
|
+
// subdue when the target is nearly down) and stands down
|
|
43
|
+
// if they surrender
|
|
44
|
+
// aggressive -- fights until they're dead; surrender means nothing
|
|
45
|
+
autoStance = 'manual';
|
|
46
|
+
// Set by the "surrender" command: this actor has thrown their hands up.
|
|
47
|
+
// Cleared the moment they initiate violence again (see
|
|
48
|
+
// CombatExchange.beginExchange). Guarded auto-fight honors it;
|
|
49
|
+
// aggressive doesn't have to.
|
|
50
|
+
surrendered = false;
|
|
51
|
+
// ==================== Planes (see utilities/planes.ts) ====================
|
|
52
|
+
// Which plane this actor currently exists on. Ice NPCs live on 'matrix',
|
|
53
|
+
// spirits on 'astral', natively and permanently; a player shifts planes
|
|
54
|
+
// via jack in / project and back.
|
|
55
|
+
plane = 'meat';
|
|
56
|
+
// While off the meat plane, the empty body slumps HERE -- visible,
|
|
57
|
+
// attackable (see planes.ts resolveBodyAttack), and where you snap back
|
|
58
|
+
// to. undefined while on the meat plane, or for native off-plane actors
|
|
59
|
+
// (ice and spirits have no body to leave behind).
|
|
60
|
+
bodyRoom;
|
|
61
|
+
// Matrix only: cold or hot sim, chosen at jack-in. undefined off-Matrix
|
|
62
|
+
// and for native ice.
|
|
63
|
+
simMode;
|
|
64
|
+
// Astral only: room-moves made while projecting. The longer you're out,
|
|
65
|
+
// the harder the snap-back -- drained as stun on return (planes.ts).
|
|
66
|
+
astralTicks = 0;
|
|
67
|
+
// The DECK you're jacked in on (set by planes.ts enterMatrix, cleared on
|
|
68
|
+
// leaving). Matrix damage is HARDWARE damage: it lands on this item's
|
|
69
|
+
// device condition monitor (see Item's cyberdeck section), persists
|
|
70
|
+
// across jack-outs, and a full track BRICKS the deck -- forced dump,
|
|
71
|
+
// and no re-entry on that deck until it's repaired.
|
|
72
|
+
activeDeck;
|
|
73
|
+
/** The best working cyberdeck carried (bricked ones don't count). */
|
|
74
|
+
getCyberdeck() {
|
|
75
|
+
return this._inventory.getAllItems()
|
|
76
|
+
.filter(i => i.isCyberdeck() && !i.isBricked)
|
|
77
|
+
.sort((a, b) => b.qualityBonus - a.qualityBonus)[0];
|
|
78
|
+
}
|
|
79
|
+
/** Any deck at all, bricked included -- for status displays and repair. */
|
|
80
|
+
getAnyCyberdeck() {
|
|
81
|
+
return this.activeDeck ?? this._inventory.getAllItems()
|
|
82
|
+
.filter(i => i.isCyberdeck())
|
|
83
|
+
.sort((a, b) => b.qualityBonus - a.qualityBonus)[0];
|
|
84
|
+
}
|
|
85
|
+
// ==================== PAN (see commands/pan.ts, hack.ts) ====================
|
|
86
|
+
// Hacked dark: an enemy cracked your PAN and cut the AR overlay. Costs
|
|
87
|
+
// the commlink initiative bonus and drops calls, until "reboot". Distinct
|
|
88
|
+
// from runningSilent (chosen dark) -- this one was done TO you.
|
|
89
|
+
arOffline = false;
|
|
90
|
+
/**
|
|
91
|
+
* The PAN's master device: your slaved gear defends behind its
|
|
92
|
+
* firewall (SR slaving, simplified). Best working deck beats the
|
|
93
|
+
* equipped commlink; no devices = no PAN (and nothing to hack).
|
|
94
|
+
*/
|
|
95
|
+
getPanMaster() {
|
|
96
|
+
const deck = this.getCyberdeck();
|
|
97
|
+
const link = this._equipment.head?.commlink ?? undefined;
|
|
98
|
+
if (deck && link)
|
|
99
|
+
return deck.qualityBonus >= link.qualityBonus ? deck : link;
|
|
100
|
+
return deck ?? link;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Defense against a PAN intrusion: Logic + the master's firewall
|
|
104
|
+
* (+2 while running silent -- a hidden PAN must be FOUND first).
|
|
105
|
+
*/
|
|
106
|
+
getPanDefensePool() {
|
|
107
|
+
const master = this.getPanMaster();
|
|
108
|
+
const silentBonus = this.runningSilent ? 2 : 0;
|
|
109
|
+
return Math.max(0, this.logic + (master?.qualityBonus ?? 0) + silentBonus);
|
|
110
|
+
}
|
|
111
|
+
get matrixTaken() {
|
|
112
|
+
return this.getAnyCyberdeck()?.deckDamage ?? 0;
|
|
113
|
+
}
|
|
114
|
+
get maxMatrixBoxes() {
|
|
115
|
+
return this.getAnyCyberdeck()?.maxDeckBoxes ?? 0;
|
|
116
|
+
}
|
|
117
|
+
/** Routes Matrix combat damage into the jacked-in deck's hardware track. */
|
|
118
|
+
takeMatrixDamage(boxes) {
|
|
119
|
+
const deck = this.getAnyCyberdeck();
|
|
120
|
+
return deck ? deck.takeDeckDamage(boxes) : 0;
|
|
121
|
+
}
|
|
122
|
+
get matrixCrashed() {
|
|
123
|
+
return this.getAnyCyberdeck()?.isBricked ?? false;
|
|
124
|
+
}
|
|
125
|
+
/** The deck track, same rendering as conditionSummary(). */
|
|
126
|
+
matrixSummary() {
|
|
127
|
+
return this.getAnyCyberdeck()?.deckSummary() ?? '(no deck)';
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Cross-plane perception, the single rule everything filters through:
|
|
131
|
+
* same plane sees same plane; the astral perceives the meat world
|
|
132
|
+
* (shifted, not gone -- auras and movement, dimly); meat and Matrix see
|
|
133
|
+
* nothing beyond their own plane. Used by see/hear guards, targeting,
|
|
134
|
+
* and room listings.
|
|
135
|
+
*/
|
|
136
|
+
canPerceive(other) {
|
|
137
|
+
if (this.plane === other.plane)
|
|
138
|
+
return true;
|
|
139
|
+
return this.plane === 'astral' && other.plane === 'meat';
|
|
140
|
+
}
|
|
141
|
+
// True while an AI-driven action is parked waiting for an in-flight
|
|
142
|
+
// exchange to settle (CombatExchange.waitForLull). One queued action per
|
|
143
|
+
// actor: a second colliding attempt is refused outright instead of
|
|
144
|
+
// stacking a burst of delayed attacks behind the same lull.
|
|
145
|
+
waitingForLull = false;
|
|
146
|
+
// Combat attributes (see ICombatAttributes). Defaults make an unseeded
|
|
147
|
+
// actor a competent-but-mortal street-level runner; seeds can override
|
|
148
|
+
// per-NPC to make a hardened enforcer or a soft civilian.
|
|
149
|
+
body;
|
|
150
|
+
agility;
|
|
151
|
+
reaction;
|
|
152
|
+
combatSkill;
|
|
153
|
+
magic;
|
|
154
|
+
strength;
|
|
155
|
+
willpower;
|
|
156
|
+
logic;
|
|
157
|
+
intuition;
|
|
158
|
+
charisma;
|
|
159
|
+
edge;
|
|
160
|
+
resonance;
|
|
161
|
+
adept;
|
|
162
|
+
// Edge is a per-job pool: spend a point (see commands/edge.ts) to arm a
|
|
163
|
+
// boost that supercharges your next strike. Refreshed to `edge` on every
|
|
164
|
+
// new job (see Game.continueToNextScene).
|
|
165
|
+
edgeRemaining = 0;
|
|
166
|
+
edgeBoostArmed = false;
|
|
167
|
+
// Physical condition monitor, straight from SR5: 8 + (Body / 2, rounded
|
|
168
|
+
// up) boxes. Damage fills boxes; a full track is death.
|
|
169
|
+
_damageTaken = 0;
|
|
170
|
+
// Stun condition monitor -- the second track, filled by spell drain (see
|
|
171
|
+
// cast.ts). A full stun track is a knockout, not death, but overflow
|
|
172
|
+
// past it spills into the physical track. Sized off Reaction as the
|
|
173
|
+
// stand-in for SR's Willpower (the closest thing to a mental attribute
|
|
174
|
+
// here without adding one): 8 + (Reaction / 2, rounded up).
|
|
175
|
+
_stunTaken = 0;
|
|
14
176
|
constructor(config) {
|
|
15
177
|
super();
|
|
16
178
|
this._playerName = config.playerName;
|
|
@@ -18,6 +180,20 @@ export class Player extends AbstractPlayer {
|
|
|
18
180
|
this._inventory = new Inventory();
|
|
19
181
|
this.logger = Logger.getInstance();
|
|
20
182
|
this._lifestyleChoices = config.lifestyleChoices ?? [];
|
|
183
|
+
this.body = config.combat?.body ?? 4;
|
|
184
|
+
this.agility = config.combat?.agility ?? 4;
|
|
185
|
+
this.reaction = config.combat?.reaction ?? 4;
|
|
186
|
+
this.combatSkill = config.combat?.combatSkill ?? 3;
|
|
187
|
+
this.magic = config.combat?.magic ?? 0;
|
|
188
|
+
this.strength = config.combat?.strength ?? 3;
|
|
189
|
+
this.willpower = config.combat?.willpower ?? 3;
|
|
190
|
+
this.logic = config.combat?.logic ?? 3;
|
|
191
|
+
this.intuition = config.combat?.intuition ?? 3;
|
|
192
|
+
this.charisma = config.combat?.charisma ?? 3;
|
|
193
|
+
this.edge = config.combat?.edge ?? 2;
|
|
194
|
+
this.edgeRemaining = this.edge;
|
|
195
|
+
this.resonance = config.combat?.resonance ?? 0;
|
|
196
|
+
this.adept = config.combat?.adept ?? false;
|
|
21
197
|
this._equipment = this.initializeEquipment();
|
|
22
198
|
}
|
|
23
199
|
initializeEquipment() {
|
|
@@ -31,6 +207,7 @@ export class Player extends AbstractPlayer {
|
|
|
31
207
|
commlink: null
|
|
32
208
|
},
|
|
33
209
|
torso: {
|
|
210
|
+
clothing: null,
|
|
34
211
|
armorVest: null,
|
|
35
212
|
armorCoat: null,
|
|
36
213
|
backpack: null,
|
|
@@ -97,7 +274,9 @@ export class Player extends AbstractPlayer {
|
|
|
97
274
|
this._equipment = newEquipment;
|
|
98
275
|
}
|
|
99
276
|
get maxCarryingWeight() {
|
|
100
|
-
|
|
277
|
+
// Strength-derived: 20 + 10/point. The old flat default (50kg) is
|
|
278
|
+
// exactly what a strength-3 runner gets, so pre-sheet balance holds.
|
|
279
|
+
return 20 + this.strength * 10;
|
|
101
280
|
}
|
|
102
281
|
set maxCarryingWeight(weight) {
|
|
103
282
|
if (weight > 0) {
|
|
@@ -109,10 +288,19 @@ export class Player extends AbstractPlayer {
|
|
|
109
288
|
}
|
|
110
289
|
get currentWeight() {
|
|
111
290
|
let totalWeight = this._inventory.getWeight();
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
291
|
+
// Equipment is nested one level deeper than inventory: groups (head,
|
|
292
|
+
// torso...) containing slots. This used to add `group.weight` --
|
|
293
|
+
// undefined -- which turned the total into NaN, so the carrying cap
|
|
294
|
+
// NEVER triggered (NaN compares false against everything). Found when
|
|
295
|
+
// a "take all" verification watched a 100kg engine block walk into a
|
|
296
|
+
// 50kg pack.
|
|
297
|
+
for (const group of Object.values(this._equipment)) {
|
|
298
|
+
if (!group)
|
|
299
|
+
continue;
|
|
300
|
+
for (const item of Object.values(group)) {
|
|
301
|
+
if (item) {
|
|
302
|
+
totalWeight += item.weight;
|
|
303
|
+
}
|
|
116
304
|
}
|
|
117
305
|
}
|
|
118
306
|
return totalWeight;
|
|
@@ -149,12 +337,18 @@ export class Player extends AbstractPlayer {
|
|
|
149
337
|
if (!groupRef) {
|
|
150
338
|
return `You have no ${group} equipment group initialized.`;
|
|
151
339
|
}
|
|
152
|
-
|
|
153
|
-
|
|
340
|
+
const previousItem = groupRef[slot];
|
|
341
|
+
let swapNotice = '';
|
|
342
|
+
if (previousItem) {
|
|
343
|
+
if (previousItem.name === item.name) {
|
|
344
|
+
return `You already have ${item.name} equipped in your ${group}.${slot} slot.`;
|
|
345
|
+
}
|
|
346
|
+
this.unequipBySlot(group, slot);
|
|
347
|
+
swapNotice = ` (unequipped ${previousItem.name})`;
|
|
154
348
|
}
|
|
155
349
|
groupRef[slot] = item;
|
|
156
350
|
this.removeInventory(item);
|
|
157
|
-
return `You equipped ${item.name} in your ${group}.${slot} slot
|
|
351
|
+
return `You equipped ${item.name} in your ${group}.${slot} slot.${swapNotice}`;
|
|
158
352
|
}
|
|
159
353
|
unequip(input) {
|
|
160
354
|
const slotInfo = this.determineEquipmentSlot(input);
|
|
@@ -197,8 +391,14 @@ export class Player extends AbstractPlayer {
|
|
|
197
391
|
case Category.Commlink:
|
|
198
392
|
return { group: 'head', slot: 'commlink' };
|
|
199
393
|
// TORSO
|
|
394
|
+
case Category.Clothing:
|
|
395
|
+
case 'shirt':
|
|
396
|
+
case 'hoodie':
|
|
397
|
+
case 'top':
|
|
398
|
+
return { group: 'torso', slot: 'clothing' };
|
|
200
399
|
case Category.Armor:
|
|
201
400
|
case Category.BodyArmor:
|
|
401
|
+
case Category.ArmorClothing:
|
|
202
402
|
case 'armor vest':
|
|
203
403
|
return { group: 'torso', slot: 'armorVest' };
|
|
204
404
|
case 'armor coat':
|
|
@@ -295,11 +495,15 @@ export class Player extends AbstractPlayer {
|
|
|
295
495
|
return `You unequipped ${itemToUnequip.name} from your ${group}.${slot} slot.`;
|
|
296
496
|
}
|
|
297
497
|
hear(actor, message) {
|
|
498
|
+
// Cross-plane silence: a Matrix persona's chatter never reaches meat
|
|
499
|
+
// ears and vice versa; the astral hears the meat world it overlays.
|
|
500
|
+
if (!this.canPerceive(actor))
|
|
501
|
+
return;
|
|
298
502
|
this.logger.write(`${this.name} heard ${actor.name}`);
|
|
299
503
|
this.logger.logWithColor(`{white-fg}${actor.name}:{/white-fg} ${message}`, (actor === this) ? 'yellow' : 'green');
|
|
300
504
|
}
|
|
301
505
|
see(actor, verb, details) {
|
|
302
|
-
if (actor !== this) {
|
|
506
|
+
if (actor !== this && this.canPerceive(actor)) {
|
|
303
507
|
this.logger.write(`You see ${actor.name} ${verb} ${details}`);
|
|
304
508
|
this.logger.logWithColor(`${actor.name} ${verb} ${details}`, 'cyan');
|
|
305
509
|
}
|
|
@@ -323,6 +527,318 @@ export class Player extends AbstractPlayer {
|
|
|
323
527
|
this.logger.write(`${this.name} has left ${this._currentLocation.name}`);
|
|
324
528
|
}
|
|
325
529
|
}
|
|
530
|
+
// ======================== Combat (see commands/attack.ts) ========================
|
|
531
|
+
static ARMOR_CATEGORIES = new Set([
|
|
532
|
+
Category.Armor, Category.BodyArmor, Category.ArmorClothing, Category.Shield,
|
|
533
|
+
Category.Helmet, Category.ArmArmor, Category.LegArmor, Category.Boots, Category.Gloves,
|
|
534
|
+
]);
|
|
535
|
+
get maxConditionBoxes() {
|
|
536
|
+
return 8 + Math.ceil(this.body / 2);
|
|
537
|
+
}
|
|
538
|
+
get damageTaken() {
|
|
539
|
+
return this._damageTaken;
|
|
540
|
+
}
|
|
541
|
+
get maxStunBoxes() {
|
|
542
|
+
// Willpower-based, as SR intends (Reaction stood in before Willpower
|
|
543
|
+
// existed on the sheet). Default willpower 3 keeps the same 10 boxes
|
|
544
|
+
// the old reaction-4 default produced.
|
|
545
|
+
return 8 + Math.ceil(this.willpower / 2);
|
|
546
|
+
}
|
|
547
|
+
get stunTaken() {
|
|
548
|
+
return this._stunTaken;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* SR5 wound modifier: -1 die on every pool per 3 full boxes of damage,
|
|
552
|
+
* and the two tracks STACK -- being shot and drained both make you worse
|
|
553
|
+
* at everything, including not getting shot.
|
|
554
|
+
*/
|
|
555
|
+
get woundModifier() {
|
|
556
|
+
return -(Math.floor(this._damageTaken / 3) + Math.floor(this._stunTaken / 3));
|
|
557
|
+
}
|
|
558
|
+
/** Dead: the physical track is full. */
|
|
559
|
+
isDown() {
|
|
560
|
+
return this._damageTaken >= this.maxConditionBoxes;
|
|
561
|
+
}
|
|
562
|
+
/** Out cold: the stun track is full (but not necessarily dead). */
|
|
563
|
+
isUnconscious() {
|
|
564
|
+
return this._stunTaken >= this.maxStunBoxes;
|
|
565
|
+
}
|
|
566
|
+
/** Out of the fight either way -- the gate every action should check. */
|
|
567
|
+
isIncapacitated() {
|
|
568
|
+
return this.isDown() || this.isUnconscious();
|
|
569
|
+
}
|
|
570
|
+
/** Applies damage boxes; returns the number actually taken. */
|
|
571
|
+
takeDamage(boxes) {
|
|
572
|
+
const taken = Math.max(0, Math.floor(boxes));
|
|
573
|
+
this._damageTaken = Math.min(this.maxConditionBoxes, this._damageTaken + taken);
|
|
574
|
+
return taken;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Clears damage boxes; returns how many were actually healed (can be
|
|
578
|
+
* less than asked when the track is nearly clean). Healing also restores
|
|
579
|
+
* wound-modifier dice, since that's derived from damageTaken.
|
|
580
|
+
*/
|
|
581
|
+
healDamage(boxes) {
|
|
582
|
+
const before = this._damageTaken;
|
|
583
|
+
this._damageTaken = Math.max(0, before - Math.max(0, Math.floor(boxes)));
|
|
584
|
+
return before - this._damageTaken;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Applies stun boxes; anything past a full stun track OVERFLOWS into the
|
|
588
|
+
* physical track (SR's rule -- enough drain doesn't just knock you out,
|
|
589
|
+
* it starts killing you). Returns what landed where.
|
|
590
|
+
*/
|
|
591
|
+
takeStun(boxes) {
|
|
592
|
+
const asked = Math.max(0, Math.floor(boxes));
|
|
593
|
+
const space = this.maxStunBoxes - this._stunTaken;
|
|
594
|
+
const stun = Math.min(asked, space);
|
|
595
|
+
this._stunTaken += stun;
|
|
596
|
+
const overflow = asked - stun;
|
|
597
|
+
if (overflow > 0) {
|
|
598
|
+
this.takeDamage(overflow);
|
|
599
|
+
}
|
|
600
|
+
return { stun, overflow };
|
|
601
|
+
}
|
|
602
|
+
/** e.g. "■■■□□□□□□□ (3/10)" */
|
|
603
|
+
conditionSummary() {
|
|
604
|
+
const max = this.maxConditionBoxes;
|
|
605
|
+
const filled = Math.min(max, this._damageTaken);
|
|
606
|
+
return `${'■'.repeat(filled)}${'□'.repeat(max - filled)} (${filled}/${max})`;
|
|
607
|
+
}
|
|
608
|
+
/** Clears stun boxes; returns how many were actually recovered. */
|
|
609
|
+
healStun(boxes) {
|
|
610
|
+
const before = this._stunTaken;
|
|
611
|
+
this._stunTaken = Math.max(0, before - Math.max(0, Math.floor(boxes)));
|
|
612
|
+
return before - this._stunTaken;
|
|
613
|
+
}
|
|
614
|
+
/** The stun track, same rendering as conditionSummary(). */
|
|
615
|
+
stunSummary() {
|
|
616
|
+
const max = this.maxStunBoxes;
|
|
617
|
+
const filled = Math.min(max, this._stunTaken);
|
|
618
|
+
return `${'■'.repeat(filled)}${'□'.repeat(max - filled)} (${filled}/${max})`;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* The weapon actually in hand (right hand first, then left), or bare
|
|
622
|
+
* fists. Damage value and the attack-pool bonus both come off the item's
|
|
623
|
+
* quality rating -- gear is the whole progression system in this game,
|
|
624
|
+
* so it has to be what combat power flows from.
|
|
625
|
+
*/
|
|
626
|
+
getWeaponProfile() {
|
|
627
|
+
// Off the meat plane your gun is just a memory: Matrix combat runs on
|
|
628
|
+
// attack programs (Logic-scaled), astral combat on raw will given
|
|
629
|
+
// form (Magic-scaled). Hot-sim's deeper immersion sharpens the
|
|
630
|
+
// programs' bite.
|
|
631
|
+
if (this.plane === 'matrix') {
|
|
632
|
+
const hot = this.simMode === 'hot' ? 1 : 0;
|
|
633
|
+
// The deck IS the arsenal: its quality feeds program damage the way
|
|
634
|
+
// a better gun feeds DV. A technomancer's living persona weaves
|
|
635
|
+
// resonance instead of running programs.
|
|
636
|
+
if (this.isTechnomancer()) {
|
|
637
|
+
return { name: 'resonance surge', damage: 2 + Math.floor(this.logic / 2) + Math.floor(this.resonance / 2) + hot, poolBonus: 0 };
|
|
638
|
+
}
|
|
639
|
+
const deck = this.activeDeck ?? this.getCyberdeck();
|
|
640
|
+
return { name: 'attack programs', damage: 2 + Math.floor(this.logic / 2) + (deck?.qualityBonus ?? 0) + hot, poolBonus: 0 };
|
|
641
|
+
}
|
|
642
|
+
if (this.plane === 'astral') {
|
|
643
|
+
return { name: 'astral strike', damage: 2 + Math.floor(Math.max(1, this.magic) / 2), poolBonus: 0 };
|
|
644
|
+
}
|
|
645
|
+
// Muscle drives melee: unarmed 1 + STR/2, melee weapons 2 + STR/2 +
|
|
646
|
+
// quality (a strength-3 actor lands exactly the old flat numbers).
|
|
647
|
+
// Firearms don't care how strong you are.
|
|
648
|
+
const strengthBase = Math.floor(this.strength / 2);
|
|
649
|
+
const weapon = this._equipment.rightHand?.weapon ?? this._equipment.leftHand?.weapon;
|
|
650
|
+
if (!weapon) {
|
|
651
|
+
return { name: 'bare fists', damage: 1 + strengthBase, poolBonus: 0 };
|
|
652
|
+
}
|
|
653
|
+
const bonus = weapon.qualityBonus;
|
|
654
|
+
const base = weapon.category === Category.MeleeWeapon ? 2 + strengthBase : 4;
|
|
655
|
+
return { name: weapon.name, damage: base + bonus, poolBonus: bonus };
|
|
656
|
+
}
|
|
657
|
+
/** Total soak dice from every equipped armor-category item. */
|
|
658
|
+
getArmorValue() {
|
|
659
|
+
let total = 0;
|
|
660
|
+
for (const group of Object.values(this._equipment)) {
|
|
661
|
+
if (!group)
|
|
662
|
+
continue;
|
|
663
|
+
for (const item of Object.values(group)) {
|
|
664
|
+
if (item && Player.ARMOR_CATEGORIES.has(item.category)) {
|
|
665
|
+
total += 1 + item.qualityBonus;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return total;
|
|
670
|
+
}
|
|
671
|
+
getAttackPool() {
|
|
672
|
+
// Plane decides which attribute drives the fight: meat is muscle
|
|
673
|
+
// (Agility), the Matrix is mind (Logic, hot-sim +2), the astral is
|
|
674
|
+
// will (Willpower + half Magic). Combat skill is trained instinct and
|
|
675
|
+
// carries everywhere; wounds and sustaining drag everywhere too.
|
|
676
|
+
if (this.plane === 'matrix') {
|
|
677
|
+
const hot = this.simMode === 'hot' ? 2 : 0;
|
|
678
|
+
return Math.max(0, this.logic + this.combatSkill + Math.floor(this.resonance / 2) + hot + this.woundModifier - this.sustainingPenalty);
|
|
679
|
+
}
|
|
680
|
+
if (this.plane === 'astral') {
|
|
681
|
+
return Math.max(0, this.willpower + Math.floor(this.magic / 2) + this.combatSkill + this.woundModifier - this.sustainingPenalty);
|
|
682
|
+
}
|
|
683
|
+
// An adept's magic lives in their hands: half of it (rounded down)
|
|
684
|
+
// rides every meat-plane strike -- killing hands, improved reflexes.
|
|
685
|
+
const adeptBonus = this.adept ? Math.floor(this.magic / 2) : 0;
|
|
686
|
+
return Math.max(0, this.agility + this.combatSkill + adeptBonus + this.getWeaponProfile().poolBonus + this.woundModifier - this.sustainingPenalty);
|
|
687
|
+
}
|
|
688
|
+
getDefensePool() {
|
|
689
|
+
// Reaction plus HALF intuition (rounded down) -- full SR
|
|
690
|
+
// Reaction+Intuition turned text-paced exchanges into whiff-fests, so
|
|
691
|
+
// gut feel contributes at half weight. Intuition 3 (the default) adds
|
|
692
|
+
// +1 over the old reaction-only pool. Off-plane variants swap the
|
|
693
|
+
// reflex attribute but keep the same shape.
|
|
694
|
+
if (this.plane === 'matrix') {
|
|
695
|
+
const hot = this.simMode === 'hot' ? 1 : 0;
|
|
696
|
+
return Math.max(0, this.intuition + Math.floor(this.logic / 2) + Math.floor(this.resonance / 2) + hot + this.woundModifier - this.sustainingPenalty);
|
|
697
|
+
}
|
|
698
|
+
if (this.plane === 'astral') {
|
|
699
|
+
return Math.max(0, this.intuition + Math.floor(this.magic / 2) + this.woundModifier - this.sustainingPenalty);
|
|
700
|
+
}
|
|
701
|
+
// Combat sense: the adept's magic reads the fight before it happens.
|
|
702
|
+
const adeptBonus = this.adept ? Math.floor(this.magic / 2) : 0;
|
|
703
|
+
return Math.max(0, this.reaction + Math.floor(this.intuition / 2) + adeptBonus + this.woundModifier - this.sustainingPenalty);
|
|
704
|
+
}
|
|
705
|
+
getSoakPool() {
|
|
706
|
+
// Matrix: firewall -- Logic plus the DECK's quality (your deck is your
|
|
707
|
+
// armor); a technomancer's firewall is Willpower plus half resonance
|
|
708
|
+
// (their mind guards itself). Astral: pure Willpower; worn armor is
|
|
709
|
+
// meat left behind.
|
|
710
|
+
if (this.plane === 'matrix') {
|
|
711
|
+
if (this.isTechnomancer()) {
|
|
712
|
+
return Math.max(0, this.willpower + Math.floor(this.resonance / 2));
|
|
713
|
+
}
|
|
714
|
+
const deck = this.activeDeck ?? this.getCyberdeck();
|
|
715
|
+
return Math.max(0, this.logic + (deck?.qualityBonus ?? 0));
|
|
716
|
+
}
|
|
717
|
+
if (this.plane === 'astral') {
|
|
718
|
+
return Math.max(0, this.willpower);
|
|
719
|
+
}
|
|
720
|
+
return Math.max(0, this.body + this.getArmorValue() + this._sustainedArmor);
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Initiative: Reaction + Intuition (straight SR), plus the AR edge from
|
|
724
|
+
* an equipped, ONLINE commlink -- the tactical overlay feeding you
|
|
725
|
+
* angles. Running silent kills the overlay along with the broadcast, so
|
|
726
|
+
* going dark costs you speed on the draw: the silent tradeoff grows
|
|
727
|
+
* teeth. Wounds and sustained spells slow you like everything else.
|
|
728
|
+
*/
|
|
729
|
+
getInitiativePool() {
|
|
730
|
+
// Matrix speed is thought-speed: Logic + Intuition, hot-sim +2 (the
|
|
731
|
+
// whole point of running hot). Astral forms move at the speed of
|
|
732
|
+
// intention: Intuition doubled.
|
|
733
|
+
if (this.plane === 'matrix') {
|
|
734
|
+
const hot = this.simMode === 'hot' ? 2 : 0;
|
|
735
|
+
return Math.max(0, this.logic + this.intuition + Math.floor(this.resonance / 2) + hot + this.woundModifier - this.sustainingPenalty);
|
|
736
|
+
}
|
|
737
|
+
if (this.plane === 'astral') {
|
|
738
|
+
return Math.max(0, this.intuition * 2 + this.woundModifier - this.sustainingPenalty);
|
|
739
|
+
}
|
|
740
|
+
const link = this._equipment.head?.commlink;
|
|
741
|
+
// AR needs a link that's on you, broadcasting, and not hacked dark.
|
|
742
|
+
const arBonus = link && !this.runningSilent && !this.arOffline ? link.qualityBonus : 0;
|
|
743
|
+
return Math.max(0, this.reaction + this.intuition + arBonus + this.woundModifier - this.sustainingPenalty);
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* If an edge boost is armed (see commands/edge.ts), consume it: returns
|
|
747
|
+
* the bonus dice for the strike being rolled right now and disarms.
|
|
748
|
+
* Called by the strike-profile builders, so the boost lands on the next
|
|
749
|
+
* attack/subdue/manabolt this actor initiates.
|
|
750
|
+
*/
|
|
751
|
+
consumeEdgeBoost() {
|
|
752
|
+
if (!this.edgeBoostArmed)
|
|
753
|
+
return 0;
|
|
754
|
+
this.edgeBoostArmed = false;
|
|
755
|
+
return this.edge;
|
|
756
|
+
}
|
|
757
|
+
/** New job, fresh luck. */
|
|
758
|
+
refreshEdge() {
|
|
759
|
+
this.edgeRemaining = this.edge;
|
|
760
|
+
this.edgeBoostArmed = false;
|
|
761
|
+
}
|
|
762
|
+
// ======================== Magic (see commands/cast.ts) ========================
|
|
763
|
+
// Two roads to casting. GEAR-DRIVEN: what a mundane can cast comes from
|
|
764
|
+
// the spell formulas/grimoires they carry, at a flat scholar's base pool
|
|
765
|
+
// -- consistent with the rest of the game, where gear IS the progression
|
|
766
|
+
// system. INNATE: an Awakened actor (magic attribute > 0) knows every
|
|
767
|
+
// spell without any gear -- the talent is the knowledge; formulas are
|
|
768
|
+
// how mundanes fake it -- and casts from magic + 2 instead of the flat
|
|
769
|
+
// base, so a strong mage out-throws any book-taught scholar. A Focus
|
|
770
|
+
// stacks on top of either road.
|
|
771
|
+
/** Base casting dice for a mundane casting from a formula/grimoire. */
|
|
772
|
+
static BASE_MAGIC_POOL = 5;
|
|
773
|
+
/** Awakened: innate talent, no gear required. Adepts count -- their
|
|
774
|
+
* aura burns like any mage's, and assensing works -- but they channel
|
|
775
|
+
* magic into the body, not spells (see knowsSpell/isCaster). */
|
|
776
|
+
isAwakened() {
|
|
777
|
+
return this.magic > 0;
|
|
778
|
+
}
|
|
779
|
+
/** Technomancer: living persona -- the Matrix without a deck (see ICombatAttributes.resonance). */
|
|
780
|
+
isTechnomancer() {
|
|
781
|
+
return this.resonance > 0;
|
|
782
|
+
}
|
|
783
|
+
// A sustained spell (currently only the armor spell) grants its bonus
|
|
784
|
+
// continuously but costs concentration: -2 dice on every active pool
|
|
785
|
+
// while it's up. See cast.ts.
|
|
786
|
+
_sustainedArmor = 0;
|
|
787
|
+
get sustainedArmor() {
|
|
788
|
+
return this._sustainedArmor;
|
|
789
|
+
}
|
|
790
|
+
setSustainedArmor(bonus) {
|
|
791
|
+
this._sustainedArmor = Math.max(0, Math.floor(bonus));
|
|
792
|
+
}
|
|
793
|
+
get sustainingPenalty() {
|
|
794
|
+
return this._sustainedArmor > 0 ? 2 : 0;
|
|
795
|
+
}
|
|
796
|
+
/** The best Focus item on this actor, if any -- scales casting power. */
|
|
797
|
+
getBestFocus() {
|
|
798
|
+
return this._inventory.getAllItems()
|
|
799
|
+
.filter(i => i.category === Category.Focus)
|
|
800
|
+
.sort((a, b) => b.qualityBonus - a.qualityBonus)[0];
|
|
801
|
+
}
|
|
802
|
+
getMagicPool() {
|
|
803
|
+
const focus = this.getBestFocus();
|
|
804
|
+
const focusBonus = focus ? 1 + focus.qualityBonus : 0;
|
|
805
|
+
// An Awakened caster's raw talent replaces the scholar's flat base --
|
|
806
|
+
// but never undercuts it (a barely-Awakened magic 1 still casts at
|
|
807
|
+
// least as well as someone who studied the book).
|
|
808
|
+
const base = this.isAwakened()
|
|
809
|
+
? Math.max(Player.BASE_MAGIC_POOL, this.magic + 2)
|
|
810
|
+
: Player.BASE_MAGIC_POOL;
|
|
811
|
+
return Math.max(0, base + focusBonus + this.woundModifier - this.sustainingPenalty);
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Awakened actors know every spell innately. A mundane knows a spell if
|
|
815
|
+
* they carry its formula (item name must contain the spell's name) or
|
|
816
|
+
* any grimoire (a spellbook teaches everything).
|
|
817
|
+
*/
|
|
818
|
+
knowsSpell(spellName) {
|
|
819
|
+
// An adept's magic is muscle memory, not incantation -- no spells,
|
|
820
|
+
// not even from a formula in hand. Their magic already lives in their
|
|
821
|
+
// meat-plane combat pools.
|
|
822
|
+
if (this.adept)
|
|
823
|
+
return false;
|
|
824
|
+
if (this.isAwakened())
|
|
825
|
+
return true;
|
|
826
|
+
const wanted = spellName.toLowerCase();
|
|
827
|
+
return this._inventory.getAllItems().some(i => i.category === Category.Grimoire ||
|
|
828
|
+
(i.category === Category.SpellFormula && i.name.toLowerCase().includes(wanted)));
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* The gate for attempting magic at all (ward bypass included): innate
|
|
832
|
+
* talent, or any magic gear.
|
|
833
|
+
*/
|
|
834
|
+
isCaster() {
|
|
835
|
+
if (this.adept)
|
|
836
|
+
return false; // body-magic only -- see knowsSpell
|
|
837
|
+
return this.isAwakened() || this._inventory.getAllItems().some(i => i.category === Category.SpellFormula ||
|
|
838
|
+
i.category === Category.Grimoire ||
|
|
839
|
+
i.category === Category.Focus);
|
|
840
|
+
}
|
|
841
|
+
// ================================================================================
|
|
326
842
|
get lifestyleChoices() {
|
|
327
843
|
return this._lifestyleChoices;
|
|
328
844
|
}
|