@archivist-gg/dnd5e 0.1.0 → 0.1.1
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/{chunk-QQ5W2AZP.js → chunk-ADLTSHOR.js} +63 -12
- package/dist/{chunk-XURS6Q6Q.js → chunk-GEJSJP6E.js} +19 -5
- package/dist/{chunk-537BHVSH.js → chunk-TPZSPU4L.js} +1 -1
- package/dist/{chunk-4C2KOKLF.js → chunk-VLKIB33F.js} +6 -0
- package/dist/{chunk-4SSDYJS2.js → chunk-YDPWD2T7.js} +2 -1
- package/dist/index.js +304 -46
- package/dist/item/item.codec.js +3 -3
- package/dist/item/item.parser.js +2 -2
- package/dist/item/item.schema.d.ts +10 -0
- package/dist/item/item.schema.js +1 -1
- package/dist/item/item.types.d.ts +9 -0
- package/dist/pc/pc.equipment.d.ts +2 -1
- package/dist/pc/pc.equipment.js +5 -1
- package/dist/pc/pc.feature-effects.d.ts +31 -9
- package/dist/pc/pc.feature-effects.js +4 -1
- package/dist/pc/pc.recalc.js +63 -55
- package/dist/pc/pc.resolver.d.ts +12 -0
- package/dist/pc/pc.resolver.js +111 -22
- package/dist/pc/pc.types.d.ts +48 -1
- package/dist/weapon/weapon-mastery.d.ts +9 -0
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { EntityRegistry } from "@archivist-gg/core";
|
|
2
2
|
import type { Ability } from "@archivist-gg/dnd5e";
|
|
3
3
|
import type { AppliedBonuses, DerivedEquipment, ResolvedCharacter, ProficiencySet } from "./pc.types.js";
|
|
4
|
+
import type { WeaponAbilityOverride } from "./pc.feature-effects.js";
|
|
4
5
|
interface ProficienciesForQuery {
|
|
5
6
|
armor: ProficiencySet;
|
|
6
7
|
weapons: ProficiencySet;
|
|
@@ -8,5 +9,5 @@ interface ProficienciesForQuery {
|
|
|
8
9
|
}
|
|
9
10
|
export declare function emptyAppliedBonuses(): AppliedBonuses;
|
|
10
11
|
export declare function computeAppliedBonuses(resolved: ResolvedCharacter, _profs: ProficienciesForQuery, registry: EntityRegistry, warnings: string[]): AppliedBonuses;
|
|
11
|
-
export declare function computeSlotsAndAttacks(resolved: ResolvedCharacter, mods: Record<Ability, number>, profs: ProficienciesForQuery, registry: EntityRegistry, warnings: string[], proficiencyBonus: number,
|
|
12
|
+
export declare function computeSlotsAndAttacks(resolved: ResolvedCharacter, mods: Record<Ability, number>, profs: ProficienciesForQuery, registry: EntityRegistry, warnings: string[], proficiencyBonus: number, weaponAbilities?: WeaponAbilityOverride[], weaponMasteries?: string[]): DerivedEquipment;
|
|
12
13
|
export {};
|
package/dist/pc/pc.equipment.js
CHANGED
|
@@ -2,12 +2,16 @@ import {
|
|
|
2
2
|
computeAppliedBonuses,
|
|
3
3
|
computeSlotsAndAttacks,
|
|
4
4
|
emptyAppliedBonuses
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-ADLTSHOR.js";
|
|
6
6
|
import "../chunk-UC4FE5X3.js";
|
|
7
|
+
import "../chunk-TJCNX6UZ.js";
|
|
8
|
+
import "../chunk-BH6AMZVY.js";
|
|
9
|
+
import "../chunk-JEYQU3JT.js";
|
|
7
10
|
import "../chunk-LWFTHJIE.js";
|
|
8
11
|
import "../chunk-DNAJRMNN.js";
|
|
9
12
|
import "../chunk-DC74TZNA.js";
|
|
10
13
|
import "../chunk-3TRGRSCH.js";
|
|
14
|
+
import "../chunk-WP2CX2JO.js";
|
|
11
15
|
export {
|
|
12
16
|
computeAppliedBonuses,
|
|
13
17
|
computeSlotsAndAttacks,
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import type { SenseType } from "@archivist-gg/dnd5e/types/feature-effect";
|
|
2
2
|
import type { Ability } from "@archivist-gg/dnd5e";
|
|
3
|
-
import type { ResolvedFeature, RollModifierEntry } from "./pc.types.js";
|
|
3
|
+
import type { DamageRider, ResolvedFeature, RollModifierEntry } from "./pc.types.js";
|
|
4
|
+
/**
|
|
5
|
+
* A melee-attack ability override from a `weapon-ability` effect. `weaponSlugs`
|
|
6
|
+
* (bare, namespace-stripped) scopes the override to matching weapon types only;
|
|
7
|
+
* empty/absent `weaponSlugs` = GLOBAL (applies to every melee weapon). The
|
|
8
|
+
* `"spellcasting"` sentinel is NOT stored here — it is resolved against the
|
|
9
|
+
* caster ability in recalc and prepended as a global.
|
|
10
|
+
*/
|
|
11
|
+
export interface WeaponAbilityOverride {
|
|
12
|
+
ability: Ability;
|
|
13
|
+
weaponSlugs?: string[];
|
|
14
|
+
}
|
|
4
15
|
/**
|
|
5
16
|
* Aggregated passive feature effects (effects-application engine).
|
|
6
17
|
* One pure scan over resolved.features[].feature.effects[]. Merge semantics
|
|
7
18
|
* mirror pc.conditions mergePartial: numbers add, each sense range takes max,
|
|
8
|
-
* lists union case-insensitively. apply-condition
|
|
9
|
-
*
|
|
10
|
-
*
|
|
19
|
+
* lists union case-insensitively. apply-condition is action time and
|
|
20
|
+
* intentionally not aggregated; `damage-bonus` folds into damageBonuses (additive
|
|
21
|
+
* on-hit riders); `while`-gated immune-condition entries are skipped entirely
|
|
22
|
+
* (conditional effects are a named deferral).
|
|
11
23
|
*/
|
|
12
24
|
export interface FeatureEffectTotals {
|
|
13
25
|
initiative_bonus: number;
|
|
@@ -46,12 +58,15 @@ export interface FeatureEffectTotals {
|
|
|
46
58
|
weapons: string[];
|
|
47
59
|
};
|
|
48
60
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
61
|
+
* Melee-attack ability overrides (Hexblade "Lies", MCDM Illrigger scoped
|
|
62
|
+
* "Lies", etc.), in fold order. Each carries an optional `weaponSlugs` scope
|
|
63
|
+
* (bare slugs) — absent/empty = GLOBAL (every melee weapon). The
|
|
64
|
+
* `"spellcasting"` sentinel is excluded here (no caster context in the fold);
|
|
65
|
+
* recalc resolves it and prepends the resolved global so it wins over concrete
|
|
66
|
+
* globals. recalc threads this list into attack computation, where a
|
|
67
|
+
* scoped-match wins over a global for the matching weapon. [] = no override.
|
|
53
68
|
*/
|
|
54
|
-
|
|
69
|
+
weaponAbilities: WeaponAbilityOverride[];
|
|
55
70
|
/**
|
|
56
71
|
* Order-preserving list of structured advantage/disadvantage entries from
|
|
57
72
|
* `roll-modifier` effects. Pass-through (no dedupe/merge); each entry is
|
|
@@ -79,6 +94,13 @@ export interface FeatureEffectTotals {
|
|
|
79
94
|
* non-empty, so untouched attack rows keep `attackNotes: undefined`.
|
|
80
95
|
*/
|
|
81
96
|
attackNotes: string[];
|
|
97
|
+
/**
|
|
98
|
+
* Additive on-hit damage riders from `damage-bonus` effects with
|
|
99
|
+
* `applies_to` weapon/all (spell-only ignored — no spell surface). Each is
|
|
100
|
+
* labeled with the owning feature's name. recalc merges these onto every
|
|
101
|
+
* weapon AttackRow's `damageRiders`.
|
|
102
|
+
*/
|
|
103
|
+
damageBonuses: DamageRider[];
|
|
82
104
|
}
|
|
83
105
|
export declare function emptyFeatureEffectTotals(): FeatureEffectTotals;
|
|
84
106
|
/**
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
computeFeatureEffects,
|
|
3
3
|
emptyFeatureEffectTotals
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-GEJSJP6E.js";
|
|
5
|
+
import "../chunk-TJCNX6UZ.js";
|
|
6
|
+
import "../chunk-BH6AMZVY.js";
|
|
7
|
+
import "../chunk-JEYQU3JT.js";
|
|
5
8
|
import "../chunk-WP2CX2JO.js";
|
|
6
9
|
export {
|
|
7
10
|
computeFeatureEffects,
|
package/dist/pc/pc.recalc.js
CHANGED
|
@@ -7,21 +7,21 @@ import "../chunk-5XUXXAQJ.js";
|
|
|
7
7
|
import {
|
|
8
8
|
computeConditionEffects
|
|
9
9
|
} from "../chunk-R2ZV233X.js";
|
|
10
|
-
import {
|
|
11
|
-
collectChosenAbilityPoints,
|
|
12
|
-
collectChosenProficiencies
|
|
13
|
-
} from "../chunk-TJCNX6UZ.js";
|
|
14
|
-
import "../chunk-BH6AMZVY.js";
|
|
15
|
-
import "../chunk-JEYQU3JT.js";
|
|
16
10
|
import {
|
|
17
11
|
computeAppliedBonuses,
|
|
18
12
|
computeSlotsAndAttacks,
|
|
19
13
|
emptyAppliedBonuses
|
|
20
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-ADLTSHOR.js";
|
|
21
15
|
import "../chunk-UC4FE5X3.js";
|
|
22
16
|
import {
|
|
23
17
|
computeFeatureEffects
|
|
24
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-GEJSJP6E.js";
|
|
19
|
+
import {
|
|
20
|
+
collectChosenAbilityPoints,
|
|
21
|
+
collectChosenProficiencies
|
|
22
|
+
} from "../chunk-TJCNX6UZ.js";
|
|
23
|
+
import "../chunk-BH6AMZVY.js";
|
|
24
|
+
import "../chunk-JEYQU3JT.js";
|
|
25
25
|
import "../chunk-LWFTHJIE.js";
|
|
26
26
|
import "../chunk-DNAJRMNN.js";
|
|
27
27
|
import "../chunk-DC74TZNA.js";
|
|
@@ -362,24 +362,26 @@ function computeProficiencies(resolved) {
|
|
|
362
362
|
};
|
|
363
363
|
}
|
|
364
364
|
function recalc(resolved, registry) {
|
|
365
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W
|
|
365
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W;
|
|
366
366
|
const warnings = [];
|
|
367
367
|
const overrides = (_a = resolved.definition.overrides) != null ? _a : {};
|
|
368
368
|
const chosenProfs = collectChosenProficiencies(resolved);
|
|
369
369
|
const activeBuffs = new Set((_b = resolved.state.active_buffs) != null ? _b : []);
|
|
370
370
|
const buffFeatures = [];
|
|
371
|
+
const pushBoon = (item, pool) => {
|
|
372
|
+
var _a2, _b2, _c2, _d2, _e2, _f2;
|
|
373
|
+
const e = item.entity;
|
|
374
|
+
if (!e || ((_b2 = (_a2 = e.effects) == null ? void 0 : _a2.length) != null ? _b2 : 0) === 0) return;
|
|
375
|
+
buffFeatures.push({
|
|
376
|
+
feature: { id: item.slug, name: e.name, activatable: (_c2 = e.activatable) != null ? _c2 : false, effects: e.effects },
|
|
377
|
+
// source is inert for the fold; attribute to the pool's owning class
|
|
378
|
+
// (never a hardcoded class) so the generic engine carries no homebrew name.
|
|
379
|
+
source: { kind: "class", slug: (_f2 = (_e2 = (_d2 = resolved.classes[pool.classIndex]) == null ? void 0 : _d2.entity) == null ? void 0 : _e2.slug) != null ? _f2 : pool.id, level: pool.anchorLevel }
|
|
380
|
+
});
|
|
381
|
+
};
|
|
371
382
|
for (const pool of (_c = resolved.pools) != null ? _c : []) {
|
|
372
|
-
for (const sel of (_d = pool.selected) != null ? _d : [])
|
|
373
|
-
|
|
374
|
-
if ((e == null ? void 0 : e.activatable) && ((_f = (_e = e.effects) == null ? void 0 : _e.length) != null ? _f : 0) > 0) {
|
|
375
|
-
buffFeatures.push({
|
|
376
|
-
feature: { id: sel.slug, name: e.name, activatable: true, effects: e.effects },
|
|
377
|
-
// source is inert for the fold; attribute to the pool's owning class
|
|
378
|
-
// (never a hardcoded class) so the generic engine carries no homebrew name.
|
|
379
|
-
source: { kind: "class", slug: (_i = (_h = (_g = resolved.classes[pool.classIndex]) == null ? void 0 : _g.entity) == null ? void 0 : _h.slug) != null ? _i : pool.id, level: pool.anchorLevel }
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
+
for (const sel of (_d = pool.selected) != null ? _d : []) pushBoon(sel, pool);
|
|
384
|
+
for (const g of (_e = pool.grants) != null ? _e : []) pushBoon(g, pool);
|
|
383
385
|
}
|
|
384
386
|
const featureEffects = computeFeatureEffects([...resolved.features, ...buffFeatures], { activeBuffs });
|
|
385
387
|
const profsForApply = computeProficiencies(resolved);
|
|
@@ -443,19 +445,20 @@ function recalc(resolved, registry) {
|
|
|
443
445
|
resolved.definition.edition,
|
|
444
446
|
scores
|
|
445
447
|
);
|
|
446
|
-
const firstClass = (
|
|
447
|
-
const saveProfs = new Set((
|
|
448
|
+
const firstClass = (_g = (_f = resolved.classes[0]) == null ? void 0 : _f.entity) != null ? _g : null;
|
|
449
|
+
const saveProfs = new Set((_h = firstClass == null ? void 0 : firstClass.saving_throws) != null ? _h : []);
|
|
448
450
|
for (const ab of featureEffects.proficiencies.saves) saveProfs.add(ab);
|
|
449
451
|
const saves = {};
|
|
450
452
|
for (const ab of ABILITY_KEYS) {
|
|
451
|
-
const override = (
|
|
452
|
-
const prof = (
|
|
453
|
+
const override = (_i = overrides.saves) == null ? void 0 : _i[ab];
|
|
454
|
+
const prof = (_j = override == null ? void 0 : override.proficient) != null ? _j : saveProfs.has(ab);
|
|
453
455
|
const derivedBonus = savingThrow(scores[ab], prof, proficiencyBonus) + applied.save_bonus + conditionEffects.d20_test_penalty;
|
|
454
|
-
const bonus = (
|
|
456
|
+
const bonus = (_k = override == null ? void 0 : override.bonus) != null ? _k : derivedBonus;
|
|
455
457
|
saves[ab] = { bonus, proficient: prof };
|
|
456
458
|
}
|
|
457
459
|
const profSet = /* @__PURE__ */ new Set([
|
|
458
460
|
...resolved.definition.skills.proficient,
|
|
461
|
+
...(_m = (_l = resolved.background) == null ? void 0 : _l.skill_proficiencies) != null ? _m : [],
|
|
459
462
|
...chosenProfs.skills,
|
|
460
463
|
...featureEffects.proficiencies.skills
|
|
461
464
|
]);
|
|
@@ -464,9 +467,9 @@ function recalc(resolved, registry) {
|
|
|
464
467
|
for (const skill of ALL_SKILLS) {
|
|
465
468
|
const skillKey = skillSlugFromDisplay(skill);
|
|
466
469
|
const ab = SKILL_ABILITY[skillSlugToAbilityLookup(skillKey)];
|
|
467
|
-
const override = (
|
|
468
|
-
const tri = (
|
|
469
|
-
const bonus = (
|
|
470
|
+
const override = (_n = overrides.skills) == null ? void 0 : _n[skillKey];
|
|
471
|
+
const tri = (_o = override == null ? void 0 : override.proficiency) != null ? _o : expSet.has(skillKey) ? "expertise" : profSet.has(skillKey) ? "proficient" : "none";
|
|
472
|
+
const bonus = (_p = override == null ? void 0 : override.bonus) != null ? _p : skillBonus(scores[ab], tri, proficiencyBonus) + conditionEffects.d20_test_penalty;
|
|
470
473
|
skills[skillKey] = {
|
|
471
474
|
bonus,
|
|
472
475
|
proficiency: tri,
|
|
@@ -477,16 +480,16 @@ function recalc(resolved, registry) {
|
|
|
477
480
|
const investigationTri = skills.investigation.proficiency;
|
|
478
481
|
const insightTri = skills.insight.proficiency;
|
|
479
482
|
const passives = {
|
|
480
|
-
perception: (
|
|
481
|
-
investigation: (
|
|
482
|
-
insight: (
|
|
483
|
+
perception: (_r = (_q = overrides.passives) == null ? void 0 : _q.perception) != null ? _r : passivePerception(scores.wis, perceptionTri, proficiencyBonus),
|
|
484
|
+
investigation: (_t = (_s = overrides.passives) == null ? void 0 : _s.investigation) != null ? _t : passive(scores.int, investigationTri, proficiencyBonus),
|
|
485
|
+
insight: (_v = (_u = overrides.passives) == null ? void 0 : _u.insight) != null ? _v : passive(scores.wis, insightTri, proficiencyBonus)
|
|
483
486
|
};
|
|
484
487
|
const hpMaxDerived = multiclassMaxHP(resolved.classes, mods.con) + featureEffects.hp_per_level_bonus * totalLevel;
|
|
485
488
|
const hpMaxAfterConditions = Math.floor(hpMaxDerived * conditionEffects.hp_max_multiplier);
|
|
486
|
-
const hpMax = (
|
|
489
|
+
const hpMax = (_x = (_w = overrides.hp) == null ? void 0 : _w.max) != null ? _x : hpMaxAfterConditions;
|
|
487
490
|
const featureAcTermsFor = (hasArmor) => featureEffects.ac_terms.filter((t) => !t.requires_armor || hasArmor).map((t) => ({ source: t.label, amount: t.value, kind: "feature" }));
|
|
488
491
|
const sumTerms = (terms) => terms.reduce((s, t) => s + t.amount, 0);
|
|
489
|
-
|
|
492
|
+
const weaponAbilities = [...featureEffects.weaponAbilities];
|
|
490
493
|
const wantsSpellcasting = resolved.features.some((rf) => {
|
|
491
494
|
var _a2;
|
|
492
495
|
return ((_a2 = rf.feature.effects) != null ? _a2 : []).some((e) => e.kind === "weapon-ability" && e.ability === "spellcasting");
|
|
@@ -501,14 +504,14 @@ function recalc(resolved, registry) {
|
|
|
501
504
|
break;
|
|
502
505
|
}
|
|
503
506
|
}
|
|
504
|
-
if (primaryCasterAbility)
|
|
507
|
+
if (primaryCasterAbility) weaponAbilities.unshift({ ability: primaryCasterAbility });
|
|
505
508
|
}
|
|
506
509
|
let derivedEquipment = null;
|
|
507
510
|
let acDerived;
|
|
508
511
|
let acBreakdownDerived = [];
|
|
509
512
|
let acInformationalDerived = [];
|
|
510
513
|
if (registry) {
|
|
511
|
-
derivedEquipment = computeSlotsAndAttacks(resolved, mods, profsForApply, registry, warnings, proficiencyBonus,
|
|
514
|
+
derivedEquipment = computeSlotsAndAttacks(resolved, mods, profsForApply, registry, warnings, proficiencyBonus, weaponAbilities, (_y = resolved.weaponMasteries) != null ? _y : []);
|
|
512
515
|
if (derivedEquipment.equippedSlots.armor) {
|
|
513
516
|
const featTerms = featureAcTermsFor(true);
|
|
514
517
|
acDerived = derivedEquipment.ac + sumTerms(featTerms);
|
|
@@ -531,16 +534,16 @@ function recalc(resolved, registry) {
|
|
|
531
534
|
acDerived = total + sumTerms(featTerms);
|
|
532
535
|
acBreakdownDerived = [...terms, ...featTerms];
|
|
533
536
|
}
|
|
534
|
-
const ac = (
|
|
537
|
+
const ac = (_z = overrides.ac) != null ? _z : acDerived;
|
|
535
538
|
const additiveSpeed = speedFromRace(resolved) + applied.speed_bonuses.walk + featureEffects.speed_walk_bonus;
|
|
536
539
|
const baseSpeed = Math.max(featureEffects.speed_walk_set, additiveSpeed);
|
|
537
540
|
const adjustedSpeed = baseSpeed * conditionEffects.speed_multiplier - conditionEffects.speed_reduction_ft;
|
|
538
541
|
const conditionSpeed = conditionEffects.speed_floor_zero ? 0 : Math.max(0, Math.floor(adjustedSpeed));
|
|
539
|
-
const speed = (
|
|
542
|
+
const speed = (_A = overrides.speed) != null ? _A : conditionSpeed;
|
|
540
543
|
if (!resolved.race) warnings.push("No race resolved; speed defaulted to 30.");
|
|
541
|
-
const init = (
|
|
544
|
+
const init = (_B = overrides.initiative) != null ? _B : initiativeBonus(mods.dex, resolved.feats, resolved.definition.edition) + featureEffects.initiative_bonus;
|
|
542
545
|
const senses = {
|
|
543
|
-
darkvision: Math.max((
|
|
546
|
+
darkvision: Math.max((_E = (_D = (_C = resolved.race) == null ? void 0 : _C.vision) == null ? void 0 : _D.darkvision) != null ? _E : 0, featureEffects.senses.darkvision, applied.senses.darkvision),
|
|
544
547
|
blindsight: Math.max(featureEffects.senses.blindsight, applied.senses.blindsight),
|
|
545
548
|
tremorsense: Math.max(featureEffects.senses.tremorsense, applied.senses.tremorsense),
|
|
546
549
|
truesight: Math.max(featureEffects.senses.truesight, applied.senses.truesight)
|
|
@@ -558,8 +561,8 @@ function recalc(resolved, registry) {
|
|
|
558
561
|
classSlug: c.entity.slug,
|
|
559
562
|
className: c.entity.name,
|
|
560
563
|
ability: profile.ability,
|
|
561
|
-
saveDC: (
|
|
562
|
-
attackBonus: (
|
|
564
|
+
saveDC: (_G = (_F = overrides.spellcasting) == null ? void 0 : _F.saveDC) != null ? _G : dc,
|
|
565
|
+
attackBonus: (_I = (_H = overrides.spellcasting) == null ? void 0 : _H.attackBonus) != null ? _I : atk,
|
|
563
566
|
casterType: profile.casterType,
|
|
564
567
|
preparation: profile.preparation
|
|
565
568
|
});
|
|
@@ -575,20 +578,20 @@ function recalc(resolved, registry) {
|
|
|
575
578
|
const spellLimits = computeSpellLimits(limitInputs);
|
|
576
579
|
const defenses = {
|
|
577
580
|
resistances: dedupeDefenseList([
|
|
578
|
-
(
|
|
581
|
+
(_K = (_J = resolved.definition.defenses) == null ? void 0 : _J.resistances) != null ? _K : [],
|
|
579
582
|
applied.defenses.resistances,
|
|
580
583
|
featureEffects.resistances
|
|
581
584
|
]),
|
|
582
585
|
immunities: dedupeDefenseList([
|
|
583
|
-
(
|
|
586
|
+
(_M = (_L = resolved.definition.defenses) == null ? void 0 : _L.immunities) != null ? _M : [],
|
|
584
587
|
applied.defenses.immunities
|
|
585
588
|
]),
|
|
586
589
|
vulnerabilities: dedupeDefenseList([
|
|
587
|
-
(
|
|
590
|
+
(_O = (_N = resolved.definition.defenses) == null ? void 0 : _N.vulnerabilities) != null ? _O : [],
|
|
588
591
|
applied.defenses.vulnerabilities
|
|
589
592
|
]),
|
|
590
593
|
condition_immunities: dedupeDefenseList([
|
|
591
|
-
(
|
|
594
|
+
(_Q = (_P = resolved.definition.defenses) == null ? void 0 : _P.condition_immunities) != null ? _Q : [],
|
|
592
595
|
applied.defenses.condition_immunities,
|
|
593
596
|
featureEffects.condition_immunities
|
|
594
597
|
])
|
|
@@ -630,16 +633,21 @@ function recalc(resolved, registry) {
|
|
|
630
633
|
// untouched rows keep `critRange`/`attackNotes` ABSENT (not 20 / not []).
|
|
631
634
|
// crit-range: folded weapon crit threshold, only when an effect lowered it.
|
|
632
635
|
// attackNotes: reroll-damage / attack-rule captions, only when non-empty.
|
|
633
|
-
attacks: ((
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
636
|
+
attacks: ((_R = derivedEquipment == null ? void 0 : derivedEquipment.attacks) != null ? _R : []).map((a) => {
|
|
637
|
+
var _a2;
|
|
638
|
+
const riders = [...(_a2 = a.damageRiders) != null ? _a2 : [], ...featureEffects.damageBonuses];
|
|
639
|
+
return {
|
|
640
|
+
...a,
|
|
641
|
+
toHit: a.toHit + conditionEffects.d20_test_penalty,
|
|
642
|
+
...featureEffects.critRange < 20 ? { critRange: featureEffects.critRange } : {},
|
|
643
|
+
...featureEffects.attackNotes.length ? { attackNotes: featureEffects.attackNotes } : {},
|
|
644
|
+
...riders.length ? { damageRiders: riders } : {}
|
|
645
|
+
};
|
|
646
|
+
}),
|
|
647
|
+
equippedSlots: (_S = derivedEquipment == null ? void 0 : derivedEquipment.equippedSlots) != null ? _S : {},
|
|
648
|
+
carriedWeight: (_T = derivedEquipment == null ? void 0 : derivedEquipment.carriedWeight) != null ? _T : 0,
|
|
649
|
+
attunementUsed: (_U = derivedEquipment == null ? void 0 : derivedEquipment.attunementUsed) != null ? _U : 0,
|
|
650
|
+
attunementLimit: (_W = derivedEquipment == null ? void 0 : derivedEquipment.attunementLimit) != null ? _W : (_V = overrides.attunement_limit) != null ? _V : 3,
|
|
643
651
|
conditionEffects,
|
|
644
652
|
rollModifiers: featureEffects.rollModifiers
|
|
645
653
|
};
|
package/dist/pc/pc.resolver.d.ts
CHANGED
|
@@ -15,6 +15,18 @@ export declare class PCResolver {
|
|
|
15
15
|
get registry(): EntityRegistry;
|
|
16
16
|
resolve(character: Character): ResolveResult;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* 2024 Weapon Mastery: unions the persisted `weapon-mastery` picks across every
|
|
20
|
+
* class/level `ClassEntry.choices[level]["weapon-mastery"]`. Returns both the
|
|
21
|
+
* ORIGINAL full picked slugs (`full` — used for exact-match display-name lookup)
|
|
22
|
+
* and their BARE-normalized form (`bare` — e.g. "srd-2024_greatsword" →
|
|
23
|
+
* "greatsword"), each de-duplicated in first-seen order. Bare-authored picks
|
|
24
|
+
* normalize to themselves. Non-string / non-array picks are ignored.
|
|
25
|
+
*/
|
|
26
|
+
export declare function collectChosenWeaponMasteries(character: Character): {
|
|
27
|
+
bare: string[];
|
|
28
|
+
full: string[];
|
|
29
|
+
};
|
|
18
30
|
/**
|
|
19
31
|
* Walks class.choices for `feat` entries and adds background feat slugs.
|
|
20
32
|
* Returns bare slugs (no `[[ ]]`).
|
package/dist/pc/pc.resolver.js
CHANGED
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
resolveSpellcasting
|
|
7
7
|
} from "../chunk-IXZXFFFW.js";
|
|
8
8
|
import "../chunk-5XUXXAQJ.js";
|
|
9
|
-
import
|
|
9
|
+
import {
|
|
10
|
+
bareEntitySlug
|
|
11
|
+
} from "../chunk-TJCNX6UZ.js";
|
|
10
12
|
import "../chunk-BH6AMZVY.js";
|
|
11
13
|
import "../chunk-JEYQU3JT.js";
|
|
12
14
|
import "../chunk-J2RYSZMQ.js";
|
|
@@ -75,6 +77,27 @@ var PCResolver = class {
|
|
|
75
77
|
const prep = isCantrip || n.alwaysPrepared ? true : (_e = n.preparedFlag) != null ? _e : false;
|
|
76
78
|
spells.push({ entity, slug: n.slug, classSlug, source: n.source, prepared: prep, alwaysPrepared: n.alwaysPrepared });
|
|
77
79
|
}
|
|
80
|
+
const chosenMasteries = collectChosenWeaponMasteries(character);
|
|
81
|
+
if (chosenMasteries.full.length > 0) {
|
|
82
|
+
const target = features.find(
|
|
83
|
+
(rf) => {
|
|
84
|
+
var _a2;
|
|
85
|
+
return rf.feature.id === "weapon-mastery" || ((_a2 = rf.feature.choices) == null ? void 0 : _a2.some(
|
|
86
|
+
(ch) => ch.kind === "select-entity" && ch.id === "weapon-mastery" && ch.entity_type === "weapon"
|
|
87
|
+
));
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
if (target) {
|
|
91
|
+
const names = chosenMasteries.full.map(
|
|
92
|
+
(fullSlug) => {
|
|
93
|
+
var _a2, _b2;
|
|
94
|
+
return (_b2 = (_a2 = this.entities.getByTypeAndSlug("weapon", fullSlug)) == null ? void 0 : _a2.name) != null ? _b2 : fullSlug;
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
const entry = { label: "Mastered weapons", description: names.join(", ") };
|
|
98
|
+
target.chosenInline = target.chosenInline ? [...target.chosenInline, entry] : [entry];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
78
101
|
const resolvedCharacter = {
|
|
79
102
|
definition: character,
|
|
80
103
|
race,
|
|
@@ -85,12 +108,26 @@ var PCResolver = class {
|
|
|
85
108
|
features,
|
|
86
109
|
spells,
|
|
87
110
|
pools: [],
|
|
111
|
+
weaponMasteries: chosenMasteries.bare,
|
|
88
112
|
state: character.state
|
|
89
113
|
};
|
|
90
114
|
resolvedCharacter.pools = resolveAllPools(resolvedCharacter, this.entities);
|
|
91
115
|
return { character: resolvedCharacter, warnings };
|
|
92
116
|
}
|
|
93
117
|
};
|
|
118
|
+
function collectChosenWeaponMasteries(character) {
|
|
119
|
+
var _a, _b;
|
|
120
|
+
const full = /* @__PURE__ */ new Set();
|
|
121
|
+
for (const cls of (_a = character.class) != null ? _a : []) {
|
|
122
|
+
for (const lvl of Object.values((_b = cls.choices) != null ? _b : {})) {
|
|
123
|
+
const pick = lvl["weapon-mastery"];
|
|
124
|
+
if (Array.isArray(pick)) {
|
|
125
|
+
for (const s of pick) if (typeof s === "string") full.add(s);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { full: [...full], bare: [...new Set([...full].map(bareEntitySlug))] };
|
|
130
|
+
}
|
|
94
131
|
function collectFeatSlugs(character) {
|
|
95
132
|
const slugs = /* @__PURE__ */ new Set();
|
|
96
133
|
for (const c of character.class) {
|
|
@@ -119,8 +156,12 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
|
|
|
119
156
|
const features = (_b2 = ((_a2 = entity.features_by_level) != null ? _a2 : {})[lvl]) != null ? _b2 : [];
|
|
120
157
|
const subFeatures = c.subclass ? (_d = ((_c = c.subclass.features_by_level) != null ? _c : {})[lvl]) != null ? _d : [] : [];
|
|
121
158
|
for (const feature of [...features, ...subFeatures]) {
|
|
122
|
-
walkChoiceGrants(feature.choices, atLevel, (granted) => {
|
|
123
|
-
out.push({
|
|
159
|
+
walkChoiceGrants(feature.choices, atLevel, (granted, suppress) => {
|
|
160
|
+
out.push({
|
|
161
|
+
feature: granted,
|
|
162
|
+
source: { kind: "class", slug: entity.slug, level: lvl },
|
|
163
|
+
...suppress ? { renderSuppressed: true } : {}
|
|
164
|
+
});
|
|
124
165
|
}, registry);
|
|
125
166
|
}
|
|
126
167
|
}
|
|
@@ -135,8 +176,12 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
|
|
|
135
176
|
};
|
|
136
177
|
if (race) {
|
|
137
178
|
const atRace = originAt("race");
|
|
138
|
-
const emitRace = (granted) => {
|
|
139
|
-
out.push({
|
|
179
|
+
const emitRace = (granted, suppress) => {
|
|
180
|
+
out.push({
|
|
181
|
+
feature: granted,
|
|
182
|
+
source: { kind: "race", slug: race.slug },
|
|
183
|
+
...suppress ? { renderSuppressed: true } : {}
|
|
184
|
+
});
|
|
140
185
|
};
|
|
141
186
|
walkChoiceGrants(race.choices, atRace, emitRace, registry);
|
|
142
187
|
for (const trait of (_b = race.traits) != null ? _b : []) {
|
|
@@ -145,8 +190,12 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
|
|
|
145
190
|
}
|
|
146
191
|
if (background) {
|
|
147
192
|
const atBg = originAt("background");
|
|
148
|
-
const emitBg = (granted) => {
|
|
149
|
-
out.push({
|
|
193
|
+
const emitBg = (granted, suppress) => {
|
|
194
|
+
out.push({
|
|
195
|
+
feature: granted,
|
|
196
|
+
source: { kind: "background", slug: background.slug },
|
|
197
|
+
...suppress ? { renderSuppressed: true } : {}
|
|
198
|
+
});
|
|
150
199
|
};
|
|
151
200
|
walkChoiceGrants(background.choices, atBg, emitBg, registry);
|
|
152
201
|
if (background.feature) {
|
|
@@ -156,7 +205,7 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
|
|
|
156
205
|
return out;
|
|
157
206
|
}
|
|
158
207
|
function walkChoiceGrants(choices, atLevel, emit, registry) {
|
|
159
|
-
var _a, _b, _c, _d;
|
|
208
|
+
var _a, _b, _c, _d, _e;
|
|
160
209
|
for (const ch of choices != null ? choices : []) {
|
|
161
210
|
const sel = atLevel[ch.id];
|
|
162
211
|
if (ch.kind === "select-entity" && ch.entity_type === "optional-feature") {
|
|
@@ -177,14 +226,39 @@ function walkChoiceGrants(choices, atLevel, emit, registry) {
|
|
|
177
226
|
if (ch.kind === "select-inline") {
|
|
178
227
|
const branch = typeof sel === "string" ? ch.options.find((o) => o.value === sel) : void 0;
|
|
179
228
|
if ((_d = branch == null ? void 0 : branch.effects) == null ? void 0 : _d.length) {
|
|
180
|
-
|
|
229
|
+
const wc = (_e = branch.choices) == null ? void 0 : _e.find(
|
|
230
|
+
(c) => c.kind === "select-entity" && c.entity_type === "weapon"
|
|
231
|
+
);
|
|
232
|
+
const picked = wc ? atLevel == null ? void 0 : atLevel[wc.id] : void 0;
|
|
233
|
+
const effects = branch.effects.map(
|
|
234
|
+
(e) => e.kind === "weapon-ability" && e.weapons === "chosen" && typeof picked === "string" ? { ...e, weapons: [bareEntitySlug(picked)] } : e
|
|
235
|
+
);
|
|
236
|
+
emit({ id: `${ch.id}-${branch.value}`, name: branch.label, description: branch.description, effects }, true);
|
|
181
237
|
}
|
|
182
238
|
if (branch == null ? void 0 : branch.choices) walkChoiceGrants(branch.choices, atLevel, emit, registry);
|
|
183
239
|
}
|
|
184
240
|
}
|
|
185
241
|
}
|
|
242
|
+
function resolveChosenInline(feature, atLevel) {
|
|
243
|
+
var _a;
|
|
244
|
+
if (!atLevel) return void 0;
|
|
245
|
+
const picks = [];
|
|
246
|
+
for (const ch of (_a = feature.choices) != null ? _a : []) {
|
|
247
|
+
if (ch.kind !== "select-inline") continue;
|
|
248
|
+
const sel = atLevel[ch.id];
|
|
249
|
+
if (typeof sel !== "string") continue;
|
|
250
|
+
const opt = ch.options.find((o) => o.value === sel);
|
|
251
|
+
if (!opt) continue;
|
|
252
|
+
picks.push({ label: opt.label, description: opt.description });
|
|
253
|
+
}
|
|
254
|
+
return picks.length ? picks : void 0;
|
|
255
|
+
}
|
|
256
|
+
function isAsiSlotFeature(feature) {
|
|
257
|
+
var _a, _b, _c, _d;
|
|
258
|
+
return feature.id === "ability-score-improvement" && !((_a = feature.effects) == null ? void 0 : _a.length) && !((_b = feature.resources) == null ? void 0 : _b.length) && !feature.action && !((_c = feature.sub_features) == null ? void 0 : _c.length) && !((_d = feature.attacks) == null ? void 0 : _d.length);
|
|
259
|
+
}
|
|
186
260
|
function collectResolvedFeatures(race, classes, background, feats) {
|
|
187
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
261
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
|
|
188
262
|
const out = [];
|
|
189
263
|
for (const c of classes) {
|
|
190
264
|
if (!c.entity) continue;
|
|
@@ -194,10 +268,16 @@ function collectResolvedFeatures(race, classes, background, feats) {
|
|
|
194
268
|
const lvl = parseInt(lvlStr, 10);
|
|
195
269
|
if (Number.isNaN(lvl) || lvl > c.level) continue;
|
|
196
270
|
for (const feat of feats0) {
|
|
197
|
-
|
|
271
|
+
const chosenInline = resolveChosenInline(feat, (_b = c.choices) == null ? void 0 : _b[lvl]);
|
|
272
|
+
out.push({
|
|
273
|
+
feature: feat,
|
|
274
|
+
source: { kind: "class", slug, level: lvl },
|
|
275
|
+
...chosenInline ? { chosenInline } : {},
|
|
276
|
+
...isAsiSlotFeature(feat) ? { buildOnly: true } : {}
|
|
277
|
+
});
|
|
198
278
|
}
|
|
199
279
|
}
|
|
200
|
-
if ((
|
|
280
|
+
if ((_c = c.entity.resources) == null ? void 0 : _c.length) {
|
|
201
281
|
out.push({
|
|
202
282
|
feature: { name: c.entity.name, resources: c.entity.resources },
|
|
203
283
|
source: { kind: "class", slug, level: 1 }
|
|
@@ -205,15 +285,21 @@ function collectResolvedFeatures(race, classes, background, feats) {
|
|
|
205
285
|
}
|
|
206
286
|
if (c.subclass) {
|
|
207
287
|
const sSlug = c.subclass.slug;
|
|
208
|
-
const sByLevel = (
|
|
288
|
+
const sByLevel = (_d = c.subclass.features_by_level) != null ? _d : {};
|
|
209
289
|
for (const [lvlStr, feats0] of Object.entries(sByLevel)) {
|
|
210
290
|
const lvl = parseInt(lvlStr, 10);
|
|
211
291
|
if (Number.isNaN(lvl) || lvl > c.level) continue;
|
|
212
292
|
for (const feat of feats0) {
|
|
213
|
-
|
|
293
|
+
const chosenInline = resolveChosenInline(feat, (_e = c.choices) == null ? void 0 : _e[lvl]);
|
|
294
|
+
out.push({
|
|
295
|
+
feature: feat,
|
|
296
|
+
source: { kind: "subclass", slug: sSlug, level: lvl },
|
|
297
|
+
...chosenInline ? { chosenInline } : {},
|
|
298
|
+
...isAsiSlotFeature(feat) ? { buildOnly: true } : {}
|
|
299
|
+
});
|
|
214
300
|
}
|
|
215
301
|
}
|
|
216
|
-
if ((
|
|
302
|
+
if ((_f = c.subclass.resources) == null ? void 0 : _f.length) {
|
|
217
303
|
out.push({
|
|
218
304
|
feature: { name: c.subclass.name, resources: c.subclass.resources },
|
|
219
305
|
source: { kind: "subclass", slug: sSlug, level: 1 }
|
|
@@ -222,7 +308,7 @@ function collectResolvedFeatures(race, classes, background, feats) {
|
|
|
222
308
|
}
|
|
223
309
|
}
|
|
224
310
|
if (race) {
|
|
225
|
-
const traits = (
|
|
311
|
+
const traits = (_g = race.traits) != null ? _g : [];
|
|
226
312
|
for (const feat of traits) {
|
|
227
313
|
out.push({ feature: feat, source: { kind: "race", slug: race.slug } });
|
|
228
314
|
}
|
|
@@ -234,8 +320,8 @@ function collectResolvedFeatures(race, classes, background, feats) {
|
|
|
234
320
|
}
|
|
235
321
|
}
|
|
236
322
|
for (const feat of feats) {
|
|
237
|
-
const bundled = (
|
|
238
|
-
const entityEffects = (
|
|
323
|
+
const bundled = (_h = feat.features) != null ? _h : [];
|
|
324
|
+
const entityEffects = (_i = feat.effects) != null ? _i : [];
|
|
239
325
|
if (bundled.length > 0) {
|
|
240
326
|
bundled.forEach((f, i) => {
|
|
241
327
|
var _a2;
|
|
@@ -243,16 +329,18 @@ function collectResolvedFeatures(race, classes, background, feats) {
|
|
|
243
329
|
out.push({ feature, source: { kind: "feat", slug: feat.slug } });
|
|
244
330
|
});
|
|
245
331
|
} else {
|
|
246
|
-
const name = (
|
|
247
|
-
const
|
|
332
|
+
const name = (_j = feat.name) != null ? _j : feat.slug;
|
|
333
|
+
const foldedDesc = [feat.description, ...(_k = feat.benefits) != null ? _k : []].filter(Boolean).join("\n\n") || void 0;
|
|
334
|
+
const buildOnly = ((_l = feat.choices) != null ? _l : []).some((c) => c.kind === "ability-points" && c.points >= 2) && !((_m = feat.effects) == null ? void 0 : _m.length) && !((_n = feat.resources) == null ? void 0 : _n.length) && bundled.length === 0;
|
|
248
335
|
out.push({
|
|
249
336
|
feature: {
|
|
250
337
|
name,
|
|
251
|
-
description,
|
|
338
|
+
...foldedDesc ? { description: foldedDesc } : {},
|
|
252
339
|
...feat.resources ? { resources: feat.resources } : {},
|
|
253
340
|
...entityEffects.length > 0 ? { effects: entityEffects } : {}
|
|
254
341
|
},
|
|
255
|
-
source: { kind: "feat", slug: feat.slug }
|
|
342
|
+
source: { kind: "feat", slug: feat.slug },
|
|
343
|
+
...buildOnly ? { buildOnly: true } : {}
|
|
256
344
|
});
|
|
257
345
|
}
|
|
258
346
|
}
|
|
@@ -261,6 +349,7 @@ function collectResolvedFeatures(race, classes, background, feats) {
|
|
|
261
349
|
export {
|
|
262
350
|
PCResolver,
|
|
263
351
|
collectChosenGrantedFeatures,
|
|
352
|
+
collectChosenWeaponMasteries,
|
|
264
353
|
collectFeatSlugs,
|
|
265
354
|
collectResolvedFeatures,
|
|
266
355
|
stripSlug
|