@maka/maka-cli 5.17.16 → 5.17.18
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.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.d.ts +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +35 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/command.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/command.js +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/command.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/compile.d.ts +27 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/compile.js +8 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/compile.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/crew.d.ts +12 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/crew.js +69 -79
- package/bundle/typescript/src/commands/game/sideQuest/commands/crew.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js +4 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/players.d.ts +4 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/players.js +6 -9
- package/bundle/typescript/src/commands/game/sideQuest/commands/players.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/register.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/register.js +74 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/register.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js +18 -9
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/thread.d.ts +17 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/thread.js +165 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/thread.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.d.ts +19 -0
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +22 -0
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +19 -4
- package/bundle/typescript/src/commands/game/sideQuest/game.js +149 -49
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +10 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +20 -4
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/ui.d.ts +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.d.ts +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js +13 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/session-telemetry.js +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/session-telemetry.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/shared-run.d.ts +12 -7
- package/bundle/typescript/src/commands/game/sideQuest/utilities/shared-run.js +29 -17
- package/bundle/typescript/src/commands/game/sideQuest/utilities/shared-run.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/social.d.ts +1 -30
- package/bundle/typescript/src/commands/game/sideQuest/utilities/social.js +4 -89
- package/bundle/typescript/src/commands/game/sideQuest/utilities/social.js.map +1 -1
- package/package.json +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/crew-ledger.d.ts +0 -21
- package/bundle/typescript/src/commands/game/sideQuest/utilities/crew-ledger.js +0 -25
- package/bundle/typescript/src/commands/game/sideQuest/utilities/crew-ledger.js.map +0 -1
|
@@ -6,8 +6,7 @@ import { Room } from './models/_index.js';
|
|
|
6
6
|
import { Door } from './models/door.js';
|
|
7
7
|
import { SAVE_VERSION } from './types/save-file.js';
|
|
8
8
|
import { serializePlayer, restorePlayer, captureHubOverlay, applyHubOverlay, writeSaveAtomic, deleteSave, restoreItem, readSave, serializeItem, saveSlug, } from './utilities/persistence.js';
|
|
9
|
-
import {
|
|
10
|
-
import { heartbeatPresence, heartbeatPresenceNow, ghostHireable, syncCrewLedger, claimGhostEarnings } from './utilities/social.js';
|
|
9
|
+
import { heartbeatPresence, heartbeatPresenceNow } from './utilities/social.js';
|
|
11
10
|
import { pushSave, burnCloudSaveFor } from './utilities/cloud-saves.js';
|
|
12
11
|
import { DOCWAGON_TIERS, RESUSC_FEE, DEATH_COMP } from './utilities/docwagon.js';
|
|
13
12
|
import { isFence, LIFESTYLE_TIERS } from './utilities/commerce.js';
|
|
@@ -77,7 +76,9 @@ import { ContactsCommand } from './commands/contacts.js';
|
|
|
77
76
|
import { JumpCommand } from './commands/jump.js';
|
|
78
77
|
import { InitiateCommand } from './commands/initiate.js';
|
|
79
78
|
import { SummonCommand } from './commands/summon.js';
|
|
80
|
-
import { CompileCommand } from './commands/compile.js';
|
|
79
|
+
import { CompileCommand, SPRITE_TYPES } from './commands/compile.js';
|
|
80
|
+
import { ThreadCommand } from './commands/thread.js';
|
|
81
|
+
import { RegisterCommand } from './commands/register.js';
|
|
81
82
|
import { OrderCommand } from './commands/order.js';
|
|
82
83
|
import { DeployCommand, AgentCommand, RecallCommand } from './commands/companions.js';
|
|
83
84
|
import { DeckCommand, LoadProgramCommand, UnloadProgramCommand } from './commands/deck.js';
|
|
@@ -133,6 +134,11 @@ export default class Game {
|
|
|
133
134
|
_inputBox;
|
|
134
135
|
_useAi = false;
|
|
135
136
|
_aiRefusalAnnounced = false;
|
|
137
|
+
// ACCOUNT BINDING (ruling 2026-08-23): who this runner belongs to,
|
|
138
|
+
// and whether they're explicitly account-less. Anonymous = the whole
|
|
139
|
+
// social layer stays dark (presence, cloud mirror, street, drafts).
|
|
140
|
+
_owner;
|
|
141
|
+
_anonymous = false;
|
|
136
142
|
_logFilePath = '';
|
|
137
143
|
_tier = 1;
|
|
138
144
|
// Guards against two overlapping continueToNextScene() calls -- found via
|
|
@@ -222,9 +228,9 @@ export default class Game {
|
|
|
222
228
|
// talent pool churns; whoever you HIRED is the persistent part.
|
|
223
229
|
crewForHire = [];
|
|
224
230
|
// PRESENCE (social layer, Phase B): a hub heartbeat announces this
|
|
225
|
-
// runner to the street's player list on maka-cli.com
|
|
226
|
-
// the
|
|
227
|
-
//
|
|
231
|
+
// runner to the street's player list on maka-cli.com. Only beats
|
|
232
|
+
// while the scene IS the hub (the run is nobody's business); silent
|
|
233
|
+
// no-op logged out.
|
|
228
234
|
_presenceTimer;
|
|
229
235
|
static PRESENCE_TICK_MS = 60_000;
|
|
230
236
|
presencePayload() {
|
|
@@ -232,7 +238,6 @@ export default class Game {
|
|
|
232
238
|
playerName: this.player.name,
|
|
233
239
|
slug: saveSlug(this.player.name),
|
|
234
240
|
hubName: this._hubName,
|
|
235
|
-
ghostHireable: ghostHireable(),
|
|
236
241
|
// ONLINE means the LINK is live (player ruling): commlink equipped
|
|
237
242
|
// and broadcasting. Pull the link or run silent -> off the street.
|
|
238
243
|
linkUp: this.player.equipment.head?.commlink != null && !this.player.runningSilent,
|
|
@@ -276,32 +281,19 @@ export default class Game {
|
|
|
276
281
|
* push one right now and say so.
|
|
277
282
|
*/
|
|
278
283
|
async pushPresenceNow() {
|
|
284
|
+
if (this._anonymous)
|
|
285
|
+
return 'offline';
|
|
279
286
|
return heartbeatPresenceNow(this.presencePayload());
|
|
280
287
|
}
|
|
281
288
|
startPresenceHeartbeat() {
|
|
289
|
+
if (this._anonymous)
|
|
290
|
+
return; // account-less: never on the street
|
|
282
291
|
if (this._presenceTimer)
|
|
283
292
|
return;
|
|
284
293
|
this.beatPresence();
|
|
285
294
|
this._presenceTimer = setInterval(() => this.beatPresence(), Game.PRESENCE_TICK_MS);
|
|
286
295
|
this._presenceTimer.unref();
|
|
287
296
|
}
|
|
288
|
-
/** Ghost earnings home delivery: what YOUR runner made riding in
|
|
289
|
-
* other players' crews since last time. Fire-and-forget from hub
|
|
290
|
-
* arrival beats; pays onto the job credstick when anything landed. */
|
|
291
|
-
async collectGhostEarnings() {
|
|
292
|
-
try {
|
|
293
|
-
const claimed = await claimGhostEarnings(saveSlug(this.player.name));
|
|
294
|
-
if (!claimed || this.scene !== this._hubScene)
|
|
295
|
-
return;
|
|
296
|
-
const paid = this.scene.payRunner(claimed.total, 'ghost earnings');
|
|
297
|
-
if (!paid)
|
|
298
|
-
return;
|
|
299
|
-
const jobs = claimed.entries.map(e => `"${e.job}" with ${e.hiredBy} (${e.amount}¥)`).join(', ');
|
|
300
|
-
Logger.getInstance().logWithColor(`While you were gone, your runner rode other crews: ${jobs}. ${paid.net}¥ certified lands on your stick.`, 'green');
|
|
301
|
-
this.updateStatus();
|
|
302
|
-
}
|
|
303
|
-
catch { /* offline -- next homecoming */ }
|
|
304
|
-
}
|
|
305
297
|
static HIRE_NAMES = [
|
|
306
298
|
'Jax', 'Sable', 'Vex', 'Rook', 'Nyx', 'Cinder', 'Halo', 'Grifter',
|
|
307
299
|
'Static', 'Mercy', 'Tinder', 'Chrome Marie', 'Deacon', 'Fetch', 'Loa',
|
|
@@ -451,9 +443,6 @@ export default class Game {
|
|
|
451
443
|
const cut = Math.floor(gross * (member.cutPct * scale) / 100);
|
|
452
444
|
taken += cut;
|
|
453
445
|
parts.push(`${member.name} ${member.cutPct}% (${cut}¥)`);
|
|
454
|
-
if (member.ghost) {
|
|
455
|
-
appendCrewLedger({ slug: member.ghost.slug, owner: member.ghost.owner, amount: cut, job: this.currentJobName ?? 'a job', when: new Date().toISOString() });
|
|
456
|
-
}
|
|
457
446
|
}
|
|
458
447
|
return { share: gross - taken, line: `Crew cut: ${parts.join(', ')}.` };
|
|
459
448
|
}
|
|
@@ -723,6 +712,9 @@ export default class Game {
|
|
|
723
712
|
// death both remove the entry; persistent state (the frame, the deck)
|
|
724
713
|
// lives on Items in the player's possession, never here.
|
|
725
714
|
companions = [];
|
|
715
|
+
/** A registered sprite folded for travel, waiting to re-shell at the
|
|
716
|
+
* destination (the drone _pendingRedeploys school). */
|
|
717
|
+
_pendingSpriteRedeploy;
|
|
726
718
|
// Drones deployed at travel time re-shell on arrival ("the rotors spin
|
|
727
719
|
// back up at the curb") -- tracked as their items across the swap.
|
|
728
720
|
_pendingRedeploys = [];
|
|
@@ -924,7 +916,15 @@ export default class Game {
|
|
|
924
916
|
lines.push(`Your bound spirit thins into the rain -- the summons doesn't outlast the ride.`);
|
|
925
917
|
}
|
|
926
918
|
else if (entry.kind === 'sprite') {
|
|
927
|
-
|
|
919
|
+
// A REGISTERED sprite persists (register.ts): it folds into
|
|
920
|
+
// your PAN for the ride and re-shells at the curb.
|
|
921
|
+
if (entry.registered && entry.spriteType) {
|
|
922
|
+
this._pendingSpriteRedeploy = { spriteType: entry.spriteType, level: entry.force, tasks: entry.services ?? 0 };
|
|
923
|
+
lines.push(`${entry.npc.name} folds into your PAN for the ride -- registered code travels with you.`);
|
|
924
|
+
}
|
|
925
|
+
else {
|
|
926
|
+
lines.push(`Your sprite decompiles into clean static -- the thread doesn't outlast the ride.`);
|
|
927
|
+
}
|
|
928
928
|
}
|
|
929
929
|
else if (entry.kind === 'drone') {
|
|
930
930
|
lines.push(`The ${entry.boundDevice?.name ?? 'drone'} autopilots home to your hand for the ride.`);
|
|
@@ -951,8 +951,42 @@ export default class Game {
|
|
|
951
951
|
if (shell)
|
|
952
952
|
lines.push(`The ${item.name}'s rotors spin back up at the curb.`);
|
|
953
953
|
}
|
|
954
|
+
// A REGISTERED sprite re-shells from the PAN (register.ts).
|
|
955
|
+
if (this._pendingSpriteRedeploy) {
|
|
956
|
+
const spec = this._pendingSpriteRedeploy;
|
|
957
|
+
this._pendingSpriteRedeploy = undefined;
|
|
958
|
+
const line = this.respawnRegisteredSprite(spec);
|
|
959
|
+
if (line)
|
|
960
|
+
lines.push(line);
|
|
961
|
+
}
|
|
954
962
|
return lines;
|
|
955
963
|
}
|
|
964
|
+
/** Re-shell a registered sprite (travel arrival, or a resumed save).
|
|
965
|
+
* Returns the arrival line, or undefined when the shell can't land
|
|
966
|
+
* (name collision -- vanishingly rare). */
|
|
967
|
+
respawnRegisteredSprite(spec) {
|
|
968
|
+
const type = SPRITE_TYPES[spec.spriteType];
|
|
969
|
+
if (!type)
|
|
970
|
+
return undefined;
|
|
971
|
+
const shell = this.spawnCompanionShell({
|
|
972
|
+
name: `${type.name} Sprite (Level ${spec.level})`,
|
|
973
|
+
description: `A registered ${type.name.toLowerCase()} sprite riding ${this.player.name}'s PAN -- ${type.blurb}. It serves until its ${spec.tasks} task${spec.tasks === 1 ? '' : 's'} are spent.`,
|
|
974
|
+
kind: 'sprite',
|
|
975
|
+
combat: type.combat(spec.level),
|
|
976
|
+
room: this.player.currentLocation,
|
|
977
|
+
plane: 'matrix',
|
|
978
|
+
force: spec.level,
|
|
979
|
+
services: spec.tasks,
|
|
980
|
+
});
|
|
981
|
+
if (!shell)
|
|
982
|
+
return undefined;
|
|
983
|
+
const entry = this.companions.find(c => c.npc === shell);
|
|
984
|
+
if (entry) {
|
|
985
|
+
entry.registered = true;
|
|
986
|
+
entry.spriteType = spec.spriteType;
|
|
987
|
+
}
|
|
988
|
+
return `${shell.name} unfolds from your PAN, luminous and patient -- ${spec.tasks} task${spec.tasks === 1 ? '' : 's'} standing.`;
|
|
989
|
+
}
|
|
956
990
|
/**
|
|
957
991
|
* Records a real transaction with a hub NPC: first contact opens the
|
|
958
992
|
* relationship at Loyalty 1 (a safeguard -- the hub cast is pre-seeded
|
|
@@ -1210,6 +1244,8 @@ export default class Game {
|
|
|
1210
1244
|
/** The transport, hooks-first, CLI default second; undefined =
|
|
1211
1245
|
* offline/logged out/classic mode (callers degrade quietly). */
|
|
1212
1246
|
async streetTransport() {
|
|
1247
|
+
if (this._anonymous)
|
|
1248
|
+
return undefined; // account-less: no street
|
|
1213
1249
|
if (this.listStreetPlayers && this.sendPage) {
|
|
1214
1250
|
return { list: this.listStreetPlayers, send: this.sendPage };
|
|
1215
1251
|
}
|
|
@@ -1242,7 +1278,11 @@ export default class Game {
|
|
|
1242
1278
|
if (!channel)
|
|
1243
1279
|
return undefined;
|
|
1244
1280
|
player.crewChannel = undefined;
|
|
1245
|
-
|
|
1281
|
+
// The PARTY LINE is a conference: dropping off is local, nobody
|
|
1282
|
+
// else's line dies. Only a 1:1 call signals the far side.
|
|
1283
|
+
if (!channel.party && channel.handle) {
|
|
1284
|
+
void this.streetTransport().then(t => t?.send({ from: player.name, handle: channel.handle, text: END_CALL_SENTINEL })).catch(() => undefined);
|
|
1285
|
+
}
|
|
1246
1286
|
return channel.playerName;
|
|
1247
1287
|
}
|
|
1248
1288
|
/** Trash the offer wholesale ("decline", and any future fixer snub). */
|
|
@@ -1567,6 +1607,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1567
1607
|
CommandFactory.registerCommand('conjure', SummonCommand);
|
|
1568
1608
|
CommandFactory.registerCommand('compile', CompileCommand);
|
|
1569
1609
|
CommandFactory.registerCommand('decompile', CompileCommand);
|
|
1610
|
+
CommandFactory.registerCommand('thread', ThreadCommand);
|
|
1611
|
+
CommandFactory.registerCommand('register', RegisterCommand);
|
|
1570
1612
|
// "At" spots (utilities/spots.ts): walk/move/approach are one
|
|
1571
1613
|
// crossing; run and sprint are the fast modes (combat semantics in
|
|
1572
1614
|
// the spot-move phase).
|
|
@@ -1676,6 +1718,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1676
1718
|
this._useAi = options.useAi;
|
|
1677
1719
|
this._logFilePath = options.logFilePath;
|
|
1678
1720
|
this._saveFilePath = options.saveFilePath;
|
|
1721
|
+
this._owner = options.owner;
|
|
1722
|
+
this._anonymous = options.anonymousRunner === true;
|
|
1679
1723
|
// ONE loud notice the moment the AI provider refuses on ACCOUNT
|
|
1680
1724
|
// grounds (empty credits, bad key). Without it a dead key mutes
|
|
1681
1725
|
// every NPC and makes every job "fall through" invisibly -- a real
|
|
@@ -1751,16 +1795,12 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1751
1795
|
// First deal of the for-hire board (rotates each homecoming).
|
|
1752
1796
|
this.rollHireCandidates();
|
|
1753
1797
|
// The street learns you're home (social layer): presence beats,
|
|
1754
|
-
//
|
|
1755
|
-
//
|
|
1756
|
-
// link (run invites, shared sessions -- Phase C) warms up behind
|
|
1757
|
-
// them.
|
|
1798
|
+
// and the DDP hub link (run invites, shared sessions -- Phase C)
|
|
1799
|
+
// warms up behind them. All fire-and-forget.
|
|
1758
1800
|
this.startPresenceHeartbeat();
|
|
1759
1801
|
void import('./utilities/shared-run.js')
|
|
1760
1802
|
.then(m => m.ensureHubLink(this))
|
|
1761
1803
|
.catch(() => undefined);
|
|
1762
|
-
void syncCrewLedger();
|
|
1763
|
-
void this.collectGhostEarnings();
|
|
1764
1804
|
// The next run starts cooking the moment you're home (player
|
|
1765
1805
|
// request): generation takes 20-40s, and waiting for the player
|
|
1766
1806
|
// to CALL first meant "give me a bit" every time. Draft now --
|
|
@@ -1808,6 +1848,15 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1808
1848
|
if (this._hubScene && this.scene === this._hubScene) {
|
|
1809
1849
|
this.requestSave('hub-arrival');
|
|
1810
1850
|
}
|
|
1851
|
+
// A REGISTERED sprite from the save re-shells at the cot (the
|
|
1852
|
+
// technomancer's persistent companion -- register.ts).
|
|
1853
|
+
if (this._pendingSpriteRedeploy && this.scene === this._hubScene) {
|
|
1854
|
+
const spec = this._pendingSpriteRedeploy;
|
|
1855
|
+
this._pendingSpriteRedeploy = undefined;
|
|
1856
|
+
const line = this.respawnRegisteredSprite(spec);
|
|
1857
|
+
if (line)
|
|
1858
|
+
Logger.getInstance().logWithColor(line, 'cyan');
|
|
1859
|
+
}
|
|
1811
1860
|
if (this._resume) {
|
|
1812
1861
|
const pendingNote = this._pendingRunSeed
|
|
1813
1862
|
? hint(`\n\n${FIXER_NAME}'s job is still on offer: "${this._pendingRunSeed.name}". "call taxi" when you're ready, or "call ${FIXER_NAME}" to hear it again.`)
|
|
@@ -1872,7 +1921,16 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1872
1921
|
this.docwagon = save.game.docwagon ? { ...save.game.docwagon } : undefined;
|
|
1873
1922
|
// THE CREW rides the save; the deprecated one-slot enlistedAlly
|
|
1874
1923
|
// migrates in once (old saves) and is never written again.
|
|
1875
|
-
|
|
1924
|
+
// GHOST MIGRATION (mechanic removed 2026-08-23): hired AI copies of
|
|
1925
|
+
// other players' runners dissolve on load -- live seats (liveOnly)
|
|
1926
|
+
// and ordinary hires ride on unchanged.
|
|
1927
|
+
this.crew = (save.game.crew ?? [])
|
|
1928
|
+
.filter(m => !(m.ghost && !m.ghost.liveOnly))
|
|
1929
|
+
.map(m => ({ ...m, gear: [...m.gear] }));
|
|
1930
|
+
// The registered sprite stages for its re-shell at hub arrival.
|
|
1931
|
+
if (save.game.registeredSprite) {
|
|
1932
|
+
this._pendingSpriteRedeploy = { ...save.game.registeredSprite };
|
|
1933
|
+
}
|
|
1876
1934
|
if (this.crew.length === 0 && save.game.enlistedAlly) {
|
|
1877
1935
|
const legacy = save.game.enlistedAlly;
|
|
1878
1936
|
this.crew.push({
|
|
@@ -1942,7 +2000,9 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1942
2000
|
// Local-first: the beat is DONE the moment the file lands. The
|
|
1943
2001
|
// cloud mirror rides behind, fire-and-forget (see cloud-saves.ts
|
|
1944
2002
|
// -- logged-out/offline/expired all degrade to a silent no-op).
|
|
1945
|
-
|
|
2003
|
+
// An ANONYMOUS runner's life never leaves this machine.
|
|
2004
|
+
if (!this._anonymous)
|
|
2005
|
+
void pushSave(this._saveFilePath, save);
|
|
1946
2006
|
}
|
|
1947
2007
|
catch (err) {
|
|
1948
2008
|
logger.error(`requestSave(${reason}) failed: ${err}`);
|
|
@@ -1953,6 +2013,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1953
2013
|
version: SAVE_VERSION,
|
|
1954
2014
|
savedAt: new Date().toISOString(),
|
|
1955
2015
|
reason,
|
|
2016
|
+
owner: this._owner,
|
|
2017
|
+
anonymous: this._anonymous ? true : undefined,
|
|
1956
2018
|
playerName: this.player.name,
|
|
1957
2019
|
summary: {
|
|
1958
2020
|
archetypeName: this.player.archetypeName,
|
|
@@ -1970,6 +2032,14 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1970
2032
|
contacts: [...this.contacts.entries()],
|
|
1971
2033
|
procurements: [...this.pendingProcurements],
|
|
1972
2034
|
docwagon: this.docwagon ? { ...this.docwagon } : undefined,
|
|
2035
|
+
registeredSprite: (() => {
|
|
2036
|
+
// A live registered sprite rides the save; a travel-folded one
|
|
2037
|
+
// is in the redeploy stash. Either way it survives the session.
|
|
2038
|
+
const live = this.companions.find(c => c.kind === 'sprite' && c.registered && c.spriteType);
|
|
2039
|
+
if (live)
|
|
2040
|
+
return { spriteType: live.spriteType, level: live.force, tasks: live.services ?? 0 };
|
|
2041
|
+
return this._pendingSpriteRedeploy ? { ...this._pendingSpriteRedeploy } : undefined;
|
|
2042
|
+
})(),
|
|
1973
2043
|
crew: this.crew.map(m => ({ ...m, gear: [...m.gear] })),
|
|
1974
2044
|
commandRefusals: { ...this.commandRefusals },
|
|
1975
2045
|
fencedCategories: [...this.fencedCategories],
|
|
@@ -2170,11 +2240,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2170
2240
|
// homecoming lines (obituaries, write-back) rode in above. Fresh
|
|
2171
2241
|
// faces on the for-hire board each homecoming.
|
|
2172
2242
|
this.rollHireCandidates();
|
|
2173
|
-
// Social settlement:
|
|
2174
|
-
// come home.
|
|
2243
|
+
// Social settlement: the street hears you're home.
|
|
2175
2244
|
this.beatPresence();
|
|
2176
|
-
void syncCrewLedger();
|
|
2177
|
-
void this.collectGhostEarnings();
|
|
2178
2245
|
const allyLine = crewLines.length > 0 ? `\n${crewLines.join('\n')}` : '';
|
|
2179
2246
|
// DOCWAGON's year ticks in homecomings (12 = a street year, player
|
|
2180
2247
|
// ruling): the contract lapses quietly unless renewed at Doc's.
|
|
@@ -2235,7 +2302,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2235
2302
|
// offer flow (his call, accept/decline, the taxi) works unchanged.
|
|
2236
2303
|
// Offline owners fall back to the Phase B ghost riders below.
|
|
2237
2304
|
let sharedDeclineNote = '';
|
|
2238
|
-
if (this.hasHub) {
|
|
2305
|
+
if (this.hasHub && !this._anonymous) {
|
|
2239
2306
|
const srMod = await import('./utilities/shared-run.js').catch(() => undefined);
|
|
2240
2307
|
const shared = srMod ? await srMod.tryCreateSharedSession(this).catch(() => undefined) : undefined;
|
|
2241
2308
|
if (shared && 'declined' in shared) {
|
|
@@ -2247,7 +2314,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2247
2314
|
// means update THIS machine.
|
|
2248
2315
|
const skew = /runs engine ([\d.]+)/.exec(String(shared.declined));
|
|
2249
2316
|
sharedDeclineNote = skew
|
|
2250
|
-
? ` "Crew channel's scrambled tonight -- this one's quoted without them."\n{yellow-fg}(Out of character:
|
|
2317
|
+
? ` "Crew channel's scrambled tonight -- this one's quoted without them."\n{yellow-fg}(Out of character: engine mismatch -- this machine runs ${srMod?.engineVersion() ?? '?'}, the street runs ${skew[1]}. If yours is older: npm i -g @maka/maka-cli@latest, then relaunch. If newer, the street is still rolling out.){/yellow-fg}`
|
|
2251
2318
|
: ` "Couldn't raise the whole crew -- ${shared.declined} This one's quoted without them."`;
|
|
2252
2319
|
}
|
|
2253
2320
|
else if (shared) {
|
|
@@ -2913,7 +2980,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2913
2980
|
if (player.crewChannel) {
|
|
2914
2981
|
// Reads exactly like an NPC call (player ruling: "keep it simple
|
|
2915
2982
|
// -- On a call with Steve, just like any call to a contact").
|
|
2916
|
-
lines.push(`{${CALL_COLOR}-fg}${CALL_ICON} On a call with ${player.crewChannel.playerName}{/${CALL_COLOR}-fg}`);
|
|
2983
|
+
lines.push(`{${CALL_COLOR}-fg}${CALL_ICON} ${player.crewChannel.party ? 'On the party line' : `On a call with ${player.crewChannel.playerName}`}{/${CALL_COLOR}-fg}`);
|
|
2917
2984
|
}
|
|
2918
2985
|
// The party: one line per live companion shell, with the tracks that
|
|
2919
2986
|
// matter for its kind. A spirit shows its BODY too (it's a full
|
|
@@ -3168,7 +3235,10 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3168
3235
|
// players" / "crew street"); the bare verb still answers quietly.
|
|
3169
3236
|
{ title: 'party', entries: [['crew', 'party'], ['hire'], ['dismiss'], ['train'], ['order'], ['lead'], ['command'], ['deploy'], ['recall']] },
|
|
3170
3237
|
{ title: 'magic', entries: [['spells'], ['cast'], ['summon', 'conjure'], ['project'], ['return'], ['assense']] },
|
|
3171
|
-
{ title: 'matrix', entries: [['jack'], ['deck'], ['load'], ['unload'], ['hack'], ['pan'], ['silent'], ['reboot'], ['agent'], ['
|
|
3238
|
+
{ title: 'matrix', entries: [['jack'], ['deck'], ['load'], ['unload'], ['hack'], ['pan'], ['silent'], ['reboot'], ['agent'], ['drone'], ['jump', 'rig']] },
|
|
3239
|
+
// The Emerged get their own shelf (player request: "there MUST be
|
|
3240
|
+
// resonance" -- matrix is the place, Resonance is the talent).
|
|
3241
|
+
{ title: 'resonance', entries: [['compile', 'decompile'], ['thread'], ['register']] },
|
|
3172
3242
|
{ title: 'social', entries: [['say'], ['talk-to'], ['call'], ['end-call', 'end', 'hangup'], ['accept'], ['decline', 'deny', 'refuse'], ['haggle', 'negotiate'], ['persuade', 'convince'], ['ask'], ['contacts']] },
|
|
3173
3243
|
{ title: 'street', entries: [['browse', 'buy'], ['sell'], ['catalog'], ['source', 'procure'], ['work', 'gig'], ['perform'], ['palm'], ['docwagon'], ['treat'], ['eat'], ['drink'], ['lifestyle'], ['pay']] },
|
|
3174
3244
|
{ title: 'barriers', entries: [['solve'], ['pick'], ['bluff'], ['unlock'], ['lock']] },
|
|
@@ -3211,14 +3281,44 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3211
3281
|
}
|
|
3212
3282
|
return lines.join('\n');
|
|
3213
3283
|
}
|
|
3214
|
-
// "help <command>"
|
|
3215
|
-
|
|
3216
|
-
|
|
3284
|
+
// "help <command>" (and "help <command> <sub>"): the command's
|
|
3285
|
+
// one-liner plus a TIDY subcommand table when it declares one
|
|
3286
|
+
// (player ruling: structured rows, not a paragraph; a second word
|
|
3287
|
+
// drills into one row).
|
|
3288
|
+
const words = query.split(/\s+/);
|
|
3289
|
+
const pickedVerb = fuzzyPickName(words[0], [...covered, ...uncategorized])
|
|
3290
|
+
?? fuzzyPickName(query, [...covered, ...uncategorized]);
|
|
3217
3291
|
if (pickedVerb) {
|
|
3218
3292
|
const entry = Game.HELP_CATEGORIES.flatMap(c => c.entries).find(e => e.includes(pickedVerb));
|
|
3219
3293
|
const primary = entry?.[0] ?? pickedVerb;
|
|
3220
3294
|
const aliases = entry?.slice(1) ?? [];
|
|
3221
|
-
|
|
3295
|
+
const CommandClass = CommandFactory.getAllRegisteredCommands()[primary];
|
|
3296
|
+
const subs = CommandClass?.getSubcommands?.();
|
|
3297
|
+
const title = `${primary}${aliases.length > 0 ? ` (${aliases.join(', ')})` : ''}`;
|
|
3298
|
+
if (subs && subs.length > 0) {
|
|
3299
|
+
// Drill-down: "help crew hire" -> just the hire row, long form.
|
|
3300
|
+
const subQuery = words.slice(1).join(' ');
|
|
3301
|
+
if (subQuery) {
|
|
3302
|
+
// A row's key: the word after the primary verb when usage
|
|
3303
|
+
// starts with it, else its first word.
|
|
3304
|
+
const subKey = (s) => {
|
|
3305
|
+
const t = s.usage.split(/\\s+/);
|
|
3306
|
+
return (t[0] === primary ? t[1] ?? t[0] : t[0]).replace(/[<>|]/g, '');
|
|
3307
|
+
};
|
|
3308
|
+
const pickedSub = fuzzyPickName(subQuery, subs.map(subKey));
|
|
3309
|
+
const sub = pickedSub ? subs.find(s => subKey(s) === pickedSub) : undefined;
|
|
3310
|
+
if (sub)
|
|
3311
|
+
return `${sub.usage}\n ${sub.long ?? sub.short}`;
|
|
3312
|
+
return `${title}: no subcommand matching "${subQuery}".\n${subs.map(s => ` ${s.usage.padEnd(24)} ${s.short}`).join('\n')}`;
|
|
3313
|
+
}
|
|
3314
|
+
const lines = [`${title}: ${this.describeVerb(primary).split('. ')[0]}.`];
|
|
3315
|
+
for (const s of subs)
|
|
3316
|
+
lines.push(` ${s.usage.padEnd(24)} ${s.short}`);
|
|
3317
|
+
lines.push('');
|
|
3318
|
+
lines.push(`"help ${primary} <sub>" drills into one.`);
|
|
3319
|
+
return lines.join('\n');
|
|
3320
|
+
}
|
|
3321
|
+
return `${title}: ${this.describeVerb(primary)}`;
|
|
3222
3322
|
}
|
|
3223
3323
|
return `No command or category called "${query}". Try plain "help".`;
|
|
3224
3324
|
}
|