@rpgm-tools/neo-angband-mod-sdk 0.10.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/LICENSE.md +43 -0
- package/README.md +72 -0
- package/dist/capabilities.d.ts +116 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +170 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/compose.d.ts +71 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/compose.js +118 -0
- package/dist/compose.js.map +1 -0
- package/dist/conflicts.d.ts +78 -0
- package/dist/conflicts.d.ts.map +1 -0
- package/dist/conflicts.js +160 -0
- package/dist/conflicts.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +83 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +314 -0
- package/dist/loader.js.map +1 -0
- package/dist/manifest.d.ts +261 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +264 -0
- package/dist/manifest.js.map +1 -0
- package/dist/patch.d.ts +90 -0
- package/dist/patch.d.ts.map +1 -0
- package/dist/patch.js +195 -0
- package/dist/patch.js.map +1 -0
- package/dist/record-key.d.ts +99 -0
- package/dist/record-key.d.ts.map +1 -0
- package/dist/record-key.js +157 -0
- package/dist/record-key.js.map +1 -0
- package/dist/resolve.d.ts +42 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +161 -0
- package/dist/resolve.js.map +1 -0
- package/dist/semver.d.ts +37 -0
- package/dist/semver.d.ts.map +1 -0
- package/dist/semver.js +212 -0
- package/dist/semver.js.map +1 -0
- package/package.json +58 -0
- package/src/capabilities.ts +205 -0
- package/src/compose.ts +186 -0
- package/src/conflicts.ts +242 -0
- package/src/index.ts +73 -0
- package/src/loader.ts +393 -0
- package/src/manifest.ts +523 -0
- package/src/patch.ts +257 -0
- package/src/record-key.ts +180 -0
- package/src/resolve.ts +175 -0
- package/src/semver.ts +231 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# License
|
|
2
|
+
|
|
3
|
+
Neo Angband is a derivative work of Angband and retains upstream's dual
|
|
4
|
+
license, as the Angband project asks of its variants (see
|
|
5
|
+
`reference/docs/copying.rst`, which remains the authoritative statement for
|
|
6
|
+
the original code and its assets).
|
|
7
|
+
|
|
8
|
+
Copyright (c) 1997 Ben Harrison, James E. Wilson, Robert A. Koeneke, and the
|
|
9
|
+
Angband contributors; port code copyright (c) 2026 the Neo Angband
|
|
10
|
+
contributors.
|
|
11
|
+
|
|
12
|
+
This work is free software; you can redistribute it and/or modify it under
|
|
13
|
+
the terms of either:
|
|
14
|
+
|
|
15
|
+
a) the GNU General Public License as published by the Free Software
|
|
16
|
+
Foundation, version 2, or
|
|
17
|
+
|
|
18
|
+
b) the "Angband licence":
|
|
19
|
+
This software may be copied and distributed for educational, research,
|
|
20
|
+
and not for profit purposes provided that this copyright and statement
|
|
21
|
+
are included in all such copies. Other copyrights may also apply.
|
|
22
|
+
|
|
23
|
+
## Assets
|
|
24
|
+
|
|
25
|
+
Asset licenses under `reference/` vary and are NOT all dual-licensed as
|
|
26
|
+
above; see `reference/docs/copying.rst` for the full statements, and
|
|
27
|
+
`packages/web/public/tiles/CREDITS.md` for the four tile packs this port
|
|
28
|
+
actually bundles and the terms each one is bundled under.
|
|
29
|
+
|
|
30
|
+
The Adam Bolt tiles (any purpose, with or without modification) and David
|
|
31
|
+
Gervais tiles (CC BY 3.0, attribution given in that CREDITS.md) are available
|
|
32
|
+
to tile packs under their own terms. The Shockbolt tile set is a distinct
|
|
33
|
+
case and is **not** bundled: its license grants fee-free distribution *with*
|
|
34
|
+
Angband but withholds modification without the author's permission, and
|
|
35
|
+
withholds use with "other games or projects" absent explicit permission —
|
|
36
|
+
so whether this port may carry it turns on a question only the author can
|
|
37
|
+
settle. CREDITS.md states the grants and the prohibitions in full.
|
|
38
|
+
|
|
39
|
+
## Mods
|
|
40
|
+
|
|
41
|
+
Declarative content packs and tile packs are independent works and may be
|
|
42
|
+
licensed as their authors choose. Distributed scripted plugins are safest
|
|
43
|
+
treated as GPLv2 derivatives. See `docs/MODS.md`.
|
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @rpgm-tools/neo-angband-mod-sdk
|
|
2
|
+
|
|
3
|
+
The mod machinery for [Neo Angband](https://github.com/neostryder/neo-angband):
|
|
4
|
+
manifest schema and validation, the deterministic load-order resolver, and the
|
|
5
|
+
record-composition engine that merges packs.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @rpgm-tools/neo-angband-mod-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Why it is a separate package
|
|
12
|
+
|
|
13
|
+
The base game loads its own content **through this pipeline, as pack zero**. That
|
|
14
|
+
is the point: there is no privileged path a mod cannot take, because the game
|
|
15
|
+
itself does not have one. This package is that pipeline, with no engine attached —
|
|
16
|
+
so a mod's build script or test can validate a manifest, resolve a load order or
|
|
17
|
+
compose records without pulling in a game.
|
|
18
|
+
|
|
19
|
+
## What is in it
|
|
20
|
+
|
|
21
|
+
| Area | Exports |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| Manifests | `validateManifest`, `PackManifest`, `PackShape`, `packFacets`, `hasFacet`, `packRef`, `slugify` |
|
|
24
|
+
| Load order | `resolveLoadOrder`, `satisfies` (the semver subset the manifests use) |
|
|
25
|
+
| Composition | `composePacks`, `composeContentPacks`, `mergePatch`, `applyFieldPatch`, `composeFieldPatches` |
|
|
26
|
+
| Conflicts | `computeConflictReport`, `RecordConflict`, `FieldTouch`, `RecordOverride` |
|
|
27
|
+
| Record identity | `recordKey`, `keySpecFor`, `KEYED_RECORD_FILES`, `RECORD_KEY_SPECS` |
|
|
28
|
+
| Capabilities | `CapabilitySet`, `parseCapability` — what a scripted plugin is allowed to reach |
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { validateManifest, ManifestError } from "@rpgm-tools/neo-angband-mod-sdk";
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const manifest = validateManifest(JSON.parse(text));
|
|
35
|
+
console.log(manifest.id, manifest.shape, manifest.modApi);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
if (e instanceof ManifestError) console.error(`bad manifest: ${e.message}`);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## The three pack shapes
|
|
42
|
+
|
|
43
|
+
- **content** — declarative JSON validated against the record schemas. Safe by
|
|
44
|
+
construction: it cannot execute anything.
|
|
45
|
+
- **tiles** — a tile pack, either a tilesheet re-skin or a loose Linoleum pack.
|
|
46
|
+
- **plugin** — a scripted mod. It default-exports a `ModPlugin` and receives the
|
|
47
|
+
running engine as `ctx.core`, because a module loaded out of a mod folder cannot
|
|
48
|
+
resolve a bare specifier and a bundled copy of the engine would give the plugin
|
|
49
|
+
its own registries while the game ran on another set.
|
|
50
|
+
|
|
51
|
+
A single mod may declare several of these as **facets**; `packFacets` is what
|
|
52
|
+
reads them.
|
|
53
|
+
|
|
54
|
+
## Related
|
|
55
|
+
|
|
56
|
+
- [`@rpgm-tools/neo-angband-core`](https://www.npmjs.com/package/@rpgm-tools/neo-angband-core) — the engine itself
|
|
57
|
+
- [docs/MODS.md](https://github.com/neostryder/neo-angband/blob/master/docs/MODS.md) — the full modding guide
|
|
58
|
+
- First-party mods: [qol](https://github.com/neostryder/neo-angband-mod-qol) ·
|
|
59
|
+
[bug-fixes](https://github.com/neostryder/neo-angband-mod-bug-fixes) ·
|
|
60
|
+
[neo-linoleum](https://github.com/neostryder/neo-angband-mod-linoleum) ·
|
|
61
|
+
[borg](https://github.com/neostryder/neo-angband-mod-borg)
|
|
62
|
+
|
|
63
|
+
## Versioning
|
|
64
|
+
|
|
65
|
+
`0.9.x` is the pre-release line and the API can change inside it; `1.0.0` is
|
|
66
|
+
reserved for the game's public release.
|
|
67
|
+
|
|
68
|
+
## Licence
|
|
69
|
+
|
|
70
|
+
GNU GPL v2, or the Angband licence, at your option — Angband's dual licence, kept
|
|
71
|
+
as the Angband project asks of its variants. npm carries one SPDX identifier so the
|
|
72
|
+
manifest says `GPL-2.0-only`; both texts are in [LICENSE.md](LICENSE.md).
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability model for scripted plugins (MOD_LIFECYCLE.md section 4, P7
|
|
3
|
+
* phase 5).
|
|
4
|
+
*
|
|
5
|
+
* Only `shape: plugin` packs may request capabilities; content and tile
|
|
6
|
+
* packs are validated data that cannot execute, so they request none
|
|
7
|
+
* (docs/MODS.md trust tiers). A plugin's `capabilities` list in its
|
|
8
|
+
* manifest is the consent surface: the installer shows each one in plain
|
|
9
|
+
* language, the user approves, and the runtime grants exactly that set -
|
|
10
|
+
* nothing a plugin did not ask for and the user did not see. The
|
|
11
|
+
* perceive/act facades (a later P7 phase) call `CapabilitySet.check()`
|
|
12
|
+
* before honoring a request; an ungranted capability throws a clear
|
|
13
|
+
* author-facing error rather than silently doing nothing or diverging.
|
|
14
|
+
*
|
|
15
|
+
* Vocabulary (four forms, MOD_LIFECYCLE section 4 / the frost example in
|
|
16
|
+
* section 2):
|
|
17
|
+
* - "command:add" - register commands on the act facade.
|
|
18
|
+
* - "event:<name>" - subscribe to an engine event, e.g.
|
|
19
|
+
* "event:turn-start".
|
|
20
|
+
* - "state:<domain>.read" - read one perceive-facade domain, e.g.
|
|
21
|
+
* "state:party.read"; or the wildcard
|
|
22
|
+
* "state:*.read" for any domain.
|
|
23
|
+
* - "network:<host>" - outbound network to one host; or "network:*"
|
|
24
|
+
* for any host. Not in the section-4 examples
|
|
25
|
+
* verbatim, but named there ("network access to
|
|
26
|
+
* api.example.com"); "*" is this module's
|
|
27
|
+
* extension for a plugin that genuinely needs
|
|
28
|
+
* unrestricted egress, and reads the same way
|
|
29
|
+
* the other wildcards do.
|
|
30
|
+
* - "registry:<domain>" - override a game SYSTEM registry from a TRUSTED
|
|
31
|
+
* in-process plugin (W2.2, core/mod/registry-host.ts):
|
|
32
|
+
* "registry:effect" | "registry:room" |
|
|
33
|
+
* "registry:command" | "registry:monster" |
|
|
34
|
+
* "registry:vocab"; or the wildcard "registry:*"
|
|
35
|
+
* for all of them. "registry:vocab" (W2.3) declares
|
|
36
|
+
* NEW vocabulary (flags/stats/any kind). Distinct
|
|
37
|
+
* from "command:add": that adds a command via the
|
|
38
|
+
* act facade, this replaces what a command DOES (and
|
|
39
|
+
* the effect/room/AI logic behind the game).
|
|
40
|
+
* (The named-core-rule flags the bundled qol /
|
|
41
|
+
* bug-fixes mods use, GameState.modRules, are a
|
|
42
|
+
* DECLARATIVE manifest field - PackManifest.rules -
|
|
43
|
+
* applied by the host, so they need no capability.)
|
|
44
|
+
*
|
|
45
|
+
* This module only surfaces `nondeterministic` from the manifest. The
|
|
46
|
+
* save's determinism ratchet itself - flipping a save from DETERMINISTIC to
|
|
47
|
+
* NONDETERMINISTIC the first time such a mod is enabled, once and
|
|
48
|
+
* irreversibly - lives in core/save (decisions 4/18/22), not here.
|
|
49
|
+
*/
|
|
50
|
+
import { type PackManifest } from "./manifest.js";
|
|
51
|
+
export declare class CapabilityError extends Error {
|
|
52
|
+
}
|
|
53
|
+
/** A capability string parsed into its structured form. */
|
|
54
|
+
export type ParsedCapability = {
|
|
55
|
+
kind: "command";
|
|
56
|
+
action: "add";
|
|
57
|
+
} | {
|
|
58
|
+
kind: "event";
|
|
59
|
+
name: string;
|
|
60
|
+
} | {
|
|
61
|
+
kind: "state";
|
|
62
|
+
domain: string;
|
|
63
|
+
access: "read";
|
|
64
|
+
} | {
|
|
65
|
+
kind: "network";
|
|
66
|
+
host: string;
|
|
67
|
+
} | {
|
|
68
|
+
kind: "registry";
|
|
69
|
+
domain: string;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Parse and validate a capability string against the vocabulary above,
|
|
73
|
+
* returning its structured form. Throws CapabilityError on anything
|
|
74
|
+
* malformed or outside the recognized patterns - an unknown capability is
|
|
75
|
+
* a hard error, not a silent no-op, since a typo'd request should fail
|
|
76
|
+
* loudly at install rather than quietly never matching a grant.
|
|
77
|
+
*/
|
|
78
|
+
export declare function parseCapability(cap: string): ParsedCapability;
|
|
79
|
+
/**
|
|
80
|
+
* The capabilities a plugin was granted, built from its manifest. Facades
|
|
81
|
+
* hold one of these per loaded plugin and call `check()` before honoring
|
|
82
|
+
* any request that touches commands, events, state reads, or the network.
|
|
83
|
+
*/
|
|
84
|
+
export declare class CapabilitySet {
|
|
85
|
+
private readonly grants;
|
|
86
|
+
private readonly nondeterministic;
|
|
87
|
+
private readonly affectsGameplay;
|
|
88
|
+
private constructor();
|
|
89
|
+
/**
|
|
90
|
+
* Build a CapabilitySet from a pack manifest. Only `shape: plugin` packs
|
|
91
|
+
* may request capabilities (MOD_LIFECYCLE section 4): a content or tile
|
|
92
|
+
* pack with a non-empty `capabilities` list throws CapabilityError, since
|
|
93
|
+
* that shape cannot execute and so has nothing to grant capabilities to -
|
|
94
|
+
* the request signals author confusion or an upstream validation bug,
|
|
95
|
+
* not something to silently ignore.
|
|
96
|
+
*/
|
|
97
|
+
static fromManifest(manifest: PackManifest): CapabilitySet;
|
|
98
|
+
/**
|
|
99
|
+
* True if `cap` is covered by a grant in this set, honoring the
|
|
100
|
+
* "state:*.read" and "network:*" wildcards. Throws CapabilityError if
|
|
101
|
+
* `cap` itself is not a recognized capability string.
|
|
102
|
+
*/
|
|
103
|
+
has(cap: string): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Throws CapabilityError, naming the missing capability and how to fix
|
|
106
|
+
* it, unless `cap` is granted. This is the guard the perceive/act facades
|
|
107
|
+
* call before honoring a plugin's request - an author-facing error, not
|
|
108
|
+
* a silent divergence.
|
|
109
|
+
*/
|
|
110
|
+
check(cap: string): void;
|
|
111
|
+
/** True if the manifest declared `nondeterministic: true` (section 4). */
|
|
112
|
+
isNondeterministic(): boolean;
|
|
113
|
+
/** True if the manifest declared `affectsGameplay: true`. */
|
|
114
|
+
isAffectsGameplay(): boolean;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=capabilities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,OAAO,EAAY,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAE5D,qBAAa,eAAgB,SAAQ,KAAK;CAAG;AAE7C,2DAA2D;AAC3D,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,KAAK,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAQzC;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAqB7D;AAgCD;;;;GAIG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IACrD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAU;IAC3C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAU;IAE1C,OAAO;IAUP;;;;;;;OAOG;IACH,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,YAAY,GAAG,aAAa;IAgB1D;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAKzB;;;;;OAKG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAOxB,0EAA0E;IAC1E,kBAAkB,IAAI,OAAO;IAI7B,6DAA6D;IAC7D,iBAAiB,IAAI,OAAO;CAG7B"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability model for scripted plugins (MOD_LIFECYCLE.md section 4, P7
|
|
3
|
+
* phase 5).
|
|
4
|
+
*
|
|
5
|
+
* Only `shape: plugin` packs may request capabilities; content and tile
|
|
6
|
+
* packs are validated data that cannot execute, so they request none
|
|
7
|
+
* (docs/MODS.md trust tiers). A plugin's `capabilities` list in its
|
|
8
|
+
* manifest is the consent surface: the installer shows each one in plain
|
|
9
|
+
* language, the user approves, and the runtime grants exactly that set -
|
|
10
|
+
* nothing a plugin did not ask for and the user did not see. The
|
|
11
|
+
* perceive/act facades (a later P7 phase) call `CapabilitySet.check()`
|
|
12
|
+
* before honoring a request; an ungranted capability throws a clear
|
|
13
|
+
* author-facing error rather than silently doing nothing or diverging.
|
|
14
|
+
*
|
|
15
|
+
* Vocabulary (four forms, MOD_LIFECYCLE section 4 / the frost example in
|
|
16
|
+
* section 2):
|
|
17
|
+
* - "command:add" - register commands on the act facade.
|
|
18
|
+
* - "event:<name>" - subscribe to an engine event, e.g.
|
|
19
|
+
* "event:turn-start".
|
|
20
|
+
* - "state:<domain>.read" - read one perceive-facade domain, e.g.
|
|
21
|
+
* "state:party.read"; or the wildcard
|
|
22
|
+
* "state:*.read" for any domain.
|
|
23
|
+
* - "network:<host>" - outbound network to one host; or "network:*"
|
|
24
|
+
* for any host. Not in the section-4 examples
|
|
25
|
+
* verbatim, but named there ("network access to
|
|
26
|
+
* api.example.com"); "*" is this module's
|
|
27
|
+
* extension for a plugin that genuinely needs
|
|
28
|
+
* unrestricted egress, and reads the same way
|
|
29
|
+
* the other wildcards do.
|
|
30
|
+
* - "registry:<domain>" - override a game SYSTEM registry from a TRUSTED
|
|
31
|
+
* in-process plugin (W2.2, core/mod/registry-host.ts):
|
|
32
|
+
* "registry:effect" | "registry:room" |
|
|
33
|
+
* "registry:command" | "registry:monster" |
|
|
34
|
+
* "registry:vocab"; or the wildcard "registry:*"
|
|
35
|
+
* for all of them. "registry:vocab" (W2.3) declares
|
|
36
|
+
* NEW vocabulary (flags/stats/any kind). Distinct
|
|
37
|
+
* from "command:add": that adds a command via the
|
|
38
|
+
* act facade, this replaces what a command DOES (and
|
|
39
|
+
* the effect/room/AI logic behind the game).
|
|
40
|
+
* (The named-core-rule flags the bundled qol /
|
|
41
|
+
* bug-fixes mods use, GameState.modRules, are a
|
|
42
|
+
* DECLARATIVE manifest field - PackManifest.rules -
|
|
43
|
+
* applied by the host, so they need no capability.)
|
|
44
|
+
*
|
|
45
|
+
* This module only surfaces `nondeterministic` from the manifest. The
|
|
46
|
+
* save's determinism ratchet itself - flipping a save from DETERMINISTIC to
|
|
47
|
+
* NONDETERMINISTIC the first time such a mod is enabled, once and
|
|
48
|
+
* irreversibly - lives in core/save (decisions 4/18/22), not here.
|
|
49
|
+
*/
|
|
50
|
+
import { hasFacet } from "./manifest.js";
|
|
51
|
+
export class CapabilityError extends Error {
|
|
52
|
+
}
|
|
53
|
+
const EVENT_RE = /^event:([a-z][a-z0-9-]*)$/;
|
|
54
|
+
const STATE_RE = /^state:(\*|[a-z][a-z0-9-]*)\.read$/;
|
|
55
|
+
const NETWORK_RE = /^network:(\*|[a-zA-Z0-9.-]+)$/;
|
|
56
|
+
/** The override domains ModRegistryHost gates, plus the "*" wildcard. */
|
|
57
|
+
const REGISTRY_RE = /^registry:(\*|effect|room|command|monster|vocab)$/;
|
|
58
|
+
/**
|
|
59
|
+
* Parse and validate a capability string against the vocabulary above,
|
|
60
|
+
* returning its structured form. Throws CapabilityError on anything
|
|
61
|
+
* malformed or outside the recognized patterns - an unknown capability is
|
|
62
|
+
* a hard error, not a silent no-op, since a typo'd request should fail
|
|
63
|
+
* loudly at install rather than quietly never matching a grant.
|
|
64
|
+
*/
|
|
65
|
+
export function parseCapability(cap) {
|
|
66
|
+
if (cap === "command:add") {
|
|
67
|
+
return { kind: "command", action: "add" };
|
|
68
|
+
}
|
|
69
|
+
const event = EVENT_RE.exec(cap);
|
|
70
|
+
if (event) {
|
|
71
|
+
return { kind: "event", name: event[1] };
|
|
72
|
+
}
|
|
73
|
+
const state = STATE_RE.exec(cap);
|
|
74
|
+
if (state) {
|
|
75
|
+
return { kind: "state", domain: state[1], access: "read" };
|
|
76
|
+
}
|
|
77
|
+
const network = NETWORK_RE.exec(cap);
|
|
78
|
+
if (network) {
|
|
79
|
+
return { kind: "network", host: network[1] };
|
|
80
|
+
}
|
|
81
|
+
const registry = REGISTRY_RE.exec(cap);
|
|
82
|
+
if (registry) {
|
|
83
|
+
return { kind: "registry", domain: registry[1] };
|
|
84
|
+
}
|
|
85
|
+
throw new CapabilityError(`unrecognized capability: "${cap}"`);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* True if `grant` covers `request`. Exact match for command/event, plus the
|
|
89
|
+
* two documented wildcards: a "state:*.read" grant covers a read of any
|
|
90
|
+
* domain, and a "network:*" grant covers egress to any host.
|
|
91
|
+
*/
|
|
92
|
+
function grantCovers(grant, request) {
|
|
93
|
+
switch (request.kind) {
|
|
94
|
+
case "command":
|
|
95
|
+
return grant.kind === "command";
|
|
96
|
+
case "event":
|
|
97
|
+
return grant.kind === "event" && grant.name === request.name;
|
|
98
|
+
case "state":
|
|
99
|
+
return (grant.kind === "state" &&
|
|
100
|
+
grant.access === request.access &&
|
|
101
|
+
(grant.domain === "*" || grant.domain === request.domain));
|
|
102
|
+
case "network":
|
|
103
|
+
return (grant.kind === "network" &&
|
|
104
|
+
(grant.host === "*" || grant.host === request.host));
|
|
105
|
+
case "registry":
|
|
106
|
+
return (grant.kind === "registry" &&
|
|
107
|
+
(grant.domain === "*" || grant.domain === request.domain));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The capabilities a plugin was granted, built from its manifest. Facades
|
|
112
|
+
* hold one of these per loaded plugin and call `check()` before honoring
|
|
113
|
+
* any request that touches commands, events, state reads, or the network.
|
|
114
|
+
*/
|
|
115
|
+
export class CapabilitySet {
|
|
116
|
+
grants;
|
|
117
|
+
nondeterministic;
|
|
118
|
+
affectsGameplay;
|
|
119
|
+
constructor(grants, nondeterministic, affectsGameplay) {
|
|
120
|
+
this.grants = grants;
|
|
121
|
+
this.nondeterministic = nondeterministic;
|
|
122
|
+
this.affectsGameplay = affectsGameplay;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Build a CapabilitySet from a pack manifest. Only `shape: plugin` packs
|
|
126
|
+
* may request capabilities (MOD_LIFECYCLE section 4): a content or tile
|
|
127
|
+
* pack with a non-empty `capabilities` list throws CapabilityError, since
|
|
128
|
+
* that shape cannot execute and so has nothing to grant capabilities to -
|
|
129
|
+
* the request signals author confusion or an upstream validation bug,
|
|
130
|
+
* not something to silently ignore.
|
|
131
|
+
*/
|
|
132
|
+
static fromManifest(manifest) {
|
|
133
|
+
const requested = manifest.capabilities ?? [];
|
|
134
|
+
if (!hasFacet(manifest, "plugin") && requested.length > 0) {
|
|
135
|
+
throw new CapabilityError(`pack ${manifest.id}: only shape "plugin" packs may request capabilities ` +
|
|
136
|
+
`(this pack is shape "${manifest.shape}")`);
|
|
137
|
+
}
|
|
138
|
+
const grants = requested.map((cap) => parseCapability(cap));
|
|
139
|
+
return new CapabilitySet(grants, manifest.nondeterministic ?? false, manifest.affectsGameplay ?? false);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* True if `cap` is covered by a grant in this set, honoring the
|
|
143
|
+
* "state:*.read" and "network:*" wildcards. Throws CapabilityError if
|
|
144
|
+
* `cap` itself is not a recognized capability string.
|
|
145
|
+
*/
|
|
146
|
+
has(cap) {
|
|
147
|
+
const request = parseCapability(cap);
|
|
148
|
+
return this.grants.some((grant) => grantCovers(grant, request));
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Throws CapabilityError, naming the missing capability and how to fix
|
|
152
|
+
* it, unless `cap` is granted. This is the guard the perceive/act facades
|
|
153
|
+
* call before honoring a plugin's request - an author-facing error, not
|
|
154
|
+
* a silent divergence.
|
|
155
|
+
*/
|
|
156
|
+
check(cap) {
|
|
157
|
+
if (this.has(cap))
|
|
158
|
+
return;
|
|
159
|
+
throw new CapabilityError(`this plugin needs capability "${cap}"; add it to pack.json capabilities`);
|
|
160
|
+
}
|
|
161
|
+
/** True if the manifest declared `nondeterministic: true` (section 4). */
|
|
162
|
+
isNondeterministic() {
|
|
163
|
+
return this.nondeterministic;
|
|
164
|
+
}
|
|
165
|
+
/** True if the manifest declared `affectsGameplay: true`. */
|
|
166
|
+
isAffectsGameplay() {
|
|
167
|
+
return this.affectsGameplay;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=capabilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,OAAO,EAAE,QAAQ,EAAqB,MAAM,eAAe,CAAC;AAE5D,MAAM,OAAO,eAAgB,SAAQ,KAAK;CAAG;AAU7C,MAAM,QAAQ,GAAG,2BAA2B,CAAC;AAC7C,MAAM,QAAQ,GAAG,oCAAoC,CAAC;AACtD,MAAM,UAAU,GAAG,+BAA+B,CAAC;AACnD,yEAAyE;AACzE,MAAM,WAAW,GAAG,mDAAmD,CAAC;AAExE;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,IAAI,GAAG,KAAK,aAAa,EAAE,CAAC;QAC1B,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC5C,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAW,EAAE,CAAC;IACrD,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAW,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACvE,CAAC;IACD,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAW,EAAE,CAAC;IACzD,CAAC;IACD,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvC,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAW,EAAE,CAAC;IAC7D,CAAC;IACD,MAAM,IAAI,eAAe,CAAC,6BAA6B,GAAG,GAAG,CAAC,CAAC;AACjE,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,KAAuB,EAAE,OAAyB;IACrE,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,SAAS;YACZ,OAAO,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC;QAClC,KAAK,OAAO;YACV,OAAO,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC;QAC/D,KAAK,OAAO;YACV,OAAO,CACL,KAAK,CAAC,IAAI,KAAK,OAAO;gBACtB,KAAK,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM;gBAC/B,CAAC,KAAK,CAAC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAC1D,CAAC;QACJ,KAAK,SAAS;YACZ,OAAO,CACL,KAAK,CAAC,IAAI,KAAK,SAAS;gBACxB,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CACpD,CAAC;QACJ,KAAK,UAAU;YACb,OAAO,CACL,KAAK,CAAC,IAAI,KAAK,UAAU;gBACzB,CAAC,KAAK,CAAC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAC1D,CAAC;IACN,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,aAAa;IACP,MAAM,CAA8B;IACpC,gBAAgB,CAAU;IAC1B,eAAe,CAAU;IAE1C,YACE,MAAmC,EACnC,gBAAyB,EACzB,eAAwB;QAExB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;QACzC,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IACzC,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,YAAY,CAAC,QAAsB;QACxC,MAAM,SAAS,GAAG,QAAQ,CAAC,YAAY,IAAI,EAAE,CAAC;QAC9C,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1D,MAAM,IAAI,eAAe,CACvB,QAAQ,QAAQ,CAAC,EAAE,uDAAuD;gBACxE,wBAAwB,QAAQ,CAAC,KAAK,IAAI,CAC7C,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5D,OAAO,IAAI,aAAa,CACtB,MAAM,EACN,QAAQ,CAAC,gBAAgB,IAAI,KAAK,EAClC,QAAQ,CAAC,eAAe,IAAI,KAAK,CAClC,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,GAAG,CAAC,GAAW;QACb,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;QACrC,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;IAClE,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,GAAW;QACf,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO;QAC1B,MAAM,IAAI,eAAe,CACvB,iCAAiC,GAAG,qCAAqC,CAC1E,CAAC;IACJ,CAAC;IAED,0EAA0E;IAC1E,kBAAkB;QAChB,OAAO,IAAI,CAAC,gBAAgB,CAAC;IAC/B,CAAC;IAED,6DAA6D;IAC7D,iBAAiB;QACf,OAAO,IAAI,CAAC,eAAe,CAAC;IAC9B,CAAC;CACF"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record composition: how a stack of packs becomes one game.
|
|
3
|
+
*
|
|
4
|
+
* Every record in the composed game is identified by a PackRef
|
|
5
|
+
* ("<owner-pack>:<slug>"). Packs may:
|
|
6
|
+
* - add records (they become the owner),
|
|
7
|
+
* - patch records owned by packs they declare as dependencies
|
|
8
|
+
* (deep merge: objects merge per key, arrays and scalars replace,
|
|
9
|
+
* an explicit null deletes the key),
|
|
10
|
+
* - replace such records wholesale, or
|
|
11
|
+
* - remove them.
|
|
12
|
+
*
|
|
13
|
+
* The base game is pack zero ("core") and gets no special treatment:
|
|
14
|
+
* a total conversion is just a pack that replaces or removes core
|
|
15
|
+
* records. Composition is deterministic given the resolved load order,
|
|
16
|
+
* and every record carries provenance (owner plus every pack that
|
|
17
|
+
* modified it) for savefiles and debugging.
|
|
18
|
+
*/
|
|
19
|
+
import type { PackManifest, PackRef } from "./manifest.js";
|
|
20
|
+
import type { FieldPatch } from "./patch.js";
|
|
21
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
22
|
+
[key: string]: JsonValue;
|
|
23
|
+
};
|
|
24
|
+
export type JsonRecord = {
|
|
25
|
+
[key: string]: JsonValue;
|
|
26
|
+
};
|
|
27
|
+
/** One pack's contribution to one record file (e.g. "monster"). */
|
|
28
|
+
export interface FileContribution {
|
|
29
|
+
/** New records; this pack becomes their owner. Each needs a name. */
|
|
30
|
+
records?: JsonRecord[];
|
|
31
|
+
/** Deep-merge patches onto records owned by declared dependencies. */
|
|
32
|
+
patches?: Record<string, JsonRecord>;
|
|
33
|
+
/** Wholesale replacements (owner and ref are preserved). */
|
|
34
|
+
replaces?: Record<string, JsonRecord>;
|
|
35
|
+
/** Refs to delete from the composed game. */
|
|
36
|
+
removes?: string[];
|
|
37
|
+
/**
|
|
38
|
+
* Field-level patches (see patch.ts): ordered field ops per target ref.
|
|
39
|
+
* composePacks applies these in load order after the coarse `patches`/
|
|
40
|
+
* `replaces` for the same pack (each pack's ops fold onto the running
|
|
41
|
+
* value, which is identical to composeFieldPatches over the ordered
|
|
42
|
+
* list). The pre-launch conflict report (P7 phase 6) reads the same data
|
|
43
|
+
* to find same-field collisions without the false-positive whole-record
|
|
44
|
+
* conflicts `patches` produces.
|
|
45
|
+
*/
|
|
46
|
+
fieldPatches?: Record<string, FieldPatch>;
|
|
47
|
+
}
|
|
48
|
+
export interface PackContent {
|
|
49
|
+
manifest: PackManifest;
|
|
50
|
+
/** Contributions keyed by record file: "monster", "object", ... */
|
|
51
|
+
files: Record<string, FileContribution>;
|
|
52
|
+
}
|
|
53
|
+
export interface ComposedRecord {
|
|
54
|
+
ref: PackRef;
|
|
55
|
+
/** The pack that added the record. */
|
|
56
|
+
owner: string;
|
|
57
|
+
/** Every pack that patched or replaced it, in load order. */
|
|
58
|
+
modifiedBy: string[];
|
|
59
|
+
value: JsonRecord;
|
|
60
|
+
}
|
|
61
|
+
export declare class ComposeError extends Error {
|
|
62
|
+
}
|
|
63
|
+
/** Deep merge per the pack patch rules. Returns a new object. */
|
|
64
|
+
export declare function mergePatch(base: JsonRecord, patch: JsonRecord): JsonRecord;
|
|
65
|
+
/**
|
|
66
|
+
* Compose packs (already in resolved load order) into per-file record
|
|
67
|
+
* maps. Iteration order of each map is deterministic: records appear
|
|
68
|
+
* in the order their owning packs added them.
|
|
69
|
+
*/
|
|
70
|
+
export declare function composePacks(packs: readonly PackContent[]): Map<string, Map<PackRef, ComposedRecord>>;
|
|
71
|
+
//# sourceMappingURL=compose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,MAAM,SAAS,GACjB,IAAI,GACJ,OAAO,GACP,MAAM,GACN,MAAM,GACN,SAAS,EAAE,GACX;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AACjC,MAAM,MAAM,UAAU,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEtD,mEAAmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,qEAAqE;IACrE,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACtC,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,YAAY,CAAC;IACvB,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,OAAO,CAAC;IACb,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,qBAAa,YAAa,SAAQ,KAAK;CAAG;AAE1C,iEAAiE;AACjE,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAkB1E;AAWD;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,WAAW,EAAE,GAC5B,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CA4E3C"}
|
package/dist/compose.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record composition: how a stack of packs becomes one game.
|
|
3
|
+
*
|
|
4
|
+
* Every record in the composed game is identified by a PackRef
|
|
5
|
+
* ("<owner-pack>:<slug>"). Packs may:
|
|
6
|
+
* - add records (they become the owner),
|
|
7
|
+
* - patch records owned by packs they declare as dependencies
|
|
8
|
+
* (deep merge: objects merge per key, arrays and scalars replace,
|
|
9
|
+
* an explicit null deletes the key),
|
|
10
|
+
* - replace such records wholesale, or
|
|
11
|
+
* - remove them.
|
|
12
|
+
*
|
|
13
|
+
* The base game is pack zero ("core") and gets no special treatment:
|
|
14
|
+
* a total conversion is just a pack that replaces or removes core
|
|
15
|
+
* records. Composition is deterministic given the resolved load order,
|
|
16
|
+
* and every record carries provenance (owner plus every pack that
|
|
17
|
+
* modified it) for savefiles and debugging.
|
|
18
|
+
*/
|
|
19
|
+
import { packRef } from "./manifest.js";
|
|
20
|
+
import { applyFieldPatch } from "./patch.js";
|
|
21
|
+
export class ComposeError extends Error {
|
|
22
|
+
}
|
|
23
|
+
/** Deep merge per the pack patch rules. Returns a new object. */
|
|
24
|
+
export function mergePatch(base, patch) {
|
|
25
|
+
const out = { ...base };
|
|
26
|
+
for (const [key, val] of Object.entries(patch)) {
|
|
27
|
+
if (val === null) {
|
|
28
|
+
delete out[key];
|
|
29
|
+
}
|
|
30
|
+
else if (typeof val === "object" &&
|
|
31
|
+
!Array.isArray(val) &&
|
|
32
|
+
typeof out[key] === "object" &&
|
|
33
|
+
out[key] !== null &&
|
|
34
|
+
!Array.isArray(out[key])) {
|
|
35
|
+
out[key] = mergePatch(out[key], val);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
out[key] = val;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
function mayModify(m, ownerPack) {
|
|
44
|
+
return ownerPack === m.id || (m.dependencies ?? {})[ownerPack] !== undefined;
|
|
45
|
+
}
|
|
46
|
+
function ownerOf(ref) {
|
|
47
|
+
const at = ref.indexOf(":");
|
|
48
|
+
return at === -1 ? "" : ref.slice(0, at);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Compose packs (already in resolved load order) into per-file record
|
|
52
|
+
* maps. Iteration order of each map is deterministic: records appear
|
|
53
|
+
* in the order their owning packs added them.
|
|
54
|
+
*/
|
|
55
|
+
export function composePacks(packs) {
|
|
56
|
+
const game = new Map();
|
|
57
|
+
for (const pack of packs) {
|
|
58
|
+
const pid = pack.manifest.id;
|
|
59
|
+
for (const [file, contrib] of Object.entries(pack.files)) {
|
|
60
|
+
let table = game.get(file);
|
|
61
|
+
if (!table) {
|
|
62
|
+
table = new Map();
|
|
63
|
+
game.set(file, table);
|
|
64
|
+
}
|
|
65
|
+
for (const rec of contrib.records ?? []) {
|
|
66
|
+
const name = rec["name"];
|
|
67
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
68
|
+
throw new ComposeError(`${pid}/${file}: record without a name`);
|
|
69
|
+
}
|
|
70
|
+
const ref = packRef(pid, name);
|
|
71
|
+
if (table.has(ref)) {
|
|
72
|
+
throw new ComposeError(`${pid}/${file}: duplicate record ${ref}`);
|
|
73
|
+
}
|
|
74
|
+
table.set(ref, { ref, owner: pid, modifiedBy: [], value: rec });
|
|
75
|
+
}
|
|
76
|
+
for (const kind of ["patches", "replaces"]) {
|
|
77
|
+
for (const [refStr, body] of Object.entries(contrib[kind] ?? {})) {
|
|
78
|
+
const ref = refStr;
|
|
79
|
+
const existing = table.get(ref);
|
|
80
|
+
if (!existing) {
|
|
81
|
+
const verb = kind === "patches" ? "patch" : "replace";
|
|
82
|
+
throw new ComposeError(`${pid}/${file}: ${verb} target ${ref} does not exist`);
|
|
83
|
+
}
|
|
84
|
+
if (!mayModify(pack.manifest, ownerOf(ref))) {
|
|
85
|
+
throw new ComposeError(`${pid}/${file}: cannot modify ${ref} without declaring ${ownerOf(ref)} as a dependency`);
|
|
86
|
+
}
|
|
87
|
+
existing.value =
|
|
88
|
+
kind === "patches" ? mergePatch(existing.value, body) : body;
|
|
89
|
+
existing.modifiedBy.push(pid);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const [refStr, ops] of Object.entries(contrib.fieldPatches ?? {})) {
|
|
93
|
+
const ref = refStr;
|
|
94
|
+
const existing = table.get(ref);
|
|
95
|
+
if (!existing) {
|
|
96
|
+
throw new ComposeError(`${pid}/${file}: fieldPatch target ${ref} does not exist`);
|
|
97
|
+
}
|
|
98
|
+
if (!mayModify(pack.manifest, ownerOf(ref))) {
|
|
99
|
+
throw new ComposeError(`${pid}/${file}: cannot modify ${ref} without declaring ${ownerOf(ref)} as a dependency`);
|
|
100
|
+
}
|
|
101
|
+
existing.value = applyFieldPatch(existing.value, ops);
|
|
102
|
+
existing.modifiedBy.push(pid);
|
|
103
|
+
}
|
|
104
|
+
for (const refStr of contrib.removes ?? []) {
|
|
105
|
+
const ref = refStr;
|
|
106
|
+
if (!table.has(ref)) {
|
|
107
|
+
throw new ComposeError(`${pid}/${file}: remove target ${ref} does not exist`);
|
|
108
|
+
}
|
|
109
|
+
if (!mayModify(pack.manifest, ownerOf(ref))) {
|
|
110
|
+
throw new ComposeError(`${pid}/${file}: cannot remove ${ref} without declaring ${ownerOf(ref)} as a dependency`);
|
|
111
|
+
}
|
|
112
|
+
table.delete(ref);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return game;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=compose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"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"}
|