@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
@@ -10,6 +10,23 @@ import {
10
10
  expandToolPool,
11
11
  toolCategoriesOf
12
12
  } from "./chunk-H5PTKYXL.js";
13
+ import {
14
+ assembleEffectFeatures,
15
+ collectProficiencyEffectGrants
16
+ } from "./chunk-TULQ4FFX.js";
17
+ import {
18
+ ALL_LANGUAGES,
19
+ ALL_SKILL_SLUGS,
20
+ ALL_TOOLS
21
+ } from "./chunk-KU3YLF6D.js";
22
+ import {
23
+ featOfSite,
24
+ featPickSites,
25
+ isTargetChoice,
26
+ memberChoices,
27
+ poolMemberSites,
28
+ poolMemberSitesOf
29
+ } from "./chunk-HTYMW5CH.js";
13
30
  import {
14
31
  resolveFeatureResources,
15
32
  resolveResourceIndex
@@ -21,25 +38,22 @@ import {
21
38
  resolveSpellcasting
22
39
  } from "./chunk-DPUJDOKV.js";
23
40
  import {
24
- assembleEffectFeatures,
25
- collectProficiencyEffectGrants
26
- } from "./chunk-4BZ47YJ4.js";
27
- import {
28
- ALL_LANGUAGES,
29
- ALL_SKILL_SLUGS,
30
- ALL_TOOLS
31
- } from "./chunk-KU3YLF6D.js";
41
+ convertibleLegacyAsi,
42
+ flattenAsiOrFeat,
43
+ resolveLegacyAsiFeat
44
+ } from "./chunk-LBEXLN6I.js";
32
45
  import {
33
46
  humanizeProficiency,
34
47
  proficiencyLabel,
35
48
  toProfSlug
36
49
  } from "./chunk-RLGQ5PZZ.js";
37
50
  import {
51
+ bindFeatureConsumers,
38
52
  buildInfusionContext,
39
53
  resolveAllPools,
40
54
  resolveOptionRows,
41
55
  strandedPicks
42
- } from "./chunk-T5GZBKIQ.js";
56
+ } from "./chunk-3VZZDKRA.js";
43
57
  import {
44
58
  countColumnsFor,
45
59
  readTableColumn
@@ -49,33 +63,28 @@ import {
49
63
  } from "./chunk-YHYTW5QA.js";
50
64
  import {
51
65
  collectAdditionalSpells
52
- } from "./chunk-DPM6V7YX.js";
53
- import {
54
- convertibleLegacyAsi,
55
- flattenAsiOrFeat,
56
- resolveLegacyAsiFeat
57
- } from "./chunk-LBEXLN6I.js";
66
+ } from "./chunk-VRV3NNTA.js";
58
67
  import {
59
68
  isItemEntity,
60
69
  resolveEntityForEntry
61
70
  } from "./chunk-LHZXYFBU.js";
62
- import {
63
- warnOnce
64
- } from "./chunk-LVQLWRCU.js";
65
- import {
66
- bareEntitySlug,
67
- wikilinkTailSlug
68
- } from "./chunk-ZCTUYGNV.js";
69
71
  import {
70
72
  FEATURE_TYPE_FOR_CATEGORY,
71
73
  featProgressionCategory
72
74
  } from "./chunk-YBLDE5CG.js";
75
+ import {
76
+ warnOnce
77
+ } from "./chunk-LVQLWRCU.js";
73
78
  import {
74
79
  isValidMaxFormula
75
80
  } from "./chunk-EVMUJYUD.js";
81
+ import {
82
+ bareEntitySlug,
83
+ wikilinkTailSlug
84
+ } from "./chunk-ZCTUYGNV.js";
76
85
  import {
77
86
  mirrorSpellShapes
78
- } from "./chunk-XY6X2APM.js";
87
+ } from "./chunk-7Q2VPKCD.js";
79
88
  import {
80
89
  ABILITY_KEYS
81
90
  } from "./chunk-WELIKLZF.js";
