@maka/maka-cli 5.3.17 → 5.5.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 +62 -0
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +196 -0
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/advance.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/advance.js +65 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/advance.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/assense.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js +122 -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 +243 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js +39 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.d.ts +21 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.js +82 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.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 +219 -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 +397 -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 +72 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drop.js +3 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drop.js.map +1 -1
- 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 +59 -2
- 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 +50 -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 +13 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +147 -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 +199 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.d.ts +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js +75 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.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 +43 -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 +47 -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/persuade.d.ts +18 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.js +100 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.d.ts +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js +33 -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 +31 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +71 -7
- 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 +58 -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/sneak.d.ts +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/sneak.js +35 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/sneak.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 +86 -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 +138 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/talk.js +8 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/talk.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/unequip.js +6 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/unequip.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 +102 -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 +3 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.d.ts +175 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +673 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js.map +1 -0
- 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 +139 -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 +174 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js +647 -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 +138 -9
- package/bundle/typescript/src/commands/game/sideQuest/game.js +757 -197
- 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 +27 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +312 -50
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +278 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +661 -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 +199 -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 +24 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js +53 -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 +73 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +208 -3
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene1.json +319 -70
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +661 -227
- 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 +42 -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 +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/scene-seed.d.ts +8 -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 -6
- 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 +145 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +354 -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 +77 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +265 -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 +78 -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 +42 -0
- package/bundle/typescript/src/tools/ai/ai.class.js +117 -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 +3 -2
- package/bundle/typescript/src/tools/https/https.class.js +51 -2
- package/bundle/typescript/src/tools/https/https.class.js.map +1 -1
- package/bundle/typescript/src/tools/https/https.interface.d.ts +10 -0
- package/package.json +1 -1
|
@@ -2,6 +2,24 @@ 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'];
|
|
6
|
+
export const SKILL_NAMES = [
|
|
7
|
+
'firearms', 'blades', 'unarmed',
|
|
8
|
+
'sneaking', 'perception', 'athletics',
|
|
9
|
+
'hacking', 'locksmith',
|
|
10
|
+
'negotiation', 'con', 'intimidation',
|
|
11
|
+
'first-aid',
|
|
12
|
+
'assensing', 'spellcasting', 'piloting',
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Skills that fall back to the legacy `combatSkill` attribute when no
|
|
16
|
+
* explicit rating is given -- this is what keeps every existing NPC seed
|
|
17
|
+
* (which only carries combatSkill) rolling exactly the pools it always
|
|
18
|
+
* has, ice included (its Matrix strikes ride `hacking`).
|
|
19
|
+
*/
|
|
20
|
+
export const COMBAT_SKILL_FALLBACK = new Set([
|
|
21
|
+
'firearms', 'blades', 'unarmed', 'hacking',
|
|
22
|
+
]);
|
|
5
23
|
export class Player extends AbstractPlayer {
|
|
6
24
|
_playerName;
|
|
7
25
|
_currentLocation;
|
|
@@ -11,6 +29,180 @@ export class Player extends AbstractPlayer {
|
|
|
11
29
|
_lifestyleChoices = [];
|
|
12
30
|
logger;
|
|
13
31
|
_currency = 0; // for seeding/debug only
|
|
32
|
+
// Whoever this actor most recently placed/received a commlink call with.
|
|
33
|
+
// No explicit "hang up" -- placing a new call just replaces it. Lets a
|
|
34
|
+
// reply via the ordinary "say" command reach a call partner in a
|
|
35
|
+
// different room, the same way a real call would, without the NPC (or
|
|
36
|
+
// player) needing to learn a separate reply verb. See say.ts and call.ts.
|
|
37
|
+
activeCallPartner;
|
|
38
|
+
// Running silent (see commands/silent.ts): the commlink stays equipped
|
|
39
|
+
// but stops broadcasting -- no calls out, and nobody can raise this
|
|
40
|
+
// actor (call.ts checks both ends). Toggling it drops any active call.
|
|
41
|
+
runningSilent = false;
|
|
42
|
+
// Whoever this actor most recently traded combat exchanges with, and
|
|
43
|
+
// when -- set for BOTH sides by CombatExchange.beginExchange(). Walking
|
|
44
|
+
// out on a live, recent opponent invites a parting shot (see go.ts);
|
|
45
|
+
// firing one (or a death) clears it.
|
|
46
|
+
combatOpponent;
|
|
47
|
+
lastExchangeAt = 0;
|
|
48
|
+
// True while this actor is a combatant in an exchange that's still
|
|
49
|
+
// resolving. Serializes combat per actor: a second attack involving a
|
|
50
|
+
// busy combatant is refused instead of interleaving -- found via a real
|
|
51
|
+
// session where an NPC's overlapping AI loops fired two exchanges at
|
|
52
|
+
// once and the target died TWICE (two kill events, two narrations).
|
|
53
|
+
inExchange = false;
|
|
54
|
+
// Fight-or-flight autopilot (see utilities/auto-fight.ts): the FIGHT
|
|
55
|
+
// half runs automatically at the set temperature, the flight stays
|
|
56
|
+
// manual -- always. Tab cycles it in the UI; "stance" sets it by name.
|
|
57
|
+
// manual -- today's behavior, you type every swing
|
|
58
|
+
// guarded -- auto-fights, but finishes non-lethally (switches to
|
|
59
|
+
// subdue when the target is nearly down) and stands down
|
|
60
|
+
// if they surrender
|
|
61
|
+
// aggressive -- fights until they're dead; surrender means nothing
|
|
62
|
+
autoStance = 'manual';
|
|
63
|
+
// Set by the "surrender" command: this actor has thrown their hands up.
|
|
64
|
+
// Cleared the moment they initiate violence again (see
|
|
65
|
+
// CombatExchange.beginExchange). Guarded auto-fight honors it;
|
|
66
|
+
// aggressive doesn't have to.
|
|
67
|
+
surrendered = false;
|
|
68
|
+
// The archetype picked at launch (ui.ts), kept by NAME so downstream
|
|
69
|
+
// consumers -- scene generation's player dossier, above all -- can say
|
|
70
|
+
// "this is a Decker" instead of re-deriving the concept from stats.
|
|
71
|
+
// undefined for NPCs and for players created outside the launch flow.
|
|
72
|
+
archetypeName;
|
|
73
|
+
// An equipped weapon rides in a HOLSTER until it's deliberately drawn --
|
|
74
|
+
// "brandish"/"holster" flip this by hand (see commands/brandish.ts), and
|
|
75
|
+
// opening an exchange draws steel automatically for anyone whose weapon
|
|
76
|
+
// is about to speak (CombatExchange.beginExchange), so combat never
|
|
77
|
+
// punishes forgetting the verb. What the flag actually buys is the
|
|
78
|
+
// walking-around state: a holstered gun is polite, a drawn one is a
|
|
79
|
+
// threat display every NPC in the room sees happen.
|
|
80
|
+
weaponDrawn = false;
|
|
81
|
+
// ==================== Planes (see utilities/planes.ts) ====================
|
|
82
|
+
// Which plane this actor currently exists on. Ice NPCs live on 'matrix',
|
|
83
|
+
// spirits on 'astral', natively and permanently; a player shifts planes
|
|
84
|
+
// via jack in / project and back.
|
|
85
|
+
plane = 'meat';
|
|
86
|
+
// While off the meat plane, the empty body slumps HERE -- visible,
|
|
87
|
+
// attackable (see planes.ts resolveBodyAttack), and where you snap back
|
|
88
|
+
// to. undefined while on the meat plane, or for native off-plane actors
|
|
89
|
+
// (ice and spirits have no body to leave behind).
|
|
90
|
+
bodyRoom;
|
|
91
|
+
// Matrix only: cold or hot sim, chosen at jack-in. undefined off-Matrix
|
|
92
|
+
// and for native ice.
|
|
93
|
+
simMode;
|
|
94
|
+
// Astral only: room-moves made while projecting. The longer you're out,
|
|
95
|
+
// the harder the snap-back -- drained as stun on return (planes.ts).
|
|
96
|
+
astralTicks = 0;
|
|
97
|
+
// The DECK you're jacked in on (set by planes.ts enterMatrix, cleared on
|
|
98
|
+
// leaving). Matrix damage is HARDWARE damage: it lands on this item's
|
|
99
|
+
// device condition monitor (see Item's cyberdeck section), persists
|
|
100
|
+
// across jack-outs, and a full track BRICKS the deck -- forced dump,
|
|
101
|
+
// and no re-entry on that deck until it's repaired.
|
|
102
|
+
activeDeck;
|
|
103
|
+
/** The best working cyberdeck carried (bricked ones don't count). */
|
|
104
|
+
getCyberdeck() {
|
|
105
|
+
return this._inventory.getAllItems()
|
|
106
|
+
.filter(i => i.isCyberdeck() && !i.isBricked)
|
|
107
|
+
.sort((a, b) => b.qualityBonus - a.qualityBonus)[0];
|
|
108
|
+
}
|
|
109
|
+
/** Any deck at all, bricked included -- for status displays and repair. */
|
|
110
|
+
getAnyCyberdeck() {
|
|
111
|
+
return this.activeDeck ?? this._inventory.getAllItems()
|
|
112
|
+
.filter(i => i.isCyberdeck())
|
|
113
|
+
.sort((a, b) => b.qualityBonus - a.qualityBonus)[0];
|
|
114
|
+
}
|
|
115
|
+
// ==================== PAN (see commands/pan.ts, hack.ts) ====================
|
|
116
|
+
// Hacked dark: an enemy cracked your PAN and cut the AR overlay. Costs
|
|
117
|
+
// the commlink initiative bonus and drops calls, until "reboot". Distinct
|
|
118
|
+
// from runningSilent (chosen dark) -- this one was done TO you.
|
|
119
|
+
arOffline = false;
|
|
120
|
+
/**
|
|
121
|
+
* The PAN's master device: your slaved gear defends behind its
|
|
122
|
+
* firewall (SR slaving, simplified). Best working deck beats the
|
|
123
|
+
* equipped commlink; no devices = no PAN (and nothing to hack).
|
|
124
|
+
*/
|
|
125
|
+
getPanMaster() {
|
|
126
|
+
const deck = this.getCyberdeck();
|
|
127
|
+
const link = this._equipment.head?.commlink ?? undefined;
|
|
128
|
+
if (deck && link)
|
|
129
|
+
return deck.qualityBonus >= link.qualityBonus ? deck : link;
|
|
130
|
+
return deck ?? link;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Defense against a PAN intrusion: Logic + the master's firewall
|
|
134
|
+
* (+2 while running silent -- a hidden PAN must be FOUND first).
|
|
135
|
+
*/
|
|
136
|
+
getPanDefensePool() {
|
|
137
|
+
const master = this.getPanMaster();
|
|
138
|
+
const silentBonus = this.runningSilent ? 2 : 0;
|
|
139
|
+
return Math.max(0, this.logic + (master?.qualityBonus ?? 0) + silentBonus);
|
|
140
|
+
}
|
|
141
|
+
get matrixTaken() {
|
|
142
|
+
return this.getAnyCyberdeck()?.deckDamage ?? 0;
|
|
143
|
+
}
|
|
144
|
+
get maxMatrixBoxes() {
|
|
145
|
+
return this.getAnyCyberdeck()?.maxDeckBoxes ?? 0;
|
|
146
|
+
}
|
|
147
|
+
/** Routes Matrix combat damage into the jacked-in deck's hardware track. */
|
|
148
|
+
takeMatrixDamage(boxes) {
|
|
149
|
+
const deck = this.getAnyCyberdeck();
|
|
150
|
+
return deck ? deck.takeDeckDamage(boxes) : 0;
|
|
151
|
+
}
|
|
152
|
+
get matrixCrashed() {
|
|
153
|
+
return this.getAnyCyberdeck()?.isBricked ?? false;
|
|
154
|
+
}
|
|
155
|
+
/** The deck track, same rendering as conditionSummary(). */
|
|
156
|
+
matrixSummary() {
|
|
157
|
+
return this.getAnyCyberdeck()?.deckSummary() ?? '(no deck)';
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Cross-plane perception, the single rule everything filters through:
|
|
161
|
+
* same plane sees same plane; the astral perceives the meat world
|
|
162
|
+
* (shifted, not gone -- auras and movement, dimly); meat and Matrix see
|
|
163
|
+
* nothing beyond their own plane. Used by see/hear guards, targeting,
|
|
164
|
+
* and room listings.
|
|
165
|
+
*/
|
|
166
|
+
canPerceive(other) {
|
|
167
|
+
if (this.plane === other.plane)
|
|
168
|
+
return true;
|
|
169
|
+
return this.plane === 'astral' && other.plane === 'meat';
|
|
170
|
+
}
|
|
171
|
+
// True while an AI-driven action is parked waiting for an in-flight
|
|
172
|
+
// exchange to settle (CombatExchange.waitForLull). One queued action per
|
|
173
|
+
// actor: a second colliding attempt is refused outright instead of
|
|
174
|
+
// stacking a burst of delayed attacks behind the same lull.
|
|
175
|
+
waitingForLull = false;
|
|
176
|
+
// Combat attributes (see ICombatAttributes). Defaults make an unseeded
|
|
177
|
+
// actor a competent-but-mortal street-level runner; seeds can override
|
|
178
|
+
// per-NPC to make a hardened enforcer or a soft civilian.
|
|
179
|
+
body;
|
|
180
|
+
agility;
|
|
181
|
+
reaction;
|
|
182
|
+
combatSkill;
|
|
183
|
+
magic;
|
|
184
|
+
strength;
|
|
185
|
+
willpower;
|
|
186
|
+
logic;
|
|
187
|
+
intuition;
|
|
188
|
+
charisma;
|
|
189
|
+
edge;
|
|
190
|
+
resonance;
|
|
191
|
+
adept;
|
|
192
|
+
// Edge is a per-job pool: spend a point (see commands/edge.ts) to arm a
|
|
193
|
+
// boost that supercharges your next strike. Refreshed to `edge` on every
|
|
194
|
+
// new job (see Game.continueToNextScene).
|
|
195
|
+
edgeRemaining = 0;
|
|
196
|
+
edgeBoostArmed = false;
|
|
197
|
+
// Physical condition monitor, straight from SR5: 8 + (Body / 2, rounded
|
|
198
|
+
// up) boxes. Damage fills boxes; a full track is death.
|
|
199
|
+
_damageTaken = 0;
|
|
200
|
+
// Stun condition monitor -- the second track, filled by spell drain (see
|
|
201
|
+
// cast.ts). A full stun track is a knockout, not death, but overflow
|
|
202
|
+
// past it spills into the physical track. Sized off Reaction as the
|
|
203
|
+
// stand-in for SR's Willpower (the closest thing to a mental attribute
|
|
204
|
+
// here without adding one): 8 + (Reaction / 2, rounded up).
|
|
205
|
+
_stunTaken = 0;
|
|
14
206
|
constructor(config) {
|
|
15
207
|
super();
|
|
16
208
|
this._playerName = config.playerName;
|
|
@@ -18,8 +210,53 @@ export class Player extends AbstractPlayer {
|
|
|
18
210
|
this._inventory = new Inventory();
|
|
19
211
|
this.logger = Logger.getInstance();
|
|
20
212
|
this._lifestyleChoices = config.lifestyleChoices ?? [];
|
|
213
|
+
this.body = config.combat?.body ?? 4;
|
|
214
|
+
this.agility = config.combat?.agility ?? 4;
|
|
215
|
+
this.reaction = config.combat?.reaction ?? 4;
|
|
216
|
+
this.combatSkill = config.combat?.combatSkill ?? 3;
|
|
217
|
+
this.magic = config.combat?.magic ?? 0;
|
|
218
|
+
this.strength = config.combat?.strength ?? 3;
|
|
219
|
+
this.willpower = config.combat?.willpower ?? 3;
|
|
220
|
+
this.logic = config.combat?.logic ?? 3;
|
|
221
|
+
this.intuition = config.combat?.intuition ?? 3;
|
|
222
|
+
this.charisma = config.combat?.charisma ?? 3;
|
|
223
|
+
this.edge = config.combat?.edge ?? 2;
|
|
224
|
+
this.edgeRemaining = this.edge;
|
|
225
|
+
this.resonance = config.combat?.resonance ?? 0;
|
|
226
|
+
this.adept = config.combat?.adept ?? false;
|
|
227
|
+
this.skills = { ...(config.combat?.skills ?? {}) };
|
|
21
228
|
this._equipment = this.initializeEquipment();
|
|
22
229
|
}
|
|
230
|
+
// ======================== Skills (see SkillName) ========================
|
|
231
|
+
/** Explicit per-skill ratings; anything absent resolves via skillRating(). */
|
|
232
|
+
skills = {};
|
|
233
|
+
// Karma: the advancement currency, earned per completed job (see
|
|
234
|
+
// Scene.checkWinCondition) and spent in the hideout via "advance" --
|
|
235
|
+
// gear stops being the only progression axis.
|
|
236
|
+
karma = 0;
|
|
237
|
+
/**
|
|
238
|
+
* The effective rating for a skill: explicit rating if set; otherwise
|
|
239
|
+
* combat skills fall back to the legacy `combatSkill` attribute (which
|
|
240
|
+
* keeps every skill-less NPC seed rolling its historical pools), and
|
|
241
|
+
* everything else is 0 (unskilled).
|
|
242
|
+
*/
|
|
243
|
+
skillRating(name) {
|
|
244
|
+
const explicit = this.skills[name];
|
|
245
|
+
if (typeof explicit === 'number')
|
|
246
|
+
return Math.max(0, explicit);
|
|
247
|
+
return COMBAT_SKILL_FALLBACK.has(name) ? this.combatSkill : 0;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Which combat skill the current armament exercises: firearms for a
|
|
251
|
+
* drawn gun, blades for a drawn melee weapon, unarmed for empty hands
|
|
252
|
+
* (or anything holstered -- see weaponDrawn).
|
|
253
|
+
*/
|
|
254
|
+
activeCombatSkill() {
|
|
255
|
+
const weapon = this.weaponDrawn ? this.getCarriedWeapon() : null;
|
|
256
|
+
if (!weapon)
|
|
257
|
+
return 'unarmed';
|
|
258
|
+
return weapon.isFirearm() ? 'firearms' : 'blades';
|
|
259
|
+
}
|
|
23
260
|
initializeEquipment() {
|
|
24
261
|
return {
|
|
25
262
|
head: {
|
|
@@ -31,6 +268,7 @@ export class Player extends AbstractPlayer {
|
|
|
31
268
|
commlink: null
|
|
32
269
|
},
|
|
33
270
|
torso: {
|
|
271
|
+
clothing: null,
|
|
34
272
|
armorVest: null,
|
|
35
273
|
armorCoat: null,
|
|
36
274
|
backpack: null,
|
|
@@ -97,7 +335,9 @@ export class Player extends AbstractPlayer {
|
|
|
97
335
|
this._equipment = newEquipment;
|
|
98
336
|
}
|
|
99
337
|
get maxCarryingWeight() {
|
|
100
|
-
|
|
338
|
+
// Strength-derived: 20 + 10/point. The old flat default (50kg) is
|
|
339
|
+
// exactly what a strength-3 runner gets, so pre-sheet balance holds.
|
|
340
|
+
return 20 + this.strength * 10;
|
|
101
341
|
}
|
|
102
342
|
set maxCarryingWeight(weight) {
|
|
103
343
|
if (weight > 0) {
|
|
@@ -109,10 +349,19 @@ export class Player extends AbstractPlayer {
|
|
|
109
349
|
}
|
|
110
350
|
get currentWeight() {
|
|
111
351
|
let totalWeight = this._inventory.getWeight();
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
352
|
+
// Equipment is nested one level deeper than inventory: groups (head,
|
|
353
|
+
// torso...) containing slots. This used to add `group.weight` --
|
|
354
|
+
// undefined -- which turned the total into NaN, so the carrying cap
|
|
355
|
+
// NEVER triggered (NaN compares false against everything). Found when
|
|
356
|
+
// a "take all" verification watched a 100kg engine block walk into a
|
|
357
|
+
// 50kg pack.
|
|
358
|
+
for (const group of Object.values(this._equipment)) {
|
|
359
|
+
if (!group)
|
|
360
|
+
continue;
|
|
361
|
+
for (const item of Object.values(group)) {
|
|
362
|
+
if (item) {
|
|
363
|
+
totalWeight += item.weight;
|
|
364
|
+
}
|
|
116
365
|
}
|
|
117
366
|
}
|
|
118
367
|
return totalWeight;
|
|
@@ -149,12 +398,29 @@ export class Player extends AbstractPlayer {
|
|
|
149
398
|
if (!groupRef) {
|
|
150
399
|
return `You have no ${group} equipment group initialized.`;
|
|
151
400
|
}
|
|
152
|
-
|
|
153
|
-
|
|
401
|
+
const previousItem = groupRef[slot];
|
|
402
|
+
let swapNotice = '';
|
|
403
|
+
if (previousItem) {
|
|
404
|
+
if (previousItem.name === item.name) {
|
|
405
|
+
return `You already have ${item.name} equipped in your ${group}.${slot} slot.`;
|
|
406
|
+
}
|
|
407
|
+
this.unequipBySlot(group, slot);
|
|
408
|
+
swapNotice = ` (unequipped ${previousItem.name})`;
|
|
154
409
|
}
|
|
155
410
|
groupRef[slot] = item;
|
|
156
411
|
this.removeInventory(item);
|
|
157
|
-
|
|
412
|
+
// A freshly equipped weapon goes to the HOLSTER, not the hand -- see
|
|
413
|
+
// the weaponDrawn field. Swapping weapons re-holsters even if the old
|
|
414
|
+
// one was out: the moment of the exchange, both hands are busy.
|
|
415
|
+
if (Player.isWeaponSlot(group, slot)) {
|
|
416
|
+
this.weaponDrawn = false;
|
|
417
|
+
return `You slide ${item.name} into your holster.${swapNotice} "brandish" when you want it in hand.`;
|
|
418
|
+
}
|
|
419
|
+
return `You equipped ${item.name} in your ${group}.${slot} slot.${swapNotice}`;
|
|
420
|
+
}
|
|
421
|
+
/** True for the hand slots that hold a wielded weapon. */
|
|
422
|
+
static isWeaponSlot(group, slot) {
|
|
423
|
+
return (group === 'rightHand' || group === 'leftHand') && slot === 'weapon';
|
|
158
424
|
}
|
|
159
425
|
unequip(input) {
|
|
160
426
|
const slotInfo = this.determineEquipmentSlot(input);
|
|
@@ -197,8 +463,14 @@ export class Player extends AbstractPlayer {
|
|
|
197
463
|
case Category.Commlink:
|
|
198
464
|
return { group: 'head', slot: 'commlink' };
|
|
199
465
|
// TORSO
|
|
466
|
+
case Category.Clothing:
|
|
467
|
+
case 'shirt':
|
|
468
|
+
case 'hoodie':
|
|
469
|
+
case 'top':
|
|
470
|
+
return { group: 'torso', slot: 'clothing' };
|
|
200
471
|
case Category.Armor:
|
|
201
472
|
case Category.BodyArmor:
|
|
473
|
+
case Category.ArmorClothing:
|
|
202
474
|
case 'armor vest':
|
|
203
475
|
return { group: 'torso', slot: 'armorVest' };
|
|
204
476
|
case 'armor coat':
|
|
@@ -292,14 +564,21 @@ export class Player extends AbstractPlayer {
|
|
|
292
564
|
}
|
|
293
565
|
this.addInventory(itemToUnequip);
|
|
294
566
|
groupRef[slot] = null;
|
|
567
|
+
// No weapon, nothing drawn -- keeps the flag honest for the next equip.
|
|
568
|
+
if (Player.isWeaponSlot(group, slot))
|
|
569
|
+
this.weaponDrawn = false;
|
|
295
570
|
return `You unequipped ${itemToUnequip.name} from your ${group}.${slot} slot.`;
|
|
296
571
|
}
|
|
297
572
|
hear(actor, message) {
|
|
573
|
+
// Cross-plane silence: a Matrix persona's chatter never reaches meat
|
|
574
|
+
// ears and vice versa; the astral hears the meat world it overlays.
|
|
575
|
+
if (!this.canPerceive(actor))
|
|
576
|
+
return;
|
|
298
577
|
this.logger.write(`${this.name} heard ${actor.name}`);
|
|
299
578
|
this.logger.logWithColor(`{white-fg}${actor.name}:{/white-fg} ${message}`, (actor === this) ? 'yellow' : 'green');
|
|
300
579
|
}
|
|
301
580
|
see(actor, verb, details) {
|
|
302
|
-
if (actor !== this) {
|
|
581
|
+
if (actor !== this && this.canPerceive(actor)) {
|
|
303
582
|
this.logger.write(`You see ${actor.name} ${verb} ${details}`);
|
|
304
583
|
this.logger.logWithColor(`${actor.name} ${verb} ${details}`, 'cyan');
|
|
305
584
|
}
|
|
@@ -323,6 +602,379 @@ export class Player extends AbstractPlayer {
|
|
|
323
602
|
this.logger.write(`${this.name} has left ${this._currentLocation.name}`);
|
|
324
603
|
}
|
|
325
604
|
}
|
|
605
|
+
// ======================== Combat (see commands/attack.ts) ========================
|
|
606
|
+
static ARMOR_CATEGORIES = new Set([
|
|
607
|
+
Category.Armor, Category.BodyArmor, Category.ArmorClothing, Category.Shield,
|
|
608
|
+
Category.Helmet, Category.ArmArmor, Category.LegArmor, Category.Boots, Category.Gloves,
|
|
609
|
+
]);
|
|
610
|
+
get maxConditionBoxes() {
|
|
611
|
+
return 8 + Math.ceil(this.body / 2);
|
|
612
|
+
}
|
|
613
|
+
get damageTaken() {
|
|
614
|
+
return this._damageTaken;
|
|
615
|
+
}
|
|
616
|
+
get maxStunBoxes() {
|
|
617
|
+
// Willpower-based, as SR intends (Reaction stood in before Willpower
|
|
618
|
+
// existed on the sheet). Default willpower 3 keeps the same 10 boxes
|
|
619
|
+
// the old reaction-4 default produced.
|
|
620
|
+
return 8 + Math.ceil(this.willpower / 2);
|
|
621
|
+
}
|
|
622
|
+
get stunTaken() {
|
|
623
|
+
return this._stunTaken;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* SR5 wound modifier: -1 die on every pool per 3 full boxes of damage,
|
|
627
|
+
* and the two tracks STACK -- being shot and drained both make you worse
|
|
628
|
+
* at everything, including not getting shot.
|
|
629
|
+
*/
|
|
630
|
+
get woundModifier() {
|
|
631
|
+
return -(Math.floor(this._damageTaken / 3) + Math.floor(this._stunTaken / 3));
|
|
632
|
+
}
|
|
633
|
+
/** Dead: the physical track is full. */
|
|
634
|
+
isDown() {
|
|
635
|
+
return this._damageTaken >= this.maxConditionBoxes;
|
|
636
|
+
}
|
|
637
|
+
/** Out cold: the stun track is full (but not necessarily dead). */
|
|
638
|
+
isUnconscious() {
|
|
639
|
+
return this._stunTaken >= this.maxStunBoxes;
|
|
640
|
+
}
|
|
641
|
+
/** Out of the fight either way -- the gate every action should check. */
|
|
642
|
+
isIncapacitated() {
|
|
643
|
+
return this.isDown() || this.isUnconscious();
|
|
644
|
+
}
|
|
645
|
+
/** Applies damage boxes; returns the number actually taken. */
|
|
646
|
+
takeDamage(boxes) {
|
|
647
|
+
const taken = Math.max(0, Math.floor(boxes));
|
|
648
|
+
this._damageTaken = Math.min(this.maxConditionBoxes, this._damageTaken + taken);
|
|
649
|
+
return taken;
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Clears damage boxes; returns how many were actually healed (can be
|
|
653
|
+
* less than asked when the track is nearly clean). Healing also restores
|
|
654
|
+
* wound-modifier dice, since that's derived from damageTaken.
|
|
655
|
+
*/
|
|
656
|
+
healDamage(boxes) {
|
|
657
|
+
const before = this._damageTaken;
|
|
658
|
+
this._damageTaken = Math.max(0, before - Math.max(0, Math.floor(boxes)));
|
|
659
|
+
return before - this._damageTaken;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Applies stun boxes; anything past a full stun track OVERFLOWS into the
|
|
663
|
+
* physical track (SR's rule -- enough drain doesn't just knock you out,
|
|
664
|
+
* it starts killing you). Returns what landed where.
|
|
665
|
+
*/
|
|
666
|
+
takeStun(boxes) {
|
|
667
|
+
const asked = Math.max(0, Math.floor(boxes));
|
|
668
|
+
const space = this.maxStunBoxes - this._stunTaken;
|
|
669
|
+
const stun = Math.min(asked, space);
|
|
670
|
+
this._stunTaken += stun;
|
|
671
|
+
const overflow = asked - stun;
|
|
672
|
+
if (overflow > 0) {
|
|
673
|
+
this.takeDamage(overflow);
|
|
674
|
+
}
|
|
675
|
+
return { stun, overflow };
|
|
676
|
+
}
|
|
677
|
+
/** e.g. "■■■□□□□□□□ (3/10)" */
|
|
678
|
+
conditionSummary() {
|
|
679
|
+
const max = this.maxConditionBoxes;
|
|
680
|
+
const filled = Math.min(max, this._damageTaken);
|
|
681
|
+
return `${'■'.repeat(filled)}${'□'.repeat(max - filled)} (${filled}/${max})`;
|
|
682
|
+
}
|
|
683
|
+
/** Clears stun boxes; returns how many were actually recovered. */
|
|
684
|
+
healStun(boxes) {
|
|
685
|
+
const before = this._stunTaken;
|
|
686
|
+
this._stunTaken = Math.max(0, before - Math.max(0, Math.floor(boxes)));
|
|
687
|
+
return before - this._stunTaken;
|
|
688
|
+
}
|
|
689
|
+
/** The stun track, same rendering as conditionSummary(). */
|
|
690
|
+
stunSummary() {
|
|
691
|
+
const max = this.maxStunBoxes;
|
|
692
|
+
const filled = Math.min(max, this._stunTaken);
|
|
693
|
+
return `${'■'.repeat(filled)}${'□'.repeat(max - filled)} (${filled}/${max})`;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* The weapon actually in hand (right hand first, then left), or bare
|
|
697
|
+
* fists. Damage value and the attack-pool bonus both come off the item's
|
|
698
|
+
* quality rating -- gear is the whole progression system in this game,
|
|
699
|
+
* so it has to be what combat power flows from.
|
|
700
|
+
*/
|
|
701
|
+
getWeaponProfile() {
|
|
702
|
+
// Off the meat plane your gun is just a memory: Matrix combat runs on
|
|
703
|
+
// attack programs (Logic-scaled), astral combat on raw will given
|
|
704
|
+
// form (Magic-scaled). Hot-sim's deeper immersion sharpens the
|
|
705
|
+
// programs' bite.
|
|
706
|
+
if (this.plane === 'matrix') {
|
|
707
|
+
const hot = this.simMode === 'hot' ? 1 : 0;
|
|
708
|
+
// The deck IS the arsenal: its quality feeds program damage the way
|
|
709
|
+
// a better gun feeds DV. A technomancer's living persona weaves
|
|
710
|
+
// resonance instead of running programs.
|
|
711
|
+
if (this.isTechnomancer()) {
|
|
712
|
+
return { name: 'resonance surge', damage: 2 + Math.floor(this.logic / 2) + Math.floor(this.resonance / 2) + hot, poolBonus: 0 };
|
|
713
|
+
}
|
|
714
|
+
const deck = this.activeDeck ?? this.getCyberdeck();
|
|
715
|
+
return { name: 'attack programs', damage: 2 + Math.floor(this.logic / 2) + (deck?.qualityBonus ?? 0) + hot, poolBonus: 0 };
|
|
716
|
+
}
|
|
717
|
+
if (this.plane === 'astral') {
|
|
718
|
+
return { name: 'astral strike', damage: 2 + Math.floor(Math.max(1, this.magic) / 2), poolBonus: 0 };
|
|
719
|
+
}
|
|
720
|
+
// Muscle drives melee: unarmed 1 + STR/2, melee weapons 2 + STR/2 +
|
|
721
|
+
// quality (a strength-3 actor lands exactly the old flat numbers).
|
|
722
|
+
// Firearms don't care how strong you are. A HOLSTERED weapon counts
|
|
723
|
+
// for nothing -- until it's drawn (brandish, or the auto-draw when an
|
|
724
|
+
// exchange opens), your hands are empty.
|
|
725
|
+
const strengthBase = Math.floor(this.strength / 2);
|
|
726
|
+
const weapon = this.weaponDrawn ? this.getCarriedWeapon() : null;
|
|
727
|
+
if (!weapon) {
|
|
728
|
+
return { name: 'bare fists', damage: 1 + strengthBase, poolBonus: 0 };
|
|
729
|
+
}
|
|
730
|
+
const bonus = weapon.qualityBonus;
|
|
731
|
+
const base = weapon.category === Category.MeleeWeapon ? 2 + strengthBase : 4;
|
|
732
|
+
return { name: weapon.name, damage: base + bonus, poolBonus: bonus };
|
|
733
|
+
}
|
|
734
|
+
/** The weapon riding in a hand slot, drawn or holstered. */
|
|
735
|
+
getCarriedWeapon() {
|
|
736
|
+
return this._equipment.rightHand?.weapon ?? this._equipment.leftHand?.weapon ?? null;
|
|
737
|
+
}
|
|
738
|
+
/** Draws the carried weapon; returns it, or null if nothing changed
|
|
739
|
+
* (unarmed, or already in hand). */
|
|
740
|
+
drawWeapon() {
|
|
741
|
+
const weapon = this.getCarriedWeapon();
|
|
742
|
+
if (!weapon || this.weaponDrawn)
|
|
743
|
+
return null;
|
|
744
|
+
this.weaponDrawn = true;
|
|
745
|
+
return weapon;
|
|
746
|
+
}
|
|
747
|
+
/** Stows the drawn weapon; returns it, or null if nothing changed. */
|
|
748
|
+
stowWeapon() {
|
|
749
|
+
const weapon = this.getCarriedWeapon();
|
|
750
|
+
if (!weapon || !this.weaponDrawn)
|
|
751
|
+
return null;
|
|
752
|
+
this.weaponDrawn = false;
|
|
753
|
+
return weapon;
|
|
754
|
+
}
|
|
755
|
+
/** Total soak dice from every equipped armor-category item. */
|
|
756
|
+
getArmorValue() {
|
|
757
|
+
let total = 0;
|
|
758
|
+
for (const group of Object.values(this._equipment)) {
|
|
759
|
+
if (!group)
|
|
760
|
+
continue;
|
|
761
|
+
for (const item of Object.values(group)) {
|
|
762
|
+
if (item && Player.ARMOR_CATEGORIES.has(item.category)) {
|
|
763
|
+
total += 1 + item.qualityBonus;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return total;
|
|
768
|
+
}
|
|
769
|
+
getAttackPool() {
|
|
770
|
+
// Plane decides which attribute drives the fight: meat is muscle
|
|
771
|
+
// (Agility), the Matrix is mind (Logic, hot-sim +2), the astral is
|
|
772
|
+
// will (Willpower + half Magic). The SKILL is per-armament now (see
|
|
773
|
+
// activeCombatSkill / skillRating): firearms, blades, or unarmed in
|
|
774
|
+
// the meat; hacking is cybercombat; astral strikes ride unarmed (will
|
|
775
|
+
// given form). Wounds and sustaining drag everywhere.
|
|
776
|
+
if (this.plane === 'matrix') {
|
|
777
|
+
const hot = this.simMode === 'hot' ? 2 : 0;
|
|
778
|
+
return Math.max(0, this.logic + this.skillRating('hacking') + Math.floor(this.resonance / 2) + hot + this.woundModifier - this.sustainingPenalty);
|
|
779
|
+
}
|
|
780
|
+
if (this.plane === 'astral') {
|
|
781
|
+
return Math.max(0, this.willpower + Math.floor(this.magic / 2) + this.skillRating('unarmed') + this.woundModifier - this.sustainingPenalty);
|
|
782
|
+
}
|
|
783
|
+
// An adept's magic lives in their hands: half of it (rounded down)
|
|
784
|
+
// rides every meat-plane strike -- killing hands, improved reflexes.
|
|
785
|
+
const adeptBonus = this.adept ? Math.floor(this.magic / 2) : 0;
|
|
786
|
+
return Math.max(0, this.agility + this.skillRating(this.activeCombatSkill()) + adeptBonus + this.getWeaponProfile().poolBonus + this.woundModifier - this.sustainingPenalty);
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* The subdual pool: bare-hands work regardless of what's in the other
|
|
790
|
+
* fist -- Agility + unarmed (never the drawn weapon's skill or bonus).
|
|
791
|
+
* See subdue.ts.
|
|
792
|
+
*/
|
|
793
|
+
getUnarmedAttackPool() {
|
|
794
|
+
const adeptBonus = this.adept ? Math.floor(this.magic / 2) : 0;
|
|
795
|
+
return Math.max(0, this.agility + this.skillRating('unarmed') + adeptBonus + this.woundModifier - this.sustainingPenalty);
|
|
796
|
+
}
|
|
797
|
+
getDefensePool() {
|
|
798
|
+
// Reaction plus HALF intuition (rounded down) -- full SR
|
|
799
|
+
// Reaction+Intuition turned text-paced exchanges into whiff-fests, so
|
|
800
|
+
// gut feel contributes at half weight. Intuition 3 (the default) adds
|
|
801
|
+
// +1 over the old reaction-only pool. Off-plane variants swap the
|
|
802
|
+
// reflex attribute but keep the same shape.
|
|
803
|
+
if (this.plane === 'matrix') {
|
|
804
|
+
const hot = this.simMode === 'hot' ? 1 : 0;
|
|
805
|
+
return Math.max(0, this.intuition + Math.floor(this.logic / 2) + Math.floor(this.resonance / 2) + hot + this.woundModifier - this.sustainingPenalty);
|
|
806
|
+
}
|
|
807
|
+
if (this.plane === 'astral') {
|
|
808
|
+
return Math.max(0, this.intuition + Math.floor(this.magic / 2) + this.woundModifier - this.sustainingPenalty);
|
|
809
|
+
}
|
|
810
|
+
// Combat sense: the adept's magic reads the fight before it happens.
|
|
811
|
+
const adeptBonus = this.adept ? Math.floor(this.magic / 2) : 0;
|
|
812
|
+
return Math.max(0, this.reaction + Math.floor(this.intuition / 2) + adeptBonus + this.woundModifier - this.sustainingPenalty);
|
|
813
|
+
}
|
|
814
|
+
getSoakPool() {
|
|
815
|
+
// Matrix: firewall -- Logic plus the DECK's quality (your deck is your
|
|
816
|
+
// armor); a technomancer's firewall is Willpower plus half resonance
|
|
817
|
+
// (their mind guards itself). Astral: pure Willpower; worn armor is
|
|
818
|
+
// meat left behind.
|
|
819
|
+
if (this.plane === 'matrix') {
|
|
820
|
+
if (this.isTechnomancer()) {
|
|
821
|
+
return Math.max(0, this.willpower + Math.floor(this.resonance / 2));
|
|
822
|
+
}
|
|
823
|
+
const deck = this.activeDeck ?? this.getCyberdeck();
|
|
824
|
+
return Math.max(0, this.logic + (deck?.qualityBonus ?? 0));
|
|
825
|
+
}
|
|
826
|
+
if (this.plane === 'astral') {
|
|
827
|
+
return Math.max(0, this.willpower);
|
|
828
|
+
}
|
|
829
|
+
return Math.max(0, this.body + this.getArmorValue() + this._sustainedArmor);
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Initiative: Reaction + Intuition (straight SR), plus the AR edge from
|
|
833
|
+
* an equipped, ONLINE commlink -- the tactical overlay feeding you
|
|
834
|
+
* angles. Running silent kills the overlay along with the broadcast, so
|
|
835
|
+
* going dark costs you speed on the draw: the silent tradeoff grows
|
|
836
|
+
* teeth. Wounds and sustained spells slow you like everything else.
|
|
837
|
+
*/
|
|
838
|
+
getInitiativePool() {
|
|
839
|
+
// Matrix speed is thought-speed: Logic + Intuition, hot-sim +2 (the
|
|
840
|
+
// whole point of running hot). Astral forms move at the speed of
|
|
841
|
+
// intention: Intuition doubled.
|
|
842
|
+
if (this.plane === 'matrix') {
|
|
843
|
+
const hot = this.simMode === 'hot' ? 2 : 0;
|
|
844
|
+
return Math.max(0, this.logic + this.intuition + Math.floor(this.resonance / 2) + hot + this.woundModifier - this.sustainingPenalty);
|
|
845
|
+
}
|
|
846
|
+
if (this.plane === 'astral') {
|
|
847
|
+
return Math.max(0, this.intuition * 2 + this.woundModifier - this.sustainingPenalty);
|
|
848
|
+
}
|
|
849
|
+
const link = this._equipment.head?.commlink;
|
|
850
|
+
// AR needs a link that's on you, broadcasting, and not hacked dark.
|
|
851
|
+
const arBonus = link && !this.runningSilent && !this.arOffline ? link.qualityBonus : 0;
|
|
852
|
+
return Math.max(0, this.reaction + this.intuition + arBonus + this.woundModifier - this.sustainingPenalty);
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* If an edge boost is armed (see commands/edge.ts), consume it: returns
|
|
856
|
+
* the bonus dice for the strike being rolled right now and disarms.
|
|
857
|
+
* Called by the strike-profile builders, so the boost lands on the next
|
|
858
|
+
* attack/subdue/manabolt this actor initiates.
|
|
859
|
+
*/
|
|
860
|
+
consumeEdgeBoost() {
|
|
861
|
+
if (!this.edgeBoostArmed)
|
|
862
|
+
return 0;
|
|
863
|
+
this.edgeBoostArmed = false;
|
|
864
|
+
return this.edge;
|
|
865
|
+
}
|
|
866
|
+
/** New job, fresh luck -- and a fresh seat at the negotiating table. */
|
|
867
|
+
refreshEdge() {
|
|
868
|
+
this.edgeRemaining = this.edge;
|
|
869
|
+
this.edgeBoostArmed = false;
|
|
870
|
+
this.haggledThisJob = false;
|
|
871
|
+
}
|
|
872
|
+
// ======================== Magic (see commands/cast.ts) ========================
|
|
873
|
+
// Two roads to casting. GEAR-DRIVEN: what a mundane can cast comes from
|
|
874
|
+
// the spell formulas/grimoires they carry, at a flat scholar's base pool
|
|
875
|
+
// -- consistent with the rest of the game, where gear IS the progression
|
|
876
|
+
// system. INNATE: an Awakened actor (magic attribute > 0) knows every
|
|
877
|
+
// spell without any gear -- the talent is the knowledge; formulas are
|
|
878
|
+
// how mundanes fake it -- and casts from magic + 2 instead of the flat
|
|
879
|
+
// base, so a strong mage out-throws any book-taught scholar. A Focus
|
|
880
|
+
// stacks on top of either road.
|
|
881
|
+
/** Base casting dice for a mundane casting from a formula/grimoire. */
|
|
882
|
+
static BASE_MAGIC_POOL = 5;
|
|
883
|
+
/** Awakened: innate talent, no gear required. Adepts count -- their
|
|
884
|
+
* aura burns like any mage's, and assensing works -- but they channel
|
|
885
|
+
* magic into the body, not spells (see knowsSpell/isCaster). */
|
|
886
|
+
isAwakened() {
|
|
887
|
+
return this.magic > 0;
|
|
888
|
+
}
|
|
889
|
+
/** Technomancer: living persona -- the Matrix without a deck (see ICombatAttributes.resonance). */
|
|
890
|
+
isTechnomancer() {
|
|
891
|
+
return this.resonance > 0;
|
|
892
|
+
}
|
|
893
|
+
// A sustained spell (currently only the armor spell) grants its bonus
|
|
894
|
+
// continuously but costs concentration: -2 dice on every active pool
|
|
895
|
+
// while it's up. See cast.ts.
|
|
896
|
+
_sustainedArmor = 0;
|
|
897
|
+
get sustainedArmor() {
|
|
898
|
+
return this._sustainedArmor;
|
|
899
|
+
}
|
|
900
|
+
setSustainedArmor(bonus) {
|
|
901
|
+
this._sustainedArmor = Math.max(0, Math.floor(bonus));
|
|
902
|
+
}
|
|
903
|
+
get sustainingPenalty() {
|
|
904
|
+
// Each sustained effect costs -2 dice across the board: the armor
|
|
905
|
+
// spell, and open astral senses (see commands/assense.ts). They stack
|
|
906
|
+
// -- holding both is walking around half-blind.
|
|
907
|
+
return (this._sustainedArmor > 0 ? 2 : 0) + (this.astralPerceiving ? 2 : 0);
|
|
908
|
+
}
|
|
909
|
+
// Astral perception (see commands/assense.ts): senses held OPEN as a
|
|
910
|
+
// sustained state -- passive aura reads, at -2 dice to everything else.
|
|
911
|
+
// Meat-plane only; projecting IS full astral senses (cleared on
|
|
912
|
+
// project), and the Matrix has no auras at all.
|
|
913
|
+
astralPerceiving = false;
|
|
914
|
+
// Sneak-walking (see commands/sneak.ts): room entries become opposed
|
|
915
|
+
// Sneaking-vs-Perception rolls per witness (NPC.see). Broken by any
|
|
916
|
+
// loud act -- attacking, casting, speaking, brandishing, calling.
|
|
917
|
+
sneaking = false;
|
|
918
|
+
// One haggle per job (see commands/haggle.ts); reset with the edge pool
|
|
919
|
+
// on each new job (refreshEdge).
|
|
920
|
+
haggledThisJob = false;
|
|
921
|
+
// Where this actor just came from and the direction BACK -- the HUD's
|
|
922
|
+
// breadcrumb (see game.ts updateStatus / go.ts). Big scenes (the hub's
|
|
923
|
+
// 18 rooms) made "which way did I come in" a real question. Cleared on
|
|
924
|
+
// scene transitions; only meaningful room-to-room.
|
|
925
|
+
cameFrom;
|
|
926
|
+
// Social-play bookkeeping ON THE TARGET (see commands/persuade.ts):
|
|
927
|
+
// "persuaded-by:<name>" marks who already made their play on this actor
|
|
928
|
+
// this job. NPCs are rebuilt each scene, so it resets naturally.
|
|
929
|
+
socialMarks = new Set();
|
|
930
|
+
/** The best Focus item on this actor, if any -- scales casting power. */
|
|
931
|
+
getBestFocus() {
|
|
932
|
+
return this._inventory.getAllItems()
|
|
933
|
+
.filter(i => i.category === Category.Focus)
|
|
934
|
+
.sort((a, b) => b.qualityBonus - a.qualityBonus)[0];
|
|
935
|
+
}
|
|
936
|
+
getMagicPool() {
|
|
937
|
+
const focus = this.getBestFocus();
|
|
938
|
+
const focusBonus = focus ? 1 + focus.qualityBonus : 0;
|
|
939
|
+
// SR-proper when the SPELLCASTING skill is rated: Magic + Spellcasting
|
|
940
|
+
// for the Awakened, base + skill for a book-caster. Unrated falls back
|
|
941
|
+
// to the legacy shapes (talent max(5, magic+2); scholar's flat 5), so
|
|
942
|
+
// skill-less NPC mages keep their historical pools.
|
|
943
|
+
const spellSkill = this.skillRating('spellcasting');
|
|
944
|
+
const base = this.isAwakened()
|
|
945
|
+
? (spellSkill > 0 ? this.magic + spellSkill : Math.max(Player.BASE_MAGIC_POOL, this.magic + 2))
|
|
946
|
+
: Player.BASE_MAGIC_POOL + spellSkill;
|
|
947
|
+
return Math.max(0, base + focusBonus + this.woundModifier - this.sustainingPenalty);
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Awakened actors know every spell innately. A mundane knows a spell if
|
|
951
|
+
* they carry its formula (item name must contain the spell's name) or
|
|
952
|
+
* any grimoire (a spellbook teaches everything).
|
|
953
|
+
*/
|
|
954
|
+
knowsSpell(spellName) {
|
|
955
|
+
// An adept's magic is muscle memory, not incantation -- no spells,
|
|
956
|
+
// not even from a formula in hand. Their magic already lives in their
|
|
957
|
+
// meat-plane combat pools.
|
|
958
|
+
if (this.adept)
|
|
959
|
+
return false;
|
|
960
|
+
if (this.isAwakened())
|
|
961
|
+
return true;
|
|
962
|
+
const wanted = spellName.toLowerCase();
|
|
963
|
+
return this._inventory.getAllItems().some(i => i.category === Category.Grimoire ||
|
|
964
|
+
(i.category === Category.SpellFormula && i.name.toLowerCase().includes(wanted)));
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* The gate for attempting magic at all (ward bypass included): innate
|
|
968
|
+
* talent, or any magic gear.
|
|
969
|
+
*/
|
|
970
|
+
isCaster() {
|
|
971
|
+
if (this.adept)
|
|
972
|
+
return false; // body-magic only -- see knowsSpell
|
|
973
|
+
return this.isAwakened() || this._inventory.getAllItems().some(i => i.category === Category.SpellFormula ||
|
|
974
|
+
i.category === Category.Grimoire ||
|
|
975
|
+
i.category === Category.Focus);
|
|
976
|
+
}
|
|
977
|
+
// ================================================================================
|
|
326
978
|
get lifestyleChoices() {
|
|
327
979
|
return this._lifestyleChoices;
|
|
328
980
|
}
|