@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.
- package/dist/background/background.codec.js +5 -5
- package/dist/background/background.parser.js +4 -4
- package/dist/background/background.schema.js +3 -3
- package/dist/chunk-3LWMFYKS.js +57 -0
- package/dist/{chunk-4SLOJXJ4.js → chunk-3SFGWFVQ.js} +1 -1
- package/dist/{chunk-Q6UOHCCT.js → chunk-3SIUAFDQ.js} +1 -1
- package/dist/chunk-4J6DS3EJ.js +30 -0
- package/dist/{chunk-RLGQ5PZZ.js → chunk-5JXFJ5AU.js} +11 -1
- package/dist/{chunk-4BZ47YJ4.js → chunk-64VNLMSG.js} +59 -16
- package/dist/{chunk-XY6X2APM.js → chunk-7Q2VPKCD.js} +17 -0
- package/dist/{chunk-Q5XTEYHD.js → chunk-7XJHI5H5.js} +16 -9
- package/dist/{chunk-FPGXFD2M.js → chunk-B2QHP6UF.js} +2 -2
- package/dist/{chunk-HJQNK3J5.js → chunk-B3DP42OM.js} +1 -4
- package/dist/{chunk-MC5YJ4PB.js → chunk-BJGOSM76.js} +1 -1
- package/dist/{chunk-VDPRBUU5.js → chunk-BJWSOQUC.js} +1 -1
- package/dist/{chunk-QEO7LYTG.js → chunk-EJD47JHL.js} +181 -93
- package/dist/{chunk-X7VZY4ON.js → chunk-FJYBLXV7.js} +2 -2
- package/dist/{chunk-QSBJ6NEM.js → chunk-FMUD3LDV.js} +1 -1
- package/dist/{chunk-LVX6TQ3R.js → chunk-FMYWORTP.js} +2 -2
- package/dist/{chunk-PX25R3DK.js → chunk-FVXLP7P3.js} +1 -1
- package/dist/{chunk-XJRNEQPD.js → chunk-GVWEFECG.js} +1 -1
- package/dist/{chunk-R2V5LN3I.js → chunk-HY2SRSVM.js} +1 -1
- package/dist/{chunk-ZMJQUZQH.js → chunk-JXWNXIIZ.js} +24 -0
- package/dist/chunk-KR6JF3BF.js +14 -0
- package/dist/{chunk-DPM6V7YX.js → chunk-LICYHPI3.js} +82 -6
- package/dist/{chunk-J53ERF6K.js → chunk-LNT64X7C.js} +1 -1
- package/dist/{chunk-RQNJXADQ.js → chunk-NKZMQV2C.js} +1 -1
- package/dist/{chunk-UE6LKRXQ.js → chunk-NTU2DXNG.js} +24 -1
- package/dist/{chunk-T5GZBKIQ.js → chunk-NXUY5XQN.js} +168 -33
- package/dist/{chunk-M2OWVAZG.js → chunk-NZO7M5GV.js} +1 -1
- package/dist/{chunk-BRGLDWUP.js → chunk-OOL4KMG2.js} +7 -3
- package/dist/{chunk-MWCCGPG3.js → chunk-OOSRUJBI.js} +3 -2
- package/dist/{chunk-H5PTKYXL.js → chunk-QA3YCOML.js} +1 -1
- package/dist/{chunk-MUIU7RZ3.js → chunk-QFWQKS3M.js} +1 -1
- package/dist/chunk-RD2MID3M.js +292 -0
- package/dist/chunk-TRBGC4ZG.js +112 -0
- package/dist/{chunk-PLOSP4XQ.js → chunk-UJCK5IW4.js} +11 -2
- package/dist/{chunk-5KGV6TF7.js → chunk-UNYR5GLI.js} +1 -1
- package/dist/{chunk-JNNNBEUH.js → chunk-VN5DLURA.js} +7 -2
- package/dist/{chunk-EL6QPF6I.js → chunk-VP5TIXDK.js} +1 -1
- package/dist/chunk-WOHX7YTH.js +165 -0
- package/dist/{chunk-G7ED5H2G.js → chunk-XTEFPUSN.js} +1 -1
- package/dist/{chunk-6ZY3U6BQ.js → chunk-ZOWKWMD2.js} +1 -1
- package/dist/class/class.codec.js +6 -6
- package/dist/class/class.parser.js +5 -5
- package/dist/class/class.schema.d.ts +8 -0
- package/dist/class/class.schema.js +4 -4
- package/dist/class/class.standard-array.d.ts +16 -0
- package/dist/class/class.standard-array.js +20 -0
- package/dist/class/class.types.d.ts +3 -0
- package/dist/dnd/amount-text.d.ts +42 -0
- package/dist/dnd/amount-text.js +15 -0
- package/dist/dnd/resource-words.d.ts +4 -0
- package/dist/dnd/resource-words.js +30 -0
- package/dist/entities/registry-revision.d.ts +25 -0
- package/dist/entities/registry-revision.js +10 -0
- package/dist/feat/feat.codec.js +5 -5
- package/dist/feat/feat.parser.js +4 -4
- package/dist/feat/feat.schema.d.ts +34 -0
- package/dist/feat/feat.schema.js +3 -3
- package/dist/index.js +4027 -383
- package/dist/monster/monster.parser.js +5 -5
- package/dist/monster/monster.schema.js +4 -4
- package/dist/optional-feature/optional-feature.codec.js +5 -4
- package/dist/optional-feature/optional-feature.parser.js +4 -3
- package/dist/optional-feature/optional-feature.schema.d.ts +35 -0
- package/dist/optional-feature/optional-feature.schema.js +3 -2
- package/dist/pc/pc.additional-spells.d.ts +16 -2
- package/dist/pc/pc.additional-spells.js +5 -2
- package/dist/pc/pc.amount-bindings.d.ts +41 -0
- package/dist/pc/pc.amount-bindings.js +28 -0
- package/dist/pc/pc.decision-engine.d.ts +11 -0
- package/dist/pc/pc.decision-engine.js +20 -16
- package/dist/pc/pc.entry-overrides.js +3 -2
- package/dist/pc/pc.equipment.js +7 -5
- package/dist/pc/pc.feature-effects.d.ts +21 -2
- package/dist/pc/pc.feature-effects.js +15 -2
- package/dist/pc/pc.infusions.d.ts +9 -1
- package/dist/pc/pc.infusions.js +3 -2
- package/dist/pc/pc.member-choices.d.ts +122 -0
- package/dist/pc/pc.member-choices.js +39 -0
- package/dist/pc/pc.parser.js +1 -1
- package/dist/pc/pc.pools.d.ts +7 -0
- package/dist/pc/pc.pools.js +5 -2
- package/dist/pc/pc.proficiencies.d.ts +14 -0
- package/dist/pc/pc.proficiencies.js +72 -22
- package/dist/pc/pc.proficiency-normalize.d.ts +14 -0
- package/dist/pc/pc.proficiency-normalize.js +9 -3
- package/dist/pc/pc.proficiency-query.js +7 -1
- package/dist/pc/pc.recalc.js +212 -99
- package/dist/pc/pc.resolver.d.ts +6 -2
- package/dist/pc/pc.resolver.js +16 -14
- package/dist/pc/pc.rest.js +4 -3
- package/dist/pc/pc.schema.d.ts +48 -2
- package/dist/pc/pc.schema.js +1 -1
- package/dist/pc/pc.spell-scope.d.ts +22 -0
- package/dist/pc/pc.tool-picks.js +2 -2
- package/dist/pc/pc.types.d.ts +110 -6
- package/dist/pc/pc.weapon-grant.d.ts +15 -0
- package/dist/pc/pool-layout.d.ts +3 -9
- package/dist/pc/pool-layout.js +1 -3
- package/dist/pc/resource-equivalents.d.ts +44 -0
- package/dist/pc/spell-source.js +4 -9
- package/dist/race/race.codec.js +6 -6
- package/dist/race/race.parser.js +5 -5
- package/dist/race/race.schema.js +4 -4
- package/dist/schemas/choice-schema.js +2 -2
- package/dist/schemas/feature-effect-schema.d.ts +34 -0
- package/dist/schemas/feature-effect-schema.js +1 -1
- package/dist/schemas/feature-schema.js +3 -3
- package/dist/spell/spell.codec.js +2 -2
- package/dist/spell/spell.parser.d.ts +6 -1
- package/dist/spell/spell.parser.js +5 -3
- package/dist/spell/spell.types.d.ts +10 -0
- package/dist/subclass/subclass.codec.js +6 -6
- package/dist/subclass/subclass.parser.js +5 -5
- package/dist/subclass/subclass.schema.js +4 -4
- package/dist/types/choice.d.ts +7 -2
- package/dist/types/feature-effect.d.ts +24 -0
- package/dist/types/optional-feature.types.d.ts +6 -1
- package/dist/types/selection-pool.d.ts +2 -2
- package/package.json +1 -1
|
@@ -4,12 +4,15 @@ 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";
|
|
10
13
|
import {
|
|
11
14
|
mirrorSpellShapes
|
|
12
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-7Q2VPKCD.js";
|
|
13
16
|
import {
|
|
14
17
|
ABILITY_KEYS
|
|
15
18
|
} from "./chunk-WELIKLZF.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
|
-
|
|
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
|
-
|
|
191
|
+
const buckets = ["known", "prepared"].map((bucket) => {
|
|
122
192
|
const levels = entry[bucket];
|
|
123
|
-
|
|
124
|
-
|
|
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
|
};
|
|
@@ -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.
|
|
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
|
|
@@ -251,6 +254,25 @@ var knownSpellObjectSchema = z.object({
|
|
|
251
254
|
always_prepared: z.boolean().optional()
|
|
252
255
|
}).strict();
|
|
253
256
|
var knownSpellEntrySchema = z.union([z.string().min(1), knownSpellObjectSchema]);
|
|
257
|
+
var sheetViewFilterSchema = z.object({
|
|
258
|
+
show: z.array(z.string().min(1)).optional(),
|
|
259
|
+
hide: z.array(z.string().min(1)).optional()
|
|
260
|
+
});
|
|
261
|
+
var sheetPoolViewSchema = z.object({
|
|
262
|
+
show: z.enum(["known", "granted", "not-known"]).optional().catch(void 0),
|
|
263
|
+
off: z.record(z.string(), z.array(z.string())).optional(),
|
|
264
|
+
folded: z.array(z.string().min(1)).optional()
|
|
265
|
+
});
|
|
266
|
+
var sheetStateSchema = z.object({
|
|
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(),
|
|
272
|
+
favorites: z.array(z.string().min(1)).optional(),
|
|
273
|
+
filters: z.object({ actions: sheetViewFilterSchema.optional(), passive: sheetViewFilterSchema.optional() }).optional(),
|
|
274
|
+
pools: z.record(z.string(), sheetPoolViewSchema).optional()
|
|
275
|
+
});
|
|
254
276
|
var characterSchema = z.object({
|
|
255
277
|
name: z.string().min(1),
|
|
256
278
|
edition: z.enum(["2014", "2024"]),
|
|
@@ -323,6 +345,7 @@ var characterSchema = z.object({
|
|
|
323
345
|
vulnerabilities: z.array(z.string()).default([]),
|
|
324
346
|
condition_immunities: z.array(z.string()).default([])
|
|
325
347
|
}).partial().optional(),
|
|
348
|
+
sheet: sheetStateSchema.optional(),
|
|
326
349
|
state: characterStateSchema
|
|
327
350
|
});
|
|
328
351
|
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-W6GC6OKK.js";
|
|
4
4
|
import {
|
|
5
5
|
derivePoolLayout
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-B3DP42OM.js";
|
|
7
7
|
import {
|
|
8
8
|
readTableColumn
|
|
9
9
|
} from "./chunk-IUAAXXNO.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,
|
|
@@ -41,29 +45,99 @@ import {
|
|
|
41
45
|
isCanonicalDamageType
|
|
42
46
|
} from "./chunk-PDJSUH24.js";
|
|
43
47
|
|
|
48
|
+
// src/pc/resource-equivalents.ts
|
|
49
|
+
var RESOURCE_EQUIVALENTS = [
|
|
50
|
+
{ name: "Superiority Dice", ids: ["fighter:superiority-dice", "fighter-2024:superiority-dice"] },
|
|
51
|
+
{ name: "Sorcery Points", ids: ["sorcerer:sorcery-point", "sorcerer:sorcery-points", "sorcerer-2024:sorcery-point"] },
|
|
52
|
+
{ name: "Ki / Focus Points", ids: ["monk:ki", "monk:ki-points", "monk:focus-point", "monk:focus-points"] },
|
|
53
|
+
{ name: "Channel Divinity (Cleric)", ids: ["cleric:channel-divinity", "cleric-2024:channel-divinity", "channel-divinity"] },
|
|
54
|
+
{ name: "Channel Divinity (Paladin)", ids: ["paladin:channel-divinity", "paladin-2024:channel-divinity", "channel-divinity"] },
|
|
55
|
+
{ name: "Bardic Inspiration", ids: ["bard:bardic-inspiration", "bard:bardic-die"] },
|
|
56
|
+
{ name: "Rage", ids: ["barbarian:rage", "barbarian:rages", "barbarian-2024:rages"] },
|
|
57
|
+
{ name: "Wild Shape", ids: ["druid:wild-shape", "druid-2024:wild-shape"] },
|
|
58
|
+
{ name: "Soul Trinkets", ids: ["rogue:soul-trinket", "rogue-2024:soul-trinket"] }
|
|
59
|
+
];
|
|
60
|
+
function equivalentResourceIds(id, groups = RESOURCE_EQUIVALENTS) {
|
|
61
|
+
const out = [];
|
|
62
|
+
for (const g of groups) {
|
|
63
|
+
if (!g.ids.includes(id)) continue;
|
|
64
|
+
for (const other of g.ids) if (other !== id && !out.includes(other)) out.push(other);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
function ownedByClass(res, rc) {
|
|
69
|
+
var _a, _b;
|
|
70
|
+
const s = (_a = res == null ? void 0 : res.owner) == null ? void 0 : _a.source;
|
|
71
|
+
if (!s) return false;
|
|
72
|
+
if (s.kind === "class") return s.slug === ((_b = rc.entity) == null ? void 0 : _b.slug);
|
|
73
|
+
if (s.kind === "subclass") return !!rc.subclass && s.slug === rc.subclass.slug;
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
function payingResourceId(id, index, owner, groups = RESOURCE_EQUIVALENTS) {
|
|
77
|
+
const candidates = [id, ...equivalentResourceIds(id, groups)].filter((c) => index.has(c));
|
|
78
|
+
if (candidates.length === 0) return void 0;
|
|
79
|
+
if (owner) {
|
|
80
|
+
const own = candidates.find((c) => ownedByClass(index.get(c), owner));
|
|
81
|
+
if (own) return own;
|
|
82
|
+
}
|
|
83
|
+
return candidates[0];
|
|
84
|
+
}
|
|
85
|
+
function classOfSource(source, classes) {
|
|
86
|
+
var _a, _b;
|
|
87
|
+
if (source.kind === "class") return (_a = classes.find((c) => {
|
|
88
|
+
var _a2;
|
|
89
|
+
return ((_a2 = c.entity) == null ? void 0 : _a2.slug) === source.slug;
|
|
90
|
+
})) != null ? _a : null;
|
|
91
|
+
if (source.kind === "subclass") return (_b = classes.find((c) => {
|
|
92
|
+
var _a2;
|
|
93
|
+
return ((_a2 = c.subclass) == null ? void 0 : _a2.slug) === source.slug;
|
|
94
|
+
})) != null ? _b : null;
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
function bindFeatureConsumers(features, index, classes) {
|
|
98
|
+
return features.map((rf) => {
|
|
99
|
+
const consumes = rf.feature.consumes;
|
|
100
|
+
const id = consumes == null ? void 0 : consumes.resource;
|
|
101
|
+
if (!consumes || typeof id !== "string" || id.length === 0) return rf;
|
|
102
|
+
const payer = payingResourceId(id, index, classOfSource(rf.source, classes));
|
|
103
|
+
if (payer === void 0 || payer === id) return rf;
|
|
104
|
+
return { ...rf, feature: { ...rf.feature, consumes: { ...consumes, resource: payer } } };
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
44
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"]);
|
|
45
114
|
function levelPrereqMax(d) {
|
|
46
115
|
var _a;
|
|
47
116
|
return ((_a = d.prerequisites) != null ? _a : []).filter((p) => p.kind === "level").reduce((m, p) => Math.max(m, p.min), 0);
|
|
48
117
|
}
|
|
49
|
-
function
|
|
50
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
51
|
-
const entity = rc.entity;
|
|
52
|
-
const level = rc.level;
|
|
53
|
-
const ownerBare = bareEntitySlug(entity.slug);
|
|
54
|
-
const ownerPrefix = entity.slug.split("_")[0];
|
|
55
|
-
const col = pool.count.column;
|
|
56
|
-
const count = (_a = readTableColumn(entity.table, level, [col])) != null ? _a : 0;
|
|
118
|
+
function poolAnchorLevel(entity, pool) {
|
|
57
119
|
let anchorLevel = 1;
|
|
58
120
|
let columnSeen = false;
|
|
59
121
|
for (let l = 1; l <= 20; l++) {
|
|
60
|
-
const c = readTableColumn(entity.table, l, [
|
|
122
|
+
const c = readTableColumn(entity.table, l, [pool.count.column]);
|
|
61
123
|
if (c != null) columnSeen = true;
|
|
62
124
|
if (c != null && c >= 1) {
|
|
63
125
|
anchorLevel = l;
|
|
64
126
|
break;
|
|
65
127
|
}
|
|
66
128
|
}
|
|
129
|
+
return { anchorLevel, columnSeen };
|
|
130
|
+
}
|
|
131
|
+
function resolvePool(rc, classIndex, pool, registry, index, edition) {
|
|
132
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
133
|
+
const entity = rc.entity;
|
|
134
|
+
const level = rc.level;
|
|
135
|
+
const ownerBare = bareEntitySlug(entity.slug);
|
|
136
|
+
const ownerPrefix = entity.slug.split("_")[0];
|
|
137
|
+
const ownEdition = SRD_OWNER_PREFIXES.has(ownerPrefix) ? entity.edition : void 0;
|
|
138
|
+
const col = pool.count.column;
|
|
139
|
+
const count = (_a = readTableColumn(entity.table, level, [col])) != null ? _a : 0;
|
|
140
|
+
const { anchorLevel, columnSeen } = poolAnchorLevel(entity, pool);
|
|
67
141
|
if (!columnSeen) {
|
|
68
142
|
warnOnce(
|
|
69
143
|
`pool-column:${pool.id}`,
|
|
@@ -71,7 +145,7 @@ function resolvePool(rc, classIndex, pool, registry, index, edition) {
|
|
|
71
145
|
);
|
|
72
146
|
}
|
|
73
147
|
const ft = pool.source.where.feature_type;
|
|
74
|
-
const all = registry
|
|
148
|
+
const all = sortedOptionalFeatures(registry);
|
|
75
149
|
const entryOf = (slug, entity2, compendium) => ({ slug, entity: entity2, ...compendium ? { compendium } : {}, key: slug, param: null, itemBound: isItemBound(entity2) });
|
|
76
150
|
const byBare = /* @__PURE__ */ new Map();
|
|
77
151
|
const candidates = /* @__PURE__ */ new Map();
|
|
@@ -83,14 +157,15 @@ function resolvePool(rc, classIndex, pool, registry, index, edition) {
|
|
|
83
157
|
if (d.feature_type !== ft) continue;
|
|
84
158
|
if (!((_b = d.available_to) != null ? _b : []).some((l) => wikilinkTailSlug(l) === ownerBare)) continue;
|
|
85
159
|
if (levelPrereqMax(d) > level) continue;
|
|
160
|
+
if (ownEdition && d.edition && d.edition !== ownEdition) continue;
|
|
86
161
|
const group = candidates.get(bare);
|
|
87
162
|
if (group) group.push(entry);
|
|
88
163
|
else candidates.set(bare, [entry]);
|
|
89
164
|
}
|
|
90
|
-
const
|
|
165
|
+
const collapsed = [];
|
|
91
166
|
for (const [bare, group] of candidates) {
|
|
92
167
|
if (group.length === 1) {
|
|
93
|
-
|
|
168
|
+
collapsed.push(group[0]);
|
|
94
169
|
continue;
|
|
95
170
|
}
|
|
96
171
|
const owned = group.find((e) => e.slug.split("_")[0] === ownerPrefix);
|
|
@@ -100,7 +175,7 @@ function resolvePool(rc, classIndex, pool, registry, index, edition) {
|
|
|
100
175
|
`Archivist: pool "${pool.id}" holds ${group.length} cross-edition entries for "${bare}" and none shares the owner's compendium prefix "${ownerPrefix}"; keeping "${group[0].slug}"`
|
|
101
176
|
);
|
|
102
177
|
}
|
|
103
|
-
|
|
178
|
+
collapsed.push(owned != null ? owned : group[0]);
|
|
104
179
|
}
|
|
105
180
|
const grantDecls = [
|
|
106
181
|
...(_d = (_c = rc.entity) == null ? void 0 : _c.pool_grants) != null ? _d : [],
|
|
@@ -114,6 +189,7 @@ function resolvePool(rc, classIndex, pool, registry, index, edition) {
|
|
|
114
189
|
grantedBare.add(key);
|
|
115
190
|
return true;
|
|
116
191
|
});
|
|
192
|
+
const available = collapsed.filter((e) => !grantedBare.has(bareEntitySlug(e.slug)));
|
|
117
193
|
const bySlugAvailable = new Map(available.map((e) => [e.slug, e]));
|
|
118
194
|
const byBareAvailable = new Map(available.map((e) => [bareEntitySlug(e.slug), e]));
|
|
119
195
|
const resolveStored = (s) => {
|
|
@@ -152,8 +228,9 @@ function resolvePool(rc, classIndex, pool, registry, index, edition) {
|
|
|
152
228
|
const votes = /* @__PURE__ */ new Map();
|
|
153
229
|
for (const e of members) {
|
|
154
230
|
const id = (_h = e.entity.consumes) == null ? void 0 : _h.resource;
|
|
155
|
-
|
|
156
|
-
|
|
231
|
+
const payer = id ? payingResourceId(id, index, rc) : void 0;
|
|
232
|
+
if (!payer) continue;
|
|
233
|
+
votes.set(payer, ((_i = votes.get(payer)) != null ? _i : 0) + 1);
|
|
157
234
|
}
|
|
158
235
|
let resource;
|
|
159
236
|
let bestN = 0;
|
|
@@ -161,7 +238,7 @@ function resolvePool(rc, classIndex, pool, registry, index, edition) {
|
|
|
161
238
|
resource = id;
|
|
162
239
|
bestN = n;
|
|
163
240
|
}
|
|
164
|
-
const bind =
|
|
241
|
+
const bind = poolRebinder(rc, pool.id, resource, index);
|
|
165
242
|
return {
|
|
166
243
|
id: pool.id,
|
|
167
244
|
label: pool.label,
|
|
@@ -184,14 +261,20 @@ function poolRebinder(rc, poolId, vote, index) {
|
|
|
184
261
|
return (e) => {
|
|
185
262
|
const consumes = e.entity.consumes;
|
|
186
263
|
const id = consumes == null ? void 0 : consumes.resource;
|
|
187
|
-
if (!consumes || typeof id !== "string" || id.length === 0
|
|
264
|
+
if (!consumes || typeof id !== "string" || id.length === 0) return e;
|
|
265
|
+
const payer = payingResourceId(id, index, rc);
|
|
266
|
+
if (payer === id) return e;
|
|
267
|
+
const target = payer != null ? payer : vote !== void 0 && !declared.has(id) ? vote : void 0;
|
|
268
|
+
if (target === void 0) return e;
|
|
188
269
|
const done = copies.get(e.key);
|
|
189
270
|
if (done) return done;
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
271
|
+
if (payer === void 0) {
|
|
272
|
+
warnOnce(
|
|
273
|
+
`pool-rebind:${poolId}:${id}`,
|
|
274
|
+
`Archivist: pool "${poolId}" members consume "${id}", which this character does not own; bound to the pool's resource "${target}"`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
const copy = { ...e, entity: { ...e.entity, consumes: { ...consumes, resource: target } } };
|
|
195
278
|
copies.set(e.key, copy);
|
|
196
279
|
return copy;
|
|
197
280
|
};
|
|
@@ -281,13 +364,49 @@ function rowMatches(row, it) {
|
|
|
281
364
|
if (c.variant) return c.variant.candidates.some((l) => bareEntitySlug(linkSlug(l)) === bareEntitySlug(it.slug));
|
|
282
365
|
return !!c.filter && matchesFilter(it.data, c.filter);
|
|
283
366
|
}
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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));
|
|
291
410
|
function candidateOrder(edition) {
|
|
292
411
|
return (a, b) => {
|
|
293
412
|
const ea = edition && lc(a.data.edition) === edition ? 0 : 1;
|
|
@@ -321,9 +440,10 @@ function candidatesFrom(installed, registry, opts) {
|
|
|
321
440
|
}
|
|
322
441
|
function resolveOptionRows(note, registry, opts = {}) {
|
|
323
442
|
var _a;
|
|
324
|
-
const
|
|
443
|
+
const ix = itemIndexOf(registry);
|
|
325
444
|
return ((_a = note.options) != null ? _a : []).map((row) => {
|
|
326
|
-
|
|
445
|
+
var _a2;
|
|
446
|
+
const rowItems = rowInstalledIn(row, ix);
|
|
327
447
|
const candidates = candidatesFrom(rowItems, registry, opts);
|
|
328
448
|
const anyVisible = candidates.length > 0 || candidatesFrom(rowItems, registry, { ...opts, exclude: [] }).length > 0;
|
|
329
449
|
const installed = rowItems.length > 0;
|
|
@@ -334,10 +454,23 @@ function resolveOptionRows(note, registry, opts = {}) {
|
|
|
334
454
|
...row.attunement !== void 0 ? { attunement: row.attunement } : {},
|
|
335
455
|
arm: row.creates.item ? "item" : row.creates.variant ? "variant" : "filter",
|
|
336
456
|
candidates,
|
|
337
|
-
availability: anyVisible ? "installed" : installed ? "hidden-only" : "not-installed"
|
|
457
|
+
availability: anyVisible ? "installed" : installed ? "hidden-only" : "not-installed",
|
|
458
|
+
...rowItemWords(row, candidates[0] ? ix.items[(_a2 = ix.bySlug.get(candidates[0])) != null ? _a2 : -1] : rowItems[0])
|
|
338
459
|
};
|
|
339
460
|
});
|
|
340
461
|
}
|
|
462
|
+
function rowItemWords(row, item) {
|
|
463
|
+
var _a, _b, _c, _d;
|
|
464
|
+
const f = row.creates.filter;
|
|
465
|
+
if (f) return { type: ((_a = f.types) == null ? void 0 : _a[0]) ? lc(f.types[0]) : null, rarity: f.rarity[0] ? lc(f.rarity[0]) : null, charges: null };
|
|
466
|
+
const data = (_b = item == null ? void 0 : item.data) != null ? _b : {};
|
|
467
|
+
const max = (_c = data.charges) == null ? void 0 : _c.max;
|
|
468
|
+
return {
|
|
469
|
+
type: lc(data.type) || null,
|
|
470
|
+
rarity: lc(data.rarity) || null,
|
|
471
|
+
charges: typeof max === "number" && max > 0 ? { max, ...typeof ((_d = data.charges) == null ? void 0 : _d.recharge) === "string" ? { recharge: data.charges.recharge } : {} } : null
|
|
472
|
+
};
|
|
473
|
+
}
|
|
341
474
|
function resolveCopyOption(note, param, ctx) {
|
|
342
475
|
var _a, _b, _c;
|
|
343
476
|
const rows = (_a = note.options) != null ? _a : [];
|
|
@@ -920,7 +1053,9 @@ export {
|
|
|
920
1053
|
infusionsEndedByClassEdit,
|
|
921
1054
|
reconcileClassEdit,
|
|
922
1055
|
eligibleTargets,
|
|
1056
|
+
bindFeatureConsumers,
|
|
923
1057
|
levelPrereqMax,
|
|
1058
|
+
poolAnchorLevel,
|
|
924
1059
|
resolvePool,
|
|
925
1060
|
resolveAllPools,
|
|
926
1061
|
strandedPicks
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
featureEffectSchema
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JXWNXIIZ.js";
|
|
4
4
|
|
|
5
5
|
// src/schemas/choice-schema.ts
|
|
6
6
|
import { z } from "zod";
|
|
@@ -16,7 +16,9 @@ var entityFilterSchema = z.object({
|
|
|
16
16
|
mundane: z.boolean().optional(),
|
|
17
17
|
list: z.string().min(1).optional(),
|
|
18
18
|
level: z.number().int().min(0).max(9).optional(),
|
|
19
|
-
edition: z.string().min(1).optional()
|
|
19
|
+
edition: z.string().min(1).optional(),
|
|
20
|
+
// D2b-A (design §2.2 step 1): Pact of the Tome's "level 1 ritual spells".
|
|
21
|
+
ritual: z.boolean().optional()
|
|
20
22
|
}).strict();
|
|
21
23
|
var inlineOptionSchema = z.lazy(() => z.object({
|
|
22
24
|
value: z.string().min(1),
|
|
@@ -40,7 +42,9 @@ var choiceSchema = z.lazy(() => z.discriminatedUnion("kind", [
|
|
|
40
42
|
count: z.number().int().positive().optional(),
|
|
41
43
|
entity_type: z.string().min(1),
|
|
42
44
|
from: z.array(z.string().min(1)).nonempty().optional(),
|
|
43
|
-
where: entityFilterSchema.optional()
|
|
45
|
+
where: entityFilterSchema.optional(),
|
|
46
|
+
// D2b-A: a pick that names a target (a blast's cantrip), never a grant.
|
|
47
|
+
target: z.literal(true).optional()
|
|
44
48
|
}).strict(),
|
|
45
49
|
z.object({
|
|
46
50
|
kind: z.literal("select-proficiency"),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseSpell
|
|
3
|
-
} from "./chunk-
|
|
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
|
-
|
|
12
|
+
const { material: _material, ...doc } = spell;
|
|
13
|
+
return yaml.dump(doc, { lineWidth: -1, sortKeys: false });
|
|
13
14
|
}
|
|
14
15
|
};
|
|
15
16
|
|