@maka/maka-cli 5.376.0 → 5.378.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/archetypes.js +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/disable.js +16 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +16 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/overwatch.js +3 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +15 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/treat.js +5 -3
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +18 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.js +26 -6
- package/bundle/typescript/src/commands/game/sideQuest/utilities/overwatch.js +11 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/street-doc.js +23 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.378.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.",
|
|
@@ -828,9 +828,19 @@ export function outfitArchetype(player, archetype) {
|
|
|
828
828
|
* as a manual equip would.
|
|
829
829
|
*/
|
|
830
830
|
export function equipIntoEmptySlots(player) {
|
|
831
|
+
// A DECK IS ALREADY THE COMMLINK (RsnjkLpgLcSmsrzKa, Maka: "I have a
|
|
832
|
+
// cyberdeck, not a commlink, yet the commlink section still shows").
|
|
833
|
+
// A deck "performs all commlink functions" (p.223) -- calls ride it
|
|
834
|
+
// (call.ts liveLink) -- so a commlink looted mid-run and left in the
|
|
835
|
+
// bag got WORN on the next boot with no word said, and the sheet then
|
|
836
|
+
// honestly showed gear the runner never chose to put on. Only a runner
|
|
837
|
+
// with no working deck or RCC gets one slotted for them.
|
|
838
|
+
const coveredLink = !!player.getCyberdeck() || !!player.getRcc();
|
|
831
839
|
for (const item of [...player.inventory.getAllItems()]) {
|
|
832
840
|
if (!EQUIP_ON_START.has(item.category))
|
|
833
841
|
continue;
|
|
842
|
+
if (item.category === Category.Commlink && coveredLink)
|
|
843
|
+
continue;
|
|
834
844
|
const slotInfo = player.determineEquipmentSlot(item.category);
|
|
835
845
|
if (!slotInfo)
|
|
836
846
|
continue;
|
|
@@ -429,17 +429,27 @@ export class DisableCommand extends Command {
|
|
|
429
429
|
}
|
|
430
430
|
else {
|
|
431
431
|
// A gun is a Rating 2 device with a monitor of 9 (p.228, the
|
|
432
|
-
// book's own smartgun example)
|
|
433
|
-
//
|
|
434
|
-
|
|
435
|
-
|
|
432
|
+
// book's own smartgun example).
|
|
433
|
+
//
|
|
434
|
+
// THE BOXES STAY (AD8TzWT89yFGDHMAE, Maka: "the gun I bricked
|
|
435
|
+
// never actually gets bricked"). Each spike used to be judged
|
|
436
|
+
// alone -- 9 in one hit or nothing -- so the Ancient's Browning
|
|
437
|
+
// ate 7, 6, 6 and 8 boxes and was still "one more spike from
|
|
438
|
+
// bricked" after all four. A condition monitor FILLS (p.228), the
|
|
439
|
+
// same as a camera's or a deck's; it goes on the gun's own track,
|
|
440
|
+
// and a reboot unlocks the smartlink but mends none of it.
|
|
441
|
+
const monitor = 8 + Math.ceil(this.deviceRatingOf(t) / 2);
|
|
442
|
+
t.gun.takeDeckDamage(dealt);
|
|
443
|
+
const filled = Math.min(monitor, t.gun.deckDamage);
|
|
444
|
+
if (filled >= monitor) {
|
|
436
445
|
t.gun.bricked = true;
|
|
437
|
-
lines.push(`The spike lands on ${t.owner.name}'s ${t.gun.name} -- it sparks, crackles and smokes in their hand. BRICKED: it will not fire again until somebody's bench work.`);
|
|
446
|
+
lines.push(`The spike lands on ${t.owner.name}'s ${t.gun.name} -- ${dealt} box${dealt === 1 ? '' : 'es'} (${filled}/${monitor}): it sparks, crackles and smokes in their hand. BRICKED: it will not fire again until somebody's bench work.`);
|
|
438
447
|
t.owner.performAction('suffers a PAN crack', `their ${t.gun.name} smoking in their hand`);
|
|
439
448
|
}
|
|
440
449
|
else {
|
|
441
450
|
t.gun.jammed = true;
|
|
442
|
-
|
|
451
|
+
const left = monitor - filled;
|
|
452
|
+
lines.push(`The spike lands on ${t.owner.name}'s ${t.gun.name} -- ${dealt} box${dealt === 1 ? '' : 'es'} (${filled}/${monitor}): the smartlink seizes and LOCKS. A reboot unlocks it; ${left} more box${left === 1 ? '' : 'es'} bricks it.`);
|
|
443
453
|
t.owner.performAction('suffers a PAN crack', `their ${t.gun.name} locking up in their hand`);
|
|
444
454
|
}
|
|
445
455
|
this.scene.addWorldEvent(`${actor.name} spiked ${t.owner.name}'s ${t.gun.name}.`);
|
|
@@ -1103,8 +1103,22 @@ export class HackCommand extends BypassCommand {
|
|
|
1103
1103
|
const now = Math.min(MAX_MARKS, heldMarks + 1);
|
|
1104
1104
|
target.panMarksBy.set(actor.name, now);
|
|
1105
1105
|
actor.noteConditionChanged();
|
|
1106
|
-
|
|
1107
|
-
|
|
1106
|
+
// THE SECOND KEY SAYS SO (WbCPspahquHnXX3wF, Maka: "this is
|
|
1107
|
+
// confusing"). The line that bought mark 2 used to read the same as
|
|
1108
|
+
// the one that bought mark 1 -- "that is all it is yet ... (2/2 to
|
|
1109
|
+
// command it)" and "hack again" -- so a player holding everything
|
|
1110
|
+
// Control Device asks for was told they were still short. Same
|
|
1111
|
+
// wording as the device hack's (wan-device-hack.test.ts).
|
|
1112
|
+
if (now >= MARKS_TO_COMMAND_A_PAN) {
|
|
1113
|
+
const held = target.equipment.rightHand?.weapon ?? target.equipment.leftHand?.weapon;
|
|
1114
|
+
lines.push(held?.isFirearm()
|
|
1115
|
+
? ` IN -- a second key settles on ${target.name}'s PAN (${now}/${MARKS_TO_COMMAND_A_PAN} marks). That is enough to command it: "disable ${target.name}" locks their ${held.name} (Control Device), and "brick ${target.name}" spikes it at +${2 * now} DV for the marks.`
|
|
1116
|
+
: ` IN -- a second key settles on ${target.name}'s PAN (${now}/${MARKS_TO_COMMAND_A_PAN} marks). That is enough to command it.`);
|
|
1117
|
+
}
|
|
1118
|
+
else {
|
|
1119
|
+
lines.push(` IN -- and that is all it is yet. You have a MARK on ${target.name}'s PAN (${now} of the ${MARKS_TO_COMMAND_A_PAN} it takes to command it), not a hand on the controls.`);
|
|
1120
|
+
lines.push(` A mark is the key; making their gear DO something is the next action, and it wants ${MARKS_TO_COMMAND_A_PAN}.${hint(` ("hack ${target.name}" again, or "mark ${target.name}" -- both place keys.)`)}`);
|
|
1121
|
+
}
|
|
1108
1122
|
target.performAction('is marked', 'an unfamiliar key settling on their PAN');
|
|
1109
1123
|
lines.push(...godLines);
|
|
1110
1124
|
this.scene.updateStatus();
|
|
@@ -24,7 +24,9 @@ export class OverwatchCommand extends Command {
|
|
|
24
24
|
if (actor.plane !== 'matrix' && !actor.isTechnomancer() && !deck) {
|
|
25
25
|
return `Checking your overwatch takes a live persona -- jack in, or put a working deck in hand.`;
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
// No file means no illegal action since the last boot -- not a score
|
|
28
|
+
// of 0, which a defender who rolled nothing leaves on a running tally.
|
|
29
|
+
if (actor.overwatchLastTick === 0) {
|
|
28
30
|
return `You listen for the grid's counting... nothing. Fresh boot, clean slate -- GOD has no file on this persona yet.`;
|
|
29
31
|
}
|
|
30
32
|
const skill = actor.skillRating('electronic-warfare');
|
|
@@ -31,7 +31,7 @@ export class RebootCommand extends Command {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
const hadArDown = actor.arOffline;
|
|
34
|
-
const hadOverwatch = actor.
|
|
34
|
+
const hadOverwatch = actor.overwatchLastTick > 0;
|
|
35
35
|
const marksOnMe = marksOnYou(this.scene, actor).length;
|
|
36
36
|
if (jammedGear.length === 0 && !hadArDown && !hadOverwatch && marksOnMe === 0) {
|
|
37
37
|
return `Your PAN is clean -- nothing needs a reboot.`;
|
|
@@ -72,7 +72,12 @@ export class RestCommand extends Command {
|
|
|
72
72
|
// like a deck does. This filtered on isCyberdeck(), so the link
|
|
73
73
|
// panel's "BRICKED (rest to repair)" sent that rider to bed and the
|
|
74
74
|
// link stayed bricked with no way out. The bench takes the whole bag.
|
|
75
|
-
|
|
75
|
+
// A spiked gun carries boxes on the same track (disable.ts), and the
|
|
76
|
+
// one in your hand is not always in the bag.
|
|
77
|
+
const damagedDevices = [...new Set([
|
|
78
|
+
...actor.inventory.getAllItems(),
|
|
79
|
+
actor.equipment.rightHand?.weapon, actor.equipment.leftHand?.weapon,
|
|
80
|
+
])].filter((i) => !!i && i.deckDamage > 0 && !i.permanentlyBricked);
|
|
76
81
|
// Any implant with a track of its own (every piece of cyberware --
|
|
77
82
|
// a housed deck is in the inventory above). p.228 bench work mends
|
|
78
83
|
// it the same way; the datajack end of a toolkit reaches it.
|
|
@@ -253,7 +258,7 @@ export class RestCommand extends Command {
|
|
|
253
258
|
if (cannotBench)
|
|
254
259
|
lines.push(` The bench waits: ${cannotBench}`);
|
|
255
260
|
for (const deck of cannotBench ? [] : damagedDevices) {
|
|
256
|
-
const wasBricked = deck.isBricked;
|
|
261
|
+
const wasBricked = deck.isBricked || deck.bricked;
|
|
257
262
|
const bench = benchRoll(actor);
|
|
258
263
|
lines.push(` Bench work on the ${deck.name}: ${formatRoll(bench.roll)}`);
|
|
259
264
|
if (bench.permanent) {
|
|
@@ -264,14 +269,18 @@ export class RestCommand extends Command {
|
|
|
264
269
|
continue;
|
|
265
270
|
}
|
|
266
271
|
const repaired = deck.repairDeck(bench.boxes);
|
|
272
|
+
// A spiked gun bricks at its own monitor (disable.ts) and carries
|
|
273
|
+
// the flag attack.ts reads; one box off a full track works again.
|
|
274
|
+
if (repaired > 0 && deck.bricked)
|
|
275
|
+
deck.bricked = false;
|
|
267
276
|
if (repaired > 0)
|
|
268
277
|
actor.noteConditionChanged(); // the deck track on the live sheet moves with the bench
|
|
269
278
|
this.logger.write(`RestCommand: ${actor.name} repaired ${repaired} boxes on ${deck.name} (${deck.deckDamage}/${deck.maxDeckBoxes}).`);
|
|
270
|
-
lines.push(` ${repaired} box${repaired === 1 ? '' : 'es'} repaired: ${deck.deckSummary()}${wasBricked && !deck.isBricked ? ' -- it boots again.' : ''}`);
|
|
279
|
+
lines.push(` ${repaired} box${repaired === 1 ? '' : 'es'} repaired: ${deck.deckSummary()}${wasBricked && !deck.isBricked && !deck.bricked ? ' -- it boots again.' : ''}`);
|
|
271
280
|
}
|
|
272
|
-
//
|
|
273
|
-
// a
|
|
274
|
-
//
|
|
281
|
+
// FLAG-ONLY BRICKS (a gun saved before spikes kept a count): one box
|
|
282
|
+
// off a full track is a device that works again -- so one hit brings
|
|
283
|
+
// it back, none leaves it dead.
|
|
275
284
|
for (const gear of cannotBench ? [] : brickedGear) {
|
|
276
285
|
const bench = benchRoll(actor);
|
|
277
286
|
lines.push(` Bench work on the ${gear.name}: ${formatRoll(bench.roll)}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from './command.js';
|
|
2
2
|
import { sameSpot, spotOf, spotRefusal } from '../utilities/spots.js';
|
|
3
|
-
import { TREAT_PRICE_PER_BOX, applyMedicineCare, isClinicRoom, isStreetDoc } from '../utilities/street-doc.js';
|
|
3
|
+
import { TREAT_PRICE_PER_BOX, applyMedicineCare, isClinicRoom, isStreetDoc, woundWords, DOCTOR_TALK } from '../utilities/street-doc.js';
|
|
4
4
|
/**
|
|
5
5
|
* Street-doc services (SR5e p.42, "Staying Healthy"): paid physical
|
|
6
6
|
* healing at a clinic -- the certain, expensive alternative to burning
|
|
@@ -58,7 +58,7 @@ export class TreatCommand extends Command {
|
|
|
58
58
|
this.logger.write(`TreatCommand: ${doc.name} healed ${healed} boxes for ${cost} (${actor.currency} left; ${actor.damageTaken}/${actor.maxConditionBoxes}).`);
|
|
59
59
|
this.scene.addWorldEvent(`${actor.name} paid for treatment at ${room.name}.`);
|
|
60
60
|
this.scene.updateStatus();
|
|
61
|
-
void Promise.resolve(doc.hear(actor, `Patch me up. [System note: you just treated ${actor.name} for ${cost} nuyen
|
|
61
|
+
void Promise.resolve(doc.hear(actor, `Patch me up. [System note: you just treated ${actor.name} for ${cost} nuyen. They are now ${woundWords(actor.damageTaken, actor.maxConditionBoxes)}${actor.damageTaken > 0 ? ' (they ran out of cred for the rest)' : ''}. React briefly, in character. ${DOCTOR_TALK}]`)).catch(err => {
|
|
62
62
|
this.logger.error(`${doc.name} failed to hear the treatment: ${err}`);
|
|
63
63
|
});
|
|
64
64
|
const lines = [
|
|
@@ -69,7 +69,9 @@ export class TreatCommand extends Command {
|
|
|
69
69
|
` The doc doesn't ask for a name -- cash is its own paperwork down here.`,
|
|
70
70
|
];
|
|
71
71
|
if (actor.damageTaken > 0) {
|
|
72
|
-
|
|
72
|
+
// The count is on the receipt line above; the doc's own words carry
|
|
73
|
+
// none (7G2avXprhjrawdc7L -- a doc does not speak in boxes).
|
|
74
|
+
lines.push(` "I'll close the rest when you can afford it. Try not to reopen my work."`);
|
|
73
75
|
// MEDICINE'S AFTERCARE (p.207-208; XpW98XthhN4biT4gM): the doc's
|
|
74
76
|
// one Medicine + Logic test on this set of wounds rides the boxes
|
|
75
77
|
// the patient could not pay for as bonus dice on their recovery
|
|
@@ -3218,5 +3218,22 @@
|
|
|
3218
3218
|
// does not have, and threw -- so every phase of a street gang closing
|
|
3219
3219
|
// on a runner failed and the gang stood still (prod X82wWvdwR3pPjZyR3).
|
|
3220
3220
|
// - A failed NPC phase logs its stack, not just the message.
|
|
3221
|
-
|
|
3221
|
+
// 1.189.0 (2026-10-03): THE SPIKE THAT STAYS, THE SECOND KEY, GOD'S BILL.
|
|
3222
|
+
// - A Data Spike's boxes on a gun ACCUMULATE on its Matrix monitor
|
|
3223
|
+
// (p.228); each was judged alone against 9, so a gun ate 27 boxes and
|
|
3224
|
+
// never bricked (AD8TzWT89yFGDHMAE). The bench mends a gun's boxes.
|
|
3225
|
+
// - The hack that places a PAN's second mark says it is enough and names
|
|
3226
|
+
// "disable" / "brick", instead of "that is all it is yet" (WbCPspahquHnXX3wF).
|
|
3227
|
+
// - Overwatch adds the defender's hits with no floor of 1 (p.231-232);
|
|
3228
|
+
// a 0-hit defence starts the tally but adds nothing (XR9xDyyxygCGzTLCz).
|
|
3229
|
+
// - A runner carrying a working deck or RCC no longer has a looted
|
|
3230
|
+
// commlink worn for them at session load (p.223, RsnjkLpgLcSmsrzKa).
|
|
3231
|
+
// 1.190.0 (2026-10-04): VOICES, NOT TEMPLATES.
|
|
3232
|
+
// - The arc's Johnson picks up in their own AI voice, told the gist the
|
|
3233
|
+
// fixed quote used to print; the quote is the AI-off fallback
|
|
3234
|
+
// (mYocnosxLvj24CuWt: "this should be Mr. Halyard speaking").
|
|
3235
|
+
// - The street doc is told how the patient is in a body's words and is
|
|
3236
|
+
// barred from "boxes" and other screen terms, in the treat note and in
|
|
3237
|
+
// the brief (7G2avXprhjrawdc7L: "three boxes left ... breaks the fiction").
|
|
3238
|
+
export const ENGINE_VERSION = '1.190.0';
|
|
3222
3239
|
//# sourceMappingURL=engine-version.js.map
|
|
@@ -4721,11 +4721,11 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
4721
4721
|
const knows = `"${pending.name}" -- ${pending.story} ${meetRoom ? `The meet is at ${meetRoom}; name that place and no other.` : 'You will find the runner when they arrive; never name a venue.'}`;
|
|
4722
4722
|
if (this.pendingRunAccepted) {
|
|
4723
4723
|
this.openJohnsonLine(`${knows} The runner has ALREADY ACCEPTED; the deal stands and the cab is on your account.`);
|
|
4724
|
-
return
|
|
4724
|
+
return this.johnsonPicksUp(`We've shaken on it. The cab's on my account -- get moving.`, ` ("call taxi" when you're ready -- "end call" hangs up.)`);
|
|
4725
4725
|
}
|
|
4726
|
-
const where = meetRoom ? `
|
|
4726
|
+
const where = meetRoom ? ` Meet me at ${meetRoom}.` : ` I'll find you when you arrive.`;
|
|
4727
4727
|
this.openJohnsonLine(`${knows} The runner has NOT accepted yet: they say they accept, or they pass -- and passing ends your operation.`);
|
|
4728
|
-
return `${
|
|
4728
|
+
return this.johnsonPicksUp(`${pending.story}${where} Same as last time -- the number we settle face to face.`, `\n(You're on the line with ${j}: "accept" locks it in, "decline" passes -- and passing ends the operation.)`);
|
|
4729
4729
|
}
|
|
4730
4730
|
// THE CHAPTER WAITS IN THE DRAWER (it landed while another job held
|
|
4731
4731
|
// the table): calling the Johnson brings it onto the table and they
|
|
@@ -4735,7 +4735,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
4735
4735
|
const refused = this.takeFromDrawer(waiting);
|
|
4736
4736
|
if (refused) {
|
|
4737
4737
|
this.openJohnsonLine(`Your next chapter is ready, but the runner is committed to another job tonight. Yours keeps until they are back.`);
|
|
4738
|
-
return
|
|
4738
|
+
return this.johnsonPicksUp(`You're committed elsewhere tonight. Mine keeps -- call me when you're back.`, ` ("end call" hangs up.)`);
|
|
4739
4739
|
}
|
|
4740
4740
|
return this.callOperationJohnson(said);
|
|
4741
4741
|
}
|
|
@@ -4743,7 +4743,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
4743
4743
|
if (!gate.allowed) {
|
|
4744
4744
|
const busy = gate.message ?? 'Working on it.';
|
|
4745
4745
|
this.openJohnsonLine(`You have nothing new for the runner right now: "${busy}"`);
|
|
4746
|
-
return
|
|
4746
|
+
return this.johnsonPicksUp(busy, ` ("end call" hangs up.)`);
|
|
4747
4747
|
}
|
|
4748
4748
|
const beat = op.beats[op.nextBeat];
|
|
4749
4749
|
// THE SIZE IS STILL THE PLAYER'S (job-order.ts): "call Veil, something
|
|
@@ -4752,7 +4752,27 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
4752
4752
|
// client does not deal in milk runs. The SHAPE is the beat's.
|
|
4753
4753
|
this.draftNextSceneInBackground({ size: parseJobSize(said) ?? 'large', runType: beat.kind, chapter: true });
|
|
4754
4754
|
this.openJohnsonLine(`Chapter ${op.nextBeat + 1} of "${op.title}": ${beat.brief} You are putting the details together and will message the runner the where and when shortly.`);
|
|
4755
|
-
return
|
|
4755
|
+
return this.johnsonPicksUp(`Chapter ${op.nextBeat + 1}: ${beat.brief} Give me a minute -- I'll send the where and when.`, ` ("end call" hangs up -- the job comes as a message.)`);
|
|
4756
|
+
}
|
|
4757
|
+
/**
|
|
4758
|
+
* THE JOHNSON SPEAKS FOR THEMSELVES (mYocnosxLvj24CuWt, Maka: "I'm still
|
|
4759
|
+
* getting canned text, this should be Mr. Halyard speaking"). 1.180.0
|
|
4760
|
+
* opened a real line, but the pickup was still a fixed quote. Krow's
|
|
4761
|
+
* business call already works this way (fixerForBusiness, call.ts): with
|
|
4762
|
+
* AI on, the voice picks up off a note and says the GIST in its own words;
|
|
4763
|
+
* the fixed quote is only the AI-off fallback. The gist is the same
|
|
4764
|
+
* sentence the quote was, and the hint (the verbs) stays out of fiction.
|
|
4765
|
+
*/
|
|
4766
|
+
johnsonPicksUp(gist, hintText) {
|
|
4767
|
+
const voice = this._johnsonVoice;
|
|
4768
|
+
const me = this.player;
|
|
4769
|
+
const j = voice?.name ?? this._operation?.johnson.name ?? 'The client';
|
|
4770
|
+
if (this.aiOn && voice && me && me.activeCallPartner === voice) {
|
|
4771
|
+
// hear() never rethrows (models/npc.ts), so fire-and-forget is safe.
|
|
4772
|
+
void voice.hear(me, `[System note: ${me.name} is calling you and you have picked up. Tell them this in your own voice and words, and nothing more: "${gist}" Add no new facts, places or numbers.]`, { viaLink: true });
|
|
4773
|
+
return `${j} picks up.${hint(hintText)}`;
|
|
4774
|
+
}
|
|
4775
|
+
return `${j} picks up. "${gist}"${hint(hintText)}`;
|
|
4756
4776
|
}
|
|
4757
4777
|
/**
|
|
4758
4778
|
* The cab that answers "call taxi" varies per job (player request: not
|
|
@@ -30,7 +30,8 @@ function d6() {
|
|
|
30
30
|
}
|
|
31
31
|
/** The canon time bill (+2d6 per 15 min while tallying), settled lazily. */
|
|
32
32
|
function billTime(actor) {
|
|
33
|
-
|
|
33
|
+
// The tally runs from the first illegal action, even one that scored 0.
|
|
34
|
+
if (actor.overwatchLastTick === 0)
|
|
34
35
|
return;
|
|
35
36
|
const ticks = Math.min(MAX_BACKLOG_TICKS, Math.floor((Date.now() - actor.overwatchLastTick) / TICK_MS));
|
|
36
37
|
if (ticks <= 0)
|
|
@@ -60,9 +61,16 @@ export function accrueOverwatch(scene, actor, defenseHits, why) {
|
|
|
60
61
|
if (actor.overwatchLastTick === 0)
|
|
61
62
|
actor.overwatchLastTick = Date.now();
|
|
62
63
|
billTime(actor);
|
|
64
|
+
// NO FLOOR (XR9xDyyxygCGzTLCz): "your OS increases by the number of
|
|
65
|
+
// hits the target gets on its defense test" (p.231-232) -- a defender
|
|
66
|
+
// who rolls nothing adds nothing. This was max(1, hits), and two 0-hit
|
|
67
|
+
// Data Spikes' worth of it was the difference between 39 and a
|
|
68
|
+
// convergence. The action still STARTS the tally (the clock above):
|
|
69
|
+
// "the moment you perform an illegal action, you get an OS".
|
|
63
70
|
const before = actor.overwatchScore;
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
const added = Math.max(0, defenseHits);
|
|
72
|
+
actor.overwatchScore += added;
|
|
73
|
+
logger.write(`Overwatch: ${actor.name} +${added} (${why}) -> OS ${actor.overwatchScore}.`);
|
|
66
74
|
if (actor.overwatchScore >= CONVERGENCE_AT) {
|
|
67
75
|
return { lines: converge(scene, actor), converged: true };
|
|
68
76
|
}
|
|
@@ -44,7 +44,29 @@ export function treatHint(price = TREAT_PRICE_PER_BOX) {
|
|
|
44
44
|
/** The doc's standing orders in the AI brief: treatment is the trade,
|
|
45
45
|
* and the word for it is "treat". */
|
|
46
46
|
export function streetDocBrief(price = TREAT_PRICE_PER_BOX) {
|
|
47
|
-
return `You are this clinic's STREET DOC and treatment is your trade: you close physical wounds
|
|
47
|
+
return `You are this clinic's STREET DOC and treatment is your trade: you close physical wounds, ${price} nuyen for each one you close, cash up front, no names asked. The player makes it happen by typing the command "treat" -- when anyone asks for help with wounds, tell them that word plainly and quote the price. Never say you cannot help a paying patient and never leave them with a diagnosis alone; if they are broke, name the price and mean it. Stun is not your department -- sleep is free. ${DOCTOR_TALK}`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A DOCTOR TALKS ABOUT BODIES, NOT BOOKKEEPING (7G2avXprhjrawdc7L, Maka:
|
|
51
|
+
* "You've got three boxes left ... this breaks the fiction"). The brief
|
|
52
|
+
* priced treatment "a box" and treat.ts told the doc how many "boxes
|
|
53
|
+
* closed", and the model said it back. Boxes are the player's screen;
|
|
54
|
+
* the doc sees cuts, bruising, broken ribs. Every note to the doc ends
|
|
55
|
+
* with this, and woundWords() is the fiction for how bad it still is.
|
|
56
|
+
*/
|
|
57
|
+
export const DOCTOR_TALK = `Speak as a doctor about a body -- cuts, bleeding, bruising, ribs, stitches, rest. NEVER say "box", "boxes", "condition monitor", "damage", "dice" or any count of them; those are the player's screen, not words a person uses.`;
|
|
58
|
+
/** How bad the patient still is, in words a doctor would see. */
|
|
59
|
+
export function woundWords(taken, max) {
|
|
60
|
+
if (taken <= 0)
|
|
61
|
+
return 'patched up completely -- nothing left open';
|
|
62
|
+
const share = max > 0 ? taken / max : 1;
|
|
63
|
+
if (share <= 0.25)
|
|
64
|
+
return 'mostly mended -- a few shallow cuts and bruising left';
|
|
65
|
+
if (share <= 0.5)
|
|
66
|
+
return 'still carrying real wounds -- sore, stiff, nothing reopening if they take it easy';
|
|
67
|
+
if (share <= 0.75)
|
|
68
|
+
return 'still badly hurt -- deep wounds you could not get to';
|
|
69
|
+
return 'barely holding together -- most of the damage is still open';
|
|
48
70
|
}
|
|
49
71
|
/**
|
|
50
72
|
* Medicine + Logic, once per set of wounds (p.208): the hits ride on the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.378.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.",
|