@archivist-gg/dnd5e 0.1.0 → 0.2.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.
Files changed (67) hide show
  1. package/dist/background/background.codec.js +4 -4
  2. package/dist/background/background.parser.js +3 -3
  3. package/dist/background/background.schema.js +2 -2
  4. package/dist/{chunk-FFULAF2P.js → chunk-2BWAID3Y.js} +2 -2
  5. package/dist/{chunk-FUK7QSN5.js → chunk-42Y5IJKT.js} +1 -1
  6. package/dist/{chunk-537BHVSH.js → chunk-4JHOHWBI.js} +1 -1
  7. package/dist/{chunk-ZLG6FQFB.js → chunk-5WQD3WNZ.js} +16 -1
  8. package/dist/{chunk-FEOB24KP.js → chunk-73HOFQQ5.js} +1 -1
  9. package/dist/{chunk-4SSDYJS2.js → chunk-7VJ6BOA5.js} +5 -1
  10. package/dist/{chunk-DABBCYTT.js → chunk-CZPMTQOY.js} +1 -1
  11. package/dist/{chunk-DRNZJNJP.js → chunk-DZXSWSBP.js} +1 -1
  12. package/dist/{chunk-RVYYKYFL.js → chunk-GE4TLPWM.js} +1 -1
  13. package/dist/{chunk-4C2KOKLF.js → chunk-K6Q5OUGK.js} +12 -0
  14. package/dist/{chunk-PMADYMUI.js → chunk-L7E2UT5A.js} +1 -1
  15. package/dist/{chunk-CDW7R2QT.js → chunk-NXSWH3AE.js} +4 -1
  16. package/dist/{chunk-IDTDACM6.js → chunk-PHQRGDRQ.js} +1 -1
  17. package/dist/{chunk-BAXWWGFJ.js → chunk-Q5K6LWBZ.js} +1 -1
  18. package/dist/chunk-R5SQHP2X.js +1092 -0
  19. package/dist/{chunk-Z6GXV2GO.js → chunk-TLYDBT5F.js} +1 -1
  20. package/dist/{chunk-QQ5W2AZP.js → chunk-TQZB4OCJ.js} +67 -12
  21. package/dist/{chunk-A7T5L7SS.js → chunk-TUIFSVLI.js} +1 -1
  22. package/dist/{chunk-XURS6Q6Q.js → chunk-V6SOFUJW.js} +19 -5
  23. package/dist/{chunk-Z4TL2V7S.js → chunk-VTYUBINW.js} +1 -1
  24. package/dist/{chunk-B7UXKNLD.js → chunk-W7HWMMBF.js} +1 -1
  25. package/dist/{chunk-SSXBYF3P.js → chunk-XLSVNUTK.js} +1 -1
  26. package/dist/{chunk-GIOH5JB5.js → chunk-XTIQWSFM.js} +1 -1
  27. package/dist/{chunk-PKC77VZP.js → chunk-ZZGEVJKX.js} +1 -1
  28. package/dist/class/class.codec.js +5 -5
  29. package/dist/class/class.parser.js +4 -4
  30. package/dist/class/class.schema.js +3 -3
  31. package/dist/feat/feat.codec.js +4 -4
  32. package/dist/feat/feat.parser.js +3 -3
  33. package/dist/feat/feat.schema.js +2 -2
  34. package/dist/index.js +622 -118
  35. package/dist/item/item.actions-map.d.ts +10 -3
  36. package/dist/item/item.actions-map.js +13 -8
  37. package/dist/item/item.codec.js +3 -3
  38. package/dist/item/item.parser.js +2 -2
  39. package/dist/item/item.schema.d.ts +13 -0
  40. package/dist/item/item.schema.js +1 -1
  41. package/dist/item/item.types.d.ts +17 -0
  42. package/dist/pc/pc.decision-engine.js +8 -1
  43. package/dist/pc/pc.equipment.d.ts +2 -1
  44. package/dist/pc/pc.equipment.js +9 -1
  45. package/dist/pc/pc.feature-effects.d.ts +31 -9
  46. package/dist/pc/pc.feature-effects.js +11 -1
  47. package/dist/pc/pc.parser.js +1 -1
  48. package/dist/pc/pc.pools.js +8 -3
  49. package/dist/pc/pc.recalc.js +80 -62
  50. package/dist/pc/pc.resolver.d.ts +71 -1
  51. package/dist/pc/pc.resolver.js +21 -254
  52. package/dist/pc/pc.schema.d.ts +21 -0
  53. package/dist/pc/pc.schema.js +1 -1
  54. package/dist/pc/pc.types.d.ts +80 -1
  55. package/dist/race/race.codec.js +5 -5
  56. package/dist/race/race.parser.js +4 -4
  57. package/dist/race/race.schema.js +3 -3
  58. package/dist/schemas/choice-schema.js +1 -1
  59. package/dist/schemas/feature-schema.js +2 -2
  60. package/dist/subclass/subclass.codec.js +5 -5
  61. package/dist/subclass/subclass.parser.js +4 -4
  62. package/dist/subclass/subclass.schema.js +3 -3
  63. package/dist/types/choice.d.ts +5 -0
  64. package/dist/weapon/weapon-mastery.d.ts +12 -0
  65. package/package.json +1 -1
  66. package/dist/chunk-6PXE7T7C.js +0 -79
  67. package/dist/chunk-TJCNX6UZ.js +0 -521
