@archivist-gg/dnd5e 0.3.13 → 0.3.17

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 (111) hide show
  1. package/dist/background/background.codec.js +5 -5
  2. package/dist/background/background.parser.js +4 -4
  3. package/dist/background/background.schema.js +3 -3
  4. package/dist/{chunk-ZMJQUZQH.js → chunk-2JSQE3DD.js} +11 -0
  5. package/dist/{chunk-Q6UOHCCT.js → chunk-2UDSN6VW.js} +1 -1
  6. package/dist/chunk-3LWMFYKS.js +57 -0
  7. package/dist/{chunk-MUIU7RZ3.js → chunk-3MGQFFNE.js} +1 -1
  8. package/dist/{chunk-T5GZBKIQ.js → chunk-3VZZDKRA.js} +114 -27
  9. package/dist/{chunk-BRGLDWUP.js → chunk-5MXFW4O3.js} +7 -3
  10. package/dist/{chunk-RQNJXADQ.js → chunk-6EI737IO.js} +1 -1
  11. package/dist/{chunk-EL6QPF6I.js → chunk-7ATNGKBS.js} +1 -1
  12. package/dist/{chunk-XY6X2APM.js → chunk-7Q2VPKCD.js} +17 -0
  13. package/dist/{chunk-4SLOJXJ4.js → chunk-7QN7XSAK.js} +1 -1
  14. package/dist/{chunk-MC5YJ4PB.js → chunk-AZRGFUSR.js} +1 -1
  15. package/dist/{chunk-HJQNK3J5.js → chunk-B3DP42OM.js} +1 -4
  16. package/dist/{chunk-JNNNBEUH.js → chunk-DOXSFYLH.js} +7 -2
  17. package/dist/{chunk-X7VZY4ON.js → chunk-FANQVQ3D.js} +2 -2
  18. package/dist/{chunk-PX25R3DK.js → chunk-G6PBI2BU.js} +1 -1
  19. package/dist/chunk-HTYMW5CH.js +228 -0
  20. package/dist/{chunk-VDPRBUU5.js → chunk-JCMUVZIK.js} +1 -1
  21. package/dist/{chunk-Q5XTEYHD.js → chunk-JSM6JFZR.js} +15 -8
  22. package/dist/chunk-KR6JF3BF.js +14 -0
  23. package/dist/{chunk-J53ERF6K.js → chunk-LF5AGJA4.js} +1 -1
  24. package/dist/{chunk-XJRNEQPD.js → chunk-MBX25B6D.js} +1 -1
  25. package/dist/{chunk-5KGV6TF7.js → chunk-OFCSP6MT.js} +1 -1
  26. package/dist/{chunk-QEO7LYTG.js → chunk-ONTGZR5T.js} +127 -71
  27. package/dist/{chunk-MWCCGPG3.js → chunk-OOSRUJBI.js} +3 -2
  28. package/dist/{chunk-LVX6TQ3R.js → chunk-PKX6TGRE.js} +2 -2
  29. package/dist/{chunk-QSBJ6NEM.js → chunk-PQZ4LM4Z.js} +1 -1
  30. package/dist/chunk-PXVNAY7T.js +84 -0
  31. package/dist/chunk-QH36K2PU.js +158 -0
  32. package/dist/{chunk-M2OWVAZG.js → chunk-QZSWN2KT.js} +1 -1
  33. package/dist/{chunk-6ZY3U6BQ.js → chunk-RYLXAYIQ.js} +1 -1
  34. package/dist/{chunk-UE6LKRXQ.js → chunk-T6QIF5XR.js} +16 -0
  35. package/dist/{chunk-4BZ47YJ4.js → chunk-TULQ4FFX.js} +32 -11
  36. package/dist/{chunk-PLOSP4XQ.js → chunk-UUBJCNQU.js} +11 -2
  37. package/dist/{chunk-DPM6V7YX.js → chunk-VRV3NNTA.js} +1 -1
  38. package/dist/{chunk-R2V5LN3I.js → chunk-Y5VVP73C.js} +1 -1
  39. package/dist/{chunk-FPGXFD2M.js → chunk-YD2VAHA6.js} +2 -2
  40. package/dist/{chunk-G7ED5H2G.js → chunk-YKL6SHRJ.js} +1 -1
  41. package/dist/class/class.codec.js +6 -6
  42. package/dist/class/class.parser.js +5 -5
  43. package/dist/class/class.schema.d.ts +8 -0
  44. package/dist/class/class.schema.js +4 -4
  45. package/dist/class/class.standard-array.d.ts +16 -0
  46. package/dist/class/class.standard-array.js +20 -0
  47. package/dist/class/class.types.d.ts +3 -0
  48. package/dist/dnd/amount-text.d.ts +38 -0
  49. package/dist/dnd/amount-text.js +13 -0
  50. package/dist/dnd/resource-words.d.ts +4 -0
  51. package/dist/dnd/resource-words.js +30 -0
  52. package/dist/feat/feat.codec.js +5 -5
  53. package/dist/feat/feat.parser.js +4 -4
  54. package/dist/feat/feat.schema.d.ts +25 -0
  55. package/dist/feat/feat.schema.js +3 -3
  56. package/dist/index.js +332 -70
  57. package/dist/monster/monster.parser.js +5 -5
  58. package/dist/monster/monster.schema.js +4 -4
  59. package/dist/optional-feature/optional-feature.codec.js +5 -4
  60. package/dist/optional-feature/optional-feature.parser.js +4 -3
  61. package/dist/optional-feature/optional-feature.schema.d.ts +26 -0
  62. package/dist/optional-feature/optional-feature.schema.js +3 -2
  63. package/dist/pc/pc.additional-spells.js +2 -2
  64. package/dist/pc/pc.amount-bindings.d.ts +32 -0
  65. package/dist/pc/pc.amount-bindings.js +23 -0
  66. package/dist/pc/pc.decision-engine.d.ts +3 -0
  67. package/dist/pc/pc.decision-engine.js +14 -13
  68. package/dist/pc/pc.entry-overrides.js +3 -3
  69. package/dist/pc/pc.equipment.js +6 -5
  70. package/dist/pc/pc.feature-effects.d.ts +12 -0
  71. package/dist/pc/pc.feature-effects.js +1 -1
  72. package/dist/pc/pc.infusions.d.ts +9 -1
  73. package/dist/pc/pc.infusions.js +3 -3
  74. package/dist/pc/pc.member-choices.d.ts +90 -0
  75. package/dist/pc/pc.member-choices.js +32 -0
  76. package/dist/pc/pc.parser.js +1 -1
  77. package/dist/pc/pc.pools.d.ts +7 -0
  78. package/dist/pc/pc.pools.js +5 -3
  79. package/dist/pc/pc.proficiencies.js +13 -12
  80. package/dist/pc/pc.proficiency-query.js +6 -0
  81. package/dist/pc/pc.recalc.js +75 -58
  82. package/dist/pc/pc.resolver.d.ts +2 -1
  83. package/dist/pc/pc.resolver.js +13 -12
  84. package/dist/pc/pc.rest.js +4 -4
  85. package/dist/pc/pc.schema.d.ts +29 -2
  86. package/dist/pc/pc.schema.js +1 -1
  87. package/dist/pc/pc.types.d.ts +45 -2
  88. package/dist/pc/pc.weapon-grant.d.ts +15 -0
  89. package/dist/pc/pool-layout.d.ts +3 -9
  90. package/dist/pc/pool-layout.js +1 -3
  91. package/dist/pc/resource-equivalents.d.ts +44 -0
  92. package/dist/pc/spell-source.js +4 -9
  93. package/dist/race/race.codec.js +6 -6
  94. package/dist/race/race.parser.js +5 -5
  95. package/dist/race/race.schema.js +4 -4
  96. package/dist/schemas/choice-schema.js +2 -2
  97. package/dist/schemas/feature-effect-schema.d.ts +25 -0
  98. package/dist/schemas/feature-effect-schema.js +1 -1
  99. package/dist/schemas/feature-schema.js +3 -3
  100. package/dist/spell/spell.codec.js +2 -2
  101. package/dist/spell/spell.parser.d.ts +6 -1
  102. package/dist/spell/spell.parser.js +5 -3
  103. package/dist/spell/spell.types.d.ts +10 -0
  104. package/dist/subclass/subclass.codec.js +6 -6
  105. package/dist/subclass/subclass.parser.js +5 -5
  106. package/dist/subclass/subclass.schema.js +4 -4
  107. package/dist/types/choice.d.ts +5 -0
  108. package/dist/types/feature-effect.d.ts +6 -0
  109. package/dist/types/optional-feature.types.d.ts +5 -0
  110. package/dist/types/selection-pool.d.ts +2 -2
  111. package/package.json +1 -1
