@maka/maka-cli 5.49.0 → 5.51.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 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +9 -8
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.d.ts +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.js +27 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/give.js +2 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/give.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +43 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +6 -24
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/loot.d.ts +21 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/loot.js +93 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/loot.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/map.d.ts +4 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/map.js +23 -31
- package/bundle/typescript/src/commands/game/sideQuest/commands/map.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/move.d.ts +32 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/move.js +313 -17
- package/bundle/typescript/src/commands/game/sideQuest/commands/move.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/note.d.ts +25 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/note.js +57 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/note.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +20 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/solve.js +9 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/solve.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/talk.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/talk.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.d.ts +13 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +66 -9
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +37 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.d.ts +2 -2
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js +2 -2
- package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +66 -19
- package/bundle/typescript/src/commands/game/sideQuest/game.js +264 -91
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/headless.js +17 -2
- package/bundle/typescript/src/commands/game/sideQuest/headless.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +84 -3
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +31 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +28 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.d.ts +5 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js +24 -6
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/room.d.ts +85 -2
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js +182 -2
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.d.ts +4 -4
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +17 -7
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +17 -13
- package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +29 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/puzzle-seed.d.ts +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/room-seed.d.ts +5 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.d.ts +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +87 -8
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js +28 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.d.ts +7 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/range.d.ts +22 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/range.js +69 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/range.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/room-grid.d.ts +186 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/room-grid.js +742 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/room-grid.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/room-view.d.ts +34 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/room-view.js +536 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/room-view.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.d.ts +273 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js +890 -24
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js.map +1 -1
- package/bundle/typescript/src/commands/set/global.sub.cmd.js +18 -1
- package/bundle/typescript/src/commands/set/global.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/tools/ai/ai.class.d.ts +1 -1
- package/bundle/typescript/src/tools/ai/ai.class.js +39 -7
- package/bundle/typescript/src/tools/ai/ai.class.js.map +1 -1
- package/bundle/typescript/src/tools/fsi/fsi.interfaces.d.ts +1 -0
- package/bundle/typescript/src/tools/fsi/fsi.interfaces.js.map +1 -1
- package/bundle/typescript/src/tools/https/https.class.d.ts +19 -0
- package/bundle/typescript/src/tools/https/https.class.js +36 -0
- package/bundle/typescript/src/tools/https/https.class.js.map +1 -1
- package/package.json +1 -1
|
@@ -12,6 +12,7 @@ import { DOCWAGON_TIERS, RESUSC_FEE, DEATH_COMP } from './utilities/docwagon.js'
|
|
|
12
12
|
import { isFence, LIFESTYLE_TIERS } from './utilities/commerce.js';
|
|
13
13
|
import { bestFakeSin, wandRead, processArrest } from './utilities/sin.js';
|
|
14
14
|
import { renderMapViewport } from './utilities/map-view.js';
|
|
15
|
+
import { renderRoomLayoutCompact } from './utilities/room-view.js';
|
|
15
16
|
import { getPref } from './utilities/prefs.js';
|
|
16
17
|
import { rollPool, formatRoll } from './utilities/dice.js';
|
|
17
18
|
import { Direction } from './types/shared/direction-enum.js';
|
|
@@ -21,6 +22,8 @@ import { currentSession } from './utilities/session-context.js';
|
|
|
21
22
|
import { PlayersCommand } from './commands/players.js';
|
|
22
23
|
import { LookCommand } from './commands/look.js';
|
|
23
24
|
import { TakeCommand } from './commands/take.js';
|
|
25
|
+
import { LootCommand } from './commands/loot.js';
|
|
26
|
+
import { NoteCommand } from './commands/note.js';
|
|
24
27
|
import { GoCommand } from './commands/go.js';
|
|
25
28
|
import { UseCommand } from './commands/use.js';
|
|
26
29
|
import { EquipCommand } from './commands/equip.js';
|
|
@@ -146,6 +149,9 @@ export default class Game {
|
|
|
146
149
|
/** The floating icon-minimap (map-overlay wave 2026-08-24). Optional:
|
|
147
150
|
* headless sessions never build widgets. */
|
|
148
151
|
_mapBox;
|
|
152
|
+
/** Its twin, docked to its right: the current room's own layout
|
|
153
|
+
* (spatial-viz follow-up 2026-08-25). See utilities/room-view.ts. */
|
|
154
|
+
_roomMapBox;
|
|
149
155
|
_useAi = false;
|
|
150
156
|
_aiRefusalAnnounced = false;
|
|
151
157
|
// ACCOUNT BINDING (ruling 2026-08-23): who this runner belongs to,
|
|
@@ -1081,9 +1087,13 @@ export default class Game {
|
|
|
1081
1087
|
* Returns a player-facing line when something changed, else null.
|
|
1082
1088
|
*/
|
|
1083
1089
|
/** A hub-cast member by name, for roster displays that need the live
|
|
1084
|
-
* actor (contacts.ts reads their commlink to show reachability).
|
|
1090
|
+
* actor (contacts.ts reads their commlink to show reachability).
|
|
1091
|
+
* ROOM GRIDS wave (2026-08-25): falls back to the CURRENT run scene
|
|
1092
|
+
* so a live client (not yet a hub contact -- see onClientInteraction)
|
|
1093
|
+
* resolves too; call.ts already reaches them the same way. */
|
|
1085
1094
|
hubActor(name) {
|
|
1086
|
-
return this._hubScene?.allActors?.find(a => a.name === name && a !== this.player)
|
|
1095
|
+
return this._hubScene?.allActors?.find(a => a.name === name && a !== this.player)
|
|
1096
|
+
?? (this.scene !== this._hubScene ? this.scene?.allActors?.find(a => a.name === name && a !== this.player) : undefined);
|
|
1087
1097
|
}
|
|
1088
1098
|
/** The whole hub cast -- call.ts merges these into its candidates so
|
|
1089
1099
|
* a linked contact answers from ANYWHERE (a real mid-run session:
|
|
@@ -1415,16 +1425,18 @@ export default class Game {
|
|
|
1415
1425
|
// state never is (runs are disposable); reconstructed fresh by
|
|
1416
1426
|
// activateRunScene, settled into the roster by returnToHub.
|
|
1417
1427
|
currentClient;
|
|
1418
|
-
// Taxi warn-once (player ruling): leaving with the
|
|
1428
|
+
// Taxi warn-once (player ruling): leaving with the pay unclaimed
|
|
1419
1429
|
// arms this; the second "call taxi" forfeits. Mirrors _quitArmed.
|
|
1420
1430
|
forfeitArmed = false;
|
|
1421
1431
|
/**
|
|
1422
1432
|
* The meet and the wrap-up, triggered by the player's first/next real
|
|
1423
1433
|
* interaction with the client (talk-to, a connected call, a give).
|
|
1424
|
-
* Returns the beat's text
|
|
1425
|
-
*
|
|
1426
|
-
*
|
|
1427
|
-
*
|
|
1434
|
+
* Returns the beat's text or null when this NPC isn't the client /
|
|
1435
|
+
* nothing new happens. LUMP SUM (player ruling 2026-08-25, replacing
|
|
1436
|
+
* canon p.28's half-up-front: the advance beat kept misfiring in
|
|
1437
|
+
* play): the meet sets the terms, the wrap-up pays the WHOLE number.
|
|
1438
|
+
* Completing the objective before ever meeting them still collapses
|
|
1439
|
+
* cleanly -- the wrap-up is the only beat that moves money.
|
|
1428
1440
|
*/
|
|
1429
1441
|
onClientInteraction(npcName) {
|
|
1430
1442
|
const client = this.currentClient;
|
|
@@ -1438,30 +1450,22 @@ export default class Game {
|
|
|
1438
1450
|
const lines = [];
|
|
1439
1451
|
if (!client.met) {
|
|
1440
1452
|
client.met = true;
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
// crew shares the take) -- the player banks their share only.
|
|
1444
|
-
const split = this.applyCrewCut(advance);
|
|
1445
|
-
const paid = this.scene.payRunner(split.share, 'meet advance');
|
|
1446
|
-
client.advancePaid = advance;
|
|
1447
|
-
if (paid) {
|
|
1448
|
-
lines.push(`THE MEET: ${client.name} slides half across up front -- ${paid.net}¥ certified${paid.tax > 0 ? ` (${paid.tax}¥ skimmed: SIN on file)` : ''}. The other half on delivery.${hint(` (Canon terms, p.28 -- and the rate is negotiable exactly once: "haggle" while you have them.)`)}`);
|
|
1449
|
-
if (split.line)
|
|
1450
|
-
lines.push(` ${split.line}`);
|
|
1451
|
-
}
|
|
1452
|
-
Logger.getInstance().write(`Client meet: ${this.player.name} met ${client.name}, advance ${advance} (gross, crew took ${advance - split.share}).`);
|
|
1453
|
+
lines.push(`THE MEET: ${client.name} lays out the terms -- ${payout}¥ certified, in full, on delivery.${hint(` (The rate is negotiable exactly once: "haggle" while you have them.)`)}`);
|
|
1454
|
+
Logger.getInstance().write(`Client meet: ${this.player.name} met ${client.name}, terms ${payout} lump sum on delivery.`);
|
|
1453
1455
|
}
|
|
1454
1456
|
if (this.scene.isCompleted() && !client.reported) {
|
|
1455
1457
|
client.reported = true;
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1458
|
+
// The team's split comes off the top (canon: the crew shares the
|
|
1459
|
+
// take) -- the player banks their share only. Payout read LIVE so
|
|
1460
|
+
// a haggle at any point before delivery moves the real number.
|
|
1461
|
+
const split = this.applyCrewCut(payout);
|
|
1462
|
+
const paid = this.scene.payRunner(split.share, 'wrap-up settlement');
|
|
1459
1463
|
if (paid) {
|
|
1460
|
-
lines.push(`WRAP-UP: ${client.name}
|
|
1464
|
+
lines.push(`WRAP-UP: ${client.name} pays out in full -- ${paid.net}¥ certified${paid.tax > 0 ? ` (${paid.tax}¥ skimmed: SIN on file)` : ''}. Clean work; the name will remember yours.`);
|
|
1461
1465
|
if (split.line)
|
|
1462
1466
|
lines.push(` ${split.line}`);
|
|
1463
1467
|
}
|
|
1464
|
-
Logger.getInstance().write(`Client wrap-up: ${this.player.name} reported to ${client.name},
|
|
1468
|
+
Logger.getInstance().write(`Client wrap-up: ${this.player.name} reported to ${client.name}, settlement ${payout} (gross, crew took ${payout - split.share}).`);
|
|
1465
1469
|
// JOHNSONS DON'T LINGER (real session: a settled Ms. Slate paced
|
|
1466
1470
|
// in and out of rooms and tailed the runner around the scene).
|
|
1467
1471
|
// After the settlement beat lands, the client walks -- their AI
|
|
@@ -1611,6 +1615,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1611
1615
|
CommandFactory.registerCommand('look', LookCommand);
|
|
1612
1616
|
CommandFactory.registerCommand('go', GoCommand);
|
|
1613
1617
|
CommandFactory.registerCommand('take', TakeCommand);
|
|
1618
|
+
CommandFactory.registerCommand('loot', LootCommand);
|
|
1619
|
+
CommandFactory.registerCommand('note', NoteCommand);
|
|
1614
1620
|
CommandFactory.registerCommand('use', UseCommand);
|
|
1615
1621
|
CommandFactory.registerCommand('equip', EquipCommand);
|
|
1616
1622
|
CommandFactory.registerCommand('unequip', UnequipCommand);
|
|
@@ -1830,6 +1836,29 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1830
1836
|
this.initStatusBox(screen);
|
|
1831
1837
|
this.initMapBox(screen); // docked atop the Mechanics column
|
|
1832
1838
|
this._screen = screen;
|
|
1839
|
+
// Paint the meat-plane border up front rather than leaving blessed's
|
|
1840
|
+
// default until the first status refresh.
|
|
1841
|
+
this.paintPlaneBorder();
|
|
1842
|
+
// RESIZE REDRAWS (player request 2026-08-25). blessed re-solves its
|
|
1843
|
+
// own percentage geometry on a resize, but the docked panels are
|
|
1844
|
+
// drawn CONTENT, not layout: the district viewport's width and the
|
|
1845
|
+
// room box's elevation justification are both computed from
|
|
1846
|
+
// screen.width at paint time (see updateExits), so after a resize
|
|
1847
|
+
// they kept the old terminal's dimensions until the next command
|
|
1848
|
+
// happened to repaint them -- a map sized for a window that no
|
|
1849
|
+
// longer exists, which is exactly how content spills a border.
|
|
1850
|
+
screen.on('resize', () => {
|
|
1851
|
+
try {
|
|
1852
|
+
this.updateStatus();
|
|
1853
|
+
const here = this.player?.currentLocation;
|
|
1854
|
+
if (here)
|
|
1855
|
+
this.updateExits(here);
|
|
1856
|
+
screen.render();
|
|
1857
|
+
}
|
|
1858
|
+
catch (err) {
|
|
1859
|
+
Logger.getInstance().write(`resize redraw failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1860
|
+
}
|
|
1861
|
+
});
|
|
1833
1862
|
}
|
|
1834
1863
|
/** The engine half of startup: scene synthesis, command registration,
|
|
1835
1864
|
* hub seeding, resume overlay -- no widget contact. */
|
|
@@ -2717,7 +2746,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2717
2746
|
// THE MEET opens the job (canon p.28): the seed's client is this
|
|
2718
2747
|
// run's Johnson -- track them, and point the arrival at the meet.
|
|
2719
2748
|
this.currentClient = seed.client
|
|
2720
|
-
? { name: seed.client, met: false,
|
|
2749
|
+
? { name: seed.client, met: false, reported: false }
|
|
2721
2750
|
: undefined;
|
|
2722
2751
|
this.forfeitArmed = false;
|
|
2723
2752
|
// THE CLIENT REACHES OUT (ruling 2026-08-24, friction pass): the
|
|
@@ -2729,7 +2758,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2729
2758
|
const clientRoom = clientActor?.currentLocation?.name;
|
|
2730
2759
|
const clientSpot = clientActor ? spotOf(clientActor) : undefined;
|
|
2731
2760
|
const meetLine = seed.client
|
|
2732
|
-
? `\n📞 Your link chirps -- {bold}{underline}${seed.client}{/underline}{/bold}: "${clientRoom ? `I'm at ${clientRoom}${clientSpot ? `, the ${clientSpot}` : ''}` : `I'm on-site`} -- waiting on you. Come sit down before somebody else does."
|
|
2761
|
+
? `\n📞 Your link chirps -- {bold}{underline}${seed.client}{/underline}{/bold}: "${clientRoom ? `I'm at ${clientRoom}${clientSpot ? `, the ${clientSpot}` : ''}` : `I'm on-site`} -- waiting on you. Come sit down before somebody else does." Payment in full on delivery.${hint(` (Find them and talk, or "call ${seed.client}" -- the rate is negotiable at the meet, once.)`)}`
|
|
2733
2762
|
: '';
|
|
2734
2763
|
// No "=== JOB #n ===" banner on entry (player request) -- the Game
|
|
2735
2764
|
// Log window's own title carries the job name now.
|
|
@@ -3159,22 +3188,32 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3159
3188
|
screen.append(statusBox);
|
|
3160
3189
|
}
|
|
3161
3190
|
/**
|
|
3162
|
-
* THE MINIMAP (map-overlay wave 2026-08-24
|
|
3163
|
-
*
|
|
3164
|
-
*
|
|
3165
|
-
*
|
|
3166
|
-
*
|
|
3167
|
-
*
|
|
3168
|
-
*
|
|
3169
|
-
*
|
|
3191
|
+
* THE MINIMAP (map-overlay wave 2026-08-24; split into a pair
|
|
3192
|
+
* 2026-08-25 -- player ask: "ample space to split the existing map in
|
|
3193
|
+
* two, one for the area, one for the room"): two small icon
|
|
3194
|
+
* viewports DOCKED side by side at the top of the Mechanics column --
|
|
3195
|
+
* not floated over the log (a float occluded log text and, being
|
|
3196
|
+
* numerically anchored, drifted on terminal resize; percentage
|
|
3197
|
+
* geometry re-solves both). The Mechanics pane shifts down while
|
|
3198
|
+
* EITHER is shown and takes the space back only once both hide.
|
|
3199
|
+
* keys:false -- a keyable box appended after the input steals focus
|
|
3200
|
+
* (the "eexxiitt" bug, see initInputBox). Hidden until the first
|
|
3201
|
+
* setMapOverlay/setRoomOverlay with content.
|
|
3170
3202
|
*/
|
|
3171
|
-
|
|
3203
|
+
// 9 viewport rows + a caption row (the room name / "@ spot", moved off
|
|
3204
|
+
// the HUD 2026-08-25) + border.
|
|
3205
|
+
static MAP_DOCK_ROWS = 12;
|
|
3206
|
+
/** Must track the roomMapBox's `width` below -- updateExits derives
|
|
3207
|
+
* the inner width from it to right-justify the elevation bar. */
|
|
3208
|
+
static ROOM_DOCK_PCT = 0.23;
|
|
3209
|
+
/** Must track the mapBox's `width` below -- used to trim its caption. */
|
|
3210
|
+
static MAP_DOCK_PCT = 0.12;
|
|
3172
3211
|
initMapBox(screen) {
|
|
3173
3212
|
const mapBox = blessed.box({
|
|
3174
3213
|
id: 'mapBox',
|
|
3175
3214
|
top: 0,
|
|
3176
3215
|
left: '65%',
|
|
3177
|
-
width: '
|
|
3216
|
+
width: '12%',
|
|
3178
3217
|
height: Game.MAP_DOCK_ROWS,
|
|
3179
3218
|
border: { type: 'line' },
|
|
3180
3219
|
style: { border: { fg: 'blue' } },
|
|
@@ -3186,19 +3225,48 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3186
3225
|
});
|
|
3187
3226
|
this._mapBox = mapBox;
|
|
3188
3227
|
screen.append(mapBox);
|
|
3228
|
+
const roomMapBox = blessed.box({
|
|
3229
|
+
id: 'roomMapBox',
|
|
3230
|
+
top: 0,
|
|
3231
|
+
// Much wider than its district twin: this box carries the grid,
|
|
3232
|
+
// the elevation bar down its right side, AND (since the
|
|
3233
|
+
// 2026-08-25 room resize) a `large` room's full 12 cells at two
|
|
3234
|
+
// columns each -- the district view needs only its fixed 15-wide
|
|
3235
|
+
// viewport, so the space is better spent here.
|
|
3236
|
+
left: '77%',
|
|
3237
|
+
width: '23%',
|
|
3238
|
+
height: Game.MAP_DOCK_ROWS,
|
|
3239
|
+
border: { type: 'line' },
|
|
3240
|
+
style: { border: { fg: 'blue' } },
|
|
3241
|
+
label: ' Room ',
|
|
3242
|
+
tags: true,
|
|
3243
|
+
keys: false,
|
|
3244
|
+
content: '',
|
|
3245
|
+
hidden: true,
|
|
3246
|
+
});
|
|
3247
|
+
this._roomMapBox = roomMapBox;
|
|
3248
|
+
screen.append(roomMapBox);
|
|
3189
3249
|
}
|
|
3190
|
-
/** Shifts the Mechanics pane down (map shown) or back up
|
|
3250
|
+
/** Shifts the Mechanics pane down (a map box is shown) or back up
|
|
3251
|
+
* (neither is). Driven by syncMapDock, never called directly. */
|
|
3191
3252
|
dockMap(shown) {
|
|
3192
3253
|
if (!this._metaBox)
|
|
3193
3254
|
return;
|
|
3194
3255
|
this._metaBox.top = shown ? Game.MAP_DOCK_ROWS : 0;
|
|
3195
3256
|
this._metaBox.height = shown ? `100%-${11 + Game.MAP_DOCK_ROWS}` : '100%-11';
|
|
3196
3257
|
}
|
|
3258
|
+
/** Keeps the Mechanics pane's dock offset in sync with whichever
|
|
3259
|
+
* docked box(es) -- district, room, or both -- are currently shown. */
|
|
3260
|
+
syncMapDock() {
|
|
3261
|
+
const anyShown = (this._mapBox !== undefined && !this._mapBox.hidden)
|
|
3262
|
+
|| (this._roomMapBox !== undefined && !this._roomMapBox.hidden);
|
|
3263
|
+
this.dockMap(anyShown);
|
|
3264
|
+
}
|
|
3197
3265
|
/**
|
|
3198
|
-
* Paints (or hides) the docked minimap. Solo: updateExits
|
|
3199
|
-
* from the local scene; shared runs: SharedRunSession.route
|
|
3200
|
-
* from the server's per-player kind:'map' events. The
|
|
3201
|
-
* gates both
|
|
3266
|
+
* Paints (or hides) the docked DISTRICT minimap. Solo: updateExits
|
|
3267
|
+
* feeds it from the local scene; shared runs: SharedRunSession.route
|
|
3268
|
+
* feeds it from the server's per-player kind:'map' events. The
|
|
3269
|
+
* preference gates both this and setRoomOverlay in one place.
|
|
3202
3270
|
*/
|
|
3203
3271
|
setMapOverlay(lines) {
|
|
3204
3272
|
if (!this._mapBox)
|
|
@@ -3208,15 +3276,41 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3208
3276
|
return;
|
|
3209
3277
|
}
|
|
3210
3278
|
this._mapBox.setContent(lines.join('\n'));
|
|
3211
|
-
this.dockMap(true);
|
|
3212
3279
|
this._mapBox.show();
|
|
3280
|
+
this.syncMapDock();
|
|
3213
3281
|
this._screen?.render();
|
|
3214
3282
|
}
|
|
3215
3283
|
hideMapOverlay() {
|
|
3216
3284
|
if (!this._mapBox)
|
|
3217
3285
|
return;
|
|
3218
3286
|
this._mapBox.hide();
|
|
3219
|
-
this.
|
|
3287
|
+
this.syncMapDock();
|
|
3288
|
+
this._screen?.render();
|
|
3289
|
+
}
|
|
3290
|
+
/**
|
|
3291
|
+
* Paints (or hides) the docked ROOM minimap -- the district box's
|
|
3292
|
+
* twin, fed by updateExits from utilities/room-view.ts. Empty
|
|
3293
|
+
* whenever the current room has no synthesized grid (spotless --
|
|
3294
|
+
* THE COMPAT RULE) or the player is circulating in it (nothing fixed
|
|
3295
|
+
* to center on): both read as "hide", same as an empty district.
|
|
3296
|
+
*/
|
|
3297
|
+
setRoomOverlay(lines) {
|
|
3298
|
+
if (!this._roomMapBox)
|
|
3299
|
+
return;
|
|
3300
|
+
if (lines.length === 0 || !getPref('mapOverlay', true)) {
|
|
3301
|
+
this.hideRoomOverlay();
|
|
3302
|
+
return;
|
|
3303
|
+
}
|
|
3304
|
+
this._roomMapBox.setContent(lines.join('\n'));
|
|
3305
|
+
this._roomMapBox.show();
|
|
3306
|
+
this.syncMapDock();
|
|
3307
|
+
this._screen?.render();
|
|
3308
|
+
}
|
|
3309
|
+
hideRoomOverlay() {
|
|
3310
|
+
if (!this._roomMapBox)
|
|
3311
|
+
return;
|
|
3312
|
+
this._roomMapBox.hide();
|
|
3313
|
+
this.syncMapDock();
|
|
3220
3314
|
this._screen?.render();
|
|
3221
3315
|
}
|
|
3222
3316
|
/**
|
|
@@ -3234,6 +3328,9 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3234
3328
|
if (this.remoteSession)
|
|
3235
3329
|
return;
|
|
3236
3330
|
this._statusBox.setContent(this.statusLinesFor(this.player).join('\n'));
|
|
3331
|
+
// The log's border tracks the plane -- updateStatus is the one hook
|
|
3332
|
+
// every plane transition already funnels through.
|
|
3333
|
+
this.paintPlaneBorder();
|
|
3237
3334
|
this._screen.render();
|
|
3238
3335
|
}
|
|
3239
3336
|
/** ui plumbing for session mode (utilities/shared-run.ts). */
|
|
@@ -3248,32 +3345,75 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3248
3345
|
* no widgets. updateStatus paints it for the local player; a shared-run
|
|
3249
3346
|
* server ships it per player as status events (Phase C).
|
|
3250
3347
|
*/
|
|
3348
|
+
/**
|
|
3349
|
+
* What's in your hands, in one phrase. Three armed states on the meat
|
|
3350
|
+
* plane: drawn, HOLSTERED (carried but riding leather -- "brandish"
|
|
3351
|
+
* puts it in hand; combat auto-draws, so this is posture rather than
|
|
3352
|
+
* readiness), or nothing at all. Off-plane the profile is attack
|
|
3353
|
+
* programs / astral will and reads as Armed. White, not gray, for the
|
|
3354
|
+
* same legibility reason as the stance it now sits beside.
|
|
3355
|
+
*/
|
|
3356
|
+
armedLine(player) {
|
|
3357
|
+
const weapon = player.getWeaponProfile();
|
|
3358
|
+
const weaponItem = player.getCarriedWeapon();
|
|
3359
|
+
const ammoNote = weaponItem?.isFirearm()
|
|
3360
|
+
? (weaponItem.jammed
|
|
3361
|
+
? ` {red-fg}[JAMMED -- reboot]{/red-fg}`
|
|
3362
|
+
: weaponItem.ammo === 0 ? ` {red-fg}[DRY]{/red-fg}` : ` [${weaponItem.ammo} rds]`)
|
|
3363
|
+
: '';
|
|
3364
|
+
if (player.plane === 'meat' && weaponItem && !player.weaponDrawn) {
|
|
3365
|
+
return `{white-fg}Holstered: ${weaponItem.name}${ammoNote}${hint(' -- "brandish"')}{/white-fg}`;
|
|
3366
|
+
}
|
|
3367
|
+
if (weapon.name === 'bare fists')
|
|
3368
|
+
return `{gray-fg}Unarmed{/gray-fg}`;
|
|
3369
|
+
return `Armed: ${weapon.name} (DV ${weapon.damage}${weapon.poolBonus > 0 ? `, +${weapon.poolBonus} dice` : ''})${ammoNote}`;
|
|
3370
|
+
}
|
|
3371
|
+
/**
|
|
3372
|
+
* THE LOG WEARS THE PLANE (player request 2026-08-25). Jacking in or
|
|
3373
|
+
* stepping out of your body changes what every line in that log even
|
|
3374
|
+
* MEANS, and the only marker was a HUD row well below it. The border
|
|
3375
|
+
* takes the plane's own established colour -- cyan for the Matrix,
|
|
3376
|
+
* magenta for astral, yellow for a jumped-in drone, matching the
|
|
3377
|
+
* tints those planes already use throughout the prose.
|
|
3378
|
+
*/
|
|
3379
|
+
planeBorderColor(player) {
|
|
3380
|
+
switch (player.plane) {
|
|
3381
|
+
case 'matrix': return 'cyan';
|
|
3382
|
+
case 'astral': return 'magenta';
|
|
3383
|
+
case 'drone': return 'yellow';
|
|
3384
|
+
default: return 'white';
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
paintPlaneBorder() {
|
|
3388
|
+
if (!this._logBox)
|
|
3389
|
+
return;
|
|
3390
|
+
const style = this._logBox.style;
|
|
3391
|
+
style.border = { ...(style.border ?? {}), fg: this.planeBorderColor(this.player) };
|
|
3392
|
+
}
|
|
3251
3393
|
statusLinesFor(player) {
|
|
3252
3394
|
const lines = [];
|
|
3253
|
-
// Line 1:
|
|
3254
|
-
//
|
|
3255
|
-
//
|
|
3256
|
-
//
|
|
3257
|
-
//
|
|
3258
|
-
//
|
|
3395
|
+
// Line 1: the fight autopilot mode. The stance lives HERE -- the
|
|
3396
|
+
// shortest HUD line -- because appended to the long readiness line it
|
|
3397
|
+
// wrapped out of the box on narrower terminals and effectively only
|
|
3398
|
+
// ever showed in the Tab announcement. Always shown, even MANUAL: the
|
|
3399
|
+
// HUD is how players discover Tab cycles it at all (see
|
|
3400
|
+
// utilities/auto-fight.ts). MANUAL is white, not gray -- blessed's
|
|
3259
3401
|
// gray is ANSI "bright black", which on a stock dark terminal palette
|
|
3260
3402
|
// is illegible enough that the stance looked absent entirely.
|
|
3403
|
+
//
|
|
3404
|
+
// WHERE YOU ARE MOVED OUT (player ruling 2026-08-25): the room name
|
|
3405
|
+
// now captions the Map box and the "@ spot" captions the Room box,
|
|
3406
|
+
// each under the picture it belongs to, so the HUD stops repeating
|
|
3407
|
+
// what two panels already show. The breadcrumb went with it -- the
|
|
3408
|
+
// district map draws the way you came far better than a text trail.
|
|
3261
3409
|
const stanceColor = player.autoStance === 'aggressive' ? 'red' : player.autoStance === 'guarded' ? 'yellow' : 'white';
|
|
3262
|
-
|
|
3263
|
-
// The
|
|
3264
|
-
//
|
|
3265
|
-
//
|
|
3266
|
-
//
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
// "At" spots: where you stand WITHIN the room, when it has places
|
|
3270
|
-
// to stand (see utilities/spots.ts).
|
|
3271
|
-
const atNote = spotOf(player) ? ` @ ${player.atSpot}` : '';
|
|
3272
|
-
lines.push(`${crumb}{bold}📍 ${player.currentLocation.name}${atNote}{/bold} | ${stance}`);
|
|
3273
|
-
}
|
|
3274
|
-
else {
|
|
3275
|
-
lines.push(stance);
|
|
3276
|
-
}
|
|
3410
|
+
// WHAT'S IN YOUR HANDS rides alongside the stance (player ruling
|
|
3411
|
+
// 2026-08-25). The two answer the same question -- how ready are you
|
|
3412
|
+
// -- and the stance line was left short and lonely once the location
|
|
3413
|
+
// moved out to the map captions, while the readiness line below ran
|
|
3414
|
+
// long. armedLine is built further down with the rest of the gear
|
|
3415
|
+
// read; only its placement moved.
|
|
3416
|
+
lines.push(`{${stanceColor}-fg}⚔ ${player.autoStance.toUpperCase()}{/${stanceColor}-fg} ${this.armedLine(player)}`);
|
|
3277
3417
|
// Line 2: both condition monitors -- in a game where a fight can kill
|
|
3278
3418
|
// you and casting can knock you out, current damage on either track is
|
|
3279
3419
|
// never something a player should have to ask for. Colors shift as
|
|
@@ -3292,26 +3432,10 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3292
3432
|
const heat = this.heatBand();
|
|
3293
3433
|
if (heat && player === this.player)
|
|
3294
3434
|
lines.push(heat);
|
|
3295
|
-
// Line 3: readiness --
|
|
3296
|
-
// actually live (equipped), the recurring "why can't I
|
|
3297
|
-
// friction surfaced at a glance.
|
|
3298
|
-
|
|
3299
|
-
const weaponItem = player.getCarriedWeapon();
|
|
3300
|
-
const ammoNote = weaponItem?.isFirearm()
|
|
3301
|
-
? (weaponItem.jammed
|
|
3302
|
-
? ` {red-fg}[JAMMED -- reboot]{/red-fg}`
|
|
3303
|
-
: weaponItem.ammo === 0 ? ` {red-fg}[DRY]{/red-fg}` : ` [${weaponItem.ammo} rds]`)
|
|
3304
|
-
: '';
|
|
3305
|
-
// Three armed states on the meat plane: drawn, holstered (weapon
|
|
3306
|
-
// carried but riding leather -- "brandish" puts it in hand; combat
|
|
3307
|
-
// auto-draws so this is posture, not readiness), or nothing at all.
|
|
3308
|
-
// Off-plane the profile is attack programs / astral will and reads as
|
|
3309
|
-
// Armed. White, not gray, for the same legibility reason as the stance.
|
|
3310
|
-
const armed = player.plane === 'meat' && weaponItem && !player.weaponDrawn
|
|
3311
|
-
? `{white-fg}Holstered: ${weaponItem.name}${ammoNote}${hint(' -- "brandish"')}{/white-fg}`
|
|
3312
|
-
: weapon.name === 'bare fists'
|
|
3313
|
-
? `{gray-fg}Unarmed{/gray-fg}`
|
|
3314
|
-
: `Armed: ${weapon.name} (DV ${weapon.damage}${weapon.poolBonus > 0 ? `, +${weapon.poolBonus} dice` : ''})${ammoNote}`;
|
|
3435
|
+
// Line 3: the rest of the readiness read -- armor, and whether the
|
|
3436
|
+
// commlink is actually live (equipped), the recurring "why can't I
|
|
3437
|
+
// call anyone" friction surfaced at a glance. The armed state used
|
|
3438
|
+
// to lead this line; it now rides with the stance on line 1.
|
|
3315
3439
|
const armorNote = `Armor +${player.getArmorValue()}`;
|
|
3316
3440
|
const hasLink = player.equipment.head?.commlink != null;
|
|
3317
3441
|
const link = !hasLink
|
|
@@ -3321,7 +3445,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3321
3445
|
: player.arOffline
|
|
3322
3446
|
? `{red-fg}AR CUT -- reboot{/red-fg}`
|
|
3323
3447
|
: `{${CALL_COLOR}-fg}Link online{/${CALL_COLOR}-fg}`;
|
|
3324
|
-
lines.push(`${
|
|
3448
|
+
lines.push(`${armorNote} | ${link}`);
|
|
3325
3449
|
// Lines 4+: transient states, present only while active. Future
|
|
3326
3450
|
// contexts (astral projection, VR hot-sim) join here the same way.
|
|
3327
3451
|
if (player.sustainedArmor > 0) {
|
|
@@ -3414,22 +3538,71 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3414
3538
|
this.notePresenceAffectingChange();
|
|
3415
3539
|
}
|
|
3416
3540
|
updateExits(_room) {
|
|
3417
|
-
// THE ICON
|
|
3418
|
-
// kept as a no-op for exactly this -- it already
|
|
3419
|
-
// (go/lock/unlock, ui.ts), scene
|
|
3541
|
+
// THE ICON OVERLAYS (map-overlay wave 2026-08-24, split 2026-08-25):
|
|
3542
|
+
// this hook was kept as a no-op for exactly this -- it already
|
|
3543
|
+
// fires on movement (go/lock/unlock, move.ts, ui.ts), scene
|
|
3544
|
+
// transitions, and barrier changes (solve/bypass/lock/unlock).
|
|
3420
3545
|
// Session mode: the server's per-player kind:'map' events feed the
|
|
3421
|
-
// overlay instead (SharedRunSession.route)
|
|
3546
|
+
// district overlay instead (SharedRunSession.route); the room box
|
|
3547
|
+
// has no server-pushed path yet, so it just stays hidden in a
|
|
3548
|
+
// shared run, same as the district box did before that wiring.
|
|
3422
3549
|
if (!this._mapBox || this.remoteSession)
|
|
3423
3550
|
return;
|
|
3424
3551
|
const here = this.player.currentLocation;
|
|
3425
3552
|
if (!here || this.player.plane === 'matrix') {
|
|
3426
3553
|
this.hideMapOverlay();
|
|
3554
|
+
this.hideRoomOverlay();
|
|
3427
3555
|
return;
|
|
3428
3556
|
}
|
|
3429
|
-
|
|
3557
|
+
const screenW = Number(this._screen?.width) || 0;
|
|
3558
|
+
// Each box's inner width. Derived from the screen rather than read
|
|
3559
|
+
// off the widget: blessed resolves percentage geometry at render
|
|
3560
|
+
// time, so `.width` is still the raw '23%' here. Used to right-
|
|
3561
|
+
// justify the elevation bar and to trim the caption rows.
|
|
3562
|
+
const boxWidth = screenW > 0 ? Math.floor(screenW * Game.ROOM_DOCK_PCT) - 2 : undefined;
|
|
3563
|
+
const mapWidth = screenW > 0 ? Math.floor(screenW * Game.MAP_DOCK_PCT) - 2 : undefined;
|
|
3564
|
+
const caption = (text, width) => {
|
|
3565
|
+
const w = width ?? text.length;
|
|
3566
|
+
return `{bold}${text.length > w ? `${text.slice(0, Math.max(1, w - 1))}…` : text}{/bold}`;
|
|
3567
|
+
};
|
|
3568
|
+
// The district viewport must FIT ITS BOX. It was a hardcoded 15
|
|
3569
|
+
// columns, which was fine while the Map box was 35% wide and
|
|
3570
|
+
// silently overflowed into the Room box once it shrank to 12% --
|
|
3571
|
+
// on a narrower terminal the spill landed inside the neighbouring
|
|
3572
|
+
// border and smartCSR's partial redraws smeared it into stray
|
|
3573
|
+
// characters (playtest 2026-08-25: "getting some artifacting 'g'";
|
|
3574
|
+
// ui.ts documents this same smear from column drift). Odd widths
|
|
3575
|
+
// keep the player marker exactly centred.
|
|
3576
|
+
const mapInner = mapWidth ?? 15;
|
|
3577
|
+
const mapView = Math.max(7, mapInner % 2 === 0 ? mapInner - 1 : mapInner);
|
|
3578
|
+
const mapLines = renderMapViewport(this.scene.getRooms(), here, (r, d) => this.scene.barrierSealing(r, d), {
|
|
3430
3579
|
visited: this.player.visitedRooms,
|
|
3431
3580
|
arEyes: this.player.hasMatrixEyes(),
|
|
3432
|
-
})
|
|
3581
|
+
}, mapView);
|
|
3582
|
+
// The captions the HUD used to carry: where you are under the
|
|
3583
|
+
// district, where you stand under the room.
|
|
3584
|
+
if (mapLines.length > 0)
|
|
3585
|
+
mapLines.push(caption(here.name, mapWidth));
|
|
3586
|
+
this.setMapOverlay(mapLines);
|
|
3587
|
+
// A DECORATION MUST NEVER KILL THE RUN. This panel is pure derived
|
|
3588
|
+
// geometry -- if synthesis or rendering ever throws (a malformed
|
|
3589
|
+
// seed, a room shape no one anticipated), the right outcome is a
|
|
3590
|
+
// blank box and a log line, not a dead session mid-job.
|
|
3591
|
+
try {
|
|
3592
|
+
const roomLines = renderRoomLayoutCompact(here, this.player, {
|
|
3593
|
+
boxWidth,
|
|
3594
|
+
isOtherPlayer: a => this.scene.isHumanControlled(a),
|
|
3595
|
+
});
|
|
3596
|
+
if (roomLines.length > 0) {
|
|
3597
|
+
const spot = spotOf(this.player);
|
|
3598
|
+
roomLines.push(caption(spot ? `@ ${spot}` : '(moving through)', boxWidth));
|
|
3599
|
+
}
|
|
3600
|
+
this.setRoomOverlay(roomLines);
|
|
3601
|
+
}
|
|
3602
|
+
catch (err) {
|
|
3603
|
+
Logger.getInstance().write(`Room overlay render failed in ${here.name}: ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
|
|
3604
|
+
this.hideRoomOverlay();
|
|
3605
|
+
}
|
|
3433
3606
|
}
|
|
3434
3607
|
updateInventory(_playerInventory, _currencyName = 'currency') {
|
|
3435
3608
|
this.updateStatus();
|