@archivist-gg/dnd5e 0.3.13 → 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 (122) 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-3LWMFYKS.js +57 -0
  5. package/dist/{chunk-4SLOJXJ4.js → chunk-3SFGWFVQ.js} +1 -1
  6. package/dist/{chunk-Q6UOHCCT.js → chunk-3SIUAFDQ.js} +1 -1
  7. package/dist/chunk-4J6DS3EJ.js +30 -0
  8. package/dist/{chunk-RLGQ5PZZ.js → chunk-5JXFJ5AU.js} +11 -1
  9. package/dist/{chunk-4BZ47YJ4.js → chunk-64VNLMSG.js} +59 -16
  10. package/dist/{chunk-XY6X2APM.js → chunk-7Q2VPKCD.js} +17 -0
  11. package/dist/{chunk-Q5XTEYHD.js → chunk-7XJHI5H5.js} +16 -9
  12. package/dist/{chunk-FPGXFD2M.js → chunk-B2QHP6UF.js} +2 -2
  13. package/dist/{chunk-HJQNK3J5.js → chunk-B3DP42OM.js} +1 -4
  14. package/dist/{chunk-MC5YJ4PB.js → chunk-BJGOSM76.js} +1 -1
  15. package/dist/{chunk-VDPRBUU5.js → chunk-BJWSOQUC.js} +1 -1
  16. package/dist/{chunk-QEO7LYTG.js → chunk-EJD47JHL.js} +181 -93
  17. package/dist/{chunk-X7VZY4ON.js → chunk-FJYBLXV7.js} +2 -2
  18. package/dist/{chunk-QSBJ6NEM.js → chunk-FMUD3LDV.js} +1 -1
  19. package/dist/{chunk-LVX6TQ3R.js → chunk-FMYWORTP.js} +2 -2
  20. package/dist/{chunk-PX25R3DK.js → chunk-FVXLP7P3.js} +1 -1
  21. package/dist/{chunk-XJRNEQPD.js → chunk-GVWEFECG.js} +1 -1
  22. package/dist/{chunk-R2V5LN3I.js → chunk-HY2SRSVM.js} +1 -1
  23. package/dist/{chunk-ZMJQUZQH.js → chunk-JXWNXIIZ.js} +24 -0
  24. package/dist/chunk-KR6JF3BF.js +14 -0
  25. package/dist/{chunk-DPM6V7YX.js → chunk-LICYHPI3.js} +82 -6
  26. package/dist/{chunk-J53ERF6K.js → chunk-LNT64X7C.js} +1 -1
  27. package/dist/{chunk-RQNJXADQ.js → chunk-NKZMQV2C.js} +1 -1
  28. package/dist/{chunk-UE6LKRXQ.js → chunk-NTU2DXNG.js} +24 -1
  29. package/dist/{chunk-T5GZBKIQ.js → chunk-NXUY5XQN.js} +168 -33
  30. package/dist/{chunk-M2OWVAZG.js → chunk-NZO7M5GV.js} +1 -1
  31. package/dist/{chunk-BRGLDWUP.js → chunk-OOL4KMG2.js} +7 -3
  32. package/dist/{chunk-MWCCGPG3.js → chunk-OOSRUJBI.js} +3 -2
  33. package/dist/{chunk-H5PTKYXL.js → chunk-QA3YCOML.js} +1 -1
  34. package/dist/{chunk-MUIU7RZ3.js → chunk-QFWQKS3M.js} +1 -1
  35. package/dist/chunk-RD2MID3M.js +292 -0
  36. package/dist/chunk-TRBGC4ZG.js +112 -0
  37. package/dist/{chunk-PLOSP4XQ.js → chunk-UJCK5IW4.js} +11 -2
  38. package/dist/{chunk-5KGV6TF7.js → chunk-UNYR5GLI.js} +1 -1
  39. package/dist/{chunk-JNNNBEUH.js → chunk-VN5DLURA.js} +7 -2
  40. package/dist/{chunk-EL6QPF6I.js → chunk-VP5TIXDK.js} +1 -1
  41. package/dist/chunk-WOHX7YTH.js +165 -0
  42. package/dist/{chunk-G7ED5H2G.js → chunk-XTEFPUSN.js} +1 -1
  43. package/dist/{chunk-6ZY3U6BQ.js → chunk-ZOWKWMD2.js} +1 -1
  44. package/dist/class/class.codec.js +6 -6
  45. package/dist/class/class.parser.js +5 -5
  46. package/dist/class/class.schema.d.ts +8 -0
  47. package/dist/class/class.schema.js +4 -4
  48. package/dist/class/class.standard-array.d.ts +16 -0
  49. package/dist/class/class.standard-array.js +20 -0
  50. package/dist/class/class.types.d.ts +3 -0
  51. package/dist/dnd/amount-text.d.ts +42 -0
  52. package/dist/dnd/amount-text.js +15 -0
  53. package/dist/dnd/resource-words.d.ts +4 -0
  54. package/dist/dnd/resource-words.js +30 -0
  55. package/dist/entities/registry-revision.d.ts +25 -0
  56. package/dist/entities/registry-revision.js +10 -0
  57. package/dist/feat/feat.codec.js +5 -5
  58. package/dist/feat/feat.parser.js +4 -4
  59. package/dist/feat/feat.schema.d.ts +34 -0
  60. package/dist/feat/feat.schema.js +3 -3
  61. package/dist/index.js +4027 -383
  62. package/dist/monster/monster.parser.js +5 -5
  63. package/dist/monster/monster.schema.js +4 -4
  64. package/dist/optional-feature/optional-feature.codec.js +5 -4
  65. package/dist/optional-feature/optional-feature.parser.js +4 -3
  66. package/dist/optional-feature/optional-feature.schema.d.ts +35 -0
  67. package/dist/optional-feature/optional-feature.schema.js +3 -2
  68. package/dist/pc/pc.additional-spells.d.ts +16 -2
  69. package/dist/pc/pc.additional-spells.js +5 -2
  70. package/dist/pc/pc.amount-bindings.d.ts +41 -0
  71. package/dist/pc/pc.amount-bindings.js +28 -0
  72. package/dist/pc/pc.decision-engine.d.ts +11 -0
  73. package/dist/pc/pc.decision-engine.js +20 -16
  74. package/dist/pc/pc.entry-overrides.js +3 -2
  75. package/dist/pc/pc.equipment.js +7 -5
  76. package/dist/pc/pc.feature-effects.d.ts +21 -2
  77. package/dist/pc/pc.feature-effects.js +15 -2
  78. package/dist/pc/pc.infusions.d.ts +9 -1
  79. package/dist/pc/pc.infusions.js +3 -2
  80. package/dist/pc/pc.member-choices.d.ts +122 -0
  81. package/dist/pc/pc.member-choices.js +39 -0
  82. package/dist/pc/pc.parser.js +1 -1
  83. package/dist/pc/pc.pools.d.ts +7 -0
  84. package/dist/pc/pc.pools.js +5 -2
  85. package/dist/pc/pc.proficiencies.d.ts +14 -0
  86. package/dist/pc/pc.proficiencies.js +72 -22
  87. package/dist/pc/pc.proficiency-normalize.d.ts +14 -0
  88. package/dist/pc/pc.proficiency-normalize.js +9 -3
  89. package/dist/pc/pc.proficiency-query.js +7 -1
  90. package/dist/pc/pc.recalc.js +212 -99
  91. package/dist/pc/pc.resolver.d.ts +6 -2
  92. package/dist/pc/pc.resolver.js +16 -14
  93. package/dist/pc/pc.rest.js +4 -3
  94. package/dist/pc/pc.schema.d.ts +48 -2
  95. package/dist/pc/pc.schema.js +1 -1
  96. package/dist/pc/pc.spell-scope.d.ts +22 -0
  97. package/dist/pc/pc.tool-picks.js +2 -2
  98. package/dist/pc/pc.types.d.ts +110 -6
  99. package/dist/pc/pc.weapon-grant.d.ts +15 -0
  100. package/dist/pc/pool-layout.d.ts +3 -9
  101. package/dist/pc/pool-layout.js +1 -3
  102. package/dist/pc/resource-equivalents.d.ts +44 -0
  103. package/dist/pc/spell-source.js +4 -9
  104. package/dist/race/race.codec.js +6 -6
  105. package/dist/race/race.parser.js +5 -5
  106. package/dist/race/race.schema.js +4 -4
  107. package/dist/schemas/choice-schema.js +2 -2
  108. package/dist/schemas/feature-effect-schema.d.ts +34 -0
  109. package/dist/schemas/feature-effect-schema.js +1 -1
  110. package/dist/schemas/feature-schema.js +3 -3
  111. package/dist/spell/spell.codec.js +2 -2
  112. package/dist/spell/spell.parser.d.ts +6 -1
  113. package/dist/spell/spell.parser.js +5 -3
  114. package/dist/spell/spell.types.d.ts +10 -0
  115. package/dist/subclass/subclass.codec.js +6 -6
  116. package/dist/subclass/subclass.parser.js +5 -5
  117. package/dist/subclass/subclass.schema.js +4 -4
  118. package/dist/types/choice.d.ts +7 -2
  119. package/dist/types/feature-effect.d.ts +24 -0
  120. package/dist/types/optional-feature.types.d.ts +6 -1
  121. package/dist/types/selection-pool.d.ts +2 -2
  122. package/package.json +1 -1
