@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
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-LVQLWRCU.js";
7
7
  import {
8
8
  slugify
9
- } from "./chunk-6X4KUDMU.js";
9
+ } from "./chunk-ZCTUYGNV.js";
10
10
  import {
11
11
  mirrorSpellShapes
12
12
  } from "./chunk-XY6X2APM.js";
@@ -0,0 +1,186 @@
1
+ import {
2
+ resolveBaseItem
3
+ } from "./chunk-CM5CWO4K.js";
4
+ import {
5
+ bareEntitySlug,
6
+ slugify,
7
+ wikilinkTailSlug
8
+ } from "./chunk-ZCTUYGNV.js";
9
+
10
+ // src/pc/pc.containment.ts
11
+ function nonEmptyRef(v) {
12
+ return typeof v === "string" && v.length > 0 ? v : null;
13
+ }
14
+ function isContainerItem(entity) {
15
+ if (!entity || typeof entity !== "object") return false;
16
+ const c = entity.container;
17
+ if (!c || typeof c !== "object") return false;
18
+ const container = c;
19
+ if (typeof container.holds_items === "boolean") return container.holds_items;
20
+ if (typeof container.capacity_weight === "number" && container.capacity_weight > 0) return true;
21
+ const cap = container.capacity;
22
+ if (!cap || typeof cap !== "object") return false;
23
+ const k = cap;
24
+ if (Array.isArray(k.weight) && k.weight.length > 0) return true;
25
+ if (Array.isArray(k.item) && k.item.length > 0) return true;
26
+ return k.weightless === true;
27
+ }
28
+ function idOwners(equipment) {
29
+ const ownerOfId = /* @__PURE__ */ new Map();
30
+ const duplicateIds = [];
31
+ equipment.forEach((entry, i) => {
32
+ const id = nonEmptyRef(entry.id);
33
+ if (id === null) return;
34
+ if (ownerOfId.has(id)) duplicateIds.push(i);
35
+ else ownerOfId.set(id, i);
36
+ });
37
+ return { ownerOfId, duplicateIds };
38
+ }
39
+ function entityOfEntry(entry, registry) {
40
+ var _a, _b;
41
+ if (!registry) return null;
42
+ return (_b = (_a = resolveBaseItem(entry.item, registry)) == null ? void 0 : _a.data) != null ? _b : null;
43
+ }
44
+ function resolveContainment(equipment, registry) {
45
+ const n = equipment.length;
46
+ const { ownerOfId, duplicateIds } = idOwners(equipment);
47
+ const entities = equipment.map((entry) => entityOfEntry(entry, registry));
48
+ const isContainer = entities.map((entity) => isContainerItem(entity));
49
+ const parentOf = new Array(n).fill(null);
50
+ const broken = [];
51
+ const closesCycle = (child, owner) => {
52
+ for (let cur = owner; cur !== null; cur = parentOf[cur]) if (cur === child) return true;
53
+ return false;
54
+ };
55
+ for (let i = 0; i < n; i++) {
56
+ const target = nonEmptyRef(equipment[i].in);
57
+ if (target === null) continue;
58
+ const owner = ownerOfId.get(target);
59
+ let reason = null;
60
+ if (equipment[i].equipped === true) reason = "equipped";
61
+ else if (owner === void 0) reason = "missing";
62
+ else if (owner === i) reason = "self";
63
+ else if (entities[owner] === null) reason = "unresolved";
64
+ else if (!isContainer[owner]) reason = "not-container";
65
+ else if (closesCycle(i, owner)) reason = "cycle";
66
+ if (reason !== null) {
67
+ broken.push({ index: i, reason });
68
+ continue;
69
+ }
70
+ parentOf[i] = owner;
71
+ }
72
+ const childrenOf = Array.from({ length: n }, () => []);
73
+ parentOf.forEach((p, i) => {
74
+ if (p !== null) childrenOf[p].push(i);
75
+ });
76
+ return { parentOf, childrenOf, isContainer, ownerOfId, duplicateIds, broken };
77
+ }
78
+ function mintBase(entry, registry) {
79
+ var _a, _b, _c;
80
+ const found = registry ? resolveBaseItem(entry.item, registry) : null;
81
+ const byName = found ? slugify((_a = found.name) != null ? _a : "") : "";
82
+ if (byName) return byName;
83
+ const inner = (_c = (_b = /^\[\[(.+?)\]\]$/.exec(entry.item.trim())) == null ? void 0 : _b[1]) != null ? _c : entry.item.trim();
84
+ const ref = inner.split("|")[0].trim();
85
+ const byRef = ref.includes("/") ? wikilinkTailSlug(ref) : slugify(bareEntitySlug(ref));
86
+ return byRef || "item";
87
+ }
88
+ function ensureEntryId(equipment, index, registry) {
89
+ const { ownerOfId } = idOwners(equipment);
90
+ const own = nonEmptyRef(equipment[index].id);
91
+ if (own !== null && ownerOfId.get(own) === index) return own;
92
+ const taken = /* @__PURE__ */ new Set();
93
+ for (const entry of equipment) {
94
+ const a = nonEmptyRef(entry.id);
95
+ if (a !== null) taken.add(a);
96
+ const b = nonEmptyRef(entry.in);
97
+ if (b !== null) taken.add(b);
98
+ }
99
+ const base = mintBase(equipment[index], registry);
100
+ let id = base;
101
+ for (let k = 2; taken.has(id); k++) id = `${base}-${k}`;
102
+ equipment[index].id = id;
103
+ return id;
104
+ }
105
+ function canMoveInto(equipment, index, target, registry) {
106
+ var _a;
107
+ const inRange = (i) => Number.isInteger(i) && i >= 0 && i < equipment.length;
108
+ if (!inRange(index) || target !== null && !inRange(target)) return { ok: false, reason: "out-of-range" };
109
+ if (target === null) return { ok: true };
110
+ if (equipment[index].equipped === true) return { ok: false, reason: "equipped" };
111
+ if (target === index) return { ok: false, reason: "self" };
112
+ const view = resolveContainment(equipment, registry);
113
+ const unresolvedLink = new Set(view.broken.filter((b) => b.reason === "unresolved").map((b) => b.index));
114
+ const seen = /* @__PURE__ */ new Set();
115
+ for (let cur = target; cur !== null && !seen.has(cur); ) {
116
+ if (cur === index) return { ok: false, reason: "descendant" };
117
+ seen.add(cur);
118
+ const up = view.parentOf[cur];
119
+ if (up !== null) {
120
+ cur = up;
121
+ continue;
122
+ }
123
+ if (unresolvedLink.has(cur)) {
124
+ const raw = nonEmptyRef(equipment[cur].in);
125
+ cur = raw === null ? null : (_a = view.ownerOfId.get(raw)) != null ? _a : null;
126
+ continue;
127
+ }
128
+ cur = null;
129
+ }
130
+ const entity = entityOfEntry(equipment[target], registry);
131
+ if (entity === null) return { ok: false, reason: "unresolved" };
132
+ if (!isContainerItem(entity)) return { ok: false, reason: "not-container" };
133
+ return { ok: true };
134
+ }
135
+ function moveInto(equipment, index, target, registry) {
136
+ const check = canMoveInto(equipment, index, target, registry);
137
+ if (!check.ok) return check;
138
+ const entry = equipment[index];
139
+ if (target === null) {
140
+ const had = entry.in !== void 0;
141
+ delete entry.in;
142
+ return { ok: true, changed: had };
143
+ }
144
+ const wasHonoured = resolveContainment(equipment, registry).parentOf[index] === target;
145
+ const idBefore = equipment[target].id;
146
+ const id = ensureEntryId(equipment, target, registry);
147
+ entry.in = id;
148
+ return { ok: true, changed: idBefore !== id || !wasHonoured };
149
+ }
150
+ function repairContainment(equipment, registry) {
151
+ if (!registry) return 0;
152
+ let removed = 0;
153
+ for (const entry of equipment) {
154
+ if (entry.id === "") {
155
+ delete entry.id;
156
+ removed++;
157
+ }
158
+ if (entry.in === "") {
159
+ delete entry.in;
160
+ removed++;
161
+ }
162
+ }
163
+ const view = resolveContainment(equipment, registry);
164
+ for (const b of view.broken) {
165
+ if (b.reason === "unresolved") continue;
166
+ delete equipment[b.index].in;
167
+ removed++;
168
+ }
169
+ for (const i of view.duplicateIds) {
170
+ delete equipment[i].id;
171
+ removed++;
172
+ }
173
+ return removed;
174
+ }
175
+
176
+ export {
177
+ nonEmptyRef,
178
+ isContainerItem,
179
+ idOwners,
180
+ entityOfEntry,
181
+ resolveContainment,
182
+ ensureEntryId,
183
+ canMoveInto,
184
+ moveInto,
185
+ repairContainment
186
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  featureObjectSchema
3
- } from "./chunk-PI4LLJG4.js";
3
+ } from "./chunk-FPGXFD2M.js";
4
4
  import {
5
5
  imageField
6
6
  } from "./chunk-UA5KS2PL.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  itemEntitySchema
