@heroiclands/package-build 9.0.0 → 10.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 +694 -0
- package/CONTENT.md +273 -13
- package/bin/content-build.mjs +437 -7
- package/content-config.mjs +259 -28
- package/docs/content-format.md +1418 -0
- package/engine/address-charset.mjs +62 -0
- package/engine/alias-index.mjs +153 -0
- package/engine/base-compiler.mjs +194 -4
- package/engine/content-address.mjs +4 -4
- package/engine/content-format-check.mjs +570 -0
- package/engine/content-format.mjs +253 -0
- package/engine/content-links.mjs +132 -56
- package/engine/content-lint.mjs +8 -1
- package/engine/diagnostics.mjs +33 -0
- package/engine/document-subtypes.mjs +440 -0
- package/engine/field-spec.mjs +49 -43
- package/engine/frontmatter-lint.mjs +343 -27
- package/engine/generate.mjs +32 -4
- package/engine/helpers.mjs +41 -29
- package/engine/ids.mjs +19 -1
- package/engine/index.mjs +15 -0
- package/engine/item-registry.mjs +72 -5
- package/engine/kb-manifest.mjs +36 -7
- package/engine/map-notes.mjs +34 -18
- package/engine/note-claims.mjs +383 -0
- package/engine/note-vocabulary.mjs +678 -0
- package/engine/pack-config.mjs +39 -22
- package/engine/pack-router.mjs +17 -6
- package/engine/prose-lint.mjs +55 -3
- package/engine/retired-fields.mjs +117 -3
- package/engine/scenes.mjs +19 -1
- package/engine/schema-check.mjs +347 -3
- package/engine/site-build.mjs +1 -1
- package/engine/site-index.mjs +38 -21
- package/engine/system-block.mjs +513 -0
- package/engine/web-wikilinks.mjs +112 -80
- package/engine/wikilink-syntax.mjs +30 -0
- package/engine/wikilinks.mjs +67 -51
- package/package.json +6 -2
- package/sohl/actors.mjs +249 -36
- package/sohl/document-subtypes.mjs +82 -0
- package/sohl/index.mjs +3 -0
- package/sohl/items.mjs +110 -14
- package/sohl/note-schemas.mjs +11 -7
- package/types/content-config.d.mts +48 -4
- package/types/engine/address-charset.d.mts +45 -0
- package/types/engine/alias-index.d.mts +122 -0
- package/types/engine/base-compiler.d.mts +132 -4
- package/types/engine/content-address.d.mts +2 -2
- package/types/engine/content-format-check.d.mts +163 -0
- package/types/engine/content-format.d.mts +101 -0
- package/types/engine/content-links.d.mts +16 -1
- package/types/engine/content-lint.d.mts +6 -0
- package/types/engine/diagnostics.d.mts +29 -0
- package/types/engine/document-subtypes.d.mts +233 -0
- package/types/engine/field-spec.d.mts +76 -23
- package/types/engine/frontmatter-lint.d.mts +47 -2
- package/types/engine/generate.d.mts +14 -1
- package/types/engine/helpers.d.mts +21 -13
- package/types/engine/ids.d.mts +10 -0
- package/types/engine/index.d.mts +5 -0
- package/types/engine/item-registry.d.mts +21 -2
- package/types/engine/kb-manifest.d.mts +35 -8
- package/types/engine/map-notes.d.mts +21 -11
- package/types/engine/note-claims.d.mts +113 -0
- package/types/engine/note-vocabulary.d.mts +251 -0
- package/types/engine/pack-config.d.mts +4 -3
- package/types/engine/pack-router.d.mts +4 -4
- package/types/engine/prose-lint.d.mts +6 -2
- package/types/engine/retired-fields.d.mts +73 -2
- package/types/engine/schema-check.d.mts +182 -0
- package/types/engine/system-block.d.mts +281 -0
- package/types/engine/web-wikilinks.d.mts +23 -12
- package/types/engine/wikilink-syntax.d.mts +29 -0
- package/types/sohl/actors.d.mts +62 -6
- package/types/sohl/document-subtypes.d.mts +14 -0
- package/types/sohl/index.d.mts +1 -0
- package/types/sohl/items.d.mts +21 -0
package/sohl/actors.mjs
CHANGED
|
@@ -15,12 +15,18 @@
|
|
|
15
15
|
* Actors pack compiler — produces JSON pack files for the "actors" Foundry
|
|
16
16
|
* compendium from markdown `being` notes in the `assets/content/` tree.
|
|
17
17
|
*
|
|
18
|
-
* One content type,
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* One content type today, and the actor subtype it produces is **declared**
|
|
19
|
+
* rather than assumed to be the same word: `sohl/document-subtypes.mjs` maps
|
|
20
|
+
* `being` → `Actor` / `being`, and this pass looks it up (#79). It read
|
|
21
|
+
* `ACTOR_VAULT_TYPE = "being"` here and emitted `type: "being"` several hundred
|
|
22
|
+
* lines below, which made the two vocabularies agree by coincidence.
|
|
22
23
|
*
|
|
23
|
-
*
|
|
24
|
+
* It was two content types — `character` and `creature` — which compiled to the
|
|
25
|
+
* same `being` with no branch anywhere between them; they were retired in
|
|
26
|
+
* SoHL#1580 and are now reported by `assertTypeNotRetired` in
|
|
27
|
+
* `engine/ids.mjs`.
|
|
28
|
+
*
|
|
29
|
+
* Each actor's embedded items are resolved by looking `(type, shortcode)` up
|
|
24
30
|
* against the generated JSON tree of every Item pack (built in prior items
|
|
25
31
|
* passes and named by the caller as `itemsSourceDirs`). All of them, because a
|
|
26
32
|
* repository may group its items into several Item packs (#1566) and a being
|
|
@@ -30,6 +36,14 @@
|
|
|
30
36
|
* resolved by `(type, shortcode)` and deep-merged with the entry's other
|
|
31
37
|
* properties. `sohl.skills` is ignored.
|
|
32
38
|
*
|
|
39
|
+
* **Those references are in the note vocabulary and the addresses are in the
|
|
40
|
+
* document's**, and the difference is stated rather than assumed away (#140):
|
|
41
|
+
* {@link itemAddress} keys a predefined item by the subtype its compiled
|
|
42
|
+
* document carries, and {@link Actors#embeddedSubtype} translates each
|
|
43
|
+
* authored reference forward through the system's map before the lookup. A
|
|
44
|
+
* reference that then resolves to nothing is a finding naming the note and the
|
|
45
|
+
* reference — never an item quietly missing from the compiled actor.
|
|
46
|
+
*
|
|
33
47
|
* Not a standalone script — exports the `Actors` compiler class, imported and
|
|
34
48
|
* driven by `packages/content-build/engine/generate.mjs` (via `npm run build:compiledb`). Must run
|
|
35
49
|
* after the items pass, since it reads the items pack's generated JSON tree.
|
|
@@ -50,27 +64,81 @@ import {
|
|
|
50
64
|
resolveName,
|
|
51
65
|
resolveImg,
|
|
52
66
|
defaultStats,
|
|
53
|
-
|
|
67
|
+
systemArchetype,
|
|
54
68
|
md,
|
|
55
69
|
} from "../engine/helpers.mjs";
|
|
56
70
|
import { emitDiagnostic } from "../engine/diagnostics.mjs";
|
|
57
71
|
import { openingMasteryLevel } from "./skill-base.mjs";
|
|
58
72
|
import { BasePackCompiler } from "../engine/base-compiler.mjs";
|
|
59
73
|
import { contentPackage } from "../engine/content-package.mjs";
|
|
74
|
+
// Which Foundry Actor subtype a note's `type` compiles into, and which note
|
|
75
|
+
// types are actors at all. Looked up in the system's declared map, never
|
|
76
|
+
// inferred from the type itself (#79).
|
|
77
|
+
import {
|
|
78
|
+
documentSubtype,
|
|
79
|
+
mapsNoteType,
|
|
80
|
+
noteTypesFor,
|
|
81
|
+
referencedSubtype,
|
|
82
|
+
} from "../engine/document-subtypes.mjs";
|
|
83
|
+
import { SOHL_DOCUMENT_SUBTYPES } from "./document-subtypes.mjs";
|
|
84
|
+
import { locateFrontmatterKey } from "../engine/retired-fields.mjs";
|
|
85
|
+
// The note-level `sohl:` block: `sohl.system` onto the document's `system`
|
|
86
|
+
// verbatim, and `sohl.img` / `sohl.effects` / `sohl.flags` overriding their
|
|
87
|
+
// shared top-level forms for this system alone (#58).
|
|
88
|
+
import { blockProperty, mergeSystemData } from "../engine/system-block.mjs";
|
|
60
89
|
|
|
61
90
|
/**
|
|
62
|
-
* The
|
|
63
|
-
*
|
|
91
|
+
* The system this pass compiles for — the block its notes write.
|
|
92
|
+
*
|
|
93
|
+
* Read from the map rather than spelled here, so the block name and the subtype
|
|
94
|
+
* map are one statement (#58/#79).
|
|
95
|
+
*
|
|
96
|
+
* @type {string}
|
|
64
97
|
*/
|
|
65
|
-
const
|
|
98
|
+
const SYSTEM = SOHL_DOCUMENT_SUBTYPES.block;
|
|
66
99
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
100
|
+
/**
|
|
101
|
+
* The note types this pass claims — every one the system's map sends to an
|
|
102
|
+
* `Actor`.
|
|
103
|
+
*
|
|
104
|
+
* Read from the map rather than restated as a constant. It *was* a constant:
|
|
105
|
+
* `ACTOR_VAULT_TYPE = "being"` sat here and `type: "being"` was emitted several
|
|
106
|
+
* hundred lines below, with nothing relating them, so a change to either
|
|
107
|
+
* followed the other only by coincidence (#79).
|
|
108
|
+
*
|
|
109
|
+
* @type {readonly string[]}
|
|
110
|
+
*/
|
|
111
|
+
const ACTOR_NOTE_TYPES = Object.freeze(noteTypesFor(SOHL_DOCUMENT_SUBTYPES, "Actor"));
|
|
112
|
+
|
|
113
|
+
// Default art per actor **subtype**, applied when frontmatter supplies no `img`
|
|
114
|
+
// / `portrait`. Beings default to the generic person icon; another subtype adds
|
|
115
|
+
// its own entry here as the map gains a row for it.
|
|
70
116
|
const DEFAULT_IMG = {
|
|
71
117
|
being: "systems/sohl/assets/icons/game-icons/delapouite/person.svg",
|
|
72
118
|
};
|
|
73
119
|
|
|
120
|
+
/**
|
|
121
|
+
* The default art for an actor subtype.
|
|
122
|
+
*
|
|
123
|
+
* Fail-fast, for the reason {@link itemArt} is: a subtype with no art would
|
|
124
|
+
* otherwise ship a document with no image and nothing said about it.
|
|
125
|
+
*
|
|
126
|
+
* @param {string} subType - The Foundry actor subtype.
|
|
127
|
+
* @returns {string} The default image path.
|
|
128
|
+
* @throws {Error} When this map pairs no art with the subtype.
|
|
129
|
+
*/
|
|
130
|
+
function defaultActorImg(subType) {
|
|
131
|
+
const img = /** @type {Record<string, string|undefined>} */ (DEFAULT_IMG)[subType];
|
|
132
|
+
if (!img) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`No default art for actor subtype "${subType}" — add an entry to ` +
|
|
135
|
+
`\`DEFAULT_IMG\` in sohl/actors.mjs, beside the map row that ` +
|
|
136
|
+
`introduced the subtype.`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return img;
|
|
140
|
+
}
|
|
141
|
+
|
|
74
142
|
/**
|
|
75
143
|
* Strip compendium-only fields from a predefined item before embedding it
|
|
76
144
|
* inside an actor's `items[]`. These fields belong on a top-level
|
|
@@ -155,11 +223,31 @@ function extractBodyAndMovement(fm) {
|
|
|
155
223
|
};
|
|
156
224
|
}
|
|
157
225
|
|
|
226
|
+
/**
|
|
227
|
+
* The key one predefined item is held under, and every place that spells it.
|
|
228
|
+
*
|
|
229
|
+
* **The vocabulary is the document's, not the note's** — `subType` is the
|
|
230
|
+
* Foundry Item subtype the compiled document carries, which is the only thing a
|
|
231
|
+
* compiled pack (or an extracted dependency catalogue) records about what an
|
|
232
|
+
* item *is*. A being's frontmatter addresses the same item in the *note*
|
|
233
|
+
* vocabulary, so a reference is translated forward through the system's map
|
|
234
|
+
* before it reaches this function; see {@link Actors#embeddedSubtype} for why
|
|
235
|
+
* the translation goes that way and not the other (#140).
|
|
236
|
+
*
|
|
237
|
+
* @param {string} subType - The Foundry Item subtype.
|
|
238
|
+
* @param {string} shortcode - The item's `system.shortcode`.
|
|
239
|
+
* @returns {string} The address, `subType:shortcode`.
|
|
240
|
+
*/
|
|
241
|
+
function itemAddress(subType, shortcode) {
|
|
242
|
+
return `${subType}:${shortcode}`;
|
|
243
|
+
}
|
|
244
|
+
|
|
158
245
|
/**
|
|
159
246
|
* Load every JSON file under each of `itemsSourceDirs`, returning one Map keyed
|
|
160
|
-
* by
|
|
161
|
-
* shortcode
|
|
162
|
-
* it is not part of the item
|
|
247
|
+
* by {@link itemAddress} — the compiled document's **subtype** and its
|
|
248
|
+
* `system.shortcode`. Folder docs and entries without a shortcode are skipped.
|
|
249
|
+
* The `_key` field is stripped from each entry — it is not part of the item
|
|
250
|
+
* data model.
|
|
163
251
|
*
|
|
164
252
|
* The directories are read as one address space, because a being names an item
|
|
165
253
|
* by `(type, shortcode)` and never by the pack it happens to ship in. Two local
|
|
@@ -213,7 +301,7 @@ function loadItemsMap(itemsSourceDirs, foreignSourceDirs = []) {
|
|
|
213
301
|
}
|
|
214
302
|
const shortcode = doc?.system?.shortcode;
|
|
215
303
|
if (!doc?.type || !shortcode) continue;
|
|
216
|
-
const address =
|
|
304
|
+
const address = itemAddress(doc.type, shortcode);
|
|
217
305
|
const owner = source.get(address);
|
|
218
306
|
if (owner && owner !== itemsSourceDir) {
|
|
219
307
|
throw new Error(
|
|
@@ -247,7 +335,7 @@ function loadItemsMap(itemsSourceDirs, foreignSourceDirs = []) {
|
|
|
247
335
|
}
|
|
248
336
|
const shortcode = doc?.system?.shortcode;
|
|
249
337
|
if (!doc?.type || !shortcode) continue;
|
|
250
|
-
const address =
|
|
338
|
+
const address = itemAddress(doc.type, shortcode);
|
|
251
339
|
if (map.has(address)) {
|
|
252
340
|
// Deliberate: this repository defines it, so its version wins.
|
|
253
341
|
if (source.has(address)) shadowed.push(address);
|
|
@@ -364,14 +452,34 @@ export class Actors extends BasePackCompiler {
|
|
|
364
452
|
});
|
|
365
453
|
}
|
|
366
454
|
|
|
455
|
+
/**
|
|
456
|
+
* The note-type → document-subtype map this pass compiles against.
|
|
457
|
+
*
|
|
458
|
+
* Stated by the class rather than reached for through the module import, so
|
|
459
|
+
* every subtype decision the pass makes — the actor's own, and each
|
|
460
|
+
* embedded item reference's — reads one declaration that a subclass
|
|
461
|
+
* compiling for another system can replace. That is also what lets the
|
|
462
|
+
* non-identity behaviour be exercised without introducing a non-identity
|
|
463
|
+
* row into SoHL's own map, which is #78's job and moves compiled bytes.
|
|
464
|
+
*
|
|
465
|
+
* @type {import("../engine/document-subtypes.mjs").DocumentSubtypeMap}
|
|
466
|
+
*/
|
|
467
|
+
static documentSubtypes = SOHL_DOCUMENT_SUBTYPES;
|
|
468
|
+
|
|
367
469
|
/**
|
|
368
470
|
* @param {object} fm - The note's frontmatter.
|
|
369
|
-
* @returns {boolean} True for a `
|
|
471
|
+
* @returns {boolean} True for a note type the system maps onto an `Actor`.
|
|
370
472
|
*/
|
|
371
473
|
selects(fm) {
|
|
372
|
-
return fm.type
|
|
474
|
+
return mapsNoteType(this.constructor.documentSubtypes, fm.type, "Actor");
|
|
373
475
|
}
|
|
374
476
|
|
|
477
|
+
/**
|
|
478
|
+
* An Actor **is** a system's data, so this pack takes only notes carrying
|
|
479
|
+
* this system's block (#58).
|
|
480
|
+
*/
|
|
481
|
+
static requiresSystemBlock = true;
|
|
482
|
+
|
|
375
483
|
/**
|
|
376
484
|
* The predefined items each being's embedded items resolve against, loaded
|
|
377
485
|
* before the walk from the items passes' output.
|
|
@@ -400,41 +508,113 @@ export class Actors extends BasePackCompiler {
|
|
|
400
508
|
reportDetail(stats) {
|
|
401
509
|
log.debug(
|
|
402
510
|
`Skipped ${stats.skippedOther} non-actor file(s) ` +
|
|
403
|
-
`(not ${
|
|
511
|
+
`(not ${ACTOR_NOTE_TYPES.join("/")}, package:${contentPackage()})`,
|
|
404
512
|
);
|
|
405
513
|
}
|
|
406
514
|
|
|
515
|
+
/**
|
|
516
|
+
* The Foundry Item subtype an embedded reference's `type` addresses.
|
|
517
|
+
*
|
|
518
|
+
* **The reference is in the note vocabulary; the address is in the
|
|
519
|
+
* document's** (#140). A being writes `(type, shortcode)` with the type an
|
|
520
|
+
* author authors, while {@link itemAddress} keys the predefined items by
|
|
521
|
+
* the subtype each compiled document carries — so exactly one of the two
|
|
522
|
+
* sides has to translate, and it is this one. The system's map is a
|
|
523
|
+
* function from note type to subtype by construction; the reverse is not,
|
|
524
|
+
* and a compiled document records nothing about the note that produced it,
|
|
525
|
+
* so there is no honest way to key the addresses the other way round.
|
|
526
|
+
*
|
|
527
|
+
* The two vocabularies are the same string in every SoHL row today, which
|
|
528
|
+
* is why looking a reference up verbatim worked. The first non-identity row
|
|
529
|
+
* (#78: `armor` → `armorgear`) ends that, and a reference resolving to
|
|
530
|
+
* nothing must be a finding rather than an item quietly missing from the
|
|
531
|
+
* compiled actor.
|
|
532
|
+
*
|
|
533
|
+
* @param {string} type - The type the reference names.
|
|
534
|
+
* @returns {import("../engine/document-subtypes.mjs").ReferencedSubtype}
|
|
535
|
+
* The subtype, or why the reference names none.
|
|
536
|
+
*/
|
|
537
|
+
embeddedSubtype(type) {
|
|
538
|
+
return referencedSubtype(this.constructor.documentSubtypes, type, "Item");
|
|
539
|
+
}
|
|
540
|
+
|
|
407
541
|
/**
|
|
408
542
|
* Resolve one embedded item from a `(type, shortcode?, overlay)`
|
|
409
543
|
* descriptor. If `shortcode` is given, the predefined item is fetched
|
|
410
544
|
* from `itemsMap` and the overlay deep-merged on top. If absent, the
|
|
411
545
|
* descriptor must carry enough fields to stand alone. The embedded
|
|
412
546
|
* item's `_id` is regenerated deterministically from
|
|
413
|
-
* `(actorId,
|
|
547
|
+
* `(actorId, subType, shortcode, indexKey)` so re-exports are stable —
|
|
548
|
+
* from the **document subtype**, so that renaming a note type (#78) leaves
|
|
549
|
+
* every embedded id exactly where it was.
|
|
414
550
|
* Returns null if the descriptor cannot be resolved.
|
|
551
|
+
*
|
|
552
|
+
* @param {Map<string, object>} itemsMap - The predefined items, by address.
|
|
553
|
+
* @param {string} actorId - The owning actor's id, seeding embedded ids.
|
|
554
|
+
* @param {string} type - The **note** type the reference names.
|
|
555
|
+
* @param {string|null} shortcode - The referenced item's shortcode, or
|
|
556
|
+
* `null` for a stand-alone entry.
|
|
557
|
+
* @param {object} [overlay] - The entry's remaining properties.
|
|
558
|
+
* @param {string} indexKey - Distinguishes two references to one item.
|
|
559
|
+
* @param {string} ctx - Diagnostic context (the actor's label).
|
|
560
|
+
* @param {object} [at] - Where to locate a finding.
|
|
561
|
+
* @param {string} [at.fmKey] - The frontmatter key the reference sits
|
|
562
|
+
* under, so an unresolved one is reported at the reference rather than
|
|
563
|
+
* at the note.
|
|
564
|
+
* @returns {object|null} The embedded item, or null when it resolved to
|
|
565
|
+
* nothing — always with a finding emitted.
|
|
415
566
|
*/
|
|
416
|
-
resolveEmbedded(itemsMap, actorId, type, shortcode, overlay, indexKey, ctx) {
|
|
567
|
+
resolveEmbedded(itemsMap, actorId, type, shortcode, overlay, indexKey, ctx, { fmKey } = {}) {
|
|
568
|
+
// Where a finding about this reference points. The value locates the
|
|
569
|
+
// exact entry in a list; the key is the fallback when it cannot be
|
|
570
|
+
// found, which still beats naming the note alone.
|
|
571
|
+
const where = () =>
|
|
572
|
+
locateFrontmatterKey(this.currentNote?.absPath, fmKey ?? "items", shortcode || type);
|
|
573
|
+
|
|
574
|
+
const { subType, problem } = this.embeddedSubtype(type);
|
|
575
|
+
if (problem) {
|
|
576
|
+
this.noteError(`${ctx}: ${indexKey}: ${problem}`, where());
|
|
577
|
+
this.errorCount++;
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
const address = itemAddress(/** @type {string} */ (subType), shortcode ?? "");
|
|
581
|
+
|
|
417
582
|
let base = null;
|
|
418
583
|
if (shortcode) {
|
|
419
|
-
base = itemsMap.get(
|
|
584
|
+
base = itemsMap.get(address);
|
|
420
585
|
if (!base) {
|
|
421
|
-
|
|
586
|
+
// Both vocabularies where they differ, so an author sees why an
|
|
587
|
+
// address they wrote did not land where they expected.
|
|
588
|
+
const translated =
|
|
589
|
+
subType === type ? "" : (
|
|
590
|
+
` (looked up as "${address}", the ` +
|
|
591
|
+
`${this.constructor.documentSubtypes.system} Item subtype a ` +
|
|
592
|
+
`"${type}" note compiles into)`
|
|
593
|
+
);
|
|
594
|
+
this.noteError(
|
|
595
|
+
`${ctx}: no predefined item for "${type}:${shortcode}"${translated}`,
|
|
596
|
+
where(),
|
|
597
|
+
);
|
|
422
598
|
this.errorCount++;
|
|
423
599
|
return null;
|
|
424
600
|
}
|
|
425
601
|
base = stripCompendiumFields(base);
|
|
426
602
|
} else if (overlay && overlay.name && overlay.system) {
|
|
427
|
-
base = { type, name: overlay.name, system: {} };
|
|
603
|
+
base = { type: subType, name: overlay.name, system: {} };
|
|
428
604
|
} else {
|
|
429
605
|
this.noteError(
|
|
430
606
|
`${ctx}: embedded item missing shortcode and not enough fields to stand alone`,
|
|
607
|
+
where(),
|
|
431
608
|
);
|
|
432
609
|
this.errorCount++;
|
|
433
610
|
return null;
|
|
434
611
|
}
|
|
435
612
|
const merged = overlay ? deepMerge(base, overlay) : base;
|
|
436
|
-
merged.type =
|
|
437
|
-
merged._id = makeId(
|
|
613
|
+
merged.type = subType;
|
|
614
|
+
merged._id = makeId(
|
|
615
|
+
actorId,
|
|
616
|
+
`${itemAddress(/** @type {string} */ (subType), shortcode || merged.name)}:${indexKey}`,
|
|
617
|
+
);
|
|
438
618
|
// Foundry's pack compiler flattens the document hierarchy into LevelDB,
|
|
439
619
|
// storing each embedded document under its own `_key`. Embedded items
|
|
440
620
|
// therefore need a hierarchical key, as do any effects they carry
|
|
@@ -469,6 +649,7 @@ export class Actors extends BasePackCompiler {
|
|
|
469
649
|
overlay,
|
|
470
650
|
`attr:${shortcode}`,
|
|
471
651
|
ctx,
|
|
652
|
+
{ fmKey: "attributes" },
|
|
472
653
|
);
|
|
473
654
|
if (embedded) items.push(embedded);
|
|
474
655
|
}
|
|
@@ -496,6 +677,7 @@ export class Actors extends BasePackCompiler {
|
|
|
496
677
|
rest,
|
|
497
678
|
`items:${index}`,
|
|
498
679
|
ctx,
|
|
680
|
+
{ fmKey: "items" },
|
|
499
681
|
);
|
|
500
682
|
if (embedded) items.push(embedded);
|
|
501
683
|
});
|
|
@@ -559,6 +741,14 @@ export class Actors extends BasePackCompiler {
|
|
|
559
741
|
const name = resolveName(fm);
|
|
560
742
|
const id = fm.id;
|
|
561
743
|
const ctx = `actor "${name}"`;
|
|
744
|
+
// The document's own subtype, and the art that goes with it. Both are
|
|
745
|
+
// looked up from the note's `type` rather than spelled here (#79).
|
|
746
|
+
const subType = /** @type {string} */ (
|
|
747
|
+
documentSubtype(this.constructor.documentSubtypes, fm.type, fm, {
|
|
748
|
+
absPath: this.currentNote?.absPath,
|
|
749
|
+
})
|
|
750
|
+
);
|
|
751
|
+
const defaultImg = defaultActorImg(subType);
|
|
562
752
|
|
|
563
753
|
const items = this.buildEmbeddedItems(itemsMap, id, fm, ctx);
|
|
564
754
|
|
|
@@ -567,10 +757,13 @@ export class Actors extends BasePackCompiler {
|
|
|
567
757
|
|
|
568
758
|
const system = {
|
|
569
759
|
// The frontmatter shortcode is the actor's stable `(type, shortcode)`
|
|
570
|
-
// key — and, for a
|
|
760
|
+
// key — and, for a being that is an archetype, its archetype
|
|
571
761
|
// identity (the dedup/override key of the Create-dialog picker, #604).
|
|
572
762
|
shortcode: fm.shortcode || "",
|
|
573
|
-
|
|
763
|
+
// Required nullable number: a priority, or `null` for a being that
|
|
764
|
+
// is not an archetype (#126 / archetype contract #604).
|
|
765
|
+
archetype: systemArchetype(fm, ctx),
|
|
766
|
+
portrait: resolveImg(blockProperty(fm, SYSTEM, "portrait")) || defaultImg,
|
|
574
767
|
appearance: renderSection(body || "", "appearance"),
|
|
575
768
|
dossier: renderSection(body || "", "dossier"),
|
|
576
769
|
};
|
|
@@ -600,10 +793,29 @@ export class Actors extends BasePackCompiler {
|
|
|
600
793
|
system.defaultCombatGroup = defaultCombatGroup;
|
|
601
794
|
}
|
|
602
795
|
|
|
796
|
+
// Whatever the note authors under `sohl.system`, at the DataModel's own
|
|
797
|
+
// paths (#58). This pass has no field declaration, so it claims
|
|
798
|
+
// nothing: every authored path is the author's, and the fields above
|
|
799
|
+
// are what a note that authors none still gets.
|
|
800
|
+
mergeSystemData(system, fm, { block: SYSTEM });
|
|
801
|
+
this.reportUndeclaredSystemData(fm, SYSTEM, "Actor", subType);
|
|
802
|
+
// And what this pass wrote itself. There is no field declaration for a
|
|
803
|
+
// being at all, so *every* key here is a compiler emission — including
|
|
804
|
+
// `archetype` (#126), which nothing compared until #155.
|
|
805
|
+
this.reportEmittedSystemData(system, {
|
|
806
|
+
fm,
|
|
807
|
+
block: SYSTEM,
|
|
808
|
+
documentType: "Actor",
|
|
809
|
+
subType,
|
|
810
|
+
type: fm.type,
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
const effects = blockProperty(fm, SYSTEM, "effects");
|
|
814
|
+
|
|
603
815
|
return {
|
|
604
816
|
name,
|
|
605
|
-
type:
|
|
606
|
-
img: resolveImg(fm
|
|
817
|
+
type: subType,
|
|
818
|
+
img: resolveImg(blockProperty(fm, SYSTEM, "img")) || defaultImg,
|
|
607
819
|
_id: id,
|
|
608
820
|
system,
|
|
609
821
|
items,
|
|
@@ -611,19 +823,20 @@ export class Actors extends BasePackCompiler {
|
|
|
611
823
|
name,
|
|
612
824
|
displayName: 0,
|
|
613
825
|
actorLink: false,
|
|
614
|
-
texture: { src: resolveImg(fm
|
|
826
|
+
texture: { src: resolveImg(blockProperty(fm, SYSTEM, "img")) || defaultImg },
|
|
615
827
|
width: 1,
|
|
616
828
|
height: 1,
|
|
617
829
|
sight: { enabled: false },
|
|
618
830
|
detectionModes: [],
|
|
619
831
|
},
|
|
620
|
-
effects: [],
|
|
832
|
+
effects: Array.isArray(effects) ? [...effects] : [],
|
|
621
833
|
folder,
|
|
622
834
|
sort: 0,
|
|
623
835
|
ownership: { default: 0 },
|
|
624
|
-
// `
|
|
625
|
-
// `flags.sohl.docArchetype
|
|
626
|
-
|
|
836
|
+
// Whatever the note authors, and nothing else. `archetype` used to
|
|
837
|
+
// be spliced in here as `flags.sohl.docArchetype`; it is a schema
|
|
838
|
+
// field now and sits in `system` (#126).
|
|
839
|
+
flags: blockProperty(fm, SYSTEM, "flags", {}),
|
|
627
840
|
_stats: this.stats,
|
|
628
841
|
_key: `!actors!${id}`,
|
|
629
842
|
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* **SoHL's note-type → document-subtype map** — which Foundry document, and
|
|
16
|
+
* which subtype of it, a note of each content type compiles into (#79).
|
|
17
|
+
*
|
|
18
|
+
* The mechanism is `engine/document-subtypes.mjs`; what a *game system* does
|
|
19
|
+
* with it is here, which is the `engine/` ÷ `sohl/` line this package draws
|
|
20
|
+
* everywhere else (#36). A second system declares its own map in its own half
|
|
21
|
+
* and shares nothing but the mechanism.
|
|
22
|
+
*
|
|
23
|
+
* **Every row is written out, identity rows included.** `skill → skill` looks
|
|
24
|
+
* like a row that could be derived from the item registry's keys, and deriving
|
|
25
|
+
* it is precisely the defect this map exists to remove: the note vocabulary
|
|
26
|
+
* and the document vocabulary would once again be the same identifier because
|
|
27
|
+
* one list generated the other, and a rename on either side would follow
|
|
28
|
+
* silently. Sixteen lines of data are the price of the two vocabularies being
|
|
29
|
+
* separately stated.
|
|
30
|
+
*
|
|
31
|
+
* **Today every row is the identity, and that is a fact about SoHL rather than
|
|
32
|
+
* a rule.** The renames the format calls for (`armorgear` → `armor`, and its
|
|
33
|
+
* three siblings) are #78, deliberately deferred: they cost 30,741 embedded
|
|
34
|
+
* `(type, shortcode)` references across four content repositories, and until
|
|
35
|
+
* they land the compiled packs must not move by a byte. When one does land it
|
|
36
|
+
* changes one row here and the notes that address it — a data change, not a
|
|
37
|
+
* mechanism change, which is the whole point of having the map first.
|
|
38
|
+
*
|
|
39
|
+
* **What this map is not.** It says which document a note becomes, never what
|
|
40
|
+
* that document contains: the `system` block comes from the item registry's
|
|
41
|
+
* builders (`item-builders.mjs`), and an item's own `system.subType` field —
|
|
42
|
+
* a skill's `physical` / `combattechnique`, an affliction's kind — is a
|
|
43
|
+
* *data-model field*, unrelated to the document subtype named here.
|
|
44
|
+
*
|
|
45
|
+
* @module
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import { defineDocumentSubtypes } from "../engine/document-subtypes.mjs";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Every content type SoHL compiles into a Foundry document, and what it
|
|
52
|
+
* becomes.
|
|
53
|
+
*
|
|
54
|
+
* The Item rows are the thirteen types the item registry declares; the one
|
|
55
|
+
* Actor row is `being`, which was two types (`character` and `creature`)
|
|
56
|
+
* compiling to the same actor until they were retired in SoHL#1580. Types this
|
|
57
|
+
* map does not name — `doc`, `macro`, the three map types — compile into
|
|
58
|
+
* documents that carry no system subtype at all, so they have no row and never
|
|
59
|
+
* needed one.
|
|
60
|
+
*
|
|
61
|
+
* @type {import("../engine/document-subtypes.mjs").DocumentSubtypeMap}
|
|
62
|
+
*/
|
|
63
|
+
export const SOHL_DOCUMENT_SUBTYPES = defineDocumentSubtypes({
|
|
64
|
+
system: "sohl",
|
|
65
|
+
types: {
|
|
66
|
+
affiliation: { document: "Item", subType: "affiliation" },
|
|
67
|
+
affliction: { document: "Item", subType: "affliction" },
|
|
68
|
+
armorgear: { document: "Item", subType: "armorgear" },
|
|
69
|
+
attribute: { document: "Item", subType: "attribute" },
|
|
70
|
+
concoctiongear: { document: "Item", subType: "concoctiongear" },
|
|
71
|
+
containergear: { document: "Item", subType: "containergear" },
|
|
72
|
+
miscgear: { document: "Item", subType: "miscgear" },
|
|
73
|
+
mystery: { document: "Item", subType: "mystery" },
|
|
74
|
+
mysticalability: { document: "Item", subType: "mysticalability" },
|
|
75
|
+
projectilegear: { document: "Item", subType: "projectilegear" },
|
|
76
|
+
skill: { document: "Item", subType: "skill" },
|
|
77
|
+
trauma: { document: "Item", subType: "trauma" },
|
|
78
|
+
weapongear: { document: "Item", subType: "weapongear" },
|
|
79
|
+
|
|
80
|
+
being: { document: "Actor", subType: "being" },
|
|
81
|
+
},
|
|
82
|
+
});
|
package/sohl/index.mjs
CHANGED
|
@@ -33,6 +33,9 @@
|
|
|
33
33
|
/** The item-type registry: every type that compiles into an Item, and its builder. */
|
|
34
34
|
export * as itemBuilders from "./item-builders.mjs";
|
|
35
35
|
|
|
36
|
+
/** This system's note-type → document-subtype map, identity rows included (#79). */
|
|
37
|
+
export * as documentSubtypes from "./document-subtypes.mjs";
|
|
38
|
+
|
|
36
39
|
/** The Item compiler. */
|
|
37
40
|
export * as items from "./items.mjs";
|
|
38
41
|
|