@archivist-gg/dnd5e 0.1.0 → 0.1.1
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/dist/{chunk-QQ5W2AZP.js → chunk-ADLTSHOR.js} +63 -12
- package/dist/{chunk-XURS6Q6Q.js → chunk-GEJSJP6E.js} +19 -5
- package/dist/{chunk-537BHVSH.js → chunk-TPZSPU4L.js} +1 -1
- package/dist/{chunk-4C2KOKLF.js → chunk-VLKIB33F.js} +6 -0
- package/dist/{chunk-4SSDYJS2.js → chunk-YDPWD2T7.js} +2 -1
- package/dist/index.js +304 -46
- package/dist/item/item.codec.js +3 -3
- package/dist/item/item.parser.js +2 -2
- package/dist/item/item.schema.d.ts +10 -0
- package/dist/item/item.schema.js +1 -1
- package/dist/item/item.types.d.ts +9 -0
- package/dist/pc/pc.equipment.d.ts +2 -1
- package/dist/pc/pc.equipment.js +5 -1
- package/dist/pc/pc.feature-effects.d.ts +31 -9
- package/dist/pc/pc.feature-effects.js +4 -1
- package/dist/pc/pc.recalc.js +63 -55
- package/dist/pc/pc.resolver.d.ts +12 -0
- package/dist/pc/pc.resolver.js +111 -22
- package/dist/pc/pc.types.d.ts +48 -1
- package/dist/weapon/weapon-mastery.d.ts +9 -0
- package/package.json +1 -1
|
@@ -5,6 +5,9 @@ import {
|
|
|
5
5
|
isWeaponEntity,
|
|
6
6
|
resolveEntityForEntry
|
|
7
7
|
} from "./chunk-UC4FE5X3.js";
|
|
8
|
+
import {
|
|
9
|
+
bareEntitySlug
|
|
10
|
+
} from "./chunk-TJCNX6UZ.js";
|
|
8
11
|
import {
|
|
9
12
|
readNumericBonus
|
|
10
13
|
} from "./chunk-LWFTHJIE.js";
|
|
@@ -12,6 +15,23 @@ import {
|
|
|
12
15
|
resolveBaseItem
|
|
13
16
|
} from "./chunk-DC74TZNA.js";
|
|
14
17
|
|
|
18
|
+
// src/weapon/weapon-mastery.ts
|
|
19
|
+
var MASTERY = {
|
|
20
|
+
cleave: { label: "Cleave", description: "If you hit a creature with a melee attack roll using this weapon, you can make an attack roll with the weapon against a second creature within 5 feet of the first that is also within your reach. On a hit, the second creature takes the weapon's damage, but don't add your ability modifier to that damage unless it is negative. You can make this extra attack only once per turn." },
|
|
21
|
+
graze: { label: "Graze", description: "If your attack roll with this weapon misses a creature, you can deal damage to that creature equal to the ability modifier you used to make the attack roll. This damage is the same type dealt by the weapon, and the damage can be increased only by increasing the ability modifier." },
|
|
22
|
+
nick: { label: "Nick", description: "When you make the extra attack of the Light property, you can make it as part of the Attack action instead of as a Bonus Action. You can make this extra attack only once per turn." },
|
|
23
|
+
push: { label: "Push", description: "If you hit a creature with this weapon, you can push the creature up to 10 feet straight away from yourself if it is Large or smaller." },
|
|
24
|
+
sap: { label: "Sap", description: "If you hit a creature with this weapon, that creature has Disadvantage on its next attack roll before the start of your next turn." },
|
|
25
|
+
slow: { label: "Slow", description: "If you hit a creature with this weapon and deal damage, you can reduce its Speed by 10 feet until the start of your next turn. If the creature is hit more than once by weapons that have this property, the Speed reduction doesn't exceed 10 feet." },
|
|
26
|
+
topple: { label: "Topple", description: "If you hit a creature with this weapon, you can force the creature to make a Constitution saving throw (DC 8 plus the ability modifier used to make the attack roll and your Proficiency Bonus). On a failed save, the creature has the Prone condition." },
|
|
27
|
+
vex: { label: "Vex", description: "If you hit a creature with this weapon and deal damage, you have Advantage on your next attack roll against that creature before the end of your next turn." }
|
|
28
|
+
};
|
|
29
|
+
function masteryDerived(slug, abilityMod, proficiencyBonus) {
|
|
30
|
+
if (slug === "topple") return { label: "Save DC", value: 8 + proficiencyBonus + abilityMod };
|
|
31
|
+
if (slug === "graze") return { label: "On miss", value: abilityMod };
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
15
35
|
// src/pc/pc.equipment.ts
|
|
16
36
|
var ABILITY_KEYS = ["str", "dex", "con", "int", "wis", "cha"];
|
|
17
37
|
function isAbilityKey(k) {
|
|
@@ -304,9 +324,17 @@ function computeAC(equippedSlots, resolved, mods, registry) {
|
|
|
304
324
|
function hasProperty(weapon, prop) {
|
|
305
325
|
return weapon.properties.some((p) => p === prop);
|
|
306
326
|
}
|
|
307
|
-
function attackAbility(weapon, mods,
|
|
327
|
+
function attackAbility(weapon, mods, weaponAbilities) {
|
|
308
328
|
const isRanged = /ranged/.test(weapon.category);
|
|
309
|
-
if (
|
|
329
|
+
if (!isRanged) {
|
|
330
|
+
const scoped = weaponAbilities.find((o2) => {
|
|
331
|
+
var _a;
|
|
332
|
+
return (_a = o2.weaponSlugs) == null ? void 0 : _a.includes(bareEntitySlug(weapon.slug));
|
|
333
|
+
});
|
|
334
|
+
const glob = weaponAbilities.find((o2) => !o2.weaponSlugs);
|
|
335
|
+
const o = scoped != null ? scoped : glob;
|
|
336
|
+
if (o) return o.ability;
|
|
337
|
+
}
|
|
310
338
|
if (isRanged) return "dex";
|
|
311
339
|
if (hasProperty(weapon, "finesse")) return mods.dex >= mods.str ? "dex" : "str";
|
|
312
340
|
return "str";
|
|
@@ -336,6 +364,7 @@ function magicBonusesForWeaponEntry(entry, registry, ctx) {
|
|
|
336
364
|
let damageTypeOverride;
|
|
337
365
|
let propertiesOverride;
|
|
338
366
|
const informational = [];
|
|
367
|
+
let itemRiders = [];
|
|
339
368
|
if (entity && isItemEntity(entity)) {
|
|
340
369
|
sourceName = entity.name;
|
|
341
370
|
itemName = entity.name;
|
|
@@ -350,6 +379,10 @@ function magicBonusesForWeaponEntry(entry, registry, ctx) {
|
|
|
350
379
|
if ((dmgOut == null ? void 0 : dmgOut.kind) === "applied") itemDamage = dmgOut.value;
|
|
351
380
|
else if ((dmgOut == null ? void 0 : dmgOut.kind) === "informational")
|
|
352
381
|
informational.push({ field: "weapon_damage", source: entity.name, value: dmgOut.value, conditions: dmgOut.conditions });
|
|
382
|
+
itemRiders = Array.isArray(entity.damage_riders) ? entity.damage_riders.filter((r) => {
|
|
383
|
+
var _a2;
|
|
384
|
+
return ((_a2 = r.applies_to) != null ? _a2 : "weapon") !== "spell";
|
|
385
|
+
}).map((r) => ({ amount: r.amount, damage_type: r.damage_type, source: entity.name })) : [];
|
|
353
386
|
}
|
|
354
387
|
return {
|
|
355
388
|
atk: itemAttack + entryAttack,
|
|
@@ -359,12 +392,13 @@ function magicBonusesForWeaponEntry(entry, registry, ctx) {
|
|
|
359
392
|
itemName,
|
|
360
393
|
damageTypeOverride,
|
|
361
394
|
propertiesOverride,
|
|
362
|
-
informational
|
|
395
|
+
informational,
|
|
396
|
+
riders: itemRiders
|
|
363
397
|
};
|
|
364
398
|
}
|
|
365
399
|
function buildAttackRow(args) {
|
|
366
|
-
var _a, _b;
|
|
367
|
-
const { id, name, weapon, baseDice, ability, mods, proficient, proficiencyBonus, magic } = args;
|
|
400
|
+
var _a, _b, _c;
|
|
401
|
+
const { id, name, weapon, baseDice, ability, mods, proficient, proficiencyBonus, magic, weaponMasteries } = args;
|
|
368
402
|
const abilityMod = mods[ability];
|
|
369
403
|
const pb = proficient ? proficiencyBonus : 0;
|
|
370
404
|
const toHit = abilityMod + pb + magic.atk;
|
|
@@ -388,14 +422,21 @@ function buildAttackRow(args) {
|
|
|
388
422
|
(p) => typeof p === "string"
|
|
389
423
|
);
|
|
390
424
|
const finalProps = (_a = magic.propertiesOverride) != null ? _a : stringProps;
|
|
391
|
-
|
|
425
|
+
const row = {
|
|
392
426
|
id,
|
|
393
427
|
name,
|
|
394
428
|
range,
|
|
395
429
|
toHit,
|
|
396
430
|
damageDice,
|
|
397
431
|
damageType: (_b = magic.damageTypeOverride) != null ? _b : weapon.damage.type,
|
|
398
|
-
|
|
432
|
+
// Riders: item damage_riders (Task 2) + the migrated manual override string.
|
|
433
|
+
...(() => {
|
|
434
|
+
const riders = [
|
|
435
|
+
...magic.riders,
|
|
436
|
+
...magic.extra ? [{ amount: magic.extra, source: "manual" }] : []
|
|
437
|
+
];
|
|
438
|
+
return riders.length ? { damageRiders: riders } : {};
|
|
439
|
+
})(),
|
|
399
440
|
properties: finalProps,
|
|
400
441
|
proficient,
|
|
401
442
|
breakdown: { toHit: toHitBreakdown, damage: damageBreakdown },
|
|
@@ -405,6 +446,15 @@ function buildAttackRow(args) {
|
|
|
405
446
|
actionCost: args.actionCost,
|
|
406
447
|
versatile: args.versatileDice ? { damageDice: formatDice(args.versatileDice) } : void 0
|
|
407
448
|
};
|
|
449
|
+
const mSlug = (_c = weapon.mastery) == null ? void 0 : _c[0];
|
|
450
|
+
if (mSlug && proficient && weaponMasteries.includes(bareEntitySlug(weapon.slug))) {
|
|
451
|
+
const g = MASTERY[mSlug];
|
|
452
|
+
if (g) {
|
|
453
|
+
const derived = masteryDerived(mSlug, mods[ability], proficiencyBonus);
|
|
454
|
+
row.mastery = { slug: mSlug, label: g.label, description: g.description, ...derived ? { derived } : {} };
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return row;
|
|
408
458
|
}
|
|
409
459
|
function formatWeaponSubLabel(weapon, properties) {
|
|
410
460
|
const parts = [];
|
|
@@ -412,7 +462,7 @@ function formatWeaponSubLabel(weapon, properties) {
|
|
|
412
462
|
if (properties.length > 0) parts.push(properties.join(", "));
|
|
413
463
|
return parts.join(" \xB7 ");
|
|
414
464
|
}
|
|
415
|
-
function computeAttacks(equippedSlots, mods, profs, registry, warnings, proficiencyBonus, ctx,
|
|
465
|
+
function computeAttacks(equippedSlots, mods, profs, registry, warnings, proficiencyBonus, ctx, weaponMasteries, weaponAbilities = []) {
|
|
416
466
|
var _a, _b, _c, _d, _e;
|
|
417
467
|
const rows = [];
|
|
418
468
|
const handedSlots = [
|
|
@@ -443,7 +493,7 @@ function computeAttacks(equippedSlots, mods, profs, registry, warnings, proficie
|
|
|
443
493
|
if (!proficient) {
|
|
444
494
|
warnings.push(`Character not proficient with ${weapon.name}; proficiency bonus excluded.`);
|
|
445
495
|
}
|
|
446
|
-
const ability = attackAbility(weapon, mods,
|
|
496
|
+
const ability = attackAbility(weapon, mods, weaponAbilities);
|
|
447
497
|
const magic = magicBonusesForWeaponEntry(entry, registry, ctx);
|
|
448
498
|
const ovr = (_b = entry.overrides) != null ? _b : {};
|
|
449
499
|
const displayName = (_d = (_c = ovr.name) != null ? _c : magic.itemName) != null ? _d : weapon.name;
|
|
@@ -460,7 +510,8 @@ function computeAttacks(equippedSlots, mods, profs, registry, warnings, proficie
|
|
|
460
510
|
magic,
|
|
461
511
|
slotKey: key,
|
|
462
512
|
actionCost: (_e = ovr.action) != null ? _e : "action",
|
|
463
|
-
versatileDice
|
|
513
|
+
versatileDice,
|
|
514
|
+
weaponMasteries
|
|
464
515
|
});
|
|
465
516
|
rows.push(baseRow);
|
|
466
517
|
}
|
|
@@ -488,13 +539,13 @@ function computeCarriedWeight(resolved, registry) {
|
|
|
488
539
|
}
|
|
489
540
|
return total;
|
|
490
541
|
}
|
|
491
|
-
function computeSlotsAndAttacks(resolved, mods, profs, registry, warnings, proficiencyBonus,
|
|
542
|
+
function computeSlotsAndAttacks(resolved, mods, profs, registry, warnings, proficiencyBonus, weaponAbilities = [], weaponMasteries = []) {
|
|
492
543
|
var _a, _b, _c;
|
|
493
544
|
const equippedSlots = assignSlots(resolved, registry, warnings);
|
|
494
545
|
const overrides = (_a = resolved.definition.overrides) != null ? _a : {};
|
|
495
546
|
const acOut = computeAC(equippedSlots, resolved, mods, registry);
|
|
496
547
|
const ctx = buildConditionContext(resolved, equippedSlots);
|
|
497
|
-
const attacks = computeAttacks(equippedSlots, mods, profs, registry, warnings, proficiencyBonus, ctx,
|
|
548
|
+
const attacks = computeAttacks(equippedSlots, mods, profs, registry, warnings, proficiencyBonus, ctx, weaponMasteries, weaponAbilities);
|
|
498
549
|
return {
|
|
499
550
|
ac: acOut.ac,
|
|
500
551
|
acBreakdown: acOut.breakdown,
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bareEntitySlug
|
|
3
|
+
} from "./chunk-TJCNX6UZ.js";
|
|
1
4
|
import {
|
|
2
5
|
ABILITY_KEYS
|
|
3
6
|
} from "./chunk-WP2CX2JO.js";
|
|
@@ -14,11 +17,12 @@ function emptyFeatureEffectTotals() {
|
|
|
14
17
|
resistances: [],
|
|
15
18
|
condition_immunities: [],
|
|
16
19
|
proficiencies: { skills: [], tools: [], languages: [], saves: [], armor: [], weapons: [] },
|
|
17
|
-
|
|
20
|
+
weaponAbilities: [],
|
|
18
21
|
rollModifiers: [],
|
|
19
22
|
critRange: 20,
|
|
20
23
|
extraAttack: 0,
|
|
21
|
-
attackNotes: []
|
|
24
|
+
attackNotes: [],
|
|
25
|
+
damageBonuses: []
|
|
22
26
|
};
|
|
23
27
|
}
|
|
24
28
|
var ABILITY_NAME_TO_KEY = {
|
|
@@ -55,7 +59,7 @@ function computeFeatureEffects(features, opts) {
|
|
|
55
59
|
return out;
|
|
56
60
|
}
|
|
57
61
|
function applyEffect(out, eff, label) {
|
|
58
|
-
var _a;
|
|
62
|
+
var _a, _b;
|
|
59
63
|
switch (eff.kind) {
|
|
60
64
|
case "initiative-bonus":
|
|
61
65
|
out.initiative_bonus += eff.value;
|
|
@@ -98,9 +102,14 @@ function applyEffect(out, eff, label) {
|
|
|
98
102
|
case "ac-bonus":
|
|
99
103
|
out.ac_terms.push({ value: eff.value, requires_armor: eff.requires_armor === true, label });
|
|
100
104
|
break;
|
|
101
|
-
case "weapon-ability":
|
|
102
|
-
if (
|
|
105
|
+
case "weapon-ability": {
|
|
106
|
+
if (eff.ability !== "spellcasting") {
|
|
107
|
+
const w = eff.weapons;
|
|
108
|
+
const weaponSlugs = !w || w === "chosen" ? void 0 : Array.isArray(w) ? w.map(bareEntitySlug) : [bareEntitySlug(w)];
|
|
109
|
+
out.weaponAbilities.push({ ability: eff.ability, weaponSlugs });
|
|
110
|
+
}
|
|
103
111
|
break;
|
|
112
|
+
}
|
|
104
113
|
case "roll-modifier":
|
|
105
114
|
out.rollModifiers.push({ mode: eff.mode, roll: eff.roll, scope: eff.scope, condition: eff.condition, label });
|
|
106
115
|
break;
|
|
@@ -116,6 +125,11 @@ function applyEffect(out, eff, label) {
|
|
|
116
125
|
case "attack-rule":
|
|
117
126
|
if (eff.flag === "no-ranged-in-melee-disadvantage") out.attackNotes.push("No disadvantage firing in melee");
|
|
118
127
|
break;
|
|
128
|
+
case "damage-bonus":
|
|
129
|
+
if (((_b = eff.applies_to) != null ? _b : "weapon") !== "spell") {
|
|
130
|
+
out.damageBonuses.push({ amount: eff.amount, damage_type: eff.damage_type, source: label });
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
119
133
|
default:
|
|
120
134
|
break;
|
|
121
135
|
}
|
|
@@ -170,6 +170,12 @@ var itemEntitySchema = z.object({
|
|
|
170
170
|
description: z.string().optional(),
|
|
171
171
|
entries: z.array(z.unknown()).optional(),
|
|
172
172
|
effects: z.array(z.unknown()).optional(),
|
|
173
|
+
damage_riders: z.array(z.object({
|
|
174
|
+
amount: z.string(),
|
|
175
|
+
damage_type: z.string(),
|
|
176
|
+
applies_to: z.enum(["weapon", "spell", "all"]).optional(),
|
|
177
|
+
condition: z.string().optional()
|
|
178
|
+
})).optional(),
|
|
173
179
|
raw: z.record(z.string(), z.unknown()).optional(),
|
|
174
180
|
// Legacy fields preserved
|
|
175
181
|
damage_dice: z.string().optional(),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
itemEntitySchema
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-VLKIB33F.js";
|
|
4
4
|
|
|
5
5
|
// src/item/item.parser.ts
|
|
6
6
|
import { parseYaml } from "@archivist-gg/core";
|
|
@@ -36,6 +36,7 @@ var KNOWN_KEYS = /* @__PURE__ */ new Set([
|
|
|
36
36
|
"description",
|
|
37
37
|
"entries",
|
|
38
38
|
"effects",
|
|
39
|
+
"damage_riders",
|
|
39
40
|
"raw",
|
|
40
41
|
// Legacy fields kept (removed in Slice 7 after grep verifies no consumer)
|
|
41
42
|
"damage_dice",
|