@archivist-gg/dnd5e 0.3.1 → 0.3.5
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/LICENSES/SRD.md +25 -6
- package/dist/armor/armor.codec.js +4 -3
- package/dist/armor/armor.parser.js +3 -2
- package/dist/armor/armor.schema.d.ts +4 -0
- package/dist/armor/armor.schema.js +2 -1
- package/dist/armor/armor.types.d.ts +8 -0
- package/dist/background/background.codec.js +7 -6
- package/dist/background/background.parser.js +6 -5
- package/dist/background/background.schema.d.ts +26 -0
- package/dist/background/background.schema.js +5 -4
- package/dist/background/background.types.d.ts +22 -0
- package/dist/{chunk-4TIJKN6O.js → chunk-23LZLBJK.js} +1 -1
- package/dist/{chunk-AFKTJXRA.js → chunk-2DFENQIJ.js} +18 -0
- package/dist/chunk-2FRUSVZS.js +489 -0
- package/dist/chunk-2VDTW7QO.js +1580 -0
- package/dist/{chunk-ZZGEVJKX.js → chunk-3JOTQW7A.js} +1 -1
- package/dist/{chunk-5ZBE4CWV.js → chunk-3PZHBLFG.js} +1 -1
- package/dist/chunk-4U55VNC7.js +22 -0
- package/dist/{chunk-4JHOHWBI.js → chunk-4XTNPNQB.js} +1 -1
- package/dist/{chunk-HCP3GMHS.js → chunk-5N2GW72F.js} +15 -2
- package/dist/{chunk-42Y5IJKT.js → chunk-5RKLL2AD.js} +5 -1
- package/dist/{chunk-SE7AZDR3.js → chunk-63XQXPZL.js} +29 -3
- package/dist/{chunk-4F6AIZ36.js → chunk-6BFWZW6T.js} +1 -1
- package/dist/{chunk-GJOI2GHP.js → chunk-6GNXYAYP.js} +1 -1
- package/dist/{chunk-66XXW2Z2.js → chunk-6PXTNCBP.js} +2 -2
- package/dist/chunk-6X2NV665.js +33 -0
- package/dist/{chunk-LV4SNWZQ.js → chunk-6X4KUDMU.js} +8 -1
- package/dist/{chunk-ZMR77DDL.js → chunk-AW3EHF3L.js} +1 -1
- package/dist/{chunk-TLYDBT5F.js → chunk-CGRLQ34U.js} +64 -7
- package/dist/chunk-CJYCVNW2.js +7 -0
- package/dist/{chunk-XLSVNUTK.js → chunk-CV5XDJKU.js} +1 -1
- package/dist/{chunk-RAWJTA45.js → chunk-DEEHHLAP.js} +1 -1
- package/dist/{chunk-6IRYTM5V.js → chunk-DPUJDOKV.js} +119 -28
- package/dist/{chunk-GE4TLPWM.js → chunk-EHDATXP7.js} +1 -1
- package/dist/chunk-EVMUJYUD.js +195 -0
- package/dist/{chunk-K6Q5OUGK.js → chunk-GFZ7PITJ.js} +86 -10
- package/dist/{chunk-6NK2MUD3.js → chunk-HZA7OE7N.js} +9 -2
- package/dist/{chunk-TN6ZLTNU.js → chunk-I2NDWFDD.js} +51 -4
- package/dist/{chunk-3DZ74AKK.js → chunk-IN4M4X7P.js} +8 -2
- package/dist/chunk-IUAAXXNO.js +58 -0
- package/dist/{chunk-NXSWH3AE.js → chunk-IWRVOCT5.js} +1 -1
- package/dist/{chunk-XJNECUPT.js → chunk-JC5UNIVM.js} +19 -0
- package/dist/{chunk-24GFQU7T.js → chunk-JSZ4H6AQ.js} +1 -1
- package/dist/{chunk-XTIQWSFM.js → chunk-KADJZGZY.js} +1 -1
- package/dist/{chunk-L7E2UT5A.js → chunk-KK33PIDN.js} +5 -1
- package/dist/chunk-KPEJMNRD.js +302 -0
- package/dist/chunk-KU3YLF6D.js +106 -0
- package/dist/{chunk-5INR4NSL.js → chunk-LDDCNPHD.js} +1 -1
- package/dist/chunk-LVQLWRCU.js +15 -0
- package/dist/chunk-MI7P3NX3.js +161 -0
- package/dist/{chunk-PHQRGDRQ.js → chunk-N3A5ICBO.js} +5 -1
- package/dist/chunk-O4SRD3J5.js +165 -0
- package/dist/chunk-OAXWSVJB.js +81 -0
- package/dist/chunk-OZP6JFGT.js +162 -0
- package/dist/{chunk-NAEPSZAM.js → chunk-PDJSUH24.js} +24 -2
- package/dist/chunk-PP2UHLBA.js +50 -0
- package/dist/chunk-PVCEA4HZ.js +6 -0
- package/dist/{chunk-RSDRMH54.js → chunk-RF4TYCA2.js} +1 -1
- package/dist/chunk-RLGQ5PZZ.js +16 -0
- package/dist/chunk-RVXC6ZHY.js +89 -0
- package/dist/chunk-S7REMWW4.js +21 -0
- package/dist/{chunk-7VJ6BOA5.js → chunk-SMLXZUBH.js} +9 -1
- package/dist/chunk-TOM66P2P.js +38 -0
- package/dist/chunk-UA5KS2PL.js +26 -0
- package/dist/{chunk-N6N74K6X.js → chunk-UBBKSYBY.js} +1 -1
- package/dist/{chunk-353WXS6S.js → chunk-UIRMA5KM.js} +1 -1
- package/dist/{chunk-BIK4DFWK.js → chunk-ULCQ73LA.js} +1 -1
- package/dist/{chunk-2BWAID3Y.js → chunk-UMWIPLLM.js} +29 -3
- package/dist/chunk-UQ3ZFCWD.js +178 -0
- package/dist/chunk-UXPGW4TI.js +101 -0
- package/dist/chunk-V2MJE6NC.js +133 -0
- package/dist/{chunk-D3CLOBE3.js → chunk-V373TRJE.js} +48 -10
- package/dist/chunk-VQAXTIBW.js +100 -0
- package/dist/chunk-W74BCA7V.js +159 -0
- package/dist/{chunk-WP2CX2JO.js → chunk-WELIKLZF.js} +22 -1
- package/dist/{chunk-MDEJHMKK.js → chunk-WHVLNFTG.js} +26 -5
- package/dist/{chunk-GZ46OVGZ.js → chunk-WSVMLNTO.js} +1 -1
- package/dist/{chunk-4LZJXJJS.js → chunk-WXWPK3LI.js} +6 -2
- package/dist/chunk-X3D6LXUU.js +185 -0
- package/dist/chunk-XJ2F2WO6.js +8 -0
- package/dist/{chunk-73HOFQQ5.js → chunk-Y4AGLCY6.js} +1 -1
- package/dist/chunk-YBLDE5CG.js +42 -0
- package/dist/{chunk-JEYQU3JT.js → chunk-YHYTW5QA.js} +2 -18
- package/dist/{chunk-PZOWBI3B.js → chunk-YLL2QXY2.js} +7 -2
- package/dist/class/class.codec.js +12 -9
- package/dist/class/class.parser.js +11 -8
- package/dist/class/class.schema.d.ts +55 -3
- package/dist/class/class.schema.js +9 -7
- package/dist/class/class.types.d.ts +48 -3
- package/dist/condition/condition.codec.d.ts +9 -0
- package/dist/condition/condition.entity-type.d.ts +2 -0
- package/dist/condition/condition.parser.d.ts +13 -0
- package/dist/condition/condition.parser.js +7 -0
- package/dist/condition/condition.schema.d.ts +29 -0
- package/dist/condition/condition.types.d.ts +15 -0
- package/dist/condition/condition.types.js +0 -0
- package/dist/dnd/constants.d.ts +22 -0
- package/dist/dnd/constants.js +9 -3
- package/dist/dnd/formula-tags.js +2 -2
- package/dist/dnd/math.d.ts +26 -0
- package/dist/dnd/math.js +12 -2
- package/dist/dnd/prose-tags.js +1 -1
- package/dist/dnd/resource-formula.d.ts +36 -10
- package/dist/dnd/resource-formula.js +10 -155
- package/dist/dnd/srd-tag-converter.js +3 -3
- package/dist/dnd/tag-grammar.js +1 -1
- package/dist/dnd/warn-once.d.ts +2 -0
- package/dist/dnd/warn-once.js +8 -0
- package/dist/entities/base-item-resolver.js +2 -2
- package/dist/entities/slug.d.ts +13 -0
- package/dist/entities/slug.js +5 -3
- package/dist/entities/wikilink-display.d.ts +3 -0
- package/dist/entities/wikilink-display.js +10 -0
- package/dist/feat/feat.category-codes.d.ts +32 -0
- package/dist/feat/feat.category-codes.js +14 -0
- package/dist/feat/feat.codec.js +7 -5
- package/dist/feat/feat.parser.js +6 -4
- package/dist/feat/feat.schema.d.ts +239 -0
- package/dist/feat/feat.schema.js +5 -3
- package/dist/feat/feat.types.d.ts +37 -1
- package/dist/index.js +2033 -6907
- package/dist/item/item.codec.js +4 -3
- package/dist/item/item.enrichment.js +4 -4
- package/dist/item/item.parser.js +3 -2
- package/dist/item/item.schema.d.ts +69 -3
- package/dist/item/item.schema.js +2 -1
- package/dist/item/item.types.d.ts +69 -3
- package/dist/monster/monster.codec.d.ts +7 -6
- package/dist/monster/monster.enrichment.js +4 -4
- package/dist/monster/monster.format.d.ts +135 -0
- package/dist/monster/monster.format.js +78 -0
- package/dist/monster/monster.parser.d.ts +6 -1
- package/dist/monster/monster.parser.js +10 -1
- package/dist/monster/monster.schema.d.ts +369 -0
- package/dist/monster/monster.schema.js +34 -0
- package/dist/monster/monster.types.d.ts +200 -14
- package/dist/optional-feature/optional-feature.codec.js +6 -5
- package/dist/optional-feature/optional-feature.parser.js +5 -4
- package/dist/optional-feature/optional-feature.schema.d.ts +185 -0
- package/dist/optional-feature/optional-feature.schema.js +4 -3
- package/dist/pack.d.ts +1 -1
- package/dist/pc/conditions.constants.d.ts +0 -1
- package/dist/pc/conditions.constants.js +1 -3
- package/dist/pc/decision-recognizer.js +1 -1
- package/dist/pc/pc.additional-spells.d.ts +47 -0
- package/dist/pc/pc.additional-spells.js +23 -0
- package/dist/pc/pc.asi-flatten.d.ts +18 -0
- package/dist/pc/pc.decision-engine.d.ts +105 -17
- package/dist/pc/pc.decision-engine.js +28 -12
- package/dist/pc/pc.defense-normalize.d.ts +11 -0
- package/dist/pc/pc.defense-normalize.js +6 -0
- package/dist/pc/pc.equipment.d.ts +31 -2
- package/dist/pc/pc.equipment.js +12 -8
- package/dist/pc/pc.feature-effects.d.ts +234 -11
- package/dist/pc/pc.feature-effects.js +21 -5
- package/dist/pc/pc.parser.js +2 -2
- package/dist/pc/pc.pools.d.ts +11 -3
- package/dist/pc/pc.pools.js +9 -13
- package/dist/pc/pc.proficiencies.d.ts +54 -29
- package/dist/pc/pc.proficiencies.js +74 -95
- package/dist/pc/pc.proficiency-grants.d.ts +58 -0
- package/dist/pc/pc.proficiency-normalize.d.ts +24 -0
- package/dist/pc/pc.proficiency-normalize.js +12 -0
- package/dist/pc/pc.proficiency-query.d.ts +13 -0
- package/dist/pc/pc.proficiency-query.js +1 -1
- package/dist/pc/pc.recalc.d.ts +69 -14
- package/dist/pc/pc.recalc.js +261 -122
- package/dist/pc/pc.resolver.d.ts +34 -8
- package/dist/pc/pc.resolver.js +27 -12
- package/dist/pc/pc.resource-seed.d.ts +3 -2
- package/dist/pc/pc.resource-seed.js +19 -17
- package/dist/pc/pc.resources.d.ts +87 -0
- package/dist/pc/pc.resources.js +21 -0
- package/dist/pc/pc.rest.d.ts +10 -1
- package/dist/pc/pc.rest.js +66 -12
- package/dist/pc/pc.schema.d.ts +139 -3
- package/dist/pc/pc.schema.js +4 -2
- package/dist/pc/pc.slotting.js +3 -3
- package/dist/pc/pc.spellcasting.d.ts +75 -6
- package/dist/pc/pc.spellcasting.js +16 -6
- package/dist/pc/pc.table-column.d.ts +23 -0
- package/dist/pc/pc.table-column.js +13 -3
- package/dist/pc/pc.types.d.ts +251 -12
- package/dist/pc/pool-layout.d.ts +47 -0
- package/dist/pc/pool-layout.js +16 -0
- package/dist/pc/roll-scope.d.ts +2 -0
- package/dist/pc/roll-scope.js +8 -0
- package/dist/pc/roll-tag-labels.d.ts +40 -0
- package/dist/pc/roll-tag-labels.js +17 -0
- package/dist/pc/spell-source.d.ts +21 -0
- package/dist/pc/spell-source.js +13 -0
- package/dist/race/race.codec.js +9 -7
- package/dist/race/race.parser.js +8 -6
- package/dist/race/race.schema.d.ts +51 -1
- package/dist/race/race.schema.js +8 -5
- package/dist/race/race.structural.d.ts +7 -0
- package/dist/race/race.structural.js +6 -0
- package/dist/race/race.types.d.ts +20 -0
- package/dist/schemas/attack-schema.d.ts +5 -0
- package/dist/schemas/caster-type-schema.d.ts +14 -0
- package/dist/schemas/caster-type-schema.js +6 -0
- package/dist/schemas/choice-schema.js +2 -2
- package/dist/schemas/entity-extras-schema.d.ts +53 -0
- package/dist/schemas/entity-extras-schema.js +10 -0
- package/dist/schemas/equipment-grant-schema.d.ts +2 -0
- package/dist/schemas/equipment-grant-schema.js +1 -1
- package/dist/schemas/feature-alias.d.ts +34 -0
- package/dist/schemas/feature-effect-schema.d.ts +133 -0
- package/dist/schemas/feature-effect-schema.js +1 -1
- package/dist/schemas/feature-schema.d.ts +8 -0
- package/dist/schemas/feature-schema.js +6 -4
- package/dist/schemas/resource-schema.d.ts +43 -0
- package/dist/schemas/resource-schema.js +1 -1
- package/dist/schemas/selection-pool-schema.d.ts +2 -0
- package/dist/schemas/selection-pool-schema.js +1 -1
- package/dist/spell/spell.access.js +23 -3
- package/dist/spell/spell.codec.js +4 -3
- package/dist/spell/spell.enrichment.js +4 -4
- package/dist/spell/spell.parser.d.ts +30 -0
- package/dist/spell/spell.parser.js +9 -2
- package/dist/spell/spell.scaling.d.ts +27 -6
- package/dist/spell/spell.scaling.js +25 -6
- package/dist/spell/spell.schema.d.ts +44 -2
- package/dist/spell/spell.schema.js +2 -1
- package/dist/spell/spell.types.d.ts +14 -0
- package/dist/subclass/subclass.codec.d.ts +1 -1
- package/dist/subclass/subclass.codec.js +11 -8
- package/dist/subclass/subclass.parser.js +10 -7
- package/dist/subclass/subclass.schema.d.ts +54 -7
- package/dist/subclass/subclass.schema.js +8 -6
- package/dist/subclass/subclass.types.d.ts +20 -2
- package/dist/types/choice.d.ts +53 -2
- package/dist/types/choice.js +13 -1
- package/dist/types/equipment-grant.d.ts +8 -1
- package/dist/types/feature-effect.d.ts +85 -23
- package/dist/types/feature.d.ts +70 -1
- package/dist/types/optional-feature.types.d.ts +28 -0
- package/dist/types/resource.d.ts +21 -1
- package/dist/types/selection-pool.d.ts +17 -2
- package/dist/weapon/weapon.classify.js +2 -2
- package/dist/weapon/weapon.codec.js +4 -3
- package/dist/weapon/weapon.parser.js +3 -2
- package/dist/weapon/weapon.schema.d.ts +4 -0
- package/dist/weapon/weapon.schema.js +2 -1
- package/dist/weapon/weapon.types.d.ts +8 -0
- package/package.json +2 -2
- package/dist/chunk-5XUXXAQJ.js +0 -30
- package/dist/chunk-7P65Q426.js +0 -158
- package/dist/chunk-7TEK5KK7.js +0 -49
- package/dist/chunk-AIRFC5NQ.js +0 -143
- package/dist/chunk-CZPMTQOY.js +0 -68
- package/dist/chunk-N2JE7BWL.js +0 -81
- package/dist/chunk-NXR3BD2V.js +0 -90
- package/dist/chunk-O5EYMKFH.js +0 -6
- package/dist/chunk-Q5K6LWBZ.js +0 -45
- package/dist/chunk-TNUFPIXI.js +0 -1110
- package/dist/chunk-TZK36BU7.js +0 -38
- package/dist/chunk-W7HWMMBF.js +0 -46
- package/dist/chunk-Y4I74YEG.js +0 -33
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {
|
|
2
|
+
imageField
|
|
3
|
+
} from "./chunk-UA5KS2PL.js";
|
|
4
|
+
|
|
5
|
+
// src/spell/spell.schema.ts
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
var castingOptionSchema = z.object({
|
|
8
|
+
type: z.string(),
|
|
9
|
+
damage_roll: z.string().optional(),
|
|
10
|
+
target_count: z.number().optional(),
|
|
11
|
+
duration: z.string().optional(),
|
|
12
|
+
range: z.number().optional(),
|
|
13
|
+
concentration: z.boolean().optional(),
|
|
14
|
+
shape_size: z.number().optional(),
|
|
15
|
+
desc: z.string().optional()
|
|
16
|
+
});
|
|
17
|
+
var spellMaterialSchema = z.union([
|
|
18
|
+
z.boolean(),
|
|
19
|
+
z.string(),
|
|
20
|
+
z.strictObject({
|
|
21
|
+
text: z.string(),
|
|
22
|
+
cost: z.number().optional(),
|
|
23
|
+
consume: z.union([z.boolean(), z.string()]).optional()
|
|
24
|
+
})
|
|
25
|
+
]);
|
|
26
|
+
var spellComponentsObjectSchema = z.strictObject({
|
|
27
|
+
v: z.boolean().optional(),
|
|
28
|
+
s: z.boolean().optional(),
|
|
29
|
+
m: spellMaterialSchema.optional(),
|
|
30
|
+
r: z.boolean().optional()
|
|
31
|
+
});
|
|
32
|
+
var spellDurationEntrySchema = z.discriminatedUnion("type", [
|
|
33
|
+
z.strictObject({
|
|
34
|
+
type: z.literal("permanent"),
|
|
35
|
+
ends: z.array(z.enum(["dispel", "trigger"])).nonempty()
|
|
36
|
+
}),
|
|
37
|
+
z.strictObject({
|
|
38
|
+
type: z.literal("timed"),
|
|
39
|
+
duration: z.strictObject({
|
|
40
|
+
type: z.enum(["minute", "hour"]),
|
|
41
|
+
amount: z.number().int().positive(),
|
|
42
|
+
up_to: z.boolean().optional()
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
]);
|
|
46
|
+
var spellEntitySchema = z.object({
|
|
47
|
+
name: z.string().min(1),
|
|
48
|
+
level: z.number().int().min(0).optional(),
|
|
49
|
+
school: z.string().optional(),
|
|
50
|
+
casting_time: z.string().optional(),
|
|
51
|
+
range: z.string().optional(),
|
|
52
|
+
// Union arms per §3.2; `parseSpell` normalises the structured arms back to the corpus's own
|
|
53
|
+
// string form, so `Spell.components` / `Spell.duration` stay `string` and no consumer changes.
|
|
54
|
+
components: z.union([z.string(), spellComponentsObjectSchema]).optional(),
|
|
55
|
+
duration: z.union([z.string(), z.array(spellDurationEntrySchema).nonempty()]).optional(),
|
|
56
|
+
concentration: z.boolean().optional(),
|
|
57
|
+
ritual: z.boolean().optional(),
|
|
58
|
+
classes: z.array(z.string()).optional(),
|
|
59
|
+
description: z.string().optional(),
|
|
60
|
+
at_higher_levels: z.array(z.string()).optional(),
|
|
61
|
+
damage: z.object({ types: z.array(z.string()) }).optional(),
|
|
62
|
+
saving_throw: z.object({ ability: z.string() }).optional(),
|
|
63
|
+
casting_options: z.array(castingOptionSchema).optional(),
|
|
64
|
+
// §2 · the fourteen converter keys this root refused before R4-G2. Doc counts are MEASURED over
|
|
65
|
+
// the frozen tree; the two zero-carrier keys are declared because the root is `.strict()` and an
|
|
66
|
+
// images-ON run (`image`) or a non-images-gated fluff flag (`has_fluff_images`) would otherwise
|
|
67
|
+
// refuse the whole document. Declared-only: NO rendering this phase (booked to G8 by name).
|
|
68
|
+
rendering_hint: z.string().optional(),
|
|
69
|
+
// 1,048 docs — NEVER .min(1): every value is ''
|
|
70
|
+
misc_tags: z.array(z.string()).optional(),
|
|
71
|
+
// 769
|
|
72
|
+
area_tags: z.array(z.string()).optional(),
|
|
73
|
+
// 707
|
|
74
|
+
condition_inflict: z.array(z.string()).optional(),
|
|
75
|
+
// 194
|
|
76
|
+
affects_creature_type: z.array(z.string()).optional(),
|
|
77
|
+
// 87
|
|
78
|
+
spell_attack: z.enum(["melee", "ranged"]).optional(),
|
|
79
|
+
// 77 (ranged 44 / melee 33)
|
|
80
|
+
ability_check: z.array(z.string()).optional(),
|
|
81
|
+
// 61
|
|
82
|
+
damage_resist: z.array(z.string()).optional(),
|
|
83
|
+
// 35
|
|
84
|
+
damage_immune: z.array(z.string()).optional(),
|
|
85
|
+
// 21
|
|
86
|
+
condition_immune: z.array(z.string()).optional(),
|
|
87
|
+
// 11
|
|
88
|
+
damage_vulnerable: z.array(z.string()).optional(),
|
|
89
|
+
// 2
|
|
90
|
+
has_fluff: z.boolean().optional(),
|
|
91
|
+
// 1
|
|
92
|
+
image: imageField,
|
|
93
|
+
// 0 today; `imageField` already ends .optional()
|
|
94
|
+
has_fluff_images: z.boolean().optional()
|
|
95
|
+
// 0 today
|
|
96
|
+
}).strict();
|
|
97
|
+
|
|
98
|
+
export {
|
|
99
|
+
spellEntitySchema
|
|
100
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
durationSchema
|
|
3
|
+
} from "./chunk-WYGD2UGN.js";
|
|
4
|
+
import {
|
|
5
|
+
actionCostEnum,
|
|
6
|
+
resourceConsumptionSchema,
|
|
7
|
+
resourceSchema
|
|
8
|
+
} from "./chunk-YLL2QXY2.js";
|
|
9
|
+
import {
|
|
10
|
+
choiceSchema
|
|
11
|
+
} from "./chunk-IWRVOCT5.js";
|
|
12
|
+
import {
|
|
13
|
+
featureEffectSchema
|
|
14
|
+
} from "./chunk-X3D6LXUU.js";
|
|
15
|
+
|
|
16
|
+
// src/schemas/feature-schema.ts
|
|
17
|
+
import { z as z2 } from "zod";
|
|
18
|
+
|
|
19
|
+
// src/schemas/attack-schema.ts
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
var abilityEnum = z.enum(["str", "dex", "con", "int", "wis", "cha"]);
|
|
22
|
+
var attackSchema = z.object({
|
|
23
|
+
name: z.string().min(1),
|
|
24
|
+
type: z.enum(["melee", "ranged", "spell"]),
|
|
25
|
+
ability: z.union([abilityEnum, z.array(abilityEnum).nonempty()]).optional(),
|
|
26
|
+
damage: z.string().optional(),
|
|
27
|
+
damage_type: z.string().optional(),
|
|
28
|
+
action: z.enum(["action", "bonus-action", "reaction"]).optional(),
|
|
29
|
+
properties: z.array(z.string()).optional(),
|
|
30
|
+
range: z.object({
|
|
31
|
+
normal: z.number().int().nonnegative().optional(),
|
|
32
|
+
long: z.number().int().nonnegative().optional(),
|
|
33
|
+
reach: z.number().int().nonnegative().optional()
|
|
34
|
+
}).optional(),
|
|
35
|
+
condition: z.string().optional(),
|
|
36
|
+
// R4-G6 §5: declared on the `Attack` interface since the monster port, stripped by this schema until now.
|
|
37
|
+
// Monsters are the only carriers (8,176 bonuses, 1,796 extra-damage riders in the converter corpus).
|
|
38
|
+
bonus: z.number().optional(),
|
|
39
|
+
extra_damage: z.object({ dice: z.string(), type: z.string() }).optional()
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// src/schemas/feature-schema.ts
|
|
43
|
+
var companionSchema = z2.object({
|
|
44
|
+
statblock: z2.string().optional(),
|
|
45
|
+
statblock_wikilink: z2.string().nullable().optional(),
|
|
46
|
+
count: z2.string().optional(),
|
|
47
|
+
count_scaling: z2.array(z2.object({
|
|
48
|
+
level: z2.number().optional(),
|
|
49
|
+
count: z2.string().optional(),
|
|
50
|
+
source_quote: z2.string().optional()
|
|
51
|
+
})).optional(),
|
|
52
|
+
shares_action_economy: z2.boolean().optional(),
|
|
53
|
+
turn_order: z2.string().optional(),
|
|
54
|
+
turn_order_source_quote: z2.string().nullable().optional(),
|
|
55
|
+
proficiency_bonus_scaled: z2.array(z2.object({
|
|
56
|
+
stat: z2.string().optional(),
|
|
57
|
+
formula: z2.string().optional(),
|
|
58
|
+
source_quote: z2.string().optional()
|
|
59
|
+
})).optional(),
|
|
60
|
+
lifecycle: z2.object({
|
|
61
|
+
created_by: z2.string().optional(),
|
|
62
|
+
recreate_on: z2.string().nullable().optional(),
|
|
63
|
+
previous_instance_perishes: z2.boolean().nullable().optional(),
|
|
64
|
+
vanishes_if_owner_dies: z2.boolean().nullable().optional(),
|
|
65
|
+
vanishes_after: z2.string().nullable().optional(),
|
|
66
|
+
source_quote: z2.string().optional()
|
|
67
|
+
}).nullable().optional(),
|
|
68
|
+
autonomy: z2.object({
|
|
69
|
+
when: z2.string().optional(),
|
|
70
|
+
behaviour: z2.string().optional(),
|
|
71
|
+
source_quote: z2.string().optional()
|
|
72
|
+
}).nullable().optional(),
|
|
73
|
+
variants: z2.array(z2.object({
|
|
74
|
+
axis: z2.string().optional(),
|
|
75
|
+
choices: z2.array(z2.string()).optional(),
|
|
76
|
+
chosen_at: z2.string().optional(),
|
|
77
|
+
source_quote: z2.string().optional()
|
|
78
|
+
})).optional(),
|
|
79
|
+
base_statblock: z2.string().nullable().optional(),
|
|
80
|
+
overrides: z2.array(z2.object({
|
|
81
|
+
field: z2.string().optional(),
|
|
82
|
+
value: z2.string().optional(),
|
|
83
|
+
source_quote: z2.string().optional()
|
|
84
|
+
})).optional(),
|
|
85
|
+
source_quote: z2.string().optional()
|
|
86
|
+
});
|
|
87
|
+
var featureObjectSchema = z2.lazy(
|
|
88
|
+
() => z2.object({
|
|
89
|
+
id: z2.string().min(1).optional(),
|
|
90
|
+
name: z2.string().min(1),
|
|
91
|
+
description: z2.string().optional(),
|
|
92
|
+
entries: z2.array(z2.string()).optional(),
|
|
93
|
+
choices: z2.array(choiceSchema).optional(),
|
|
94
|
+
grants_resource: z2.string().optional(),
|
|
95
|
+
consumes: resourceConsumptionSchema.optional(),
|
|
96
|
+
attacks: z2.array(attackSchema).optional(),
|
|
97
|
+
action: z2.enum(["action", "bonus-action", "reaction", "free", "special"]).optional(),
|
|
98
|
+
trigger: z2.string().optional(),
|
|
99
|
+
dc_formula: z2.string().optional(),
|
|
100
|
+
effects: z2.array(featureEffectSchema).optional(),
|
|
101
|
+
sub_features: z2.array(featureSchemaTyped).optional(),
|
|
102
|
+
resources: z2.array(resourceSchema).optional(),
|
|
103
|
+
// Phase 3 activatable buffs: a feature flagged `activatable` folds its effects
|
|
104
|
+
// only while its id is present in state.active_buffs. `passive` renders a tag;
|
|
105
|
+
// `duration` is a static label (no live countdown this phase).
|
|
106
|
+
activatable: z2.boolean().optional(),
|
|
107
|
+
passive: z2.boolean().optional(),
|
|
108
|
+
duration: durationSchema.optional(),
|
|
109
|
+
// Converter feature-level keys (164 census strip rows, class 80 + subclass 84; counts per finding 2):
|
|
110
|
+
class_source: z2.string().min(1).optional(),
|
|
111
|
+
// class 646 + subclass 1616 leaves
|
|
112
|
+
rendering_hint: z2.string().optional(),
|
|
113
|
+
// always "" — NEVER .min(1)
|
|
114
|
+
subclass_short_name: z2.string().min(1).optional(),
|
|
115
|
+
// 1616
|
|
116
|
+
subclass_source: z2.string().min(1).optional(),
|
|
117
|
+
// 1616
|
|
118
|
+
header: z2.number().optional(),
|
|
119
|
+
// 1,237 carriers, ALL numbers
|
|
120
|
+
is_class_feature_variant: z2.boolean().optional(),
|
|
121
|
+
gain_subclass_feature: z2.boolean().optional(),
|
|
122
|
+
gain_subclass_feature_has_content: z2.boolean().optional(),
|
|
123
|
+
type: z2.string().min(1).optional(),
|
|
124
|
+
// values {"inset","item"}
|
|
125
|
+
recharge: z2.object({
|
|
126
|
+
// ONE class doc (UA Mystic), FOUR features, ALL
|
|
127
|
+
type: z2.enum(["recharge_on_roll", "per_day", "per_short_rest", "per_long_rest"]),
|
|
128
|
+
// {type:"per_day",param:1..4}
|
|
129
|
+
param: z2.number()
|
|
130
|
+
// tree-wide nested-recharge type census: exactly these
|
|
131
|
+
}).optional(),
|
|
132
|
+
// four members (gate1-r1 f9) = the EXISTING FeatureRecharge type
|
|
133
|
+
// SRD-BUNDLE-side keys (the converter emits NEITHER at feature level; finding 14 — our own
|
|
134
|
+
// generator emits them and featureSchema strips them, losing the SRD Dragonborn Breath Weapon's
|
|
135
|
+
// action cost and save DC). MAPPED since R4-G3a §10: `action_cost` is aliased onto `action` by
|
|
136
|
+
// the race / class / subclass parsers, and `save` renders as the feature card's Save line
|
|
137
|
+
// (`dc_formula` is echoed as TEXT, never evaluated).
|
|
138
|
+
action_cost: actionCostEnum.optional(),
|
|
139
|
+
// ONE vocabulary, imported from schemas/resource-schema
|
|
140
|
+
// (no cycle: resource-schema imports neither; two spellings of a closed enum is the G1a
|
|
141
|
+
// "fifth enum consumer" drift — gate1-r2 NEW-12)
|
|
142
|
+
save: z2.object({ ability: z2.string().min(1), dc_formula: z2.string().min(1) }).optional(),
|
|
143
|
+
// R4-G5 §7 (ruling G5-UR2): DECLARED so the class / subclass / race parsers stop stripping it on the day the
|
|
144
|
+
// G7 re-emit lands. The converter withholds it today (`P9_NOT_EMITTED_FIELDS`), so 0 converted and 0 bundle
|
|
145
|
+
// documents carry it and the census cannot see this key in either state (§12.1); the 47-block cache
|
|
146
|
+
// round-trip in `tests/feature-schema-widening.test.ts` is its instrument.
|
|
147
|
+
companion: companionSchema.optional()
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
var featureSchemaTyped = featureObjectSchema.refine(
|
|
151
|
+
(f) => f.description !== void 0 || f.entries !== void 0 && f.entries.length > 0,
|
|
152
|
+
{ message: "feature requires either description or non-empty entries" }
|
|
153
|
+
);
|
|
154
|
+
var featureSchema = featureSchemaTyped;
|
|
155
|
+
|
|
156
|
+
export {
|
|
157
|
+
featureObjectSchema,
|
|
158
|
+
featureSchema
|
|
159
|
+
};
|
|
@@ -203,6 +203,10 @@ var CONDITIONS = [
|
|
|
203
203
|
"Unconscious"
|
|
204
204
|
];
|
|
205
205
|
var STANDARD_SENSES = ["Blindsight", "Darkvision", "Tremorsense", "Truesight"];
|
|
206
|
+
var STANDARD_ACTIONS = {
|
|
207
|
+
"2014": ["Attack", "Cast a Spell", "Dash", "Disengage", "Dodge", "Help", "Hide", "Ready", "Search", "Use an Object"],
|
|
208
|
+
"2024": ["Attack", "Dash", "Disengage", "Dodge", "Help", "Hide", "Influence", "Magic", "Ready", "Search", "Study", "Utilize"]
|
|
209
|
+
};
|
|
206
210
|
var ALIGNMENT_ETHICAL = ["Lawful", "Neutral", "Chaotic", "Unaligned", "Any"];
|
|
207
211
|
var ALIGNMENT_MORAL = ["Good", "Neutral", "Evil"];
|
|
208
212
|
var ALL_SECTIONS = [
|
|
@@ -214,6 +218,20 @@ var ALL_SECTIONS = [
|
|
|
214
218
|
"Lair Actions",
|
|
215
219
|
"Mythic Actions"
|
|
216
220
|
];
|
|
221
|
+
var ABILITY_NAME_TO_KEY = {
|
|
222
|
+
strength: "str",
|
|
223
|
+
dexterity: "dex",
|
|
224
|
+
constitution: "con",
|
|
225
|
+
intelligence: "int",
|
|
226
|
+
wisdom: "wis",
|
|
227
|
+
charisma: "cha"
|
|
228
|
+
};
|
|
229
|
+
function normalizeAbility(value) {
|
|
230
|
+
var _a;
|
|
231
|
+
const k = value.trim().toLowerCase();
|
|
232
|
+
if (ABILITY_KEYS.includes(k)) return k;
|
|
233
|
+
return (_a = ABILITY_NAME_TO_KEY[k]) != null ? _a : null;
|
|
234
|
+
}
|
|
217
235
|
|
|
218
236
|
export {
|
|
219
237
|
CR_PROFICIENCY,
|
|
@@ -229,7 +247,10 @@ export {
|
|
|
229
247
|
DAMAGE_NONMAGICAL_VARIANTS,
|
|
230
248
|
CONDITIONS,
|
|
231
249
|
STANDARD_SENSES,
|
|
250
|
+
STANDARD_ACTIONS,
|
|
232
251
|
ALIGNMENT_ETHICAL,
|
|
233
252
|
ALIGNMENT_MORAL,
|
|
234
|
-
ALL_SECTIONS
|
|
253
|
+
ALL_SECTIONS,
|
|
254
|
+
ABILITY_NAME_TO_KEY,
|
|
255
|
+
normalizeAbility
|
|
235
256
|
};
|
|
@@ -5,23 +5,31 @@ import {
|
|
|
5
5
|
actionCostEnum,
|
|
6
6
|
resetTriggerEnum,
|
|
7
7
|
resourceConsumptionSchema
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-YLL2QXY2.js";
|
|
9
9
|
import {
|
|
10
10
|
featureEffectSchema
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-X3D6LXUU.js";
|
|
12
|
+
import {
|
|
13
|
+
additionalSpellsEntrySchema,
|
|
14
|
+
imageField,
|
|
15
|
+
progressionSchema
|
|
16
|
+
} from "./chunk-UA5KS2PL.js";
|
|
12
17
|
|
|
13
18
|
// src/optional-feature/optional-feature.schema.ts
|
|
14
19
|
import { z } from "zod";
|
|
15
20
|
var editionEnum = z.enum(["2014", "2024"]);
|
|
16
21
|
var abilityEnum = z.enum(["str", "dex", "con", "int", "wis", "cha"]);
|
|
17
22
|
var wikilinkRegex = /^\[\[[^[\]]+\]\]$/;
|
|
23
|
+
var namedRefSchema = z.object({ name: z.string().min(1), source: z.string().min(1).optional(), visible_stats: z.boolean().optional() });
|
|
18
24
|
var prerequisiteSchema = z.discriminatedUnion("kind", [
|
|
19
|
-
z.object({ kind: z.literal("level"), min: z.number().int().positive() }),
|
|
25
|
+
z.object({ kind: z.literal("level"), min: z.number().int().positive(), class: namedRefSchema.optional(), subclass: namedRefSchema.optional() }),
|
|
20
26
|
z.object({ kind: z.literal("spell-known"), spell: z.string().regex(wikilinkRegex) }),
|
|
21
27
|
z.object({ kind: z.literal("pact"), pact: z.enum(["tome", "blade", "chain", "talisman"]) }),
|
|
22
28
|
z.object({ kind: z.literal("class"), class: z.string().regex(wikilinkRegex) }),
|
|
23
29
|
z.object({ kind: z.literal("ability"), ability: abilityEnum, min: z.number().int().positive() }),
|
|
24
|
-
z.object({ kind: z.literal("other"), detail: z.string() })
|
|
30
|
+
z.object({ kind: z.literal("other"), detail: z.string() }),
|
|
31
|
+
z.object({ kind: z.literal("optionalfeature"), optionalfeature: z.string().regex(wikilinkRegex) }),
|
|
32
|
+
z.object({ kind: z.literal("spell-choose"), choose: z.string().min(1), entry: z.string().optional(), entry_summary: z.string().optional() })
|
|
25
33
|
]);
|
|
26
34
|
var usesSchema = z.object({
|
|
27
35
|
max: z.union([z.number(), z.string()]),
|
|
@@ -44,7 +52,20 @@ var optionalFeatureEntitySchema = z.object({
|
|
|
44
52
|
passive: z.boolean().optional(),
|
|
45
53
|
// Phase 3 activatable buffs: an activatable boon folds its effects only while
|
|
46
54
|
// its slug is present in state.active_buffs (toggled in the PoolTab).
|
|
47
|
-
activatable: z.boolean().optional()
|
|
55
|
+
activatable: z.boolean().optional(),
|
|
56
|
+
rendering_hint: z.string().optional(),
|
|
57
|
+
// the ONE root with load-bearing VALUES (88 records, G4's)
|
|
58
|
+
additional_spells: z.array(additionalSpellsEntrySchema).optional(),
|
|
59
|
+
// 54
|
|
60
|
+
is_class_feature_variant: z.boolean().optional(),
|
|
61
|
+
// 25
|
|
62
|
+
has_fluff_images: z.boolean().optional(),
|
|
63
|
+
// 2
|
|
64
|
+
feat_progression: z.array(progressionSchema).optional(),
|
|
65
|
+
// 1
|
|
66
|
+
optionalfeature_progression: z.array(progressionSchema).optional(),
|
|
67
|
+
// 1
|
|
68
|
+
image: imageField
|
|
48
69
|
});
|
|
49
70
|
|
|
50
71
|
export {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
slugify
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-6X4KUDMU.js";
|
|
4
4
|
|
|
5
5
|
// src/entities/base-item-resolver.ts
|
|
6
6
|
function unwrapWikilink(input) {
|
|
@@ -10,7 +10,11 @@ function unwrapWikilink(input) {
|
|
|
10
10
|
const pipe = inner.indexOf("|");
|
|
11
11
|
return pipe === -1 ? inner : inner.slice(0, pipe);
|
|
12
12
|
}
|
|
13
|
-
var FOLDER_TO_TYPE = {
|
|
13
|
+
var FOLDER_TO_TYPE = {
|
|
14
|
+
Weapons: "weapon",
|
|
15
|
+
Armor: "armor",
|
|
16
|
+
"Magic Items": "item"
|
|
17
|
+
};
|
|
14
18
|
function vaultPathToSlug(target) {
|
|
15
19
|
const segments = target.split("/").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
16
20
|
if (segments.length === 0) return "";
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// src/schemas/feature-effect-schema.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var abilityEnum = z.enum(["str", "dex", "con", "int", "wis", "cha"]);
|
|
4
|
+
var subjectField = z.string().min(1).optional();
|
|
5
|
+
var conditionField = z.string().optional();
|
|
6
|
+
var featureEffectSchema = z.discriminatedUnion("kind", [
|
|
7
|
+
z.object({ kind: z.literal("initiative-bonus"), value: z.number().int(), condition: conditionField, subject: subjectField }),
|
|
8
|
+
// `condition` here is the condition NAME (frightened, poisoned); the qualifier is `while`.
|
|
9
|
+
z.object({ kind: z.literal("immune-condition"), condition: z.string().min(1), while: z.string().optional(), subject: subjectField }),
|
|
10
|
+
z.object({ kind: z.literal("resistance"), damage_type: z.string().min(1), condition: conditionField, subject: subjectField }),
|
|
11
|
+
z.object({ kind: z.literal("hp-per-level-bonus"), value: z.number().int(), condition: conditionField, subject: subjectField }),
|
|
12
|
+
z.object({
|
|
13
|
+
kind: z.literal("speed-bonus"),
|
|
14
|
+
mode: z.enum(["walk", "fly", "swim", "climb", "burrow"]),
|
|
15
|
+
value: z.number().int(),
|
|
16
|
+
// when true, value is an absolute floor (Math.max), not additive; currently
|
|
17
|
+
// only surfaced for mode:"walk" · other modes have no derived speed yet.
|
|
18
|
+
set: z.boolean().optional(),
|
|
19
|
+
// R4-G7 §7.3: the WHOLE progression lives inside the effect, because the overlay's `class_features`
|
|
20
|
+
// map is keyed `<class>:<feature-slug>` with no level component and, after §7.1's fold, the sheet
|
|
21
|
+
// reads exactly ONE copy of a repeated feature. The engine takes the highest entry at or below the
|
|
22
|
+
// effect's OWN source level (`levelFor`), and applies it to the additive bonus and to a `set` floor
|
|
23
|
+
// alike. The Monk's Unarmored Movement is the shipped carrier shape.
|
|
24
|
+
scales_at: z.array(z.object({ level: z.number().int().min(1).max(20), value: z.number().int() })).optional(),
|
|
25
|
+
condition: conditionField,
|
|
26
|
+
subject: subjectField
|
|
27
|
+
}),
|
|
28
|
+
z.object({
|
|
29
|
+
// The attunement CAP, as data. `value` is an absolute cap ("you can now attune to up to four"),
|
|
30
|
+
// never an increment, so two grants MAX rather than sum and a grant can never lower the cap:
|
|
31
|
+
// recalc folds it as Math.max(3, granted). `scales_at` carries a whole progression on ONE effect —
|
|
32
|
+
// the `speed-bonus` / `extra-attack` shape (R4-G7 §7.3) — because the overlay has no level axis and
|
|
33
|
+
// the Artificer's three steps (10 -> 4, 14 -> 5, 18 -> 6) would otherwise need three separate
|
|
34
|
+
// features, one of which is embedded prose inside another feature's description.
|
|
35
|
+
kind: z.literal("attunement-limit"),
|
|
36
|
+
value: z.number().int().positive(),
|
|
37
|
+
scales_at: z.array(z.object({ level: z.number().int().min(1).max(20), value: z.number().int().positive() })).optional(),
|
|
38
|
+
condition: conditionField,
|
|
39
|
+
subject: subjectField
|
|
40
|
+
}),
|
|
41
|
+
z.object({
|
|
42
|
+
kind: z.literal("sense"),
|
|
43
|
+
type: z.enum(["darkvision", "blindsight", "tremorsense", "truesight"]),
|
|
44
|
+
range: z.number().int().nonnegative(),
|
|
45
|
+
condition: conditionField,
|
|
46
|
+
subject: subjectField
|
|
47
|
+
}),
|
|
48
|
+
// `condition` here is the condition NAME being applied.
|
|
49
|
+
z.object({
|
|
50
|
+
kind: z.literal("apply-condition"),
|
|
51
|
+
condition: z.string().min(1),
|
|
52
|
+
duration: z.string().optional(),
|
|
53
|
+
ends_on: z.array(z.string()).optional(),
|
|
54
|
+
save_repeat: z.object({ ability: z.string().min(1), timing: z.string().min(1) }).optional(),
|
|
55
|
+
subject: subjectField
|
|
56
|
+
}),
|
|
57
|
+
z.object({
|
|
58
|
+
kind: z.literal("damage-bonus"),
|
|
59
|
+
// intentionally an open string, NOT a damage-type enum: must accept the
|
|
60
|
+
// literal "chosen" (player-selected damage type at action time) alongside
|
|
61
|
+
// canonical damage types. Do not tighten to an enum.
|
|
62
|
+
damage_type: z.string().min(1),
|
|
63
|
+
amount: z.string().min(1),
|
|
64
|
+
applies_to: z.enum(["weapon", "spell", "all"]).optional(),
|
|
65
|
+
condition: conditionField,
|
|
66
|
+
subject: subjectField
|
|
67
|
+
}),
|
|
68
|
+
z.object({
|
|
69
|
+
kind: z.literal("proficiency"),
|
|
70
|
+
proficiency_type: z.enum(["skill", "tool", "language", "saving-throw", "armor", "weapon"]),
|
|
71
|
+
value: z.string().min(1),
|
|
72
|
+
expertise: z.boolean().optional(),
|
|
73
|
+
condition: conditionField,
|
|
74
|
+
subject: subjectField
|
|
75
|
+
}),
|
|
76
|
+
z.object({ kind: z.literal("ac-bonus"), value: z.number().int(), requires_armor: z.boolean().optional(), condition: conditionField, subject: subjectField }),
|
|
77
|
+
z.object({
|
|
78
|
+
kind: z.literal("unarmored-ac"),
|
|
79
|
+
abilities: z.array(abilityEnum),
|
|
80
|
+
base: z.number().int().optional(),
|
|
81
|
+
allow_shield: z.boolean().optional(),
|
|
82
|
+
condition: conditionField,
|
|
83
|
+
subject: subjectField
|
|
84
|
+
}),
|
|
85
|
+
z.object({
|
|
86
|
+
kind: z.literal("unarmed-strike"),
|
|
87
|
+
// R4-G7 §7.2: a bare die expression, `<count>d<faces>`. `resolveUnarmedStrike` averages the flat arm with
|
|
88
|
+
// /^(\d+)d(\d+)$/ and silently averages anything else as 1, so a modifier tail ("1d6+1") or a missing
|
|
89
|
+
// half ("d6", "1d") must refuse here rather than mis-average there. The `i` arm keeps an authored `1D6`.
|
|
90
|
+
dice: z.union([z.string().regex(/^\d+d\d+$/i), z.object({ column: z.string().min(1) })]).optional(),
|
|
91
|
+
abilities: z.array(abilityEnum).nonempty().optional(),
|
|
92
|
+
condition: conditionField,
|
|
93
|
+
subject: subjectField
|
|
94
|
+
}),
|
|
95
|
+
z.object({
|
|
96
|
+
kind: z.literal("weapon-ability"),
|
|
97
|
+
ability: z.union([abilityEnum, z.literal("spellcasting")]),
|
|
98
|
+
weapons: z.union([z.literal("chosen"), z.string(), z.array(z.string())]).optional(),
|
|
99
|
+
condition: conditionField,
|
|
100
|
+
subject: subjectField
|
|
101
|
+
}),
|
|
102
|
+
z.object({
|
|
103
|
+
kind: z.literal("roll-modifier"),
|
|
104
|
+
mode: z.enum(["advantage", "disadvantage", "reroll", "add-d4"]),
|
|
105
|
+
roll: z.enum(["ability-check", "saving-throw", "attack", "any"]),
|
|
106
|
+
scope: z.string().optional(),
|
|
107
|
+
condition: conditionField,
|
|
108
|
+
subject: subjectField
|
|
109
|
+
}),
|
|
110
|
+
z.object({
|
|
111
|
+
kind: z.literal("extra-attack"),
|
|
112
|
+
// `count` is EXTRA attacks, never total attacks: the engine renders `1 + count`.
|
|
113
|
+
count: z.number().int().positive(),
|
|
114
|
+
// R4-G7 §7.3, the `speed-bonus` twin: the SRD 5e Fighter's one Extra Attack feature carries its whole
|
|
115
|
+
// 5 / 11 / 20 progression here, because `bucketFeaturesByLevel` emits one overlay record into every
|
|
116
|
+
// `gained_at` bucket and a per-copy count cannot be authored.
|
|
117
|
+
scales_at: z.array(z.object({ level: z.number().int().min(1).max(20), count: z.number().int().positive() })).optional(),
|
|
118
|
+
condition: conditionField,
|
|
119
|
+
subject: subjectField
|
|
120
|
+
}),
|
|
121
|
+
z.object({
|
|
122
|
+
kind: z.literal("crit-range"),
|
|
123
|
+
min_roll: z.number().int().min(2).max(20),
|
|
124
|
+
applies_to: z.enum(["weapon", "spell", "all"]).optional(),
|
|
125
|
+
condition: conditionField,
|
|
126
|
+
subject: subjectField
|
|
127
|
+
}),
|
|
128
|
+
z.object({
|
|
129
|
+
kind: z.literal("reroll-damage"),
|
|
130
|
+
max_reroll: z.number().int().positive(),
|
|
131
|
+
applies_to: z.enum(["weapon", "spell", "all"]).optional(),
|
|
132
|
+
once_per_die: z.boolean().optional(),
|
|
133
|
+
condition: conditionField,
|
|
134
|
+
subject: subjectField
|
|
135
|
+
}),
|
|
136
|
+
z.object({ kind: z.literal("attack-rule"), flag: z.enum(["no-ranged-in-melee-disadvantage"]), condition: conditionField, subject: subjectField }),
|
|
137
|
+
// R4-G1a declared these seven converter-emitted arms with the engine folding NOTHING for them
|
|
138
|
+
// (spec D3). R4-G3a §3-§7 gives SIX of them semantics: immunity / vulnerability FOLD (defenses
|
|
139
|
+
// pane); temp-hp / heal / extra-action are still NOT folded, the plugin reads them raw as
|
|
140
|
+
// row-local captions; save-outcome FOLDS (save-chip tag rail).
|
|
141
|
+
// `ability-score-increase` folds FLAT at the fold since R4-G3b §4 (fixed-list arms only; `chosen` arms
|
|
142
|
+
// are the ASI slot's second encoding; `max` is unread by user ruling).
|
|
143
|
+
// Damage immunity / vulnerability. NOT `immune-condition`, which is condition immunity.
|
|
144
|
+
z.object({ kind: z.literal("immunity"), damage_type: z.string().min(1), condition: conditionField, subject: subjectField }),
|
|
145
|
+
z.object({ kind: z.literal("vulnerability"), damage_type: z.string().min(1), condition: conditionField, subject: subjectField }),
|
|
146
|
+
// `amount` is the open non-empty STRING `damage-bonus.amount` already uses: "2d6", "5", "your Artificer level".
|
|
147
|
+
z.object({ kind: z.literal("temp-hp"), amount: z.string().min(1), condition: conditionField, subject: subjectField }),
|
|
148
|
+
z.object({ kind: z.literal("heal"), amount: z.string().min(1), condition: conditionField, subject: subjectField }),
|
|
149
|
+
z.object({
|
|
150
|
+
kind: z.literal("ability-score-increase"),
|
|
151
|
+
// "chosen" follows `weapon-ability.weapons`; the list follows `unarmored-ac.abilities`.
|
|
152
|
+
abilities: z.union([z.literal("chosen"), z.array(abilityEnum).min(1)]),
|
|
153
|
+
amount: z.number().int().positive(),
|
|
154
|
+
// Required-WITH-NULL, never optional: "the prose stated no cap" and "the extractor forgot" must not look alike.
|
|
155
|
+
choose: z.number().int().positive().nullable(),
|
|
156
|
+
max: z.number().int().positive().nullable(),
|
|
157
|
+
condition: conditionField,
|
|
158
|
+
subject: subjectField
|
|
159
|
+
}).refine((e) => e.abilities === "chosen" === (e.choose !== null), {
|
|
160
|
+
path: ["choose"],
|
|
161
|
+
message: 'choose is required IFF abilities is "chosen": a fixed LIST is its own count'
|
|
162
|
+
}),
|
|
163
|
+
// `extra-action` is an additional ACTION on your turn; `extra-attack` is attacks WITHIN the Attack action.
|
|
164
|
+
z.object({
|
|
165
|
+
kind: z.literal("extra-action"),
|
|
166
|
+
count: z.number().int().positive(),
|
|
167
|
+
action_type: z.enum(["action", "bonus-action", "reaction"]),
|
|
168
|
+
condition: conditionField,
|
|
169
|
+
subject: subjectField
|
|
170
|
+
}),
|
|
171
|
+
// `save-outcome` remaps the OUTCOME; `roll-modifier` modifies the ROLL.
|
|
172
|
+
z.object({
|
|
173
|
+
kind: z.literal("save-outcome"),
|
|
174
|
+
ability: z.union([abilityEnum, z.literal("any")]),
|
|
175
|
+
on_success: z.enum(["none", "half", "full"]),
|
|
176
|
+
on_failure: z.enum(["none", "half", "full"]),
|
|
177
|
+
applies_to: z.string().nullable(),
|
|
178
|
+
condition: conditionField,
|
|
179
|
+
subject: subjectField
|
|
180
|
+
})
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
export {
|
|
184
|
+
featureEffectSchema
|
|
185
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src/feat/feat.category-codes.ts
|
|
2
|
+
var FEAT_CATEGORY_CODE_LABELS = {
|
|
3
|
+
D: "Dragonmark",
|
|
4
|
+
G: "General",
|
|
5
|
+
O: "Origin",
|
|
6
|
+
FS: "Fighting Style",
|
|
7
|
+
EB: "Epic Boon",
|
|
8
|
+
DG: "Dark Gift",
|
|
9
|
+
"FS:P": "Fighting Style",
|
|
10
|
+
"FS:R": "Fighting Style"
|
|
11
|
+
};
|
|
12
|
+
function featCategoryLabel(code) {
|
|
13
|
+
var _a;
|
|
14
|
+
return (_a = FEAT_CATEGORY_CODE_LABELS[code]) != null ? _a : code;
|
|
15
|
+
}
|
|
16
|
+
var FEAT_PROGRESSION_CATEGORY = {
|
|
17
|
+
FS: "fighting-style",
|
|
18
|
+
"FS:P": "fighting-style",
|
|
19
|
+
"FS:R": "fighting-style",
|
|
20
|
+
EB: "epic-boon",
|
|
21
|
+
O: "origin",
|
|
22
|
+
G: "general"
|
|
23
|
+
};
|
|
24
|
+
var FEATURE_TYPE_FOR_CATEGORY = {
|
|
25
|
+
"fighting-style": "fighting_style"
|
|
26
|
+
};
|
|
27
|
+
function featProgressionCategory(codes) {
|
|
28
|
+
for (const code of codes != null ? codes : []) {
|
|
29
|
+
if (Object.prototype.hasOwnProperty.call(FEAT_PROGRESSION_CATEGORY, code)) {
|
|
30
|
+
return FEAT_PROGRESSION_CATEGORY[code];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
FEAT_CATEGORY_CODE_LABELS,
|
|
38
|
+
featCategoryLabel,
|
|
39
|
+
FEAT_PROGRESSION_CATEGORY,
|
|
40
|
+
FEATURE_TYPE_FOR_CATEGORY,
|
|
41
|
+
featProgressionCategory
|
|
42
|
+
};
|
|
@@ -1,24 +1,8 @@
|
|
|
1
1
|
// src/pc/decision-recognizer.ts
|
|
2
2
|
var DECISION_SIGNAL = [/\bchoose (one|two|three|a|an)\b/i, /\bof your choice\b/i, /\byour choice of\b/i];
|
|
3
|
-
var
|
|
4
|
-
kind: "select-inline",
|
|
5
|
-
id: "asi-or-feat",
|
|
6
|
-
count: 1,
|
|
7
|
-
options: [
|
|
8
|
-
{
|
|
9
|
-
value: "asi",
|
|
10
|
-
label: "Ability Score Increase",
|
|
11
|
-
choices: [{ kind: "ability-points", id: "asi", points: 2, max_per: 2 }]
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
value: "feat",
|
|
15
|
-
label: "Feat",
|
|
16
|
-
choices: [{ kind: "select-entity", id: "feat", entity_type: "feat", count: 1 }]
|
|
17
|
-
}
|
|
18
|
-
]
|
|
19
|
-
};
|
|
3
|
+
var ASI_FEAT = { kind: "select-entity", id: "feat", entity_type: "feat", count: 1 };
|
|
20
4
|
var TABLE = {
|
|
21
|
-
"ability-score-improvement": [
|
|
5
|
+
"ability-score-improvement": [ASI_FEAT],
|
|
22
6
|
"expertise": [{ kind: "select-proficiency", id: "expertise", count: 2, domain: "skill", from_proficient: true, expertise: true }],
|
|
23
7
|
"fighting-style": [{ kind: "select-entity", id: "fighting-style", count: 1, entity_type: "optional-feature", where: { feature_type: "fighting_style", available_to: "self" } }]
|
|
24
8
|
};
|