@archivist-gg/dnd5e 0.3.11 → 0.3.13

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 (164) hide show
  1. package/LICENSES/SRD.md +38 -3
  2. package/dist/armor/armor.codec.js +3 -3
  3. package/dist/armor/armor.parser.js +2 -2
  4. package/dist/armor/armor.schema.d.ts +1 -0
  5. package/dist/armor/armor.schema.js +1 -1
  6. package/dist/armor/armor.types.d.ts +2 -0
  7. package/dist/background/background.codec.js +5 -5
  8. package/dist/background/background.parser.js +4 -4
  9. package/dist/background/background.schema.js +3 -3
  10. package/dist/chunk-2LULMQTF.js +14 -0
  11. package/dist/{chunk-CBEKVAX4.js → chunk-4BZ47YJ4.js} +10 -3
  12. package/dist/{chunk-T2SNEJOE.js → chunk-4SLOJXJ4.js} +1 -1
  13. package/dist/{chunk-2DFENQIJ.js → chunk-5GKJAUMX.js} +2 -0
  14. package/dist/{chunk-J77CKYV3.js → chunk-5KGV6TF7.js} +1 -1
  15. package/dist/{chunk-CHUALUIX.js → chunk-6ZY3U6BQ.js} +1 -1
  16. package/dist/{chunk-DNAJRMNN.js → chunk-A54HHCWW.js} +6 -7
  17. package/dist/{chunk-2M34FLAY.js → chunk-BRGLDWUP.js} +3 -1
  18. package/dist/chunk-CM5CWO4K.js +735 -0
  19. package/dist/{chunk-3HX5VLWP.js → chunk-DPM6V7YX.js} +1 -1
  20. package/dist/chunk-EJP263OP.js +186 -0
  21. package/dist/{chunk-G247QXWW.js → chunk-EL6QPF6I.js} +1 -1
  22. package/dist/{chunk-SMLXZUBH.js → chunk-EYX4JMAU.js} +4 -1
  23. package/dist/{chunk-AW3EHF3L.js → chunk-FC4IGH5Y.js} +1 -1
  24. package/dist/{chunk-PI4LLJG4.js → chunk-FPGXFD2M.js} +2 -2
  25. package/dist/{chunk-23LZLBJK.js → chunk-G6MHJDVT.js} +6 -2
  26. package/dist/{chunk-NWDF5HG3.js → chunk-G7ED5H2G.js} +1 -1
  27. package/dist/chunk-H5PTKYXL.js +101 -0
  28. package/dist/{chunk-LWFTHJIE.js → chunk-HHBD4SQ6.js} +1 -1
  29. package/dist/{chunk-PP2UHLBA.js → chunk-HJQNK3J5.js} +8 -0
  30. package/dist/{chunk-JC5UNIVM.js → chunk-HS6FIOLP.js} +2 -0
  31. package/dist/chunk-IHE6KVC3.js +13 -0
  32. package/dist/{chunk-BLRW2UU6.js → chunk-J53ERF6K.js} +2 -2
  33. package/dist/{chunk-MQ747UCR.js → chunk-JNNNBEUH.js} +65 -2
  34. package/dist/{chunk-LDDCNPHD.js → chunk-K5NJBTE2.js} +1 -1
  35. package/dist/{chunk-4ISR76DH.js → chunk-KSTV7J2U.js} +9 -3
  36. package/dist/chunk-LBEXLN6I.js +91 -0
  37. package/dist/{chunk-UBBKSYBY.js → chunk-LHZXYFBU.js} +30 -1
  38. package/dist/{chunk-U3BABVJF.js → chunk-LVX6TQ3R.js} +2 -2
  39. package/dist/{chunk-R747YKS7.js → chunk-M2OWVAZG.js} +1 -1
  40. package/dist/{chunk-7YU4NPKR.js → chunk-MC5YJ4PB.js} +1 -1
  41. package/dist/{chunk-JCEV7KYN.js → chunk-MUIU7RZ3.js} +1 -1
  42. package/dist/{chunk-R2ZV233X.js → chunk-MYY2C5MB.js} +3 -3
  43. package/dist/{chunk-S56D6B6C.js → chunk-PLOSP4XQ.js} +3 -3
  44. package/dist/{chunk-JANNRGJC.js → chunk-PX25R3DK.js} +1 -1
  45. package/dist/{chunk-V373TRJE.js → chunk-Q5XTEYHD.js} +191 -125
  46. package/dist/{chunk-MAH6DCRI.js → chunk-Q6UOHCCT.js} +1 -1
  47. package/dist/{chunk-2INMRR3Q.js → chunk-QEO7LYTG.js} +215 -94
  48. package/dist/{chunk-PJMG5XV3.js → chunk-QSBJ6NEM.js} +1 -1
  49. package/dist/{chunk-BL3VELKP.js → chunk-R2V5LN3I.js} +1 -1
  50. package/dist/{chunk-4DOMDEBZ.js → chunk-RQNJXADQ.js} +1 -1
  51. package/dist/{chunk-GFZ7PITJ.js → chunk-S3YUXDSC.js} +6 -1
  52. package/dist/chunk-T5GZBKIQ.js +927 -0
  53. package/dist/{chunk-PZCE7DJM.js → chunk-UE6LKRXQ.js} +15 -1
  54. package/dist/{chunk-5N2GW72F.js → chunk-UZXG6QSY.js} +2 -1
  55. package/dist/{chunk-QHMWA3SZ.js → chunk-VDPRBUU5.js} +1 -1
  56. package/dist/{chunk-4XTNPNQB.js → chunk-VPYBK2MG.js} +1 -1
  57. package/dist/{chunk-V6LJFP4R.js → chunk-X7VZY4ON.js} +2 -2
  58. package/dist/{chunk-HZA7OE7N.js → chunk-XCFC7LGF.js} +2 -1
  59. package/dist/{chunk-BIA32MQ5.js → chunk-XJRNEQPD.js} +1 -1
  60. package/dist/{chunk-UIRMA5KM.js → chunk-YDGMBMYI.js} +1 -1
  61. package/dist/{chunk-6X4KUDMU.js → chunk-ZCTUYGNV.js} +7 -2
  62. package/dist/{chunk-XTRDCSJB.js → chunk-ZMJQUZQH.js} +4 -1
  63. package/dist/chunk-ZUQW5VBZ.js +89 -0
  64. package/dist/class/class.codec.js +7 -7
  65. package/dist/class/class.parser.js +6 -6
  66. package/dist/class/class.schema.d.ts +10 -0
  67. package/dist/class/class.schema.js +5 -5
  68. package/dist/entities/base-item-resolver.js +2 -2
  69. package/dist/entities/slug.d.ts +18 -8
  70. package/dist/entities/slug.js +3 -1
  71. package/dist/feat/feat.codec.js +5 -5
  72. package/dist/feat/feat.parser.js +4 -4
  73. package/dist/feat/feat.schema.d.ts +16 -3
  74. package/dist/feat/feat.schema.js +3 -3
  75. package/dist/index.js +7190 -4116
  76. package/dist/item/item.actions-map.d.ts +40 -5
  77. package/dist/item/item.actions-map.js +5 -84
  78. package/dist/item/item.attunement.js +3 -9
  79. package/dist/item/item.bonuses.js +5 -2
  80. package/dist/item/item.codec.js +3 -3
  81. package/dist/item/item.conditions.js +4 -1
  82. package/dist/item/item.conditions.types.d.ts +4 -0
  83. package/dist/item/item.cost.d.ts +3 -0
  84. package/dist/item/item.cost.js +29 -0
  85. package/dist/item/item.icon.d.ts +15 -0
  86. package/dist/item/item.icon.js +211 -0
  87. package/dist/item/item.parser.js +2 -2
  88. package/dist/item/item.schema.d.ts +16 -1
  89. package/dist/item/item.schema.js +3 -1
  90. package/dist/item/item.types.d.ts +9 -2
  91. package/dist/monster/monster.parser.js +5 -5
  92. package/dist/monster/monster.schema.js +4 -4
  93. package/dist/optional-feature/optional-feature.codec.js +4 -4
  94. package/dist/optional-feature/optional-feature.parser.js +3 -3
  95. package/dist/optional-feature/optional-feature.schema.d.ts +229 -3
  96. package/dist/optional-feature/optional-feature.schema.js +6 -2
  97. package/dist/pc/pc.additional-spells.js +2 -2
  98. package/dist/pc/pc.asi-flatten.d.ts +62 -1
  99. package/dist/pc/pc.asi-flatten.js +16 -0
  100. package/dist/pc/pc.conditions.js +1 -1
  101. package/dist/pc/pc.containment.d.ts +45 -0
  102. package/dist/pc/pc.containment.js +24 -0
  103. package/dist/pc/pc.decision-engine.d.ts +21 -3
  104. package/dist/pc/pc.decision-engine.js +21 -12
  105. package/dist/pc/pc.entry-overrides.d.ts +79 -0
  106. package/dist/pc/pc.entry-overrides.js +40 -0
  107. package/dist/pc/pc.equipment.d.ts +2 -2
  108. package/dist/pc/pc.equipment.js +18 -7
  109. package/dist/pc/pc.feature-effects.js +2 -2
  110. package/dist/pc/pc.infusions.d.ts +220 -0
  111. package/dist/pc/pc.infusions.js +64 -0
  112. package/dist/pc/pc.parser.js +18 -1
  113. package/dist/pc/pc.pools.d.ts +1 -1
  114. package/dist/pc/pc.pools.js +11 -3
  115. package/dist/pc/pc.proficiencies.js +19 -12
  116. package/dist/pc/pc.recalc.d.ts +39 -36
  117. package/dist/pc/pc.recalc.js +58 -30
  118. package/dist/pc/pc.resolver.js +19 -12
  119. package/dist/pc/pc.resource-seed.js +1 -1
  120. package/dist/pc/pc.resources.d.ts +6 -1
  121. package/dist/pc/pc.resources.js +3 -1
  122. package/dist/pc/pc.rest.d.ts +21 -1
  123. package/dist/pc/pc.rest.js +52 -19
  124. package/dist/pc/pc.schema.d.ts +14 -0
  125. package/dist/pc/pc.schema.js +1 -1
  126. package/dist/pc/pc.slotting.d.ts +36 -3
  127. package/dist/pc/pc.slotting.js +13 -5
  128. package/dist/pc/pc.tool-picks.d.ts +51 -0
  129. package/dist/pc/pc.tool-picks.js +18 -0
  130. package/dist/pc/pc.types.d.ts +70 -12
  131. package/dist/pc/pool-layout.d.ts +14 -0
  132. package/dist/pc/pool-layout.js +7 -3
  133. package/dist/race/race.codec.js +6 -6
  134. package/dist/race/race.parser.js +5 -5
  135. package/dist/race/race.schema.js +4 -4
  136. package/dist/schemas/attack-schema.d.ts +1 -1
  137. package/dist/schemas/choice-schema.js +2 -2
  138. package/dist/schemas/feature-effect-schema.d.ts +16 -3
  139. package/dist/schemas/feature-effect-schema.js +1 -1
  140. package/dist/schemas/feature-schema.js +3 -3
  141. package/dist/schemas/selection-pool-schema.d.ts +10 -0
  142. package/dist/schemas/selection-pool-schema.js +1 -1
  143. package/dist/srd/retired-items.d.ts +14 -0
  144. package/dist/srd/retired-items.js +11 -0
  145. package/dist/subclass/subclass.codec.js +7 -7
  146. package/dist/subclass/subclass.parser.js +6 -6
  147. package/dist/subclass/subclass.schema.d.ts +10 -0
  148. package/dist/subclass/subclass.schema.js +5 -5
  149. package/dist/types/choice.d.ts +8 -0
  150. package/dist/types/equipment-grant.d.ts +18 -1
  151. package/dist/types/equipment-grant.js +12 -0
  152. package/dist/types/feature-effect.d.ts +6 -1
  153. package/dist/types/optional-feature.types.d.ts +79 -1
  154. package/dist/types/selection-pool.d.ts +16 -1
  155. package/dist/weapon/weapon.classify.d.ts +10 -8
  156. package/dist/weapon/weapon.classify.js +2 -2
  157. package/dist/weapon/weapon.codec.js +3 -3
  158. package/dist/weapon/weapon.parser.js +2 -2
  159. package/dist/weapon/weapon.schema.d.ts +1 -0
  160. package/dist/weapon/weapon.schema.js +1 -1
  161. package/dist/weapon/weapon.types.d.ts +2 -0
  162. package/package.json +1 -1
  163. package/dist/chunk-O4SRD3J5.js +0 -165
  164. package/dist/chunk-WXWPK3LI.js +0 -55
