@maka/maka-cli 5.382.0 → 5.384.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 +89 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +206 -41
- package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.js +9 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/command.js +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/dispel.js +15 -7
- package/bundle/typescript/src/commands/game/sideQuest/commands/follow.js +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +25 -9
- package/bundle/typescript/src/commands/game/sideQuest/commands/inv.js +3 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/map.js +10 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/move.js +9 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +4 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js +7 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/talk.js +8 -2
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +37 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.js +12 -4
- package/bundle/typescript/src/commands/game/sideQuest/headless.js +3 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +3 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +12 -6
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js +4 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +87 -18
- package/bundle/typescript/src/commands/game/sideQuest/utilities/astral-move.js +4 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/barrier-combat.js +102 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/barrier-geometry.js +253 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +32 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-turn.js +25 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/condition-report.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/earshot.js +26 -4
- package/bundle/typescript/src/commands/game/sideQuest/utilities/map-glyphs.js +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/map-rulers.js +58 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/map-view.js +30 -4
- package/bundle/typescript/src/commands/game/sideQuest/utilities/npc-authorization.js +27 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +38 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/room-grid.js +22 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/room-view.js +51 -11
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js +24 -2
- package/bundle/typescript/src/commands/game/sideQuest/utilities/ways-out.js +34 -2
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from './command.js';
|
|
2
2
|
import { hint } from '../utilities/hints.js';
|
|
3
3
|
import { capitalCase } from 'change-case';
|
|
4
|
-
import { planeTintItemName,
|
|
4
|
+
import { planeTintItemName, jobMark } from '../utilities/planes.js';
|
|
5
5
|
import { dataDevices } from '../utilities/data-library.js';
|
|
6
6
|
/**
|
|
7
7
|
* On-demand inventory panel, replacing the always-on blessed sidebar box
|
|
@@ -76,7 +76,7 @@ export class InventoryCommand extends Command {
|
|
|
76
76
|
const tinted = item.plane === 'meat'
|
|
77
77
|
? rarityTint(item.rating, planeTintItemName(item, 'meat'))
|
|
78
78
|
: planeTintItemName(item, 'meat');
|
|
79
|
-
let line = ` ${CARRIED_MARK}${
|
|
79
|
+
let line = ` ${CARRIED_MARK}${jobMark(this.scene, item)}${tinted}`;
|
|
80
80
|
// x N for a stack, the same shape the currency tag below uses.
|
|
81
81
|
if (item.quantity > 1)
|
|
82
82
|
line += ` �${item.quantity}`;
|
|
@@ -130,7 +130,7 @@ export class InventoryCommand extends Command {
|
|
|
130
130
|
if (libraries.length > 0) {
|
|
131
131
|
lines.push('', `{light-blue-fg}On your devices:{/light-blue-fg}`);
|
|
132
132
|
for (const device of libraries) {
|
|
133
|
-
lines.push(` ${device.name}: ${device.files.map(f => `${
|
|
133
|
+
lines.push(` ${device.name}: ${device.files.map(f => `${jobMark(this.scene, f)}${planeTintItemName(f, 'meat')}`).join(', ')}`);
|
|
134
134
|
}
|
|
135
135
|
const legend = hint(`("transfer <file> to <device>" moves one; "transfer <file> to chip" writes it to physical media.)`);
|
|
136
136
|
if (legend)
|
|
@@ -12,7 +12,7 @@ import { getPref, setPref } from '../utilities/prefs.js';
|
|
|
12
12
|
import { roomMetresPerDot } from '../utilities/room-view.js';
|
|
13
13
|
import { roomRasterRows } from '../utilities/room-grid.js';
|
|
14
14
|
import { PLANE_METRES_PER_DOT, fogEye, fogName, fogOf } from '../utilities/map-view.js';
|
|
15
|
-
import { PLAYER_GLYPH, OTHER_PLAYER_GLYPH, CREW_GLYPH, NEUTRAL_GLYPH, HOSTILE_GLYPH, DOWNED_GLYPH, DEAD_GLYPH, RIGGED_GLYPH, SPOT_GLYPH, CAMERA_GLYPH, CAMERA_COLOR, CAMERA_DEAD_COLOR, WALL_GLYPH, DOOR_GLYPH, FLOOR_GLYPH, FRONTIER_GLYPH, BLOCKED_GLYPH, UP_GLYPH, DOWN_GLYPH, } from '../utilities/map-glyphs.js';
|
|
15
|
+
import { PLAYER_GLYPH, OTHER_PLAYER_GLYPH, CREW_GLYPH, NEUTRAL_GLYPH, HOSTILE_GLYPH, DOWNED_GLYPH, DEAD_GLYPH, RIGGED_GLYPH, SPOT_GLYPH, CAMERA_GLYPH, CAMERA_COLOR, CAMERA_DEAD_COLOR, BARRIER_GLYPH, BARRIER_COLOR, MANA_BARRIER_COLOR, WALL_GLYPH, DOOR_GLYPH, FLOOR_GLYPH, FRONTIER_GLYPH, BLOCKED_GLYPH, UP_GLYPH, DOWN_GLYPH, } from '../utilities/map-glyphs.js';
|
|
16
16
|
/**
|
|
17
17
|
* The on-demand WAYS-ON list. Registered as both "map" and "exits";
|
|
18
18
|
* since 2026-08-25 the drawn maps are permanently docked beside the log
|
|
@@ -41,7 +41,7 @@ export class MapCommand extends Command {
|
|
|
41
41
|
if (sub === 'legend' || sub === 'key') {
|
|
42
42
|
// Asked of the panel rather than guessed -- see Game.roomGridWidth.
|
|
43
43
|
const availW = this.game?.roomGridWidth() ?? 19;
|
|
44
|
-
const metres = roomMetresPerDot(room, availW, roomRasterRows());
|
|
44
|
+
const metres = roomMetresPerDot(room, availW, roomRasterRows(), room.planeFrame?.()?.origin);
|
|
45
45
|
const out = [];
|
|
46
46
|
out.push(`{bold}THE MAP KEY{/bold}`);
|
|
47
47
|
out.push('');
|
|
@@ -81,6 +81,14 @@ export class MapCommand extends Command {
|
|
|
81
81
|
out.push(` {${CAMERA_DEAD_COLOR}-fg}${CAMERA_GLYPH}{/${CAMERA_DEAD_COLOR}-fg} a smashed one`);
|
|
82
82
|
out.push(` On the elevation bar, ${CAMERA_GLYPH} marks the height a camera hangs at.`);
|
|
83
83
|
out.push('');
|
|
84
|
+
out.push(`{bold}Barriers{/bold}`);
|
|
85
|
+
out.push(` {${BARRIER_COLOR}-fg}${BARRIER_GLYPH}{/${BARRIER_COLOR}-fg} a force barrier -- nothing physical passes; "attack barrier" breaks a face`);
|
|
86
|
+
out.push(` {${MANA_BARRIER_COLOR}-fg}${BARRIER_GLYPH}{/${MANA_BARRIER_COLOR}-fg} a mana barrier, if you can see the astral -- it stops spirits and astral forms`);
|
|
87
|
+
out.push('');
|
|
88
|
+
out.push(`{bold}Coordinates{/bold}`);
|
|
89
|
+
out.push(` The numbers along the top and left edges are district x and y -- 2 m a cell, north is up.`);
|
|
90
|
+
out.push(` Your own x,y,z is under the Floor Plan. "cast barrier <x,y> <x,y>" raises a wall between two of them.`);
|
|
91
|
+
out.push('');
|
|
84
92
|
out.push(`{bold}Scale{/bold}`);
|
|
85
93
|
out.push(` Floor Plan: one dot is {white-fg}${metres} m{/white-fg} here.`);
|
|
86
94
|
out.push(` Outside, the map runs at {white-fg}${PLANE_METRES_PER_DOT} m{/white-fg} a dot on the ground and scrolls with you;`);
|
|
@@ -45,6 +45,7 @@ import { onTheWall, midClimbRefusal } from '../utilities/climb-state.js';
|
|
|
45
45
|
* verb that leaves a room.
|
|
46
46
|
*/
|
|
47
47
|
export class MoveCommand extends Command {
|
|
48
|
+
static movesActor = true;
|
|
48
49
|
static verb = 'walk';
|
|
49
50
|
static description = 'Cross the room to a named spot or person: "move to bar", "move to Brick" (also "walk"/"approach"; "run to"/"sprint to" are the fast crossings). "move south 2" paces you across the room by cells instead -- use "go" to leave the room entirely. Bare "move" lists the room\'s spots. You must share a spot with someone to talk or trade with them.';
|
|
50
51
|
/** Which movement mode this registration answers to (see subclasses). */
|
|
@@ -1200,7 +1201,10 @@ export class MoveCommand extends Command {
|
|
|
1200
1201
|
// world simply stops. Naming it a wall would send the
|
|
1201
1202
|
// player looking for a door.
|
|
1202
1203
|
: blockedBy === 'edge' ? `the ground ends there`
|
|
1203
|
-
:
|
|
1204
|
+
// A barrier spell, said as one: it is drawn, and it is
|
|
1205
|
+
// somebody's working (barrier-geometry.ts).
|
|
1206
|
+
: blockedBy === 'barrier' ? `a shimmer-wall of hardened air stands there${blocker ? ` -- ${blocker}'s barrier` : ''}`
|
|
1207
|
+
: `there's a wall`;
|
|
1204
1208
|
const refusal = `You can't step ${direction} -- ${why}.${this.doorHint(room, cell, blockedBy)}`;
|
|
1205
1209
|
// SAY IT ONCE (player screenshot 2026-09-21: twelve identical
|
|
1206
1210
|
// "You can't step south -- there's a wall." filling the log).
|
|
@@ -1279,7 +1283,8 @@ export class MoveCommand extends Command {
|
|
|
1279
1283
|
? `Pulled up short after ${meters} m -- ${blockedBy === 'someone' ? 'someone is standing there'
|
|
1280
1284
|
: blockedBy === 'furniture' ? 'furniture blocks the way'
|
|
1281
1285
|
: blockedBy === 'terrain' ? 'the way is blocked'
|
|
1282
|
-
: blockedBy === 'edge' ? 'the ground ends there'
|
|
1286
|
+
: blockedBy === 'edge' ? 'the ground ends there'
|
|
1287
|
+
: blockedBy === 'barrier' ? 'a force barrier stands there' : 'the wall'}.`
|
|
1283
1288
|
: '';
|
|
1284
1289
|
const arrived = actor.atSpot !== wasAt && actor.atSpot !== OPEN_FLOOR
|
|
1285
1290
|
? `You're at the ${actor.atSpot} now.`
|
|
@@ -1724,7 +1729,8 @@ export class MoveCommand extends Command {
|
|
|
1724
1729
|
return driftRefusal(drift, direction);
|
|
1725
1730
|
const stopped = drift.blockedBy === undefined ? ''
|
|
1726
1731
|
: drift.blockedBy === 'ward' ? ` A ward stops you${drift.ward ? ` at ${drift.ward}` : ''}.`
|
|
1727
|
-
: '
|
|
1732
|
+
: drift.blockedBy === 'barrier' ? ` A mana barrier stops you${drift.ward ? ` -- ${drift.ward}'s working` : ''}.`
|
|
1733
|
+
: ' Then solid earth.';
|
|
1728
1734
|
// Silent on a plain drift, like a meat step: the map moved, which
|
|
1729
1735
|
// is the answer. Speak when you arrived over somewhere named, or
|
|
1730
1736
|
// when something out here stopped you.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from './command.js';
|
|
2
2
|
import { hint } from '../utilities/hints.js';
|
|
3
|
-
import { planeTintItemName, emphasizeFind,
|
|
3
|
+
import { planeTintItemName, emphasizeFind, jobMark, fileReach } from '../utilities/planes.js';
|
|
4
4
|
import { rollPool, formatRoll } from '../utilities/dice.js';
|
|
5
5
|
import { perceptionTest, matrixSearchTest, matrixSearchSeconds, MATRIX_SEARCH } from '../utilities/perception.js';
|
|
6
6
|
import { gridVicinity } from '../utilities/grid-reach.js';
|
|
@@ -194,7 +194,7 @@ export class SearchCommand extends Command {
|
|
|
194
194
|
if (files.length === 0) {
|
|
195
195
|
return `The archive is indexed and EMPTY -- ${hostLabel(where)} keeps nothing worth the trip. ${clock}`;
|
|
196
196
|
}
|
|
197
|
-
const list = files.map(i => `${
|
|
197
|
+
const list = files.map(i => `${jobMark(this.scene, i)}${emphasizeFind(planeTintItemName(i, actor.plane))}`).join(', ');
|
|
198
198
|
const seal = where.hostCracked
|
|
199
199
|
? hint(` ("take" them -- the seal is already broken.)`)
|
|
200
200
|
: hint(` (sealed in the host's data vault: "hack" the node to break the seal, then "take".)`);
|
|
@@ -206,7 +206,7 @@ export class SearchCommand extends Command {
|
|
|
206
206
|
const sealedElsewhere = files.length > 0 && where.hasNode;
|
|
207
207
|
return `Nothing loose on ${where.name}'s grid. ${clock}${sealedElsewhere ? hint(` (${hostLabel(where)} keeps its files INSIDE -- "enter" it and search from in there.)`) : ''}`;
|
|
208
208
|
}
|
|
209
|
-
const list = loose.map(i => `${
|
|
209
|
+
const list = loose.map(i => `${jobMark(this.scene, i)}${emphasizeFind(planeTintItemName(i, actor.plane))}`).join(', ');
|
|
210
210
|
return `Loose on ${where.name}'s grid: ${list}. ${clock}${hint(` ("take" rides them out on your deck.)`)}`;
|
|
211
211
|
}
|
|
212
212
|
async execute(_args) {
|
|
@@ -303,7 +303,7 @@ export class SearchCommand extends Command {
|
|
|
303
303
|
// Plain lowercase for the log/AI history; the SCREEN list is tinted
|
|
304
304
|
// per plane (see planeTintItemName) so paydata reads as data.
|
|
305
305
|
const itemsList = found.map(i => i.name.toLowerCase()).join(', ');
|
|
306
|
-
const displayList = found.map(i => `${
|
|
306
|
+
const displayList = found.map(i => `${jobMark(this.scene, i)}${emphasizeFind(planeTintItemName(i, this.actor.plane))}`).join(', ');
|
|
307
307
|
this.logger.write(`SearchCommand: ${this.actor.name} found the following items: ${itemsList}`);
|
|
308
308
|
this.actor.performAction('search', itemsList);
|
|
309
309
|
description += `You search the area and find: ${displayList}`;
|
|
@@ -4,6 +4,7 @@ import { Category } from '../types/shared/item-enum.js';
|
|
|
4
4
|
import { hint } from '../utilities/hints.js';
|
|
5
5
|
import { getAspect } from '../aspects.js';
|
|
6
6
|
import { spellCreationCap } from '../spells.js';
|
|
7
|
+
import { describeBarrier } from '../utilities/barrier-geometry.js';
|
|
7
8
|
/**
|
|
8
9
|
* The spellbook: how a player actually discovers what they can cast and
|
|
9
10
|
* why. Especially load-bearing for INNATE casting -- an Awakened character
|
|
@@ -118,10 +119,10 @@ export class SpellsCommand extends Command {
|
|
|
118
119
|
lines.push(`Sustaining on ${w.bearer}: ${w.key === 'reflexes' ? 'increase reflexes' : w.key} (${sustainBurden(w, ' to everything else')}).${hint(` Cast ${w.key} on ${w.bearer} again to release.`)}`);
|
|
119
120
|
}
|
|
120
121
|
if (actor.sustainedBarrier) {
|
|
121
|
-
lines.push(`Sustained: force barrier
|
|
122
|
+
lines.push(`Sustained: force barrier -- ${describeBarrier(actor.sustainedBarrier)} (${burden('barrier')}).${hint(' "cast barrier off" releases it.')}`);
|
|
122
123
|
}
|
|
123
124
|
if (actor.sustainedManaBarrier) {
|
|
124
|
-
lines.push(`Sustained: mana barrier
|
|
125
|
+
lines.push(`Sustained: mana barrier -- ${describeBarrier(actor.sustainedManaBarrier)} (${burden('manabarrier')}).${hint(' "cast manabarrier off" releases it.')}`);
|
|
125
126
|
}
|
|
126
127
|
if (actor.sustainedReflexes) {
|
|
127
128
|
lines.push(`Sustained: increase reflexes, +2 initiative (${burden('reflexes')}).${hint(' Cast it again to release.')}`);
|
|
@@ -176,10 +177,10 @@ export class SpellsCommand extends Command {
|
|
|
176
177
|
line: 'sustained: light slides around the bearer, +3 on sneak contests at -2 dice. Drain F-4.' },
|
|
177
178
|
{ key: 'reflexes', cast: 'increase reflexes', how: 'cast increase reflexes [on <ally>]',
|
|
178
179
|
line: 'sustained: +2 initiative for the bearer at -2 dice. Drain F-3.' },
|
|
179
|
-
{ key: 'barrier', cast: 'barrier', how: 'cast barrier <direction
|
|
180
|
-
line: 'sustained: hardened air
|
|
181
|
-
{ key: 'manabarrier', cast: 'manabarrier', how: 'cast manabarrier <direction>',
|
|
182
|
-
line: 'sustained: woven mana
|
|
180
|
+
{ key: 'barrier', cast: 'barrier', how: 'cast barrier [F] <x,y> <x,y> (wall) | <x,y> (dome) | <direction> [cells] | at <someone> | (bare: a dome round you); cast barrier off',
|
|
181
|
+
line: 'sustained: hardened air -- a wall Force x 2 m long and Force m high, or a Force-metre dome. Armor/Structure = hits, a track per face; nobody meat-side passes (you included) at -2 dice to you. Drain F-1.' },
|
|
182
|
+
{ key: 'manabarrier', cast: 'manabarrier', how: 'cast manabarrier [F] <x,y> <x,y> | <x,y> | <direction> [cells] | at <someone>; cast manabarrier off',
|
|
183
|
+
line: 'sustained: woven mana, same shapes -- stops SPIRITS and astral forms; the living pass freely, and so do you. Armor/Structure = Force. Drain F-2.' },
|
|
183
184
|
];
|
|
184
185
|
for (const entry of book) {
|
|
185
186
|
if (!actor.knowsSpell(entry.key))
|
|
@@ -153,7 +153,8 @@ export class TalkCommand extends Command {
|
|
|
153
153
|
if (insideWithMe.includes(target)) {
|
|
154
154
|
await speaker.hear(speaker, `(to ${target.name}) ${message}`);
|
|
155
155
|
await target.hear(speaker, `(privately) ${message}`);
|
|
156
|
-
|
|
156
|
+
// The echo above IS the confirmation (see the meat branch below).
|
|
157
|
+
return '';
|
|
157
158
|
}
|
|
158
159
|
// "At" spots: conversation needs shared ground (see
|
|
159
160
|
// utilities/spots.ts) -- the bartender doesn't lean across the room.
|
|
@@ -174,7 +175,12 @@ export class TalkCommand extends Command {
|
|
|
174
175
|
// Face time with the run's CLIENT is THE MEET (or the wrap-up) --
|
|
175
176
|
// see Game.onClientInteraction (terms / lump-sum settlement).
|
|
176
177
|
const clientBeat = this.game?.onClientInteraction(target.name);
|
|
177
|
-
|
|
178
|
+
// ONE LINE PER THING SAID (86LjwvK7xSbXhWXGi: "this repeated text can
|
|
179
|
+
// be removed: You talk to Mr. Vellum privately: ..."). The speaker's
|
|
180
|
+
// own hear() above already printed "Maka: (to Mr. Vellum) ...", so a
|
|
181
|
+
// reply quoting the same words back was the same sentence twice. The
|
|
182
|
+
// reply carries only what the echo cannot: the meet's beat.
|
|
183
|
+
return clientBeat ?? '';
|
|
178
184
|
}
|
|
179
185
|
}
|
|
180
186
|
//# sourceMappingURL=talk.js.map
|
|
@@ -3319,5 +3319,41 @@
|
|
|
3319
3319
|
// hub boot (reaching a save that already slid) and after a shared
|
|
3320
3320
|
// run's homecoming. A move that cannot land now is retried next time;
|
|
3321
3321
|
// a session with no districtSeedLoader never moves.
|
|
3322
|
-
|
|
3322
|
+
// 1.195.0 (2026-10-04): THE MEET, CLEANED UP (four live-table reports).
|
|
3323
|
+
// - The run's client holds his seat until the job is settled: his AI's
|
|
3324
|
+
// move/approach/run/sprint/fly/go/follow are refused with a reason
|
|
3325
|
+
// (npc-authorization.ts clientHoldsTheMeet; Command.movesActor), except
|
|
3326
|
+
// inside a fight (CWMrB2aNyL4hhrjyG).
|
|
3327
|
+
// - A body nudged onto an exact cell (shoulderAside, yieldDoorway) still
|
|
3328
|
+
// hears and is heard: earshot reads an exact cell as a place, so the
|
|
3329
|
+
// shoved Johnson is no longer deaf to `say` (xurFSiqNtRNbXA88A).
|
|
3330
|
+
// - talk-to/tell no longer quote the words back under the speaker's own
|
|
3331
|
+
// echo (86LjwvK7xSbXhWXGi).
|
|
3332
|
+
// - `inv`/`search` mark job leads with ☆ -- a page spelling a scene
|
|
3333
|
+
// device's code, or a device's own key -- beside the objective's ★
|
|
3334
|
+
// (nfrBW27eRRxstJ8uC).
|
|
3335
|
+
// - The HUD's holstered line drops its `-- "brandish"` tip
|
|
3336
|
+
// (MkxamPxmsSyonR3wC).
|
|
3337
|
+
// 1.196.0 (2026-10-05): BARRIER SPELLS ON THE DISTRICT PLANE (SR5 p.294,
|
|
3338
|
+
// p.197-198, p.315-316; utilities/barrier-geometry.ts).
|
|
3339
|
+
// - A barrier is cells, not a tag on an exit: `cast barrier [F] <x,y[,z]>
|
|
3340
|
+
// [<x,y[,z]>]` (a wall between two points, or a Force-metre dome round
|
|
3341
|
+
// one), `cast barrier [F] <dir> [cells]` (1x3 across the way), `cast
|
|
3342
|
+
// barrier [F] at <someone>`, bare `cast barrier` (a dome round you),
|
|
3343
|
+
// `cast barrier off`. Plane coordinates; LOS-gated; clipped by matter
|
|
3344
|
+
// (physical) or by earth/wards/other weaves (mana).
|
|
3345
|
+
// - Physical Barrier rolls for real: Armor and Structure = hits [Force].
|
|
3346
|
+
// Mana Barrier: Force. One damage track per FACE (a wall has one; a
|
|
3347
|
+
// dome N/E/S/W, plus a crown past one level). `attack barrier [x,y]`
|
|
3348
|
+
// breaks a face (p.197 text: DV must EXCEED Armor; guns do 1 box);
|
|
3349
|
+
// a gun fired through one loses a box to it. Both regenerate at the
|
|
3350
|
+
// Combat Turn boundary (mana at the end, physical at the start).
|
|
3351
|
+
// - Movement: findPath/stepFrom route round a physical barrier; astral
|
|
3352
|
+
// `move` now stops at a mana barrier (it drifted through); astral `go`
|
|
3353
|
+
// presses through on Magic + Charisma [Astral] v. Force x 2.
|
|
3354
|
+
// - Bare `cast <direction>` no longer unweaves a ward -- `dispel <dir>`.
|
|
3355
|
+
// - Wire: the room grid carries `barriers` (room-local cells; a mana
|
|
3356
|
+
// barrier only for eyes that can see it). Docks draw x/y rulers in
|
|
3357
|
+
// plane coordinates and the caption gives your x,y,z.
|
|
3358
|
+
export const ENGINE_VERSION = '1.196.0';
|
|
3323
3359
|
//# sourceMappingURL=engine-version.js.map
|
|
@@ -149,6 +149,7 @@ import { ForceCommand } from './commands/force.js';
|
|
|
149
149
|
import { CompleteCommand } from './commands/complete.js';
|
|
150
150
|
import { DisableCommand, BrickCommand } from './commands/disable.js';
|
|
151
151
|
import { CastCommand } from './commands/cast.js';
|
|
152
|
+
import { barrierSpan } from './utilities/barrier-geometry.js';
|
|
152
153
|
import { FollowCommand, UnfollowCommand } from './commands/follow.js';
|
|
153
154
|
import { BringCommand } from './commands/bring.js';
|
|
154
155
|
import { SurrenderCommand } from './commands/surrender.js';
|
|
@@ -9273,8 +9274,12 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
9273
9274
|
? ` {red-fg}[JAMMED -- reboot]{/red-fg}`
|
|
9274
9275
|
: weaponItem.ammo === 0 ? ` {red-fg}[DRY]{/red-fg}` : ` [${weaponItem.ammo} rds]`)
|
|
9275
9276
|
: '';
|
|
9277
|
+
// NO VERB COACHING IN THE HUD (MkxamPxmsSyonR3wC: "get rid of this
|
|
9278
|
+
// line in the status bar: -- "brandish""). The status bar is state;
|
|
9279
|
+
// the `brandish` hint already rides the equip/holster replies and
|
|
9280
|
+
// `equipment`, where it answers the moment the question comes up.
|
|
9276
9281
|
if (player.plane === 'meat' && weaponItem && !player.weaponDrawn) {
|
|
9277
|
-
return `{white-fg}Holstered: ${weaponItem.name}${ammoNote}
|
|
9282
|
+
return `{white-fg}Holstered: ${weaponItem.name}${ammoNote}{/white-fg}`;
|
|
9278
9283
|
}
|
|
9279
9284
|
// White, not gray: gray is ANSI bright-black and illegible on
|
|
9280
9285
|
// stock dark palettes -- the same ruling that moved the HUD
|
|
@@ -9473,10 +9478,10 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
9473
9478
|
// the one that strands the team -- and both of them seal an exit
|
|
9474
9479
|
// that the caster cannot pass either.
|
|
9475
9480
|
if (player.sustainedBarrier) {
|
|
9476
|
-
heldSpells.push(`physical barrier (${player.sustainedBarrier.
|
|
9481
|
+
heldSpells.push(`physical barrier (${player.sustainedBarrier.shape} ${barrierSpan(player.sustainedBarrier)}, ${player.sustainedBarrier.roomName})`);
|
|
9477
9482
|
}
|
|
9478
9483
|
if (player.sustainedManaBarrier) {
|
|
9479
|
-
heldSpells.push(`mana barrier (${player.sustainedManaBarrier.
|
|
9484
|
+
heldSpells.push(`mana barrier (${player.sustainedManaBarrier.shape} ${barrierSpan(player.sustainedManaBarrier)}, ${player.sustainedManaBarrier.roomName})`);
|
|
9480
9485
|
}
|
|
9481
9486
|
// What this character holds on OTHERS -- the caster's burden, named
|
|
9482
9487
|
// by bearer so the mage knows whose armor is costing them two dice.
|
|
@@ -9717,6 +9722,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
9717
9722
|
// Whose eyes, and what counts as hostile -- so the district draws
|
|
9718
9723
|
// the same people, with the same marks, the room panel does.
|
|
9719
9724
|
viewer: this.player,
|
|
9725
|
+
rulers: true,
|
|
9720
9726
|
isOtherPlayer: a => this.scene.isHumanControlled(a),
|
|
9721
9727
|
isTableAlly: crewOfTable(this.scene.getPlayers()),
|
|
9722
9728
|
isHostile: a => hostileByStance(a) || enemyInFight(this.scene, this.player, a),
|
|
@@ -9749,6 +9755,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
9749
9755
|
const roomLines = renderRoomLayoutCompact(here, this.player, {
|
|
9750
9756
|
isTableAlly: crewOfTable(this.scene.getPlayers()),
|
|
9751
9757
|
boxWidth,
|
|
9758
|
+
// Rulers in PLANE coordinates -- the numbers "cast barrier x,y" takes.
|
|
9759
|
+
origin: here.planeFrame?.()?.origin,
|
|
9752
9760
|
isOtherPlayer: a => this.scene.isHumanControlled(a),
|
|
9753
9761
|
// Red is the stance flag OR whoever the Combat Turn says is
|
|
9754
9762
|
// fighting you (room-view.ts hostileByStance).
|
|
@@ -9763,7 +9771,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
9763
9771
|
// of its own. The GLYPHS moved to `map legend`, where there is
|
|
9764
9772
|
// room to say what they mean instead of abbreviating them into
|
|
9765
9773
|
// a strip nobody could read.
|
|
9766
|
-
roomLines.push(rightAlign(dockScale(roomMetresPerDot(here, this.roomGridWidth() ?? Math.max(6, (boxWidth ?? 27) - 8), roomRasterRows())), boxWidth));
|
|
9774
|
+
roomLines.push(rightAlign(dockScale(roomMetresPerDot(here, this.roomGridWidth() ?? Math.max(6, (boxWidth ?? 27) - 8), roomRasterRows(), here.planeFrame?.()?.origin)), boxWidth));
|
|
9767
9775
|
roomLines.push(caption(roomCaption(here, this.player), boxWidth));
|
|
9768
9776
|
}
|
|
9769
9777
|
this.setRoomOverlay(roomLines);
|
|
@@ -564,6 +564,7 @@ function wireHeadless(game, ctx, logger) {
|
|
|
564
564
|
arEyes: p.hasMatrixEyes(),
|
|
565
565
|
at: game.scene.worldCellOf(p),
|
|
566
566
|
viewer: p,
|
|
567
|
+
rulers: true,
|
|
567
568
|
isOtherPlayer: (a) => game.scene.isHumanControlled(a),
|
|
568
569
|
isTableAlly: crewOfTable(game.scene.getPlayers()),
|
|
569
570
|
isHostile: (a) => hostileByStance(a) || enemyInFight(game.scene, p, a),
|
|
@@ -651,6 +652,7 @@ function wireHeadless(game, ctx, logger) {
|
|
|
651
652
|
try {
|
|
652
653
|
const roomLines = renderRoomLayoutCompact(here, p, {
|
|
653
654
|
isOtherPlayer: a => game.scene.isHumanControlled(a),
|
|
655
|
+
origin: here.planeFrame?.()?.origin,
|
|
654
656
|
isHostile: a => hostileByStance(a) || enemyInFight(game.scene, p, a),
|
|
655
657
|
// The leader's crew are the TABLE's crew -- without this
|
|
656
658
|
// every non-leader saw them as neutral bystanders.
|
|
@@ -671,7 +673,7 @@ function wireHeadless(game, ctx, logger) {
|
|
|
671
673
|
// here anyway rather than asserted, since a decoration must
|
|
672
674
|
// never take down the room push (same reasoning as the
|
|
673
675
|
// try/catch this sits inside).
|
|
674
|
-
const grid = roomGridWire(here);
|
|
676
|
+
const grid = roomGridWire(here, p);
|
|
675
677
|
const data = grid ? { grid } : undefined;
|
|
676
678
|
logger.system(roomLines.join('\n'), { actor: p.name }, 'room', data);
|
|
677
679
|
}
|
|
@@ -2613,7 +2613,9 @@ staff, so codes, passphrases, and lock combinations are exactly the thing you we
|
|
|
2613
2613
|
THEM to solve -- say so plainly when they ask, and send them to find it rather than
|
|
2614
2614
|
handing them a number you'd be making up. If you genuinely don't know a thing, say so plainly and point at who might. You
|
|
2615
2615
|
front half the pay at the meet and settle the rest on delivery (the engine moves the money
|
|
2616
|
-
-- never haggle it yourself unless they invoke a negotiation)
|
|
2616
|
+
-- never haggle it yourself unless they invoke a negotiation).
|
|
2617
|
+
You chose this venue and you HOLD YOUR SEAT in it: never move, walk, approach, follow or
|
|
2618
|
+
leave -- the runner comes to you. Turn, lean, gesture or speak instead.`
|
|
2617
2619
|
: '';
|
|
2618
2620
|
// ---------- prompt ----------
|
|
2619
2621
|
const prompt = `
|
|
@@ -3180,6 +3180,10 @@ export class Player extends AbstractPlayer {
|
|
|
3180
3180
|
* rather than leaving you drawn at the old one. NPCs keep re-deriving --
|
|
3181
3181
|
* a picket or a crowd is placed by spot on purpose (NPC overrides).
|
|
3182
3182
|
*/
|
|
3183
|
+
/** An exact cell somebody PUT this body on -- a step, a shove, a
|
|
3184
|
+
* flight -- as opposed to a drift seat pinned only so it is drawn in
|
|
3185
|
+
* one place (pinDerivedSeat). earshot.ts reads the first as a place. */
|
|
3186
|
+
get placedOnCell() { return this._atCell !== undefined && !this._cellPinnedFromSeat; }
|
|
3183
3187
|
get pinsDerivedSeat() { return true; }
|
|
3184
3188
|
pinDerivedSeat(cell) {
|
|
3185
3189
|
this.atCell = cell;
|
|
@@ -5240,10 +5244,11 @@ export class Player extends AbstractPlayer {
|
|
|
5240
5244
|
}
|
|
5241
5245
|
}
|
|
5242
5246
|
}
|
|
5243
|
-
/** The sustained PHYSICAL BARRIER spell
|
|
5244
|
-
*
|
|
5245
|
-
*
|
|
5246
|
-
* Session-only, like every
|
|
5247
|
+
/** The sustained PHYSICAL BARRIER spell: cells on the district plane
|
|
5248
|
+
* (utilities/barrier-geometry.ts, 2026-10-05 -- it used to seal one
|
|
5249
|
+
* exit). Nothing physical crosses a standing face, the caster
|
|
5250
|
+
* included; walls don't play favorites. Session-only, like every
|
|
5251
|
+
* sustain. */
|
|
5247
5252
|
_sustainedBarrier;
|
|
5248
5253
|
get sustainedBarrier() { return this._sustainedBarrier; }
|
|
5249
5254
|
set sustainedBarrier(v) {
|
|
@@ -5255,8 +5260,9 @@ export class Player extends AbstractPlayer {
|
|
|
5255
5260
|
this.noteConditionChanged();
|
|
5256
5261
|
}
|
|
5257
5262
|
/** The MANA BARRIER twin (canon p.294, RAG-checked): blocks SPIRITS
|
|
5258
|
-
* and astral forms at
|
|
5259
|
-
* objects pass freely
|
|
5263
|
+
* and astral forms at its cells; living mundanes and physical
|
|
5264
|
+
* objects pass freely, and so does its creator (p.315). Same
|
|
5265
|
+
* lifecycle as sustainedBarrier. */
|
|
5260
5266
|
_sustainedManaBarrier;
|
|
5261
5267
|
get sustainedManaBarrier() { return this._sustainedManaBarrier; }
|
|
5262
5268
|
set sustainedManaBarrier(v) {
|
|
@@ -16,7 +16,7 @@ import { dwellingDims } from '../utilities/dwellings.js';
|
|
|
16
16
|
import { spotsActive, isPerchSpot, actorDistanceMeters, voiceBandFor, voiceTag, doorwayKindOf, exitDoorwayKind } from '../utilities/spots.js';
|
|
17
17
|
import { canHearSpeech, canHearShout } from '../utilities/earshot.js';
|
|
18
18
|
import { freshCrowd, clampHeadcount, deriveOccupancy } from '../utilities/crowd.js';
|
|
19
|
-
import { synthesizeGrid, attachExitCell, detachExitCell, hashFnv1a, mulberry32, } from '../utilities/room-grid.js';
|
|
19
|
+
import { synthesizeGrid, attachExitCell, detachExitCell, hashFnv1a, mulberry32, setCellBlocker, } from '../utilities/room-grid.js';
|
|
20
20
|
/**
|
|
21
21
|
* A CAMERA'S DEVICE RATING when the scene does not say (p.356, which
|
|
22
22
|
* lists "Camera" at 1 -- the same table that gives the door lock the 2
|
|
@@ -560,6 +560,9 @@ export class Room extends AbstractRoom {
|
|
|
560
560
|
levels: this.levels,
|
|
561
561
|
terrainHints: this.terrainHints,
|
|
562
562
|
});
|
|
563
|
+
// A BARRIER SPELL IS ASKED, NOT STORED (room-grid.ts CELL_BLOCKERS):
|
|
564
|
+
// its cells come and go while the grid lives, and the scene owns them.
|
|
565
|
+
setCellBlocker(this._grid, c => this.scene?.physicalBarrierAtLocal?.(this, c) ?? false);
|
|
563
566
|
return this._grid;
|
|
564
567
|
}
|
|
565
568
|
/**
|
|
@@ -8,6 +8,12 @@ import { Direction } from '../types/shared/direction-enum.js';
|
|
|
8
8
|
import { Door } from './door.js';
|
|
9
9
|
import { dwellingDims } from '../utilities/dwellings.js';
|
|
10
10
|
import { actorCell } from '../utilities/spots.js';
|
|
11
|
+
import { line3D } from '../utilities/room-grid.js';
|
|
12
|
+
import { faceAt } from '../utilities/barrier-geometry.js';
|
|
13
|
+
/** One step out through a doorway, per exit direction (north is -y). */
|
|
14
|
+
const BARRIER_STEP = {
|
|
15
|
+
north: [0, -1], south: [0, 1], east: [1, 0], west: [-1, 0],
|
|
16
|
+
};
|
|
11
17
|
import { packDistrict, worldCell, buildAuthoredPlane, partyLayout, planeFaults } from '../utilities/district-plane.js';
|
|
12
18
|
import { Category, Size, Rating } from '../types/shared/item-enum.js';
|
|
13
19
|
import { AbstractScene } from '../types/shared/abstracts.js';
|
|
@@ -1222,30 +1228,93 @@ export class Scene extends AbstractScene {
|
|
|
1222
1228
|
return this.characterLocations.get(character.name);
|
|
1223
1229
|
}
|
|
1224
1230
|
/**
|
|
1225
|
-
*
|
|
1226
|
-
*
|
|
1227
|
-
* of the
|
|
1228
|
-
* go.ts refuses traversal for every meat-plane actor while it holds.
|
|
1231
|
+
* EVERY BARRIER SPELL STANDING IN THIS SCENE (barrier-geometry.ts).
|
|
1232
|
+
* They live on the casters -- one of each kind apiece -- so this is a
|
|
1233
|
+
* walk of the players, the same place the old exit seal read them.
|
|
1229
1234
|
*/
|
|
1230
|
-
|
|
1235
|
+
spellBarriers(kind) {
|
|
1236
|
+
const out = [];
|
|
1237
|
+
for (const p of this.getPlayers()) {
|
|
1238
|
+
if (p.sustainedBarrier && (!kind || kind === 'physical'))
|
|
1239
|
+
out.push(p.sustainedBarrier);
|
|
1240
|
+
if (p.sustainedManaBarrier && (!kind || kind === 'mana'))
|
|
1241
|
+
out.push(p.sustainedManaBarrier);
|
|
1242
|
+
}
|
|
1243
|
+
return out;
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* The standing barrier face on this PLANE cell, if any. `passer` is
|
|
1247
|
+
* who is asking to cross: a mana barrier "does not affect its
|
|
1248
|
+
* creator, who can pass through at will" (p.315), so its own weaver
|
|
1249
|
+
* gets undefined. A physical wall plays no favourites.
|
|
1250
|
+
*/
|
|
1251
|
+
barrierAt(c, kind, passer) {
|
|
1252
|
+
for (const barrier of this.spellBarriers(kind)) {
|
|
1253
|
+
if (kind === 'mana' && passer !== undefined && barrier.caster === passer)
|
|
1254
|
+
continue;
|
|
1255
|
+
const face = faceAt(barrier, c);
|
|
1256
|
+
if (face)
|
|
1257
|
+
return { barrier, face };
|
|
1258
|
+
}
|
|
1259
|
+
return undefined;
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* The first standing barrier face a straight line crosses between two
|
|
1263
|
+
* PLANE cells, endpoints excluded -- what a shot at someone behind a
|
|
1264
|
+
* wall has to go through first (p.197-198).
|
|
1265
|
+
*/
|
|
1266
|
+
barrierOnLine(a, b, kind) {
|
|
1267
|
+
if (this.spellBarriers(kind).length === 0)
|
|
1268
|
+
return undefined;
|
|
1269
|
+
for (const c of line3D(a, b).slice(1, -1)) {
|
|
1270
|
+
const hit = this.barrierAt(c, kind);
|
|
1271
|
+
if (hit)
|
|
1272
|
+
return hit;
|
|
1273
|
+
}
|
|
1274
|
+
return undefined;
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* A ROOM-LOCAL cell under a physical barrier -- what Room.ensureGrid
|
|
1278
|
+
* registers with the grid, so findPath, stepFrom and seating all
|
|
1279
|
+
* route round the wall without knowing spells exist.
|
|
1280
|
+
*/
|
|
1281
|
+
physicalBarrierAtLocal(room, local) {
|
|
1282
|
+
if (this.spellBarriers('physical').length === 0)
|
|
1283
|
+
return false;
|
|
1284
|
+
const c = worldCell(this.ensurePlane(), room.name, local);
|
|
1285
|
+
return !!c && !!this.barrierAt(c, 'physical');
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* IS THIS EXIT WALLED OFF BY A BARRIER SPELL. Since 2026-10-05 a
|
|
1289
|
+
* barrier is cells, not a tag on an exit, so the question is whether a
|
|
1290
|
+
* standing face covers the doorway: the exit's own cell or the gap in
|
|
1291
|
+
* the wall beyond it -- from either side, because the far room's
|
|
1292
|
+
* doorway is the same hole.
|
|
1293
|
+
*/
|
|
1294
|
+
barrierSealing(room, direction, kind = 'physical', passer) {
|
|
1295
|
+
if (this.spellBarriers(kind).length === 0)
|
|
1296
|
+
return undefined;
|
|
1297
|
+
const plane = this.ensurePlane();
|
|
1298
|
+
const cellsOf = (r, dir) => {
|
|
1299
|
+
const local = r.ensureGrid()?.exitCells.get(dir.toLowerCase());
|
|
1300
|
+
const at = local && worldCell(plane, r.name, local);
|
|
1301
|
+
if (!at)
|
|
1302
|
+
return [];
|
|
1303
|
+
const step = BARRIER_STEP[dir.toLowerCase()];
|
|
1304
|
+
return step ? [at, { x: at.x + step[0], y: at.y + step[1], z: at.z }] : [at];
|
|
1305
|
+
};
|
|
1231
1306
|
const exit = room.exits.get(direction);
|
|
1232
1307
|
// getOtherSide (the Room), not getOtherSideToName (a "To X" display
|
|
1233
1308
|
// string -- a probe caught the far-side check silently never
|
|
1234
1309
|
// matching on it).
|
|
1235
|
-
const
|
|
1236
|
-
:
|
|
1237
|
-
: exit.getOtherSide?.(room)?.name;
|
|
1310
|
+
const far = !exit || typeof exit === 'string' ? undefined
|
|
1311
|
+
: exit.getOtherSide?.(room);
|
|
1238
1312
|
const opposite = room.getOppositeDirection(direction);
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
return { caster: p.name, force: b.force };
|
|
1245
|
-
}
|
|
1246
|
-
if (destName && b.roomName === destName && opposite && b.direction.toLowerCase() === String(opposite).toLowerCase()) {
|
|
1247
|
-
return { caster: p.name, force: b.force };
|
|
1248
|
-
}
|
|
1313
|
+
const cells = [...cellsOf(room, direction), ...(far && opposite ? cellsOf(far, String(opposite)) : [])];
|
|
1314
|
+
for (const c of cells) {
|
|
1315
|
+
const hit = this.barrierAt(c, kind, passer);
|
|
1316
|
+
if (hit)
|
|
1317
|
+
return { caster: hit.barrier.caster, force: hit.barrier.force };
|
|
1249
1318
|
}
|
|
1250
1319
|
return undefined;
|
|
1251
1320
|
}
|
|
@@ -75,7 +75,7 @@ export function driftAstral(scene, actor, rooms, direction, count, untilOut = fa
|
|
|
75
75
|
// the room it is trying to leave is large.
|
|
76
76
|
const limit = untilOut ? 50 : count;
|
|
77
77
|
while (taken < limit) {
|
|
78
|
-
const step = astralStepFrom(plane, cell, direction, 1, byKey);
|
|
78
|
+
const step = astralStepFrom(plane, cell, direction, 1, byKey, c => scene.barrierAt?.(c, 'mana', actor.name)?.barrier.caster);
|
|
79
79
|
if (step.taken === 0) {
|
|
80
80
|
blockedBy = step.blockedBy;
|
|
81
81
|
ward = step.ward;
|
|
@@ -193,6 +193,9 @@ export function driftRefusal(d, direction) {
|
|
|
193
193
|
* nearest outdoor room and simply goes. The only things that stop a
|
|
194
194
|
* form are the two canon gives: solid earth and a ward.
|
|
195
195
|
*/
|
|
196
|
+
if (d.blockedBy === 'barrier') {
|
|
197
|
+
return `A mana barrier hangs across the way ${direction}${d.ward ? ` -- ${d.ward}'s working` : ''}: a hazy, solid sheet of woven mana. The astral does not pass (p.315-316).`;
|
|
198
|
+
}
|
|
196
199
|
if (d.blockedBy === 'ward') {
|
|
197
200
|
return `A lattice of woven mana stands across the way ${direction}${d.ward ? ` -- ${d.ward}'s` : ''}. The astral does not pass.`;
|
|
198
201
|
}
|