@maka/maka-cli 5.228.0 → 5.230.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/pan.js +122 -18
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +48 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/item.js +18 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +91 -5
- package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js +11 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.js +10 -4
- package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js +6 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.230.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.",
|
|
@@ -1,19 +1,99 @@
|
|
|
1
1
|
import { Command } from './command.js';
|
|
2
2
|
import { hint } from '../utilities/hints.js';
|
|
3
|
+
import { Player } from '../models/player.js';
|
|
3
4
|
import { Category } from '../types/shared/item-enum.js';
|
|
5
|
+
import { billAction } from '../utilities/action-cost.js';
|
|
4
6
|
/**
|
|
5
7
|
* The PAN dashboard: your personal area network at a glance -- master
|
|
6
8
|
* device and its firewall, every slaved device with what it grants while
|
|
7
9
|
* loud, and the silent-vs-loud tradeoff spelled out. This is the screen
|
|
8
10
|
* you check before deciding whether the AR bonus is worth being hackable.
|
|
11
|
+
*
|
|
12
|
+
* AND THE SWITCH (CgJ6uTfEvTd8JXdDN): "pan off <device>" drops one
|
|
13
|
+
* device off the network and "pan on <device>" puts it back. The
|
|
14
|
+
* reporter asked for `pan link <device>` and `pan link <device> on|off`;
|
|
15
|
+
* `link`/`unlink` are accepted as spellings of the same two verbs, but
|
|
16
|
+
* membership itself is DERIVED rather than hand-built, which is the
|
|
17
|
+
* reporter's own ruling: "most equipment is wireless, so a lot can just
|
|
18
|
+
* be that easy." Nobody has to assemble their network before it works.
|
|
9
19
|
*/
|
|
10
20
|
export class PanCommand extends Command {
|
|
11
21
|
static verb = 'pan';
|
|
12
|
-
static description = 'Your personal area network: master device, slaved gear, wireless bonuses, and what running silent costs and protects.';
|
|
13
|
-
async execute(
|
|
22
|
+
static description = 'Your personal area network: master device, slaved gear, wireless bonuses, and what running silent costs and protects. "pan off <device>" drops one off the net.';
|
|
23
|
+
async execute(args = []) {
|
|
24
|
+
const [head, ...rest] = args;
|
|
25
|
+
const verb = (head ?? '').toLowerCase();
|
|
26
|
+
// "pan link <device> off" (the reporter's spelling) and "pan off
|
|
27
|
+
// <device>" mean the same thing. A trailing on/off wins over the
|
|
28
|
+
// leading verb, so `pan link X off` unlinks rather than linking.
|
|
29
|
+
if (verb === 'on' || verb === 'off' || verb === 'link' || verb === 'unlink') {
|
|
30
|
+
const tail = (rest[rest.length - 1] ?? '').toLowerCase();
|
|
31
|
+
const trailing = tail === 'on' || tail === 'off' ? tail : undefined;
|
|
32
|
+
const nameParts = trailing ? rest.slice(0, -1) : rest;
|
|
33
|
+
const wanted = trailing ?? (verb === 'on' || verb === 'link' ? 'on' : 'off');
|
|
34
|
+
return this.switchDevice(nameParts.join(' ').trim(), wanted === 'on');
|
|
35
|
+
}
|
|
36
|
+
return this.dashboard();
|
|
37
|
+
}
|
|
38
|
+
/** One device on or off the network (p.233 -- slaving is a choice). */
|
|
39
|
+
switchDevice(name, on) {
|
|
40
|
+
const a = this.actor;
|
|
41
|
+
if (!name)
|
|
42
|
+
return `${on ? 'Link' : 'Unlink'} what? Try "pan ${on ? 'on' : 'off'} <device>".`;
|
|
43
|
+
const candidates = [
|
|
44
|
+
a.equipment.head?.commlink,
|
|
45
|
+
a.equipment.rightHand?.weapon,
|
|
46
|
+
a.equipment.leftHand?.weapon,
|
|
47
|
+
...a.inventory.getAllItems(),
|
|
48
|
+
].filter((i) => i != null);
|
|
49
|
+
const needle = name.toLowerCase();
|
|
50
|
+
const item = candidates.find(i => i.name.toLowerCase() === needle)
|
|
51
|
+
?? candidates.find(i => i.name.toLowerCase().includes(needle));
|
|
52
|
+
if (!item)
|
|
53
|
+
return `You are not carrying "${name}".`;
|
|
54
|
+
if (!Player.canJoinPan(item)) {
|
|
55
|
+
// p.233 is explicit that only DEVICES can be in a PAN, so this is
|
|
56
|
+
// a refusal with a reason rather than a silent no-op.
|
|
57
|
+
return `${item.name} is not a device -- only devices can be slaved to a PAN (p.233). Nothing to switch.`;
|
|
58
|
+
}
|
|
59
|
+
if (item.wirelessOn === on) {
|
|
60
|
+
return `${item.name} is already wireless ${on ? 'ON' : 'OFF'}.`;
|
|
61
|
+
}
|
|
62
|
+
// p.421, "Turning It Off": "Toggling an individual device's wireless
|
|
63
|
+
// functionality off is a Free Action." Priced where the book prices
|
|
64
|
+
// it -- which also means it is free OUT of combat, because
|
|
65
|
+
// billAction only charges inside an encounter.
|
|
66
|
+
const bill = billAction(this.scene, a, 'free', `Wireless ${on ? 'On' : 'Off'}`);
|
|
67
|
+
if (bill)
|
|
68
|
+
return bill;
|
|
69
|
+
item.wirelessOn = on;
|
|
70
|
+
if (!on) {
|
|
71
|
+
return [
|
|
72
|
+
`${item.name} drops off the network -- wireless OFF.`,
|
|
73
|
+
`It broadcasts nothing now: no bonus, no icon, nothing to hack. It also stops borrowing your master's ratings to defend itself (p.233), which is only a loss if anyone was looking.`,
|
|
74
|
+
].join('\n');
|
|
75
|
+
}
|
|
76
|
+
const over = a.panOverflow();
|
|
77
|
+
const lines = [`${item.name} joins the network -- wireless ON.`];
|
|
78
|
+
if (over > 0) {
|
|
79
|
+
// Canon ceiling, said out loud rather than truncating in silence.
|
|
80
|
+
lines.push(`Your master handles ${a.panCapacity()} slaved devices (Device Rating x 3, p.233) and you are carrying ${over} too many. The overflow is not on the net -- switch something off.`);
|
|
81
|
+
}
|
|
82
|
+
return lines.join('\n');
|
|
83
|
+
}
|
|
84
|
+
dashboard() {
|
|
14
85
|
const a = this.actor;
|
|
15
86
|
const master = a.getPanMaster();
|
|
16
87
|
if (!master) {
|
|
88
|
+
const off = a.inventory.getAllItems().filter(i => !i.wirelessOn && Player.canJoinPan(i));
|
|
89
|
+
if (off.length > 0) {
|
|
90
|
+
// The honest reason, because "no PAN" reads like a bug when you
|
|
91
|
+
// are holding a commlink you switched off yourself.
|
|
92
|
+
return [
|
|
93
|
+
`No PAN -- everything that could master one is switched off.`,
|
|
94
|
+
...off.map(i => ` • ${i.name} -- wireless OFF${hint(` ("pan on ${i.name}")`)}`),
|
|
95
|
+
].join('\n');
|
|
96
|
+
}
|
|
17
97
|
return `No devices, no PAN. You are unhackable and unreachable -- the monk's build.`;
|
|
18
98
|
}
|
|
19
99
|
const state = a.runningSilent
|
|
@@ -21,11 +101,12 @@ export class PanCommand extends Command {
|
|
|
21
101
|
: a.arOffline
|
|
22
102
|
? `{red-fg}LOUD, AR CUT{/red-fg} -- someone hacked your overlay dark ("reboot")`
|
|
23
103
|
: `{green-fg}LOUD{/green-fg} -- full bonuses, and hackable`;
|
|
104
|
+
const slaves = a.panSlaves();
|
|
24
105
|
const lines = [
|
|
25
106
|
`PAN: ${a.name.toUpperCase()}`,
|
|
26
107
|
'',
|
|
27
108
|
`Broadcast: ${state}`,
|
|
28
|
-
`Master: ${master.name}`,
|
|
109
|
+
`Master: ${master.name} [DR ${master.deviceRating}] -- holds ${slaves.length}/${a.panCapacity()} slaved devices (DR x 3, p.233)`,
|
|
29
110
|
// TWO DEFENCES, BECAUSE THERE ARE TWO (p.240 / p.238). What the
|
|
30
111
|
// intruder is DOING picks the attribute, so a quiet crack and a
|
|
31
112
|
// smash meet different pools -- for most characters, different
|
|
@@ -42,24 +123,26 @@ export class PanCommand extends Command {
|
|
|
42
123
|
'',
|
|
43
124
|
'Slaved devices:',
|
|
44
125
|
];
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const bonus = a.runningSilent || a.arOffline ? 'inactive' : `+${link.qualityBonus} initiative (AR overlay)`;
|
|
48
|
-
lines.push(` • ${link.name} (commlink) -- calls; ${bonus}`);
|
|
126
|
+
if (slaves.length === 0) {
|
|
127
|
+
lines.push(' (none -- the master is the whole network)');
|
|
49
128
|
}
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
lines.push(` • ${deck.name} (cyberdeck) -- Matrix access; ${deck.deckSummary()}${deck.isBricked ? ' BRICKED (rest to repair)' : ''}`);
|
|
129
|
+
for (const item of slaves) {
|
|
130
|
+
lines.push(` • ${this.slaveLine(item)}`);
|
|
53
131
|
}
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
lines.push(` • ${gun.name} (smartgun) -- ${gun.jammed ? '{red-fg}JAMMED -- smartlink cracked ("reboot"){/red-fg}' : 'smartlink live'}`);
|
|
132
|
+
const over = a.panOverflow();
|
|
133
|
+
if (over > 0) {
|
|
134
|
+
lines.push('');
|
|
135
|
+
lines.push(`{red-fg}${over} device${over === 1 ? '' : 's'} over capacity{/red-fg} -- a master handles only (Device Rating x 3) slaves (p.233). The excess is off the net.`);
|
|
59
136
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
137
|
+
// Anything deliberately switched off is listed too: a device you
|
|
138
|
+
// cannot see on this screen is a device you will forget you muted.
|
|
139
|
+
const off = [...a.inventory.getAllItems()].filter(i => !i.wirelessOn && Player.canJoinPan(i));
|
|
140
|
+
if (off.length > 0) {
|
|
141
|
+
lines.push('');
|
|
142
|
+
lines.push('Switched off:');
|
|
143
|
+
for (const item of off) {
|
|
144
|
+
lines.push(` · ${item.name} -- wireless OFF, no icon, nothing to hack${hint(` ("pan on ${item.name}")`)}`);
|
|
145
|
+
}
|
|
63
146
|
}
|
|
64
147
|
lines.push('');
|
|
65
148
|
lines.push(a.runningSilent
|
|
@@ -67,5 +150,26 @@ export class PanCommand extends Command {
|
|
|
67
150
|
: `Running loud: any decker in reach can try your firewall -- jammed guns and dead AR are how that feels.${hint(' "silent" goes dark.')}`);
|
|
68
151
|
return lines.join('\n');
|
|
69
152
|
}
|
|
153
|
+
/** What one slaved device is and what being on the net buys it. */
|
|
154
|
+
slaveLine(item) {
|
|
155
|
+
const a = this.actor;
|
|
156
|
+
const inactive = a.runningSilent || a.arOffline;
|
|
157
|
+
if (item.isCyberdeck()) {
|
|
158
|
+
return `${item.name} (cyberdeck) -- Matrix access; ${item.deckSummary()}${item.isBricked ? ' BRICKED (rest to repair)' : ''}`;
|
|
159
|
+
}
|
|
160
|
+
if (item.category === Category.Commlink) {
|
|
161
|
+
const bonus = inactive ? 'inactive' : `+${item.qualityBonus} initiative (AR overlay)`;
|
|
162
|
+
return `${item.name} (commlink) -- calls; ${bonus}`;
|
|
163
|
+
}
|
|
164
|
+
// Only a SMARTGUN is on the PAN (p.433; catalog v13): a plain gun has
|
|
165
|
+
// nothing to broadcast and nothing to crack.
|
|
166
|
+
if (item.isFirearm()) {
|
|
167
|
+
return `${item.name} (smartgun) -- ${item.jammed ? '{red-fg}JAMMED -- smartlink cracked ("reboot"){/red-fg}' : 'smartlink live'}`;
|
|
168
|
+
}
|
|
169
|
+
if (item.category === Category.Drone) {
|
|
170
|
+
return `${item.name} (drone) -- recon uplink${hint(' ("drone")')}`;
|
|
171
|
+
}
|
|
172
|
+
return `${item.name} (${item.category})`;
|
|
173
|
+
}
|
|
70
174
|
}
|
|
71
175
|
//# sourceMappingURL=pan.js.map
|
|
@@ -1371,5 +1371,52 @@
|
|
|
1371
1371
|
// shows it with no explanation at all -- indistinguishable from an
|
|
1372
1372
|
// icon that was never hidden, which is most of why 1.80.0 read to
|
|
1373
1373
|
// the reporter as nothing having happened.
|
|
1374
|
-
|
|
1374
|
+
// 1.83.0 (2026-09-16): THE WIRELESS SWITCH (CgJ6uTfEvTd8JXdDN)
|
|
1375
|
+
// The reporter rejected the last pass and wrote the spec themselves:
|
|
1376
|
+
// "1) new command 'pan' shows you your current connected devices.
|
|
1377
|
+
// 2) 'pan link <device>' ... 3) 'pan link <device> on|off' 4) if no
|
|
1378
|
+
// commlink/deck/control rig is linked and on, and you're a
|
|
1379
|
+
// Technomancer, you default to your persona."
|
|
1380
|
+
// RAG-checked, and it is not a house rule -- it is p.421, "Turning It
|
|
1381
|
+
// Off", almost word for word:
|
|
1382
|
+
// "Toggling an individual device's wireless functionality off is a
|
|
1383
|
+
// Free Action ... YOU LOSE WIRELESS BONUSES, but the items can no
|
|
1384
|
+
// longer be wirelessly hacked."
|
|
1385
|
+
// That sentence is a TRADE, and both halves are now real.
|
|
1386
|
+
// - Item.wirelessOn, default ON. Membership in a PAN stays DERIVED
|
|
1387
|
+
// rather than becoming a list you assemble by hand -- which is the
|
|
1388
|
+
// reporter's own ruling ("most equipment is wireless, so a lot can
|
|
1389
|
+
// just be that easy") and is what keeps every existing character's
|
|
1390
|
+
// network exactly as it was. The switch is the override, not a
|
|
1391
|
+
// chore. Saved SPARSE and INVERTED (`wirelessOff`), so a save
|
|
1392
|
+
// written before today has no key and comes back on the air.
|
|
1393
|
+
// - `pan off <device>` / `pan on <device>`, with the reporter's own
|
|
1394
|
+
// `pan link <device> [on|off]` spelling accepted for the same two
|
|
1395
|
+
// verbs. Billed as p.421's FREE ACTION, which also means free
|
|
1396
|
+
// outside a fight, since billAction only charges in an encounter.
|
|
1397
|
+
// - THE "NO LONGER HACKED" HALF IS REAL, which is the half a switch
|
|
1398
|
+
// like this usually fakes: a muted commlink stops making you
|
|
1399
|
+
// isLinked (no calls, and no PAN icon for a sweep to find), a muted
|
|
1400
|
+
// gun has no grid icon for `hack` to seize, and a muted deck cannot
|
|
1401
|
+
// master a PAN.
|
|
1402
|
+
// - AND THE "LOSE WIRELESS BONUSES" HALF, split exactly where p.421
|
|
1403
|
+
// and p.433 split it: the smartgun's wireless +1 DICE goes, and its
|
|
1404
|
+
// +2 ACCURACY STAYS -- that one rides a cable to your glasses and
|
|
1405
|
+
// never needed the radio. Same discipline as the Matrix brackets: a
|
|
1406
|
+
// limit is never a die.
|
|
1407
|
+
// - Player.panSlaves/panCapacity/panOverflow, so the dashboard, the
|
|
1408
|
+
// capacity rule and "is this on the net?" all read ONE list. p.233
|
|
1409
|
+
// caps a master at (Device Rating x 3) slaves and the engine had
|
|
1410
|
+
// never counted; over-capacity is now said out loud rather than
|
|
1411
|
+
// silently truncated. This is the same drift that cost us three
|
|
1412
|
+
// disagreeing camera ratings in 1.82.0.
|
|
1413
|
+
// NOT SHIPPED, deliberately, and it is the other half of the item: a
|
|
1414
|
+
// technomancer deliberately RIDING a deck ("Using Mundane
|
|
1415
|
+
// Electronics", p.251). A device persona mutes every Resonance
|
|
1416
|
+
// ability, so that mode is worth nothing until compile/register/
|
|
1417
|
+
// thread and their siblings all know about it -- shipping the switch
|
|
1418
|
+
// alone would be a fresh canon violation wearing a feature's clothes.
|
|
1419
|
+
// A technomancer still comes up on the living persona, which is what
|
|
1420
|
+
// the reporter's point 4 asks for.
|
|
1421
|
+
export const ENGINE_VERSION = '1.83.0';
|
|
1375
1422
|
//# sourceMappingURL=engine-version.js.map
|
|
@@ -49,6 +49,24 @@ export class Item extends AbstractItem {
|
|
|
49
49
|
// work (rest.ts). A reboot does NOT clear it; that is the whole
|
|
50
50
|
// difference from `jammed`.
|
|
51
51
|
bricked = false;
|
|
52
|
+
/**
|
|
53
|
+
* THE WIRELESS SWITCH (p.233 PANs and WANs; CgJ6uTfEvTd8JXdDN: "'pan
|
|
54
|
+
* link <device> on|off'"). Slaving is a thing an owner CHOOSES, and
|
|
55
|
+
* the choice has a price both ways: a device on the network borrows
|
|
56
|
+
* its master's ratings to defend itself, and a device off it is not
|
|
57
|
+
* on the Matrix at all -- no wireless bonus, no icon to find, nothing
|
|
58
|
+
* to hack.
|
|
59
|
+
*
|
|
60
|
+
* DEFAULT ON, which is the reporter's own ruling ("most equipment is
|
|
61
|
+
* wireless, so a lot can just be that easy") and is why membership
|
|
62
|
+
* stays DERIVED rather than becoming a list you have to build by
|
|
63
|
+
* hand. Every runner who never touches `pan` keeps exactly the PAN
|
|
64
|
+
* they have today; this switch is the override.
|
|
65
|
+
*
|
|
66
|
+
* Saved SPARSE, as `wirelessOff` -- absent means on, so every save
|
|
67
|
+
* written before this existed restores unchanged.
|
|
68
|
+
*/
|
|
69
|
+
wirelessOn = true;
|
|
52
70
|
/**
|
|
53
71
|
* RUINED (Room.layOutBody, catalog v21 NPC kits): armor that soaked
|
|
54
72
|
* the rounds that killed its wearer. Rated 0 (armorValue, armorBonus)
|
|
@@ -544,10 +544,11 @@ export class Player extends AbstractPlayer {
|
|
|
544
544
|
// across jack-outs, and a full track BRICKS the deck -- forced dump,
|
|
545
545
|
// and no re-entry on that deck until it's repaired.
|
|
546
546
|
activeDeck;
|
|
547
|
-
/** The best working cyberdeck carried (bricked ones don't count
|
|
547
|
+
/** The best working cyberdeck carried (bricked ones don't count, and
|
|
548
|
+
* neither does one you have switched off -- p.233, `pan off`). */
|
|
548
549
|
getCyberdeck() {
|
|
549
550
|
return this._inventory.getAllItems()
|
|
550
|
-
.filter(i => i.isCyberdeck() && !i.isBricked)
|
|
551
|
+
.filter(i => i.isCyberdeck() && !i.isBricked && i.wirelessOn)
|
|
551
552
|
.sort((a, b) => b.qualityBonus - a.qualityBonus)[0];
|
|
552
553
|
}
|
|
553
554
|
/** Any deck at all, bricked included -- for status displays and repair. */
|
|
@@ -960,13 +961,91 @@ export class Player extends AbstractPlayer {
|
|
|
960
961
|
// Worn first, then CARRIED (playtest 2026-08-24: NPCs keep their
|
|
961
962
|
// links in inventory, not the head slot -- a pocketed commlink is
|
|
962
963
|
// still the PAN's master; only running silent hides it).
|
|
963
|
-
|
|
964
|
-
|
|
964
|
+
// A device switched off masters nothing (p.233, `pan off`).
|
|
965
|
+
const worn = this._equipment.head?.commlink;
|
|
966
|
+
const link = (worn?.wirelessOn ? worn : undefined)
|
|
967
|
+
?? this._inventory.getAllItems().find(i => i.category === 'commlink' && i.wirelessOn)
|
|
965
968
|
?? undefined;
|
|
966
969
|
if (deck && link)
|
|
967
970
|
return deck.qualityBonus >= link.qualityBonus ? deck : link;
|
|
968
971
|
return deck ?? link;
|
|
969
972
|
}
|
|
973
|
+
/**
|
|
974
|
+
* HOW MANY SLAVES A MASTER CAN HOLD: Device Rating x 3 (p.233, "your
|
|
975
|
+
* commlink or deck can handle up to (Device Rating x 3) slaved
|
|
976
|
+
* devices"). Zero without a master, because a PAN is defined as the
|
|
977
|
+
* slaves PLUS the master -- there is no network to join.
|
|
978
|
+
*/
|
|
979
|
+
panCapacity() {
|
|
980
|
+
const master = this.getPanMaster();
|
|
981
|
+
return master ? master.deviceRating * 3 : 0;
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* THE PAN, ITEMISED (CgJ6uTfEvTd8JXdDN: "new command 'pan' shows you
|
|
985
|
+
* your current connected devices"). One list, so the dashboard, the
|
|
986
|
+
* capacity check and anything that asks "is this on the network?"
|
|
987
|
+
* cannot drift apart -- which is the bug pattern the camera cluster
|
|
988
|
+
* just cost us in eJANPpm7qCaCAJBJw.
|
|
989
|
+
*
|
|
990
|
+
* MEMBERSHIP IS DERIVED, not a hand-built list, per the reporter's own
|
|
991
|
+
* ruling: "most equipment is wireless, so a lot can just be that
|
|
992
|
+
* easy." What you carry that can be on a PAN, is -- unless you switch
|
|
993
|
+
* it off. So no existing character's network changes, and `pan off`
|
|
994
|
+
* is the override rather than `pan link` being a chore.
|
|
995
|
+
*
|
|
996
|
+
* Only DEVICES can be slaves (p.233), which is why a plain revolver is
|
|
997
|
+
* not here and a smartgun is: the smartlink is the device.
|
|
998
|
+
*/
|
|
999
|
+
panSlaves() {
|
|
1000
|
+
const master = this.getPanMaster();
|
|
1001
|
+
if (!master)
|
|
1002
|
+
return [];
|
|
1003
|
+
const carried = this._inventory.getAllItems();
|
|
1004
|
+
const worn = [
|
|
1005
|
+
this._equipment.head?.commlink,
|
|
1006
|
+
this._equipment.rightHand?.weapon,
|
|
1007
|
+
this._equipment.leftHand?.weapon,
|
|
1008
|
+
].filter((i) => i !== undefined && i !== null);
|
|
1009
|
+
const seen = new Set([master]);
|
|
1010
|
+
const out = [];
|
|
1011
|
+
for (const item of [...worn, ...carried]) {
|
|
1012
|
+
if (seen.has(item) || !item.wirelessOn)
|
|
1013
|
+
continue;
|
|
1014
|
+
if (!Player.canJoinPan(item))
|
|
1015
|
+
continue;
|
|
1016
|
+
seen.add(item);
|
|
1017
|
+
out.push(item);
|
|
1018
|
+
}
|
|
1019
|
+
// Over capacity is the master's problem, not a silent truncation --
|
|
1020
|
+
// panOverflow() reports the excess so `pan` can say so out loud.
|
|
1021
|
+
return out.slice(0, this.panCapacity());
|
|
1022
|
+
}
|
|
1023
|
+
/** Slaves that do not fit under the master's (DR x 3) ceiling. */
|
|
1024
|
+
panOverflow() {
|
|
1025
|
+
const master = this.getPanMaster();
|
|
1026
|
+
if (!master)
|
|
1027
|
+
return 0;
|
|
1028
|
+
const wanted = this._inventory.getAllItems().filter(i => i !== master && i.wirelessOn && Player.canJoinPan(i)).length;
|
|
1029
|
+
return Math.max(0, wanted - this.panCapacity());
|
|
1030
|
+
}
|
|
1031
|
+
/** Whether an item is the kind of thing a PAN can hold at all (p.233:
|
|
1032
|
+
* "only devices can be slaves, masters, or part of a PAN"). */
|
|
1033
|
+
static canJoinPan(item) {
|
|
1034
|
+
if (item.isCyberdeck() || item.category === Category.Commlink)
|
|
1035
|
+
return true;
|
|
1036
|
+
if (item.isFirearm())
|
|
1037
|
+
return item.isSmartgun;
|
|
1038
|
+
if (item.category === Category.Drone)
|
|
1039
|
+
return true;
|
|
1040
|
+
// ELECTRONICS ONLY IF IT IS ACTUALLY A DEVICE. p.233 is narrow --
|
|
1041
|
+
// "only devices can be slaves, masters, or part of a PAN" -- and
|
|
1042
|
+
// Category.Electronics is not: it is also where the survival kit,
|
|
1043
|
+
// the binoculars and a thermographic vision MOD live. A catalog row
|
|
1044
|
+
// that declares a `device` block is the engine's own record of
|
|
1045
|
+
// having a Device Rating, so that is the test. Without it the
|
|
1046
|
+
// dashboard listed a first-aid tin as hackable gear.
|
|
1047
|
+
return item.category === Category.Electronics && item.row?.device !== undefined;
|
|
1048
|
+
}
|
|
970
1049
|
/**
|
|
971
1050
|
* Defense against a PAN intrusion, itemised: the book's attribute plus
|
|
972
1051
|
* the master's whole Firewall, +2 while running silent (a hidden PAN
|
|
@@ -3343,8 +3422,15 @@ export class Player extends AbstractPlayer {
|
|
|
3343
3422
|
// (p.433), and a crossbow has nothing to plug into.
|
|
3344
3423
|
// AND THE GUN MUST BE A SMARTGUN (catalog v13, p.433): the wireless
|
|
3345
3424
|
// bonus is the smartgun system talking to the smartlink.
|
|
3425
|
+
// AND THE WIRELESS MUST BE ON, which is the whole of p.421's
|
|
3426
|
+
// "Turning It Off": "You lose wireless bonuses, but the items can no
|
|
3427
|
+
// longer be wirelessly hacked." THIS is the bonus that goes -- the
|
|
3428
|
+
// dice -- while smartlinkAccuracyBonus's +2 Accuracy STAYS, because
|
|
3429
|
+
// that one is the smartgun talking to your smartlink over a cable to
|
|
3430
|
+
// your glasses and never needed the radio. Same discipline as the
|
|
3431
|
+
// Matrix brackets: a limit is never a die (CgJ6uTfEvTd8JXdDN).
|
|
3346
3432
|
const drawnGun = this.weaponDrawn ? this.getCarriedWeapon() : null;
|
|
3347
|
-
const smartlink = drawnGun?.isSmartgun && FIREARM_SKILLS.includes(this.activeCombatSkill())
|
|
3433
|
+
const smartlink = drawnGun?.isSmartgun && drawnGun.wirelessOn && FIREARM_SKILLS.includes(this.activeCombatSkill())
|
|
3348
3434
|
// Chrome again, and for the same reason: the tier is already
|
|
3349
3435
|
// decided above, and this branch is the implant's own dice.
|
|
3350
3436
|
? (this.smartlinkTier() === 'gear' ? 1 : this.augBonus('firearms'))
|
|
@@ -26,9 +26,17 @@ export const END_CALL_SENTINEL = 'end-call';
|
|
|
26
26
|
export function isLinked(actor) {
|
|
27
27
|
if (actor.runningSilent)
|
|
28
28
|
return false;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
// A LINK YOU SWITCHED OFF IS NOT A LINK (p.233, `pan off <device>`;
|
|
30
|
+
// CgJ6uTfEvTd8JXdDN). This used to ask only whether a commlink was
|
|
31
|
+
// present, so a runner who deliberately took theirs off the air still
|
|
32
|
+
// took calls and still hung a PAN icon on the grid for anyone to
|
|
33
|
+
// find -- the switch would have been a lie everywhere it mattered.
|
|
34
|
+
// Undefined counts as ON, so every caller that passes a plain shape
|
|
35
|
+
// (NPC fixtures, saves written before the switch) is unchanged.
|
|
36
|
+
const worn = actor.equipment.head?.commlink;
|
|
37
|
+
const wornOn = worn != null && worn.wirelessOn !== false;
|
|
38
|
+
const carried = actor.inventory.getAllItems().some(i => i.category === 'commlink' && i.wirelessOn !== false);
|
|
39
|
+
return wornOn || carried;
|
|
32
40
|
}
|
|
33
41
|
/**
|
|
34
42
|
* DROP A LIVE CALL, BOTH SIDES, and say who was on it.
|
|
@@ -263,16 +263,22 @@ export function gridIcons(scene, actor, room) {
|
|
|
263
263
|
// SAME SELECTOR AS hack.ts's top-priority PAN effect, on
|
|
264
264
|
// purpose: the icon must name exactly the device the verb will
|
|
265
265
|
// seize, or "identified rapidly" points at the wrong thing.
|
|
266
|
-
// This engine treats a held firearm as smartlinked
|
|
267
|
-
//
|
|
268
|
-
//
|
|
266
|
+
// This engine treats a held firearm as smartlinked, so
|
|
267
|
+
// held-and-a-firearm is the whole test and the two surfaces
|
|
268
|
+
// agree by construction -- EXCEPT for a gun whose owner has
|
|
269
|
+
// switched it off the net (Item.wirelessOn, `pan off <gun>`,
|
|
270
|
+
// CgJ6uTfEvTd8JXdDN). A device off the network broadcasts
|
|
271
|
+
// nothing, so there is no icon here to identify and nothing for
|
|
272
|
+
// `hack` to seize; the trade is p.421's, priced there -- the
|
|
273
|
+
// wireless DICE bonus goes (getAttackPool reads the same flag)
|
|
274
|
+
// while the smartgun's +2 Accuracy stays.
|
|
269
275
|
//
|
|
270
276
|
// Sitting INSIDE the isLinked branch is what makes running
|
|
271
277
|
// silent hide it, which is canon too: the book notes a user can
|
|
272
278
|
// hide these icons, and isLinked() is already false while
|
|
273
279
|
// silent. No second rule needed.
|
|
274
280
|
const gun = a.equipment.rightHand?.weapon ?? a.equipment.leftHand?.weapon;
|
|
275
|
-
if (gun?.isFirearm()) {
|
|
281
|
+
if (gun?.isFirearm() && gun.wirelessOn) {
|
|
276
282
|
// TERSE ON PURPOSE (AKfemBJJWJP99nx8K, third pass: "overly
|
|
277
283
|
// verbose"). The earlier line explained WHY a gun breaks out
|
|
278
284
|
// of the PAN -- "a wireless gun does not hide inside a
|
|
@@ -165,6 +165,10 @@ export function serializeItem(item) {
|
|
|
165
165
|
out.jammed = true;
|
|
166
166
|
if (item.bricked)
|
|
167
167
|
out.bricked = true;
|
|
168
|
+
// Sparse and INVERTED on purpose: wireless is on by default, so a save
|
|
169
|
+
// written before the switch existed has no key and restores to on.
|
|
170
|
+
if (!item.wirelessOn)
|
|
171
|
+
out.wirelessOff = true;
|
|
168
172
|
if (item.deckDamage > 0)
|
|
169
173
|
out.deckDamage = item.deckDamage;
|
|
170
174
|
if (item.droneDamage > 0)
|
|
@@ -224,6 +228,8 @@ export function restoreItem(json) {
|
|
|
224
228
|
item.jammed = true;
|
|
225
229
|
if (json.bricked)
|
|
226
230
|
item.bricked = true;
|
|
231
|
+
if (json.wirelessOff)
|
|
232
|
+
item.wirelessOn = false;
|
|
227
233
|
if (json.ruined)
|
|
228
234
|
item.ruined = true;
|
|
229
235
|
if (json.deckDamage)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.230.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.",
|