@@ -205,6 +214,7 @@ function matchesFilter(e, where, ownerBare) {
205
214
  if (where.list && !((_a = d.classes) != null ? _a : []).includes(where.list)) return false;
206
215
  if (where.level !== void 0 && ((_b = d.level) != null ? _b : 0) !== where.level) return false;
207
216
  if (where.edition && d.edition !== where.edition) return false;
217
+ if (where.ritual && d.ritual !== true) return false;
208
218
  if (where.weapon_category) {
209
219
  const cat = ((_c = d.category) != null ? _c : "").toLowerCase();
210
220
  if (!cat.startsWith(`${where.weapon_category}-`) && cat !== where.weapon_category) return false;
@@ -527,8 +537,13 @@ function visitProficiencyChoices(resolved, visit) {
527
537
  const originRead = (ns) => (id) => oc[`${ns}:${id}`];
528
538
  const stripRef = (ref) => ref.replace(/^\[\[/, "").replace(/\]\]$/, "");
529
539
  const featBySlug = new Map(((_b = resolved.feats) != null ? _b : []).map((f) => [f.slug, f]));
530
- resolved.classes.forEach((c, i) => {
540
+ const memberSites = poolMemberSitesOf(resolved);
541
+ const featSites = featPickSites(resolved.classes, memberSites, (ci) => {
531
542
  var _a2, _b2, _c2, _d;
543
+ return (_d = (_c2 = (_b2 = (_a2 = resolved.classes) == null ? void 0 : _a2[ci]) == null ? void 0 : _b2.entity) == null ? void 0 : _c2.slug) != null ? _d : null;
544
+ });
545
+ resolved.classes.forEach((c, i) => {
546
+ var _a2, _b2, _c2, _d, _e;
532
547
  if (!c.entity) return;
533
548
  const entity = c.entity;
534
549
  const readAt = (lvl) => (id) => {
@@ -553,14 +568,13 @@ function visitProficiencyChoices(resolved, visit) {
553
568
  for (const copy of (_c2 = rf.foldedFrom) != null ? _c2 : []) walk(copy.choices, readAt(copy.level));
554
569
  walk(rf.feature.choices, readAt(rf.source.level));
555
570
  }
556
- for (const atLevel of Object.values((_d = c.choices) != null ? _d : {})) {
557
- const block = atLevel;
558
- const featRef = block == null ? void 0 : block.feat;
559
- if (!block || typeof featRef !== "string") continue;
560
- const feat = featBySlug.get(stripRef(featRef));
571
+ for (const site of featSites) {
572
+ if (site.classIndex !== i) continue;
573
+ const feat = (_e = featBySlug.get(stripRef(site.slug))) != null ? _e : featOfSite(site, (_d = resolved.feats) != null ? _d : []);
561
574
  if (!feat) continue;
562
- walk(feat.choices, (id) => block[`feat:${id}`]);
575
+ walk(feat.choices, site.read);
563
576
  }
577
+ for (const site of memberSites) if (site.classIndex === i) walk(site.choices, site.read);
564
578
  });
565
579
  if (resolved.race) {
566
580
  walk(resolved.race.choices, originRead("race"));
@@ -961,6 +975,28 @@ function buildDecisionLedger(resolved, ctx) {
961
975
  });
962
976
  }
963
977
  for (const o of item.options) if (strandedSlugs.has(o.value)) o.stranded = true;
978
+ const memberChildren = [];
979
+ for (const entry of pool.selected) {
980
+ for (const ch of memberChoices(entry.entity)) {
981
+ const child = buildItem(
982
+ ch,
983
+ { kind: "class", slug: entity.slug, level: pool.anchorLevel },
984
+ pool.anchorLevel,
985
+ entry.entity.name,
986
+ readAt(pool.anchorLevel),
987
+ ctx,
988
+ ownerBare,
989
+ effective,
990
+ { keyPrefix: `${pool.id}:${entry.key}:` }
991
+ );
992
+ child.parentLabel = entry.entity.name;
993
+ memberChildren.push(child);
994
+ }
995
+ }
996
+ if (memberChildren.length) {
997
+ item.children = memberChildren;
998
+ if (item.status === "resolved" && memberChildren.some((c2) => c2.status !== "resolved")) item.status = "partial";
999
+ }
964
1000
  push(pool.anchorLevel, item);
965
1001
  }
966
1002
  for (const entry of featProgressions) {
@@ -1168,7 +1204,7 @@ var PCResolver = class {
1168
1204
  return this.entities;
1169
1205
  }
1170
1206
  resolve(character) {
1171
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
1207
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1172
1208
  const warnings = [];
1173
1209
  const lookup = (rawRef, type) => {
1174
1210
  const slug = stripSlug(rawRef);
@@ -1189,14 +1225,19 @@ var PCResolver = class {
1189
1225
  subclass: lookup(c.subclass, "subclass"),
1190
1226
  choices: c.choices
1191
1227
  }));
1228
+ const memberSites = poolMemberSites(classes, this.entities);
1229
+ const classSlugOf = (i) => {
1230
+ var _a2, _b2, _c2, _d2, _e2;
1231
+ return (_e2 = (_b2 = (_a2 = classes[i]) == null ? void 0 : _a2.entity) == null ? void 0 : _b2.slug) != null ? _e2 : stripSlug((_d2 = (_c2 = character.class[i]) == null ? void 0 : _c2.name) != null ? _d2 : null);
1232
+ };
1233
+ const featSites = featPickSites(character.class, memberSites, classSlugOf);
1192
1234
  const feats = [];
1193
1235
  const featVia = /* @__PURE__ */ new Map();
1194
- for (const pick of collectFeatPicks(character)) {
1195
- const f = lookup(`[[${pick.slug}]]`, "feat");
1236
+ for (const site of featSites) {
1237
+ const f = lookup(`[[${site.slug}]]`, "feat");
1196
1238
  if (!f) continue;
1197
- feats.push(f);
1198
- const classSlug = (_e = (_b = (_a = classes[pick.classIndex]) == null ? void 0 : _a.entity) == null ? void 0 : _b.slug) != null ? _e : stripSlug((_d = (_c = character.class[pick.classIndex]) == null ? void 0 : _c.name) != null ? _d : null);
1199
- if (classSlug && pick.level !== null && !featVia.has(f.slug)) featVia.set(f.slug, { kind: "class", slug: classSlug, level: pick.level });
1239
+ if (!feats.some((x) => x.slug === f.slug)) feats.push(f);
1240
+ if (site.via && !featVia.has(f.slug)) featVia.set(f.slug, site.via);
1200
1241
  }
1201
1242
  const originFeat = (background == null ? void 0 : background.origin_feat) ? resolveOriginFeat(this.entities, background.origin_feat, background.slug) : null;
1202
1243
  const originFeatSlug = originFeat == null ? void 0 : originFeat.feat.slug;
@@ -1208,7 +1249,7 @@ var PCResolver = class {
1208
1249
  }
1209
1250
  const totalLevel = classes.reduce((sum, c) => sum + c.level, 0);
1210
1251
  const features = collectResolvedFeatures(race, classes, background, feats, featVia);
1211
- const extraFeatures = collectChosenGrantedFeatures(character, classes, this.entities, race, background);
1252
+ const extraFeatures = collectChosenGrantedFeatures(character, classes, this.entities, race, background, memberSites);
1212
1253
  features.push(...extraFeatures);
1213
1254
  features.push(...collectAdditionalFeatures(character, this.entities, warnings));
1214
1255
  const casters = [];
@@ -1218,7 +1259,7 @@ var PCResolver = class {
1218
1259
  if (profile && classSlug != null) casters.push({ classSlug, spellList: profile.spellList });
1219
1260
  });
1220
1261
  const spells = [];
1221
- for (const raw of (_f = character.spells.known) != null ? _f : []) {
1262
+ for (const raw of (_a = character.spells.known) != null ? _a : []) {
1222
1263
  const n = normalizeKnownSpell(raw);
1223
1264
  const reg = this.entities.getByTypeAndSlug("spell", n.slug);
1224
1265
  if (!reg) {
@@ -1226,34 +1267,45 @@ var PCResolver = class {
1226
1267
  continue;
1227
1268
  }
1228
1269
  const entity = mirrorSpellShapes(reg.data);
1229
- const isCantrip = ((_g = entity.level) != null ? _g : 0) === 0;
1230
- const classSlug = (_h = n.classSlug) != null ? _h : attributeUnclassedSpell(entity.classes, casters, this.entities);
1231
- const prep = isCantrip || n.alwaysPrepared ? true : (_i = n.preparedFlag) != null ? _i : false;
1270
+ const isCantrip = ((_b = entity.level) != null ? _b : 0) === 0;
1271
+ const classSlug = (_c = n.classSlug) != null ? _c : attributeUnclassedSpell(entity.classes, casters, this.entities);
1272
+ const prep = isCantrip || n.alwaysPrepared ? true : (_d = n.preparedFlag) != null ? _d : false;
1232
1273
  spells.push({ entity, slug: n.slug, classSlug, source: n.source, prepared: prep, alwaysPrepared: n.alwaysPrepared, persisted: true });
1233
1274
  }
1234
- const oc = (_j = character.origin_choices) != null ? _j : {};
1275
+ const oc = (_e = character.origin_choices) != null ? _e : {};
1235
1276
  if (originFeat) {
1236
1277
  const read = (childId) => oc[`background:feat:${childId}`];
1237
1278
  spells.push(...collectFeatGrantedSpells(originFeat.feat, read, this.entities, warnings));
1238
1279
  }
1239
- for (const entry of character.class) {
1240
- for (const atLevel of Object.values((_k = entry.choices) != null ? _k : {})) {
1241
- const featRef = atLevel.feat;
1242
- if (typeof featRef !== "string") continue;
1243
- const slug = stripSlug(featRef);
1244
- if (!slug) continue;
1245
- const reg = this.entities.getByTypeAndSlug("feat", slug);
1246
- if (!reg) continue;
1247
- const feat = reg.data;
1248
- const read = (childId) => atLevel[`feat:${childId}`];
1249
- spells.push(...collectFeatGrantedSpells(feat, read, this.entities, warnings));
1280
+ for (const site of featSites) {
1281
+ const reg = this.entities.getByTypeAndSlug("feat", site.slug);
1282
+ if (!reg) continue;
1283
+ const feat = reg.data;
1284
+ spells.push(...collectFeatGrantedSpells(feat, site.read, this.entities, warnings));
1285
+ }
1286
+ for (const site of memberSites) {
1287
+ const classSlug = stripSlug((_g = (_f = character.class[site.classIndex]) == null ? void 0 : _f.name) != null ? _g : null);
1288
+ for (const ch of site.choices) {
1289
+ if (ch.kind !== "select-entity" || ch.entity_type !== "spell" || isTargetChoice(ch)) continue;
1290
+ const v = site.read(ch.id);
1291
+ for (const ref of Array.isArray(v) ? v : typeof v === "string" ? [v] : []) {
1292
+ const slug = typeof ref === "string" ? stripSlug(ref) : null;
1293
+ if (!slug) continue;
1294
+ const reg = this.entities.getByTypeAndSlug("spell", slug);
1295
+ if (!reg) {
1296
+ warnings.push(`Spell [[${slug}]] not found in compendium.`);
1297
+ continue;
1298
+ }
1299
+ const entity = mirrorSpellShapes(reg.data);
1300
+ spells.push({ entity, slug, classSlug, source: "class", prepared: true, alwaysPrepared: true });
1301
+ }
1250
1302
  }
1251
1303
  }
1252
- const ownSpellcastingAbility = (_l = classes.map((c) => {
1304
+ const ownSpellcastingAbility = (_h = classes.map((c) => {
1253
1305
  if (!c.entity) return null;
1254
1306
  const p = resolveSpellcasting(c);
1255
1307
  return p ? effectiveSpellcastingAbility(c.entity.slug, p.ability, character.overrides) : null;
1256
- }).find((a) => a != null)) != null ? _l : null;
1308
+ }).find((a) => a != null)) != null ? _h : null;
1257
1309
  spells.push(...collectItemGrantedSpells(character, ownSpellcastingAbility, this.entities, warnings));
1258
1310
  spells.push(...collectAdditionalSpells({
1259
1311
  race,
@@ -1304,6 +1356,7 @@ var PCResolver = class {
1304
1356
  };
1305
1357
  resolvedCharacter.pools = resolveAllPools(resolvedCharacter, this.entities, featureResources);
1306
1358
  resolvedCharacter.resources = resolveResourceIndex(resolvedCharacter);
1359
+ resolvedCharacter.features = bindFeatureConsumers(resolvedCharacter.features, resolvedCharacter.resources, classes);
1307
1360
  const infusions = buildInfusionContext(character, resolvedCharacter, this.entities);
1308
1361
  if (infusions) resolvedCharacter.infusions = infusions;
1309
1362
  return { character: resolvedCharacter, warnings };
@@ -1324,16 +1377,9 @@ function collectChosenWeaponMasteries(character) {
1324
1377
  }
1325
1378
  function collectFeatPicks(character) {
1326
1379
  const picks = /* @__PURE__ */ new Map();
1327
- character.class.forEach((c, classIndex) => {
1328
- for (const [lvl, choiceBlock] of Object.entries(c.choices)) {
1329
- const feat = choiceBlock == null ? void 0 : choiceBlock.feat;
1330
- if (typeof feat === "string") {
1331
- const s = stripSlug(feat);
1332
- const level = /^\d+$/.test(lvl) ? Number(lvl) : null;
1333
- if (s && !picks.has(s)) picks.set(s, { slug: s, classIndex, level });
1334
- }
1335
- }
1336
- });
1380
+ for (const site of featPickSites(character.class, [], () => null)) {
1381
+ if (!picks.has(site.slug)) picks.set(site.slug, { slug: site.slug, classIndex: site.classIndex, level: site.level });
1382
+ }
1337
1383
  return [...picks.values()];
1338
1384
  }
1339
1385
  function collectFeatGrantedSpells(feat, read, entities, warnings) {
@@ -1410,11 +1456,21 @@ function collectSpellPickSlugs(choices, read, out) {
1410
1456
  }
1411
1457
  }
1412
1458
  }
1413
- function collectChosenGrantedFeatures(character, classes, registry, race = null, background = null) {
1414
- var _a, _b;
1459
+ function collectChosenGrantedFeatures(character, classes, registry, race = null, background = null, memberSites = []) {
1460
+ var _a, _b, _c, _d;
1415
1461
  const out = [];
1462
+ for (const site of memberSites) {
1463
+ const classSlug = (_b = (_a = classes[site.classIndex]) == null ? void 0 : _a.entity) == null ? void 0 : _b.slug;
1464
+ if (!classSlug) continue;
1465
+ const at = {};
1466
+ const grants = site.choices.filter((ch) => !isTargetChoice(ch));
1467
+ for (const ch of grants) at[ch.id] = site.read(ch.id);
1468
+ walkChoiceGrants(grants, at, (granted, suppress) => {
1469
+ out.push({ feature: granted, source: { kind: "class", slug: classSlug, level: site.level }, ...suppress ? { renderSuppressed: true } : {} });
1470
+ }, registry);
1471
+ }
1416
1472
  classes.forEach((c, i) => {
1417
- var _a2, _b2, _c, _d;
1473
+ var _a2, _b2, _c2, _d2;
1418
1474
  if (!c.entity) return;
1419
1475
  const entity = c.entity;
1420
1476
  const entry = character.class[i];
@@ -1423,7 +1479,7 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
1423
1479
  const lvl = Number(lvlStr);
1424
1480
  if (!Number.isFinite(lvl) || lvl > entry.level) continue;
1425
1481
  const features = (_b2 = ((_a2 = entity.features_by_level) != null ? _a2 : {})[lvl]) != null ? _b2 : [];
1426
- const subFeatures = c.subclass ? (_d = ((_c = c.subclass.features_by_level) != null ? _c : {})[lvl]) != null ? _d : [] : [];
1482
+ const subFeatures = c.subclass ? (_d2 = ((_c2 = c.subclass.features_by_level) != null ? _c2 : {})[lvl]) != null ? _d2 : [] : [];
1427
1483
  for (const feature of [...features, ...subFeatures]) {
1428
1484
  walkChoiceGrants(feature.choices, atLevel, (granted, suppress) => {
1429
1485
  out.push({
@@ -1435,7 +1491,7 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
1435
1491
  }
1436
1492
  }
1437
1493
  });
1438
- const oc = (_a = character.origin_choices) != null ? _a : {};
1494
+ const oc = (_c = character.origin_choices) != null ? _c : {};
1439
1495
  const originAt = (ns) => {
1440
1496
  const at = {};
1441
1497
  for (const [key, val] of Object.entries(oc)) {
@@ -1453,7 +1509,7 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
1453
1509
  });
1454
1510
  };
1455
1511
  walkChoiceGrants(race.choices, atRace, emitRace, registry);
1456
- for (const trait of (_b = race.traits) != null ? _b : []) {
1512
+ for (const trait of (_d = race.traits) != null ? _d : []) {
1457
1513
  walkChoiceGrants(trait.choices, atRace, emitRace, registry);
1458
1514
  }
1459
1515
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseSpell
3
- } from "./chunk-XY6X2APM.js";
3
+ } from "./chunk-7Q2VPKCD.js";
4
4
 
5
5
  // src/spell/spell.codec.ts
6
6
  import yaml from "js-yaml";
@@ -9,7 +9,8 @@ var spellCodec = {
9
9
  return parseSpell(doc.body);
10
10
  },
11
11
  serialize(spell) {
12
- return yaml.dump(spell, { lineWidth: -1, sortKeys: false });
12
+ const { material: _material, ...doc } = spell;
13
+ return yaml.dump(doc, { lineWidth: -1, sortKeys: false });
13
14
  }
14
15
  };
15
16
 
@@ -4,10 +4,10 @@ import {
4
4
  } from "./chunk-ZJXSXVFP.js";
5
5
  import {
6
6
  choiceSchema
7
- } from "./chunk-BRGLDWUP.js";
7
+ } from "./chunk-5MXFW4O3.js";
8
8
  import {
9
9
  featureEffectSchema
10
- } from "./chunk-ZMJQUZQH.js";
10
+ } from "./chunk-2JSQE3DD.js";
11
11
  import {
12
12
  additionalSpellsEntrySchema,
13
13
  imageField,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  optionalFeatureEntitySchema
3
- } from "./chunk-JNNNBEUH.js";
3
+ } from "./chunk-DOXSFYLH.js";
4
4
 
5
5
  // src/optional-feature/optional-feature.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -0,0 +1,84 @@
1
+ import {
2
+ spellSource
3
+ } from "./chunk-KR6JF3BF.js";
4
+ import {
5
+ resourceLevelFor
6
+ } from "./chunk-KSTV7J2U.js";
7
+ import {
8
+ baseClassName
9
+ } from "./chunk-VTTA2ALZ.js";
10
+
11
+ // src/pc/pc.amount-bindings.ts
12
+ function ownerClassSlug(source, resolved) {
13
+ var _a, _b, _c;
14
+ if (!source) return null;
15
+ if (source.kind === "class") return source.slug;
16
+ if (source.kind === "subclass") return (_c = (_b = (_a = resolved.classes.find((c) => {
17
+ var _a2;
18
+ return ((_a2 = c.subclass) == null ? void 0 : _a2.slug) === source.slug;
19
+ })) == null ? void 0 : _a.entity) == null ? void 0 : _b.slug) != null ? _c : null;
20
+ if (source.kind === "feat" && source.via) {
21
+ if (source.via.kind === "class") return source.via.slug;
22
+ if (source.via.kind === "pool") return source.via.classSlug;
23
+ }
24
+ return null;
25
+ }
26
+ function casterRow(classSlug, derived) {
27
+ var _a, _b;
28
+ const rows = (_a = derived.spellcastingClasses) != null ? _a : [];
29
+ const own = classSlug ? rows.find((c) => baseClassName(c.classSlug) === baseClassName(classSlug)) : void 0;
30
+ return (_b = own != null ? own : rows[0]) != null ? _b : null;
31
+ }
32
+ function amountBindingsFor(owner, resolved, derived) {
33
+ const classSlug = ownerClassSlug(owner, resolved);
34
+ const classSource = classSlug ? { kind: "class", slug: classSlug, level: 0 } : owner;
35
+ const classLevel = classSource ? resourceLevelFor(classSource, resolved) : resolved.totalLevel;
36
+ const row = casterRow(classSlug, derived);
37
+ return {
38
+ level: resolved.totalLevel,
39
+ class_level: classLevel,
40
+ prof: derived.proficiencyBonus,
41
+ str_mod: derived.mods.str,
42
+ dex_mod: derived.mods.dex,
43
+ con_mod: derived.mods.con,
44
+ int_mod: derived.mods.int,
45
+ wis_mod: derived.mods.wis,
46
+ cha_mod: derived.mods.cha,
47
+ columns: {},
48
+ ...row ? { spell_mod: derived.mods[row.ability], spell_dc: row.saveDC } : {}
49
+ };
50
+ }
51
+ function spellcastingFor(spell, derived) {
52
+ var _a, _b;
53
+ if ((spellSource(spell).ownAbility || spell.classSlug == null) && spell.ability) {
54
+ const own = (_a = derived.abilitySpellcasting) == null ? void 0 : _a[spell.ability];
55
+ if (own) return { ability: spell.ability, saveDC: own.saveDC, attackBonus: own.attackBonus };
56
+ }
57
+ const row = casterRow((_b = spell.classSlug) != null ? _b : null, derived);
58
+ return row ? { ability: row.ability, saveDC: row.saveDC, attackBonus: row.attackBonus } : null;
59
+ }
60
+ function spellAmountBindings(spell, resolved, derived) {
61
+ const cast = spellcastingFor(spell, derived);
62
+ const base = amountBindingsFor(spell.classSlug ? { kind: "class", slug: spell.classSlug, level: 0 } : void 0, resolved, derived);
63
+ const out = { ...base };
64
+ delete out.spell_mod;
65
+ delete out.spell_dc;
66
+ return cast ? { ...out, spell_mod: derived.mods[cast.ability], spell_dc: cast.saveDC } : out;
67
+ }
68
+ function spellRollBonus(spell) {
69
+ var _a;
70
+ const roll = (_a = spell.damage_roll) == null ? void 0 : _a.trim();
71
+ const text = spell.description;
72
+ if (!roll || !text) return null;
73
+ const esc = roll.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
74
+ const re = new RegExp(`(?<![\\w])${esc}\`?\\s*(?:plus|\\+)\\s*your\\s+spellcasting\\s+(?:ability\\s+)?modifier`, "i");
75
+ return re.test(text) ? "{spell_mod}" : null;
76
+ }
77
+
78
+ export {
79
+ ownerClassSlug,
80
+ amountBindingsFor,
81
+ spellcastingFor,
82
+ spellAmountBindings,
83
+ spellRollBonus
84
+ };
@@ -0,0 +1,158 @@
1
+ import {
2
+ evaluateMaxFormula,
3
+ isValidMaxFormula
4
+ } from "./chunk-EVMUJYUD.js";
5
+
6
+ // src/dnd/amount-text.ts
7
+ var ABILITY_WORD = {
8
+ strength: "str",
9
+ dexterity: "dex",
10
+ constitution: "con",
11
+ intelligence: "int",
12
+ wisdom: "wis",
13
+ charisma: "cha",
14
+ str: "str",
15
+ dex: "dex",
16
+ con: "con",
17
+ int: "int",
18
+ wis: "wis",
19
+ cha: "cha"
20
+ };
21
+ var ABILITY_RE = "(Strength|Dexterity|Constitution|Intelligence|Wisdom|Charisma)";
22
+ var CLASS_RE = "(Artificer|Barbarian|Bard|Cleric|Druid|Fighter|Monk|Paladin|Ranger|Rogue|Sorcerer|Warlock|Wizard)";
23
+ var TOKEN_ALIAS = {
24
+ prof_bonus: "prof",
25
+ proficiency_bonus: "prof",
26
+ spellcasting_mod: "spell_mod",
27
+ spell_save_dc: "spell_dc"
28
+ };
29
+ var IDENT_LABEL = {
30
+ level: "level",
31
+ class_level: "class level",
32
+ prof: "PB",
33
+ str_mod: "STR mod",
34
+ dex_mod: "DEX mod",
35
+ con_mod: "CON mod",
36
+ int_mod: "INT mod",
37
+ wis_mod: "WIS mod",
38
+ cha_mod: "CHA mod",
39
+ spell_mod: "spellcasting mod",
40
+ spell_dc: "spell save DC"
41
+ };
42
+ var abilityToken = (word) => `{${ABILITY_WORD[word.toLowerCase()]}_mod}`;
43
+ var PHRASES = [
44
+ // Save DCs: "your spell save DC", "your sorcerer spell save DC", "the spell save DC from this class's Spellcasting feature".
45
+ [/\b(?:your|the)\s+(?:[a-z]+\s+)?spell save DC(?:\s+from this class's Spellcasting feature)?/gi, "{spell_dc}"],
46
+ // "8 plus your Wisdom modifier and Proficiency Bonus" (2024) / the 2014 order is caught by the rows below.
47
+ [
48
+ new RegExp(`\\b8 plus your ${ABILITY_RE} modifier and (?:your )?Proficiency Bonus`, "gi"),
49
+ (_m, a) => `8 + ${abilityToken(a)} + {prof}`
50
+ ],
51
+ // The spellcasting modifier, in every spelling the notes carry.
52
+ [/\b(?:your\s+)?spellcasting ability modifier\b/gi, "{spell_mod}"],
53
+ [/\b(?:your\s+)?spellcasting modifier\b/gi, "{spell_mod}"],
54
+ [/\b(?:your\s+)?spell(?:casting)? mod\b/gi, "{spell_mod}"],
55
+ // An ability modifier: "your Wisdom modifier", "Intelligence modifier", "WIS mod".
56
+ [new RegExp(`\\b(?:your\\s+)?${ABILITY_RE} modifier\\b`, "gi"), (_m, a) => abilityToken(a)],
57
+ [/\b(STR|DEX|CON|INT|WIS|CHA) mod(?:ifier)?\b/g, (_m, a) => abilityToken(a)],
58
+ // Class level: "half your Warlock level", "your fighter level"; the character's level.
59
+ [new RegExp(`\\bhalf your ${CLASS_RE} level(?:\\s*\\(round(?:ed)? up\\))`, "gi"), "ceil({class_level} / 2)"],
60
+ [new RegExp(`\\bhalf your ${CLASS_RE} level(?:\\s*\\(round(?:ed)? down\\))?`, "gi"), "floor({class_level} / 2)"],
61
+ [/\bhalf your level in this class(?:\s*\(round(?:ed)? up\))/gi, "ceil({class_level} / 2)"],
62
+ [/\bhalf your level in this class(?:\s*\(round(?:ed)? down\))?/gi, "floor({class_level} / 2)"],
63
+ [new RegExp(`\\byour ${CLASS_RE} level\\b`, "gi"), "{class_level}"],
64
+ [/\byour level in this class\b/gi, "{class_level}"],
65
+ [/\byour (?:character )?level\b/gi, "{level}"],
66
+ // Proficiency bonus.
67
+ [/\b(?:your\s+)?proficiency bonus\b/gi, "{prof}"],
68
+ [/\bPB\b/g, "{prof}"]
69
+ ];
70
+ function normalizeAmountPhrases(text) {
71
+ let out = text;
72
+ for (const [re, to] of PHRASES) out = out.replace(re, to);
73
+ if (out === text) return text;
74
+ out = out.replace(/\s+plus\s+/gi, " + ").replace(/\s+minus\s+/gi, " - ").replace(/\btwice (\{[a-z_]+\})/gi, "2 * $1");
75
+ return out;
76
+ }
77
+ var TRAILING_MIN = /\s*\(minimum(?: bonus)? of \+?(\d+)[^)]*\)\s*$/i;
78
+ var DICE = /^\d*d\d+$/i;
79
+ function tidySigns(s) {
80
+ return s.replace(/\+\s*-\s*/g, "- ").replace(/-\s*-\s*/g, "+ ");
81
+ }
82
+ function foldDice(expr) {
83
+ const parts = expr.replace(/\s+/g, "").match(/[+-]?[^+-]+/g);
84
+ if (!parts) return null;
85
+ const dice = [];
86
+ let sum = 0;
87
+ for (const raw of parts) {
88
+ const sign = raw.startsWith("-") ? -1 : 1;
89
+ const body = raw.replace(/^[+-]/, "");
90
+ if (DICE.test(body)) {
91
+ if (sign < 0) return null;
92
+ dice.push(body.toLowerCase());
93
+ continue;
94
+ }
95
+ if (!isValidMaxFormula(body)) return null;
96
+ sum += sign * evaluateMaxFormula(body, ZERO_BINDINGS);
97
+ }
98
+ if (dice.length === 0) return null;
99
+ const n = Math.trunc(sum);
100
+ return `${dice.join("+")}${n > 0 ? `+${n}` : n < 0 ? `${n}` : ""}`;
101
+ }
102
+ var ZERO_BINDINGS = {
103
+ level: 0,
104
+ class_level: 0,
105
+ prof: 0,
106
+ str_mod: 0,
107
+ dex_mod: 0,
108
+ con_mod: 0,
109
+ int_mod: 0,
110
+ wis_mod: 0,
111
+ cha_mod: 0,
112
+ columns: {}
113
+ };
114
+ function resolveAmountText(text, b) {
115
+ const normalized = normalizeAmountPhrases(text);
116
+ if (!normalized.includes("{")) return text;
117
+ const values = b;
118
+ let unbound = false;
119
+ const substitute = (numeric2) => normalized.replace(/\{([a-z_]+)\}/g, (_m, raw) => {
120
+ var _a, _b;
121
+ const id = (_a = TOKEN_ALIAS[raw]) != null ? _a : raw;
122
+ const v = values[id];
123
+ if (typeof v !== "number" || !Number.isFinite(v)) {
124
+ unbound = true;
125
+ return (_b = IDENT_LABEL[id]) != null ? _b : raw;
126
+ }
127
+ return numeric2 && v < 0 ? `(0 - ${-v})` : String(v);
128
+ });
129
+ const display = tidySigns(substitute(false));
130
+ if (unbound) return text.includes("{") ? display : text;
131
+ const min = TRAILING_MIN.exec(display);
132
+ const minText = min ? display.slice(min.index) : "";
133
+ const numeric = substitute(true);
134
+ const numericCore = numeric.replace(TRAILING_MIN, "");
135
+ if (isValidMaxFormula(numericCore)) {
136
+ const v = evaluateMaxFormula(numericCore, ZERO_BINDINGS);
137
+ return String(Math.trunc(min ? Math.max(Number(min[1]), v) : v));
138
+ }
139
+ const roll = foldDice(min ? display.slice(0, min.index) : display);
140
+ if (roll) return `${roll}${minText}`;
141
+ if (/\b(?:floor|ceil|max|min)\(/.test(display)) return text;
142
+ return display;
143
+ }
144
+ function diceRollOf(resolved) {
145
+ var _a, _b;
146
+ return (_b = (_a = diceRollPrefix(resolved)) == null ? void 0 : _a.notation) != null ? _b : null;
147
+ }
148
+ function diceRollPrefix(text) {
149
+ const m = /^(\d*d\d+(?:\s*[+-]\s*\d+)*)(?=\s|$)/i.exec(text);
150
+ return m ? { raw: m[1], notation: m[1].replace(/\s+/g, "") } : null;
151
+ }
152
+
153
+ export {
154
+ normalizeAmountPhrases,
155
+ resolveAmountText,
156
+ diceRollOf,
157
+ diceRollPrefix
158
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseSubclass
3
- } from "./chunk-5KGV6TF7.js";
3
+ } from "./chunk-OFCSP6MT.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
  parseFeat
3
- } from "./chunk-4SLOJXJ4.js";
3
+ } from "./chunk-7QN7XSAK.js";
4
4
 
5
5
  // src/feat/feat.codec.ts
6
6
  import yaml from "js-yaml";