@maka/maka-cli 5.4.0 → 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 +20 -1
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +76 -11
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -1
- 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 +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js +72 -29
- package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +21 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js.map +1 -1
- 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.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +71 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +21 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js +13 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js.map +1 -1
- 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/equip.js +19 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js +9 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.d.ts +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +33 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +43 -20
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
- 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.js +5 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/inv.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +16 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js.map +1 -1
- 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 +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js +22 -7
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js +8 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +55 -8
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +11 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js.map +1 -1
- 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/spells.js +21 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.js +4 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +70 -13
- 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/use.js +5 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js +1 -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.js +1 -0
- 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 +64 -9
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js +265 -434
- 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 +69 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.js +388 -67
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +92 -3
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +66 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +151 -15
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js +29 -4
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/room.d.ts +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js +14 -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 +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +35 -3
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene1.json +275 -105
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +662 -233
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/player-seed.d.ts +8 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/puzzle-seed.d.ts +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/room-seed.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/scene-seed.d.ts +6 -3
- package/bundle/typescript/src/commands/game/sideQuest/ui.js +19 -20
- package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.d.ts +6 -4
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +31 -5
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.d.ts +34 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +72 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +27 -3
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/tools/ai/ai.class.d.ts +8 -1
- package/bundle/typescript/src/tools/ai/ai.class.js +8 -3
- package/bundle/typescript/src/tools/ai/ai.class.js.map +1 -1
- package/bundle/typescript/src/tools/https/https.class.d.ts +2 -2
- package/bundle/typescript/src/tools/https/https.class.js +10 -4
- package/bundle/typescript/src/tools/https/https.class.js.map +1 -1
- package/bundle/typescript/src/tools/https/https.interface.d.ts +2 -0
- package/package.json +1 -1
|
@@ -33,6 +33,7 @@ import { EdgeCommand } from './commands/edge.js';
|
|
|
33
33
|
import { SheetCommand } from './commands/sheet.js';
|
|
34
34
|
import { CallCommand } from './commands/call.js';
|
|
35
35
|
import { EndCallCommand } from './commands/end-call.js';
|
|
36
|
+
import { fuzzyPickName, levenshteinDistance } from './utilities/fuzzy-match.js';
|
|
36
37
|
import { PickCommand } from './commands/pick.js';
|
|
37
38
|
import { HackCommand } from './commands/hack.js';
|
|
38
39
|
import { CastCommand } from './commands/cast.js';
|
|
@@ -44,6 +45,14 @@ import { AssenseCommand } from './commands/assense.js';
|
|
|
44
45
|
import { DroneCommand } from './commands/drone.js';
|
|
45
46
|
import { PanCommand } from './commands/pan.js';
|
|
46
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';
|
|
47
56
|
import { AutoFight } from './utilities/auto-fight.js';
|
|
48
57
|
import { CommandFactory } from './factories/command-factory.js';
|
|
49
58
|
import { SceneSynthesizer } from './factories/scene-factory.js';
|
|
@@ -78,6 +87,91 @@ export default class Game {
|
|
|
78
87
|
// player-location state out from under whatever the player was already
|
|
79
88
|
// doing in it.
|
|
80
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
|
+
}
|
|
81
175
|
// A single fixed room, created once and never torn down across job
|
|
82
176
|
// transitions -- the player's own place, unlike every job's generated
|
|
83
177
|
// rooms which get discarded wholesale when the next job replaces them.
|
|
@@ -138,6 +232,22 @@ export default class Game {
|
|
|
138
232
|
CommandFactory.registerCommand('drone', DroneCommand);
|
|
139
233
|
CommandFactory.registerCommand('pan', PanCommand);
|
|
140
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);
|
|
141
251
|
}
|
|
142
252
|
determineStartRoom() {
|
|
143
253
|
return this.scene.determineStartRoom();
|
|
@@ -180,32 +290,19 @@ export default class Game {
|
|
|
180
290
|
this.initInputBox(screen);
|
|
181
291
|
this.initLogBox(screen);
|
|
182
292
|
this.initStatusBox(screen);
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
content: 'Generating Scene... Please wait.',
|
|
191
|
-
border: { type: 'line' },
|
|
192
|
-
style: {
|
|
193
|
-
fg: 'white',
|
|
194
|
-
bg: 'black',
|
|
195
|
-
border: { fg: 'cyan' },
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
screen.append(loadingBox);
|
|
199
|
-
screen.render();
|
|
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.
|
|
200
300
|
try {
|
|
201
|
-
this.scene = await SceneSynthesizer.synthesizeFromJson(this.sceneSeed, this.player,
|
|
301
|
+
this.scene = await SceneSynthesizer.synthesizeFromJson(this.sceneSeed, this.player, false);
|
|
202
302
|
}
|
|
203
303
|
catch (err) {
|
|
204
304
|
throw new Error(`Scene generation failed: ${err.message}`);
|
|
205
305
|
}
|
|
206
|
-
// Remove loading box
|
|
207
|
-
screen.remove(loadingBox);
|
|
208
|
-
screen.render();
|
|
209
306
|
this.scene.initialize(this);
|
|
210
307
|
this.player.currentLocation = this.determineStartRoom();
|
|
211
308
|
this.registerCommands();
|
|
@@ -217,6 +314,24 @@ export default class Game {
|
|
|
217
314
|
});
|
|
218
315
|
this.scene.addRoom(this._homeRoom);
|
|
219
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();
|
|
220
335
|
this.initialized = true;
|
|
221
336
|
// Nudge the player toward "call" up front -- the fixer convention means
|
|
222
337
|
// the story deliberately doesn't narrate a completed briefing, so
|
|
@@ -235,9 +350,14 @@ export default class Game {
|
|
|
235
350
|
*/
|
|
236
351
|
canDraftNextScene() {
|
|
237
352
|
if (this._draftInProgress) {
|
|
238
|
-
return { allowed: false, message: `
|
|
353
|
+
return { allowed: false, message: `Still lining it up -- I'll ping you.` };
|
|
239
354
|
}
|
|
240
|
-
if (
|
|
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()) {
|
|
241
361
|
return { allowed: false, message: `This job isn't finished yet.` };
|
|
242
362
|
}
|
|
243
363
|
if (!this._useAi) {
|
|
@@ -274,6 +394,13 @@ export default class Game {
|
|
|
274
394
|
* instant feedback, but this stays safe to call directly too).
|
|
275
395
|
*/
|
|
276
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
|
+
}
|
|
277
404
|
const gate = this.canDraftNextScene();
|
|
278
405
|
if (!gate.allowed) {
|
|
279
406
|
return gate.message ?? `This job isn't finished yet.`;
|
|
@@ -286,6 +413,44 @@ export default class Game {
|
|
|
286
413
|
this._draftInProgress = false;
|
|
287
414
|
}
|
|
288
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
|
+
}
|
|
289
454
|
/**
|
|
290
455
|
* The actual drafting work, split out from continueToNextScene() purely
|
|
291
456
|
* so that method can wrap it in the try/finally that clears
|
|
@@ -309,40 +474,99 @@ export default class Game {
|
|
|
309
474
|
// seconds for a tiered scene with AI flavor enabled. Framed as the
|
|
310
475
|
// fixer's own line when he's actually in this scene, rather than a
|
|
311
476
|
// bare system message -- he's the one drumming up the next gig.
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
this
|
|
318
|
-
|
|
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);
|
|
319
485
|
if (!nextSeed) {
|
|
320
|
-
return `
|
|
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."`;
|
|
321
487
|
}
|
|
322
|
-
|
|
323
|
-
|
|
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;
|
|
324
510
|
// A call partner from the old scene wouldn't exist in the new one --
|
|
325
511
|
// hang up before moving on, rather than leave a dangling reference.
|
|
326
512
|
if (this.player.activeCallPartner) {
|
|
513
|
+
this.player.activeCallPartner.activeCallPartner = undefined;
|
|
327
514
|
this.player.activeCallPartner = undefined;
|
|
328
515
|
}
|
|
329
|
-
// false:
|
|
330
|
-
//
|
|
331
|
-
|
|
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);
|
|
332
520
|
this.scene.initialize(this);
|
|
333
521
|
this.player.currentLocation = this.determineStartRoom();
|
|
334
522
|
// New job, fresh luck (see commands/edge.ts).
|
|
335
523
|
this.player.refreshEdge();
|
|
336
524
|
// The home base is owned by Game, not any one Scene -- re-inject it and
|
|
337
|
-
// rewire its one exit to point at this job's start room
|
|
338
|
-
// just-abandoned one.
|
|
525
|
+
// rewire its one exit to point at this job's start room.
|
|
339
526
|
this.scene.addRoom(this._homeRoom);
|
|
340
527
|
this.connectHomeRoom(this.player.currentLocation);
|
|
528
|
+
if (opts.startInHideout) {
|
|
529
|
+
this.player.currentLocation = this._homeRoom;
|
|
530
|
+
}
|
|
341
531
|
this.updateEquipment(this.player.equipment);
|
|
342
532
|
this.updateInventory(this.player.inventory, capitalCase(this.scene.getCurrencyType()));
|
|
343
533
|
this.updateExits(this.player.currentLocation);
|
|
344
534
|
this.updateStatus();
|
|
345
|
-
|
|
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
|
+
}
|
|
346
570
|
}
|
|
347
571
|
async close() {
|
|
348
572
|
this._inputBox.removeAllListeners(); // removes all event listeners
|
|
@@ -471,8 +695,13 @@ export default class Game {
|
|
|
471
695
|
// is illegible enough that the stance looked absent entirely.
|
|
472
696
|
const stanceColor = player.autoStance === 'aggressive' ? 'red' : player.autoStance === 'guarded' ? 'yellow' : 'white';
|
|
473
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}) > ` : '';
|
|
474
703
|
if (player.currentLocation) {
|
|
475
|
-
lines.push(
|
|
704
|
+
lines.push(`${crumb}{bold}📍 ${player.currentLocation.name}{/bold} | ${stance}`);
|
|
476
705
|
}
|
|
477
706
|
else {
|
|
478
707
|
lines.push(stance);
|
|
@@ -493,15 +722,22 @@ export default class Game {
|
|
|
493
722
|
// actually live (equipped), the recurring "why can't I call anyone"
|
|
494
723
|
// friction surfaced at a glance.
|
|
495
724
|
const weapon = player.getWeaponProfile();
|
|
496
|
-
const weaponItem = player.
|
|
725
|
+
const weaponItem = player.getCarriedWeapon();
|
|
497
726
|
const ammoNote = weaponItem?.isFirearm()
|
|
498
727
|
? (weaponItem.jammed
|
|
499
728
|
? ` {red-fg}[JAMMED -- reboot]{/red-fg}`
|
|
500
729
|
: weaponItem.ammo === 0 ? ` {red-fg}[DRY]{/red-fg}` : ` [${weaponItem.ammo} rds]`)
|
|
501
730
|
: '';
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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}`;
|
|
505
741
|
const armorNote = `Armor +${player.getArmorValue()}`;
|
|
506
742
|
const hasLink = player.equipment.head?.commlink != null;
|
|
507
743
|
const link = !hasLink
|
|
@@ -517,6 +753,12 @@ export default class Game {
|
|
|
517
753
|
if (player.sustainedArmor > 0) {
|
|
518
754
|
lines.push(`{magenta-fg}Sustained: armor +${player.sustainedArmor} soak (-2 dice to everything else){/magenta-fg}`);
|
|
519
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
|
+
}
|
|
520
762
|
if (player.activeCallPartner) {
|
|
521
763
|
lines.push(`{${CALL_COLOR}-fg}${CALL_ICON} On a call with ${player.activeCallPartner.name}{/${CALL_COLOR}-fg}`);
|
|
522
764
|
}
|
|
@@ -562,7 +804,10 @@ export default class Game {
|
|
|
562
804
|
return Game.instance;
|
|
563
805
|
}
|
|
564
806
|
async handleInput(input) {
|
|
565
|
-
|
|
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(' ');
|
|
566
811
|
const verb = parts[0].toLowerCase();
|
|
567
812
|
const args = parts.slice(1);
|
|
568
813
|
if (verb === 'quit' || verb === 'exit') {
|
|
@@ -572,19 +817,13 @@ export default class Game {
|
|
|
572
817
|
return 'You are still in the game, type "quit" to exit';
|
|
573
818
|
}
|
|
574
819
|
if (verb === 'continue') {
|
|
575
|
-
//
|
|
576
|
-
//
|
|
577
|
-
//
|
|
578
|
-
|
|
579
|
-
const gate = this.canDraftNextScene();
|
|
580
|
-
if (!gate.allowed) {
|
|
581
|
-
return gate.message ?? `This job isn't finished yet.`;
|
|
582
|
-
}
|
|
583
|
-
this.draftNextSceneInBackground();
|
|
584
|
-
return `Understood -- drafting the next job now. Feel free to keep exploring in the meantime.`;
|
|
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.`;
|
|
585
824
|
}
|
|
586
825
|
if (verb === 'help') {
|
|
587
|
-
return this.getHelpMessage();
|
|
826
|
+
return this.getHelpMessage(args);
|
|
588
827
|
}
|
|
589
828
|
if (verb === 'clear') {
|
|
590
829
|
return '';
|
|
@@ -601,21 +840,103 @@ export default class Game {
|
|
|
601
840
|
const result = await command.execute(args);
|
|
602
841
|
return result;
|
|
603
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
|
+
}
|
|
604
865
|
return `I don't understand the command "${verb}".`;
|
|
605
866
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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');
|
|
613
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(', ')}`);
|
|
614
936
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
return helpText;
|
|
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');
|
|
619
940
|
}
|
|
620
941
|
}
|
|
621
942
|
//# sourceMappingURL=game.js.map
|