@maka/maka-cli 5.134.0 → 5.135.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.
Files changed (43) hide show
  1. package/bundle/typescript/package.json +1 -1
  2. package/bundle/typescript/src/commands/game/sideQuest/commands/aim.js +60 -0
  3. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +132 -3
  4. package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.js +12 -1
  5. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +102 -0
  6. package/bundle/typescript/src/commands/game/sideQuest/commands/cover.js +24 -6
  7. package/bundle/typescript/src/commands/game/sideQuest/commands/defend.js +74 -0
  8. package/bundle/typescript/src/commands/game/sideQuest/commands/delay.js +34 -0
  9. package/bundle/typescript/src/commands/game/sideQuest/commands/dispel.js +5 -0
  10. package/bundle/typescript/src/commands/game/sideQuest/commands/drop.js +5 -0
  11. package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js +11 -2
  12. package/bundle/typescript/src/commands/game/sideQuest/commands/end-turn.js +58 -0
  13. package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js +6 -0
  14. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +14 -0
  15. package/bundle/typescript/src/commands/game/sideQuest/commands/grapple.js +15 -0
  16. package/bundle/typescript/src/commands/game/sideQuest/commands/heal.js +6 -0
  17. package/bundle/typescript/src/commands/game/sideQuest/commands/initiative.js +20 -0
  18. package/bundle/typescript/src/commands/game/sideQuest/commands/move.js +42 -59
  19. package/bundle/typescript/src/commands/game/sideQuest/commands/posture.js +23 -7
  20. package/bundle/typescript/src/commands/game/sideQuest/commands/reload.js +31 -10
  21. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +6 -0
  22. package/bundle/typescript/src/commands/game/sideQuest/commands/summon.js +5 -0
  23. package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.js +6 -0
  24. package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +5 -0
  25. package/bundle/typescript/src/commands/game/sideQuest/commands/unequip.js +6 -0
  26. package/bundle/typescript/src/commands/game/sideQuest/commands/use.js +8 -0
  27. package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +11 -1
  28. package/bundle/typescript/src/commands/game/sideQuest/game.js +24 -1
  29. package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js +2 -0
  30. package/bundle/typescript/src/commands/game/sideQuest/models/item.js +21 -4
  31. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +44 -0
  32. package/bundle/typescript/src/commands/game/sideQuest/models/player.js +33 -0
  33. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +98 -0
  34. package/bundle/typescript/src/commands/game/sideQuest/ui.js +3 -0
  35. package/bundle/typescript/src/commands/game/sideQuest/utilities/action-budget.js +87 -0
  36. package/bundle/typescript/src/commands/game/sideQuest/utilities/action-cost.js +66 -0
  37. package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js +58 -0
  38. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +99 -3
  39. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-turn.js +568 -0
  40. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js +20 -0
  41. package/bundle/typescript/src/commands/game/sideQuest/utilities/movement-cost.js +80 -0
  42. package/bundle/typescript/src/commands/game/sideQuest/utilities/npc-combat-brain.js +122 -0
  43. package/package.json +1 -1
@@ -308,6 +308,8 @@ export class CombatExchange {
308
308
  if (lines.length > 0)
309
309
  this.logger.log(`\n${lines.join('\n')}`, fightScope);
310
310
  this.scene.updateStatus();
311
+ if (process.env.MAKA_NO_BEATS === '1')
312
+ return;
311
313
  await new Promise(resolve => setTimeout(resolve, CombatExchange.BEAT_MS));
312
314
  }
313
315
  /**
@@ -454,6 +456,94 @@ export class CombatExchange {
454
456
  * rebuild AI-history reports as [...world, ...meta] so an NPC's
455
457
  * memory of the fight keeps the dice.
456
458
  */