@@ -33,9 +33,16 @@ export declare const ITEM_ACTIONS: Record<string, ItemAction>;
33
33
  export declare function findItemAction(slug: string): ItemAction | undefined;
34
34
  /**
35
35
  * Resolve the ItemAction for an equipped entry.
36
- * Priority: entry.overrides (action + range) merged onto curated map.
37
- * Returns null when neither source supplies an action cost.
36
+ * Priority: per-instance override > curated map > `itemType==="potion"`
37
+ * bonus-action default > null.
38
+ *
39
+ * `itemType` is the entity's `data.type` (from the registry, statically
40
+ * `unknown`); per the 2024 SRD, drinking/administering a potion is a Bonus
41
+ * Action, so an uncurated `type==="potion"` item still surfaces (curated oils
42
+ * mis-typed `"potion"` are pinned to "action" so this default cannot mislabel
43
+ * them). Returns null when no source supplies a cost (non-potion uncurated with
44
+ * no override).
38
45
  *
39
46
  * Accepts both bare and compendium-prefixed slugs (see `findItemAction`).
40
47
  */
41
- export declare function resolveItemAction(slug: string, entry: EquipmentEntry): ItemAction | null;
48
+ export declare function resolveItemAction(slug: string, entry: EquipmentEntry, itemType?: unknown): ItemAction | null;
@@ -46,13 +46,19 @@ var ITEM_ACTIONS = {
46
46
  // Magic weapons with activation actions (surface in items table only via override; weapons table is primary)
47
47
  "sun-blade": { cost: "free", range: "self" },
48
48
  // Potions / consumables
49
- "potion-of-healing": { cost: "action", range: "self", max_charges: 1, recovery: { amount: "0", reset: "special" } },
50
- "potion-of-greater-healing": { cost: "action", range: "self", max_charges: 1, recovery: { amount: "0", reset: "special" } },
51
- "potion-of-superior-healing": { cost: "action", range: "self", max_charges: 1, recovery: { amount: "0", reset: "special" } },
52
- "potion-of-supreme-healing": { cost: "action", range: "self", max_charges: 1, recovery: { amount: "0", reset: "special" } },
49
+ // 2024 healing potions are a Bonus Action to drink/administer (curation still
50
+ // carries range/max_charges/recovery; the type=potion default covers uncurated ones).
51
+ "potion-of-healing": { cost: "bonus-action", range: "self", max_charges: 1, recovery: { amount: "0", reset: "special" } },
52
+ "potion-of-greater-healing": { cost: "bonus-action", range: "self", max_charges: 1, recovery: { amount: "0", reset: "special" } },
53
+ "potion-of-superior-healing": { cost: "bonus-action", range: "self", max_charges: 1, recovery: { amount: "0", reset: "special" } },
54
+ "potion-of-supreme-healing": { cost: "bonus-action", range: "self", max_charges: 1, recovery: { amount: "0", reset: "special" } },
53
55
  "alchemists-fire": { cost: "action", range: "20 ft.", max_charges: 1, recovery: { amount: "0", reset: "special" } },
54
56
  "holy-water": { cost: "action", range: "20 ft.", max_charges: 1, recovery: { amount: "0", reset: "special" } },
57
+ // Oils are APPLIED (minutes), not drunk: curated to "action" so the blanket
58
+ // type=potion → bonus-action default never mislabels these mis-typed items.
55
59
  "oil-of-sharpness": { cost: "action", range: "self", max_charges: 1, recovery: { amount: "0", reset: "special" } },
60
+ "oil-of-etherealness": { cost: "action", range: "self", max_charges: 1, recovery: { amount: "0", reset: "special" } },
61
+ "oil-of-slipperiness": { cost: "action", range: "self", max_charges: 1, recovery: { amount: "0", reset: "special" } },
56
62
  "dust-of-disappearance": { cost: "action", range: "self", max_charges: 1, recovery: { amount: "0", reset: "special" } }
57
63
  };