@@ -0,0 +1,228 @@
1
+ import {
2
+ parsePickKey,
3
+ poolAnchorLevel
4
+ } from "./chunk-3VZZDKRA.js";
5
+ import {
6
+ featProgressionCategory
7
+ } from "./chunk-YBLDE5CG.js";
8
+ import {
9
+ warnOnce
10
+ } from "./chunk-LVQLWRCU.js";
11
+ import {
12
+ bareEntitySlug
13
+ } from "./chunk-ZCTUYGNV.js";
14
+
15
+ // src/pc/pc.member-choices.ts
16
+ var OPTIONAL_FEATURE_TYPE_CODE = {
17
+ "MV:B": "maneuver",
18
+ MV: "maneuver",
19
+ EI: "invocation",
20
+ MM: "metamagic",
21
+ "FS:F": "fighting_style"
22
+ };
23
+ function spellFilter(choose, edition) {
24
+ const where = { edition };
25
+ for (const part of choose.split("|")) {
26
+ const [rawKey, rawValue = ""] = part.split("=");
27
+ const key = rawKey.trim().toLowerCase();
28
+ const value = rawValue.trim();
29
+ if (key === "level") {
30
+ const n = Number(value.split(";")[0]);
31
+ if (!Number.isInteger(n)) return null;
32
+ where.level = n;
33
+ } else if (key === "class") where.list = value.toLowerCase();
34
+ else if (key === "components & miscellaneous" && /ritual/i.test(value)) where.ritual = true;
35
+ }
36
+ return where;
37
+ }
38
+ function isTargetChoice(ch) {
39
+ return ch.kind === "select-entity" && ch.target === true;
40
+ }
41
+ function progressionCount(p) {
42
+ var _a;
43
+ if (Array.isArray(p)) return 0;
44
+ return (_a = p["*"]) != null ? _a : Math.max(0, ...Object.values(p));
45
+ }
46
+ function spellChoices(entity, used) {
47
+ var _a, _b;
48
+ const out = [];
49
+ for (const block of (_a = entity.additional_spells) != null ? _a : []) {
50
+ for (const bucket of ["known", "prepared"]) {
51
+ const byLevel = block[bucket];
52
+ if (!byLevel || typeof byLevel !== "object") continue;
53
+ for (const rows of Object.values(byLevel)) {
54
+ for (const row of Array.isArray(rows) ? rows : []) {
55
+ const r = row;
56
+ if (typeof r.choose !== "string") continue;
57
+ const where = spellFilter(r.choose, entity.edition);
58
+ if (!where) continue;
59
+ const base = where.ritual ? "rituals" : where.level === 0 ? "cantrips" : `spells-${(_b = where.level) != null ? _b : "any"}`;
60
+ const label = where.ritual ? "Rituals" : where.level === 0 ? "Cantrips" : `Level ${where.level} spells`;
61
+ let id = base;
62
+ for (let i = 2; used.has(id); i++) id = `${base}-${i}`;
63
+ used.add(id);
64
+ out.push({ kind: "select-entity", id, label, count: typeof r.count === "number" ? r.count : 1, entity_type: "spell", where });
65
+ }
66
+ }
67
+ }
68
+ }
69
+ return out;
70
+ }
71
+ function memberChoices(entity) {
72
+ var _a, _b, _c, _d, _e, _f;
73
+ if (!entity) return [];
74
+ if ((_a = entity.choices) == null ? void 0 : _a.length) return entity.choices;
75
+ const out = [];
76
+ const used = /* @__PURE__ */ new Set();
77
+ for (const fp of (_b = entity.feat_progression) != null ? _b : []) {
78
+ const category = featProgressionCategory(fp.category);
79
+ const count = progressionCount(fp.progression);
80
+ if (!category || count < 1) continue;
81
+ const id = used.has("feat") ? `feat-${used.size}` : "feat";
82
+ used.add(id);
83
+ out.push({ kind: "select-entity", id, label: fp.name, count, entity_type: "feat", where: { category, edition: entity.edition } });
84
+ }
85
+ for (const op of (_c = entity.optionalfeature_progression) != null ? _c : []) {
86
+ const codes = (_e = (_d = op.feature_type) != null ? _d : op.featureType) != null ? _e : [];
87
+ const featureType = codes.map((c) => OPTIONAL_FEATURE_TYPE_CODE[c]).find((t) => !!t);
88
+ const count = progressionCount(op.progression);
89
+ if (!featureType || count < 1) {
90
+ if (!featureType) warnOnce(`member-of-code:${entity.slug}`, `Archivist: "${entity.slug}" asks for optional features of type ${codes.join(", ")}, which this engine does not map; no pick is offered`);
91
+ continue;
92
+ }
93
+ const id = op.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "option";
94
+ used.add(id);
95
+ out.push({ kind: "select-entity", id, label: op.name, count, entity_type: "optional-feature", where: { feature_type: featureType, edition: entity.edition } });
96
+ }
97
+ out.push(...spellChoices(entity, used));
98
+ for (const pre of (_f = entity.prerequisites) != null ? _f : []) {
99
+ if (pre.kind !== "spell-choose") continue;
100
+ const where = spellFilter(pre.choose, entity.edition);
101
+ if (!where || used.has("cantrip")) continue;
102
+ used.add("cantrip");
103
+ out.push({ kind: "select-entity", id: "cantrip", label: where.level === 0 ? "Cantrip" : "Spell", count: 1, entity_type: "spell", where, target: true });
104
+ }
105
+ return out;
106
+ }
107
+ function siteOf(classIndex, level, poolId, pickKey, member, memberSlug, block) {
108
+ const choices = memberChoices(member);
109
+ if (!choices.length) return null;
110
+ const prefix = `${poolId}:${pickKey}:`;
111
+ return { classIndex, level, poolId, pickKey, member, memberSlug, choices, prefix, block, read: (id) => block[`${prefix}${id}`] };
112
+ }
113
+ function poolMemberSites(classes, registry) {
114
+ const out = [];
115
+ let byBare = null;
116
+ let all = null;
117
+ const lookup = (s, ownerPrefix) => {
118
+ all != null ? all : all = registry.search("", "optional-feature", Number.POSITIVE_INFINITY);
119
+ const bare = bareEntitySlug(s);
120
+ const owned = all.find((e) => bareEntitySlug(e.slug) === bare && e.slug.split("_")[0] === ownerPrefix);
121
+ if (owned) return owned;
122
+ const direct = registry.getByTypeAndSlug("optional-feature", s);
123
+ if (direct) return direct;
124
+ byBare != null ? byBare : byBare = new Map(all.map((e) => [bareEntitySlug(e.slug), e]));
125
+ return byBare.get(bare);
126
+ };
127
+ classes.forEach((rc, classIndex) => {
128
+ var _a, _b, _c, _d, _e;
129
+ if (!rc.entity) return;
130
+ const seen = /* @__PURE__ */ new Set();
131
+ for (const pool of [...(_a = rc.entity.selection_pools) != null ? _a : [], ...(_c = (_b = rc.subclass) == null ? void 0 : _b.selection_pools) != null ? _c : []]) {
132
+ if (seen.has(pool.id)) continue;
133
+ seen.add(pool.id);
134
+ const { anchorLevel } = poolAnchorLevel(rc.entity, pool);
135
+ const block = (_e = (_d = rc.choices) == null ? void 0 : _d[anchorLevel]) != null ? _e : {};
136
+ const raw = block[pool.id];
137
+ const picks = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : [];
138
+ for (const s of picks) {
139
+ if (typeof s !== "string") continue;
140
+ const { slugPart, param } = parsePickKey(s);
141
+ const reg = lookup(slugPart, rc.entity.slug.split("_")[0]);
142
+ if (!reg) continue;
143
+ const member = reg.data;
144
+ const key = member.repeatable === true || param !== null ? s : reg.slug;
145
+ const site = siteOf(classIndex, anchorLevel, pool.id, key, member, reg.slug, block);
146
+ if (site) out.push(site);
147
+ }
148
+ }
149
+ });
150
+ return out;
151
+ }
152
+ function poolMemberSitesOf(resolved) {
153
+ var _a, _b, _c, _d, _e, _f;
154
+ const out = [];
155
+ for (const pool of (_a = resolved.pools) != null ? _a : []) {
156
+ const block = (_e = (_d = (_c = (_b = resolved.classes) == null ? void 0 : _b[pool.classIndex]) == null ? void 0 : _c.choices) == null ? void 0 : _d[pool.anchorLevel]) != null ? _e : {};
157
+ for (const e of (_f = pool.selected) != null ? _f : []) {
158
+ const site = siteOf(pool.classIndex, pool.anchorLevel, pool.id, e.key, e.entity, e.slug, block);
159
+ if (site) out.push(site);
160
+ }
161
+ }
162
+ return out;
163
+ }
164
+ var stripRef = (ref) => ref.replace(/^\[\[/, "").replace(/\]\]$/, "");
165
+ function featPickSites(classEntries, memberSites, classSlugOf) {
166
+ const out = [];
167
+ (classEntries != null ? classEntries : []).forEach((c, classIndex) => {
168
+ var _a;
169
+ for (const [lvl, choiceBlock] of Object.entries((_a = c.choices) != null ? _a : {})) {
170
+ const block = choiceBlock;
171
+ const feat = block == null ? void 0 : block.feat;
172
+ if (!block || typeof feat !== "string") continue;
173
+ const slug = stripRef(feat);
174
+ if (!slug) continue;
175
+ const level = /^\d+$/.test(lvl) ? Number(lvl) : null;
176
+ const cls = classSlugOf(classIndex);
177
+ out.push({
178
+ slug,
179
+ classIndex,
180
+ level,
181
+ via: cls && level !== null ? { kind: "class", slug: cls, level } : null,
182
+ read: (id) => block[`feat:${id}`]
183
+ });
184
+ }
185
+ });
186
+ for (const site of memberSites) {
187
+ for (const ch of site.choices) {
188
+ if (ch.kind !== "select-entity" || ch.entity_type !== "feat" || ch.target === true) continue;
189
+ const v = site.read(ch.id);
190
+ for (const ref of Array.isArray(v) ? v : typeof v === "string" ? [v] : []) {
191
+ if (typeof ref !== "string") continue;
192
+ const slug = stripRef(ref);
193
+ if (!slug) continue;
194
+ const cls = classSlugOf(site.classIndex);
195
+ out.push({
196
+ slug,
197
+ classIndex: site.classIndex,
198
+ level: site.level,
199
+ via: cls ? { kind: "pool", slug: site.memberSlug, name: site.member.name, classSlug: cls, level: site.level } : null,
200
+ read: (id) => site.block[`${site.prefix}feat:${id}`]
201
+ });
202
+ }
203
+ }
204
+ }
205
+ return out;
206
+ }
207
+ function featOfSite(site, feats) {
208
+ var _a;
209
+ return (_a = feats.find((f) => f.slug === site.slug)) != null ? _a : feats.find((f) => bareEntitySlug(f.slug) === bareEntitySlug(site.slug));
210
+ }
211
+ function nestedPickKeysToPrune(block, key, next) {
212
+ const prev = block == null ? void 0 : block[key];
213
+ if (!block || !Array.isArray(prev)) return [];
214
+ const keep = new Set(next);
215
+ const gone = prev.filter((k) => typeof k === "string" && !keep.has(k));
216
+ if (!gone.length) return [];
217
+ return Object.keys(block).filter((k) => gone.some((g) => k.startsWith(`${key}:${g}:`)));
218
+ }
219
+
220
+ export {
221
+ isTargetChoice,
222
+ memberChoices,
223
+ poolMemberSites,
224
+ poolMemberSitesOf,
225
+ featPickSites,
226
+ featOfSite,
227
+ nestedPickKeysToPrune
228
+ };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  MONSTER_KNOWN_KEYS,
3
3
  monsterSchema
