@archivist-gg/dnd5e 0.1.1 → 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 (65) 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-TPZSPU4L.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-YDPWD2T7.js → chunk-7VJ6BOA5.js} +4 -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-VLKIB33F.js → chunk-K6Q5OUGK.js} +6 -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-ADLTSHOR.js → chunk-TQZB4OCJ.js} +16 -12
  21. package/dist/{chunk-A7T5L7SS.js → chunk-TUIFSVLI.js} +1 -1
  22. package/dist/{chunk-GEJSJP6E.js → chunk-V6SOFUJW.js} +1 -1
  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 +321 -75
  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 +3 -0
  40. package/dist/item/item.schema.js +1 -1
  41. package/dist/item/item.types.d.ts +8 -0
  42. package/dist/pc/pc.decision-engine.js +8 -1
  43. package/dist/pc/pc.equipment.js +7 -3
  44. package/dist/pc/pc.feature-effects.js +9 -2
  45. package/dist/pc/pc.parser.js +1 -1
  46. package/dist/pc/pc.pools.js +8 -3
  47. package/dist/pc/pc.recalc.js +20 -10
  48. package/dist/pc/pc.resolver.d.ts +59 -1
  49. package/dist/pc/pc.resolver.js +20 -342
  50. package/dist/pc/pc.schema.d.ts +21 -0
  51. package/dist/pc/pc.schema.js +1 -1
  52. package/dist/pc/pc.types.d.ts +37 -5
  53. package/dist/race/race.codec.js +5 -5
  54. package/dist/race/race.parser.js +4 -4
  55. package/dist/race/race.schema.js +3 -3
  56. package/dist/schemas/choice-schema.js +1 -1
  57. package/dist/schemas/feature-schema.js +2 -2
  58. package/dist/subclass/subclass.codec.js +5 -5
  59. package/dist/subclass/subclass.parser.js +4 -4
  60. package/dist/subclass/subclass.schema.js +3 -3
  61. package/dist/types/choice.d.ts +5 -0
  62. package/dist/weapon/weapon-mastery.d.ts +3 -0
  63. package/package.json +1 -1
  64. package/dist/chunk-6PXE7T7C.js +0 -79
  65. package/dist/chunk-TJCNX6UZ.js +0 -521
@@ -1,356 +1,34 @@
1
1
  import {
2
- resolveAllPools
3
- } from "../chunk-6PXE7T7C.js";
4
- import {
5
- normalizeKnownSpell,
6
- resolveSpellcasting
7
- } from "../chunk-IXZXFFFW.js";
8
- import "../chunk-5XUXXAQJ.js";
9
- import {
10
- bareEntitySlug
11
- } from "../chunk-TJCNX6UZ.js";
2
+ PCResolver,
3
+ collectChosenGrantedFeatures,
4
+ collectChosenWeaponMasteries,
5
+ collectFeatGrantedSpells,
6
+ collectFeatSlugs,
7
+ collectItemGrantedSpells,
8
+ collectResolvedFeatures,
9
+ dedupeResolvedSpells,
10
+ resolveOriginFeat,
11
+ stripSlug
12
+ } from "../chunk-R5SQHP2X.js";
12
13
  import "../chunk-BH6AMZVY.js";
14
+ import "../chunk-UC4FE5X3.js";
15
+ import "../chunk-IXZXFFFW.js";
16
+ import "../chunk-5XUXXAQJ.js";
13
17
  import "../chunk-JEYQU3JT.js";
18
+ import "../chunk-DC74TZNA.js";
19
+ import "../chunk-3TRGRSCH.js";
14
20
  import "../chunk-J2RYSZMQ.js";
15
21
  import "../chunk-NAEPSZAM.js";
16
22
  import "../chunk-WP2CX2JO.js";
