@archivist-gg/dnd5e 0.1.0

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 (322) hide show
  1. package/LICENSE +21 -0
  2. package/LICENSES/SRD.md +37 -0
  3. package/README.md +65 -0
  4. package/dist/armor/armor.codec.d.ts +10 -0
  5. package/dist/armor/armor.codec.js +8 -0
  6. package/dist/armor/armor.entity-type.d.ts +2 -0
  7. package/dist/armor/armor.parser.d.ts +3 -0
  8. package/dist/armor/armor.parser.js +7 -0
  9. package/dist/armor/armor.schema.d.ts +25 -0
  10. package/dist/armor/armor.schema.js +6 -0
  11. package/dist/armor/armor.types.d.ts +26 -0
  12. package/dist/armor/armor.types.js +0 -0
  13. package/dist/background/background.codec.d.ts +9 -0
  14. package/dist/background/background.codec.js +11 -0
  15. package/dist/background/background.entity-type.d.ts +2 -0
  16. package/dist/background/background.parser.d.ts +3 -0
  17. package/dist/background/background.parser.js +10 -0
  18. package/dist/background/background.schema.d.ts +74 -0
  19. package/dist/background/background.schema.js +9 -0
  20. package/dist/background/background.types.d.ts +52 -0
  21. package/dist/background/background.types.js +0 -0
  22. package/dist/chunk-24GFQU7T.js +18 -0
  23. package/dist/chunk-3DZ74AKK.js +28 -0
  24. package/dist/chunk-3TM34RSJ.js +23 -0
  25. package/dist/chunk-3TRGRSCH.js +8 -0
  26. package/dist/chunk-4C2KOKLF.js +185 -0
  27. package/dist/chunk-4F6AIZ36.js +19 -0
  28. package/dist/chunk-4SSDYJS2.js +86 -0
  29. package/dist/chunk-4TIJKN6O.js +37 -0
  30. package/dist/chunk-537BHVSH.js +18 -0
  31. package/dist/chunk-5INR4NSL.js +18 -0
  32. package/dist/chunk-5XUXXAQJ.js +30 -0
  33. package/dist/chunk-656QCX4U.js +22 -0
  34. package/dist/chunk-66XXW2Z2.js +133 -0
  35. package/dist/chunk-6NK2MUD3.js +114 -0
  36. package/dist/chunk-6PXE7T7C.js +79 -0
  37. package/dist/chunk-7P65Q426.js +158 -0
  38. package/dist/chunk-A7T5L7SS.js +83 -0
  39. package/dist/chunk-AFKTJXRA.js +39 -0
  40. package/dist/chunk-B7UXKNLD.js +46 -0
  41. package/dist/chunk-BAXWWGFJ.js +45 -0
  42. package/dist/chunk-BH6AMZVY.js +25 -0
  43. package/dist/chunk-CDW7R2QT.js +62 -0
  44. package/dist/chunk-DABBCYTT.js +68 -0
  45. package/dist/chunk-DC74TZNA.js +46 -0
  46. package/dist/chunk-DNAJRMNN.js +68 -0
  47. package/dist/chunk-DRNZJNJP.js +19 -0
  48. package/dist/chunk-FEOB24KP.js +18 -0
  49. package/dist/chunk-FFULAF2P.js +63 -0
  50. package/dist/chunk-FUK7QSN5.js +19 -0
  51. package/dist/chunk-GIOH5JB5.js +19 -0
  52. package/dist/chunk-GJOI2GHP.js +25 -0
  53. package/dist/chunk-GZ46OVGZ.js +31 -0
  54. package/dist/chunk-HCP3GMHS.js +44 -0
  55. package/dist/chunk-IDTDACM6.js +19 -0
  56. package/dist/chunk-IKSMX4TN.js +0 -0
  57. package/dist/chunk-IXZXFFFW.js +269 -0
  58. package/dist/chunk-J2RYSZMQ.js +16 -0
  59. package/dist/chunk-JEYQU3JT.js +36 -0
  60. package/dist/chunk-LSLSG67V.js +31 -0
  61. package/dist/chunk-LWFTHJIE.js +24 -0
  62. package/dist/chunk-MDEJHMKK.js +52 -0
  63. package/dist/chunk-N2JE7BWL.js +81 -0
  64. package/dist/chunk-NAEPSZAM.js +88 -0
  65. package/dist/chunk-NXR3BD2V.js +90 -0
  66. package/dist/chunk-PKC77VZP.js +18 -0
  67. package/dist/chunk-PMADYMUI.js +19 -0
  68. package/dist/chunk-PTXBIDG7.js +63 -0
  69. package/dist/chunk-PZOWBI3B.js +49 -0
  70. package/dist/chunk-QQ5W2AZP.js +515 -0
  71. package/dist/chunk-QS5L5YTJ.js +24 -0
  72. package/dist/chunk-QYZV2MQA.js +97 -0
  73. package/dist/chunk-R2ZV233X.js +233 -0
  74. package/dist/chunk-RAWJTA45.js +271 -0
  75. package/dist/chunk-RSDRMH54.js +18 -0
  76. package/dist/chunk-RVYYKYFL.js +18 -0
  77. package/dist/chunk-SSXBYF3P.js +18 -0
  78. package/dist/chunk-TJCNX6UZ.js +521 -0
  79. package/dist/chunk-TZK36BU7.js +38 -0
  80. package/dist/chunk-UC4FE5X3.js +65 -0
  81. package/dist/chunk-WP2CX2JO.js +235 -0
  82. package/dist/chunk-WYGD2UGN.js +14 -0
  83. package/dist/chunk-XJNECUPT.js +31 -0
  84. package/dist/chunk-XURS6Q6Q.js +127 -0
  85. package/dist/chunk-Y4I74YEG.js +33 -0
  86. package/dist/chunk-Z4TL2V7S.js +18 -0
  87. package/dist/chunk-Z6GXV2GO.js +121 -0
  88. package/dist/chunk-ZLG6FQFB.js +237 -0
  89. package/dist/chunk-ZMR77DDL.js +18 -0
  90. package/dist/class/class.codec.d.ts +10 -0
  91. package/dist/class/class.codec.js +15 -0
  92. package/dist/class/class.entity-type.d.ts +2 -0
  93. package/dist/class/class.parser.d.ts +3 -0
  94. package/dist/class/class.parser.js +14 -0
  95. package/dist/class/class.schema.d.ts +202 -0
  96. package/dist/class/class.schema.js +13 -0
  97. package/dist/class/class.slug.d.ts +11 -0
  98. package/dist/class/class.slug.js +8 -0
  99. package/dist/class/class.types.d.ts +74 -0
  100. package/dist/class/class.types.js +0 -0
  101. package/dist/data-srd/index.generated.d.ts +7 -0
  102. package/dist/data-srd/index.generated.js +146 -0
  103. package/dist/dnd/constants.d.ts +16 -0
  104. package/dist/dnd/constants.js +36 -0
  105. package/dist/dnd/formula-tags.d.ts +16 -0
  106. package/dist/dnd/formula-tags.js +119 -0
  107. package/dist/dnd/math.d.ts +24 -0
  108. package/dist/dnd/math.js +35 -0
  109. package/dist/dnd/prose-tags.d.ts +16 -0
  110. package/dist/dnd/prose-tags.js +18 -0
  111. package/dist/dnd/resource-die.d.ts +6 -0
  112. package/dist/dnd/resource-die.js +17 -0
  113. package/dist/dnd/resource-formula.d.ts +41 -0
  114. package/dist/dnd/resource-formula.js +159 -0
  115. package/dist/dnd/srd-tag-converter.d.ts +44 -0
  116. package/dist/dnd/srd-tag-converter.js +12 -0
  117. package/dist/dnd/tag-grammar.d.ts +19 -0
  118. package/dist/dnd/tag-grammar.js +12 -0
  119. package/dist/encounter/encounter.ai-schema.d.ts +29 -0
  120. package/dist/encounter/encounter.ai-schema.js +8 -0
  121. package/dist/encounter/encounter.entity-type.d.ts +2 -0
  122. package/dist/encounter/encounter.generatable.d.ts +2 -0
  123. package/dist/entities/base-item-resolver.d.ts +21 -0
  124. package/dist/entities/base-item-resolver.js +9 -0
  125. package/dist/entities/slug.d.ts +5 -0
  126. package/dist/entities/slug.js +6 -0
  127. package/dist/feat/feat.codec.d.ts +9 -0
  128. package/dist/feat/feat.codec.js +10 -0
  129. package/dist/feat/feat.entity-type.d.ts +2 -0
  130. package/dist/feat/feat.parser.d.ts +3 -0
  131. package/dist/feat/feat.parser.js +9 -0
  132. package/dist/feat/feat.schema.d.ts +194 -0
  133. package/dist/feat/feat.schema.js +8 -0
  134. package/dist/feat/feat.types.d.ts +42 -0
  135. package/dist/feat/feat.types.js +0 -0
  136. package/dist/index.d.ts +12 -0
  137. package/dist/index.js +139149 -0
  138. package/dist/inline-tag-parser.d.ts +11 -0
  139. package/dist/inline-tag-parser.js +6 -0
  140. package/dist/item/item.actions-map.d.ts +41 -0
  141. package/dist/item/item.actions-map.js +85 -0
  142. package/dist/item/item.ai-schema.d.ts +35 -0
  143. package/dist/item/item.ai-schema.js +6 -0
  144. package/dist/item/item.attunement.d.ts +18 -0
  145. package/dist/item/item.attunement.js +12 -0
  146. package/dist/item/item.bonuses.d.ts +2 -0
  147. package/dist/item/item.bonuses.js +7 -0
  148. package/dist/item/item.codec.d.ts +3 -0
  149. package/dist/item/item.codec.js +8 -0
  150. package/dist/item/item.conditions.d.ts +10 -0
  151. package/dist/item/item.conditions.js +8 -0
  152. package/dist/item/item.conditions.types.d.ts +28 -0
  153. package/dist/item/item.conditions.types.js +0 -0
  154. package/dist/item/item.enrichment.d.ts +4 -0
  155. package/dist/item/item.enrichment.js +9 -0
  156. package/dist/item/item.entity-type.d.ts +2 -0
  157. package/dist/item/item.generatable.d.ts +2 -0
  158. package/dist/item/item.parser.d.ts +3 -0
  159. package/dist/item/item.parser.js +7 -0
  160. package/dist/item/item.schema.d.ts +174 -0
  161. package/dist/item/item.schema.js +8 -0
  162. package/dist/item/item.types.d.ts +108 -0
  163. package/dist/item/item.types.js +0 -0
  164. package/dist/monster/monster.ai-schema.d.ts +67 -0
  165. package/dist/monster/monster.ai-schema.js +6 -0
  166. package/dist/monster/monster.codec.d.ts +13 -0
  167. package/dist/monster/monster.enrichment.d.ts +10 -0
  168. package/dist/monster/monster.enrichment.js +19 -0
  169. package/dist/monster/monster.entity-type.d.ts +2 -0
  170. package/dist/monster/monster.generatable.d.ts +2 -0
  171. package/dist/monster/monster.parser.d.ts +3 -0
  172. package/dist/monster/monster.parser.js +6 -0
  173. package/dist/monster/monster.resolve.d.ts +10 -0
  174. package/dist/monster/monster.types.d.ts +29 -0
  175. package/dist/monster/monster.types.js +1 -0
  176. package/dist/npc/npc.ai-schema.d.ts +6 -0
  177. package/dist/npc/npc.entity-type.d.ts +2 -0
  178. package/dist/npc/npc.generatable.d.ts +2 -0
  179. package/dist/optional-feature/optional-feature.codec.d.ts +14 -0
  180. package/dist/optional-feature/optional-feature.codec.js +11 -0
  181. package/dist/optional-feature/optional-feature.entity-type.d.ts +2 -0
  182. package/dist/optional-feature/optional-feature.parser.d.ts +3 -0
  183. package/dist/optional-feature/optional-feature.parser.js +10 -0
  184. package/dist/optional-feature/optional-feature.schema.d.ts +236 -0
  185. package/dist/optional-feature/optional-feature.schema.js +9 -0
  186. package/dist/pack.d.ts +11 -0
  187. package/dist/pc/conditions.constants.d.ts +3 -0
  188. package/dist/pc/conditions.constants.js +8 -0
  189. package/dist/pc/decision-recognizer.d.ts +4 -0
  190. package/dist/pc/decision-recognizer.js +8 -0
  191. package/dist/pc/pc.conditions.d.ts +3 -0
  192. package/dist/pc/pc.conditions.js +6 -0
  193. package/dist/pc/pc.decision-engine.d.ts +97 -0
  194. package/dist/pc/pc.decision-engine.js +19 -0
  195. package/dist/pc/pc.equipment.d.ts +12 -0
  196. package/dist/pc/pc.equipment.js +15 -0
  197. package/dist/pc/pc.feature-effects.d.ts +95 -0
  198. package/dist/pc/pc.feature-effects.js +9 -0
  199. package/dist/pc/pc.formula-context.d.ts +4 -0
  200. package/dist/pc/pc.formula-context.js +11 -0
  201. package/dist/pc/pc.parser.d.ts +3 -0
  202. package/dist/pc/pc.parser.js +41 -0
  203. package/dist/pc/pc.pools.d.ts +12 -0
  204. package/dist/pc/pc.pools.js +15 -0
  205. package/dist/pc/pc.proficiencies.d.ts +8 -0
  206. package/dist/pc/pc.proficiencies.js +47 -0
  207. package/dist/pc/pc.proficiency-query.d.ts +11 -0
  208. package/dist/pc/pc.proficiency-query.js +17 -0
  209. package/dist/pc/pc.recalc.d.ts +105 -0
  210. package/dist/pc/pc.recalc.js +662 -0
  211. package/dist/pc/pc.resolver.d.ts +34 -0
  212. package/dist/pc/pc.resolver.js +267 -0
  213. package/dist/pc/pc.resource-seed.d.ts +10 -0
  214. package/dist/pc/pc.resource-seed.js +40 -0
  215. package/dist/pc/pc.rest.d.ts +23 -0
  216. package/dist/pc/pc.rest.js +128 -0
  217. package/dist/pc/pc.schema.d.ts +294 -0
  218. package/dist/pc/pc.schema.js +7 -0
  219. package/dist/pc/pc.slotting.d.ts +22 -0
  220. package/dist/pc/pc.slotting.js +20 -0
  221. package/dist/pc/pc.spellcasting.d.ts +56 -0
  222. package/dist/pc/pc.spellcasting.js +16 -0
  223. package/dist/pc/pc.table-column.d.ts +12 -0
  224. package/dist/pc/pc.table-column.js +8 -0
  225. package/dist/pc/pc.types.d.ts +514 -0
  226. package/dist/pc/pc.types.js +0 -0
  227. package/dist/race/race.codec.d.ts +9 -0
  228. package/dist/race/race.codec.js +13 -0
  229. package/dist/race/race.entity-type.d.ts +2 -0
  230. package/dist/race/race.parser.d.ts +3 -0
  231. package/dist/race/race.parser.js +12 -0
  232. package/dist/race/race.schema.d.ts +67 -0
  233. package/dist/race/race.schema.js +11 -0
  234. package/dist/race/race.types.d.ts +44 -0
  235. package/dist/race/race.types.js +0 -0
  236. package/dist/schemas/attack-schema.d.ts +38 -0
  237. package/dist/schemas/choice-schema.d.ts +5 -0
  238. package/dist/schemas/choice-schema.js +7 -0
  239. package/dist/schemas/duration-schema.d.ts +13 -0
  240. package/dist/schemas/duration-schema.js +6 -0
  241. package/dist/schemas/equipment-grant-schema.d.ts +6 -0
  242. package/dist/schemas/equipment-grant-schema.js +12 -0
  243. package/dist/schemas/feature-effect-schema.d.ts +131 -0
  244. package/dist/schemas/feature-effect-schema.js +6 -0
  245. package/dist/schemas/feature-schema.d.ts +2 -0
  246. package/dist/schemas/feature-schema.js +10 -0
  247. package/dist/schemas/index.d.ts +6 -0
  248. package/dist/schemas/resource-schema.d.ts +95 -0
  249. package/dist/schemas/resource-schema.js +12 -0
  250. package/dist/schemas/selection-pool-schema.d.ts +34 -0
  251. package/dist/schemas/selection-pool-schema.js +10 -0
  252. package/dist/spell/spell.access.d.ts +15 -0
  253. package/dist/spell/spell.access.js +25 -0
  254. package/dist/spell/spell.ai-schema.d.ts +24 -0
  255. package/dist/spell/spell.ai-schema.js +6 -0
  256. package/dist/spell/spell.codec.d.ts +3 -0
  257. package/dist/spell/spell.codec.js +8 -0
  258. package/dist/spell/spell.enrichment.d.ts +2 -0
  259. package/dist/spell/spell.enrichment.js +9 -0
  260. package/dist/spell/spell.entity-type.d.ts +2 -0
  261. package/dist/spell/spell.filter.d.ts +15 -0
  262. package/dist/spell/spell.filter.js +87 -0
  263. package/dist/spell/spell.generatable.d.ts +2 -0
  264. package/dist/spell/spell.parser.d.ts +3 -0
  265. package/dist/spell/spell.parser.js +7 -0
  266. package/dist/spell/spell.scaling.d.ts +14 -0
  267. package/dist/spell/spell.scaling.js +28 -0
  268. package/dist/spell/spell.schema.d.ts +31 -0
  269. package/dist/spell/spell.schema.js +6 -0
  270. package/dist/spell/spell.types.d.ts +25 -0
  271. package/dist/spell/spell.types.js +0 -0
  272. package/dist/srd-store.d.ts +53 -0
  273. package/dist/subclass/subclass.codec.d.ts +11 -0
  274. package/dist/subclass/subclass.codec.js +14 -0
  275. package/dist/subclass/subclass.entity-type.d.ts +2 -0
  276. package/dist/subclass/subclass.parser.d.ts +3 -0
  277. package/dist/subclass/subclass.parser.js +13 -0
  278. package/dist/subclass/subclass.schema.d.ts +118 -0
  279. package/dist/subclass/subclass.schema.js +12 -0
  280. package/dist/subclass/subclass.types.d.ts +20 -0
  281. package/dist/subclass/subclass.types.js +0 -0
  282. package/dist/types/abilities.d.ts +8 -0
  283. package/dist/types/abilities.js +0 -0
  284. package/dist/types/ac.d.ts +5 -0
  285. package/dist/types/attack.d.ts +22 -0
  286. package/dist/types/attack.js +0 -0
  287. package/dist/types/casting-option.d.ts +14 -0
  288. package/dist/types/casting-option.js +0 -0
  289. package/dist/types/choice.d.ts +66 -0
  290. package/dist/types/choice.js +6 -0
  291. package/dist/types/edition.d.ts +4 -0
  292. package/dist/types/edition.js +0 -0
  293. package/dist/types/equipment-grant.d.ts +41 -0
  294. package/dist/types/equipment-grant.js +0 -0
  295. package/dist/types/feature-effect.d.ts +79 -0
  296. package/dist/types/feature-effect.js +0 -0
  297. package/dist/types/feature.d.ts +41 -0
  298. package/dist/types/feature.js +0 -0
  299. package/dist/types/formula-context.d.ts +7 -0
  300. package/dist/types/formula-context.js +0 -0
  301. package/dist/types/hp.d.ts +4 -0
  302. package/dist/types/index.d.ts +10 -0
  303. package/dist/types/item-conditions.types.d.ts +63 -0
  304. package/dist/types/item-conditions.types.js +0 -0
  305. package/dist/types/optional-feature.types.d.ts +60 -0
  306. package/dist/types/optional-feature.types.js +0 -0
  307. package/dist/types/resource.d.ts +44 -0
  308. package/dist/types/resource.js +0 -0
  309. package/dist/types/selection-pool.d.ts +37 -0
  310. package/dist/types/selection-pool.js +0 -0
  311. package/dist/types/speed.d.ts +8 -0
  312. package/dist/weapon/weapon.codec.d.ts +12 -0
  313. package/dist/weapon/weapon.codec.js +8 -0
  314. package/dist/weapon/weapon.entity-type.d.ts +2 -0
  315. package/dist/weapon/weapon.parser.d.ts +3 -0
  316. package/dist/weapon/weapon.parser.js +7 -0
  317. package/dist/weapon/weapon.schema.d.ts +34 -0
  318. package/dist/weapon/weapon.schema.js +6 -0
  319. package/dist/weapon/weapon.types.d.ts +36 -0
  320. package/dist/weapon/weapon.types.js +0 -0
  321. package/package.json +32 -0
  322. package/src/data-srd/ATTRIBUTION.md +2 -0
