@maka/maka-cli 5.151.0 → 5.153.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/typescript/package.json +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +15 -8
- package/bundle/typescript/src/commands/game/sideQuest/game.js +27 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +31 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +14 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/ar.js +11 -4
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +9 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.153.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.",
|
|
@@ -211,12 +211,15 @@ export class AttackCommand extends Command {
|
|
|
211
211
|
}
|
|
212
212
|
// A deliberately INITIATED attack with a dry gun is blocked with a
|
|
213
213
|
// reload prompt -- the involuntary strikes (retaliation) auto-fall-back
|
|
214
|
-
// to fists instead (see CombatExchange.weaponProfile).
|
|
214
|
+
// to fists instead (see CombatExchange.weaponProfile). Only a DRAWN
|
|
215
|
+
// gun is actually "in play" for this swing (2jbprbSYqF7EPXrkR) -- a
|
|
216
|
+
// holstered dry/jammed piece is not what's being attacked with, so it
|
|
217
|
+
// must not block a bare-fisted attack the player never asked to fail.
|
|
215
218
|
const weaponItem = this.actor.equipment.rightHand?.weapon ?? this.actor.equipment.leftHand?.weapon;
|
|
216
|
-
if (this.usesEquippedWeapon() && weaponItem?.isFirearm() && weaponItem.jammed) {
|
|
219
|
+
if (this.usesEquippedWeapon() && this.actor.weaponDrawn && weaponItem?.isFirearm() && weaponItem.jammed) {
|
|
217
220
|
return `The ${weaponItem.name}'s smartlink is LOCKED -- someone cracked your PAN. "reboot" to clear it, or go in swinging.`;
|
|
218
221
|
}
|
|
219
|
-
if (this.usesEquippedWeapon() && weaponItem?.isFirearm() && weaponItem.ammo <= 0) {
|
|
222
|
+
if (this.usesEquippedWeapon() && this.actor.weaponDrawn && weaponItem?.isFirearm() && weaponItem.ammo <= 0) {
|
|
220
223
|
return `Click -- the ${weaponItem.name} is dry. "reload" (needs ammunition), or go in swinging with something else.`;
|
|
221
224
|
}
|
|
222
225
|
// THE COMBAT TURN (utilities/combat-turn.ts): on the physical planes
|
|
@@ -360,11 +363,15 @@ export class AttackCommand extends Command {
|
|
|
360
363
|
return notYourPhase(enc, actor);
|
|
361
364
|
}
|
|
362
365
|
enc.noteAttack(actor, target);
|
|
363
|
-
//
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
366
|
+
// A holstered weapon doesn't back this attack -- SR5 always allows a
|
|
367
|
+
// bare-handed strike, and weaponProfile() already resolves the punch's
|
|
368
|
+
// own pool/damage the moment the weapon isn't drawn (the character
|
|
369
|
+
// sheet advertises it: "Attack ... (bare fists) -- holstered"). Only a
|
|
370
|
+
// DRAWN weapon is what this swing is actually attacking WITH; want to
|
|
371
|
+
// shoot or swing the blade instead? READY WEAPON (p.165) is its own
|
|
372
|
+
// Simple Action -- "draw" first (2jbprbSYqF7EPXrkR).
|
|
373
|
+
const activeWeapon = this.usesEquippedWeapon() && actor.weaponDrawn ? weaponItem : undefined;
|
|
374
|
+
const isMelee = !activeWeapon || !activeWeapon.isFirearm();
|
|
368
375
|
const report = [];
|
|
369
376
|
// MELEE REACH: ground costs (p.161-162).
|
|
370
377
|
if (isMelee) {
|
|
@@ -552,6 +552,33 @@ export default class Game {
|
|
|
552
552
|
return `{yellow-fg}🔥 Heat: ears up all over the block{/yellow-fg}`;
|
|
553
553
|
return `{yellow-fg}🔥 Heat: somebody heard something{/yellow-fg}`;
|
|
554
554
|
}
|
|
555
|
+
/**
|
|
556
|
+
* THE SAME BAND, FOR AN NPC'S MOUTH (YE9G3Q5P85y5xcP7n: "Mr. Ibis said
|
|
557
|
+
* 'Quiet as promised' however, there's a status of: 🔥 Heat: sirens
|
|
558
|
+
* circling closer"). heatBand() paints the player's own HUD; nothing
|
|
559
|
+
* fed the run's actual heat into NPC.respondTo's prompt at all, so an
|
|
560
|
+
* NPC narrating "the coast is clear" had no way to know it wasn't --
|
|
561
|
+
* the same shape as payoutSummary's fix for an invented payout, here
|
|
562
|
+
* for an invented all-clear.
|
|
563
|
+
*
|
|
564
|
+
* DELIBERATELY NOT ITS OWN THRESHOLDS. heat-band-honesty.test.ts pins
|
|
565
|
+
* the top band's heat check to the ONE line inside heatBand(), on
|
|
566
|
+
* purpose: the HUD's promise and checkpointOnRideHome's verifierDice
|
|
567
|
+
* escalation share that number so they cannot drift apart. A second
|
|
568
|
+
* copy of the same comparison here for the AI prompt would be exactly
|
|
569
|
+
* the drift that guard exists to catch. So this reads heatBand()'s OWN
|
|
570
|
+
* text -- strip the color codes and the HUD's "🔥 Heat: " lede, and
|
|
571
|
+
* what is left is plain enough for a prompt -- rather than re-deriving
|
|
572
|
+
* the bands.
|
|
573
|
+
*/
|
|
574
|
+
heatSummaryForDialogue() {
|
|
575
|
+
if (!this.onRun)
|
|
576
|
+
return undefined;
|
|
577
|
+
const band = this.heatBand();
|
|
578
|
+
if (!band)
|
|
579
|
+
return undefined;
|
|
580
|
+
return band.replace(/\{[^}]*\}/g, '').replace(/^🔥\s*Heat:\s*/, '');
|
|
581
|
+
}
|
|
555
582
|
// THE FOR-HIRE BOARD: archetype-built candidates, fresh faces each
|
|
556
583
|
// homecoming (the gig-pool rotation school). Not saved -- the street's
|
|
557
584
|
// talent pool churns; whoever you HIRED is the persistent part.
|
|
@@ -1660,6 +1660,15 @@ ${worldEventsSummary}
|
|
|
1660
1660
|
const payoutSummary = payout
|
|
1661
1661
|
? `The agreed payout for the current job is EXACTLY ${payout} nuyen, paid as ONE LUMP SUM on delivery -- there is no advance and no half up front; never speak as if part has already been paid. Any time you mention payment, reward, or what the job is worth, use exactly this figure -- never invent a different one. Payment transfers automatically when the job completes; you never need to hand it over.`
|
|
1662
1662
|
: '';
|
|
1663
|
+
// HEAT IS MECHANICALLY REAL TOO (YE9G3Q5P85y5xcP7n: an NPC said
|
|
1664
|
+
// "Quiet as promised" while the player's own HUD read "Heat: sirens
|
|
1665
|
+
// circling closer"). Same shape as payoutSummary above -- nothing
|
|
1666
|
+
// told the model the run's actual heat, so a reassuring line had
|
|
1667
|
+
// nothing to check itself against.
|
|
1668
|
+
const heat = this._scene?.ownerGame?.heatSummaryForDialogue?.();
|
|
1669
|
+
const heatSummary = heat
|
|
1670
|
+
? `The district's current HEAT on this run is ${heat}. If you would naturally reassure them that things are quiet, clean, or under control, that claim has to be TRUE against this -- never tell them the coast is clear, that nobody noticed, or that things went quiet if heat says otherwise. Speak honestly about how hot it actually is instead.`
|
|
1671
|
+
: '';
|
|
1663
1672
|
// If nothing else has happened to/around this NPC yet this scene, this
|
|
1664
1673
|
// is the first time anyone's reached out -- nudge toward volunteering
|
|
1665
1674
|
// the job rather than waiting to be asked, which real playtesting
|
|
@@ -2103,6 +2112,7 @@ Your objective is to drive the story line.`}
|
|
|
2103
2112
|
${eph ? '' : objectiveSummary}
|
|
2104
2113
|
${dialogueSummary}
|
|
2105
2114
|
${eph ? '' : payoutSummary}
|
|
2115
|
+
${eph ? '' : heatSummary}
|
|
2106
2116
|
|
|
2107
2117
|
${roomDesc}${spotBlock}${combatSpatialBlock}
|
|
2108
2118
|
${formattedExitsStr}
|
|
@@ -652,7 +652,37 @@ export class Player extends AbstractPlayer {
|
|
|
652
652
|
return true;
|
|
653
653
|
if (this.arOffline)
|
|
654
654
|
return false;
|
|
655
|
-
|
|
655
|
+
if (!this.getPanMaster())
|
|
656
|
+
return false;
|
|
657
|
+
return this.hasARDisplay();
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* THE MISSING HALF OF AR (canon p.53-54/221-222, RAG-checked
|
|
661
|
+
* 2026-09-10, acBi46ZKKJCPyBKXb: "it looks like I'm able to see AR
|
|
662
|
+
* with the Hermes Ikon, however, I don't have a method to interface
|
|
663
|
+
* to it -- no trode, or commlink jack. I think this is a gap"). It
|
|
664
|
+
* was: "a commlink alone does not automatically give you AR -- the
|
|
665
|
+
* commlink processes and sends the information, but you still need a
|
|
666
|
+
* display device to receive it." hasMatrixEyes gated ONLY on a
|
|
667
|
+
* PAN master existing, never on having any way to actually SEE what
|
|
668
|
+
* it sends -- so a runner with a commlink in their pocket and nothing
|
|
669
|
+
* on their face or in their skull got the full overlay regardless.
|
|
670
|
+
*
|
|
671
|
+
* Two independent roads in, matching what the rulebook actually lists:
|
|
672
|
+
* a DNI (trodes/datajack/technomancer -- Player.hasDirectNeuralInterface,
|
|
673
|
+
* "you can use all your senses to view AR without any extra devices"),
|
|
674
|
+
* or a worn visual display (cybereyes, glasses, goggles, contacts --
|
|
675
|
+
* same head.eyes slot and same two categories smartlink detection
|
|
676
|
+
* already reads, Category.Glasses / Category.EyeCyberwere). Audio-only
|
|
677
|
+
* and haptic-only AROs have their own separate gear (earbuds/cyberear,
|
|
678
|
+
* AR gloves, p.222/439) and are not modelled here -- this predicate is
|
|
679
|
+
* specifically the VISUAL overlay hasMatrixEyes has always been about.
|
|
680
|
+
*/
|
|
681
|
+
hasARDisplay() {
|
|
682
|
+
if (this.hasDirectNeuralInterface())
|
|
683
|
+
return true;
|
|
684
|
+
const onFace = this._equipment.head?.eyes;
|
|
685
|
+
return !!onFace && (onFace.category === Category.Glasses || onFace.category === Category.EyeCyberwere);
|
|
656
686
|
}
|
|
657
687
|
/**
|
|
658
688
|
* A DIRECT NEURAL INTERFACE -- what VR needs and AR does not (SR5 core
|
|
@@ -592,10 +592,21 @@ export class Scene extends AbstractScene {
|
|
|
592
592
|
this.logger.write(`Removed actor: ${name}`);
|
|
593
593
|
// A fallen participant's Action Phase, if it was live, moves on;
|
|
594
594
|
// the encounter itself re-checks who is still standing on its next
|
|
595
|
-
// step (utilities/combat-turn.ts run).
|
|
595
|
+
// step (utilities/combat-turn.ts run). But run()'s own stillHostile()
|
|
596
|
+
// check only fires from ITS OWN loop, which never runs again while a
|
|
597
|
+
// human's Action Phase stays open (they can still move/loot before
|
|
598
|
+
// "end turn") -- so killing the last hostile mid-phase left the fight
|
|
599
|
+
// "still going" until the human explicitly ended their turn
|
|
600
|
+
// (5aqFRn2xPtRYDvNxR). Mirrors leave()'s own direct stillHostile()
|
|
601
|
+
// check for the same "nobody left to fight" shape.
|
|
596
602
|
const enc = room ? this.encounterIn(room) : undefined;
|
|
597
|
-
if (enc && enc.
|
|
598
|
-
|
|
603
|
+
if (enc && !enc.ended) {
|
|
604
|
+
if (enc.phaseActor === actor) {
|
|
605
|
+
void enc.dropPhaseActor(actor);
|
|
606
|
+
}
|
|
607
|
+
else if (!enc.stillHostile()) {
|
|
608
|
+
void enc.end('over');
|
|
609
|
+
}
|
|
599
610
|
}
|
|
600
611
|
// Tell the Game an actor left the world -- the companion roster
|
|
601
612
|
// (Game.onActorRemoved) prunes shells and settles their bound
|
|
@@ -225,10 +225,17 @@ export function arStatusSlots(actor) {
|
|
|
225
225
|
? `{white-fg}◌ AR OFF{/white-fg}`
|
|
226
226
|
: !actor.getPanMaster() && !actor.isTechnomancer()
|
|
227
227
|
? `{white-fg}◌ no AR rig{/white-fg}`
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
|
|
228
|
+
// A processor with nothing to paint it on (acBi46ZKKJCPyBKXb):
|
|
229
|
+
// a commlink sends the overlay, but nothing here receives it
|
|
230
|
+
// (Player.hasARDisplay -- no DNI, no worn glasses/goggles/
|
|
231
|
+
// cybereyes). Distinct from "no AR rig" -- the player HAS a
|
|
232
|
+
// rig, it just has nowhere to show up.
|
|
233
|
+
: !actor.hasARDisplay()
|
|
234
|
+
? `{white-fg}◌ no display -- trodes or eyewear{/white-fg}`
|
|
235
|
+
// Off-plane, or any future state that puts the overlay down
|
|
236
|
+
// without being one of the above. game.ts drops the slot
|
|
237
|
+
// entirely while off-plane -- a persona has its own HUD line.
|
|
238
|
+
: `{white-fg}◌ AR dark{/white-fg}`;
|
|
232
239
|
// The link slot asks for the LIVE LINK -- worn commlink or working
|
|
233
240
|
// deck -- because that is what call.ts now asks for (p.223: a deck
|
|
234
241
|
// performs all commlink functions). The old "AR ON | Link offline"
|
|
@@ -956,6 +956,15 @@ export class CombatExchange {
|
|
|
956
956
|
// icon must drop to ☹ the moment they go down, not on the
|
|
957
957
|
// player's next step.
|
|
958
958
|
this.scene.updateExits(room);
|
|
959
|
+
// Same "combat sat there until 'end turn'" gap as removeActor
|
|
960
|
+
// below (5aqFRn2xPtRYDvNxR): a KO'd-not-killed last hostile also
|
|
961
|
+
// stops being present() (isIncapacitated), but nothing re-runs
|
|
962
|
+
// run()'s own stillHostile() check while the killer's own Action
|
|
963
|
+
// Phase stays open. End it directly, the same way leave() does.
|
|
964
|
+
const koEnc = this.scene.encounterFor?.(fallen);
|
|
965
|
+
if (koEnc && !koEnc.ended && koEnc.phaseActor !== fallen && !koEnc.stillHostile()) {
|
|
966
|
+
void koEnc.end('over');
|
|
967
|
+
}
|
|
959
968
|
return ['', ...lines];
|
|
960
969
|
}
|
|
961
970
|
// A BODY ALWAYS FORMS (except for companion shells, which leave
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.153.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.",
|