@maka/maka-cli 5.119.0 → 5.121.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/typescript/package.json +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/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/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 +28 -4
- 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/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/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/search.js +5 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.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 +29 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.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 +7 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.js +12 -7
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/device.d.ts +72 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/device.js +89 -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 +134 -45
- package/bundle/typescript/src/commands/game/sideQuest/models/item.js +448 -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 +31 -3
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +171 -47
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
- 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 +4 -1
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/device-seed.d.ts +17 -0
- 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 +24 -5
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.js +50 -17
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.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 +28 -11
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/condition-report.js +16 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/condition-report.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/persistence.js +6 -2
- 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/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/web-character-generator.d.ts +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.js +22 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.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';
|
|
@@ -49,6 +51,7 @@ export const SKILL_NAMES = [
|
|
|
49
51
|
'alchemy', 'disenchanting', 'artificing',
|
|
50
52
|
'aeronautics-mechanic', 'industrial-mechanic', 'automotive-mechanic', 'nautical-mechanic',
|
|
51
53
|
'navigation', 'tracking', 'survival',
|
|
54
|
+
'heavy-weapons', 'throwing-weapons', 'exotic-melee', 'exotic-ranged',
|
|
52
55
|
];
|
|
53
56
|
/**
|
|
54
57
|
* Skills that fall back to the legacy `combatSkill` attribute when no
|
|
@@ -61,8 +64,8 @@ export const COMBAT_SKILL_FALLBACK = new Set([
|
|
|
61
64
|
// `combatSkill`, and after the split the skill it rolls depends on the
|
|
62
65
|
// gun it happens to be holding. Listing one would leave every guard
|
|
63
66
|
// with a shotgun rolling zero.
|
|
64
|
-
'automatics', 'pistols', 'longarms', 'archery',
|
|
65
|
-
'blades', 'unarmed', 'hacking',
|
|
67
|
+
'automatics', 'pistols', 'longarms', 'archery', 'heavy-weapons', 'throwing-weapons',
|
|
68
|
+
'blades', 'clubs', 'unarmed', 'exotic-melee', 'exotic-ranged', 'hacking',
|
|
66
69
|
]);
|
|
67
70
|
/**
|
|
68
71
|
* WHICH GUN ROLLS WHICH SKILL (SR5 p.130, p.132).
|
|
@@ -101,8 +104,13 @@ export const COMBAT_SKILL_FALLBACK = new Set([
|
|
|
101
104
|
* (p.433 firearm accessory), a drone's armed mount -- do not apply to a
|
|
102
105
|
* crossbow.
|
|
103
106
|
*/
|
|
104
|
-
export const FIREARM_SKILLS = ['automatics', 'pistols', 'longarms'];
|
|
107
|
+
export const FIREARM_SKILLS = ['automatics', 'pistols', 'longarms', 'heavy-weapons'];
|
|
105
108
|
export function firearmSkillFor(weapon) {
|
|
109
|
+
// THE ROW SAYS (catalog v13): an Item carries its weapon block's skill,
|
|
110
|
+
// and the regexes below are the fallback for a plain seed object or
|
|
111
|
+
// generated gear (Item.weaponSkill reads the same block first).
|
|
112
|
+
if (weapon.weaponSkill)
|
|
113
|
+
return weapon.weaponSkill;
|
|
106
114
|
const hay = `${weapon.name} ${weapon.shape ?? ''}`.toLowerCase();
|
|
107
115
|
if (/bow|crossbow|sling/.test(hay))
|
|
108
116
|
return 'archery';
|
|
@@ -587,6 +595,10 @@ export class Player extends AbstractPlayer {
|
|
|
587
595
|
}
|
|
588
596
|
/** True for the one Electronics item that is a trode net (p.439-440). */
|
|
589
597
|
static isTrodeNet(item) {
|
|
598
|
+
if (item.hasCapability('dni'))
|
|
599
|
+
return true;
|
|
600
|
+
if (item.row)
|
|
601
|
+
return false;
|
|
590
602
|
return item.category === Category.Electronics && /\btrodes?\b/i.test(item.name);
|
|
591
603
|
}
|
|
592
604
|
/**
|
|
@@ -783,8 +795,10 @@ export class Player extends AbstractPlayer {
|
|
|
783
795
|
panDefence(against = 'sleaze') {
|
|
784
796
|
const master = this.getPanMaster();
|
|
785
797
|
const silent = this.runningSilent ? 2 : 0;
|
|
786
|
-
|
|
787
|
-
|
|
798
|
+
// What the running programs add (catalog v13): Signal Scrub always,
|
|
799
|
+
// Sneak only while silent -- the rows carry both, and the `only`.
|
|
800
|
+
const scrub = this.gearBonus('pan-defense');
|
|
801
|
+
const sneak = this.runningSilent ? this.gearBonus('pan-defense', { only: 'silent' }) - scrub : 0;
|
|
788
802
|
const attributeName = against === 'attack' ? 'Willpower' : 'Intuition';
|
|
789
803
|
const attribute = against === 'attack' ? this.willpower : this.intuition;
|
|
790
804
|
const firewall = this.isTechnomancer()
|
|
@@ -887,7 +901,7 @@ export class Player extends AbstractPlayer {
|
|
|
887
901
|
// SMARTLINK, so the bonus belongs to the pairing. Item.accuracy
|
|
888
902
|
// stays the bare table value -- a gun on a table has no idea who
|
|
889
903
|
// will pick it up -- and the shooter supplies the other half here.
|
|
890
|
-
return acc + this.smartlinkAccuracyBonus(weapon);
|
|
904
|
+
return acc + Math.max(this.smartlinkAccuracyBonus(weapon), this.laserSightBonus(weapon));
|
|
891
905
|
}
|
|
892
906
|
return undefined;
|
|
893
907
|
}
|
|
@@ -955,6 +969,13 @@ export class Player extends AbstractPlayer {
|
|
|
955
969
|
// generated gear that carries the fiction but not the tag.
|
|
956
970
|
const eyeCategory = (i) => i.category === Category.Glasses || i.category === Category.EyeCyberwere;
|
|
957
971
|
const eyeWords = /goggles|glasses|contacts|cybereyes|monocle|visor/;
|
|
972
|
+
// THE ROW FIRST (catalog v13): a Smartlink fitted into the goggles
|
|
973
|
+
// grants the capability through Item.effects; the name pattern is
|
|
974
|
+
// the fallback for generated eyewear with no row.
|
|
975
|
+
if (onFace.hasCapability('smartlink'))
|
|
976
|
+
return 'gear';
|
|
977
|
+
if (onFace.row)
|
|
978
|
+
return undefined;
|
|
958
979
|
const found = /smartlink/.test(hay(onFace))
|
|
959
980
|
&& (eyeCategory(onFace) || eyeWords.test(hay(onFace)));
|
|
960
981
|
return found ? 'gear' : undefined;
|
|
@@ -976,10 +997,25 @@ export class Player extends AbstractPlayer {
|
|
|
976
997
|
* never a die, and a die is never a limit.
|
|
977
998
|
*/
|
|
978
999
|
smartlinkAccuracyBonus(weapon) {
|
|
979
|
-
|
|
1000
|
+
// A SMARTGUN, not any gun (catalog v13; p.433): the system is an
|
|
1001
|
+
// accessory -- built in (Predator V) or bolted on (smartgun-external).
|
|
1002
|
+
// Before v13 every firearm counted; that was the engine's shortcut,
|
|
1003
|
+
// not the book's rule.
|
|
1004
|
+
if (!weapon?.isFirearm?.() || !weapon.isSmartgun)
|
|
980
1005
|
return 0;
|
|
981
1006
|
return this.smartlinkTier() ? 2 : 0;
|
|
982
1007
|
}
|
|
1008
|
+
/**
|
|
1009
|
+
* A LASER SIGHT's +1 Accuracy (p.432), built in or fitted -- and never
|
|
1010
|
+
* cumulative with a smartlink: getAttackLimit takes the larger of the two.
|
|
1011
|
+
*/
|
|
1012
|
+
laserSightBonus(weapon) {
|
|
1013
|
+
if (!weapon?.isFirearm?.())
|
|
1014
|
+
return 0;
|
|
1015
|
+
if (weapon.hasBuiltIn('laser-sight'))
|
|
1016
|
+
return 1;
|
|
1017
|
+
return weapon.effects.reduce((n, e) => n + (e.type === 'limit' && e.property === 'accuracy' ? e.value : 0), 0);
|
|
1018
|
+
}
|
|
983
1019
|
get matrixTaken() {
|
|
984
1020
|
return this.getAnyCyberdeck()?.deckDamage ?? 0;
|
|
985
1021
|
}
|
|
@@ -1178,6 +1214,88 @@ export class Player extends AbstractPlayer {
|
|
|
1178
1214
|
this.magic = Math.max(0, this.baseMagic - burn);
|
|
1179
1215
|
this.resonance = Math.max(0, this.baseResonance - burn);
|
|
1180
1216
|
}
|
|
1217
|
+
// ==================== Gear effects (catalog v13) ====================
|
|
1218
|
+
// A catalog row says what it does (`effects`), and this is the one
|
|
1219
|
+
// place the engine reads that: what is worn (and fitted into it), what
|
|
1220
|
+
// is drawn, what is carried, what runs on the deck, what is stored on
|
|
1221
|
+
// a commlink. Same shape as augBonus / qualityBonus so a pool adds the
|
|
1222
|
+
// three the same way.
|
|
1223
|
+
/** Every effect that applies right now, with the rating it scales by. */
|
|
1224
|
+
applyingEffects() {
|
|
1225
|
+
const out = [];
|
|
1226
|
+
const take = (effects, defaultWhen, state, rating) => {
|
|
1227
|
+
for (const e of effects)
|
|
1228
|
+
if ((e.when ?? defaultWhen) === state)
|
|
1229
|
+
out.push({ effect: e, rating: rating ?? 1 });
|
|
1230
|
+
};
|
|
1231
|
+
for (const [groupName, group] of Object.entries(this._equipment)) {
|
|
1232
|
+
if (!group)
|
|
1233
|
+
continue;
|
|
1234
|
+
for (const [slot, item] of Object.entries(group)) {
|
|
1235
|
+
if (!item)
|
|
1236
|
+
continue;
|
|
1237
|
+
if (Player.isWeaponSlot(groupName, slot)) {
|
|
1238
|
+
if (this.weaponDrawn)
|
|
1239
|
+
take(item.effects, item.defaultWhen, 'held', item.ratingValue);
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
1242
|
+
take(item.effects, item.defaultWhen, 'worn', item.ratingValue);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
for (const item of this._inventory.getAllItems())
|
|
1246
|
+
take(item.effects, item.defaultWhen, 'carried', item.ratingValue);
|
|
1247
|
+
const runningOn = new Set();
|
|
1248
|
+
const deck = this.activeDeck ?? this.getCyberdeck();
|
|
1249
|
+
if (deck)
|
|
1250
|
+
runningOn.add(deck);
|
|
1251
|
+
const master = this.getPanMaster();
|
|
1252
|
+
if (master)
|
|
1253
|
+
runningOn.add(master);
|
|
1254
|
+
for (const device of runningOn) {
|
|
1255
|
+
for (const key of device.loadedPrograms) {
|
|
1256
|
+
const p = getProgram(key);
|
|
1257
|
+
const row = p ? catalogRowFor(`${p.name} Program`) ?? catalogRowFor(p.name) : undefined;
|
|
1258
|
+
if (row?.effects)
|
|
1259
|
+
take(row.effects, 'running', 'running', row.ratingValue);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
for (const device of [...this.commlinks(), ...this._inventory.getAllItems().filter(i => i.isCyberdeck())]) {
|
|
1263
|
+
for (const key of device.storedPrograms) {
|
|
1264
|
+
const soft = getSoft(key);
|
|
1265
|
+
const row = soft ? catalogRowFor(soft.name) : undefined;
|
|
1266
|
+
if (row?.effects)
|
|
1267
|
+
take(row.effects, 'stored', 'stored', row.ratingValue);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
return out;
|
|
1271
|
+
}
|
|
1272
|
+
sumEffects(type, property, only) {
|
|
1273
|
+
let total = 0;
|
|
1274
|
+
for (const { effect, rating } of this.applyingEffects()) {
|
|
1275
|
+
if (effect.type !== type || effect.property !== property)
|
|
1276
|
+
continue;
|
|
1277
|
+
if (effect.only !== undefined && effect.only !== only)
|
|
1278
|
+
continue;
|
|
1279
|
+
total += effect.value * (effect.scaleBy === 'rating' ? rating : 1);
|
|
1280
|
+
}
|
|
1281
|
+
return total;
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Dice the gear adds to a pool or a skill's tests (cf. augBonus,
|
|
1285
|
+
* qualityBonus): `kind` is a pool kind or a SkillName; `only` names a
|
|
1286
|
+
* narrowing an effect may carry ('haggle', 'silent', 'matrix-search').
|
|
1287
|
+
*/
|
|
1288
|
+
gearBonus(kind, opts = {}) {
|
|
1289
|
+
return this.sumEffects('dice', kind, opts.only);
|
|
1290
|
+
}
|
|
1291
|
+
/** What the gear adds to a LIMIT (a Shopsoft's +1 Social on a haggle). */
|
|
1292
|
+
gearLimit(kind, opts = {}) {
|
|
1293
|
+
return this.sumEffects('limit', kind, opts.only);
|
|
1294
|
+
}
|
|
1295
|
+
/** A capability the gear grants (dni, smartlink, no-fog, low-light...), by key. */
|
|
1296
|
+
hasCapability(key) {
|
|
1297
|
+
return this.sumEffects('flag', key) > 0;
|
|
1298
|
+
}
|
|
1181
1299
|
/**
|
|
1182
1300
|
* The single tap every pool reads chrome through. Wired Reflexes tiers
|
|
1183
1301
|
* don't stack -- the higher tier wins (II supersedes I) -- but the
|
|
@@ -1442,15 +1560,9 @@ export class Player extends AbstractPlayer {
|
|
|
1442
1560
|
const weapon = this.weaponDrawn ? this.getCarriedWeapon() : null;
|
|
1443
1561
|
if (!weapon)
|
|
1444
1562
|
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';
|
|
1563
|
+
// The weapon block names the skill (catalog v13); Item.weaponSkill
|
|
1564
|
+
// keeps the name/shape duck-typing for generated gear.
|
|
1565
|
+
return weapon.weaponSkill;
|
|
1454
1566
|
}
|
|
1455
1567
|
initializeEquipment() {
|
|
1456
1568
|
return {
|
|
@@ -2318,15 +2430,18 @@ export class Player extends AbstractPlayer {
|
|
|
2318
2430
|
// partly from ignoring armor. Quality still rides on top. A HOLSTERED
|
|
2319
2431
|
// weapon counts for nothing -- until it's drawn (brandish, or the
|
|
2320
2432
|
// auto-draw when an exchange opens), your hands are empty.
|
|
2321
|
-
|
|
2433
|
+
// CANON NUMBERS (catalog v13, project owner 2026-09-03): the DV is the
|
|
2434
|
+
// book's -- a Predator's 8, a knife's Strength + 2 -- read off the
|
|
2435
|
+
// weapon block (Item.weaponDv / dvAttribute), with the class value as
|
|
2436
|
+
// the fallback for generated gear. The PRICE TIER NO LONGER ADDS DAMAGE
|
|
2437
|
+
// OR DICE: canon has no such thing, and a Legendary knife is a knife.
|
|
2438
|
+
// Unarmed is (STR)S (p.187); bone lacing's +2 rides augBonus.
|
|
2322
2439
|
const weapon = this.weaponDrawn ? this.getCarriedWeapon() : null;
|
|
2323
2440
|
if (!weapon) {
|
|
2324
|
-
|
|
2325
|
-
return { name: 'bare fists', damage: 1 + strengthBase + this.augBonus('unarmed-damage'), poolBonus: 0, armorPen: 0 };
|
|
2441
|
+
return { name: 'bare fists', damage: this.strength + this.augBonus('unarmed-damage'), poolBonus: 0, armorPen: 0 };
|
|
2326
2442
|
}
|
|
2327
|
-
const
|
|
2328
|
-
|
|
2329
|
-
return { name: weapon.name, damage: base + bonus, poolBonus: bonus, armorPen: weapon.armorPen };
|
|
2443
|
+
const base = weapon.weaponDv + (weapon.dvAttribute === 'strength' ? this.strength : 0);
|
|
2444
|
+
return { name: weapon.name, damage: base, poolBonus: 0, armorPen: weapon.armorPen };
|
|
2330
2445
|
}
|
|
2331
2446
|
/** The weapon riding in a hand slot, drawn or holstered. */
|
|
2332
2447
|
getCarriedWeapon() {
|
|
@@ -2378,18 +2493,16 @@ export class Player extends AbstractPlayer {
|
|
|
2378
2493
|
for (const item of Object.values(group)) {
|
|
2379
2494
|
if (!item || !Player.ARMOR_CATEGORIES.has(item.category))
|
|
2380
2495
|
continue;
|
|
2381
|
-
|
|
2382
|
-
|
|
2496
|
+
// CANON NUMBERS (catalog v13): the row's Armor rating -- a jacket's
|
|
2497
|
+
// 12, a helmet's +2 -- with the class value as the fallback.
|
|
2498
|
+
const bonus = item.armorBonus;
|
|
2499
|
+
if (bonus > 0) {
|
|
2500
|
+
extras.push({ item, bonus });
|
|
2383
2501
|
continue;
|
|
2384
2502
|
}
|
|
2385
|
-
|
|
2386
|
-
|
|
2503
|
+
const value = item.armorValue;
|
|
2504
|
+
if (value <= 0)
|
|
2387
2505
|
continue;
|
|
2388
|
-
}
|
|
2389
|
-
if (item.category === Category.Boots || item.category === Category.Gloves
|
|
2390
|
-
|| item.category === Category.ArmArmor || item.category === Category.LegArmor)
|
|
2391
|
-
continue;
|
|
2392
|
-
const value = 1 + item.qualityBonus + (item.isHeavyArmor ? 1 : 0);
|
|
2393
2506
|
if (value > best) {
|
|
2394
2507
|
if (piece)
|
|
2395
2508
|
ignored.push(piece);
|
|
@@ -2401,7 +2514,16 @@ export class Player extends AbstractPlayer {
|
|
|
2401
2514
|
}
|
|
2402
2515
|
}
|
|
2403
2516
|
}
|
|
2404
|
-
|
|
2517
|
+
// p.169: the bonus from "+" accessories is capped at Strength, and
|
|
2518
|
+
// every 2 full points over it costs -1 Agility and Reaction.
|
|
2519
|
+
const bonusRaw = extras.reduce((n, e) => n + e.bonus, 0);
|
|
2520
|
+
const bonusCapped = Math.min(bonusRaw, this.strength);
|
|
2521
|
+
const encumbrance = Math.floor(Math.max(0, bonusRaw - this.strength) / 2);
|
|
2522
|
+
return { total: best + bonusCapped, piece, extras, ignored, bonusCapped, encumbrance };
|
|
2523
|
+
}
|
|
2524
|
+
/** The p.169 encumbrance penalty on Agility and Reaction, as dice off those pools. */
|
|
2525
|
+
get encumbrancePenalty() {
|
|
2526
|
+
return this.armorBreakdown().encumbrance;
|
|
2405
2527
|
}
|
|
2406
2528
|
getAttackPool() {
|
|
2407
2529
|
// Plane decides which attribute drives the fight: meat is muscle
|
|
@@ -2477,7 +2599,10 @@ export class Player extends AbstractPlayer {
|
|
|
2477
2599
|
// weapon exercises a FIREARM skill at all. Archery is deliberately
|
|
2478
2600
|
// not in that set -- a smartgun system is a firearm accessory
|
|
2479
2601
|
// (p.433), and a crossbow has nothing to plug into.
|
|
2480
|
-
|
|
2602
|
+
// AND THE GUN MUST BE A SMARTGUN (catalog v13, p.433): the wireless
|
|
2603
|
+
// bonus is the smartgun system talking to the smartlink.
|
|
2604
|
+
const drawnGun = this.weaponDrawn ? this.getCarriedWeapon() : null;
|
|
2605
|
+
const smartlink = drawnGun?.isSmartgun && FIREARM_SKILLS.includes(this.activeCombatSkill())
|
|
2481
2606
|
? (this.smartlinkTier() === 'gear' ? 1 : this.augBonus('firearms'))
|
|
2482
2607
|
: 0;
|
|
2483
2608
|
// RUNNING COSTS YOU (p.162 Running Modifiers): -2 dice on everything
|
|
@@ -2488,7 +2613,7 @@ export class Player extends AbstractPlayer {
|
|
|
2488
2613
|
// sprinted across a room to reach someone arrived shooting exactly
|
|
2489
2614
|
// as straight as one who strolled -- and the movement budget's whole
|
|
2490
2615
|
// 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);
|
|
2616
|
+
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
2617
|
}
|
|
2493
2618
|
/**
|
|
2494
2619
|
* The subdual pool: bare-hands work regardless of what's in the other
|
|
@@ -2535,7 +2660,7 @@ export class Player extends AbstractPlayer {
|
|
|
2535
2660
|
// Improved Reflexes rides Reaction itself (p.309), so it lands here too.
|
|
2536
2661
|
const adeptBonus = this.adeptDefenseBonus() + this.adeptReactionBonus();
|
|
2537
2662
|
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);
|
|
2663
|
+
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
2664
|
}
|
|
2540
2665
|
getSoakPool() {
|
|
2541
2666
|
// Matrix: firewall -- Logic plus the DECK's quality (your deck is your
|
|
@@ -2544,7 +2669,8 @@ export class Player extends AbstractPlayer {
|
|
|
2544
2669
|
// meat left behind. Drone: the HULL soaks -- airframe body scaled by
|
|
2545
2670
|
// build quality; the rider's armor is streets away with their body.
|
|
2546
2671
|
if (this.plane === 'drone') {
|
|
2547
|
-
|
|
2672
|
+
// Body + Armor (p.199), from the drone's vehicle block (catalog v13).
|
|
2673
|
+
return Math.max(0, (this.riggedDrone?.droneBody ?? 0) + (this.riggedDrone?.droneArmor ?? 0));
|
|
2548
2674
|
}
|
|
2549
2675
|
if (this.plane === 'matrix') {
|
|
2550
2676
|
// MATRIX DAMAGE IS RESISTED WITH DEVICE RATING + FIREWALL
|
|
@@ -2561,13 +2687,13 @@ export class Player extends AbstractPlayer {
|
|
|
2561
2687
|
}
|
|
2562
2688
|
// A loaded Armor program hardens it further (+2, p.245 cited).
|
|
2563
2689
|
const deck = this.activeDeck ?? this.getCyberdeck();
|
|
2564
|
-
return Math.max(0, (deck?.deviceRating ?? 0) + this.matrixAttribute('firewall') + (
|
|
2690
|
+
return Math.max(0, (deck?.deviceRating ?? 0) + this.matrixAttribute('firewall') + this.gearBonus('matrix-soak'));
|
|
2565
2691
|
}
|
|
2566
2692
|
if (this.plane === 'astral') {
|
|
2567
2693
|
return Math.max(0, this.willpower);
|
|
2568
2694
|
}
|
|
2569
2695
|
// 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'));
|
|
2696
|
+
return Math.max(0, this.body + this.getArmorValue() + this._sustainedArmor + this.adeptArmorBonus() + this.augBonus('soak') + this.qualityBonus('soak') + this.gearBonus('soak'));
|
|
2571
2697
|
}
|
|
2572
2698
|
/**
|
|
2573
2699
|
* THE INITIATIVE ATTRIBUTE -- what the dice are added TO (p.159,
|
|
@@ -3156,12 +3282,9 @@ export class Player extends AbstractPlayer {
|
|
|
3156
3282
|
// whose quality feeds the drone pools, and whose destruction is a
|
|
3157
3283
|
// DUMPSHOCK (see planes.ts leaveDrone).
|
|
3158
3284
|
riggedDrone;
|
|
3159
|
-
/** Whether the ridden drone carries a weapon mount (name
|
|
3285
|
+
/** Whether the ridden drone carries a weapon mount (its vehicle block, catalog v13; the name for generated frames). */
|
|
3160
3286
|
get droneArmed() {
|
|
3161
|
-
|
|
3162
|
-
if (!d)
|
|
3163
|
-
return false;
|
|
3164
|
-
return /armed|combat|gun|roto|assault|sentry|weapon/i.test(`${d.name} ${d.description ?? ''}`);
|
|
3287
|
+
return this.riggedDrone?.hasWeaponMount ?? false;
|
|
3165
3288
|
}
|
|
3166
3289
|
// Dumpshock disorientation (SR5e p.229): -2 on everything for
|
|
3167
3290
|
// (10 - Willpower) minutes after a forced dump, wall-clock -- the
|
|
@@ -3193,15 +3316,16 @@ export class Player extends AbstractPlayer {
|
|
|
3193
3316
|
// "persuaded-by:<name>" marks who already made their play on this actor
|
|
3194
3317
|
// this job. NPCs are rebuilt each scene, so it resets naturally.
|
|
3195
3318
|
socialMarks = new Set();
|
|
3196
|
-
/** The best Focus item on this actor, if any --
|
|
3319
|
+
/** The best Focus item on this actor, if any -- by Force (catalog v13: ratingValue). */
|
|
3197
3320
|
getBestFocus() {
|
|
3198
3321
|
return this._inventory.getAllItems()
|
|
3199
3322
|
.filter(i => i.category === Category.Focus)
|
|
3200
|
-
.sort((a, b) => b.
|
|
3323
|
+
.sort((a, b) => (b.ratingValue ?? 0) - (a.ratingValue ?? 0))[0];
|
|
3201
3324
|
}
|
|
3202
3325
|
getMagicPool() {
|
|
3203
|
-
|
|
3204
|
-
|
|
3326
|
+
// A focus adds its Force to the tests it is bonded for (p.318): the
|
|
3327
|
+
// row's effects, scaled by Force (catalog v13).
|
|
3328
|
+
const focusBonus = this.gearBonus('spellcasting');
|
|
3205
3329
|
// SR-proper when the SPELLCASTING skill is rated: Magic + Spellcasting
|
|
3206
3330
|
// for the Awakened, base + skill for a book-caster. Unrated falls back
|
|
3207
3331
|
// to the legacy shapes (talent max(5, magic+2); scholar's flat 5), so
|