@archivist-gg/dnd5e 0.3.17 → 0.3.19

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 (99) 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-7QN7XSAK.js → chunk-3SFGWFVQ.js} +1 -1
  5. package/dist/{chunk-2UDSN6VW.js → chunk-3SIUAFDQ.js} +1 -1
  6. package/dist/chunk-4J6DS3EJ.js +30 -0
  7. package/dist/{chunk-RLGQ5PZZ.js → chunk-5JXFJ5AU.js} +11 -1
  8. package/dist/{chunk-TULQ4FFX.js → chunk-64VNLMSG.js} +31 -9
  9. package/dist/{chunk-JSM6JFZR.js → chunk-7XJHI5H5.js} +2 -2
  10. package/dist/{chunk-YD2VAHA6.js → chunk-B2QHP6UF.js} +2 -2
  11. package/dist/{chunk-AZRGFUSR.js → chunk-BJGOSM76.js} +1 -1
  12. package/dist/{chunk-JCMUVZIK.js → chunk-BJWSOQUC.js} +1 -1
  13. package/dist/{chunk-ONTGZR5T.js → chunk-EJD47JHL.js} +64 -32
  14. package/dist/{chunk-FANQVQ3D.js → chunk-FJYBLXV7.js} +2 -2
  15. package/dist/{chunk-PQZ4LM4Z.js → chunk-FMUD3LDV.js} +1 -1
  16. package/dist/{chunk-PKX6TGRE.js → chunk-FMYWORTP.js} +2 -2
  17. package/dist/{chunk-G6PBI2BU.js → chunk-FVXLP7P3.js} +1 -1
  18. package/dist/{chunk-MBX25B6D.js → chunk-GVWEFECG.js} +1 -1
  19. package/dist/{chunk-Y5VVP73C.js → chunk-HY2SRSVM.js} +1 -1
  20. package/dist/{chunk-2JSQE3DD.js → chunk-JXWNXIIZ.js} +13 -0
  21. package/dist/{chunk-VRV3NNTA.js → chunk-LICYHPI3.js} +81 -5
  22. package/dist/{chunk-LF5AGJA4.js → chunk-LNT64X7C.js} +1 -1
  23. package/dist/{chunk-6EI737IO.js → chunk-NKZMQV2C.js} +1 -1
  24. package/dist/{chunk-T6QIF5XR.js → chunk-NTU2DXNG.js} +9 -2
  25. package/dist/{chunk-3VZZDKRA.js → chunk-NXUY5XQN.js} +63 -15
  26. package/dist/{chunk-QZSWN2KT.js → chunk-NZO7M5GV.js} +1 -1
  27. package/dist/{chunk-5MXFW4O3.js → chunk-OOL4KMG2.js} +1 -1
  28. package/dist/{chunk-H5PTKYXL.js → chunk-QA3YCOML.js} +1 -1
  29. package/dist/{chunk-3MGQFFNE.js → chunk-QFWQKS3M.js} +1 -1
  30. package/dist/{chunk-HTYMW5CH.js → chunk-RD2MID3M.js} +67 -3
  31. package/dist/{chunk-PXVNAY7T.js → chunk-TRBGC4ZG.js} +29 -1
  32. package/dist/{chunk-UUBJCNQU.js → chunk-UJCK5IW4.js} +2 -2
  33. package/dist/{chunk-OFCSP6MT.js → chunk-UNYR5GLI.js} +1 -1
  34. package/dist/{chunk-DOXSFYLH.js → chunk-VN5DLURA.js} +2 -2
  35. package/dist/{chunk-7ATNGKBS.js → chunk-VP5TIXDK.js} +1 -1
  36. package/dist/{chunk-QH36K2PU.js → chunk-WOHX7YTH.js} +7 -0
  37. package/dist/{chunk-YKL6SHRJ.js → chunk-XTEFPUSN.js} +1 -1
  38. package/dist/{chunk-RYLXAYIQ.js → chunk-ZOWKWMD2.js} +1 -1
  39. package/dist/class/class.codec.js +6 -6
  40. package/dist/class/class.parser.js +5 -5
  41. package/dist/class/class.schema.js +4 -4
  42. package/dist/dnd/amount-text.d.ts +4 -0
  43. package/dist/dnd/amount-text.js +3 -1
  44. package/dist/entities/registry-revision.d.ts +25 -0
  45. package/dist/entities/registry-revision.js +10 -0
  46. package/dist/feat/feat.codec.js +5 -5
  47. package/dist/feat/feat.parser.js +4 -4
  48. package/dist/feat/feat.schema.d.ts +9 -0
  49. package/dist/feat/feat.schema.js +3 -3
  50. package/dist/index.js +3720 -338
  51. package/dist/monster/monster.parser.js +5 -5
  52. package/dist/monster/monster.schema.js +4 -4
  53. package/dist/optional-feature/optional-feature.codec.js +5 -5
  54. package/dist/optional-feature/optional-feature.parser.js +4 -4
  55. package/dist/optional-feature/optional-feature.schema.d.ts +9 -0
  56. package/dist/optional-feature/optional-feature.schema.js +3 -3
  57. package/dist/pc/pc.additional-spells.d.ts +16 -2
  58. package/dist/pc/pc.additional-spells.js +4 -1
  59. package/dist/pc/pc.amount-bindings.d.ts +11 -2
  60. package/dist/pc/pc.amount-bindings.js +6 -1
  61. package/dist/pc/pc.decision-engine.d.ts +8 -0
  62. package/dist/pc/pc.decision-engine.js +13 -10
  63. package/dist/pc/pc.entry-overrides.js +3 -2
  64. package/dist/pc/pc.equipment.js +5 -4
  65. package/dist/pc/pc.feature-effects.d.ts +9 -2
  66. package/dist/pc/pc.feature-effects.js +15 -2
  67. package/dist/pc/pc.infusions.js +3 -2
  68. package/dist/pc/pc.member-choices.d.ts +33 -1
  69. package/dist/pc/pc.member-choices.js +12 -5
  70. package/dist/pc/pc.parser.js +1 -1
  71. package/dist/pc/pc.pools.js +3 -2
  72. package/dist/pc/pc.proficiencies.d.ts +14 -0
  73. package/dist/pc/pc.proficiencies.js +68 -19
  74. package/dist/pc/pc.proficiency-normalize.d.ts +14 -0
  75. package/dist/pc/pc.proficiency-normalize.js +9 -3
  76. package/dist/pc/pc.proficiency-query.js +1 -1
  77. package/dist/pc/pc.recalc.js +151 -55
  78. package/dist/pc/pc.resolver.d.ts +4 -1
  79. package/dist/pc/pc.resolver.js +10 -9
  80. package/dist/pc/pc.rest.js +3 -2
  81. package/dist/pc/pc.schema.d.ts +19 -0
  82. package/dist/pc/pc.schema.js +1 -1
  83. package/dist/pc/pc.spell-scope.d.ts +22 -0
  84. package/dist/pc/pc.tool-picks.js +2 -2
  85. package/dist/pc/pc.types.d.ts +65 -4
  86. package/dist/race/race.codec.js +6 -6
  87. package/dist/race/race.parser.js +5 -5
  88. package/dist/race/race.schema.js +4 -4
  89. package/dist/schemas/choice-schema.js +2 -2
  90. package/dist/schemas/feature-effect-schema.d.ts +9 -0
  91. package/dist/schemas/feature-effect-schema.js +1 -1
  92. package/dist/schemas/feature-schema.js +3 -3
  93. package/dist/subclass/subclass.codec.js +6 -6
  94. package/dist/subclass/subclass.parser.js +5 -5
  95. package/dist/subclass/subclass.schema.js +4 -4
  96. package/dist/types/choice.d.ts +2 -2
  97. package/dist/types/feature-effect.d.ts +18 -0
  98. package/dist/types/optional-feature.types.d.ts +1 -1
  99. package/package.json +1 -1