58
64
  function findItemAction(slug) {
@@ -62,14 +68,13 @@ function findItemAction(slug) {
62
68
  const bare = slug.slice(underscore + 1);
63
69
  return ITEM_ACTIONS[bare];
64
70
  }
65
- function resolveItemAction(slug, entry) {
66
- var _a;
71
+ function resolveItemAction(slug, entry, itemType) {
72
+ var _a, _b;
67
73
  const curated = (_a = findItemAction(slug)) != null ? _a : null;
68
74
  const override = entry.overrides;
69
75
  const overrideCost = override == null ? void 0 : override.action;
70
76
  const overrideRange = override == null ? void 0 : override.range;
71
- if (!curated && !overrideCost) return null;
72
- const cost = overrideCost != null ? overrideCost : curated == null ? void 0 : curated.cost;
77
+ const cost = (_b = overrideCost != null ? overrideCost : curated == null ? void 0 : curated.cost) != null ? _b : itemType === "potion" ? "bonus-action" : void 0;
73
78
  if (!cost) return null;
74
79
  return {
75
80
  cost,
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  itemCodec
3
- } from "../chunk-537BHVSH.js";
4
- import "../chunk-4SSDYJS2.js";
5
- import "../chunk-4C2KOKLF.js";
3
+ } from "../chunk-4JHOHWBI.js";
4
+ import "../chunk-7VJ6BOA5.js";
5
+ import "../chunk-K6Q5OUGK.js";
6
6
  export {
7
7
  itemCodec
8
8
  };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  parseItem
3
- } from "../chunk-4SSDYJS2.js";
4
- import "../chunk-4C2KOKLF.js";
3
+ } from "../chunk-7VJ6BOA5.js";
4
+ import "../chunk-K6Q5OUGK.js";
5
5
  export {
6
6
  parseItem
7
7
  };