459
+ /**
460
+ * WHAT THE COMBAT TURN ADDS TO ONE STRIKE (RAG-checked 2026-09-06):
461
+ * - TAKE AIM (p.166): +1 per aim on the shot it was for, capped at
462
+ * half Willpower; consumed here whether or not it was for this
463
+ * target (aiming at one person then shooting another wastes it).
464
+ * - CHARGING (p.186): a running attacker closing into melee gets +4
465
+ * -- net +2 with the -2 running penalty already in the pool.
466
+ * - DEFENDER RUNNING (p.189 Defense Modifiers): +2 to defend
467
+ * against a ranged attack.
468
+ * - SURPRISE (p.192): a surprised defender gets no Defense Test at
469
+ * all against someone who is not.
470
+ * - FULL DEFENSE (p.191): +Willpower for the rest of the turn.
471
+ * - DODGE / BLOCK / PARRY (p.168): -5 Initiative each, +Gymnastics /
472
+ * +Unarmed / +weapon skill on this one test, by the defender's
473
+ * standing policy (commands/defend.ts), and only when the score
474
+ * can pay.
475
+ * Outside an encounter only the aim, charge and running lines apply --
476
+ * they are properties of the actors, not of the turn structure.
477
+ */
478
+ turnModifiers(attacker, defender, isMelee, strike, meta) {
479
+ let attack = 0;
480
+ let defense = 0;
481
+ if (attacker.aimBonus > 0 && !isMelee && !strike.isSpell) {
482
+ if (!attacker.aimTarget || attacker.aimTarget === defender.name) {
483
+ const aimed = Math.min(attacker.aimBonus, attacker.aimCap);
484
+ attack += aimed;
485
+ meta.push(` Aimed: +${aimed} (Take Aim, p.166)`);
486
+ }
487
+ else {
488
+ meta.push(` The aim on ${attacker.aimTarget} is wasted on ${defender.name}.`);
489
+ }
490
+ attacker.clearAim();
491
+ }
492
+ if (isMelee && !strike.isSpell && attacker.isRunningThisTurn) {
493
+ attack += 4;
494
+ meta.push(` Charging: +4 (p.186; the -2 for running is already in the pool)`);
495
+ }
496
+ if (!isMelee && defender.isRunningThisTurn) {
497
+ defense += 2;
498
+ meta.push(` ${defender.name} is running: +2 to defend (p.189)`);
499
+ }
500
+ const enc = this.scene.encounterFor?.(defender) ?? this.scene.encounterFor?.(attacker);
501
+ if (!enc)
502
+ return { attack, defense, noDefense: false };
503
+ if (enc.isSurprised(defender) && !enc.isSurprised(attacker)) {
504
+ meta.push(` ${defender.name} is SURPRISED -- no Defense Test (p.192).`);
505
+ return { attack, defense, noDefense: true };
506
+ }
507
+ if (enc.fullDefenseActive(defender)) {
508
+ defense += defender.willpower;
509
+ meta.push(` Full Defense: +${defender.willpower} Willpower (p.191)`);
510
+ }
511
+ const policy = defender.defensePolicy;
512
+ if (policy !== 'none' && (defender.plane === 'meat' || defender.plane === 'drone')) {
513
+ let skill = 0;
514
+ let label = '';
515
+ let source = '';
516
+ if (policy === 'dodge') {
517
+ skill = defender.skillRating('gymnastics');
518
+ label = 'Dodge';
519
+ source = 'Gymnastics';
520
+ }
521
+ else if (policy === 'block' && isMelee) {
522
+ skill = defender.skillRating('unarmed');
523
+ label = 'Block';
524
+ source = 'Unarmed Combat';
525
+ }
526
+ else if (policy === 'parry' && isMelee) {
527
+ const held = defender.weaponDrawn ? defender.getCarriedWeapon() : null;
528
+ if (held && !held.isFirearm()) {
529
+ skill = defender.skillRating(defender.activeCombatSkill());
530
+ label = 'Parry';
531
+ source = 'weapon skill';
532
+ }
533
+ }
534
+ if (label && skill > 0) {
535
+ const refusal = enc.spendInterrupt(defender, 5, label);
536
+ if (refusal) {
537
+ meta.push(` ${label} not taken: ${refusal}`);
538
+ }
539
+ else {
540
+ defense += skill;
541
+ meta.push(` ${label}: +${skill} (${source}, -5 Initiative, p.168)`);
542
+ }
543
+ }
544
+ }
545
+ return { attack, defense, noDefense: false };
546
+ }
457
547
  resolveStrike(attacker, defender, profile) {
458
548
  const strike = profile ?? this.weaponProfile(attacker, defender);
459
549
  const world = [];
@@ -564,10 +654,16 @@ export class CombatExchange {
564
654
  const attackLimit = strike.isSpell
565
655
  ? strike.limit
566
656
  : (strike.limit ?? attacker.getAttackLimit(strike.weaponItem));
567
- const attack = rollPool(strike.pool + directed + rangeMod + attackerFloor + targetDown, attackLimit);
568
- const defense = rollPool(defender.getDefensePool() + inspired + counterspell + coverDice + proneDefence);
569
657
  const header = `${attacker.name} attacks ${defender.name} ${strike.label}`;
570
658
  meta.push(`${header}:`);
659
+ // THE COMBAT TURN'S MODIFIERS (utilities/combat-turn.ts): Take Aim,
660
+ // charging, a running defender, surprise, Full Defense and the
661
+ // dodge/block/parry interrupts -- see turnModifiers below.
662
+ const turn = this.turnModifiers(attacker, defender, isMelee, strike, meta);
663
+ const attack = rollPool(strike.pool + directed + rangeMod + attackerFloor + targetDown + turn.attack, attackLimit);
664
+ const defense = turn.noDefense
665
+ ? rollPool(0)
666
+ : rollPool(defender.getDefensePool() + inspired + counterspell + coverDice + proneDefence + turn.defense);
571
667
  const rangeNote = weaponClass && spatial.meters > 0
572
668
  ? ` (${rangeMod !== 0 ? `${rangeMod} range` : `${rangeBracketName(spatial.meters, weaponClass)} range`} -- ~${Math.round(spatial.meters)} m)`
573
669
  : '';
@@ -908,7 +1004,7 @@ export class CombatExchange {
908
1004
  if (this.scene.isHumanControlled(this.actor)) {
909
1005
  return '';
910
1006
  }
911
- if (this.playerWitnesses()) {
1007
+ if (this.playerWitnesses() && process.env.MAKA_NO_BEATS !== '1') {
912
1008
  await new Promise(resolve => setTimeout(resolve, CombatExchange.NPC_FOLLOWUP_PAUSE_MS));
913
1009
  }
914
1010
  return report.join('\n');