@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.
@@ -1,6 +1,7 @@
1
1
  import type { EntityRegistry } from "@archivist-gg/core";
2
2
  import type { Ability } from "@archivist-gg/dnd5e";
3
3
  import type { AppliedBonuses, DerivedEquipment, ResolvedCharacter, ProficiencySet } from "./pc.types.js";
4
+ import type { WeaponAbilityOverride } from "./pc.feature-effects.js";
4
5
  interface ProficienciesForQuery {
5
6
  armor: ProficiencySet;
6
7
  weapons: ProficiencySet;
@@ -8,5 +9,5 @@ interface ProficienciesForQuery {
8
9
  }
9
10
  export declare function emptyAppliedBonuses(): AppliedBonuses;
10
11
  export declare function computeAppliedBonuses(resolved: ResolvedCharacter, _profs: ProficienciesForQuery, registry: EntityRegistry, warnings: string[]): AppliedBonuses;
11
- export declare function computeSlotsAndAttacks(resolved: ResolvedCharacter, mods: Record<Ability, number>, profs: ProficienciesForQuery, registry: EntityRegistry, warnings: string[], proficiencyBonus: number, weaponAbilityOverride?: Ability): DerivedEquipment;
12
+ export declare function computeSlotsAndAttacks(resolved: ResolvedCharacter, mods: Record<Ability, number>, profs: ProficienciesForQuery, registry: EntityRegistry, warnings: string[], proficiencyBonus: number, weaponAbilities?: WeaponAbilityOverride[], weaponMasteries?: string[]): DerivedEquipment;
12
13
  export {};
@@ -2,12 +2,16 @@ import {
2
2
  computeAppliedBonuses,
3
3
  computeSlotsAndAttacks,
4
4
  emptyAppliedBonuses
5
- } from "../chunk-QQ5W2AZP.js";
5
+ } from "../chunk-ADLTSHOR.js";
6
6
  import "../chunk-UC4FE5X3.js";
7
+ import "../chunk-TJCNX6UZ.js";
8
+ import "../chunk-BH6AMZVY.js";
9
+ import "../chunk-JEYQU3JT.js";
7
10
  import "../chunk-LWFTHJIE.js";
8
11
  import "../chunk-DNAJRMNN.js";
9
12
  import "../chunk-DC74TZNA.js";
10
13
  import "../chunk-3TRGRSCH.js";