@@ -165,6 +165,19 @@ export declare const itemEntitySchema: z.ZodObject<{
165
165
  description: z.ZodOptional<z.ZodString>;
166
166
  entries: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
167
167
  effects: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
168
+ damage_riders: z.ZodOptional<z.ZodArray<z.ZodObject<{
169
+ amount: z.ZodString;
170
+ damage_type: z.ZodString;
171
+ applies_to: z.ZodOptional<z.ZodEnum<{
172
+ weapon: "weapon";
173
+ spell: "spell";
174
+ all: "all";
175
+ }>>;
176
+ condition: z.ZodOptional<z.ZodString>;
177
+ }, z.core.$strip>>>;
178
+ scroll_level: z.ZodOptional<z.ZodNumber>;
179
+ unidentified: z.ZodOptional<z.ZodBoolean>;
180
+ masked_category: z.ZodOptional<z.ZodString>;
168
181
  raw: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
169
182
  damage_dice: z.ZodOptional<z.ZodString>;
170
183
  damage_type: z.ZodOptional<z.ZodString>;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  conditionSchema,
3
3
  itemEntitySchema
4
- } from "../chunk-4C2KOKLF.js";
4
+ } from "../chunk-K6Q5OUGK.js";
5
5
  export {
6
6
  conditionSchema,
7
7
  itemEntitySchema
@@ -97,6 +97,23 @@ export interface ItemEntity {
97
97
  description?: string;
98
98
  entries?: unknown[];
99
99
  effects?: unknown[];
100
+ /** Structured additive on-hit damage riders (e.g. Flame Tongue "+2d6 fire",
101
+ * Wounding "+2d6 necrotic"). Read per-weapon in the equipment builder →
102
+ * AttackRow.damageRiders. `amount` is a dice/flat string. */
103
+ damage_riders?: Array<{
104
+ amount: string;
105
+ damage_type: string;
106
+ applies_to?: "weapon" | "spell" | "all";
107
+ condition?: string;
108
+ }>;
109
+ /** Spell-scroll casting level (e.g. 3 for a "Spell Scroll (3rd Level)"). */
110
+ scroll_level?: number;
111
+ /** Marks an item the player has not yet identified: the sheet shows the
112
+ * masked_category label instead of the item's true identity. */
113
+ unidentified?: boolean;
114
+ /** Generic category label shown for an unidentified item (e.g. "potion",
115
+ * "scroll", "wand") in place of its real name. */
116
+ masked_category?: string;
100
117
  raw?: Record<string, unknown>;
101
118
  damage_dice?: string;
102
119
  damage_type?: string;
@@ -5,9 +5,16 @@ import {
5
5
  collectChosenAbilityPoints,
6
6
  collectChosenProficiencies,
7
7
  wikilinkTailSlug
8
- } from "../chunk-TJCNX6UZ.js";
8
+ } from "../chunk-R5SQHP2X.js";
9
9
  import "../chunk-BH6AMZVY.js";
10
+ import "../chunk-UC4FE5X3.js";
11
+ import "../chunk-IXZXFFFW.js";
12
+ import "../chunk-5XUXXAQJ.js";
10
13
  import "../chunk-JEYQU3JT.js";
14
+ import "../chunk-DC74TZNA.js";
15
+ import "../chunk-3TRGRSCH.js";
16
+ import "../chunk-J2RYSZMQ.js";
17
+ import "../chunk-NAEPSZAM.js";
11
18
  import "../chunk-WP2CX2JO.js";
12
19
  export {
13
20
  __matchesFilterForTest,
@@ -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,20 @@ import {
2
2
  computeAppliedBonuses,
3
3
  computeSlotsAndAttacks,
4
4
  emptyAppliedBonuses
5
- } from "../chunk-QQ5W2AZP.js";
5
+ } from "../chunk-TQZB4OCJ.js";
6
+ import "../chunk-R5SQHP2X.js";
7
+ import "../chunk-BH6AMZVY.js";
6
8
  import "../chunk-UC4FE5X3.js";
9
+ import "../chunk-IXZXFFFW.js";
10
+ import "../chunk-5XUXXAQJ.js";
11
+ import "../chunk-JEYQU3JT.js";
7
12
  import "../chunk-LWFTHJIE.js";
8
13
  import "../chunk-DNAJRMNN.js";
9
14
  import "../chunk-DC74TZNA.js";
10
15
  import "../chunk-3TRGRSCH.js";
16
+ import "../chunk-J2RYSZMQ.js";
17
+ import "../chunk-NAEPSZAM.js";
18
+ import "../chunk-WP2CX2JO.js";
11
19
  export {
12
20
  computeAppliedBonuses,
13
21
  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,17 @@
1
1
  import {
2
2
  computeFeatureEffects,
3
3
  emptyFeatureEffectTotals
4
- } from "../chunk-XURS6Q6Q.js";
4
+ } from "../chunk-V6SOFUJW.js";
5
+ import "../chunk-R5SQHP2X.js";
6
+ import "../chunk-BH6AMZVY.js";
7
+ import "../chunk-UC4FE5X3.js";
8
+ import "../chunk-IXZXFFFW.js";
9
+ import "../chunk-5XUXXAQJ.js";
10
+ import "../chunk-JEYQU3JT.js";
11
+ import "../chunk-DC74TZNA.js";
12
+ import "../chunk-3TRGRSCH.js";
13
+ import "../chunk-J2RYSZMQ.js";
14
+ import "../chunk-NAEPSZAM.js";
5
15
  import "../chunk-WP2CX2JO.js";
6
16
  export {
7
17
  computeFeatureEffects,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  characterSchema
3
- } from "../chunk-ZLG6FQFB.js";
3
+ } from "../chunk-5WQD3WNZ.js";
4
4
  import "../chunk-TZK36BU7.js";