@@ -0,0 +1,927 @@
1
+ import {
2
+ parseCastingTime
3
+ } from "./chunk-W6GC6OKK.js";
4
+ import {
5
+ derivePoolLayout
6
+ } from "./chunk-HJQNK3J5.js";
7
+ import {
8
+ readTableColumn
9
+ } from "./chunk-IUAAXXNO.js";
10
+ import {
11
+ findItemAction,
12
+ resolveItemAction
13
+ } from "./chunk-ZUQW5VBZ.js";
14
+ import {
15
+ requiresAttunement
16
+ } from "./chunk-IHE6KVC3.js";
17
+ import {
18
+ defaultSlotForType,
19
+ effectiveArmor,
20
+ isItemEntity,
21
+ isShieldArmor,
22
+ resolveEntityForEntry,
23
+ slotAcTerms
24
+ } from "./chunk-LHZXYFBU.js";
25
+ import {
26
+ warnOnce
27
+ } from "./chunk-LVQLWRCU.js";
28
+ import {
29
+ keptSlugFor,
30
+ resolveBaseItem
31
+ } from "./chunk-CM5CWO4K.js";
32
+ import {
33
+ bareEntitySlug,
34
+ slugify,
35
+ wikilinkTailSlug
36
+ } from "./chunk-ZCTUYGNV.js";
37
+ import {
38
+ parseInlineTag
39
+ } from "./chunk-3TM34RSJ.js";
40
+ import {
41
+ isCanonicalDamageType
42
+ } from "./chunk-PDJSUH24.js";
43
+
44
+ // src/pc/pc.pools.ts
45
+ function levelPrereqMax(d) {
46
+ var _a;
47
+ return ((_a = d.prerequisites) != null ? _a : []).filter((p) => p.kind === "level").reduce((m, p) => Math.max(m, p.min), 0);
48
+ }
49
+ function resolvePool(rc, classIndex, pool, registry, index, edition) {
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;
57
+ let anchorLevel = 1;
58
+ let columnSeen = false;
59
+ for (let l = 1; l <= 20; l++) {
60
+ const c = readTableColumn(entity.table, l, [col]);
61
+ if (c != null) columnSeen = true;
62
+ if (c != null && c >= 1) {
63
+ anchorLevel = l;
64
+ break;
65
+ }
66
+ }
67
+ if (!columnSeen) {
68
+ warnOnce(
69
+ `pool-column:${pool.id}`,
70
+ `Archivist: pool "${pool.id}" declares count column "${col}", which "${entity.slug}"'s class table carries at no level; the pool resolves count 0 at anchor level 1`
71
+ );
72
+ }
73
+ const ft = pool.source.where.feature_type;
74
+ const all = registry.search("", "optional-feature", Number.POSITIVE_INFINITY).slice().sort((a, b) => a.name.localeCompare(b.name) || a.slug.localeCompare(b.slug));
75
+ const entryOf = (slug, entity2, compendium) => ({ slug, entity: entity2, ...compendium ? { compendium } : {}, key: slug, param: null, itemBound: isItemBound(entity2) });
76
+ const byBare = /* @__PURE__ */ new Map();
77
+ const candidates = /* @__PURE__ */ new Map();
78
+ for (const e of all) {
79
+ const d = e.data;
80
+ const entry = entryOf(e.slug, d, e.compendium);
81
+ const bare = bareEntitySlug(e.slug);
82
+ byBare.set(bare, entry);
83
+ if (d.feature_type !== ft) continue;
84
+ if (!((_b = d.available_to) != null ? _b : []).some((l) => wikilinkTailSlug(l) === ownerBare)) continue;
85
+ if (levelPrereqMax(d) > level) continue;
86
+ const group = candidates.get(bare);
87
+ if (group) group.push(entry);
88
+ else candidates.set(bare, [entry]);
89
+ }
90
+ const available = [];
91
+ for (const [bare, group] of candidates) {
92
+ if (group.length === 1) {
93
+ available.push(group[0]);
94
+ continue;
95
+ }
96
+ const owned = group.find((e) => e.slug.split("_")[0] === ownerPrefix);
97
+ if (!owned) {
98
+ warnOnce(
99
+ `pool-collapse:${bare}`,
100
+ `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
+ );
102
+ }
103
+ available.push(owned != null ? owned : group[0]);
104
+ }
105
+ const grantDecls = [
106
+ ...(_d = (_c = rc.entity) == null ? void 0 : _c.pool_grants) != null ? _d : [],
107
+ ...(_f = (_e = rc.subclass) == null ? void 0 : _e.pool_grants) != null ? _f : []
108
+ ];
109
+ const grantsRaw = grantDecls.filter((g) => g.pool === pool.id).flatMap((g) => g.grants).filter((g) => g.at_level <= level).map((g) => byBare.get(wikilinkTailSlug(g.feature))).filter((e) => e != null);
110
+ const grantedBare = /* @__PURE__ */ new Set();
111
+ const grants = grantsRaw.filter((e) => {
112
+ const key = bareEntitySlug(e.slug);
113
+ if (grantedBare.has(key)) return false;
114
+ grantedBare.add(key);
115
+ return true;
116
+ });
117
+ const bySlugAvailable = new Map(available.map((e) => [e.slug, e]));
118
+ const byBareAvailable = new Map(available.map((e) => [bareEntitySlug(e.slug), e]));
119
+ const resolveStored = (s) => {
120
+ const exact = bySlugAvailable.get(s);
121
+ if (exact) return exact;
122
+ const survivor = byBareAvailable.get(bareEntitySlug(s));
123
+ if (survivor) return survivor;
124
+ const full = registry.getByTypeAndSlug("optional-feature", s);
125
+ if (full) return entryOf(full.slug, full.data, full.compendium);
126
+ return byBare.get(bareEntitySlug(s));
127
+ };
128
+ const raw = (_g = rc.choices[anchorLevel]) == null ? void 0 : _g[pool.id];
129
+ const selectedSlugs = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : [];
130
+ const selected = [];
131
+ const seenKeys = /* @__PURE__ */ new Set();
132
+ for (const s of selectedSlugs) {
133
+ const { slugPart, param } = parsePickKey(s);
134
+ const entry = resolveStored(slugPart);
135
+ if (!entry) continue;
136
+ const repeatable = entry.entity.repeatable === true;
137
+ if (!repeatable && param !== null) {
138
+ warnOnce(`pick-param:${pool.id}:${s}`, `Archivist: pool "${pool.id}" pick "${s}" names a parameter, but "${entry.slug}" is not repeatable; the parameter is ignored`);
139
+ }
140
+ const nkey = normalizedPickKey(s, repeatable);
141
+ if (grantedBare.has(bareEntitySlug(entry.slug)) || seenKeys.has(nkey)) continue;
142
+ seenKeys.add(nkey);
143
+ selected.push({
144
+ ...entry,
145
+ key: repeatable || param !== null ? s : entry.slug,
146
+ param: repeatable ? param : null,
147
+ ...repeatable ? { option: resolveCopyOption(entry.entity, param, { classLevel: level, registry, edition }) } : {}
148
+ });
149
+ }
150
+ const members = [...available, ...grants];
151
+ const layout = derivePoolLayout(members);
152
+ const votes = /* @__PURE__ */ new Map();
153
+ for (const e of members) {
154
+ const id = (_h = e.entity.consumes) == null ? void 0 : _h.resource;
155
+ if (!id || !index.has(id)) continue;
156
+ votes.set(id, ((_i = votes.get(id)) != null ? _i : 0) + 1);
157
+ }
158
+ let resource;
159
+ let bestN = 0;
160
+ for (const [id, n] of votes) if (n > bestN) {
161
+ resource = id;
162
+ bestN = n;
163
+ }
164
+ const bind = resource ? poolRebinder(rc, pool.id, resource, index) : (e) => e;
165
+ return {
166
+ id: pool.id,
167
+ label: pool.label,
168
+ classIndex,
169
+ count,
170
+ anchorLevel,
171
+ selected: selected.map(bind),
172
+ available: available.map(bind),
173
+ grants: grants.map(bind),
174
+ ...layout ? { layout } : {},
175
+ ...resource ? { resource } : {},
176
+ ...pool.count.label ? { countLabel: pool.count.label } : {},
177
+ ...pool.active ? { active: pool.active } : {},
178
+ ...pool.head_resource ? { headResource: pool.head_resource } : {}
179
+ };
180
+ }
181
+ function poolRebinder(rc, poolId, vote, index) {
182
+ const declared = declaredResourceIds(rc);
183
+ const copies = /* @__PURE__ */ new Map();
184
+ return (e) => {
185
+ const consumes = e.entity.consumes;
186
+ const id = consumes == null ? void 0 : consumes.resource;
187
+ if (!consumes || typeof id !== "string" || id.length === 0 || index.has(id) || declared.has(id)) return e;
188
+ const done = copies.get(e.key);
189
+ if (done) return done;
190
+ warnOnce(
191
+ `pool-rebind:${poolId}:${id}`,
192
+ `Archivist: pool "${poolId}" members consume "${id}", which this character does not own; bound to the pool's resource "${vote}"`
193
+ );
194
+ const copy = { ...e, entity: { ...e.entity, consumes: { ...consumes, resource: vote } } };
195
+ copies.set(e.key, copy);
196
+ return copy;
197
+ };
198
+ }
199
+ function declaredResourceIds(rc) {
200
+ var _a;
201
+ const out = /* @__PURE__ */ new Set();
202
+ const add = (list) => {
203
+ for (const r of list != null ? list : []) if (typeof (r == null ? void 0 : r.id) === "string") out.add(r.id);
204
+ };
205
+ for (const owner of [rc.entity, rc.subclass]) {
206
+ if (!owner) continue;
207
+ add(owner.resources);
208
+ for (const features of Object.values((_a = owner.features_by_level) != null ? _a : {})) for (const f of features != null ? features : []) add(f.resources);
209
+ }
210
+ return out;
211
+ }
212
+ function resolveAllPools(resolved, registry, index) {
213
+ const out = [];
214
+ resolved.classes.forEach((rc, classIndex) => {
215
+ var _a, _b, _c, _d;
216
+ if (!rc.entity) return;
217
+ const decls = [...(_a = rc.entity.selection_pools) != null ? _a : [], ...(_c = (_b = rc.subclass) == null ? void 0 : _b.selection_pools) != null ? _c : []];
218
+ const seen = /* @__PURE__ */ new Set();
219
+ for (const pool of decls) {
220
+ if (seen.has(pool.id)) continue;
221
+ seen.add(pool.id);
222
+ out.push(resolvePool(rc, classIndex, pool, registry, index, (_d = resolved.definition) == null ? void 0 : _d.edition));
223
+ }
224
+ });
225
+ return out;
226
+ }
227
+ function strandedPicks(pool) {
228
+ const inAvailable = new Set(pool.available.map((e) => e.slug));
229
+ return pool.selected.filter((e) => !inAvailable.has(e.slug));
230
+ }
231
+
232
+ // src/pc/pc.infusions.ts
233
+ function poolCountLabel(pool) {
234
+ var _a;
235
+ return (_a = pool.countLabel) != null ? _a : "Known";
236
+ }
237
+ function poolActiveWords(pool) {
238
+ var _a, _b, _c;
239
+ const a = pool.active;
240
+ if (!a) return null;
241
+ return {
242
+ column: a.column,
243
+ label: a.label,
244
+ verb: a.verb,
245
+ counterLabel: (_a = a.counter_label) != null ? _a : a.label,
246
+ createLabel: (_b = a.create_label) != null ? _b : "Created",
247
+ createVerb: (_c = a.create_verb) != null ? _c : "Create"
248
+ };
249
+ }
250
+ function parsePickKey(stored) {
251
+ const i = stored.indexOf("#");
252
+ return i === -1 ? { slugPart: stored, param: null } : { slugPart: stored.slice(0, i), param: stored.slice(i + 1) };
253
+ }
254
+ function normalizedPickKey(stored, repeatable) {
255
+ const { slugPart, param } = parsePickKey(stored);
256
+ return repeatable ? `${bareEntitySlug(slugPart)}#${param != null ? param : ""}` : bareEntitySlug(slugPart);
257
+ }
258
+ function markerKey(by) {
259
+ const { slugPart, param } = parsePickKey(by);
260
+ return param === null ? bareEntitySlug(slugPart) : `${bareEntitySlug(slugPart)}#${param}`;
261
+ }
262
+ function isItemBound(note) {
263
+ var _a;
264
+ return !!note.infuse || note.repeatable === true && !!((_a = note.options) == null ? void 0 : _a.some((r) => !!r.creates));
265
+ }
266
+ var SRD_BOOKS = /* @__PURE__ */ new Set(["SRD 2024", "SRD 5e"]);
267
+ var lc = (v) => String(v != null ? v : "").trim().toLowerCase();
268
+ var linkSlug = (link) => link.replace(/^\[\[/, "").replace(/\]\]$/, "").split("|")[0];
269
+ var itemsOf = (registry) => registry.search("", "item", Number.POSITIVE_INFINITY);
270
+ function matchesFilter(data, f) {
271
+ var _a;
272
+ const type = lc(data.type);
273
+ if (!f.rarity.map(lc).includes(lc(data.rarity))) return false;
274
+ if (f.types && !f.types.map(lc).includes(type)) return false;
275
+ if ((_a = f.exclude_types) == null ? void 0 : _a.map(lc).includes(type)) return false;
276
+ return !(f.cursed === false && data.cursed === true);
277
+ }
278
+ function rowMatches(row, it) {
279
+ const c = row.creates;
280
+ if (c.item) return lc(it.name) === lc(row.name) || it.slug === linkSlug(c.item);
281
+ if (c.variant) return c.variant.candidates.some((l) => bareEntitySlug(linkSlug(l)) === bareEntitySlug(it.slug));
282
+ return !!c.filter && matchesFilter(it.data, c.filter);
283
+ }
284
+ var rowInstalledIn = (row, items) => {
285
+ const v = row.creates.item ? void 0 : row.creates.variant;
286
+ if (!v) return items.filter((it) => rowMatches(row, it));
287
+ const bares = new Set(v.candidates.map((l) => bareEntitySlug(linkSlug(l))));
288
+ return items.filter((it) => bares.has(bareEntitySlug(it.slug)));
289
+ };
290
+ var rowInstalled = (row, registry) => rowInstalledIn(row, itemsOf(registry));
291
+ function candidateOrder(edition) {
292
+ return (a, b) => {
293
+ const ea = edition && lc(a.data.edition) === edition ? 0 : 1;
294
+ const eb = edition && lc(b.data.edition) === edition ? 0 : 1;
295
+ if (ea !== eb) return ea - eb;
296
+ const sa = SRD_BOOKS.has(a.compendium) ? 0 : 1, sb = SRD_BOOKS.has(b.compendium) ? 0 : 1;
297
+ if (sa !== sb) return sa - sb;
298
+ return a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0;
299
+ };
300
+ }
301
+ function optionCandidates(row, registry, opts = {}) {
302
+ return candidatesFrom(rowInstalled(row, registry), registry, opts);
303
+ }
304
+ function candidatesFrom(installed, registry, opts) {
305
+ var _a, _b;
306
+ const visible = (_a = opts.isEntityVisible) != null ? _a : (() => true);
307
+ const excluded = ((_b = opts.exclude) != null ? _b : []).map((s) => {
308
+ var _a2;
309
+ return registry.getByTypeAndSlug("item", (_a2 = keptSlugFor(s)) != null ? _a2 : s);
310
+ }).filter((e) => !!e);
311
+ const exNames = new Set(excluded.map((e) => lc(e.name))), exBares = new Set(excluded.map((e) => bareEntitySlug(e.slug)));
312
+ const seen = /* @__PURE__ */ new Set(), out = [];
313
+ for (const e of installed.filter(visible).sort(candidateOrder(opts.edition))) {
314
+ const bare = bareEntitySlug(e.slug);
315
+ if (seen.has(bare)) continue;
316
+ seen.add(bare);
317
+ if (exNames.has(lc(e.name)) || exBares.has(bare)) continue;
318
+ out.push(e.slug);
319
+ }
320
+ return out;
321
+ }
322
+ function resolveOptionRows(note, registry, opts = {}) {
323
+ var _a;
324
+ const items = itemsOf(registry);
325
+ return ((_a = note.options) != null ? _a : []).map((row) => {
326
+ const rowItems = rowInstalledIn(row, items);
327
+ const candidates = candidatesFrom(rowItems, registry, opts);
328
+ const anyVisible = candidates.length > 0 || candidatesFrom(rowItems, registry, { ...opts, exclude: [] }).length > 0;
329
+ const installed = rowItems.length > 0;
330
+ return {
331
+ key: row.key,
332
+ name: row.name,
333
+ minLevel: row.min_level,
334
+ ...row.attunement !== void 0 ? { attunement: row.attunement } : {},
335
+ arm: row.creates.item ? "item" : row.creates.variant ? "variant" : "filter",
336
+ candidates,
337
+ availability: anyVisible ? "installed" : installed ? "hidden-only" : "not-installed"
338
+ };
339
+ });
340
+ }
341
+ function resolveCopyOption(note, param, ctx) {
342
+ var _a, _b, _c;
343
+ const rows = (_a = note.options) != null ? _a : [];
344
+ if (param === null || param === "") return { row: null, status: "needs-choice" };
345
+ if (note.item_choice === "create") {
346
+ const row = rows.find((r) => r.key === param);
347
+ if (!row) return { row: null, status: "invalid" };
348
+ const installed = rowInstalled(row, ctx.registry).sort(candidateOrder(ctx.edition));
349
+ if (installed.length === 0) return { row, status: "not-installed" };
350
+ const own = row.creates.item ? linkSlug(row.creates.item) : void 0;
351
+ const item = row.creates.item ? ((_b = installed.find((e) => e.slug === own)) != null ? _b : installed[0]).slug : void 0;
352
+ return { row, ...item ? { item } : {}, status: row.min_level <= ctx.classLevel ? "ok" : "level" };
353
+ }
354
+ const slug = (_c = keptSlugFor(param)) != null ? _c : param;
355
+ const it = ctx.registry.getByTypeAndSlug("item", slug);
356
+ if (!it) return { row: null, item: slug, status: "not-installed" };
357
+ const matching = rows.filter((r) => rowMatches(r, it)).sort((a, b) => a.min_level - b.min_level);
358
+ if (matching.length === 0) return { row: null, item: slug, status: "invalid" };
359
+ return { row: matching[0], item: slug, status: matching[0].min_level <= ctx.classLevel ? "ok" : "level" };
360
+ }
361
+ var MUNDANE = /* @__PURE__ */ new Set(["", "none", "mundane"]);
362
+ var isMagic = (data) => {
363
+ const r = data == null ? void 0 : data.rarity;
364
+ return typeof r === "string" && !MUNDANE.has(r.trim().toLowerCase());
365
+ };
366
+ var plainText = (item) => item.replace(/^\[\[/, "").replace(/\]\]$/, "").split("|").pop().split("/").pop();
367
+ function scaledInfuseItem(infuse, classLevel) {
368
+ var _a, _b;
369
+ let item = { ...(_a = infuse.item) != null ? _a : {} };
370
+ const rows = [...(_b = infuse.scales_at) != null ? _b : []].sort((a, b) => a.level - b.level);
371
+ for (const row of rows) if (row.level <= classLevel) item = { ...item, ...row.item };
372
+ return item;
373
+ }
374
+ function wrapInfused(entry, note, classLevel, registry) {
375
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
376
+ const infuse = note.infuse;
377
+ const item = scaledInfuseItem(infuse, classLevel);
378
+ const resist = [...(_a = item.resist) != null ? _a : [], ...((_b = infuse.choice) == null ? void 0 : _b.kind) === "damage-type" && ((_c = entry.infusion) == null ? void 0 : _c.choice) ? [entry.infusion.choice] : []];
379
+ const overlay = {
380
+ ...item.bonuses ? { bonuses: item.bonuses } : {},
381
+ resist,
382
+ ...item.charges ? { charges: item.charges } : {},
383
+ ...item.action ? { action: item.action } : {},
384
+ ...item.range ? { range: item.range } : {},
385
+ ...infuse.attunement ? { attunement: { required: true } } : {}
386
+ };
387
+ const base = resolveBaseItem(entry.item, registry);
388
+ const data = (_d = base == null ? void 0 : base.data) != null ? _d : null;
389
+ if (base && isMagic(data)) {
390
+ const b = data;
391
+ const bonuses = { ...(_e = b.bonuses) != null ? _e : {} };
392
+ for (const [k, v] of Object.entries((_f = item.bonuses) != null ? _f : {})) if (typeof v === "number") bonuses[k] = (typeof bonuses[k] === "number" ? bonuses[k] : 0) + v;
393
+ return { entity: {
394
+ ...b,
395
+ bonuses,
396
+ resist: [.../* @__PURE__ */ new Set([...(_g = b.resist) != null ? _g : [], ...resist])],
397
+ attunement: { required: requiresAttunement(b) || !!infuse.attunement }
398
+ }, magicBase: true };
399
+ }
400
+ const rarity = (_h = infuse.rarity) != null ? _h : "unknown";
401
+ if (!base) {
402
+ const text = plainText(entry.item);
403
+ return { entity: { slug: `infused:${slugify(text)}`, name: text, type: "wondrous item", rarity, ...overlay, description: "" }, magicBase: false };
404
+ }
405
+ const name = String((_i = data == null ? void 0 : data.name) != null ? _i : base.name), description = String((_j = data == null ? void 0 : data.description) != null ? _j : "");
406
+ const slotted = base.entityType === "armor" || base.entityType === "weapon" ? base : typeof (data == null ? void 0 : data.base_item) === "string" ? resolveBaseItem(data.base_item, registry) : null;
407
+ if (slotted && (slotted.entityType === "armor" || slotted.entityType === "weapon")) {
408
+ const type = slotted.entityType === "weapon" ? "weapon" : isShieldArmor(slotted.data) ? "shield" : "armor";
409
+ return { entity: { slug: base.slug, name, type, rarity, base_item: `[[${slotted.slug}]]`, ...overlay, description }, magicBase: false };
410
+ }
411
+ return { entity: { slug: base.slug, name, type: String((_k = data == null ? void 0 : data.type) != null ? _k : "adventuring gear"), rarity, ...overlay, description }, magicBase: false };
412
+ }
413
+ function resolveEntryEntity(entry, index, registry, infusions) {
414
+ const view = !infusions ? void 0 : "views" in infusions ? infusions.views.get(index) : infusions.index === index ? infusions : void 0;
415
+ if (view) return { entity: view.entity, entityType: view.entityType };
416
+ return resolveEntityForEntry(entry.item, registry);
417
+ }
418
+ function buildInfusionContext(character, resolved, registry) {
419
+ var _a, _b, _c, _d, _e;
420
+ const equipment = (_a = character.equipment) != null ? _a : [];
421
+ const pools = ((_b = resolved.pools) != null ? _b : []).filter((p) => !!p.active || p.selected.some((e) => e.itemBound));
422
+ if (pools.length === 0 && !equipment.some((e) => !!e.infusion)) return void 0;
423
+ const owners = /* @__PURE__ */ new Map();
424
+ for (const pool of pools) for (const entry of pool.selected) if (entry.itemBound) owners.set(normalizedPickKey(entry.key, entry.entity.repeatable === true), { pool, entry });
425
+ const views = /* @__PURE__ */ new Map(), flags = [];
426
+ const newest = /* @__PURE__ */ new Map();
427
+ equipment.forEach((e, i) => {
428
+ if (!e.infusion) return;
429
+ const k = markerKey(e.infusion.by);
430
+ const j = newest.get(k);
431
+ if (j === void 0 || equipment[j].infusion.seq < e.infusion.seq) newest.set(k, i);
432
+ });
433
+ equipment.forEach((e, i) => {
434
+ var _a2, _b2, _c2;
435
+ const m = e.infusion;
436
+ if (!m) return;
437
+ const owner = owners.get(markerKey(m.by));
438
+ const nullView = () => {
439
+ var _a3, _b3, _c3, _d2, _e2;
440
+ return {
441
+ index: i,
442
+ kind: m.kind,
443
+ entity: null,
444
+ entityType: null,
445
+ key: m.by,
446
+ seq: m.seq,
447
+ poolId: (_a3 = owner == null ? void 0 : owner.pool.id) != null ? _a3 : "",
448
+ classIndex: (_b3 = owner == null ? void 0 : owner.pool.classIndex) != null ? _b3 : -1,
449
+ noteSlug: (_c3 = owner == null ? void 0 : owner.entry.slug) != null ? _c3 : "",
450
+ noteName: (_d2 = owner == null ? void 0 : owner.entry.entity.name) != null ? _d2 : "",
451
+ sourceLabel: (_e2 = owner == null ? void 0 : owner.entry.entity.name) != null ? _e2 : ""
452
+ };
453
+ };
454
+ if (!owner) {
455
+ flags.push({ index: i, reason: "orphan" });
456
+ if (m.kind === "created") views.set(i, nullView());
457
+ return;
458
+ }
459
+ if (newest.get(markerKey(m.by)) !== i) {
460
+ flags.push({ index: i, reason: "duplicate" });
461
+ if (m.kind === "created") views.set(i, nullView());
462
+ return;
463
+ }
464
+ if (((_a2 = e.qty) != null ? _a2 : 1) > 1) flags.push({ index: i, reason: "stack" });
465
+ const classLevel = (_c2 = (_b2 = resolved.classes[owner.pool.classIndex]) == null ? void 0 : _b2.level) != null ? _c2 : 0;
466
+ const base = {
467
+ index: i,
468
+ kind: m.kind,
469
+ key: m.by,
470
+ seq: m.seq,
471
+ poolId: owner.pool.id,
472
+ classIndex: owner.pool.classIndex,
473
+ noteSlug: owner.entry.slug,
474
+ noteName: owner.entry.entity.name,
475
+ sourceLabel: owner.entry.entity.name
476
+ };
477
+ if (m.kind === "created") {
478
+ const own = resolveEntityForEntry(e.item, registry);
479
+ const it = resolveBaseItem(e.item, registry);
480
+ const opt = owner.entry.option;
481
+ const fits = !!it && !!(opt == null ? void 0 : opt.row) && (opt.item === it.slug || rowMatches(opt.row, it));
482
+ if (!fits) flags.push({ index: i, reason: "mismatch" });
483
+ views.set(i, { ...base, entity: own.entity, entityType: own.entityType });
484
+ return;
485
+ }
486
+ if (!owner.entry.entity.infuse) {
487
+ flags.push({ index: i, reason: "orphan" });
488
+ return;
489
+ }
490
+ const w = wrapInfused(e, owner.entry.entity, classLevel, registry);
491
+ if (w.magicBase) flags.push({ index: i, reason: "magic-base" });
492
+ views.set(i, { ...base, entity: w.entity, entityType: "item" });
493
+ });
494
+ const counters = /* @__PURE__ */ new Map();
495
+ for (const pool of pools) {
496
+ const words = poolActiveWords(pool);
497
+ if (!words) continue;
498
+ const rc = resolved.classes[pool.classIndex];
499
+ const max = (_e = readTableColumn((_c = rc == null ? void 0 : rc.entity) == null ? void 0 : _c.table, (_d = rc == null ? void 0 : rc.level) != null ? _d : 0, [words.column])) != null ? _e : 0;
500
+ const used = [...views.values()].filter((v) => v.poolId === pool.id && v.entity !== null).length;
501
+ counters.set(pool.id, { label: words.counterLabel, used, max, over: used > max });
502
+ }
503
+ return { views, counters, flags };
504
+ }
505
+ function entryCharges(entry, view, registry) {
506
+ return effectiveItemCharges(entry, registry, view);
507
+ }
508
+ function withEntryCharges(entry, view, current, registry) {
509
+ var _a, _b, _c, _d, _e, _f;
510
+ const overlayMax = (view == null ? void 0 : view.kind) === "infused" ? itemChargesMax(view.entity) : void 0;
511
+ if (overlayMax !== void 0 && entry.infusion) return { ...entry, infusion: { ...entry.infusion, charges: { current: Math.max(0, Math.min(current, overlayMax)) } } };
512
+ const max = (_e = (_d = (_b = (_a = entry.state) == null ? void 0 : _a.charges) == null ? void 0 : _b.max) != null ? _d : (_c = effectiveItemCharges(entry, registry, view)) == null ? void 0 : _c.max) != null ? _e : Math.max(0, current);
513
+ return { ...entry, state: { ...(_f = entry.state) != null ? _f : {}, charges: { current: Math.max(0, Math.min(current, max)), max } } };
514
+ }
515
+ function isMarkedEntry(entry) {
516
+ return !!entry.infusion;
517
+ }
518
+ function splitOne(equipment, index) {
519
+ var _a, _b;
520
+ const src = equipment[index];
521
+ if (!src || ((_a = src.qty) != null ? _a : 1) <= 1) return [...equipment];
522
+ const one = { item: src.item, qty: 1, ...src.in !== void 0 ? { in: src.in } : {}, ...src.overrides ? { overrides: src.overrides } : {} };
523
+ return [...equipment.slice(0, index), { ...src, qty: ((_b = src.qty) != null ? _b : 1) - 1 }, one, ...equipment.slice(index + 1)];
524
+ }
525
+ var refuse = (reason) => ({ ok: false, reason });
526
+ function endInfusion(equipment, index, registry) {
527
+ const entry = equipment[index];
528
+ if (!(entry == null ? void 0 : entry.infusion)) return { equipment: [...equipment], ended: [] };
529
+ const { kind, by } = entry.infusion;
530
+ if (kind === "infused") {
531
+ const { infusion: _m, ...rest } = entry;
532
+ const keep = requiresAttunement(resolveEntryEntity(rest, index, registry).entity);
533
+ const next = keep ? rest : (({ attuned: _a, ...r }) => r)(rest);
534
+ return { equipment: equipment.map((e, i) => i === index ? next : e), ended: [{ index, key: by, kind, item: entry.item }] };
535
+ }
536
+ const moved = equipment.map((e, i) => {
537
+ if (i === index || entry.id === void 0 || e.in !== entry.id) return e;
538
+ if (entry.in === void 0) {
539
+ const { in: _in, ...r } = e;
540
+ return r;
541
+ }
542
+ return { ...e, in: entry.in };
543
+ });
544
+ return { equipment: moved.filter((_, i) => i !== index), ended: [{ index: -1, key: by, kind, item: entry.item }] };
545
+ }
546
+ var pickKeyOf = (e) => normalizedPickKey(e.key, e.entity.repeatable === true);
547
+ var nextSeq = (equipment) => equipment.reduce((m, e) => {
548
+ var _a, _b;
549
+ return Math.max(m, (_b = (_a = e.infusion) == null ? void 0 : _a.seq) != null ? _b : 0);
550
+ }, 0) + 1;
551
+ function ownedPick(resolved, key) {
552
+ var _a, _b;
553
+ const k = markerKey(key);
554
+ for (const pool of (_a = resolved.pools) != null ? _a : []) {
555
+ const entry = pool.selected.find((e) => e.itemBound && pickKeyOf(e) === k);
556
+ if (entry) return { pool, entry, stranded: strandedPicks(pool).includes(entry) || ((_b = entry.option) == null ? void 0 : _b.status) === "level" };
557
+ }
558
+ return null;
559
+ }
560
+ var wordsOf = (...texts) => texts.map(lc).join(" ").split(/[^a-z]+/).filter((w) => w !== "");
561
+ function shapeOf(found, text, registry) {
562
+ var _a, _b, _c;
563
+ if (!found) return { kind: "gear", words: wordsOf(text) };
564
+ const data = (_a = found.data) != null ? _a : {};
565
+ const words = wordsOf((_b = data.name) != null ? _b : text, data.type);
566
+ const slotted = found.entityType === "armor" || found.entityType === "weapon" ? found : typeof data.base_item === "string" ? resolveBaseItem(data.base_item, registry) : null;
567
+ if ((slotted == null ? void 0 : slotted.entityType) === "armor") {
568
+ const armor = slotted.data;
569
+ return { kind: isShieldArmor(armor) ? "shield" : "armor", words, category: lc(armor.category) };
570
+ }
571
+ if ((slotted == null ? void 0 : slotted.entityType) === "weapon") {
572
+ const w = slotted.data;
573
+ return { kind: "weapon", words, category: lc(w.category), properties: ((_c = w.properties) != null ? _c : []).filter((p) => typeof p === "string").map(lc) };
574
+ }
575
+ return { kind: "gear", words };
576
+ }
577
+ function admits(t, s) {
578
+ var _a, _b, _c, _d;
579
+ if (t.kind !== s.kind) return false;
580
+ if (((_a = t.category) == null ? void 0 : _a.length) && !t.category.map(lc).includes(((_b = s.category) != null ? _b : "").split("-")[0])) return false;
581
+ if (t.property && !((_c = s.properties) != null ? _c : []).includes(lc(t.property))) return false;
582
+ if (((_d = t.name_any) == null ? void 0 : _d.length) && !t.name_any.some((n) => {
583
+ const w = wordsOf(n);
584
+ return w.length > 0 && w.every((x) => s.words.includes(x));
585
+ })) return false;
586
+ return true;
587
+ }
588
+ function targetVerdict(entry, infuse, registry) {
589
+ const found = resolveBaseItem(entry.item, registry);
590
+ if (!infuse.targets.some((t) => admits(t, shapeOf(found, plainText(entry.item), registry)))) return "target-kind";
591
+ return found && isMagic(found.data) ? "magic-base" : "ok";
592
+ }
593
+ function newObjectEntry(name, infuse, registry, visible) {
594
+ const want = lc(name);
595
+ const found = [];
596
+ for (const type of ["armor", "weapon", "item"]) {
597
+ for (const e of registry.search(name, type, Number.POSITIVE_INFINITY)) {
598
+ if (lc(e.name) === want && !isMagic(e.data) && visible(e) && infuse.targets.some((t) => admits(t, shapeOf(e, e.name, registry)))) found.push(e);
599
+ }
600
+ }
601
+ const first = found.sort(candidateOrder(void 0))[0];
602
+ return first ? { item: `[[${first.slug}]]`, qty: 1 } : { item: name, qty: 1 };
603
+ }
604
+ function endBySeqs(equipment, seqs, registry) {
605
+ let eq = [...equipment];
606
+ let slots = eq.map((_, i) => i);
607
+ const rows = [];
608
+ for (const { seq, by } of seqs) {
609
+ const i = eq.findIndex((e) => {
610
+ var _a;
611
+ return ((_a = e.infusion) == null ? void 0 : _a.seq) === seq && e.infusion.by === by;
612
+ });
613
+ if (i === -1) continue;
614
+ const w = endInfusion(eq, i, registry);
615
+ for (const row of w.ended) rows.push({ row, slot: slots[i] });
616
+ if (eq[i].infusion.kind === "created") slots = slots.filter((_, j) => j !== i);
617
+ eq = w.equipment;
618
+ }
619
+ return { equipment: eq, ended: rows.map(({ row, slot }) => row.kind === "infused" ? { ...row, index: slots.indexOf(slot) } : row) };
620
+ }
621
+ function displaced(equipment, resolved, pool, key) {
622
+ var _a;
623
+ const k = markerKey(key);
624
+ const out = equipment.filter((e) => !!e.infusion && markerKey(e.infusion.by) === k).map((e) => ({ seq: e.infusion.seq, by: e.infusion.by }));
625
+ const counter = (_a = resolved.infusions) == null ? void 0 : _a.counters.get(pool.id);
626
+ if (!counter) return out;
627
+ const live = [...resolved.infusions.views.values()].filter((v) => v.poolId === pool.id && v.classIndex === pool.classIndex && v.entity !== null && markerKey(v.key) !== k).sort((a, b) => a.seq - b.seq);
628
+ let used = live.length;
629
+ for (const v of live) {
630
+ if (used + 1 <= counter.max) break;
631
+ out.push({ seq: v.seq, by: v.key });
632
+ used--;
633
+ }
634
+ return out;
635
+ }
636
+ function infuseEntry(character, resolved, registry, req) {
637
+ var _a, _b, _c, _d, _e, _f, _g;
638
+ const owned = ownedPick(resolved, req.key);
639
+ if (!owned) return refuse("not-owned");
640
+ const infuse = owned.entry.entity.infuse;
641
+ if (!infuse) return refuse("not-infusing");
642
+ if (owned.stranded) return refuse("stranded");
643
+ const wantsChoice = ((_a = infuse.choice) == null ? void 0 : _a.kind) === "damage-type";
644
+ const choice = lc(req.choice);
645
+ if (wantsChoice && choice === "") return refuse("choice-required");
646
+ if (wantsChoice && !isCanonicalDamageType(choice)) return refuse("bad-choice");
647
+ if (wantsChoice && ((_b = infuse.choice) == null ? void 0 : _b.options) && !infuse.choice.options.some((o) => lc(o) === choice)) return refuse("bad-choice");
648
+ const equipment = (_c = character.equipment) != null ? _c : [];
649
+ const charges = scaledInfuseItem(infuse, (_e = (_d = resolved.classes[owned.pool.classIndex]) == null ? void 0 : _d.level) != null ? _e : 0).charges;
650
+ const marker = {
651
+ kind: "infused",
652
+ by: owned.entry.key,
653
+ seq: nextSeq(equipment),
654
+ ...wantsChoice ? { choice } : {},
655
+ ...charges ? { charges: { current: charges.max } } : {}
656
+ };
657
+ let placed;
658
+ if (typeof req.target === "number") {
659
+ const target = equipment[req.target];
660
+ if (!target) return refuse("no-target");
661
+ if (target.infusion) return refuse("marked");
662
+ const verdict = targetVerdict(target, infuse, registry);
663
+ if (verdict !== "ok") return refuse(verdict);
664
+ const split = ((_f = target.qty) != null ? _f : 1) > 1;
665
+ placed = split ? splitOne(equipment, req.target) : [...equipment];
666
+ const i = split ? req.target + 1 : req.target;
667
+ placed[i] = { ...placed[i], infusion: marker };
668
+ } else {
669
+ const name = req.target.newObject.trim();
670
+ if (name === "") return refuse("no-target");
671
+ placed = [...equipment, { ...newObjectEntry(name, infuse, registry, (_g = req.isEntityVisible) != null ? _g : (() => true)), infusion: marker }];
672
+ }
673
+ return { ok: true, ...endBySeqs(placed, displaced(equipment, resolved, owned.pool, owned.entry.key), registry) };
674
+ }
675
+ function createEntry(character, resolved, registry, req) {
676
+ var _a, _b;
677
+ const owned = ownedPick(resolved, req.key);
678
+ if (!owned) return refuse("not-owned");
679
+ const note = owned.entry.entity;
680
+ if (!(note.repeatable === true && !!((_a = note.options) == null ? void 0 : _a.some((r) => !!r.creates)))) return refuse("not-creating");
681
+ if (owned.stranded) return refuse("stranded");
682
+ const opt = owned.entry.option;
683
+ if (!(opt == null ? void 0 : opt.row) || opt.status !== "ok") return refuse("invalid-item");
684
+ const it = registry.getByTypeAndSlug("item", linkSlug(req.item.trim()));
685
+ if (!it || !(opt.item === it.slug || rowMatches(opt.row, it))) return refuse("invalid-item");
686
+ const equipment = (_b = character.equipment) != null ? _b : [];
687
+ const placed = [...equipment, { item: `[[${it.slug}]]`, qty: 1, infusion: { kind: "created", by: owned.entry.key, seq: nextSeq(equipment) } }];
688
+ return { ok: true, ...endBySeqs(placed, displaced(equipment, resolved, owned.pool, owned.entry.key), registry) };
689
+ }
690
+ function markersWhere(equipment, pick) {
691
+ const out = [];
692
+ equipment.forEach((e, index) => {
693
+ const m = e.infusion;
694
+ if (m && pick(m)) out.push({ index, key: m.by, kind: m.kind, item: e.item, seq: m.seq, by: m.by });
695
+ });
696
+ return out;
697
+ }
698
+ var asRows = (list) => list.map(({ index, key, kind, item }) => ({ index, key, kind, item }));
699
+ var storedKeys = (v) => typeof v === "string" ? [v] : Array.isArray(v) ? v : [];
700
+ function choiceEnds(character, resolved, req) {
701
+ var _a, _b, _c, _d, _e, _f;
702
+ const pool = ((_a = resolved.pools) != null ? _a : []).find((p) => p.classIndex === req.classIndex && p.id === req.choiceKey);
703
+ if (!pool || !(pool.active || pool.selected.some((e) => e.itemBound))) return [];
704
+ const choices = (_c = (_b = character.class[req.classIndex]) == null ? void 0 : _b.choices) != null ? _c : {};
705
+ const kept = new Set(req.next.map(markerKey));
706
+ for (const lvl of Object.keys(choices)) if (Number(lvl) !== req.level) for (const s of storedKeys((_d = choices[Number(lvl)]) == null ? void 0 : _d[req.choiceKey])) kept.add(markerKey(s));
707
+ const removed = new Set(storedKeys((_e = choices[req.level]) == null ? void 0 : _e[req.choiceKey]).map(markerKey).filter((k) => !kept.has(k)));
708
+ return markersWhere((_f = character.equipment) != null ? _f : [], (m) => removed.has(markerKey(m.by)));
709
+ }
710
+ function infusionsEndedByChoice(character, resolved, _registry, req) {
711
+ return asRows(choiceEnds(character, resolved, req));
712
+ }
713
+ function reconcileChoiceEdit(character, resolved, registry, req) {
714
+ var _a;
715
+ const list = choiceEnds(character, resolved, req);
716
+ return list.length === 0 ? null : endBySeqs((_a = character.equipment) != null ? _a : [], list, registry);
717
+ }
718
+ function classEnds(character, resolved, req) {
719
+ var _a, _b, _c, _d, _e;
720
+ let pools = ((_a = resolved.pools) != null ? _a : []).filter((p) => p.classIndex === req.classIndex);
721
+ if (!("remove" in req)) {
722
+ const old = (_c = (_b = resolved.classes[req.classIndex]) == null ? void 0 : _b.subclass) != null ? _c : null;
723
+ if (!old || req.subclass !== null && bareEntitySlug(linkSlug(req.subclass.trim())) === bareEntitySlug(old.slug)) return [];
724
+ const ids = new Set(((_d = old.selection_pools) != null ? _d : []).map((p) => p.id));
725
+ pools = pools.filter((p) => ids.has(p.id));
726
+ }
727
+ const owned = /* @__PURE__ */ new Set();
728
+ for (const pool of pools) for (const e of pool.selected) if (e.itemBound) owned.add(pickKeyOf(e));
729
+ return markersWhere((_e = character.equipment) != null ? _e : [], (m) => owned.has(markerKey(m.by)));
730
+ }
731
+ function infusionsEndedByClassEdit(character, resolved, _registry, req) {
732
+ return asRows(classEnds(character, resolved, req));
733
+ }
734
+ function reconcileClassEdit(character, resolved, registry, req) {
735
+ var _a;
736
+ const list = classEnds(character, resolved, req);
737
+ return list.length === 0 ? null : endBySeqs((_a = character.equipment) != null ? _a : [], list, registry);
738
+ }
739
+ function eligibleTargets(character, resolved, registry, key) {
740
+ var _a, _b;
741
+ const infuse = (_a = ownedPick(resolved, key)) == null ? void 0 : _a.entry.entity.infuse;
742
+ if (!infuse) return [];
743
+ const out = [];
744
+ ((_b = character.equipment) != null ? _b : []).forEach((e, index) => {
745
+ var _a2;
746
+ if (!e.infusion && targetVerdict(e, infuse, registry) === "ok") out.push({ index, stack: ((_a2 = e.qty) != null ? _a2 : 1) > 1 });
747
+ });
748
+ return out;
749
+ }
750
+
751
+ // src/pc/pc.entry-overrides.ts
752
+ var WEAPON_DEFAULT_ACTION_COST = "action";
753
+ var linkOf = (item) => {
754
+ var _a, _b;
755
+ return (_b = (_a = item.match(/^\[\[(.+)\]\]$/)) == null ? void 0 : _a[1]) != null ? _b : null;
756
+ };
757
+ var ruleOf = (entry, registry, view) => resolveEntryEntity(entry, view ? view.index : -1, registry, view != null ? view : null);
758
+ function isWeaponLikeEntry(entry, registry, view) {
759
+ if (!registry) return false;
760
+ const r = ruleOf(entry, registry, view);
761
+ return defaultSlotForType(r.entityType, r.entity, registry) === "mainhand";
762
+ }
763
+ function itemsRowBranch(entry, registry, view) {
764
+ var _a, _b;
765
+ const link = linkOf(entry.item);
766
+ if (!link || !registry) return false;
767
+ const t = view ? view.entityType : (_b = (_a = registry.getBySlug(link)) == null ? void 0 : _a.entityType) != null ? _b : null;
768
+ return t !== "weapon" && t !== "armor" && !isWeaponLikeEntry(entry, registry, view);
769
+ }
770
+ function customizableOverrideKeys(entry, registry) {
771
+ const keys = new Set(isConsumedOnUse(entry, registry) ? ["name"] : ["name", "charges", "recovery"]);
772
+ if (!registry) return keys;
773
+ const r = ruleOf(entry, registry);
774
+ const slot = defaultSlotForType(r.entityType, r.entity, registry);
775
+ const weaponLike = slot === "mainhand";
776
+ const itemsRow = itemsRowBranch(entry, registry);
777
+ if (weaponLike) {
778
+ keys.add("bonus");
779
+ keys.add("damage_bonus");
780
+ keys.add("extra_damage");
781
+ }
782
+ if (slot === "armor" || slot === "shield") keys.add("ac_bonus");
783
+ if (weaponLike || itemsRow) keys.add("action");
784
+ if (itemsRow) keys.add("range");
785
+ if (r.entityType === "item") keys.add("resist");
786
+ return keys;
787
+ }
788
+ function defaultActionCost(entry, registry, view) {
789
+ var _a, _b, _c, _d, _e;
790
+ if (isWeaponLikeEntry(entry, registry, view)) return WEAPON_DEFAULT_ACTION_COST;
791
+ const link = linkOf(entry.item);
792
+ if (!link || !registry || !itemsRowBranch(entry, registry, view)) return null;
793
+ const withoutOverrides = { ...entry, overrides: void 0 };
794
+ const type = view ? (_a = view.entity) == null ? void 0 : _a.type : (_c = (_b = registry.getBySlug(link)) == null ? void 0 : _b.data) == null ? void 0 : _c.type;
795
+ return (_e = (_d = resolveItemAction(link, withoutOverrides, type, { view, spellCost: scrollSpellCost(entry, registry) })) == null ? void 0 : _d.cost) != null ? _e : null;
796
+ }
797
+ var SPELL_COST = { action: "action", bonus: "bonus-action", reaction: "reaction" };
798
+ function scrollSpellCost(entry, registry) {
799
+ var _a, _b, _c, _d, _e;
800
+ const ref = (_a = entry.overrides) == null ? void 0 : _a.spell;
801
+ if (!ref || !registry) return void 0;
802
+ const castingTime = (_d = (_c = registry.getByTypeAndSlug("spell", (_b = linkOf(ref)) != null ? _b : ref)) == null ? void 0 : _c.data) == null ? void 0 : _d.casting_time;
803
+ const kind = typeof castingTime === "string" ? (_e = parseCastingTime(castingTime)) == null ? void 0 : _e.kind : void 0;
804
+ return kind ? SPELL_COST[kind] : void 0;
805
+ }
806
+ var chargesOf = (e) => e == null ? void 0 : e.charges;
807
+ function itemChargesMax(entity) {
808
+ const c = chargesOf(entity);
809
+ if (typeof c === "number") return c > 0 ? c : void 0;
810
+ const m = c == null ? void 0 : c.max;
811
+ return typeof m === "number" && m > 0 ? m : void 0;
812
+ }
813
+ var RESET_OF = (r) => {
814
+ const s = String(r != null ? r : "").toLowerCase();
815
+ return s.includes("dawn") ? "dawn" : s.includes("short") ? "short" : s.includes("long") ? "long" : "special";
816
+ };
817
+ function rechargeAmount(c) {
818
+ var _a, _b;
819
+ const raw = [c.recharge_amount, c.dice].find((v) => typeof v === "string" && v.trim() !== "");
820
+ if (!raw) return "1";
821
+ const bare = raw.trim().replace(/^`+|`+$/g, "");
822
+ return ((_b = (_a = parseInlineTag(bare)) == null ? void 0 : _a.content) != null ? _b : bare).replace(/\s+/g, "");
823
+ }
824
+ var recoveryOf = (c, needRecharge) => !c || typeof c === "number" || needRecharge && (typeof c.recharge !== "string" || c.recharge.trim() === "") ? void 0 : { amount: rechargeAmount(c), reset: RESET_OF(c.recharge) };
825
+ function effectiveItemRecovery(entry, registry, view) {
826
+ var _a, _b;
827
+ if (isConsumedOnUse(entry, registry, view)) return void 0;
828
+ if (view && view.entity === null) return void 0;
829
+ if ((view == null ? void 0 : view.kind) === "infused" && itemChargesMax(view.entity) !== void 0) return recoveryOf(chargesOf(view.entity), false);
830
+ if ((_a = entry.state) == null ? void 0 : _a.recovery) return entry.state.recovery;
831
+ const own = registry ? recoveryOf(chargesOf(ruleOf(entry, registry, view).entity), true) : void 0;
832
+ if (own) return own;
833
+ const link = linkOf(entry.item);
834
+ return link ? (_b = findItemAction(link)) == null ? void 0 : _b.recovery : void 0;
835
+ }
836
+ function isConsumedOnUse(entry, registry, view) {
837
+ var _a;
838
+ const link = linkOf(entry.item);
839
+ if (!link) return false;
840
+ if ((_a = findItemAction(link)) == null ? void 0 : _a.consumed) return true;
841
+ const entity = entityOf(entry, registry, view);
842
+ return (entity == null ? void 0 : entity.type) === "potion" || isScrollEntity(entity);
843
+ }
844
+ function isScrollEntity(entity) {
845
+ if (!entity || typeof entity !== "object") return false;
846
+ return entity.type === "scroll" || isCastableScroll(entity);
847
+ }
848
+ function isCastableScroll(entity) {
849
+ return !!entity && typeof entity === "object" && entity.scroll_level != null;
850
+ }
851
+ function entityOf(entry, registry, view) {
852
+ if (!registry || !linkOf(entry.item)) return null;
853
+ return ruleOf(entry, registry, view).entity;
854
+ }
855
+ function effectiveItemCharges(entry, registry, view) {
856
+ var _a, _b, _c, _d, _e, _f;
857
+ if (isConsumedOnUse(entry, registry, view)) return void 0;
858
+ if (view && view.entity === null) return void 0;
859
+ const infused = (view == null ? void 0 : view.kind) === "infused" ? itemChargesMax(view.entity) : void 0;
860
+ if (infused !== void 0) return { current: Math.max(0, Math.min((_c = (_b = (_a = entry.infusion) == null ? void 0 : _a.charges) == null ? void 0 : _b.current) != null ? _c : infused, infused)), max: infused };
861
+ const stored = (_d = entry.state) == null ? void 0 : _d.charges;
862
+ if (stored) return stored;
863
+ const own = registry ? itemChargesMax(ruleOf(entry, registry, view).entity) : void 0;
864
+ if (own !== void 0) return { current: own, max: own };
865
+ const link = linkOf(entry.item);
866
+ const max = link ? (_f = (_e = findItemAction(link)) == null ? void 0 : _e.max_charges) != null ? _f : 0 : 0;
867
+ return max > 0 ? { current: max, max } : void 0;
868
+ }
869
+ function effectiveItemArmorClass(entry, registry, view) {
870
+ var _a;
871
+ if (!registry) return null;
872
+ const { entity } = ruleOf(entry, registry, view);
873
+ const armor = effectiveArmor(entity, registry);
874
+ if (!armor) return null;
875
+ const shield = isShieldArmor(armor);
876
+ const bonus = isItemEntity(entity) && typeof ((_a = entity.bonuses) == null ? void 0 : _a.ac) === "number" ? entity.bonuses.ac : 0;
877
+ const terms = slotAcTerms(armor, entry);
878
+ return {
879
+ value: terms.armor + terms.override + bonus,
880
+ shield,
881
+ // raw vault data may omit the key (the armour type's `flat` note applies to it too): absent reads as no Dex
882
+ addDex: !shield && armor.ac.add_dex === true
883
+ };
884
+ }
885
+
886
+ export {
887
+ WEAPON_DEFAULT_ACTION_COST,
888
+ isWeaponLikeEntry,
889
+ customizableOverrideKeys,
890
+ defaultActionCost,
891
+ scrollSpellCost,
892
+ itemChargesMax,
893
+ effectiveItemRecovery,
894
+ isConsumedOnUse,
895
+ isScrollEntity,
896
+ isCastableScroll,
897
+ effectiveItemCharges,
898
+ effectiveItemArmorClass,
899
+ poolCountLabel,
900
+ poolActiveWords,
901
+ parsePickKey,
902
+ normalizedPickKey,
903
+ markerKey,
904
+ isItemBound,
905
+ optionCandidates,
906
+ resolveOptionRows,
907
+ resolveCopyOption,
908
+ scaledInfuseItem,
909
+ resolveEntryEntity,
910
+ buildInfusionContext,
911
+ entryCharges,
912
+ withEntryCharges,
913
+ isMarkedEntry,
914
+ splitOne,
915
+ endInfusion,
916
+ infuseEntry,
917
+ createEntry,
918
+ infusionsEndedByChoice,
919
+ reconcileChoiceEdit,
920
+ infusionsEndedByClassEdit,
921
+ reconcileClassEdit,
922
+ eligibleTargets,
923
+ levelPrereqMax,
924
+ resolvePool,
925
+ resolveAllPools,
926
+ strandedPicks
927
+ };