@archivist-gg/dnd5e 0.3.1 → 0.3.5

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 (259) hide show
  1. package/LICENSES/SRD.md +25 -6
  2. package/dist/armor/armor.codec.js +4 -3
  3. package/dist/armor/armor.parser.js +3 -2
  4. package/dist/armor/armor.schema.d.ts +4 -0
  5. package/dist/armor/armor.schema.js +2 -1
  6. package/dist/armor/armor.types.d.ts +8 -0
  7. package/dist/background/background.codec.js +7 -6
  8. package/dist/background/background.parser.js +6 -5
  9. package/dist/background/background.schema.d.ts +26 -0
  10. package/dist/background/background.schema.js +5 -4
  11. package/dist/background/background.types.d.ts +22 -0
  12. package/dist/{chunk-4TIJKN6O.js → chunk-23LZLBJK.js} +1 -1
  13. package/dist/{chunk-AFKTJXRA.js → chunk-2DFENQIJ.js} +18 -0
  14. package/dist/chunk-2FRUSVZS.js +489 -0
  15. package/dist/chunk-2VDTW7QO.js +1580 -0
  16. package/dist/{chunk-ZZGEVJKX.js → chunk-3JOTQW7A.js} +1 -1
  17. package/dist/{chunk-5ZBE4CWV.js → chunk-3PZHBLFG.js} +1 -1
  18. package/dist/chunk-4U55VNC7.js +22 -0
  19. package/dist/{chunk-4JHOHWBI.js → chunk-4XTNPNQB.js} +1 -1
  20. package/dist/{chunk-HCP3GMHS.js → chunk-5N2GW72F.js} +15 -2
  21. package/dist/{chunk-42Y5IJKT.js → chunk-5RKLL2AD.js} +5 -1
  22. package/dist/{chunk-SE7AZDR3.js → chunk-63XQXPZL.js} +29 -3
  23. package/dist/{chunk-4F6AIZ36.js → chunk-6BFWZW6T.js} +1 -1
  24. package/dist/{chunk-GJOI2GHP.js → chunk-6GNXYAYP.js} +1 -1
  25. package/dist/{chunk-66XXW2Z2.js → chunk-6PXTNCBP.js} +2 -2
  26. package/dist/chunk-6X2NV665.js +33 -0
  27. package/dist/{chunk-LV4SNWZQ.js → chunk-6X4KUDMU.js} +8 -1
  28. package/dist/{chunk-ZMR77DDL.js → chunk-AW3EHF3L.js} +1 -1
  29. package/dist/{chunk-TLYDBT5F.js → chunk-CGRLQ34U.js} +64 -7
  30. package/dist/chunk-CJYCVNW2.js +7 -0
  31. package/dist/{chunk-XLSVNUTK.js → chunk-CV5XDJKU.js} +1 -1
  32. package/dist/{chunk-RAWJTA45.js → chunk-DEEHHLAP.js} +1 -1
  33. package/dist/{chunk-6IRYTM5V.js → chunk-DPUJDOKV.js} +119 -28
  34. package/dist/{chunk-GE4TLPWM.js → chunk-EHDATXP7.js} +1 -1
  35. package/dist/chunk-EVMUJYUD.js +195 -0
  36. package/dist/{chunk-K6Q5OUGK.js → chunk-GFZ7PITJ.js} +86 -10
  37. package/dist/{chunk-6NK2MUD3.js → chunk-HZA7OE7N.js} +9 -2
  38. package/dist/{chunk-TN6ZLTNU.js → chunk-I2NDWFDD.js} +51 -4
  39. package/dist/{chunk-3DZ74AKK.js → chunk-IN4M4X7P.js} +8 -2
  40. package/dist/chunk-IUAAXXNO.js +58 -0
  41. package/dist/{chunk-NXSWH3AE.js → chunk-IWRVOCT5.js} +1 -1
  42. package/dist/{chunk-XJNECUPT.js → chunk-JC5UNIVM.js} +19 -0
  43. package/dist/{chunk-24GFQU7T.js → chunk-JSZ4H6AQ.js} +1 -1
  44. package/dist/{chunk-XTIQWSFM.js → chunk-KADJZGZY.js} +1 -1
  45. package/dist/{chunk-L7E2UT5A.js → chunk-KK33PIDN.js} +5 -1
  46. package/dist/chunk-KPEJMNRD.js +302 -0
  47. package/dist/chunk-KU3YLF6D.js +106 -0
  48. package/dist/{chunk-5INR4NSL.js → chunk-LDDCNPHD.js} +1 -1
  49. package/dist/chunk-LVQLWRCU.js +15 -0
  50. package/dist/chunk-MI7P3NX3.js +161 -0
  51. package/dist/{chunk-PHQRGDRQ.js → chunk-N3A5ICBO.js} +5 -1
  52. package/dist/chunk-O4SRD3J5.js +165 -0
  53. package/dist/chunk-OAXWSVJB.js +81 -0
  54. package/dist/chunk-OZP6JFGT.js +162 -0
  55. package/dist/{chunk-NAEPSZAM.js → chunk-PDJSUH24.js} +24 -2
  56. package/dist/chunk-PP2UHLBA.js +50 -0
  57. package/dist/chunk-PVCEA4HZ.js +6 -0
  58. package/dist/{chunk-RSDRMH54.js → chunk-RF4TYCA2.js} +1 -1
  59. package/dist/chunk-RLGQ5PZZ.js +16 -0
  60. package/dist/chunk-RVXC6ZHY.js +89 -0
  61. package/dist/chunk-S7REMWW4.js +21 -0
  62. package/dist/{chunk-7VJ6BOA5.js → chunk-SMLXZUBH.js} +9 -1
  63. package/dist/chunk-TOM66P2P.js +38 -0
  64. package/dist/chunk-UA5KS2PL.js +26 -0
  65. package/dist/{chunk-N6N74K6X.js → chunk-UBBKSYBY.js} +1 -1
  66. package/dist/{chunk-353WXS6S.js → chunk-UIRMA5KM.js} +1 -1
  67. package/dist/{chunk-BIK4DFWK.js → chunk-ULCQ73LA.js} +1 -1
  68. package/dist/{chunk-2BWAID3Y.js → chunk-UMWIPLLM.js} +29 -3
  69. package/dist/chunk-UQ3ZFCWD.js +178 -0
  70. package/dist/chunk-UXPGW4TI.js +101 -0
  71. package/dist/chunk-V2MJE6NC.js +133 -0
  72. package/dist/{chunk-D3CLOBE3.js → chunk-V373TRJE.js} +48 -10
  73. package/dist/chunk-VQAXTIBW.js +100 -0
  74. package/dist/chunk-W74BCA7V.js +159 -0
  75. package/dist/{chunk-WP2CX2JO.js → chunk-WELIKLZF.js} +22 -1
  76. package/dist/{chunk-MDEJHMKK.js → chunk-WHVLNFTG.js} +26 -5
  77. package/dist/{chunk-GZ46OVGZ.js → chunk-WSVMLNTO.js} +1 -1
  78. package/dist/{chunk-4LZJXJJS.js → chunk-WXWPK3LI.js} +6 -2
  79. package/dist/chunk-X3D6LXUU.js +185 -0
  80. package/dist/chunk-XJ2F2WO6.js +8 -0
  81. package/dist/{chunk-73HOFQQ5.js → chunk-Y4AGLCY6.js} +1 -1
  82. package/dist/chunk-YBLDE5CG.js +42 -0
  83. package/dist/{chunk-JEYQU3JT.js → chunk-YHYTW5QA.js} +2 -18
  84. package/dist/{chunk-PZOWBI3B.js → chunk-YLL2QXY2.js} +7 -2
  85. package/dist/class/class.codec.js +12 -9
  86. package/dist/class/class.parser.js +11 -8
  87. package/dist/class/class.schema.d.ts +55 -3
  88. package/dist/class/class.schema.js +9 -7
  89. package/dist/class/class.types.d.ts +48 -3
  90. package/dist/condition/condition.codec.d.ts +9 -0
  91. package/dist/condition/condition.entity-type.d.ts +2 -0
  92. package/dist/condition/condition.parser.d.ts +13 -0
  93. package/dist/condition/condition.parser.js +7 -0
  94. package/dist/condition/condition.schema.d.ts +29 -0
  95. package/dist/condition/condition.types.d.ts +15 -0
  96. package/dist/condition/condition.types.js +0 -0
  97. package/dist/dnd/constants.d.ts +22 -0
  98. package/dist/dnd/constants.js +9 -3
  99. package/dist/dnd/formula-tags.js +2 -2
  100. package/dist/dnd/math.d.ts +26 -0
  101. package/dist/dnd/math.js +12 -2
  102. package/dist/dnd/prose-tags.js +1 -1
  103. package/dist/dnd/resource-formula.d.ts +36 -10
  104. package/dist/dnd/resource-formula.js +10 -155
  105. package/dist/dnd/srd-tag-converter.js +3 -3
  106. package/dist/dnd/tag-grammar.js +1 -1
  107. package/dist/dnd/warn-once.d.ts +2 -0
  108. package/dist/dnd/warn-once.js +8 -0
  109. package/dist/entities/base-item-resolver.js +2 -2
  110. package/dist/entities/slug.d.ts +13 -0
  111. package/dist/entities/slug.js +5 -3
  112. package/dist/entities/wikilink-display.d.ts +3 -0
  113. package/dist/entities/wikilink-display.js +10 -0
  114. package/dist/feat/feat.category-codes.d.ts +32 -0
  115. package/dist/feat/feat.category-codes.js +14 -0
  116. package/dist/feat/feat.codec.js +7 -5
  117. package/dist/feat/feat.parser.js +6 -4
  118. package/dist/feat/feat.schema.d.ts +239 -0
  119. package/dist/feat/feat.schema.js +5 -3
  120. package/dist/feat/feat.types.d.ts +37 -1
  121. package/dist/index.js +2033 -6907
  122. package/dist/item/item.codec.js +4 -3
  123. package/dist/item/item.enrichment.js +4 -4
  124. package/dist/item/item.parser.js +3 -2
  125. package/dist/item/item.schema.d.ts +69 -3
  126. package/dist/item/item.schema.js +2 -1
  127. package/dist/item/item.types.d.ts +69 -3
  128. package/dist/monster/monster.codec.d.ts +7 -6
  129. package/dist/monster/monster.enrichment.js +4 -4
  130. package/dist/monster/monster.format.d.ts +135 -0
  131. package/dist/monster/monster.format.js +78 -0
  132. package/dist/monster/monster.parser.d.ts +6 -1
  133. package/dist/monster/monster.parser.js +10 -1
  134. package/dist/monster/monster.schema.d.ts +369 -0
  135. package/dist/monster/monster.schema.js +34 -0
  136. package/dist/monster/monster.types.d.ts +200 -14
  137. package/dist/optional-feature/optional-feature.codec.js +6 -5
  138. package/dist/optional-feature/optional-feature.parser.js +5 -4
  139. package/dist/optional-feature/optional-feature.schema.d.ts +185 -0
  140. package/dist/optional-feature/optional-feature.schema.js +4 -3
  141. package/dist/pack.d.ts +1 -1
  142. package/dist/pc/conditions.constants.d.ts +0 -1
  143. package/dist/pc/conditions.constants.js +1 -3
  144. package/dist/pc/decision-recognizer.js +1 -1
  145. package/dist/pc/pc.additional-spells.d.ts +47 -0
  146. package/dist/pc/pc.additional-spells.js +23 -0
  147. package/dist/pc/pc.asi-flatten.d.ts +18 -0
  148. package/dist/pc/pc.decision-engine.d.ts +105 -17
  149. package/dist/pc/pc.decision-engine.js +28 -12
  150. package/dist/pc/pc.defense-normalize.d.ts +11 -0
  151. package/dist/pc/pc.defense-normalize.js +6 -0
  152. package/dist/pc/pc.equipment.d.ts +31 -2
  153. package/dist/pc/pc.equipment.js +12 -8
  154. package/dist/pc/pc.feature-effects.d.ts +234 -11
  155. package/dist/pc/pc.feature-effects.js +21 -5
  156. package/dist/pc/pc.parser.js +2 -2
  157. package/dist/pc/pc.pools.d.ts +11 -3
  158. package/dist/pc/pc.pools.js +9 -13
  159. package/dist/pc/pc.proficiencies.d.ts +54 -29
  160. package/dist/pc/pc.proficiencies.js +74 -95
  161. package/dist/pc/pc.proficiency-grants.d.ts +58 -0
  162. package/dist/pc/pc.proficiency-normalize.d.ts +24 -0
  163. package/dist/pc/pc.proficiency-normalize.js +12 -0
  164. package/dist/pc/pc.proficiency-query.d.ts +13 -0
  165. package/dist/pc/pc.proficiency-query.js +1 -1
  166. package/dist/pc/pc.recalc.d.ts +69 -14
  167. package/dist/pc/pc.recalc.js +261 -122
  168. package/dist/pc/pc.resolver.d.ts +34 -8
  169. package/dist/pc/pc.resolver.js +27 -12
  170. package/dist/pc/pc.resource-seed.d.ts +3 -2
  171. package/dist/pc/pc.resource-seed.js +19 -17
  172. package/dist/pc/pc.resources.d.ts +87 -0
  173. package/dist/pc/pc.resources.js +21 -0
  174. package/dist/pc/pc.rest.d.ts +10 -1
  175. package/dist/pc/pc.rest.js +66 -12
  176. package/dist/pc/pc.schema.d.ts +139 -3
  177. package/dist/pc/pc.schema.js +4 -2
  178. package/dist/pc/pc.slotting.js +3 -3
  179. package/dist/pc/pc.spellcasting.d.ts +75 -6
  180. package/dist/pc/pc.spellcasting.js +16 -6
  181. package/dist/pc/pc.table-column.d.ts +23 -0
  182. package/dist/pc/pc.table-column.js +13 -3
  183. package/dist/pc/pc.types.d.ts +251 -12
  184. package/dist/pc/pool-layout.d.ts +47 -0
  185. package/dist/pc/pool-layout.js +16 -0
  186. package/dist/pc/roll-scope.d.ts +2 -0
  187. package/dist/pc/roll-scope.js +8 -0
  188. package/dist/pc/roll-tag-labels.d.ts +40 -0
  189. package/dist/pc/roll-tag-labels.js +17 -0
  190. package/dist/pc/spell-source.d.ts +21 -0
  191. package/dist/pc/spell-source.js +13 -0
  192. package/dist/race/race.codec.js +9 -7
  193. package/dist/race/race.parser.js +8 -6
  194. package/dist/race/race.schema.d.ts +51 -1
  195. package/dist/race/race.schema.js +8 -5
  196. package/dist/race/race.structural.d.ts +7 -0
  197. package/dist/race/race.structural.js +6 -0
  198. package/dist/race/race.types.d.ts +20 -0
  199. package/dist/schemas/attack-schema.d.ts +5 -0
  200. package/dist/schemas/caster-type-schema.d.ts +14 -0
  201. package/dist/schemas/caster-type-schema.js +6 -0
  202. package/dist/schemas/choice-schema.js +2 -2
  203. package/dist/schemas/entity-extras-schema.d.ts +53 -0
  204. package/dist/schemas/entity-extras-schema.js +10 -0
  205. package/dist/schemas/equipment-grant-schema.d.ts +2 -0
  206. package/dist/schemas/equipment-grant-schema.js +1 -1
  207. package/dist/schemas/feature-alias.d.ts +34 -0
  208. package/dist/schemas/feature-effect-schema.d.ts +133 -0
  209. package/dist/schemas/feature-effect-schema.js +1 -1
  210. package/dist/schemas/feature-schema.d.ts +8 -0
  211. package/dist/schemas/feature-schema.js +6 -4
  212. package/dist/schemas/resource-schema.d.ts +43 -0
  213. package/dist/schemas/resource-schema.js +1 -1
  214. package/dist/schemas/selection-pool-schema.d.ts +2 -0
  215. package/dist/schemas/selection-pool-schema.js +1 -1
  216. package/dist/spell/spell.access.js +23 -3
  217. package/dist/spell/spell.codec.js +4 -3
  218. package/dist/spell/spell.enrichment.js +4 -4
  219. package/dist/spell/spell.parser.d.ts +30 -0
  220. package/dist/spell/spell.parser.js +9 -2
  221. package/dist/spell/spell.scaling.d.ts +27 -6
  222. package/dist/spell/spell.scaling.js +25 -6
  223. package/dist/spell/spell.schema.d.ts +44 -2
  224. package/dist/spell/spell.schema.js +2 -1
  225. package/dist/spell/spell.types.d.ts +14 -0
  226. package/dist/subclass/subclass.codec.d.ts +1 -1
  227. package/dist/subclass/subclass.codec.js +11 -8
  228. package/dist/subclass/subclass.parser.js +10 -7
  229. package/dist/subclass/subclass.schema.d.ts +54 -7
  230. package/dist/subclass/subclass.schema.js +8 -6
  231. package/dist/subclass/subclass.types.d.ts +20 -2
  232. package/dist/types/choice.d.ts +53 -2
  233. package/dist/types/choice.js +13 -1
  234. package/dist/types/equipment-grant.d.ts +8 -1
  235. package/dist/types/feature-effect.d.ts +85 -23
  236. package/dist/types/feature.d.ts +70 -1
  237. package/dist/types/optional-feature.types.d.ts +28 -0
  238. package/dist/types/resource.d.ts +21 -1
  239. package/dist/types/selection-pool.d.ts +17 -2
  240. package/dist/weapon/weapon.classify.js +2 -2
  241. package/dist/weapon/weapon.codec.js +4 -3
  242. package/dist/weapon/weapon.parser.js +3 -2
  243. package/dist/weapon/weapon.schema.d.ts +4 -0
  244. package/dist/weapon/weapon.schema.js +2 -1
  245. package/dist/weapon/weapon.types.d.ts +8 -0
  246. package/package.json +2 -2
  247. package/dist/chunk-5XUXXAQJ.js +0 -30
  248. package/dist/chunk-7P65Q426.js +0 -158
  249. package/dist/chunk-7TEK5KK7.js +0 -49
  250. package/dist/chunk-AIRFC5NQ.js +0 -143
  251. package/dist/chunk-CZPMTQOY.js +0 -68
  252. package/dist/chunk-N2JE7BWL.js +0 -81
  253. package/dist/chunk-NXR3BD2V.js +0 -90
  254. package/dist/chunk-O5EYMKFH.js +0 -6
  255. package/dist/chunk-Q5K6LWBZ.js +0 -45
  256. package/dist/chunk-TNUFPIXI.js +0 -1110
  257. package/dist/chunk-TZK36BU7.js +0 -38
  258. package/dist/chunk-W7HWMMBF.js +0 -46
  259. package/dist/chunk-Y4I74YEG.js +0 -33
