@archivist-gg/dnd5e 0.3.9 → 0.3.11

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.
@@ -5,12 +5,12 @@ import {
5
5
  prefixOf,
6
6
  raceIsUngated,
7
7
  resolveSpellByName
8
- } from "../chunk-MI7P3NX3.js";
8
+ } from "../chunk-3HX5VLWP.js";
9
9
  import "../chunk-PVCEA4HZ.js";
10
10
  import "../chunk-LVQLWRCU.js";
11
11
  import "../chunk-6X4KUDMU.js";
12
- import "../chunk-OZP6JFGT.js";
13
- import "../chunk-VQAXTIBW.js";
12
+ import "../chunk-XY6X2APM.js";
13
+ import "../chunk-ZDAEGJH4.js";
14
14
  import "../chunk-WELIKLZF.js";
15
15
  import "../chunk-UA5KS2PL.js";
16
16
  export {
@@ -4,14 +4,14 @@ import {
4
4
  collectChosenAbilityPoints,
5
5
  collectChosenProficiencies,
6
6
  computeEffectiveProficiencies
7
- } from "../chunk-HLQAFZKQ.js";
7
+ } from "../chunk-2INMRR3Q.js";
8
8
  import "../chunk-4ISR76DH.js";
9
9
  import "../chunk-DPUJDOKV.js";
10
10
  import "../chunk-O4SRD3J5.js";
11
11
  import "../chunk-PP2UHLBA.js";
12
12
  import "../chunk-IUAAXXNO.js";
13
13
  import "../chunk-YHYTW5QA.js";
14
- import "../chunk-MI7P3NX3.js";
14
+ import "../chunk-3HX5VLWP.js";
15
15
  import "../chunk-PVCEA4HZ.js";
16
16
  import "../chunk-UBBKSYBY.js";
17
17
  import "../chunk-CBEKVAX4.js";
@@ -26,8 +26,8 @@ import {
26
26
  import "../chunk-YBLDE5CG.js";
27
27
  import "../chunk-EVMUJYUD.js";
28
28
  import "../chunk-VTTA2ALZ.js";
29
- import "../chunk-OZP6JFGT.js";
30
- import "../chunk-VQAXTIBW.js";
29
+ import "../chunk-XY6X2APM.js";
30
+ import "../chunk-ZDAEGJH4.js";
31
31
  import "../chunk-PDJSUH24.js";
32
32
  import "../chunk-WELIKLZF.js";
33
33
  import "../chunk-4U55VNC7.js";
@@ -2,14 +2,14 @@ import {
2
2
  collectChosenProficiencies,
3
3
  collectProficiencyGrants,
4
4
  computeEffectiveProficiencies
5
- } from "../chunk-HLQAFZKQ.js";
5
+ } from "../chunk-2INMRR3Q.js";
6
6
  import "../chunk-4ISR76DH.js";
7
7
  import "../chunk-DPUJDOKV.js";
8
8
  import "../chunk-O4SRD3J5.js";
9
9
  import "../chunk-PP2UHLBA.js";
10
10
  import "../chunk-IUAAXXNO.js";
11
11
  import "../chunk-YHYTW5QA.js";
12
- import "../chunk-MI7P3NX3.js";
12
+ import "../chunk-3HX5VLWP.js";
13
13
  import "../chunk-PVCEA4HZ.js";
14
14
  import "../chunk-UBBKSYBY.js";
15
15
  import "../chunk-CBEKVAX4.js";
@@ -25,8 +25,8 @@ import "../chunk-6X4KUDMU.js";
25
25
  import "../chunk-YBLDE5CG.js";
26
26
  import "../chunk-EVMUJYUD.js";
27
27
  import "../chunk-VTTA2ALZ.js";
28
- import "../chunk-OZP6JFGT.js";
29
- import "../chunk-VQAXTIBW.js";
28
+ import "../chunk-XY6X2APM.js";
29
+ import "../chunk-ZDAEGJH4.js";
30
30
  import "../chunk-PDJSUH24.js";
