@maka/maka-cli 5.15.2 → 5.16.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/game/sideQuest/commands/call.d.ts +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +49 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/crew.js +151 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/crew.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +34 -0
- package/bundle/typescript/src/commands/game/sideQuest/game.js +139 -15
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/headless.d.ts +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/headless.js +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/headless.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +4 -1
- package/bundle/typescript/src/commands/game/sideQuest/ui.js +25 -2
- package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.d.ts +8 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js +33 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +10 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/shared-run.d.ts +45 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/shared-run.js +179 -12
- 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 +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/social.js +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/social.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +53 -16
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
- package/package.json +2 -2
|
@@ -7,7 +7,7 @@ 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
9
|
import { appendCrewLedger } from './utilities/crew-ledger.js';
|
|
10
|
-
import { heartbeatPresence, ghostHireable, syncCrewLedger, claimGhostEarnings } from './utilities/social.js';
|
|
10
|
+
import { heartbeatPresence, heartbeatPresenceNow, ghostHireable, syncCrewLedger, claimGhostEarnings } from './utilities/social.js';
|
|
11
11
|
import { pushSave, burnCloudSaveFor } from './utilities/cloud-saves.js';
|
|
12
12
|
import { DOCWAGON_TIERS, RESUSC_FEE, DEATH_COMP } from './utilities/docwagon.js';
|
|
13
13
|
import { isFence, LIFESTYLE_TIERS } from './utilities/commerce.js';
|
|
@@ -224,10 +224,8 @@ export default class Game {
|
|
|
224
224
|
// IS the hub (the run is nobody's business); silent no-op logged out.
|
|
225
225
|
_presenceTimer;
|
|
226
226
|
static PRESENCE_TICK_MS = 60_000;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
return;
|
|
230
|
-
void heartbeatPresence({
|
|
227
|
+
presencePayload() {
|
|
228
|
+
return {
|
|
231
229
|
playerName: this.player.name,
|
|
232
230
|
slug: saveSlug(this.player.name),
|
|
233
231
|
hubName: this._hubName,
|
|
@@ -237,7 +235,28 @@ export default class Game {
|
|
|
237
235
|
karma: this.player.careerKarma,
|
|
238
236
|
streetCred: this.player.streetCred,
|
|
239
237
|
},
|
|
240
|
-
}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
beatPresence() {
|
|
241
|
+
if (!this.hasHub || this.scene !== this._hubScene)
|
|
242
|
+
return;
|
|
243
|
+
void heartbeatPresence(this.presencePayload());
|
|
244
|
+
// The hub link rides the same pulse: a failed connect at boot must
|
|
245
|
+
// not mean dead party notifications all session (ensure() no-ops
|
|
246
|
+
// when already connected). Lazy import -- the server never loads
|
|
247
|
+
// the client module.
|
|
248
|
+
void import('./utilities/shared-run.js')
|
|
249
|
+
.then(m => { m.ensureHubLink(this); m.HubLink.reconcileSeats(this); })
|
|
250
|
+
.catch(() => undefined);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* An IMMEDIATE presence beat that reports how it went -- the answer
|
|
254
|
+
* to "why can't my friend see me": the regular heartbeat is silent
|
|
255
|
+
* and up to 60s behind, so "crew players" and the hireable toggle
|
|
256
|
+
* push one right now and say so.
|
|
257
|
+
*/
|
|
258
|
+
async pushPresenceNow() {
|
|
259
|
+
return heartbeatPresenceNow(this.presencePayload());
|
|
241
260
|
}
|
|
242
261
|
startPresenceHeartbeat() {
|
|
243
262
|
if (this._presenceTimer)
|
|
@@ -300,6 +319,9 @@ export default class Game {
|
|
|
300
319
|
spawnCrew() {
|
|
301
320
|
const lines = [];
|
|
302
321
|
for (const member of this.crew) {
|
|
322
|
+
// Friend invites ride LIVE or not at all -- never as AI shells.
|
|
323
|
+
if (member.ghost?.liveOnly)
|
|
324
|
+
continue;
|
|
303
325
|
const shell = this.spawnCompanionShell({
|
|
304
326
|
name: member.name,
|
|
305
327
|
description: `${member.description} Right now they run as ${this.player.name}'s crew: they fight beside them, follow their lead, and expect their ${member.cutPct}% of the take.`,
|
|
@@ -348,6 +370,9 @@ export default class Game {
|
|
|
348
370
|
const lines = [];
|
|
349
371
|
const fallen = [];
|
|
350
372
|
for (const member of this.crew) {
|
|
373
|
+
// Friend invites never spawned -- no shell is not a death.
|
|
374
|
+
if (member.ghost?.liveOnly)
|
|
375
|
+
continue;
|
|
351
376
|
const entry = this.companions.find(c => c.kind === 'ally' && c.npc.name === member.name);
|
|
352
377
|
if (!entry || entry.npc.deathClaimed) {
|
|
353
378
|
fallen.push(member.name);
|
|
@@ -391,13 +416,18 @@ export default class Game {
|
|
|
391
416
|
* line (empty crew: full amount, no line).
|
|
392
417
|
*/
|
|
393
418
|
applyCrewCut(gross) {
|
|
394
|
-
|
|
419
|
+
// Friend invites take no cut (live runs pay the canon team split
|
|
420
|
+
// through payRunner) -- and an all-invite crew must not divide by
|
|
421
|
+
// its own zero.
|
|
422
|
+
const payable = this.crew.filter(m => m.cutPct > 0 && !m.ghost?.liveOnly);
|
|
423
|
+
if (payable.length === 0 || gross <= 0)
|
|
395
424
|
return { share: gross, line: '' };
|
|
396
|
-
const
|
|
397
|
-
const
|
|
425
|
+
const sumPct = payable.reduce((s, m) => s + m.cutPct, 0);
|
|
426
|
+
const totalPct = Math.min(60, sumPct);
|
|
427
|
+
const scale = totalPct / sumPct;
|
|
398
428
|
const parts = [];
|
|
399
429
|
let taken = 0;
|
|
400
|
-
for (const member of
|
|
430
|
+
for (const member of payable) {
|
|
401
431
|
const cut = Math.floor(gross * (member.cutPct * scale) / 100);
|
|
402
432
|
taken += cut;
|
|
403
433
|
parts.push(`${member.name} ${member.cutPct}% (${cut}¥)`);
|
|
@@ -435,6 +465,8 @@ export default class Game {
|
|
|
435
465
|
const contract = this.docwagon;
|
|
436
466
|
if (!contract || !this.initialized || this._dead)
|
|
437
467
|
return false;
|
|
468
|
+
// (Solo path: THE player, THE contract, hub medevac. Shared runs use
|
|
469
|
+
// tryDocwagonRescueFor below -- per-player contracts, no hub.)
|
|
438
470
|
const tier = DOCWAGON_TIERS[contract.tier];
|
|
439
471
|
const logger = Logger.getInstance();
|
|
440
472
|
const bills = [];
|
|
@@ -476,6 +508,76 @@ export default class Game {
|
|
|
476
508
|
void this.docwagonMedevac();
|
|
477
509
|
return true;
|
|
478
510
|
}
|
|
511
|
+
// ---------------- DocWagon in SHARED runs (per-player, ruling:
|
|
512
|
+
// "full permadeath, but the doc wagon still works if purchased") ----
|
|
513
|
+
/** Each human player's contract, loaded from their own save doc by
|
|
514
|
+
* the headless boot (their `save.game.docwagon`). Solo play never
|
|
515
|
+
* touches this map -- the legacy single-contract path above is the
|
|
516
|
+
* solo behavior, byte for byte. */
|
|
517
|
+
docwagonByPlayer = new Map();
|
|
518
|
+
/** Shared-run hook: a player's HTR extraction (stabilized and carried
|
|
519
|
+
* off the job). The hosting server writes their snapshot back and
|
|
520
|
+
* marks them 'left'. */
|
|
521
|
+
onPlayerExtracted;
|
|
522
|
+
/**
|
|
523
|
+
* The per-player band (shared runs -- scene.players.length > 1):
|
|
524
|
+
* resolves the FALLEN player's own contract, bills THAT player
|
|
525
|
+
* (whatever their stick holds; a guest's shortfall is DocWagon's
|
|
526
|
+
* collections problem, not the rent tab's), and either stabilizes
|
|
527
|
+
* them in place ('resusc') or extracts them from the session ('htr'
|
|
528
|
+
* -- the member-leave flow, via onPlayerExtracted). Returns true when
|
|
529
|
+
* the rescue engages -- the caller must then NOT kill them.
|
|
530
|
+
*/
|
|
531
|
+
tryDocwagonRescueFor(player, kind, opts = {}) {
|
|
532
|
+
const contract = this.docwagonByPlayer.get(player.name);
|
|
533
|
+
if (!contract || !this.initialized)
|
|
534
|
+
return false;
|
|
535
|
+
const tier = DOCWAGON_TIERS[contract.tier];
|
|
536
|
+
const logger = Logger.getInstance();
|
|
537
|
+
const bills = [];
|
|
538
|
+
const billPlayer = (amount, label) => {
|
|
539
|
+
if (amount <= 0)
|
|
540
|
+
return '';
|
|
541
|
+
const paid = Math.min(player.currency, amount);
|
|
542
|
+
if (paid > 0)
|
|
543
|
+
player.adjustCurrency(-paid);
|
|
544
|
+
const owed = amount - paid;
|
|
545
|
+
return owed === 0
|
|
546
|
+
? `DocWagon bills ${player.name} ${amount} for ${label}.`
|
|
547
|
+
: `DocWagon bills ${player.name} ${amount} for ${label} -- ${paid} paid, ${owed} on account (they WILL collect).`;
|
|
548
|
+
};
|
|
549
|
+
if (kind === 'resusc') {
|
|
550
|
+
if (contract.resuscsLeft > 0) {
|
|
551
|
+
contract.resuscsLeft -= 1;
|
|
552
|
+
bills.push(`A contract resuscitation is burned for ${player.name} -- ${contract.resuscsLeft} left this year.`);
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
bills.push(billPlayer(RESUSC_FEE, 'resuscitation'));
|
|
556
|
+
}
|
|
557
|
+
player.healDamage(Math.max(0, player.damageTaken - (player.maxConditionBoxes - 2)));
|
|
558
|
+
player.healStun(Math.max(0, player.stunTaken - Math.max(0, player.maxStunBoxes - 2)));
|
|
559
|
+
logger.logWithColor(`${CALL_ICON} ${player.name}'s wristband SCREAMS the flatline. Ten minutes later there is foam, light, and a trauma team refusing to let go.`, CALL_COLOR);
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
bills.push(billPlayer(tier.htrFee, 'High Threat Response'));
|
|
563
|
+
const hostiles = opts.standingHostiles ?? [];
|
|
564
|
+
if (hostiles.length > 0 && tier.deathComp) {
|
|
565
|
+
const casualtyRoll = rollPool(Math.min(4, hostiles.length));
|
|
566
|
+
if (casualtyRoll.hits > 0) {
|
|
567
|
+
bills.push(billPlayer(DEATH_COMP, 'employee death compensation -- a medic did not make it out'));
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
player.healStun(Math.max(0, player.stunTaken - Math.max(0, player.maxStunBoxes - 2)));
|
|
571
|
+
logger.logWithColor(`${CALL_ICON} ${player.name}'s band reads the flatline and howls -- a DocWagon HTR team storms the scene and carries them out behind riot shields.`, CALL_COLOR);
|
|
572
|
+
// Extraction: off the job, alive, gear on the stretcher with them.
|
|
573
|
+
this.scene.removeActorQuietly(player.name);
|
|
574
|
+
this.onPlayerExtracted?.(player);
|
|
575
|
+
}
|
|
576
|
+
for (const b of bills.filter(Boolean))
|
|
577
|
+
logger.logWithColor(b, 'yellow');
|
|
578
|
+
this.scene.addWorldEvent(`A DocWagon team ${kind === 'htr' ? 'extracted' : 'resuscitated'} ${player.name} under the ${contract.tier} contract.`);
|
|
579
|
+
return true;
|
|
580
|
+
}
|
|
479
581
|
/** Where the wagon drops you (player ruling: the CLINIC, not your own
|
|
480
582
|
* cot): the district Street Doc's room, generically -- so generated
|
|
481
583
|
* hubs work too. Falls back to the hideout if the hub has no doc. */
|
|
@@ -1328,6 +1430,9 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1328
1430
|
CommandFactory.registerCommand('lead', LeadCommand);
|
|
1329
1431
|
CommandFactory.registerCommand('command', RecruitCommand);
|
|
1330
1432
|
CommandFactory.registerCommand('crew', CrewCommand);
|
|
1433
|
+
// "party" is the same table by another name (user vocabulary --
|
|
1434
|
+
// "basic party operations").
|
|
1435
|
+
CommandFactory.registerCommand('party', CrewCommand);
|
|
1331
1436
|
CommandFactory.registerCommand('hire', HireCommand);
|
|
1332
1437
|
CommandFactory.registerCommand('dismiss', DismissCommand);
|
|
1333
1438
|
CommandFactory.registerCommand('train', TrainCommand);
|
|
@@ -1605,6 +1710,14 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1605
1710
|
}
|
|
1606
1711
|
}
|
|
1607
1712
|
this.initialized = true;
|
|
1713
|
+
// FIRST-BEAT SAVE (M4.5, from a real incident): a fresh character
|
|
1714
|
+
// had no cloud copy until their first rest/quit, so a friend's
|
|
1715
|
+
// party invite bounced with "no living runner". A hub session is
|
|
1716
|
+
// party-loadable within moments of launch now -- requestSave pushes
|
|
1717
|
+
// the cloud mirror behind the local write.
|
|
1718
|
+
if (this._hubScene && this.scene === this._hubScene) {
|
|
1719
|
+
this.requestSave('hub-arrival');
|
|
1720
|
+
}
|
|
1608
1721
|
if (this._resume) {
|
|
1609
1722
|
const pendingNote = this._pendingRunSeed
|
|
1610
1723
|
? 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.`)
|
|
@@ -1878,7 +1991,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1878
1991
|
// then the wrapped-run tally and the training window reset.
|
|
1879
1992
|
const crewLines = this.scene !== hub ? this.settleCrewAfterRun() : [];
|
|
1880
1993
|
for (const member of this.crew) {
|
|
1881
|
-
|
|
1994
|
+
// Friend invites weren't on this local run -- no tally for them.
|
|
1995
|
+
if (wrappedRun && !member.ghost?.liveOnly)
|
|
1882
1996
|
member.jobsRun += 1;
|
|
1883
1997
|
member.trainedThisCycle = false;
|
|
1884
1998
|
}
|
|
@@ -2030,11 +2144,18 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2030
2144
|
// down), and locally we keep only Krow's pitch stub so the whole
|
|
2031
2145
|
// offer flow (his call, accept/decline, the taxi) works unchanged.
|
|
2032
2146
|
// Offline owners fall back to the Phase B ghost riders below.
|
|
2147
|
+
let sharedDeclineNote = '';
|
|
2033
2148
|
if (this.hasHub) {
|
|
2034
2149
|
const shared = await import('./utilities/shared-run.js')
|
|
2035
2150
|
.then(m => m.tryCreateSharedSession(this))
|
|
2036
2151
|
.catch(() => undefined);
|
|
2037
|
-
if (shared) {
|
|
2152
|
+
if (shared && 'declined' in shared) {
|
|
2153
|
+
// The crew was invited but the table couldn't seat them -- the
|
|
2154
|
+
// job drafts locally and KROW SAYS WHY (a real playtest lost a
|
|
2155
|
+
// run to this being silent).
|
|
2156
|
+
sharedDeclineNote = ` "Couldn't raise the whole crew -- ${shared.declined} This one's quoted without them."`;
|
|
2157
|
+
}
|
|
2158
|
+
else if (shared) {
|
|
2038
2159
|
this.pendingSharedRun = { sessionID: shared.sessionID };
|
|
2039
2160
|
this._pendingRunSeed = {
|
|
2040
2161
|
name: shared.pitch.name,
|
|
@@ -2068,7 +2189,10 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2068
2189
|
//
|
|
2069
2190
|
// The player object rides along so the job is drafted FOR this runner
|
|
2070
2191
|
// -- their archetype, stats, and toolkit (see buildPlayerDossier).
|
|
2071
|
-
|
|
2192
|
+
// Friend invites don't ride local drafts (they're live players or
|
|
2193
|
+
// nothing) -- the dossier scales to who'll actually show.
|
|
2194
|
+
const localCrew = this.crew.filter(m => !m.ghost?.liveOnly);
|
|
2195
|
+
const nextSeed = await SceneSeedGenerator.generate(this._logFilePath, nextTier, this.player, this.activeRumor, localCrew);
|
|
2072
2196
|
if (!nextSeed) {
|
|
2073
2197
|
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."`;
|
|
2074
2198
|
}
|
|
@@ -2082,7 +2206,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2082
2206
|
this._pendingRunTier = nextTier;
|
|
2083
2207
|
this._pendingRide = Game.RIDE_POOL[Math.floor(Math.random() * Game.RIDE_POOL.length)];
|
|
2084
2208
|
this.pendingRunAccepted = false;
|
|
2085
|
-
return `Your link pings -- 1 new message: ${FIXER_NAME}.${hint(` ("call ${FIXER_NAME}" to hear the job.)`)}`;
|
|
2209
|
+
return `Your link pings -- 1 new message: ${FIXER_NAME}.${sharedDeclineNote}${hint(` ("call ${FIXER_NAME}" to hear the job.)`)}`;
|
|
2086
2210
|
}
|
|
2087
2211
|
// Classic sessions transition immediately, hideout-first, as always.
|
|
2088
2212
|
return await this.activateRunScene(nextSeed, nextTier, { startInHideout: true });
|
|
@@ -2925,7 +3049,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2925
3049
|
{ title: 'combat', entries: [['attack', 'kill'], ['subdue'], ['grapple', 'restrain', 'clinch'], ['struggle'], ['release'], ['stance'], ['surrender'], ['edge'], ['rest']] },
|
|
2926
3050
|
// The party layer: everyone who walks (or flies, or manifests) at
|
|
2927
3051
|
// your side answers to these.
|
|
2928
|
-
{ title: 'party', entries: [['crew'], ['hire'], ['dismiss'], ['train'], ['order'], ['lead'], ['command'], ['deploy'], ['recall']] },
|
|
3052
|
+
{ title: 'party', entries: [['crew', 'party'], ['hire'], ['dismiss'], ['train'], ['order'], ['lead'], ['command'], ['deploy'], ['recall']] },
|
|
2929
3053
|
{ title: 'magic', entries: [['spells'], ['cast'], ['summon', 'conjure'], ['project'], ['return'], ['assense']] },
|
|
2930
3054
|
{ title: 'matrix', entries: [['jack'], ['deck'], ['load'], ['unload'], ['hack'], ['pan'], ['silent'], ['reboot'], ['agent'], ['drone'], ['jump', 'rig']] },
|
|
2931
3055
|
{ title: 'social', entries: [['say'], ['talk-to'], ['call'], ['end-call', 'end', 'hangup'], ['accept'], ['decline', 'deny', 'refuse'], ['haggle', 'negotiate'], ['persuade', 'convince'], ['ask'], ['contacts']] },
|