@@ -0,0 +1,515 @@
1
+ import {
2
+ defaultSlotForType,
3
+ effectiveArmor,
4
+ isItemEntity,
5
+ isWeaponEntity,
6
+ resolveEntityForEntry
7
+ } from "./chunk-UC4FE5X3.js";
8
+ import {
9
+ readNumericBonus
10
+ } from "./chunk-LWFTHJIE.js";
11
+ import {
12
+ resolveBaseItem
13
+ } from "./chunk-DC74TZNA.js";
14
+
15
+ // src/pc/pc.equipment.ts
16
+ var ABILITY_KEYS = ["str", "dex", "con", "int", "wis", "cha"];
17
+ function isAbilityKey(k) {
18
+ return ABILITY_KEYS.includes(k);
19
+ }
20
+ function emptyAppliedBonuses() {
21
+ return {
22
+ ability_bonuses: {},
23
+ ability_statics: {},
24
+ save_bonus: 0,
25
+ speed_bonuses: { walk: 0, fly: null, swim: 0, climb: 0 },
26
+ spell_attack: 0,
27
+ spell_save_dc: 0,
28
+ defenses: { resistances: [], immunities: [], vulnerabilities: [], condition_immunities: [] },
29
+ informational: [],
30
+ senses: { darkvision: 0, blindsight: 0, tremorsense: 0, truesight: 0 }
31
+ };
32
+ }
33
+ function unwrapSlug(item) {
34
+ const m = item.match(/^\[\[(.+)\]\]$/);
35
+ return m ? m[1] : null;
36
+ }
37
+ function isAttunedActive(entry, entity) {
38
+ if (!entry.equipped) return false;
39
+ const requires = isItemEntity(entity) && entity.attunement && typeof entity.attunement === "object" && "required" in entity.attunement && entity.attunement.required === true;
40
+ if (!requires) return true;
41
+ return entry.attuned === true;
42
+ }
43
+ function unwrapClassSlug(maybeWiki) {
44
+ if (!maybeWiki) return "";
45
+ const m = /^\[\[([^\]]+)\]\]$/.exec(maybeWiki);
46
+ return (m ? m[1] : maybeWiki).toLowerCase();
47
+ }
48
+ function buildConditionContext(resolved, equippedSlots) {
49
+ var _a, _b;
50
+ return {
51
+ derived: { equippedSlots },
52
+ classList: (_a = resolved.definition.class) != null ? _a : [],
53
+ race: resolved.definition.race,
54
+ subclasses: ((_b = resolved.definition.class) != null ? _b : []).map((c) => c.subclass).filter((s) => typeof s === "string" && s.length > 0).map(unwrapClassSlug)
55
+ };
56
+ }
57
+ function pushApply(outcome, field, source, pool, onApply) {
58
+ if (!outcome) return;
59
+ if (outcome.kind === "applied") onApply(outcome.value);
60
+ else if (outcome.kind === "informational") {
61
+ pool.push({ field, source, value: outcome.value, conditions: outcome.conditions });
62
+ }
63
+ }
64
+ function computeAppliedBonuses(resolved, _profs, registry, warnings) {
65
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
66
+ const out = emptyAppliedBonuses();
67
+ const equipment = (_a = resolved.definition.equipment) != null ? _a : [];
68
+ const ctx = buildConditionContext(resolved, {});
69
+ for (const entry of equipment) {
70
+ const { entity, entityType } = resolveEntityForEntry(entry.item, registry);
71
+ if (!entity) {
72
+ const slug = unwrapSlug(entry.item);
73
+ if (slug && entry.equipped) warnings.push(`Equipped item [[${slug}]] not found in compendium.`);
74
+ continue;
75
+ }
76
+ if (!isAttunedActive(entry, entity)) continue;
77
+ if (entityType !== "item") continue;
78
+ const item = entity;
79
+ (_b = item.resist) == null ? void 0 : _b.forEach((s) => out.defenses.resistances.push(s));
80
+ (_c = item.immune) == null ? void 0 : _c.forEach((s) => out.defenses.immunities.push(s));
81
+ (_d = item.vulnerable) == null ? void 0 : _d.forEach((s) => out.defenses.vulnerabilities.push(s));
82
+ (_e = item.condition_immune) == null ? void 0 : _e.forEach((s) => out.defenses.condition_immunities.push(s));
83
+ (_g = (_f = entry.overrides) == null ? void 0 : _f.resist) == null ? void 0 : _g.forEach((s) => out.defenses.resistances.push(s));
84
+ (_i = (_h = entry.overrides) == null ? void 0 : _h.immune) == null ? void 0 : _i.forEach((s) => out.defenses.immunities.push(s));
85
+ (_k = (_j = entry.overrides) == null ? void 0 : _j.vulnerable) == null ? void 0 : _k.forEach((s) => out.defenses.vulnerabilities.push(s));
86
+ (_m = (_l = entry.overrides) == null ? void 0 : _l.condition_immune) == null ? void 0 : _m.forEach((s) => out.defenses.condition_immunities.push(s));
87
+ const gs = (_n = item.grants) == null ? void 0 : _n.senses;
88
+ if (gs) {
89
+ if (typeof gs.darkvision === "number") out.senses.darkvision = Math.max(out.senses.darkvision, gs.darkvision);
90
+ if (typeof gs.blindsight === "number") out.senses.blindsight = Math.max(out.senses.blindsight, gs.blindsight);
91
+ if (typeof gs.tremorsense === "number") out.senses.tremorsense = Math.max(out.senses.tremorsense, gs.tremorsense);
92
+ if (typeof gs.truesight === "number") out.senses.truesight = Math.max(out.senses.truesight, gs.truesight);
93
+ }
94
+ const b = item.bonuses;
95
+ if (!b) continue;
96
+ pushApply(
97
+ readNumericBonus(b.saving_throws, ctx),
98
+ "saving_throws",
99
+ item.name,
100
+ out.informational,
101
+ (v) => {
102
+ out.save_bonus += v;
103
+ }
104
+ );
105
+ pushApply(
106
+ readNumericBonus(b.spell_attack, ctx),
107
+ "spell_attack",
108
+ item.name,
109
+ out.informational,
110
+ (v) => {
111
+ out.spell_attack += v;
112
+ }
113
+ );
114
+ pushApply(
115
+ readNumericBonus(b.spell_save_dc, ctx),
116
+ "spell_save_dc",
117
+ item.name,
118
+ out.informational,
119
+ (v) => {
120
+ out.spell_save_dc += v;
121
+ }
122
+ );
123
+ if ((_o = b.ability_scores) == null ? void 0 : _o.bonus) {
124
+ for (const [k, v] of Object.entries(b.ability_scores.bonus)) {
125
+ if (!isAbilityKey(k)) continue;
126
+ pushApply(
127
+ readNumericBonus(v, ctx),
128
+ `ability_scores.bonus.${k}`,
129
+ item.name,
130
+ out.informational,
131
+ (val) => {
132
+ var _a2;
133
+ out.ability_bonuses[k] = ((_a2 = out.ability_bonuses[k]) != null ? _a2 : 0) + val;
134
+ }
135
+ );
136
+ }
137
+ }
138
+ if ((_p = b.ability_scores) == null ? void 0 : _p.static) {
139
+ for (const [k, n] of Object.entries(b.ability_scores.static)) {
140
+ if (!isAbilityKey(k) || typeof n !== "number") continue;
141
+ const prev = out.ability_statics[k];
142
+ if (prev === void 0) {
143
+ out.ability_statics[k] = n;
144
+ } else {
145
+ if (n !== prev) {
146
+ warnings.push(
147
+ `Multiple static ${k.toUpperCase()} bonuses on equipped+attuned items; using highest (${Math.max(prev, n)}).`
148
+ );
149
+ }
150
+ out.ability_statics[k] = Math.max(prev, n);
151
+ }
152
+ }
153
+ }
154
+ if (b.speed) {
155
+ pushApply(
156
+ readNumericBonus(b.speed.walk, ctx),
157
+ "speed.walk",
158
+ item.name,
159
+ out.informational,
160
+ (v) => {
161
+ out.speed_bonuses.walk += v;
162
+ }
163
+ );
164
+ if (b.speed.fly === "walk") {
165
+ out.speed_bonuses.fly = "walk";
166
+ } else {
167
+ pushApply(
168
+ readNumericBonus(b.speed.fly, ctx),
169
+ "speed.fly",
170
+ item.name,
171
+ out.informational,
172
+ (v) => {
173
+ const cur = out.speed_bonuses.fly;
174
+ out.speed_bonuses.fly = (typeof cur === "number" ? cur : 0) + v;
175
+ }
176
+ );
177
+ }
178
+ pushApply(
179
+ readNumericBonus(b.speed.swim, ctx),
180
+ "speed.swim",
181
+ item.name,
182
+ out.informational,
183
+ (v) => {
184
+ out.speed_bonuses.swim += v;
185
+ }
186
+ );
187
+ pushApply(
188
+ readNumericBonus(b.speed.climb, ctx),
189
+ "speed.climb",
190
+ item.name,
191
+ out.informational,
192
+ (v) => {
193
+ out.speed_bonuses.climb += v;
194
+ }
195
+ );
196
+ }
197
+ }
198
+ return out;
199
+ }
200
+ function isTwoHanded(weapon) {
201
+ return weapon.properties.some((p) => p === "two_handed");
202
+ }
203
+ function assignSlots(resolved, registry, warnings) {
204
+ var _a, _b;
205
+ const slots = {};
206
+ const eq = (_a = resolved.definition.equipment) != null ? _a : [];
207
+ for (let i = 0; i < eq.length; i++) {
208
+ const entry = eq[i];
209
+ if (!entry.equipped || !entry.slot) continue;
210
+ const { entity, entityType } = resolveEntityForEntry(entry.item, registry);
211
+ if (slots[entry.slot]) {
212
+ warnings.push(`${entry.slot} slot conflict: ${entry.item} ignored (already taken).`);
213
+ continue;
214
+ }
215
+ const placed = { index: i, entity, entityType, entry };
216
+ slots[entry.slot] = placed;
217
+ }
218
+ for (let i = 0; i < eq.length; i++) {
219
+ const entry = eq[i];
220
+ if (!entry.equipped || entry.slot) continue;
221
+ const { entity, entityType } = resolveEntityForEntry(entry.item, registry);
222
+ if (!entity || !entityType) continue;
223
+ const defaultSlot = defaultSlotForType(entityType, entity, registry);
224
+ if (!defaultSlot) continue;
225
+ const placed = { index: i, entity, entityType, entry };
226
+ if (defaultSlot === "mainhand") {
227
+ if (!slots.mainhand) slots.mainhand = placed;
228
+ else if (!slots.offhand) slots.offhand = placed;
229
+ else warnings.push(`No free hand for ${entry.item}; entry remains equipped without slot.`);
230
+ } else {
231
+ if (slots[defaultSlot]) {
232
+ warnings.push(`${defaultSlot} slot already filled; ${entry.item} ignored.`);
233
+ } else {
234
+ slots[defaultSlot] = placed;
235
+ }
236
+ }
237
+ }
238
+ const main = (_b = slots.mainhand) == null ? void 0 : _b.entity;
239
+ if (main && isWeaponEntity(main) && isTwoHanded(main) && slots.shield) {
240
+ warnings.push(`Two-handed weapon equipped; shield ignored.`);
241
+ }
242
+ return slots;
243
+ }
244
+ function computeAC(equippedSlots, resolved, mods, registry) {
245
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
246
+ const breakdown = [];
247
+ const armorSlot = equippedSlots.armor;
248
+ const armorEntity = (_a = armorSlot == null ? void 0 : armorSlot.entity) != null ? _a : null;
249
+ const armor = effectiveArmor(armorEntity, registry);
250
+ let ac = 10;
251
+ if (armor) {
252
+ const armorBase = armor.ac.base + ((_b = armor.ac.flat) != null ? _b : 0);
253
+ ac = armorBase;
254
+ breakdown.push({ source: (_c = armorEntity == null ? void 0 : armorEntity.name) != null ? _c : armor.name, amount: armorBase, kind: "armor" });
255
+ if (armor.ac.add_dex) {
256
+ const dexCap = (_d = armor.ac.dex_max) != null ? _d : Number.POSITIVE_INFINITY;
257
+ const dexAdd = Math.min(mods.dex, dexCap);
258
+ ac += dexAdd;
259
+ const capped = mods.dex > dexCap;
260
+ breakdown.push({ source: `DEX modifier${capped ? " (capped)" : ""}`, amount: dexAdd, kind: "dex" });
261
+ }
262
+ } else {
263
+ ac = 10 + mods.dex;
264
+ breakdown.push({ source: "Base", amount: 10, kind: "unarmored" });
265
+ breakdown.push({ source: "DEX modifier", amount: mods.dex, kind: "dex" });
266
+ }
267
+ const acBonus = (_e = armorSlot == null ? void 0 : armorSlot.entry.overrides) == null ? void 0 : _e.ac_bonus;
268
+ if (typeof acBonus === "number" && acBonus !== 0) {
269
+ ac += acBonus;
270
+ breakdown.push({ source: `${(_f = armorEntity == null ? void 0 : armorEntity.name) != null ? _f : "Armor"} (override)`, amount: acBonus, kind: "override" });
271
+ }
272
+ const main = (_g = equippedSlots.mainhand) == null ? void 0 : _g.entity;
273
+ const mainIsTwoHanded = main && isWeaponEntity(main) && isTwoHanded(main);
274
+ const shieldEntity = (_i = (_h = equippedSlots.shield) == null ? void 0 : _h.entity) != null ? _i : null;
275
+ const shield = effectiveArmor(shieldEntity, registry);
276
+ if (shield && !mainIsTwoHanded) {
277
+ const n = shield.ac.base + ((_j = shield.ac.flat) != null ? _j : 0);
278
+ ac += n;
279
+ breakdown.push({ source: (_k = shieldEntity == null ? void 0 : shieldEntity.name) != null ? _k : shield.name, amount: n, kind: "shield" });
280
+ }
281
+ const acInformational = [];
282
+ const acCtx = buildConditionContext(resolved, equippedSlots);
283
+ for (const entry of (_l = resolved.definition.equipment) != null ? _l : []) {
284
+ const { entity, entityType } = resolveEntityForEntry(entry.item, registry);
285
+ if (!entity || entityType !== "item") continue;
286
+ if (!isAttunedActive(entry, entity)) continue;
287
+ const item = entity;
288
+ const out = readNumericBonus((_m = item.bonuses) == null ? void 0 : _m.ac, acCtx);
289
+ if (!out) continue;
290
+ if (out.kind === "applied") {
291
+ ac += out.value;
292
+ breakdown.push({ source: item.name, amount: out.value, kind: "item" });
293
+ } else if (out.kind === "informational") {
294
+ acInformational.push({
295
+ field: "ac",
296
+ source: item.name,
297
+ value: out.value,
298
+ conditions: out.conditions
299
+ });
300
+ }
301
+ }
302
+ return { ac, breakdown, informational: acInformational };
303
+ }
304
+ function hasProperty(weapon, prop) {
305
+ return weapon.properties.some((p) => p === prop);
306
+ }
307
+ function attackAbility(weapon, mods, override) {
308
+ const isRanged = /ranged/.test(weapon.category);
309
+ if (override && !isRanged) return override;
310
+ if (isRanged) return "dex";
311
+ if (hasProperty(weapon, "finesse")) return mods.dex >= mods.str ? "dex" : "str";
312
+ return "str";
313
+ }
314
+ function isWeaponSlugProficient(weapon, weaponSlug, profs) {
315
+ const wp = profs.weapons;
316
+ if (wp.specific.includes(weaponSlug)) return true;
317
+ const cat = weapon.category;
318
+ if (typeof cat === "string") {
319
+ if (/^simple\b/.test(cat) && wp.categories.includes("simple")) return true;
320
+ if (/^martial\b/.test(cat) && wp.categories.includes("martial")) return true;
321
+ if (wp.categories.includes(cat)) return true;
322
+ }
323
+ return false;
324
+ }
325
+ function magicBonusesForWeaponEntry(entry, registry, ctx) {
326
+ var _a;
327
+ const { entity } = resolveEntityForEntry(entry.item, registry);
328
+ const ovr = (_a = entry.overrides) != null ? _a : {};
329
+ const entryAttack = typeof ovr.bonus === "number" ? ovr.bonus : 0;
330
+ const entryDamage = typeof ovr.damage_bonus === "number" ? ovr.damage_bonus : 0;
331
+ const extra = typeof ovr.extra_damage === "string" ? ovr.extra_damage : void 0;
332
+ let itemAttack = 0;
333
+ let itemDamage = 0;
334
+ let sourceName;
335
+ let itemName;
336
+ let damageTypeOverride;
337
+ let propertiesOverride;
338
+ const informational = [];
339
+ if (entity && isItemEntity(entity)) {
340
+ sourceName = entity.name;
341
+ itemName = entity.name;
342
+ damageTypeOverride = typeof entity.damage_type === "string" && entity.damage_type.length > 0 ? entity.damage_type : void 0;
343
+ propertiesOverride = Array.isArray(entity.properties) && entity.properties.length > 0 ? entity.properties : void 0;
344
+ const b = entity.bonuses;
345
+ const atkOut = readNumericBonus(b == null ? void 0 : b.weapon_attack, ctx);
346
+ const dmgOut = readNumericBonus(b == null ? void 0 : b.weapon_damage, ctx);
347
+ if ((atkOut == null ? void 0 : atkOut.kind) === "applied") itemAttack = atkOut.value;
348
+ else if ((atkOut == null ? void 0 : atkOut.kind) === "informational")
349
+ informational.push({ field: "weapon_attack", source: entity.name, value: atkOut.value, conditions: atkOut.conditions });
350
+ if ((dmgOut == null ? void 0 : dmgOut.kind) === "applied") itemDamage = dmgOut.value;
351
+ else if ((dmgOut == null ? void 0 : dmgOut.kind) === "informational")
352
+ informational.push({ field: "weapon_damage", source: entity.name, value: dmgOut.value, conditions: dmgOut.conditions });
353
+ }
354
+ return {
355
+ atk: itemAttack + entryAttack,
356
+ dmg: itemDamage + entryDamage,
357
+ extra,
358
+ sourceName,
359
+ itemName,
360
+ damageTypeOverride,
361
+ propertiesOverride,
362
+ informational
363
+ };
364
+ }
365
+ function buildAttackRow(args) {
366
+ var _a, _b;
367
+ const { id, name, weapon, baseDice, ability, mods, proficient, proficiencyBonus, magic } = args;
368
+ const abilityMod = mods[ability];
369
+ const pb = proficient ? proficiencyBonus : 0;
370
+ const toHit = abilityMod + pb + magic.atk;
371
+ const dmgFlat = abilityMod + magic.dmg;
372
+ const formatDice = (dice) => `${dice}${dmgFlat >= 0 ? "+" : ""}${dmgFlat}`.replace(/\+0$/, "");
373
+ const damageDice = formatDice(baseDice);
374
+ const magicSource = magic.sourceName ? `${magic.sourceName} bonus` : "Magic weapon";
375
+ const toHitBreakdown = [
376
+ { source: `${ability.toUpperCase()} modifier`, amount: abilityMod, kind: "ability" },
377
+ { source: "Proficiency bonus", amount: proficient ? proficiencyBonus : 0, kind: "ability" },
378
+ { source: magicSource, amount: magic.atk, kind: "item" }
379
+ ];
380
+ const damageBreakdown = [
381
+ { source: "Base damage", amount: 0, kind: "ability" },
382
+ { source: `${ability.toUpperCase()} modifier`, amount: abilityMod, kind: "ability" },
383
+ { source: magicSource, amount: magic.dmg, kind: "item" }
384
+ ];
385
+ const isRanged = /ranged/.test(weapon.category);
386
+ const range = isRanged && weapon.range ? `${weapon.range.normal}/${weapon.range.long}` : "melee";
387
+ const stringProps = weapon.properties.filter(
388
+ (p) => typeof p === "string"
389
+ );
390
+ const finalProps = (_a = magic.propertiesOverride) != null ? _a : stringProps;
391
+ return {
392
+ id,
393
+ name,
394
+ range,
395
+ toHit,
396
+ damageDice,
397
+ damageType: (_b = magic.damageTypeOverride) != null ? _b : weapon.damage.type,
398
+ extraDamage: magic.extra,
399
+ properties: finalProps,
400
+ proficient,
401
+ breakdown: { toHit: toHitBreakdown, damage: damageBreakdown },
402
+ informational: args.magic.informational.length > 0 ? args.magic.informational : void 0,
403
+ slotKey: args.slotKey,
404
+ subLabel: formatWeaponSubLabel(weapon, finalProps),
405
+ actionCost: args.actionCost,
406
+ versatile: args.versatileDice ? { damageDice: formatDice(args.versatileDice) } : void 0
407
+ };
408
+ }
409
+ function formatWeaponSubLabel(weapon, properties) {
410
+ const parts = [];
411
+ if (weapon.category) parts.push(weapon.category);
412
+ if (properties.length > 0) parts.push(properties.join(", "));
413
+ return parts.join(" \xB7 ");
414
+ }
415
+ function computeAttacks(equippedSlots, mods, profs, registry, warnings, proficiencyBonus, ctx, weaponAbilityOverride) {
416
+ var _a, _b, _c, _d, _e;
417
+ const rows = [];
418
+ const handedSlots = [
419
+ { key: "mainhand", placed: equippedSlots.mainhand },
420
+ { key: "offhand", placed: equippedSlots.offhand }
421
+ ];
422
+ for (const { key, placed } of handedSlots) {
423
+ if (!placed) continue;
424
+ const { entity, entry } = placed;
425
+ if (!entity) continue;
426
+ let weapon = null;
427
+ if (isWeaponEntity(entity)) {
428
+ weapon = entity;
429
+ } else if (isItemEntity(entity) && typeof entity.base_item === "string") {
430
+ const baseSlug = entity.base_item;
431
+ const found = resolveBaseItem(baseSlug, registry);
432
+ if (found && found.entityType === "weapon" && isWeaponEntity(found.data)) {
433
+ weapon = found.data;
434
+ } else {
435
+ warnings.push(`Magic weapon ${entity.name} references missing base_item [[${baseSlug}]].`);
436
+ continue;
437
+ }
438
+ } else {
439
+ continue;
440
+ }
441
+ const slugFromEntry = (_a = unwrapSlug(entry.item)) != null ? _a : weapon.slug;
442
+ const proficient = isWeaponSlugProficient(weapon, slugFromEntry, profs) || isWeaponSlugProficient(weapon, weapon.slug, profs);
443
+ if (!proficient) {
444
+ warnings.push(`Character not proficient with ${weapon.name}; proficiency bonus excluded.`);
445
+ }
446
+ const ability = attackAbility(weapon, mods, weaponAbilityOverride);
447
+ const magic = magicBonusesForWeaponEntry(entry, registry, ctx);
448
+ const ovr = (_b = entry.overrides) != null ? _b : {};
449
+ const displayName = (_d = (_c = ovr.name) != null ? _c : magic.itemName) != null ? _d : weapon.name;
450
+ const versatileDice = key === "mainhand" && hasProperty(weapon, "versatile") && typeof weapon.damage.versatile_dice === "string" && !equippedSlots.offhand ? weapon.damage.versatile_dice : void 0;
451
+ const baseRow = buildAttackRow({
452
+ id: `${placed.index}:standard`,
453
+ name: displayName,
454
+ weapon,
455
+ baseDice: weapon.damage.dice,
456
+ ability,
457
+ mods,
458
+ proficient,
459
+ proficiencyBonus,
460
+ magic,
461
+ slotKey: key,
462
+ actionCost: (_e = ovr.action) != null ? _e : "action",
463
+ versatileDice
464
+ });
465
+ rows.push(baseRow);
466
+ }
467
+ return rows;
468
+ }
469
+ function coerceWeight(raw) {
470
+ if (typeof raw === "number" && Number.isFinite(raw) && raw >= 0) return raw;
471
+ if (typeof raw === "string") {
472
+ const n = Number.parseFloat(raw);
473
+ if (Number.isFinite(n) && n >= 0) return n;
474
+ }
475
+ return 0;
476
+ }
477
+ function computeCarriedWeight(resolved, registry) {
478
+ var _a;
479
+ let total = 0;
480
+ for (const entry of (_a = resolved.definition.equipment) != null ? _a : []) {
481
+ const found = resolveBaseItem(entry.item, registry);
482
+ if (!found) continue;
483
+ const entity = found.data;
484
+ const w = coerceWeight(entity.weight);
485
+ if (w === 0) continue;
486
+ const qty = typeof entry.qty === "number" && entry.qty >= 0 ? entry.qty : 1;
487
+ total += w * qty;
488
+ }
489
+ return total;
490
+ }
491
+ function computeSlotsAndAttacks(resolved, mods, profs, registry, warnings, proficiencyBonus, weaponAbilityOverride) {
492
+ var _a, _b, _c;
493
+ const equippedSlots = assignSlots(resolved, registry, warnings);
494
+ const overrides = (_a = resolved.definition.overrides) != null ? _a : {};
495
+ const acOut = computeAC(equippedSlots, resolved, mods, registry);
496
+ const ctx = buildConditionContext(resolved, equippedSlots);
497
+ const attacks = computeAttacks(equippedSlots, mods, profs, registry, warnings, proficiencyBonus, ctx, weaponAbilityOverride);
498
+ return {
499
+ ac: acOut.ac,
500
+ acBreakdown: acOut.breakdown,
501
+ acInformational: acOut.informational,
502
+ attacks,
503
+ equippedSlots,
504
+ carriedWeight: computeCarriedWeight(resolved, registry),
505
+ // Attunement is persistent: an attuned item still occupies a slot even when unequipped (SRD).
506
+ attunementUsed: ((_b = resolved.definition.equipment) != null ? _b : []).filter((e) => e.attuned).length,
507
+ attunementLimit: (_c = overrides.attunement_limit) != null ? _c : 3
508
+ };
509
+ }
510
+
511
+ export {
512
+ emptyAppliedBonuses,
513
+ computeAppliedBonuses,
514
+ computeSlotsAndAttacks
515
+ };
@@ -0,0 +1,24 @@
1
+ // src/item/item.ai-schema.ts
2
+ import { z } from "zod";
3
+ var itemInputSchema = z.object({
4
+ name: z.string().describe("Item name"),
5
+ type: z.enum(["weapon", "armor", "potion", "ring", "rod", "scroll", "staff", "wand", "wondrous item", "shield"]),
6
+ rarity: z.enum(["common", "uncommon", "rare", "very rare", "legendary", "artifact"]),
7
+ entries: z.array(z.string()).optional().describe(
8
+ "Item description and properties. Use inline formula tags for attack rolls, damage, and save DCs: `atk:ABILITY` for attack bonus, `damage:DICE+ABILITY` for damage (e.g. `damage:2d6`), `dc:ABILITY` for save DCs. Valid abilities: STR, DEX, CON, INT, WIS, CHA."
9
+ ),
10
+ weight: z.number().optional(),
11
+ value: z.number().optional(),
12
+ attunement: z.union([z.boolean(), z.string()]).optional(),
13
+ properties: z.array(z.string()).optional(),
14
+ damage: z.string().optional(),
15
+ damage_type: z.string().optional(),
16
+ ac: z.number().optional(),
17
+ charges: z.number().optional(),
18
+ recharge: z.string().optional(),
19
+ curse: z.boolean().optional()
20
+ });
21
+
22
+ export {
23
+ itemInputSchema
24
+ };
@@ -0,0 +1,97 @@
1
+ // src/dnd/tag-grammar.ts
2
+ var TAG_TYPE_ALIASES = {
3
+ atk: "atk",
4
+ attack: "atk",
5
+ dmg: "dmg",
6
+ damage: "dmg",
7
+ dc: "dc",
8
+ dice: "dice"
9
+ };
10
+ function normalizeTagType(input) {
11
+ var _a;
12
+ const lower = input.toLowerCase().trim();
13
+ return (_a = TAG_TYPE_ALIASES[lower]) != null ? _a : null;
14
+ }
15
+ var ABILITY_RE = /^(str|dex|con|int|wis|cha)$/i;
16
+ var PB_RE = /^pb$/i;
17
+ var DICE_RE = /^(\d+)d(\d+)$/;
18
+ var LITERAL_RE = /^([+-]?\d+)$/;
19
+ var SLUG_RE = /^\[\[([^[\]]+)\]\]$/;
20
+ function parseTagTerms(content) {
21
+ const out = {
22
+ pbTerm: false,
23
+ literalTerms: [],
24
+ diceTerms: [],
25
+ slugTerms: []
26
+ };
27
+ const trimmed = content.trim();
28
+ if (trimmed.length === 0) return { error: "empty content" };
29
+ const tokens = [];
30
+ let i = 0;
31
+ const advancePastTerm = (start) => {
32
+ let j = start;
33
+ while (j < trimmed.length && trimmed[j] !== "+" && trimmed[j] !== "-") {
34
+ if (trimmed[j] === "[" && trimmed[j + 1] === "[") {
35
+ const close = trimmed.indexOf("]]", j + 2);
36
+ if (close === -1) return trimmed.length;
37
+ j = close + 2;
38
+ continue;
39
+ }
40
+ j++;
41
+ }
42
+ return j;
43
+ };
44
+ while (i < trimmed.length) {
45
+ if (trimmed[i] === "+" || trimmed[i] === "-") {
46
+ const j = advancePastTerm(i + 1);
47
+ tokens.push(trimmed.slice(i, j));
48
+ i = j;
49
+ } else {
50
+ const j = advancePastTerm(i);
51
+ tokens.push(trimmed.slice(i, j));
52
+ i = j;
53
+ }
54
+ }
55
+ for (let raw of tokens) {
56
+ raw = raw.trim();
57
+ if (raw.length === 0) return { error: "empty term" };
58
+ let signed = raw;
59
+ let body = raw;
60
+ if (raw[0] === "+" || raw[0] === "-") {
61
+ body = raw.slice(1).trim();
62
+ } else {
63
+ signed = "+" + raw;
64
+ }
65
+ if (DICE_RE.test(body)) {
66
+ const m = body.match(DICE_RE);
67
+ out.diceTerms.push({ count: Number.parseInt(m[1], 10), sides: Number.parseInt(m[2], 10) });
68
+ continue;
69
+ }
70
+ if (ABILITY_RE.test(body)) {
71
+ if (out.abilityTerm) return { error: `multiple ability terms (${out.abilityTerm}, ${body.toLowerCase()})` };
72
+ out.abilityTerm = body.toLowerCase();
73
+ continue;
74
+ }
75
+ if (PB_RE.test(body)) {
76
+ if (out.pbTerm) return { error: "duplicate PB term" };
77
+ out.pbTerm = true;
78
+ continue;
79
+ }
80
+ const slugMatch = body.match(SLUG_RE);
81
+ if (slugMatch) {
82
+ out.slugTerms.push(slugMatch[1]);
83
+ continue;
84
+ }
85
+ if (LITERAL_RE.test(signed)) {
86
+ out.literalTerms.push(Number.parseInt(signed, 10));
87
+ continue;
88
+ }
89
+ return { error: `unrecognized term: ${raw}` };
90
+ }
91
+ return out;
92
+ }
93
+
94
+ export {
95
+ normalizeTagType,
96
+ parseTagTerms
97
+ };