@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,58 @@
|
|
|
1
|
+
import { Command } from './command.js';
|
|
2
|
+
import { notYourPhase } from '../utilities/action-cost.js';
|
|
3
|
+
/**
|
|
4
|
+
* END TURN -- committing your Action Phase (SR5 p.158-159).
|
|
5
|
+
*
|
|
6
|
+
* The Combat Turn (utilities/combat-turn.ts) hands each combatant an
|
|
7
|
+
* Action Phase in initiative order: two Simple Actions or one Complex,
|
|
8
|
+
* plus a Free. A human's phase waits for this verb. Whatever is unspent
|
|
9
|
+
* when you type it is forfeit -- the book gives no refund -- and the
|
|
10
|
+
* pass moves to the next Initiative Score. When everyone has acted,
|
|
11
|
+
* every score drops by 10 (p.159) and anyone still above zero goes
|
|
12
|
+
* again; when nobody is, a new Combat Turn rolls fresh initiative.
|
|
13
|
+
*
|
|
14
|
+
* WHAT NOW COSTS AN ACTION, swept as one family (CLAUDE.md: "partial
|
|
15
|
+
* canon is worse than either extreme"):
|
|
16
|
+
* Free drop prone (p.164) · drop an object (p.163) · surrender ·
|
|
17
|
+
* release a hold · running past your Walk Rate (p.162)
|
|
18
|
+
* Simple draw / holster (Ready Weapon, p.165) · take cover (p.166) ·
|
|
19
|
+
* stand up (p.166) · take aim (p.166) · fire a weapon (p.165)
|
|
20
|
+
* · pick up an object (p.165) · equip / unequip · use a
|
|
21
|
+
* simple device · observe in detail (search, p.165)
|
|
22
|
+
* Complex melee attack (p.167) · subdue / grapple · struggle free
|
|
23
|
+
* (p.195) · cast a spell (p.167) · summon (p.167) · sprint
|
|
24
|
+
* (p.167) · reload (p.163/167) · first aid (p.206)
|
|
25
|
+
* Score Full Defense -10 for the turn (p.191) · dodge / block /
|
|
26
|
+
* parry -5 each, by "defend" policy (p.168)
|
|
27
|
+
* Movement is metres from a whole-turn budget (p.161), not an action.
|
|
28
|
+
*
|
|
29
|
+
* WHY THE WORD IS "end turn" AND NOT "end": "end" already hangs up a
|
|
30
|
+
* commlink call (commands/end-call.ts). EndCallCommand hands "end turn"
|
|
31
|
+
* / "end phase" here; "done" and "pass" are the short forms.
|
|
32
|
+
*/
|
|
33
|
+
export class EndTurnCommand extends Command {
|
|
34
|
+
static verb = 'end-turn';
|
|
35
|
+
static description = 'Commit your Action Phase (SR5 p.158): anything unspent is forfeit and the pass moves to the next combatant. Also "end turn", "done", "pass". "initiative" shows the order; "help end-turn" lists what each verb costs.';
|
|
36
|
+
async execute(_args = []) {
|
|
37
|
+
const actor = this.actor;
|
|
38
|
+
const enc = this.scene.encounterFor?.(actor);
|
|
39
|
+
if (!enc) {
|
|
40
|
+
return `You're not in a fight -- there's no Action Phase to end.`;
|
|
41
|
+
}
|
|
42
|
+
if (!enc.isPhaseOf(actor)) {
|
|
43
|
+
return notYourPhase(enc, actor);
|
|
44
|
+
}
|
|
45
|
+
const budget = enc.budgetOf(actor);
|
|
46
|
+
const ledger = budget?.ledger() ?? 'nothing';
|
|
47
|
+
this.logger.write(`${actor.name} ends their Action Phase (${ledger}).`);
|
|
48
|
+
if (this.scene.isHumanControlled(actor)) {
|
|
49
|
+
// Logged now rather than returned: the phases that follow announce
|
|
50
|
+
// themselves while this command is still running, and a return
|
|
51
|
+
// value would land under them, out of order.
|
|
52
|
+
this.logger.log(`{cyan-fg}You end your Action Phase${budget && budget.spent.length > 0 ? ` -- ${ledger}` : ' -- nothing spent'}.{/cyan-fg}`, { actor: actor.name });
|
|
53
|
+
}
|
|
54
|
+
await enc.endPhase(actor);
|
|
55
|
+
return this.scene.isHumanControlled(actor) ? '' : `${actor.name} ends their Action Phase.`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=end-turn.js.map
|
|
@@ -2,12 +2,18 @@ import { Command } from './command.js';
|
|
|
2
2
|
import { hint } from '../utilities/hints.js';
|
|
3
3
|
import { Category } from '../types/shared/item-enum.js';
|
|
4
4
|
import { Player } from '../models/player.js';
|
|
5
|
+
import { billAction } from '../utilities/action-cost.js';
|
|
5
6
|
export class EquipCommand extends Command {
|
|
6
7
|
static verb = 'equip';
|
|
7
8
|
static description = 'Equip an item or everything in your inventory.';
|
|
8
9
|
async execute(args) {
|
|
9
10
|
if (!args)
|
|
10
11
|
return '';
|
|
12
|
+
// Getting gear onto your body mid-fight is a Simple Action's worth
|
|
13
|
+
// of hands (SR5 p.165 Ready Weapon / Pick Up Object).
|
|
14
|
+
const bill = billAction(this.scene, this.actor, 'simple', 'Equip');
|
|
15
|
+
if (bill)
|
|
16
|
+
return bill;
|
|
11
17
|
const out = this.equip(args);
|
|
12
18
|
// Worn state is sheet-visible and changes rarely, so the DURABLE
|
|
13
19
|
// path carries it (maka-cli.com suggestion, 2026-09-01): an equip
|
|
@@ -9,6 +9,7 @@ import { heldBy, holding } from '../utilities/grapple.js';
|
|
|
9
9
|
import { hint } from '../utilities/hints.js';
|
|
10
10
|
import { fuzzyPickName } from '../utilities/fuzzy-match.js';
|
|
11
11
|
import { gridIconBlock } from '../utilities/grid-view.js';
|
|
12
|
+
import { requirePhase } from '../utilities/action-cost.js';
|
|
12
13
|
import { camerasLive } from '../utilities/surveillance.js';
|
|
13
14
|
import { maybeSinSweep } from '../utilities/sin.js';
|
|
14
15
|
import { despawnEphemerals } from '../utilities/ephemeral.js';
|
|
@@ -166,6 +167,11 @@ export class GoCommand extends Command {
|
|
|
166
167
|
if (gripHeld) {
|
|
167
168
|
return `You're holding ${gripHeld.name} -- walk away and the hold is gone. "release" first if that's the plan.`;
|
|
168
169
|
}
|
|
170
|
+
// THE COMBAT TURN (utilities/combat-turn.ts): leaving the room is
|
|
171
|
+
// movement, and movement happens on your own Action Phase.
|
|
172
|
+
const phase = requirePhase(this.scene, this.actor);
|
|
173
|
+
if (phase)
|
|
174
|
+
return phase;
|
|
169
175
|
// No walking out from between the narration beats of a combat exchange
|
|
170
176
|
// you're part of (see CombatExchange.beginExchange) -- found via a real
|
|
171
177
|
// session where the player slipped rooms mid-exchange. The pause
|
|
@@ -381,6 +387,10 @@ export class GoCommand extends Command {
|
|
|
381
387
|
const gapBlock = this.checkHostBoundary(targetRoom);
|
|
382
388
|
if (gapBlock)
|
|
383
389
|
return gapBlock;
|
|
390
|
+
// Out of the fight before the room hears you go: a participant's
|
|
391
|
+
// AI chain is suspended (NPC.combatPhaseLocked), and the "exits"
|
|
392
|
+
// stimulus below is what makes a hostile give chase.
|
|
393
|
+
this.scene.encounterIn?.(currentRoom)?.leave(this.actor);
|
|
384
394
|
this.actor.performAction('exits', `${this.actor.currentLocation.name} to ${targetRoom.name}`);
|
|
385
395
|
this.actor.cameFrom = { room: this.actor.currentLocation.name, back: GoCommand.opposite(direction) };
|
|
386
396
|
this.actor.currentLocation = targetRoom; // Update player's location
|
|
@@ -418,6 +428,7 @@ export class GoCommand extends Command {
|
|
|
418
428
|
const gapBlock = this.checkHostBoundary(nextRoom);
|
|
419
429
|
if (gapBlock)
|
|
420
430
|
return gapBlock;
|
|
431
|
+
this.scene.encounterIn?.(currentRoom)?.leave(this.actor);
|
|
421
432
|
this.actor.cameFrom = { room: this.actor.currentLocation.name, back: GoCommand.opposite(direction) };
|
|
422
433
|
this.actor.currentLocation = nextRoom;
|
|
423
434
|
// Land IN the doorway you came through when there is one to
|
|
@@ -436,6 +447,9 @@ export class GoCommand extends Command {
|
|
|
436
447
|
}
|
|
437
448
|
this.actor.performAction('enters', `${nextRoom.name}${this.actor.atSpot ? ` (at the ${this.actor.atSpot})` : ''}`);
|
|
438
449
|
}
|
|
450
|
+
// Walking into a room with a fight on joins it, late (p.160): the
|
|
451
|
+
// party and the hostile roll in; anyone else stays out until they act.
|
|
452
|
+
this.scene.admitToEncounter?.(this.actor);
|
|
439
453
|
return this.finishMove(crossNote, currentRoom);
|
|
440
454
|
}
|
|
441
455
|
/**
|
|
@@ -4,6 +4,7 @@ import { rollPool, formatRoll } from '../utilities/dice.js';
|
|
|
4
4
|
import { fuzzyPickName } from '../utilities/fuzzy-match.js';
|
|
5
5
|
import { physicalLimit, heldBy, holding, releaseGrapple } from '../utilities/grapple.js';
|
|
6
6
|
import { spotOf, ensureAtSpot } from '../utilities/spots.js';
|
|
7
|
+
import { billAction } from '../utilities/action-cost.js';
|
|
7
8
|
/**
|
|
8
9
|
* SUBDUING (SR5e p.195) -- "grapple"/"restrain"/"clinch": seize someone
|
|
9
10
|
* and hold them, dealing NO damage. The RAW shape, faithfully: a normal
|
|
@@ -74,6 +75,12 @@ export class GrappleCommand extends Command {
|
|
|
74
75
|
const isPlayer = this.scene.isHumanControlled(actor);
|
|
75
76
|
const lines = [];
|
|
76
77
|
const meta = [];
|
|
78
|
+
// A GRAPPLE IS A MELEE ATTACK (p.195 Subduing Combat: "the attacker
|
|
79
|
+
// makes a normal melee attack"), so it is the Complex Action a melee
|
|
80
|
+
// attack is (p.167) -- and the phase's one attack (p.164).
|
|
81
|
+
const bill = billAction(this.scene, actor, 'complex', 'Grapple (Melee Attack)', { attack: true });
|
|
82
|
+
if (bill)
|
|
83
|
+
return bill;
|
|
77
84
|
// Someone with their hands up doesn't resist a zip-tie: restraining a
|
|
78
85
|
// surrendered target is automatic and quiet (threshold 4 to wriggle
|
|
79
86
|
// out -- betraying a surrender is on them).
|
|
@@ -137,6 +144,10 @@ export class GrappleCommand extends Command {
|
|
|
137
144
|
}
|
|
138
145
|
}
|
|
139
146
|
release(held) {
|
|
147
|
+
// Letting go is a Free Action (p.163 Drop Object -- opening the hand).
|
|
148
|
+
const bill = billAction(this.scene, this.actor, 'free', 'Release Hold');
|
|
149
|
+
if (bill)
|
|
150
|
+
return bill;
|
|
140
151
|
releaseGrapple(held, this.actor);
|
|
141
152
|
this.actor.performAction('releases', `${held.name} from the hold`);
|
|
142
153
|
this.scene.updateStatus();
|
|
@@ -168,6 +179,10 @@ export class StruggleCommand extends Command {
|
|
|
168
179
|
return `Nobody's holding you.`;
|
|
169
180
|
}
|
|
170
181
|
const threshold = actor.grappleThreshold ?? 2;
|
|
182
|
+
// THE ESCAPE IS A COMPLEX ACTION (p.195), spent on the attempt.
|
|
183
|
+
const bill = billAction(this.scene, actor, 'complex', 'Break Grapple');
|
|
184
|
+
if (bill)
|
|
185
|
+
return bill;
|
|
171
186
|
// Two RAW roads out (p.195 Unarmed+Strength; p.135 Escape Artist,
|
|
172
187
|
// Agility-linked -- the contortionist's slip vs the wrestler's
|
|
173
188
|
// wrench). Whichever pool is bigger is the one they instinctively use.
|
|
@@ -4,6 +4,7 @@ import { formatRoll } from '../utilities/dice.js';
|
|
|
4
4
|
import { hint } from '../utilities/hints.js';
|
|
5
5
|
import { sameSpot, spotOf, spotRefusal } from '../utilities/spots.js';
|
|
6
6
|
import { FIRST_AID_THRESHOLD, firstAidCap, firstAidRefusal, medkitRating, performFirstAid, } from '../utilities/first-aid.js';
|
|
7
|
+
import { billAction } from '../utilities/action-cost.js';
|
|
7
8
|
/**
|
|
8
9
|
* HEAL -- first aid in the field (SR5 p.205-206). The mechanic is in
|
|
9
10
|
* utilities/first-aid.ts; this file is the verb and its words.
|
|
@@ -84,6 +85,11 @@ export class HealCommand extends Command {
|
|
|
84
85
|
return spotRefusal(patient.name, spotOf(patient));
|
|
85
86
|
}
|
|
86
87
|
const cap = firstAidCap(actor, kit);
|
|
88
|
+
// FIRST AID IS A COMPLEX ACTION (p.206) -- the action budget the
|
|
89
|
+
// class doc once said this engine lacked now exists.
|
|
90
|
+
const bill = billAction(this.scene, actor, 'complex', 'First Aid');
|
|
91
|
+
if (bill)
|
|
92
|
+
return bill;
|
|
87
93
|
const result = performFirstAid(actor, patient, kit, track);
|
|
88
94
|
const summary = track === 'physical' ? patient.conditionSummary() : patient.stunSummary();
|
|
89
95
|
const trained = actor.skillRating('first-aid') > 0;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Command } from './command.js';
|
|
2
|
+
import { hint } from '../utilities/hints.js';
|
|
3
|
+
/**
|
|
4
|
+
* THE INITIATIVE TRACKER (SR5 p.159-160): who acts when, this Combat
|
|
5
|
+
* Turn and pass, with scores, and what the live phase has spent. A
|
|
6
|
+
* reference panel, so ui.ts renders it into the Mechanics column
|
|
7
|
+
* (MENU_VERBS) rather than the world log.
|
|
8
|
+
*/
|
|
9
|
+
export class InitiativeCommand extends Command {
|
|
10
|
+
static verb = 'initiative';
|
|
11
|
+
static description = 'The initiative tracker: Combat Turn, Initiative Pass, every combatant\'s score and whose Action Phase is live (SR5 p.159). Also "turn", "tracker".';
|
|
12
|
+
async execute(_args = []) {
|
|
13
|
+
const enc = this.scene.encounterIn?.(this.actor.currentLocation);
|
|
14
|
+
if (!enc) {
|
|
15
|
+
return `No fight running here.${hint(` (Attacking someone opens a Combat Turn: initiative for everyone, then Action Phases in order -- "end turn" commits yours.)`)}`;
|
|
16
|
+
}
|
|
17
|
+
return enc.trackerLines().join('\n');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=initiative.js.map
|
|
@@ -2,11 +2,10 @@ import { Command } from './command.js';
|
|
|
2
2
|
import { hint } from '../utilities/hints.js';
|
|
3
3
|
import { GoCommand } from './go.js';
|
|
4
4
|
import { NPC } from '../models/npc.js';
|
|
5
|
-
import { Player } from '../models/player.js';
|
|
6
5
|
import { Direction } from '../types/shared/direction-enum.js';
|
|
7
6
|
import { Door } from '../models/door.js';
|
|
8
7
|
import { fuzzyPickName } from '../utilities/fuzzy-match.js';
|
|
9
|
-
import { heldBy, holding
|
|
8
|
+
import { heldBy, holding } from '../utilities/grapple.js';
|
|
10
9
|
import { spotsActive, resolveSpot, spotOf, describeSpotRoster, OPEN_FLOOR, pathBetweenSpots, spotDistanceMeters, spotClimbSteps, blockingRegion, terrainRefusal, reseatOpenFloor, routeForMove, stepFrom, spotForCell, actorCell, standingRoomNear, yieldSquareToMaster, furnitureCells, approachCellFor, exitSpot, concealedFrom, revealActor, onTheFloor } from '../utilities/spots.js';
|
|
11
10
|
// fallDamageDVMeters, not fallDamageDV: a climb that fell short drops
|
|
12
11
|
// you from where you GOT to, which is a distance in metres, not a whole
|
|
@@ -15,6 +14,8 @@ import { spotsActive, resolveSpot, spotOf, describeSpotRoster, OPEN_FLOOR, pathB
|
|
|
15
14
|
import { fallDamageDVMeters, climbHitsFor, climbMetersFrom, climbSurfaceModifier, METERS_PER_CELL, METERS_PER_LEVEL, key, distanceMeters } from '../utilities/room-grid.js';
|
|
16
15
|
import { rollPool, formatRoll } from '../utilities/dice.js';
|
|
17
16
|
import { trailMaster } from '../utilities/companion-heel.js';
|
|
17
|
+
import { spendMovementMeters, paceNote } from '../utilities/movement-cost.js';
|
|
18
|
+
import { requirePhase } from '../utilities/action-cost.js';
|
|
18
19
|
/**
|
|
19
20
|
* "walk to <spot>" / "run to <spot>" / "sprint to <spot>" -- crossing
|
|
20
21
|
* the CURRENT room to one of its "at" spots (see utilities/spots.ts;
|
|
@@ -114,6 +115,10 @@ export class MoveCommand extends Command {
|
|
|
114
115
|
async execute(args = []) {
|
|
115
116
|
const actor = this.actor;
|
|
116
117
|
const room = actor.currentLocation;
|
|
118
|
+
// THE COMBAT TURN: you move on your own Action Phase (p.158-161).
|
|
119
|
+
const phase = requirePhase(this.scene, actor);
|
|
120
|
+
if (phase)
|
|
121
|
+
return phase;
|
|
117
122
|
// (see offPlaneMoveRefusal below for why the off-plane refusals are
|
|
118
123
|
// no longer one sentence shared by every plane)
|
|
119
124
|
// "move to the bar" reads naturally -- strip the connective and
|
|
@@ -633,6 +638,14 @@ export class MoveCommand extends Command {
|
|
|
633
638
|
const crossing = mine !== undefined && room.ensureGrid()
|
|
634
639
|
? spotDistanceMeters(room, mine, spot.name)
|
|
635
640
|
: undefined;
|
|
641
|
+
// GROUND COSTS IN A FIGHT (p.161-162, utilities/movement-cost.ts).
|
|
642
|
+
const wasRunning = actor.isRunningThisTurn;
|
|
643
|
+
if (crossing !== undefined && crossing > 0) {
|
|
644
|
+
const spend = this.spendMeters(crossing);
|
|
645
|
+
if (spend)
|
|
646
|
+
return spend;
|
|
647
|
+
}
|
|
648
|
+
const pace = paceNote(this.scene, actor, wasRunning);
|
|
636
649
|
actor.atSpot = spot.name;
|
|
637
650
|
// Crossing to a named place gives up any stepped-to square: you're
|
|
638
651
|
// AT the bar now, and seating puts you beside it (spots.ts).
|
|
@@ -675,16 +688,16 @@ export class MoveCommand extends Command {
|
|
|
675
688
|
// "cross to" a place that isn't one.
|
|
676
689
|
if (spot.name === OPEN_FLOOR) {
|
|
677
690
|
if (toWhom) {
|
|
678
|
-
return `You step out onto the open floor where ${toWhom} stands${crossNote}.${here.length > 0 ? ` Also out here: ${here.join(', ')}.` : ''}`;
|
|
691
|
+
return `You step out onto the open floor where ${toWhom} stands${crossNote}.${here.length > 0 ? ` Also out here: ${here.join(', ')}.` : ''}${pace}`;
|
|
679
692
|
}
|
|
680
|
-
return `You peel off to the open floor${crossNote} -- nobody's company but your own.${here.length > 0 ? ` Out here with you: ${here.join(', ')}.` : ''}`;
|
|
693
|
+
return `You peel off to the open floor${crossNote} -- nobody's company but your own.${here.length > 0 ? ` Out here with you: ${here.join(', ')}.` : ''}${pace}`;
|
|
681
694
|
}
|
|
682
|
-
const
|
|
695
|
+
const paceVerb = this.mode() === 'walk' ? 'cross to' : this.mode() === 'run' ? 'hustle over to' : 'sprint to';
|
|
683
696
|
const spotDesc = spot.description ? ` ${spot.description}` : '';
|
|
684
697
|
const company = toWhom
|
|
685
698
|
? `, coming up beside ${toWhom}.`
|
|
686
699
|
: '.';
|
|
687
|
-
return `${descentNote ? `${descentNote} ` : ''}You ${
|
|
700
|
+
return `${descentNote ? `${descentNote} ` : ''}You ${paceVerb} the ${spot.name}${crossNote}${company}${spotDesc}${here.length > 0 ? ` Here: ${here.join(', ')}.` : ''}${pace}`;
|
|
688
701
|
}
|
|
689
702
|
/**
|
|
690
703
|
* "move south 2" -- a tactical step across the room's own grid, as
|
|
@@ -767,6 +780,12 @@ export class MoveCommand extends Command {
|
|
|
767
780
|
: `there's a wall`;
|
|
768
781
|
return `You can't step ${direction} -- ${why}.`;
|
|
769
782
|
}
|
|
783
|
+
// Paces are metres from the turn's budget (p.161-162).
|
|
784
|
+
const wasRunning = actor.isRunningThisTurn;
|
|
785
|
+
const stepSpend = this.spendMeters(taken * METERS_PER_CELL);
|
|
786
|
+
if (stepSpend)
|
|
787
|
+
return stepSpend;
|
|
788
|
+
const stepPace = paceNote(this.scene, actor, wasRunning);
|
|
770
789
|
const wasAt = actor.atSpot;
|
|
771
790
|
actor.atCell = cell;
|
|
772
791
|
actor.atSpot = spotForCell(room, cell);
|
|
@@ -792,69 +811,27 @@ export class MoveCommand extends Command {
|
|
|
792
811
|
const arrived = actor.atSpot !== wasAt && actor.atSpot !== OPEN_FLOOR
|
|
793
812
|
? `You're at the ${actor.atSpot} now.`
|
|
794
813
|
: '';
|
|
795
|
-
return [shortNote, arrived].filter(Boolean).join(' ');
|
|
796
|
-
}
|
|
797
|
-
/**
|
|
798
|
-
* IS THIS ACTOR IN A COMBAT TURN AT ALL?
|
|
799
|
-
*
|
|
800
|
-
* `inExchange` is only true while an exchange is being resolved, so it
|
|
801
|
-
* cannot answer "am I in a fight" between commands. `lastExchangeAt`
|
|
802
|
-
* can, and it is the predicate resolvePartingShot already uses for
|
|
803
|
-
* "engaged" -- reusing it keeps one definition of being in a fight
|
|
804
|
-
* rather than minting a second that can drift from it.
|
|
805
|
-
*/
|
|
806
|
-
static FIGHT_WINDOW_MS = 60_000;
|
|
807
|
-
inCombatTurn(actor) {
|
|
808
|
-
return !!actor.combatOpponent && Date.now() - actor.lastExchangeAt < MoveCommand.FIGHT_WINDOW_MS;
|
|
814
|
+
return [shortNote, arrived, stepPace.trim()].filter(Boolean).join(' ');
|
|
809
815
|
}
|
|
810
816
|
/**
|
|
811
817
|
* Bills a crossing against the Combat Turn's movement rate, or returns
|
|
812
|
-
* the refusal when there is not enough left (SR5 p.161-162).
|
|
818
|
+
* the refusal when there is not enough left (SR5 p.161-162). The
|
|
819
|
+
* rules -- walk free, running spends the Free Action, sprint is a
|
|
820
|
+
* Complex Action with a test -- live in utilities/movement-cost.ts,
|
|
821
|
+
* shared with the melee reach in attack.ts and the NPC brain.
|
|
813
822
|
*
|
|
814
|
-
* Returns undefined -- meaning "go ahead" -- whenever
|
|
815
|
-
*
|
|
823
|
+
* Returns undefined -- meaning "go ahead" -- whenever there is no
|
|
824
|
+
* Combat Turn running, which is every ordinary walk across a bar.
|
|
816
825
|
*/
|
|
817
826
|
spendMovement(actor, to) {
|
|
818
|
-
if (!this.inCombatTurn(actor))
|
|
819
|
-
return undefined;
|
|
820
827
|
const room = actor.currentLocation;
|
|
821
828
|
const from = actorCell(room, actor);
|
|
822
829
|
if (!from)
|
|
823
830
|
return undefined;
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
// "sprint to <x>" verb finally means something mechanical. Canon
|
|
829
|
-
// makes it a Complex Action -- Running + Strength [Physical], each
|
|
830
|
-
// hit buying more ground -- so it is rolled when the player asks for
|
|
831
|
-
// it, not applied automatically to cover a shortfall.
|
|
832
|
-
//
|
|
833
|
-
// ATHLETICS, NOT RUNNING: this engine's skill list has no Running
|
|
834
|
-
// (nor Gymnastics); it collapses both into `athletics`, which
|
|
835
|
-
// resolvePartingShot already rolls for exactly this -- breaking away
|
|
836
|
-
// at a dead run. Mapping canon's skill onto the one this game has is
|
|
837
|
-
// better than inventing a skill nobody can raise.
|
|
838
|
-
if (this.mode() === 'sprint' && actor.sprintMetersThisTurn === 0) {
|
|
839
|
-
// RUNNING: sprinting is Running (p.133), and this pool already
|
|
840
|
-
// pairs it with Strength, which is the attribute canon links.
|
|
841
|
-
const skill = actor.skillRating('running');
|
|
842
|
-
const roll = rollPool(Math.max(1, skill + actor.strength + actor.bonus('athletics') + actor.woundModifier), physicalLimit(actor));
|
|
843
|
-
const bought = roll.hits * Player.SPRINT_METERS_PER_HIT;
|
|
844
|
-
actor.sprintMetersThisTurn = bought;
|
|
845
|
-
this.logger.meta(`Sprint (Running + Strength [Physical]): ${formatRoll(roll)} -- +${bought} m this turn`);
|
|
846
|
-
}
|
|
847
|
-
if (cost > actor.movementLeftMeters) {
|
|
848
|
-
const left = actor.movementLeftMeters;
|
|
849
|
-
// Name the numbers. A refusal that says only "you can't" in a
|
|
850
|
-
// system the player cannot see is the kind of wall that gets
|
|
851
|
-
// reported as a bug.
|
|
852
|
-
return left <= 0
|
|
853
|
-
? `You've already covered your ground this turn (${actor.runRateMeters} m at a dead run). Nothing left in the legs until the next exchange.`
|
|
854
|
-
: `That's ${cost.toFixed(1)} m and you have ${left.toFixed(1)} m left this turn (Run Rate ${actor.runRateMeters} m).${hint(` ("sprint" buys more ground -- Athletics + Strength, ${Player.SPRINT_METERS_PER_HIT} m a hit.)`)}`;
|
|
855
|
-
}
|
|
856
|
-
actor.movedMetersThisTurn += cost;
|
|
857
|
-
return undefined;
|
|
831
|
+
return this.spendMeters(distanceMeters(from, to));
|
|
832
|
+
}
|
|
833
|
+
spendMeters(meters) {
|
|
834
|
+
return spendMovementMeters(this.scene, this.actor, meters, this.mode(), this.logger);
|
|
858
835
|
}
|
|
859
836
|
/** Closes the last few paces to someone already at your own spot --
|
|
860
837
|
* you end up beside them rather than merely in the same zone. */
|
|
@@ -1026,6 +1003,9 @@ export class MoveCommand extends Command {
|
|
|
1026
1003
|
const cell = standingRoomNear(room, target, actor);
|
|
1027
1004
|
if (!cell)
|
|
1028
1005
|
return `There's no room to stand at the ${direction} way out.`;
|
|
1006
|
+
const doorSpend = this.spendMovement(actor, cell);
|
|
1007
|
+
if (doorSpend)
|
|
1008
|
+
return doorSpend;
|
|
1029
1009
|
actor.atCell = cell;
|
|
1030
1010
|
actor.atSpot = spotForCell(room, cell);
|
|
1031
1011
|
actor.performAction('moves to', `the ${direction} doorway`);
|
|
@@ -1055,6 +1035,9 @@ export class MoveCommand extends Command {
|
|
|
1055
1035
|
const cell = standingRoomNear(room, body.cell, actor);
|
|
1056
1036
|
if (!cell)
|
|
1057
1037
|
return `There's no room to stand over ${body.name}.`;
|
|
1038
|
+
const bodySpend = this.spendMovement(actor, cell);
|
|
1039
|
+
if (bodySpend)
|
|
1040
|
+
return bodySpend;
|
|
1058
1041
|
actor.atCell = cell;
|
|
1059
1042
|
actor.atSpot = spotForCell(room, cell);
|
|
1060
1043
|
actor.performAction('stands over', `${body.name}'s body`);
|
|
@@ -2,6 +2,7 @@ import { Command } from './command.js';
|
|
|
2
2
|
import { hint } from '../utilities/hints.js';
|
|
3
3
|
import { rollPool, formatRoll } from '../utilities/dice.js';
|
|
4
4
|
import { spotOf } from '../utilities/spots.js';
|
|
5
|
+
import { billAction, phaseHint } from '../utilities/action-cost.js';
|
|
5
6
|
/**
|
|
6
7
|
* SIT, LIE DOWN, STAND UP (player request 2026-08-27: sit and lie down
|
|
7
8
|
* as "moving" commands).
|
|
@@ -23,9 +24,11 @@ import { spotOf } from '../utilities/spots.js';
|
|
|
23
24
|
* be inventing rules, which is a worse failure than a verb that is
|
|
24
25
|
* merely flavour.
|
|
25
26
|
*
|
|
26
|
-
* The surprise clause on p.164 is
|
|
27
|
-
*
|
|
28
|
-
*
|
|
27
|
+
* The surprise clause on p.164 is modelled since the Combat Turn landed
|
|
28
|
+
* (utilities/combat-turn.ts): a character who failed the Surprise Test
|
|
29
|
+
* cannot drop prone until their next Action Phase. Both postures bill
|
|
30
|
+
* their action through action-cost.ts -- prone the phase's Free, standing
|
|
31
|
+
* a Simple.
|
|
29
32
|
*/
|
|
30
33
|
export class PostureCommand extends Command {
|
|
31
34
|
static verb = 'sit';
|
|
@@ -59,14 +62,22 @@ export class PostureCommand extends Command {
|
|
|
59
62
|
if (want === 'standing')
|
|
60
63
|
return this.standUp(here);
|
|
61
64
|
if (want === 'prone') {
|
|
62
|
-
// FREE ACTION (p.164) --
|
|
63
|
-
// is worth doing the moment rounds start coming
|
|
65
|
+
// FREE ACTION (p.164) -- the phase's one Free Action, which is
|
|
66
|
+
// exactly why it is worth doing the moment rounds start coming
|
|
67
|
+
// in. "As long as he is not surprised": the encounter knows.
|
|
68
|
+
const enc = this.scene.encounterFor?.(actor);
|
|
69
|
+
if (enc?.isSurprised(actor)) {
|
|
70
|
+
return `You're caught flat-footed -- you don't even get to hit the dirt (SR5 p.164, p.192).`;
|
|
71
|
+
}
|
|
72
|
+
const bill = billAction(this.scene, actor, 'free', 'Drop Prone');
|
|
73
|
+
if (bill)
|
|
74
|
+
return bill;
|
|
64
75
|
actor.posture = 'prone';
|
|
65
76
|
actor.performAction('drops prone', where ?? actor.currentLocation.name);
|
|
66
77
|
this.scene.updateStatus?.();
|
|
67
78
|
return `You go flat${here}.`
|
|
68
79
|
+ `\n {green-fg}PRONE:{/green-fg} +2 harder to hit at range, but -2 to defend up close and -1 swinging from the floor.`
|
|
69
|
-
+ hint(` ("stand" to get back up -- a Simple Action.)`);
|
|
80
|
+
+ hint(` ("stand" to get back up -- a Simple Action.)`) + phaseHint(this.scene, actor);
|
|
70
81
|
}
|
|
71
82
|
actor.posture = 'sitting';
|
|
72
83
|
actor.performAction('sits down', where ?? actor.currentLocation.name);
|
|
@@ -76,6 +87,11 @@ export class PostureCommand extends Command {
|
|
|
76
87
|
/** STAND UP: a Simple Action, and a real test when you're hurt. */
|
|
77
88
|
standUp(here) {
|
|
78
89
|
const actor = this.actor;
|
|
90
|
+
// The SIMPLE ACTION (p.166) is spent on the attempt: a wounded
|
|
91
|
+
// character who fails the test has still used it trying.
|
|
92
|
+
const bill = billAction(this.scene, actor, 'simple', 'Stand Up');
|
|
93
|
+
if (bill)
|
|
94
|
+
return bill;
|
|
79
95
|
const wounded = actor.woundModifier < 0;
|
|
80
96
|
if (wounded) {
|
|
81
97
|
// p.166: "if the character is wounded and attempting to stand, he
|
|
@@ -95,7 +111,7 @@ export class PostureCommand extends Command {
|
|
|
95
111
|
actor.posture = 'standing';
|
|
96
112
|
actor.performAction('stands up', here.trim() || actor.currentLocation.name);
|
|
97
113
|
this.scene.updateStatus?.();
|
|
98
|
-
return wounded ? `You drag yourself upright${here}.` : `You get to your feet${here}
|
|
114
|
+
return (wounded ? `You drag yourself upright${here}.` : `You get to your feet${here}.`) + phaseHint(this.scene, actor);
|
|
99
115
|
}
|
|
100
116
|
}
|
|
101
117
|
/** "lie"/"lie down"/"prone" -- same command, different way in. */
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
import { Command } from './command.js';
|
|
2
2
|
import { Category } from '../types/shared/item-enum.js';
|
|
3
3
|
import { Item } from '../models/item.js';
|
|
4
|
+
import { billAction, encounterOf, phaseHint } from '../utilities/action-cost.js';
|
|
4
5
|
/**
|
|
5
6
|
* Refills the equipped firearm's magazine by consuming one
|
|
6
|
-
* Ammunition-category item from the pack.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Ammunition-category item from the pack. Every shot spends a round --
|
|
8
|
+
* see combat-exchange.ts.
|
|
9
|
+
*
|
|
10
|
+
* WHAT A RELOAD COSTS IN A COMBAT TURN (SR5 p.163 Reloading Weapons,
|
|
11
|
+
* p.165-167, RAG-checked 2026-09-06): a clip is Remove Clip (Simple) +
|
|
12
|
+
* Insert Clip (Simple) -- both of a phase's Simple Actions; every other
|
|
13
|
+
* feed (cylinder, break action, belt, drum, internal magazine, muzzle
|
|
14
|
+
* loader, speed loader) is a Complex Action. Either way it is the whole
|
|
15
|
+
* phase, so it is billed as one. The one discount canon gives: a
|
|
16
|
+
* smartgun linked to a smartlink ejects its clip as a Free Action
|
|
17
|
+
* (p.164 Eject Smartgun Clip), leaving only the Simple to seat the new
|
|
18
|
+
* one.
|
|
11
19
|
*/
|
|
12
20
|
export class ReloadCommand extends Command {
|
|
13
21
|
static verb = 'reload';
|
|
14
|
-
static description = 'Reload your equipped firearm -- consumes one Ammunition item from your pack.';
|
|
22
|
+
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).';
|
|
15
23
|
async execute(_args = []) {
|
|
16
24
|
const actor = this.actor;
|
|
17
25
|
if (this.scene.isHumanControlled(actor) && this.scene.isPlayerExchangeActive(actor.name)) {
|
|
@@ -21,15 +29,28 @@ export class ReloadCommand extends Command {
|
|
|
21
29
|
if (!weapon || !weapon.isFirearm()) {
|
|
22
30
|
return `You have no firearm equipped to reload.`;
|
|
23
31
|
}
|
|
24
|
-
if (weapon.ammo >=
|
|
25
|
-
return `The ${weapon.name} is already fully loaded (${weapon.ammo}/${
|
|
32
|
+
if (weapon.ammo >= weapon.magazineCapacity) {
|
|
33
|
+
return `The ${weapon.name} is already fully loaded (${weapon.ammo}/${weapon.magazineCapacity}).`;
|
|
26
34
|
}
|
|
27
35
|
// NON-LETHAL STANCE: stun rounds first if you carry any (gel,
|
|
28
36
|
// stick-n-shock -- p.434); otherwise whatever is in the pack.
|
|
29
37
|
const boxes = actor.inventory.getAllItems().filter(i => i.category === Category.Ammunition);
|
|
30
38
|
const ammo = (actor.autoStance === 'non-lethal' ? boxes.find(b => Item.isStunAmmo(b)) : undefined) ?? boxes[0];
|
|
31
39
|
if (!ammo) {
|
|
32
|
-
return `No ammunition in your pack -- the ${weapon.name} stays at ${weapon.ammo}/${
|
|
40
|
+
return `No ammunition in your pack -- the ${weapon.name} stays at ${weapon.ammo}/${weapon.magazineCapacity}. Find a spare clip.`;
|
|
41
|
+
}
|
|
42
|
+
// THE ACTION (see the class doc). A reload also needs the gun in
|
|
43
|
+
// hand: "insert a fresh clip into a READY firearm" (p.165).
|
|
44
|
+
if (encounterOf(this.scene, actor)) {
|
|
45
|
+
if (!actor.weaponDrawn) {
|
|
46
|
+
return `The ${weapon.name} is holstered -- "draw" it first (Ready Weapon, a Simple Action, p.165).`;
|
|
47
|
+
}
|
|
48
|
+
const smartEject = weapon.isSmartgun && actor.smartlinkTier() !== undefined;
|
|
49
|
+
const bill = smartEject
|
|
50
|
+
? (billAction(this.scene, actor, 'free', 'Eject Smartgun Clip') ?? billAction(this.scene, actor, 'simple', 'Insert Clip'))
|
|
51
|
+
: billAction(this.scene, actor, 'complex', weapon.reloadFeed === 'c' ? 'Remove Clip + Insert Clip' : 'Reload Weapon');
|
|
52
|
+
if (bill)
|
|
53
|
+
return bill;
|
|
33
54
|
}
|
|
34
55
|
actor.inventory.consumeOne(ammo.name);
|
|
35
56
|
weapon.reload(ammo.name);
|
|
@@ -37,7 +58,7 @@ export class ReloadCommand extends Command {
|
|
|
37
58
|
this.actor.performAction('reloads', weapon.name);
|
|
38
59
|
this.scene.updateInventory(this.scene.getPlayer().inventory);
|
|
39
60
|
this.scene.updateStatus();
|
|
40
|
-
return `You slap the ${ammo.name} home -- ${weapon.name} loaded, ${weapon.ammo}/${
|
|
61
|
+
return `You slap the ${ammo.name} home -- ${weapon.name} loaded, ${weapon.ammo}/${weapon.magazineCapacity}.${weapon.stunAmmo() ? ' Stun rounds: it drops people, it does not kill them.' : actor.autoStance === 'non-lethal' ? ' No stun rounds in the pack -- that gun is LETHAL until you find some.' : ''}${phaseHint(this.scene, actor)}`;
|
|
41
62
|
}
|
|
42
63
|
}
|
|
43
64
|
//# sourceMappingURL=reload.js.map
|
|
@@ -5,6 +5,7 @@ import { rollPool, formatRoll } from '../utilities/dice.js';
|
|
|
5
5
|
import { perceptionTest } from '../utilities/perception.js';
|
|
6
6
|
import { canReach, exitSpot, spotsActive, revealWithPerception, theSpot, sealedRouteTo, } from '../utilities/spots.js';
|
|
7
7
|
import { placeName } from '../utilities/log-style.js';
|
|
8
|
+
import { billAction } from '../utilities/action-cost.js';
|
|
8
9
|
/**
|
|
9
10
|
* Barrier rendering (2026-08-24 playtest: "I don't see THE THING I'm
|
|
10
11
|
* supposed to act on -- just a description... and I still see the
|
|
@@ -136,6 +137,11 @@ export class SearchCommand extends Command {
|
|
|
136
137
|
static description = 'Search for items in your area';
|
|
137
138
|
async execute(_args) {
|
|
138
139
|
const room = this.actor.currentLocation;
|
|
140
|
+
// OBSERVE IN DETAIL (SR5 p.165): the Perception Test below is the
|
|
141
|
+
// Simple Action the book names, inside a Combat Turn.
|
|
142
|
+
const bill = billAction(this.scene, this.actor, 'simple', 'Observe in Detail');
|
|
143
|
+
if (bill)
|
|
144
|
+
return bill;
|
|
139
145
|
// Searching is a PERCEPTION test now (Intuition + perception skill;
|
|
140
146
|
// unskilled defaults to Intuition - 1): no hits means the room keeps
|
|
141
147
|
// its secrets this pass -- retry freely, but until a roll lands the
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from './command.js';
|
|
2
2
|
import { rollPool, formatRoll } from '../utilities/dice.js';
|
|
3
3
|
import { hint } from '../utilities/hints.js';
|
|
4
|
+
import { billAction } from '../utilities/action-cost.js';
|
|
4
5
|
/**
|
|
5
6
|
* Conjuring (SR5e p.300-303): "summon <force>" calls a spirit -- an
|
|
6
7
|
* opposed Magic + conjuring test against the spirit's Force dice. NET
|
|
@@ -50,6 +51,10 @@ export class SummonCommand extends Command {
|
|
|
50
51
|
return `A Force ${force} spirit would wear YOU as the vessel -- twice your Magic (${actor.magic * 2}) is the hard ceiling.`;
|
|
51
52
|
}
|
|
52
53
|
const overcast = force > actor.magic;
|
|
54
|
+
// SUMMONING IS A COMPLEX ACTION (SR5 p.167) inside a Combat Turn.
|
|
55
|
+
const bill = billAction(this.scene, actor, 'complex', 'Summoning');
|
|
56
|
+
if (bill)
|
|
57
|
+
return bill;
|
|
53
58
|
// The opposed test (p.300): Magic + conjuring vs the spirit's Force.
|
|
54
59
|
const conjuring = actor.skillRating('conjuring');
|
|
55
60
|
const mine = rollPool(Math.max(1, actor.magic + (conjuring > 0 ? conjuring : -1) + actor.woundModifier - actor.sustainingPenalty));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from './command.js';
|
|
2
|
+
import { billAction } from '../utilities/action-cost.js';
|
|
2
3
|
/**
|
|
3
4
|
* Throwing your hands up, as a real mechanical act instead of just talk.
|
|
4
5
|
* The wound-aware AI guidance already nudges a losing NPC toward
|
|
@@ -27,6 +28,11 @@ export class SurrenderCommand extends Command {
|
|
|
27
28
|
if (actor.inExchange) {
|
|
28
29
|
return `Lead is flying -- wait for the exchange to resolve, then surrender in the lull.`;
|
|
29
30
|
}
|
|
31
|
+
// Hands up is a gesture and a phrase -- a Free Action (SR5 p.164
|
|
32
|
+
// Gesture / Speak) in a Combat Turn.
|
|
33
|
+
const bill = billAction(this.scene, actor, 'free', 'Surrender');
|
|
34
|
+
if (bill)
|
|
35
|
+
return bill;
|
|
30
36
|
actor.surrendered = true;
|
|
31
37
|
// Break the fight both ways: no parting shot for walking away from a
|
|
32
38
|
// surrender, and the opponent's own combat memory of you clears too.
|
|
@@ -5,6 +5,7 @@ import { fileReach } from '../utilities/planes.js';
|
|
|
5
5
|
import { canReach, ensureAtSpot, sealedRouteTo } from '../utilities/spots.js';
|
|
6
6
|
import { hint } from '../utilities/hints.js';
|
|
7
7
|
import { Category } from '../types/shared/item-enum.js';
|
|
8
|
+
import { billAction } from '../utilities/action-cost.js';
|
|
8
9
|
export class TakeCommand extends Command {
|
|
9
10
|
static verb = 'take';
|
|
10
11
|
static description = 'Pick up an item. "take all" sweeps everything here -- but only after you have searched the room.';
|
|
@@ -25,6 +26,10 @@ export class TakeCommand extends Command {
|
|
|
25
26
|
screen: this.screen, game: this.game,
|
|
26
27
|
}).execute();
|
|
27
28
|
}
|
|
29
|
+
// PICK UP OBJECT (SR5 p.165): a Simple Action in a Combat Turn.
|
|
30
|
+
const bill = billAction(this.scene, this.actor, 'simple', 'Pick Up Object');
|
|
31
|
+
if (bill)
|
|
32
|
+
return bill;
|
|
28
33
|
if (wanted === 'all' || wanted === 'everything') {
|
|
29
34
|
return this.takeAll();
|
|
30
35
|
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { Command } from './command.js';
|
|
2
2
|
import { dropActiveCall, CALL_ICON, CALL_COLOR } from '../utilities/comm-style.js';
|
|
3
|
+
import { billAction } from '../utilities/action-cost.js';
|
|
3
4
|
export class UnequipCommand extends Command {
|
|
4
5
|
static verb = 'unequip';
|
|
5
6
|
static description = 'Unequip an item.';
|
|
6
7
|
async execute(args) {
|
|
8
|
+
// Taking gear off is a Simple Action's worth of hands (SR5 p.165
|
|
9
|
+
// Pick Up/Put Down Object) inside a Combat Turn.
|
|
10
|
+
const bill = billAction(this.scene, this.actor, 'simple', 'Unequip');
|
|
11
|
+
if (bill)
|
|
12
|
+
return bill;
|
|
7
13
|
const slotName = args.join(' ').toLowerCase();
|
|
8
14
|
// TAKING THE LINK OFF HANGS UP THE CALL (MgrjzDYTzY8sJdvkT: "when I
|
|
9
15
|
// unequipped my commlink, I went offline, but the call with deditri
|