@@ -4,6 +4,9 @@ import {
4
4
  import {
5
5
  warnOnce
6
6
  } from "./chunk-LVQLWRCU.js";
7
+ import {
8
+ memoByRevision
9
+ } from "./chunk-4J6DS3EJ.js";
7
10
  import {
8
11
  slugify
9
12
  } from "./chunk-ZCTUYGNV.js";
@@ -41,6 +44,10 @@ function normalizeSpellRef(ref) {
41
44
  return out.length > 0 ? out : null;
42
45
  }
43
46
  function buildSpellNameIndex(entities) {
47
+ return memoByRevision(SPELL_NAME_INDEX, entities, () => spellNameIndexOf(entities));
48
+ }
49
+ var SPELL_NAME_INDEX = /* @__PURE__ */ new WeakMap();
50
+ function spellNameIndexOf(entities) {
44
51
  var _a;
45
52
  const byName = /* @__PURE__ */ new Map();
46
53
  for (const e of entities.search("", "spell", Number.POSITIVE_INFINITY)) {
@@ -95,20 +102,78 @@ var entryHasInSliceLeaf = (e, comparand, own) => readAbility(e.ability, own) !==
95
102
  const lv = e[b];
96
103
  return !!lv && typeof lv === "object" && !Array.isArray(lv) && Object.entries(lv).some(([k, v]) => (k === "_" || Number(k) <= comparand) && Array.isArray(v) && v.some((x) => typeof x === "string"));
97
104
  });
105
+ function chosenRaceOptionKeys(race, read) {
106
+ var _a;
107
+ const out = /* @__PURE__ */ new Set();
108
+ const r = race;
109
+ const lists = [r.choices, ...((_a = r.traits) != null ? _a : []).map((t) => t == null ? void 0 : t.choices)];
110
+ for (const list of lists) {
111
+ for (const c of Array.isArray(list) ? list : []) {
112
+ if ((c == null ? void 0 : c.kind) !== "select-inline" || typeof c.id !== "string" || RACE_STRUCTURAL_PSEUDO.has(c.id)) continue;
113
+ const v = read(c.id);
114
+ for (const value of Array.isArray(v) ? v : typeof v === "string" ? [v] : []) {
115
+ if (typeof value !== "string") continue;
116
+ out.add(slugify(value));
117
+ const opt = (Array.isArray(c.options) ? c.options : []).find((o) => (o == null ? void 0 : o.value) === value);
118
+ if (typeof (opt == null ? void 0 : opt.label) === "string") out.add(slugify(opt.label));
119
+ }
120
+ }
121
+ }
122
+ return out;
123
+ }
124
+ function innateLeaves(innate, comparand) {
125
+ if (!innate || typeof innate !== "object" || Array.isArray(innate)) return [];
126
+ const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : v && typeof v === "object" ? Object.values(v).flatMap(strings) : [];
127
+ return Object.entries(innate).filter(([k]) => k === "_" || Number(k) <= comparand).flatMap(([, v]) => strings(v));
128
+ }
129
+ function memberAtWillLeaves(innate, comparand, slug) {
130
+ if (!innate || typeof innate !== "object" || Array.isArray(innate)) return [];
131
+ const out = [];
132
+ for (const [k, v] of Object.entries(innate)) {
133
+ if (!(k === "_" || Number(k) <= comparand)) continue;
134
+ if (Array.isArray(v)) {
135
+ out.push(...v);
136
+ continue;
137
+ }
138
+ for (const [usage, list] of v && typeof v === "object" ? Object.entries(v) : []) {
139
+ if (usage === "will" && Array.isArray(list)) out.push(...list);
140
+ else warnOnce(`addspells:usage:${slug}:innate:${k}:${usage}`, `archivist: additional_spells on "${slug}" innate.${k}.${usage} is a usage economy (skipped; G8)`);
141
+ }
142
+ }
143
+ return out;
144
+ }
98
145
  function collectAdditionalSpells(args) {
146
+ var _a, _b;
99
147
  const { race, feats, classes, totalLevel, ownAbility, alreadyCollected, entities, warnings } = args;
148
+ const raceChoice = (_a = args.raceChoice) != null ? _a : (() => void 0);
100
149
  let index = null;
101
150
  const indexOf = () => index != null ? index : index = buildSpellNameIndex(entities);
102
151
  const out = [];
103
152
  const carriers = [];
104
153
  const asRaw = (x) => x == null ? void 0 : x.additional_spells;
105
154
  if (race && raceIsUngated(race)) carriers.push({ root: "race", slug: race.slug, edition: editionOf(race), entries: entriesOf(asRaw(race)), comparand: totalLevel, classSlug: null });
155
+ else if (race) {
156
+ const chosen = chosenRaceOptionKeys(race, raceChoice);
157
+ for (const e of entriesOf(asRaw(race))) {
158
+ if (typeof e.name === "string" && chosen.has(slugify(e.name))) {
159
+ carriers.push({ root: "race", slug: race.slug, edition: editionOf(race), entries: [e], comparand: totalLevel, classSlug: null });
160
+ }
161
+ }
162
+ }
106
163
  for (const f of feats) carriers.push({ root: "feat", slug: f.slug, edition: editionOf(f), entries: entriesOf(asRaw(f)), comparand: totalLevel, classSlug: null });
107
164
  for (const c of classes) {
108
165
  if (!c.entity) continue;
109
166
  carriers.push({ root: "class", slug: c.entity.slug, edition: editionOf(c.entity), entries: entriesOf(asRaw(c.entity)), comparand: c.level, classSlug: c.entity.slug });
110
167
  if (c.subclass) carriers.push({ root: "subclass", slug: c.subclass.slug, edition: editionOf(c.subclass), entries: entriesOf(asRaw(c.subclass)), comparand: c.level, classSlug: c.entity.slug });
111
168
  }
169
+ for (const pool of (_b = args.pools) != null ? _b : []) {
170
+ const c = classes[pool.classIndex];
171
+ if (!(c == null ? void 0 : c.entity)) continue;
172
+ for (const m of [...pool.selected, ...pool.grants]) {
173
+ if (m.itemBound === true) continue;
174
+ carriers.push({ root: "member", slug: m.slug, edition: editionOf(m.entity), entries: entriesOf(asRaw(m.entity)), comparand: c.level, classSlug: c.entity.slug });
175
+ }
176
+ }
112
177
  for (const car of carriers) {
113
178
  if (car.entries.length === 0) continue;
114
179
  if (car.entries.length > 1) {
@@ -116,12 +181,21 @@ function collectAdditionalSpells(args) {
116
181
  continue;
117
182
  }
118
183
  const entry = car.entries[0];
119
- const ability = readAbility(entry.ability, ownAbility);
184
+ let ability = readAbility(entry.ability, ownAbility);
185
+ if (ability === "choose" && car.root === "race") {
186
+ const pick = raceChoice("spellcasting-ability");
187
+ const allowed = entry.ability.choose;
188
+ ability = typeof pick === "string" && Array.isArray(allowed) && allowed.includes(pick) ? pick : "choose";
189
+ }
120
190
  if (ability === "choose") continue;
121
- for (const bucket of ["known", "prepared"]) {
191
+ const buckets = ["known", "prepared"].map((bucket) => {
122
192
  const levels = entry[bucket];
123
- if (!levels || typeof levels !== "object" || Array.isArray(levels)) continue;
124
- for (const [k, v] of Object.entries(levels)) {
193
+ return [bucket, !levels || typeof levels !== "object" || Array.isArray(levels) ? [] : Object.entries(levels)];
194
+ });
195
+ if (car.root === "race") buckets.push(["innate", [["_", innateLeaves(entry.innate, car.comparand)]]]);
196
+ if (car.root === "member") buckets.push(["at-will", [["_", memberAtWillLeaves(entry.innate, car.comparand, car.slug)]]]);
197
+ for (const [bucket, levelEntries] of buckets) {
198
+ for (const [k, v] of levelEntries) {
125
199
  if (!(k === "_" || Number(k) <= car.comparand)) continue;
126
200
  if (!Array.isArray(v)) {
127
201
  warnOnce(`addspells:usage:${car.slug}:${bucket}:${k}`, `archivist: additional_spells on "${car.slug}" ${bucket}.${k} is a usage economy (skipped; G8)`);
@@ -142,7 +216,8 @@ function collectAdditionalSpells(args) {
142
216
  source: car.root === "race" ? "race" : car.root === "feat" ? "feat" : "class",
143
217
  prepared: true,
144
218
  alwaysPrepared: true,
145
- ...ability ? { ability } : {}
219
+ ...ability ? { ability } : {},
220
+ ...bucket === "at-will" ? { atWill: true } : {}
146
221
  });
147
222
  }
148
223
  }
@@ -157,5 +232,6 @@ export {
157
232
  buildSpellNameIndex,
158
233
  resolveSpellByName,
159
234
  raceIsUngated,
235
+ chosenRaceOptionKeys,
160
236
  collectAdditionalSpells
161
237
  };
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-CJYCVNW2.js";
9
9
  import {
10
10
  featureSchema
11
- } from "./chunk-YD2VAHA6.js";
11
+ } from "./chunk-B2QHP6UF.js";
12
12
  import {
13
13
  resourceSchema
14
14
  } from "./chunk-ZJXSXVFP.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  backgroundEntitySchema
3
- } from "./chunk-3MGQFFNE.js";
3
+ } from "./chunk-QFWQKS3M.js";
4
4
 
5
5
  // src/background/background.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -143,13 +143,16 @@ var characterOverridesShape = z.object({
143
143
  * `toProfSlug` output with the apostrophe RETAINED ("thieves'-tools"), matching the engine's
144
144
  * own tool vocabulary in types/choice.ts. The READER is wider: `computeEffectiveProficiencies`
145
145
  * normalises each key with `toProfSlug` on read, so a hand-typed "Thieves' Tools" also
146
- * matches; and the plugin's two writers (`CharacterEditState.setToolProficiency` and the
146
+ * matches; and the plugin's two writers (`CharacterEditState.setToolExpertise` and the
147
147
  * `addProficiency` clearance) match an existing key by slug too, so a key the note ALREADY
148
148
  * carries is written THROUGH in its own spelling rather than duplicated · nothing here
149
149
  * re-spells it (review M-4: "repaired in place" read as "normalised", which it is not).
150
150
  * `languages` gets no such sibling: languages have no tri (§9.3). */
151
151
  proficiency: z.record(z.string(), proficiencyTri).optional()
152
152
  }).optional(),