17
-
18
- // src/pc/pc.resolver.ts
19
- var SLUG_RE = /^\[\[(.+?)\]\]$/;
20
- function stripSlug(ref) {
21
- if (!ref) return null;
22
- const m = ref.match(SLUG_RE);
23
- return m ? m[1] : ref;
24
- }
25
- var PCResolver = class {
26
- constructor(entities) {
27
- this.entities = entities;
28
- }
29
- /** Exposed so recalc() can apply equipment bonuses (Pass A / Pass B). */
30
- get registry() {
31
- return this.entities;
32
- }
33
- resolve(character) {
34
- var _a, _b, _c, _d, _e;
35
- const warnings = [];
36
- const lookup = (rawRef, type) => {
37
- const slug = stripSlug(rawRef);
38
- if (!slug) return null;
39
- const reg = this.entities.getByTypeAndSlug(type, slug);
40
- if (!reg) {
41
- warnings.push(`Slug [[${slug}]] not found in compendium as ${type}.`);
42
- return null;
43
- }
44
- const data = reg.data;
45
- return data.slug == null ? { ...data, slug: reg.slug } : data;
46
- };
47
- const race = lookup(character.race, "race");
48
- const background = lookup(character.background, "background");
49
- const classes = character.class.map((c) => ({
50
- entity: lookup(c.name, "class"),
51
- level: c.level,
52
- subclass: lookup(c.subclass, "subclass"),
53
- choices: c.choices
54
- }));
55
- const featSlugs = collectFeatSlugs(character);
56
- const feats = [];
57
- for (const slug of featSlugs) {
58
- const f = lookup(`[[${slug}]]`, "feat");
59
- if (f) feats.push(f);
60
- }
61
- const totalLevel = classes.reduce((sum, c) => sum + c.level, 0);
62
- const features = collectResolvedFeatures(race, classes, background, feats);
63
- const extraFeatures = collectChosenGrantedFeatures(character, classes, this.entities, race, background);
64
- features.push(...extraFeatures);
65
- const primaryCasterSlug = (_a = classes.map((c, i) => c.entity && resolveSpellcasting(c) ? stripSlug(character.class[i].name) : null).find((slug) => slug != null)) != null ? _a : null;
66
- const spells = [];
67
- for (const raw of (_b = character.spells.known) != null ? _b : []) {
68
- const n = normalizeKnownSpell(raw);
69
- const reg = this.entities.getByTypeAndSlug("spell", n.slug);
70
- if (!reg) {
71
- warnings.push(`Spell [[${n.slug}]] not found in compendium.`);
72
- continue;
73
- }
74
- const entity = reg.data;
75
- const isCantrip = ((_c = entity.level) != null ? _c : 0) === 0;
76
- const classSlug = (_d = n.classSlug) != null ? _d : primaryCasterSlug;
77
- const prep = isCantrip || n.alwaysPrepared ? true : (_e = n.preparedFlag) != null ? _e : false;
78
- spells.push({ entity, slug: n.slug, classSlug, source: n.source, prepared: prep, alwaysPrepared: n.alwaysPrepared });
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
- }
101
- const resolvedCharacter = {
102
- definition: character,
103
- race,
104
- classes,
105
- background,
106
- feats,
107
- totalLevel,
108
- features,
109
- spells,
110
- pools: [],
111
- weaponMasteries: chosenMasteries.bare,
112
- state: character.state
113
- };
114
- resolvedCharacter.pools = resolveAllPools(resolvedCharacter, this.entities);
115
- return { character: resolvedCharacter, warnings };
116
- }
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
- }
131
- function collectFeatSlugs(character) {
132
- const slugs = /* @__PURE__ */ new Set();
133
- for (const c of character.class) {
134
- for (const [, choiceBlock] of Object.entries(c.choices)) {
135
- const feat = choiceBlock == null ? void 0 : choiceBlock.feat;
136
- if (typeof feat === "string") {
137
- const s = stripSlug(feat);
138
- if (s) slugs.add(s);
139
- }
140
- }
141
- }
142
- return [...slugs];
143
- }
144
- function collectChosenGrantedFeatures(character, classes, registry, race = null, background = null) {
145
- var _a, _b;
146
- const out = [];
147
- classes.forEach((c, i) => {
148
- var _a2, _b2, _c, _d;
149
- if (!c.entity) return;
150
- const entity = c.entity;
151
- const entry = character.class[i];
152
- if (!entry) return;
153
- for (const [lvlStr, atLevel] of Object.entries(entry.choices)) {
154
- const lvl = Number(lvlStr);
155
- if (!Number.isFinite(lvl) || lvl > entry.level) continue;
156
- const features = (_b2 = ((_a2 = entity.features_by_level) != null ? _a2 : {})[lvl]) != null ? _b2 : [];
157
- const subFeatures = c.subclass ? (_d = ((_c = c.subclass.features_by_level) != null ? _c : {})[lvl]) != null ? _d : [] : [];
158
- for (const feature of [...features, ...subFeatures]) {
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
- });
165
- }, registry);
166
- }
167
- }
168
- });
169
- const oc = (_a = character.origin_choices) != null ? _a : {};
170
- const originAt = (ns) => {
171
- const at = {};
172
- for (const [key, val] of Object.entries(oc)) {
173
- if (key.startsWith(`${ns}:`)) at[key.slice(ns.length + 1)] = val;
174
- }
175
- return at;
176
- };
177
- if (race) {
178
- const atRace = originAt("race");
179
- const emitRace = (granted, suppress) => {
180
- out.push({
181
- feature: granted,
182
- source: { kind: "race", slug: race.slug },
183
- ...suppress ? { renderSuppressed: true } : {}
184
- });
185
- };
186
- walkChoiceGrants(race.choices, atRace, emitRace, registry);
187
- for (const trait of (_b = race.traits) != null ? _b : []) {
188
- walkChoiceGrants(trait.choices, atRace, emitRace, registry);
189
- }
190
- }
191
- if (background) {
192
- const atBg = originAt("background");
193
- const emitBg = (granted, suppress) => {
194
- out.push({
195
- feature: granted,
196
- source: { kind: "background", slug: background.slug },
197
- ...suppress ? { renderSuppressed: true } : {}
198
- });
199
- };
200
- walkChoiceGrants(background.choices, atBg, emitBg, registry);
201
- if (background.feature) {
202
- walkChoiceGrants(background.feature.choices, atBg, emitBg, registry);
203
- }
204
- }
205
- return out;
206
- }
207
- function walkChoiceGrants(choices, atLevel, emit, registry) {
208
- var _a, _b, _c, _d, _e;
209
- for (const ch of choices != null ? choices : []) {
210
- const sel = atLevel[ch.id];
211
- if (ch.kind === "select-entity" && ch.entity_type === "optional-feature") {
212
- const slugs = Array.isArray(sel) ? sel : typeof sel === "string" ? [sel] : [];
213
- for (const slug of slugs) {
214
- const reg = registry.getByTypeAndSlug("optional-feature", (_a = stripSlug(String(slug))) != null ? _a : String(slug));
215
- if (!reg) continue;
216
- const d = reg.data;
217
- emit({
218
- id: (_b = d.slug) != null ? _b : String(slug),
219
- name: (_c = d.name) != null ? _c : String(slug),
220
- description: d.description,
221
- effects: d.effects,
222
- ...d.action_cost ? { action: d.action_cost } : {}
223
- });
224
- }
225
- }
226
- if (ch.kind === "select-inline") {
227
- const branch = typeof sel === "string" ? ch.options.find((o) => o.value === sel) : void 0;
228
- if ((_d = branch == null ? void 0 : branch.effects) == null ? void 0 : _d.length) {
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);
237
- }
238
- if (branch == null ? void 0 : branch.choices) walkChoiceGrants(branch.choices, atLevel, emit, registry);
239
- }
240
- }
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
- }
260
- function collectResolvedFeatures(race, classes, background, feats) {
261
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
262
- const out = [];
263
- for (const c of classes) {
264
- if (!c.entity) continue;
265
- const slug = c.entity.slug;
266
- const byLevel = (_a = c.entity.features_by_level) != null ? _a : {};
267
- for (const [lvlStr, feats0] of Object.entries(byLevel)) {
268
- const lvl = parseInt(lvlStr, 10);
269
- if (Number.isNaN(lvl) || lvl > c.level) continue;
270
- for (const feat of feats0) {
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
- });
278
- }
279
- }
280
- if ((_c = c.entity.resources) == null ? void 0 : _c.length) {
281
- out.push({
282
- feature: { name: c.entity.name, resources: c.entity.resources },
283
- source: { kind: "class", slug, level: 1 }
284
- });
285
- }
286
- if (c.subclass) {
287
- const sSlug = c.subclass.slug;
288
- const sByLevel = (_d = c.subclass.features_by_level) != null ? _d : {};
289
- for (const [lvlStr, feats0] of Object.entries(sByLevel)) {
290
- const lvl = parseInt(lvlStr, 10);
291
- if (Number.isNaN(lvl) || lvl > c.level) continue;
292
- for (const feat of feats0) {
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
- });
300
- }
301
- }
302
- if ((_f = c.subclass.resources) == null ? void 0 : _f.length) {
303
- out.push({
304
- feature: { name: c.subclass.name, resources: c.subclass.resources },
305
- source: { kind: "subclass", slug: sSlug, level: 1 }
306
- });
307
- }
308
- }
309
- }
310
- if (race) {
311
- const traits = (_g = race.traits) != null ? _g : [];
312
- for (const feat of traits) {
313
- out.push({ feature: feat, source: { kind: "race", slug: race.slug } });
314
- }
315
- }
316
- if (background) {
317
- const bgFeature = background.feature;
318
- if (bgFeature) {
319
- out.push({ feature: bgFeature, source: { kind: "background", slug: background.slug } });
320
- }
321
- }
322
- for (const feat of feats) {
323
- const bundled = (_h = feat.features) != null ? _h : [];
324
- const entityEffects = (_i = feat.effects) != null ? _i : [];
325
- if (bundled.length > 0) {
326
- bundled.forEach((f, i) => {
327
- var _a2;
328
- const feature = i === 0 && entityEffects.length > 0 ? { ...f, effects: [...(_a2 = f.effects) != null ? _a2 : [], ...entityEffects] } : f;
329
- out.push({ feature, source: { kind: "feat", slug: feat.slug } });
330
- });
331
- } else {
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;
335
- out.push({
336
- feature: {
337
- name,
338
- ...foldedDesc ? { description: foldedDesc } : {},
339
- ...feat.resources ? { resources: feat.resources } : {},
340
- ...entityEffects.length > 0 ? { effects: entityEffects } : {}
341
- },
342
- source: { kind: "feat", slug: feat.slug },
343
- ...buildOnly ? { buildOnly: true } : {}
344
- });
345
- }
346
- }
347
- return out;
348
- }
349
23
  export {
350
24
  PCResolver,
351
25
  collectChosenGrantedFeatures,
352
26
  collectChosenWeaponMasteries,
27
+ collectFeatGrantedSpells,
353
28
  collectFeatSlugs,
29
+ collectItemGrantedSpells,
354
30
  collectResolvedFeatures,
31
+ dedupeResolvedSpells,
32
+ resolveOriginFeat,
355
33
  stripSlug
356
34
  };