31
31
  import "../chunk-WELIKLZF.js";
32
32
  import "../chunk-4U55VNC7.js";
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  collectChosenAbilityPoints,
6
6
  collectChosenProficiencies
7
- } from "../chunk-HLQAFZKQ.js";
7
+ } from "../chunk-2INMRR3Q.js";
8
8
  import {
9
9
  resourceLevelFor
10
10
  } from "../chunk-4ISR76DH.js";
@@ -21,7 +21,7 @@ import {
21
21
  unarmedDieColumnFor
22
22
  } from "../chunk-IUAAXXNO.js";
23
23
  import "../chunk-YHYTW5QA.js";
24
- import "../chunk-MI7P3NX3.js";
24
+ import "../chunk-3HX5VLWP.js";
25
25
  import "../chunk-PVCEA4HZ.js";
26
26
  import {
27
27
  toDefenseSlug
@@ -54,8 +54,8 @@ import {
54
54
  isValidMaxFormula
55
55
  } from "../chunk-EVMUJYUD.js";
56
56
  import "../chunk-VTTA2ALZ.js";
57
- import "../chunk-OZP6JFGT.js";
58
- import "../chunk-VQAXTIBW.js";
57
+ import "../chunk-XY6X2APM.js";
58
+ import "../chunk-ZDAEGJH4.js";
59
59
  import {
60
60
  abilityModifier,
61
61
  attackBonus,
@@ -10,14 +10,14 @@ import {
10
10
  dedupeResolvedSpells,
11
11
  resolveOriginFeat,
12
12
  stripSlug
13
- } from "../chunk-HLQAFZKQ.js";
13
+ } from "../chunk-2INMRR3Q.js";
14
14
  import "../chunk-4ISR76DH.js";
15
15
  import "../chunk-DPUJDOKV.js";
16
16
  import "../chunk-O4SRD3J5.js";
17
17
  import "../chunk-PP2UHLBA.js";
18
18
  import "../chunk-IUAAXXNO.js";
19
19
  import "../chunk-YHYTW5QA.js";
20
- import "../chunk-MI7P3NX3.js";
20
+ import "../chunk-3HX5VLWP.js";
21
21
  import "../chunk-PVCEA4HZ.js";
22
22
  import "../chunk-UBBKSYBY.js";
23
23
  import "../chunk-CBEKVAX4.js";
@@ -30,8 +30,8 @@ import "../chunk-6X4KUDMU.js";
30
30
  import "../chunk-YBLDE5CG.js";
31
31
  import "../chunk-EVMUJYUD.js";
32
32
  import "../chunk-VTTA2ALZ.js";
33
- import "../chunk-OZP6JFGT.js";
34
- import "../chunk-VQAXTIBW.js";
33
+ import "../chunk-XY6X2APM.js";
34
+ import "../chunk-ZDAEGJH4.js";
35
35
  import "../chunk-PDJSUH24.js";
36
36
  import "../chunk-WELIKLZF.js";
37
37
  import "../chunk-4U55VNC7.js";
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The ONE reader of a spell's `casting_time` string. Every consumer (the plugin's compact Cast-table label,
3
+ * the add-drawer's filter bucket and its sort rank) goes through here, so a spelling that one of them
4
+ * understands the others understand too. Before this, each kept its own exact-string switch over the bundle's
5
+ * spellings (`action`, `bonus-action`, `1minute`), and they drifted: a converted or AI-generated spell's
6
+ * `1 action` / `10 minute` printed raw in the Cast table, and every vault spell spelled `bonus action`,
7
+ * `1 minute` or `1 hour` filtered as "special".
8
+ *
9
+ * The LEADING token decides. Prose after it (a reaction's trigger, Plant Growth's "or 8 hours") is left for
10
+ * the caller's tooltip and never matched, and the trailing word boundary stops a word that merely starts with
11
+ * a unit (`reactionary`, `bonuses`, `minuteman`) from reading as one. A count is optional and only meaningful
12
+ * for minutes and hours; `bonus` may drop the word `action`, which hand-authored spells do.
13
+ */
14
+ export type CastingTimeKind = "action" | "bonus" | "reaction" | "time";
15
+ export interface ParsedCastingTime {
16
+ kind: CastingTimeKind;
17
+ /** 1 for the action economy; the stated number of minutes / hours for `time` (1 when omitted). */
18
+ count: number;
19
+ /** Present only for `time`. */
20
+ unit?: "minute" | "hour";
21
+ }
22
+ export declare function parseCastingTime(token: string | undefined): ParsedCastingTime | null;
@@ -0,0 +1,6 @@
1
+ import {
2
+ parseCastingTime
3
+ } from "../chunk-W6GC6OKK.js";
4
+ export {
5
+ parseCastingTime
6
+ };
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  spellCodec
3
- } from "../chunk-JSZ4H6AQ.js";
4
- import "../chunk-OZP6JFGT.js";
5
- import "../chunk-VQAXTIBW.js";
3
+ } from "../chunk-MWCCGPG3.js";
4
+ import "../chunk-XY6X2APM.js";
5
+ import "../chunk-ZDAEGJH4.js";
6
6
  import "../chunk-UA5KS2PL.js";
