@maka/maka-cli 5.6.0 → 5.8.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 +2 -2
- package/bundle/typescript/src/commands/_index.d.ts +4 -0
- package/bundle/typescript/src/commands/_index.js +4 -0
- package/bundle/typescript/src/commands/_index.js.map +1 -1
- package/bundle/typescript/src/commands/debug.js +1 -7
- package/bundle/typescript/src/commands/debug.js.map +1 -1
- package/bundle/typescript/src/commands/deploy/ubuntu.sub.cmd.js +33 -32
- package/bundle/typescript/src/commands/deploy/ubuntu.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/commands/env/_index.d.ts +3 -0
- package/bundle/typescript/src/commands/env/_index.js +4 -0
- package/bundle/typescript/src/commands/env/_index.js.map +1 -0
- package/bundle/typescript/src/commands/env/env.command.d.ts +3 -0
- package/bundle/typescript/src/commands/env/env.command.js +22 -0
- package/bundle/typescript/src/commands/env/env.command.js.map +1 -0
- package/bundle/typescript/src/commands/env/get.sub.cmd.d.ts +1 -0
- package/bundle/typescript/src/commands/env/get.sub.cmd.js +46 -0
- package/bundle/typescript/src/commands/env/get.sub.cmd.js.map +1 -0
- package/bundle/typescript/src/commands/env/prompt-for-env.d.ts +10 -0
- package/bundle/typescript/src/commands/env/prompt-for-env.js +27 -0
- package/bundle/typescript/src/commands/env/prompt-for-env.js.map +1 -0
- package/bundle/typescript/src/commands/env/set.sub.cmd.d.ts +1 -0
- package/bundle/typescript/src/commands/env/set.sub.cmd.js +59 -0
- package/bundle/typescript/src/commands/env/set.sub.cmd.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +81 -2
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/augmentations.d.ts +31 -0
- package/bundle/typescript/src/commands/game/sideQuest/augmentations.js +125 -0
- package/bundle/typescript/src/commands/game/sideQuest/augmentations.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/advance.d.ts +17 -7
- package/bundle/typescript/src/commands/game/sideQuest/commands/advance.js +113 -15
- package/bundle/typescript/src/commands/game/sideQuest/commands/advance.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/ask.d.ts +37 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js +197 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js +3 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +21 -11
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.d.ts +3 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js +13 -5
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.js +7 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/buy.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/buy.js +97 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/buy.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.d.ts +21 -10
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js +41 -27
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.d.ts +6 -7
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +54 -20
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.d.ts +35 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +306 -55
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/companions.d.ts +33 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/companions.js +137 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/companions.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.d.ts +13 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.js +37 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drink.d.ts +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drink.js +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drink.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/drop.js +7 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drop.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/eat.d.ts +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/eat.js +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/eat.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js +3 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/give.js +101 -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 +6 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +67 -22
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +15 -7
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js +58 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/initiate.d.ts +26 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/initiate.js +125 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/initiate.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/install.d.ts +21 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/install.js +125 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/install.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/jump.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/jump.js +83 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/jump.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/lifestyle.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/lifestyle.js +76 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/lifestyle.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/lock.js +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/lock.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +7 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/order.d.ts +19 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/order.js +151 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/order.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pay.d.ts +13 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pay.js +39 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pay.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.js +5 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js +6 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/quality-trade.d.ts +21 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/quality-trade.js +121 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/quality-trade.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +60 -12
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js +27 -10
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +15 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sell.d.ts +17 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/sell.js +125 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/sell.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +36 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sneak.js +9 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sneak.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js +27 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/summon.d.ts +19 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/summon.js +112 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/summon.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +8 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/treat.d.ts +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/treat.js +67 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/treat.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js +7 -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 +29 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.js +118 -11
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/work.d.ts +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/work.js +68 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/work.js.map +1 -0
- 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/scene-chunks.d.ts +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +132 -5
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.d.ts +6 -2
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js +10 -6
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +196 -0
- package/bundle/typescript/src/commands/game/sideQuest/game.js +828 -31
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/item.d.ts +28 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/item.js +111 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/item.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +33 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +232 -10
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +111 -5
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +432 -41
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.d.ts +6 -6
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js +38 -14
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/room.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.d.ts +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +48 -8
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/qualities.d.ts +44 -0
- package/bundle/typescript/src/commands/game/sideQuest/qualities.js +155 -0
- package/bundle/typescript/src/commands/game/sideQuest/qualities.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +274 -5
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/item-seed.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/scene-seed.d.ts +17 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.d.ts +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/ui.js +82 -8
- package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.d.ts +13 -6
- package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js +28 -15
- package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.d.ts +15 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +207 -34
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/commerce.d.ts +56 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/commerce.js +144 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/commerce.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.d.ts +7 -4
- package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js +8 -5
- package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/log-style.d.ts +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/log-style.js +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/log-style.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/logger.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/logger.js +49 -4
- package/bundle/typescript/src/commands/game/sideQuest/utilities/logger.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/outdoors.d.ts +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/outdoors.js +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/outdoors.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.d.ts +21 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +107 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/terminal-reset.d.ts +17 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/terminal-reset.js +23 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/terminal-reset.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +51 -15
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/commands/remote/reconfig.sub.cmd.js +27 -1
- package/bundle/typescript/src/commands/remote/reconfig.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/commands/run/run.command.js +1 -7
- package/bundle/typescript/src/commands/run/run.command.js.map +1 -1
- package/bundle/typescript/src/commands/ssl.js +10 -15
- package/bundle/typescript/src/commands/ssl.js.map +1 -1
- package/bundle/typescript/src/commands/testCmd/mocha.sub.cmd.js +1 -6
- package/bundle/typescript/src/commands/testCmd/mocha.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/generators/config.js +11 -10
- package/bundle/typescript/src/generators/config.js.map +1 -1
- package/bundle/typescript/src/templates/meteor/config/pm2.config.js +3 -3
- package/bundle/typescript/src/templates/meteor/project/gitignore +2 -1
- package/bundle/typescript/src/templates/react/kit/mui/auth/password-reset/basic-password-reset-page.js.tsx +3 -4
- package/bundle/typescript/src/tool.d.ts +2 -0
- package/bundle/typescript/src/tool.js +3 -0
- package/bundle/typescript/src/tool.js.map +1 -1
- package/bundle/typescript/src/tools/aws/aws.class.js +6 -4
- package/bundle/typescript/src/tools/aws/aws.class.js.map +1 -1
- package/bundle/typescript/src/tools/dotenvx/dotenvx.class.d.ts +24 -0
- package/bundle/typescript/src/tools/dotenvx/dotenvx.class.js +66 -0
- package/bundle/typescript/src/tools/dotenvx/dotenvx.class.js.map +1 -0
- package/bundle/typescript/src/tools/ssh/ssh.class.js +1 -1
- package/bundle/typescript/src/tools/ssh/ssh.class.js.map +1 -1
- package/bundle/typescript/src/tools/ssl/ssl.class.d.ts +8 -0
- package/bundle/typescript/src/tools/ssl/ssl.class.js +53 -0
- package/bundle/typescript/src/tools/ssl/ssl.class.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import blessed from 'blessed';
|
|
2
2
|
import { capitalCase } from 'change-case';
|
|
3
|
+
import { Category } from './types/shared/item-enum.js';
|
|
4
|
+
import { Item } from './models/item.js';
|
|
3
5
|
import { Room } from './models/_index.js';
|
|
6
|
+
import { isFence, LIFESTYLE_TIERS } from './utilities/commerce.js';
|
|
7
|
+
import { rollPool, formatRoll } from './utilities/dice.js';
|
|
4
8
|
import { Direction } from './types/shared/direction-enum.js';
|
|
5
9
|
import { Logger } from './utilities/logger.js';
|
|
6
10
|
import { LookCommand } from './commands/look.js';
|
|
@@ -47,8 +51,26 @@ import { PanCommand } from './commands/pan.js';
|
|
|
47
51
|
import { RebootCommand } from './commands/reboot.js';
|
|
48
52
|
import { BrandishCommand, HolsterCommand } from './commands/brandish.js';
|
|
49
53
|
import { AdvanceCommand } from './commands/advance.js';
|
|
54
|
+
import { QualityTradeCommand } from './commands/quality-trade.js';
|
|
55
|
+
import { AskCommand } from './commands/ask.js';
|
|
56
|
+
import { ContactsCommand } from './commands/contacts.js';
|
|
57
|
+
import { JumpCommand } from './commands/jump.js';
|
|
58
|
+
import { InitiateCommand } from './commands/initiate.js';
|
|
59
|
+
import { SummonCommand } from './commands/summon.js';
|
|
60
|
+
import { OrderCommand } from './commands/order.js';
|
|
61
|
+
import { DeployCommand, AgentCommand, RecallCommand } from './commands/companions.js';
|
|
62
|
+
import { InstallCommand } from './commands/install.js';
|
|
63
|
+
import { BuyCommand } from './commands/buy.js';
|
|
64
|
+
import { SellCommand } from './commands/sell.js';
|
|
65
|
+
import { PayCommand } from './commands/pay.js';
|
|
66
|
+
import { LifestyleCommand } from './commands/lifestyle.js';
|
|
67
|
+
import { TreatCommand } from './commands/treat.js';
|
|
68
|
+
import { WorkCommand } from './commands/work.js';
|
|
69
|
+
import { EatCommand } from './commands/eat.js';
|
|
70
|
+
import { DrinkCommand } from './commands/drink.js';
|
|
50
71
|
import { equipIntoEmptySlots } from './archetypes.js';
|
|
51
72
|
import { NPC } from './models/npc.js';
|
|
73
|
+
import { CommandRegistry } from './commands/command-registry.js';
|
|
52
74
|
import { HaggleCommand } from './commands/haggle.js';
|
|
53
75
|
import { SneakCommand } from './commands/sneak.js';
|
|
54
76
|
import { PersuadeCommand } from './commands/persuade.js';
|
|
@@ -58,6 +80,8 @@ import { CommandFactory } from './factories/command-factory.js';
|
|
|
58
80
|
import { SceneSynthesizer } from './factories/scene-factory.js';
|
|
59
81
|
import { SceneSeedGenerator, FIXER_NAME } from './factories/scene-seed-generator.js';
|
|
60
82
|
import { CALL_ICON, CALL_COLOR } from './utilities/comm-style.js';
|
|
83
|
+
import { AI } from '../../../tools/ai/ai.class.js';
|
|
84
|
+
import { BULLET } from './utilities/log-style.js';
|
|
61
85
|
export default class Game {
|
|
62
86
|
initialized;
|
|
63
87
|
static instance;
|
|
@@ -73,6 +97,7 @@ export default class Game {
|
|
|
73
97
|
// give the log the whole screen.
|
|
74
98
|
_screen;
|
|
75
99
|
_logBox;
|
|
100
|
+
_metaBox;
|
|
76
101
|
_statusBox;
|
|
77
102
|
_inputBox;
|
|
78
103
|
_useAi = false;
|
|
@@ -99,6 +124,476 @@ export default class Game {
|
|
|
99
124
|
get hasHub() {
|
|
100
125
|
return this._hubScene !== undefined;
|
|
101
126
|
}
|
|
127
|
+
/** The active job's name (seed name) -- notoriety incidents cite it. */
|
|
128
|
+
get currentJobName() {
|
|
129
|
+
return this.sceneSeed?.name;
|
|
130
|
+
}
|
|
131
|
+
// ==================== CONTACTS (SR5e p.386-389) ====================
|
|
132
|
+
// Earned, never bought: a hub NPC becomes a contact through a real
|
|
133
|
+
// transaction (a sale, a bowl served, stitches, a fenced score), and
|
|
134
|
+
// the relationship deepens through gigs (to Loyalty 6) or patronage
|
|
135
|
+
// (money alone caps at Loyalty 3 -- cred buys familiarity, not
|
|
136
|
+
// friendship). CONNECTION is the contact's reach (role heuristic,
|
|
137
|
+
// same duck-typing school as commerce.isFence); LOYALTY is yours to
|
|
138
|
+
// grow. "ask" (commands/ask.ts) is what the roster is FOR: legwork
|
|
139
|
+
// against the real pending-run seed. One pull per contact per
|
|
140
|
+
// homecoming (asked resets in returnToHub).
|
|
141
|
+
contacts = new Map();
|
|
142
|
+
/** How much juice the name carries, read off the role. */
|
|
143
|
+
static connectionFor(npc) {
|
|
144
|
+
const hay = `${npc.description ?? ''} ${npc.name}`.toLowerCase();
|
|
145
|
+
if (npc.name === FIXER_NAME || /fixer|johnson/.test(hay))
|
|
146
|
+
return 5;
|
|
147
|
+
if (/fence|pawn|launder|smuggl|no questions/.test(hay))
|
|
148
|
+
return 4;
|
|
149
|
+
if (/talismonger|street doc|clinic|surgeon|decker|information/.test(hay))
|
|
150
|
+
return 3;
|
|
151
|
+
if (/bartender|barkeep|vendor|noodle|shop|stall|merchant|dealer|market/.test(hay))
|
|
152
|
+
return 2;
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* HOME TURF: the hub is where the runner LIVES -- everyone in the
|
|
157
|
+
* district already knows them (player request: "all NPCs there should
|
|
158
|
+
* already know them"). Called at hub capture: the whole meat-plane
|
|
159
|
+
* cast opens at Loyalty 1 ("knows your name"), so legwork works from
|
|
160
|
+
* day one and only run-scene faces are ever strangers. Depth is still
|
|
161
|
+
* earned -- trade, gigs, patronage -- exactly as before. Ice and
|
|
162
|
+
* spirits don't do noodles: non-meat actors stay off the roster.
|
|
163
|
+
*/
|
|
164
|
+
seedHomeTurfContacts() {
|
|
165
|
+
for (const actor of this._hubScene?.allActors ?? []) {
|
|
166
|
+
if (actor === this.player || actor.plane !== 'meat')
|
|
167
|
+
continue;
|
|
168
|
+
if (this.contacts.has(actor.name))
|
|
169
|
+
continue;
|
|
170
|
+
this.contacts.set(actor.name, { connection: Game.connectionFor(actor), loyalty: 1, spent: 0, gigs: 0, asked: false });
|
|
171
|
+
}
|
|
172
|
+
Logger.getInstance().write(`Home turf: ${this.contacts.size} contacts seeded from the hub cast.`);
|
|
173
|
+
}
|
|
174
|
+
// ==================== COMPANIONS: the party layer ====================
|
|
175
|
+
// Every companion is a per-scene NPC SHELL plus persistent STATE, and
|
|
176
|
+
// scene transitions are covered by narration, never instance rebinding
|
|
177
|
+
// (design decision with the player): spirits fade (RAW p.300: the
|
|
178
|
+
// summons doesn't outlast the ride), drones fold to the pack, agents
|
|
179
|
+
// into the deck -- damage lives on the bound Item and redeploys free.
|
|
180
|
+
// Tenants: summoned spirits (summon.ts), matrix agents (agent-cmd.ts),
|
|
181
|
+
// autonomous drones (deploy in drone-deploy.ts). Future tenants
|
|
182
|
+
// (enlisted allies, anthroform frames) ride the same rail with
|
|
183
|
+
// different words ("takes their own ride and meets you there").
|
|
184
|
+
// Roster invariant: entries are LIVE SHELLS only -- dismissal and
|
|
185
|
+
// death both remove the entry; persistent state (the frame, the deck)
|
|
186
|
+
// lives on Items in the player's possession, never here.
|
|
187
|
+
companions = [];
|
|
188
|
+
// Drones deployed at travel time re-shell on arrival ("the rotors spin
|
|
189
|
+
// back up at the curb") -- tracked as their items across the swap.
|
|
190
|
+
_pendingRedeploys = [];
|
|
191
|
+
/**
|
|
192
|
+
* The one correct runtime NPC spawn (mirrors scene-factory's seed
|
|
193
|
+
* path): construct with a scene-bound registry, add to the scene
|
|
194
|
+
* (names must be unique -- addActor throws), place via
|
|
195
|
+
* setCharacterLocation (the ONLY path that binds room observers), and
|
|
196
|
+
* hand it the scene for its AI context.
|
|
197
|
+
*/
|
|
198
|
+
spawnCompanionShell(opts) {
|
|
199
|
+
if (this.scene.getActor(opts.name)) {
|
|
200
|
+
Logger.getInstance().write(`spawnCompanionShell: name "${opts.name}" already in scene -- refusing.`);
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const npc = new NPC({
|
|
204
|
+
dialog: [],
|
|
205
|
+
description: opts.description,
|
|
206
|
+
playerConstructorConfig: { playerName: opts.name, startLocation: opts.room, combat: opts.combat },
|
|
207
|
+
}, new CommandRegistry(this.scene));
|
|
208
|
+
npc.plane = opts.plane;
|
|
209
|
+
npc.allyOf = this.player.name;
|
|
210
|
+
npc.companionKind = opts.kind;
|
|
211
|
+
npc.boundDevice = opts.boundDevice;
|
|
212
|
+
this.scene.addActor(npc);
|
|
213
|
+
this.scene.setCharacterLocation(npc, opts.room.name);
|
|
214
|
+
npc.scene = this.scene;
|
|
215
|
+
this.companions.push({ npc, kind: opts.kind, services: opts.services, force: opts.force, boundDevice: opts.boundDevice });
|
|
216
|
+
Logger.getInstance().write(`Companion spawned: ${opts.name} (${opts.kind}, force ${opts.force}) in ${opts.room.name}.`);
|
|
217
|
+
return npc;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* The autonomous-drone shell, shared by the deploy command and
|
|
221
|
+
* arrival re-shelling: the frame's dog-brain (Pilot = 2 + quality)
|
|
222
|
+
* stands in for every attribute it lacks (RAW p.269-271).
|
|
223
|
+
*/
|
|
224
|
+
// Cargo sling by frame class (player request: capacity enforced -- a
|
|
225
|
+
// palm-sized Fly-Spy is not a mule). Keyed by the frame Item's size.
|
|
226
|
+
static DRONE_CARGO_KG = {
|
|
227
|
+
tiny: 0.5, small: 2, medium: 8, large: 20, huge: 40,
|
|
228
|
+
};
|
|
229
|
+
deployDroneShell(item, room) {
|
|
230
|
+
const pilot = 2 + item.qualityBonus;
|
|
231
|
+
const armed = /armed|combat|gun|roto|assault|sentry|weapon/i.test(`${item.name} ${item.description ?? ''}`);
|
|
232
|
+
const shell = this.spawnCompanionShell({
|
|
233
|
+
name: `${item.name} (autopilot)`,
|
|
234
|
+
description: `${this.player.name}'s drone running its dog-brain autopilot: it follows them, watches, and ${armed ? 'shoots what threatens them' : 'has cameras where guns would go -- it never attacks'}.`,
|
|
235
|
+
kind: 'drone',
|
|
236
|
+
combat: { body: 2 + item.qualityBonus, agility: pilot, reaction: pilot, logic: pilot, intuition: pilot, willpower: pilot, strength: 1, charisma: 1, skills: { perception: pilot, ...(armed ? { firearms: pilot } : {}) } },
|
|
237
|
+
room, plane: 'meat', force: pilot, boundDevice: item,
|
|
238
|
+
});
|
|
239
|
+
// Take-command weight checks (Player.addInventory) enforce this cap
|
|
240
|
+
// for the shell exactly as they do for a runner.
|
|
241
|
+
if (shell)
|
|
242
|
+
shell.maxCarryingWeight = Game.DRONE_CARGO_KG[String(item.size)] ?? 2;
|
|
243
|
+
return shell;
|
|
244
|
+
}
|
|
245
|
+
/** Drops a companion's shell and its roster entry. */
|
|
246
|
+
dismissCompanion(entry, reason) {
|
|
247
|
+
if (this.scene.getActor(entry.npc.name) === entry.npc) {
|
|
248
|
+
// Cargo comes home with the shell (player request: fetch-and-carry
|
|
249
|
+
// runs -- a real session's drone took a credstick and had no way
|
|
250
|
+
// to hand it over). A deliberate dismissal passes whatever the
|
|
251
|
+
// shell carried to its master; without this, scene.removeActor
|
|
252
|
+
// dumps it on the floor wherever the shell happened to be.
|
|
253
|
+
const carried = entry.npc.inventory.getAllItems().map(i => i.name);
|
|
254
|
+
const delivered = carried.filter(n => entry.npc.inventory.transferItemTo(n, this.player.inventory));
|
|
255
|
+
if (delivered.length > 0) {
|
|
256
|
+
Logger.getInstance().logWithColor(`👥 From ${entry.npc.name}'s cargo, now yours: ${delivered.join(', ')}.`, 'green');
|
|
257
|
+
}
|
|
258
|
+
// Mark deliberate before removeActor fires onActorRemoved, so no
|
|
259
|
+
// wreckage is charged to a clean dismissal.
|
|
260
|
+
entry.npc.deathClaimed = true;
|
|
261
|
+
this.scene.removeActor(entry.npc.name);
|
|
262
|
+
}
|
|
263
|
+
const idx = this.companions.indexOf(entry);
|
|
264
|
+
if (idx >= 0)
|
|
265
|
+
this.companions.splice(idx, 1);
|
|
266
|
+
Logger.getInstance().write(`Companion dismissed: ${entry.npc.name} -- ${reason}`);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* An actor left the world (Scene.removeActor). For a companion shell
|
|
270
|
+
* that DIED (not a deliberate dismissal -- those set deathClaimed
|
|
271
|
+
* first and prune themselves), settle the wreckage: a drone's frame is
|
|
272
|
+
* wrecked where it fell, an agent derezzes (the deck already ate the
|
|
273
|
+
* damage), a spirit is banished outright.
|
|
274
|
+
*/
|
|
275
|
+
onActorRemoved(name) {
|
|
276
|
+
const entry = this.companions.find(c => c.npc.name === name);
|
|
277
|
+
if (!entry)
|
|
278
|
+
return;
|
|
279
|
+
const idx = this.companions.indexOf(entry);
|
|
280
|
+
if (idx >= 0)
|
|
281
|
+
this.companions.splice(idx, 1);
|
|
282
|
+
if (entry.kind === 'drone' && entry.boundDevice && !entry.npc.deathClaimed) {
|
|
283
|
+
entry.boundDevice.takeDroneDamage(entry.boundDevice.maxDroneBoxes);
|
|
284
|
+
Logger.getInstance().logWithColor(`👥 The ${entry.boundDevice.name} is WRECKED where it fell -- rest to repair the frame.`, 'yellow');
|
|
285
|
+
}
|
|
286
|
+
this.updateStatus();
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Spirits with no services left depart once no exchange holds them
|
|
290
|
+
* (called from CombatExchange.finishExchange): the bargain is spent.
|
|
291
|
+
*/
|
|
292
|
+
settleSpiritServices() {
|
|
293
|
+
for (const entry of [...this.companions]) {
|
|
294
|
+
if (entry.kind !== 'spirit' || (entry.services ?? 0) > 0)
|
|
295
|
+
continue;
|
|
296
|
+
if (entry.npc.inExchange)
|
|
297
|
+
continue;
|
|
298
|
+
// The paid fight is fought to the END. Between exchanges of the
|
|
299
|
+
// same fight inExchange is briefly false -- a real session's
|
|
300
|
+
// spirit spent its last service to join a fight and was dismissed
|
|
301
|
+
// 3ms later, before a single blow. Same freshness window as the
|
|
302
|
+
// per-fight billing in combat-exchange.
|
|
303
|
+
if (entry.npc.combatOpponent && Date.now() - entry.npc.lastExchangeAt < 60_000)
|
|
304
|
+
continue;
|
|
305
|
+
Logger.getInstance().logWithColor(`👥 ${entry.npc.name}'s bargain is spent -- it bows once and thins into nothing.`, 'green');
|
|
306
|
+
this.dismissCompanion(entry, 'services exhausted');
|
|
307
|
+
this.updateStatus();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// ==================== Pursuit heartbeat ====================
|
|
311
|
+
// Companions are stimulus-driven like every NPC: a directive burst ends
|
|
312
|
+
// at the reflect cap ("Max recursion reached") and a shell alone in a
|
|
313
|
+
// distant room then NEVER acts again -- a real session's drone was
|
|
314
|
+
// ordered to "find Mr. Krow", walked three rooms, and froze mid-hunt.
|
|
315
|
+
// The heartbeat re-spurs every shell with an open objective on a slow
|
|
316
|
+
// tick until it reports DONE/FAILED (which clears the objective -- see
|
|
317
|
+
// NPC.relayToMaster) or is dismissed.
|
|
318
|
+
_pursuitTimer;
|
|
319
|
+
_pursuitInFlight = new Set();
|
|
320
|
+
static PURSUIT_TICK_MS = 30_000;
|
|
321
|
+
startPursuitHeartbeat() {
|
|
322
|
+
if (this._pursuitTimer)
|
|
323
|
+
return;
|
|
324
|
+
this._pursuitTimer = setInterval(() => this.spurCompanionPursuits(), Game.PURSUIT_TICK_MS);
|
|
325
|
+
// Never hold the process open on our account.
|
|
326
|
+
this._pursuitTimer.unref?.();
|
|
327
|
+
}
|
|
328
|
+
stopPursuitHeartbeat() {
|
|
329
|
+
if (this._pursuitTimer)
|
|
330
|
+
clearInterval(this._pursuitTimer);
|
|
331
|
+
this._pursuitTimer = undefined;
|
|
332
|
+
}
|
|
333
|
+
spurCompanionPursuits() {
|
|
334
|
+
// The tick doubles as the spent-bargain sweep: finishExchange's own
|
|
335
|
+
// settle call now skips spirits whose fight is still warm (see
|
|
336
|
+
// settleSpiritServices), so without this a victorious spirit would
|
|
337
|
+
// linger at zero services forever.
|
|
338
|
+
this.settleSpiritServices();
|
|
339
|
+
if (!AI.isConfigured())
|
|
340
|
+
return;
|
|
341
|
+
for (const entry of this.companions) {
|
|
342
|
+
const npc = entry.npc;
|
|
343
|
+
if (!npc.objective)
|
|
344
|
+
continue;
|
|
345
|
+
if (npc.isIncapacitated() || npc.inExchange || npc.waitingForLull)
|
|
346
|
+
continue;
|
|
347
|
+
// A stale shell (already removed from the world) gets no spur.
|
|
348
|
+
if (this.scene.getActor(npc.name) !== npc)
|
|
349
|
+
continue;
|
|
350
|
+
// One spur in flight per shell -- a slow AI burst must not stack
|
|
351
|
+
// with the next tick.
|
|
352
|
+
if (this._pursuitInFlight.has(npc))
|
|
353
|
+
continue;
|
|
354
|
+
this._pursuitInFlight.add(npc);
|
|
355
|
+
Logger.getInstance().write(`Pursuit heartbeat: spurring ${npc.name} -- ${npc.objective}`);
|
|
356
|
+
void Promise.resolve(npc.hear(this.player, `[System note: HEARTBEAT -- your standing directive is still open: "${npc.objective}". Continue it NOW with [COMMAND] actions -- several steps if needed. If it is complete, say a line starting "DONE: <result>"; if it cannot be done, say a line starting "FAILED: <why>". Do not go quiet.]`, { viaLink: true }))
|
|
357
|
+
.catch(err => Logger.getInstance().error(`Pursuit heartbeat failed for ${npc.name}: ${err}`))
|
|
358
|
+
.finally(() => this._pursuitInFlight.delete(npc));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/** Pre-travel: every shell folds, each with its own goodbye. */
|
|
362
|
+
foldCompanionsForTravel() {
|
|
363
|
+
const lines = [];
|
|
364
|
+
for (const entry of [...this.companions]) {
|
|
365
|
+
if (entry.kind === 'spirit') {
|
|
366
|
+
lines.push(`Your bound spirit thins into the rain -- the summons doesn't outlast the ride.`);
|
|
367
|
+
}
|
|
368
|
+
else if (entry.kind === 'drone') {
|
|
369
|
+
lines.push(`The ${entry.boundDevice?.name ?? 'drone'} autopilots home to your hand for the ride.`);
|
|
370
|
+
if (entry.boundDevice)
|
|
371
|
+
this._pendingRedeploys.push(entry.boundDevice);
|
|
372
|
+
}
|
|
373
|
+
else if (entry.kind === 'agent') {
|
|
374
|
+
lines.push(`Your agent folds back into the deck.`);
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
lines.push(`${entry.npc.name} takes their own ride -- they'll meet you there.`);
|
|
378
|
+
}
|
|
379
|
+
this.dismissCompanion(entry, `travel: ${entry.kind} folds`);
|
|
380
|
+
}
|
|
381
|
+
return lines;
|
|
382
|
+
}
|
|
383
|
+
/** Post-arrival: re-shell what was deployed when you left (drones). */
|
|
384
|
+
unfoldCompanionsOnArrival() {
|
|
385
|
+
const lines = [];
|
|
386
|
+
for (const item of this._pendingRedeploys.splice(0)) {
|
|
387
|
+
if (item.isWrecked)
|
|
388
|
+
continue;
|
|
389
|
+
const shell = this.deployDroneShell(item, this.player.currentLocation);
|
|
390
|
+
if (shell)
|
|
391
|
+
lines.push(`The ${item.name}'s rotors spin back up at the curb.`);
|
|
392
|
+
}
|
|
393
|
+
return lines;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Records a real transaction with a hub NPC: first contact opens the
|
|
397
|
+
* relationship at Loyalty 1 (a safeguard -- the hub cast is pre-seeded
|
|
398
|
+
* by seedHomeTurfContacts); gigs raise it a point each (cap 6);
|
|
399
|
+
* cumulative spend raises it at each full 1,000 nuyen (cap 3).
|
|
400
|
+
* Returns a player-facing line when something changed, else null.
|
|
401
|
+
*/
|
|
402
|
+
touchContact(name, kind, spent = 0) {
|
|
403
|
+
if (!this.hasHub)
|
|
404
|
+
return null;
|
|
405
|
+
const npc = this._hubScene?.allActors?.find(a => a.name === name && a !== this.player);
|
|
406
|
+
if (!npc)
|
|
407
|
+
return null;
|
|
408
|
+
let c = this.contacts.get(name);
|
|
409
|
+
if (!c) {
|
|
410
|
+
c = { connection: Game.connectionFor(npc), loyalty: 1, spent: 0, gigs: 0, asked: false };
|
|
411
|
+
this.contacts.set(name, c);
|
|
412
|
+
if (kind === 'trade')
|
|
413
|
+
c.spent += spent;
|
|
414
|
+
if (kind === 'gig')
|
|
415
|
+
c.gigs += 1;
|
|
416
|
+
Logger.getInstance().write(`Contact opened: ${name} (connection ${c.connection}).`);
|
|
417
|
+
return `${name} knows your name now -- a CONTACT (Connection ${c.connection}, Loyalty 1). ("contacts" for the roster, "ask ${name} about ..." for legwork.)`;
|
|
418
|
+
}
|
|
419
|
+
if (kind === 'gig') {
|
|
420
|
+
c.gigs += 1;
|
|
421
|
+
if (c.loyalty < 6) {
|
|
422
|
+
c.loyalty += 1;
|
|
423
|
+
return `${name} remembers who made that hand-off happen. (Loyalty ${c.loyalty}.)`;
|
|
424
|
+
}
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
const thousandsBefore = Math.floor(c.spent / 1000);
|
|
428
|
+
c.spent += spent;
|
|
429
|
+
if (Math.floor(c.spent / 1000) > thousandsBefore && c.loyalty < 3) {
|
|
430
|
+
c.loyalty += 1;
|
|
431
|
+
return `${name} has started keeping your usual ready. (Loyalty ${c.loyalty} -- patronage tops out at 3; run their errands to go deeper.)`;
|
|
432
|
+
}
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
// ==================== Downtime economy (hub life) ====================
|
|
436
|
+
// A fence's standing quote (see commands/sell.ts / haggle.ts): one at a
|
|
437
|
+
// time, replaced by any new quote, dead the moment the fence isn't at
|
|
438
|
+
// the counter. Lives here because sell and haggle are separate commands
|
|
439
|
+
// negotiating the same number.
|
|
440
|
+
pendingSale;
|
|
441
|
+
// LIFESTYLE (SR5e p.369, monthly costs scaled to per-job-cycle): the
|
|
442
|
+
// hideout has a tier, upkeep is charged on every homecoming
|
|
443
|
+
// (returnToHub), and the tier buys real mechanics -- rest quality at
|
|
444
|
+
// home (rest.ts) and, higher up, a bonus edge every job. Can't cover
|
|
445
|
+
// the upkeep? You slide DOWN a tier and the shortfall becomes debt --
|
|
446
|
+
// and while the tab stands, rest at the hideout recovers half (rest.ts;
|
|
447
|
+
// "pay" squares it, pay.ts; "lifestyle" views and moves tiers).
|
|
448
|
+
lifestyleTier = 'Squatter';
|
|
449
|
+
rentDebt = 0;
|
|
450
|
+
/** The active lifestyle tier's row (falls back to Squatter). */
|
|
451
|
+
get lifestyle() {
|
|
452
|
+
return LIFESTYLE_TIERS.find(t => t.name === this.lifestyleTier) ?? LIFESTYLE_TIERS[1];
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Makes the hideout LOOK like what the rent buys: the room's description
|
|
456
|
+
* follows the active tier (player request -- an upgraded lifestyle read
|
|
457
|
+
* as the same windowless squat). Called wherever the tier moves: the
|
|
458
|
+
* lifestyle command, and the shortfall slide in returnToHub.
|
|
459
|
+
*/
|
|
460
|
+
syncHomeToLifestyle() {
|
|
461
|
+
if (this._homeRoom)
|
|
462
|
+
this._homeRoom.description = this.lifestyle.homeDescription;
|
|
463
|
+
}
|
|
464
|
+
// RESTOCK (see returnToHub): consumable shop stock -- food, drink,
|
|
465
|
+
// ammo, patches -- snapshotted at hub capture and restored to any shelf
|
|
466
|
+
// found empty on each homecoming. Durable one-offs are deliberately NOT
|
|
467
|
+
// in the category set: the pawnshop's lockpicks, once sold, stay sold.
|
|
468
|
+
static RESTOCK_CATEGORIES = new Set([
|
|
469
|
+
Category.Food, Category.Drink, Category.Ammunition, Category.StimPatch, Category.Medical, Category.SlapPatch,
|
|
470
|
+
]);
|
|
471
|
+
_restockSnapshot = [];
|
|
472
|
+
/** Captures every restockable shelf item's blueprint (hub capture time). */
|
|
473
|
+
snapshotVendorStock() {
|
|
474
|
+
this._restockSnapshot = [];
|
|
475
|
+
for (const room of Object.values(this.scene.getRooms())) {
|
|
476
|
+
for (const item of room.inventory.getAllItems()) {
|
|
477
|
+
if (!item.owner || !Game.RESTOCK_CATEGORIES.has(item.category))
|
|
478
|
+
continue;
|
|
479
|
+
this._restockSnapshot.push({
|
|
480
|
+
room: room.name,
|
|
481
|
+
owner: item.owner,
|
|
482
|
+
config: {
|
|
483
|
+
name: item.name, description: item.description, size: item.size,
|
|
484
|
+
category: item.category, shape: item.shape, color: item.color,
|
|
485
|
+
texture: item.texture, rating: item.rating, weight: item.weight,
|
|
486
|
+
details: item.details, price: item.price,
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
/** Refills empty shelves from the snapshot; returns how many came back. */
|
|
493
|
+
restockVendorShelves() {
|
|
494
|
+
let restocked = 0;
|
|
495
|
+
const rooms = this.scene.getRooms();
|
|
496
|
+
for (const entry of this._restockSnapshot) {
|
|
497
|
+
const room = Object.values(rooms).find(r => r.name === entry.room);
|
|
498
|
+
if (!room || room.inventory.hasItem(entry.config.name))
|
|
499
|
+
continue;
|
|
500
|
+
const fresh = new Item(entry.config);
|
|
501
|
+
fresh.owner = entry.owner;
|
|
502
|
+
room.inventory.addItem(fresh);
|
|
503
|
+
restocked += 1;
|
|
504
|
+
}
|
|
505
|
+
return restocked;
|
|
506
|
+
}
|
|
507
|
+
/** The fence moves merchandise: anything sold to a fence is sold ON. */
|
|
508
|
+
fenceSellsOn() {
|
|
509
|
+
for (const actor of this.scene.allActors) {
|
|
510
|
+
if (actor === this.player || !isFence(actor, actor.currentLocation))
|
|
511
|
+
continue;
|
|
512
|
+
for (const item of [...actor.inventory.getAllItems()]) {
|
|
513
|
+
if (item.transferable && item.category !== Category.Commlink) {
|
|
514
|
+
actor.inventory.removeItem(item.name);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
// SIDE GIGS (see commands/work.ts / give.ts): one small favor on offer
|
|
520
|
+
// at a time, rotating each homecoming like the rumor. Take it with
|
|
521
|
+
// "work" in front of the giver; deliver the package with "give"; the
|
|
522
|
+
// pay lands on delivery.
|
|
523
|
+
gigAvailable;
|
|
524
|
+
activeGig;
|
|
525
|
+
_gigPool = [];
|
|
526
|
+
_gigIndex = 0;
|
|
527
|
+
/** Rotates the gig on offer (skipped while one is already in hand). */
|
|
528
|
+
advanceGig() {
|
|
529
|
+
if (this._gigPool.length === 0 || this.activeGig)
|
|
530
|
+
return;
|
|
531
|
+
this.gigAvailable = this._gigPool[this._gigIndex % this._gigPool.length];
|
|
532
|
+
this._gigIndex += 1;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* WORK FROM A RELATIONSHIP (see commands/ask.ts): a contact puts THEIR
|
|
536
|
+
* errand on the table. Their seeded gigs get first claim -- spliced
|
|
537
|
+
* out of the rotating pool, so work handed to you personally never
|
|
538
|
+
* reappears on the ambient board -- and when the pool holds nothing of
|
|
539
|
+
* theirs, the relationship synthesizes one: a sealed parcel to another
|
|
540
|
+
* hub local, priced off Connection (their reach IS the stakes) with a
|
|
541
|
+
* Loyalty kicker. Loyalty 5+ pays the family rate (+25%). The offer
|
|
542
|
+
* lands as gigAvailable: the existing "work" pickup and "give"
|
|
543
|
+
* delivery handle the rest unchanged.
|
|
544
|
+
*/
|
|
545
|
+
offerGigFrom(contactName) {
|
|
546
|
+
const contact = this.contacts.get(contactName);
|
|
547
|
+
if (!contact || !this.hasHub || this.activeGig)
|
|
548
|
+
return null;
|
|
549
|
+
let gig = undefined;
|
|
550
|
+
const seeded = this._gigPool.findIndex(g => g.from === contactName);
|
|
551
|
+
if (seeded >= 0) {
|
|
552
|
+
gig = this._gigPool.splice(seeded, 1)[0];
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
const locals = (this._hubScene?.allActors ?? [])
|
|
556
|
+
.filter(a => a !== this.player && a.name !== contactName && a.plane === 'meat' && !a.isIncapacitated())
|
|
557
|
+
.map(a => a.name);
|
|
558
|
+
if (locals.length === 0)
|
|
559
|
+
return null;
|
|
560
|
+
const to = locals[Math.floor(Math.random() * locals.length)];
|
|
561
|
+
const pay = 60 + contact.connection * 40 + contact.loyalty * 10;
|
|
562
|
+
gig = {
|
|
563
|
+
from: contactName,
|
|
564
|
+
to,
|
|
565
|
+
itemName: `${contactName}'s Sealed Parcel`,
|
|
566
|
+
itemDescription: `A tight-wrapped parcel, taped past curiosity. Whatever it is, ${contactName} trusts you to not ask.`,
|
|
567
|
+
pay,
|
|
568
|
+
brief: `Quiet job, no questions asked: this needs to reach ${to}, today, unopened. ${pay} nuyen on delivery.`,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
const familyRate = contact.loyalty >= 5;
|
|
572
|
+
if (familyRate)
|
|
573
|
+
gig = { ...gig, pay: Math.round(gig.pay * 1.25) };
|
|
574
|
+
this.gigAvailable = gig;
|
|
575
|
+
Logger.getInstance().write(`Contact gig offered by ${contactName}: "${gig.itemName}" -> ${gig.to}, ${gig.pay}${familyRate ? ' (family rate)' : ''}.`);
|
|
576
|
+
return { ...gig, familyRate };
|
|
577
|
+
}
|
|
578
|
+
// FIRST-OF-ITS-KIND bounty (see commands/sell.ts): the fence pays a
|
|
579
|
+
// 50% premium on the first item of each category to cross her counter.
|
|
580
|
+
fencedCategories = new Set();
|
|
581
|
+
// Street rumors: the hub seed ships a pool (scene2.json "rumors"); one
|
|
582
|
+
// is ACTIVE at a time, rotating each time a run wraps. The active rumor
|
|
583
|
+
// is planted in the world-event mesh (every hub NPC's prompt reads it,
|
|
584
|
+
// so Mama Wu can actually gossip) and handed to the next job's skeleton
|
|
585
|
+
// prompt as its backdrop -- the street foreshadows the work.
|
|
586
|
+
_rumorPool = [];
|
|
587
|
+
_rumorIndex = 0;
|
|
588
|
+
activeRumor;
|
|
589
|
+
/** Rotates to the next rumor and plants it where NPCs will hear it. */
|
|
590
|
+
advanceRumor() {
|
|
591
|
+
if (this._rumorPool.length === 0)
|
|
592
|
+
return;
|
|
593
|
+
this.activeRumor = this._rumorPool[this._rumorIndex % this._rumorPool.length];
|
|
594
|
+
this._rumorIndex += 1;
|
|
595
|
+
this._hubScene?.addWorldEvent(`Word around the district: ${this.activeRumor}`);
|
|
596
|
+
}
|
|
102
597
|
/** The persistent hideout room -- go.ts's walk-home trigger checks it. */
|
|
103
598
|
get homeRoom() {
|
|
104
599
|
return this._homeRoom;
|
|
@@ -242,6 +737,32 @@ export default class Game {
|
|
|
242
737
|
CommandFactory.registerCommand('hangup', EndCallCommand);
|
|
243
738
|
CommandFactory.registerCommand('advance', AdvanceCommand);
|
|
244
739
|
CommandFactory.registerCommand('train', AdvanceCommand);
|
|
740
|
+
CommandFactory.registerCommand('qualities', QualityTradeCommand);
|
|
741
|
+
CommandFactory.registerCommand('quality', QualityTradeCommand);
|
|
742
|
+
CommandFactory.registerCommand('ask', AskCommand);
|
|
743
|
+
CommandFactory.registerCommand('contacts', ContactsCommand);
|
|
744
|
+
CommandFactory.registerCommand('jump', JumpCommand);
|
|
745
|
+
CommandFactory.registerCommand('rig', JumpCommand);
|
|
746
|
+
CommandFactory.registerCommand('initiate', InitiateCommand);
|
|
747
|
+
CommandFactory.registerCommand('submerge', InitiateCommand);
|
|
748
|
+
CommandFactory.registerCommand('summon', SummonCommand);
|
|
749
|
+
CommandFactory.registerCommand('conjure', SummonCommand);
|
|
750
|
+
CommandFactory.registerCommand('order', OrderCommand);
|
|
751
|
+
CommandFactory.registerCommand('deploy', DeployCommand);
|
|
752
|
+
CommandFactory.registerCommand('recall', RecallCommand);
|
|
753
|
+
CommandFactory.registerCommand('agent', AgentCommand);
|
|
754
|
+
CommandFactory.registerCommand('install', InstallCommand);
|
|
755
|
+
CommandFactory.registerCommand('chrome', InstallCommand);
|
|
756
|
+
CommandFactory.registerCommand('buy', BuyCommand);
|
|
757
|
+
CommandFactory.registerCommand('browse', BuyCommand);
|
|
758
|
+
CommandFactory.registerCommand('sell', SellCommand);
|
|
759
|
+
CommandFactory.registerCommand('pay', PayCommand);
|
|
760
|
+
CommandFactory.registerCommand('eat', EatCommand);
|
|
761
|
+
CommandFactory.registerCommand('drink', DrinkCommand);
|
|
762
|
+
CommandFactory.registerCommand('lifestyle', LifestyleCommand);
|
|
763
|
+
CommandFactory.registerCommand('treat', TreatCommand);
|
|
764
|
+
CommandFactory.registerCommand('work', WorkCommand);
|
|
765
|
+
CommandFactory.registerCommand('gig', WorkCommand);
|
|
245
766
|
CommandFactory.registerCommand('haggle', HaggleCommand);
|
|
246
767
|
CommandFactory.registerCommand('negotiate', HaggleCommand);
|
|
247
768
|
CommandFactory.registerCommand('sneak', SneakCommand);
|
|
@@ -281,6 +802,9 @@ export default class Game {
|
|
|
281
802
|
get logBox() {
|
|
282
803
|
return this._logBox;
|
|
283
804
|
}
|
|
805
|
+
get metaBox() {
|
|
806
|
+
return this._metaBox;
|
|
807
|
+
}
|
|
284
808
|
get inputBox() {
|
|
285
809
|
return this._inputBox;
|
|
286
810
|
}
|
|
@@ -289,6 +813,7 @@ export default class Game {
|
|
|
289
813
|
this._logFilePath = options.logFilePath;
|
|
290
814
|
this.initInputBox(screen);
|
|
291
815
|
this.initLogBox(screen);
|
|
816
|
+
this.initMetaBox(screen);
|
|
292
817
|
this.initStatusBox(screen);
|
|
293
818
|
// No "Generating Scene... Please wait." box anymore: every path now
|
|
294
819
|
// arrives with REAL prose (hand-authored static seeds, chunk-authored
|
|
@@ -307,9 +832,12 @@ export default class Game {
|
|
|
307
832
|
this.player.currentLocation = this.determineStartRoom();
|
|
308
833
|
this.registerCommands();
|
|
309
834
|
this._screen = screen;
|
|
835
|
+
// The description follows the rent from day one (see
|
|
836
|
+
// syncHomeToLifestyle) -- a fresh session at the default Squatter tier
|
|
837
|
+
// reads exactly as the hideout always has.
|
|
310
838
|
this._homeRoom = new Room({
|
|
311
839
|
name: 'Your Hideout',
|
|
312
|
-
description:
|
|
840
|
+
description: this.lifestyle.homeDescription,
|
|
313
841
|
roomType: 'Residence',
|
|
314
842
|
});
|
|
315
843
|
this.scene.addRoom(this._homeRoom);
|
|
@@ -325,6 +853,17 @@ export default class Game {
|
|
|
325
853
|
if (this.scene.isHub) {
|
|
326
854
|
this._hubScene = this.scene;
|
|
327
855
|
this._hubName = this.sceneSeed?.name ?? 'home';
|
|
856
|
+
// The street starts talking from minute one.
|
|
857
|
+
this._rumorPool = this.sceneSeed?.rumors ?? [];
|
|
858
|
+
this.advanceRumor();
|
|
859
|
+
// The economy wakes up with it: shelf blueprints for restocking,
|
|
860
|
+
// the first odd job on offer, and the lifestyle's edge perk.
|
|
861
|
+
this.snapshotVendorStock();
|
|
862
|
+
this._gigPool = this.sceneSeed?.gigs ?? [];
|
|
863
|
+
this.advanceGig();
|
|
864
|
+
this.player.lifestyleEdgeBonus = this.lifestyle.edgeBonus;
|
|
865
|
+
// Home turf knows its own (see seedHomeTurfContacts).
|
|
866
|
+
this.seedHomeTurfContacts();
|
|
328
867
|
}
|
|
329
868
|
// Gear up automatically: wearables (and the commlink) slot into any
|
|
330
869
|
// EMPTY slots at load -- no "equip all" ritual to start a session
|
|
@@ -332,6 +871,7 @@ export default class Game {
|
|
|
332
871
|
// never swapped out.
|
|
333
872
|
equipIntoEmptySlots(this.player);
|
|
334
873
|
this.warmNpcAppearancesInBackground();
|
|
874
|
+
this.startPursuitHeartbeat();
|
|
335
875
|
this.initialized = true;
|
|
336
876
|
// Nudge the player toward "call" up front -- the fixer convention means
|
|
337
877
|
// the story deliberately doesn't narrate a completed briefing, so
|
|
@@ -394,11 +934,13 @@ export default class Game {
|
|
|
394
934
|
* instant feedback, but this stays safe to call directly too).
|
|
395
935
|
*/
|
|
396
936
|
async continueToNextScene() {
|
|
397
|
-
// Hub sessions: a
|
|
398
|
-
//
|
|
399
|
-
//
|
|
400
|
-
//
|
|
401
|
-
|
|
937
|
+
// Hub sessions: a RUN doesn't chain into the next draft -- going
|
|
938
|
+
// home is going home, and the next run is commissioned from there
|
|
939
|
+
// (call the fixer). Deliberately NOT gated on completion: the taxi
|
|
940
|
+
// will carry a runner away from a job gone sideways (see call.ts) --
|
|
941
|
+
// walking away just means the payout never landed. Classic sessions
|
|
942
|
+
// skip this and keep the old continue->draft loop.
|
|
943
|
+
if (this._hubScene && this.scene !== this._hubScene) {
|
|
402
944
|
return this.returnToHub();
|
|
403
945
|
}
|
|
404
946
|
const gate = this.canDraftNextScene();
|
|
@@ -425,6 +967,11 @@ export default class Game {
|
|
|
425
967
|
const hub = this._hubScene;
|
|
426
968
|
if (!hub)
|
|
427
969
|
return `There's no home to go back to.`;
|
|
970
|
+
// Read the run's fate BEFORE the scene swap below discards it -- a
|
|
971
|
+
// walked-away job gets a different homecoming line than a wrapped one.
|
|
972
|
+
const wrappedRun = this.scene !== hub && this.scene.isCompleted();
|
|
973
|
+
// Companions fold for the ride home, same as the trip out.
|
|
974
|
+
const companionFoldLines = this.foldCompanionsForTravel();
|
|
428
975
|
// Settle cross-plane and combat state that must not leak into the hub
|
|
429
976
|
// (the run's rooms -- including a bodyRoom the player left behind --
|
|
430
977
|
// are about to be orphaned wholesale).
|
|
@@ -433,6 +980,7 @@ export default class Game {
|
|
|
433
980
|
this.player.simMode = undefined;
|
|
434
981
|
this.player.astralTicks = 0;
|
|
435
982
|
this.player.activeDeck = undefined;
|
|
983
|
+
this.player.riggedDrone = undefined;
|
|
436
984
|
this.player.astralPerceiving = false;
|
|
437
985
|
this.player.sneaking = false;
|
|
438
986
|
this.player.inExchange = false;
|
|
@@ -444,12 +992,63 @@ export default class Game {
|
|
|
444
992
|
this.connectHomeRoom(hub.determineStartRoom());
|
|
445
993
|
this.player.currentLocation = this._homeRoom;
|
|
446
994
|
this.player.cameFrom = undefined;
|
|
995
|
+
this.pendingSale = undefined;
|
|
996
|
+
// Contacts have had time to work their networks again -- one legwork
|
|
997
|
+
// pull each per homecoming (see commands/ask.ts).
|
|
998
|
+
for (const contact of this.contacts.values())
|
|
999
|
+
contact.asked = false;
|
|
1000
|
+
// LIFESTYLE upkeep comes due with the homecoming: this tier's share
|
|
1001
|
+
// plus any standing debt, paid from carried cred as far as it goes.
|
|
1002
|
+
// Coming up short SLIDES YOU DOWN a tier (the district notices) and
|
|
1003
|
+
// the shortfall becomes the tab -- rest.ts halves recovery while you
|
|
1004
|
+
// owe; "pay" squares it; "lifestyle" climbs back up.
|
|
1005
|
+
const tier = this.lifestyle;
|
|
1006
|
+
const due = tier.upkeep + this.rentDebt;
|
|
1007
|
+
const paid = Math.min(this.player.currency, due);
|
|
1008
|
+
if (paid > 0)
|
|
1009
|
+
this.player.adjustCurrency(-paid);
|
|
1010
|
+
this.rentDebt = due - paid;
|
|
1011
|
+
let rentLine;
|
|
1012
|
+
if (this.rentDebt > 0) {
|
|
1013
|
+
const tierIndex = LIFESTYLE_TIERS.findIndex(t => t.name === tier.name);
|
|
1014
|
+
if (tierIndex > 0) {
|
|
1015
|
+
this.lifestyleTier = LIFESTYLE_TIERS[tierIndex - 1].name;
|
|
1016
|
+
// The downgrade is visible the moment you look around.
|
|
1017
|
+
this.syncHomeToLifestyle();
|
|
1018
|
+
rentLine = `The landlord's drone pings before the door even shuts: ${paid > 0 ? `${paid} paid, ` : ''}${this.rentDebt} nuyen still owed. Your ${tier.name} arrangement lapses -- you're living ${this.lifestyleTier} now. ("pay" the tab, "lifestyle" to climb back.)`;
|
|
1019
|
+
}
|
|
1020
|
+
else {
|
|
1021
|
+
rentLine = `Even the doorway costs something: ${this.rentDebt} nuyen on the tab. ("pay" when you're flush.)`;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
else {
|
|
1025
|
+
rentLine = `The landlord's drone takes its ${paid} nuyen (${tier.name} upkeep) at the door and buzzes off satisfied.`;
|
|
1026
|
+
}
|
|
1027
|
+
this.player.lifestyleEdgeBonus = this.lifestyle.edgeBonus;
|
|
1028
|
+
// A High lifestyle erases the run: hot shower, real bed, full reset.
|
|
1029
|
+
let comfortLine = '';
|
|
1030
|
+
if (this.lifestyle.clearStunOnReturn && this.player.stunTaken > 0) {
|
|
1031
|
+
const cleared = this.player.healStun(this.player.stunTaken);
|
|
1032
|
+
comfortLine = `\nThe shower runs hot until the run is a rumor -- ${cleared} stun gone before your head hits the pillow.`;
|
|
1033
|
+
}
|
|
1034
|
+
// Fresh streets, fresh gossip, restocked shelves, new work.
|
|
1035
|
+
this.advanceRumor();
|
|
1036
|
+
this.fenceSellsOn();
|
|
1037
|
+
const restocked = this.restockVendorShelves();
|
|
1038
|
+
const restockLine = restocked > 0 ? `\nThe district has restocked around you -- the wok steams, the bar's shelf is full, the market stalls are loud again.` : '';
|
|
1039
|
+
this.advanceGig();
|
|
447
1040
|
this.updateEquipment(this.player.equipment);
|
|
448
1041
|
this.updateInventory(this.player.inventory, capitalCase(this.scene.getCurrencyType()));
|
|
449
1042
|
this.updateExits(this.player.currentLocation);
|
|
450
1043
|
this.updateStatus();
|
|
451
|
-
Logger.getInstance().write(`Returned to hub "${this._hubName}"
|
|
452
|
-
|
|
1044
|
+
Logger.getInstance().write(`Returned to hub "${this._hubName}". Lifestyle ${this.lifestyleTier}: ${paid} paid, ${this.rentDebt} owed. Restocked ${restocked}. Rumor: ${this.activeRumor ?? '(none)'}`);
|
|
1045
|
+
// Drones spin back up beside the cot.
|
|
1046
|
+
const companionArrivalLines = this.unfoldCompanionsOnArrival();
|
|
1047
|
+
const companionBlock = [...companionFoldLines, ...companionArrivalLines];
|
|
1048
|
+
const fateLine = wrappedRun
|
|
1049
|
+
? `The run's wrapped and the street swallows you back up.`
|
|
1050
|
+
: `The run's behind you -- unfinished, unpaid, already someone else's problem. The street doesn't ask.`;
|
|
1051
|
+
return `\n=== ${this._hubName.toUpperCase()} ===\n${fateLine} You're home -- your hideout, one door off ${hub.determineStartRoom().name}. Call ${FIXER_NAME} when you're hungry for the next job.\n${rentLine}${comfortLine}${restockLine}${companionBlock.length > 0 ? `\n${companionBlock.join('\n')}` : ''}`;
|
|
453
1052
|
}
|
|
454
1053
|
/**
|
|
455
1054
|
* The actual drafting work, split out from continueToNextScene() purely
|
|
@@ -481,7 +1080,7 @@ export default class Game {
|
|
|
481
1080
|
//
|
|
482
1081
|
// The player object rides along so the job is drafted FOR this runner
|
|
483
1082
|
// -- their archetype, stats, and toolkit (see buildPlayerDossier).
|
|
484
|
-
const nextSeed = await SceneSeedGenerator.generate(this._logFilePath, nextTier, previousFixerName, this.player);
|
|
1083
|
+
const nextSeed = await SceneSeedGenerator.generate(this._logFilePath, nextTier, previousFixerName, this.player, this.activeRumor);
|
|
485
1084
|
if (!nextSeed) {
|
|
486
1085
|
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
1086
|
}
|
|
@@ -513,20 +1112,30 @@ export default class Game {
|
|
|
513
1112
|
this.player.activeCallPartner.activeCallPartner = undefined;
|
|
514
1113
|
this.player.activeCallPartner = undefined;
|
|
515
1114
|
}
|
|
1115
|
+
// Companions fold for the ride (spirits depart, drones to the pack,
|
|
1116
|
+
// agents into the deck) -- their lines join the JOB banner below.
|
|
1117
|
+
const companionLines = this.foldCompanionsForTravel();
|
|
516
1118
|
// false, false: no starter kit (the player carries their own gear
|
|
517
1119
|
// forward), and no blocking AI-redressing pass either -- the chunked
|
|
518
1120
|
// rooms pass already wrote this scene's prose.
|
|
519
1121
|
this.scene = await SceneSynthesizer.synthesizeFromJson(seed, this.player, false, false);
|
|
520
1122
|
this.scene.initialize(this);
|
|
521
1123
|
this.player.currentLocation = this.determineStartRoom();
|
|
522
|
-
// New job
|
|
523
|
-
|
|
524
|
-
//
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
1124
|
+
// New job -- and the needs come due at the door (see
|
|
1125
|
+
// applyDepartureNeeds): edge only refills fed-and-slept, and ignored
|
|
1126
|
+
// needs land as locked fatigue. Lines ride the JOB banner below.
|
|
1127
|
+
const needsLines = this.applyDepartureNeeds();
|
|
1128
|
+
// The home base is owned by Game, not any one Scene. HUB sessions
|
|
1129
|
+
// leave it OUT of run scenes (player request): your cot is across
|
|
1130
|
+
// town, not stapled to the jobsite -- the taxi ("call taxi", fare on
|
|
1131
|
+
// the tab if you're broke) is the road home. Classic sessions keep
|
|
1132
|
+
// the hideout adjacent as always: it's the only home they have.
|
|
1133
|
+
if (!this.hasHub) {
|
|
1134
|
+
this.scene.addRoom(this._homeRoom);
|
|
1135
|
+
this.connectHomeRoom(this.player.currentLocation);
|
|
1136
|
+
if (opts.startInHideout) {
|
|
1137
|
+
this.player.currentLocation = this._homeRoom;
|
|
1138
|
+
}
|
|
530
1139
|
}
|
|
531
1140
|
this.updateEquipment(this.player.equipment);
|
|
532
1141
|
this.updateInventory(this.player.inventory, capitalCase(this.scene.getCurrencyType()));
|
|
@@ -534,8 +1143,72 @@ export default class Game {
|
|
|
534
1143
|
this.updateStatus();
|
|
535
1144
|
// Fresh streets, no trail yet (see Player.cameFrom / the HUD crumb).
|
|
536
1145
|
this.player.cameFrom = undefined;
|
|
1146
|
+
// Deployed drones spin back up at the curb -- BEFORE appearance
|
|
1147
|
+
// warming so re-shelled companions get warmed with the cast.
|
|
1148
|
+
const arrivalLines = this.unfoldCompanionsOnArrival();
|
|
537
1149
|
this.warmNpcAppearancesInBackground();
|
|
538
|
-
|
|
1150
|
+
const companionBlock = [...companionLines, ...arrivalLines];
|
|
1151
|
+
return `\n=== JOB #${this._tier}: ${seed.name} ===\n${this.scene.story}${needsLines.length > 0 ? `\n${needsLines.join('\n')}` : ''}${companionBlock.length > 0 ? `\n${companionBlock.join('\n')}` : ''}`;
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* The needs check at every job departure (SR5e p.56 / p.172, scaled to
|
|
1155
|
+
* job-cycle "days"). Fed AND slept since the last departure: edge
|
|
1156
|
+
* refills and you head out sharp. Skip either: edge stays at whatever's
|
|
1157
|
+
* left. Keep skipping: escalating fatigue (1S, 2S, 3S... per cycle),
|
|
1158
|
+
* resisted Body + Willpower, landing as LOCKED stun no recovery touches
|
|
1159
|
+
* until the need is met (Player.healStun / markFed / markSlept). A
|
|
1160
|
+
* Sleep Regulator (augmentations.ts) halves the effective sleep debt.
|
|
1161
|
+
* Fatigue is capped one box short of a knockout -- RAW would drop you,
|
|
1162
|
+
* but collapsing INSIDE the departure transition would strand the
|
|
1163
|
+
* session between scenes; "running on fumes" carries the spirit.
|
|
1164
|
+
*/
|
|
1165
|
+
applyDepartureNeeds() {
|
|
1166
|
+
const p = this.player;
|
|
1167
|
+
const fed = p.ateThisCycle;
|
|
1168
|
+
const slept = p.sleptThisCycle;
|
|
1169
|
+
const lines = [];
|
|
1170
|
+
p.hungerStreak = fed ? 0 : p.hungerStreak + 1;
|
|
1171
|
+
p.sleepStreak = slept ? 0 : p.sleepStreak + 1;
|
|
1172
|
+
p.refreshEdge(fed && slept);
|
|
1173
|
+
if (fed && slept) {
|
|
1174
|
+
lines.push(`Fed and slept -- you head out SHARP. (Edge ${p.edgeRemaining}/${p.edge + p.lifestyleEdgeBonus}.)`);
|
|
1175
|
+
}
|
|
1176
|
+
else {
|
|
1177
|
+
const missing = !fed && !slept ? 'meal and no sleep' : !fed ? 'meal' : 'sleep';
|
|
1178
|
+
lines.push(`No ${missing} since the last job -- your edge doesn't come back (${p.edgeRemaining} left). A hot meal and a night in your own bed reset it.`);
|
|
1179
|
+
}
|
|
1180
|
+
// Escalating, resisted, LOCKED fatigue per ignored need.
|
|
1181
|
+
const events = [];
|
|
1182
|
+
if (p.hungerStreak > 0) {
|
|
1183
|
+
events.push({ kind: 'hunger', label: 'Hunger', dmg: p.hungerStreak });
|
|
1184
|
+
}
|
|
1185
|
+
const effectiveSleepDebt = p.hasAug('sleep-regulator') ? Math.floor(p.sleepStreak / 2) : p.sleepStreak;
|
|
1186
|
+
if (effectiveSleepDebt > 0) {
|
|
1187
|
+
events.push({ kind: 'sleep', label: 'Sleep deprivation', dmg: effectiveSleepDebt });
|
|
1188
|
+
}
|
|
1189
|
+
for (const event of events) {
|
|
1190
|
+
const resist = rollPool(Math.max(1, p.body + p.willpower));
|
|
1191
|
+
const taken = Math.max(0, event.dmg - resist.hits);
|
|
1192
|
+
const headroom = Math.max(0, p.maxStunBoxes - p.stunTaken - 1);
|
|
1193
|
+
const applied = Math.min(taken, headroom);
|
|
1194
|
+
if (applied > 0) {
|
|
1195
|
+
p.takeStun(applied);
|
|
1196
|
+
p.addLockedFatigue(event.kind, applied);
|
|
1197
|
+
lines.push(`${event.label} (${event.dmg}S): resist ${formatRoll(resist)} -- ${applied} stun, and it will NOT heal until you ${event.kind === 'hunger' ? 'eat' : 'sleep'}: ${p.stunSummary()}`);
|
|
1198
|
+
}
|
|
1199
|
+
else if (taken > 0) {
|
|
1200
|
+
lines.push(`${event.label} (${event.dmg}S): resist ${formatRoll(resist)} -- you're running on fumes; there's barely anything left to take.`);
|
|
1201
|
+
}
|
|
1202
|
+
else {
|
|
1203
|
+
lines.push(`${event.label} (${event.dmg}S): resist ${formatRoll(resist)} -- you shake it off. For now.`);
|
|
1204
|
+
}
|
|
1205
|
+
Logger.getInstance().write(`Departure needs: ${event.label} ${event.dmg}S, resisted ${resist.hits}, applied ${applied} (locked).`);
|
|
1206
|
+
}
|
|
1207
|
+
// The new cycle starts unfed and unslept -- Wu's counter and your own
|
|
1208
|
+
// cot are how these flip back (use.ts eat / rest.ts at a Residence).
|
|
1209
|
+
p.ateThisCycle = false;
|
|
1210
|
+
p.sleptThisCycle = false;
|
|
1211
|
+
return lines;
|
|
539
1212
|
}
|
|
540
1213
|
/**
|
|
541
1214
|
* "travel": takes the arranged transport to the pending job (see
|
|
@@ -569,6 +1242,7 @@ export default class Game {
|
|
|
569
1242
|
}
|
|
570
1243
|
}
|
|
571
1244
|
async close() {
|
|
1245
|
+
this.stopPursuitHeartbeat();
|
|
572
1246
|
this._inputBox.removeAllListeners(); // removes all event listeners
|
|
573
1247
|
this._inputBox.cancel(); // cancels any active input session
|
|
574
1248
|
this._inputBox.detach(); // optionally remove from screen
|
|
@@ -588,8 +1262,13 @@ export default class Game {
|
|
|
588
1262
|
border: { type: 'line' },
|
|
589
1263
|
style: { fg: 'white', border: { fg: 'cyan' } },
|
|
590
1264
|
keys: true, // Enable keyboard input
|
|
591
|
-
mouse
|
|
592
|
-
|
|
1265
|
+
// NO mouse -- deliberately. Any widget with mouse:true makes
|
|
1266
|
+
// blessed arm xterm mouse tracking (?1000h/?1002h/?1003h/?1005h),
|
|
1267
|
+
// and three real sessions ended with the whole process tree dying
|
|
1268
|
+
// hard (no JS cleanup possible) and the shell flooded by raw mouse
|
|
1269
|
+
// reports on every cursor move. The input box is auto-focused and
|
|
1270
|
+
// keyboard-driven; click-to-focus bought nothing worth that risk.
|
|
1271
|
+
inputOnFocus: true,
|
|
593
1272
|
vi: true
|
|
594
1273
|
});
|
|
595
1274
|
this._inputBox = inputBox;
|
|
@@ -620,13 +1299,17 @@ export default class Game {
|
|
|
620
1299
|
id: 'logBox',
|
|
621
1300
|
top: '0%',
|
|
622
1301
|
left: '0%',
|
|
623
|
-
|
|
1302
|
+
// COLUMNS, not rows (playtest note: a 3-row Mechanics strip made
|
|
1303
|
+
// combat rolls "near impossible to read" -- they scrolled out as
|
|
1304
|
+
// fast as they landed). The world log takes the left ~two-thirds;
|
|
1305
|
+
// the Mechanics column stands full-height beside it.
|
|
1306
|
+
width: '65%',
|
|
624
1307
|
// Fixed-row footer: 8 rows of HUD (status box) + 3 rows of input.
|
|
625
1308
|
height: '100%-11',
|
|
626
1309
|
border: {
|
|
627
1310
|
type: 'line'
|
|
628
1311
|
},
|
|
629
|
-
label: ' Game Log ',
|
|
1312
|
+
label: ' Game Log (PgUp/PgDn to scroll) ',
|
|
630
1313
|
scrollable: true, // Enable scrolling
|
|
631
1314
|
alwaysScroll: true,
|
|
632
1315
|
scrollbar: {
|
|
@@ -639,13 +1322,90 @@ export default class Game {
|
|
|
639
1322
|
}
|
|
640
1323
|
},
|
|
641
1324
|
keys: true, // Enable keyboard input
|
|
642
|
-
mouse:
|
|
1325
|
+
// NO mouse (see initInputBox): never arm mouse tracking. The log
|
|
1326
|
+
// auto-scrolls (alwaysScroll) and keys/vi still scroll it when
|
|
1327
|
+
// focused -- wheel-scroll wasn't worth shells full of coordinate
|
|
1328
|
+
// bytes every time a session died hard.
|
|
643
1329
|
tags: true // Intercept tags
|
|
644
1330
|
});
|
|
645
1331
|
this._logBox = logBox;
|
|
646
|
-
|
|
1332
|
+
// Scrolling without focus (player report: "can't scroll the game log
|
|
1333
|
+
// anymore"): focus lives in the input box on every path, so the
|
|
1334
|
+
// box's own keys never fire, and the wheel died with mouse tracking.
|
|
1335
|
+
// SCREEN-level PgUp/PgDn scroll the log while you keep typing; while
|
|
1336
|
+
// you're up in history, followBottom stops every new line from
|
|
1337
|
+
// yanking the view back down (Logger.log and ui.ts honor it).
|
|
1338
|
+
// Paging back to the bottom -- or End -- re-engages the follow.
|
|
1339
|
+
logBox.followBottom = true;
|
|
1340
|
+
const page = () => Math.max(1, (typeof logBox.height === 'number' ? logBox.height : 20) - 3);
|
|
1341
|
+
const setFollow = (on) => {
|
|
1342
|
+
logBox.followBottom = on;
|
|
1343
|
+
logBox.setLabel(on ? ' Game Log (PgUp/PgDn to scroll) ' : ' Game Log -- SCROLLED (End to follow) ');
|
|
1344
|
+
};
|
|
1345
|
+
const atBottom = () => logBox.getScrollPerc() >= 100 || logBox.getScrollHeight() <= page() + 3;
|
|
1346
|
+
screen.key(['pageup'], () => {
|
|
1347
|
+
logBox.scroll(-page());
|
|
1348
|
+
setFollow(false);
|
|
1349
|
+
screen.render();
|
|
1350
|
+
});
|
|
1351
|
+
screen.key(['pagedown'], () => {
|
|
1352
|
+
logBox.scroll(page());
|
|
1353
|
+
if (atBottom())
|
|
1354
|
+
setFollow(true);
|
|
1355
|
+
screen.render();
|
|
1356
|
+
});
|
|
1357
|
+
screen.key(['end'], () => {
|
|
1358
|
+
logBox.scrollTo(logBox.getScrollHeight());
|
|
1359
|
+
setFollow(true);
|
|
1360
|
+
screen.render();
|
|
1361
|
+
});
|
|
647
1362
|
screen.append(logBox);
|
|
648
1363
|
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Scroll the log to its tail -- unless the player has paged up to read
|
|
1366
|
+
* history (followBottom off), in which case their place is left alone.
|
|
1367
|
+
* Every "new line arrived" site goes through here.
|
|
1368
|
+
*/
|
|
1369
|
+
followLog() {
|
|
1370
|
+
const box = this._logBox;
|
|
1371
|
+
if (!box || box.followBottom === false)
|
|
1372
|
+
return;
|
|
1373
|
+
box.scrollTo(box.getScrollHeight());
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* The MECHANICS column (readability pass, player request: the log was
|
|
1377
|
+
* a "wall of text"): dice anatomy and resource arithmetic land here
|
|
1378
|
+
* (via Logger.meta) instead of the world log, so the narrative stays
|
|
1379
|
+
* prose. A full-height COLUMN beside the world log, not a bottom
|
|
1380
|
+
* strip -- the first playtest's 3-row ticker scrolled combat rolls
|
|
1381
|
+
* away faster than they could be read. De-emphasis is the BLUE border
|
|
1382
|
+
* -- gray is banned (ANSI bright-black, illegible on stock dark
|
|
1383
|
+
* palettes). Follow-only (no keys); the full interleaved record lives
|
|
1384
|
+
* in session.log. Roll lines wrap at the column width -- fine in a
|
|
1385
|
+
* tall column. NO mouse (see initInputBox): never arm mouse tracking.
|
|
1386
|
+
*/
|
|
1387
|
+
initMetaBox(screen) {
|
|
1388
|
+
const metaBox = blessed.box({
|
|
1389
|
+
id: 'metaBox',
|
|
1390
|
+
top: '0%',
|
|
1391
|
+
left: '65%',
|
|
1392
|
+
width: '35%',
|
|
1393
|
+
// Same height as the world log: HUD (8) + input (3) below both.
|
|
1394
|
+
height: '100%-11',
|
|
1395
|
+
border: {
|
|
1396
|
+
type: 'line'
|
|
1397
|
+
},
|
|
1398
|
+
style: {
|
|
1399
|
+
border: { fg: 'blue' }
|
|
1400
|
+
},
|
|
1401
|
+
label: ' Mechanics ',
|
|
1402
|
+
scrollable: true,
|
|
1403
|
+
alwaysScroll: true,
|
|
1404
|
+
tags: true,
|
|
1405
|
+
});
|
|
1406
|
+
this._metaBox = metaBox;
|
|
1407
|
+
screen.append(metaBox);
|
|
1408
|
+
}
|
|
649
1409
|
/**
|
|
650
1410
|
* The HUD: a fixed strip between the log and the input box for the
|
|
651
1411
|
* important real-time state -- condition tracks, armed/link status,
|
|
@@ -762,6 +1522,30 @@ export default class Game {
|
|
|
762
1522
|
if (player.activeCallPartner) {
|
|
763
1523
|
lines.push(`{${CALL_COLOR}-fg}${CALL_ICON} On a call with ${player.activeCallPartner.name}{/${CALL_COLOR}-fg}`);
|
|
764
1524
|
}
|
|
1525
|
+
// The party: one line per live companion shell, with the tracks that
|
|
1526
|
+
// matter for its kind. A spirit shows its BODY too (it's a full
|
|
1527
|
+
// Force-statted combatant and hits land on it -- player request:
|
|
1528
|
+
// the track was real but invisible); drones show hull, agents deck.
|
|
1529
|
+
for (const c of this.companions) {
|
|
1530
|
+
const track = c.kind === 'spirit'
|
|
1531
|
+
? `${c.services ?? 0} service${(c.services ?? 0) === 1 ? '' : 's'} ${c.npc.conditionSummary()}${c.npc.stunTaken > 0 ? ` stun ${c.npc.stunSummary()}` : ''}`
|
|
1532
|
+
: c.kind === 'drone' && c.boundDevice ? `hull ${c.boundDevice.droneSummary()}`
|
|
1533
|
+
: c.kind === 'agent' && c.boundDevice ? `deck ${c.boundDevice.deckSummary()}`
|
|
1534
|
+
: c.npc.conditionSummary();
|
|
1535
|
+
// Each kind speaks its own language (a real rigger session read a
|
|
1536
|
+
// generic "(F3)" as spell Force on a Fly-Spy): drones fly on a
|
|
1537
|
+
// PILOT rating, agents run at a RATING, and a spirit's Force is
|
|
1538
|
+
// already in its name.
|
|
1539
|
+
const grade = c.kind === 'drone' ? `Pilot ${c.force} -- ` : c.kind === 'agent' ? `Rating ${c.force} -- ` : '';
|
|
1540
|
+
lines.push(`{green-fg}👥 ${c.npc.name} -- ${grade}${track}{/green-fg}`);
|
|
1541
|
+
}
|
|
1542
|
+
// DECK-AR INDICATOR (player request): "Link online" is the
|
|
1543
|
+
// COMMLINK's AR overlay; this is the DECK's -- lit when an agent
|
|
1544
|
+
// works the grid while you're back in the meat (the AR-drop exit).
|
|
1545
|
+
const remoteAgent = this.companions.find(c => c.kind === 'agent');
|
|
1546
|
+
if (remoteAgent && player.plane !== 'matrix') {
|
|
1547
|
+
lines.push(`{cyan-fg}▣ DECK IN AR -- ${remoteAgent.npc.name} on the grid${remoteAgent.npc.objective ? ', working its directive' : ''} ("jack in" to rejoin it, "recall" to fold it home){/cyan-fg}`);
|
|
1548
|
+
}
|
|
765
1549
|
// Off-plane context: which reality you're in, your persona's Matrix
|
|
766
1550
|
// track (cold-sim damage lands there), and where your empty body lies.
|
|
767
1551
|
if (player.plane === 'matrix') {
|
|
@@ -776,6 +1560,15 @@ export default class Game {
|
|
|
776
1560
|
else if (player.plane === 'astral') {
|
|
777
1561
|
lines.push(`{magenta-fg}✧ ASTRAL -- ${player.astralTicks} move${player.astralTicks === 1 ? '' : 's'} out -- body at ${player.bodyRoom?.name ?? '???'}{/magenta-fg}`);
|
|
778
1562
|
}
|
|
1563
|
+
else if (player.plane === 'drone') {
|
|
1564
|
+
// Jumped-in: the HULL is the track that matters (attacks land there,
|
|
1565
|
+
// half bleeding through the link), so it lives on the HUD alongside
|
|
1566
|
+
// where the empty body was left.
|
|
1567
|
+
const drone = player.riggedDrone;
|
|
1568
|
+
const hull = drone ? ` Hull ${drone.droneSummary()}` : '';
|
|
1569
|
+
const mount = player.droneArmed ? ' ⌖ armed' : ' (recon -- no mounts)';
|
|
1570
|
+
lines.push(`{yellow-fg}✈ JUMPED IN: ${drone?.name ?? 'drone'} ${(player.simMode ?? 'cold').toUpperCase()}-SIM${mount}${hull} -- body at ${player.bodyRoom?.name ?? '???'}{/yellow-fg}`);
|
|
1571
|
+
}
|
|
779
1572
|
this._statusBox.setContent(lines.join('\n'));
|
|
780
1573
|
this._screen.render();
|
|
781
1574
|
}
|
|
@@ -860,7 +1653,10 @@ export default class Game {
|
|
|
860
1653
|
return `I don't understand "${verb}" -- did you mean "${guess}"?`;
|
|
861
1654
|
}
|
|
862
1655
|
const corrected = await this.handleInput([guess, ...args].join(' '));
|
|
863
|
-
|
|
1656
|
+
// The correction note is bookkeeping -- Mechanics ticker, not the
|
|
1657
|
+
// narrative (readability pass).
|
|
1658
|
+
Logger.getInstance().meta(`(read "${verb}" as "${guess}")`);
|
|
1659
|
+
return corrected;
|
|
864
1660
|
}
|
|
865
1661
|
return `I don't understand the command "${verb}".`;
|
|
866
1662
|
}
|
|
@@ -876,12 +1672,13 @@ export default class Game {
|
|
|
876
1672
|
static HELP_CATEGORIES = [
|
|
877
1673
|
{ title: 'moving', entries: [['look'], ['go'], ['map', 'exits'], ['search'], ['sneak']] },
|
|
878
1674
|
{ 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']] },
|
|
1675
|
+
{ title: 'combat', entries: [['attack', 'kill'], ['subdue'], ['order'], ['stance'], ['surrender'], ['edge'], ['rest']] },
|
|
1676
|
+
{ title: 'magic', entries: [['spells'], ['cast'], ['summon', 'conjure'], ['project'], ['return'], ['assense']] },
|
|
1677
|
+
{ title: 'matrix', entries: [['jack'], ['hack'], ['pan'], ['silent'], ['reboot'], ['agent'], ['drone'], ['deploy'], ['recall'], ['jump', 'rig']] },
|
|
1678
|
+
{ title: 'social', entries: [['say'], ['talk-to'], ['call'], ['end-call', 'end', 'hangup'], ['haggle', 'negotiate'], ['persuade', 'convince'], ['ask'], ['contacts']] },
|
|
1679
|
+
{ title: 'street', entries: [['browse', 'buy'], ['sell'], ['work', 'gig'], ['treat'], ['eat'], ['drink'], ['lifestyle'], ['pay']] },
|
|
883
1680
|
{ title: 'barriers', entries: [['solve'], ['pick'], ['bluff'], ['unlock'], ['lock']] },
|
|
884
|
-
{ title: 'character', entries: [['sheet', 'stats'], ['advance', 'train']] },
|
|
1681
|
+
{ title: 'character', entries: [['sheet', 'stats'], ['advance', 'train'], ['qualities', 'quality'], ['initiate', 'submerge'], ['install', 'chrome']] },
|
|
885
1682
|
{ title: 'session', entries: [['help'], ['clear'], ['quit', 'exit']] },
|
|
886
1683
|
];
|
|
887
1684
|
/** Descriptions for the verbs handled inline in handleInput() rather than registered. */
|
|
@@ -910,7 +1707,7 @@ export default class Game {
|
|
|
910
1707
|
const category = Game.HELP_CATEGORIES.find(c => c.title === pickedCategory);
|
|
911
1708
|
const lines = [`=== ${category.title.toUpperCase()} ===`];
|
|
912
1709
|
for (const [primary, ...aliases] of category.entries) {
|
|
913
|
-
lines.push(
|
|
1710
|
+
lines.push(`${BULLET}${primary}${aliases.length > 0 ? ` (${aliases.join(', ')})` : ''}: ${this.describeVerb(primary)}`);
|
|
914
1711
|
}
|
|
915
1712
|
return lines.join('\n');
|
|
916
1713
|
}
|