5
5
 
6
6
  // src/pc/pc.parser.ts
@@ -2,11 +2,16 @@ import {
2
2
  levelPrereqMax,
3
3
  resolveAllPools,
4
4
  resolvePool
5
- } from "../chunk-6PXE7T7C.js";
6
- import "../chunk-5XUXXAQJ.js";
7
- import "../chunk-TJCNX6UZ.js";
5
+ } from "../chunk-R5SQHP2X.js";
8
6
  import "../chunk-BH6AMZVY.js";
7
+ import "../chunk-UC4FE5X3.js";
8
+ import "../chunk-IXZXFFFW.js";
9
+ import "../chunk-5XUXXAQJ.js";
9
10
  import "../chunk-JEYQU3JT.js";
11
+ import "../chunk-DC74TZNA.js";
12
+ import "../chunk-3TRGRSCH.js";
13
+ import "../chunk-J2RYSZMQ.js";
14
+ import "../chunk-NAEPSZAM.js";
10
15
  import "../chunk-WP2CX2JO.js";
11
16
  export {
12
17
  levelPrereqMax,
@@ -1,27 +1,27 @@
1
- import {
2
- computeSpellLimits,
3
- deriveSpellSlots,
4
- resolveSpellcasting
5
- } from "../chunk-IXZXFFFW.js";
6
- import "../chunk-5XUXXAQJ.js";
7
1
  import {
8
2
  computeConditionEffects
9
3
  } 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
4
  import {
17
5
  computeAppliedBonuses,
18
6
  computeSlotsAndAttacks,
19
7
  emptyAppliedBonuses
20
- } from "../chunk-QQ5W2AZP.js";
21
- import "../chunk-UC4FE5X3.js";
8
+ } from "../chunk-TQZB4OCJ.js";
22
9
  import {
23
10
  computeFeatureEffects
24
- } from "../chunk-XURS6Q6Q.js";
11
+ } from "../chunk-V6SOFUJW.js";
12
+ import {
13
+ collectChosenAbilityPoints,
14
+ collectChosenProficiencies
15
+ } from "../chunk-R5SQHP2X.js";
16
+ import "../chunk-BH6AMZVY.js";
17
+ import "../chunk-UC4FE5X3.js";
18
+ import {
19
+ computeSpellLimits,
20
+ deriveSpellSlots,
21
+ resolveSpellcasting
22
+ } from "../chunk-IXZXFFFW.js";
23
+ import "../chunk-5XUXXAQJ.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
  });
@@ -571,24 +574,33 @@ function recalc(resolved, registry) {
571
574
  saveDC: spellcastingClasses[0].saveDC,
572
575
  attackBonus: spellcastingClasses[0].attackBonus
573
576
  } : null;
577
+ const abilitySpellcasting = {};
578
+ for (const s of resolved.spells) {
579
+ const ab = s.ability;
580
+ if (!ab || abilitySpellcasting[ab]) continue;
581
+ abilitySpellcasting[ab] = {
582
+ saveDC: saveDC(scores[ab], proficiencyBonus) + applied.spell_save_dc,
583
+ attackBonus: attackBonus(scores[ab], proficiencyBonus) + applied.spell_attack
584
+ };
585
+ }
574
586
  const derivedSlots = deriveSpellSlots(slotInputs);
