@maka/maka-cli 5.133.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 (44) 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 +56 -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 +17 -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/companion-heel.js +67 -9
  41. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js +20 -0
  42. package/bundle/typescript/src/commands/game/sideQuest/utilities/movement-cost.js +80 -0
  43. package/bundle/typescript/src/commands/game/sideQuest/utilities/npc-combat-brain.js +122 -0
  44. package/package.json +1 -1
@@ -0,0 +1,80 @@
1
+ import { Player } from '../models/player.js';
2
+ import { rollPool, formatRoll } from './dice.js';
3
+ import { physicalLimit } from './grapple.js';
4
+ import { hint } from './hints.js';
5
+ import { billAction, encounterOf } from './action-cost.js';
6
+ /**
7
+ * GROUND COSTS IN A FIGHT (SR5 p.161-162, RAG-checked 2026-09-06).
8
+ *
9
+ * The rates are the Player's (walkRateMeters, runRateMeters, the
10
+ * whole-turn movedMetersThisTurn); this is the one place a crossing is
11
+ * BILLED against them, shared by move.ts, the melee reach in attack.ts,
12
+ * and the NPC brain so the three cannot drift:
13
+ *
14
+ * - Up to the Walk Rate, movement is free of actions.
15
+ * - "As soon as a character exceeds their Walk Rate they are
16
+ * considered Running until the end of the Combat Turn" -- and
17
+ * "Running characters must use a Free Action in each Initiative
18
+ * Pass they are considered running" (p.162). The first crossing
19
+ * that breaks the Walk Rate spends the phase's Free Action; the
20
+ * encounter charges it again at the start of every later phase in
21
+ * the turn (combat-turn.ts beginPhase). No Free Action left means
22
+ * no breaking into a run this phase.
23
+ * - Past the Run Rate needs a SPRINT: a Complex Action, Running +
24
+ * Strength [Physical], +2 m per hit (+1 for dwarfs and trolls,
25
+ * which this engine has no character for -- Player.SPRINT_METERS_PER_HIT).
26
+ *
27
+ * Out of combat everything here is a no-op returning undefined: a walk
28
+ * across a bar is not a Combat Turn.
29
+ */
30
+ export function spendMovementMeters(scene, actor, meters, mode, logger) {
31
+ const enc = encounterOf(scene, actor);
32
+ if (!enc || enc.ended)
33
+ return undefined;
34
+ if (meters <= 0)
35
+ return undefined;
36
+ if (!enc.isPhaseOf(actor)) {
37
+ return `It's not your Action Phase -- movement happens on your turn (SR5 p.161).`;
38
+ }
39
+ // THE SPRINT (p.162): Complex Action, rolled when asked for, never
40
+ // applied silently to cover a shortfall.
41
+ if (mode === 'sprint' && actor.sprintMetersThisTurn === 0) {
42
+ const bill = billAction(scene, actor, 'complex', 'Sprint');
43
+ if (bill)
44
+ return bill;
45
+ const skill = actor.skillRating('running');
46
+ const roll = rollPool(Math.max(1, skill + actor.strength + actor.bonus('athletics') + actor.woundModifier), physicalLimit(actor));
47
+ const bought = roll.hits * Player.SPRINT_METERS_PER_HIT;
48
+ actor.sprintMetersThisTurn = bought;
49
+ logger?.meta(`Sprint (Running + Strength [Physical]): ${formatRoll(roll)} -- +${bought} m this turn`);
50
+ }
51
+ const left = actor.movementLeftMeters;
52
+ if (meters > left) {
53
+ return left <= 0
54
+ ? `You've covered all your ground this Combat Turn (${actor.runRateMeters} m at a dead run). Nothing left in the legs until the next turn.`
55
+ : `That's ${meters.toFixed(1)} m and you have ${left.toFixed(1)} m left this turn (Run Rate ${actor.runRateMeters} m).${hint(` ("sprint" buys more ground -- a Complex Action, Running + Strength.)`)}`;
56
+ }
57
+ // BREAKING INTO A RUN costs the phase's Free Action (p.162).
58
+ const wasRunning = actor.isRunningThisTurn;
59
+ const willRun = actor.movedMetersThisTurn + meters > actor.walkRateMeters;
60
+ if (willRun && !wasRunning) {
61
+ const bill = billAction(scene, actor, 'free', 'Run');
62
+ if (bill) {
63
+ const walkLeft = Math.max(0, actor.walkRateMeters - actor.movedMetersThisTurn);
64
+ return `${bill} Running takes a Free Action (SR5 p.162) -- you can still walk ${walkLeft.toFixed(1)} m.`;
65
+ }
66
+ }
67
+ actor.movedMetersThisTurn += meters;
68
+ return undefined;
69
+ }
70
+ /** A line for the player after a crossing that changed their pace. */
71
+ export function paceNote(scene, actor, wasRunning) {
72
+ const enc = encounterOf(scene, actor);
73
+ if (!enc || enc.ended)
74
+ return '';
75
+ if (actor.isRunningThisTurn && !wasRunning) {
76
+ return ` You're RUNNING now -- -2 dice on everything else this Combat Turn, harder to hit at range (SR5 p.162).`;
77
+ }
78
+ return '';
79
+ }
80
+ //# sourceMappingURL=movement-cost.js.map
@@ -0,0 +1,122 @@
1
+ import { Category } from '../types/shared/item-enum.js';
2
+ import { spotOf, canReach, isInCover, coverAvailableFor, actorDistanceMeters } from './spots.js';
3
+ import { Logger } from './logger.js';
4
+ /**
5
+ * AN NPC'S ACTION PHASE, PLAYED BY RULE (SR5 p.163-167).
6
+ *
7
+ * The LLM reflect chain (models/npc.ts) is how NPCs live between fights
8
+ * -- talk, wander, react. It is the wrong instrument for a Combat Turn:
9
+ * it answers on its own clock, cannot be told "you have two Simple
10
+ * Actions and it is your phase now", and its grind-breaker stops after
11
+ * two refusals. So while an encounter runs, every participant NPC's
12
+ * chain is suspended (NPC.combatPhaseLocked) and this plays their
13
+ * phases instead, through the same verbs a player types, so every
14
+ * action pays the same cost through the same gate (action-cost.ts).
15
+ *
16
+ * The doctrine is a soldier's, not a genius's, and deliberately short:
17
+ * 1. A holstered weapon is readied first (Ready Weapon, Simple, p.165).
18
+ * 2. A dry gun is reloaded when there is ammunition to do it with
19
+ * (the whole phase, p.163/167); otherwise the fists come up.
20
+ * 3. Melee closes the distance (movement, p.161) and swings (Complex,
21
+ * p.167) -- or takes cover if it cannot reach this turn.
22
+ * 4. A shooter with a free Simple takes cover if there is any to take
23
+ * (Simple, p.166), then fires (Simple, p.165). One attack per
24
+ * phase (p.164), so the second Simple is never a second shot.
25
+ * Nothing here rolls a die of its own: the verbs do.
26
+ */
27
+ export async function runNpcActionPhase(enc, npc) {
28
+ const logger = Logger.getInstance();
29
+ if (npc.isIncapacitated() || npc.surrendered)
30
+ return;
31
+ const enemies = enc.enemiesOf(npc);
32
+ if (enemies.length === 0) {
33
+ logger.write(`Brain: ${npc.name} has nobody to fight -- phase forfeited.`);
34
+ return;
35
+ }
36
+ const target = pickTarget(npc, enemies);
37
+ npc.combatOpponent = target;
38
+ npc.lastExchangeAt = Date.now();
39
+ let guard = 0;
40
+ while (guard++ < 6) {
41
+ const budget = enc.budgetOf(npc);
42
+ if (!budget || budget.actionsExhausted)
43
+ return;
44
+ if (target.isIncapacitated() || target.currentLocation !== npc.currentLocation)
45
+ return;
46
+ const weapon = npc.getCarriedWeapon();
47
+ const firearm = !!weapon?.isFirearm();
48
+ // 1. Ready Weapon.
49
+ if (weapon && !npc.weaponDrawn) {
50
+ const r = await npc.actInCombat('draw');
51
+ logger.write(`Brain: ${npc.name} draw -> ${r}`);
52
+ if (!npc.weaponDrawn)
53
+ return; // refused -- nothing else will go better
54
+ continue;
55
+ }
56
+ // 2. A dry gun: reload if the pack allows, else it is a club.
57
+ const dry = firearm && (weapon.ammo <= 0);
58
+ if (dry && !weapon.jammed && hasAmmo(npc)) {
59
+ const r = await npc.actInCombat('reload');
60
+ logger.write(`Brain: ${npc.name} reload -> ${r}`);
61
+ return; // a reload is the phase (p.163 Reloading Weapons)
62
+ }
63
+ const melee = !firearm || dry || weapon.jammed;
64
+ if (melee) {
65
+ const targetSpot = spotOf(target);
66
+ if (!canReach(npc, targetSpot)) {
67
+ const r = await npc.actInCombat(`move to ${targetSpot}`);
68
+ logger.write(`Brain: ${npc.name} move to ${targetSpot} -> ${r}`);
69
+ if (!canReach(npc, targetSpot)) {
70
+ // Couldn't close this turn: get behind something and wait.
71
+ if (!isInCover(npc) && coverAvailableFor(npc) && (enc.budgetOf(npc)?.simple ?? 0) > 0) {
72
+ await npc.actInCombat('cover');
73
+ }
74
+ return;
75
+ }
76
+ }
77
+ const b = enc.budgetOf(npc);
78
+ if (b?.complexAvailable) {
79
+ const r = await npc.actInCombat(`attack ${target.name}`);
80
+ logger.write(`Brain: ${npc.name} attack ${target.name} -> ${r.split('\n')[0]}`);
81
+ }
82
+ return;
83
+ }
84
+ // Ranged: cover first when both Simples are still on the table.
85
+ if (!isInCover(npc) && coverAvailableFor(npc) && budget.simple >= 2) {
86
+ const r = await npc.actInCombat('cover');
87
+ logger.write(`Brain: ${npc.name} cover -> ${r}`);
88
+ if (!isInCover(npc))
89
+ return;
90
+ continue;
91
+ }
92
+ if (!budget.attackTaken && budget.simple >= 1) {
93
+ const r = await npc.actInCombat(`attack ${target.name}`);
94
+ logger.write(`Brain: ${npc.name} attack ${target.name} -> ${r.split('\n')[0]}`);
95
+ if (!enc.budgetOf(npc)?.attackTaken)
96
+ return; // refused: don't loop on it
97
+ continue;
98
+ }
99
+ return;
100
+ }
101
+ }
102
+ /** The opponent already engaged, else the nearest of the rest. */
103
+ function pickTarget(npc, enemies) {
104
+ const engaged = npc.combatOpponent;
105
+ if (engaged && enemies.includes(engaged))
106
+ return engaged;
107
+ const room = npc.currentLocation;
108
+ let best = enemies[0];
109
+ let bestD = Number.POSITIVE_INFINITY;
110
+ for (const e of enemies) {
111
+ const d = actorDistanceMeters(room, npc, e);
112
+ if (d < bestD) {
113
+ best = e;
114
+ bestD = d;
115
+ }
116
+ }
117
+ return best;
118
+ }
119
+ function hasAmmo(npc) {
120
+ return npc.inventory.getAllItems().some(i => i.category === Category.Ammunition);
121
+ }
122
+ //# sourceMappingURL=npc-combat-brain.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.133.0",
3
+ "version": "5.135.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.",