@maka/maka-cli 5.12.0 → 5.14.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 +2 -1
- package/bundle/typescript/src/commands/aws/ci.sub.cmd.js +1 -0
- package/bundle/typescript/src/commands/aws/ci.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/commands/build/build.js +13 -37
- package/bundle/typescript/src/commands/build/build.js.map +1 -1
- package/bundle/typescript/src/commands/create.js +13 -28
- package/bundle/typescript/src/commands/create.js.map +1 -1
- package/bundle/typescript/src/commands/deploy/_index.d.ts +1 -0
- package/bundle/typescript/src/commands/deploy/_index.js +1 -0
- package/bundle/typescript/src/commands/deploy/_index.js.map +1 -1
- package/bundle/typescript/src/commands/deploy/deploy.command.js +1 -0
- package/bundle/typescript/src/commands/deploy/deploy.command.js.map +1 -1
- package/bundle/typescript/src/commands/deploy/github.sub.cmd.d.ts +1 -0
- package/bundle/typescript/src/commands/deploy/github.sub.cmd.js +147 -0
- package/bundle/typescript/src/commands/deploy/github.sub.cmd.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js +2 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +5 -5
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +48 -37
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +14 -14
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/catalog.js +25 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/catalog.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.js +6 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/crew.d.ts +43 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/crew.js +259 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/crew.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js +6 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/give.js +15 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/give.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +8 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/grapple.js +3 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/grapple.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +3 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.d.ts +5 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js +45 -27
- package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/job-offer.js +12 -7
- package/bundle/typescript/src/commands/game/sideQuest/commands/job-offer.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/lead.d.ts +5 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/lead.js +31 -17
- package/bundle/typescript/src/commands/game/sideQuest/commands/lead.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/order.js +21 -10
- package/bundle/typescript/src/commands/game/sideQuest/commands/order.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/palm.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/palm.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/perform.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/perform.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/reload.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/reload.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js +21 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.d.ts +2 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +81 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/source.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/source.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +3 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/talk.js +4 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/talk.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.js +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.d.ts +15 -2
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +70 -35
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.d.ts +16 -5
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js +36 -8
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +139 -4
- package/bundle/typescript/src/commands/game/sideQuest/game.js +591 -85
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/headless.d.ts +86 -0
- package/bundle/typescript/src/commands/game/sideQuest/headless.js +185 -0
- package/bundle/typescript/src/commands/game/sideQuest/headless.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +50 -4
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.d.ts +59 -11
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +242 -109
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +11 -11
- package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +22 -2
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/scene-seed.d.ts +8 -0
- package/bundle/typescript/src/commands/game/sideQuest/ui.js +7 -5
- package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.d.ts +6 -4
- package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js +36 -24
- package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.d.ts +7 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +59 -18
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/command-queue.d.ts +19 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/command-queue.js +56 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/command-queue.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/crew-ledger.d.ts +21 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/crew-ledger.js +25 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/crew-ledger.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/emitter.d.ts +44 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/emitter.js +22 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/emitter.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.d.ts +4 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.js +5 -2
- package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/hints.d.ts +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/hints.js +24 -7
- package/bundle/typescript/src/commands/game/sideQuest/utilities/hints.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/logger.d.ts +6 -5
- package/bundle/typescript/src/commands/game/sideQuest/utilities/logger.js +30 -6
- package/bundle/typescript/src/commands/game/sideQuest/utilities/logger.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/session-context.d.ts +28 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/session-context.js +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/session-context.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/social.d.ts +50 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/social.js +166 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/social.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/commands/generate.js +7 -0
- package/bundle/typescript/src/commands/generate.js.map +1 -1
- package/bundle/typescript/src/commands/run/run.command.js +31 -31
- package/bundle/typescript/src/commands/run/run.command.js.map +1 -1
- package/bundle/typescript/src/templates/meteor/config/deploy.sh.template +73 -0
- package/bundle/typescript/src/templates/meteor/config/nginx.conf.template +31 -0
- package/bundle/typescript/src/templates/meteor/github/workflows/deploy.yaml +82 -0
- package/bundle/typescript/src/tools/mpi/mpi.class.d.ts +18 -0
- package/bundle/typescript/src/tools/mpi/mpi.class.js +276 -3
- package/bundle/typescript/src/tools/mpi/mpi.class.js.map +1 -1
- package/bundle/typescript/src/tools/mpi/sdk-runtime.d.ts +6 -0
- package/bundle/typescript/src/tools/mpi/sdk-runtime.js +64 -0
- package/bundle/typescript/src/tools/mpi/sdk-runtime.js.map +1 -0
- package/bundle/typescript/src/tools/pkg/pkg.class.js +89 -78
- package/bundle/typescript/src/tools/pkg/pkg.class.js.map +1 -1
- package/bundle/typescript/src/tools/scaffold/scaffold.class.js +23 -7
- package/bundle/typescript/src/tools/scaffold/scaffold.class.js.map +1 -1
- package/package.json +2 -1
|
@@ -7,7 +7,15 @@ import { AbstractScene } from '../types/shared/abstracts.js';
|
|
|
7
7
|
import { rollPool, formatRoll } from '../utilities/dice.js';
|
|
8
8
|
export class Scene extends AbstractScene {
|
|
9
9
|
story;
|
|
10
|
-
|
|
10
|
+
// SYMMETRIC PLAYERS: the scene holds a LIST of human players --
|
|
11
|
+
// players[0] is the primary (the solo game's one human; the leader's
|
|
12
|
+
// character in shared runs). The `player` getter keeps the historical
|
|
13
|
+
// singular reads pointed at players[0]; code that can act for ANY
|
|
14
|
+
// human should take an explicit actor instead of reaching for it.
|
|
15
|
+
players;
|
|
16
|
+
get player() {
|
|
17
|
+
return this.players[0];
|
|
18
|
+
}
|
|
11
19
|
logger;
|
|
12
20
|
actors;
|
|
13
21
|
game;
|
|
@@ -33,7 +41,7 @@ export class Scene extends AbstractScene {
|
|
|
33
41
|
currency;
|
|
34
42
|
constructor(config) {
|
|
35
43
|
super();
|
|
36
|
-
this.
|
|
44
|
+
this.players = [config.player];
|
|
37
45
|
this.logger = Logger.getInstance();
|
|
38
46
|
this.actors = new Map();
|
|
39
47
|
this.currency = config.currency ?? {
|
|
@@ -84,15 +92,22 @@ export class Scene extends AbstractScene {
|
|
|
84
92
|
// room between an NPC attack's narration beats, mid-exchange. Cleared
|
|
85
93
|
// BEFORE the between-exchanges pause, which stays the legitimate window
|
|
86
94
|
// to flee or heal. NPC-vs-NPC fights elsewhere never set this.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
// Per-player (symmetric multiplayer: two humans can be in two
|
|
96
|
+
// different fights): the set holds the NAMES of human players whose
|
|
97
|
+
// exchange is mid-resolution. No-arg calls keep the historical solo
|
|
98
|
+
// meaning -- begin/end act on the primary player, the query asks
|
|
99
|
+
// "is ANY human's exchange live".
|
|
100
|
+
_playerExchangesActive = new Set();
|
|
101
|
+
beginPlayerExchange(playerName) {
|
|
102
|
+
this._playerExchangesActive.add(playerName ?? this.player.name);
|
|
103
|
+
}
|
|
104
|
+
endPlayerExchange(playerName) {
|
|
105
|
+
this._playerExchangesActive.delete(playerName ?? this.player.name);
|
|
106
|
+
}
|
|
107
|
+
isPlayerExchangeActive(playerName) {
|
|
108
|
+
if (playerName !== undefined)
|
|
109
|
+
return this._playerExchangesActive.has(playerName);
|
|
110
|
+
return this._playerExchangesActive.size > 0;
|
|
96
111
|
}
|
|
97
112
|
/**
|
|
98
113
|
* CombatExchange.finishExchange() -> here -> the fight autopilot (see
|
|
@@ -102,13 +117,18 @@ export class Scene extends AbstractScene {
|
|
|
102
117
|
*/
|
|
103
118
|
notifyExchangeSettled(combatants) {
|
|
104
119
|
this.game?.autoFight?.onExchangeSettled?.(combatants);
|
|
105
|
-
// While
|
|
120
|
+
// While a player lies KO'd, every settling exchange is a beat of
|
|
106
121
|
// the fight still raging around them: learn its hostiles, then see
|
|
107
122
|
// whether the room has settled (see maybeResolvePlayerKnockout).
|
|
108
|
-
if (this.
|
|
109
|
-
for (const
|
|
110
|
-
|
|
111
|
-
|
|
123
|
+
if (this.playerKnockouts.size > 0) {
|
|
124
|
+
for (const [name, ko] of this.playerKnockouts) {
|
|
125
|
+
const p = this.getPlayerByName(name);
|
|
126
|
+
if (!p)
|
|
127
|
+
continue;
|
|
128
|
+
for (const c of combatants) {
|
|
129
|
+
if (c !== p && c.allyOf !== p.name) {
|
|
130
|
+
ko.hostiles.add(c.name);
|
|
131
|
+
}
|
|
112
132
|
}
|
|
113
133
|
}
|
|
114
134
|
this.maybeResolvePlayerKnockout();
|
|
@@ -126,40 +146,64 @@ export class Scene extends AbstractScene {
|
|
|
126
146
|
// player comes to with a recovery test; a conscious hostile left
|
|
127
147
|
// standing over them with nobody fighting for them -> the street
|
|
128
148
|
// decides, same as ever.
|
|
129
|
-
|
|
130
|
-
|
|
149
|
+
/** One pending knockout per HUMAN player, keyed by player name --
|
|
150
|
+
* two humans can be down in two different rooms (symmetric MP). */
|
|
151
|
+
playerKnockouts = new Map();
|
|
152
|
+
/** Solo-compat view: the PRIMARY player's pending knockout, in the
|
|
153
|
+
* historical single-record shape. Multiplayer-aware code should ask
|
|
154
|
+
* getKnockout(name) instead. */
|
|
155
|
+
get playerKnockout() {
|
|
156
|
+
return this.playerKnockouts.get(this.player.name);
|
|
157
|
+
}
|
|
158
|
+
getKnockout(playerName) {
|
|
159
|
+
return this.playerKnockouts.get(playerName);
|
|
160
|
+
}
|
|
161
|
+
/** Fight-side entry: a player just dropped from stun. Hostile names
|
|
131
162
|
* seed the watch list (grows via notifyExchangeSettled); empty means a
|
|
132
163
|
* self-inflicted collapse (drain, backlash) with nobody to fear. */
|
|
133
|
-
beginPlayerKnockout(hostileNames) {
|
|
134
|
-
|
|
164
|
+
beginPlayerKnockout(hostileNames, player = this.player) {
|
|
165
|
+
const existing = this.playerKnockouts.get(player.name);
|
|
166
|
+
if (existing) {
|
|
135
167
|
for (const n of hostileNames)
|
|
136
|
-
|
|
168
|
+
existing.hostiles.add(n);
|
|
137
169
|
return;
|
|
138
170
|
}
|
|
139
|
-
this.
|
|
140
|
-
this.addWorldEvent(`${
|
|
141
|
-
this.spurCompanionsForFallenMaster();
|
|
171
|
+
this.playerKnockouts.set(player.name, { hostiles: new Set(hostileNames), spurs: 0 });
|
|
172
|
+
this.addWorldEvent(`${player.name} is DOWN -- out cold on the floor of ${player.currentLocation.name}.`);
|
|
173
|
+
this.spurCompanionsForFallenMaster(player);
|
|
142
174
|
// First reckoning comes after the collapse narration has landed --
|
|
143
175
|
// synchronous evaluation here would print "you come to" (or the
|
|
144
176
|
// game-over) BEFORE the knockout line the caller is still announcing.
|
|
145
|
-
this.armKnockoutTimer(1500);
|
|
177
|
+
this.armKnockoutTimer(1500, player);
|
|
146
178
|
}
|
|
147
|
-
armKnockoutTimer(delayMs) {
|
|
148
|
-
|
|
179
|
+
armKnockoutTimer(delayMs, player) {
|
|
180
|
+
const ko = this.playerKnockouts.get(player.name);
|
|
181
|
+
if (!ko)
|
|
149
182
|
return;
|
|
150
|
-
if (
|
|
151
|
-
clearTimeout(
|
|
152
|
-
|
|
183
|
+
if (ko.timer)
|
|
184
|
+
clearTimeout(ko.timer);
|
|
185
|
+
// The reckoning is engine mutation -- with a second human's commands
|
|
186
|
+
// in flight it must take its turn in the queue like everything else
|
|
187
|
+
// (M1's deferral, landed here in M2). Solo: the queue is empty and
|
|
188
|
+
// this is effectively immediate.
|
|
189
|
+
const t = setTimeout(() => {
|
|
190
|
+
const q = this.game?.commandQueue;
|
|
191
|
+
const run = () => this.maybeResolvePlayerKnockout(true, player);
|
|
192
|
+
if (q)
|
|
193
|
+
void q.enqueue(run);
|
|
194
|
+
else
|
|
195
|
+
run();
|
|
196
|
+
}, delayMs);
|
|
153
197
|
t.unref?.();
|
|
154
|
-
|
|
198
|
+
ko.timer = t;
|
|
155
199
|
}
|
|
156
200
|
/** Hostiles from the watch list still on their feet in the player's
|
|
157
201
|
* room: present, conscious, and not surrendered. */
|
|
158
|
-
standingHostiles() {
|
|
159
|
-
const ko = this.
|
|
202
|
+
standingHostiles(player) {
|
|
203
|
+
const ko = this.playerKnockouts.get(player.name);
|
|
160
204
|
if (!ko)
|
|
161
205
|
return [];
|
|
162
|
-
const room =
|
|
206
|
+
const room = player.currentLocation;
|
|
163
207
|
const out = [];
|
|
164
208
|
for (const name of ko.hostiles) {
|
|
165
209
|
const a = this.getActor(name);
|
|
@@ -170,8 +214,8 @@ export class Scene extends AbstractScene {
|
|
|
170
214
|
return out;
|
|
171
215
|
}
|
|
172
216
|
/** Conscious companions in the player's room -- the reason judgment waits. */
|
|
173
|
-
consciousAlliesInRoom() {
|
|
174
|
-
const room =
|
|
217
|
+
consciousAlliesInRoom(player) {
|
|
218
|
+
const room = player.currentLocation;
|
|
175
219
|
return (this.game?.companions ?? [])
|
|
176
220
|
.map(c => c.npc)
|
|
177
221
|
.filter(n => n.currentLocation === room && !n.isIncapacitated());
|
|
@@ -179,30 +223,40 @@ export class Scene extends AbstractScene {
|
|
|
179
223
|
/**
|
|
180
224
|
* The reckoning. Runs on every settled exchange, every surrender, and a
|
|
181
225
|
* heartbeat timer (fights can stall on an idle AI). Quiet no-op unless a
|
|
182
|
-
* knockout is actually pending and
|
|
226
|
+
* knockout is actually pending and that player is still merely stunned.
|
|
227
|
+
* With no `player` given, sweeps every pending knockout (settled
|
|
228
|
+
* exchanges and surrenders don't know whose fight just ended).
|
|
183
229
|
*/
|
|
184
|
-
maybeResolvePlayerKnockout(fromTimer = false) {
|
|
185
|
-
|
|
230
|
+
maybeResolvePlayerKnockout(fromTimer = false, player) {
|
|
231
|
+
if (!player) {
|
|
232
|
+
for (const name of [...this.playerKnockouts.keys()]) {
|
|
233
|
+
const p = this.getPlayerByName(name);
|
|
234
|
+
if (p)
|
|
235
|
+
this.maybeResolvePlayerKnockout(fromTimer, p);
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const ko = this.playerKnockouts.get(player.name);
|
|
186
240
|
if (!ko)
|
|
187
241
|
return;
|
|
188
|
-
if (
|
|
242
|
+
if (player.isDown()) {
|
|
189
243
|
// Died for real since the KO (overflow) -- the death flow owns this.
|
|
190
|
-
this.clearPlayerKnockout();
|
|
244
|
+
this.clearPlayerKnockout(player);
|
|
191
245
|
return;
|
|
192
246
|
}
|
|
193
|
-
if (!
|
|
247
|
+
if (!player.isUnconscious()) {
|
|
194
248
|
// Something else woke them (a companion's medkit, say).
|
|
195
|
-
this.clearPlayerKnockout();
|
|
249
|
+
this.clearPlayerKnockout(player);
|
|
196
250
|
return;
|
|
197
251
|
}
|
|
198
|
-
const standing = this.standingHostiles();
|
|
252
|
+
const standing = this.standingHostiles(player);
|
|
199
253
|
if (standing.length === 0) {
|
|
200
|
-
void this.recoverFromKnockout();
|
|
254
|
+
void this.recoverFromKnockout(player);
|
|
201
255
|
return;
|
|
202
256
|
}
|
|
203
|
-
const allies = this.consciousAlliesInRoom();
|
|
257
|
+
const allies = this.consciousAlliesInRoom(player);
|
|
204
258
|
if (allies.length === 0) {
|
|
205
|
-
this.streetDecidesKnockout(standing.map(s => s.name));
|
|
259
|
+
this.streetDecidesKnockout(standing.map(s => s.name), player);
|
|
206
260
|
return;
|
|
207
261
|
}
|
|
208
262
|
// Allies still stand for the player -- the fight goes on. On timer
|
|
@@ -211,22 +265,23 @@ export class Scene extends AbstractScene {
|
|
|
211
265
|
if (fromTimer) {
|
|
212
266
|
ko.spurs += 1;
|
|
213
267
|
if (ko.spurs > 3) {
|
|
214
|
-
this.streetDecidesKnockout(standing.map(s => s.name));
|
|
268
|
+
this.streetDecidesKnockout(standing.map(s => s.name), player);
|
|
215
269
|
return;
|
|
216
270
|
}
|
|
217
|
-
this.spurCompanionsForFallenMaster();
|
|
271
|
+
this.spurCompanionsForFallenMaster(player);
|
|
218
272
|
}
|
|
219
|
-
this.armKnockoutTimer(10000);
|
|
273
|
+
this.armKnockoutTimer(10000, player);
|
|
220
274
|
}
|
|
221
|
-
clearPlayerKnockout() {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
275
|
+
clearPlayerKnockout(player) {
|
|
276
|
+
const ko = this.playerKnockouts.get(player.name);
|
|
277
|
+
if (ko?.timer)
|
|
278
|
+
clearTimeout(ko.timer);
|
|
279
|
+
this.playerKnockouts.delete(player.name);
|
|
225
280
|
}
|
|
226
|
-
spurCompanionsForFallenMaster() {
|
|
227
|
-
const standing = this.standingHostiles().map(s => s.name).join(', ');
|
|
228
|
-
for (const ally of this.consciousAlliesInRoom()) {
|
|
229
|
-
void Promise.resolve(ally.hear(
|
|
281
|
+
spurCompanionsForFallenMaster(player) {
|
|
282
|
+
const standing = this.standingHostiles(player).map(s => s.name).join(', ');
|
|
283
|
+
for (const ally of this.consciousAlliesInRoom(player)) {
|
|
284
|
+
void Promise.resolve(ally.hear(player, `[System note: your master ${player.name} is DOWN -- out cold on the floor, still breathing. END the threat NOW${standing ? `: ${standing} still stand${standing.includes(',') ? '' : 's'}` : ''}. Attack with [COMMAND] attack <name> and do not stop until every hostile is down, surrendered, or gone. Do NOT surrender, do NOT wander off.]`, { viaLink: true })).catch(err => {
|
|
230
285
|
this.logger.error(`spurCompanionsForFallenMaster: ${ally.name} failed to hear: ${err}`);
|
|
231
286
|
});
|
|
232
287
|
}
|
|
@@ -234,35 +289,47 @@ export class Scene extends AbstractScene {
|
|
|
234
289
|
/** The room settled with the player's side upright: RAW natural
|
|
235
290
|
* recovery (p.207), abstracted to the game's job-scale clock the same
|
|
236
291
|
* way rest is -- unconsciousness itself counted as the rest. */
|
|
237
|
-
async recoverFromKnockout() {
|
|
238
|
-
const p =
|
|
239
|
-
this.clearPlayerKnockout();
|
|
292
|
+
async recoverFromKnockout(player) {
|
|
293
|
+
const p = player;
|
|
294
|
+
this.clearPlayerKnockout(p);
|
|
240
295
|
const pool = Math.max(1, p.body + p.willpower);
|
|
241
296
|
const roll = rollPool(pool);
|
|
242
297
|
const healed = p.healStun(Math.max(1, roll.hits));
|
|
243
298
|
if (p.isUnconscious()) {
|
|
244
299
|
// Locked fatigue holds the track full -- the body has nothing left.
|
|
245
|
-
this.streetDecidesKnockout([]);
|
|
300
|
+
this.streetDecidesKnockout([], p);
|
|
246
301
|
return;
|
|
247
302
|
}
|
|
248
303
|
this.logger.meta(`Recovery (Body ${p.body} + Willpower ${p.willpower}): ${formatRoll(roll)} -- ${healed} stun healed (unconsciousness counts as rest, p.207)`);
|
|
249
|
-
const allies = this.consciousAlliesInRoom();
|
|
304
|
+
const allies = this.consciousAlliesInRoom(p);
|
|
250
305
|
const watch = allies.length > 0 ? ` ${allies.map(a => a.name).join(' and ')} stand${allies.length === 1 ? 's' : ''} watch over you.` : '';
|
|
251
306
|
this.logger.logWithColor(`\nTime blurs past on the cold floor... then the world seeps back in. You come to -- the fight is over.${watch}`, 'green');
|
|
252
307
|
this.logger.logWithColor(`You're battered but breathing: ${healed} stun recovered. ${p.stunSummary()}`, 'yellow');
|
|
253
308
|
this.addWorldEvent(`${p.name} came to in ${p.currentLocation.name} after the fight.`);
|
|
254
309
|
this.updateStatus();
|
|
255
310
|
}
|
|
256
|
-
streetDecidesKnockout(standingNames) {
|
|
257
|
-
const p =
|
|
258
|
-
this.clearPlayerKnockout();
|
|
311
|
+
streetDecidesKnockout(standingNames, player) {
|
|
312
|
+
const p = player;
|
|
313
|
+
this.clearPlayerKnockout(p);
|
|
259
314
|
// DOCWAGON (SR5e p.450, Game.tryDocwagonRescue): a contract's
|
|
260
315
|
// wristband reads the flatline before the street gets its say --
|
|
261
|
-
// the HTR team extracts the sleeping runner instead.
|
|
262
|
-
|
|
316
|
+
// the HTR team extracts the sleeping runner instead. The contract is
|
|
317
|
+
// the LOCAL save's (primary player); a partied guest's own DocWagon
|
|
318
|
+
// rides their own session (M3 settles this server-side).
|
|
319
|
+
if (p === this.players[0] && this.game?.tryDocwagonRescue?.('htr', { standingHostiles: standingNames })) {
|
|
263
320
|
return;
|
|
264
321
|
}
|
|
265
322
|
const who = standingNames.length > 0 ? `with ${standingNames.join(', ')} still standing over you` : `and this time nobody is coming`;
|
|
323
|
+
this.markPlayerDead(p);
|
|
324
|
+
// The primary player's death ends the session (permadeath, the solo
|
|
325
|
+
// path). A non-primary human in a shared run dies for real too --
|
|
326
|
+
// their save burns via Game.onPlayerDeath -- but the run continues
|
|
327
|
+
// while any human still stands.
|
|
328
|
+
if (p !== this.players[0]) {
|
|
329
|
+
this.game?.onPlayerDeath?.(p);
|
|
330
|
+
this.logger.logWithColor(`💀 ${p.name} is out cold ${who} -- the street decides what happens to sleeping runners.`, 'red');
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
266
333
|
void this.endGame().then(() => {
|
|
267
334
|
this.logger.logWithColor(`💀 GAME OVER: ${p.name} is out cold ${who} -- the street decides what happens to sleeping runners.`, 'red');
|
|
268
335
|
this.logger.logWithColor(`Press ctrl-c to quit.`, 'red');
|
|
@@ -340,11 +407,17 @@ export class Scene extends AbstractScene {
|
|
|
340
407
|
// PLAIN SIGHT (see Room.plainSightItems): the player just watched it
|
|
341
408
|
// scatter, so "take all" must not demand a "search" to sweep it.
|
|
342
409
|
const dropped = [];
|
|
410
|
+
// Spilled gear lands where the BODY fell -- an item's stale atSpot
|
|
411
|
+
// from wherever it was last shelved sent a real session hunting a
|
|
412
|
+
// "counter" that didn't exist in the room (the note had ridden a
|
|
413
|
+
// crew member's pack from a different scene entirely).
|
|
414
|
+
const fallSpot = actor.atSpot;
|
|
343
415
|
if (room && actor.inventory) {
|
|
344
416
|
const items = actor.inventory.getAllItems();
|
|
345
417
|
for (const item of items) {
|
|
346
418
|
const didDrop = actor.inventory.transferItemTo(item.name, room.inventory);
|
|
347
419
|
if (didDrop) {
|
|
420
|
+
item.atSpot = fallSpot;
|
|
348
421
|
this.logger.write(`${actor.name} dropped ${item.name} into ${room.name} upon removal.`);
|
|
349
422
|
room.plainSightItems.add(item.name.toLowerCase());
|
|
350
423
|
dropped.push(item.name);
|
|
@@ -362,6 +435,7 @@ export class Scene extends AbstractScene {
|
|
|
362
435
|
continue;
|
|
363
436
|
for (const [slot, item] of Object.entries(group)) {
|
|
364
437
|
if (item) {
|
|
438
|
+
item.atSpot = fallSpot;
|
|
365
439
|
room.inventory.addItem(item);
|
|
366
440
|
group[slot] = null;
|
|
367
441
|
this.logger.write(`${actor.name} dropped equipped ${item.name} into ${room.name} upon removal.`);
|
|
@@ -509,7 +583,39 @@ export class Scene extends AbstractScene {
|
|
|
509
583
|
return this.getRoom(roomNames[0]);
|
|
510
584
|
}
|
|
511
585
|
getPlayer() {
|
|
512
|
-
return this.
|
|
586
|
+
return this.players[0];
|
|
587
|
+
}
|
|
588
|
+
getPlayers() {
|
|
589
|
+
return this.players;
|
|
590
|
+
}
|
|
591
|
+
getPlayerByName(name) {
|
|
592
|
+
const n = name.toLowerCase();
|
|
593
|
+
return this.players.find(p => p.name.toLowerCase() === n);
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* True when `actor` is one of the scene's HUMAN players (vs an NPC or
|
|
597
|
+
* companion shell). The multiplayer-aware replacement for the old
|
|
598
|
+
* `actor === scene.getPlayer()` identity checks -- use this wherever
|
|
599
|
+
* the question is "is this a human" rather than "is this the local
|
|
600
|
+
* terminal's player".
|
|
601
|
+
*/
|
|
602
|
+
isHumanControlled(actor) {
|
|
603
|
+
return this.players.includes(actor);
|
|
604
|
+
}
|
|
605
|
+
/** A second human joins the live scene (shared runs, late join):
|
|
606
|
+
* spawn them into `room` and register them like any actor. */
|
|
607
|
+
addPlayer(player, room) {
|
|
608
|
+
this.players.push(player);
|
|
609
|
+
player.currentLocation = room;
|
|
610
|
+
this.addActor(player);
|
|
611
|
+
room.addActor(player);
|
|
612
|
+
}
|
|
613
|
+
/** Human players who died in THIS scene. Solo play ends the game on
|
|
614
|
+
* the primary's death before this matters; shared runs continue
|
|
615
|
+
* while any human still stands. */
|
|
616
|
+
deadPlayers = new Set();
|
|
617
|
+
markPlayerDead(player) {
|
|
618
|
+
this.deadPlayers.add(player.name);
|
|
513
619
|
}
|
|
514
620
|
isCompleted() {
|
|
515
621
|
return this._completed;
|
|
@@ -559,52 +665,34 @@ export class Scene extends AbstractScene {
|
|
|
559
665
|
if (condition.item.toLowerCase() !== itemName.toLowerCase()) {
|
|
560
666
|
return null;
|
|
561
667
|
}
|
|
668
|
+
// 'possess' completes when ANY human player ends up holding the
|
|
669
|
+
// macguffin (symmetric MP: a partied runner's grab counts for the
|
|
670
|
+
// team); the reward lands in the hands that did the holding.
|
|
671
|
+
const possessorPlayer = this.getPlayerByName(possessorName);
|
|
562
672
|
const satisfied = condition.type === 'give'
|
|
563
673
|
? condition.toNpc?.toLowerCase() === possessorName.toLowerCase()
|
|
564
|
-
:
|
|
674
|
+
: possessorPlayer !== undefined;
|
|
565
675
|
if (!satisfied)
|
|
566
676
|
return null;
|
|
567
677
|
this._completed = true;
|
|
568
678
|
if (condition.rewardItem) {
|
|
569
|
-
this.player
|
|
679
|
+
const rewardee = possessorPlayer ?? this.player;
|
|
680
|
+
rewardee.addInventory(condition.rewardItem);
|
|
570
681
|
this.updateInventory(this.player.inventory);
|
|
571
682
|
}
|
|
572
|
-
//
|
|
573
|
-
//
|
|
574
|
-
//
|
|
575
|
-
//
|
|
576
|
-
//
|
|
683
|
+
// THE PAY (canon split, p.28-29): with a CLIENT in the scene, money
|
|
684
|
+
// moves at the MEET (advance) and the WRAP-UP (remainder) -- see
|
|
685
|
+
// Game.onClientInteraction -- so completion itself pays nothing
|
|
686
|
+
// here. Legacy/classic scenes (no client on the seed) keep the
|
|
687
|
+
// original instant payout.
|
|
577
688
|
let paymentNote = '';
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
size: Size.Tiny, category: Category.Credstick,
|
|
586
|
-
shape: 'Rectangle', color: 'Matte Black', texture: 'Smooth',
|
|
587
|
-
rating: Rating.Standard, weight: 0.05, transferable: true,
|
|
588
|
-
});
|
|
589
|
-
try {
|
|
590
|
-
this.player.addInventory(paystick);
|
|
591
|
-
}
|
|
592
|
-
catch {
|
|
593
|
-
paystick = undefined; // over the carry cap somehow -- log, don't crash the win
|
|
594
|
-
this.logger.error(`Could not hand the player their payment credstick (carry weight).`);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
if (paystick) {
|
|
598
|
-
// SINner (p.84-85): a certified payment to a SIN on file gets its
|
|
599
|
-
// cut skimmed before it ever lands -- the drain that makes the
|
|
600
|
-
// negative quality's karma refund an honest trade.
|
|
601
|
-
const taxRate = this.player.sinTaxRate;
|
|
602
|
-
const tax = Math.floor(condition.payout * taxRate);
|
|
603
|
-
const netPay = condition.payout - tax;
|
|
604
|
-
paystick.addCurrency(netPay);
|
|
605
|
-
this.updateInventory(this.player.inventory);
|
|
606
|
-
this.logger.write(`Job payout: ${netPay} nuyen credited to "${PAYSTICK_NAME}"${tax > 0 ? ` (${tax} withheld: SIN tax ${Math.round(taxRate * 100)}%)` : ''}.`);
|
|
607
|
-
paymentNote = `\nPayment received: ${netPay}¥, certified. It's on your stick.${tax > 0 ? `\nSIN on file: ${tax}¥ withheld at source (${Math.round(taxRate * 100)}% -- the system never forgets your number).` : ''}`;
|
|
689
|
+
const clientHandlesPay = !!this.game?.currentClient;
|
|
690
|
+
if (!clientHandlesPay && condition.payout && condition.payout > 0) {
|
|
691
|
+
// Legacy instant path still splits with the crew (canon team take).
|
|
692
|
+
const split = this.game?.applyCrewCut(condition.payout) ?? { share: condition.payout, line: '' };
|
|
693
|
+
const paid = this.payRunner(split.share, 'job payout');
|
|
694
|
+
if (paid) {
|
|
695
|
+
paymentNote = `\nPayment received: ${paid.net}¥, certified. It's on your stick.${split.line ? `\n${split.line}` : ''}${paid.tax > 0 ? `\nSIN on file: ${paid.tax}¥ withheld at source (${Math.round(this.player.sinTaxRate * 100)}% -- the system never forgets your number).` : ''}`;
|
|
608
696
|
}
|
|
609
697
|
}
|
|
610
698
|
this.addWorldEvent(`${possessorName} ended up with ${itemName} -- the job is done.`);
|
|
@@ -626,14 +714,59 @@ export class Scene extends AbstractScene {
|
|
|
626
714
|
const karmaNote = `\nKarma earned: ${karmaEarned} (${this.player.karma} banked${hint(` -- "advance" at your hideout to train`)}).${credNote}`;
|
|
627
715
|
// Hub sessions go HOME between jobs (walking into the hideout takes
|
|
628
716
|
// you back -- see go.ts); classic sessions get the next job from the
|
|
629
|
-
// fixer over comms. No system verbs in either path.
|
|
717
|
+
// fixer over comms. No system verbs in either path. With a client
|
|
718
|
+
// holding the purse, the banner points at the WRAP-UP instead of the
|
|
719
|
+
// curb -- the remainder is theirs to hand over (p.29: "deliver...
|
|
720
|
+
// collect the remainder of your pay").
|
|
721
|
+
const client = this.game?.currentClient;
|
|
722
|
+
const settleLine = clientHandlesPay && client && !client.reported
|
|
723
|
+
? `\nThe balance is with ${client.name} -- settle up before you skip town (talk to them, or call).`
|
|
724
|
+
: '';
|
|
630
725
|
const nextStep = this.game?.hasHub
|
|
631
726
|
? hint(`"call taxi" from the curb when you're ready to head home.`)
|
|
632
727
|
: hint(`Call your Johnson when you're ready for more work, or "quit" to stop here.`);
|
|
633
|
-
const banner = `${condition.completionMessage}${paymentNote}${karmaNote}\n\n=== JOB COMPLETE ===\n${nextStep}`;
|
|
728
|
+
const banner = `${condition.completionMessage}${paymentNote}${karmaNote}${settleLine}\n\n=== ${clientHandlesPay ? 'OBJECTIVE COMPLETE' : 'JOB COMPLETE'} ===\n${nextStep}`;
|
|
634
729
|
this.logger.logWithColor(banner, 'green');
|
|
635
730
|
return banner;
|
|
636
731
|
}
|
|
732
|
+
/**
|
|
733
|
+
* Money actually landing in the runner's hands: accumulates on ONE
|
|
734
|
+
* certified credstick across jobs (minting a same-named stick per job
|
|
735
|
+
* would silently overwrite in the name-keyed inventory), with the
|
|
736
|
+
* SINner tax (p.84-85) skimmed at source. Shared by the meet advance,
|
|
737
|
+
* the wrap-up remainder (Game.onClientInteraction), and the legacy
|
|
738
|
+
* instant payout above. Returns what moved, or undefined when the
|
|
739
|
+
* stick couldn't be handed over.
|
|
740
|
+
*/
|
|
741
|
+
payRunner(amount, label) {
|
|
742
|
+
if (!amount || amount <= 0)
|
|
743
|
+
return undefined;
|
|
744
|
+
const PAYSTICK_NAME = 'Certified Credstick (job payments)';
|
|
745
|
+
let paystick = this.player.inventory.getAllItems().find(i => i.name === PAYSTICK_NAME);
|
|
746
|
+
if (!paystick) {
|
|
747
|
+
paystick = new Item({
|
|
748
|
+
name: PAYSTICK_NAME,
|
|
749
|
+
description: 'A matte-black certified credstick. Every job that pays out lands here, clean.',
|
|
750
|
+
size: Size.Tiny, category: Category.Credstick,
|
|
751
|
+
shape: 'Rectangle', color: 'Matte Black', texture: 'Smooth',
|
|
752
|
+
rating: Rating.Standard, weight: 0.05, transferable: true,
|
|
753
|
+
});
|
|
754
|
+
try {
|
|
755
|
+
this.player.addInventory(paystick);
|
|
756
|
+
}
|
|
757
|
+
catch {
|
|
758
|
+
this.logger.error(`Could not hand the player their payment credstick (carry weight).`);
|
|
759
|
+
return undefined;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
const taxRate = this.player.sinTaxRate;
|
|
763
|
+
const tax = Math.floor(amount * taxRate);
|
|
764
|
+
const net = amount - tax;
|
|
765
|
+
paystick.addCurrency(net);
|
|
766
|
+
this.updateInventory(this.player.inventory);
|
|
767
|
+
this.logger.write(`${label}: ${net} nuyen credited to "${PAYSTICK_NAME}"${tax > 0 ? ` (${tax} withheld: SIN tax ${Math.round(taxRate * 100)}%)` : ''}.`);
|
|
768
|
+
return { net, tax };
|
|
769
|
+
}
|
|
637
770
|
addWorldEvent(event) {
|
|
638
771
|
this._worldEvents.push(event);
|
|
639
772
|
if (this._worldEvents.length > this._maxWorldEvents) {
|
|
@@ -682,7 +815,7 @@ export class Scene extends AbstractScene {
|
|
|
682
815
|
return Object.values(this.rooms).some(room => room.inventory.getAllItems().some(item => item.satisfiesLifestyle?.addictionTypes?.includes(addiction)));
|
|
683
816
|
}
|
|
684
817
|
validateLifestyleSupport() {
|
|
685
|
-
const allActors = [this.
|
|
818
|
+
const allActors = [...this.players, ...this.allActors];
|
|
686
819
|
for (const actor of allActors) {
|
|
687
820
|
const lifestyle = actor.lifestyleChoices ?? [];
|
|
688
821
|
const name = actor.name;
|