@@ -130,6 +130,19 @@ export declare const characterSchema: z.ZodObject<{
130
130
  special: "special";
131
131
  }>>;
132
132
  range: z.ZodOptional<z.ZodString>;
133
+ resist: z.ZodOptional<z.ZodArray<z.ZodString>>;
134
+ immune: z.ZodOptional<z.ZodArray<z.ZodString>>;
135
+ vulnerable: z.ZodOptional<z.ZodArray<z.ZodString>>;
136
+ condition_immune: z.ZodOptional<z.ZodArray<z.ZodString>>;
137
+ spell: z.ZodOptional<z.ZodString>;
138
+ spell_ability: z.ZodOptional<z.ZodEnum<{
139
+ str: "str";
140
+ dex: "dex";
141
+ con: "con";
142
+ int: "int";
143
+ wis: "wis";
144
+ cha: "cha";
145
+ }>>;
133
146
  }, z.core.$strict>>;
134
147
  state: z.ZodOptional<z.ZodObject<{
135
148
  charges: z.ZodOptional<z.ZodObject<{
@@ -216,6 +229,14 @@ export declare const characterSchema: z.ZodObject<{
216
229
  saveDC: z.ZodOptional<z.ZodNumber>;
217
230
  attackBonus: z.ZodOptional<z.ZodNumber>;
218
231
  }, z.core.$strip>>;
232
+ spellcasting_ability: z.ZodOptional<z.ZodEnum<{
233
+ str: "str";
234
+ dex: "dex";
235
+ con: "con";
236
+ int: "int";
237
+ wis: "wis";
238
+ cha: "cha";
239
+ }>>;
219
240
  spell_slots: z.ZodOptional<z.ZodRecord<z.ZodCoercedNumber<unknown>, z.ZodNumber>>;
220
241
  attunement_limit: z.ZodOptional<z.ZodNumber>;
221
242
  }, z.core.$strip>>;
@@ -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
  export {
6
6
  characterSchema
@@ -35,6 +35,10 @@ export interface EquipmentEntryOverrides {
35
35
  immune?: string[];
36
36
  vulnerable?: string[];
37
37
  condition_immune?: string[];
38
+ /** Per-instance spell payload (e.g. the spell a Spell Scroll casts). */
39
+ spell?: string;
40
+ /** Ability governing this instance's spell save DC / attack when cast. */
41
+ spell_ability?: Ability;
38
42
  }
39
43
  export interface EquipmentEntryState {
40
44
  charges?: {
@@ -92,6 +96,10 @@ export interface KnownSpellObject {
92
96
  source?: "class" | "feat" | "item" | "race" | "domain";
93
97
  prepared?: boolean;
94
98
  always_prepared?: boolean;
99
+ /** The spell's own spellcasting ability (e.g. WIS for a Magic Initiate (Cleric)
100
+ * pick). Belongs on the persisted shape for symmetry with ResolvedSpell.ability;
101
+ * feat spells arrive via origin_choices, but a known-spell entry may carry it. */
102
+ ability?: Ability;
95
103
  }
96
104
  export type KnownSpellEntry = string | KnownSpellObject;
97
105
  export type PassiveKind = "perception" | "investigation" | "insight";
@@ -120,6 +128,10 @@ export interface CharacterOverrides {
120
128
  saveDC?: number;
121
129
  attackBonus?: number;
122
130
  };
131
+ /** Character-level spellcasting ability for spells that carry no ability of
132
+ * their own (e.g. a Spell Scroll cast by a non-caster): the DC-ability fallback
133
+ * after a per-instance override and the character's own class ability. */
134
+ spellcasting_ability?: Ability;
123
135
  spell_slots?: Record<number, number>;
124
136
  attunement_limit?: number;
125
137
  }
@@ -267,6 +279,14 @@ export interface ResolvedSpell {
267
279
  source: "class" | "feat" | "item" | "race" | "domain";
268
280
  prepared: boolean;
269
281
  alwaysPrepared: boolean;
282
+ /** The spell's OWN spellcasting ability. For a feat spell this carries the
283
+ * chosen ability (e.g. WIS for Magic Initiate (Cleric)) while classSlug stays
284
+ * null (a feat spell is not owned by a class for DC/ability). Absent/undefined
285
+ * for class spells, which derive their ability from classSlug. */
286
+ ability?: Ability | null;
287
+ /** Index of the originating equipment entry when this spell was granted by a
288
+ * per-instance item (e.g. a Spell Scroll). Absent for class/feat/race spells. */
289
+ entryIndex?: number;
270
290
  }
271
291
  export interface ResolvedClass {
272
292
  entity: ClassEntity | null;
@@ -376,15 +396,17 @@ export interface AttackRow {
376
396
  attackNotes?: string[];
377
397
  /** 2024 Weapon Mastery for this attack, present only when the character has
378
398
  * chosen mastery of this weapon AND is proficient with it AND the weapon
379
- * declares a mastery property. `slug`/`label`/`description` come from the
380
- * mastery glossary; `derived` carries the computed number for the two
381
- * numeric masteries (topple → Save DC, graze → On-miss damage) and is absent
382
- * for the rest. Additive + display-only — absent (undefined) on untouched
383
- * rows so nothing else changes. */
399
+ * declares a mastery property. `slug`/`label`/`description`/`gist` come from
400
+ * the mastery glossary (`gist` = a one-line effect summary for the Actions-tab
401
+ * mastery column, e.g. "on fail: Prone"); `derived` carries the computed
402
+ * number for the two numeric masteries (topple → Save DC, graze → On-miss
403
+ * damage) and is absent for the rest. Additive + display-only, absent
404
+ * (undefined) on untouched rows so nothing else changes. */
384
405
  mastery?: {
385
406
  slug: string;
386
407
  label: string;
387
408
  description: string;
409
+ gist?: string;
388
410
  derived?: {
389
411
  label: string;
390
412
  value: number;
@@ -480,6 +502,16 @@ export interface DerivedStats {
480
502
  } | null;
481
503
  /** One entry per casting class (multiclass-aware). Empty for non-casters. */
482
504
  spellcastingClasses: SpellcastingClassInfo[];
505
+ /** Save DC / attack bonus for spells that carry their OWN spellcasting
506
+ * ability rather than a class (feat-granted spells, e.g. Magic Initiate).
507
+ * Keyed by that ability; populated for every ability a feat spell uses.
508
+ * Computed independently of class casters, so a NON-caster (empty
509
+ * spellcastingClasses / null spellcasting) still gets a real DC/attack.
510
+ * Empty object when the character has no such spell. */
511
+ abilitySpellcasting: Partial<Record<Ability, {
512
+ saveDC: number;
513
+ attackBonus: number;
514
+ }>>;
483
515
  /** Derived standard slot totals by spell level (before user override). */
484
516
  derivedSpellSlots: Record<number, number>;
485
517
  /** Warlock Pact Magic, or null. */
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  raceCodec
3
- } from "../chunk-PKC77VZP.js";
4
- import "../chunk-IDTDACM6.js";
5
- import "../chunk-FFULAF2P.js";
6
- import "../chunk-DABBCYTT.js";
3
+ } from "../chunk-ZZGEVJKX.js";
4
+ import "../chunk-PHQRGDRQ.js";
5
+ import "../chunk-2BWAID3Y.js";
6
+ import "../chunk-CZPMTQOY.js";
7
7
  import "../chunk-WYGD2UGN.js";
8
8
  import "../chunk-PZOWBI3B.js";
9
- import "../chunk-CDW7R2QT.js";
9
+ import "../chunk-NXSWH3AE.js";
10
10
  import "../chunk-NXR3BD2V.js";
11
11
  export {
12
12
  raceCodec
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  parseRace
3
- } from "../chunk-IDTDACM6.js";
4
- import "../chunk-FFULAF2P.js";
5
- import "../chunk-DABBCYTT.js";
3
+ } from "../chunk-PHQRGDRQ.js";
4
+ import "../chunk-2BWAID3Y.js";
5
+ import "../chunk-CZPMTQOY.js";
6
6
  import "../chunk-WYGD2UGN.js";
7
7
  import "../chunk-PZOWBI3B.js";
8
- import "../chunk-CDW7R2QT.js";
8
+ import "../chunk-NXSWH3AE.js";
9
9
  import "../chunk-NXR3BD2V.js";
10
10
  export {
11
11
  parseRace
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  raceEntitySchema
3
- } from "../chunk-FFULAF2P.js";
4
- import "../chunk-DABBCYTT.js";
3
+ } from "../chunk-2BWAID3Y.js";
4
+ import "../chunk-CZPMTQOY.js";
5
5
  import "../chunk-WYGD2UGN.js";
6
6
  import "../chunk-PZOWBI3B.js";
7
- import "../chunk-CDW7R2QT.js";
7
+ import "../chunk-NXSWH3AE.js";
8
8
  import "../chunk-NXR3BD2V.js";
9
9
  export {
10
10
  raceEntitySchema
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  choiceSchema
3
- } from "../chunk-CDW7R2QT.js";
3
+ } from "../chunk-NXSWH3AE.js";
4
4
  import "../chunk-NXR3BD2V.js";
5
5
  export {
6
6
  choiceSchema
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  featureSchema
3
- } from "../chunk-DABBCYTT.js";
3
+ } from "../chunk-CZPMTQOY.js";
4
4
  import "../chunk-WYGD2UGN.js";
5
5
  import "../chunk-PZOWBI3B.js";
6
- import "../chunk-CDW7R2QT.js";
6
+ import "../chunk-NXSWH3AE.js";
7
7
  import "../chunk-NXR3BD2V.js";
8
8
  export {
9
9
  featureSchema
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  subclassCodec
3
- } from "../chunk-SSXBYF3P.js";
4
- import "../chunk-PMADYMUI.js";
5
- import "../chunk-B7UXKNLD.js";
3
+ } from "../chunk-XLSVNUTK.js";
4
+ import "../chunk-L7E2UT5A.js";
5
+ import "../chunk-W7HWMMBF.js";
6
6
  import "../chunk-4TIJKN6O.js";
