@archivist-gg/dnd5e 0.3.13 → 0.3.19
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 +5 -5
- package/dist/background/background.parser.js +4 -4
- package/dist/background/background.schema.js +3 -3
- package/dist/chunk-3LWMFYKS.js +57 -0
- package/dist/{chunk-4SLOJXJ4.js → chunk-3SFGWFVQ.js} +1 -1
- package/dist/{chunk-Q6UOHCCT.js → chunk-3SIUAFDQ.js} +1 -1
- package/dist/chunk-4J6DS3EJ.js +30 -0
- package/dist/{chunk-RLGQ5PZZ.js → chunk-5JXFJ5AU.js} +11 -1
- package/dist/{chunk-4BZ47YJ4.js → chunk-64VNLMSG.js} +59 -16
- package/dist/{chunk-XY6X2APM.js → chunk-7Q2VPKCD.js} +17 -0
- package/dist/{chunk-Q5XTEYHD.js → chunk-7XJHI5H5.js} +16 -9
- package/dist/{chunk-FPGXFD2M.js → chunk-B2QHP6UF.js} +2 -2
- package/dist/{chunk-HJQNK3J5.js → chunk-B3DP42OM.js} +1 -4
- package/dist/{chunk-MC5YJ4PB.js → chunk-BJGOSM76.js} +1 -1
- package/dist/{chunk-VDPRBUU5.js → chunk-BJWSOQUC.js} +1 -1
- package/dist/{chunk-QEO7LYTG.js → chunk-EJD47JHL.js} +181 -93
- package/dist/{chunk-X7VZY4ON.js → chunk-FJYBLXV7.js} +2 -2
- package/dist/{chunk-QSBJ6NEM.js → chunk-FMUD3LDV.js} +1 -1
- package/dist/{chunk-LVX6TQ3R.js → chunk-FMYWORTP.js} +2 -2
- package/dist/{chunk-PX25R3DK.js → chunk-FVXLP7P3.js} +1 -1
- package/dist/{chunk-XJRNEQPD.js → chunk-GVWEFECG.js} +1 -1
- package/dist/{chunk-R2V5LN3I.js → chunk-HY2SRSVM.js} +1 -1
- package/dist/{chunk-ZMJQUZQH.js → chunk-JXWNXIIZ.js} +24 -0
- package/dist/chunk-KR6JF3BF.js +14 -0
- package/dist/{chunk-DPM6V7YX.js → chunk-LICYHPI3.js} +82 -6
- package/dist/{chunk-J53ERF6K.js → chunk-LNT64X7C.js} +1 -1
- package/dist/{chunk-RQNJXADQ.js → chunk-NKZMQV2C.js} +1 -1
- package/dist/{chunk-UE6LKRXQ.js → chunk-NTU2DXNG.js} +24 -1
- package/dist/{chunk-T5GZBKIQ.js → chunk-NXUY5XQN.js} +168 -33
- package/dist/{chunk-M2OWVAZG.js → chunk-NZO7M5GV.js} +1 -1
- package/dist/{chunk-BRGLDWUP.js → chunk-OOL4KMG2.js} +7 -3
- package/dist/{chunk-MWCCGPG3.js → chunk-OOSRUJBI.js} +3 -2
- package/dist/{chunk-H5PTKYXL.js → chunk-QA3YCOML.js} +1 -1
- package/dist/{chunk-MUIU7RZ3.js → chunk-QFWQKS3M.js} +1 -1
- package/dist/chunk-RD2MID3M.js +292 -0
- package/dist/chunk-TRBGC4ZG.js +112 -0
- package/dist/{chunk-PLOSP4XQ.js → chunk-UJCK5IW4.js} +11 -2
- package/dist/{chunk-5KGV6TF7.js → chunk-UNYR5GLI.js} +1 -1
- package/dist/{chunk-JNNNBEUH.js → chunk-VN5DLURA.js} +7 -2
- package/dist/{chunk-EL6QPF6I.js → chunk-VP5TIXDK.js} +1 -1
- package/dist/chunk-WOHX7YTH.js +165 -0
- package/dist/{chunk-G7ED5H2G.js → chunk-XTEFPUSN.js} +1 -1
- package/dist/{chunk-6ZY3U6BQ.js → chunk-ZOWKWMD2.js} +1 -1
- package/dist/class/class.codec.js +6 -6
- package/dist/class/class.parser.js +5 -5
- package/dist/class/class.schema.d.ts +8 -0
- package/dist/class/class.schema.js +4 -4
- package/dist/class/class.standard-array.d.ts +16 -0
- package/dist/class/class.standard-array.js +20 -0
- package/dist/class/class.types.d.ts +3 -0
- package/dist/dnd/amount-text.d.ts +42 -0
- package/dist/dnd/amount-text.js +15 -0
- package/dist/dnd/resource-words.d.ts +4 -0
- package/dist/dnd/resource-words.js +30 -0
- package/dist/entities/registry-revision.d.ts +25 -0
- package/dist/entities/registry-revision.js +10 -0
- package/dist/feat/feat.codec.js +5 -5
- package/dist/feat/feat.parser.js +4 -4
- package/dist/feat/feat.schema.d.ts +34 -0
- package/dist/feat/feat.schema.js +3 -3
- package/dist/index.js +4027 -383
- package/dist/monster/monster.parser.js +5 -5
- package/dist/monster/monster.schema.js +4 -4
- package/dist/optional-feature/optional-feature.codec.js +5 -4
- package/dist/optional-feature/optional-feature.parser.js +4 -3
- package/dist/optional-feature/optional-feature.schema.d.ts +35 -0
- package/dist/optional-feature/optional-feature.schema.js +3 -2
- package/dist/pc/pc.additional-spells.d.ts +16 -2
- package/dist/pc/pc.additional-spells.js +5 -2
- package/dist/pc/pc.amount-bindings.d.ts +41 -0
- package/dist/pc/pc.amount-bindings.js +28 -0
- package/dist/pc/pc.decision-engine.d.ts +11 -0
- package/dist/pc/pc.decision-engine.js +20 -16
- package/dist/pc/pc.entry-overrides.js +3 -2
- package/dist/pc/pc.equipment.js +7 -5
- package/dist/pc/pc.feature-effects.d.ts +21 -2
- package/dist/pc/pc.feature-effects.js +15 -2
- package/dist/pc/pc.infusions.d.ts +9 -1
- package/dist/pc/pc.infusions.js +3 -2
- package/dist/pc/pc.member-choices.d.ts +122 -0
- package/dist/pc/pc.member-choices.js +39 -0
- package/dist/pc/pc.parser.js +1 -1
- package/dist/pc/pc.pools.d.ts +7 -0
- package/dist/pc/pc.pools.js +5 -2
- package/dist/pc/pc.proficiencies.d.ts +14 -0
- package/dist/pc/pc.proficiencies.js +72 -22
- package/dist/pc/pc.proficiency-normalize.d.ts +14 -0
- package/dist/pc/pc.proficiency-normalize.js +9 -3
- package/dist/pc/pc.proficiency-query.js +7 -1
- package/dist/pc/pc.recalc.js +212 -99
- package/dist/pc/pc.resolver.d.ts +6 -2
- package/dist/pc/pc.resolver.js +16 -14
- package/dist/pc/pc.rest.js +4 -3
- package/dist/pc/pc.schema.d.ts +48 -2
- package/dist/pc/pc.schema.js +1 -1
- package/dist/pc/pc.spell-scope.d.ts +22 -0
- package/dist/pc/pc.tool-picks.js +2 -2
- package/dist/pc/pc.types.d.ts +110 -6
- package/dist/pc/pc.weapon-grant.d.ts +15 -0
- package/dist/pc/pool-layout.d.ts +3 -9
- package/dist/pc/pool-layout.js +1 -3
- package/dist/pc/resource-equivalents.d.ts +44 -0
- package/dist/pc/spell-source.js +4 -9
- package/dist/race/race.codec.js +6 -6
- package/dist/race/race.parser.js +5 -5
- package/dist/race/race.schema.js +4 -4
- package/dist/schemas/choice-schema.js +2 -2
- package/dist/schemas/feature-effect-schema.d.ts +34 -0
- package/dist/schemas/feature-effect-schema.js +1 -1
- package/dist/schemas/feature-schema.js +3 -3
- package/dist/spell/spell.codec.js +2 -2
- package/dist/spell/spell.parser.d.ts +6 -1
- package/dist/spell/spell.parser.js +5 -3
- package/dist/spell/spell.types.d.ts +10 -0
- package/dist/subclass/subclass.codec.js +6 -6
- package/dist/subclass/subclass.parser.js +5 -5
- package/dist/subclass/subclass.schema.js +4 -4
- package/dist/types/choice.d.ts +7 -2
- package/dist/types/feature-effect.d.ts +24 -0
- package/dist/types/optional-feature.types.d.ts +6 -1
- package/dist/types/selection-pool.d.ts +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D2b-A · nested choices on pool members (design §2.2, user rulings 2026-09-27: GENERAL and RECURSIVE).
|
|
3
|
+
*
|
|
4
|
+
* A pool member (an invocation, a maneuver, a fighting-style option, ...) may carry its own choices: Lessons of the First
|
|
5
|
+
* Ones' Origin feat, Superior Technique's maneuver, Blessed and Druidic Warrior's cantrips, Pact of the Tome's cantrips
|
|
6
|
+
* and rituals, the three blasts' cantrip. A nested pick lives in the pool's ANCHOR block under
|
|
7
|
+
* `<poolId>:<pickKey>:<choiceId>` (a nested feat's own children under `<poolId>:<pickKey>:feat:<childId>`, the decision
|
|
8
|
+
* engine's prefix chaining), keyed by the stored pick string, so reordering or adding picks never moves it.
|
|
9
|
+
*
|
|
10
|
+
* The choices are the member's authored `choices`; absent (every note in the vault today), they are DERIVED at read time
|
|
11
|
+
* from the raw keys the notes already carry (`feat_progression`, `optionalfeature_progression`, `additional_spells`
|
|
12
|
+
* with `choose`, a `spell-choose` prerequisite), so existing notes work with no re-import. Authored `choices` win.
|
|
13
|
+
*/
|
|
14
|
+
import type { Character, ChoiceValue, FeatVia, ResolvedCharacter, ResolvedClass, ResolvedSpell, SpellTargets } from "./pc.types.js";
|
|
15
|
+
import type { Choice } from "@archivist-gg/dnd5e/types/choice";
|
|
16
|
+
import type { OptionalFeatureEntity } from "@archivist-gg/dnd5e/types/optional-feature.types";
|
|
17
|
+
import type { FeatEntity } from "@archivist-gg/dnd5e/feat/feat.types";
|
|
18
|
+
/** Whether `ch` names a target rather than granting what it picks (the data flag `target: true`, which the spell-choose
|
|
19
|
+
* derivation below writes and an authored note may carry). */
|
|
20
|
+
export declare function isTargetChoice(ch: Choice): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The member's choices: its authored `choices`, else the ones its raw keys ask for (see the module docblock). Pure; the
|
|
23
|
+
* derived rows carry the member's edition so a pick never offers the other edition's twin.
|
|
24
|
+
*/
|
|
25
|
+
export declare function memberChoices(entity: OptionalFeatureEntity | null | undefined): Choice[];
|
|
26
|
+
/**
|
|
27
|
+
* The spells a member's spell-scoped damage rider reaches, as its DATA names them (found live 2026-09-28: Agonizing
|
|
28
|
+
* Blast's Charisma never reached its cantrip): each answer of a spell target pick (`isTargetChoice`, the blasts' cantrip,
|
|
29
|
+
* read through `read`, the member's nested key) and each `spell-known` prerequisite (the 2014 Agonizing Blast's Eldritch
|
|
30
|
+
* Blast). A target pick left unanswered keeps its filter in `open` (see {@link spellsReached}). Null when the member names
|
|
31
|
+
* no spell, so its spell riders reach none.
|
|
32
|
+
*/
|
|
33
|
+
export declare function memberSpellTargets(member: OptionalFeatureEntity, read: (choiceId: string) => ChoiceValue | undefined): SpellTargets | null;
|
|
34
|
+
/**
|
|
35
|
+
* The slugs of the character's `spells` a rider with `targets` reaches, unique, in `spells` order: every spell a ref
|
|
36
|
+
* names (its full slug, else its bare name, so a path link `[[SRD 5e/eldritch-blast]]` finds `srd-5e_spell_eldritch-blast`),
|
|
37
|
+
* and, for each open target pick, its ONE legal answer: the character's own spells that match the pick's filter and roll
|
|
38
|
+
* damage, when exactly one does ("one of your known Warlock cantrips that deals damage" with only Eldritch Blast known).
|
|
39
|
+
* Two or more legal answers are a choice the player has not made, and reach none.
|
|
40
|
+
*/
|
|
41
|
+
export declare function spellsReached(targets: SpellTargets | undefined, spells: ReadonlyArray<Pick<ResolvedSpell, "slug" | "entity">>): string[];
|
|
42
|
+
/** One stored pool pick whose member carries choices, with the block its nested picks live in. */
|
|
43
|
+
export interface PoolMemberSite {
|
|
44
|
+
classIndex: number;
|
|
45
|
+
/** The pool's anchor level (the block's key in `choices`). */
|
|
46
|
+
level: number;
|
|
47
|
+
poolId: string;
|
|
48
|
+
/** The stored pick string (a copy of a repeatable note keeps its `slug#param`). */
|
|
49
|
+
pickKey: string;
|
|
50
|
+
member: OptionalFeatureEntity;
|
|
51
|
+
memberSlug: string;
|
|
52
|
+
choices: Choice[];
|
|
53
|
+
/** `<poolId>:<pickKey>:` */
|
|
54
|
+
prefix: string;
|
|
55
|
+
/** The nested pick stored for a member choice id. */
|
|
56
|
+
read: (choiceId: string) => ChoiceValue | undefined;
|
|
57
|
+
/** The anchor block, as stored. */
|
|
58
|
+
block: Record<string, ChoiceValue>;
|
|
59
|
+
}
|
|
60
|
+
/** Structural subset of the registry the raw enumerator reads. */
|
|
61
|
+
export interface MemberRegistry {
|
|
62
|
+
getByTypeAndSlug(entityType: string, slug: string): {
|
|
63
|
+
slug: string;
|
|
64
|
+
data: Record<string, unknown>;
|
|
65
|
+
} | undefined;
|
|
66
|
+
search(query: string, entityType: string, limit: number): Array<{
|
|
67
|
+
slug: string;
|
|
68
|
+
data: Record<string, unknown>;
|
|
69
|
+
}>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Every pool member site from the RAW character (the resolver's view, before pools resolve): each class's and subclass's
|
|
73
|
+
* declared pools, their anchor levels (`poolAnchorLevel`, the one derivation `resolvePool` uses), the picks stored there,
|
|
74
|
+
* each resolved to its note (full slug, else bare). A pick with no note, or a note with no choices, yields no site, so an
|
|
75
|
+
* orphan nested key is inert.
|
|
76
|
+
*/
|
|
77
|
+
export declare function poolMemberSites(classes: ResolvedClass[], registry: MemberRegistry): PoolMemberSite[];
|
|
78
|
+
/** The same sites read from a RESOLVED character's pools (the ledger's, the proficiency walk's and recalc's view). */
|
|
79
|
+
export declare function poolMemberSitesOf(resolved: Pick<ResolvedCharacter, "pools" | "classes">): PoolMemberSite[];
|
|
80
|
+
/** A feat the character picked, where it was picked, and how its own children are read. */
|
|
81
|
+
export interface FeatPickSite {
|
|
82
|
+
slug: string;
|
|
83
|
+
classIndex: number;
|
|
84
|
+
level: number | null;
|
|
85
|
+
via: FeatVia | null;
|
|
86
|
+
/** A child choice of THIS feat, as stored (`feat:<id>` in the slot's block, or `<prefix>feat:<id>` under a member). */
|
|
87
|
+
read: (childId: string) => ChoiceValue | undefined;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Every feat pick with its own read (design §2.2 step 4): the class-slot picks (`choices[<level>].feat`, children under
|
|
91
|
+
* `feat:<id>`) and the pool-member picks (a member choice of `entity_type: feat`, children under `<prefix>feat:<id>`).
|
|
92
|
+
* The ONE list the four feat readers walk (the resolver's feats and feat spells, the proficiency walk, ability points).
|
|
93
|
+
* `classSlugOf` names a class for the provenance. NOT de-duplicated: a feat taken twice (Magic Initiate, one spell list
|
|
94
|
+
* each) is two sites with two reads; a reader that lists feats de-duplicates by slug itself (first wins).
|
|
95
|
+
*/
|
|
96
|
+
export declare function featPickSites(classEntries: ReadonlyArray<{
|
|
97
|
+
choices?: Character["class"][number]["choices"] | ResolvedClass["choices"];
|
|
98
|
+
}> | undefined, memberSites: PoolMemberSite[], classSlugOf: (classIndex: number) => string | null): FeatPickSite[];
|
|
99
|
+
/** SRD-G T4 (audit §3b): a feat the character picked through a SPECIES (the SRD 5.2 Human's Versatile: "You gain an
|
|
100
|
+
* Origin feat of your choice"): a `select-entity` of `entity_type: feat` on `race.choices[]` or a trait's `choices[]`,
|
|
101
|
+
* answered under `origin_choices["race:<id>"]`. The feat's OWN children are read under `race:feat:<childId>`, the key
|
|
102
|
+
* the builder's ledger writes them to (a race item's feat children carry the `feat:` prefix, namespace `race`). */
|
|
103
|
+
export interface RaceFeatSite {
|
|
104
|
+
slug: string;
|
|
105
|
+
via: FeatVia;
|
|
106
|
+
read: (childId: string) => ChoiceValue | undefined;
|
|
107
|
+
}
|
|
108
|
+
export declare function raceFeatSites(race: {
|
|
109
|
+
slug: string;
|
|
110
|
+
choices?: unknown;
|
|
111
|
+
traits?: ReadonlyArray<{
|
|
112
|
+
choices?: unknown;
|
|
113
|
+
}>;
|
|
114
|
+
} | null | undefined, originChoices: Readonly<Record<string, ChoiceValue>> | undefined): RaceFeatSite[];
|
|
115
|
+
/** Look a feat pick up in the resolved feats (by slug, then bare slug). */
|
|
116
|
+
export declare function featOfSite(site: FeatPickSite, feats: ReadonlyArray<FeatEntity>): FeatEntity | undefined;
|
|
117
|
+
/**
|
|
118
|
+
* The nested keys an array write to `key` orphans (design §2.2 step 8): every `<key>:<k>:*` whose `k` was stored before
|
|
119
|
+
* and is not in `next`. Pure; the writer deletes them in the same write, so a re-pick never resurrects an old answer.
|
|
120
|
+
* A key that is not a pool (no array stored) orphans nothing.
|
|
121
|
+
*/
|
|
122
|
+
export declare function nestedPickKeysToPrune(block: Record<string, unknown> | undefined, key: string, next: readonly string[]): string[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
featOfSite,
|
|
3
|
+
featPickSites,
|
|
4
|
+
isTargetChoice,
|
|
5
|
+
memberChoices,
|
|
6
|
+
memberSpellTargets,
|
|
7
|
+
nestedPickKeysToPrune,
|
|
8
|
+
poolMemberSites,
|
|
9
|
+
poolMemberSitesOf,
|
|
10
|
+
raceFeatSites,
|
|
11
|
+
spellsReached
|
|
12
|
+
} from "../chunk-RD2MID3M.js";
|
|
13
|
+
import "../chunk-NXUY5XQN.js";
|
|
14
|
+
import "../chunk-W6GC6OKK.js";
|
|
15
|
+
import "../chunk-B3DP42OM.js";
|
|
16
|
+
import "../chunk-IUAAXXNO.js";
|
|
17
|
+
import "../chunk-ZUQW5VBZ.js";
|
|
18
|
+
import "../chunk-IHE6KVC3.js";
|
|
19
|
+
import "../chunk-LHZXYFBU.js";
|
|
20
|
+
import "../chunk-YBLDE5CG.js";
|
|
21
|
+
import "../chunk-LVQLWRCU.js";
|
|
22
|
+
import "../chunk-CM5CWO4K.js";
|
|
23
|
+
import "../chunk-4J6DS3EJ.js";
|
|
24
|
+
import "../chunk-ZCTUYGNV.js";
|
|
25
|
+
import "../chunk-3TM34RSJ.js";
|
|
26
|
+
import "../chunk-PDJSUH24.js";
|
|
27
|
+
import "../chunk-WELIKLZF.js";
|
|
28
|
+
export {
|
|
29
|
+
featOfSite,
|
|
30
|
+
featPickSites,
|
|
31
|
+
isTargetChoice,
|
|
32
|
+
memberChoices,
|
|
33
|
+
memberSpellTargets,
|
|
34
|
+
nestedPickKeysToPrune,
|
|
35
|
+
poolMemberSites,
|
|
36
|
+
poolMemberSitesOf,
|
|
37
|
+
raceFeatSites,
|
|
38
|
+
spellsReached
|
|
39
|
+
};
|
package/dist/pc/pc.parser.js
CHANGED
package/dist/pc/pc.pools.d.ts
CHANGED
|
@@ -9,6 +9,13 @@ export interface PoolRegistry {
|
|
|
9
9
|
getByTypeAndSlug(entityType: string, slug: string): RegisteredEntity | undefined;
|
|
10
10
|
}
|
|
11
11
|
export declare function levelPrereqMax(d: OptionalFeatureEntity): number;
|
|
12
|
+
/** The pool's ANCHOR level (its picks live in `choices[anchorLevel][pool.id]`): the first level whose count column
|
|
13
|
+
* carries a value of at least 1, else 1; `columnSeen` says whether any level carried the column at all. ONE derivation
|
|
14
|
+
* shared by `resolvePool` and the nested-choice enumerator (D2b-A, `pc.member-choices.ts`), so the two always agree. */
|
|
15
|
+
export declare function poolAnchorLevel(entity: Pick<NonNullable<ResolvedClass["entity"]>, "table">, pool: Pick<SelectionPool, "count">): {
|
|
16
|
+
anchorLevel: number;
|
|
17
|
+
columnSeen: boolean;
|
|
18
|
+
};
|
|
12
19
|
export declare function resolvePool(rc: ResolvedClass, classIndex: number, pool: SelectionPool, registry: PoolRegistry, index: ResourceIndex, edition?: string): ResolvedPool;
|
|
13
20
|
export declare function resolveAllPools(resolved: ResolvedCharacter, registry: PoolRegistry, index: ResourceIndex): ResolvedPool[];
|
|
14
21
|
/** The stored picks that survived resolution but are NOT in the collapsed `available` list, compared by FULL
|
package/dist/pc/pc.pools.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
2
|
levelPrereqMax,
|
|
3
|
+
poolAnchorLevel,
|
|
3
4
|
resolveAllPools,
|
|
4
5
|
resolvePool,
|
|
5
6
|
strandedPicks
|
|
6
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-NXUY5XQN.js";
|
|
7
8
|
import "../chunk-W6GC6OKK.js";
|
|
8
|
-
import "../chunk-
|
|
9
|
+
import "../chunk-B3DP42OM.js";
|
|
9
10
|
import "../chunk-IUAAXXNO.js";
|
|
10
11
|
import "../chunk-ZUQW5VBZ.js";
|
|
11
12
|
import "../chunk-IHE6KVC3.js";
|
|
12
13
|
import "../chunk-LHZXYFBU.js";
|
|
13
14
|
import "../chunk-LVQLWRCU.js";
|
|
14
15
|
import "../chunk-CM5CWO4K.js";
|
|
16
|
+
import "../chunk-4J6DS3EJ.js";
|
|
15
17
|
import "../chunk-ZCTUYGNV.js";
|
|
16
18
|
import "../chunk-3TM34RSJ.js";
|
|
17
19
|
import "../chunk-PDJSUH24.js";
|
|
18
20
|
import "../chunk-WELIKLZF.js";
|
|
19
21
|
export {
|
|
20
22
|
levelPrereqMax,
|
|
23
|
+
poolAnchorLevel,
|
|
21
24
|
resolveAllPools,
|
|
22
25
|
resolvePool,
|
|
23
26
|
strandedPicks
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ResolvedCharacter } from "./pc.types.js";
|
|
2
|
+
import type { WeaponEntity } from "@archivist-gg/dnd5e/weapon/weapon.types";
|
|
3
|
+
import { type ProficiencyKind } from "./pc.proficiency-normalize.js";
|
|
2
4
|
import { type ProficiencyEntry, type ProficiencyGrant } from "./pc.proficiency-grants.js";
|
|
3
5
|
/** Re-exported for the plugin: both types are DECLARED in the leaf so that
|
|
4
6
|
* pc.decision-engine can reference them without importing this module, which
|
|
@@ -62,3 +64,15 @@ export interface ProficiencySources {
|
|
|
62
64
|
}
|
|
63
65
|
export declare function collectProficiencySources(resolved: ResolvedCharacter): ProficiencySources;
|
|
64
66
|
export declare function aggregateProficiencies(resolved: ResolvedCharacter): ProficiencyAggregate;
|
|
67
|
+
/** D4 A: one kind's proficiencies as the picker needs them: `effective` is exactly the sheet's line (the aggregate's
|
|
68
|
+
* bucket), `removed` every grant or pick the user's overrides suppress, keeping its `sources`. Both halves come out of
|
|
69
|
+
* the composers the sheet reads, so the picker and the sheet line cannot disagree. */
|
|
70
|
+
export declare function proficiencyOverview(resolved: ResolvedCharacter, kind: ProficiencyKind): {
|
|
71
|
+
effective: ProficiencyEntry[];
|
|
72
|
+
removed: ProficiencyEntry[];
|
|
73
|
+
};
|
|
74
|
+
/** D4 A: the effective weapon entry that covers `weapon` without naming it, a category (`simple`, `martial`, or the
|
|
75
|
+
* entity's own `martial-melee` form) or a property-qualified grant ("Martial weapons that have the Light property").
|
|
76
|
+
* The same three arms as the live gate, `isWeaponSlugProficient` (pc.equipment.ts), so the picker's dimmed "via" row
|
|
77
|
+
* and the attack row's proficiency agree. Undefined when nothing but the weapon's own entry could cover it. */
|
|
78
|
+
export declare function weaponCoveredBy(effective: readonly ProficiencyEntry[], weapon: Pick<WeaponEntity, "category" | "properties" | "range" | "slug">): ProficiencyEntry | undefined;
|
|
@@ -1,37 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
qualifiedGrantCovers
|
|
3
|
+
} from "../chunk-3LWMFYKS.js";
|
|
4
|
+
import "../chunk-YDGMBMYI.js";
|
|
1
5
|
import {
|
|
2
6
|
collectChosenProficiencies,
|
|
3
7
|
collectProficiencyGrants,
|
|
4
|
-
computeEffectiveProficiencies
|
|
5
|
-
|
|
8
|
+
computeEffectiveProficiencies,
|
|
9
|
+
computeSuppressedProficiencies
|
|
10
|
+
} from "../chunk-EJD47JHL.js";
|
|
6
11
|
import "../chunk-2LULMQTF.js";
|
|
7
|
-
import "../chunk-
|
|
8
|
-
import "../chunk-
|
|
9
|
-
import "../chunk-DPUJDOKV.js";
|
|
10
|
-
import "../chunk-4BZ47YJ4.js";
|
|
12
|
+
import "../chunk-QA3YCOML.js";
|
|
13
|
+
import "../chunk-64VNLMSG.js";
|
|
11
14
|
import "../chunk-TOM66P2P.js";
|
|
12
15
|
import "../chunk-KU3YLF6D.js";
|
|
13
16
|
import {
|
|
17
|
+
normKey,
|
|
18
|
+
proficiencyKey,
|
|
14
19
|
proficiencyLabel,
|
|
15
|
-
toProfSlug
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
toProfSlug,
|
|
21
|
+
weaponProfKey
|
|
22
|
+
} from "../chunk-5JXFJ5AU.js";
|
|
23
|
+
import "../chunk-RD2MID3M.js";
|
|
24
|
+
import "../chunk-LICYHPI3.js";
|
|
25
|
+
import "../chunk-PVCEA4HZ.js";
|
|
26
|
+
import "../chunk-KSTV7J2U.js";
|
|
27
|
+
import "../chunk-DPUJDOKV.js";
|
|
28
|
+
import "../chunk-LBEXLN6I.js";
|
|
29
|
+
import "../chunk-NXUY5XQN.js";
|
|
18
30
|
import "../chunk-W6GC6OKK.js";
|
|
19
|
-
import "../chunk-
|
|
31
|
+
import "../chunk-B3DP42OM.js";
|
|
20
32
|
import "../chunk-IUAAXXNO.js";
|
|
21
33
|
import "../chunk-YHYTW5QA.js";
|
|
22
|
-
import "../chunk-DPM6V7YX.js";
|
|
23
|
-
import "../chunk-PVCEA4HZ.js";
|
|
24
|
-
import "../chunk-LBEXLN6I.js";
|
|
25
34
|
import "../chunk-ZUQW5VBZ.js";
|
|
26
35
|
import "../chunk-IHE6KVC3.js";
|
|
27
36
|
import "../chunk-LHZXYFBU.js";
|
|
37
|
+
import "../chunk-YBLDE5CG.js";
|
|
28
38
|
import "../chunk-LVQLWRCU.js";
|
|
29
39
|
import "../chunk-CM5CWO4K.js";
|
|
30
|
-
import "../chunk-
|
|
31
|
-
import "../chunk-YBLDE5CG.js";
|
|
40
|
+
import "../chunk-4J6DS3EJ.js";
|
|
32
41
|
import "../chunk-EVMUJYUD.js";
|
|
33
42
|
import "../chunk-VTTA2ALZ.js";
|
|
34
|
-
import "../chunk-
|
|
43
|
+
import "../chunk-ZCTUYGNV.js";
|
|
44
|
+
import "../chunk-7Q2VPKCD.js";
|
|
35
45
|
import "../chunk-ZDAEGJH4.js";
|
|
36
46
|
import "../chunk-3TM34RSJ.js";
|
|
37
47
|
import "../chunk-PDJSUH24.js";
|
|
@@ -51,8 +61,29 @@ function collectProficiencySources(resolved) {
|
|
|
51
61
|
};
|
|
52
62
|
}
|
|
53
63
|
function aggregateProficiencies(resolved) {
|
|
54
|
-
const
|
|
64
|
+
const { armor, weapons } = composeArmorWeapons(resolved);
|
|
55
65
|
const effective = computeEffectiveProficiencies(resolved);
|
|
66
|
+
return { armor: armor.kept, weapons: weapons.kept, tools: effective.tools, languages: effective.languages };
|
|
67
|
+
}
|
|
68
|
+
function proficiencyOverview(resolved, kind) {
|
|
69
|
+
if (kind === "armor" || kind === "weapons") {
|
|
70
|
+
const c = composeArmorWeapons(resolved)[kind];
|
|
71
|
+
return { effective: c.kept, removed: c.suppressed };
|
|
72
|
+
}
|
|
73
|
+
return { effective: computeEffectiveProficiencies(resolved)[kind], removed: computeSuppressedProficiencies(resolved)[kind] };
|
|
74
|
+
}
|
|
75
|
+
function weaponCoveredBy(effective, weapon) {
|
|
76
|
+
const cat = typeof weapon.category === "string" ? weapon.category : "";
|
|
77
|
+
const base = cat.split("-")[0];
|
|
78
|
+
return effective.find((e) => {
|
|
79
|
+
const key = weaponProfKey(e.value);
|
|
80
|
+
if ((key === "simple" || key === "martial") && key === base) return true;
|
|
81
|
+
if (cat && key === normKey(cat)) return true;
|
|
82
|
+
return qualifiedGrantCovers(e.value, weapon);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function composeArmorWeapons(resolved) {
|
|
86
|
+
const src = collectProficiencySources(resolved);
|
|
56
87
|
return {
|
|
57
88
|
// Walk order defines `sources` order (spec §4.1): class before feats, and
|
|
58
89
|
// for weapons the class `fixed` names before the class categories, matching
|
|
@@ -67,10 +98,27 @@ function aggregateProficiencies(resolved) {
|
|
|
67
98
|
// keeps origin `grant` and its granting source(s) (R4-G3b §7 F5 · the same
|
|
68
99
|
// grants-then-picks order computeEffectiveProficiencies uses for the
|
|
69
100
|
// language and tool buckets). Armor passes none.
|
|
70
|
-
armor: composeGrantEntries([src.classArmor, src.featArmor, src.effectArmor]),
|
|
71
|
-
weapons:
|
|
72
|
-
|
|
73
|
-
|
|
101
|
+
armor: applyKindOverrides(resolved, "armor", composeGrantEntries([src.classArmor, src.featArmor, src.effectArmor])),
|
|
102
|
+
weapons: applyKindOverrides(
|
|
103
|
+
resolved,
|
|
104
|
+
"weapons",
|
|
105
|
+
composeGrantEntries([src.classWeaponFixed, src.classWeaponCategories, src.featWeapons, src.effectWeapons], src.chosenWeapons)
|
|
106
|
+
)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function applyKindOverrides(resolved, kind, entries) {
|
|
110
|
+
var _a, _b, _c, _d;
|
|
111
|
+
const ov = (_b = (_a = resolved.definition) == null ? void 0 : _a.overrides) == null ? void 0 : _b[kind];
|
|
112
|
+
const key = (v) => proficiencyKey(kind, v);
|
|
113
|
+
const all = [...entries];
|
|
114
|
+
for (const v of (_c = ov == null ? void 0 : ov.add) != null ? _c : []) {
|
|
115
|
+
if (!all.some((e) => key(e.value) === key(v))) all.push({ value: v, label: prettyName(v), sources: [], origin: "manual" });
|
|
116
|
+
}
|
|
117
|
+
const removes = new Set(((_d = ov == null ? void 0 : ov.remove) != null ? _d : []).map(key));
|
|
118
|
+
const byLabel = (a, b) => a.label < b.label ? -1 : a.label > b.label ? 1 : 0;
|
|
119
|
+
return {
|
|
120
|
+
kept: all.filter((e) => !removes.has(key(e.value))).sort(byLabel),
|
|
121
|
+
suppressed: all.filter((e) => removes.has(key(e.value))).sort(byLabel)
|
|
74
122
|
};
|
|
75
123
|
}
|
|
76
124
|
function composeGrantEntries(buckets, picks = []) {
|
|
@@ -102,5 +150,7 @@ function prettyName(slug) {
|
|
|
102
150
|
}
|
|
103
151
|
export {
|
|
104
152
|
aggregateProficiencies,
|
|
105
|
-
collectProficiencySources
|
|
153
|
+
collectProficiencySources,
|
|
154
|
+
proficiencyOverview,
|
|
155
|
+
weaponCoveredBy
|
|
106
156
|
};
|
|
@@ -27,3 +27,17 @@ export declare const humanizeProficiency: (s: string) => string;
|
|
|
27
27
|
* the first character capitalised, every other letter untouched. Title-casing it word by word lower-cased
|
|
28
28
|
* `2H` to `2h` and read "That Have The Light Property". */
|
|
29
29
|
export declare const proficiencyLabel: (raw: string) => string;
|
|
30
|
+
/** The four proficiency kinds the sheet lists and the D4 pickers edit, one picker per kind. */
|
|
31
|
+
export type ProficiencyKind = "armor" | "weapons" | "tools" | "languages";
|
|
32
|
+
/** D4 A: the key of a WEAPON proficiency value, so every spelling of one grant folds onto one key. A category reads
|
|
33
|
+
* `simple` or `martial` whatever its spelling ("martial", "Martial weapons"); anything else is keyed by
|
|
34
|
+
* {@link normKey}, the rule the live weapon gate (`isWeaponSlugProficient`) matches a weapon's name by, so
|
|
35
|
+
* "longswords", "Longsword" and "longsword" meet, and "hand-crossbow" meets "Hand Crossbows" and "Crossbow, hand". */
|
|
36
|
+
export declare const weaponProfKey: (raw: string) => string;
|
|
37
|
+
/** D4 A: the key of an ARMOR proficiency value: `light`, `medium`, `heavy`, `shield` whatever the spelling
|
|
38
|
+
* ("Heavy armor", "shields"). */
|
|
39
|
+
export declare const armorProfKey: (raw: string) => string;
|
|
40
|
+
/** D4 A: the one comparison key per kind, used by the engine's override readers and the plugin's writers alike, so a
|
|
41
|
+
* value the writer stores and the value the reader suppresses always meet. Languages and tools keep `toProfSlug`,
|
|
42
|
+
* unchanged. */
|
|
43
|
+
export declare const proficiencyKey: (kind: ProficiencyKind, raw: string) => string;
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
+
armorProfKey,
|
|
2
3
|
humanizeProficiency,
|
|
3
4
|
normKey,
|
|
5
|
+
proficiencyKey,
|
|
4
6
|
proficiencyLabel,
|
|
5
|
-
toProfSlug
|
|
6
|
-
|
|
7
|
+
toProfSlug,
|
|
8
|
+
weaponProfKey
|
|
9
|
+
} from "../chunk-5JXFJ5AU.js";
|
|
7
10
|
export {
|
|
11
|
+
armorProfKey,
|
|
8
12
|
humanizeProficiency,
|
|
9
13
|
normKey,
|
|
14
|
+
proficiencyKey,
|
|
10
15
|
proficiencyLabel,
|
|
11
|
-
toProfSlug
|
|
16
|
+
toProfSlug,
|
|
17
|
+
weaponProfKey
|
|
12
18
|
};
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
qualifiedGrantCovers
|
|
3
|
+
} from "../chunk-3LWMFYKS.js";
|
|
4
|
+
import "../chunk-YDGMBMYI.js";
|
|
1
5
|
import {
|
|
2
6
|
normKey
|
|
3
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-5JXFJ5AU.js";
|
|
8
|
+
import "../chunk-ZCTUYGNV.js";
|
|
4
9
|
|
|
5
10
|
// src/pc/pc.proficiency-query.ts
|
|
6
11
|
function isProficientWithWeapon(weapon, profs) {
|
|
7
12
|
if (profs.weapons.specific.some((e) => e === weapon.slug || normKey(e) === normKey(weapon.name))) return true;
|
|
13
|
+
if (profs.weapons.specific.some((e) => qualifiedGrantCovers(e, weapon))) return true;
|
|
8
14
|
const baseCategory = weapon.category.split("-")[0];
|
|
9
15
|
return profs.weapons.categories.includes(baseCategory);
|
|
10
16
|
}
|