@maka/maka-cli 5.152.0 → 5.154.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.152.0",
3
+ "version": "5.154.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
- // READY WEAPON (p.165) is an action, not a beat.
364
- if (this.usesEquippedWeapon() && weaponItem && !actor.weaponDrawn) {
365
- return `The ${weaponItem.name} is holstered -- "draw" it first (Ready Weapon, a Simple Action, SR5 p.165).${phaseHint(this.scene, actor)}`;
366
- }
367
- const isMelee = !this.usesEquippedWeapon() || !weaponItem?.isFirearm();
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) {
@@ -2,6 +2,7 @@ import { Command } from './command.js';
2
2
  import { Category } from '../types/shared/item-enum.js';
3
3
  import { Item } from '../models/item.js';
4
4
  import { billAction, encounterOf, phaseHint } from '../utilities/action-cost.js';
5
+ import { fuzzyPickName } from '../utilities/fuzzy-match.js';
5
6
  /**
6
7
  * Refills the equipped firearm's magazine by consuming one
7
8
  * Ammunition-category item from the pack. Every shot spends a round --
@@ -20,7 +21,7 @@ import { billAction, encounterOf, phaseHint } from '../utilities/action-cost.js'
20
21
  export class ReloadCommand extends Command {
21
22
  static verb = 'reload';
22
23
  static description = 'Reload your equipped firearm -- consumes one Ammunition item from your pack. In a fight it takes the whole Action Phase (Remove Clip + Insert Clip, SR5 p.163), or a Free + a Simple with a smartgun you\'re linked to (p.164).';
23
- async execute(_args = []) {
24
+ async execute(args = []) {
24
25
  const actor = this.actor;
25
26
  if (this.scene.isHumanControlled(actor) && this.scene.isPlayerExchangeActive(actor.name)) {
26
27
  return `No time -- the exchange is still resolving. Reload in the lull.`;
@@ -32,10 +33,25 @@ export class ReloadCommand extends Command {
32
33
  if (weapon.ammo >= weapon.magazineCapacity) {
33
34
  return `The ${weapon.name} is already fully loaded (${weapon.ammo}/${weapon.magazineCapacity}).`;
34
35
  }
35
- // NON-LETHAL STANCE: stun rounds first if you carry any (gel,
36
- // stick-n-shock -- p.434); otherwise whatever is in the pack.
37
36
  const boxes = actor.inventory.getAllItems().filter(i => i.category === Category.Ammunition);
38
- const ammo = (actor.autoStance === 'non-lethal' ? boxes.find(b => Item.isStunAmmo(b)) : undefined) ?? boxes[0];
37
+ // "reload APDS Rounds" names which box to slap home -- the arg was
38
+ // being silently discarded (bktSfejkhvmjFZ5EH), always loading the
39
+ // stance default instead of what the player actually asked for.
40
+ let ammo;
41
+ if (args.length > 0) {
42
+ const requested = args.join(' ');
43
+ const picked = fuzzyPickName(requested, boxes.map(b => b.name));
44
+ ammo = picked ? boxes.find(b => b.name === picked) : undefined;
45
+ if (!ammo) {
46
+ const present = boxes.map(b => b.name).join(', ') || 'none';
47
+ return `You're not carrying any ammunition named "${requested}" -- carrying: ${present}.`;
48
+ }
49
+ }
50
+ else {
51
+ // NON-LETHAL STANCE: stun rounds first if you carry any (gel,
52
+ // stick-n-shock -- p.434); otherwise whatever is in the pack.
53
+ ammo = (actor.autoStance === 'non-lethal' ? boxes.find(b => Item.isStunAmmo(b)) : undefined) ?? boxes[0];
54
+ }
39
55
  if (!ammo) {
40
56
  return `No ammunition in your pack -- the ${weapon.name} stays at ${weapon.ammo}/${weapon.magazineCapacity}. Find a spare clip.`;
41
57
  }
@@ -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.phaseActor === actor) {
598
- void enc.dropPhaseActor(actor);
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
@@ -130,7 +130,7 @@ function renderRow(item) {
130
130
  // extra steps (user, 2026-08-30).
131
131
  const liveReason = liveReasonOf(item.repro);
132
132
  Log.plain(liveReason
133
- ? ` ${Log.colors.warn(`${liveReason} -- card: maka play --backlog ${item._id}`)}`
133
+ ? ` ${Log.colors.warn(`${liveReason} -- card: maka play:backlog ${item._id}`)}`
134
134
  : ` ${Log.colors.success(`bench: maka play --repro ${item._id}`)}${benchLockNote(item)}`);
135
135
  }
136
136
  }
@@ -286,7 +286,7 @@ export async function dumpBacklog(opts = {}) {
286
286
  if (!found) {
287
287
  if (!items.some(it => it._id.startsWith(id))) {
288
288
  Log.error(`No item with id "${id}" on the board${filter ? ` in ${filter}` : ''}.`);
289
- Log.info('`maka play --backlog` lists them -- every row carries its id.');
289
+ Log.info('`maka play:backlog` lists them -- every row carries its id.');
290
290
  }
291
291
  return;
292
292
  }
@@ -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
@@ -333,7 +333,7 @@ const ShadowrunCommand = Command.create({
333
333
  }
334
334
  if (found.outcome === 'not-found') {
335
335
  Log.error(`Nothing in your review queue matches "${reproTarget}".`);
336
- Log.info('`maka play --backlog --filter in-review` lists them, with ids.');
336
+ Log.info('`maka play:backlog --filter in-review` lists them, with ids.');
337
337
  return;
338
338
  }
339
339
  if (found.outcome === 'no-repro') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.152.0",
3
+ "version": "5.154.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.",