153
+ /** D4 A: weapon and armor proficiency edits, the `languages` shape. Same no-defaults rule. */
154
+ weapons: z.object({ add: z.array(z.string()).optional(), remove: z.array(z.string()).optional() }).optional(),
155
+ armor: z.object({ add: z.array(z.string()).optional(), remove: z.array(z.string()).optional() }).optional(),
153
156
  /** Manual defense edits. SUPPRESSION-ONLY, and the missing `add` channel is a DELIBERATE
154
157
  * asymmetry with `languages`/`tools` above · do not "complete the pattern". The additive
155
158
  * store is `character.defenses.*`, which already exists and works; a second additive
@@ -261,7 +264,11 @@ var sheetPoolViewSchema = z.object({
261
264
  folded: z.array(z.string().min(1)).optional()
262
265
  });
263
266
  var sheetStateSchema = z.object({
264
- layout: z.object({ saves: z.enum(["section"]).optional().catch(void 0) }).optional(),
267
+ // D13 (D3-A): `body: wide` is the wide three-column sheet (it draws the saves as a section, like `saves: section`).
268
+ layout: z.object({
269
+ saves: z.enum(["section"]).optional().catch(void 0),
270
+ body: z.enum(["wide"]).optional().catch(void 0)
271
+ }).optional(),
265
272
  favorites: z.array(z.string().min(1)).optional(),
266
273
  filters: z.object({ actions: sheetViewFilterSchema.optional(), passive: sheetViewFilterSchema.optional() }).optional(),
267
274
  pools: z.record(z.string(), sheetPoolViewSchema).optional()
@@ -7,6 +7,10 @@ import {
7
7
  import {
8
8
  readTableColumn
9
9
  } from "./chunk-IUAAXXNO.js";
10
+ import {
11
+ findItemAction,
12
+ resolveItemAction
13
+ } from "./chunk-ZUQW5VBZ.js";
10
14
  import {
11
15
  requiresAttunement
12
16
  } from "./chunk-IHE6KVC3.js";
@@ -18,10 +22,6 @@ import {
18
22
  resolveEntityForEntry,
19
23
  slotAcTerms
20
24
  } from "./chunk-LHZXYFBU.js";
21
- import {
22
- findItemAction,
23
- resolveItemAction
24
- } from "./chunk-ZUQW5VBZ.js";
25
25
  import {
26
26
  warnOnce
27
27
  } from "./chunk-LVQLWRCU.js";
@@ -29,6 +29,10 @@ import {
29
29
  keptSlugFor,
30
30
  resolveBaseItem
31
31
  } from "./chunk-CM5CWO4K.js";
32
+ import {
33
+ bucketOf,
34
+ memoByRevision
35
+ } from "./chunk-4J6DS3EJ.js";
32
36
  import {
33
37
  bareEntitySlug,
34
38
  slugify,
@@ -102,6 +106,11 @@ function bindFeatureConsumers(features, index, classes) {
102
106
  }
103
107
 
104
108
  // src/pc/pc.pools.ts
109
+ var SORTED_OPTIONAL_FEATURES = /* @__PURE__ */ new WeakMap();
110
+ function sortedOptionalFeatures(registry) {
111
+ return memoByRevision(SORTED_OPTIONAL_FEATURES, registry, () => bucketOf(registry, "optional-feature").slice().sort((a, b) => a.name.localeCompare(b.name) || a.slug.localeCompare(b.slug)));
112
+ }
113
+ var SRD_OWNER_PREFIXES = /* @__PURE__ */ new Set(["srd-5e", "srd-2024"]);
105
114
  function levelPrereqMax(d) {
106
115
  var _a;
107
116
  return ((_a = d.prerequisites) != null ? _a : []).filter((p) => p.kind === "level").reduce((m, p) => Math.max(m, p.min), 0);
@@ -125,6 +134,7 @@ function resolvePool(rc, classIndex, pool, registry, index, edition) {
125
134
  const level = rc.level;
126
135
  const ownerBare = bareEntitySlug(entity.slug);
127
136
  const ownerPrefix = entity.slug.split("_")[0];
137
+ const ownEdition = SRD_OWNER_PREFIXES.has(ownerPrefix) ? entity.edition : void 0;
128
138
  const col = pool.count.column;
129
139
  const count = (_a = readTableColumn(entity.table, level, [col])) != null ? _a : 0;
130
140
  const { anchorLevel, columnSeen } = poolAnchorLevel(entity, pool);
@@ -135,7 +145,7 @@ function resolvePool(rc, classIndex, pool, registry, index, edition) {
135
145
  );
136
146
  }
137
147
  const ft = pool.source.where.feature_type;
138
- const all = registry.search("", "optional-feature", Number.POSITIVE_INFINITY).slice().sort((a, b) => a.name.localeCompare(b.name) || a.slug.localeCompare(b.slug));
148
+ const all = sortedOptionalFeatures(registry);
139
149
  const entryOf = (slug, entity2, compendium) => ({ slug, entity: entity2, ...compendium ? { compendium } : {}, key: slug, param: null, itemBound: isItemBound(entity2) });
140
150
  const byBare = /* @__PURE__ */ new Map();
141
151
  const candidates = /* @__PURE__ */ new Map();
@@ -147,6 +157,7 @@ function resolvePool(rc, classIndex, pool, registry, index, edition) {
147
157
  if (d.feature_type !== ft) continue;
148
158
  if (!((_b = d.available_to) != null ? _b : []).some((l) => wikilinkTailSlug(l) === ownerBare)) continue;
149
159
  if (levelPrereqMax(d) > level) continue;
160
+ if (ownEdition && d.edition && d.edition !== ownEdition) continue;
150
161
  const group = candidates.get(bare);
151
162
  if (group) group.push(entry);
152
163
  else candidates.set(bare, [entry]);
@@ -353,13 +364,49 @@ function rowMatches(row, it) {
353
364
  if (c.variant) return c.variant.candidates.some((l) => bareEntitySlug(linkSlug(l)) === bareEntitySlug(it.slug));
354
365
  return !!c.filter && matchesFilter(it.data, c.filter);
355
366
  }
356
- var rowInstalledIn = (row, items) => {
357
- const v = row.creates.item ? void 0 : row.creates.variant;
358
- if (!v) return items.filter((it) => rowMatches(row, it));
359
- const bares = new Set(v.candidates.map((l) => bareEntitySlug(linkSlug(l))));
360
- return items.filter((it) => bares.has(bareEntitySlug(it.slug)));
361
- };
362
- var rowInstalled = (row, registry) => rowInstalledIn(row, itemsOf(registry));
367
+ function buildItemIndex(items) {
368
+ const lcType = [], lcRarity = [];
369
+ const byLcName = /* @__PURE__ */ new Map(), bySlug = /* @__PURE__ */ new Map(), byBare = /* @__PURE__ */ new Map();
370
+ const push = (m, k, i) => {
371
+ const l = m.get(k);
372
+ if (l) l.push(i);
373
+ else m.set(k, [i]);
374
+ };
375
+ items.forEach((it, i) => {
376
+ const data = it.data;
377
+ lcType.push(lc(data.type));
378
+ lcRarity.push(lc(data.rarity));
379
+ push(byLcName, lc(it.name), i);
380
+ if (!bySlug.has(it.slug)) bySlug.set(it.slug, i);
381
+ push(byBare, bareEntitySlug(it.slug), i);
382
+ });
383
+ return { items, lcType, lcRarity, byLcName, bySlug, byBare };
384
+ }
385
+ var ITEM_INDEX = /* @__PURE__ */ new WeakMap();
386
+ var itemIndexOf = (registry) => memoByRevision(ITEM_INDEX, registry, () => buildItemIndex(itemsOf(registry)));
387
+ var inBucketOrder = (ix, hits) => [...hits].sort((a, b) => a - b).map((i) => ix.items[i]);
388
+ function rowInstalledIn(row, ix) {
389
+ var _a, _b, _c;
390
+ const c = row.creates;
391
+ if (c.item) {
392
+ const hits = new Set((_a = ix.byLcName.get(lc(row.name))) != null ? _a : []);
393
+ const own = ix.bySlug.get(linkSlug(c.item));
394
+ if (own !== void 0) hits.add(own);
395
+ return inBucketOrder(ix, hits);
396
+ }
397
+ if (c.variant) {
398
+ const hits = /* @__PURE__ */ new Set();
399
+ for (const l of c.variant.candidates) for (const i of (_b = ix.byBare.get(bareEntitySlug(linkSlug(l)))) != null ? _b : []) hits.add(i);
400
+ return inBucketOrder(ix, hits);
401
+ }
402
+ const f = c.filter;
403
+ if (!f) return [];
404
+ const rarities = new Set(f.rarity.map(lc));
405
+ const types = f.types ? new Set(f.types.map(lc)) : null;
406
+ const excluded = new Set(((_c = f.exclude_types) != null ? _c : []).map(lc));
407
+ return ix.items.filter((it, i) => rarities.has(ix.lcRarity[i]) && (!types || types.has(ix.lcType[i])) && !excluded.has(ix.lcType[i]) && !(f.cursed === false && it.data.cursed === true));
408
+ }
409
+ var rowInstalled = (row, registry) => rowInstalledIn(row, itemIndexOf(registry));
363
410
  function candidateOrder(edition) {
364
411
  return (a, b) => {
365
412
  const ea = edition && lc(a.data.edition) === edition ? 0 : 1;
@@ -393,9 +440,10 @@ function candidatesFrom(installed, registry, opts) {
393
440
  }
394
441
  function resolveOptionRows(note, registry, opts = {}) {
395
442
  var _a;
396
- const items = itemsOf(registry);
443
+ const ix = itemIndexOf(registry);
397
444
  return ((_a = note.options) != null ? _a : []).map((row) => {
398
- const rowItems = rowInstalledIn(row, items);
445
+ var _a2;
446
+ const rowItems = rowInstalledIn(row, ix);
399
447
  const candidates = candidatesFrom(rowItems, registry, opts);
400
448
  const anyVisible = candidates.length > 0 || candidatesFrom(rowItems, registry, { ...opts, exclude: [] }).length > 0;
401
449
  const installed = rowItems.length > 0;
@@ -407,7 +455,7 @@ function resolveOptionRows(note, registry, opts = {}) {
407
455
  arm: row.creates.item ? "item" : row.creates.variant ? "variant" : "filter",
408
456
  candidates,
409
457
  availability: anyVisible ? "installed" : installed ? "hidden-only" : "not-installed",
410
- ...rowItemWords(row, candidates[0] ? registry.getByTypeAndSlug("item", candidates[0]) : rowItems[0])
458
+ ...rowItemWords(row, candidates[0] ? ix.items[(_a2 = ix.bySlug.get(candidates[0])) != null ? _a2 : -1] : rowItems[0])
411
459
  };
412
460
  });
413
461
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseSubclass
3
- } from "./chunk-OFCSP6MT.js";
3
+ } from "./chunk-UNYR5GLI.js";
4
4
 
5
5
  // src/subclass/subclass.codec.ts
6
6
  import yaml from "js-yaml";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  featureEffectSchema
3
- } from "./chunk-2JSQE3DD.js";
3
+ } from "./chunk-JXWNXIIZ.js";
4
4
 
5
5
  // src/schemas/choice-schema.ts
6
6
  import { z } from "zod";
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-KU3YLF6D.js";
7
7
  import {
8
8
  toProfSlug
9
- } from "./chunk-RLGQ5PZZ.js";
9
+ } from "./chunk-5JXFJ5AU.js";
10
10
 
