@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.
- package/bundle/typescript/package.json +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/aim.js +60 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +132 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.js +12 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +102 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/cover.js +24 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/defend.js +74 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/delay.js +34 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/dispel.js +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drop.js +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js +11 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/end-turn.js +58 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/grapple.js +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/heal.js +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/initiative.js +20 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/move.js +42 -59
- package/bundle/typescript/src/commands/game/sideQuest/commands/posture.js +23 -7
- package/bundle/typescript/src/commands/game/sideQuest/commands/reload.js +31 -10
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/summon.js +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.js +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/unequip.js +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.js +8 -0
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +11 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.js +24 -1
- package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/item.js +21 -4
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +44 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +33 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +98 -0
- package/bundle/typescript/src/commands/game/sideQuest/ui.js +3 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/action-budget.js +87 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/action-cost.js +66 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js +58 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +99 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-turn.js +568 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js +20 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/movement-cost.js +80 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/npc-combat-brain.js +122 -0
- package/package.json +1 -1
|
@@ -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.
|
|
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.",
|