@maka/maka-cli 5.120.0 → 5.122.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/archetypes.d.ts +72 -0
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +300 -61
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/aspects.js +11 -1
- package/bundle/typescript/src/commands/game/sideQuest/aspects.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/augmentations.d.ts +33 -0
- package/bundle/typescript/src/commands/game/sideQuest/augmentations.js +85 -0
- package/bundle/typescript/src/commands/game/sideQuest/augmentations.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js +2 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +31 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/buy.js +8 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/buy.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.d.ts +10 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js +17 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/catalog.d.ts +0 -7
- package/bundle/typescript/src/commands/game/sideQuest/commands/catalog.js +39 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/catalog.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js +31 -5
- package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/fit.d.ts +29 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/fit.js +85 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/fit.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.d.ts +16 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +273 -28
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js +4 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +16 -5
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/mark.d.ts +19 -10
- package/bundle/typescript/src/commands/game/sideQuest/commands/mark.js +43 -227
- package/bundle/typescript/src/commands/game/sideQuest/commands/mark.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/move.js +31 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/move.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/pan.js +3 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/pan.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.d.ts +34 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js +81 -12
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +5 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sell.js +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/sell.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js +4 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/stance.d.ts +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js +50 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/unequip.js +27 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/unequip.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.d.ts +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +45 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.d.ts +3 -2
- package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.js +34 -41
- package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.js +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +44 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +47 -0
- package/bundle/typescript/src/commands/game/sideQuest/game.js +115 -11
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/headless-harness.d.ts +17 -0
- package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js +30 -2
- package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/device.d.ts +86 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/device.js +103 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/device.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/item.d.ts +140 -45
- package/bundle/typescript/src/commands/game/sideQuest/models/item.js +456 -104
- package/bundle/typescript/src/commands/game/sideQuest/models/item.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +7 -5
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +113 -4
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +289 -47
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/qualities.d.ts +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/qualities.js +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/qualities.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene1.json +9 -9
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +6 -6
- package/bundle/typescript/src/commands/game/sideQuest/types/repro.d.ts +8 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/repro.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +9 -2
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/device-seed.d.ts +17 -0
- package/bundle/typescript/src/commands/game/sideQuest/ui.js +23 -26
- package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/affordances.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/affordances.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/barrier-combat.d.ts +38 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/barrier-combat.js +67 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/barrier-combat.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog-data.d.ts +279 -4
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog-data.js +221 -212
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog-data.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.d.ts +48 -5
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.js +84 -17
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.d.ts +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js +8 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.d.ts +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +52 -12
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.d.ts +32 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js +33 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/condition-report.js +10 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/condition-report.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.d.ts +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js +35 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/first-aid.js +3 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/first-aid.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/matrix-intrusion.d.ts +121 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/matrix-intrusion.js +141 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/matrix-intrusion.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js +11 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/pregen-worksheet.d.ts +93 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/pregen-worksheet.js +201 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/pregen-worksheet.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/range.d.ts +19 -13
- package/bundle/typescript/src/commands/game/sideQuest/utilities/range.js +77 -34
- package/bundle/typescript/src/commands/game/sideQuest/utilities/range.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/room-view.js +91 -9
- package/bundle/typescript/src/commands/game/sideQuest/utilities/room-view.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js +50 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.d.ts +9 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.js +28 -4
- package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +43 -3
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getSoft, getProgram } from '../utilities/programs.js';
|
|
2
|
+
import { catalogRowFor } from '../utilities/catalog.js';
|
|
1
3
|
import { getQuality, resolveQualityKey } from '../qualities.js';
|
|
2
4
|
import { isAspectKey } from '../aspects.js';
|
|
3
5
|
import { hint } from '../utilities/hints.js';
|
|
@@ -15,6 +17,23 @@ import { AbstractPlayer } from '../types/shared/abstracts.js';
|
|
|
15
17
|
export const STANCE_CYCLE = ['manual', 'non-lethal', 'lethal'];
|
|
16
18
|
/** The old names, still accepted from saves and the stance command. */
|
|
17
19
|
export const STANCE_ALIASES = { guarded: 'non-lethal', aggressive: 'lethal', nonlethal: 'non-lethal', 'non lethal': 'non-lethal' };
|
|
20
|
+
export const MATRIX_STANCE_CYCLE = ['attack', 'sleaze'];
|
|
21
|
+
/** What each stance is CALLED in the book -- printed everywhere the
|
|
22
|
+
* engine used to say "loud"/"quiet", so the vocabulary teaches the
|
|
23
|
+
* real action names (the whole point of uMQAhaAysgaKpWFkn). */
|
|
24
|
+
export const MATRIX_STANCE_ACTION = {
|
|
25
|
+
attack: 'Brute Force',
|
|
26
|
+
sleaze: 'Hack on the Fly',
|
|
27
|
+
};
|
|
28
|
+
/** The words a player may type, including the engine's older ones. */
|
|
29
|
+
export const MATRIX_STANCE_ALIASES = {
|
|
30
|
+
loud: 'attack', force: 'attack', brute: 'attack', 'brute force': 'attack', bf: 'attack',
|
|
31
|
+
quiet: 'sleaze', sneak: 'sleaze', 'hack on the fly': 'sleaze', hotf: 'sleaze', fly: 'sleaze',
|
|
32
|
+
};
|
|
33
|
+
export function normalizeMatrixStance(v) {
|
|
34
|
+
const k = (v ?? 'attack').toLowerCase();
|
|
35
|
+
return MATRIX_STANCE_ALIASES[k] ?? (MATRIX_STANCE_CYCLE.includes(k) ? k : 'attack');
|
|
36
|
+
}
|
|
18
37
|
export function normalizeStance(v) {
|
|
19
38
|
const k = (v ?? 'manual').toLowerCase();
|
|
20
39
|
return STANCE_ALIASES[k] ?? (STANCE_CYCLE.includes(k) ? k : 'manual');
|
|
@@ -49,6 +68,7 @@ export const SKILL_NAMES = [
|
|
|
49
68
|
'alchemy', 'disenchanting', 'artificing',
|
|
50
69
|
'aeronautics-mechanic', 'industrial-mechanic', 'automotive-mechanic', 'nautical-mechanic',
|
|
51
70
|
'navigation', 'tracking', 'survival',
|
|
71
|
+
'heavy-weapons', 'throwing-weapons', 'exotic-melee', 'exotic-ranged',
|
|
52
72
|
];
|
|
53
73
|
/**
|
|
54
74
|
* Skills that fall back to the legacy `combatSkill` attribute when no
|
|
@@ -61,8 +81,8 @@ export const COMBAT_SKILL_FALLBACK = new Set([
|
|
|
61
81
|
// `combatSkill`, and after the split the skill it rolls depends on the
|
|
62
82
|
// gun it happens to be holding. Listing one would leave every guard
|
|
63
83
|
// with a shotgun rolling zero.
|
|
64
|
-
'automatics', 'pistols', 'longarms', 'archery',
|
|
65
|
-
'blades', 'unarmed', 'hacking',
|
|
84
|
+
'automatics', 'pistols', 'longarms', 'archery', 'heavy-weapons', 'throwing-weapons',
|
|
85
|
+
'blades', 'clubs', 'unarmed', 'exotic-melee', 'exotic-ranged', 'hacking',
|
|
66
86
|
]);
|
|
67
87
|
/**
|
|
68
88
|
* WHICH GUN ROLLS WHICH SKILL (SR5 p.130, p.132).
|
|
@@ -101,8 +121,13 @@ export const COMBAT_SKILL_FALLBACK = new Set([
|
|
|
101
121
|
* (p.433 firearm accessory), a drone's armed mount -- do not apply to a
|
|
102
122
|
* crossbow.
|
|
103
123
|
*/
|
|
104
|
-
export const FIREARM_SKILLS = ['automatics', 'pistols', 'longarms'];
|
|
124
|
+
export const FIREARM_SKILLS = ['automatics', 'pistols', 'longarms', 'heavy-weapons'];
|
|
105
125
|
export function firearmSkillFor(weapon) {
|
|
126
|
+
// THE ROW SAYS (catalog v13): an Item carries its weapon block's skill,
|
|
127
|
+
// and the regexes below are the fallback for a plain seed object or
|
|
128
|
+
// generated gear (Item.weaponSkill reads the same block first).
|
|
129
|
+
if (weapon.weaponSkill)
|
|
130
|
+
return weapon.weaponSkill;
|
|
106
131
|
const hay = `${weapon.name} ${weapon.shape ?? ''}`.toLowerCase();
|
|
107
132
|
if (/bow|crossbow|sling/.test(hay))
|
|
108
133
|
return 'archery';
|
|
@@ -168,6 +193,16 @@ export class Player extends AbstractPlayer {
|
|
|
168
193
|
this._autoStance = v;
|
|
169
194
|
this.noteConditionChanged();
|
|
170
195
|
}
|
|
196
|
+
/** Which of the book's two intrusion actions the next hack uses
|
|
197
|
+
* (see MatrixStance). Sheet-visible, like its meat-world twin. */
|
|
198
|
+
_matrixStance = 'attack';
|
|
199
|
+
get matrixStance() { return this._matrixStance; }
|
|
200
|
+
set matrixStance(v) {
|
|
201
|
+
if (this._matrixStance === v)
|
|
202
|
+
return;
|
|
203
|
+
this._matrixStance = v;
|
|
204
|
+
this.noteConditionChanged();
|
|
205
|
+
}
|
|
171
206
|
// SUBDUING (RAW p.195, the "restrain" ask from a real session's runaway
|
|
172
207
|
// NPC): a grapple links two actors BY NAME -- grappledBy/grappling are
|
|
173
208
|
// the two ends, grappleThreshold the escape test's bar (the original
|
|
@@ -587,6 +622,10 @@ export class Player extends AbstractPlayer {
|
|
|
587
622
|
}
|
|
588
623
|
/** True for the one Electronics item that is a trode net (p.439-440). */
|
|
589
624
|
static isTrodeNet(item) {
|
|
625
|
+
if (item.hasCapability('dni'))
|
|
626
|
+
return true;
|
|
627
|
+
if (item.row)
|
|
628
|
+
return false;
|
|
590
629
|
return item.category === Category.Electronics && /\btrodes?\b/i.test(item.name);
|
|
591
630
|
}
|
|
592
631
|
/**
|
|
@@ -783,8 +822,10 @@ export class Player extends AbstractPlayer {
|
|
|
783
822
|
panDefence(against = 'sleaze') {
|
|
784
823
|
const master = this.getPanMaster();
|
|
785
824
|
const silent = this.runningSilent ? 2 : 0;
|
|
786
|
-
|
|
787
|
-
|
|
825
|
+
// What the running programs add (catalog v13): Signal Scrub always,
|
|
826
|
+
// Sneak only while silent -- the rows carry both, and the `only`.
|
|
827
|
+
const scrub = this.gearBonus('pan-defense');
|
|
828
|
+
const sneak = this.runningSilent ? this.gearBonus('pan-defense', { only: 'silent' }) - scrub : 0;
|
|
788
829
|
const attributeName = against === 'attack' ? 'Willpower' : 'Intuition';
|
|
789
830
|
const attribute = against === 'attack' ? this.willpower : this.intuition;
|
|
790
831
|
const firewall = this.isTechnomancer()
|
|
@@ -887,7 +928,7 @@ export class Player extends AbstractPlayer {
|
|
|
887
928
|
// SMARTLINK, so the bonus belongs to the pairing. Item.accuracy
|
|
888
929
|
// stays the bare table value -- a gun on a table has no idea who
|
|
889
930
|
// will pick it up -- and the shooter supplies the other half here.
|
|
890
|
-
return acc + this.smartlinkAccuracyBonus(weapon);
|
|
931
|
+
return acc + Math.max(this.smartlinkAccuracyBonus(weapon), this.laserSightBonus(weapon));
|
|
891
932
|
}
|
|
892
933
|
return undefined;
|
|
893
934
|
}
|
|
@@ -955,6 +996,13 @@ export class Player extends AbstractPlayer {
|
|
|
955
996
|
// generated gear that carries the fiction but not the tag.
|
|
956
997
|
const eyeCategory = (i) => i.category === Category.Glasses || i.category === Category.EyeCyberwere;
|
|
957
998
|
const eyeWords = /goggles|glasses|contacts|cybereyes|monocle|visor/;
|
|
999
|
+
// THE ROW FIRST (catalog v13): a Smartlink fitted into the goggles
|
|
1000
|
+
// grants the capability through Item.effects; the name pattern is
|
|
1001
|
+
// the fallback for generated eyewear with no row.
|
|
1002
|
+
if (onFace.hasCapability('smartlink'))
|
|
1003
|
+
return 'gear';
|
|
1004
|
+
if (onFace.row)
|
|
1005
|
+
return undefined;
|
|
958
1006
|
const found = /smartlink/.test(hay(onFace))
|
|
959
1007
|
&& (eyeCategory(onFace) || eyeWords.test(hay(onFace)));
|
|
960
1008
|
return found ? 'gear' : undefined;
|
|
@@ -976,10 +1024,25 @@ export class Player extends AbstractPlayer {
|
|
|
976
1024
|
* never a die, and a die is never a limit.
|
|
977
1025
|
*/
|
|
978
1026
|
smartlinkAccuracyBonus(weapon) {
|
|
979
|
-
|
|
1027
|
+
// A SMARTGUN, not any gun (catalog v13; p.433): the system is an
|
|
1028
|
+
// accessory -- built in (Predator V) or bolted on (smartgun-external).
|
|
1029
|
+
// Before v13 every firearm counted; that was the engine's shortcut,
|
|
1030
|
+
// not the book's rule.
|
|
1031
|
+
if (!weapon?.isFirearm?.() || !weapon.isSmartgun)
|
|
980
1032
|
return 0;
|
|
981
1033
|
return this.smartlinkTier() ? 2 : 0;
|
|
982
1034
|
}
|
|
1035
|
+
/**
|
|
1036
|
+
* A LASER SIGHT's +1 Accuracy (p.432), built in or fitted -- and never
|
|
1037
|
+
* cumulative with a smartlink: getAttackLimit takes the larger of the two.
|
|
1038
|
+
*/
|
|
1039
|
+
laserSightBonus(weapon) {
|
|
1040
|
+
if (!weapon?.isFirearm?.())
|
|
1041
|
+
return 0;
|
|
1042
|
+
if (weapon.hasBuiltIn('laser-sight'))
|
|
1043
|
+
return 1;
|
|
1044
|
+
return weapon.effects.reduce((n, e) => n + (e.type === 'limit' && e.property === 'accuracy' ? e.value : 0), 0);
|
|
1045
|
+
}
|
|
983
1046
|
get matrixTaken() {
|
|
984
1047
|
return this.getAnyCyberdeck()?.deckDamage ?? 0;
|
|
985
1048
|
}
|
|
@@ -1156,6 +1219,53 @@ export class Player extends AbstractPlayer {
|
|
|
1156
1219
|
hasAug(key) {
|
|
1157
1220
|
return this.augmentations.has(key);
|
|
1158
1221
|
}
|
|
1222
|
+
/**
|
|
1223
|
+
* THE WEAPON BUILT INTO YOUR HANDS (core p.458, xHarA3TNgxZXtZwgp).
|
|
1224
|
+
* Canon routes these through Unarmed Combat and the Physical limit,
|
|
1225
|
+
* so they REPLACE the bare-fists profile rather than sitting in a
|
|
1226
|
+
* hand slot -- see getWeaponProfile and utilities/combat-exchange.ts,
|
|
1227
|
+
* the two places that compute a punch.
|
|
1228
|
+
*
|
|
1229
|
+
* A drawn weapon always wins: chrome in the palm does not stop you
|
|
1230
|
+
* shooting. Mutually exclusive by row (excludes), so at most one is
|
|
1231
|
+
* ever installed and the find is the answer rather than a contest.
|
|
1232
|
+
*/
|
|
1233
|
+
implantWeapon() {
|
|
1234
|
+
for (const key of this.augmentations) {
|
|
1235
|
+
const w = getAug(key)?.implantWeapon;
|
|
1236
|
+
if (!w)
|
|
1237
|
+
continue;
|
|
1238
|
+
// A shock hand out of charges is a hand (p.458).
|
|
1239
|
+
if (w.charges !== undefined && this.implantCharges <= 0)
|
|
1240
|
+
return undefined;
|
|
1241
|
+
return w;
|
|
1242
|
+
}
|
|
1243
|
+
return undefined;
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Jolts left in a shock hand. Ten per hand in the book, recharging
|
|
1247
|
+
* one per ten seconds while plugged in -- there is no wall clock here
|
|
1248
|
+
* for a hand to be plugged into, so the trickle is compressed to a
|
|
1249
|
+
* refill on rest (commands/rest.ts). Session state: a fresh install
|
|
1250
|
+
* arrives charged.
|
|
1251
|
+
*/
|
|
1252
|
+
implantCharges = 0;
|
|
1253
|
+
/** Spends one jolt. Returns what is left. */
|
|
1254
|
+
spendImplantCharge() {
|
|
1255
|
+
if (this.implantCharges > 0)
|
|
1256
|
+
this.implantCharges--;
|
|
1257
|
+
return this.implantCharges;
|
|
1258
|
+
}
|
|
1259
|
+
/** Back to full -- a rest, or the moment the chrome goes in. */
|
|
1260
|
+
refillImplantCharges() {
|
|
1261
|
+
for (const key of this.augmentations) {
|
|
1262
|
+
const w = getAug(key)?.implantWeapon;
|
|
1263
|
+
if (w?.charges !== undefined) {
|
|
1264
|
+
this.implantCharges = w.charges;
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1159
1269
|
/** Full essence points lost, rounded up -- the toll Magic/Resonance pay. */
|
|
1160
1270
|
get essenceBurn() {
|
|
1161
1271
|
return Math.ceil(Player.ESSENCE_MAX - this.essence - 1e-9);
|
|
@@ -1177,6 +1287,95 @@ export class Player extends AbstractPlayer {
|
|
|
1177
1287
|
const burn = this.essenceBurn;
|
|
1178
1288
|
this.magic = Math.max(0, this.baseMagic - burn);
|
|
1179
1289
|
this.resonance = Math.max(0, this.baseResonance - burn);
|
|
1290
|
+
// A shock hand wakes up charged (p.458: ten per hand).
|
|
1291
|
+
this.refillImplantCharges();
|
|
1292
|
+
// A HOUSING TAKES THE DECK YOU BROUGHT (Chrome Flesh p.222). Nothing
|
|
1293
|
+
// is granted -- the book charges 5,000 PLUS the deck, so this moves
|
|
1294
|
+
// the one you own inside your skull and it stops being liftable.
|
|
1295
|
+
if (getAug(aug.key)?.implantsCarriedDeck)
|
|
1296
|
+
this.getAnyCyberdeck()?.implantIntoBody();
|
|
1297
|
+
}
|
|
1298
|
+
// ==================== Gear effects (catalog v13) ====================
|
|
1299
|
+
// A catalog row says what it does (`effects`), and this is the one
|
|
1300
|
+
// place the engine reads that: what is worn (and fitted into it), what
|
|
1301
|
+
// is drawn, what is carried, what runs on the deck, what is stored on
|
|
1302
|
+
// a commlink. Same shape as augBonus / qualityBonus so a pool adds the
|
|
1303
|
+
// three the same way.
|
|
1304
|
+
/** Every effect that applies right now, with the rating it scales by. */
|
|
1305
|
+
applyingEffects() {
|
|
1306
|
+
const out = [];
|
|
1307
|
+
const take = (effects, defaultWhen, state, rating) => {
|
|
1308
|
+
for (const e of effects)
|
|
1309
|
+
if ((e.when ?? defaultWhen) === state)
|
|
1310
|
+
out.push({ effect: e, rating: rating ?? 1 });
|
|
1311
|
+
};
|
|
1312
|
+
for (const [groupName, group] of Object.entries(this._equipment)) {
|
|
1313
|
+
if (!group)
|
|
1314
|
+
continue;
|
|
1315
|
+
for (const [slot, item] of Object.entries(group)) {
|
|
1316
|
+
if (!item)
|
|
1317
|
+
continue;
|
|
1318
|
+
if (Player.isWeaponSlot(groupName, slot)) {
|
|
1319
|
+
if (this.weaponDrawn)
|
|
1320
|
+
take(item.effects, item.defaultWhen, 'held', item.ratingValue);
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
take(item.effects, item.defaultWhen, 'worn', item.ratingValue);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
for (const item of this._inventory.getAllItems())
|
|
1327
|
+
take(item.effects, item.defaultWhen, 'carried', item.ratingValue);
|
|
1328
|
+
const runningOn = new Set();
|
|
1329
|
+
const deck = this.activeDeck ?? this.getCyberdeck();
|
|
1330
|
+
if (deck)
|
|
1331
|
+
runningOn.add(deck);
|
|
1332
|
+
const master = this.getPanMaster();
|
|
1333
|
+
if (master)
|
|
1334
|
+
runningOn.add(master);
|
|
1335
|
+
for (const device of runningOn) {
|
|
1336
|
+
for (const key of device.loadedPrograms) {
|
|
1337
|
+
const p = getProgram(key);
|
|
1338
|
+
const row = p ? catalogRowFor(`${p.name} Program`) ?? catalogRowFor(p.name) : undefined;
|
|
1339
|
+
if (row?.effects)
|
|
1340
|
+
take(row.effects, 'running', 'running', row.ratingValue);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
for (const device of [...this.commlinks(), ...this._inventory.getAllItems().filter(i => i.isCyberdeck())]) {
|
|
1344
|
+
for (const key of device.storedPrograms) {
|
|
1345
|
+
const soft = getSoft(key);
|
|
1346
|
+
const row = soft ? catalogRowFor(soft.name) : undefined;
|
|
1347
|
+
if (row?.effects)
|
|
1348
|
+
take(row.effects, 'stored', 'stored', row.ratingValue);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
return out;
|
|
1352
|
+
}
|
|
1353
|
+
sumEffects(type, property, only) {
|
|
1354
|
+
let total = 0;
|
|
1355
|
+
for (const { effect, rating } of this.applyingEffects()) {
|
|
1356
|
+
if (effect.type !== type || effect.property !== property)
|
|
1357
|
+
continue;
|
|
1358
|
+
if (effect.only !== undefined && effect.only !== only)
|
|
1359
|
+
continue;
|
|
1360
|
+
total += effect.value * (effect.scaleBy === 'rating' ? rating : 1);
|
|
1361
|
+
}
|
|
1362
|
+
return total;
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Dice the gear adds to a pool or a skill's tests (cf. augBonus,
|
|
1366
|
+
* qualityBonus): `kind` is a pool kind or a SkillName; `only` names a
|
|
1367
|
+
* narrowing an effect may carry ('haggle', 'silent', 'matrix-search').
|
|
1368
|
+
*/
|
|
1369
|
+
gearBonus(kind, opts = {}) {
|
|
1370
|
+
return this.sumEffects('dice', kind, opts.only);
|
|
1371
|
+
}
|
|
1372
|
+
/** What the gear adds to a LIMIT (a Shopsoft's +1 Social on a haggle). */
|
|
1373
|
+
gearLimit(kind, opts = {}) {
|
|
1374
|
+
return this.sumEffects('limit', kind, opts.only);
|
|
1375
|
+
}
|
|
1376
|
+
/** A capability the gear grants (dni, smartlink, no-fog, low-light...), by key. */
|
|
1377
|
+
hasCapability(key) {
|
|
1378
|
+
return this.sumEffects('flag', key) > 0;
|
|
1180
1379
|
}
|
|
1181
1380
|
/**
|
|
1182
1381
|
* The single tap every pool reads chrome through. Wired Reflexes tiers
|
|
@@ -1442,15 +1641,9 @@ export class Player extends AbstractPlayer {
|
|
|
1442
1641
|
const weapon = this.weaponDrawn ? this.getCarriedWeapon() : null;
|
|
1443
1642
|
if (!weapon)
|
|
1444
1643
|
return 'unarmed';
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
// clubs by name/shape; everything else edged defaults to blades.
|
|
1449
|
-
// Short tokens are boundary-anchored both sides -- "bat" once
|
|
1450
|
-
// matched the tail of "Combat Knife".
|
|
1451
|
-
return /club|\bbat\b|baton|staff|hammer|\bpipe\b|wrench|\bsap\b|\bmace\b|crowbar|cudgel/i.test(`${weapon.name} ${weapon.shape}`)
|
|
1452
|
-
? 'clubs'
|
|
1453
|
-
: 'blades';
|
|
1644
|
+
// The weapon block names the skill (catalog v13); Item.weaponSkill
|
|
1645
|
+
// keeps the name/shape duck-typing for generated gear.
|
|
1646
|
+
return weapon.weaponSkill;
|
|
1454
1647
|
}
|
|
1455
1648
|
initializeEquipment() {
|
|
1456
1649
|
return {
|
|
@@ -1883,6 +2076,28 @@ export class Player extends AbstractPlayer {
|
|
|
1883
2076
|
static isWeaponSlot(group, slot) {
|
|
1884
2077
|
return (group === 'rightHand' || group === 'leftHand') && slot === 'weapon';
|
|
1885
2078
|
}
|
|
2079
|
+
/**
|
|
2080
|
+
* WHERE A THING ACTUALLY IS RIGHT NOW, which for a weapon is not a
|
|
2081
|
+
* property of the slot (user report, 2026-09-03: "when looking at
|
|
2082
|
+
* equipment, and I have a Katana Holstered, I should not see Right
|
|
2083
|
+
* hand Katana (weapon hand) -- it's not in hand, it's in holster").
|
|
2084
|
+
*
|
|
2085
|
+
* SLOT_WORDS is static, so it can only ever say where a weapon lives
|
|
2086
|
+
* when drawn. The weapon slot is really two places -- the hand and the
|
|
2087
|
+
* holster -- and `weaponDrawn` is which one. Everything else in the
|
|
2088
|
+
* game already knew: the HUD prints "Holstered:", equip prints "You
|
|
2089
|
+
* slide it into your holster", and the equipment screen's own top line
|
|
2090
|
+
* prints HOLSTERED. Only the worn listing underneath still read the
|
|
2091
|
+
* static word, so one screen contradicted itself.
|
|
2092
|
+
*
|
|
2093
|
+
* Instance method, not static, precisely because the answer depends on
|
|
2094
|
+
* the actor rather than the slot.
|
|
2095
|
+
*/
|
|
2096
|
+
wornSlotWord(group, slot) {
|
|
2097
|
+
if (Player.isWeaponSlot(group, slot) && !this.weaponDrawn)
|
|
2098
|
+
return 'holstered';
|
|
2099
|
+
return Player.slotWord(group, slot);
|
|
2100
|
+
}
|
|
1886
2101
|
unequip(input) {
|
|
1887
2102
|
const slotInfo = this.determineEquipmentSlot(input);
|
|
1888
2103
|
if (slotInfo) {
|
|
@@ -2318,15 +2533,33 @@ export class Player extends AbstractPlayer {
|
|
|
2318
2533
|
// partly from ignoring armor. Quality still rides on top. A HOLSTERED
|
|
2319
2534
|
// weapon counts for nothing -- until it's drawn (brandish, or the
|
|
2320
2535
|
// auto-draw when an exchange opens), your hands are empty.
|
|
2321
|
-
|
|
2536
|
+
// CANON NUMBERS (catalog v13, project owner 2026-09-03): the DV is the
|
|
2537
|
+
// book's -- a Predator's 8, a knife's Strength + 2 -- read off the
|
|
2538
|
+
// weapon block (Item.weaponDv / dvAttribute), with the class value as
|
|
2539
|
+
// the fallback for generated gear. The PRICE TIER NO LONGER ADDS DAMAGE
|
|
2540
|
+
// OR DICE: canon has no such thing, and a Legendary knife is a knife.
|
|
2541
|
+
// Unarmed is (STR)S (p.187); bone lacing's +2 rides augBonus.
|
|
2322
2542
|
const weapon = this.weaponDrawn ? this.getCarriedWeapon() : null;
|
|
2323
2543
|
if (!weapon) {
|
|
2324
|
-
//
|
|
2325
|
-
|
|
2544
|
+
// CHROME IN THE HANDS REPLACES THE FISTS (core p.458,
|
|
2545
|
+
// xHarA3TNgxZXtZwgp): cyber melee weapons use Unarmed Combat and
|
|
2546
|
+
// the Physical limit, so they arrive here rather than in a hand
|
|
2547
|
+
// slot. SURFACE ONE OF TWO -- utilities/combat-exchange.ts computes
|
|
2548
|
+
// this same punch independently, and both read implantWeapon.
|
|
2549
|
+
const implant = this.implantWeapon();
|
|
2550
|
+
if (implant) {
|
|
2551
|
+
const aug = [...this.augmentations].map(k => getAug(k)).find(a => a?.implantWeapon);
|
|
2552
|
+
return {
|
|
2553
|
+
name: aug?.name?.toLowerCase() ?? 'implanted blades',
|
|
2554
|
+
damage: implant.dv + (implant.dvAttribute === 'strength' ? this.strength : 0),
|
|
2555
|
+
poolBonus: 0,
|
|
2556
|
+
armorPen: implant.ap,
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
return { name: 'bare fists', damage: this.strength + this.augBonus('unarmed-damage'), poolBonus: 0, armorPen: 0 };
|
|
2326
2560
|
}
|
|
2327
|
-
const
|
|
2328
|
-
|
|
2329
|
-
return { name: weapon.name, damage: base + bonus, poolBonus: bonus, armorPen: weapon.armorPen };
|
|
2561
|
+
const base = weapon.weaponDv + (weapon.dvAttribute === 'strength' ? this.strength : 0);
|
|
2562
|
+
return { name: weapon.name, damage: base, poolBonus: 0, armorPen: weapon.armorPen };
|
|
2330
2563
|
}
|
|
2331
2564
|
/** The weapon riding in a hand slot, drawn or holstered. */
|
|
2332
2565
|
getCarriedWeapon() {
|
|
@@ -2378,18 +2611,16 @@ export class Player extends AbstractPlayer {
|
|
|
2378
2611
|
for (const item of Object.values(group)) {
|
|
2379
2612
|
if (!item || !Player.ARMOR_CATEGORIES.has(item.category))
|
|
2380
2613
|
continue;
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
extras.push({ item, bonus: 2 });
|
|
2614
|
+
// CANON NUMBERS (catalog v13): the row's Armor rating -- a jacket's
|
|
2615
|
+
// 12, a helmet's +2 -- with the class value as the fallback.
|
|
2616
|
+
const bonus = item.armorBonus;
|
|
2617
|
+
if (bonus > 0) {
|
|
2618
|
+
extras.push({ item, bonus });
|
|
2387
2619
|
continue;
|
|
2388
2620
|
}
|
|
2389
|
-
|
|
2390
|
-
|
|
2621
|
+
const value = item.armorValue;
|
|
2622
|
+
if (value <= 0)
|
|
2391
2623
|
continue;
|
|
2392
|
-
const value = 1 + item.qualityBonus + (item.isHeavyArmor ? 1 : 0);
|
|
2393
2624
|
if (value > best) {
|
|
2394
2625
|
if (piece)
|
|
2395
2626
|
ignored.push(piece);
|
|
@@ -2401,7 +2632,16 @@ export class Player extends AbstractPlayer {
|
|
|
2401
2632
|
}
|
|
2402
2633
|
}
|
|
2403
2634
|
}
|
|
2404
|
-
|
|
2635
|
+
// p.169: the bonus from "+" accessories is capped at Strength, and
|
|
2636
|
+
// every 2 full points over it costs -1 Agility and Reaction.
|
|
2637
|
+
const bonusRaw = extras.reduce((n, e) => n + e.bonus, 0);
|
|
2638
|
+
const bonusCapped = Math.min(bonusRaw, this.strength);
|
|
2639
|
+
const encumbrance = Math.floor(Math.max(0, bonusRaw - this.strength) / 2);
|
|
2640
|
+
return { total: best + bonusCapped, piece, extras, ignored, bonusCapped, encumbrance };
|
|
2641
|
+
}
|
|
2642
|
+
/** The p.169 encumbrance penalty on Agility and Reaction, as dice off those pools. */
|
|
2643
|
+
get encumbrancePenalty() {
|
|
2644
|
+
return this.armorBreakdown().encumbrance;
|
|
2405
2645
|
}
|
|
2406
2646
|
getAttackPool() {
|
|
2407
2647
|
// Plane decides which attribute drives the fight: meat is muscle
|
|
@@ -2477,7 +2717,10 @@ export class Player extends AbstractPlayer {
|
|
|
2477
2717
|
// weapon exercises a FIREARM skill at all. Archery is deliberately
|
|
2478
2718
|
// not in that set -- a smartgun system is a firearm accessory
|
|
2479
2719
|
// (p.433), and a crossbow has nothing to plug into.
|
|
2480
|
-
|
|
2720
|
+
// AND THE GUN MUST BE A SMARTGUN (catalog v13, p.433): the wireless
|
|
2721
|
+
// bonus is the smartgun system talking to the smartlink.
|
|
2722
|
+
const drawnGun = this.weaponDrawn ? this.getCarriedWeapon() : null;
|
|
2723
|
+
const smartlink = drawnGun?.isSmartgun && FIREARM_SKILLS.includes(this.activeCombatSkill())
|
|
2481
2724
|
? (this.smartlinkTier() === 'gear' ? 1 : this.augBonus('firearms'))
|
|
2482
2725
|
: 0;
|
|
2483
2726
|
// RUNNING COSTS YOU (p.162 Running Modifiers): -2 dice on everything
|
|
@@ -2488,7 +2731,7 @@ export class Player extends AbstractPlayer {
|
|
|
2488
2731
|
// sprinted across a room to reach someone arrived shooting exactly
|
|
2489
2732
|
// as straight as one who strolled -- and the movement budget's whole
|
|
2490
2733
|
// point is that ground has a price.
|
|
2491
|
-
return Math.max(0, this.agility + this.skillRating(this.activeCombatSkill()) + adeptBonus + this.getWeaponProfile().poolBonus + this.augBonus('attack') + smartlink + this.woundModifier - this.sustainingPenalty - this.runningPenalty);
|
|
2734
|
+
return Math.max(0, this.agility + this.skillRating(this.activeCombatSkill()) + adeptBonus + this.getWeaponProfile().poolBonus + this.augBonus('attack') + this.gearBonus('attack') + this.gearBonus(this.activeCombatSkill()) + smartlink + this.woundModifier - this.sustainingPenalty - this.runningPenalty - this.encumbrancePenalty);
|
|
2492
2735
|
}
|
|
2493
2736
|
/**
|
|
2494
2737
|
* The subdual pool: bare-hands work regardless of what's in the other
|
|
@@ -2535,7 +2778,7 @@ export class Player extends AbstractPlayer {
|
|
|
2535
2778
|
// Improved Reflexes rides Reaction itself (p.309), so it lands here too.
|
|
2536
2779
|
const adeptBonus = this.adeptDefenseBonus() + this.adeptReactionBonus();
|
|
2537
2780
|
const grappled = this.grappledBy ? 2 : 0;
|
|
2538
|
-
return Math.max(0, this.reaction + Math.floor(this.intuition / 2) + adeptBonus + this.augBonus('defense') + this.woundModifier - this.sustainingPenalty - grappled);
|
|
2781
|
+
return Math.max(0, this.reaction + Math.floor(this.intuition / 2) + adeptBonus + this.augBonus('defense') + this.gearBonus('defense') + this.woundModifier - this.sustainingPenalty - grappled - this.encumbrancePenalty);
|
|
2539
2782
|
}
|
|
2540
2783
|
getSoakPool() {
|
|
2541
2784
|
// Matrix: firewall -- Logic plus the DECK's quality (your deck is your
|
|
@@ -2544,7 +2787,8 @@ export class Player extends AbstractPlayer {
|
|
|
2544
2787
|
// meat left behind. Drone: the HULL soaks -- airframe body scaled by
|
|
2545
2788
|
// build quality; the rider's armor is streets away with their body.
|
|
2546
2789
|
if (this.plane === 'drone') {
|
|
2547
|
-
|
|
2790
|
+
// Body + Armor (p.199), from the drone's vehicle block (catalog v13).
|
|
2791
|
+
return Math.max(0, (this.riggedDrone?.droneBody ?? 0) + (this.riggedDrone?.droneArmor ?? 0));
|
|
2548
2792
|
}
|
|
2549
2793
|
if (this.plane === 'matrix') {
|
|
2550
2794
|
// MATRIX DAMAGE IS RESISTED WITH DEVICE RATING + FIREWALL
|
|
@@ -2561,13 +2805,13 @@ export class Player extends AbstractPlayer {
|
|
|
2561
2805
|
}
|
|
2562
2806
|
// A loaded Armor program hardens it further (+2, p.245 cited).
|
|
2563
2807
|
const deck = this.activeDeck ?? this.getCyberdeck();
|
|
2564
|
-
return Math.max(0, (deck?.deviceRating ?? 0) + this.matrixAttribute('firewall') + (
|
|
2808
|
+
return Math.max(0, (deck?.deviceRating ?? 0) + this.matrixAttribute('firewall') + this.gearBonus('matrix-soak'));
|
|
2565
2809
|
}
|
|
2566
2810
|
if (this.plane === 'astral') {
|
|
2567
2811
|
return Math.max(0, this.willpower);
|
|
2568
2812
|
}
|
|
2569
2813
|
// Mystic Armor (p.309): the adept power is armor that was never worn.
|
|
2570
|
-
return Math.max(0, this.body + this.getArmorValue() + this._sustainedArmor + this.adeptArmorBonus() + this.augBonus('soak') + this.qualityBonus('soak'));
|
|
2814
|
+
return Math.max(0, this.body + this.getArmorValue() + this._sustainedArmor + this.adeptArmorBonus() + this.augBonus('soak') + this.qualityBonus('soak') + this.gearBonus('soak'));
|
|
2571
2815
|
}
|
|
2572
2816
|
/**
|
|
2573
2817
|
* THE INITIATIVE ATTRIBUTE -- what the dice are added TO (p.159,
|
|
@@ -3156,12 +3400,9 @@ export class Player extends AbstractPlayer {
|
|
|
3156
3400
|
// whose quality feeds the drone pools, and whose destruction is a
|
|
3157
3401
|
// DUMPSHOCK (see planes.ts leaveDrone).
|
|
3158
3402
|
riggedDrone;
|
|
3159
|
-
/** Whether the ridden drone carries a weapon mount (name
|
|
3403
|
+
/** Whether the ridden drone carries a weapon mount (its vehicle block, catalog v13; the name for generated frames). */
|
|
3160
3404
|
get droneArmed() {
|
|
3161
|
-
|
|
3162
|
-
if (!d)
|
|
3163
|
-
return false;
|
|
3164
|
-
return /armed|combat|gun|roto|assault|sentry|weapon/i.test(`${d.name} ${d.description ?? ''}`);
|
|
3405
|
+
return this.riggedDrone?.hasWeaponMount ?? false;
|
|
3165
3406
|
}
|
|
3166
3407
|
// Dumpshock disorientation (SR5e p.229): -2 on everything for
|
|
3167
3408
|
// (10 - Willpower) minutes after a forced dump, wall-clock -- the
|
|
@@ -3193,15 +3434,16 @@ export class Player extends AbstractPlayer {
|
|
|
3193
3434
|
// "persuaded-by:<name>" marks who already made their play on this actor
|
|
3194
3435
|
// this job. NPCs are rebuilt each scene, so it resets naturally.
|
|
3195
3436
|
socialMarks = new Set();
|
|
3196
|
-
/** The best Focus item on this actor, if any --
|
|
3437
|
+
/** The best Focus item on this actor, if any -- by Force (catalog v13: ratingValue). */
|
|
3197
3438
|
getBestFocus() {
|
|
3198
3439
|
return this._inventory.getAllItems()
|
|
3199
3440
|
.filter(i => i.category === Category.Focus)
|
|
3200
|
-
.sort((a, b) => b.
|
|
3441
|
+
.sort((a, b) => (b.ratingValue ?? 0) - (a.ratingValue ?? 0))[0];
|
|
3201
3442
|
}
|
|
3202
3443
|
getMagicPool() {
|
|
3203
|
-
|
|
3204
|
-
|
|
3444
|
+
// A focus adds its Force to the tests it is bonded for (p.318): the
|
|
3445
|
+
// row's effects, scaled by Force (catalog v13).
|
|
3446
|
+
const focusBonus = this.gearBonus('spellcasting');
|
|
3205
3447
|
// SR-proper when the SPELLCASTING skill is rated: Magic + Spellcasting
|
|
3206
3448
|
// for the Awakened, base + skill for a book-caster. Unrated falls back
|
|
3207
3449
|
// to the legacy shapes (talent max(5, magic+2); scholar's flat 5), so
|