11
11
  // src/pc/pc.tool-picks.ts
12
12
  var TOOL_CATEGORIES = {
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-IN4M4X7P.js";
4
4
  import {
5
5
  choiceSchema
6
- } from "./chunk-5MXFW4O3.js";
6
+ } from "./chunk-OOL4KMG2.js";
7
7
  import {
8
8
  additionalSpellsEntrySchema,
9
9
  imageField
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  parsePickKey,
3
3
  poolAnchorLevel
4
- } from "./chunk-3VZZDKRA.js";
4
+ } from "./chunk-NXUY5XQN.js";
5
5
  import {
6
6
  featProgressionCategory
7
7
  } from "./chunk-YBLDE5CG.js";
@@ -9,7 +9,11 @@ import {
9
9
  warnOnce
10
10
  } from "./chunk-LVQLWRCU.js";
11
11
  import {
12
- bareEntitySlug
12
+ bucketOf
13
+ } from "./chunk-4J6DS3EJ.js";
14
+ import {
15
+ bareEntitySlug,
16
+ wikilinkTailSlug
13
17
  } from "./chunk-ZCTUYGNV.js";
14
18
 
15
19
  // src/pc/pc.member-choices.ts
@@ -104,6 +108,46 @@ function memberChoices(entity) {
104
108
  }