@@ -0,0 +1,302 @@
1
+ import {
2
+ normalizeRollScope
3
+ } from "./chunk-TOM66P2P.js";
4
+ import {
5
+ toProfSlug
6
+ } from "./chunk-RLGQ5PZZ.js";
7
+ import {
8
+ warnOnce
9
+ } from "./chunk-LVQLWRCU.js";
10
+ import {
11
+ bareEntitySlug
12
+ } from "./chunk-6X4KUDMU.js";
13
+ import {
14
+ normalizeAbility
15
+ } from "./chunk-WELIKLZF.js";
16
+
17
+ // src/pc/pc.feature-effects.ts
18
+ function emptyFeatureEffectTotals() {
19
+ return {
20
+ initiative_bonus: 0,
21
+ hp_per_level_bonus: 0,
22
+ hp_per_level_terms: [],
23
+ speed_walk_bonus: 0,
24
+ speed_walk_set: 0,
25
+ attunement_set: 0,
26
+ ability_bonus: {},
27
+ senses: { darkvision: 0, blindsight: 0, tremorsense: 0, truesight: 0 },
28
+ ac_terms: [],
29
+ resistances: [],
30
+ immunities: [],
31
+ vulnerabilities: [],
32
+ condition_immunities: [],
33
+ proficiencies: { skills: [], skillExpertise: [], tools: [], languages: [], saves: [], armor: [], weapons: [] },
34
+ weaponAbilities: [],
35
+ rollModifiers: [],
36
+ saveOutcomes: [],
37
+ critRange: 20,
38
+ extraAttack: 0,
39
+ attackNotes: [],
40
+ damageBonuses: []
41
+ };
42
+ }
43
+ function pushUnique(list, value) {
44
+ const key = value.trim().toLowerCase();
45
+ if (!key) return;
46
+ if (!list.some((v) => v.trim().toLowerCase() === key)) list.push(value.trim());
47
+ }
48
+ function pushDefenseGrant(list, value, source, condition) {
49
+ const key = value.trim().toLowerCase();
50
+ if (!key) return;
51
+ const existing = list.find((g) => g.value.trim().toLowerCase() === key);
52
+ if (existing) {
53
+ if (!existing.sources.includes(source)) existing.sources.push(source);
54
+ if (!existing.condition && condition) existing.condition = condition;
55
+ return;
56
+ }
57
+ list.push({ value: value.trim(), sources: [source], ...condition ? { condition } : {} });
58
+ }
59
+ function foldsNow(rf, activeBuffs) {
60
+ if (rf.feature.activatable !== true) return true;
61
+ const id = rf.feature.id;
62
+ return !!id && activeBuffs.has(id);
63
+ }
64
+ function assembleEffectFeatures(resolved) {
65
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
66
+ const activeBuffs = new Set((_b = (_a = resolved.state) == null ? void 0 : _a.active_buffs) != null ? _b : []);
67
+ const featureIds = new Set(((_c = resolved.features) != null ? _c : []).map((f) => f.feature.id).filter((id) => !!id));
68
+ const aliases = [];
69
+ for (const pool of (_d = resolved.pools) != null ? _d : []) {
70
+ for (const entry of [...(_e = pool.selected) != null ? _e : [], ...(_f = pool.grants) != null ? _f : []]) {
71
+ if (activeBuffs.has(entry.slug)) continue;
72
+ const bare = bareEntitySlug(entry.slug);
73
+ for (const stored of activeBuffs) {
74
+ if (featureIds.has(stored)) continue;
75
+ if (bareEntitySlug(stored) === bare) {
76
+ aliases.push(entry.slug);
77
+ break;
78
+ }
79
+ }
80
+ }
81
+ }
82
+ for (const a of aliases) activeBuffs.add(a);
83
+ const buffFeatures = [];
84
+ const pushBoon = (item, pool) => {
85
+ var _a2, _b2, _c2, _d2, _e2, _f2, _g2;
86
+ const e = item.entity;
87
+ if (!e || ((_b2 = (_a2 = e.effects) == null ? void 0 : _a2.length) != null ? _b2 : 0) === 0) return;
88
+ buffFeatures.push({
89
+ feature: { id: item.slug, name: e.name, activatable: (_c2 = e.activatable) != null ? _c2 : false, effects: e.effects },
90
+ // source is inert for the fold; attribute to the pool's owning class
91
+ // (never a hardcoded class) so the generic engine carries no homebrew name.
92
+ source: { kind: "class", slug: (_g2 = (_f2 = (_e2 = (_d2 = resolved.classes) == null ? void 0 : _d2[pool.classIndex]) == null ? void 0 : _e2.entity) == null ? void 0 : _f2.slug) != null ? _g2 : pool.id, level: pool.anchorLevel }
93
+ });
94
+ };
95
+ for (const pool of (_g = resolved.pools) != null ? _g : []) {
96
+ for (const sel of (_h = pool.selected) != null ? _h : []) pushBoon(sel, pool);
97
+ for (const g of (_i = pool.grants) != null ? _i : []) pushBoon(g, pool);
98
+ }
99
+ return { features: [...(_j = resolved.features) != null ? _j : [], ...buffFeatures], activeBuffs };
100
+ }
101
+ function foldsOnSelf(eff) {
102
+ if (eff.subject === void 0 || eff.subject === "self") return true;
103
+ warnOnce(eff.subject, `archivist: unrecognised effect subject "${eff.subject}" (only "self" folds)`);
104
+ return false;
105
+ }
106
+ function selfEffectsOf(f) {
107
+ var _a;
108
+ return ((_a = f.effects) != null ? _a : []).filter(foldsOnSelf);
109
+ }
110
+ function computeFeatureEffects(features, opts) {
111
+ var _a, _b, _c;
112
+ const out = emptyFeatureEffectTotals();
113
+ for (const rf of features) {
114
+ if (!foldsNow(rf, (_a = opts == null ? void 0 : opts.activeBuffs) != null ? _a : /* @__PURE__ */ new Set())) continue;
115
+ const level = (_b = opts == null ? void 0 : opts.levelFor) == null ? void 0 : _b.call(opts, rf.source);
116
+ for (const eff of selfEffectsOf(rf.feature)) {
117
+ applyEffect(out, eff, (_c = rf.feature.name) != null ? _c : "Feature", level);
118
+ }
119
+ }
120
+ return out;
121
+ }
122
+ function classifyProficiencyEffect(eff) {
123
+ if (eff.kind !== "proficiency") return null;
124
+ const raw = eff.value;
125
+ const expertise = eff.expertise === true;
126
+ switch (eff.proficiency_type) {
127
+ case "skill":
128
+ return { bucket: "skills", value: toProfSlug(raw), raw, expertise };
129
+ case "tool":
130
+ return { bucket: "tools", value: toProfSlug(raw), raw, expertise };
131
+ case "language":
132
+ return { bucket: "languages", value: toProfSlug(raw), raw, expertise };
133
+ case "armor":
134
+ return { bucket: "armor", value: raw.toLowerCase(), raw, expertise };
135
+ case "weapon":
136
+ return { bucket: "weapons", value: raw.toLowerCase(), raw, expertise };
137
+ default: {
138
+ const ab = normalizeAbility(raw);
139
+ return ab ? { bucket: "saves", value: ab, raw, expertise } : null;
140
+ }
141
+ }
142
+ }
143
+ function collectProficiencyEffectGrants(features, activeBuffs) {
144
+ const out = {
145
+ skills: [],
146
+ tools: [],
147
+ languages: [],
148
+ saves: [],
149
+ armor: [],
150
+ weapons: []
151
+ };
152
+ for (const rf of features) {
153
+ if (!foldsNow(rf, activeBuffs)) continue;
154
+ for (const eff of selfEffectsOf(rf.feature)) {
155
+ const c = classifyProficiencyEffect(eff);
156
+ if (!c) continue;
157
+ out[c.bucket].push({
158
+ value: c.value,
159
+ raw: c.raw,
160
+ sourceKind: rf.source.kind,
161
+ sourceSlug: rf.source.slug,
162
+ ...c.expertise ? { expertise: true } : {}
163
+ });
164
+ }
165
+ }
166
+ return out;
167
+ }
168
+ function scaled(base, key, scalesAt, level) {
169
+ if (level === void 0 || !(scalesAt == null ? void 0 : scalesAt.length)) return base;
170
+ let best = base;
171
+ let bestLevel = -1;
172
+ for (const step of scalesAt) {
173
+ if (step.level > level || step.level <= bestLevel) continue;
174
+ const v = step[key];
175
+ if (typeof v !== "number") continue;
176
+ best = v;
177
+ bestLevel = step.level;
178
+ }
179
+ return best;
180
+ }
181
+ function applyEffect(out, eff, label, level) {
182
+ var _a, _b, _c, _d, _e;
183
+ switch (eff.kind) {
184
+ case "initiative-bonus":
185
+ out.initiative_bonus += eff.value;
186
+ break;
187
+ case "hp-per-level-bonus":
188
+ out.hp_per_level_bonus += eff.value;
189
+ out.hp_per_level_terms.push({ label, value: eff.value });
190
+ break;
191
+ case "speed-bonus": {
192
+ const value = scaled(eff.value, "value", eff.scales_at, level);
193
+ if (eff.mode === "walk") {
194
+ if (eff.set) out.speed_walk_set = Math.max(out.speed_walk_set, value);
195
+ else out.speed_walk_bonus += value;
196
+ }
197
+ break;
198
+ }
199
+ case "attunement-limit": {
200
+ out.attunement_set = Math.max(out.attunement_set, scaled(eff.value, "value", eff.scales_at, level));
201
+ break;
202
+ }
203
+ case "sense":
204
+ out.senses[eff.type] = Math.max(out.senses[eff.type], eff.range);
205
+ break;
206
+ case "resistance":
207
+ pushDefenseGrant(out.resistances, eff.damage_type, label, eff.condition);
208
+ break;
209
+ case "immunity":
210
+ pushDefenseGrant(out.immunities, eff.damage_type, label, eff.condition);
211
+ break;
212
+ case "vulnerability":
213
+ pushDefenseGrant(out.vulnerabilities, eff.damage_type, label, eff.condition);
214
+ break;
215
+ case "immune-condition":
216
+ if (!eff.while) pushDefenseGrant(out.condition_immunities, eff.condition, label);
217
+ break;
218
+ case "proficiency": {
219
+ const c = classifyProficiencyEffect(eff);
220
+ if (!c) break;
221
+ if (c.bucket === "saves") {
222
+ pushUnique(out.proficiencies.saves, c.value);
223
+ } else {
224
+ pushUnique(out.proficiencies[c.bucket], c.value);
225
+ }
226
+ if (c.expertise && c.bucket === "skills") pushUnique(out.proficiencies.skillExpertise, c.value);
227
+ break;
228
+ }
229
+ case "ac-bonus":
230
+ out.ac_terms.push({
231
+ value: eff.value,
232
+ requires_armor: eff.requires_armor === true,
233
+ label,
234
+ condition: eff.condition
235
+ });
236
+ break;
237
+ case "weapon-ability": {
238
+ if (eff.ability !== "spellcasting") {
239
+ const w = eff.weapons;
240
+ const weaponSlugs = !w || w === "chosen" ? void 0 : Array.isArray(w) ? w.map(bareEntitySlug) : [bareEntitySlug(w)];
241
+ out.weaponAbilities.push({ ability: eff.ability, weaponSlugs });
242
+ }
243
+ break;
244
+ }
245
+ case "roll-modifier": {
246
+ const rolls = eff.roll === "any" ? ["ability-check", "saving-throw", "attack"] : [eff.roll];
247
+ const scopes = (_a = normalizeRollScope(eff.scope, rolls[0])) != null ? _a : [eff.scope];
248
+ for (const roll of rolls) for (const scope of scopes)
249
+ out.rollModifiers.push({ mode: eff.mode, roll, scope, condition: eff.condition, label });
250
+ break;
251
+ }
252
+ case "save-outcome":
253
+ out.saveOutcomes.push({
254
+ ability: eff.ability === "any" ? void 0 : eff.ability,
255
+ on_success: eff.on_success,
256
+ on_failure: eff.on_failure,
257
+ appliesTo: (_b = eff.applies_to) != null ? _b : void 0,
258
+ condition: eff.condition,
259
+ label
260
+ });
261
+ break;
262
+ case "crit-range":
263
+ if (((_c = eff.applies_to) != null ? _c : "weapon") !== "spell") out.critRange = Math.min(out.critRange, eff.min_roll);
264
+ break;
265
+ case "extra-attack":
266
+ out.extraAttack = Math.max(out.extraAttack, scaled(eff.count, "count", eff.scales_at, level));
267
+ break;
268
+ case "reroll-damage":
269
+ out.attackNotes.push(`Reroll ${eff.max_reroll}s${eff.once_per_die ? " (once/die)" : ""}`);
270
+ break;
271
+ case "attack-rule":
272
+ if (eff.flag === "no-ranged-in-melee-disadvantage") out.attackNotes.push("No disadvantage firing in melee");
273
+ break;
274
+ case "damage-bonus":
275
+ if (((_d = eff.applies_to) != null ? _d : "weapon") !== "spell") {
276
+ out.damageBonuses.push({
277
+ amount: eff.amount,
278
+ damage_type: eff.damage_type,
279
+ source: label,
280
+ ...eff.condition ? { condition: eff.condition } : {}
281
+ });
282
+ }
283
+ break;
284
+ case "ability-score-increase":
285
+ if (eff.abilities === "chosen") break;
286
+ for (const ab of eff.abilities) out.ability_bonus[ab] = ((_e = out.ability_bonus[ab]) != null ? _e : 0) + eff.amount;
287
+ break;
288
+ default:
289
+ break;
290
+ }
291
+ }
292
+
293
+ export {
294
+ emptyFeatureEffectTotals,
295
+ foldsNow,
296
+ assembleEffectFeatures,
297
+ foldsOnSelf,
298
+ selfEffectsOf,
299
+ computeFeatureEffects,
300
+ classifyProficiencyEffect,
301
+ collectProficiencyEffectGrants
302
+ };
@@ -0,0 +1,106 @@
1
+ // src/types/choice.ts
2
+ var ALL_SKILL_SLUGS = [
3
+ "acrobatics",
4
+ "animal-handling",
5
+ "arcana",
6
+ "athletics",
7
+ "deception",
8
+ "history",
9
+ "insight",
10
+ "intimidation",
11
+ "investigation",
12
+ "medicine",
13
+ "nature",
14
+ "perception",
15
+ "performance",
16
+ "persuasion",
17
+ "religion",
18
+ "sleight-of-hand",
19
+ "stealth",
20
+ "survival"
21
+ ];
22
+ var STANDARD_LANGUAGES = [
23
+ "common",
24
+ "dwarvish",
25
+ "elvish",
26
+ "giant",
27
+ "gnomish",
28
+ "goblin",
29
+ "halfling",
30
+ "orc"
31
+ ];
32
+ var EXOTIC_LANGUAGES = [
33
+ "abyssal",
34
+ "celestial",
35
+ "deep-speech",
36
+ "draconic",
37
+ "infernal",
38
+ "primordial",
39
+ "sylvan",
40
+ "undercommon"
41
+ ];
42
+ var SECRET_LANGUAGES = ["druidic", "thieves'-cant"];
43
+ var ALL_LANGUAGES = [
44
+ ...STANDARD_LANGUAGES,
45
+ ...EXOTIC_LANGUAGES,
46
+ ...SECRET_LANGUAGES
47
+ ];
48
+ var ARTISANS_TOOLS = [
49
+ "alchemist's-supplies",
50
+ "brewer's-supplies",
51
+ "calligrapher's-supplies",
52
+ "carpenter's-tools",
53
+ "cartographer's-tools",
54
+ "cobbler's-tools",
55
+ "cook's-utensils",
56
+ "glassblower's-tools",
57
+ "jeweler's-tools",
58
+ "leatherworker's-tools",
59
+ "mason's-tools",
60
+ "painter's-supplies",
61
+ "potter's-tools",
62
+ "smith's-tools",
63
+ "tinker's-tools",
64
+ "weaver's-tools",
65
+ "woodcarver's-tools"
66
+ ];
67
+ var MUSICAL_INSTRUMENTS = [
68
+ "bagpipes",
69
+ "drum",
70
+ "dulcimer",
71
+ "flute",
72
+ "horn",
73
+ "lute",
74
+ "lyre",
75
+ "pan-flute",
76
+ "shawm",
77
+ "viol"
78
+ ];
79
+ var GAMING_SETS = ["dice-set", "playing-cards"];
80
+ var OTHER_TOOLS = [
81
+ "disguise-kit",
82
+ "forgery-kit",
83
+ "herbalism-kit",
84
+ "navigator's-tools",
85
+ "poisoner's-kit",
86
+ "thieves'-tools"
87
+ ];
88
+ var ALL_TOOLS = [
89
+ ...ARTISANS_TOOLS,
90
+ ...MUSICAL_INSTRUMENTS,
91
+ ...GAMING_SETS,
92
+ ...OTHER_TOOLS
93
+ ];
94
+
95
+ export {
96
+ ALL_SKILL_SLUGS,
97
+ STANDARD_LANGUAGES,
98
+ EXOTIC_LANGUAGES,
99
+ SECRET_LANGUAGES,
100
+ ALL_LANGUAGES,
101
+ ARTISANS_TOOLS,
102
+ MUSICAL_INSTRUMENTS,
103
+ GAMING_SETS,
104
+ OTHER_TOOLS,
105
+ ALL_TOOLS
106
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseWeapon
3
- } from "./chunk-6NK2MUD3.js";
3
+ } from "./chunk-HZA7OE7N.js";
4
4
 
