@maka/maka-cli 5.177.0 → 5.179.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/say.js +8 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +129 -10
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +42 -1
- package/bundle/typescript/src/commands/game/sideQuest/headless.js +4 -3
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +2 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +13 -2
- package/bundle/typescript/src/commands/game/sideQuest/utilities/logger.js +5 -3
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.179.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.",
|
|
@@ -100,7 +100,14 @@ export class SayCommand extends Command {
|
|
|
100
100
|
// comms visually reads as a different "space" than in-person speech.
|
|
101
101
|
const inCall = !!currentPlayer.activeCallPartner || !!currentPlayer.crewChannel;
|
|
102
102
|
const prefix = inCall ? `${CALL_ICON} ` : '';
|
|
103
|
-
|
|
103
|
+
const speech = {
|
|
104
|
+
speaker: currentPlayer.name,
|
|
105
|
+
text: message,
|
|
106
|
+
self: true,
|
|
107
|
+
...(inCall ? { comms: true } : {}),
|
|
108
|
+
...(currentPlayer.gender ? { gender: currentPlayer.gender } : {}),
|
|
109
|
+
};
|
|
110
|
+
this.logger.logWithColor(`${prefix}${message}`, inCall ? CALL_COLOR : 'yellow', { actor: currentPlayer.name }, { speech });
|
|
104
111
|
}
|
|
105
112
|
// "say" and "call" are deliberately similar -- once a call connects
|
|
106
113
|
// (see call.ts), the two ends keep talking with the same "say" command
|
|
@@ -458,10 +458,13 @@ ${JSON_ONLY}
|
|
|
458
458
|
"plane"?: "meat"|"matrix", // "matrix" = paydata: only in a hasNode room, category
|
|
459
459
|
// Datachip/Document. A matrix-plane winCondition item makes
|
|
460
460
|
// the job a datarun (its room MUST have ice).
|
|
461
|
-
"
|
|
462
|
-
//
|
|
463
|
-
// placed in a DIFFERENT room than
|
|
464
|
-
// room)
|
|
461
|
+
"forDevice"?: string // roles "clue" and "key" only: the devices[].name this item
|
|
462
|
+
// opens. A "clue" reveals that panel's code -- EVERY panel needs
|
|
463
|
+
// exactly one, category "Book", placed in a DIFFERENT room than
|
|
464
|
+
// the panel (door panels: any room); its text is written in a
|
|
465
|
+
// later pass. A "key" is what that lock takes -- EVERY lock
|
|
466
|
+
// needs exactly one, category "Key", and the lock opens to
|
|
467
|
+
// nothing else.
|
|
465
468
|
// ${isContinuation ? 'Role "starting" is FORBIDDEN in this scene -- the runner arrives fully equipped (see dossier).' : 'Role "starting" items are the fresh runner\'s basic kit.'}
|
|
466
469
|
}],
|
|
467
470
|
"winCondition": {
|
|
@@ -829,6 +832,60 @@ export function parseJsonReply(reply) {
|
|
|
829
832
|
* tokens building on it. Deeper semantics (reachability, clue coverage,
|
|
830
833
|
* matrix defense...) are still enforced by the assembled-scene validators.
|
|
831
834
|
*/
|
|
835
|
+
/**
|
|
836
|
+
* WHICH KEY OPENS THIS LOCK -- the one answer the validator, the
|
|
837
|
+
* normalizer and assembleScene all have to agree on, so it lives once.
|
|
838
|
+
*
|
|
839
|
+
* In order of how much the skeleton actually SAID:
|
|
840
|
+
* 1. a key-role item whose `forDevice` names the lock (the field the
|
|
841
|
+
* prompt asks for);
|
|
842
|
+
* 2. the `keyRequired` of the exit this lock holds (the older way of
|
|
843
|
+
* saying the same thing);
|
|
844
|
+
* 3. a key whose name shares a real word with the lock's name or
|
|
845
|
+
* concept ("Warehouse Fob" for "Warehouse Maglock") -- accepted
|
|
846
|
+
* only when exactly one does;
|
|
847
|
+
* 4. the one unclaimed key in a scene with one unkeyed lock: the
|
|
848
|
+
* model wrote "one key per lock" and simply never joined them.
|
|
849
|
+
*
|
|
850
|
+
* `claimed` lets a caller walking several locks keep one key from
|
|
851
|
+
* opening two of them by rule 3 or 4. Nothing here invents a key --
|
|
852
|
+
* that is normalizeSkeleton's call, capped, with a log line.
|
|
853
|
+
*/
|
|
854
|
+
export function resolveLockKey(skeleton, device, claimed = new Set()) {
|
|
855
|
+
const items = skeleton.items ?? [];
|
|
856
|
+
const keys = items.filter(i => i.role === 'key');
|
|
857
|
+
const declared = keys.find(i => i.forDevice === device.name);
|
|
858
|
+
if (declared)
|
|
859
|
+
return declared.name;
|
|
860
|
+
for (const room of skeleton.rooms ?? []) {
|
|
861
|
+
for (const e of room.exits ?? []) {
|
|
862
|
+
if (e.device !== device.name || !e.keyRequired)
|
|
863
|
+
continue;
|
|
864
|
+
const wanted = e.keyRequired.toLowerCase();
|
|
865
|
+
const hit = items.find(i => i.name.toLowerCase() === wanted);
|
|
866
|
+
if (hit)
|
|
867
|
+
return hit.name;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
const free = keys.filter(k => !claimed.has(k.name) && !k.forDevice);
|
|
871
|
+
const words = (s) => new Set(s.toLowerCase().replace(/'s\b/g, '').split(/[^a-z0-9]+/).filter(w => w.length >= 4 && !GENERIC_LOCK_WORDS.has(w)));
|
|
872
|
+
const lockWords = new Set([...words(device.name), ...words(device.concept ?? '')]);
|
|
873
|
+
const named = free.filter(k => [...words(k.name)].some(w => lockWords.has(w)));
|
|
874
|
+
if (named.length === 1)
|
|
875
|
+
return named[0].name;
|
|
876
|
+
const locks = (skeleton.devices ?? []).filter(d => (d.kind ?? 'panel') === 'lock');
|
|
877
|
+
const unkeyed = locks.filter(l => !keys.some(k => k.forDevice === l.name)
|
|
878
|
+
&& !(skeleton.rooms ?? []).some(r => (r.exits ?? []).some(e => e.device === l.name && e.keyRequired)));
|
|
879
|
+
if (free.length === 1 && unkeyed.length === 1 && unkeyed[0].name === device.name)
|
|
880
|
+
return free[0].name;
|
|
881
|
+
return undefined;
|
|
882
|
+
}
|
|
883
|
+
/** Words every lock and every key share, which therefore prove nothing
|
|
884
|
+
* about which key opens which lock. */
|
|
885
|
+
const GENERIC_LOCK_WORDS = new Set([
|
|
886
|
+
'lock', 'maglock', 'door', 'gate', 'grate', 'hatch', 'safe', 'vault', 'cage', 'shutter', 'padlock', 'cabinet', 'locker',
|
|
887
|
+
'keycard', 'card', 'passkey', 'access', 'security', 'back', 'front', 'side', 'room', 'office', 'main', 'inner', 'outer',
|
|
888
|
+
]);
|
|
832
889
|
export function validateSkeleton(skeleton, player, crew) {
|
|
833
890
|
const fail = (msg) => { throw new Error(`Skeleton invalid: ${msg}`); };
|
|
834
891
|
if (!Array.isArray(skeleton?.rooms) || skeleton.rooms.length === 0)
|
|
@@ -1004,17 +1061,19 @@ export function validateSkeleton(skeleton, player, crew) {
|
|
|
1004
1061
|
}
|
|
1005
1062
|
}
|
|
1006
1063
|
else if (kind === 'lock') {
|
|
1007
|
-
// A lock's findable way through is its KEY
|
|
1008
|
-
//
|
|
1064
|
+
// A lock's findable way through is its KEY -- and it has to be
|
|
1065
|
+
// THIS lock's key, resolvable by resolveLockKey, because that is
|
|
1066
|
+
// the exact question assembleScene asks when it writes the
|
|
1067
|
+
// device's `keyItem`. "Some key-role item exists somewhere" used
|
|
1068
|
+
// to pass here (8XDR2BCZoSmpqdkXG: Corvid's Fob existed, Yuri's
|
|
1069
|
+
// Maglock shipped with no keyItem, and the fob opened nothing).
|
|
1009
1070
|
const holdsDoor = refs > 0;
|
|
1010
|
-
const keyed = skeleton.rooms.some(r => (r.exits ?? []).some(e => e.device === device.name && e.keyRequired));
|
|
1011
|
-
const looseKey = skeleton.items.some(i => i.role === 'key');
|
|
1012
1071
|
// The same floor for a lock that holds a PRIZE rather than a
|
|
1013
1072
|
// door (a safe): its findable way in is a key, and "holdsDoor"
|
|
1014
1073
|
// used to exempt it -- so a keyless safe passed here and then
|
|
1015
1074
|
// stood in play with only breach options (bPiyyWu9TQ4pBfGbM).
|
|
1016
|
-
if (!
|
|
1017
|
-
fail(`lock "${device.name}" ${holdsDoor ? 'holds a door
|
|
1075
|
+
if (!resolveLockKey(skeleton, device)) {
|
|
1076
|
+
fail(`lock "${device.name}" ${holdsDoor ? 'holds a door' : 'holds a prize'} and no "key"-role item opens it -- add one with "forDevice": "${device.name}", so a runner without locksmith has a way through.`);
|
|
1018
1077
|
}
|
|
1019
1078
|
}
|
|
1020
1079
|
// A WARD is the exception no clue can rescue: there is no findable
|
|
@@ -1673,6 +1732,56 @@ export function normalizeSkeleton(skeleton) {
|
|
|
1673
1732
|
synthesized++;
|
|
1674
1733
|
notes.push(`normalized: panel "${device.name}" had no clue item; synthesized Book clue "${name}" in "${safe[0]}" (reachable without passing the barrier).`);
|
|
1675
1734
|
}
|
|
1735
|
+
// EVERY LOCK HAS ITS KEY, BY NAME (8XDR2BCZoSmpqdkXG: "the Barrier
|
|
1736
|
+
// explicitly states to use Corvid's Fob, I have Corvid's Fob, but
|
|
1737
|
+
// doesn't unlock the maglock").
|
|
1738
|
+
//
|
|
1739
|
+
// The skeleton prompt has always asked for "one key per lock", and
|
|
1740
|
+
// the model has always delivered one -- but nothing ever wrote WHICH
|
|
1741
|
+
// lock a key opened onto the key, and assembleScene never wrote a
|
|
1742
|
+
// `keyItem` onto the lock. The validator only asked whether a
|
|
1743
|
+
// key-role item existed at all, so the pair shipped unrelated: a fob
|
|
1744
|
+
// that opened nothing beside a maglock that took nothing, with every
|
|
1745
|
+
// line of prose in between insisting they matched. The same shape as
|
|
1746
|
+
// the panel clue above, one loop later: resolve the pair here,
|
|
1747
|
+
// record it on the key so every later pass (and the log) can see it,
|
|
1748
|
+
// and invent a key only when the model left a lock with none.
|
|
1749
|
+
const SYNTHESIZED_KEY_CAP = 2;
|
|
1750
|
+
let synthesizedKeys = 0;
|
|
1751
|
+
const claimedKeys = new Set();
|
|
1752
|
+
for (const device of skeleton.devices ?? []) {
|
|
1753
|
+
if ((device.kind ?? 'panel') !== 'lock')
|
|
1754
|
+
continue;
|
|
1755
|
+
const items = skeleton.items ?? [];
|
|
1756
|
+
const keyName = resolveLockKey(skeleton, device, claimedKeys);
|
|
1757
|
+
if (keyName) {
|
|
1758
|
+
claimedKeys.add(keyName);
|
|
1759
|
+
const key = items.find(i => i.name === keyName);
|
|
1760
|
+
if (key && key.forDevice !== device.name) {
|
|
1761
|
+
const was = key.forDevice ? `pointed at "${key.forDevice}"` : 'named no device';
|
|
1762
|
+
key.forDevice = device.name;
|
|
1763
|
+
notes.push(`normalized: key "${key.name}" ${was}; it is the key to lock "${device.name}".`);
|
|
1764
|
+
}
|
|
1765
|
+
continue;
|
|
1766
|
+
}
|
|
1767
|
+
const safe = safeRoomsFor(skeleton, device);
|
|
1768
|
+
if (safe.length === 0) {
|
|
1769
|
+
notes.push(`could not synthesize a key for lock "${device.name}": nowhere is reachable without passing it.`);
|
|
1770
|
+
continue;
|
|
1771
|
+
}
|
|
1772
|
+
if (synthesizedKeys >= SYNTHESIZED_KEY_CAP) {
|
|
1773
|
+
notes.push(`declined to synthesize a key for lock "${device.name}": already invented ${SYNTHESIZED_KEY_CAP} this skeleton -- a fresh draft is the better bet.`);
|
|
1774
|
+
continue;
|
|
1775
|
+
}
|
|
1776
|
+
let name = `${device.name} Key`;
|
|
1777
|
+
for (let i = 2; items.some(it => it.name === name); i++)
|
|
1778
|
+
name = `${device.name} Key ${i}`;
|
|
1779
|
+
skeleton.items = items;
|
|
1780
|
+
skeleton.items.push({ name, role: 'key', category: 'Key', forDevice: device.name, room: safe[0] });
|
|
1781
|
+
claimedKeys.add(name);
|
|
1782
|
+
synthesizedKeys++;
|
|
1783
|
+
notes.push(`normalized: lock "${device.name}" had no key; synthesized Key "${name}" in "${safe[0]}" (reachable without passing the barrier).`);
|
|
1784
|
+
}
|
|
1676
1785
|
// REWARD-REFERENCE reconciliation. A rewardItem (a puzzle's, or the
|
|
1677
1786
|
// winCondition's) must point at an UNPLACED role-"reward" item, but
|
|
1678
1787
|
// models regularly point it at a placed loot/clue item instead -- and
|
|
@@ -2335,8 +2444,17 @@ export function assembleScene(skeleton, details, playerName) {
|
|
|
2335
2444
|
heldExit.set(canonical, e.direction);
|
|
2336
2445
|
}
|
|
2337
2446
|
}
|
|
2447
|
+
// WHICH KEY EACH LOCK TAKES -- the field Device.offer actually reads.
|
|
2448
|
+
// Same ruling as heldExit just above: the skeleton says it once (on
|
|
2449
|
+
// the key's forDevice, or the held exit's keyRequired) and this is
|
|
2450
|
+
// the only place it becomes the device's own `keyItem`. Without it
|
|
2451
|
+
// the door never opens to its key (8XDR2BCZoSmpqdkXG).
|
|
2452
|
+
const claimedKeys = new Set();
|
|
2338
2453
|
const devices = (skeleton.devices ?? []).map(device => {
|
|
2339
2454
|
const detail = deviceDetail.get(device.name);
|
|
2455
|
+
const keyItem = (device.kind ?? 'panel') === 'lock' ? resolveLockKey(skeleton, device, claimedKeys) : undefined;
|
|
2456
|
+
if (keyItem)
|
|
2457
|
+
claimedKeys.add(keyItem);
|
|
2340
2458
|
const guardsExit = device.room && detail?.guardsExit && roomExitDirsByName.get(device.room)?.has(detail.guardsExit.toLowerCase())
|
|
2341
2459
|
? detail.guardsExit.toLowerCase()
|
|
2342
2460
|
: undefined;
|
|
@@ -2357,6 +2475,7 @@ export function assembleScene(skeleton, details, playerName) {
|
|
|
2357
2475
|
opensExit: heldExit.get(device.name),
|
|
2358
2476
|
description: detail?.description ?? device.concept,
|
|
2359
2477
|
code,
|
|
2478
|
+
keyItem,
|
|
2360
2479
|
grantsItem: device.grantsItem,
|
|
2361
2480
|
openMessage: detail?.openMessage ?? 'It opens.',
|
|
2362
2481
|
refuseMessage: detail?.refuseMessage ?? 'Nothing happens.',
|
|
@@ -334,6 +334,47 @@ Key Locations: ${json.rooms.map(r => r.name).join(', ')}
|
|
|
334
334
|
logger.write(`Scene "${json.name}": "${name}" resolved to item "${fuzzy}" by fuzzy match -- names generated apart drifted.`);
|
|
335
335
|
return itemMap.get(fuzzy);
|
|
336
336
|
};
|
|
337
|
+
// A LOCK THE SEED NEVER GAVE A KEY, WHOSE OWN PROSE NAMES ONE
|
|
338
|
+
// (8XDR2BCZoSmpqdkXG: "the Barrier explicitly states to use
|
|
339
|
+
// Corvid's Fob, I have Corvid's Fob, but doesn't unlock the
|
|
340
|
+
// maglock"). Until 2026-09-13 the generator never wrote `keyItem`
|
|
341
|
+
// onto a lock at all, so every generated run on the board carries a
|
|
342
|
+
// maglock with no key beside a Key-category item whose details say
|
|
343
|
+
// which door it opens. Those seeds are already stored on live
|
|
344
|
+
// sessions and will be rebuilt from as-is, so the repair has to
|
|
345
|
+
// happen HERE, on the shipped content, exactly like the fuzzy
|
|
346
|
+
// keyItem repair above: read the prose the player reads. A Key
|
|
347
|
+
// item whose text names the device, or whose name the device's
|
|
348
|
+
// description uses, is its key -- when exactly one does. Failing
|
|
349
|
+
// that, the one Key in a scene with one lock is that lock's key.
|
|
350
|
+
// Anything less certain stays unrepaired and logged, because
|
|
351
|
+
// guessing wrong hands a player a key that opens the wrong door.
|
|
352
|
+
const allDevices = json.rooms.flatMap(r => (r.devices ?? []).map(d => ({ ...d, roomName: r.name })));
|
|
353
|
+
const inferKeyItem = (d) => {
|
|
354
|
+
if (d.kind !== 'lock' || d.open)
|
|
355
|
+
return undefined;
|
|
356
|
+
const keys = [...itemMap.values()].filter(i => i.category === Category.Key);
|
|
357
|
+
const deviceName = d.name.toLowerCase();
|
|
358
|
+
const prose = (d.description ?? '').toLowerCase();
|
|
359
|
+
const mentions = keys.filter(k => `${k.description ?? ''} ${k.details ?? ''}`.toLowerCase().includes(deviceName)
|
|
360
|
+
|| prose.includes(k.name.toLowerCase()));
|
|
361
|
+
let hit;
|
|
362
|
+
let how = '';
|
|
363
|
+
if (mentions.length === 1) {
|
|
364
|
+
hit = mentions[0];
|
|
365
|
+
how = 'its prose names';
|
|
366
|
+
}
|
|
367
|
+
else if (mentions.length === 0 && keys.length === 1 && allDevices.filter(x => x.kind === 'lock' && !x.keyItem && !x.open).length === 1) {
|
|
368
|
+
hit = keys[0];
|
|
369
|
+
how = 'the only key in the scene beside the only lock is';
|
|
370
|
+
}
|
|
371
|
+
if (!hit) {
|
|
372
|
+
logger.write(`Scene "${json.name}": lock "${d.name}" declares no keyItem and no single Key item can be read as its key -- it opens only to force.`);
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
375
|
+
logger.write(`Scene "${json.name}": lock "${d.name}" declared no keyItem; ${how} "${hit.name}", which now opens it.`);
|
|
376
|
+
return hit;
|
|
377
|
+
};
|
|
337
378
|
// Step 4.9: DEVICES, and the doors they hold shut.
|
|
338
379
|
// After items, because a device may hold a key or a reward.
|
|
339
380
|
//
|
|
@@ -372,7 +413,7 @@ Key Locations: ${json.rooms.map(r => r.name).join(', ')}
|
|
|
372
413
|
name: d.name,
|
|
373
414
|
description: d.description,
|
|
374
415
|
code: d.code,
|
|
375
|
-
keyItem: d.keyItem ? findItem(d.keyItem) :
|
|
416
|
+
keyItem: d.keyItem ? findItem(d.keyItem) : inferKeyItem(d),
|
|
376
417
|
grantsItem: d.grantsItem ? findItem(d.grantsItem) : undefined,
|
|
377
418
|
atSpot: d.atSpot,
|
|
378
419
|
opensExit: d.opensExit,
|
|
@@ -84,13 +84,14 @@ export class EmitterLogger extends Logger {
|
|
|
84
84
|
const actor = g?.scene.getPlayerByName(d.actorName);
|
|
85
85
|
return actor?.currentLocation ? { room: actor.currentLocation.name } : { actor: d.actorName };
|
|
86
86
|
}
|
|
87
|
-
log(message, scope) {
|
|
87
|
+
log(message, scope, data) {
|
|
88
88
|
this.emit('log', message, scope ?? this.ambient('log'), {
|
|
89
89
|
actor: currentSession()?.dispatch?.actorName,
|
|
90
|
+
...(data ? { data } : {}),
|
|
90
91
|
});
|
|
91
92
|
}
|
|
92
|
-
logWithColor(message, color = 'white', scope) {
|
|
93
|
-
this.log(`{${color}-fg}${message}{/${color}-fg}`, scope);
|
|
93
|
+
logWithColor(message, color = 'white', scope, data) {
|
|
94
|
+
this.log(`{${color}-fg}${message}{/${color}-fg}`, scope, data);
|
|
94
95
|
}
|
|
95
96
|
meta(message, color, scope) {
|
|
96
97
|
const text = color ? `{${color}-fg}${message}{/${color}-fg}` : message;
|
|
@@ -766,7 +766,8 @@ export class NPC extends Player {
|
|
|
766
766
|
// SPEECH STAYS IN THE STORY. Someone on the other end of the bond
|
|
767
767
|
// talking to you is fiction, and burying it in the Mechanics
|
|
768
768
|
// ticker would mean missing "Rattle's not budging, need your call".
|
|
769
|
-
this.
|
|
769
|
+
const speech = { speaker: this.name, text: spoken, link: true, ...(this.gender ? { gender: this.gender } : {}) };
|
|
770
|
+
this.logger.logWithColor(`${style.icon} ${this.name}: "${spoken}"`, style.color, undefined, { speech });
|
|
770
771
|
}
|
|
771
772
|
else {
|
|
772
773
|
// TELEMETRY GOES TO MECHANICS (player ruling 2026-08-25: the
|
|
@@ -2821,11 +2821,22 @@ export class Player extends AbstractPlayer {
|
|
|
2821
2821
|
// room -- a cross-room say over a call would render to the wrong
|
|
2822
2822
|
// side, and two humans sharing a room would each see the line
|
|
2823
2823
|
// twice. Solo Logger ignores the scope (inert third arg).
|
|
2824
|
+
// SPOKEN LINES AS DATA (emitter.ts SpeechData): the words ride beside
|
|
2825
|
+
// the text so the browser can hand them to a voice. Inert solo.
|
|
2826
|
+
const speech = (comms) => ({
|
|
2827
|
+
speech: {
|
|
2828
|
+
speaker: actor.name,
|
|
2829
|
+
text: message,
|
|
2830
|
+
...(actor === this ? { self: true } : {}),
|
|
2831
|
+
...(comms ? { comms: true } : {}),
|
|
2832
|
+
...(actor.gender ? { gender: actor.gender } : {}),
|
|
2833
|
+
},
|
|
2834
|
+
});
|
|
2824
2835
|
if (this.activeCallPartner === actor && actor.currentLocation !== this.currentLocation) {
|
|
2825
|
-
this.logger.logWithColor(`${CALL_ICON} ${actor.name} (over comms): ${message}`, CALL_COLOR, { actor: this.name });
|
|
2836
|
+
this.logger.logWithColor(`${CALL_ICON} ${actor.name} (over comms): ${message}`, CALL_COLOR, { actor: this.name }, speech(true));
|
|
2826
2837
|
return;
|
|
2827
2838
|
}
|
|
2828
|
-
this.logger.logWithColor(`{white-fg}${actor.name}:{/white-fg} ${message}`, (actor === this) ? 'yellow' : 'green', { actor: this.name });
|
|
2839
|
+
this.logger.logWithColor(`{white-fg}${actor.name}:{/white-fg} ${message}`, (actor === this) ? 'yellow' : 'green', { actor: this.name }, speech(false));
|
|
2829
2840
|
}
|
|
2830
2841
|
see(actor, verb, details, quiet) {
|
|
2831
2842
|
if (actor !== this && this.canPerceive(actor)) {
|
|
@@ -88,7 +88,7 @@ export class Logger {
|
|
|
88
88
|
// solo screen shows the local player everything its callers already
|
|
89
89
|
// gated for. A headless session's EmitterLogger reads it (or applies
|
|
90
90
|
// the ambient dispatch default) to stamp per-player visibility.
|
|
91
|
-
log(message, _scope) {
|
|
91
|
+
log(message, _scope, _data) {
|
|
92
92
|
const logBox = this.screen?.children.find(child => child.type === 'box' && child.options.id === 'logBox');
|
|
93
93
|
if (logBox) {
|
|
94
94
|
logBox.insertBottom(message); // Supports color tags
|
|
@@ -106,9 +106,11 @@ export class Logger {
|
|
|
106
106
|
// not the system log (player request: two files).
|
|
107
107
|
this.transcript(message);
|
|
108
108
|
}
|
|
109
|
-
|
|
109
|
+
// `data` rides the same way as `scope`: inert on the solo screen, stamped
|
|
110
|
+
// onto the event by a headless session's EmitterLogger (GameEvent.data).
|
|
111
|
+
logWithColor(message, color = 'white', scope, data) {
|
|
110
112
|
const coloredMessage = `{${color}-fg}${message}{/${color}-fg}`;
|
|
111
|
-
this.log(coloredMessage, scope);
|
|
113
|
+
this.log(coloredMessage, scope, data);
|
|
112
114
|
}
|
|
113
115
|
/**
|
|
114
116
|
* The MECHANICS ticker (readability pass): dice anatomy and resource
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.179.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.",
|