105
109
  return out;
106
110
  }
111
+ function memberSpellTargets(member, read) {
112
+ var _a;
113
+ const refs = [];
114
+ const open = [];
115
+ for (const ch of memberChoices(member)) {
116
+ if (ch.kind !== "select-entity" || !isTargetChoice(ch) || ch.entity_type !== "spell") continue;
117
+ const v = read(ch.id);
118
+ const picked = (Array.isArray(v) ? v : [v]).filter((x) => typeof x === "string" && x.trim() !== "");
119
+ if (picked.length) refs.push(...picked);
120
+ else if (ch.where) open.push(ch.where);
121
+ }
122
+ for (const pre of (_a = member.prerequisites) != null ? _a : []) if (pre.kind === "spell-known") refs.push(pre.spell);
123
+ return refs.length || open.length ? { refs, open } : null;
124
+ }
125
+ function spellMatchesFilter(d, where) {
126
+ var _a, _b;
127
+ if (where.list && !((_a = d.classes) != null ? _a : []).includes(where.list)) return false;
128
+ if (where.level !== void 0 && ((_b = d.level) != null ? _b : 0) !== where.level) return false;
129
+ if (where.edition && d.edition !== where.edition) return false;
130
+ if (where.ritual && d.ritual !== true) return false;
131
+ return true;
132
+ }
133
+ function spellsReached(targets, spells) {
134
+ if (!targets) return [];
135
+ const hit = /* @__PURE__ */ new Set();
136
+ for (const ref of targets.refs) {
137
+ const full = stripRef(ref);
138
+ const exact = spells.filter((s) => s.slug === full);
139
+ const bare = wikilinkTailSlug(ref);
140
+ for (const s of exact.length ? exact : spells.filter((s2) => bareEntitySlug(s2.slug) === bare)) hit.add(s.slug);
141
+ }
142
+ for (const where of targets.open) {
143
+ const legal = new Set(spells.filter((s) => {
144
+ var _a;
145
+ return spellMatchesFilter(s.entity, where) && !!((_a = s.entity.damage_roll) == null ? void 0 : _a.trim());
146
+ }).map((s) => s.slug));
147
+ if (legal.size === 1) hit.add([...legal][0]);
148
+ }
149
+ return [...new Set(spells.map((s) => s.slug))].filter((slug) => hit.has(slug));
150
+ }
107
151
  function siteOf(classIndex, level, poolId, pickKey, member, memberSlug, block) {
108
152
  const choices = memberChoices(member);
109
153
  if (!choices.length) return null;
@@ -115,7 +159,7 @@ function poolMemberSites(classes, registry) {
115
159
  let byBare = null;
116
160
  let all = null;
117
161
  const lookup = (s, ownerPrefix) => {
118
- all != null ? all : all = registry.search("", "optional-feature", Number.POSITIVE_INFINITY);
162
+ all != null ? all : all = bucketOf(registry, "optional-feature");
119
163
  const bare = bareEntitySlug(s);
120
164
  const owned = all.find((e) => bareEntitySlug(e.slug) === bare && e.slug.split("_")[0] === ownerPrefix);
121
165
  if (owned) return owned;
@@ -204,6 +248,23 @@ function featPickSites(classEntries, memberSites, classSlugOf) {
204
248
  }
205
249
  return out;
206
250
  }
251
+ function raceFeatSites(race, originChoices) {
252
+ var _a;
253
+ if (!race) return [];
254
+ const oc = originChoices != null ? originChoices : {};
255
+ const out = [];
256
+ for (const list of [race.choices, ...((_a = race.traits) != null ? _a : []).map((t) => t == null ? void 0 : t.choices)]) {
257
+ for (const ch of Array.isArray(list) ? list : []) {
258
+ if ((ch == null ? void 0 : ch.kind) !== "select-entity" || ch.entity_type !== "feat") continue;
259
+ const v = oc[`race:${ch.id}`];
260
+ for (const ref of Array.isArray(v) ? v : typeof v === "string" ? [v] : []) {
261
+ const slug = typeof ref === "string" ? stripRef(ref) : "";
262
+ if (slug) out.push({ slug, via: { kind: "race", slug: race.slug }, read: (id) => oc[`race:feat:${id}`] });
263
+ }
264
+ }
265
+ }
266
+ return out;
267
+ }
207
268
  function featOfSite(site, feats) {
208
269
  var _a;
209
270
  return (_a = feats.find((f) => f.slug === site.slug)) != null ? _a : feats.find((f) => bareEntitySlug(f.slug) === bareEntitySlug(site.slug));
@@ -220,9 +281,12 @@ function nestedPickKeysToPrune(block, key, next) {
220
281
  export {
221
282
  isTargetChoice,
222
283
  memberChoices,
284
+ memberSpellTargets,
285
+ spellsReached,
223
286
  poolMemberSites,
224
287
  poolMemberSitesOf,
225
288
  featPickSites,
289
+ raceFeatSites,
226
290
  featOfSite,
227
291
  nestedPickKeysToPrune
228
292
  };
@@ -4,6 +4,9 @@ import {
4
4
  import {
5
5
  resourceLevelFor
6
6
  } from "./chunk-KSTV7J2U.js";
7
+ import {
8
+ addToRoll
9
+ } from "./chunk-WOHX7YTH.js";
7
10
  import {
8
11
  baseClassName
9
12
  } from "./chunk-VTTA2ALZ.js";
@@ -74,11 +77,36 @@ function spellRollBonus(spell) {
74
77
  const re = new RegExp(`(?<![\\w])${esc}\`?\\s*(?:plus|\\+)\\s*your\\s+spellcasting\\s+(?:ability\\s+)?modifier`, "i");
75
78
  return re.test(text) ? "{spell_mod}" : null;
76
79
  }
80
+ function foldsIntoRoll(rider, spell) {
81
+ var _a, _b;
82
+ const own = ((_b = (_a = spell.entity.damage) == null ? void 0 : _a.types) != null ? _b : []).map((t) => t.toLowerCase());
83
+ return !rider.condition && (!rider.damage_type || own.includes(rider.damage_type.toLowerCase()));
84
+ }
85
+ var reaching = (spell, derived) => {
86
+ var _a;
87
+ return ((_a = derived.spellDamageRiders) != null ? _a : []).filter((r) => {
88
+ var _a2;
89
+ return (_a2 = r.spells) == null ? void 0 : _a2.includes(spell.slug);
90
+ });
91
+ };
92
+ function addSpellDamageRiders(roll, spell, derived) {
93
+ return addToRoll(roll, reaching(spell, derived).filter((r) => foldsIntoRoll(r, spell)).map((r) => r.amount));
94
+ }
95
+ function spellDamageRiderLines(spell, derived) {
96
+ return reaching(spell, derived).filter((r) => !foldsIntoRoll(r, spell)).map((r) => ({
97
+ amount: r.amount,
98
+ ...r.damage_type ? { damage_type: r.damage_type } : {},
99
+ ...r.source ? { source: r.source } : {},
100
+ ...r.condition ? { condition: r.condition } : {}
101
+ }));
102
+ }
77
103
 
78
104
  export {
79
105
  ownerClassSlug,
80
106
  amountBindingsFor,
81
107
  spellcastingFor,
82
108
  spellAmountBindings,
83
- spellRollBonus
109
+ spellRollBonus,
110
+ addSpellDamageRiders,
111
+ spellDamageRiderLines
84
112
  };
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-CJYCVNW2.js";
9
9
  import {
10
10
  featureSchema
11
- } from "./chunk-YD2VAHA6.js";
11
+ } from "./chunk-B2QHP6UF.js";
12
12
  import {
13
13
  resourceSchema
14
14
  } from "./chunk-ZJXSXVFP.js";
@@ -18,7 +18,7 @@ import {
18
18
  } from "./chunk-IN4M4X7P.js";
19
19
  import {
20
20
  choiceSchema
21
- } from "./chunk-5MXFW4O3.js";
21
+ } from "./chunk-OOL4KMG2.js";
22
22
  import {
23
23
  additionalSpellsEntrySchema,
24
24
  imageField,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  subclassEntitySchema
3
- } from "./chunk-LF5AGJA4.js";
3
+ } from "./chunk-LNT64X7C.js";
4
4
  import {
5
5
  aliasFeaturesByLevelActionCost
6
6
  } from "./chunk-4U55VNC7.js";
@@ -8,10 +8,10 @@ import {
8
8
  } from "./chunk-ZJXSXVFP.js";
9
9
  import {
10
10
  choiceSchema
11
- } from "./chunk-5MXFW4O3.js";
11
+ } from "./chunk-OOL4KMG2.js";
12
12
  import {
13
13
  featureEffectSchema
14
- } from "./chunk-2JSQE3DD.js";
14
+ } from "./chunk-JXWNXIIZ.js";
15
15
  import {
16
16
  additionalSpellsEntrySchema,
17
17
  imageField,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  featureObjectSchema
3
- } from "./chunk-YD2VAHA6.js";
3
+ } from "./chunk-B2QHP6UF.js";
4
4
  import {
5
5
  imageField
6
6
  } from "./chunk-UA5KS2PL.js";
