@maka/maka-cli 5.21.0 → 5.23.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/commands/breach.js +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/breach.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/compile.js +11 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/compile.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.d.ts +7 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +28 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +8 -41
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/overwatch.d.ts +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/overwatch.js +57 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/overwatch.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js +9 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.d.ts +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +5 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +25 -0
- package/bundle/typescript/src/commands/game/sideQuest/game.js +135 -4
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +17 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog-data.d.ts +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog-data.js +6 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog-data.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.js +57 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/overwatch.d.ts +34 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/overwatch.js +112 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/overwatch.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -1
- package/bundle/typescript/src/tools/mpi/mpi.class.js +3 -1
- package/bundle/typescript/src/tools/mpi/mpi.class.js.map +1 -1
- package/bundle/typescript/src/tools/mpi/sdk-runtime.d.ts +1 -0
- package/bundle/typescript/src/tools/mpi/sdk-runtime.js +30 -0
- package/bundle/typescript/src/tools/mpi/sdk-runtime.js.map +1 -1
- package/bundle/typescript/src/tools/scaffold/scaffold.class.js +2 -1
- package/bundle/typescript/src/tools/scaffold/scaffold.class.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Command } from './command.js';
|
|
2
|
+
import { rollPool, formatRoll } from '../utilities/dice.js';
|
|
3
|
+
import { accrueOverwatch, settledScore, CONVERGENCE_AT } from '../utilities/overwatch.js';
|
|
4
|
+
import { leaveMatrix } from '../utilities/planes.js';
|
|
5
|
+
/**
|
|
6
|
+
* Check Overwatch Score (canon p.238): Electronic Warfare + Logic
|
|
7
|
+
* [Sleaze] vs GOD's flat 6 dice. Win the read and you see the exact
|
|
8
|
+
* number; lose it and you only learn that a count exists. Either way
|
|
9
|
+
* the check is ITSELF a Sleaze action -- GOD's counter-sweep hits add
|
|
10
|
+
* to the very score you're checking. The score is never on the HUD:
|
|
11
|
+
* this command is the only window, and it costs.
|
|
12
|
+
*/
|
|
13
|
+
export class OverwatchCommand extends Command {
|
|
14
|
+
static verb = 'overwatch';
|
|
15
|
+
static description = 'Check your Overwatch Score -- GOD\'s evidence tally on your persona (Electronic Warfare + Logic vs GOD\'s sweep; a SLEAZE action, so asking adds to the count). Illegal hacks grow it; time grows it; at 40 GOD CONVERGES. "reboot" or jacking out resets it to zero.';
|
|
16
|
+
async execute(_args = []) {
|
|
17
|
+
const actor = this.actor;
|
|
18
|
+
if (actor.plane === 'astral') {
|
|
19
|
+
return `GOD doesn't watch the astral -- and the astral can't check.`;
|
|
20
|
+
}
|
|
21
|
+
const deck = actor.getCyberdeck();
|
|
22
|
+
if (actor.plane !== 'matrix' && !actor.isTechnomancer() && !deck) {
|
|
23
|
+
return `Checking your overwatch takes a live persona -- jack in, or put a working deck in hand.`;
|
|
24
|
+
}
|
|
25
|
+
if (actor.overwatchScore <= 0) {
|
|
26
|
+
return `You listen for the grid's counting... nothing. Fresh boot, clean slate -- GOD has no file on this persona yet.`;
|
|
27
|
+
}
|
|
28
|
+
const skill = actor.skillRating('electronic-warfare');
|
|
29
|
+
const sleazeEdge = actor.isTechnomancer()
|
|
30
|
+
? Math.floor(actor.resonance / 2)
|
|
31
|
+
: (actor.activeDeck ?? deck)?.matrixEdge('sleaze') ?? 0;
|
|
32
|
+
const mine = rollPool(Math.max(1, actor.logic + (skill > 0 ? skill : -1) + sleazeEdge + actor.woundModifier - actor.sustainingPenalty));
|
|
33
|
+
const god = rollPool(6);
|
|
34
|
+
if (this.scene.isHumanControlled(actor)) {
|
|
35
|
+
this.logger.meta(`Check Overwatch (EW, sleaze): ${formatRoll(mine)}`);
|
|
36
|
+
this.logger.meta(` GOD's counter-sweep: ${formatRoll(god)}`);
|
|
37
|
+
}
|
|
38
|
+
// The number they glimpse is the score BEFORE this check's own bill
|
|
39
|
+
// lands -- you read the file as it stood when you looked.
|
|
40
|
+
const glimpsed = settledScore(actor);
|
|
41
|
+
const bill = accrueOverwatch(this.scene, actor, god.hits, 'checked overwatch (sleaze)');
|
|
42
|
+
const out = [];
|
|
43
|
+
if (mine.hits > god.hits) {
|
|
44
|
+
out.push(`You read your own file over GOD's shoulder: Overwatch Score ${glimpsed} of ${CONVERGENCE_AT}.${glimpsed >= 30 ? ` They are CLOSE.` : glimpsed >= 20 ? ` The count is climbing.` : ``}`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
out.push(`The sweep slips past before you can read it -- no number, just the cold certainty that a count EXISTS, and that looking fed it.`);
|
|
48
|
+
}
|
|
49
|
+
out.push(...bill.lines);
|
|
50
|
+
if (bill.converged && actor.plane === 'matrix') {
|
|
51
|
+
out.push(...leaveMatrix(this.scene, actor, { forced: true, reason: `GOD force-reboots your persona --` }));
|
|
52
|
+
}
|
|
53
|
+
this.scene.updateStatus();
|
|
54
|
+
return out.join('\n');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=overwatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overwatch.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/commands/overwatch.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from './command.js';
|
|
2
|
+
import { resetOverwatch } from '../utilities/overwatch.js';
|
|
2
3
|
/**
|
|
3
4
|
* The answer to a cracked PAN (see hack.ts): reboot your network. Clears
|
|
4
5
|
* every jammed device you carry and brings a hacked-dark AR overlay back
|
|
@@ -29,7 +30,8 @@ export class RebootCommand extends Command {
|
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
const hadArDown = actor.arOffline;
|
|
32
|
-
|
|
33
|
+
const hadOverwatch = actor.overwatchScore > 0;
|
|
34
|
+
if (jammedGear.length === 0 && !hadArDown && !hadOverwatch) {
|
|
33
35
|
return `Your PAN is clean -- nothing needs a reboot.`;
|
|
34
36
|
}
|
|
35
37
|
for (const item of jammedGear) {
|
|
@@ -46,6 +48,12 @@ export class RebootCommand extends Command {
|
|
|
46
48
|
if (hadArDown) {
|
|
47
49
|
lines.push(` • AR overlay back up -- the world regrows its labels`);
|
|
48
50
|
}
|
|
51
|
+
// A fresh boot has no history (canon p.231-232): GOD's tally dies
|
|
52
|
+
// with the old session.
|
|
53
|
+
if (hadOverwatch) {
|
|
54
|
+
resetOverwatch(actor, 'PAN reboot');
|
|
55
|
+
lines.push(` • Overwatch tally ZEROED -- whatever GOD was counting, it belonged to a persona that no longer exists`);
|
|
56
|
+
}
|
|
49
57
|
return lines.join('\n');
|
|
50
58
|
}
|
|
51
59
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reboot.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/commands/reboot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAY,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"reboot.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/commands/reboot.ts"],"names":[],"mappings":"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"}
|
|
@@ -27,5 +27,9 @@
|
|
|
27
27
|
// distance (go <room> jumps anywhere; map renders the grid).
|
|
28
28
|
// 1.6.0 (2026-08-23): icon-first matrix look (PANs visible, no exits);
|
|
29
29
|
// hack forks Attack/Sleaze (canon p.236/238/240).
|
|
30
|
-
|
|
30
|
+
// 1.7.0 (2026-08-23): GOD -- Overwatch Score on illegal actions,
|
|
31
|
+
// convergence at 40, overwatch check verb, reboot/jack-out resets.
|
|
32
|
+
// 1.8.0 (2026-08-24): SINs & getting caught -- run heat, the KE
|
|
33
|
+
// checkpoint on the ride home, fake SINs in the catalog (v4).
|
|
34
|
+
export const ENGINE_VERSION = '1.8.0';
|
|
31
35
|
//# sourceMappingURL=engine-version.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine-version.js","sourceRoot":"","sources":["../../../../../../src/commands/game/sideQuest/engine-version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,sEAAsE;AACtE,mEAAmE;AACnE,oEAAoE;AACpE,sEAAsE;AACtE,6CAA6C;AAC7C,iEAAiE;AACjE,qCAAqC;AACrC,oEAAoE;AACpE,6DAA6D;AAC7D,uEAAuE;AACvE,kDAAkD;AAClD,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC"}
|
|
1
|
+
{"version":3,"file":"engine-version.js","sourceRoot":"","sources":["../../../../../../src/commands/game/sideQuest/engine-version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,sEAAsE;AACtE,mEAAmE;AACnE,oEAAoE;AACpE,sEAAsE;AACtE,6CAA6C;AAC7C,iEAAiE;AACjE,qCAAqC;AACrC,oEAAoE;AACpE,6DAA6D;AAC7D,uEAAuE;AACvE,kDAAkD;AAClD,iEAAiE;AACjE,mEAAmE;AACnE,gEAAgE;AAChE,8DAA8D;AAC9D,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC"}
|
|
@@ -95,6 +95,13 @@ export default class Game {
|
|
|
95
95
|
* standing quote. Returns the line to show, or ''. */
|
|
96
96
|
scrapPendingRunForCrewChange(): string;
|
|
97
97
|
performedThisCycle: boolean;
|
|
98
|
+
private _runHeat;
|
|
99
|
+
private static readonly CHECKPOINT_HEAT;
|
|
100
|
+
/** Bill the district's attention. No-op at the hub or in classic
|
|
101
|
+
* (hubless) scenes -- only runs have a ride home to be caught on. */
|
|
102
|
+
addHeat(amount: number, why: string): void;
|
|
103
|
+
/** The HUD's read on the district (bands, never the number). */
|
|
104
|
+
private heatBand;
|
|
98
105
|
crewForHire: ICrewMember[];
|
|
99
106
|
private _presenceTimer?;
|
|
100
107
|
private static readonly PRESENCE_TICK_MS;
|
|
@@ -572,6 +579,24 @@ export default class Game {
|
|
|
572
579
|
* open combat bookkeeping) is settled on the way home.
|
|
573
580
|
*/
|
|
574
581
|
private returnToHub;
|
|
582
|
+
/**
|
|
583
|
+
* KNIGHT ERRANT CHECKPOINT (SINs & getting caught, ruling 2026-08-24;
|
|
584
|
+
* canon p.363-368, verification values engine-adapted -- the RAG was
|
|
585
|
+
* down): past CHECKPOINT_HEAT, the ride home hits the wire and the
|
|
586
|
+
* SIN mechanic resolves. A CRIMINAL SIN broadcasts before the wand
|
|
587
|
+
* lifts -- shakedown, citation, notoriety. Otherwise your best FAKE
|
|
588
|
+
* SIN is tested: the scanner rolls its dice (R3 wand, R4 on the
|
|
589
|
+
* hottest nights) and BEATS the fake's rating to burn it -- burned
|
|
590
|
+
* means gone from every database at once, then processing. No SIN at
|
|
591
|
+
* all: fast-talk (Charisma + con) or zip ties. Heat always resets --
|
|
592
|
+
* the night ends either way.
|
|
593
|
+
*/
|
|
594
|
+
private checkpointOnRideHome;
|
|
595
|
+
/** Processing: prints, retinas, a fee -- and for the SINless, the
|
|
596
|
+
* street's cruelest joke: they ISSUE you a SIN. A criminal one.
|
|
597
|
+
* Catch-and-release always (arrest never ends a career by fiat --
|
|
598
|
+
* the record it leaves does the long-term damage). */
|
|
599
|
+
private processArrest;
|
|
575
600
|
/**
|
|
576
601
|
* The actual drafting work, split out from continueToNextScene() purely
|
|
577
602
|
* so that method can wrap it in the try/finally that clears
|
|
@@ -105,6 +105,7 @@ import { BluffCommand } from './commands/bluff.js';
|
|
|
105
105
|
import { DispelCommand } from './commands/dispel.js';
|
|
106
106
|
import { BreachCommand } from './commands/breach.js';
|
|
107
107
|
import { EmoteCommand } from './commands/emote.js';
|
|
108
|
+
import { OverwatchCommand } from './commands/overwatch.js';
|
|
108
109
|
import { AutoFight } from './utilities/auto-fight.js';
|
|
109
110
|
import { CommandFactory } from './factories/command-factory.js';
|
|
110
111
|
import { SceneSynthesizer } from './factories/scene-factory.js';
|
|
@@ -236,6 +237,35 @@ export default class Game {
|
|
|
236
237
|
// cycle, reset at homecoming like a contact's legwork. Deliberately
|
|
237
238
|
// NOT saved -- a relaunch is as good as a homecoming for new material.
|
|
238
239
|
performedThisCycle = false;
|
|
240
|
+
// HEAT (SINs & getting caught, ruling 2026-08-24): the meatspace
|
|
241
|
+
// Overwatch Score. Loud acts during a RUN bill it -- alarms, breach
|
|
242
|
+
// crashes, bodies dropped, GOD burning your location -- and the ride
|
|
243
|
+
// home is where the night catches up: past CHECKPOINT_HEAT the taxi
|
|
244
|
+
// hits a Knight Errant checkpoint and the SIN mechanic resolves
|
|
245
|
+
// (checkpointOnRideHome). Hub life never accrues heat (home turf),
|
|
246
|
+
// and a fresh run starts cold. Not saved: heat is a per-run debt.
|
|
247
|
+
_runHeat = 0;
|
|
248
|
+
static CHECKPOINT_HEAT = 6;
|
|
249
|
+
/** Bill the district's attention. No-op at the hub or in classic
|
|
250
|
+
* (hubless) scenes -- only runs have a ride home to be caught on. */
|
|
251
|
+
addHeat(amount, why) {
|
|
252
|
+
if (!this.hasHub || this.scene === this._hubScene)
|
|
253
|
+
return;
|
|
254
|
+
this._runHeat += amount;
|
|
255
|
+
Logger.getInstance().write(`Heat: +${amount} (${why}) -> ${this._runHeat}.`);
|
|
256
|
+
}
|
|
257
|
+
/** The HUD's read on the district (bands, never the number). */
|
|
258
|
+
heatBand() {
|
|
259
|
+
if (this._runHeat <= 0)
|
|
260
|
+
return undefined;
|
|
261
|
+
if (this._runHeat >= 12)
|
|
262
|
+
return `{red-fg}🔥 Heat: HTR assembling somewhere close{/red-fg}`;
|
|
263
|
+
if (this._runHeat >= Game.CHECKPOINT_HEAT)
|
|
264
|
+
return `{red-fg}🔥 Heat: sirens circling closer{/red-fg}`;
|
|
265
|
+
if (this._runHeat >= 3)
|
|
266
|
+
return `{yellow-fg}🔥 Heat: ears up all over the block{/yellow-fg}`;
|
|
267
|
+
return `{yellow-fg}🔥 Heat: somebody heard something{/yellow-fg}`;
|
|
268
|
+
}
|
|
239
269
|
// THE FOR-HIRE BOARD: archetype-built candidates, fresh faces each
|
|
240
270
|
// homecoming (the gig-pool rotation school). Not saved -- the street's
|
|
241
271
|
// talent pool churns; whoever you HIRED is the persistent part.
|
|
@@ -1664,6 +1694,9 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1664
1694
|
// Free-form roleplay ("me" is the classic MUD spelling).
|
|
1665
1695
|
CommandFactory.registerCommand('emote', EmoteCommand);
|
|
1666
1696
|
CommandFactory.registerCommand('me', EmoteCommand);
|
|
1697
|
+
// GOD's tally ("os" for the tabletop shorthand).
|
|
1698
|
+
CommandFactory.registerCommand('overwatch', OverwatchCommand);
|
|
1699
|
+
CommandFactory.registerCommand('os', OverwatchCommand);
|
|
1667
1700
|
}
|
|
1668
1701
|
determineStartRoom() {
|
|
1669
1702
|
return this.scene.determineStartRoom();
|
|
@@ -2299,14 +2332,104 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2299
2332
|
deliveryLine = `\nWaiting with your gear: ${delivered.join(', ')}.`;
|
|
2300
2333
|
}
|
|
2301
2334
|
}
|
|
2335
|
+
// The night catches up on the ride (SINs & getting caught): a hot
|
|
2336
|
+
// run means the wire between there and home. Settled AFTER the
|
|
2337
|
+
// payout (the fee comes out of tonight's take) and BEFORE the save
|
|
2338
|
+
// below -- a burned fake or an issued SIN must survive a quit.
|
|
2339
|
+
const checkpointBlock = this.checkpointOnRideHome();
|
|
2302
2340
|
// The homecoming save: everything above (payout, rent, restock,
|
|
2303
|
-
// contacts reset, deliveries) settled first, so the
|
|
2304
|
-
// the hub as the player will find it.
|
|
2341
|
+
// contacts reset, deliveries, the checkpoint) settled first, so the
|
|
2342
|
+
// save reflects the hub as the player will find it.
|
|
2305
2343
|
this.requestSave('homecoming');
|
|
2306
2344
|
const fateLine = wrappedRun
|
|
2307
2345
|
? `The run's wrapped and the street swallows you back up.`
|
|
2308
2346
|
: `The run's behind you -- unfinished, unpaid, already someone else's problem. The street doesn't ask.`;
|
|
2309
|
-
return `\n=== ${this._hubName.toUpperCase()} ===\n${fateLine} You're home -- your hideout, one door off ${hub.determineStartRoom().name}.${hint(` Call ${FIXER_NAME} when you're hungry for the next job.`)}${clientLine}\n${rentLine}${comfortLine}${restockLine}${deliveryLine}${docwagonLine}${allyLine}${companionBlock.length > 0 ? `\n${companionBlock.join('\n')}` : ''}`;
|
|
2347
|
+
return `${checkpointBlock ? `\n${checkpointBlock}\n` : ''}\n=== ${this._hubName.toUpperCase()} ===\n${fateLine} You're home -- your hideout, one door off ${hub.determineStartRoom().name}.${hint(` Call ${FIXER_NAME} when you're hungry for the next job.`)}${clientLine}\n${rentLine}${comfortLine}${restockLine}${deliveryLine}${docwagonLine}${allyLine}${companionBlock.length > 0 ? `\n${companionBlock.join('\n')}` : ''}`;
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* KNIGHT ERRANT CHECKPOINT (SINs & getting caught, ruling 2026-08-24;
|
|
2351
|
+
* canon p.363-368, verification values engine-adapted -- the RAG was
|
|
2352
|
+
* down): past CHECKPOINT_HEAT, the ride home hits the wire and the
|
|
2353
|
+
* SIN mechanic resolves. A CRIMINAL SIN broadcasts before the wand
|
|
2354
|
+
* lifts -- shakedown, citation, notoriety. Otherwise your best FAKE
|
|
2355
|
+
* SIN is tested: the scanner rolls its dice (R3 wand, R4 on the
|
|
2356
|
+
* hottest nights) and BEATS the fake's rating to burn it -- burned
|
|
2357
|
+
* means gone from every database at once, then processing. No SIN at
|
|
2358
|
+
* all: fast-talk (Charisma + con) or zip ties. Heat always resets --
|
|
2359
|
+
* the night ends either way.
|
|
2360
|
+
*/
|
|
2361
|
+
checkpointOnRideHome() {
|
|
2362
|
+
const heat = this._runHeat;
|
|
2363
|
+
this._runHeat = 0;
|
|
2364
|
+
if (heat < Game.CHECKPOINT_HEAT)
|
|
2365
|
+
return '';
|
|
2366
|
+
const p = this.player;
|
|
2367
|
+
const lines = [
|
|
2368
|
+
``,
|
|
2369
|
+
`{red-fg}Halfway home, the traffic folds around a KNIGHT ERRANT CHECKPOINT -- light bars, a drone hovering, a scanner wand. The night you just had is all over the district feeds.{/red-fg}`,
|
|
2370
|
+
];
|
|
2371
|
+
if (p.qualities.has('sinner-criminal')) {
|
|
2372
|
+
const fine = Math.min(p.currency, 500 + heat * 100);
|
|
2373
|
+
if (fine > 0)
|
|
2374
|
+
p.adjustCurrency(-fine);
|
|
2375
|
+
p.addNotoriety('swept up at a KE checkpoint on a hot night');
|
|
2376
|
+
lines.push(`Your CRIMINAL SIN broadcasts before the wand even lifts -- they know the face, the file, the record. On a night this hot, that means the full treatment.`);
|
|
2377
|
+
lines.push(` Cited and released: ${fine > 0 ? `${fine} nuyen lighter` : `your sticks came up empty, so the citation rides your file instead`}, and the record grows. (Notoriety.)`);
|
|
2378
|
+
return lines.join('\n');
|
|
2379
|
+
}
|
|
2380
|
+
const verifierDice = heat >= 12 ? 8 : 6;
|
|
2381
|
+
const fakes = p.inventory.getAllItems()
|
|
2382
|
+
.map(i => ({ item: i, m: /^Fake SIN \(Rating (\d)\)$/.exec(i.name) }))
|
|
2383
|
+
.filter(x => x.m !== null)
|
|
2384
|
+
.map(x => ({ item: x.item, rating: parseInt(x.m[1], 10) }))
|
|
2385
|
+
.sort((a, b) => b.rating - a.rating);
|
|
2386
|
+
if (fakes.length > 0) {
|
|
2387
|
+
const fake = fakes[0];
|
|
2388
|
+
const scan = rollPool(verifierDice);
|
|
2389
|
+
Logger.getInstance().meta(`SIN verification (KE scanner, ${verifierDice}d6): ${formatRoll(scan)} (beats Rating ${fake.rating} to burn the fake)`);
|
|
2390
|
+
if (scan.hits <= fake.rating) {
|
|
2391
|
+
lines.push(`You hand over the Rating ${fake.rating} fake and hold a bored face. The wand chews on it... green light. "Move along."`);
|
|
2392
|
+
return lines.join('\n');
|
|
2393
|
+
}
|
|
2394
|
+
p.inventory.removeItem(fake.item.name);
|
|
2395
|
+
lines.push(`You hand over the Rating ${fake.rating} fake. The wand chews... and SPITS. "This SIN is flagged as of now." The fake is BURNED -- worthless in every database at once.`);
|
|
2396
|
+
lines.push(this.processArrest(heat));
|
|
2397
|
+
return lines.join('\n');
|
|
2398
|
+
}
|
|
2399
|
+
const con = p.skillRating('con');
|
|
2400
|
+
const talk = rollPool(Math.max(1, p.charisma + (con > 0 ? con : -1) + p.woundModifier));
|
|
2401
|
+
const need = heat >= 12 ? 4 : 3;
|
|
2402
|
+
Logger.getInstance().meta(`Fast talk at the wire (Charisma + con): ${formatRoll(talk)} (${need}+ slips through)`);
|
|
2403
|
+
if (talk.hits >= need) {
|
|
2404
|
+
lines.push(`No SIN at all -- so you become somebody's cousin, somebody's designated driver, a story so boring the officer waves you through mid-sentence.`);
|
|
2405
|
+
return lines.join('\n');
|
|
2406
|
+
}
|
|
2407
|
+
lines.push(`No SIN, and no story that holds. Zip ties.`);
|
|
2408
|
+
lines.push(this.processArrest(heat));
|
|
2409
|
+
return lines.join('\n');
|
|
2410
|
+
}
|
|
2411
|
+
/** Processing: prints, retinas, a fee -- and for the SINless, the
|
|
2412
|
+
* street's cruelest joke: they ISSUE you a SIN. A criminal one.
|
|
2413
|
+
* Catch-and-release always (arrest never ends a career by fiat --
|
|
2414
|
+
* the record it leaves does the long-term damage). */
|
|
2415
|
+
processArrest(heat) {
|
|
2416
|
+
const p = this.player;
|
|
2417
|
+
const lines = [];
|
|
2418
|
+
const fine = Math.min(p.currency, 1000 + heat * 100);
|
|
2419
|
+
if (fine > 0)
|
|
2420
|
+
p.adjustCurrency(-fine);
|
|
2421
|
+
lines.push(`Processing: prints, retinas, a genome swab, every camera in the room.${fine > 0 ? ` The "administrative fee" is ${fine} nuyen.` : ` Your sticks come up empty; the fee rides your new file.`}`);
|
|
2422
|
+
if (!p.qualities.has('sinner-criminal') && !p.qualities.has('sinner-national') && !p.qualities.has('sinner-corp')) {
|
|
2423
|
+
p.addQuality('sinner-criminal');
|
|
2424
|
+
lines.push(`{red-fg}And the worst of it: they ISSUE you a SIN. A CRIMINAL SIN -- broadcast always, 15% of every payout withheld, forever in the system. Get caught being nobody, become somebody with a record.{/red-fg}`);
|
|
2425
|
+
}
|
|
2426
|
+
else {
|
|
2427
|
+
lines.push(`The record deepens. (Notoriety.)`);
|
|
2428
|
+
}
|
|
2429
|
+
p.addNotoriety('arrested on the ride home');
|
|
2430
|
+
lines.push(`They cut you loose before dawn -- catch-and-release. Holding a runner costs more than the paperwork is worth.`);
|
|
2431
|
+
Logger.getInstance().write(`Checkpoint arrest: fine ${fine}, heat ${heat}, criminal SIN ${p.qualities.has('sinner-criminal')}.`);
|
|
2432
|
+
return lines.join('\n');
|
|
2310
2433
|
}
|
|
2311
2434
|
/**
|
|
2312
2435
|
* The actual drafting work, split out from continueToNextScene() purely
|
|
@@ -2429,6 +2552,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2429
2552
|
*/
|
|
2430
2553
|
async activateRunScene(seed, tier, opts) {
|
|
2431
2554
|
this._tier = tier;
|
|
2555
|
+
// A fresh run starts COLD -- heat is a per-night debt.
|
|
2556
|
+
this._runHeat = 0;
|
|
2432
2557
|
this.sceneSeed = seed;
|
|
2433
2558
|
// A call partner from the old scene wouldn't exist in the new one --
|
|
2434
2559
|
// hang up before moving on, rather than leave a dangling reference.
|
|
@@ -2973,6 +3098,12 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2973
3098
|
const stunColor = trackColor(player.stunTaken, player.maxStunBoxes);
|
|
2974
3099
|
lines.push(`{${physColor}-fg}Physical ${player.conditionSummary()}{/${physColor}-fg} ` +
|
|
2975
3100
|
`{${stunColor}-fg}Stun ${player.stunSummary()}{/${stunColor}-fg}${woundNote}`);
|
|
3101
|
+
// HEAT (SINs & getting caught): the district's attention, in bands
|
|
3102
|
+
// -- the number stays the GM's. Absent entirely while cold, so hub
|
|
3103
|
+
// life never shows it.
|
|
3104
|
+
const heat = this.heatBand();
|
|
3105
|
+
if (heat && player === this.player)
|
|
3106
|
+
lines.push(heat);
|
|
2976
3107
|
// Line 3: readiness -- armed state, armor, and whether the commlink is
|
|
2977
3108
|
// actually live (equipped), the recurring "why can't I call anyone"
|
|
2978
3109
|
// friction surfaced at a glance.
|
|
@@ -3275,7 +3406,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3275
3406
|
// players" / "crew street"); the bare verb still answers quietly.
|
|
3276
3407
|
{ title: 'party', entries: [['crew', 'party'], ['hire'], ['dismiss'], ['train'], ['order'], ['lead'], ['command'], ['deploy'], ['recall']] },
|
|
3277
3408
|
{ title: 'magic', entries: [['spells'], ['cast'], ['summon', 'conjure'], ['project'], ['return'], ['assense']] },
|
|
3278
|
-
{ title: 'matrix', entries: [['jack'], ['deck'], ['load'], ['unload'], ['hack'], ['pan'], ['silent'], ['reboot'], ['agent'], ['drone'], ['jump', 'rig']] },
|
|
3409
|
+
{ title: 'matrix', entries: [['jack'], ['deck'], ['load'], ['unload'], ['hack'], ['overwatch', 'os'], ['pan'], ['silent'], ['reboot'], ['agent'], ['drone'], ['jump', 'rig']] },
|
|
3279
3410
|
// The Emerged get their own shelf (player request: "there MUST be
|
|
3280
3411
|
// resonance" -- matrix is the place, Resonance is the talent).
|
|
3281
3412
|
{ title: 'resonance', entries: [['compile', 'decompile'], ['thread'], ['register']] },
|