3
- } from "./chunk-GFZ7PITJ.js";
3
+ } from "./chunk-S3YUXDSC.js";
4
4
 
5
5
  // src/item/item.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -25,6 +25,8 @@ var KNOWN_KEYS = /* @__PURE__ */ new Set([
25
25
  "sentient",
26
26
  "focus",
27
27
  "tier",
28
+ "tattoo",
29
+ // declared I2a; the converter emits it (27 carriers)
28
30
  "damage",
29
31
  "weapon_category",
30
32
  "armor_category",
@@ -46,6 +48,7 @@ var KNOWN_KEYS = /* @__PURE__ */ new Set([
46
48
  // copied into `raw` as well, so the two lists must move together.
47
49
  "modify_speed",
48
50
  "rendering_hint",
51
+ "icon",
49
52
  "has_fluff",
50
53
  "has_fluff_images",
51
54
  "image",
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseArmor
3
- } from "./chunk-5N2GW72F.js";
3
+ } from "./chunk-UZXG6QSY.js";
4
4
 
5
5
  // src/armor/armor.codec.ts
6
6
  import yaml from "js-yaml";
@@ -8,10 +8,10 @@ import {
8
8
  } from "./chunk-ZJXSXVFP.js";
9
9
  import {
10
10
  choiceSchema
11
- } from "./chunk-2M34FLAY.js";
11
+ } from "./chunk-BRGLDWUP.js";
12
12
  import {
13
13
  featureEffectSchema
14
- } from "./chunk-XTRDCSJB.js";
14
+ } from "./chunk-ZMJQUZQH.js";
15
15
 
