@maka/maka-cli 5.381.1 → 5.383.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/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/follow.js +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/inv.js +3 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/move.js +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +4 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/talk.js +8 -2
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +27 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.js +68 -11
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +3 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/earshot.js +26 -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/shared-run.js +4 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.383.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"summary": "A command line tool for scaffolding Meteor 3.x applications using either React.",
|
|
6
6
|
"description": "A command line tool for scaffolding Meteor 3.x applications using React.",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/commands/command-registry.ts
|
|
2
2
|
import { CommandFactory } from '../factories/command-factory.js';
|
|
3
3
|
import { Logger } from '../utilities/logger.js';
|
|
4
|
-
import { authorizeNpcAction } from '../utilities/npc-authorization.js';
|
|
4
|
+
import { authorizeNpcAction, clientHoldsTheMeet } from '../utilities/npc-authorization.js';
|
|
5
5
|
export class CommandRegistry {
|
|
6
6
|
scene;
|
|
7
7
|
constructor(scene) {
|
|
@@ -74,6 +74,14 @@ export class CommandRegistry {
|
|
|
74
74
|
return { args, refusal: verdict.reason ?? `That is not yours to do.` };
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
+
// The meet's Johnson keeps his seat (npc-authorization.ts).
|
|
78
|
+
if (declaring.isMovement() && !this.scene.isHumanControlled(actor)) {
|
|
79
|
+
const held = clientHoldsTheMeet(this.scene, actor);
|
|
80
|
+
if (held) {
|
|
81
|
+
Logger.getInstance().write(`Refused "${commandString.trim()}" from ${actor.name}: the client keeps his seat.`);
|
|
82
|
+
return { args, refusal: held };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
77
85
|
return { command, args };
|
|
78
86
|
}
|
|
79
87
|
}
|
|
@@ -46,6 +46,16 @@ export class Command {
|
|
|
46
46
|
// whether or not it also remembered to say so here.
|
|
47
47
|
return this.humanOnly ? 'human-only' : this.npcPolicy;
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* THIS VERB MOVES THE ACTOR'S OWN BODY -- across the room (move, run,
|
|
51
|
+
* sprint, fly), out of it (go) or after someone (follow). Read by
|
|
52
|
+
* CommandRegistry.parse for AI-driven actors only: a Johnson holding a
|
|
53
|
+
* meet keeps his seat (npc-authorization.ts clientHoldsTheMeet).
|
|
54
|
+
*/
|
|
55
|
+
static movesActor = false;
|
|
56
|
+
static isMovement() {
|
|
57
|
+
return this.movesActor;
|
|
58
|
+
}
|
|
49
59
|
static verb;
|
|
50
60
|
static description; // Make this static
|
|
51
61
|
actor;
|
|
@@ -124,6 +124,7 @@ export function dragFollowers(scene, mover, origin, logger) {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
export class FollowCommand extends Command {
|
|
127
|
+
static movesActor = true;
|
|
127
128
|
static verb = 'follow';
|
|
128
129
|
static description = 'Tail another runner: "follow <name>" and their room changes take you with them. Bare "follow" (or "unfollow") peels off. It breaks on its own if you get caught in a fight, fall behind, or they leave the run. Same room, same plane, real players only -- NPC companions already heel by nature ("order" sends them).';
|
|
129
130
|
/** The scene's other humans -- the only legal targets. */
|
|
@@ -96,6 +96,7 @@ export class GoCommand extends Command {
|
|
|
96
96
|
return false;
|
|
97
97
|
return GoCommand.HEELS_ON[c.kind](plane);
|
|
98
98
|
}
|
|
99
|
+
static movesActor = true;
|
|
99
100
|
static verb = 'go';
|
|
100
101
|
static description = 'Move in the specified direction ("go back" retraces your last move). Fleeing someone you just fought hands them one free parting shot. Jacked in, the grid sees no distance: "go <any room>" jumps your persona straight there ("map" lights the icons). Inside a host there is no distance to cross at all -- "exit" first.';
|
|
101
102
|
/** See MoveCommand.execute: going through a door is a walk too, and
|
|
@@ -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)
|
|
@@ -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). */
|
|
@@ -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}`;
|
|
@@ -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
|
|
@@ -3308,5 +3308,31 @@
|
|
|
3308
3308
|
// at boot -- and the reputation game over is awaited before the
|
|
3309
3309
|
// command's reply, so the banner is that step's outcome on every
|
|
3310
3310
|
// client and in a dry run's transcript.
|
|
3311
|
-
|
|
3311
|
+
// 1.194.0 (2026-10-04): THE DISTRICT FOLLOWS THE TIER (nqRsvbN6gvKHFKfW9;
|
|
3312
|
+
// ruling: "a runner should end up in a district that fits their
|
|
3313
|
+
// lifestyle"). A Middle runner who slid to Low on unpaid rent woke in
|
|
3314
|
+
// Everett in a stitched-in hole named after Auburn's walk-up.
|
|
3315
|
+
// - A tier whose home is authored in ANOTHER district no longer builds a
|
|
3316
|
+
// hole here (rebindHomeForTier).
|
|
3317
|
+
// - Game.settleAddress moves the runner to hubIdForTier's district as
|
|
3318
|
+
// home, binding that district's own building, after the ride home, at
|
|
3319
|
+
// hub boot (reaching a save that already slid) and after a shared
|
|
3320
|
+
// run's homecoming. A move that cannot land now is retried next time;
|
|
3321
|
+
// a session with no districtSeedLoader never moves.
|
|
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
|
+
export const ENGINE_VERSION = '1.195.0';
|
|
3312
3338
|
//# sourceMappingURL=engine-version.js.map
|
|
@@ -42,7 +42,7 @@ import { pickedUpLine } from './utilities/takeover.js';
|
|
|
42
42
|
import { presenceBeatAllowed, presenceProcessOnTheStreet } from './utilities/presence-gate.js';
|
|
43
43
|
import { pushSave, burnCloudSaveFor, enqueueBoundPush, flushPushQueue, writeSpill, takeLastMemorial, authToken, clearSpill, hasPendingPush, getCloudSessionId, setLeasedHandler, } from './utilities/cloud-saves.js';
|
|
44
44
|
import { DOCWAGON_TIERS, RESUSC_FEE, DEATH_COMP } from './utilities/docwagon.js';
|
|
45
|
-
import { isFence, knownDistricts, LIFESTYLE_TIERS, LIFESTYLE_OPTIONS, canonicalLifestyleTier, canonicalLifestyleOption, lifestyleOptionAllowed, lifestyleTierFor } from './utilities/commerce.js';
|
|
45
|
+
import { isFence, knownDistricts, LIFESTYLE_TIERS, LIFESTYLE_OPTIONS, canonicalLifestyleTier, canonicalLifestyleOption, lifestyleOptionAllowed, lifestyleTierFor, hubIdForTier } from './utilities/commerce.js';
|
|
46
46
|
import { reconcileGarage, findDumpster, DUMPSTER_NAME } from './models/home-containers.js';
|
|
47
47
|
import { ownedDrones } from './utilities/owned-drones.js';
|
|
48
48
|
import { attachBrandMark, attachBrandMarkBottomRight } from './utilities/brand-mark.js';
|
|
@@ -3645,6 +3645,15 @@ export default class Game {
|
|
|
3645
3645
|
// ordinary Street/Squatter case, and the only no-op worth naming.
|
|
3646
3646
|
if (authored === undefined && current.residenceTier === undefined)
|
|
3647
3647
|
return;
|
|
3648
|
+
// THE TIER'S ADDRESS IS IN ANOTHER DISTRICT (nqRsvbN6gvKHFKfW9: a Middle
|
|
3649
|
+
// runner slid to Low on unpaid rent and woke in Everett, in a stitched-in
|
|
3650
|
+
// hole named after Auburn's walk-up). An authored home with no authored
|
|
3651
|
+
// successor HERE means the runner is moving out, not down the hall:
|
|
3652
|
+
// settleAddress takes them to the district that fits, and that district's
|
|
3653
|
+
// own building is bound on arrival. Building a hole here would only leave
|
|
3654
|
+
// a counterfeit of somebody else's address standing on this street.
|
|
3655
|
+
if (authored === undefined)
|
|
3656
|
+
return;
|
|
3648
3657
|
const next = authored ?? this.buildRuntimeHome();
|
|
3649
3658
|
this.moveStash(current, next);
|
|
3650
3659
|
// THE OLD HOLE IS CODE-OWNED, so it leaves with the lease -- and
|
|
@@ -5751,6 +5760,44 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
5751
5760
|
}
|
|
5752
5761
|
return { ok: true, name: seed.name, firstVisit };
|
|
5753
5762
|
}
|
|
5763
|
+
/**
|
|
5764
|
+
* LIVE WHERE THE RENT SAYS (user ruling 2026-10-04, nqRsvbN6gvKHFKfW9: "a
|
|
5765
|
+
* runner should end up in a district that fits their lifestyle").
|
|
5766
|
+
*
|
|
5767
|
+
* The lifestyle desk moves house itself. A tier that SLID on unpaid rent
|
|
5768
|
+
* (settleLifestyleUpkeep) cannot: every homecoming is synchronous and a
|
|
5769
|
+
* district move is not. So each async door a runner comes home through --
|
|
5770
|
+
* the ride home, the hub boot, a shared run's write-back -- calls this
|
|
5771
|
+
* afterwards, and the boot call also reaches a save that slid before this
|
|
5772
|
+
* shipped.
|
|
5773
|
+
*
|
|
5774
|
+
* Never refuses loudly. A move that cannot happen now (no network, a job
|
|
5775
|
+
* standing open) leaves the runner where they are, logged, and the next
|
|
5776
|
+
* homecoming or boot tries again. A visit is left alone: the runner is not
|
|
5777
|
+
* at home, and home is what this is about.
|
|
5778
|
+
*/
|
|
5779
|
+
async settleAddress() {
|
|
5780
|
+
if (!this.hasHub || !this._hubScene || this.scene !== this._hubScene || this._crewSpace || this.isVisiting)
|
|
5781
|
+
return undefined;
|
|
5782
|
+
// NO LOADER, NOWHERE TO GO: a terminal and a site-hosted hub both say how
|
|
5783
|
+
// to fetch a district (districtSeedLoader). A harness that names none
|
|
5784
|
+
// booted exactly the one district it means to test, and must not be
|
|
5785
|
+
// carried off to a network fetch because its fixture's tier lives elsewhere.
|
|
5786
|
+
if (!this.districtSeedLoader)
|
|
5787
|
+
return undefined;
|
|
5788
|
+
const here = this.districtId;
|
|
5789
|
+
const want = hubIdForTier(this.lifestyleTier);
|
|
5790
|
+
if (here === undefined || want === undefined || here === want)
|
|
5791
|
+
return undefined;
|
|
5792
|
+
const leaving = this._hubSeed?.name ?? here;
|
|
5793
|
+
const moved = await this.moveToDistrict(want, { asHome: true });
|
|
5794
|
+
if (!moved.ok) {
|
|
5795
|
+
Logger.getInstance().write(`Address: ${this.lifestyleTier} lives in ${want}, runner still in ${here} -- move deferred (${moved.why}).`);
|
|
5796
|
+
return undefined;
|
|
5797
|
+
}
|
|
5798
|
+
this.requestSave('move');
|
|
5799
|
+
return `${leaving} is past what a ${this.lifestyleTier} lifestyle pays for. The landlord's drone has your things at the curb by morning, and a cab takes you to ${moved.name} -- ${this._homeRoom.name} is home now. The footlocker came with you; the neighbourhood did not.`;
|
|
5800
|
+
}
|
|
5754
5801
|
/**
|
|
5755
5802
|
* Wires a fresh, always-unlocked path between the persistent home base
|
|
5756
5803
|
* and wherever the player is starting the current job from. Clears the
|
|
@@ -6195,6 +6242,11 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
6195
6242
|
const owed = this.applyHostedHomecoming();
|
|
6196
6243
|
if (owed)
|
|
6197
6244
|
Logger.getInstance().logWithColor(owed, 'green');
|
|
6245
|
+
// A tier that slid -- at that homecoming, or in a session that ended
|
|
6246
|
+
// before the move could land -- moves house before the runner wakes.
|
|
6247
|
+
const moved = await this.settleAddress();
|
|
6248
|
+
if (moved)
|
|
6249
|
+
Logger.getInstance().logWithColor(moved, 'green');
|
|
6198
6250
|
this.requestSave('hub-arrival');
|
|
6199
6251
|
}
|
|
6200
6252
|
// A REGISTERED sprite from the save re-shells at the cot (the
|
|
@@ -6961,7 +7013,10 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
6961
7013
|
return `You're inside a loose end's host -- "exit" or "jack out" is what ends it.`;
|
|
6962
7014
|
}
|
|
6963
7015
|
if (this._hubScene && this.scene !== this._hubScene) {
|
|
6964
|
-
|
|
7016
|
+
const home = this.returnToHub();
|
|
7017
|
+
// A tier that slid at the door moves house (settleAddress).
|
|
7018
|
+
const moved = await this.settleAddress();
|
|
7019
|
+
return moved ? `${home}\n${moved}` : home;
|
|
6965
7020
|
}
|
|
6966
7021
|
const gate = this.canDraftNextScene();
|
|
6967
7022
|
if (!gate.allowed) {
|
|
@@ -7426,14 +7481,12 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
7426
7481
|
const lapse = dropped.length > 0
|
|
7427
7482
|
? ` The garage lease goes with it -- the shutter comes down${sealedIn.length > 0 ? ` with ${sealedIn.join(', ')} still inside` : ''}.`
|
|
7428
7483
|
: '';
|
|
7429
|
-
// THE ADDRESS
|
|
7430
|
-
//
|
|
7431
|
-
//
|
|
7432
|
-
// district move is async
|
|
7433
|
-
//
|
|
7434
|
-
|
|
7435
|
-
// does, with "lifestyle".
|
|
7436
|
-
rentLine = `The landlord's drone pings before the door even shuts: ${paid > 0 ? `${paid} paid, ` : ''}${this.rentDebt} nuyen still owed.${shedLine} Your ${tier.name} arrangement lapses -- you're living ${this.lifestyleTier} now.${lapse}${hint(` ("pay" the tab, "lifestyle" to move somewhere you can afford.)`)}`;
|
|
7484
|
+
// THE ADDRESS GOES WITH THE TIER (user ruling 2026-10-04, on
|
|
7485
|
+
// nqRsvbN6gvKHFKfW9: "a runner should end up in a district that fits
|
|
7486
|
+
// their lifestyle"). Not here, though: this is synchronous and a
|
|
7487
|
+
// district move is async, so every homecoming's async caller follows
|
|
7488
|
+
// up with settleAddress, which does the moving.
|
|
7489
|
+
rentLine = `The landlord's drone pings before the door even shuts: ${paid > 0 ? `${paid} paid, ` : ''}${this.rentDebt} nuyen still owed.${shedLine} Your ${tier.name} arrangement lapses -- you're living ${this.lifestyleTier} now.${lapse}${hint(` ("pay" the tab when you're flush.)`)}`;
|
|
7437
7490
|
}
|
|
7438
7491
|
else {
|
|
7439
7492
|
rentLine = `Even the doorway costs something: ${this.rentDebt} nuyen on the tab.${shedLine}${hint(` ("pay" when you're flush.)`)}`;
|
|
@@ -9220,8 +9273,12 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
9220
9273
|
? ` {red-fg}[JAMMED -- reboot]{/red-fg}`
|
|
9221
9274
|
: weaponItem.ammo === 0 ? ` {red-fg}[DRY]{/red-fg}` : ` [${weaponItem.ammo} rds]`)
|
|
9222
9275
|
: '';
|
|
9276
|
+
// NO VERB COACHING IN THE HUD (MkxamPxmsSyonR3wC: "get rid of this
|
|
9277
|
+
// line in the status bar: -- "brandish""). The status bar is state;
|
|
9278
|
+
// the `brandish` hint already rides the equip/holster replies and
|
|
9279
|
+
// `equipment`, where it answers the moment the question comes up.
|
|
9223
9280
|
if (player.plane === 'meat' && weaponItem && !player.weaponDrawn) {
|
|
9224
|
-
return `{white-fg}Holstered: ${weaponItem.name}${ammoNote}
|
|
9281
|
+
return `{white-fg}Holstered: ${weaponItem.name}${ammoNote}{/white-fg}`;
|
|
9225
9282
|
}
|
|
9226
9283
|
// White, not gray: gray is ANSI bright-black and illegible on
|
|
9227
9284
|
// stock dark palettes -- the same ruling that moved the HUD
|
|
@@ -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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sameSpot, spotOf, spotsActive, isPerchSpot, OPEN_FLOOR, CONVERSATION_M, actorDistanceMeters } from './spots.js';
|
|
1
|
+
import { sameSpot, spotOf, spotsActive, isPerchSpot, OPEN_FLOOR, CONVERSATION_M, actorDistanceMeters, onTheFloor } from './spots.js';
|
|
2
2
|
/**
|
|
3
3
|
* WHO CAN OVERHEAR A MURMUR (playtest, Deditri: "a private place for
|
|
4
4
|
* calls"). A call is audible to the room, and that is correct and
|
|
@@ -123,6 +123,28 @@ export function hearingRangeMeters(listener) {
|
|
|
123
123
|
const bonus = listener.augBonus('hearing') + listener.qualityBonus('perception');
|
|
124
124
|
return Math.max(SPEECH_METERS, SPEECH_METERS + bonus);
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* HAS A PLACE IN THE ROOM: a named spot, OR an exact cell on the floor.
|
|
128
|
+
*
|
|
129
|
+
* THE SECOND HALF IS THE FIX (xurFSiqNtRNbXA88A: "I'm getting 'Nobody's
|
|
130
|
+
* close enough to catch that', but I'm right next to Mr. Vellum"). The
|
|
131
|
+
* runner stepped into Vellum's square; the excuse-me rule (spots.ts
|
|
132
|
+
* shoulderAside) moved him one cell over and, as it and yieldDoorway both
|
|
133
|
+
* do on purpose, left him on that EXACT cell with no spot name. This
|
|
134
|
+
* module read "no spot name" as "circulating", and a circulating listener
|
|
135
|
+
* is passing through and hears no murmur -- so a Johnson drawn one square
|
|
136
|
+
* from the runner was deaf to everything said to him for the rest of the
|
|
137
|
+
* meet. An exact cell is the most precise position the engine has
|
|
138
|
+
* (seatingIn seats it first), and distance is measured from it.
|
|
139
|
+
*
|
|
140
|
+
* A DRIFT SEAT IS STILL NOT A PLACE: a circulating runner's derived seat
|
|
141
|
+
* is pinned to atCell only so it is drawn in one spot (Player.
|
|
142
|
+
* pinDerivedSeat), and placedOnCell says no to it -- the Kessler rule
|
|
143
|
+
* below stands (speech-range.test.ts).
|
|
144
|
+
*/
|
|
145
|
+
function standsSomewhere(actor) {
|
|
146
|
+
return spotOf(actor) !== undefined || (onTheFloor(actor) && actor.placedOnCell);
|
|
147
|
+
}
|
|
126
148
|
/**
|
|
127
149
|
* CAN THIS LISTENER HEAR ORDINARY SPEECH FROM THERE -- the whole of the
|
|
128
150
|
* `say` rule, in one place so the verb, the NPC fan-out and any future
|
|
@@ -176,7 +198,7 @@ export function canHearShout(speaker, listener) {
|
|
|
176
198
|
const room = speaker.currentLocation;
|
|
177
199
|
if (!spotsActive(room))
|
|
178
200
|
return true;
|
|
179
|
-
if (
|
|
201
|
+
if (!standsSomewhere(speaker) || !standsSomewhere(listener))
|
|
180
202
|
return true;
|
|
181
203
|
const reach = SHOUT_METERS + (hearingRangeMeters(listener) - SPEECH_METERS);
|
|
182
204
|
return actorDistanceMeters(room, speaker, listener) <= reach;
|
|
@@ -186,10 +208,10 @@ export function canHearSpeech(speaker, listener) {
|
|
|
186
208
|
if (!spotsActive(room))
|
|
187
209
|
return true;
|
|
188
210
|
// The speaker is unplaced: they are moving among everyone.
|
|
189
|
-
if (
|
|
211
|
+
if (!standsSomewhere(speaker))
|
|
190
212
|
return true;
|
|
191
213
|
// The listener is unplaced: they are passing through, not standing here.
|
|
192
|
-
if (
|
|
214
|
+
if (!standsSomewhere(listener))
|
|
193
215
|
return false;
|
|
194
216
|
return actorDistanceMeters(room, speaker, listener) <= hearingRangeMeters(listener);
|
|
195
217
|
}
|
|
@@ -126,6 +126,33 @@ export function authorizeNpcAction(scene, actor, verb, args) {
|
|
|
126
126
|
}
|
|
127
127
|
return ALLOWED;
|
|
128
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* THE JOHNSON HOLDS THE MEET (CWMrB2aNyL4hhrjyG: "when at the meet, the
|
|
131
|
+
* johnson shouldn't just keep moving around the room").
|
|
132
|
+
*
|
|
133
|
+
* The run's client picks the venue and waits in it (Run Faster's meet,
|
|
134
|
+
* p.197-198: the Johnson sets the place, the runners come to them). His
|
|
135
|
+
* AI read every beat as a chance to stage himself -- "Mr. Lacquer moves
|
|
136
|
+
* to the open floor", "moves to the counter", prod 2026-10-04 -- so the
|
|
137
|
+
* man the runner came to see wandered the room for the whole briefing.
|
|
138
|
+
* Prose can still have him turn, lean, or look at the door; the engine
|
|
139
|
+
* keeps his body where the meet put it until the job is settled, and his
|
|
140
|
+
* leaving after the settlement is the engine's own beat (game.ts
|
|
141
|
+
* "Johnsons don't linger"), not his AI's.
|
|
142
|
+
*
|
|
143
|
+
* A FIGHT IS NOT A MEET: inside a Combat Turn the turn loop moves him,
|
|
144
|
+
* and a Johnson ducking for cover is not this rule's business.
|
|
145
|
+
*
|
|
146
|
+
* Returns a refusal reason, or undefined when he may move.
|
|
147
|
+
*/
|
|
148
|
+
export function clientHoldsTheMeet(scene, actor) {
|
|
149
|
+
const client = scene.ownerGame?.currentClient;
|
|
150
|
+
if (!client || client.name !== actor.name || client.reported || client.dead)
|
|
151
|
+
return undefined;
|
|
152
|
+
if (actor.inExchange || scene.encounterIn?.(actor.currentLocation))
|
|
153
|
+
return undefined;
|
|
154
|
+
return `You hold your seat for the meet -- the runner came to you, and you stay where you are until the job is settled. Turn, gesture, or speak instead of walking.`;
|
|
155
|
+
}
|
|
129
156
|
/**
|
|
130
157
|
* Is this actor home? A decker jacked into the Matrix, a magician
|
|
131
158
|
* projecting astrally and a rigger jumped into a drone all leave a body
|
|
@@ -7,6 +7,7 @@ import { fuzzyPickName } from './fuzzy-match.js';
|
|
|
7
7
|
import { resetOverwatch } from './overwatch.js';
|
|
8
8
|
import { rebootWipe } from './marks.js';
|
|
9
9
|
import { hint } from './hints.js';
|
|
10
|
+
import { codesOnPage } from './clue-reading.js';
|
|
10
11
|
import { actorCell } from './spots.js';
|
|
11
12
|
import { announceGameOver } from './game-over.js';
|
|
12
13
|
import { holdAtBrink } from './not-dead-yet.js';
|
|
@@ -624,6 +625,43 @@ export function isObjectiveItem(scene, itemName) {
|
|
|
624
625
|
export function objectiveMark(scene, itemName) {
|
|
625
626
|
return isObjectiveItem(scene, itemName) ? `{yellow-fg}${OBJECTIVE_GLYPH}{/yellow-fg} ` : '';
|
|
626
627
|
}
|
|
628
|
+
/**
|
|
629
|
+
* PART OF THE JOB, NOT THE PRIZE (nfrBW27eRRxstJ8uC: "the 'Lot Catalogue
|
|
630
|
+
* Proof Sheet' isn't marked in my inventory as quest specific").
|
|
631
|
+
*
|
|
632
|
+
* The star is the objective alone, so a page the job's route runs
|
|
633
|
+
* through -- the proof sheet with the vault shutter's code pencilled in
|
|
634
|
+
* its margin -- sat unmarked among the loot, and the runner could not
|
|
635
|
+
* tell it from a souvenir. A LEAD is an item this scene's barriers
|
|
636
|
+
* answer to, read off the same facts the engine itself opens them with:
|
|
637
|
+
* - a page that spells one of the scene's device codes (clue-reading.ts
|
|
638
|
+
* codesOnPage -- the fact ensureClueDetails guarantees is written), or
|
|
639
|
+
* - the key a device declares as its own (Device.keyItem).
|
|
640
|
+
* Nothing else: a lead the engine cannot point to would be a guess.
|
|
641
|
+
*
|
|
642
|
+
* It marks the item, never the code -- whether the runner can READ the
|
|
643
|
+
* number off the page is still the Perception Test (clue-reading.ts).
|
|
644
|
+
* Hub scenes have no win condition and mark nothing, like the star.
|
|
645
|
+
*/
|
|
646
|
+
export const LEAD_GLYPH = '☆';
|
|
647
|
+
export function isJobLead(scene, item) {
|
|
648
|
+
if (!scene?.getWinCondition?.())
|
|
649
|
+
return false;
|
|
650
|
+
const name = item.name.trim().toLowerCase();
|
|
651
|
+
for (const room of Object.values(scene.getRooms())) {
|
|
652
|
+
for (const device of room.devices ?? []) {
|
|
653
|
+
if (device.keyItem?.name.trim().toLowerCase() === name)
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return codesOnPage(scene, item).length > 0;
|
|
658
|
+
}
|
|
659
|
+
/** `★ ` for the objective, `☆ ` for a lead (isJobLead), '' otherwise. */
|
|
660
|
+
export function jobMark(scene, item) {
|
|
661
|
+
if (isObjectiveItem(scene, item.name))
|
|
662
|
+
return objectiveMark(scene, item.name);
|
|
663
|
+
return isJobLead(scene, item) ? `{yellow-fg}${LEAD_GLYPH}{/yellow-fg} ` : '';
|
|
664
|
+
}
|
|
627
665
|
/**
|
|
628
666
|
* The "you can see them, but you can't touch them" refusal. When a target
|
|
629
667
|
* name matches someone in the room on a plane the actor perceives but
|
|
@@ -1996,6 +1996,10 @@ export class SharedRunSession {
|
|
|
1996
1996
|
const lines = g.applyHostedHomecoming(owed);
|
|
1997
1997
|
if (lines)
|
|
1998
1998
|
Logger.getInstance().logWithColor(lines, 'green');
|
|
1999
|
+
// A tier that slid at that homecoming moves house (settleAddress).
|
|
2000
|
+
const moved = await g.settleAddress();
|
|
2001
|
+
if (moved)
|
|
2002
|
+
Logger.getInstance().logWithColor(moved, 'green');
|
|
1999
2003
|
}
|
|
2000
2004
|
}
|
|
2001
2005
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.383.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.",
|