575
587
  const spellLimits = computeSpellLimits(limitInputs);
576
588
  const defenses = {
577
589
  resistances: dedupeDefenseList([
578
- (_L = (_K = resolved.definition.defenses) == null ? void 0 : _K.resistances) != null ? _L : [],
590
+ (_K = (_J = resolved.definition.defenses) == null ? void 0 : _J.resistances) != null ? _K : [],
579
591
  applied.defenses.resistances,
580
592
  featureEffects.resistances
581
593
  ]),
582
594
  immunities: dedupeDefenseList([
583
- (_N = (_M = resolved.definition.defenses) == null ? void 0 : _M.immunities) != null ? _N : [],
595
+ (_M = (_L = resolved.definition.defenses) == null ? void 0 : _L.immunities) != null ? _M : [],
584
596
  applied.defenses.immunities
585
597
  ]),
586
598
  vulnerabilities: dedupeDefenseList([
587
- (_P = (_O = resolved.definition.defenses) == null ? void 0 : _O.vulnerabilities) != null ? _P : [],
599
+ (_O = (_N = resolved.definition.defenses) == null ? void 0 : _N.vulnerabilities) != null ? _O : [],
588
600
  applied.defenses.vulnerabilities
589
601
  ]),
590
602
  condition_immunities: dedupeDefenseList([
591
- (_R = (_Q = resolved.definition.defenses) == null ? void 0 : _Q.condition_immunities) != null ? _R : [],
603
+ (_Q = (_P = resolved.definition.defenses) == null ? void 0 : _P.condition_immunities) != null ? _Q : [],
592
604
  applied.defenses.condition_immunities,
593
605
  featureEffects.condition_immunities
594
606
  ])
@@ -613,6 +625,7 @@ function recalc(resolved, registry) {
613
625
  initiative: init,
614
626
  spellcasting,
615
627
  spellcastingClasses,
628
+ abilitySpellcasting,
616
629
  derivedSpellSlots: derivedSlots.standard,
617
630
  pactMagic: derivedSlots.pact,
618
631
  spellLimits,
@@ -630,16 +643,21 @@ function recalc(resolved, registry) {
630
643
  // untouched rows keep `critRange`/`attackNotes` ABSENT (not 20 / not []).
631
644
  // crit-range: folded weapon crit threshold, only when an effect lowered it.
632
645
  // 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,
646
+ attacks: ((_R = derivedEquipment == null ? void 0 : derivedEquipment.attacks) != null ? _R : []).map((a) => {
647
+ var _a2;
648
+ const riders = [...(_a2 = a.damageRiders) != null ? _a2 : [], ...featureEffects.damageBonuses];
649
+ return {
650
+ ...a,
651
+ toHit: a.toHit + conditionEffects.d20_test_penalty,
652
+ ...featureEffects.critRange < 20 ? { critRange: featureEffects.critRange } : {},
653
+ ...featureEffects.attackNotes.length ? { attackNotes: featureEffects.attackNotes } : {},
654
+ ...riders.length ? { damageRiders: riders } : {}
655
+ };
656
+ }),
657
+ equippedSlots: (_S = derivedEquipment == null ? void 0 : derivedEquipment.equippedSlots) != null ? _S : {},
658
+ carriedWeight: (_T = derivedEquipment == null ? void 0 : derivedEquipment.carriedWeight) != null ? _T : 0,
659
+ attunementUsed: (_U = derivedEquipment == null ? void 0 : derivedEquipment.attunementUsed) != null ? _U : 0,
660
+ attunementLimit: (_W = derivedEquipment == null ? void 0 : derivedEquipment.attunementLimit) != null ? _W : (_V = overrides.attunement_limit) != null ? _V : 3,
643
661
  conditionEffects,
644
662
  rollModifiers: featureEffects.rollModifiers
645
663
  };