@@ -0,0 +1,292 @@
1
+ import {
2
+ parsePickKey,
3
+ poolAnchorLevel
4
+ } from "./chunk-NXUY5XQN.js";
5
+ import {
6
+ featProgressionCategory
7
+ } from "./chunk-YBLDE5CG.js";
8
+ import {
9
+ warnOnce
10
+ } from "./chunk-LVQLWRCU.js";
11
+ import {
12
+ bucketOf
13
+ } from "./chunk-4J6DS3EJ.js";
14
+ import {
15
+ bareEntitySlug,
16
+ wikilinkTailSlug
17
+ } from "./chunk-ZCTUYGNV.js";
18
+
19
+ // src/pc/pc.member-choices.ts
20
+ var OPTIONAL_FEATURE_TYPE_CODE = {
21
+ "MV:B": "maneuver",
22
+ MV: "maneuver",
23
+ EI: "invocation",
24
+ MM: "metamagic",
25
+ "FS:F": "fighting_style"
26
+ };
27
+ function spellFilter(choose, edition) {
28
+ const where = { edition };
29
+ for (const part of choose.split("|")) {
30
+ const [rawKey, rawValue = ""] = part.split("=");
31
+ const key = rawKey.trim().toLowerCase();
32
+ const value = rawValue.trim();
33
+ if (key === "level") {
34
+ const n = Number(value.split(";")[0]);
35
+ if (!Number.isInteger(n)) return null;
36
+ where.level = n;
37
+ } else if (key === "class") where.list = value.toLowerCase();
38
+ else if (key === "components & miscellaneous" && /ritual/i.test(value)) where.ritual = true;
39
+ }
40
+ return where;
41
+ }
42
+ function isTargetChoice(ch) {
43
+ return ch.kind === "select-entity" && ch.target === true;
44
+ }
45
+ function progressionCount(p) {
46
+ var _a;
47
+ if (Array.isArray(p)) return 0;
48
+ return (_a = p["*"]) != null ? _a : Math.max(0, ...Object.values(p));
49
+ }
50
+ function spellChoices(entity, used) {
51
+ var _a, _b;
52
+ const out = [];
53
+ for (const block of (_a = entity.additional_spells) != null ? _a : []) {
54
+ for (const bucket of ["known", "prepared"]) {
55
+ const byLevel = block[bucket];
56
+ if (!byLevel || typeof byLevel !== "object") continue;
57
+ for (const rows of Object.values(byLevel)) {
58
+ for (const row of Array.isArray(rows) ? rows : []) {
59
+ const r = row;
60
+ if (typeof r.choose !== "string") continue;
61
+ const where = spellFilter(r.choose, entity.edition);
62
+ if (!where) continue;
63
+ const base = where.ritual ? "rituals" : where.level === 0 ? "cantrips" : `spells-${(_b = where.level) != null ? _b : "any"}`;
64
+ const label = where.ritual ? "Rituals" : where.level === 0 ? "Cantrips" : `Level ${where.level} spells`;
65
+ let id = base;
66
+ for (let i = 2; used.has(id); i++) id = `${base}-${i}`;
67
+ used.add(id);
68
+ out.push({ kind: "select-entity", id, label, count: typeof r.count === "number" ? r.count : 1, entity_type: "spell", where });
69
+ }
70
+ }
71
+ }
72
+ }
73
+ return out;
74
+ }
75
+ function memberChoices(entity) {
76
+ var _a, _b, _c, _d, _e, _f;
77
+ if (!entity) return [];
78
+ if ((_a = entity.choices) == null ? void 0 : _a.length) return entity.choices;
79
+ const out = [];
80
+ const used = /* @__PURE__ */ new Set();
81
+ for (const fp of (_b = entity.feat_progression) != null ? _b : []) {
82
+ const category = featProgressionCategory(fp.category);
83
+ const count = progressionCount(fp.progression);
84
+ if (!category || count < 1) continue;
85
+ const id = used.has("feat") ? `feat-${used.size}` : "feat";
86
+ used.add(id);
87
+ out.push({ kind: "select-entity", id, label: fp.name, count, entity_type: "feat", where: { category, edition: entity.edition } });
88
+ }
89
+ for (const op of (_c = entity.optionalfeature_progression) != null ? _c : []) {
90
+ const codes = (_e = (_d = op.feature_type) != null ? _d : op.featureType) != null ? _e : [];
91
+ const featureType = codes.map((c) => OPTIONAL_FEATURE_TYPE_CODE[c]).find((t) => !!t);
92
+ const count = progressionCount(op.progression);
93
+ if (!featureType || count < 1) {
94
+ if (!featureType) warnOnce(`member-of-code:${entity.slug}`, `Archivist: "${entity.slug}" asks for optional features of type ${codes.join(", ")}, which this engine does not map; no pick is offered`);
95
+ continue;
96
+ }
97
+ const id = op.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "option";
98
+ used.add(id);
99
+ out.push({ kind: "select-entity", id, label: op.name, count, entity_type: "optional-feature", where: { feature_type: featureType, edition: entity.edition } });
100
+ }
101
+ out.push(...spellChoices(entity, used));
102
+ for (const pre of (_f = entity.prerequisites) != null ? _f : []) {
103
+ if (pre.kind !== "spell-choose") continue;
104
+ const where = spellFilter(pre.choose, entity.edition);
105
+ if (!where || used.has("cantrip")) continue;
106
+ used.add("cantrip");
107
+ out.push({ kind: "select-entity", id: "cantrip", label: where.level === 0 ? "Cantrip" : "Spell", count: 1, entity_type: "spell", where, target: true });
108
+ }
109
+ return out;
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
+ }
151
+ function siteOf(classIndex, level, poolId, pickKey, member, memberSlug, block) {
152
+ const choices = memberChoices(member);
153
+ if (!choices.length) return null;
154
+ const prefix = `${poolId}:${pickKey}:`;
155
+ return { classIndex, level, poolId, pickKey, member, memberSlug, choices, prefix, block, read: (id) => block[`${prefix}${id}`] };
156
+ }
157
+ function poolMemberSites(classes, registry) {
158
+ const out = [];
159
+ let byBare = null;
160
+ let all = null;
161
+ const lookup = (s, ownerPrefix) => {
162
+ all != null ? all : all = bucketOf(registry, "optional-feature");
163
+ const bare = bareEntitySlug(s);
164
+ const owned = all.find((e) => bareEntitySlug(e.slug) === bare && e.slug.split("_")[0] === ownerPrefix);
165
+ if (owned) return owned;
166
+ const direct = registry.getByTypeAndSlug("optional-feature", s);
167
+ if (direct) return direct;
168
+ byBare != null ? byBare : byBare = new Map(all.map((e) => [bareEntitySlug(e.slug), e]));
169
+ return byBare.get(bare);
170
+ };
171
+ classes.forEach((rc, classIndex) => {
172
+ var _a, _b, _c, _d, _e;
173
+ if (!rc.entity) return;
174
+ const seen = /* @__PURE__ */ new Set();
175
+ for (const pool of [...(_a = rc.entity.selection_pools) != null ? _a : [], ...(_c = (_b = rc.subclass) == null ? void 0 : _b.selection_pools) != null ? _c : []]) {
176
+ if (seen.has(pool.id)) continue;
177
+ seen.add(pool.id);
178
+ const { anchorLevel } = poolAnchorLevel(rc.entity, pool);
179
+ const block = (_e = (_d = rc.choices) == null ? void 0 : _d[anchorLevel]) != null ? _e : {};
180
+ const raw = block[pool.id];
181
+ const picks = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : [];
182
+ for (const s of picks) {
183
+ if (typeof s !== "string") continue;
184
+ const { slugPart, param } = parsePickKey(s);
185
+ const reg = lookup(slugPart, rc.entity.slug.split("_")[0]);
186
+ if (!reg) continue;
187
+ const member = reg.data;
188
+ const key = member.repeatable === true || param !== null ? s : reg.slug;
189
+ const site = siteOf(classIndex, anchorLevel, pool.id, key, member, reg.slug, block);
190
+ if (site) out.push(site);
191
+ }
192
+ }
193
+ });
194
+ return out;
195
+ }
196
+ function poolMemberSitesOf(resolved) {
197
+ var _a, _b, _c, _d, _e, _f;
198
+ const out = [];
199
+ for (const pool of (_a = resolved.pools) != null ? _a : []) {
200
+ const block = (_e = (_d = (_c = (_b = resolved.classes) == null ? void 0 : _b[pool.classIndex]) == null ? void 0 : _c.choices) == null ? void 0 : _d[pool.anchorLevel]) != null ? _e : {};
201
+ for (const e of (_f = pool.selected) != null ? _f : []) {
202
+ const site = siteOf(pool.classIndex, pool.anchorLevel, pool.id, e.key, e.entity, e.slug, block);
203
+ if (site) out.push(site);
204
+ }
205
+ }
206
+ return out;
207
+ }
208
+ var stripRef = (ref) => ref.replace(/^\[\[/, "").replace(/\]\]$/, "");
209
+ function featPickSites(classEntries, memberSites, classSlugOf) {
210
+ const out = [];
211
+ (classEntries != null ? classEntries : []).forEach((c, classIndex) => {
212
+ var _a;
213
+ for (const [lvl, choiceBlock] of Object.entries((_a = c.choices) != null ? _a : {})) {
214
+ const block = choiceBlock;
215
+ const feat = block == null ? void 0 : block.feat;
216
+ if (!block || typeof feat !== "string") continue;
217
+ const slug = stripRef(feat);
218
+ if (!slug) continue;
219
+ const level = /^\d+$/.test(lvl) ? Number(lvl) : null;
220
+ const cls = classSlugOf(classIndex);
221
+ out.push({
222
+ slug,
223
+ classIndex,
224
+ level,
225
+ via: cls && level !== null ? { kind: "class", slug: cls, level } : null,
226
+ read: (id) => block[`feat:${id}`]
227
+ });
228
+ }
229
+ });
230
+ for (const site of memberSites) {
231
+ for (const ch of site.choices) {
232
+ if (ch.kind !== "select-entity" || ch.entity_type !== "feat" || ch.target === true) continue;
233
+ const v = site.read(ch.id);
234
+ for (const ref of Array.isArray(v) ? v : typeof v === "string" ? [v] : []) {
235
+ if (typeof ref !== "string") continue;
236
+ const slug = stripRef(ref);
237
+ if (!slug) continue;
238
+ const cls = classSlugOf(site.classIndex);
239
+ out.push({
240
+ slug,
241
+ classIndex: site.classIndex,
242
+ level: site.level,
243
+ via: cls ? { kind: "pool", slug: site.memberSlug, name: site.member.name, classSlug: cls, level: site.level } : null,
244
+ read: (id) => site.block[`${site.prefix}feat:${id}`]
245
+ });
246
+ }
247
+ }
248
+ }
249
+ return out;
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
+ }
268
+ function featOfSite(site, feats) {
269
+ var _a;
270
+ return (_a = feats.find((f) => f.slug === site.slug)) != null ? _a : feats.find((f) => bareEntitySlug(f.slug) === bareEntitySlug(site.slug));
271
+ }
272
+ function nestedPickKeysToPrune(block, key, next) {
273
+ const prev = block == null ? void 0 : block[key];
274
+ if (!block || !Array.isArray(prev)) return [];
275
+ const keep = new Set(next);
276
+ const gone = prev.filter((k) => typeof k === "string" && !keep.has(k));
277
+ if (!gone.length) return [];
278
+ return Object.keys(block).filter((k) => gone.some((g) => k.startsWith(`${key}:${g}:`)));
279
+ }
280
+
281
+ export {
282
+ isTargetChoice,
283
+ memberChoices,
284
+ memberSpellTargets,
285
+ spellsReached,
286
+ poolMemberSites,
287
+ poolMemberSitesOf,
288
+ featPickSites,
289
+ raceFeatSites,
290
+ featOfSite,
291
+ nestedPickKeysToPrune
292
+ };
@@ -0,0 +1,112 @@
1
+ import {
2
+ spellSource
3
+ } from "./chunk-KR6JF3BF.js";
4
+ import {
5
+ resourceLevelFor
6
+ } from "./chunk-KSTV7J2U.js";
7
+ import {
8
+ addToRoll
9
+ } from "./chunk-WOHX7YTH.js";
10
+ import {
11
+ baseClassName
12
+ } from "./chunk-VTTA2ALZ.js";
13
+
14
+ // src/pc/pc.amount-bindings.ts
15
+ function ownerClassSlug(source, resolved) {
16
+ var _a, _b, _c;
17
+ if (!source) return null;
18
+ if (source.kind === "class") return source.slug;
19
+ if (source.kind === "subclass") return (_c = (_b = (_a = resolved.classes.find((c) => {
20
+ var _a2;
21
+ return ((_a2 = c.subclass) == null ? void 0 : _a2.slug) === source.slug;
22
+ })) == null ? void 0 : _a.entity) == null ? void 0 : _b.slug) != null ? _c : null;
23
+ if (source.kind === "feat" && source.via) {
24
+ if (source.via.kind === "class") return source.via.slug;
25
+ if (source.via.kind === "pool") return source.via.classSlug;
26
+ }
27
+ return null;
28
+ }
29
+ function casterRow(classSlug, derived) {
30
+ var _a, _b;
31
+ const rows = (_a = derived.spellcastingClasses) != null ? _a : [];
32
+ const own = classSlug ? rows.find((c) => baseClassName(c.classSlug) === baseClassName(classSlug)) : void 0;
33
+ return (_b = own != null ? own : rows[0]) != null ? _b : null;
34
+ }
35
+ function amountBindingsFor(owner, resolved, derived) {
36
+ const classSlug = ownerClassSlug(owner, resolved);
37
+ const classSource = classSlug ? { kind: "class", slug: classSlug, level: 0 } : owner;
38
+ const classLevel = classSource ? resourceLevelFor(classSource, resolved) : resolved.totalLevel;
39
+ const row = casterRow(classSlug, derived);
40
+ return {
41
+ level: resolved.totalLevel,
42
+ class_level: classLevel,
43
+ prof: derived.proficiencyBonus,
44
+ str_mod: derived.mods.str,
45
+ dex_mod: derived.mods.dex,
46
+ con_mod: derived.mods.con,
47
+ int_mod: derived.mods.int,
48
+ wis_mod: derived.mods.wis,
49
+ cha_mod: derived.mods.cha,
50
+ columns: {},
51
+ ...row ? { spell_mod: derived.mods[row.ability], spell_dc: row.saveDC } : {}
52
+ };
53
+ }
54
+ function spellcastingFor(spell, derived) {
55
+ var _a, _b;
56
+ if ((spellSource(spell).ownAbility || spell.classSlug == null) && spell.ability) {
57
+ const own = (_a = derived.abilitySpellcasting) == null ? void 0 : _a[spell.ability];
58
+ if (own) return { ability: spell.ability, saveDC: own.saveDC, attackBonus: own.attackBonus };
59
+ }
60
+ const row = casterRow((_b = spell.classSlug) != null ? _b : null, derived);
61
+ return row ? { ability: row.ability, saveDC: row.saveDC, attackBonus: row.attackBonus } : null;
62
+ }
63
+ function spellAmountBindings(spell, resolved, derived) {
64
+ const cast = spellcastingFor(spell, derived);
65
+ const base = amountBindingsFor(spell.classSlug ? { kind: "class", slug: spell.classSlug, level: 0 } : void 0, resolved, derived);
66
+ const out = { ...base };
67
+ delete out.spell_mod;
68
+ delete out.spell_dc;
69
+ return cast ? { ...out, spell_mod: derived.mods[cast.ability], spell_dc: cast.saveDC } : out;
70
+ }
71
+ function spellRollBonus(spell) {
72
+ var _a;
73
+ const roll = (_a = spell.damage_roll) == null ? void 0 : _a.trim();
74
+ const text = spell.description;
75
+ if (!roll || !text) return null;
76
+ const esc = roll.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
77
+ const re = new RegExp(`(?<![\\w])${esc}\`?\\s*(?:plus|\\+)\\s*your\\s+spellcasting\\s+(?:ability\\s+)?modifier`, "i");
78
+ return re.test(text) ? "{spell_mod}" : null;
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
+ }
103
+
104
+ export {
105
+ ownerClassSlug,
106
+ amountBindingsFor,
107
+ spellcastingFor,
108
+ spellAmountBindings,
109
+ spellRollBonus,
110
+ addSpellDamageRiders,
111
+ spellDamageRiderLines
112
+ };
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-CJYCVNW2.js";
9
9
  import {
10
10
  featureSchema
11
- } from "./chunk-FPGXFD2M.js";
11
+ } from "./chunk-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-BRGLDWUP.js";
21
+ } from "./chunk-OOL4KMG2.js";
22
22
  import {
23
23
  additionalSpellsEntrySchema,
24
24
  imageField,
@@ -110,6 +110,15 @@ var classEntitySchema = z.object({
110
110
  description: z.string(),
111
111
  hit_die: hitDieEnum,
112
112
  primary_abilities: z.array(abilityEnum).nonempty(),
113
+ // D2b-A (A-1): the class's recommended Standard Array (SRD 5.2 "Standard Array by Class"); absent on 2014 classes.
114
+ standard_array: z.strictObject({
115
+ str: z.number().int(),
116
+ dex: z.number().int(),
117
+ con: z.number().int(),
118
+ int: z.number().int(),
119
+ wis: z.number().int(),
120
+ cha: z.number().int()
121
+ }).optional(),
113
122
  saving_throws: z.array(abilityEnum).length(2),
114
123
  proficiencies: z.object({
115
124
  armor: z.array(armorCategoryEnum),
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  subclassEntitySchema
3
- } from "./chunk-J53ERF6K.js";
3
+ } from "./chunk-LNT64X7C.js";
4
4
  import {
5
5
  aliasFeaturesByLevelActionCost
6
6
  } from "./chunk-4U55VNC7.js";
@@ -6,9 +6,12 @@ import {
6
6
  resetTriggerEnum,
7
7
  resourceConsumptionSchema
8
8
  } from "./chunk-ZJXSXVFP.js";
9
+ import {
10
+ choiceSchema
11
+ } from "./chunk-OOL4KMG2.js";
9
12
  import {
10
13
  featureEffectSchema
11
- } from "./chunk-ZMJQUZQH.js";
14
+ } from "./chunk-JXWNXIIZ.js";
12
15
  import {
13
16
  additionalSpellsEntrySchema,
14
17
  imageField,
@@ -138,7 +141,9 @@ var optionalFeatureEntitySchema = z.object({
138
141
  repeatable: z.literal(true).optional(),
139
142
  item_choice: z.enum(["learn", "create"]).optional(),
140
143
  options: z.array(optionRowSchema).optional(),
141
- infuse: infuseBlockSchema.optional()
144
+ infuse: infuseBlockSchema.optional(),
145
+ // D2b-A (design §2.2 step 1): a pool member's own choices, the vocabulary features and feats use.
146
+ choices: z.array(choiceSchema).optional()
142
147
  }).superRefine((d, ctx) => {
143
148
  var _a;
144
149
  if (d.repeatable && (!d.options || !d.item_choice)) ctx.addIssue({ code: "custom", message: "a repeatable note needs options and item_choice" });
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  featureObjectSchema
3
- } from "./chunk-FPGXFD2M.js";
3
+ } from "./chunk-B2QHP6UF.js";
4
4
  import {
5
5
  imageField
6
6
  } from "./chunk-UA5KS2PL.js";
@@ -0,0 +1,165 @@
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 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
+ }
150
+ function diceRollOf(resolved) {
151
+ var _a, _b;
152
+ return (_b = (_a = diceRollPrefix(resolved)) == null ? void 0 : _a.notation) != null ? _b : null;
153
+ }
154
+ function diceRollPrefix(text) {
155
+ const m = /^(\d*d\d+(?:\s*[+-]\s*\d+)*)(?=\s|$)/i.exec(text);
156
+ return m ? { raw: m[1], notation: m[1].replace(/\s+/g, "") } : null;
157
+ }
158
+
159
+ export {
160
+ normalizeAmountPhrases,
161
+ resolveAmountText,
162
+ addToRoll,
163
+ diceRollOf,
164
+ diceRollPrefix
165
+ };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-4U55VNC7.js";
4
4
  import {
5
5
  classEntitySchema
6
- } from "./chunk-PLOSP4XQ.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-4SLOJXJ4.js";
3
+ } from "./chunk-3SFGWFVQ.js";
4
4
 
5
5
  // src/feat/feat.codec.ts
6
6
  import yaml from "js-yaml";