4
- } from "./chunk-EL6QPF6I.js";
4
+ } from "./chunk-7ATNGKBS.js";
5
5
 
6
6
  // src/monster/monster.parser.ts
7
7
  import { parseYaml } from "@archivist-gg/core";
@@ -1,16 +1,19 @@
1
+ import {
2
+ qualifiedGrantCovers
3
+ } from "./chunk-3LWMFYKS.js";
1
4
  import {
2
5
  classifyWeaponRange
3
6
  } from "./chunk-YDGMBMYI.js";
7
+ import {
8
+ resolveContainment
9
+ } from "./chunk-EJP263OP.js";
4
10
  import {
5
11
  normKey
6
12
  } from "./chunk-RLGQ5PZZ.js";
7
13
  import {
8
14
  WEAPON_DEFAULT_ACTION_COST,
9
15
  resolveEntryEntity
10
- } from "./chunk-T5GZBKIQ.js";
11
- import {
12
- resolveContainment
13
- } from "./chunk-EJP263OP.js";
16
+ } from "./chunk-3VZZDKRA.js";
14
17
  import {
15
18
  readNumericBonus
16
19
  } from "./chunk-HHBD4SQ6.js";
@@ -61,6 +64,7 @@ function emptyAppliedBonuses() {
61
64
  ability_bonuses: {},
62
65
  ability_statics: {},
63
66
  save_bonus: 0,
67
+ save_terms: [],
64
68
  speed_bonuses: { walk: 0, fly: null, swim: 0, climb: 0 },
65
69
  spell_attack: 0,
66
70
  spell_save_dc: 0,
@@ -103,7 +107,7 @@ function pushApply(outcome, field, source, pool, onApply) {
103
107
  }
104
108
  }