7
- import "../chunk-DABBCYTT.js";
7
+ import "../chunk-CZPMTQOY.js";
8
8
  import "../chunk-WYGD2UGN.js";
9
9
  import "../chunk-PZOWBI3B.js";
10
- import "../chunk-CDW7R2QT.js";
10
+ import "../chunk-NXSWH3AE.js";
11
11
  import "../chunk-NXR3BD2V.js";
12
12
  export {
13
13
  subclassCodec
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  parseSubclass
3
- } from "../chunk-PMADYMUI.js";
4
- import "../chunk-B7UXKNLD.js";
3
+ } from "../chunk-L7E2UT5A.js";
4
+ import "../chunk-W7HWMMBF.js";
5
5
  import "../chunk-4TIJKN6O.js";
6
- import "../chunk-DABBCYTT.js";
6
+ import "../chunk-CZPMTQOY.js";
7
7
  import "../chunk-WYGD2UGN.js";
8
8
  import "../chunk-PZOWBI3B.js";
9
- import "../chunk-CDW7R2QT.js";
9
+ import "../chunk-NXSWH3AE.js";
10
10
  import "../chunk-NXR3BD2V.js";
11
11
  export {
12
12
  parseSubclass
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  subclassEntitySchema
3
- } from "../chunk-B7UXKNLD.js";
3
+ } from "../chunk-W7HWMMBF.js";
4
4
  import "../chunk-4TIJKN6O.js";
