@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.
@@ -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, override) {
327
+ function attackAbility(weapon, mods, weaponAbilities) {
308
328
  const isRanged = /ranged/.test(weapon.category);
309
- if (override && !isRanged) return override;
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
- return {
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
- extraDamage: magic.extra,
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, weaponAbilityOverride) {
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, weaponAbilityOverride);
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, weaponAbilityOverride) {
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, weaponAbilityOverride);
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
- weaponAbility: null,
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 (out.weaponAbility === null && eff.ability !== "spellcasting") out.weaponAbility = eff.ability;
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
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseItem
3
- } from "./chunk-4SSDYJS2.js";
3
+ } from "./chunk-YDPWD2T7.js";
4
4
 
5
5
  // src/item/item.codec.ts
6
6
  import yaml from "js-yaml";
@@ -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-4C2KOKLF.js";
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",