5
5
  // src/weapon/weapon.codec.ts
6
6
  import yaml from "js-yaml";
@@ -0,0 +1,15 @@
1
+ // src/dnd/warn-once.ts
2
+ var seen = /* @__PURE__ */ new Set();
3
+ function warnOnce(key, msg) {
4
+ if (seen.has(key)) return;
5
+ seen.add(key);
6
+ console.warn(msg);
7
+ }
8
+ function __resetWarnOnceForTests() {
9
+ seen.clear();
10
+ }
11
+
12
+ export {
13
+ warnOnce,
14
+ __resetWarnOnceForTests
15
+ };
@@ -0,0 +1,161 @@
1
+ import {
2
+ RACE_STRUCTURAL_PSEUDO
3
+ } from "./chunk-PVCEA4HZ.js";
4
+ import {
5
+ warnOnce
6
+ } from "./chunk-LVQLWRCU.js";
7
+ import {
8
+ slugify
9
+ } from "./chunk-6X4KUDMU.js";
10
+ import {
11
+ mirrorSpellShapes
12
+ } from "./chunk-OZP6JFGT.js";
13
+ import {
14
+ ABILITY_KEYS
15
+ } from "./chunk-WELIKLZF.js";
16
+
17
+ // src/pc/pc.additional-spells.ts
18
+ var entriesOf = (a) => Array.isArray(a) ? a : a && typeof a === "object" ? [a] : [];
19
+ var editionOf = (x) => {
20
+ const e = x == null ? void 0 : x.edition;
21
+ return typeof e === "string" ? e : void 0;
22
+ };
23
+ var prefixOf = (slug) => {
24
+ var _a;
25
+ return (_a = slug.split("_")[0]) != null ? _a : slug;
26
+ };
27
+ function normalizeSpellRef(ref) {
28
+ var _a, _b, _c;
29
+ if (typeof ref !== "string") return null;
30
+ let s = ref.trim();
31
+ const wl = /^\[\[(.+)\]\]$/.exec(s);
32
+ if (wl) {
33
+ const target = (_a = wl[1].split("|")[0]) != null ? _a : "";
34
+ const tail = (_b = target.split("/").pop()) != null ? _b : target;
35
+ const w = slugify(tail.replace(/#.*$/, "").trim());
36
+ return w.length > 0 ? w : null;
37
+ }
38
+ s = s.replace(/#.*$/, "");
39
+ s = (_c = s.split("|")[0]) != null ? _c : "";
40
+ const out = slugify(s.trim());
41
+ return out.length > 0 ? out : null;
42
+ }
43
+ function buildSpellNameIndex(entities) {
44
+ var _a;
45
+ const byName = /* @__PURE__ */ new Map();
46
+ for (const e of entities.search("", "spell", Number.POSITIVE_INFINITY)) {
47
+ const raw = e.data.name;
48
+ const key = slugify(typeof raw === "string" ? raw : e.name);
49
+ const list = (_a = byName.get(key)) != null ? _a : [];
50
+ list.push(e);
51
+ byName.set(key, list);
52
+ }
53
+ return { byName };
54
+ }
55
+ function resolveSpellByName(opts) {
56
+ var _a;
57
+ const { entities, index, nameSlug, carrierSlug, carrierEdition, alreadyCollected } = opts;
58
+ const own = alreadyCollected.find((s) => {
59
+ var _a2;
60
+ return slugify(String((_a2 = s.entity.name) != null ? _a2 : "")) === nameSlug;
61
+ });
62
+ if (own) return { entity: own.entity, slug: own.slug };
63
+ const exact = entities.getByTypeAndSlug("spell", `${prefixOf(carrierSlug)}_spell_${nameSlug}`);
64
+ if (exact) return { entity: mirrorSpellShapes(exact.data), slug: exact.slug };
65
+ const cands = (_a = index.byName.get(nameSlug)) != null ? _a : [];
66
+ if (cands.length === 0) {
67
+ warnOnce(`addspells:miss:${nameSlug}`, `archivist: additional_spells ref "${nameSlug}" matches no spell in the compendium`);
68
+ return null;
69
+ }
70
+ const prefix = prefixOf(carrierSlug);
71
+ let pool = cands.filter((e) => prefixOf(e.slug) === prefix);
72
+ if (pool.length === 0) pool = cands;
73
+ if (carrierEdition !== void 0) {
74
+ const same = pool.filter((e) => editionOf(e.data) === carrierEdition);
75
+ if (same.length > 0) pool = same;
76
+ }
77
+ pool = [...pool].sort((a, b) => a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0);
78
+ if (pool.length > 1) warnOnce(`addspells:multi:${nameSlug}`, `archivist: additional_spells ref "${nameSlug}" matches ${cands.length} spells; using "${pool[0].slug}"`);
79
+ return { entity: mirrorSpellShapes(pool[0].data), slug: pool[0].slug };
80
+ }
81
+ var hasContentSelectInline = (choices) => Array.isArray(choices) && choices.some((c) => (c == null ? void 0 : c.kind) === "select-inline" && typeof c.id === "string" && !RACE_STRUCTURAL_PSEUDO.has(c.id) || Array.isArray(c == null ? void 0 : c.options) && c.options.some((o) => hasContentSelectInline(o == null ? void 0 : o.choices)));
82
+ function raceIsUngated(race) {
83
+ var _a;
84
+ const r = race;
85
+ if (hasContentSelectInline(r.choices)) return false;
86
+ return !((_a = r.traits) != null ? _a : []).some((t) => hasContentSelectInline(t == null ? void 0 : t.choices));
87
+ }
88
+ var readAbility = (v, own) => {
89
+ if (v && typeof v === "object" && "choose" in v) return "choose";
90
+ if (typeof v !== "string") return void 0;
91
+ if (v === "inherit") return own != null ? own : void 0;
92
+ return ABILITY_KEYS.includes(v) ? v : void 0;
93
+ };
94
+ var entryHasInSliceLeaf = (e, comparand, own) => readAbility(e.ability, own) !== "choose" && ["known", "prepared"].some((b) => {
95
+ const lv = e[b];
96
+ 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
+ });
98
+ function collectAdditionalSpells(args) {
99
+ const { race, feats, classes, totalLevel, ownAbility, alreadyCollected, entities, warnings } = args;
100
+ let index = null;
101
+ const indexOf = () => index != null ? index : index = buildSpellNameIndex(entities);
102
+ const out = [];
103
+ const carriers = [];
104
+ const asRaw = (x) => x == null ? void 0 : x.additional_spells;
105
+ if (race && raceIsUngated(race)) carriers.push({ root: "race", slug: race.slug, edition: editionOf(race), entries: entriesOf(asRaw(race)), comparand: totalLevel, classSlug: null });
106
+ for (const f of feats) carriers.push({ root: "feat", slug: f.slug, edition: editionOf(f), entries: entriesOf(asRaw(f)), comparand: totalLevel, classSlug: null });
107
+ for (const c of classes) {
108
+ if (!c.entity) continue;
109
+ 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
+ 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
+ }
112
+ for (const car of carriers) {
113
+ if (car.entries.length === 0) continue;
114
+ if (car.entries.length > 1) {
115
+ if (car.entries.some((e) => entryHasInSliceLeaf(e, car.comparand, ownAbility))) warnOnce(`addspells:siblings:${car.slug}`, `archivist: additional_spells on "${car.slug}" has ${car.entries.length} sibling entries (a choose-one grant); skipped`);
116
+ continue;
117
+ }
118
+ const entry = car.entries[0];
119
+ const ability = readAbility(entry.ability, ownAbility);
120
+ if (ability === "choose") continue;
121
+ for (const bucket of ["known", "prepared"]) {
122
+ const levels = entry[bucket];
123
+ if (!levels || typeof levels !== "object" || Array.isArray(levels)) continue;
124
+ for (const [k, v] of Object.entries(levels)) {
125
+ if (!(k === "_" || Number(k) <= car.comparand)) continue;
126
+ if (!Array.isArray(v)) {
127
+ warnOnce(`addspells:usage:${car.slug}:${bucket}:${k}`, `archivist: additional_spells on "${car.slug}" ${bucket}.${k} is a usage economy (skipped; G8)`);
128
+ continue;
129
+ }
130
+ for (const leaf of v) {
131
+ const nameSlug = normalizeSpellRef(leaf);
132
+ if (!nameSlug) continue;
133
+ const hit = resolveSpellByName({ entities, index: indexOf(), nameSlug, carrierSlug: car.slug, carrierEdition: car.edition, alreadyCollected: [...alreadyCollected, ...out] });
134
+ if (!hit) {
135
+ warnings.push(`additional_spells ref "${String(leaf)}" on ${car.slug} not found in compendium.`);
136
+ continue;
137
+ }
138
+ out.push({
139
+ entity: hit.entity,
140
+ slug: hit.slug,
141
+ classSlug: car.classSlug,
142
+ source: car.root === "race" ? "race" : car.root === "feat" ? "feat" : "class",
143
+ prepared: true,
144
+ alwaysPrepared: true,
145
+ ...ability ? { ability } : {}
146
+ });
147
+ }
148
+ }
149
+ }
150
+ }
151
+ return out;
152
+ }
153
+
154
+ export {
155
+ prefixOf,
156
+ normalizeSpellRef,
157
+ buildSpellNameIndex,
158
+ resolveSpellByName,
159
+ raceIsUngated,
160
+ collectAdditionalSpells
161
+ };
@@ -1,12 +1,16 @@
1
1
  import {
2
2
  raceEntitySchema
3
- } from "./chunk-2BWAID3Y.js";
3
+ } from "./chunk-UMWIPLLM.js";
4
+ import {
5
+ aliasFeatureListActionCost
6
+ } from "./chunk-4U55VNC7.js";
4
7
 
5
8
  // src/race/race.parser.ts
6
9
  import { parseYaml } from "@archivist-gg/core";
7
10
  function parseRace(source) {
8
11
  const raw = parseYaml(source, ["name", "slug"]);
9
12
  if (!raw.success) return raw;
13
+ aliasFeatureListActionCost(raw.data.traits);
10
14
  const result = raceEntitySchema.safeParse({ edition: "2014", ...raw.data });
11
15
  if (!result.success) {
12
16
  return { success: false, error: `race schema validation failed: ${result.error.message}` };