5
- import "../chunk-DABBCYTT.js";
5
+ import "../chunk-CZPMTQOY.js";
6
6
  import "../chunk-WYGD2UGN.js";
7
7
  import "../chunk-PZOWBI3B.js";
8
- import "../chunk-CDW7R2QT.js";
8
+ import "../chunk-NXSWH3AE.js";
9
9
  import "../chunk-NXR3BD2V.js";
10
10
  export {
11
11
  subclassEntitySchema
@@ -19,6 +19,11 @@ export interface EntityFilter {
19
19
  /** Armor proficiency class. Matched against an armor entity's `category`
20
20
  * field exactly; "shield" selects shields. */
21
21
  armor_category?: "light" | "medium" | "heavy" | "shield";
22
+ /** Spell axis: filter spells by class list, level, and edition. Set together on a
23
+ * select-entity{entity_type:"spell"} choice (e.g. Magic Initiate). */
24
+ list?: string;
25
+ level?: number;
26
+ edition?: string;
22
27
  }
23
28
  export interface InlineOption {
24
29
  value: string;
@@ -1,8 +1,11 @@
1
1
  export declare const MASTERY: Record<string, {
2
2
  label: string;
3
3
  description: string;
4
+ gist: string;
4
5
  }>;
5
6
  export declare function masteryLabel(slug: string): string | undefined;
7
+ /** One-line effect gist for a mastery slug (or undefined for an unknown slug). */
8
+ export declare function masteryGist(slug: string): string | undefined;
6
9
  export declare function masteryDerived(slug: string, abilityMod: number, proficiencyBonus: number): {
7
10
  label: string;
8
11
  value: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archivist-gg/dnd5e",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "publishConfig": {