@@ -141,6 +141,12 @@ function resolveAmountText(text, b) {
141
141
  if (/\b(?:floor|ceil|max|min)\(/.test(display)) return text;
142
142
  return display;
143
143
  }
144
+ function addToRoll(roll, amounts) {
145
+ var _a;
146
+ if (!amounts.length) return roll;
147
+ const text = tidySigns([roll, ...amounts].join(" + "));
148
+ return (_a = foldDice(text)) != null ? _a : text;
149
+ }
144
150
  function diceRollOf(resolved) {
145
151
  var _a, _b;
146
152
  return (_b = (_a = diceRollPrefix(resolved)) == null ? void 0 : _a.notation) != null ? _b : null;
@@ -153,6 +159,7 @@ function diceRollPrefix(text) {
153
159
  export {
154
160
  normalizeAmountPhrases,
155
161
  resolveAmountText,
162
+ addToRoll,
156
163
  diceRollOf,
157
164
  diceRollPrefix
158
165
  };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-4U55VNC7.js";
4
4
  import {
5
5
  classEntitySchema
6
- } from "./chunk-UUBJCNQU.js";
6
+ } from "./chunk-UJCK5IW4.js";
7
7
 
8
8
  // src/class/class.parser.ts
9
9
  import { parseYaml } from "@archivist-gg/core";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseFeat
3
- } from "./chunk-7QN7XSAK.js";
3
+ } from "./chunk-3SFGWFVQ.js";
4
4
 
5
5
  // src/feat/feat.codec.ts
6
6
  import yaml from "js-yaml";
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  classCodec
3
- } from "../chunk-MBX25B6D.js";
4
- import "../chunk-YKL6SHRJ.js";
3
+ } from "../chunk-GVWEFECG.js";
4
+ import "../chunk-XTEFPUSN.js";
5
5
  import "../chunk-4U55VNC7.js";
6
- import "../chunk-UUBJCNQU.js";
6
+ import "../chunk-UJCK5IW4.js";
7
7
  import "../chunk-G6MHJDVT.js";
8
8
  import "../chunk-CJYCVNW2.js";
9
- import "../chunk-YD2VAHA6.js";
9
+ import "../chunk-B2QHP6UF.js";
10
10
  import "../chunk-WYGD2UGN.js";
11
11
  import "../chunk-ZJXSXVFP.js";
12
12
  import "../chunk-IN4M4X7P.js";
13
- import "../chunk-5MXFW4O3.js";
14
- import "../chunk-2JSQE3DD.js";
13
+ import "../chunk-OOL4KMG2.js";
14
+ import "../chunk-JXWNXIIZ.js";
15
15
  import "../chunk-UA5KS2PL.js";
16
16
  export {
17
17
  classCodec