@archivist-gg/dnd5e 0.1.0 → 0.2.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.
- package/dist/background/background.codec.js +4 -4
- package/dist/background/background.parser.js +3 -3
- package/dist/background/background.schema.js +2 -2
- package/dist/{chunk-FFULAF2P.js → chunk-2BWAID3Y.js} +2 -2
- package/dist/{chunk-FUK7QSN5.js → chunk-42Y5IJKT.js} +1 -1
- package/dist/{chunk-537BHVSH.js → chunk-4JHOHWBI.js} +1 -1
- package/dist/{chunk-ZLG6FQFB.js → chunk-5WQD3WNZ.js} +16 -1
- package/dist/{chunk-FEOB24KP.js → chunk-73HOFQQ5.js} +1 -1
- package/dist/{chunk-4SSDYJS2.js → chunk-7VJ6BOA5.js} +5 -1
- package/dist/{chunk-DABBCYTT.js → chunk-CZPMTQOY.js} +1 -1
- package/dist/{chunk-DRNZJNJP.js → chunk-DZXSWSBP.js} +1 -1
- package/dist/{chunk-RVYYKYFL.js → chunk-GE4TLPWM.js} +1 -1
- package/dist/{chunk-4C2KOKLF.js → chunk-K6Q5OUGK.js} +12 -0
- package/dist/{chunk-PMADYMUI.js → chunk-L7E2UT5A.js} +1 -1
- package/dist/{chunk-CDW7R2QT.js → chunk-NXSWH3AE.js} +4 -1
- package/dist/{chunk-IDTDACM6.js → chunk-PHQRGDRQ.js} +1 -1
- package/dist/{chunk-BAXWWGFJ.js → chunk-Q5K6LWBZ.js} +1 -1
- package/dist/chunk-R5SQHP2X.js +1092 -0
- package/dist/{chunk-Z6GXV2GO.js → chunk-TLYDBT5F.js} +1 -1
- package/dist/{chunk-QQ5W2AZP.js → chunk-TQZB4OCJ.js} +67 -12
- package/dist/{chunk-A7T5L7SS.js → chunk-TUIFSVLI.js} +1 -1
- package/dist/{chunk-XURS6Q6Q.js → chunk-V6SOFUJW.js} +19 -5
- package/dist/{chunk-Z4TL2V7S.js → chunk-VTYUBINW.js} +1 -1
- package/dist/{chunk-B7UXKNLD.js → chunk-W7HWMMBF.js} +1 -1
- package/dist/{chunk-SSXBYF3P.js → chunk-XLSVNUTK.js} +1 -1
- package/dist/{chunk-GIOH5JB5.js → chunk-XTIQWSFM.js} +1 -1
- package/dist/{chunk-PKC77VZP.js → chunk-ZZGEVJKX.js} +1 -1
- package/dist/class/class.codec.js +5 -5
- package/dist/class/class.parser.js +4 -4
- package/dist/class/class.schema.js +3 -3
- package/dist/feat/feat.codec.js +4 -4
- package/dist/feat/feat.parser.js +3 -3
- package/dist/feat/feat.schema.js +2 -2
- package/dist/index.js +622 -118
- package/dist/item/item.actions-map.d.ts +10 -3
- package/dist/item/item.actions-map.js +13 -8
- package/dist/item/item.codec.js +3 -3
- package/dist/item/item.parser.js +2 -2
- package/dist/item/item.schema.d.ts +13 -0
- package/dist/item/item.schema.js +1 -1
- package/dist/item/item.types.d.ts +17 -0
- package/dist/pc/pc.decision-engine.js +8 -1
- package/dist/pc/pc.equipment.d.ts +2 -1
- package/dist/pc/pc.equipment.js +9 -1
- package/dist/pc/pc.feature-effects.d.ts +31 -9
- package/dist/pc/pc.feature-effects.js +11 -1
- package/dist/pc/pc.parser.js +1 -1
- package/dist/pc/pc.pools.js +8 -3
- package/dist/pc/pc.recalc.js +80 -62
- package/dist/pc/pc.resolver.d.ts +71 -1
- package/dist/pc/pc.resolver.js +21 -254
- package/dist/pc/pc.schema.d.ts +21 -0
- package/dist/pc/pc.schema.js +1 -1
- package/dist/pc/pc.types.d.ts +80 -1
- package/dist/race/race.codec.js +5 -5
- package/dist/race/race.parser.js +4 -4
- package/dist/race/race.schema.js +3 -3
- package/dist/schemas/choice-schema.js +1 -1
- package/dist/schemas/feature-schema.js +2 -2
- package/dist/subclass/subclass.codec.js +5 -5
- package/dist/subclass/subclass.parser.js +4 -4
- package/dist/subclass/subclass.schema.js +3 -3
- package/dist/types/choice.d.ts +5 -0
- package/dist/weapon/weapon-mastery.d.ts +12 -0
- package/package.json +1 -1
- package/dist/chunk-6PXE7T7C.js +0 -79
- package/dist/chunk-TJCNX6UZ.js +0 -521
package/dist/pc/pc.resolver.d.ts
CHANGED
|
@@ -2,12 +2,28 @@ import type { EntityRegistry } from "@archivist-gg/core";
|
|
|
2
2
|
import type { RaceEntity } from "@archivist-gg/dnd5e/race/race.types";
|
|
3
3
|
import type { BackgroundEntity } from "@archivist-gg/dnd5e/background/background.types";
|
|
4
4
|
import type { FeatEntity } from "@archivist-gg/dnd5e/feat/feat.types";
|
|
5
|
-
import type {
|
|
5
|
+
import type { Ability } from "@archivist-gg/dnd5e";
|
|
6
|
+
import type { Character, ChoiceValue, ResolvedCharacter, ResolvedClass, ResolvedFeature, ResolvedSpell } from "./pc.types.js";
|
|
6
7
|
export interface ResolveResult {
|
|
7
8
|
character: ResolvedCharacter;
|
|
8
9
|
warnings: string[];
|
|
9
10
|
}
|
|
10
11
|
export declare function stripSlug(ref: string | null): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a background's `origin_feat` wikilink into a real FeatEntity + a display
|
|
14
|
+
* name, using ONLY the EntityRegistry (no DOM). Lifted from the builder's
|
|
15
|
+
* background-step so the resolver pipeline and the builder share ONE resolution
|
|
16
|
+
* (R2-m7, [[always-retire-shims-edit-all]]). Handles both path-style refs
|
|
17
|
+
* ("[[SRD 2024/Feats/Alert]]") and bare-slug refs ("[[my-feat]]"), plus the 2024
|
|
18
|
+
* parenthetical-variant refs ("[[SRD 2024/Feats/Magic Initiate (Cleric)]]") that
|
|
19
|
+
* resolve to the BASE feat ("magic-initiate") while keeping the full variant
|
|
20
|
+
* string as the display name. Returns null when the ref is empty or resolves to no
|
|
21
|
+
* feat (an unresolvable ref folds nothing into the pipeline).
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveOriginFeat(entities: EntityRegistry, originFeatRef: string | null): {
|
|
24
|
+
feat: FeatEntity;
|
|
25
|
+
display: string;
|
|
26
|
+
} | null;
|
|
11
27
|
export declare class PCResolver {
|
|
12
28
|
private readonly entities;
|
|
13
29
|
constructor(entities: EntityRegistry);
|
|
@@ -15,11 +31,65 @@ export declare class PCResolver {
|
|
|
15
31
|
get registry(): EntityRegistry;
|
|
16
32
|
resolve(character: Character): ResolveResult;
|
|
17
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* 2024 Weapon Mastery: unions the persisted `weapon-mastery` picks across every
|
|
36
|
+
* class/level `ClassEntry.choices[level]["weapon-mastery"]`. Returns both the
|
|
37
|
+
* ORIGINAL full picked slugs (`full` — used for exact-match display-name lookup)
|
|
38
|
+
* and their BARE-normalized form (`bare` — e.g. "srd-2024_greatsword" →
|
|
39
|
+
* "greatsword"), each de-duplicated in first-seen order. Bare-authored picks
|
|
40
|
+
* normalize to themselves. Non-string / non-array picks are ignored.
|
|
41
|
+
*/
|
|
42
|
+
export declare function collectChosenWeaponMasteries(character: Character): {
|
|
43
|
+
bare: string[];
|
|
44
|
+
full: string[];
|
|
45
|
+
};
|
|
18
46
|
/**
|
|
19
47
|
* Walks class.choices for `feat` entries and adds background feat slugs.
|
|
20
48
|
* Returns bare slugs (no `[[ ]]`).
|
|
21
49
|
*/
|
|
22
50
|
export declare function collectFeatSlugs(character: Character): string[];
|
|
51
|
+
/**
|
|
52
|
+
* Feat→spell application (3d). Walks a feat's OWN `choices` to find its spell
|
|
53
|
+
* picks and turns each into a ResolvedSpell. A feat's spells sit behind a
|
|
54
|
+
* `spell-list` select-inline whose CHOSEN branch nests `select-entity{spell}`
|
|
55
|
+
* picks; the chosen spellcasting ability comes from the feat's
|
|
56
|
+
* `spellcasting-ability` select-inline. `read(childId)` yields the persisted pick
|
|
57
|
+
* for a child choice id (origin feat: `origin_choices["background:feat:<id>"]`;
|
|
58
|
+
* class-slot feat: `choices[lvl]["feat:<id>"]`). Unresolvable spell slugs warn and
|
|
59
|
+
* are skipped, mirroring the known-spell loop. classSlug stays null: a feat spell
|
|
60
|
+
* is not owned by a class for DC/ability; the carried `ability` drives its casting.
|
|
61
|
+
*/
|
|
62
|
+
export declare function collectFeatGrantedSpells(feat: FeatEntity, read: (childId: string) => ChoiceValue | undefined, entities: EntityRegistry, warnings: string[]): ResolvedSpell[];
|
|
63
|
+
/**
|
|
64
|
+
* Item→spell application (P4-T3). Walks the character's equipment for entries whose
|
|
65
|
+
* resolved item is a spell scroll (carries the T1 `scroll_level` marker) AND names a
|
|
66
|
+
* chosen spell via `overrides.spell`, turning each into a
|
|
67
|
+
* ResolvedSpell{ source:"item", classSlug:null, prepared:true, alwaysPrepared:true }.
|
|
68
|
+
* The casting `ability` is the character's OWN spellcasting ability when they have
|
|
69
|
+
* one (`ownAbility`, the first caster class's ability), else the per-instance
|
|
70
|
+
* `overrides.spell_ability`, else undefined (a no-ability scroll is left ability-less:
|
|
71
|
+
* the plugin surfaces it without a DC; an ability is NEVER fabricated here).
|
|
72
|
+
* `entryIndex` records the originating equipment index so the segmented dedupe keeps
|
|
73
|
+
* two scrolls of one spell (and a scroll of a class-known spell) as their own rows.
|
|
74
|
+
* Unresolvable spell slugs warn and are skipped, mirroring the known-spell + feat loops.
|
|
75
|
+
* In-memory only: no KnownSpellObject{source:"item"} is injected (its strict schema
|
|
76
|
+
* omits `ability`).
|
|
77
|
+
*/
|
|
78
|
+
export declare function collectItemGrantedSpells(character: Character, ownAbility: Ability | null, entities: EntityRegistry, warnings: string[]): ResolvedSpell[];
|
|
79
|
+
/**
|
|
80
|
+
* Collapses duplicate resolved spells (3d Minor #2 carry-forward + P4-T3 segmented
|
|
81
|
+
* dedupe). A spell can be emitted more than once: it may be BOTH in
|
|
82
|
+
* `character.spells.known` AND a feat pick, or a feat may be taken as both origin +
|
|
83
|
+
* class-slot. A class-sourced copy owns a real DC via its `classSlug`, so it always
|
|
84
|
+
* wins over a feat copy of the same slug; otherwise first-seen wins. Insertion order
|
|
85
|
+
* is preserved (a later class copy replaces an earlier feat copy in place), so the
|
|
86
|
+
* Spells section never renders duplicate rows while a legitimately class-known copy is
|
|
87
|
+
* never lost. Item-source spells carry INSTANCE identity: they key by
|
|
88
|
+
* `slug + "#" + entryIndex`, so two scrolls of one spell stay two rows and a scroll of
|
|
89
|
+
* a class-known spell keeps its OWN item row, never collapsing into / being collapsed
|
|
90
|
+
* by a class/feat copy. The class/feat slug-only merge is unchanged.
|
|
91
|
+
*/
|
|
92
|
+
export declare function dedupeResolvedSpells(spells: ResolvedSpell[]): ResolvedSpell[];
|
|
23
93
|
/** Selected select-entity values (optional-features) and selected inline
|
|
24
94
|
* options carrying effects[] become synthesized resolved features (SP2 Plan 3 §9).
|
|
25
95
|
* The actions/resources engines consume these synthesized features, and the
|
package/dist/pc/pc.resolver.js
CHANGED
|
@@ -1,267 +1,34 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
PCResolver,
|
|
3
|
+
collectChosenGrantedFeatures,
|
|
4
|
+
collectChosenWeaponMasteries,
|
|
5
|
+
collectFeatGrantedSpells,
|
|
6
|
+
collectFeatSlugs,
|
|
7
|
+
collectItemGrantedSpells,
|
|
8
|
+
collectResolvedFeatures,
|
|
9
|
+
dedupeResolvedSpells,
|
|
10
|
+
resolveOriginFeat,
|
|
11
|
+
stripSlug
|
|
12
|
+
} from "../chunk-R5SQHP2X.js";
|
|
10
13
|
import "../chunk-BH6AMZVY.js";
|
|
14
|
+
import "../chunk-UC4FE5X3.js";
|
|
15
|
+
import "../chunk-IXZXFFFW.js";
|
|
16
|
+
import "../chunk-5XUXXAQJ.js";
|
|
11
17
|
import "../chunk-JEYQU3JT.js";
|
|
18
|
+
import "../chunk-DC74TZNA.js";
|
|
19
|
+
import "../chunk-3TRGRSCH.js";
|
|
12
20
|
import "../chunk-J2RYSZMQ.js";
|
|
13
21
|
import "../chunk-NAEPSZAM.js";
|
|
14
22
|
import "../chunk-WP2CX2JO.js";
|
|
15
|
-
|
|
16
|
-
// src/pc/pc.resolver.ts
|
|
17
|
-
var SLUG_RE = /^\[\[(.+?)\]\]$/;
|
|
18
|
-
function stripSlug(ref) {
|
|
19
|
-
if (!ref) return null;
|
|
20
|
-
const m = ref.match(SLUG_RE);
|
|
21
|
-
return m ? m[1] : ref;
|
|
22
|
-
}
|
|
23
|
-
var PCResolver = class {
|
|
24
|
-
constructor(entities) {
|
|
25
|
-
this.entities = entities;
|
|
26
|
-
}
|
|
27
|
-
/** Exposed so recalc() can apply equipment bonuses (Pass A / Pass B). */
|
|
28
|
-
get registry() {
|
|
29
|
-
return this.entities;
|
|
30
|
-
}
|
|
31
|
-
resolve(character) {
|
|
32
|
-
var _a, _b, _c, _d, _e;
|
|
33
|
-
const warnings = [];
|
|
34
|
-
const lookup = (rawRef, type) => {
|
|
35
|
-
const slug = stripSlug(rawRef);
|
|
36
|
-
if (!slug) return null;
|
|
37
|
-
const reg = this.entities.getByTypeAndSlug(type, slug);
|
|
38
|
-
if (!reg) {
|
|
39
|
-
warnings.push(`Slug [[${slug}]] not found in compendium as ${type}.`);
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
const data = reg.data;
|
|
43
|
-
return data.slug == null ? { ...data, slug: reg.slug } : data;
|
|
44
|
-
};
|
|
45
|
-
const race = lookup(character.race, "race");
|
|
46
|
-
const background = lookup(character.background, "background");
|
|
47
|
-
const classes = character.class.map((c) => ({
|
|
48
|
-
entity: lookup(c.name, "class"),
|
|
49
|
-
level: c.level,
|
|
50
|
-
subclass: lookup(c.subclass, "subclass"),
|
|
51
|
-
choices: c.choices
|
|
52
|
-
}));
|
|
53
|
-
const featSlugs = collectFeatSlugs(character);
|
|
54
|
-
const feats = [];
|
|
55
|
-
for (const slug of featSlugs) {
|
|
56
|
-
const f = lookup(`[[${slug}]]`, "feat");
|
|
57
|
-
if (f) feats.push(f);
|
|
58
|
-
}
|
|
59
|
-
const totalLevel = classes.reduce((sum, c) => sum + c.level, 0);
|
|
60
|
-
const features = collectResolvedFeatures(race, classes, background, feats);
|
|
61
|
-
const extraFeatures = collectChosenGrantedFeatures(character, classes, this.entities, race, background);
|
|
62
|
-
features.push(...extraFeatures);
|
|
63
|
-
const primaryCasterSlug = (_a = classes.map((c, i) => c.entity && resolveSpellcasting(c) ? stripSlug(character.class[i].name) : null).find((slug) => slug != null)) != null ? _a : null;
|
|
64
|
-
const spells = [];
|
|
65
|
-
for (const raw of (_b = character.spells.known) != null ? _b : []) {
|
|
66
|
-
const n = normalizeKnownSpell(raw);
|
|
67
|
-
const reg = this.entities.getByTypeAndSlug("spell", n.slug);
|
|
68
|
-
if (!reg) {
|
|
69
|
-
warnings.push(`Spell [[${n.slug}]] not found in compendium.`);
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
const entity = reg.data;
|
|
73
|
-
const isCantrip = ((_c = entity.level) != null ? _c : 0) === 0;
|
|
74
|
-
const classSlug = (_d = n.classSlug) != null ? _d : primaryCasterSlug;
|
|
75
|
-
const prep = isCantrip || n.alwaysPrepared ? true : (_e = n.preparedFlag) != null ? _e : false;
|
|
76
|
-
spells.push({ entity, slug: n.slug, classSlug, source: n.source, prepared: prep, alwaysPrepared: n.alwaysPrepared });
|
|
77
|
-
}
|
|
78
|
-
const resolvedCharacter = {
|
|
79
|
-
definition: character,
|
|
80
|
-
race,
|
|
81
|
-
classes,
|
|
82
|
-
background,
|
|
83
|
-
feats,
|
|
84
|
-
totalLevel,
|
|
85
|
-
features,
|
|
86
|
-
spells,
|
|
87
|
-
pools: [],
|
|
88
|
-
state: character.state
|
|
89
|
-
};
|
|
90
|
-
resolvedCharacter.pools = resolveAllPools(resolvedCharacter, this.entities);
|
|
91
|
-
return { character: resolvedCharacter, warnings };
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
function collectFeatSlugs(character) {
|
|
95
|
-
const slugs = /* @__PURE__ */ new Set();
|
|
96
|
-
for (const c of character.class) {
|
|
97
|
-
for (const [, choiceBlock] of Object.entries(c.choices)) {
|
|
98
|
-
const feat = choiceBlock == null ? void 0 : choiceBlock.feat;
|
|
99
|
-
if (typeof feat === "string") {
|
|
100
|
-
const s = stripSlug(feat);
|
|
101
|
-
if (s) slugs.add(s);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return [...slugs];
|
|
106
|
-
}
|
|
107
|
-
function collectChosenGrantedFeatures(character, classes, registry, race = null, background = null) {
|
|
108
|
-
var _a, _b;
|
|
109
|
-
const out = [];
|
|
110
|
-
classes.forEach((c, i) => {
|
|
111
|
-
var _a2, _b2, _c, _d;
|
|
112
|
-
if (!c.entity) return;
|
|
113
|
-
const entity = c.entity;
|
|
114
|
-
const entry = character.class[i];
|
|
115
|
-
if (!entry) return;
|
|
116
|
-
for (const [lvlStr, atLevel] of Object.entries(entry.choices)) {
|
|
117
|
-
const lvl = Number(lvlStr);
|
|
118
|
-
if (!Number.isFinite(lvl) || lvl > entry.level) continue;
|
|
119
|
-
const features = (_b2 = ((_a2 = entity.features_by_level) != null ? _a2 : {})[lvl]) != null ? _b2 : [];
|
|
120
|
-
const subFeatures = c.subclass ? (_d = ((_c = c.subclass.features_by_level) != null ? _c : {})[lvl]) != null ? _d : [] : [];
|
|
121
|
-
for (const feature of [...features, ...subFeatures]) {
|
|
122
|
-
walkChoiceGrants(feature.choices, atLevel, (granted) => {
|
|
123
|
-
out.push({ feature: granted, source: { kind: "class", slug: entity.slug, level: lvl } });
|
|
124
|
-
}, registry);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
const oc = (_a = character.origin_choices) != null ? _a : {};
|
|
129
|
-
const originAt = (ns) => {
|
|
130
|
-
const at = {};
|
|
131
|
-
for (const [key, val] of Object.entries(oc)) {
|
|
132
|
-
if (key.startsWith(`${ns}:`)) at[key.slice(ns.length + 1)] = val;
|
|
133
|
-
}
|
|
134
|
-
return at;
|
|
135
|
-
};
|
|
136
|
-
if (race) {
|
|
137
|
-
const atRace = originAt("race");
|
|
138
|
-
const emitRace = (granted) => {
|
|
139
|
-
out.push({ feature: granted, source: { kind: "race", slug: race.slug } });
|
|
140
|
-
};
|
|
141
|
-
walkChoiceGrants(race.choices, atRace, emitRace, registry);
|
|
142
|
-
for (const trait of (_b = race.traits) != null ? _b : []) {
|
|
143
|
-
walkChoiceGrants(trait.choices, atRace, emitRace, registry);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
if (background) {
|
|
147
|
-
const atBg = originAt("background");
|
|
148
|
-
const emitBg = (granted) => {
|
|
149
|
-
out.push({ feature: granted, source: { kind: "background", slug: background.slug } });
|
|
150
|
-
};
|
|
151
|
-
walkChoiceGrants(background.choices, atBg, emitBg, registry);
|
|
152
|
-
if (background.feature) {
|
|
153
|
-
walkChoiceGrants(background.feature.choices, atBg, emitBg, registry);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return out;
|
|
157
|
-
}
|
|
158
|
-
function walkChoiceGrants(choices, atLevel, emit, registry) {
|
|
159
|
-
var _a, _b, _c, _d;
|
|
160
|
-
for (const ch of choices != null ? choices : []) {
|
|
161
|
-
const sel = atLevel[ch.id];
|
|
162
|
-
if (ch.kind === "select-entity" && ch.entity_type === "optional-feature") {
|
|
163
|
-
const slugs = Array.isArray(sel) ? sel : typeof sel === "string" ? [sel] : [];
|
|
164
|
-
for (const slug of slugs) {
|
|
165
|
-
const reg = registry.getByTypeAndSlug("optional-feature", (_a = stripSlug(String(slug))) != null ? _a : String(slug));
|
|
166
|
-
if (!reg) continue;
|
|
167
|
-
const d = reg.data;
|
|
168
|
-
emit({
|
|
169
|
-
id: (_b = d.slug) != null ? _b : String(slug),
|
|
170
|
-
name: (_c = d.name) != null ? _c : String(slug),
|
|
171
|
-
description: d.description,
|
|
172
|
-
effects: d.effects,
|
|
173
|
-
...d.action_cost ? { action: d.action_cost } : {}
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
if (ch.kind === "select-inline") {
|
|
178
|
-
const branch = typeof sel === "string" ? ch.options.find((o) => o.value === sel) : void 0;
|
|
179
|
-
if ((_d = branch == null ? void 0 : branch.effects) == null ? void 0 : _d.length) {
|
|
180
|
-
emit({ id: `${ch.id}-${branch.value}`, name: branch.label, description: branch.description, effects: branch.effects });
|
|
181
|
-
}
|
|
182
|
-
if (branch == null ? void 0 : branch.choices) walkChoiceGrants(branch.choices, atLevel, emit, registry);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
function collectResolvedFeatures(race, classes, background, feats) {
|
|
187
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
188
|
-
const out = [];
|
|
189
|
-
for (const c of classes) {
|
|
190
|
-
if (!c.entity) continue;
|
|
191
|
-
const slug = c.entity.slug;
|
|
192
|
-
const byLevel = (_a = c.entity.features_by_level) != null ? _a : {};
|
|
193
|
-
for (const [lvlStr, feats0] of Object.entries(byLevel)) {
|
|
194
|
-
const lvl = parseInt(lvlStr, 10);
|
|
195
|
-
if (Number.isNaN(lvl) || lvl > c.level) continue;
|
|
196
|
-
for (const feat of feats0) {
|
|
197
|
-
out.push({ feature: feat, source: { kind: "class", slug, level: lvl } });
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
if ((_b = c.entity.resources) == null ? void 0 : _b.length) {
|
|
201
|
-
out.push({
|
|
202
|
-
feature: { name: c.entity.name, resources: c.entity.resources },
|
|
203
|
-
source: { kind: "class", slug, level: 1 }
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
if (c.subclass) {
|
|
207
|
-
const sSlug = c.subclass.slug;
|
|
208
|
-
const sByLevel = (_c = c.subclass.features_by_level) != null ? _c : {};
|
|
209
|
-
for (const [lvlStr, feats0] of Object.entries(sByLevel)) {
|
|
210
|
-
const lvl = parseInt(lvlStr, 10);
|
|
211
|
-
if (Number.isNaN(lvl) || lvl > c.level) continue;
|
|
212
|
-
for (const feat of feats0) {
|
|
213
|
-
out.push({ feature: feat, source: { kind: "subclass", slug: sSlug, level: lvl } });
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
if ((_d = c.subclass.resources) == null ? void 0 : _d.length) {
|
|
217
|
-
out.push({
|
|
218
|
-
feature: { name: c.subclass.name, resources: c.subclass.resources },
|
|
219
|
-
source: { kind: "subclass", slug: sSlug, level: 1 }
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
if (race) {
|
|
225
|
-
const traits = (_e = race.traits) != null ? _e : [];
|
|
226
|
-
for (const feat of traits) {
|
|
227
|
-
out.push({ feature: feat, source: { kind: "race", slug: race.slug } });
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
if (background) {
|
|
231
|
-
const bgFeature = background.feature;
|
|
232
|
-
if (bgFeature) {
|
|
233
|
-
out.push({ feature: bgFeature, source: { kind: "background", slug: background.slug } });
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
for (const feat of feats) {
|
|
237
|
-
const bundled = (_f = feat.features) != null ? _f : [];
|
|
238
|
-
const entityEffects = (_g = feat.effects) != null ? _g : [];
|
|
239
|
-
if (bundled.length > 0) {
|
|
240
|
-
bundled.forEach((f, i) => {
|
|
241
|
-
var _a2;
|
|
242
|
-
const feature = i === 0 && entityEffects.length > 0 ? { ...f, effects: [...(_a2 = f.effects) != null ? _a2 : [], ...entityEffects] } : f;
|
|
243
|
-
out.push({ feature, source: { kind: "feat", slug: feat.slug } });
|
|
244
|
-
});
|
|
245
|
-
} else {
|
|
246
|
-
const name = (_h = feat.name) != null ? _h : feat.slug;
|
|
247
|
-
const description = (_i = feat.description) != null ? _i : "";
|
|
248
|
-
out.push({
|
|
249
|
-
feature: {
|
|
250
|
-
name,
|
|
251
|
-
description,
|
|
252
|
-
...feat.resources ? { resources: feat.resources } : {},
|
|
253
|
-
...entityEffects.length > 0 ? { effects: entityEffects } : {}
|
|
254
|
-
},
|
|
255
|
-
source: { kind: "feat", slug: feat.slug }
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return out;
|
|
260
|
-
}
|
|
261
23
|
export {
|
|
262
24
|
PCResolver,
|
|
263
25
|
collectChosenGrantedFeatures,
|
|
26
|
+
collectChosenWeaponMasteries,
|
|
27
|
+
collectFeatGrantedSpells,
|
|
264
28
|
collectFeatSlugs,
|
|
29
|
+
collectItemGrantedSpells,
|
|
265
30
|
collectResolvedFeatures,
|
|
31
|
+
dedupeResolvedSpells,
|
|
32
|
+
resolveOriginFeat,
|
|
266
33
|
stripSlug
|
|
267
34
|
};
|
package/dist/pc/pc.schema.d.ts
CHANGED
|
@@ -130,6 +130,19 @@ export declare const characterSchema: z.ZodObject<{
|
|
|
130
130
|
special: "special";
|
|
131
131
|
}>>;
|
|
132
132
|
range: z.ZodOptional<z.ZodString>;
|
|
133
|
+
resist: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
134
|
+
immune: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
135
|
+
vulnerable: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
136
|
+
condition_immune: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
137
|
+
spell: z.ZodOptional<z.ZodString>;
|
|
138
|
+
spell_ability: z.ZodOptional<z.ZodEnum<{
|
|
139
|
+
str: "str";
|
|
140
|
+
dex: "dex";
|
|
141
|
+
con: "con";
|
|
142
|
+
int: "int";
|
|
143
|
+
wis: "wis";
|
|
144
|
+
cha: "cha";
|
|
145
|
+
}>>;
|
|
133
146
|
}, z.core.$strict>>;
|
|
134
147
|
state: z.ZodOptional<z.ZodObject<{
|
|
135
148
|
charges: z.ZodOptional<z.ZodObject<{
|
|
@@ -216,6 +229,14 @@ export declare const characterSchema: z.ZodObject<{
|
|
|
216
229
|
saveDC: z.ZodOptional<z.ZodNumber>;
|
|
217
230
|
attackBonus: z.ZodOptional<z.ZodNumber>;
|
|
218
231
|
}, z.core.$strip>>;
|
|
232
|
+
spellcasting_ability: z.ZodOptional<z.ZodEnum<{
|
|
233
|
+
str: "str";
|
|
234
|
+
dex: "dex";
|
|
235
|
+
con: "con";
|
|
236
|
+
int: "int";
|
|
237
|
+
wis: "wis";
|
|
238
|
+
cha: "cha";
|
|
239
|
+
}>>;
|
|
219
240
|
spell_slots: z.ZodOptional<z.ZodRecord<z.ZodCoercedNumber<unknown>, z.ZodNumber>>;
|
|
220
241
|
attunement_limit: z.ZodOptional<z.ZodNumber>;
|
|
221
242
|
}, z.core.$strip>>;
|
package/dist/pc/pc.schema.js
CHANGED
package/dist/pc/pc.types.d.ts
CHANGED
|
@@ -35,6 +35,10 @@ export interface EquipmentEntryOverrides {
|
|
|
35
35
|
immune?: string[];
|
|
36
36
|
vulnerable?: string[];
|
|
37
37
|
condition_immune?: string[];
|
|
38
|
+
/** Per-instance spell payload (e.g. the spell a Spell Scroll casts). */
|
|
39
|
+
spell?: string;
|
|
40
|
+
/** Ability governing this instance's spell save DC / attack when cast. */
|
|
41
|
+
spell_ability?: Ability;
|
|
38
42
|
}
|
|
39
43
|
export interface EquipmentEntryState {
|
|
40
44
|
charges?: {
|
|
@@ -92,6 +96,10 @@ export interface KnownSpellObject {
|
|
|
92
96
|
source?: "class" | "feat" | "item" | "race" | "domain";
|
|
93
97
|
prepared?: boolean;
|
|
94
98
|
always_prepared?: boolean;
|
|
99
|
+
/** The spell's own spellcasting ability (e.g. WIS for a Magic Initiate (Cleric)
|
|
100
|
+
* pick). Belongs on the persisted shape for symmetry with ResolvedSpell.ability;
|
|
101
|
+
* feat spells arrive via origin_choices, but a known-spell entry may carry it. */
|
|
102
|
+
ability?: Ability;
|
|
95
103
|
}
|
|
96
104
|
export type KnownSpellEntry = string | KnownSpellObject;
|
|
97
105
|
export type PassiveKind = "perception" | "investigation" | "insight";
|
|
@@ -120,6 +128,10 @@ export interface CharacterOverrides {
|
|
|
120
128
|
saveDC?: number;
|
|
121
129
|
attackBonus?: number;
|
|
122
130
|
};
|
|
131
|
+
/** Character-level spellcasting ability for spells that carry no ability of
|
|
132
|
+
* their own (e.g. a Spell Scroll cast by a non-caster): the DC-ability fallback
|
|
133
|
+
* after a per-instance override and the character's own class ability. */
|
|
134
|
+
spellcasting_ability?: Ability;
|
|
123
135
|
spell_slots?: Record<number, number>;
|
|
124
136
|
attunement_limit?: number;
|
|
125
137
|
}
|
|
@@ -246,6 +258,19 @@ export type FeatureSource = {
|
|
|
246
258
|
export interface ResolvedFeature {
|
|
247
259
|
feature: Feature;
|
|
248
260
|
source: FeatureSource;
|
|
261
|
+
/** #3: chosen select-inline option prose, folded onto the PARENT feature for
|
|
262
|
+
* render (a shallow-copy wrapper field — the shared registry entity is never
|
|
263
|
+
* mutated). One entry per select-inline choice with a recorded pick. */
|
|
264
|
+
chosenInline?: {
|
|
265
|
+
label: string;
|
|
266
|
+
description?: string;
|
|
267
|
+
}[];
|
|
268
|
+
/** #3: marks a synthesized chosen-option feature the renderer must SKIP (its
|
|
269
|
+
* prose shows on the parent via chosenInline). A wrapper-only flag —
|
|
270
|
+
* computeFeatureEffects ignores it, so the synthetic's effects still fold. */
|
|
271
|
+
renderSuppressed?: boolean;
|
|
272
|
+
/** The feat's entire substance is a full ability-score improvement (a pure ASI). The DISPLAY layer hides these. */
|
|
273
|
+
buildOnly?: boolean;
|
|
249
274
|
}
|
|
250
275
|
export interface ResolvedSpell {
|
|
251
276
|
entity: Spell;
|
|
@@ -254,6 +279,14 @@ export interface ResolvedSpell {
|
|
|
254
279
|
source: "class" | "feat" | "item" | "race" | "domain";
|
|
255
280
|
prepared: boolean;
|
|
256
281
|
alwaysPrepared: boolean;
|
|
282
|
+
/** The spell's OWN spellcasting ability. For a feat spell this carries the
|
|
283
|
+
* chosen ability (e.g. WIS for Magic Initiate (Cleric)) while classSlug stays
|
|
284
|
+
* null (a feat spell is not owned by a class for DC/ability). Absent/undefined
|
|
285
|
+
* for class spells, which derive their ability from classSlug. */
|
|
286
|
+
ability?: Ability | null;
|
|
287
|
+
/** Index of the originating equipment entry when this spell was granted by a
|
|
288
|
+
* per-instance item (e.g. a Spell Scroll). Absent for class/feat/race spells. */
|
|
289
|
+
entryIndex?: number;
|
|
257
290
|
}
|
|
258
291
|
export interface ResolvedClass {
|
|
259
292
|
entity: ClassEntity | null;
|
|
@@ -290,6 +323,11 @@ export interface ResolvedCharacter {
|
|
|
290
323
|
features: ResolvedFeature[];
|
|
291
324
|
spells: ResolvedSpell[];
|
|
292
325
|
pools: ResolvedPool[];
|
|
326
|
+
/** 2024 Weapon Mastery: bare-normalized slugs of the weapons the character has
|
|
327
|
+
* chosen mastery of, unioned across every class/level `weapon-mastery` pick
|
|
328
|
+
* (e.g. "srd-2024_greatsword" → "greatsword"). Empty when the character has no
|
|
329
|
+
* such pick. Populated by the resolver's collectChosenWeaponMasteries pass. */
|
|
330
|
+
weaponMasteries: string[];
|
|
293
331
|
state: CharacterState;
|
|
294
332
|
}
|
|
295
333
|
export interface ProficiencySet {
|
|
@@ -301,6 +339,16 @@ export interface ACTerm {
|
|
|
301
339
|
amount: number;
|
|
302
340
|
kind: "armor" | "shield" | "item" | "unarmored" | "override" | "dex" | "ability" | "feature";
|
|
303
341
|
}
|
|
342
|
+
/** An additive on-hit damage rider (dice or flat) applied to an attack, from a
|
|
343
|
+
* feature (global — all weapon attacks) or a magic item (that weapon only).
|
|
344
|
+
* `amount` is a dice/flat string ("2d6", "1d8", "2"); `damage_type` optional
|
|
345
|
+
* (a bare manual override string carries its type inline); `source` is a human
|
|
346
|
+
* label for the expand/tooltip. Rendered as its own damage chip on the row. */
|
|
347
|
+
export interface DamageRider {
|
|
348
|
+
amount: string;
|
|
349
|
+
damage_type?: string;
|
|
350
|
+
source?: string;
|
|
351
|
+
}
|
|
304
352
|
export interface AttackRow {
|
|
305
353
|
id: string;
|
|
306
354
|
name: string;
|
|
@@ -308,7 +356,10 @@ export interface AttackRow {
|
|
|
308
356
|
toHit: number;
|
|
309
357
|
damageDice: string;
|
|
310
358
|
damageType: string;
|
|
311
|
-
|
|
359
|
+
/** Accumulated additive on-hit damage riders (feature-global + item-local +
|
|
360
|
+
* the migrated manual override), rendered as extra damage chips. Absent when
|
|
361
|
+
* none apply (so untouched rows are unchanged). */
|
|
362
|
+
damageRiders?: DamageRider[];
|
|
312
363
|
properties: string[];
|
|
313
364
|
proficient: boolean;
|
|
314
365
|
breakdown: {
|
|
@@ -343,6 +394,24 @@ export interface AttackRow {
|
|
|
343
394
|
* untouched attack rows are unchanged. The weapons table renders these joined
|
|
344
395
|
* with " · " as a muted caption under the weapon name. */
|
|
345
396
|
attackNotes?: string[];
|
|
397
|
+
/** 2024 Weapon Mastery for this attack, present only when the character has
|
|
398
|
+
* chosen mastery of this weapon AND is proficient with it AND the weapon
|
|
399
|
+
* declares a mastery property. `slug`/`label`/`description`/`gist` come from
|
|
400
|
+
* the mastery glossary (`gist` = a one-line effect summary for the Actions-tab
|
|
401
|
+
* mastery column, e.g. "on fail: Prone"); `derived` carries the computed
|
|
402
|
+
* number for the two numeric masteries (topple → Save DC, graze → On-miss
|
|
403
|
+
* damage) and is absent for the rest. Additive + display-only, absent
|
|
404
|
+
* (undefined) on untouched rows so nothing else changes. */
|
|
405
|
+
mastery?: {
|
|
406
|
+
slug: string;
|
|
407
|
+
label: string;
|
|
408
|
+
description: string;
|
|
409
|
+
gist?: string;
|
|
410
|
+
derived?: {
|
|
411
|
+
label: string;
|
|
412
|
+
value: number;
|
|
413
|
+
};
|
|
414
|
+
};
|
|
346
415
|
}
|
|
347
416
|
export interface AppliedBonuses {
|
|
348
417
|
ability_bonuses: Partial<Record<Ability, number>>;
|
|
@@ -433,6 +502,16 @@ export interface DerivedStats {
|
|
|
433
502
|
} | null;
|
|
434
503
|
/** One entry per casting class (multiclass-aware). Empty for non-casters. */
|
|
435
504
|
spellcastingClasses: SpellcastingClassInfo[];
|
|
505
|
+
/** Save DC / attack bonus for spells that carry their OWN spellcasting
|
|
506
|
+
* ability rather than a class (feat-granted spells, e.g. Magic Initiate).
|
|
507
|
+
* Keyed by that ability; populated for every ability a feat spell uses.
|
|
508
|
+
* Computed independently of class casters, so a NON-caster (empty
|
|
509
|
+
* spellcastingClasses / null spellcasting) still gets a real DC/attack.
|
|
510
|
+
* Empty object when the character has no such spell. */
|
|
511
|
+
abilitySpellcasting: Partial<Record<Ability, {
|
|
512
|
+
saveDC: number;
|
|
513
|
+
attackBonus: number;
|
|
514
|
+
}>>;
|
|
436
515
|
/** Derived standard slot totals by spell level (before user override). */
|
|
437
516
|
derivedSpellSlots: Record<number, number>;
|
|
438
517
|
/** Warlock Pact Magic, or null. */
|
package/dist/race/race.codec.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
raceCodec
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-ZZGEVJKX.js";
|
|
4
|
+
import "../chunk-PHQRGDRQ.js";
|
|
5
|
+
import "../chunk-2BWAID3Y.js";
|
|
6
|
+
import "../chunk-CZPMTQOY.js";
|
|
7
7
|
import "../chunk-WYGD2UGN.js";
|
|
8
8
|
import "../chunk-PZOWBI3B.js";
|
|
9
|
-
import "../chunk-
|
|
9
|
+
import "../chunk-NXSWH3AE.js";
|
|
10
10
|
import "../chunk-NXR3BD2V.js";
|
|
11
11
|
export {
|
|
12
12
|
raceCodec
|
package/dist/race/race.parser.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseRace
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-PHQRGDRQ.js";
|
|
4
|
+
import "../chunk-2BWAID3Y.js";
|
|
5
|
+
import "../chunk-CZPMTQOY.js";
|
|
6
6
|
import "../chunk-WYGD2UGN.js";
|
|
7
7
|
import "../chunk-PZOWBI3B.js";
|
|
8
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-NXSWH3AE.js";
|
|
9
9
|
import "../chunk-NXR3BD2V.js";
|
|
10
10
|
export {
|
|
11
11
|
parseRace
|
package/dist/race/race.schema.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
raceEntitySchema
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-2BWAID3Y.js";
|
|
4
|
+
import "../chunk-CZPMTQOY.js";
|
|
5
5
|
import "../chunk-WYGD2UGN.js";
|
|
6
6
|
import "../chunk-PZOWBI3B.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-NXSWH3AE.js";
|
|
8
8
|
import "../chunk-NXR3BD2V.js";
|
|
9
9
|
export {
|
|
10
10
|
raceEntitySchema
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
featureSchema
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-CZPMTQOY.js";
|
|
4
4
|
import "../chunk-WYGD2UGN.js";
|
|
5
5
|
import "../chunk-PZOWBI3B.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-NXSWH3AE.js";
|
|
7
7
|
import "../chunk-NXR3BD2V.js";
|
|
8
8
|
export {
|
|
9
9
|
featureSchema
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
subclassCodec
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-XLSVNUTK.js";
|
|
4
|
+
import "../chunk-L7E2UT5A.js";
|
|
5
|
+
import "../chunk-W7HWMMBF.js";
|
|
6
6
|
import "../chunk-4TIJKN6O.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-CZPMTQOY.js";
|
|
8
8
|
import "../chunk-WYGD2UGN.js";
|
|
9
9
|
import "../chunk-PZOWBI3B.js";
|
|
10
|
-
import "../chunk-
|
|
10
|
+
import "../chunk-NXSWH3AE.js";
|
|
11
11
|
import "../chunk-NXR3BD2V.js";
|
|
12
12
|
export {
|
|
13
13
|
subclassCodec
|