@maka/maka-cli 5.5.0 → 5.7.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 +2 -2
- package/bundle/typescript/src/commands/_index.d.ts +4 -0
- package/bundle/typescript/src/commands/_index.js +4 -0
- package/bundle/typescript/src/commands/_index.js.map +1 -1
- package/bundle/typescript/src/commands/debug.js +1 -7
- package/bundle/typescript/src/commands/debug.js.map +1 -1
- package/bundle/typescript/src/commands/deploy/ubuntu.sub.cmd.js +17 -1
- package/bundle/typescript/src/commands/deploy/ubuntu.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/commands/env/_index.d.ts +3 -0
- package/bundle/typescript/src/commands/env/_index.js +4 -0
- package/bundle/typescript/src/commands/env/_index.js.map +1 -0
- package/bundle/typescript/src/commands/env/env.command.d.ts +3 -0
- package/bundle/typescript/src/commands/env/env.command.js +22 -0
- package/bundle/typescript/src/commands/env/env.command.js.map +1 -0
- package/bundle/typescript/src/commands/env/get.sub.cmd.d.ts +1 -0
- package/bundle/typescript/src/commands/env/get.sub.cmd.js +46 -0
- package/bundle/typescript/src/commands/env/get.sub.cmd.js.map +1 -0
- package/bundle/typescript/src/commands/env/prompt-for-env.d.ts +10 -0
- package/bundle/typescript/src/commands/env/prompt-for-env.js +27 -0
- package/bundle/typescript/src/commands/env/prompt-for-env.js.map +1 -0
- package/bundle/typescript/src/commands/env/set.sub.cmd.d.ts +1 -0
- package/bundle/typescript/src/commands/env/set.sub.cmd.js +59 -0
- package/bundle/typescript/src/commands/env/set.sub.cmd.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +79 -0
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/augmentations.d.ts +31 -0
- package/bundle/typescript/src/commands/game/sideQuest/augmentations.js +125 -0
- package/bundle/typescript/src/commands/game/sideQuest/augmentations.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/advance.d.ts +17 -7
- package/bundle/typescript/src/commands/game/sideQuest/commands/advance.js +113 -15
- package/bundle/typescript/src/commands/game/sideQuest/commands/advance.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/ask.d.ts +37 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js +193 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js +3 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +10 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.d.ts +3 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js +12 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.js +7 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/buy.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/buy.js +97 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/buy.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.d.ts +21 -10
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js +41 -27
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.d.ts +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +63 -7
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.d.ts +35 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +277 -44
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.d.ts +13 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.js +37 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drink.d.ts +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drink.js +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drink.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/drop.js +7 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/drop.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/eat.d.ts +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/eat.js +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/eat.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js +3 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/give.js +101 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/give.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.d.ts +4 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +15 -11
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +3 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js +58 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/initiate.d.ts +26 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/initiate.js +125 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/initiate.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/install.d.ts +21 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/install.js +125 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/install.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/jump.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/jump.js +62 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/jump.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/lifestyle.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/lifestyle.js +76 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/lifestyle.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/lock.js +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/lock.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/pay.d.ts +13 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pay.js +39 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/pay.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.js +5 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js +5 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/quality-trade.d.ts +21 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/quality-trade.js +121 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/quality-trade.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +60 -12
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js +27 -10
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +6 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sell.d.ts +17 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/sell.js +125 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/sell.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +36 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sneak.js +9 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sneak.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js +27 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +8 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/treat.d.ts +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/treat.js +67 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/treat.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js +7 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.d.ts +29 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.js +118 -11
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/work.d.ts +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/work.js +68 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/work.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/item-factory.js +2 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/item-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.d.ts +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +131 -4
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.d.ts +6 -2
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js +10 -6
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +115 -0
- package/bundle/typescript/src/commands/game/sideQuest/game.js +464 -25
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/item.d.ts +28 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/item.js +111 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/item.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +70 -7
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +109 -4
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +423 -39
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.d.ts +6 -6
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js +38 -14
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.d.ts +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +41 -8
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/qualities.d.ts +44 -0
- package/bundle/typescript/src/commands/game/sideQuest/qualities.js +155 -0
- package/bundle/typescript/src/commands/game/sideQuest/qualities.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +274 -5
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/item-seed.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/scene-seed.d.ts +17 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.d.ts +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/ui.js +50 -3
- package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.d.ts +13 -6
- package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js +25 -14
- package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +100 -10
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/commerce.d.ts +56 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/commerce.js +144 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/commerce.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/outdoors.d.ts +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/outdoors.js +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/outdoors.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.d.ts +21 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +84 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/terminal-reset.d.ts +17 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/terminal-reset.js +23 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/terminal-reset.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +51 -15
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/commands/remote/reconfig.sub.cmd.js +27 -1
- package/bundle/typescript/src/commands/remote/reconfig.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/commands/run/run.command.js +1 -7
- package/bundle/typescript/src/commands/run/run.command.js.map +1 -1
- package/bundle/typescript/src/commands/testCmd/mocha.sub.cmd.js +1 -6
- package/bundle/typescript/src/commands/testCmd/mocha.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/generators/config.js +11 -10
- package/bundle/typescript/src/generators/config.js.map +1 -1
- package/bundle/typescript/src/templates/meteor/config/pm2.config.js +3 -3
- package/bundle/typescript/src/templates/meteor/project/gitignore +2 -1
- package/bundle/typescript/src/tool.d.ts +2 -0
- package/bundle/typescript/src/tool.js +3 -0
- package/bundle/typescript/src/tool.js.map +1 -1
- package/bundle/typescript/src/tools/aws/aws.class.js +6 -4
- package/bundle/typescript/src/tools/aws/aws.class.js.map +1 -1
- package/bundle/typescript/src/tools/dotenvx/dotenvx.class.d.ts +24 -0
- package/bundle/typescript/src/tools/dotenvx/dotenvx.class.js +66 -0
- package/bundle/typescript/src/tools/dotenvx/dotenvx.class.js.map +1 -0
- package/bundle/typescript/src/tools/ssh/ssh.class.js +1 -1
- package/bundle/typescript/src/tools/ssh/ssh.class.js.map +1 -1
- package/package.json +2 -2
|
@@ -3,6 +3,10 @@ import { Logger } from '../utilities/logger.js';
|
|
|
3
3
|
import { Inventory } from './inventory.js';
|
|
4
4
|
import { AbstractPlayer } from '../types/shared/abstracts.js';
|
|
5
5
|
export const STANCE_CYCLE = ['manual', 'guarded', 'aggressive'];
|
|
6
|
+
/** Meat and a jumped-in drone share the physical world. */
|
|
7
|
+
export function isPhysicalPlane(plane) {
|
|
8
|
+
return plane === 'meat' || plane === 'drone';
|
|
9
|
+
}
|
|
6
10
|
export const SKILL_NAMES = [
|
|
7
11
|
'firearms', 'blades', 'unarmed',
|
|
8
12
|
'sneaking', 'perception', 'athletics',
|
|
@@ -166,13 +170,28 @@ export class Player extends AbstractPlayer {
|
|
|
166
170
|
canPerceive(other) {
|
|
167
171
|
if (this.plane === other.plane)
|
|
168
172
|
return true;
|
|
169
|
-
|
|
173
|
+
// A drone is a physical machine in the meat world: meat sees it, its
|
|
174
|
+
// sensors see meat -- and combat between them is ordinary physics.
|
|
175
|
+
if (isPhysicalPlane(this.plane) && isPhysicalPlane(other.plane))
|
|
176
|
+
return true;
|
|
177
|
+
return this.plane === 'astral' && isPhysicalPlane(other.plane);
|
|
178
|
+
}
|
|
179
|
+
/** Whether these two can trade physical blows (see attack.ts targeting). */
|
|
180
|
+
sharesCombatPlane(other) {
|
|
181
|
+
return this.plane === other.plane
|
|
182
|
+
|| (isPhysicalPlane(this.plane) && isPhysicalPlane(other.plane));
|
|
170
183
|
}
|
|
171
184
|
// True while an AI-driven action is parked waiting for an in-flight
|
|
172
185
|
// exchange to settle (CombatExchange.waitForLull). One queued action per
|
|
173
186
|
// actor: a second colliding attempt is refused outright instead of
|
|
174
187
|
// stacking a burst of delayed attacks behind the same lull.
|
|
175
188
|
waitingForLull = false;
|
|
189
|
+
// Death-resolution claim (CombatExchange.resolveDeath): set
|
|
190
|
+
// SYNCHRONOUSLY by the first resolution to reach this actor, so a
|
|
191
|
+
// concurrent kill (autopilot + typed action racing -- a real session
|
|
192
|
+
// crashed on the second removeActor) skips cleanly instead of
|
|
193
|
+
// narrating a second death during the first one's narration beats.
|
|
194
|
+
deathClaimed = false;
|
|
176
195
|
// Combat attributes (see ICombatAttributes). Defaults make an unseeded
|
|
177
196
|
// actor a competent-but-mortal street-level runner; seeds can override
|
|
178
197
|
// per-NPC to make a hardened enforcer or a soft civilian.
|
|
@@ -225,8 +244,192 @@ export class Player extends AbstractPlayer {
|
|
|
225
244
|
this.resonance = config.combat?.resonance ?? 0;
|
|
226
245
|
this.adept = config.combat?.adept ?? false;
|
|
227
246
|
this.skills = { ...(config.combat?.skills ?? {}) };
|
|
247
|
+
// Essence bookkeeping needs the UNAUGMENTED talent to burn down from
|
|
248
|
+
// (see installAug) -- capture it before any 'ware lands.
|
|
249
|
+
this.baseMagic = this.magic;
|
|
250
|
+
this.baseResonance = this.resonance;
|
|
228
251
|
this._equipment = this.initializeEquipment();
|
|
229
252
|
}
|
|
253
|
+
// ==================== Augmentations (see augmentations.ts) ====================
|
|
254
|
+
/** Everyone is born whole. */
|
|
255
|
+
static ESSENCE_MAX = 6;
|
|
256
|
+
// What's left of the meat. Burned permanently by installAug; hitting 0
|
|
257
|
+
// is refused at the clinic -- there'd be nothing left to cut away.
|
|
258
|
+
essence = Player.ESSENCE_MAX;
|
|
259
|
+
// The unaugmented Magic/Resonance, from which essence loss burns down
|
|
260
|
+
// (SR5e: each full point of essence lost -- ROUNDED UP -- costs one).
|
|
261
|
+
// `magic`/`resonance` above stay the EFFECTIVE values every pool reads.
|
|
262
|
+
baseMagic = 0;
|
|
263
|
+
baseResonance = 0;
|
|
264
|
+
/** Installed 'ware, by catalog key (see AUGMENTATIONS). */
|
|
265
|
+
augmentations = new Set();
|
|
266
|
+
hasAug(key) {
|
|
267
|
+
return this.augmentations.has(key);
|
|
268
|
+
}
|
|
269
|
+
/** Full essence points lost, rounded up -- the toll Magic/Resonance pay. */
|
|
270
|
+
get essenceBurn() {
|
|
271
|
+
return Math.ceil(Player.ESSENCE_MAX - this.essence - 1e-9);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Burns the essence and records the 'ware. No gating here -- the clinic
|
|
275
|
+
* (commands/install.ts) owns the refusals (essence floor, conflicts,
|
|
276
|
+
* payment); archetype starting chrome installs through this too.
|
|
277
|
+
*/
|
|
278
|
+
installAug(aug) {
|
|
279
|
+
this.augmentations.add(aug.key);
|
|
280
|
+
// One-decimal arithmetic: 6 - 0.1 - 0.2 must read 5.7, not 5.6999....
|
|
281
|
+
this.essence = Math.round((this.essence - aug.essence) * 10) / 10;
|
|
282
|
+
const burn = this.essenceBurn;
|
|
283
|
+
this.magic = Math.max(0, this.baseMagic - burn);
|
|
284
|
+
this.resonance = Math.max(0, this.baseResonance - burn);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* The single tap every pool reads chrome through. Wired Reflexes tiers
|
|
288
|
+
* don't stack -- the higher tier wins (II supersedes I) -- but the
|
|
289
|
+
* synaptic-vs-wired conflict is the clinic's job, not this getter's.
|
|
290
|
+
*/
|
|
291
|
+
augBonus(kind) {
|
|
292
|
+
if (this.augmentations.size === 0)
|
|
293
|
+
return 0;
|
|
294
|
+
const has = (k) => this.augmentations.has(k);
|
|
295
|
+
const reflexes = (has('wired-reflexes-2') ? 2 : has('wired-reflexes-1') ? 1 : 0) + (has('synaptic-booster') ? 1 : 0);
|
|
296
|
+
switch (kind) {
|
|
297
|
+
case 'initiative': return reflexes;
|
|
298
|
+
case 'defense': return reflexes;
|
|
299
|
+
case 'attack': return has('muscle-toner') ? 1 : 0;
|
|
300
|
+
case 'firearms': return has('smartlink') ? 2 : 0;
|
|
301
|
+
case 'soak': return (has('bone-lacing') ? 1 : 0) + (has('dermal-plating') ? 2 : 0);
|
|
302
|
+
case 'unarmed-damage': return has('bone-lacing') ? 2 : 0;
|
|
303
|
+
case 'perception': return has('cybereyes') ? 2 : 0;
|
|
304
|
+
case 'matrix': return has('datajack') ? 1 : 0;
|
|
305
|
+
case 'piloting': return has('control-rig') ? 2 : 0;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// ==================== Qualities (see qualities.ts) ====================
|
|
309
|
+
// SR5e's traits (p.71-85), baked into the archetype at creation
|
|
310
|
+
// (outfitArchetype). Every entry in the catalog has a real mechanical
|
|
311
|
+
// hook; the ones that read as flat pool nudges all tap through
|
|
312
|
+
// qualityBonus below, the same single-switch pattern as augBonus.
|
|
313
|
+
/** Installed qualities, by catalog key (see QUALITIES). */
|
|
314
|
+
qualities = new Set();
|
|
315
|
+
hasQuality(key) {
|
|
316
|
+
return this.qualities.has(key);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Records a quality and applies its one-time creation effect. Lucky is
|
|
320
|
+
* the only quality that edits an attribute (RAW raises the Edge ceiling;
|
|
321
|
+
* with no attribute point-buy here, the point lands directly).
|
|
322
|
+
*/
|
|
323
|
+
addQuality(key) {
|
|
324
|
+
if (this.qualities.has(key))
|
|
325
|
+
return;
|
|
326
|
+
this.qualities.add(key);
|
|
327
|
+
if (key === 'lucky') {
|
|
328
|
+
this.edge += 1;
|
|
329
|
+
this.edgeRemaining += 1;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/** The single tap every pool reads qualities through (cf. augBonus). */
|
|
333
|
+
qualityBonus(kind) {
|
|
334
|
+
if (this.qualities.size === 0)
|
|
335
|
+
return 0;
|
|
336
|
+
const has = (k) => this.qualities.has(k);
|
|
337
|
+
switch (kind) {
|
|
338
|
+
case 'sneak': return (has('catlike') ? 2 : 0) + (has('distinctive-style') ? -2 : 0);
|
|
339
|
+
case 'athletics': return has('natural-athlete') ? 2 : 0;
|
|
340
|
+
case 'matrix': return has('simsense-vertigo') ? -2 : 0;
|
|
341
|
+
case 'hacking': return (has('analytical-mind') ? 2 : 0) + this.qualityBonus('matrix');
|
|
342
|
+
case 'piloting': return has('juryrigger') ? 2 : 0;
|
|
343
|
+
case 'soak': return has('toughness') ? 1 : 0;
|
|
344
|
+
case 'healing': return has('quick-healer') ? 2 : 0;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// First Impression (p.74): the first social play of each job rolls +2.
|
|
348
|
+
// Consumed like the edge boost and the liquid-courage die; re-armed
|
|
349
|
+
// with the per-job resets in refreshEdge.
|
|
350
|
+
_firstImpressionUsed = false;
|
|
351
|
+
/** Spends the first impression: +2 for the social roll happening NOW. */
|
|
352
|
+
consumeFirstImpression() {
|
|
353
|
+
if (!this.qualities.has('first-impression') || this._firstImpressionUsed)
|
|
354
|
+
return 0;
|
|
355
|
+
this._firstImpressionUsed = true;
|
|
356
|
+
return 2;
|
|
357
|
+
}
|
|
358
|
+
/** SINner tax (p.84-85): the cut every payout loses to having a file. */
|
|
359
|
+
get sinTaxRate() {
|
|
360
|
+
if (this.qualities.has('sinner-corp'))
|
|
361
|
+
return 0.20;
|
|
362
|
+
if (this.qualities.has('sinner-criminal') || this.qualities.has('sinner-national'))
|
|
363
|
+
return 0.15;
|
|
364
|
+
return 0;
|
|
365
|
+
}
|
|
366
|
+
// Combat Paralysis (p.80): armed by beginExchange when a FRESH fight
|
|
367
|
+
// opens, consumed by the initiative roll-off -- pool halved, then the
|
|
368
|
+
// freeze passes (subsequent exchanges of the same fight roll normally).
|
|
369
|
+
paralysisFreeze = false;
|
|
370
|
+
// ==================== Initiation & Submersion (SR5e p.324-326 / p.257-258) ====================
|
|
371
|
+
// The Awakened (and technomancer) endgame: karma-priced grades (10 +
|
|
372
|
+
// grade x3, see commands/initiate.ts) that lift the talent ceiling to
|
|
373
|
+
// 6 + grade and grant one METAMAGIC (or echo) each. Grade can never
|
|
374
|
+
// exceed the BASE talent (RAW). The picks live here as a Set so every
|
|
375
|
+
// pool can read them the same way it reads qualities and chrome.
|
|
376
|
+
initiationGrade = 0;
|
|
377
|
+
/** Metamagics (casters/adepts) and echoes (technomancers), by key. */
|
|
378
|
+
metamagics = new Set();
|
|
379
|
+
hasMetamagic(key) {
|
|
380
|
+
return this.metamagics.has(key);
|
|
381
|
+
}
|
|
382
|
+
// ==================== Reputation (SR5e p.367-368) ====================
|
|
383
|
+
// Three tracked scores. STREET CRED is earned karma made visible: one
|
|
384
|
+
// point per full 10 career karma (earned on the street -- creation
|
|
385
|
+
// karma doesn't count; the name starts unknown). NOTORIETY starts from
|
|
386
|
+
// the qualities (the book's own list) and grows from incidents --
|
|
387
|
+
// walking out on a job, cutting down someone mid-surrender -- each
|
|
388
|
+
// counted ONCE (RAW: never twice for the same incident). PUBLIC
|
|
389
|
+
// AWARENESS derives from both, plus a Distinctive Style everyone can
|
|
390
|
+
// describe to a Knight Errant sketch artist.
|
|
391
|
+
/** Total karma EARNED in play -- spending it doesn't touch this. */
|
|
392
|
+
careerKarma = 0;
|
|
393
|
+
// Each stain on the record, in the player's own fiction ("walked out
|
|
394
|
+
// on 'Tacoma Rain'"). The strings ARE the dedupe keys.
|
|
395
|
+
_notorietyReasons = [];
|
|
396
|
+
/** Records an incident once; false means this exact stain already stands. */
|
|
397
|
+
addNotoriety(reason) {
|
|
398
|
+
if (this._notorietyReasons.includes(reason))
|
|
399
|
+
return false;
|
|
400
|
+
this._notorietyReasons.push(reason);
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
get notorietyReasons() {
|
|
404
|
+
return this._notorietyReasons;
|
|
405
|
+
}
|
|
406
|
+
get streetCred() {
|
|
407
|
+
return Math.floor(this.careerKarma / 10);
|
|
408
|
+
}
|
|
409
|
+
/** The p.368 quality list, restricted to the qualities this game ships. */
|
|
410
|
+
get qualityNotoriety() {
|
|
411
|
+
const plus = ['bad-luck', 'combat-paralysis', 'sinner-criminal']
|
|
412
|
+
.filter(k => this.qualities.has(k)).length
|
|
413
|
+
+ [...this.qualities].filter(k => k.startsWith('incompetent-')).length;
|
|
414
|
+
const minus = ['first-impression', 'lucky'].filter(k => this.qualities.has(k)).length;
|
|
415
|
+
return plus - minus;
|
|
416
|
+
}
|
|
417
|
+
get notoriety() {
|
|
418
|
+
return Math.max(0, this.qualityNotoriety + this._notorietyReasons.length);
|
|
419
|
+
}
|
|
420
|
+
get publicAwareness() {
|
|
421
|
+
return Math.floor((this.streetCred + this.notoriety) / 3)
|
|
422
|
+
+ (this.qualities.has('distinctive-style') ? 1 : 0);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* The dice a NAME is worth in shadow-side social work (haggle,
|
|
426
|
+
* persuade): cred minus the stories that undercut it. RAW makes cred a
|
|
427
|
+
* social LIMIT bonus; with no limit system here, net-rep dice carry
|
|
428
|
+
* the same idea -- and notoriety eating cred keeps it self-balancing.
|
|
429
|
+
*/
|
|
430
|
+
get socialRep() {
|
|
431
|
+
return Math.max(0, this.streetCred - this.notoriety);
|
|
432
|
+
}
|
|
230
433
|
// ======================== Skills (see SkillName) ========================
|
|
231
434
|
/** Explicit per-skill ratings; anything absent resolves via skillRating(). */
|
|
232
435
|
skills = {};
|
|
@@ -241,6 +444,10 @@ export class Player extends AbstractPlayer {
|
|
|
241
444
|
* everything else is 0 (unskilled).
|
|
242
445
|
*/
|
|
243
446
|
skillRating(name) {
|
|
447
|
+
// Incompetent (p.81): the skill isn't low, it's ABSENT -- pinned to 0
|
|
448
|
+
// no matter what the sheet or the fallback would say.
|
|
449
|
+
if (this.qualities.has(`incompetent-${name}`))
|
|
450
|
+
return 0;
|
|
244
451
|
const explicit = this.skills[name];
|
|
245
452
|
if (typeof explicit === 'number')
|
|
246
453
|
return Math.max(0, explicit);
|
|
@@ -628,7 +835,22 @@ export class Player extends AbstractPlayer {
|
|
|
628
835
|
* at everything, including not getting shot.
|
|
629
836
|
*/
|
|
630
837
|
get woundModifier() {
|
|
631
|
-
|
|
838
|
+
const painEditor = this.augmentations.has('pain-editor');
|
|
839
|
+
// High Pain Tolerance (p.74): one box simply doesn't count toward
|
|
840
|
+
// modifiers -- taken off the physical track first, spilling to stun if
|
|
841
|
+
// there's nothing physical to ignore. RAW forbids stacking it with a
|
|
842
|
+
// pain editor, so the chrome wins when both are present.
|
|
843
|
+
const ignore = !painEditor && this.qualities.has('high-pain-tolerance') ? 1 : 0;
|
|
844
|
+
const dmg = Math.max(0, this._damageTaken - ignore);
|
|
845
|
+
const spill = Math.max(0, ignore - this._damageTaken);
|
|
846
|
+
const stun = Math.max(0, this._stunTaken - spill);
|
|
847
|
+
// Low Pain Tolerance (p.82): the -1 arrives every 2 boxes, not 3.
|
|
848
|
+
const step = this.qualities.has('low-pain-tolerance') ? 2 : 3;
|
|
849
|
+
const raw = Math.floor(dmg / step) + Math.floor(stun / step);
|
|
850
|
+
// A pain editor files the first wound tier under "later": the -1 never
|
|
851
|
+
// arrives, though everything past it still does.
|
|
852
|
+
const eased = painEditor ? Math.max(0, raw - 1) : raw;
|
|
853
|
+
return -eased;
|
|
632
854
|
}
|
|
633
855
|
/** Dead: the physical track is full. */
|
|
634
856
|
isDown() {
|
|
@@ -680,10 +902,17 @@ export class Player extends AbstractPlayer {
|
|
|
680
902
|
const filled = Math.min(max, this._damageTaken);
|
|
681
903
|
return `${'■'.repeat(filled)}${'□'.repeat(max - filled)} (${filled}/${max})`;
|
|
682
904
|
}
|
|
683
|
-
/**
|
|
905
|
+
/**
|
|
906
|
+
* Clears stun boxes; returns how many were actually recovered. FATIGUE
|
|
907
|
+
* boxes (see lockedFatigue) are untouchable until their need is met --
|
|
908
|
+
* SR5e p.172: fatigue damage cannot be healed while the condition
|
|
909
|
+
* causing it still exists. Eating/sleeping unlocks them (markFed /
|
|
910
|
+
* markSlept), after which they heal like any stun.
|
|
911
|
+
*/
|
|
684
912
|
healStun(boxes) {
|
|
685
913
|
const before = this._stunTaken;
|
|
686
|
-
|
|
914
|
+
const floor = Math.min(before, this.lockedFatigue);
|
|
915
|
+
this._stunTaken = Math.max(floor, before - Math.max(0, Math.floor(boxes)));
|
|
687
916
|
return before - this._stunTaken;
|
|
688
917
|
}
|
|
689
918
|
/** The stun track, same rendering as conditionSummary(). */
|
|
@@ -702,7 +931,14 @@ export class Player extends AbstractPlayer {
|
|
|
702
931
|
// Off the meat plane your gun is just a memory: Matrix combat runs on
|
|
703
932
|
// attack programs (Logic-scaled), astral combat on raw will given
|
|
704
933
|
// form (Magic-scaled). Hot-sim's deeper immersion sharpens the
|
|
705
|
-
// programs' bite.
|
|
934
|
+
// programs' bite. A jumped-in drone fights with its MOUNT (armed
|
|
935
|
+
// airframes only -- attack.ts refuses an unarmed recon frame).
|
|
936
|
+
if (this.plane === 'drone') {
|
|
937
|
+
const quality = this.riggedDrone?.qualityBonus ?? 0;
|
|
938
|
+
return this.droneArmed
|
|
939
|
+
? { name: `${this.riggedDrone?.name ?? 'drone'} weapon mount`, damage: 2 + quality, poolBonus: 0 }
|
|
940
|
+
: { name: 'no weapon mounts', damage: 0, poolBonus: 0 };
|
|
941
|
+
}
|
|
706
942
|
if (this.plane === 'matrix') {
|
|
707
943
|
const hot = this.simMode === 'hot' ? 1 : 0;
|
|
708
944
|
// The deck IS the arsenal: its quality feeds program damage the way
|
|
@@ -717,19 +953,21 @@ export class Player extends AbstractPlayer {
|
|
|
717
953
|
if (this.plane === 'astral') {
|
|
718
954
|
return { name: 'astral strike', damage: 2 + Math.floor(Math.max(1, this.magic) / 2), poolBonus: 0 };
|
|
719
955
|
}
|
|
720
|
-
// Muscle drives melee
|
|
721
|
-
//
|
|
722
|
-
//
|
|
723
|
-
//
|
|
724
|
-
//
|
|
956
|
+
// Muscle drives melee, and the weapon CLASS drives everything else
|
|
957
|
+
// now (RAW gear pass, see Item.rangedBase/meleeBase/armorPen): a
|
|
958
|
+
// holdout no longer hits like a shotgun, and a knife's bite comes
|
|
959
|
+
// partly from ignoring armor. Quality still rides on top. A HOLSTERED
|
|
960
|
+
// weapon counts for nothing -- until it's drawn (brandish, or the
|
|
961
|
+
// auto-draw when an exchange opens), your hands are empty.
|
|
725
962
|
const strengthBase = Math.floor(this.strength / 2);
|
|
726
963
|
const weapon = this.weaponDrawn ? this.getCarriedWeapon() : null;
|
|
727
964
|
if (!weapon) {
|
|
728
|
-
|
|
965
|
+
// Bone lacing turns the fists themselves into weapons.
|
|
966
|
+
return { name: 'bare fists', damage: 1 + strengthBase + this.augBonus('unarmed-damage'), poolBonus: 0, armorPen: 0 };
|
|
729
967
|
}
|
|
730
968
|
const bonus = weapon.qualityBonus;
|
|
731
|
-
const base = weapon.category === Category.MeleeWeapon ?
|
|
732
|
-
return { name: weapon.name, damage: base + bonus, poolBonus: bonus };
|
|
969
|
+
const base = weapon.category === Category.MeleeWeapon ? weapon.meleeBase + strengthBase : weapon.rangedBase;
|
|
970
|
+
return { name: weapon.name, damage: base + bonus, poolBonus: bonus, armorPen: weapon.armorPen };
|
|
733
971
|
}
|
|
734
972
|
/** The weapon riding in a hand slot, drawn or holstered. */
|
|
735
973
|
getCarriedWeapon() {
|
|
@@ -754,14 +992,28 @@ export class Player extends AbstractPlayer {
|
|
|
754
992
|
}
|
|
755
993
|
/** Total soak dice from every equipped armor-category item. */
|
|
756
994
|
getArmorValue() {
|
|
995
|
+
// RAW armor pass (p.436-437, compressed): the BODY piece carries the
|
|
996
|
+
// real protection (1 + quality, +1 for jacket/plate-class weight --
|
|
997
|
+
// RAW clothing 6 vs jacket 12), a helmet or shield adds its flat
|
|
998
|
+
// point, and trim (boots, gloves, arm/leg pieces) is flavor, not
|
|
999
|
+
// dice -- stacking four rated accessories used to out-armor the
|
|
1000
|
+
// jacket they hung off.
|
|
757
1001
|
let total = 0;
|
|
758
1002
|
for (const group of Object.values(this._equipment)) {
|
|
759
1003
|
if (!group)
|
|
760
1004
|
continue;
|
|
761
1005
|
for (const item of Object.values(group)) {
|
|
762
|
-
if (item
|
|
763
|
-
|
|
764
|
-
|
|
1006
|
+
if (!item || !Player.ARMOR_CATEGORIES.has(item.category))
|
|
1007
|
+
continue;
|
|
1008
|
+
if (item.category === Category.Helmet)
|
|
1009
|
+
total += 1;
|
|
1010
|
+
else if (item.category === Category.Shield)
|
|
1011
|
+
total += 2;
|
|
1012
|
+
else if (item.category === Category.Boots || item.category === Category.Gloves
|
|
1013
|
+
|| item.category === Category.ArmArmor || item.category === Category.LegArmor)
|
|
1014
|
+
total += 0;
|
|
1015
|
+
else
|
|
1016
|
+
total += 1 + item.qualityBonus + (item.isHeavyArmor ? 1 : 0);
|
|
765
1017
|
}
|
|
766
1018
|
}
|
|
767
1019
|
return total;
|
|
@@ -769,21 +1021,33 @@ export class Player extends AbstractPlayer {
|
|
|
769
1021
|
getAttackPool() {
|
|
770
1022
|
// Plane decides which attribute drives the fight: meat is muscle
|
|
771
1023
|
// (Agility), the Matrix is mind (Logic, hot-sim +2), the astral is
|
|
772
|
-
// will (Willpower + half Magic)
|
|
1024
|
+
// will (Willpower + half Magic), and a jumped-in drone is GUNNERY --
|
|
1025
|
+
// Logic + piloting through the control rig (its +2 rides
|
|
1026
|
+
// augBonus('piloting')), hot-sim +1 (p.266: matrix bonuses apply to
|
|
1027
|
+
// vehicle actions). The SKILL is per-armament now (see
|
|
773
1028
|
// activeCombatSkill / skillRating): firearms, blades, or unarmed in
|
|
774
1029
|
// the meat; hacking is cybercombat; astral strikes ride unarmed (will
|
|
775
1030
|
// given form). Wounds and sustaining drag everywhere.
|
|
1031
|
+
if (this.plane === 'drone') {
|
|
1032
|
+
const hot = this.simMode === 'hot' ? 1 : 0;
|
|
1033
|
+
return Math.max(0, this.logic + this.skillRating('piloting') + this.augBonus('piloting') + this.qualityBonus('piloting') + hot + this.woundModifier - this.sustainingPenalty);
|
|
1034
|
+
}
|
|
776
1035
|
if (this.plane === 'matrix') {
|
|
777
1036
|
const hot = this.simMode === 'hot' ? 2 : 0;
|
|
778
|
-
|
|
1037
|
+
// Attack Upgrade echo (p.258): +1 on the offensive matrix pool.
|
|
1038
|
+
return Math.max(0, this.logic + this.skillRating('hacking') + Math.floor(this.resonance / 2) + hot + this.augBonus('matrix') + this.qualityBonus('hacking') + (this.hasMetamagic('attack-upgrade') ? 1 : 0) + this.woundModifier - this.sustainingPenalty);
|
|
779
1039
|
}
|
|
780
1040
|
if (this.plane === 'astral') {
|
|
781
1041
|
return Math.max(0, this.willpower + Math.floor(this.magic / 2) + this.skillRating('unarmed') + this.woundModifier - this.sustainingPenalty);
|
|
782
1042
|
}
|
|
783
1043
|
// An adept's magic lives in their hands: half of it (rounded down)
|
|
784
1044
|
// rides every meat-plane strike -- killing hands, improved reflexes.
|
|
785
|
-
|
|
786
|
-
|
|
1045
|
+
// Chrome rides here too: muscle toner on every swing, the smartlink
|
|
1046
|
+
// only when the drawn armament is actually a firearm.
|
|
1047
|
+
// Power Point (initiation, adepts): one more point of magic-as-muscle.
|
|
1048
|
+
const adeptBonus = this.adept ? Math.floor(this.magic / 2) + (this.hasMetamagic('power-point') ? 1 : 0) : 0;
|
|
1049
|
+
const smartlink = this.activeCombatSkill() === 'firearms' && this.weaponDrawn ? this.augBonus('firearms') : 0;
|
|
1050
|
+
return Math.max(0, this.agility + this.skillRating(this.activeCombatSkill()) + adeptBonus + this.getWeaponProfile().poolBonus + this.augBonus('attack') + smartlink + this.woundModifier - this.sustainingPenalty);
|
|
787
1051
|
}
|
|
788
1052
|
/**
|
|
789
1053
|
* The subdual pool: bare-hands work regardless of what's in the other
|
|
@@ -791,34 +1055,44 @@ export class Player extends AbstractPlayer {
|
|
|
791
1055
|
* See subdue.ts.
|
|
792
1056
|
*/
|
|
793
1057
|
getUnarmedAttackPool() {
|
|
794
|
-
const adeptBonus = this.adept ? Math.floor(this.magic / 2) : 0;
|
|
795
|
-
return Math.max(0, this.agility + this.skillRating('unarmed') + adeptBonus + this.woundModifier - this.sustainingPenalty);
|
|
1058
|
+
const adeptBonus = this.adept ? Math.floor(this.magic / 2) + (this.hasMetamagic('power-point') ? 1 : 0) : 0;
|
|
1059
|
+
return Math.max(0, this.agility + this.skillRating('unarmed') + adeptBonus + this.augBonus('attack') + this.woundModifier - this.sustainingPenalty);
|
|
796
1060
|
}
|
|
797
1061
|
getDefensePool() {
|
|
798
1062
|
// Reaction plus HALF intuition (rounded down) -- full SR
|
|
799
1063
|
// Reaction+Intuition turned text-paced exchanges into whiff-fests, so
|
|
800
1064
|
// gut feel contributes at half weight. Intuition 3 (the default) adds
|
|
801
1065
|
// +1 over the old reaction-only pool. Off-plane variants swap the
|
|
802
|
-
// reflex attribute but keep the same shape.
|
|
1066
|
+
// reflex attribute but keep the same shape. Jumped-in: the rigger's
|
|
1067
|
+
// own reflexes drive the airframe, control rig sharpening handling.
|
|
1068
|
+
if (this.plane === 'drone') {
|
|
1069
|
+
return Math.max(0, this.reaction + Math.floor(this.intuition / 2) + this.augBonus('piloting') + this.qualityBonus('piloting') + this.woundModifier - this.sustainingPenalty);
|
|
1070
|
+
}
|
|
803
1071
|
if (this.plane === 'matrix') {
|
|
804
1072
|
const hot = this.simMode === 'hot' ? 1 : 0;
|
|
805
|
-
return Math.max(0, this.intuition + Math.floor(this.logic / 2) + Math.floor(this.resonance / 2) + hot + this.woundModifier - this.sustainingPenalty);
|
|
1073
|
+
return Math.max(0, this.intuition + Math.floor(this.logic / 2) + Math.floor(this.resonance / 2) + hot + this.augBonus('matrix') + this.qualityBonus('matrix') + this.woundModifier - this.sustainingPenalty);
|
|
806
1074
|
}
|
|
807
1075
|
if (this.plane === 'astral') {
|
|
808
1076
|
return Math.max(0, this.intuition + Math.floor(this.magic / 2) + this.woundModifier - this.sustainingPenalty);
|
|
809
1077
|
}
|
|
810
1078
|
// Combat sense: the adept's magic reads the fight before it happens.
|
|
1079
|
+
// Wired reflexes read it faster still.
|
|
811
1080
|
const adeptBonus = this.adept ? Math.floor(this.magic / 2) : 0;
|
|
812
|
-
return Math.max(0, this.reaction + Math.floor(this.intuition / 2) + adeptBonus + this.woundModifier - this.sustainingPenalty);
|
|
1081
|
+
return Math.max(0, this.reaction + Math.floor(this.intuition / 2) + adeptBonus + this.augBonus('defense') + this.woundModifier - this.sustainingPenalty);
|
|
813
1082
|
}
|
|
814
1083
|
getSoakPool() {
|
|
815
1084
|
// Matrix: firewall -- Logic plus the DECK's quality (your deck is your
|
|
816
1085
|
// armor); a technomancer's firewall is Willpower plus half resonance
|
|
817
1086
|
// (their mind guards itself). Astral: pure Willpower; worn armor is
|
|
818
|
-
// meat left behind.
|
|
1087
|
+
// meat left behind. Drone: the HULL soaks -- airframe body scaled by
|
|
1088
|
+
// build quality; the rider's armor is streets away with their body.
|
|
1089
|
+
if (this.plane === 'drone') {
|
|
1090
|
+
return Math.max(0, 4 + 2 * (this.riggedDrone?.qualityBonus ?? 0));
|
|
1091
|
+
}
|
|
819
1092
|
if (this.plane === 'matrix') {
|
|
820
1093
|
if (this.isTechnomancer()) {
|
|
821
|
-
|
|
1094
|
+
// Firewall Upgrade echo (p.258): the living persona hardens.
|
|
1095
|
+
return Math.max(0, this.willpower + Math.floor(this.resonance / 2) + (this.hasMetamagic('firewall-upgrade') ? 1 : 0));
|
|
822
1096
|
}
|
|
823
1097
|
const deck = this.activeDeck ?? this.getCyberdeck();
|
|
824
1098
|
return Math.max(0, this.logic + (deck?.qualityBonus ?? 0));
|
|
@@ -826,7 +1100,7 @@ export class Player extends AbstractPlayer {
|
|
|
826
1100
|
if (this.plane === 'astral') {
|
|
827
1101
|
return Math.max(0, this.willpower);
|
|
828
1102
|
}
|
|
829
|
-
return Math.max(0, this.body + this.getArmorValue() + this._sustainedArmor);
|
|
1103
|
+
return Math.max(0, this.body + this.getArmorValue() + this._sustainedArmor + this.augBonus('soak') + this.qualityBonus('soak'));
|
|
830
1104
|
}
|
|
831
1105
|
/**
|
|
832
1106
|
* Initiative: Reaction + Intuition (straight SR), plus the AR edge from
|
|
@@ -838,18 +1112,34 @@ export class Player extends AbstractPlayer {
|
|
|
838
1112
|
getInitiativePool() {
|
|
839
1113
|
// Matrix speed is thought-speed: Logic + Intuition, hot-sim +2 (the
|
|
840
1114
|
// whole point of running hot). Astral forms move at the speed of
|
|
841
|
-
// intention: Intuition doubled.
|
|
1115
|
+
// intention: Intuition doubled. Jumped-in rides VR reflexes (p.266:
|
|
1116
|
+
// cold +2, hot +3 -- the engine's flattened take on the extra dice).
|
|
1117
|
+
//
|
|
1118
|
+
// NO sustaining penalty here (RAW spell pass): initiative is a score,
|
|
1119
|
+
// not a TEST -- holding a spell slows what you DO, never when you get
|
|
1120
|
+
// to do it. (This also lets Increase Reflexes actually work: its +2
|
|
1121
|
+
// used to be eaten whole by its own -2 sustaining burden.) Wounds
|
|
1122
|
+
// still slow the draw -- pain is not a spell.
|
|
1123
|
+
if (this.plane === 'drone') {
|
|
1124
|
+
const vr = this.simMode === 'hot' ? 3 : 2;
|
|
1125
|
+
return Math.max(0, this.reaction + this.intuition + vr + this.woundModifier);
|
|
1126
|
+
}
|
|
842
1127
|
if (this.plane === 'matrix') {
|
|
843
|
-
|
|
844
|
-
|
|
1128
|
+
// Overclocking echo (p.258): +1 while running hot -- the living
|
|
1129
|
+
// persona accelerated past spec.
|
|
1130
|
+
const hot = this.simMode === 'hot' ? 2 + (this.hasMetamagic('overclocking') ? 1 : 0) : 0;
|
|
1131
|
+
return Math.max(0, this.logic + this.intuition + Math.floor(this.resonance / 2) + hot + this.augBonus('matrix') + this.qualityBonus('matrix') + this.woundModifier);
|
|
845
1132
|
}
|
|
846
1133
|
if (this.plane === 'astral') {
|
|
847
|
-
return Math.max(0, this.intuition * 2 + this.woundModifier
|
|
1134
|
+
return Math.max(0, this.intuition * 2 + this.woundModifier);
|
|
848
1135
|
}
|
|
849
1136
|
const link = this._equipment.head?.commlink;
|
|
850
1137
|
// AR needs a link that's on you, broadcasting, and not hacked dark.
|
|
851
1138
|
const arBonus = link && !this.runningSilent && !this.arOffline ? link.qualityBonus : 0;
|
|
852
|
-
|
|
1139
|
+
// Increase Reflexes (sustained, cast.ts): the spellwork equivalent of
|
|
1140
|
+
// wired reflexes, for as long as the caster holds it.
|
|
1141
|
+
const spellReflexes = this.sustainedReflexes ? 2 : 0;
|
|
1142
|
+
return Math.max(0, this.reaction + this.intuition + arBonus + this.augBonus('initiative') + spellReflexes + this.woundModifier);
|
|
853
1143
|
}
|
|
854
1144
|
/**
|
|
855
1145
|
* If an edge boost is armed (see commands/edge.ts), consume it: returns
|
|
@@ -863,11 +1153,60 @@ export class Player extends AbstractPlayer {
|
|
|
863
1153
|
this.edgeBoostArmed = false;
|
|
864
1154
|
return this.edge;
|
|
865
1155
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
1156
|
+
// A comfortable home sharpens the luck: Middle+ lifestyles bank a bonus
|
|
1157
|
+
// edge each job (set by Game from the active tier; 0 for NPCs).
|
|
1158
|
+
lifestyleEdgeBonus = 0;
|
|
1159
|
+
// ==================== Needs (SR5e p.56 / p.172) ====================
|
|
1160
|
+
// Edge comes back from "a fulfilling meal and a good night's sleep" --
|
|
1161
|
+
// so eating (use.ts) and sleeping (rest.ts, at a Residence) are tracked
|
|
1162
|
+
// per JOB-CYCLE and cashed in at departure (Game.applyDepartureNeeds).
|
|
1163
|
+
// Ignore a need across multiple cycles and RAW fatigue stacks: 1S, 2S,
|
|
1164
|
+
// 3S... resisted Body + Willpower -- and fatigue stun is LOCKED (won't
|
|
1165
|
+
// heal by rest, stim, food's own recovery, or the doc) until the need
|
|
1166
|
+
// that caused it is finally met.
|
|
1167
|
+
// Sessions start fresh off your own cot: fed, slept, sharp.
|
|
1168
|
+
ateThisCycle = true;
|
|
1169
|
+
sleptThisCycle = true;
|
|
1170
|
+
hungerStreak = 0;
|
|
1171
|
+
sleepStreak = 0;
|
|
1172
|
+
_hungerFatigue = 0;
|
|
1173
|
+
_sleepFatigue = 0;
|
|
1174
|
+
/** Stun boxes no recovery can touch until their need is met. */
|
|
1175
|
+
get lockedFatigue() {
|
|
1176
|
+
return this._hungerFatigue + this._sleepFatigue;
|
|
1177
|
+
}
|
|
1178
|
+
/** A real meal: hunger resets and hunger-fatigue unlocks (heals normally now). */
|
|
1179
|
+
markFed() {
|
|
1180
|
+
this.ateThisCycle = true;
|
|
1181
|
+
this.hungerStreak = 0;
|
|
1182
|
+
this._hungerFatigue = 0;
|
|
1183
|
+
}
|
|
1184
|
+
/** A real sleep (a bed, not a jobsite corner): sleep debt resets and unlocks. */
|
|
1185
|
+
markSlept() {
|
|
1186
|
+
this.sleptThisCycle = true;
|
|
1187
|
+
this.sleepStreak = 0;
|
|
1188
|
+
this._sleepFatigue = 0;
|
|
1189
|
+
}
|
|
1190
|
+
/** Records freshly-taken stun as need-locked fatigue. */
|
|
1191
|
+
addLockedFatigue(kind, boxes) {
|
|
1192
|
+
if (kind === 'hunger')
|
|
1193
|
+
this._hungerFatigue += Math.max(0, boxes);
|
|
1194
|
+
else
|
|
1195
|
+
this._sleepFatigue += Math.max(0, boxes);
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* New job bookkeeping -- the boost and the once-per-job haggle always
|
|
1199
|
+
* reset, but the edge POOL only refills when the runner left fed and
|
|
1200
|
+
* slept (SR5e p.56: a fulfilling meal and a good night's sleep).
|
|
1201
|
+
*/
|
|
1202
|
+
refreshEdge(wellRested = true) {
|
|
1203
|
+
if (wellRested) {
|
|
1204
|
+
this.edgeRemaining = this.edge + this.lifestyleEdgeBonus;
|
|
1205
|
+
}
|
|
869
1206
|
this.edgeBoostArmed = false;
|
|
870
1207
|
this.haggledThisJob = false;
|
|
1208
|
+
// A new job is a new room to read: First Impression re-arms (p.74).
|
|
1209
|
+
this._firstImpressionUsed = false;
|
|
871
1210
|
}
|
|
872
1211
|
// ======================== Magic (see commands/cast.ts) ========================
|
|
873
1212
|
// Two roads to casting. GEAR-DRIVEN: what a mundane can cast comes from
|
|
@@ -900,11 +1239,25 @@ export class Player extends AbstractPlayer {
|
|
|
900
1239
|
setSustainedArmor(bonus) {
|
|
901
1240
|
this._sustainedArmor = Math.max(0, Math.floor(bonus));
|
|
902
1241
|
}
|
|
1242
|
+
// The other sustained spells (RAW gear/spell pass): invisibility bends
|
|
1243
|
+
// light around the bearer (+3 on sneak contests, npc.ts), increase
|
|
1244
|
+
// reflexes runs the nerves hot (+2 initiative). Each holds the standard
|
|
1245
|
+
// -2 sustaining burden; cast again to release (cast.ts).
|
|
1246
|
+
sustainedInvisibility = false;
|
|
1247
|
+
sustainedReflexes = false;
|
|
903
1248
|
get sustainingPenalty() {
|
|
904
1249
|
// Each sustained effect costs -2 dice across the board: the armor
|
|
905
|
-
// spell, and open astral senses (see
|
|
906
|
-
// -- holding
|
|
907
|
-
|
|
1250
|
+
// spell, invisibility, hot reflexes, and open astral senses (see
|
|
1251
|
+
// commands/assense.ts). They stack -- holding two is walking around
|
|
1252
|
+
// half-blind. Dumpshock disorientation (p.229) rides the same rail.
|
|
1253
|
+
// QUICKENING (metamagic, p.326 adapted): the armor spell anchors into
|
|
1254
|
+
// the world and sustains itself -- its -2 simply never bills.
|
|
1255
|
+
const armorCost = this._sustainedArmor > 0 && !this.hasMetamagic('quickening') ? 2 : 0;
|
|
1256
|
+
return armorCost
|
|
1257
|
+
+ (this.sustainedInvisibility ? 2 : 0)
|
|
1258
|
+
+ (this.sustainedReflexes ? 2 : 0)
|
|
1259
|
+
+ (this.astralPerceiving ? 2 : 0)
|
|
1260
|
+
+ (this.dumpshocked ? 2 : 0);
|
|
908
1261
|
}
|
|
909
1262
|
// Astral perception (see commands/assense.ts): senses held OPEN as a
|
|
910
1263
|
// sustained state -- passive aura reads, at -2 dice to everything else.
|
|
@@ -915,9 +1268,40 @@ export class Player extends AbstractPlayer {
|
|
|
915
1268
|
// Sneaking-vs-Perception rolls per witness (NPC.see). Broken by any
|
|
916
1269
|
// loud act -- attacking, casting, speaking, brandishing, calling.
|
|
917
1270
|
sneaking = false;
|
|
1271
|
+
// ==================== Jumped-in rigging (commands/jump.ts) ====================
|
|
1272
|
+
// The drone currently being ridden while plane === 'drone': the item
|
|
1273
|
+
// whose hull track (Item.droneDamage) eats what would hit the rider,
|
|
1274
|
+
// whose quality feeds the drone pools, and whose destruction is a
|
|
1275
|
+
// DUMPSHOCK (see planes.ts leaveDrone).
|
|
1276
|
+
riggedDrone;
|
|
1277
|
+
/** Whether the ridden drone carries a weapon mount (name/description duck-typing). */
|
|
1278
|
+
get droneArmed() {
|
|
1279
|
+
const d = this.riggedDrone;
|
|
1280
|
+
if (!d)
|
|
1281
|
+
return false;
|
|
1282
|
+
return /armed|combat|gun|roto|assault|sentry|weapon/i.test(`${d.name} ${d.description ?? ''}`);
|
|
1283
|
+
}
|
|
1284
|
+
// Dumpshock disorientation (SR5e p.229): -2 on everything for
|
|
1285
|
+
// (10 - Willpower) minutes after a forced dump, wall-clock -- the
|
|
1286
|
+
// penalty rides sustainingPenalty so every pool already feels it.
|
|
1287
|
+
dumpshockedUntil = 0;
|
|
1288
|
+
get dumpshocked() {
|
|
1289
|
+
return Date.now() < this.dumpshockedUntil;
|
|
1290
|
+
}
|
|
918
1291
|
// One haggle per job (see commands/haggle.ts); reset with the edge pool
|
|
919
1292
|
// on each new job (refreshEdge).
|
|
920
1293
|
haggledThisJob = false;
|
|
1294
|
+
// A drink in you (see use.ts): +1 die on the next social roll --
|
|
1295
|
+
// persuade, haggle, bluff -- then it's gone. A second drink before it's
|
|
1296
|
+
// spent doesn't stack, it just costs a stun box ("one too many").
|
|
1297
|
+
liquidCourage = false;
|
|
1298
|
+
/** Spends the drink: returns its bonus die for the roll happening NOW. */
|
|
1299
|
+
consumeLiquidCourage() {
|
|
1300
|
+
if (!this.liquidCourage)
|
|
1301
|
+
return 0;
|
|
1302
|
+
this.liquidCourage = false;
|
|
1303
|
+
return 1;
|
|
1304
|
+
}
|
|
921
1305
|
// Where this actor just came from and the direction BACK -- the HUD's
|
|
922
1306
|
// breadcrumb (see game.ts updateStatus / go.ts). Big scenes (the hub's
|
|
923
1307
|
// 18 rooms) made "which way did I come in" a real question. Cleared on
|