16
16
  // src/schemas/feature-schema.ts
17
17
  import { z as z2 } from "zod";
@@ -11,8 +11,12 @@ var selectionPoolSchema = z.object({
11
11
  available_to: z.literal("self")
12
12
  })
13
13
  }),
14
- count: z.object({ column: z.string().min(1) }),
15
- replaceable: z.boolean().optional()
14
+ // ART-a (spec §4.5): `count.label`, `active` and `head_resource` carry NO zod default: the defaults are applied at
15
+ // read by `poolCountLabel` / `poolActiveWords`, so a parse never adds a key (the importer's round trip).
16
+ count: z.object({ column: z.string().min(1), label: z.string().min(1).optional() }),
17
+ replaceable: z.boolean().optional(),
18
+ active: z.object({ column: z.string().min(1), label: z.string().min(1), verb: z.string().min(1), counter_label: z.string().min(1).optional(), create_label: z.string().min(1).optional(), create_verb: z.string().min(1).optional() }).optional(),
19
+ head_resource: z.string().min(1).optional()
16
20
  });
17
21
  var poolGrantSchema = z.object({
18
22
  pool: z.string().min(1),
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-4U55VNC7.js";
4
4
  import {
5
5
  classEntitySchema
6
- } from "./chunk-S56D6B6C.js";
6
+ } from "./chunk-PLOSP4XQ.js";
7
7
 
8
8
  // src/class/class.parser.ts
9
9
  import { parseYaml } from "@archivist-gg/core";
@@ -0,0 +1,101 @@
1
+ import {
2
+ ALL_TOOLS,
3
+ ARTISANS_TOOLS,
4
+ GAMING_SETS,
5
+ MUSICAL_INSTRUMENTS
6
+ } from "./chunk-KU3YLF6D.js";
7
+ import {
8
+ toProfSlug
9
+ } from "./chunk-RLGQ5PZZ.js";
10
+
11
+ // src/pc/pc.tool-picks.ts
12
+ var TOOL_CATEGORIES = {
13
+ "musical-instrument": { pool: MUSICAL_INSTRUMENTS, itemType: "instrument", label: "a musical instrument" },
14
+ "artisan's-tools": { pool: ARTISANS_TOOLS, itemType: "artisan's tools", label: "artisan's tools" },
15
+ "gaming-set": { pool: GAMING_SETS, itemType: "gaming set", label: "a gaming set" }
16
+ };
17
+ function categoryWords(token) {
18
+ return token.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase().replace(/['’ʼ]/g, "").split(/[\s_-]+/).filter(Boolean).map((w) => w.length > 3 && w.endsWith("s") && !w.endsWith("ss") ? w.slice(0, -1) : w);
19
+ }
20
+ var CATEGORY_BY_WORDS = /* @__PURE__ */ new Map([
21
+ ["musical instrument", "musical-instrument"],
22
+ ["artisan tool", "artisan's-tools"],
23
+ ["gaming set", "gaming-set"]
24
+ ]);
25
+ function toolCategoriesOf(token) {
26
+ const parts = [[]];
27
+ for (const w of categoryWords(token)) {
28
+ if (w === "or") parts.push([]);
29
+ else parts[parts.length - 1].push(w);
30
+ }
31
+ const out = [];
32
+ for (const p of parts) {
33
+ const c = CATEGORY_BY_WORDS.get(p.filter((w) => w !== "any" && w !== "one").join(" "));
34
+ if (!c) return void 0;
35
+ if (!out.includes(c)) out.push(c);
36
+ }
37
+ return out;
38
+ }
39
+ function expandToolPool(from) {
40
+ const out = [];
41
+ const seen = /* @__PURE__ */ new Set();
42
+ const add = (v) => {
43
+ const k = toProfSlug(v);
44
+ if (seen.has(k)) return;
45
+ seen.add(k);
46
+ out.push(v);
47
+ };
48
+ for (const v of from) {
49
+ const cats = toolCategoriesOf(v);
50
+ if (cats) for (const c of cats) for (const t of TOOL_CATEGORIES[c].pool) add(t);
51
+ else add(v);
52
+ }
53
+ return out;
54
+ }
55
+ var EMPTY = { decisions: [], grants: [] };
56
+ var poolOf = (from) => Array.isArray(from) ? from.filter((v) => typeof v === "string" && v.length > 0) : typeof from === "string" && from.length > 0 ? [from] : [];
57
+ var countOf = (count) => {
58
+ const n = Number(count != null ? count : 1);
59
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 1;
60
+ };
61
+ var KNOWN_TOOLS = new Set(ALL_TOOLS.map(toProfSlug));
62
+ var namesToolsOutright = (from) => from.every((v) => !toolCategoriesOf(v) && KNOWN_TOOLS.has(toProfSlug(v)));
63
+ var authorsToolPick = (choices) => Array.isArray(choices) && choices.some((c) => !!c && typeof c === "object" && c.kind === "select-proficiency" && c.domain === "tool");
64
+ function toolPick(count, from, id, allowDecision) {
65
+ const authored = poolOf(from);
66
+ const pool = expandToolPool(authored);
67
+ const n = countOf(count);
68
+ if (pool.length === 0) return EMPTY;
69
+ if (n >= pool.length && namesToolsOutright(authored)) return { decisions: [], grants: pool };
70
+ if (!allowDecision) return EMPTY;
71
+ return { decisions: [{ kind: "select-proficiency", id, count: n, domain: "tool", from: pool }], grants: [] };
72
+ }
73
+ function classToolPicks(entity) {
74
+ var _a;
75
+ const tools = (_a = entity == null ? void 0 : entity.proficiencies) == null ? void 0 : _a.tools;
76
+ const choice = tools == null ? void 0 : tools.choice;
77
+ if (!choice || typeof choice !== "object") return EMPTY;
78
+ return toolPick(choice.count, choice.from, "tool", !authorsToolPick(entity == null ? void 0 : entity.choices));
79
+ }
80
+ function backgroundToolPicks(bg) {
81
+ const entries = Array.isArray(bg == null ? void 0 : bg.tool_proficiencies) ? bg.tool_proficiencies : [];
82
+ const allowDecision = !authorsToolPick(bg == null ? void 0 : bg.choices);
83
+ const out = { decisions: [], grants: [] };
84
+ for (const e of entries) {
85
+ if (!e || typeof e !== "object" || e.kind !== "choice") continue;
86
+ const id = out.decisions.length === 0 ? "tool" : `tool-${out.decisions.length + 1}`;
87
+ const r = toolPick(e.count, e.from, id, allowDecision);
88
+ out.decisions.push(...r.decisions);
89
+ out.grants.push(...r.grants);
90
+ }
91
+ return out;
92
+ }
93
+
94
+ export {
95
+ TOOL_CATEGORIES,
96
+ categoryWords,
97
+ toolCategoriesOf,
98
+ expandToolPool,
99
+ classToolPicks,
100
+ backgroundToolPicks
101
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  evaluateConditions
3
- } from "./chunk-DNAJRMNN.js";
3
+ } from "./chunk-A54HHCWW.js";
4
4
 
5
5
  // src/item/item.bonuses.ts
6
6
  function isConditionalBonus(x) {
@@ -24,6 +24,12 @@ function derivePoolLayout(entries) {
24
24
  }
25
25
  return tied ? void 0 : best;
26
26
  }
27
+ var DEFAULT_POOL_ROWS = "blocks";
28
+ function poolPresentation(authored, derived) {
29
+ const head = authored === "point-pool" ? "points" : authored === "dice-pool" ? "dice" : derived === "point-pool" ? "points" : "dice";
30
+ const rows = authored === "spell-like" ? "spell-like" : DEFAULT_POOL_ROWS;
31
+ return { head, rows };
32
+ }
27
33
  var RENDERING_HINT_AFFORDANCE = {
28
34
  "granted-die-to-ally": "granted-die"
29
35
  };
@@ -43,6 +49,8 @@ function formatAffordanceCaption(affordance, values) {
43
49
  export {
44
50
  RENDERING_HINT_LAYOUT,
45
51
  derivePoolLayout,
52
+ DEFAULT_POOL_ROWS,
53
+ poolPresentation,
46
54
  RENDERING_HINT_AFFORDANCE,
47
55
  deriveEntryAffordance,
48
56
  AFFORDANCE_CAPTIONS,
@@ -32,6 +32,8 @@ var armorEntitySchema = z.object({
32
32
  // string today, so `.min(1)` is forbidden here.
33
33
  rendering_hint: z.string().optional(),
34
34
  // 27
35
+ // The icon key (I3): a key of the renderer's icon set or a Lucide name; itemIconKey reads it first.
36
+ icon: z.string().optional(),
35
37
  has_fluff: z.boolean().optional(),
36
38
  // 0 carriers — symmetry with weapon
37
39
  has_fluff_images: z.boolean().optional(),
@@ -0,0 +1,13 @@
1
+ // src/item/item.attunement.ts
2
+ function requiresAttunement(entity) {
3
+ if (!entity || !("attunement" in entity)) return false;
4
+ const a = entity.attunement;
5
+ if (a === true) return true;
6
+ if (typeof a === "string") return a.length > 0;
7
+ if (typeof a === "object" && a !== null) return a.required === true;
8
+ return false;
9
+ }
10
+
11
+ export {
12
+ requiresAttunement
13
+ };
@@ -2,13 +2,13 @@ import {
2
2
  poolGrantSchema,
3
3
  selectionPoolSchema,
4
4
  tabDeclSchema
5
- } from "./chunk-23LZLBJK.js";
5
+ } from "./chunk-G6MHJDVT.js";
6
6
  import {
7
7
  casterTypeEnum
8
8
  } from "./chunk-CJYCVNW2.js";
9
9
  import {
10
10
  featureSchema
11
- } from "./chunk-PI4LLJG4.js";
11
+ } from "./chunk-FPGXFD2M.js";
12
12
  import {
13
13
  resourceSchema
14
14
  } from "./chunk-ZJXSXVFP.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-ZJXSXVFP.js";
9
9
  import {
10
10
  featureEffectSchema
11
- } from "./chunk-XTRDCSJB.js";
11
+ } from "./chunk-ZMJQUZQH.js";
12
12
  import {
13
13
  additionalSpellsEntrySchema,
14
14
  imageField,
@@ -46,6 +46,58 @@ var usesSchema = z.object({
46
46
  restores: z.enum(["uses", "spell-slots"]).optional()
47
47
  })).optional()
48
48
  });
49
+ var filterSpecSchema = z.object({
50
+ rarity: z.array(z.string().min(1)).min(1),
51
+ types: z.array(z.string().min(1)).optional(),
52
+ exclude_types: z.array(z.string().min(1)).optional(),
53
+ cursed: z.literal(false).optional()
54
+ }).strict();
55
+ var optionCreatesSchema = z.object({
56
+ item: z.string().regex(wikilinkRegex).optional(),
57
+ variant: z.object({ candidates: z.array(z.string().regex(wikilinkRegex)).min(1) }).strict().optional(),
58
+ filter: filterSpecSchema.optional()
59
+ }).strict().refine((c) => [c.item, c.variant, c.filter].filter((a) => a !== void 0).length === 1, { message: "creates needs exactly one of item, variant, filter" });
60
+ var optionRowSchema = z.object({
61
+ key: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/, "an option key is lower-case, with no '#' and no whitespace"),
62
+ name: z.string().min(1),
63
+ min_level: z.number().int().min(1).max(20),
64
+ attunement: z.boolean().optional(),
65
+ creates: optionCreatesSchema
66
+ }).strict();
67
+ var infuseItemSchema = z.object({
68
+ bonuses: z.object({
69
+ ac: z.number().int().optional(),
70
+ weapon_attack: z.number().int().optional(),
71
+ weapon_damage: z.number().int().optional(),
72
+ spell_attack: z.number().int().optional(),
73
+ saving_throws: z.number().int().optional(),
74
+ speed: z.object({ walk: z.number().int().optional(), fly: z.number().int().optional(), swim: z.number().int().optional(), climb: z.number().int().optional() }).strict().optional()
75
+ }).strict().optional(),
76
+ resist: z.array(z.string().min(1)).optional(),
77
+ charges: z.object({ max: z.number().int().min(1), dice: z.string().optional(), recharge: z.string().optional(), recharge_amount: z.string().optional() }).strict().optional(),
78
+ action: actionCostEnum.optional(),
79
+ // ART-c1 (R-3 a): the Actions row's range, as data (B-8.4 draws "Self").
80
+ range: z.string().min(1).optional()
81
+ }).strict();
82
+ var infuseBlockSchema = z.object({
83
+ targets: z.array(z.object({
84
+ kind: z.enum(["armor", "shield", "weapon", "gear"]),
85
+ category: z.array(z.string().min(1)).min(1).optional(),
86
+ property: z.string().min(1).optional(),
87
+ name_any: z.array(z.string().min(1)).min(1).optional()
88
+ }).strict()).min(1),
89
+ targets_label: z.string().min(1).optional(),
90
+ new_object: z.string().min(1).optional(),
91
+ rarity: z.string().min(1).optional(),
92
+ attunement: z.object({ required: z.literal(true) }).strict().optional(),
93
+ // ART-c1 (R-3 b): the damage types the choice offers (v1 U-4 draws the TCE ten); absent keeps dnd5e's canonical list.
94
+ choice: z.object({ kind: z.literal("damage-type"), options: z.array(z.string().min(1)).min(1).optional() }).strict().optional(),
95
+ item: infuseItemSchema.optional(),
96
+ scales_at: z.array(z.object({ level: z.number().int().min(1).max(20), item: infuseItemSchema }).strict()).optional()
97
+ }).strict().refine((b) => {
98
+ var _a;
99
+ return ((_a = b.scales_at) != null ? _a : []).every((r, i, a) => i === 0 || a[i - 1].level < r.level);
100
+ }, { message: "scales_at levels ascend" });
49
101
  var optionalFeatureEntitySchema = z.object({
50
102
  slug: z.string().min(1),
51
103
  name: z.string().min(1),
@@ -82,9 +134,20 @@ var optionalFeatureEntitySchema = z.object({
82
134
  // 1
83
135
  optionalfeature_progression: z.array(progressionSchema).optional(),
84
136
  // 1
85
- image: imageField
137
+ image: imageField,
138
+ repeatable: z.literal(true).optional(),
139
+ item_choice: z.enum(["learn", "create"]).optional(),
140
+ options: z.array(optionRowSchema).optional(),
141
+ infuse: infuseBlockSchema.optional()
142
+ }).superRefine((d, ctx) => {
143
+ var _a;
144
+ if (d.repeatable && (!d.options || !d.item_choice)) ctx.addIssue({ code: "custom", message: "a repeatable note needs options and item_choice" });
145
+ const keys = ((_a = d.options) != null ? _a : []).map((o) => o.key);
146
+ if (new Set(keys).size !== keys.length) ctx.addIssue({ code: "custom", message: "option keys are unique" });
86
147
  });
87
148
 
88
149
  export {
150
+ optionRowSchema,
151
+ infuseBlockSchema,
89
152
  optionalFeatureEntitySchema
90
153
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseWeapon
3
- } from "./chunk-HZA7OE7N.js";
3
+ } from "./chunk-XCFC7LGF.js";
4
4
 
5
5
  // src/weapon/weapon.codec.ts
6
6
  import yaml from "js-yaml";
@@ -59,6 +59,9 @@ function resolveFeatureResources(features) {
59
59
  }
60
60
  return out;
61
61
  }