14
+ import "../chunk-WP2CX2JO.js";
11
15
  export {
12
16
  computeAppliedBonuses,
13
17
  computeSlotsAndAttacks,
@@ -1,13 +1,25 @@
1
1
  import type { SenseType } from "@archivist-gg/dnd5e/types/feature-effect";
2
2
  import type { Ability } from "@archivist-gg/dnd5e";
3
- import type { ResolvedFeature, RollModifierEntry } from "./pc.types.js";
3
+ import type { DamageRider, ResolvedFeature, RollModifierEntry } from "./pc.types.js";
4
+ /**
5
+ * A melee-attack ability override from a `weapon-ability` effect. `weaponSlugs`
6
+ * (bare, namespace-stripped) scopes the override to matching weapon types only;
7
+ * empty/absent `weaponSlugs` = GLOBAL (applies to every melee weapon). The
8
+ * `"spellcasting"` sentinel is NOT stored here — it is resolved against the
9
+ * caster ability in recalc and prepended as a global.
10
+ */
11
+ export interface WeaponAbilityOverride {
12
+ ability: Ability;
13
+ weaponSlugs?: string[];
14
+ }
4
15
  /**
5
16
  * Aggregated passive feature effects (effects-application engine).
6
17
  * One pure scan over resolved.features[].feature.effects[]. Merge semantics
7
18
  * mirror pc.conditions mergePartial: numbers add, each sense range takes max,
8
- * lists union case-insensitively. apply-condition and damage-bonus are action/attack
9
- * time and intentionally not aggregated; `while`-gated immune-condition
10
- * entries are skipped entirely (conditional effects are a named deferral).
19
+ * lists union case-insensitively. apply-condition is action time and
20
+ * intentionally not aggregated; `damage-bonus` folds into damageBonuses (additive
21
+ * on-hit riders); `while`-gated immune-condition entries are skipped entirely
22
+ * (conditional effects are a named deferral).
11
23
  */
12
24
  export interface FeatureEffectTotals {
13
25
  initiative_bonus: number;
@@ -46,12 +58,15 @@ export interface FeatureEffectTotals {
46
58
  weapons: string[];
47
59
  };
48
60
  /**
49
- * v1 global melee-attack ability override (Hexblade "Lies", etc.). The first
50
- * `weapon-ability` effect with a concrete ability (not `"spellcasting"`) wins;
51
- * `"spellcasting"` is resolved against the caster ability in recalc. `weapons`
52
- * scope is parsed but ignored here (global override). null = no override.
61
+ * Melee-attack ability overrides (Hexblade "Lies", MCDM Illrigger scoped
62
+ * "Lies", etc.), in fold order. Each carries an optional `weaponSlugs` scope
63
+ * (bare slugs) — absent/empty = GLOBAL (every melee weapon). The
64
+ * `"spellcasting"` sentinel is excluded here (no caster context in the fold);
65
+ * recalc resolves it and prepends the resolved global so it wins over concrete
66
+ * globals. recalc threads this list into attack computation, where a
67
+ * scoped-match wins over a global for the matching weapon. [] = no override.
53
68
  */
54
- weaponAbility: Ability | null;
69
+ weaponAbilities: WeaponAbilityOverride[];
55
70
  /**
56
71
  * Order-preserving list of structured advantage/disadvantage entries from
57
72
  * `roll-modifier` effects. Pass-through (no dedupe/merge); each entry is
@@ -79,6 +94,13 @@ export interface FeatureEffectTotals {
79
94
  * non-empty, so untouched attack rows keep `attackNotes: undefined`.
80
95
  */
81
96
  attackNotes: string[];
97
+ /**
98
+ * Additive on-hit damage riders from `damage-bonus` effects with
99
+ * `applies_to` weapon/all (spell-only ignored — no spell surface). Each is
100
+ * labeled with the owning feature's name. recalc merges these onto every
101
+ * weapon AttackRow's `damageRiders`.
102
+ */
103
+ damageBonuses: DamageRider[];
82
104
  }
83
105
  export declare function emptyFeatureEffectTotals(): FeatureEffectTotals;
84
106
  /**
@@ -1,7 +1,10 @@
1
1
  import {
2
2
  computeFeatureEffects,
3
3
  emptyFeatureEffectTotals
4
- } from "../chunk-XURS6Q6Q.js";
4
+ } from "../chunk-GEJSJP6E.js";
5
+ import "../chunk-TJCNX6UZ.js";
6
+ import "../chunk-BH6AMZVY.js";
7
+ import "../chunk-JEYQU3JT.js";
5
8
  import "../chunk-WP2CX2JO.js";
6
9
  export {
7
10
  computeFeatureEffects,
@@ -7,21 +7,21 @@ import "../chunk-5XUXXAQJ.js";
7
7
  import {
8
8
  computeConditionEffects
9
9
  } from "../chunk-R2ZV233X.js";
10
- import {
11
- collectChosenAbilityPoints,
12
- collectChosenProficiencies
13
- } from "../chunk-TJCNX6UZ.js";
14
- import "../chunk-BH6AMZVY.js";
15
- import "../chunk-JEYQU3JT.js";
16
10
  import {
17
11
  computeAppliedBonuses,
18
12
  computeSlotsAndAttacks,
19
13
  emptyAppliedBonuses
20
- } from "../chunk-QQ5W2AZP.js";
14
+ } from "../chunk-ADLTSHOR.js";
21
15
  import "../chunk-UC4FE5X3.js";
22
16
  import {
23
17
  computeFeatureEffects
24
- } from "../chunk-XURS6Q6Q.js";
18
+ } from "../chunk-GEJSJP6E.js";
19
+ import {
20
+ collectChosenAbilityPoints,
21
+ collectChosenProficiencies
22
+ } from "../chunk-TJCNX6UZ.js";
23
+ import "../chunk-BH6AMZVY.js";
24
+ import "../chunk-JEYQU3JT.js";
25
25
  import "../chunk-LWFTHJIE.js";
26
26
  import "../chunk-DNAJRMNN.js";
27
27
  import "../chunk-DC74TZNA.js";
@@ -362,24 +362,26 @@ function computeProficiencies(resolved) {
362
362
  };
363
363
  }
364
364
  function recalc(resolved, registry) {
365
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X;
365
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W;
366
366
  const warnings = [];
367
367
  const overrides = (_a = resolved.definition.overrides) != null ? _a : {};
368
368
  const chosenProfs = collectChosenProficiencies(resolved);
369
369
  const activeBuffs = new Set((_b = resolved.state.active_buffs) != null ? _b : []);
370
370
  const buffFeatures = [];
371
+ const pushBoon = (item, pool) => {
372
+ var _a2, _b2, _c2, _d2, _e2, _f2;
373
+ const e = item.entity;
374
+ if (!e || ((_b2 = (_a2 = e.effects) == null ? void 0 : _a2.length) != null ? _b2 : 0) === 0) return;
375
+ buffFeatures.push({
376
+ feature: { id: item.slug, name: e.name, activatable: (_c2 = e.activatable) != null ? _c2 : false, effects: e.effects },
377
+ // source is inert for the fold; attribute to the pool's owning class
378
+ // (never a hardcoded class) so the generic engine carries no homebrew name.
379
+ source: { kind: "class", slug: (_f2 = (_e2 = (_d2 = resolved.classes[pool.classIndex]) == null ? void 0 : _d2.entity) == null ? void 0 : _e2.slug) != null ? _f2 : pool.id, level: pool.anchorLevel }
380
+ });
381
+ };
371
382
  for (const pool of (_c = resolved.pools) != null ? _c : []) {
372
- for (const sel of (_d = pool.selected) != null ? _d : []) {
373
- const e = sel.entity;
374
- if ((e == null ? void 0 : e.activatable) && ((_f = (_e = e.effects) == null ? void 0 : _e.length) != null ? _f : 0) > 0) {
375
- buffFeatures.push({
376
- feature: { id: sel.slug, name: e.name, activatable: true, effects: e.effects },
377
- // source is inert for the fold; attribute to the pool's owning class
378
- // (never a hardcoded class) so the generic engine carries no homebrew name.
379
- source: { kind: "class", slug: (_i = (_h = (_g = resolved.classes[pool.classIndex]) == null ? void 0 : _g.entity) == null ? void 0 : _h.slug) != null ? _i : pool.id, level: pool.anchorLevel }
380
- });
381
- }
382
- }
383
+ for (const sel of (_d = pool.selected) != null ? _d : []) pushBoon(sel, pool);
384
+ for (const g of (_e = pool.grants) != null ? _e : []) pushBoon(g, pool);
383
385
  }
384
386
  const featureEffects = computeFeatureEffects([...resolved.features, ...buffFeatures], { activeBuffs });
385
387
  const profsForApply = computeProficiencies(resolved);
@@ -443,19 +445,20 @@ function recalc(resolved, registry) {
443
445
  resolved.definition.edition,
444
446
  scores
445
447
  );
446
- const firstClass = (_k = (_j = resolved.classes[0]) == null ? void 0 : _j.entity) != null ? _k : null;
447
- const saveProfs = new Set((_l = firstClass == null ? void 0 : firstClass.saving_throws) != null ? _l : []);
448
+ const firstClass = (_g = (_f = resolved.classes[0]) == null ? void 0 : _f.entity) != null ? _g : null;
449
+ const saveProfs = new Set((_h = firstClass == null ? void 0 : firstClass.saving_throws) != null ? _h : []);
448
450
  for (const ab of featureEffects.proficiencies.saves) saveProfs.add(ab);
449
451
  const saves = {};
450
452
  for (const ab of ABILITY_KEYS) {
451
- const override = (_m = overrides.saves) == null ? void 0 : _m[ab];
452
- const prof = (_n = override == null ? void 0 : override.proficient) != null ? _n : saveProfs.has(ab);
453
+ const override = (_i = overrides.saves) == null ? void 0 : _i[ab];
454
+ const prof = (_j = override == null ? void 0 : override.proficient) != null ? _j : saveProfs.has(ab);
453
455
  const derivedBonus = savingThrow(scores[ab], prof, proficiencyBonus) + applied.save_bonus + conditionEffects.d20_test_penalty;
454
- const bonus = (_o = override == null ? void 0 : override.bonus) != null ? _o : derivedBonus;
456
+ const bonus = (_k = override == null ? void 0 : override.bonus) != null ? _k : derivedBonus;
455
457
  saves[ab] = { bonus, proficient: prof };
456
458
  }
457
459
  const profSet = /* @__PURE__ */ new Set([
458
460
  ...resolved.definition.skills.proficient,
461
+ ...(_m = (_l = resolved.background) == null ? void 0 : _l.skill_proficiencies) != null ? _m : [],
459
462
  ...chosenProfs.skills,
460
463
  ...featureEffects.proficiencies.skills
461
464
  ]);
@@ -464,9 +467,9 @@ function recalc(resolved, registry) {
464
467
  for (const skill of ALL_SKILLS) {
465
468
  const skillKey = skillSlugFromDisplay(skill);
466
469
  const ab = SKILL_ABILITY[skillSlugToAbilityLookup(skillKey)];
467
- const override = (_p = overrides.skills) == null ? void 0 : _p[skillKey];
468
- const tri = (_q = override == null ? void 0 : override.proficiency) != null ? _q : expSet.has(skillKey) ? "expertise" : profSet.has(skillKey) ? "proficient" : "none";
469
- const bonus = (_r = override == null ? void 0 : override.bonus) != null ? _r : skillBonus(scores[ab], tri, proficiencyBonus) + conditionEffects.d20_test_penalty;
470
+ const override = (_n = overrides.skills) == null ? void 0 : _n[skillKey];
471
+ const tri = (_o = override == null ? void 0 : override.proficiency) != null ? _o : expSet.has(skillKey) ? "expertise" : profSet.has(skillKey) ? "proficient" : "none";
472
+ const bonus = (_p = override == null ? void 0 : override.bonus) != null ? _p : skillBonus(scores[ab], tri, proficiencyBonus) + conditionEffects.d20_test_penalty;
470
473
  skills[skillKey] = {
471
474
  bonus,
472
475
  proficiency: tri,
@@ -477,16 +480,16 @@ function recalc(resolved, registry) {
477
480
  const investigationTri = skills.investigation.proficiency;
478
481
  const insightTri = skills.insight.proficiency;
479
482
  const passives = {
480
- perception: (_t = (_s = overrides.passives) == null ? void 0 : _s.perception) != null ? _t : passivePerception(scores.wis, perceptionTri, proficiencyBonus),
481
- investigation: (_v = (_u = overrides.passives) == null ? void 0 : _u.investigation) != null ? _v : passive(scores.int, investigationTri, proficiencyBonus),
482
- insight: (_x = (_w = overrides.passives) == null ? void 0 : _w.insight) != null ? _x : passive(scores.wis, insightTri, proficiencyBonus)
483
+ perception: (_r = (_q = overrides.passives) == null ? void 0 : _q.perception) != null ? _r : passivePerception(scores.wis, perceptionTri, proficiencyBonus),
484
+ investigation: (_t = (_s = overrides.passives) == null ? void 0 : _s.investigation) != null ? _t : passive(scores.int, investigationTri, proficiencyBonus),
485
+ insight: (_v = (_u = overrides.passives) == null ? void 0 : _u.insight) != null ? _v : passive(scores.wis, insightTri, proficiencyBonus)
483
486
  };
484
487
  const hpMaxDerived = multiclassMaxHP(resolved.classes, mods.con) + featureEffects.hp_per_level_bonus * totalLevel;
485
488
  const hpMaxAfterConditions = Math.floor(hpMaxDerived * conditionEffects.hp_max_multiplier);
486
- const hpMax = (_z = (_y = overrides.hp) == null ? void 0 : _y.max) != null ? _z : hpMaxAfterConditions;
489
+ const hpMax = (_x = (_w = overrides.hp) == null ? void 0 : _w.max) != null ? _x : hpMaxAfterConditions;
487
490
  const featureAcTermsFor = (hasArmor) => featureEffects.ac_terms.filter((t) => !t.requires_armor || hasArmor).map((t) => ({ source: t.label, amount: t.value, kind: "feature" }));
488
491
  const sumTerms = (terms) => terms.reduce((s, t) => s + t.amount, 0);
489
- let weaponAbility = featureEffects.weaponAbility;
492
+ const weaponAbilities = [...featureEffects.weaponAbilities];
490
493
  const wantsSpellcasting = resolved.features.some((rf) => {
491
494
  var _a2;
492
495
  return ((_a2 = rf.feature.effects) != null ? _a2 : []).some((e) => e.kind === "weapon-ability" && e.ability === "spellcasting");
@@ -501,14 +504,14 @@ function recalc(resolved, registry) {
501
504
  break;
502
505
  }
503
506
  }
504
- if (primaryCasterAbility) weaponAbility = primaryCasterAbility;
507
+ if (primaryCasterAbility) weaponAbilities.unshift({ ability: primaryCasterAbility });
505
508
  }
506
509
  let derivedEquipment = null;
507
510
  let acDerived;
508
511
  let acBreakdownDerived = [];
509
512
  let acInformationalDerived = [];
510
513
  if (registry) {
511
- derivedEquipment = computeSlotsAndAttacks(resolved, mods, profsForApply, registry, warnings, proficiencyBonus, weaponAbility != null ? weaponAbility : void 0);
514
+ derivedEquipment = computeSlotsAndAttacks(resolved, mods, profsForApply, registry, warnings, proficiencyBonus, weaponAbilities, (_y = resolved.weaponMasteries) != null ? _y : []);
512
515
  if (derivedEquipment.equippedSlots.armor) {
513
516
  const featTerms = featureAcTermsFor(true);
514
517
  acDerived = derivedEquipment.ac + sumTerms(featTerms);
@@ -531,16 +534,16 @@ function recalc(resolved, registry) {
531
534
  acDerived = total + sumTerms(featTerms);
532
535
  acBreakdownDerived = [...terms, ...featTerms];
533
536
  }
534
- const ac = (_A = overrides.ac) != null ? _A : acDerived;
537
+ const ac = (_z = overrides.ac) != null ? _z : acDerived;
535
538
  const additiveSpeed = speedFromRace(resolved) + applied.speed_bonuses.walk + featureEffects.speed_walk_bonus;
536
539
  const baseSpeed = Math.max(featureEffects.speed_walk_set, additiveSpeed);
537
540
  const adjustedSpeed = baseSpeed * conditionEffects.speed_multiplier - conditionEffects.speed_reduction_ft;
538
541
  const conditionSpeed = conditionEffects.speed_floor_zero ? 0 : Math.max(0, Math.floor(adjustedSpeed));
539
- const speed = (_B = overrides.speed) != null ? _B : conditionSpeed;
542
+ const speed = (_A = overrides.speed) != null ? _A : conditionSpeed;
540
543
  if (!resolved.race) warnings.push("No race resolved; speed defaulted to 30.");
541
- const init = (_C = overrides.initiative) != null ? _C : initiativeBonus(mods.dex, resolved.feats, resolved.definition.edition) + featureEffects.initiative_bonus;
544
+ const init = (_B = overrides.initiative) != null ? _B : initiativeBonus(mods.dex, resolved.feats, resolved.definition.edition) + featureEffects.initiative_bonus;
542
545
  const senses = {
543
- darkvision: Math.max((_F = (_E = (_D = resolved.race) == null ? void 0 : _D.vision) == null ? void 0 : _E.darkvision) != null ? _F : 0, featureEffects.senses.darkvision, applied.senses.darkvision),
546
+ darkvision: Math.max((_E = (_D = (_C = resolved.race) == null ? void 0 : _C.vision) == null ? void 0 : _D.darkvision) != null ? _E : 0, featureEffects.senses.darkvision, applied.senses.darkvision),
544
547
  blindsight: Math.max(featureEffects.senses.blindsight, applied.senses.blindsight),
545
548
  tremorsense: Math.max(featureEffects.senses.tremorsense, applied.senses.tremorsense),
546
549
  truesight: Math.max(featureEffects.senses.truesight, applied.senses.truesight)
@@ -558,8 +561,8 @@ function recalc(resolved, registry) {
558
561
  classSlug: c.entity.slug,
559
562
  className: c.entity.name,
560
563
  ability: profile.ability,
561
- saveDC: (_H = (_G = overrides.spellcasting) == null ? void 0 : _G.saveDC) != null ? _H : dc,
562
- attackBonus: (_J = (_I = overrides.spellcasting) == null ? void 0 : _I.attackBonus) != null ? _J : atk,
564
+ saveDC: (_G = (_F = overrides.spellcasting) == null ? void 0 : _F.saveDC) != null ? _G : dc,
565
+ attackBonus: (_I = (_H = overrides.spellcasting) == null ? void 0 : _H.attackBonus) != null ? _I : atk,
563
566
  casterType: profile.casterType,
564
567
  preparation: profile.preparation
565
568
  });
@@ -575,20 +578,20 @@ function recalc(resolved, registry) {
575
578
  const spellLimits = computeSpellLimits(limitInputs);
576
579
  const defenses = {
577
580
  resistances: dedupeDefenseList([
578
- (_L = (_K = resolved.definition.defenses) == null ? void 0 : _K.resistances) != null ? _L : [],
581
+ (_K = (_J = resolved.definition.defenses) == null ? void 0 : _J.resistances) != null ? _K : [],
579
582
  applied.defenses.resistances,
580
583
  featureEffects.resistances
581
584
  ]),
582
585
  immunities: dedupeDefenseList([
583
- (_N = (_M = resolved.definition.defenses) == null ? void 0 : _M.immunities) != null ? _N : [],
586
+ (_M = (_L = resolved.definition.defenses) == null ? void 0 : _L.immunities) != null ? _M : [],
584
587
  applied.defenses.immunities
585
588
  ]),
586
589
  vulnerabilities: dedupeDefenseList([
587
- (_P = (_O = resolved.definition.defenses) == null ? void 0 : _O.vulnerabilities) != null ? _P : [],
590
+ (_O = (_N = resolved.definition.defenses) == null ? void 0 : _N.vulnerabilities) != null ? _O : [],
588
591
  applied.defenses.vulnerabilities
589
592
  ]),
590
593
  condition_immunities: dedupeDefenseList([
591
- (_R = (_Q = resolved.definition.defenses) == null ? void 0 : _Q.condition_immunities) != null ? _R : [],
594
+ (_Q = (_P = resolved.definition.defenses) == null ? void 0 : _P.condition_immunities) != null ? _Q : [],
592
595
  applied.defenses.condition_immunities,
593
596
  featureEffects.condition_immunities
594
597
  ])
@@ -630,16 +633,21 @@ function recalc(resolved, registry) {
630
633
  // untouched rows keep `critRange`/`attackNotes` ABSENT (not 20 / not []).
631
634
  // crit-range: folded weapon crit threshold, only when an effect lowered it.
632
635
  // attackNotes: reroll-damage / attack-rule captions, only when non-empty.
633
- attacks: ((_S = derivedEquipment == null ? void 0 : derivedEquipment.attacks) != null ? _S : []).map((a) => ({
634
- ...a,
635
- toHit: a.toHit + conditionEffects.d20_test_penalty,
636
- ...featureEffects.critRange < 20 ? { critRange: featureEffects.critRange } : {},
637
- ...featureEffects.attackNotes.length ? { attackNotes: featureEffects.attackNotes } : {}
638
- })),
639
- equippedSlots: (_T = derivedEquipment == null ? void 0 : derivedEquipment.equippedSlots) != null ? _T : {},
640
- carriedWeight: (_U = derivedEquipment == null ? void 0 : derivedEquipment.carriedWeight) != null ? _U : 0,
641
- attunementUsed: (_V = derivedEquipment == null ? void 0 : derivedEquipment.attunementUsed) != null ? _V : 0,
642
- attunementLimit: (_X = derivedEquipment == null ? void 0 : derivedEquipment.attunementLimit) != null ? _X : (_W = overrides.attunement_limit) != null ? _W : 3,
636
+ attacks: ((_R = derivedEquipment == null ? void 0 : derivedEquipment.attacks) != null ? _R : []).map((a) => {
637
+ var _a2;
638
+ const riders = [...(_a2 = a.damageRiders) != null ? _a2 : [], ...featureEffects.damageBonuses];
639
+ return {
640
+ ...a,
641
+ toHit: a.toHit + conditionEffects.d20_test_penalty,
642
+ ...featureEffects.critRange < 20 ? { critRange: featureEffects.critRange } : {},
643
+ ...featureEffects.attackNotes.length ? { attackNotes: featureEffects.attackNotes } : {},
644
+ ...riders.length ? { damageRiders: riders } : {}
645
+ };
646
+ }),
647
+ equippedSlots: (_S = derivedEquipment == null ? void 0 : derivedEquipment.equippedSlots) != null ? _S : {},
648
+ carriedWeight: (_T = derivedEquipment == null ? void 0 : derivedEquipment.carriedWeight) != null ? _T : 0,
649
+ attunementUsed: (_U = derivedEquipment == null ? void 0 : derivedEquipment.attunementUsed) != null ? _U : 0,
650
+ attunementLimit: (_W = derivedEquipment == null ? void 0 : derivedEquipment.attunementLimit) != null ? _W : (_V = overrides.attunement_limit) != null ? _V : 3,
643
651
  conditionEffects,
644
652
  rollModifiers: featureEffects.rollModifiers
645
653
  };
@@ -15,6 +15,18 @@ export declare class PCResolver {
15
15
  get registry(): EntityRegistry;
16
16
  resolve(character: Character): ResolveResult;
17
17
  }
18
+ /**
19
+ * 2024 Weapon Mastery: unions the persisted `weapon-mastery` picks across every
20
+ * class/level `ClassEntry.choices[level]["weapon-mastery"]`. Returns both the
21
+ * ORIGINAL full picked slugs (`full` — used for exact-match display-name lookup)
22
+ * and their BARE-normalized form (`bare` — e.g. "srd-2024_greatsword" →
23
+ * "greatsword"), each de-duplicated in first-seen order. Bare-authored picks
24
+ * normalize to themselves. Non-string / non-array picks are ignored.
25
+ */
26
+ export declare function collectChosenWeaponMasteries(character: Character): {
27
+ bare: string[];
28
+ full: string[];
29
+ };
18
30
  /**
19
31
  * Walks class.choices for `feat` entries and adds background feat slugs.
20
32
  * Returns bare slugs (no `[[ ]]`).
@@ -6,7 +6,9 @@ import {
6
6
  resolveSpellcasting
7
7
  } from "../chunk-IXZXFFFW.js";
8
8
  import "../chunk-5XUXXAQJ.js";
9
- import "../chunk-TJCNX6UZ.js";
9
+ import {
10
+ bareEntitySlug
11
+ } from "../chunk-TJCNX6UZ.js";
10
12
  import "../chunk-BH6AMZVY.js";
11
13
  import "../chunk-JEYQU3JT.js";
12
14
  import "../chunk-J2RYSZMQ.js";
@@ -75,6 +77,27 @@ var PCResolver = class {
75
77
  const prep = isCantrip || n.alwaysPrepared ? true : (_e = n.preparedFlag) != null ? _e : false;
76
78
  spells.push({ entity, slug: n.slug, classSlug, source: n.source, prepared: prep, alwaysPrepared: n.alwaysPrepared });
77
79
  }
80
+ const chosenMasteries = collectChosenWeaponMasteries(character);
81
+ if (chosenMasteries.full.length > 0) {
82
+ const target = features.find(
83
+ (rf) => {
84
+ var _a2;
85
+ return rf.feature.id === "weapon-mastery" || ((_a2 = rf.feature.choices) == null ? void 0 : _a2.some(
86
+ (ch) => ch.kind === "select-entity" && ch.id === "weapon-mastery" && ch.entity_type === "weapon"
87
+ ));
88
+ }
89
+ );
90
+ if (target) {
91
+ const names = chosenMasteries.full.map(
92
+ (fullSlug) => {
93
+ var _a2, _b2;
94
+ return (_b2 = (_a2 = this.entities.getByTypeAndSlug("weapon", fullSlug)) == null ? void 0 : _a2.name) != null ? _b2 : fullSlug;
95
+ }
96
+ );
97
+ const entry = { label: "Mastered weapons", description: names.join(", ") };
98
+ target.chosenInline = target.chosenInline ? [...target.chosenInline, entry] : [entry];
99
+ }
100
+ }
78
101
  const resolvedCharacter = {
79
102
  definition: character,
80
103
  race,
@@ -85,12 +108,26 @@ var PCResolver = class {
85
108
  features,
86
109
  spells,
87
110
  pools: [],
111
+ weaponMasteries: chosenMasteries.bare,
88
112
  state: character.state
89
113
  };
90
114
  resolvedCharacter.pools = resolveAllPools(resolvedCharacter, this.entities);
91
115
  return { character: resolvedCharacter, warnings };
92
116
  }
93
117
  };
118
+ function collectChosenWeaponMasteries(character) {
119
+ var _a, _b;
120
+ const full = /* @__PURE__ */ new Set();
121
+ for (const cls of (_a = character.class) != null ? _a : []) {
122
+ for (const lvl of Object.values((_b = cls.choices) != null ? _b : {})) {
123
+ const pick = lvl["weapon-mastery"];
124
+ if (Array.isArray(pick)) {
125
+ for (const s of pick) if (typeof s === "string") full.add(s);
126
+ }
127
+ }
128
+ }
129
+ return { full: [...full], bare: [...new Set([...full].map(bareEntitySlug))] };
130
+ }
94
131
  function collectFeatSlugs(character) {
95
132
  const slugs = /* @__PURE__ */ new Set();
96
133
  for (const c of character.class) {
@@ -119,8 +156,12 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
119
156
  const features = (_b2 = ((_a2 = entity.features_by_level) != null ? _a2 : {})[lvl]) != null ? _b2 : [];
120
157
  const subFeatures = c.subclass ? (_d = ((_c = c.subclass.features_by_level) != null ? _c : {})[lvl]) != null ? _d : [] : [];
121
158
  for (const feature of [...features, ...subFeatures]) {
122
- walkChoiceGrants(feature.choices, atLevel, (granted) => {
123
- out.push({ feature: granted, source: { kind: "class", slug: entity.slug, level: lvl } });
159
+ walkChoiceGrants(feature.choices, atLevel, (granted, suppress) => {
160
+ out.push({
161
+ feature: granted,
162
+ source: { kind: "class", slug: entity.slug, level: lvl },
163
+ ...suppress ? { renderSuppressed: true } : {}
164
+ });
124
165
  }, registry);
125
166
  }
126
167
  }
@@ -135,8 +176,12 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
135
176
  };
136
177
  if (race) {
137
178
  const atRace = originAt("race");
138
- const emitRace = (granted) => {
139
- out.push({ feature: granted, source: { kind: "race", slug: race.slug } });
179
+ const emitRace = (granted, suppress) => {
180
+ out.push({
181
+ feature: granted,
182
+ source: { kind: "race", slug: race.slug },
183
+ ...suppress ? { renderSuppressed: true } : {}
184
+ });
140
185
  };
141
186
  walkChoiceGrants(race.choices, atRace, emitRace, registry);
142
187
  for (const trait of (_b = race.traits) != null ? _b : []) {
@@ -145,8 +190,12 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
145
190
  }
146
191
  if (background) {
147
192
  const atBg = originAt("background");
148
- const emitBg = (granted) => {
149
- out.push({ feature: granted, source: { kind: "background", slug: background.slug } });
193
+ const emitBg = (granted, suppress) => {
194
+ out.push({
195
+ feature: granted,
196
+ source: { kind: "background", slug: background.slug },
197
+ ...suppress ? { renderSuppressed: true } : {}
198
+ });
150
199
  };
151
200
  walkChoiceGrants(background.choices, atBg, emitBg, registry);
152
201
  if (background.feature) {
@@ -156,7 +205,7 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
156
205
  return out;
157
206
  }
158
207
  function walkChoiceGrants(choices, atLevel, emit, registry) {
159
- var _a, _b, _c, _d;
208
+ var _a, _b, _c, _d, _e;
160
209
  for (const ch of choices != null ? choices : []) {
161
210
  const sel = atLevel[ch.id];
162
211
  if (ch.kind === "select-entity" && ch.entity_type === "optional-feature") {
@@ -177,14 +226,39 @@ function walkChoiceGrants(choices, atLevel, emit, registry) {
177
226
  if (ch.kind === "select-inline") {
178
227
  const branch = typeof sel === "string" ? ch.options.find((o) => o.value === sel) : void 0;
179
228
  if ((_d = branch == null ? void 0 : branch.effects) == null ? void 0 : _d.length) {
180
- emit({ id: `${ch.id}-${branch.value}`, name: branch.label, description: branch.description, effects: branch.effects });
229
+ const wc = (_e = branch.choices) == null ? void 0 : _e.find(
230
+ (c) => c.kind === "select-entity" && c.entity_type === "weapon"
231
+ );
232
+ const picked = wc ? atLevel == null ? void 0 : atLevel[wc.id] : void 0;
233
+ const effects = branch.effects.map(
234
+ (e) => e.kind === "weapon-ability" && e.weapons === "chosen" && typeof picked === "string" ? { ...e, weapons: [bareEntitySlug(picked)] } : e
235
+ );
236
+ emit({ id: `${ch.id}-${branch.value}`, name: branch.label, description: branch.description, effects }, true);
181
237
  }
182
238
  if (branch == null ? void 0 : branch.choices) walkChoiceGrants(branch.choices, atLevel, emit, registry);
183
239
  }
184
240
  }
185
241
  }
242
+ function resolveChosenInline(feature, atLevel) {
243
+ var _a;
244
+ if (!atLevel) return void 0;
245
+ const picks = [];
246
+ for (const ch of (_a = feature.choices) != null ? _a : []) {
247
+ if (ch.kind !== "select-inline") continue;
248
+ const sel = atLevel[ch.id];
249
+ if (typeof sel !== "string") continue;
250
+ const opt = ch.options.find((o) => o.value === sel);
251
+ if (!opt) continue;
252
+ picks.push({ label: opt.label, description: opt.description });
253
+ }
254
+ return picks.length ? picks : void 0;
255
+ }
256
+ function isAsiSlotFeature(feature) {
257
+ var _a, _b, _c, _d;
258
+ return feature.id === "ability-score-improvement" && !((_a = feature.effects) == null ? void 0 : _a.length) && !((_b = feature.resources) == null ? void 0 : _b.length) && !feature.action && !((_c = feature.sub_features) == null ? void 0 : _c.length) && !((_d = feature.attacks) == null ? void 0 : _d.length);
259
+ }
186
260
  function collectResolvedFeatures(race, classes, background, feats) {
187
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
261
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
188
262
  const out = [];
189
263
  for (const c of classes) {
190
264
  if (!c.entity) continue;
@@ -194,10 +268,16 @@ function collectResolvedFeatures(race, classes, background, feats) {
194
268
  const lvl = parseInt(lvlStr, 10);
195
269
  if (Number.isNaN(lvl) || lvl > c.level) continue;
196
270
  for (const feat of feats0) {
197
- out.push({ feature: feat, source: { kind: "class", slug, level: lvl } });
271
+ const chosenInline = resolveChosenInline(feat, (_b = c.choices) == null ? void 0 : _b[lvl]);
272
+ out.push({
273
+ feature: feat,
274
+ source: { kind: "class", slug, level: lvl },
275
+ ...chosenInline ? { chosenInline } : {},
276
+ ...isAsiSlotFeature(feat) ? { buildOnly: true } : {}
277
+ });
198
278
  }
199
279
  }
200
- if ((_b = c.entity.resources) == null ? void 0 : _b.length) {
280
+ if ((_c = c.entity.resources) == null ? void 0 : _c.length) {
201
281
  out.push({
202
282
  feature: { name: c.entity.name, resources: c.entity.resources },
203
283
  source: { kind: "class", slug, level: 1 }
@@ -205,15 +285,21 @@ function collectResolvedFeatures(race, classes, background, feats) {
205
285
  }
206
286
  if (c.subclass) {
207
287
  const sSlug = c.subclass.slug;
208
- const sByLevel = (_c = c.subclass.features_by_level) != null ? _c : {};
288
+ const sByLevel = (_d = c.subclass.features_by_level) != null ? _d : {};
209
289
  for (const [lvlStr, feats0] of Object.entries(sByLevel)) {
210
290
  const lvl = parseInt(lvlStr, 10);
211
291
  if (Number.isNaN(lvl) || lvl > c.level) continue;
212
292
  for (const feat of feats0) {
213
- out.push({ feature: feat, source: { kind: "subclass", slug: sSlug, level: lvl } });
293
+ const chosenInline = resolveChosenInline(feat, (_e = c.choices) == null ? void 0 : _e[lvl]);
294
+ out.push({
295
+ feature: feat,
296
+ source: { kind: "subclass", slug: sSlug, level: lvl },
297
+ ...chosenInline ? { chosenInline } : {},
298
+ ...isAsiSlotFeature(feat) ? { buildOnly: true } : {}
299
+ });
214
300
  }
215
301
  }
216
- if ((_d = c.subclass.resources) == null ? void 0 : _d.length) {
302
+ if ((_f = c.subclass.resources) == null ? void 0 : _f.length) {
217
303
  out.push({
218
304
  feature: { name: c.subclass.name, resources: c.subclass.resources },
219
305
  source: { kind: "subclass", slug: sSlug, level: 1 }
@@ -222,7 +308,7 @@ function collectResolvedFeatures(race, classes, background, feats) {
222
308
  }
223
309
  }
224
310
  if (race) {
225
- const traits = (_e = race.traits) != null ? _e : [];
311
+ const traits = (_g = race.traits) != null ? _g : [];
226
312
  for (const feat of traits) {
227
313
  out.push({ feature: feat, source: { kind: "race", slug: race.slug } });
228
314
  }
@@ -234,8 +320,8 @@ function collectResolvedFeatures(race, classes, background, feats) {
234
320
  }
235
321
  }
236
322
  for (const feat of feats) {
237
- const bundled = (_f = feat.features) != null ? _f : [];
238
- const entityEffects = (_g = feat.effects) != null ? _g : [];
323
+ const bundled = (_h = feat.features) != null ? _h : [];
324
+ const entityEffects = (_i = feat.effects) != null ? _i : [];
239
325
  if (bundled.length > 0) {
240
326
  bundled.forEach((f, i) => {
241
327
  var _a2;
@@ -243,16 +329,18 @@ function collectResolvedFeatures(race, classes, background, feats) {
243
329
  out.push({ feature, source: { kind: "feat", slug: feat.slug } });
244
330
  });
245
331
  } else {
246
- const name = (_h = feat.name) != null ? _h : feat.slug;
247
- const description = (_i = feat.description) != null ? _i : "";
332
+ const name = (_j = feat.name) != null ? _j : feat.slug;
333
+ const foldedDesc = [feat.description, ...(_k = feat.benefits) != null ? _k : []].filter(Boolean).join("\n\n") || void 0;
334
+ const buildOnly = ((_l = feat.choices) != null ? _l : []).some((c) => c.kind === "ability-points" && c.points >= 2) && !((_m = feat.effects) == null ? void 0 : _m.length) && !((_n = feat.resources) == null ? void 0 : _n.length) && bundled.length === 0;
248
335
  out.push({
249
336
  feature: {
250
337
  name,
251
- description,
338
+ ...foldedDesc ? { description: foldedDesc } : {},
252
339
  ...feat.resources ? { resources: feat.resources } : {},
253
340
  ...entityEffects.length > 0 ? { effects: entityEffects } : {}
254
341
  },
255
- source: { kind: "feat", slug: feat.slug }
342
+ source: { kind: "feat", slug: feat.slug },
343
+ ...buildOnly ? { buildOnly: true } : {}
256
344
  });
257
345
  }
258
346
  }
@@ -261,6 +349,7 @@ function collectResolvedFeatures(race, classes, background, feats) {
261
349
  export {
262
350
  PCResolver,
263
351
  collectChosenGrantedFeatures,
352
+ collectChosenWeaponMasteries,
264
353
  collectFeatSlugs,
265
354
  collectResolvedFeatures,
266
355
  stripSlug