105
109
  function computeAppliedBonuses(resolved, _profs, registry, warnings) {
106
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
110
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s;
107
111
  const out = emptyAppliedBonuses();
108
112
  const equipment = (_a = resolved.definition.equipment) != null ? _a : [];
109
113
  const ctx = buildConditionContext(resolved, {}, registry);
@@ -135,13 +139,15 @@ function computeAppliedBonuses(resolved, _profs, registry, warnings) {
135
139
  }
136
140
  const b = item.bonuses;
137
141
  if (!b) continue;
142
+ const saveSource = (_q = (_p = entry.overrides) == null ? void 0 : _p.name) != null ? _q : item.name;
138
143
  pushApply(
139
144
  readNumericBonus(b.saving_throws, ctx),
140
145
  "saving_throws",
141
- item.name,
146
+ saveSource,
142
147
  out.informational,
143
148
  (v) => {
144
149
  out.save_bonus += v;
150
+ out.save_terms.push({ source: saveSource, amount: v });
145
151
  }
146
152
  );
147
153
  pushApply(
@@ -162,7 +168,7 @@ function computeAppliedBonuses(resolved, _profs, registry, warnings) {
162
168
  out.spell_save_dc += v;
163
169
  }
164
170
  );
165
- if ((_p = b.ability_scores) == null ? void 0 : _p.bonus) {
171
+ if ((_r = b.ability_scores) == null ? void 0 : _r.bonus) {
166
172
  for (const [k, v] of Object.entries(b.ability_scores.bonus)) {
167
173
  if (!isAbilityKey(k)) continue;
168
174
  pushApply(
@@ -177,7 +183,7 @@ function computeAppliedBonuses(resolved, _profs, registry, warnings) {
177
183
  );
178
184
  }
179
185
  }
180
- if ((_q = b.ability_scores) == null ? void 0 : _q.static) {
186
+ if ((_s = b.ability_scores) == null ? void 0 : _s.static) {
181
187
  for (const [k, n] of Object.entries(b.ability_scores.static)) {
182
188
  if (!isAbilityKey(k) || typeof n !== "number") continue;
183
189
  const prev = out.ability_statics[k];
@@ -411,6 +417,7 @@ function attackAbility(weapon, mods, weaponAbilities) {
411
417
  function isWeaponSlugProficient(weapon, weaponSlug, profs) {
412
418
  const wp = profs.weapons;
413
419
  if (wp.specific.some((e) => e === weaponSlug || normKey(e) === normKey(weapon.name))) return true;
420
+ if (wp.specific.some((e) => qualifiedGrantCovers(e, weapon))) return true;
414
421
  const cat = weapon.category;
415
422
  if (typeof cat === "string") {
416
423
  if (/^simple\b/.test(cat) && wp.categories.includes("simple")) return true;
@@ -0,0 +1,14 @@
1
+ // src/pc/spell-source.ts
2
+ var SPELL_SOURCE = {
3
+ class: { ownAbility: false, section: "spellbook", freeCastWhenNoSlot: false, showInPrepare: true, countsAsGranted: false },
4
+ feat: { ownAbility: true, section: "spellbook", freeCastWhenNoSlot: true, showInPrepare: true, countsAsGranted: true },
5
+ item: { ownAbility: false, section: "consumable", freeCastWhenNoSlot: false, showInPrepare: false, countsAsGranted: true },
6
+ race: { ownAbility: true, section: "spellbook", freeCastWhenNoSlot: true, showInPrepare: true, countsAsGranted: true },
7
+ domain: { ownAbility: false, section: "spellbook", freeCastWhenNoSlot: false, showInPrepare: true, countsAsGranted: false }
8
+ };
9
+ var spellSource = (s) => SPELL_SOURCE[s.source];
10
+
11
+ export {
12
+ SPELL_SOURCE,
13
+ spellSource
14
+ };
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-CJYCVNW2.js";
9
9
  import {
10
10
  featureSchema
11
- } from "./chunk-FPGXFD2M.js";
11
+ } from "./chunk-YD2VAHA6.js";
12
12
  import {
13
13
  resourceSchema
14
14
  } from "./chunk-ZJXSXVFP.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseClass
3
- } from "./chunk-G7ED5H2G.js";
3
+ } from "./chunk-YKL6SHRJ.js";
4
4
 
5
5
  // src/class/class.codec.ts
6
6
  import yaml from "js-yaml";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  subclassEntitySchema
3
- } from "./chunk-J53ERF6K.js";
3
+ } from "./chunk-LF5AGJA4.js";
4
4
  import {
5
5
  aliasFeaturesByLevelActionCost
6
6
  } from "./chunk-4U55VNC7.js";