@maka/maka-cli 5.181.0 → 5.183.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/attack.js +20 -11
- package/bundle/typescript/src/commands/game/sideQuest/commands/disable.js +331 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/enter-host.js +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +17 -10
- package/bundle/typescript/src/commands/game/sideQuest/commands/jack.js +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +13 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +84 -30
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +10 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.js +51 -40
- package/bundle/typescript/src/commands/game/sideQuest/models/device.js +40 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/host.js +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/item.js +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +44 -17
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-turn.js +91 -23
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.js +30 -4
- package/bundle/typescript/src/commands/game/sideQuest/utilities/host-combat.js +107 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/ic-brain.js +84 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/ic.js +84 -51
- package/bundle/typescript/src/commands/game/sideQuest/utilities/npc-combat-brain.js +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/perception.js +50 -21
- package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js +6 -2
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/surveillance.js +4 -0
- package/package.json +1 -1
|
@@ -297,37 +297,37 @@ export function runHostTurn(room, actor, scene) {
|
|
|
297
297
|
return ambientPatrol(room, actor);
|
|
298
298
|
}
|
|
299
299
|
/**
|
|
300
|
-
*
|
|
300
|
+
* THE START OF A HOST'S COMBAT TURN (p.247): "a host can launch one IC
|
|
301
|
+
* program per Combat Turn, at the beginning of each Combat Turn", up to
|
|
302
|
+
* its rating running, never two of a kind. Crashed programs leave the
|
|
303
|
+
* list first and come back the very next turn (p.355-356, "delayed,
|
|
304
|
+
* never destroyed") -- the relaunch takes the turn's one launch.
|
|
301
305
|
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
* ALERTED HOST counts as one Combat Turn. That is the closest structural
|
|
307
|
-
* analogue available; it is not something the book says, and it is not
|
|
308
|
-
* dressed up as a citation.
|
|
306
|
+
* Launches only; nothing acts here. In play the encounter's newTurn
|
|
307
|
+
* hook calls this (utilities/host-combat.ts) and each program then acts
|
|
308
|
+
* in its own Action Phase (utilities/ic-brain.ts). Returns the actor a
|
|
309
|
+
* launch produced, so the caller can seat it in the fight.
|
|
309
310
|
*/
|
|
310
|
-
export function
|
|
311
|
-
if (!hostIsHunting(room, actor))
|
|
312
|
-
return empty();
|
|
313
|
-
const out = empty();
|
|
311
|
+
export function hostLaunchStep(room, scene, out) {
|
|
314
312
|
const host = room.host;
|
|
315
|
-
|
|
316
|
-
// filled since the last turn is off the list now, and the host
|
|
317
|
-
// relaunches it the very next turn -- "delayed, never destroyed".
|
|
313
|
+
let launched;
|
|
318
314
|
if (host) {
|
|
319
|
-
//
|
|
320
|
-
//
|
|
315
|
+
// Crashes first, then the relaunch: a program whose monitor filled
|
|
316
|
+
// during the last turn is off the list at the start of this one and
|
|
317
|
+
// comes straight back -- "reactivated the very next turn" -- taking
|
|
318
|
+
// the turn's one launch.
|
|
319
|
+
for (const kind of crashedIC(host)) {
|
|
320
|
+
crashIC(scene, host, kind);
|
|
321
|
+
out.lines.push(` {red-fg}${kind} shears apart -- crashed. The host is already recompiling it.{/red-fg}`);
|
|
322
|
+
}
|
|
321
323
|
if (host.relaunchQueue.length > 0 && room.runningIC.length < room.hostRating) {
|
|
322
324
|
const kind = host.relaunchQueue.shift();
|
|
323
325
|
room.runningIC.push(kind);
|
|
324
326
|
if (scene)
|
|
325
|
-
spawnIC(scene, host, kind);
|
|
327
|
+
launched = spawnIC(scene, host, kind);
|
|
326
328
|
out.lines.push(` {red-fg}The host relaunches ${kind}.{/red-fg}`);
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
crashIC(scene, host, kind);
|
|
330
|
-
out.lines.push(` {red-fg}${kind} shears apart -- crashed. The host is already recompiling it.{/red-fg}`);
|
|
329
|
+
// ONE launch per turn (p.247): the relaunch was it.
|
|
330
|
+
return launched;
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
333
|
if (room.runningIC.length < room.hostRating) {
|
|
@@ -335,26 +335,49 @@ export function runICTurn(room, actor, scene) {
|
|
|
335
335
|
if (next) {
|
|
336
336
|
room.runningIC.push(next.name);
|
|
337
337
|
if (host && scene)
|
|
338
|
-
spawnIC(scene, host, next.name);
|
|
338
|
+
launched = spawnIC(scene, host, next.name) ?? launched;
|
|
339
339
|
out.lines.push(next.kind === 'patrol'
|
|
340
340
|
? ` {red-fg}${next.name} is already walking the host -- it never had to be launched.{/red-fg}`
|
|
341
341
|
: ` {red-fg}The host launches ${next.name}.{/red-fg}`);
|
|
342
342
|
}
|
|
343
343
|
}
|
|
344
|
-
|
|
345
|
-
|
|
344
|
+
return launched;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* ONE PROGRAM'S ACTION -- an IC attack is a Complex Action (p.247), which
|
|
348
|
+
* is one program's whole Action Phase. `ice` is the program's own actor
|
|
349
|
+
* when the scene has one: it is what a failed attack damages.
|
|
350
|
+
*/
|
|
351
|
+
export function icActs(room, actor, type, out, ice) {
|
|
352
|
+
if (type.kind === 'patrol')
|
|
353
|
+
patrolActs(room, actor, out);
|
|
354
|
+
else if (type.kind === 'probe')
|
|
355
|
+
probeActs(room, actor, type, out, ice);
|
|
356
|
+
else if (type.kind === 'damage')
|
|
357
|
+
damageActs(room, actor, type, out, ice);
|
|
358
|
+
else
|
|
359
|
+
reducerOrConditionalActs(room, actor, type, out, ice);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* ONE WHOLE COMBAT TURN of the host's ice, as a single call: the launch
|
|
363
|
+
* step, then every running program acts once. This is the shape the
|
|
364
|
+
* tests drive and the shape the engine used to run after EVERY player
|
|
365
|
+
* command ("one action inside an alerted host = one Combat Turn" -- an
|
|
366
|
+
* engine mapping that was never a rule, and that had four programs
|
|
367
|
+
* hitting a persona for typing "look"). In play the Combat Turn owns the
|
|
368
|
+
* pacing now (utilities/host-combat.ts): the launch at newTurn, each
|
|
369
|
+
* program in its own phase.
|
|
370
|
+
*/
|
|
371
|
+
export function runICTurn(room, actor, scene) {
|
|
372
|
+
if (!hostIsHunting(room, actor))
|
|
373
|
+
return empty();
|
|
374
|
+
const out = empty();
|
|
375
|
+
hostLaunchStep(room, scene, out);
|
|
346
376
|
for (const name of room.runningIC) {
|
|
347
377
|
const type = IC_TYPES.find(t => t.name === name);
|
|
348
378
|
if (!type)
|
|
349
379
|
continue;
|
|
350
|
-
|
|
351
|
-
patrolActs(room, actor, out);
|
|
352
|
-
else if (type.kind === 'probe')
|
|
353
|
-
probeActs(room, actor, out);
|
|
354
|
-
else if (type.kind === 'damage')
|
|
355
|
-
damageActs(room, actor, type, out);
|
|
356
|
-
else
|
|
357
|
-
reducerOrConditionalActs(room, actor, type, out);
|
|
380
|
+
icActs(room, actor, type, out, room.host?.iceActors.get(name));
|
|
358
381
|
}
|
|
359
382
|
return out;
|
|
360
383
|
}
|
|
@@ -363,13 +386,29 @@ export function runICTurn(room, actor, scene) {
|
|
|
363
386
|
* v. Intuition + Firewall (p.248). Returns the net hits; 0 or less is a
|
|
364
387
|
* miss. Shared so the four reducers, Track and Scramble cannot drift
|
|
365
388
|
* from Killer's version of the same roll.
|
|
389
|
+
*
|
|
390
|
+
* A FAILED ATTACK ACTION HURTS THE ATTACKER (p.247 "as with all Attack
|
|
391
|
+
* actions, a failed attack causes damage to the IC"; the amount is the
|
|
392
|
+
* general rule, Data Trails p.181: one box of Matrix damage per net hit
|
|
393
|
+
* the defender got, unresisted). Patrol makes no Attack action and is
|
|
394
|
+
* never here (hurtByFailure false). Applied to the program's own actor,
|
|
395
|
+
* which is its condition monitor (ic-actors.ts); a program crashed this
|
|
396
|
+
* way leaves at the next launch step like any other.
|
|
366
397
|
*/
|
|
367
|
-
function icAttack(room, actor, type, out) {
|
|
398
|
+
function icAttack(room, actor, type, out, ice) {
|
|
368
399
|
const { pool, limit } = icAttackPool(room);
|
|
369
400
|
const attack = rollPool(pool, limit);
|
|
370
401
|
const defence = rollPool(icDefencePool(actor));
|
|
371
402
|
const net = attack.hits - defence.hits;
|
|
372
403
|
out.meta.push(`${type.name} -- Host Rating x2 [Attack ${limit}]: ${formatRoll(attack)} v. Intuition + Firewall: ${formatRoll(defence)} (net ${net})`);
|
|
404
|
+
if (net < 0 && type.hurtByFailure && ice && !ice.isDown()) {
|
|
405
|
+
const rebound = -net;
|
|
406
|
+
ice.takeDamage(rebound);
|
|
407
|
+
out.meta.push(`${type.name} -- failed Attack action: ${rebound} box${rebound === 1 ? '' : 'es'} back on the program, unresisted (p.247)`);
|
|
408
|
+
out.lines.push(ice.isDown()
|
|
409
|
+
? ` {red-fg}${ice.name}'s own attack code rejects and tears it apart -- ${rebound} box${rebound === 1 ? '' : 'es'}, and its monitor fills.{/red-fg}`
|
|
410
|
+
: ` ${ice.name} eats its own rejected code -- ${rebound} box${rebound === 1 ? '' : 'es'} (${ice.conditionSummary()}).`);
|
|
411
|
+
}
|
|
373
412
|
return net;
|
|
374
413
|
}
|
|
375
414
|
/**
|
|
@@ -389,8 +428,8 @@ function icAttack(room, actor, type, out) {
|
|
|
389
428
|
* against the next IC, and Jammer really does lower the ceiling on your
|
|
390
429
|
* own Brute Force.
|
|
391
430
|
*/
|
|
392
|
-
function reducerOrConditionalActs(room, actor, type, out) {
|
|
393
|
-
const net = icAttack(room, actor, type, out);
|
|
431
|
+
function reducerOrConditionalActs(room, actor, type, out, ice) {
|
|
432
|
+
const net = icAttack(room, actor, type, out, ice);
|
|
394
433
|
if (net <= 0) {
|
|
395
434
|
out.lines.push(` ${type.name} probes at your icon and finds no purchase.`);
|
|
396
435
|
return;
|
|
@@ -467,17 +506,13 @@ function applyMatrixDamage(actor, dealt, type, out) {
|
|
|
467
506
|
* Probe IC spends its turns building. That coupling is canon's own, and
|
|
468
507
|
* it is why Probe is worth a slot on a host that also fields a Killer.
|
|
469
508
|
*
|
|
470
|
-
* A FAILED IC ATTACK
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
* attack ice, and neither exists: there is no cybercombat path against
|
|
474
|
-
* IC in this engine at all. Half of it -- ice that hurts itself while
|
|
475
|
-
* the player still cannot swing back -- would be worse than neither
|
|
476
|
-
* half. It lands with the verb.
|
|
509
|
+
* A FAILED IC ATTACK DAMAGES THE IC (p.247) -- see icAttack, where it
|
|
510
|
+
* lands for every attacking program at once. The player's side of it,
|
|
511
|
+
* cybercombat against ice, is `attack <program>` inside the host.
|
|
477
512
|
*/
|
|
478
|
-
function damageActs(room, actor, type, out) {
|
|
513
|
+
function damageActs(room, actor, type, out, ice) {
|
|
479
514
|
const { limit } = icAttackPool(room);
|
|
480
|
-
const net = icAttack(room, actor, type, out);
|
|
515
|
+
const net = icAttack(room, actor, type, out, ice);
|
|
481
516
|
if (net <= 0) {
|
|
482
517
|
out.lines.push(` ${type.name} lunges at your icon and closes on nothing.`);
|
|
483
518
|
return;
|
|
@@ -608,12 +643,10 @@ function patrolActs(room, actor, out) {
|
|
|
608
643
|
* read in Phase 4. The engine previously modelled the host's side as the
|
|
609
644
|
* single boolean `hostAlert`, which cannot answer "how many".
|
|
610
645
|
*/
|
|
611
|
-
function probeActs(room, actor, out) {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const
|
|
615
|
-
const net = attack.hits - defence.hits;
|
|
616
|
-
out.meta.push(`Probe IC -- Host Rating x2 [Attack ${limit}]: ${formatRoll(attack)} v. Intuition + Firewall: ${formatRoll(defence)} (net ${net})`);
|
|
646
|
+
function probeActs(room, actor, type, out, ice) {
|
|
647
|
+
// The same opposed roll as every attacking program (icAttack), so a
|
|
648
|
+
// failed probe rebounds on the program like any failed Attack action.
|
|
649
|
+
const net = icAttack(room, actor, type, out, ice);
|
|
617
650
|
const held = room.hostMarksOn.get(actor.name) ?? 0;
|
|
618
651
|
if (net <= 0) {
|
|
619
652
|
out.lines.push(` Probe IC tests your edges and finds nothing to hold onto.`);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Category } from '../types/shared/item-enum.js';
|
|
2
2
|
import { inMeleeReach, isInCover, coverAvailableFor, actorDistanceMeters } from './spots.js';
|
|
3
3
|
import { Logger } from './logger.js';
|
|
4
|
+
import { runIcActionPhase } from './ic-brain.js';
|
|
4
5
|
/**
|
|
5
6
|
* AN NPC'S ACTION PHASE, PLAYED BY RULE (SR5 p.163-167).
|
|
6
7
|
*
|
|
@@ -25,6 +26,10 @@ import { Logger } from './logger.js';
|
|
|
25
26
|
* Nothing here rolls a die of its own: the verbs do.
|
|
26
27
|
*/
|
|
27
28
|
export async function runNpcActionPhase(enc, npc) {
|
|
29
|
+
// A HOST'S PROGRAM HAS ITS OWN BRAIN (utilities/ic-brain.ts): no
|
|
30
|
+
// weapon to draw, no ground to close -- one test, by type.
|
|
31
|
+
if (npc.icKind !== undefined)
|
|
32
|
+
return runIcActionPhase(enc, npc);
|
|
28
33
|
const logger = Logger.getInstance();
|
|
29
34
|
if (npc.isIncapacitated() || npc.surrendered)
|
|
30
35
|
return;
|
|
@@ -39,36 +39,65 @@ import { mentalLimit } from './limits.js';
|
|
|
39
39
|
* an active search mechanically different from a passive glance. */
|
|
40
40
|
export const ACTIVELY_LOOKING = 3;
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
|
|
42
|
+
* THE MEAT PERCEPTION TEST. The Matrix used to ride through here as a
|
|
43
|
+
* "different test with a different bracket" -- Logic + the better of
|
|
44
|
+
* Perception and Computer [Data Processing] -- which was neither Matrix
|
|
45
|
+
* Perception nor Matrix Search. It is matrixSearchTest now, below; the
|
|
46
|
+
* grid never comes through this door.
|
|
47
|
+
*/
|
|
48
|
+
export function perceptionTest(actor, opts = {}) {
|
|
49
|
+
const skill = actor.skillRating('perception');
|
|
50
|
+
// Unskilled defaults at -1 (p.130), exactly as both callers already did.
|
|
51
|
+
const pool = Math.max(1, actor.intuition
|
|
52
|
+
+ (skill > 0 ? skill : -1)
|
|
53
|
+
+ (opts.active ? ACTIVELY_LOOKING : 0)
|
|
54
|
+
+ actor.bonus('perception')
|
|
55
|
+
+ actor.woundModifier
|
|
56
|
+
- actor.sustainingPenalty);
|
|
57
|
+
return { pool, limit: mentalLimit(actor) };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* MATRIX SEARCH (SR5 p.241, RAG-checked 2026-09-13): a Special action,
|
|
61
|
+
* "Computer + Intuition [Data Processing]" -- INTUITION, not Logic; the
|
|
62
|
+
* old grid branch above rolled Logic and called it canon. The threshold
|
|
63
|
+
* and the base time come from what is being looked for (the Matrix
|
|
64
|
+
* Search Table: public 1 / 1 minute, limited 3 / 30 minutes; inside a
|
|
65
|
+
* host the base time is one minute whatever the information), net hits
|
|
66
|
+
* over the threshold divide the time, and a failure still spends all
|
|
67
|
+
* of it. A Browse program "cuts the base time in half" (Splintered
|
|
68
|
+
* State p.54) -- time, not dice. Running silent costs its -2 like every
|
|
69
|
+
* Matrix action (p.235-236).
|
|
48
70
|
*
|
|
49
71
|
* Data Processing reads 0 for an actor with no deck at all; that is a
|
|
50
72
|
* caller's missing gate rather than a real limit, so it degrades to
|
|
51
73
|
* unlimited here instead of handing rollPool a zero it would reject.
|
|
52
74
|
*/
|
|
53
|
-
export function
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
? Math.max(actor.skillRating('perception'), actor.skillRating('computer'))
|
|
57
|
-
: actor.skillRating('perception');
|
|
58
|
-
const attribute = onGrid ? actor.logic : actor.intuition;
|
|
59
|
-
// Unskilled defaults at -1 (p.130), exactly as both callers already did.
|
|
60
|
-
const pool = Math.max(1, attribute
|
|
75
|
+
export function matrixSearchTest(actor) {
|
|
76
|
+
const skill = actor.skillRating('computer');
|
|
77
|
+
const pool = Math.max(1, actor.intuition
|
|
61
78
|
+ (skill > 0 ? skill : -1)
|
|
62
|
-
+ (opts.browse ?? 0)
|
|
63
|
-
+ (opts.active ? ACTIVELY_LOOKING : 0)
|
|
64
79
|
+ actor.bonus('perception')
|
|
80
|
+
+ (typeof actor.matrixActionPenalty === 'number' ? actor.matrixActionPenalty : 0)
|
|
65
81
|
+ actor.woundModifier
|
|
66
82
|
- actor.sustainingPenalty);
|
|
67
|
-
const dataProcessing =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
83
|
+
const dataProcessing = actor.matrixAttribute('dataProcessing');
|
|
84
|
+
return { pool, limit: dataProcessing > 0 ? dataProcessing : undefined };
|
|
85
|
+
}
|
|
86
|
+
/** The Matrix Search Table (p.241-242): threshold and base time in
|
|
87
|
+
* seconds. Inside a host the base time is one minute regardless. */
|
|
88
|
+
export const MATRIX_SEARCH = {
|
|
89
|
+
public: { threshold: 1, seconds: 60 },
|
|
90
|
+
inHost: { threshold: 3, seconds: 60 },
|
|
91
|
+
};
|
|
92
|
+
/** How long a search takes once rolled: the base time divided by the
|
|
93
|
+
* net hits over the threshold, never under one Combat Turn (3s); a
|
|
94
|
+
* failure spends the whole base time. Browse halves the base. */
|
|
95
|
+
export function matrixSearchSeconds(baseSeconds, hits, threshold, browse) {
|
|
96
|
+
const base = browse ? Math.ceil(baseSeconds / 2) : baseSeconds;
|
|
97
|
+
if (hits < threshold)
|
|
98
|
+
return base;
|
|
99
|
+
const net = hits - threshold;
|
|
100
|
+
return net > 0 ? Math.max(3, Math.ceil(base / net)) : base;
|
|
72
101
|
}
|
|
73
102
|
/**
|
|
74
103
|
* PERCEPTION THRESHOLDS (p.136). Named because the numbers appear in
|
|
@@ -164,6 +164,8 @@ export function serializeItem(item) {
|
|
|
164
164
|
out.loadedAmmo = item.loadedAmmo;
|
|
165
165
|
if (item.jammed)
|
|
166
166
|
out.jammed = true;
|
|
167
|
+
if (item.bricked)
|
|
168
|
+
out.bricked = true;
|
|
167
169
|
if (item.deckDamage > 0)
|
|
168
170
|
out.deckDamage = item.deckDamage;
|
|
169
171
|
if (item.droneDamage > 0)
|
|
@@ -219,6 +221,8 @@ export function restoreItem(json) {
|
|
|
219
221
|
item.loadedAmmo = json.loadedAmmo;
|
|
220
222
|
if (json.jammed)
|
|
221
223
|
item.jammed = true;
|
|
224
|
+
if (json.bricked)
|
|
225
|
+
item.bricked = true;
|
|
222
226
|
if (json.deckDamage)
|
|
223
227
|
item.takeDeckDamage(json.deckDamage);
|
|
224
228
|
if (json.droneDamage)
|
|
@@ -663,7 +667,7 @@ export function captureHubOverlay(scene, hubSeed, player, homeRoom) {
|
|
|
663
667
|
.map(n => n.player.name)
|
|
664
668
|
.filter(name => !scene.getActor(name));
|
|
665
669
|
const devices = [...collectDevices(scene).entries()]
|
|
666
|
-
.map(([name, d]) => ({ name, open: d.isOpen(), ...(d.barrierDamage > 0 ? { damage: d.barrierDamage } : {}) }));
|
|
670
|
+
.map(([name, d]) => ({ name, open: d.isOpen(), ...(d.barrierDamage > 0 ? { damage: d.barrierDamage } : {}), ...(d.isBricked ? { bricked: true } : {}) }));
|
|
667
671
|
// THE TOMBSTONE (see ISavedHub.puzzles): always [], never read here.
|
|
668
672
|
// Omitting it crashes v5.64.0's unguarded for-of on resume, and
|
|
669
673
|
// saves move between builds via the cloud mirror.
|
|
@@ -824,7 +828,7 @@ export function applyHubOverlay(scene, saved, player, homeRoom) {
|
|
|
824
828
|
for (const d of saved.devices ?? []) {
|
|
825
829
|
const live = liveDevices.get(d.name);
|
|
826
830
|
if (live)
|
|
827
|
-
live.restoreState({ open: d.open, damage: d.damage });
|
|
831
|
+
live.restoreState({ open: d.open, damage: d.damage, bricked: d.bricked });
|
|
828
832
|
}
|
|
829
833
|
// A SAVE WRITTEN BEFORE DEVICES EXISTED has no `devices` key at all,
|
|
830
834
|
// so every device would read shut -- while the door it holds stays
|
|
@@ -209,6 +209,11 @@ export const DUMPSHOCK_DV = 6;
|
|
|
209
209
|
export function leaveMatrix(scene, actor, opts) {
|
|
210
210
|
const lines = [];
|
|
211
211
|
const body = actor.bodyRoom;
|
|
212
|
+
// OUT OF THE MATRIX IS OUT OF THE FIGHT (utilities/host-combat.ts): a
|
|
213
|
+
// persona leaving its host arena -- jack out, link-lock broken, deck
|
|
214
|
+
// bricked, Scramble, convergence -- is no longer a participant. Before
|
|
215
|
+
// endPersona clears the position the arena is read off.
|
|
216
|
+
scene.encounterFor?.(actor)?.leave(actor);
|
|
212
217
|
if (opts?.forced) {
|
|
213
218
|
lines.push(...applyDumpshock(actor, opts.reason ?? `The connection is severed from the other side.`));
|
|
214
219
|
}
|
|
@@ -85,6 +85,10 @@ export function directConnectionBlocker(actor, room) {
|
|
|
85
85
|
export function camerasLive(rooms, room) {
|
|
86
86
|
if (!isWatched(room))
|
|
87
87
|
return false;
|
|
88
|
+
// Looped by Control Device or bricked by a Data Spike (commands/
|
|
89
|
+
// disable.ts): the house sees nothing from this cluster either way.
|
|
90
|
+
if (room.camerasLooped || room.camerasBricked)
|
|
91
|
+
return false;
|
|
88
92
|
if (room.hasNode)
|
|
89
93
|
return !room.hostCracked;
|
|
90
94
|
const hosts = Object.values(rooms).filter(r => r.hasNode);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.183.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"summary": "A command line tool for scaffolding Meteor 3.x applications using either React.",
|
|
6
6
|
"description": "A command line tool for scaffolding Meteor 3.x applications using React.",
|