@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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import blessed from 'blessed';
|
|
2
2
|
import { capitalCase } from 'change-case';
|
|
3
|
-
import {
|
|
3
|
+
import { Room } from './models/_index.js';
|
|
4
|
+
import { Direction } from './types/shared/direction-enum.js';
|
|
5
|
+
import { Logger } from './utilities/logger.js';
|
|
4
6
|
import { LookCommand } from './commands/look.js';
|
|
5
7
|
import { TakeCommand } from './commands/take.js';
|
|
6
8
|
import { GoCommand } from './commands/go.js';
|
|
@@ -18,20 +20,166 @@ import { StoreCommand } from './commands/store.js';
|
|
|
18
20
|
import { GiveCommand } from './commands/give.js';
|
|
19
21
|
import { ReadCommand } from './commands/read.js';
|
|
20
22
|
import { KillCommand } from './commands/kill.js';
|
|
23
|
+
import { AttackCommand } from './commands/attack.js';
|
|
24
|
+
import { SpellsCommand } from './commands/spells.js';
|
|
25
|
+
import { InventoryCommand } from './commands/inv.js';
|
|
26
|
+
import { EquipmentCommand } from './commands/equipment.js';
|
|
27
|
+
import { MapCommand } from './commands/map.js';
|
|
28
|
+
import { SilentCommand } from './commands/silent.js';
|
|
29
|
+
import { RestCommand } from './commands/rest.js';
|
|
30
|
+
import { ReloadCommand } from './commands/reload.js';
|
|
31
|
+
import { SubdueCommand } from './commands/subdue.js';
|
|
32
|
+
import { EdgeCommand } from './commands/edge.js';
|
|
33
|
+
import { SheetCommand } from './commands/sheet.js';
|
|
34
|
+
import { CallCommand } from './commands/call.js';
|
|
35
|
+
import { EndCallCommand } from './commands/end-call.js';
|
|
36
|
+
import { fuzzyPickName, levenshteinDistance } from './utilities/fuzzy-match.js';
|
|
37
|
+
import { PickCommand } from './commands/pick.js';
|
|
38
|
+
import { HackCommand } from './commands/hack.js';
|
|
39
|
+
import { CastCommand } from './commands/cast.js';
|
|
40
|
+
import { StanceCommand } from './commands/stance.js';
|
|
41
|
+
import { SurrenderCommand } from './commands/surrender.js';
|
|
42
|
+
import { JackCommand } from './commands/jack.js';
|
|
43
|
+
import { ProjectCommand, ReturnCommand } from './commands/project.js';
|
|
44
|
+
import { AssenseCommand } from './commands/assense.js';
|
|
45
|
+
import { DroneCommand } from './commands/drone.js';
|
|
46
|
+
import { PanCommand } from './commands/pan.js';
|
|
47
|
+
import { RebootCommand } from './commands/reboot.js';
|
|
48
|
+
import { BrandishCommand, HolsterCommand } from './commands/brandish.js';
|
|
49
|
+
import { AdvanceCommand } from './commands/advance.js';
|
|
50
|
+
import { equipIntoEmptySlots } from './archetypes.js';
|
|
51
|
+
import { NPC } from './models/npc.js';
|
|
52
|
+
import { HaggleCommand } from './commands/haggle.js';
|
|
53
|
+
import { SneakCommand } from './commands/sneak.js';
|
|
54
|
+
import { PersuadeCommand } from './commands/persuade.js';
|
|
55
|
+
import { BluffCommand } from './commands/bluff.js';
|
|
56
|
+
import { AutoFight } from './utilities/auto-fight.js';
|
|
21
57
|
import { CommandFactory } from './factories/command-factory.js';
|
|
22
58
|
import { SceneSynthesizer } from './factories/scene-factory.js';
|
|
59
|
+
import { SceneSeedGenerator, FIXER_NAME } from './factories/scene-seed-generator.js';
|
|
60
|
+
import { CALL_ICON, CALL_COLOR } from './utilities/comm-style.js';
|
|
23
61
|
export default class Game {
|
|
24
62
|
initialized;
|
|
25
63
|
static instance;
|
|
26
64
|
sceneSeed;
|
|
27
65
|
scene;
|
|
28
66
|
player;
|
|
67
|
+
// The fight autopilot (Tab / "stance" command) -- created once, reads
|
|
68
|
+
// live game state at fire time so it survives scene transitions.
|
|
69
|
+
autoFight = new AutoFight(this);
|
|
70
|
+
// Just three surfaces: a full-width log, a HUD strip, and the input box.
|
|
71
|
+
// The old always-on Inventory/Equipment/Map sidebar boxes became the
|
|
72
|
+
// on-demand inv/equipment/map commands (see commands/inv.ts et al.) to
|
|
73
|
+
// give the log the whole screen.
|
|
29
74
|
_screen;
|
|
30
|
-
_exitsBox;
|
|
31
|
-
_inventoryBox;
|
|
32
75
|
_logBox;
|
|
76
|
+
_statusBox;
|
|
33
77
|
_inputBox;
|
|
34
|
-
|
|
78
|
+
_useAi = false;
|
|
79
|
+
_logFilePath = '';
|
|
80
|
+
_tier = 1;
|
|
81
|
+
// Guards against two overlapping continueToNextScene() calls -- found via
|
|
82
|
+
// a real session where calling the fixer twice in a row (nothing stopped
|
|
83
|
+
// it, and a 20-40+ second draft invites a second, impatient call) fired
|
|
84
|
+
// two independent drafts at once. Both read the same starting _tier
|
|
85
|
+
// before either finished, both generated a "tier 2" job, and whichever
|
|
86
|
+
// one finished last silently overwrote the first one's scene, room, and
|
|
87
|
+
// player-location state out from under whatever the player was already
|
|
88
|
+
// doing in it.
|
|
89
|
+
_draftInProgress = false;
|
|
90
|
+
// The HUB scene, when this session started in one (a seed with
|
|
91
|
+
// "hub": true -- the persistent home base, e.g. Tacoma Purple Haze).
|
|
92
|
+
// Kept as the live Scene INSTANCE, same pattern as _homeRoom, so
|
|
93
|
+
// everything in it -- NPC state, shop stock, dropped loot -- persists
|
|
94
|
+
// untouched while runs play out, and returnToHub() just re-points the
|
|
95
|
+
// world at it. undefined for classic sessions (test scene, launch-
|
|
96
|
+
// generated runs), which keep the old continue->draft loop.
|
|
97
|
+
_hubScene;
|
|
98
|
+
_hubName = 'home';
|
|
99
|
+
get hasHub() {
|
|
100
|
+
return this._hubScene !== undefined;
|
|
101
|
+
}
|
|
102
|
+
/** The persistent hideout room -- go.ts's walk-home trigger checks it. */
|
|
103
|
+
get homeRoom() {
|
|
104
|
+
return this._homeRoom;
|
|
105
|
+
}
|
|
106
|
+
// A drafted run waiting for the player to take it (hub sessions only):
|
|
107
|
+
// generation finishing sends a MESSAGE instead of teleporting anyone --
|
|
108
|
+
// "call Mr. Johnson" hears the offer, "travel" goes.
|
|
109
|
+
_pendingRunSeed;
|
|
110
|
+
_pendingRunTier;
|
|
111
|
+
get pendingRun() {
|
|
112
|
+
return this._pendingRunSeed;
|
|
113
|
+
}
|
|
114
|
+
get pendingRunTier() {
|
|
115
|
+
return this._pendingRunTier;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The cab that answers "call taxi" varies per job (player request: not
|
|
119
|
+
* always a van) -- picked once when the pending run lands, so the
|
|
120
|
+
* arrival and the ride read as ONE vehicle.
|
|
121
|
+
*/
|
|
122
|
+
static RIDE_POOL = [
|
|
123
|
+
{
|
|
124
|
+
arrive: `A matte-black step-van splashes to the curb in front of you, side door already sliding open.`,
|
|
125
|
+
board: `The door rattles shut and the Haze smears past the grimy windows.`,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
arrive: `A pearl-gray Westwind glides up out of the rain, gullwing lifting -- somebody's billing this to the client.`,
|
|
129
|
+
board: `The gullwing sighs closed and the launch pins you into leather that costs more than your rent.`,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
arrive: `A patchwork gypsy cab rattles around the corner and stops dead in front of you, meter torn out, driver watching the mirrors.`,
|
|
133
|
+
board: `The cabbie pulls off before the door's even latched, humming something older than the district.`,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
arrive: `A rust-eaten GMC Bulldog bulls through traffic and mounts the curb beside you, panel doors ajar.`,
|
|
137
|
+
board: `The Bulldog coughs, catches, and shoves into traffic like it's owed something.`,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
arrive: `An unmarked ambulance rolls up silent, lights dead, back doors swinging open -- nobody looks twice at it.`,
|
|
141
|
+
board: `You ride out flat on the gurney, ceiling lights strobing past the little windows.`,
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
arrive: `A growling Harley with a reinforced sidecar pulls a U-turn and stops at your boots, rider jerking a thumb at the car.`,
|
|
145
|
+
board: `You drop into the sidecar; the pipes drown the whole district out.`,
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
_pendingRide;
|
|
149
|
+
/** The pending job's cab flavor -- "call taxi" narrates with it (see call.ts). */
|
|
150
|
+
get pendingRide() {
|
|
151
|
+
return this._pendingRide;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Generates NPC first-impression appearances ("look at <npc>") in the
|
|
155
|
+
* background, one at a time -- the only AI dressing still worth doing
|
|
156
|
+
* now that every scene arrives with real prose. Fire-and-forget: until
|
|
157
|
+
* an appearance lands, look-at just shows a bit less flavor.
|
|
158
|
+
*/
|
|
159
|
+
warmNpcAppearancesInBackground() {
|
|
160
|
+
if (!this._useAi)
|
|
161
|
+
return;
|
|
162
|
+
const npcs = this.scene.allActors.filter((a) => a instanceof NPC);
|
|
163
|
+
const context = `${this.sceneSeed?.name ?? ''} -- ${this.scene.story}`;
|
|
164
|
+
void (async () => {
|
|
165
|
+
for (const npc of npcs) {
|
|
166
|
+
try {
|
|
167
|
+
await npc.ensureAIAppearance(context);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// Non-fatal: the next look-at simply has less flavor.
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
})();
|
|
174
|
+
}
|
|
175
|
+
// A single fixed room, created once and never torn down across job
|
|
176
|
+
// transitions -- the player's own place, unlike every job's generated
|
|
177
|
+
// rooms which get discarded wholesale when the next job replaces them.
|
|
178
|
+
_homeRoom;
|
|
179
|
+
_homeRoomExitDirection;
|
|
180
|
+
static HOME_DIRECTION_PRIORITY = [
|
|
181
|
+
Direction.DOWN, Direction.UP, Direction.SOUTH, Direction.NORTH, Direction.EAST, Direction.WEST,
|
|
182
|
+
];
|
|
35
183
|
constructor(player, sceneSeed) {
|
|
36
184
|
this.player = player;
|
|
37
185
|
this.sceneSeed = sceneSeed;
|
|
@@ -55,10 +203,81 @@ export default class Game {
|
|
|
55
203
|
CommandFactory.registerCommand('give', GiveCommand);
|
|
56
204
|
CommandFactory.registerCommand('read', ReadCommand);
|
|
57
205
|
CommandFactory.registerCommand('kill', KillCommand);
|
|
206
|
+
CommandFactory.registerCommand('attack', AttackCommand);
|
|
207
|
+
CommandFactory.registerCommand('spells', SpellsCommand);
|
|
208
|
+
CommandFactory.registerCommand('inv', InventoryCommand);
|
|
209
|
+
CommandFactory.registerCommand('inventory', InventoryCommand);
|
|
210
|
+
CommandFactory.registerCommand('equipment', EquipmentCommand);
|
|
211
|
+
CommandFactory.registerCommand('eq', EquipmentCommand);
|
|
212
|
+
CommandFactory.registerCommand('map', MapCommand);
|
|
213
|
+
CommandFactory.registerCommand('exits', MapCommand);
|
|
214
|
+
CommandFactory.registerCommand('silent', SilentCommand);
|
|
215
|
+
CommandFactory.registerCommand('rest', RestCommand);
|
|
216
|
+
CommandFactory.registerCommand('reload', ReloadCommand);
|
|
217
|
+
CommandFactory.registerCommand('subdue', SubdueCommand);
|
|
218
|
+
CommandFactory.registerCommand('edge', EdgeCommand);
|
|
219
|
+
CommandFactory.registerCommand('sheet', SheetCommand);
|
|
220
|
+
CommandFactory.registerCommand('stats', SheetCommand);
|
|
221
|
+
CommandFactory.registerCommand('call', CallCommand);
|
|
222
|
+
CommandFactory.registerCommand('end-call', EndCallCommand);
|
|
223
|
+
CommandFactory.registerCommand('pick', PickCommand);
|
|
224
|
+
CommandFactory.registerCommand('hack', HackCommand);
|
|
225
|
+
CommandFactory.registerCommand('cast', CastCommand);
|
|
226
|
+
CommandFactory.registerCommand('stance', StanceCommand);
|
|
227
|
+
CommandFactory.registerCommand('surrender', SurrenderCommand);
|
|
228
|
+
CommandFactory.registerCommand('jack', JackCommand);
|
|
229
|
+
CommandFactory.registerCommand('project', ProjectCommand);
|
|
230
|
+
CommandFactory.registerCommand('return', ReturnCommand);
|
|
231
|
+
CommandFactory.registerCommand('assense', AssenseCommand);
|
|
232
|
+
CommandFactory.registerCommand('drone', DroneCommand);
|
|
233
|
+
CommandFactory.registerCommand('pan', PanCommand);
|
|
234
|
+
CommandFactory.registerCommand('reboot', RebootCommand);
|
|
235
|
+
CommandFactory.registerCommand('brandish', BrandishCommand);
|
|
236
|
+
CommandFactory.registerCommand('draw', BrandishCommand);
|
|
237
|
+
CommandFactory.registerCommand('holster', HolsterCommand);
|
|
238
|
+
CommandFactory.registerCommand('stow', HolsterCommand);
|
|
239
|
+
// "end" and "hangup" both bounced in real sessions when players
|
|
240
|
+
// wanted to end a call -- the intent is unambiguous mid-call.
|
|
241
|
+
CommandFactory.registerCommand('end', EndCallCommand);
|
|
242
|
+
CommandFactory.registerCommand('hangup', EndCallCommand);
|
|
243
|
+
CommandFactory.registerCommand('advance', AdvanceCommand);
|
|
244
|
+
CommandFactory.registerCommand('train', AdvanceCommand);
|
|
245
|
+
CommandFactory.registerCommand('haggle', HaggleCommand);
|
|
246
|
+
CommandFactory.registerCommand('negotiate', HaggleCommand);
|
|
247
|
+
CommandFactory.registerCommand('sneak', SneakCommand);
|
|
248
|
+
CommandFactory.registerCommand('persuade', PersuadeCommand);
|
|
249
|
+
CommandFactory.registerCommand('convince', PersuadeCommand);
|
|
250
|
+
CommandFactory.registerCommand('bluff', BluffCommand);
|
|
58
251
|
}
|
|
59
252
|
determineStartRoom() {
|
|
60
253
|
return this.scene.determineStartRoom();
|
|
61
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Wires a fresh, always-unlocked path between the persistent home base
|
|
257
|
+
* and wherever the player is starting the current job from. Clears the
|
|
258
|
+
* previous job's connection first (if any) so the home base never keeps
|
|
259
|
+
* a stale door into an abandoned scene's now-orphaned rooms -- only ever
|
|
260
|
+
* one way out at a time, pointing at whichever job is actually current.
|
|
261
|
+
*/
|
|
262
|
+
connectHomeRoom(jobStartRoom) {
|
|
263
|
+
if (this._homeRoomExitDirection) {
|
|
264
|
+
this._homeRoom.exits.delete(this._homeRoomExitDirection);
|
|
265
|
+
}
|
|
266
|
+
const direction = Game.HOME_DIRECTION_PRIORITY.find(d => !jobStartRoom.exits.has(d));
|
|
267
|
+
if (!direction) {
|
|
268
|
+
// Every direction is already spoken for on this job's start room --
|
|
269
|
+
// rare (most generated start rooms use 1-2 exits), but leave the
|
|
270
|
+
// player without a home-base link this job rather than clobbering
|
|
271
|
+
// one of the job's own exits.
|
|
272
|
+
this._homeRoomExitDirection = undefined;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
jobStartRoom.addExit(direction, this._homeRoom);
|
|
276
|
+
// Tracked from the HOME room's own side (the opposite of `direction`,
|
|
277
|
+
// which is the job room's side) -- that's the key actually stored on
|
|
278
|
+
// this._homeRoom.exits, and what gets cleared above next time around.
|
|
279
|
+
this._homeRoomExitDirection = jobStartRoom.getOppositeDirection(direction);
|
|
280
|
+
}
|
|
62
281
|
get logBox() {
|
|
63
282
|
return this._logBox;
|
|
64
283
|
}
|
|
@@ -66,43 +285,288 @@ export default class Game {
|
|
|
66
285
|
return this._inputBox;
|
|
67
286
|
}
|
|
68
287
|
async init(options, screen) {
|
|
69
|
-
this.
|
|
288
|
+
this._useAi = options.useAi;
|
|
289
|
+
this._logFilePath = options.logFilePath;
|
|
70
290
|
this.initInputBox(screen);
|
|
71
291
|
this.initLogBox(screen);
|
|
72
|
-
this.
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
height: 'shrink',
|
|
81
|
-
content: 'Generating Scene... Please wait.',
|
|
82
|
-
border: { type: 'line' },
|
|
83
|
-
style: {
|
|
84
|
-
fg: 'white',
|
|
85
|
-
bg: 'black',
|
|
86
|
-
border: { fg: 'cyan' },
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
screen.append(loadingBox);
|
|
90
|
-
screen.render();
|
|
292
|
+
this.initStatusBox(screen);
|
|
293
|
+
// No "Generating Scene... Please wait." box anymore: every path now
|
|
294
|
+
// arrives with REAL prose (hand-authored static seeds, chunk-authored
|
|
295
|
+
// generated scenes), so the old blocking AI-redressing pass -- 30-60
|
|
296
|
+
// seconds rewriting text that was already good -- is skipped and the
|
|
297
|
+
// game opens instantly. The one thing still worth generating (NPC
|
|
298
|
+
// first-impression appearances for "look at") warms in the background
|
|
299
|
+
// below.
|
|
91
300
|
try {
|
|
92
|
-
this.scene = await SceneSynthesizer.synthesizeFromJson(this.sceneSeed, this.player,
|
|
301
|
+
this.scene = await SceneSynthesizer.synthesizeFromJson(this.sceneSeed, this.player, false);
|
|
93
302
|
}
|
|
94
303
|
catch (err) {
|
|
95
304
|
throw new Error(`Scene generation failed: ${err.message}`);
|
|
96
305
|
}
|
|
97
|
-
// Remove loading box
|
|
98
|
-
screen.remove(loadingBox);
|
|
99
|
-
screen.render();
|
|
100
306
|
this.scene.initialize(this);
|
|
101
307
|
this.player.currentLocation = this.determineStartRoom();
|
|
102
308
|
this.registerCommands();
|
|
103
309
|
this._screen = screen;
|
|
310
|
+
this._homeRoom = new Room({
|
|
311
|
+
name: 'Your Hideout',
|
|
312
|
+
description: `A cramped, windowless room you can actually call your own -- a cot, a locker, and a door that locks from the inside. Whatever you leave here stays here, no matter where the next job takes you.`,
|
|
313
|
+
roomType: 'Residence',
|
|
314
|
+
});
|
|
315
|
+
this.scene.addRoom(this._homeRoom);
|
|
316
|
+
this.connectHomeRoom(this.player.currentLocation);
|
|
317
|
+
// Every session starts AT HOME -- the hideout, one door from the job's
|
|
318
|
+
// start room (player request, left as an in-game note: "I'd like to
|
|
319
|
+
// always start in my hideout"). The scene's isStartRoom still anchors
|
|
320
|
+
// where the hideout's door leads; the runner just wakes up on their
|
|
321
|
+
// own cot instead of already standing in the job.
|
|
322
|
+
this.player.currentLocation = this._homeRoom;
|
|
323
|
+
// Starting in a hub makes this a hub SESSION: runs are commissioned
|
|
324
|
+
// by calling the fixer from here, and completing one returns here.
|
|
325
|
+
if (this.scene.isHub) {
|
|
326
|
+
this._hubScene = this.scene;
|
|
327
|
+
this._hubName = this.sceneSeed?.name ?? 'home';
|
|
328
|
+
}
|
|
329
|
+
// Gear up automatically: wearables (and the commlink) slot into any
|
|
330
|
+
// EMPTY slots at load -- no "equip all" ritual to start a session
|
|
331
|
+
// (player request). Occupied slots (archetype signature gear) are
|
|
332
|
+
// never swapped out.
|
|
333
|
+
equipIntoEmptySlots(this.player);
|
|
334
|
+
this.warmNpcAppearancesInBackground();
|
|
104
335
|
this.initialized = true;
|
|
105
|
-
|
|
336
|
+
// Nudge the player toward "call" up front -- the fixer convention means
|
|
337
|
+
// the story deliberately doesn't narrate a completed briefing, so
|
|
338
|
+
// without this hint there'd be no in-game indication that "call" is how
|
|
339
|
+
// you actually get your orders.
|
|
340
|
+
const wc = this.scene.getWinCondition();
|
|
341
|
+
const callHint = (wc?.type === 'give' && wc.toNpc)
|
|
342
|
+
? `\n\nYour commlink is quiet for now, but ${wc.toNpc} is expecting to hear from you -- try "call ${wc.toNpc}" when you're ready to check in.`
|
|
343
|
+
: '';
|
|
344
|
+
return `Welcome, ${this.player.name}\n ${this.scene.story}${callHint}`;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Synchronous readiness check for continueToNextScene() -- lets callers
|
|
348
|
+
* give instant feedback for the "not ready yet" cases (job not done, no
|
|
349
|
+
* AI configured) without waiting on anything async.
|
|
350
|
+
*/
|
|
351
|
+
canDraftNextScene() {
|
|
352
|
+
if (this._draftInProgress) {
|
|
353
|
+
return { allowed: false, message: `Still lining it up -- I'll ping you.` };
|
|
354
|
+
}
|
|
355
|
+
if (this._pendingRunSeed) {
|
|
356
|
+
return { allowed: false, message: `You've got my message already. Call a taxi when you're ready to roll.` };
|
|
357
|
+
}
|
|
358
|
+
// A hub is ALWAYS ready to commission the next run -- it has no win
|
|
359
|
+
// condition to complete; that's the whole point of home.
|
|
360
|
+
if (!this.scene.isHub && !this.scene.isCompleted()) {
|
|
361
|
+
return { allowed: false, message: `This job isn't finished yet.` };
|
|
362
|
+
}
|
|
363
|
+
if (!this._useAi) {
|
|
364
|
+
return { allowed: false, message: `Continuing needs an AI-generated job, which requires an AI provider to be configured (OPENAI_API_KEY or ANTHROPIC_API_KEY).` };
|
|
365
|
+
}
|
|
366
|
+
return { allowed: true };
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Fires continueToNextScene() without waiting on it -- job generation can
|
|
370
|
+
* take 20-40+ seconds, and neither "continue" nor calling the fixer
|
|
371
|
+
* should freeze the rest of the session behind that. Self-announces the
|
|
372
|
+
* result once it resolves, styled the same as any other comms exchange,
|
|
373
|
+
* since by design nothing else is holding a reference to display it.
|
|
374
|
+
* Public: called directly by call.ts when the player calls the fixer
|
|
375
|
+
* after a job's done, not just from "continue" here in game.ts.
|
|
376
|
+
*/
|
|
377
|
+
draftNextSceneInBackground() {
|
|
378
|
+
void this.continueToNextScene()
|
|
379
|
+
.then(nextJobText => {
|
|
380
|
+
if (nextJobText) {
|
|
381
|
+
Logger.getInstance().logWithColor(`${CALL_ICON} ${nextJobText}`, CALL_COLOR);
|
|
382
|
+
}
|
|
383
|
+
})
|
|
384
|
+
.catch(err => {
|
|
385
|
+
Logger.getInstance().error(`Failed to draft the next job: ${err}`);
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Drafts and loads the next job once the current scene's win condition
|
|
390
|
+
* has been met. Reuses the same long-lived Player (and its inventory),
|
|
391
|
+
* so gear compounds across the session -- nothing is persisted to disk,
|
|
392
|
+
* it just carries forward for the life of this process. Re-checks
|
|
393
|
+
* canDraftNextScene() itself (callers are expected to check it first for
|
|
394
|
+
* instant feedback, but this stays safe to call directly too).
|
|
395
|
+
*/
|
|
396
|
+
async continueToNextScene() {
|
|
397
|
+
// Hub sessions: a finished RUN doesn't chain into the next draft --
|
|
398
|
+
// "continue" takes you home instead, and the next run is commissioned
|
|
399
|
+
// from there (call the fixer). Classic sessions skip this and keep
|
|
400
|
+
// the old continue->draft loop.
|
|
401
|
+
if (this._hubScene && this.scene !== this._hubScene && this.scene.isCompleted()) {
|
|
402
|
+
return this.returnToHub();
|
|
403
|
+
}
|
|
404
|
+
const gate = this.canDraftNextScene();
|
|
405
|
+
if (!gate.allowed) {
|
|
406
|
+
return gate.message ?? `This job isn't finished yet.`;
|
|
407
|
+
}
|
|
408
|
+
this._draftInProgress = true;
|
|
409
|
+
try {
|
|
410
|
+
return await this.draftNextScene();
|
|
411
|
+
}
|
|
412
|
+
finally {
|
|
413
|
+
this._draftInProgress = false;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Re-points the world at the preserved hub scene after a run: the hub's
|
|
418
|
+
* whole state (NPCs, shop stock, anything dropped there) survived
|
|
419
|
+
* untouched because the INSTANCE survived. The player walks back in
|
|
420
|
+
* through their own hideout, firmly in the meat -- any lingering run
|
|
421
|
+
* state (a persona still jacked in, an astral projection, a sneak,
|
|
422
|
+
* open combat bookkeeping) is settled on the way home.
|
|
423
|
+
*/
|
|
424
|
+
returnToHub() {
|
|
425
|
+
const hub = this._hubScene;
|
|
426
|
+
if (!hub)
|
|
427
|
+
return `There's no home to go back to.`;
|
|
428
|
+
// Settle cross-plane and combat state that must not leak into the hub
|
|
429
|
+
// (the run's rooms -- including a bodyRoom the player left behind --
|
|
430
|
+
// are about to be orphaned wholesale).
|
|
431
|
+
this.player.plane = 'meat';
|
|
432
|
+
this.player.bodyRoom = undefined;
|
|
433
|
+
this.player.simMode = undefined;
|
|
434
|
+
this.player.astralTicks = 0;
|
|
435
|
+
this.player.activeDeck = undefined;
|
|
436
|
+
this.player.astralPerceiving = false;
|
|
437
|
+
this.player.sneaking = false;
|
|
438
|
+
this.player.inExchange = false;
|
|
439
|
+
this.player.combatOpponent = undefined;
|
|
440
|
+
this.player.activeCallPartner = undefined;
|
|
441
|
+
this.autoFight.cancelPending();
|
|
442
|
+
this.scene = hub;
|
|
443
|
+
this.scene.addRoom(this._homeRoom);
|
|
444
|
+
this.connectHomeRoom(hub.determineStartRoom());
|
|
445
|
+
this.player.currentLocation = this._homeRoom;
|
|
446
|
+
this.player.cameFrom = undefined;
|
|
447
|
+
this.updateEquipment(this.player.equipment);
|
|
448
|
+
this.updateInventory(this.player.inventory, capitalCase(this.scene.getCurrencyType()));
|
|
449
|
+
this.updateExits(this.player.currentLocation);
|
|
450
|
+
this.updateStatus();
|
|
451
|
+
Logger.getInstance().write(`Returned to hub "${this._hubName}".`);
|
|
452
|
+
return `\n=== ${this._hubName.toUpperCase()} ===\nThe run's wrapped and the street swallows you back up. You're home -- your hideout, one door off ${hub.determineStartRoom().name}. Call ${FIXER_NAME} when you're hungry for the next job.`;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* The actual drafting work, split out from continueToNextScene() purely
|
|
456
|
+
* so that method can wrap it in the try/finally that clears
|
|
457
|
+
* _draftInProgress -- every return path below (including the early
|
|
458
|
+
* "couldn't draft" one) needs that cleanup to run.
|
|
459
|
+
*/
|
|
460
|
+
async draftNextScene() {
|
|
461
|
+
const nextTier = this._tier + 1;
|
|
462
|
+
// Carry the current fixer's identity forward -- without this, each
|
|
463
|
+
// "continue" generates a scene with zero knowledge of the last one, so
|
|
464
|
+
// the AI just invents a brand new job-giver every time. Looked up by
|
|
465
|
+
// identity (is there an actor actually named FIXER_NAME?), not via the
|
|
466
|
+
// win condition's toNpc -- that's often a one-off contact the fixer
|
|
467
|
+
// pointed the runner toward, not the fixer himself (see the "broker,
|
|
468
|
+
// not always the destination" convention).
|
|
469
|
+
const fixerStillInScene = this.scene.allActors.some(a => a.name === FIXER_NAME);
|
|
470
|
+
const previousFixerName = fixerStillInScene ? FIXER_NAME : undefined;
|
|
471
|
+
// Append, don't replace -- this used to be setContent(), which wiped
|
|
472
|
+
// the whole log box (including the JOB COMPLETE banner and everything
|
|
473
|
+
// before it) for the entire generation wait, which can run 20-40+
|
|
474
|
+
// seconds for a tiered scene with AI flavor enabled. Framed as the
|
|
475
|
+
// fixer's own line when he's actually in this scene, rather than a
|
|
476
|
+
// bare system message -- he's the one drumming up the next gig.
|
|
477
|
+
// No system "drafting..." line -- the fixer's own one-ring commission
|
|
478
|
+
// (call.ts) already told the player he'll call back; anything more
|
|
479
|
+
// reads as machinery (real playtest note). The wait resolves as HIS
|
|
480
|
+
// callback either way, success or failure.
|
|
481
|
+
//
|
|
482
|
+
// The player object rides along so the job is drafted FOR this runner
|
|
483
|
+
// -- their archetype, stats, and toolkit (see buildPlayerDossier).
|
|
484
|
+
const nextSeed = await SceneSeedGenerator.generate(this._logFilePath, nextTier, previousFixerName, this.player);
|
|
485
|
+
if (!nextSeed) {
|
|
486
|
+
return `Incoming call -- ${FIXER_NAME}: "Job fell through, chummer. Client got cold feet before the ink dried. Give me another call in a bit -- I'll shake something loose."`;
|
|
487
|
+
}
|
|
488
|
+
// HUB sessions: the draft lands as a MESSAGE, never a teleport (real
|
|
489
|
+
// playtest note: being yanked into the run the instant generation
|
|
490
|
+
// finished was jarring). The player invokes it on their own time:
|
|
491
|
+
// "call Mr. Johnson" delivers the offer (haggle window included --
|
|
492
|
+
// see call.ts), and "travel" actually goes (launchPendingRun).
|
|
493
|
+
if (this.hasHub) {
|
|
494
|
+
this._pendingRunSeed = nextSeed;
|
|
495
|
+
this._pendingRunTier = nextTier;
|
|
496
|
+
this._pendingRide = Game.RIDE_POOL[Math.floor(Math.random() * Game.RIDE_POOL.length)];
|
|
497
|
+
return `Your link pings -- 1 new message: ${FIXER_NAME}. ("call ${FIXER_NAME}" to hear the job.)`;
|
|
498
|
+
}
|
|
499
|
+
// Classic sessions transition immediately, hideout-first, as always.
|
|
500
|
+
return await this.activateRunScene(nextSeed, nextTier, { startInHideout: true });
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Installs a drafted run as the live scene. Shared by the classic
|
|
504
|
+
* immediate path (hideout start) and the hub's "travel" (start-room
|
|
505
|
+
* start -- you rode there, you arrive AT the job).
|
|
506
|
+
*/
|
|
507
|
+
async activateRunScene(seed, tier, opts) {
|
|
508
|
+
this._tier = tier;
|
|
509
|
+
this.sceneSeed = seed;
|
|
510
|
+
// A call partner from the old scene wouldn't exist in the new one --
|
|
511
|
+
// hang up before moving on, rather than leave a dangling reference.
|
|
512
|
+
if (this.player.activeCallPartner) {
|
|
513
|
+
this.player.activeCallPartner.activeCallPartner = undefined;
|
|
514
|
+
this.player.activeCallPartner = undefined;
|
|
515
|
+
}
|
|
516
|
+
// false, false: no starter kit (the player carries their own gear
|
|
517
|
+
// forward), and no blocking AI-redressing pass either -- the chunked
|
|
518
|
+
// rooms pass already wrote this scene's prose.
|
|
519
|
+
this.scene = await SceneSynthesizer.synthesizeFromJson(seed, this.player, false, false);
|
|
520
|
+
this.scene.initialize(this);
|
|
521
|
+
this.player.currentLocation = this.determineStartRoom();
|
|
522
|
+
// New job, fresh luck (see commands/edge.ts).
|
|
523
|
+
this.player.refreshEdge();
|
|
524
|
+
// The home base is owned by Game, not any one Scene -- re-inject it and
|
|
525
|
+
// rewire its one exit to point at this job's start room.
|
|
526
|
+
this.scene.addRoom(this._homeRoom);
|
|
527
|
+
this.connectHomeRoom(this.player.currentLocation);
|
|
528
|
+
if (opts.startInHideout) {
|
|
529
|
+
this.player.currentLocation = this._homeRoom;
|
|
530
|
+
}
|
|
531
|
+
this.updateEquipment(this.player.equipment);
|
|
532
|
+
this.updateInventory(this.player.inventory, capitalCase(this.scene.getCurrencyType()));
|
|
533
|
+
this.updateExits(this.player.currentLocation);
|
|
534
|
+
this.updateStatus();
|
|
535
|
+
// Fresh streets, no trail yet (see Player.cameFrom / the HUD crumb).
|
|
536
|
+
this.player.cameFrom = undefined;
|
|
537
|
+
this.warmNpcAppearancesInBackground();
|
|
538
|
+
return `\n=== JOB #${this._tier}: ${seed.name} ===\n${this.scene.story}`;
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* "travel": takes the arranged transport to the pending job (see
|
|
542
|
+
* commands/travel.ts). The player arrives AT the run's start room --
|
|
543
|
+
* "You travel to <location>" -- not teleported onto their own cot.
|
|
544
|
+
* A haggle struck during the offer call already mutated the pending
|
|
545
|
+
* seed's payout, and stays spent across the transition (refreshEdge
|
|
546
|
+
* would otherwise re-arm it).
|
|
547
|
+
*/
|
|
548
|
+
async launchPendingRun() {
|
|
549
|
+
const seed = this._pendingRunSeed;
|
|
550
|
+
const tier = this._pendingRunTier;
|
|
551
|
+
if (!seed || tier === undefined) {
|
|
552
|
+
return `There's no job waiting. "call ${FIXER_NAME}" to drum one up.`;
|
|
553
|
+
}
|
|
554
|
+
const haggledAtOffer = this.player.haggledThisJob;
|
|
555
|
+
try {
|
|
556
|
+
const banner = await this.activateRunScene(seed, tier, { startInHideout: false });
|
|
557
|
+
this._pendingRunSeed = undefined;
|
|
558
|
+
this._pendingRunTier = undefined;
|
|
559
|
+
this._pendingRide = undefined;
|
|
560
|
+
this.player.haggledThisJob = haggledAtOffer;
|
|
561
|
+
return `You travel to ${this.player.currentLocation.name}.\n${banner}`;
|
|
562
|
+
}
|
|
563
|
+
catch (err) {
|
|
564
|
+
this._pendingRunSeed = undefined;
|
|
565
|
+
this._pendingRunTier = undefined;
|
|
566
|
+
this._pendingRide = undefined;
|
|
567
|
+
Logger.getInstance().error(`launchPendingRun failed: ${err}`);
|
|
568
|
+
return `The ride never shows and the job address goes dead. ${FIXER_NAME} will not enjoy hearing this -- call him for another.`;
|
|
569
|
+
}
|
|
106
570
|
}
|
|
107
571
|
async close() {
|
|
108
572
|
this._inputBox.removeAllListeners(); // removes all event listeners
|
|
@@ -113,42 +577,13 @@ export default class Game {
|
|
|
113
577
|
process.exit(2);
|
|
114
578
|
});
|
|
115
579
|
}
|
|
116
|
-
initMap(screen) {
|
|
117
|
-
// Map
|
|
118
|
-
const exitsBox = blessed.box({
|
|
119
|
-
top: '70%',
|
|
120
|
-
left: '75%',
|
|
121
|
-
width: '25%',
|
|
122
|
-
height: '20%',
|
|
123
|
-
border: {
|
|
124
|
-
type: 'line'
|
|
125
|
-
},
|
|
126
|
-
label: ' Map ',
|
|
127
|
-
content: 'Available exits will be shown here...',
|
|
128
|
-
tags: true,
|
|
129
|
-
scrollable: true,
|
|
130
|
-
scrollbar: {
|
|
131
|
-
ch: ' ',
|
|
132
|
-
track: {
|
|
133
|
-
bg: 'yellow'
|
|
134
|
-
},
|
|
135
|
-
style: {
|
|
136
|
-
inverse: true
|
|
137
|
-
}
|
|
138
|
-
},
|
|
139
|
-
keys: true,
|
|
140
|
-
mouse: true,
|
|
141
|
-
});
|
|
142
|
-
screen.append(exitsBox);
|
|
143
|
-
this._exitsBox = exitsBox;
|
|
144
|
-
}
|
|
145
580
|
initInputBox(screen) {
|
|
146
581
|
// Create an input box for user commands
|
|
147
582
|
const inputBox = blessed.textbox({
|
|
148
583
|
bottom: 0,
|
|
149
584
|
left: 0,
|
|
150
585
|
width: '100%',
|
|
151
|
-
height:
|
|
586
|
+
height: 3,
|
|
152
587
|
tags: true,
|
|
153
588
|
border: { type: 'line' },
|
|
154
589
|
style: { fg: 'white', border: { fg: 'cyan' } },
|
|
@@ -161,8 +596,21 @@ export default class Game {
|
|
|
161
596
|
screen.append(inputBox);
|
|
162
597
|
// Key bindings
|
|
163
598
|
screen.key(['escape', 'C-c'], () => process.exit(0));
|
|
164
|
-
// Focus on the input box by default
|
|
165
|
-
|
|
599
|
+
// Focus on the input box by default -- but ONLY if it isn't already
|
|
600
|
+
// focused. screen.append() auto-focuses the first keyable element
|
|
601
|
+
// (Node.insert), and since the sidebar boxes were removed this textbox
|
|
602
|
+
// IS the first one, so it arrives here already focused with its input
|
|
603
|
+
// reader mid-setup. Blessed's _focus() has no same-element guard:
|
|
604
|
+
// focusing an already-focused textbox fires a self-blur that cancels
|
|
605
|
+
// that reader BEFORE its keypress listener finishes attaching (the
|
|
606
|
+
// attach rides a delayed tick), so the cancel removes nothing, a
|
|
607
|
+
// second reader starts, both listeners land -- and every keystroke
|
|
608
|
+
// doubles, from startup ("exit" arrived as "eexxiitt" in a real
|
|
609
|
+
// session). Previously the Map box was appended first and soaked up
|
|
610
|
+
// the auto-focus, which is the only reason this never fired before.
|
|
611
|
+
if (screen.focused !== inputBox) {
|
|
612
|
+
inputBox.focus();
|
|
613
|
+
}
|
|
166
614
|
// Render the screen
|
|
167
615
|
screen.render();
|
|
168
616
|
}
|
|
@@ -172,8 +620,9 @@ export default class Game {
|
|
|
172
620
|
id: 'logBox',
|
|
173
621
|
top: '0%',
|
|
174
622
|
left: '0%',
|
|
175
|
-
width: '
|
|
176
|
-
|
|
623
|
+
width: '100%',
|
|
624
|
+
// Fixed-row footer: 8 rows of HUD (status box) + 3 rows of input.
|
|
625
|
+
height: '100%-11',
|
|
177
626
|
border: {
|
|
178
627
|
type: 'line'
|
|
179
628
|
},
|
|
@@ -197,139 +646,156 @@ export default class Game {
|
|
|
197
646
|
logBox.removeAllListeners('mousemove');
|
|
198
647
|
screen.append(logBox);
|
|
199
648
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
649
|
+
/**
|
|
650
|
+
* The HUD: a fixed strip between the log and the input box for the
|
|
651
|
+
* important real-time state -- condition tracks, armed/link status,
|
|
652
|
+
* sustained spells, active call. Anything that changes moment to moment
|
|
653
|
+
* and shouldn't need a command to check lives here; everything slower
|
|
654
|
+
* (full inventory, slot-by-slot equipment, the map) is an on-demand
|
|
655
|
+
* command instead. Future contexts (astral projection, VR hot-sim) get
|
|
656
|
+
* their own line here the same way the call line did: small explicit
|
|
657
|
+
* pieces, not a status framework built ahead of need.
|
|
658
|
+
*/
|
|
659
|
+
initStatusBox(screen) {
|
|
660
|
+
const statusBox = blessed.box({
|
|
661
|
+
id: 'statusBox',
|
|
662
|
+
bottom: 3, // sits directly on the input box
|
|
663
|
+
left: '0%',
|
|
664
|
+
width: '100%',
|
|
665
|
+
height: 8, // 6 content rows inside the border: location, tracks,
|
|
666
|
+
// readiness, and up to two transient lines (sustained, call)
|
|
206
667
|
border: {
|
|
207
668
|
type: 'line'
|
|
208
669
|
},
|
|
209
|
-
label: ' Equipment ',
|
|
210
|
-
content: 'Your equipped items will be listed here...',
|
|
211
670
|
tags: true,
|
|
212
|
-
|
|
213
|
-
scrollbar: {
|
|
214
|
-
ch: ' ',
|
|
215
|
-
track: {
|
|
216
|
-
bg: 'yellow'
|
|
217
|
-
},
|
|
218
|
-
style: {
|
|
219
|
-
inverse: true
|
|
220
|
-
}
|
|
221
|
-
},
|
|
222
|
-
keys: true,
|
|
223
|
-
mouse: true,
|
|
671
|
+
content: '',
|
|
224
672
|
});
|
|
225
|
-
this.
|
|
226
|
-
|
|
227
|
-
screen.append(equipmentBox);
|
|
673
|
+
this._statusBox = statusBox;
|
|
674
|
+
screen.append(statusBox);
|
|
228
675
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
updateEquipment(playerEquipment) {
|
|
259
|
-
const sections = [];
|
|
260
|
-
const renderSlotGroup = (label, group) => {
|
|
261
|
-
if (!group) {
|
|
262
|
-
sections.push(`{bold}${label}:{/bold} None`);
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
const equipped = Object.entries(group)
|
|
266
|
-
.filter(([_, item]) => item !== null)
|
|
267
|
-
.map(([slot, item]) => ` ${slot}: ${item.name}`);
|
|
268
|
-
if (equipped.length > 0) {
|
|
269
|
-
sections.push(`{bold}${label}:{/bold}`);
|
|
270
|
-
sections.push(...equipped);
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
sections.push(`{bold}${label}:{/bold} None`);
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
|
-
renderSlotGroup("Head", playerEquipment.head);
|
|
277
|
-
renderSlotGroup("Torso", playerEquipment.torso);
|
|
278
|
-
renderSlotGroup("Legs", playerEquipment.legs);
|
|
279
|
-
renderSlotGroup("Feet", playerEquipment.feet);
|
|
280
|
-
renderSlotGroup("Arms", playerEquipment.arms);
|
|
281
|
-
renderSlotGroup("Gloves", playerEquipment.gloves);
|
|
282
|
-
renderSlotGroup("Left Hand", playerEquipment.leftHand);
|
|
283
|
-
renderSlotGroup("Right Hand", playerEquipment.rightHand);
|
|
284
|
-
this._equipmentBox.setContent(sections.join('\n'));
|
|
285
|
-
this._screen.render();
|
|
286
|
-
}
|
|
287
|
-
;
|
|
288
|
-
updateExits(room) {
|
|
289
|
-
let description = `Currently in: ${room.name}\n\n`;
|
|
290
|
-
description += `----- Exits ------\n`;
|
|
291
|
-
if (room.exits.size > 0) {
|
|
292
|
-
room.exits.forEach((exit, key) => {
|
|
293
|
-
if (exit instanceof Door) {
|
|
294
|
-
const nextRoom = exit.getOtherSideToName(room);
|
|
295
|
-
description += `${capitalCase(key)}: ${nextRoom}`;
|
|
296
|
-
if (exit.checkIfLocked()) {
|
|
297
|
-
description += " (Locked)";
|
|
298
|
-
}
|
|
299
|
-
else if (exit.checkIfLockable()) {
|
|
300
|
-
description += ' (Unlocked)';
|
|
301
|
-
}
|
|
302
|
-
description += '\n';
|
|
303
|
-
}
|
|
304
|
-
else {
|
|
305
|
-
description += `${capitalCase(key)}\n`; // Example: "NORTH"
|
|
306
|
-
}
|
|
307
|
-
});
|
|
676
|
+
/**
|
|
677
|
+
* Refreshes the status box from current player state. Called after any
|
|
678
|
+
* command that can change what belongs there (currently "call"/"end-call")
|
|
679
|
+
* and once on load/job transition.
|
|
680
|
+
*/
|
|
681
|
+
updateStatus() {
|
|
682
|
+
// updateInventory/updateEquipment route here now, and those hooks can
|
|
683
|
+
// fire from synthesis paths before the blessed UI exists.
|
|
684
|
+
if (!this._statusBox)
|
|
685
|
+
return;
|
|
686
|
+
const player = this.player;
|
|
687
|
+
const lines = [];
|
|
688
|
+
// Line 1: where you are, and the fight autopilot mode. The stance
|
|
689
|
+
// lives HERE -- the shortest HUD line -- because appended to the long
|
|
690
|
+
// readiness line it wrapped out of the box on narrower terminals and
|
|
691
|
+
// effectively only ever showed in the Tab announcement. Always shown,
|
|
692
|
+
// even MANUAL: the HUD is how players discover Tab cycles it at all
|
|
693
|
+
// (see utilities/auto-fight.ts). MANUAL is white, not gray -- blessed's
|
|
694
|
+
// gray is ANSI "bright black", which on a stock dark terminal palette
|
|
695
|
+
// is illegible enough that the stance looked absent entirely.
|
|
696
|
+
const stanceColor = player.autoStance === 'aggressive' ? 'red' : player.autoStance === 'guarded' ? 'yellow' : 'white';
|
|
697
|
+
const stance = `{${stanceColor}-fg}⚔ ${player.autoStance.toUpperCase()}{/${stanceColor}-fg}`;
|
|
698
|
+
// The breadcrumb, reading chronologically into the current location:
|
|
699
|
+
// "Neon Strip (west) > Night Market" -- origin first, the direction
|
|
700
|
+
// BACK to it in parens. An 18-room hub made "which way did I come in"
|
|
701
|
+
// a real question.
|
|
702
|
+
const crumb = player.cameFrom ? `${player.cameFrom.room} (${player.cameFrom.back}) > ` : '';
|
|
703
|
+
if (player.currentLocation) {
|
|
704
|
+
lines.push(`${crumb}{bold}📍 ${player.currentLocation.name}{/bold} | ${stance}`);
|
|
308
705
|
}
|
|
309
706
|
else {
|
|
310
|
-
|
|
707
|
+
lines.push(stance);
|
|
311
708
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
709
|
+
// Line 2: both condition monitors -- in a game where a fight can kill
|
|
710
|
+
// you and casting can knock you out, current damage on either track is
|
|
711
|
+
// never something a player should have to ask for. Colors shift as
|
|
712
|
+
// each track fills; the stacked wound modifier from both tracks shows
|
|
713
|
+
// once.
|
|
714
|
+
const trackColor = (taken, max) => taken === 0 ? 'green' : taken >= max - 3 ? 'red' : 'yellow';
|
|
715
|
+
const wound = player.woundModifier;
|
|
716
|
+
const woundNote = wound < 0 ? ` {red-fg}(${wound} dice){/red-fg}` : '';
|
|
717
|
+
const physColor = trackColor(player.damageTaken, player.maxConditionBoxes);
|
|
718
|
+
const stunColor = trackColor(player.stunTaken, player.maxStunBoxes);
|
|
719
|
+
lines.push(`{${physColor}-fg}Physical ${player.conditionSummary()}{/${physColor}-fg} ` +
|
|
720
|
+
`{${stunColor}-fg}Stun ${player.stunSummary()}{/${stunColor}-fg}${woundNote}`);
|
|
721
|
+
// Line 3: readiness -- armed state, armor, and whether the commlink is
|
|
722
|
+
// actually live (equipped), the recurring "why can't I call anyone"
|
|
723
|
+
// friction surfaced at a glance.
|
|
724
|
+
const weapon = player.getWeaponProfile();
|
|
725
|
+
const weaponItem = player.getCarriedWeapon();
|
|
726
|
+
const ammoNote = weaponItem?.isFirearm()
|
|
727
|
+
? (weaponItem.jammed
|
|
728
|
+
? ` {red-fg}[JAMMED -- reboot]{/red-fg}`
|
|
729
|
+
: weaponItem.ammo === 0 ? ` {red-fg}[DRY]{/red-fg}` : ` [${weaponItem.ammo} rds]`)
|
|
730
|
+
: '';
|
|
731
|
+
// Three armed states on the meat plane: drawn, holstered (weapon
|
|
732
|
+
// carried but riding leather -- "brandish" puts it in hand; combat
|
|
733
|
+
// auto-draws so this is posture, not readiness), or nothing at all.
|
|
734
|
+
// Off-plane the profile is attack programs / astral will and reads as
|
|
735
|
+
// Armed. White, not gray, for the same legibility reason as the stance.
|
|
736
|
+
const armed = player.plane === 'meat' && weaponItem && !player.weaponDrawn
|
|
737
|
+
? `{white-fg}Holstered: ${weaponItem.name}${ammoNote} -- "brandish"{/white-fg}`
|
|
738
|
+
: weapon.name === 'bare fists'
|
|
739
|
+
? `{gray-fg}Unarmed{/gray-fg}`
|
|
740
|
+
: `Armed: ${weapon.name} (DV ${weapon.damage}${weapon.poolBonus > 0 ? `, +${weapon.poolBonus} dice` : ''})${ammoNote}`;
|
|
741
|
+
const armorNote = `Armor +${player.getArmorValue()}`;
|
|
742
|
+
const hasLink = player.equipment.head?.commlink != null;
|
|
743
|
+
const link = !hasLink
|
|
744
|
+
? `{gray-fg}Link offline{/gray-fg}`
|
|
745
|
+
: player.runningSilent
|
|
746
|
+
? `{magenta-fg}Link SILENT{/magenta-fg}`
|
|
747
|
+
: player.arOffline
|
|
748
|
+
? `{red-fg}AR CUT -- reboot{/red-fg}`
|
|
749
|
+
: `{${CALL_COLOR}-fg}Link online{/${CALL_COLOR}-fg}`;
|
|
750
|
+
lines.push(`${armed} | ${armorNote} | ${link}`);
|
|
751
|
+
// Lines 4+: transient states, present only while active. Future
|
|
752
|
+
// contexts (astral projection, VR hot-sim) join here the same way.
|
|
753
|
+
if (player.sustainedArmor > 0) {
|
|
754
|
+
lines.push(`{magenta-fg}Sustained: armor +${player.sustainedArmor} soak (-2 dice to everything else){/magenta-fg}`);
|
|
755
|
+
}
|
|
756
|
+
if (player.astralPerceiving) {
|
|
757
|
+
lines.push(`{magenta-fg}✧ ASTRAL SENSES OPEN (-2 dice sustained) -- "assense" to close{/magenta-fg}`);
|
|
758
|
+
}
|
|
759
|
+
if (player.sneaking) {
|
|
760
|
+
lines.push(`{white-fg}🐾 SNEAKING -- entries are opposed vs their perception; loud acts break it{/white-fg}`);
|
|
761
|
+
}
|
|
762
|
+
if (player.activeCallPartner) {
|
|
763
|
+
lines.push(`{${CALL_COLOR}-fg}${CALL_ICON} On a call with ${player.activeCallPartner.name}{/${CALL_COLOR}-fg}`);
|
|
327
764
|
}
|
|
328
|
-
|
|
329
|
-
|
|
765
|
+
// Off-plane context: which reality you're in, your persona's Matrix
|
|
766
|
+
// track (cold-sim damage lands there), and where your empty body lies.
|
|
767
|
+
if (player.plane === 'matrix') {
|
|
768
|
+
// Matrix damage is hardware damage: show the DECK's track. A
|
|
769
|
+
// technomancer has no deck -- their stun track (already on line 2)
|
|
770
|
+
// is what the ice chews on.
|
|
771
|
+
const track = player.activeDeck
|
|
772
|
+
? ` Deck ${player.activeDeck.deckSummary()}${player.simMode === 'hot' ? ' +biofeedback' : ''}`
|
|
773
|
+
: ` (living persona -- damage is REAL stun)`;
|
|
774
|
+
lines.push(`{cyan-fg}▣ MATRIX ${(player.simMode ?? 'cold').toUpperCase()}-SIM${track} -- body at ${player.bodyRoom?.name ?? '???'}{/cyan-fg}`);
|
|
775
|
+
}
|
|
776
|
+
else if (player.plane === 'astral') {
|
|
777
|
+
lines.push(`{magenta-fg}✧ ASTRAL -- ${player.astralTicks} move${player.astralTicks === 1 ? '' : 's'} out -- body at ${player.bodyRoom?.name ?? '???'}{/magenta-fg}`);
|
|
778
|
+
}
|
|
779
|
+
this._statusBox.setContent(lines.join('\n'));
|
|
330
780
|
this._screen.render();
|
|
331
781
|
}
|
|
332
|
-
|
|
782
|
+
// The three methods below were the render paths for the old always-on
|
|
783
|
+
// sidebar boxes. Those boxes are gone (the log owns the whole screen; see
|
|
784
|
+
// commands/inv.ts, equipment.ts, map.ts for the on-demand replacements),
|
|
785
|
+
// but the methods stay as no-ops: dozens of call sites across commands,
|
|
786
|
+
// scene proxies, and ui.ts still ping them after inventory/equipment/exit
|
|
787
|
+
// changes, and keeping the hooks alive means a future HUD element can tap
|
|
788
|
+
// those same signals without re-plumbing. Each refreshes the HUD, since
|
|
789
|
+
// several HUD fields (armed, armor, link) derive from equipment state.
|
|
790
|
+
updateEquipment(_playerEquipment) {
|
|
791
|
+
this.updateStatus();
|
|
792
|
+
}
|
|
793
|
+
updateExits(_room) {
|
|
794
|
+
// Nothing to render -- the "map" command reads live room state.
|
|
795
|
+
}
|
|
796
|
+
updateInventory(_playerInventory, _currencyName = 'currency') {
|
|
797
|
+
this.updateStatus();
|
|
798
|
+
}
|
|
333
799
|
// Public method to get the instance of the Game class
|
|
334
800
|
static async getInstance(player, sceneSeed) {
|
|
335
801
|
if (!Game.instance) {
|
|
@@ -338,17 +804,26 @@ export default class Game {
|
|
|
338
804
|
return Game.instance;
|
|
339
805
|
}
|
|
340
806
|
async handleInput(input) {
|
|
341
|
-
|
|
807
|
+
// "jack-in"/"jack-out" read naturally but parse as unknown verbs (a
|
|
808
|
+
// real session typed "jack-in" and bounced) -- normalize the hyphen.
|
|
809
|
+
const normalized = input.replace(/^\s*jack-(in|out)\b/i, 'jack $1');
|
|
810
|
+
const parts = normalized.trim().split(' ');
|
|
342
811
|
const verb = parts[0].toLowerCase();
|
|
343
812
|
const args = parts.slice(1);
|
|
344
|
-
if (verb === 'quit') {
|
|
813
|
+
if (verb === 'quit' || verb === 'exit') {
|
|
345
814
|
return null;
|
|
346
815
|
}
|
|
347
816
|
if (verb === 'maka') {
|
|
348
817
|
return 'You are still in the game, type "quit" to exit';
|
|
349
818
|
}
|
|
819
|
+
if (verb === 'continue') {
|
|
820
|
+
// RETIRED verb, kept only as a redirect: jobs flow through the
|
|
821
|
+
// fixer ("call Mr. Johnson"), and a finished run ends by walking
|
|
822
|
+
// home to your hideout -- no system command in the loop.
|
|
823
|
+
return `That's not how the street works anymore, chummer: "call ${FIXER_NAME}" when you want work. When a run's wrapped, head home to your hideout -- the door knows the way.`;
|
|
824
|
+
}
|
|
350
825
|
if (verb === 'help') {
|
|
351
|
-
return this.getHelpMessage();
|
|
826
|
+
return this.getHelpMessage(args);
|
|
352
827
|
}
|
|
353
828
|
if (verb === 'clear') {
|
|
354
829
|
return '';
|
|
@@ -357,26 +832,111 @@ export default class Game {
|
|
|
357
832
|
actor: this.player,
|
|
358
833
|
rooms: this.scene.getRooms(),
|
|
359
834
|
scene: this.scene,
|
|
835
|
+
screen: this._screen,
|
|
836
|
+
game: this,
|
|
360
837
|
};
|
|
361
838
|
const command = CommandFactory.createCommand(verb, context);
|
|
362
839
|
if (command) {
|
|
363
840
|
const result = await command.execute(args);
|
|
364
841
|
return result;
|
|
365
842
|
}
|
|
843
|
+
// Typo tolerance: two straight sessions bounced on slips like "searh"
|
|
844
|
+
// and "maop". Reuse the same fuzzy matcher item/NPC names go through
|
|
845
|
+
// (ambiguous guesses deliberately miss). A unique match auto-runs with
|
|
846
|
+
// a note -- EXCEPT anything that ends the session, which only ever
|
|
847
|
+
// gets a suggestion: a typo must never quit the game.
|
|
848
|
+
const knownVerbs = [...Object.keys(CommandFactory.getAllRegisteredCommands()), 'help', 'clear', 'quit', 'exit'];
|
|
849
|
+
let guess = fuzzyPickName(verb, knownVerbs);
|
|
850
|
+
// The shared matcher refuses typo tolerance on words <= 3 chars (too
|
|
851
|
+
// risky for item/NPC names) -- but the verb list is small and
|
|
852
|
+
// distinct, so a unique edit-distance-1 hit is safe ("maop" -> "map").
|
|
853
|
+
if (!guess && verb.length >= 3) {
|
|
854
|
+
const close = knownVerbs.filter(v => levenshteinDistance(verb, v) <= 1);
|
|
855
|
+
if (close.length === 1)
|
|
856
|
+
guess = close[0];
|
|
857
|
+
}
|
|
858
|
+
if (guess && guess !== verb) {
|
|
859
|
+
if (guess === 'quit' || guess === 'exit') {
|
|
860
|
+
return `I don't understand "${verb}" -- did you mean "${guess}"?`;
|
|
861
|
+
}
|
|
862
|
+
const corrected = await this.handleInput([guess, ...args].join(' '));
|
|
863
|
+
return corrected === null ? null : `(read "${verb}" as "${guess}")\n${corrected ?? ''}`;
|
|
864
|
+
}
|
|
366
865
|
return `I don't understand the command "${verb}".`;
|
|
367
866
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
867
|
+
/**
|
|
868
|
+
* The help taxonomy: every entry is [primary, ...aliases], grouped the
|
|
869
|
+
* way a player thinks, not the order commands were registered. The
|
|
870
|
+
* overview shows only verbs per category (the flat all-descriptions
|
|
871
|
+
* dump had grown past 45 lines); "help <category>" and "help <command>"
|
|
872
|
+
* drill into the real descriptions. Verbs registered but missing from
|
|
873
|
+
* this table surface in an "other" bucket rather than vanishing -- a
|
|
874
|
+
* future command can't silently fall out of help.
|
|
875
|
+
*/
|
|
876
|
+
static HELP_CATEGORIES = [
|
|
877
|
+
{ title: 'moving', entries: [['look'], ['go'], ['map', 'exits'], ['search'], ['sneak']] },
|
|
878
|
+
{ title: 'gear', entries: [['inv', 'inventory'], ['equipment', 'eq'], ['take'], ['drop'], ['give'], ['store'], ['equip'], ['unequip'], ['brandish', 'draw'], ['holster', 'stow'], ['reload'], ['use'], ['read']] },
|
|
879
|
+
{ title: 'combat', entries: [['attack', 'kill'], ['subdue'], ['stance'], ['surrender'], ['edge'], ['rest']] },
|
|
880
|
+
{ title: 'magic', entries: [['spells'], ['cast'], ['project'], ['return'], ['assense']] },
|
|
881
|
+
{ title: 'matrix', entries: [['jack'], ['hack'], ['pan'], ['silent'], ['reboot'], ['drone']] },
|
|
882
|
+
{ title: 'social', entries: [['say'], ['talk-to'], ['call'], ['end-call', 'end', 'hangup'], ['haggle', 'negotiate'], ['persuade', 'convince']] },
|
|
883
|
+
{ title: 'barriers', entries: [['solve'], ['pick'], ['bluff'], ['unlock'], ['lock']] },
|
|
884
|
+
{ title: 'character', entries: [['sheet', 'stats'], ['advance', 'train']] },
|
|
885
|
+
{ title: 'session', entries: [['help'], ['clear'], ['quit', 'exit']] },
|
|
886
|
+
];
|
|
887
|
+
/** Descriptions for the verbs handled inline in handleInput() rather than registered. */
|
|
888
|
+
static BUILTIN_DESCRIPTIONS = {
|
|
889
|
+
help: 'This overview. "help <category>" for one group with full descriptions; "help <command>" for one command.',
|
|
890
|
+
clear: 'Clear the screen.',
|
|
891
|
+
quit: 'Exit the game.',
|
|
892
|
+
};
|
|
893
|
+
describeVerb(verb) {
|
|
894
|
+
const builtin = Game.BUILTIN_DESCRIPTIONS[verb];
|
|
895
|
+
if (builtin)
|
|
896
|
+
return builtin;
|
|
897
|
+
const CommandClass = CommandFactory.getAllRegisteredCommands()[verb];
|
|
898
|
+
return CommandClass?.getDescription() ?? 'No description available.';
|
|
899
|
+
}
|
|
900
|
+
getHelpMessage(args = []) {
|
|
901
|
+
const registered = Object.keys(CommandFactory.getAllRegisteredCommands());
|
|
902
|
+
const covered = new Set(Game.HELP_CATEGORIES.flatMap(c => c.entries.flat()));
|
|
903
|
+
const uncategorized = registered.filter(v => !covered.has(v));
|
|
904
|
+
const query = args.join(' ').trim().toLowerCase();
|
|
905
|
+
// "help <category>": that group, with full descriptions.
|
|
906
|
+
if (query) {
|
|
907
|
+
const categoryNames = Game.HELP_CATEGORIES.map(c => c.title);
|
|
908
|
+
const pickedCategory = fuzzyPickName(query, categoryNames);
|
|
909
|
+
if (pickedCategory) {
|
|
910
|
+
const category = Game.HELP_CATEGORIES.find(c => c.title === pickedCategory);
|
|
911
|
+
const lines = [`=== ${category.title.toUpperCase()} ===`];
|
|
912
|
+
for (const [primary, ...aliases] of category.entries) {
|
|
913
|
+
lines.push(`- ${primary}${aliases.length > 0 ? ` (${aliases.join(', ')})` : ''}: ${this.describeVerb(primary)}`);
|
|
914
|
+
}
|
|
915
|
+
return lines.join('\n');
|
|
375
916
|
}
|
|
917
|
+
// "help <command>": one description, aliases resolved to their primary.
|
|
918
|
+
const allVerbs = [...covered, ...uncategorized];
|
|
919
|
+
const pickedVerb = fuzzyPickName(query, allVerbs);
|
|
920
|
+
if (pickedVerb) {
|
|
921
|
+
const entry = Game.HELP_CATEGORIES.flatMap(c => c.entries).find(e => e.includes(pickedVerb));
|
|
922
|
+
const primary = entry?.[0] ?? pickedVerb;
|
|
923
|
+
const aliases = entry?.slice(1) ?? [];
|
|
924
|
+
return `${primary}${aliases.length > 0 ? ` (${aliases.join(', ')})` : ''}: ${this.describeVerb(primary)}`;
|
|
925
|
+
}
|
|
926
|
+
return `No command or category called "${query}". Try plain "help".`;
|
|
927
|
+
}
|
|
928
|
+
// The overview: compact category lines, primaries only.
|
|
929
|
+
const lines = ['=== COMMANDS ==='];
|
|
930
|
+
const pad = Math.max(...Game.HELP_CATEGORIES.map(c => c.title.length));
|
|
931
|
+
for (const category of Game.HELP_CATEGORIES) {
|
|
932
|
+
lines.push(`${category.title.padEnd(pad)} ${category.entries.map(e => e[0]).join(', ')}`);
|
|
933
|
+
}
|
|
934
|
+
if (uncategorized.length > 0) {
|
|
935
|
+
lines.push(`${'other'.padEnd(pad)} ${uncategorized.join(', ')}`);
|
|
376
936
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
return
|
|
937
|
+
lines.push('');
|
|
938
|
+
lines.push(`"help <category>" for a group with descriptions ("help combat"); "help <command>" for one ("help haggle").`);
|
|
939
|
+
return lines.join('\n');
|
|
380
940
|
}
|
|
381
941
|
}
|
|
382
942
|
//# sourceMappingURL=game.js.map
|