7
7
  export {
8
8
  spellCodec
@@ -1,8 +1,8 @@
1
1
  import type { SpellCandidate } from "./spell.access.js";
2
2
  export type CastTimeCat = "action" | "bonus" | "reaction" | "long" | "special";
3
3
  export type RangeCat = "self" | "touch" | "ranged" | "special";
4
- /** Cast-time bucket from the stored token (mirrors compactCastingTime's set).
5
- * Rounds/minutes/hours fold into "long"; null/unknown → "special". */
4
+ /** Cast-time bucket, read through `parseCastingTime` (the same reader the Cast table's label uses, so the two
5
+ * cannot drift). Minutes/hours fold into "long"; unreadable or absent → "special". */
6
6
  export declare function castTimeCategory(token: string | undefined): CastTimeCat;
7
7
  /** Range bucket from the human range string. Honest read, documented buckets:
8
8
  * "Self…"→self, "Touch…"→touch, "<n> feet|ft"→ranged, else (mile/Sight/…)→special. */
@@ -1,24 +1,12 @@
1
+ import {
2
+ parseCastingTime
3
+ } from "../chunk-W6GC6OKK.js";
4
+
1
5
  // src/spell/spell.filter.ts
2
6
  function castTimeCategory(token) {
3
- switch (token) {
4
- case "action":
5
- return "action";
6
- case "bonus-action":
7
- return "bonus";
8
- case "reaction":
9
- return "reaction";
10
- case "minute":
11
- case "1minute":
12
- case "10minutes":
13
- case "hour":
14
- case "1hour":
15
- case "8hours":
16
- case "12hours":
17
- case "24hours":
18
- return "long";
19
- default:
20
- return "special";
21
- }
7
+ const parsed = parseCastingTime(token);
8
+ if (!parsed) return "special";
9
+ return parsed.kind === "time" ? "long" : parsed.kind;
22
10
  }
23
11
  function rangeCategory(range) {
24
12
  if (!range) return "special";
@@ -3,8 +3,8 @@ import {
3
3
  normalizeSpellComponents,
4
4
  normalizeSpellDuration,
5
5
  parseSpell
6
- } from "../chunk-OZP6JFGT.js";
7
- import "../chunk-VQAXTIBW.js";
6
+ } from "../chunk-XY6X2APM.js";
7
+ import "../chunk-ZDAEGJH4.js";
8
8
  import "../chunk-UA5KS2PL.js";
9
9
  export {
10
10
  mirrorSpellShapes,
@@ -9,16 +9,39 @@ export interface SpellEffectParts {
9
9
  /** As printed: the roll, `"<N> targets"`, the duration, or the authored sentence verbatim. */
10
10
  value: string;
11
11
  }
12
+ /** The spell's base roll (`damage_roll`), trimmed, or null when absent, empty or whitespace-only. The ONE base-roll
13
+ * check: every reader of `damage_roll` (the scaling readers here, the spell card) goes through it. */
14
+ export declare function spellBaseRoll(spell: Spell): string | null;
12
15
  /**
13
- * At-a-glance scaled effect for casting a LEVELLED `spell` with a slot of `slotLevel`,
14
- * read from structured `casting_options` (`type: "slot_level_<N>"`), WITH the field it
15
- * came from. Returns null when absent or untrustworthy. The FIRST present field wins, in
16
- * this order: `damage_roll`, `target_count`, `duration`, `desc` (so an option carrying a
17
- * roll AND a sentence yields the roll). A `target_count` that equals the slot level is
18
- * the known SRD-2014 bad encoding (e.g. Magic Missile 2nd->2 instead of 4) and is
19
- * suppressed. This errs toward showing nothing rather than a wrong number.
16
+ * At-a-glance effect for casting a LEVELLED `spell` with a slot of `slotLevel`, WITH the field
17
+ * it came from. Returns null when absent or untrustworthy.
18
+ *
19
+ * A structured `casting_options` entry for that slot (`type: "slot_level_<N>"`) wins: the FIRST
20
+ * present field, in this order: `damage_roll`, `target_count`, `duration`, `desc` (so an option
21
+ * carrying a roll AND a sentence yields the roll). A `target_count` that equals the slot level is
22
+ * the known SRD-2014 bad encoding (e.g. Magic Missile 2nd->2 instead of 4) and is suppressed.
23
+ *
24
+ * With no option for the slot, the spell's BASE roll (`damage_roll`) answers at the spell's OWN
25
+ * level only. Above it, whether the base roll is still the roll dealt is `spellBaseRollAtSlot`'s
26
+ * question (it is when the damage does not scale); a SCALING spell missing its option above base
27
+ * stays null here. A CANTRIP is never read by slot (null): its roll follows the character's level,
28
+ * `spellEffectAtCharacterLevel`, even when it is cast from a scroll. This errs toward showing
29
+ * nothing rather than a wrong number.
20
30
  */
21
31
  export declare function spellEffectPartsAtSlot(spell: Spell, slotLevel: number): SpellEffectParts | null;
32
+ /**
33
+ * The spell's BASE roll at a slot of `slotLevel` when the data PROVES its damage does not scale, so every slot at or
34
+ * above the spell's level deals the base roll. Two proofs, and only these:
35
+ * (a) it has `slot_level_<N>` options and every one carries only a `duration` and / or a `target_count` (plus the
36
+ * `concentration` flag a duration carries): 2024 Hex and Hunter's Mark scale their duration, Magic Missile and
37
+ * Scorching Ray their target count;
38
+ * (b) it has no slot options AND no `at_higher_levels` text (Finger of Death).
39
+ * Anything else is NOT proof: prose-only scaling (2014 Cure Wounds' "increases by 1d8 for each slot level above
40
+ * 1st", with no options) would print the unscaled base on every upcast row, so it stays null there. The companion of
41
+ * `spellEffectPartsAtSlot`: a row prints this roll beside a target count, and the duration a slot option carries
42
+ * prints in its own cell. Null for a cantrip, below the spell's level, and when the spell has no base roll.
43
+ */
44
+ export declare function spellBaseRollAtSlot(spell: Spell, slotLevel: number): string | null;
22
45
  /** The value of `spellEffectPartsAtSlot`, without its field. */
23
46
  export declare function spellEffectAtSlot(spell: Spell, slotLevel: number): string | null;
24
47
  /**
@@ -26,9 +49,10 @@ export declare function spellEffectAtSlot(spell: Spell, slotLevel: number): stri
26
49
  * `casting_options` (`type: "player_level_<N>"`). The roll of the HIGHEST `N <= characterLevel` wins: SRD 5e authors
27
50
  * one option per level from 5 to 20, SRD 2024 and the converter author only 5 / 11 / 17. An option with no roll or an
28
51
  * EMPTY one never qualifies (SRD 5e Acid Splash and Poison Spray open with `player_level_2..4` carrying
29
- * `damage_roll: ''`; Eldritch Blast 2024 authors `target_count` only). Null for a levelled spell, for a level that is
30
- * not a finite number (a fixture-built `derived` omits `totalLevel`), and when nothing qualifies: no corpus authors
31
- * `player_level_1`, so a cantrip's base roll below level 5 is not in the data and nothing is invented here.
52
+ * `damage_roll: ''`; Eldritch Blast 2024 authors `target_count` only). When no tier qualifies (below the first tier,
53
+ * or tiers that carry no roll) the spell's BASE roll `damage_roll` answers: the tier-1 roll (Fire Bolt `1d10` at 4,
54
+ * Eldritch Blast's per-beam `1d10` at any level). Null for a levelled spell, for a level that is not a finite number
55
+ * (a fixture-built `derived` omits `totalLevel`), and when neither a tier nor a non-empty base roll exists.
32
56
  */
33
57
  export declare function spellEffectAtCharacterLevel(spell: Spell, characterLevel: number): string | null;
34
58
  /** Owned slot levels strictly above the spell's base level (scaling spells only). */
@@ -3,10 +3,19 @@ function spellScales(spell) {
3
3
  var _a, _b, _c, _d;
4
4
  return ((_b = (_a = spell.casting_options) == null ? void 0 : _a.length) != null ? _b : 0) > 0 || ((_d = (_c = spell.at_higher_levels) == null ? void 0 : _c.length) != null ? _d : 0) > 0;
5
5
  }
6
+ function spellBaseRoll(spell) {
7
+ const roll = typeof spell.damage_roll === "string" ? spell.damage_roll.trim() : "";
8
+ return roll !== "" ? roll : null;
9
+ }
6
10
  function spellEffectPartsAtSlot(spell, slotLevel) {
7
- var _a;
8
- const opt = ((_a = spell.casting_options) != null ? _a : []).find((o) => o.type === `slot_level_${slotLevel}`);
9
- if (!opt) return null;
11
+ var _a, _b;
12
+ const base = (_a = spell.level) != null ? _a : 0;
13
+ if (base === 0) return null;
14
+ const opt = ((_b = spell.casting_options) != null ? _b : []).find((o) => o.type === `slot_level_${slotLevel}`);
15
+ if (!opt) {
16
+ const roll = spellBaseRoll(spell);
17
+ return roll !== null && slotLevel === base ? { field: "damage_roll", value: roll } : null;
18
+ }
10
19
  if (opt.damage_roll) return { field: "damage_roll", value: opt.damage_roll };
11
20
  if (typeof opt.target_count === "number") {
12
21
  if (opt.target_count === slotLevel) return null;
@@ -15,6 +24,27 @@ function spellEffectPartsAtSlot(spell, slotLevel) {
15
24
  if (opt.duration) return { field: "duration", value: opt.duration };
16
25
  return opt.desc != null ? { field: "desc", value: opt.desc } : null;
17
26
  }
27
+ var NON_ROLL_SCALES = /* @__PURE__ */ new Set(["duration", "target_count"]);
28
+ var NON_ROLL_OPTION_FIELDS = /* @__PURE__ */ new Set([...NON_ROLL_SCALES, "concentration"]);
29
+ function spellBaseRollAtSlot(spell, slotLevel) {
30
+ var _a, _b, _c;
31
+ const base = (_a = spell.level) != null ? _a : 0;
32
+ if (base === 0 || slotLevel < base) return null;
33
+ const roll = spellBaseRoll(spell);
34
+ if (roll === null) return null;
35
+ const slotOptions = ((_b = spell.casting_options) != null ? _b : []).filter((o) => {
36
+ var _a2;
37
+ return /^slot_level_\d+$/.test(String((_a2 = o.type) != null ? _a2 : ""));
38
+ });
39
+ if (slotOptions.length > 0) {
40
+ const onlyNonRoll = slotOptions.every((o) => {
41
+ const fields = Object.entries(o).filter(([k, v]) => k !== "type" && v != null).map(([k]) => k);
42
+ return fields.some((k) => NON_ROLL_SCALES.has(k)) && fields.every((k) => NON_ROLL_OPTION_FIELDS.has(k));
43
+ });
44
+ return onlyNonRoll ? roll : null;
45
+ }
46
+ return ((_c = spell.at_higher_levels) != null ? _c : []).some((t) => typeof t === "string" && t.trim() !== "") ? null : roll;
47
+ }
18
48
  function spellEffectAtSlot(spell, slotLevel) {
19
49
  var _a, _b;
20
50
  return (_b = (_a = spellEffectPartsAtSlot(spell, slotLevel)) == null ? void 0 : _a.value) != null ? _b : null;
@@ -30,7 +60,7 @@ function spellEffectAtCharacterLevel(spell, characterLevel) {
30
60
  const n = Number(m[1]);
31
61
  if (n <= characterLevel && (best === null || n > best.n)) best = { n, roll: opt.damage_roll };
32
62
  }
33
- return best ? best.roll : null;
63
+ return best ? best.roll : spellBaseRoll(spell);
34
64
  }
35
65
  function upcastLevelsFor(spell, ownedSlotLevels) {
36
66
  var _a;
@@ -39,6 +69,8 @@ function upcastLevelsFor(spell, ownedSlotLevels) {
39
69
  return ownedSlotLevels.filter((l) => l > base).sort((a, b) => a - b);
40
70
  }
41
71
  export {
72
+ spellBaseRoll,
73
+ spellBaseRollAtSlot,
42
74
  spellEffectAtCharacterLevel,
43
75
  spellEffectAtSlot,
44
76
  spellEffectPartsAtSlot,
@@ -40,6 +40,7 @@ export declare const spellEntitySchema: z.ZodObject<{
40
40
  damage: z.ZodOptional<z.ZodObject<{
41
41
  types: z.ZodArray<z.ZodString>;
42
42
  }, z.core.$strip>>;
43
+ damage_roll: z.ZodOptional<z.ZodString>;
43
44
  saving_throw: z.ZodOptional<z.ZodObject<{
44
45
  ability: z.ZodString;
45
46
  }, z.core.$strip>>;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  spellEntitySchema
3
- } from "../chunk-VQAXTIBW.js";
3
+ } from "../chunk-ZDAEGJH4.js";
4
4
  import "../chunk-UA5KS2PL.js";
5
5
  export {
6
6
  spellEntitySchema
@@ -16,6 +16,10 @@ export interface Spell {
16
16
  damage?: {
17
17
  types: string[];
18
18
  };
19
+ /** The BASE roll: the roll at the spell's own level, or a cantrip's tier-1 roll (before its first
20
+ * `player_level_<N>` option). Like `casting_options[].damage_roll` it also carries healing rolls
21
+ * (Cure Wounds `2d8`). Mirrors Open5e v2's top-level `damage_roll`. */
22
+ damage_roll?: string;
19
23
  saving_throw?: {
20
24
  ability: string;
21
25
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archivist-gg/dnd5e",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "license": "MIT AND CC-BY-4.0",
5
5
  "type": "module",
6
6
  "publishConfig": {