62
+ function pickResourceId(entry) {
63
+ return entry.param != null ? entry.key : entry.slug;
64
+ }
62
65
  function resolveResourceIndex(resolved) {
63
66
  var _a, _b, _c, _d, _e;
64
67
  const out = new Map(resolveFeatureResources((_a = resolved.features) != null ? _a : []));
@@ -67,16 +70,18 @@ function resolveResourceIndex(resolved) {
67
70
  const classSlug = (_c = rc == null ? void 0 : rc.entity) == null ? void 0 : _c.slug;
68
71
  if (!classSlug) continue;
69
72
  for (const entry of [...(_d = pool.selected) != null ? _d : [], ...(_e = pool.grants) != null ? _e : []]) {
73
+ if (entry.itemBound === true) continue;
70
74
  const uses = entry.entity.uses;
71
- if (!uses || out.has(entry.slug)) continue;
75
+ const id = pickResourceId(entry);
76
+ if (!uses || out.has(id)) continue;
72
77
  const maxFormula = String(uses.max);
73
78
  if (!isValidMaxFormula(maxFormula)) {
74
79
  warnOnce(`pool-uses:${entry.slug}`, `optional feature "${entry.slug}" has a prose uses.max (${JSON.stringify(uses.max)}); no tracker`);
75
80
  continue;
76
81
  }
77
82
  const pres = entry.entity;
78
- out.set(entry.slug, {
79
- id: entry.slug,
83
+ out.set(id, {
84
+ id,
80
85
  name: entry.entity.name,
81
86
  reset: uses.recharge,
82
87
  maxFormula,
@@ -101,6 +106,7 @@ export {
101
106
  resourceLevelFor,
102
107
  resolveRecovery,
103
108
  resolveFeatureResources,
109
+ pickResourceId,
104
110
  resolveResourceIndex,
105
111
  poolSaveDC
106
112
  };