@heroiclands/package-build 17.2.0 → 18.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1711 -0
- package/CONTENT.md +288 -72
- package/README.md +1 -1
- package/bin/content-build.mjs +218 -137
- package/bin/package-build.mjs +43 -0
- package/content-config.mjs +62 -67
- package/docs/content-format.md +768 -58
- package/engine/actor-compiler.mjs +586 -0
- package/engine/address-charset.mjs +11 -8
- package/engine/address-diff.mjs +266 -22
- package/engine/anchored-sections.mjs +83 -0
- package/engine/anchors.mjs +83 -0
- package/engine/base-compiler.mjs +148 -18
- package/engine/bundle-notes.mjs +276 -0
- package/engine/bundles.mjs +307 -0
- package/engine/code-fences.mjs +103 -0
- package/engine/compile-corpus.mjs +89 -0
- package/engine/content-address.mjs +267 -5
- package/engine/content-format-check.mjs +54 -11
- package/engine/content-format.mjs +37 -5
- package/engine/content-index.mjs +225 -127
- package/engine/content-links.mjs +185 -77
- package/engine/content-lint.mjs +232 -14
- package/engine/content-package.mjs +2 -1
- package/engine/content-tables.mjs +99 -3
- package/engine/document-subtypes.mjs +49 -6
- package/engine/field-reference.mjs +1 -1
- package/engine/field-spec.mjs +56 -6
- package/engine/folder-notes.mjs +470 -0
- package/engine/foreign-catalog.mjs +195 -4
- package/engine/{manifest-emit.mjs → foundry-entries.mjs} +55 -83
- package/engine/frontmatter-lint.mjs +245 -19
- package/engine/frontmatter.mjs +88 -8
- package/engine/generate.mjs +304 -13
- package/engine/helpers.mjs +293 -64
- package/engine/ids.mjs +137 -2
- package/engine/index-records.mjs +126 -0
- package/engine/index.mjs +23 -5
- package/engine/item-compiler.mjs +349 -0
- package/engine/item-docs.mjs +7 -1
- package/engine/item-registry.mjs +6 -0
- package/engine/journals.mjs +93 -27
- package/engine/macros.mjs +4 -2
- package/engine/metadata-index.mjs +495 -0
- package/engine/note-claims.mjs +146 -25
- package/engine/note-ids.mjs +112 -0
- package/engine/note-renames.mjs +134 -0
- package/engine/note-schemas.mjs +25 -0
- package/engine/note-vocabulary.mjs +110 -27
- package/engine/pack-config.mjs +4 -0
- package/engine/pack-router.mjs +23 -0
- package/engine/retired-fields.mjs +137 -3
- package/engine/scenes.mjs +66 -34
- package/engine/schema-check.mjs +58 -17
- package/engine/site-build.mjs +71 -12
- package/engine/site-index.mjs +20 -7
- package/engine/sql-tables.mjs +485 -0
- package/engine/subtype-registry.mjs +102 -0
- package/engine/system-block.mjs +96 -12
- package/engine/systems.mjs +201 -0
- package/engine/web-wikilinks.mjs +50 -20
- package/engine/wikilink-syntax.mjs +17 -8
- package/engine/wikilinks.mjs +124 -51
- package/engine/yaml-lint.mjs +282 -0
- package/hm3/actors.mjs +352 -0
- package/hm3/default-item-art.mjs +75 -0
- package/hm3/document-subtypes.mjs +134 -0
- package/hm3/index.mjs +56 -0
- package/hm3/item-builders.mjs +84 -0
- package/hm3/item-fields.mjs +180 -0
- package/hm3/items.mjs +96 -0
- package/hm3/template-priority.mjs +85 -0
- package/manifest.mjs +43 -2
- package/package.json +18 -4
- package/release.mjs +62 -7
- package/sohl/actors.mjs +33 -487
- package/sohl/being-info.mjs +16 -7
- package/sohl/default-item-art.mjs +14 -3
- package/sohl/document-subtypes.mjs +16 -10
- package/sohl/item-builders.mjs +14 -5
- package/sohl/item-fields.mjs +68 -7
- package/sohl/items.mjs +44 -258
- package/sohl/note-schemas.mjs +2 -2
- package/types/content-config.d.mts +21 -42
- package/types/engine/actor-compiler.d.mts +204 -0
- package/types/engine/address-charset.d.mts +11 -8
- package/types/engine/address-diff.d.mts +53 -5
- package/types/engine/anchored-sections.d.mts +21 -0
- package/types/engine/anchors.d.mts +20 -0
- package/types/engine/base-compiler.d.mts +17 -17
- package/types/engine/bundle-notes.d.mts +173 -0
- package/types/engine/bundles.d.mts +60 -0
- package/types/engine/code-fences.d.mts +43 -0
- package/types/engine/compile-corpus.d.mts +32 -0
- package/types/engine/content-address.d.mts +205 -5
- package/types/engine/content-format-check.d.mts +6 -2
- package/types/engine/content-format.d.mts +57 -1
- package/types/engine/content-index.d.mts +54 -49
- package/types/engine/content-links.d.mts +52 -7
- package/types/engine/content-lint.d.mts +10 -1
- package/types/engine/content-package.d.mts +2 -1
- package/types/engine/content-tables.d.mts +21 -39
- package/types/engine/document-subtypes.d.mts +37 -3
- package/types/engine/field-spec.d.mts +76 -5
- package/types/engine/folder-notes.d.mts +159 -0
- package/types/engine/foreign-catalog.d.mts +53 -0
- package/types/engine/{manifest-emit.d.mts → foundry-entries.d.mts} +2 -43
- package/types/engine/frontmatter-lint.d.mts +10 -2
- package/types/engine/frontmatter.d.mts +64 -0
- package/types/engine/generate.d.mts +38 -0
- package/types/engine/helpers.d.mts +94 -30
- package/types/engine/ids.d.mts +96 -0
- package/types/engine/index-records.d.mts +68 -0
- package/types/engine/index.d.mts +9 -3
- package/types/engine/item-compiler.d.mts +131 -0
- package/types/engine/journals.d.mts +47 -9
- package/types/engine/metadata-index.d.mts +226 -0
- package/types/engine/note-claims.d.mts +57 -11
- package/types/engine/note-ids.d.mts +38 -0
- package/types/engine/note-renames.d.mts +102 -0
- package/types/engine/note-vocabulary.d.mts +44 -8
- package/types/engine/retired-fields.d.mts +75 -0
- package/types/engine/scenes.d.mts +3 -2
- package/types/engine/schema-check.d.mts +25 -4
- package/types/engine/site-build.d.mts +4 -4
- package/types/engine/site-index.d.mts +1 -1
- package/types/engine/sql-tables.d.mts +185 -0
- package/types/engine/subtype-registry.d.mts +49 -0
- package/types/engine/system-block.d.mts +40 -1
- package/types/engine/systems.d.mts +106 -0
- package/types/engine/web-wikilinks.d.mts +4 -2
- package/types/engine/wikilink-syntax.d.mts +10 -3
- package/types/engine/wikilinks.d.mts +41 -13
- package/types/engine/yaml-lint.d.mts +107 -0
- package/types/hm3/actors.d.mts +48 -0
- package/types/hm3/default-item-art.d.mts +42 -0
- package/types/hm3/document-subtypes.d.mts +24 -0
- package/types/hm3/index.d.mts +7 -0
- package/types/hm3/item-builders.d.mts +11 -0
- package/types/hm3/item-fields.d.mts +12 -0
- package/types/hm3/items.d.mts +23 -0
- package/types/hm3/template-priority.d.mts +21 -0
- package/types/manifest.d.mts +22 -1
- package/types/release.d.mts +9 -4
- package/types/sohl/actors.d.mts +7 -74
- package/types/sohl/being-info.d.mts +8 -27
- package/types/sohl/default-item-art.d.mts +5 -3
- package/types/sohl/items.d.mts +17 -32
- package/engine/foreign-manifests.mjs +0 -126
- package/engine/kb-manifest.mjs +0 -490
- package/types/engine/foreign-manifests.d.mts +0 -43
- package/types/engine/kb-manifest.d.mts +0 -241
|
@@ -40,6 +40,15 @@
|
|
|
40
40
|
*
|
|
41
41
|
* Paths are fully resolved (`systems/sohl/...`) — the served path both layers
|
|
42
42
|
* need. If a new item type is added, add its default here.
|
|
43
|
+
*
|
|
44
|
+
* **The keys are SoHL *document* subtypes, not markdown note types.** The
|
|
45
|
+
* runtime reads this with `itemData.type`, which is a Foundry Item subtype, and
|
|
46
|
+
* that is the vocabulary a compendium document carries. The two vocabularies
|
|
47
|
+
* were the same string until #78 renamed three note types (`armorgear` →
|
|
48
|
+
* `armor`, and its two siblings), and this map stayed on the document side
|
|
49
|
+
* because that is the side the runtime cannot translate: `sohl/item-builders.mjs`
|
|
50
|
+
* asks SoHL's own note-type → subtype map before it looks art up here, so the
|
|
51
|
+
* build translates once and no second copy of those three rows exists.
|
|
43
52
|
*/
|
|
44
53
|
export const DEFAULT_ITEM_ART = {
|
|
45
54
|
affiliation: "systems/sohl/assets/icons/noun/shield.svg",
|
|
@@ -58,7 +67,8 @@ export const DEFAULT_ITEM_ART = {
|
|
|
58
67
|
};
|
|
59
68
|
|
|
60
69
|
/**
|
|
61
|
-
* The default art path for an item
|
|
70
|
+
* The default art path for an item **document subtype**, or throw when it is
|
|
71
|
+
* unknown —
|
|
62
72
|
* the build's fail-fast contract, so an unrecognized type is never silently
|
|
63
73
|
* defaulted (aborting the pack build rather than shipping a mismatched icon).
|
|
64
74
|
*
|
|
@@ -66,8 +76,9 @@ export const DEFAULT_ITEM_ART = {
|
|
|
66
76
|
* {@link DEFAULT_ITEM_ART} directly and fall back to Foundry's default instead
|
|
67
77
|
* of calling this — see `SohlItem.getDefaultArtwork`.
|
|
68
78
|
*
|
|
69
|
-
* @param {string} type -
|
|
70
|
-
*
|
|
79
|
+
* @param {string} type - The SoHL Item document subtype — `armorgear`, not the
|
|
80
|
+
* `armor` note type that compiles into one.
|
|
81
|
+
* @returns {string} the default image path for that subtype.
|
|
71
82
|
*/
|
|
72
83
|
export function defaultItemArt(type) {
|
|
73
84
|
if (!(type in DEFAULT_ITEM_ART)) {
|
|
@@ -28,13 +28,19 @@
|
|
|
28
28
|
* silently. Sixteen lines of data are the price of the two vocabularies being
|
|
29
29
|
* separately stated.
|
|
30
30
|
*
|
|
31
|
-
* **
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
31
|
+
* **Three rows are no longer the identity, and that is what the map was for.**
|
|
32
|
+
* `armor`, `concoction` and `projectile` were renamed from the `…gear`
|
|
33
|
+
* spellings that named SoHL's *document* subtype rather than the thing the note
|
|
34
|
+
* is about (#78); the subtypes themselves did not move, so those three rows now
|
|
35
|
+
* say two different words where they used to say one twice. Landing the rename
|
|
36
|
+
* cost exactly what having the map first promised it would: a data change on
|
|
37
|
+
* three lines here, and no mechanism change anywhere.
|
|
38
|
+
*
|
|
39
|
+
* The old spellings are still read — {@link RENAMED_TYPES} normalises them
|
|
40
|
+
* before this map is consulted — so a tree that has not swept its 30,741
|
|
41
|
+
* embedded `(type, shortcode)` references compiles byte-identically. `weapon`
|
|
42
|
+
* is deliberately **not** among them: both systems call that document a
|
|
43
|
+
* `weapongear`, so the name says nothing SoHL-specific and #78 left it alone.
|
|
38
44
|
*
|
|
39
45
|
* **What this map is not.** It says which document a note becomes, never what
|
|
40
46
|
* that document contains: the `system` block comes from the item registry's
|
|
@@ -65,14 +71,14 @@ export const SOHL_DOCUMENT_SUBTYPES = defineDocumentSubtypes({
|
|
|
65
71
|
types: {
|
|
66
72
|
affiliation: { document: "Item", subType: "affiliation" },
|
|
67
73
|
affliction: { document: "Item", subType: "affliction" },
|
|
68
|
-
|
|
74
|
+
armor: { document: "Item", subType: "armorgear" },
|
|
69
75
|
attribute: { document: "Item", subType: "attribute" },
|
|
70
|
-
|
|
76
|
+
concoction: { document: "Item", subType: "concoctiongear" },
|
|
71
77
|
containergear: { document: "Item", subType: "containergear" },
|
|
72
78
|
miscgear: { document: "Item", subType: "miscgear" },
|
|
73
79
|
mystery: { document: "Item", subType: "mystery" },
|
|
74
80
|
mysticalability: { document: "Item", subType: "mysticalability" },
|
|
75
|
-
|
|
81
|
+
projectile: { document: "Item", subType: "projectilegear" },
|
|
76
82
|
skill: { document: "Item", subType: "skill" },
|
|
77
83
|
trauma: { document: "Item", subType: "trauma" },
|
|
78
84
|
weapongear: { document: "Item", subType: "weapongear" },
|
package/sohl/item-builders.mjs
CHANGED
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
49
|
import { defaultItemArt } from "./default-item-art.mjs";
|
|
50
|
+
import { documentSubtype } from "../engine/document-subtypes.mjs";
|
|
51
|
+
import { SOHL_DOCUMENT_SUBTYPES } from "./document-subtypes.mjs";
|
|
50
52
|
import { buildFromFields, readField } from "../engine/field-spec.mjs";
|
|
51
53
|
import { COMBAT_TECHNIQUE_STRIKE_MODE, ITEM_FIELDS } from "./item-fields.mjs";
|
|
52
54
|
|
|
@@ -82,11 +84,18 @@ const FINALIZERS = Object.freeze({
|
|
|
82
84
|
* throws and this module evaluates at import. A drift that a test used to catch
|
|
83
85
|
* is unrepresentable.
|
|
84
86
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
87
|
+
* **The art map is keyed by the document subtype**, which the runtime reads
|
|
88
|
+
* with a Foundry `Item`'s own `type`, so this asks SoHL's map what an `armor`
|
|
89
|
+
* note becomes before looking art up (#78). Translating on the build side is
|
|
90
|
+
* the only place it can happen: the runtime has no note in hand. Every SoHL
|
|
91
|
+
* Item row is one-to-one, so `documentSubtype` needs no frontmatter and cannot
|
|
92
|
+
* throw for a discriminator.
|
|
88
93
|
*
|
|
89
|
-
*
|
|
94
|
+
* Importing the art map and SoHL's own subtype map keeps this module a leaf —
|
|
95
|
+
* both are plain data, not the resolved configuration, and the cycle the module
|
|
96
|
+
* note above warns about is only ever closed by reading configuration back out.
|
|
97
|
+
*
|
|
98
|
+
* @param {string} type - The note type, and {@link ITEM_FIELDS}'s key.
|
|
90
99
|
* @returns {Readonly<{system: (fm: object) => object, img: string, fields: readonly object[]}>}
|
|
91
100
|
* The registry entry.
|
|
92
101
|
*/
|
|
@@ -96,7 +105,7 @@ function entryFor(type) {
|
|
|
96
105
|
const finalize = FINALIZERS[type];
|
|
97
106
|
return Object.freeze({
|
|
98
107
|
system: finalize ? (fm) => finalize(fm, build(fm)) : build,
|
|
99
|
-
img: defaultItemArt(type),
|
|
108
|
+
img: defaultItemArt(documentSubtype(SOHL_DOCUMENT_SUBTYPES, type, {}) ?? type),
|
|
100
109
|
fields,
|
|
101
110
|
});
|
|
102
111
|
}
|
package/sohl/item-fields.mjs
CHANGED
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
resolveCharges,
|
|
52
52
|
resolveRelation,
|
|
53
53
|
resolveSkillAptitudes,
|
|
54
|
-
|
|
54
|
+
sohlSystemField,
|
|
55
55
|
} from "../engine/frontmatter.mjs";
|
|
56
56
|
|
|
57
57
|
/* --------------------------------------------------------------------- */
|
|
@@ -115,6 +115,27 @@ const RELATION = Object.freeze({
|
|
|
115
115
|
read: (_raw, { fm }) => resolveRelation(fm, noteContext(fm, "affiliation")),
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* A list of references, each a bare shortcode.
|
|
120
|
+
*
|
|
121
|
+
* Blank entries are dropped rather than emitted: a list that has been edited
|
|
122
|
+
* down in a property editor keeps its empty rows, and an empty string is not a
|
|
123
|
+
* reference to anything. Absent reads as `[]`, because "refers to nothing" is a
|
|
124
|
+
* value here — a sovereign polity is subordinate to nobody — rather than an
|
|
125
|
+
* unset one.
|
|
126
|
+
*/
|
|
127
|
+
const SHORTCODE_LIST = Object.freeze({
|
|
128
|
+
shape: "list of shortcodes",
|
|
129
|
+
kind: "list",
|
|
130
|
+
read: (raw) =>
|
|
131
|
+
(Array.isArray(raw) ? raw
|
|
132
|
+
: raw == null || raw === "" ? []
|
|
133
|
+
: [raw]
|
|
134
|
+
)
|
|
135
|
+
.map((entry) => String(entry ?? "").trim())
|
|
136
|
+
.filter((entry) => entry !== ""),
|
|
137
|
+
});
|
|
138
|
+
|
|
118
139
|
/**
|
|
119
140
|
* Every element must carry a non-blank `shortcode`, unique on the weapon — the
|
|
120
141
|
* shortcode is the mode's identity. The list is otherwise emitted verbatim.
|
|
@@ -141,11 +162,14 @@ const STRIKE_MODES = Object.freeze({
|
|
|
141
162
|
* A projectile's impact die, read wherever the default of another impact field
|
|
142
163
|
* depends on it.
|
|
143
164
|
*
|
|
165
|
+
* Authored `impact.die`, stored `impactBase.die` — the one re-reading field
|
|
166
|
+
* whose two positions are spelled differently, so both are named (#126).
|
|
167
|
+
*
|
|
144
168
|
* @param {object} fm - The note's frontmatter.
|
|
145
169
|
* @returns {number} The die size, `0` when the projectile declares none.
|
|
146
170
|
*/
|
|
147
171
|
function impactDie(fm) {
|
|
148
|
-
return Number(
|
|
172
|
+
return Number(sohlSystemField(fm, "impactBase.die", 0, { legacyKey: "impact.die" })) || 0;
|
|
149
173
|
}
|
|
150
174
|
|
|
151
175
|
/** A strike mode discriminated by `type`, mandatory on a combat technique. */
|
|
@@ -281,12 +305,49 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
281
305
|
describe: "Standing within the society.",
|
|
282
306
|
},
|
|
283
307
|
{
|
|
284
|
-
|
|
285
|
-
|
|
308
|
+
// Plural because the field holds a map of many standings, one per
|
|
309
|
+
// affiliation — the singular was a misnomer every author read past
|
|
310
|
+
// (SoHL#1781).
|
|
311
|
+
name: "relations",
|
|
312
|
+
to: "relations",
|
|
286
313
|
...RELATION,
|
|
287
314
|
default: {},
|
|
288
315
|
describe: "How this society regards others: aligned, unaligned, rival or nemesis.",
|
|
289
316
|
},
|
|
317
|
+
{
|
|
318
|
+
// The organisational relation: which bodies this one answers to.
|
|
319
|
+
// A list, because an affiliation may sit under more than one at
|
|
320
|
+
// once — an arcane tradition within an order, say.
|
|
321
|
+
name: "parents",
|
|
322
|
+
to: "parents",
|
|
323
|
+
...SHORTCODE_LIST,
|
|
324
|
+
default: [],
|
|
325
|
+
describe: "Affiliations this one is subordinate to, by shortcode.",
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
// Not `capital` or `headquarters`: each fits about half the eleven
|
|
329
|
+
// subTypes, while a seat covers a polity, a guild, an order and a
|
|
330
|
+
// faith alike.
|
|
331
|
+
name: "seat",
|
|
332
|
+
to: "seat",
|
|
333
|
+
...BLANK_IS_NULL,
|
|
334
|
+
default: null,
|
|
335
|
+
describe: "Where the affiliation's authority sits, by place shortcode.",
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
// The geographic relation, kept apart from the organisational one
|
|
339
|
+
// above: `parents` is *subordinate to*, this is *holds sway over*.
|
|
340
|
+
//
|
|
341
|
+
// Authored plural and emitted singular, as the content format's
|
|
342
|
+
// mapping row states (`data.domains` → `system.domain`). The two
|
|
343
|
+
// spellings are deliberate rather than a slip, so the declaration
|
|
344
|
+
// carries both rather than either side guessing.
|
|
345
|
+
name: "domains",
|
|
346
|
+
to: "domain",
|
|
347
|
+
...SHORTCODE_LIST,
|
|
348
|
+
default: [],
|
|
349
|
+
describe: "Places this affiliation holds sway over, by shortcode.",
|
|
350
|
+
},
|
|
290
351
|
]),
|
|
291
352
|
|
|
292
353
|
affliction: Object.freeze([
|
|
@@ -355,7 +416,7 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
355
416
|
},
|
|
356
417
|
]),
|
|
357
418
|
|
|
358
|
-
|
|
419
|
+
armor: Object.freeze([
|
|
359
420
|
...GEAR_COMMON,
|
|
360
421
|
{
|
|
361
422
|
name: "material",
|
|
@@ -470,7 +531,7 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
470
531
|
},
|
|
471
532
|
]),
|
|
472
533
|
|
|
473
|
-
|
|
534
|
+
concoction: Object.freeze([
|
|
474
535
|
...GEAR_COMMON,
|
|
475
536
|
{
|
|
476
537
|
name: "subType",
|
|
@@ -613,7 +674,7 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
613
674
|
},
|
|
614
675
|
]),
|
|
615
676
|
|
|
616
|
-
|
|
677
|
+
projectile: Object.freeze([
|
|
617
678
|
...GEAR_COMMON,
|
|
618
679
|
{
|
|
619
680
|
name: "subType",
|
package/sohl/items.mjs
CHANGED
|
@@ -12,282 +12,68 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* **SoHL's Item pass** — the two things about compiling a note into a SoHL Item
|
|
16
|
+
* that are facts about SoHL rather than about the note format.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* Everything else lives in {@link module:engine/item-compiler}: claiming a
|
|
19
|
+
* note, looking its subtype up in the system's map, dispatching to the
|
|
20
|
+
* consumer's registry, merging the authored `sohl.system` block, checking what
|
|
21
|
+
* was emitted against the receiving schema, and writing the envelope. That was
|
|
22
|
+
* all here until a second system needed it (#139), and it reached its
|
|
23
|
+
* system-specific facts through one constant read off SoHL's own map — which is
|
|
24
|
+
* why lifting it cost a subclass rather than a rewrite.
|
|
22
25
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
* What stays:
|
|
27
|
+
*
|
|
28
|
+
* - **The map**, declared in `document-subtypes.mjs` and named here, which
|
|
29
|
+
* decides which notes this pass claims and what each one becomes (#79).
|
|
30
|
+
* - **`commonSystem`** — `shortcode`, `templatePriority`, `actionDefs`, `notes` and
|
|
31
|
+
* `docHtml`, which SoHL's compiler writes on every item of every type and no
|
|
32
|
+
* field declaration states.
|
|
28
33
|
*
|
|
29
34
|
* Not a standalone script — exports the `Items` compiler class, imported and
|
|
30
|
-
* driven by `
|
|
35
|
+
* driven by `engine/generate.mjs`.
|
|
31
36
|
*
|
|
32
|
-
*
|
|
33
|
-
* wikilinks, writing the JSON and counting errors — belongs to {@link sohl.utils.packs.BasePackCompiler}; this module
|
|
34
|
-
* states only what makes this pass its own (#1509).
|
|
37
|
+
* @module
|
|
35
38
|
*/
|
|
36
39
|
|
|
37
|
-
import
|
|
38
|
-
|
|
39
|
-
import {
|
|
40
|
-
sohlField,
|
|
41
|
-
resolveName,
|
|
42
|
-
resolveImg,
|
|
43
|
-
defaultStats,
|
|
44
|
-
systemArchetype,
|
|
45
|
-
} from "../engine/helpers.mjs";
|
|
46
|
-
import { BasePackCompiler } from "../engine/base-compiler.mjs";
|
|
47
|
-
// Per-type default art lives in one framework-free module shared with the
|
|
48
|
-
// runtime (`SohlItem.getDefaultArtwork`), so the two can't drift — see #932.
|
|
49
|
-
// It lives in the build package, not `src/`: a relative path out of the package
|
|
50
|
-
// resolves to garbage once this pipeline runs from `node_modules` (#1510).
|
|
51
|
-
import { journalPageId, splitPages } from "../engine/journals.mjs";
|
|
52
|
-
import { foundryPackageId } from "../engine/content-package.mjs";
|
|
53
|
-
import { itemDocEntryId, itemDocPointer } from "../engine/item-docs.mjs";
|
|
54
|
-
// The whitelist and the per-type `system` builders both come from the resolved
|
|
55
|
-
// configuration, so the types this pass claims and the builders it compiles
|
|
56
|
-
// them with are one table — the consuming repository's, not this package's
|
|
57
|
-
// (#1504/#1563).
|
|
58
|
-
import { itemTypes, itemBuilder, itemArt } from "../engine/item-registry.mjs";
|
|
59
|
-
// Which Foundry Item subtype a note's `type` compiles into. Looked up in the
|
|
60
|
-
// system's declared map, never inferred from the type itself (#79).
|
|
61
|
-
import { documentSubtype, subtypeRow } from "../engine/document-subtypes.mjs";
|
|
40
|
+
import { systemTemplatePriority } from "../engine/helpers.mjs";
|
|
41
|
+
import { SystemItemCompiler } from "../engine/item-compiler.mjs";
|
|
62
42
|
import { SOHL_DOCUMENT_SUBTYPES } from "./document-subtypes.mjs";
|
|
63
|
-
// The note-level `sohl:` block: `sohl.system` onto the document's `system`
|
|
64
|
-
// verbatim, and `sohl.img` / `sohl.effects` / `sohl.flags` overriding their
|
|
65
|
-
// shared top-level forms for this system alone (#58).
|
|
66
|
-
import { blockProperty, claimedPaths, mergeSystemData } from "../engine/system-block.mjs";
|
|
67
|
-
import { itemFields } from "../engine/item-registry.mjs";
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* The description an item carries: a pointer to its **item doc**, the
|
|
71
|
-
* JournalEntry the journals pass compiles this same body into (#1348).
|
|
72
|
-
*
|
|
73
|
-
* The prose is not rendered here at all. Carrying it would duplicate it onto
|
|
74
|
-
* every actor holding the item — 7.59 MB of copies across the actors pack, of
|
|
75
|
-
* which 133 KB was distinct — where a link is 60 bytes and always current. The
|
|
76
|
-
* two passes derive the target from the note's own id, so neither has to see
|
|
77
|
-
* the other's output; both split the *converted* markdown, so an H1 carrying a
|
|
78
|
-
* wikilink names the same page on both sides.
|
|
79
|
-
*
|
|
80
|
-
* An item with no prose points at nothing, exactly as the journals pass writes
|
|
81
|
-
* no entry for it.
|
|
82
|
-
*
|
|
83
|
-
* @param {string} markdown - The note body, tables expanded and wikilinks
|
|
84
|
-
* resolved.
|
|
85
|
-
* @param {object} fm - The note's frontmatter.
|
|
86
|
-
* @param {string} name - The item's name.
|
|
87
|
-
* @returns {string} The pointer, or "" for a note with no body.
|
|
88
|
-
*/
|
|
89
|
-
function itemDescription(markdown, fm, name) {
|
|
90
|
-
if (!String(markdown).trim()) return "";
|
|
91
|
-
const [leadPage] = splitPages(markdown, name);
|
|
92
|
-
const pageId = journalPageId(itemDocEntryId(fm.id), leadPage, 0);
|
|
93
|
-
return itemDocPointer(foundryPackageId(), fm.id, name, pageId);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Build the `system.*` fields shared by every item type:
|
|
98
|
-
* shortcode, archetype, actionDefs, notes, docHtml.
|
|
99
|
-
*
|
|
100
|
-
* @param {object} fm - The note's frontmatter.
|
|
101
|
-
* @param {string} description - The item's documentation pointer.
|
|
102
|
-
* @param {string} label - Human-readable context for error messages.
|
|
103
|
-
* @returns {object} The shared `system` fields.
|
|
104
|
-
*/
|
|
105
|
-
function commonSystem(fm, description, label) {
|
|
106
|
-
return {
|
|
107
|
-
shortcode: fm.shortcode,
|
|
108
|
-
// Required nullable number: a priority, or `null` for a document that
|
|
109
|
-
// is not an archetype (#126 / archetype contract #604).
|
|
110
|
-
archetype: systemArchetype(fm, label),
|
|
111
|
-
actionDefs: Array.isArray(fm.actionDefs) ? fm.actionDefs : [],
|
|
112
|
-
notes: "",
|
|
113
|
-
docHtml: description || "",
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/* -------------------------------------------------------------------- */
|
|
118
|
-
/* Synthesized Active Effects */
|
|
119
|
-
/* -------------------------------------------------------------------- */
|
|
120
|
-
|
|
121
|
-
/* -------------------------------------------------------------------- */
|
|
122
|
-
/* Compiler */
|
|
123
|
-
/* -------------------------------------------------------------------- */
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* The system this pass compiles for — the block its notes write, and the
|
|
127
|
-
* registry its builders come from.
|
|
128
|
-
*
|
|
129
|
-
* Read from the map rather than spelled here, so the block name, the subtype
|
|
130
|
-
* map and the registry key are one statement (#58/#79).
|
|
131
|
-
*
|
|
132
|
-
* @type {string}
|
|
133
|
-
*/
|
|
134
|
-
const SYSTEM = SOHL_DOCUMENT_SUBTYPES.block;
|
|
135
|
-
|
|
136
|
-
export class Items extends BasePackCompiler {
|
|
137
|
-
static id = "items";
|
|
138
|
-
static label = "item";
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* An Item **is** a system's data, so this pack takes only notes carrying
|
|
142
|
-
* this system's block (#58).
|
|
143
|
-
*/
|
|
144
|
-
static requiresSystemBlock = true;
|
|
145
43
|
|
|
44
|
+
export class Items extends SystemItemCompiler {
|
|
146
45
|
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
46
|
+
* SoHL's note-type → document-subtype map — the one declaration that says
|
|
47
|
+
* which block this pass reads, which notes it claims, and what each becomes
|
|
48
|
+
* (#58/#79).
|
|
150
49
|
*
|
|
151
|
-
* @type {
|
|
50
|
+
* @type {import("../engine/document-subtypes.mjs").DocumentSubtypeMap}
|
|
152
51
|
*/
|
|
153
|
-
|
|
52
|
+
static documentSubtypes = SOHL_DOCUMENT_SUBTYPES;
|
|
154
53
|
|
|
155
54
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* The whitelist is the consuming repository's `itemBuilders` keys (#1504),
|
|
159
|
-
* and the system's own map is a second filter on top of it: a type SoHL
|
|
160
|
-
* maps onto some *other* document class is not an item however a registry
|
|
161
|
-
* spells it, which is the "no wrongly-typed document" half of #79. A type
|
|
162
|
-
* the map does not name at all is left to the registry — see
|
|
163
|
-
* {@link Items#itemSubtype}.
|
|
55
|
+
* The `system.*` fields SoHL writes on every item, whatever its type:
|
|
56
|
+
* shortcode, templatePriority, actionDefs, notes, docHtml.
|
|
164
57
|
*
|
|
165
58
|
* @param {object} fm - The note's frontmatter.
|
|
166
|
-
* @
|
|
59
|
+
* @param {object} at - What the pass already knows about this note.
|
|
60
|
+
* @param {string} at.description - The pointer to the note's item doc.
|
|
61
|
+
* @param {string} at.label - Human-readable context for error messages.
|
|
62
|
+
* @returns {object} The shared `system` fields.
|
|
167
63
|
*/
|
|
168
|
-
|
|
169
|
-
if (!fm.type || !itemTypes().has(fm.type)) return false;
|
|
170
|
-
const row = subtypeRow(SOHL_DOCUMENT_SUBTYPES, fm.type);
|
|
171
|
-
return !row || row.document === "Item";
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* The Foundry Item subtype a note compiles into.
|
|
176
|
-
*
|
|
177
|
-
* **Looked up, not inferred.** For every type this system declares, the
|
|
178
|
-
* emitted subtype is the map's, so the note vocabulary and the document
|
|
179
|
-
* vocabulary are two separately-stated things rather than one string
|
|
180
|
-
* written twice (#79).
|
|
181
|
-
*
|
|
182
|
-
* **A type the map does not name belongs to the consumer**, and its
|
|
183
|
-
* registry entry is the declaration: a repository shipping an item type of
|
|
184
|
-
* its own writes it once, in the `itemBuilders` table of its
|
|
185
|
-
* `package-build.config.yaml`, and that key is what the document is a
|
|
186
|
-
* subtype of. That is an authored statement in the consumer's own
|
|
187
|
-
* configuration, not a coincidence inside this package's source — and
|
|
188
|
-
* refusing it here would silently drop every document of a type SoHL has
|
|
189
|
-
* no opinion about (#7/#1563).
|
|
190
|
-
*
|
|
191
|
-
* @param {object} fm - The note's frontmatter.
|
|
192
|
-
* @returns {string} The document's `type`.
|
|
193
|
-
*/
|
|
194
|
-
itemSubtype(fm) {
|
|
195
|
-
const declared = documentSubtype(SOHL_DOCUMENT_SUBTYPES, fm.type, fm, {
|
|
196
|
-
absPath: this.currentNote?.absPath,
|
|
197
|
-
});
|
|
198
|
-
return declared ?? fm.type;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** An item is named by its own type in the log, not by "item". */
|
|
202
|
-
noteLabel(fm) {
|
|
203
|
-
return fm.type;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Construct the full compendium envelope for one item, including
|
|
208
|
-
* synthesized Active Effects where applicable.
|
|
209
|
-
*
|
|
210
|
-
* @param {object} fm - The note's frontmatter.
|
|
211
|
-
* @param {string} markdown - The body, tables expanded and wikilinks
|
|
212
|
-
* resolved.
|
|
213
|
-
* @returns {object} The item document, keyed for the pack.
|
|
214
|
-
*/
|
|
215
|
-
buildEntry(fm, markdown) {
|
|
216
|
-
const type = fm.type;
|
|
217
|
-
const name = resolveName(fm);
|
|
218
|
-
const description = itemDescription(markdown, fm, name);
|
|
219
|
-
const id = fm.id;
|
|
220
|
-
const subType = this.itemSubtype(fm);
|
|
221
|
-
const system = {
|
|
222
|
-
...commonSystem(fm, description, `item "${name}"`),
|
|
223
|
-
...itemBuilder(type, SYSTEM)(fm),
|
|
224
|
-
};
|
|
225
|
-
// Whatever the note authors under `sohl.system`, at the DataModel's own
|
|
226
|
-
// paths. A path a declared field already writes is left to that field:
|
|
227
|
-
// its value came from the same authored place and went through the
|
|
228
|
-
// field's own coercion (#58).
|
|
229
|
-
mergeSystemData(system, fm, {
|
|
230
|
-
block: SYSTEM,
|
|
231
|
-
claimed: claimedPaths(itemFields(type, SYSTEM)),
|
|
232
|
-
});
|
|
233
|
-
this.reportUndeclaredSystemData(fm, SYSTEM, "Item", subType);
|
|
234
|
-
// And what *this* pass wrote on its own initiative — `shortcode`,
|
|
235
|
-
// `archetype`, `actionDefs`, `notes`, `docHtml` — which no field
|
|
236
|
-
// declaration states and so no other check can see (#155). Read off the
|
|
237
|
-
// assembled block, so a key added to `commonSystem` is checked without
|
|
238
|
-
// anyone remembering to list it.
|
|
239
|
-
this.reportEmittedSystemData(system, {
|
|
240
|
-
fm,
|
|
241
|
-
block: SYSTEM,
|
|
242
|
-
documentType: "Item",
|
|
243
|
-
subType,
|
|
244
|
-
type,
|
|
245
|
-
fields: itemFields(type, SYSTEM),
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
const effects = blockProperty(fm, SYSTEM, "effects");
|
|
249
|
-
const folderId = sohlField(fm, "folder", null);
|
|
250
|
-
const folder = this.folderResolver(folderId);
|
|
251
|
-
|
|
64
|
+
commonSystem(fm, { description, label }) {
|
|
252
65
|
return {
|
|
253
|
-
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
effects: Array.isArray(effects) ? [...effects] : [],
|
|
265
|
-
// Whatever the note authors, and nothing else. `archetype` used to
|
|
266
|
-
// be spliced in here as `flags.sohl.docArchetype`; it is a schema
|
|
267
|
-
// field now and sits in `system` (#126).
|
|
268
|
-
flags: blockProperty(fm, SYSTEM, "flags", {}),
|
|
269
|
-
_stats: this.stats,
|
|
270
|
-
ownership: { default: 0 },
|
|
271
|
-
folder,
|
|
272
|
-
_key: `!items!${id}`,
|
|
66
|
+
shortcode: fm.shortcode,
|
|
67
|
+
// Required nullable number: a priority, or `null` for a document
|
|
68
|
+
// that is not a template (#126 / archetype contract #604). The
|
|
69
|
+
// field was `system.archetype` until #266 / sohl#1836; the receiving
|
|
70
|
+
// schema declares only the new name, so the emitted key moves with
|
|
71
|
+
// it — an undeclared `system` key is discarded at construction
|
|
72
|
+
// without a warning.
|
|
73
|
+
templatePriority: systemTemplatePriority(fm, label),
|
|
74
|
+
actionDefs: Array.isArray(fm.actionDefs) ? fm.actionDefs : [],
|
|
75
|
+
notes: "",
|
|
76
|
+
docHtml: description || "",
|
|
273
77
|
};
|
|
274
78
|
}
|
|
275
|
-
|
|
276
|
-
/** @inheritdoc */
|
|
277
|
-
onCompiled(fm) {
|
|
278
|
-
this.counts[fm.type]++;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/** @inheritdoc */
|
|
282
|
-
reportCompiled(stats) {
|
|
283
|
-
log.info(`Compiled ${stats.compiled} items:`);
|
|
284
|
-
for (const [t, n] of Object.entries(this.counts)) {
|
|
285
|
-
if (n > 0) log.info(` ${t}: ${n}`);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/** @inheritdoc */
|
|
290
|
-
reportDetail(stats) {
|
|
291
|
-
log.debug(`Skipped ${stats.skippedOther} non-item file(s) (no recognized type)`);
|
|
292
|
-
}
|
|
293
79
|
}
|
package/sohl/note-schemas.mjs
CHANGED
|
@@ -244,7 +244,7 @@ const MAP_FIELDS = Object.freeze([
|
|
|
244
244
|
* @type {Readonly<Record<string, readonly import("../engine/field-spec.mjs").FieldSpec[]>>}
|
|
245
245
|
*/
|
|
246
246
|
const PRESENTATION_FIELDS = Object.freeze({
|
|
247
|
-
|
|
247
|
+
armor: Object.freeze([
|
|
248
248
|
{
|
|
249
249
|
name: "craft",
|
|
250
250
|
...MAP,
|
|
@@ -291,7 +291,7 @@ const PRESENTATION_FIELDS = Object.freeze({
|
|
|
291
291
|
"The craft that makes it — `{skill, secondary}` — for knowledgebase grouping.",
|
|
292
292
|
},
|
|
293
293
|
]),
|
|
294
|
-
|
|
294
|
+
projectile: Object.freeze([
|
|
295
295
|
{
|
|
296
296
|
name: "craft",
|
|
297
297
|
...MAP,
|