@rpgjs/map-builder 0.0.0-stage → 5.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/LICENSE +19 -0
- package/README.md +341 -2
- package/dist/authority/authority.d.ts +118 -0
- package/dist/authority/authority.d.ts.map +1 -0
- package/dist/authority/authority.js +289 -0
- package/dist/authority/authority.js.map +1 -0
- package/dist/authority/index.d.ts +3 -0
- package/dist/authority/index.d.ts.map +1 -0
- package/dist/authority/index.js +3 -0
- package/dist/authority/store.d.ts +36 -0
- package/dist/authority/store.d.ts.map +1 -0
- package/dist/authority/store.js +35 -0
- package/dist/authority/store.js.map +1 -0
- package/dist/client/index.d.ts +72 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +200 -0
- package/dist/client/index.js.map +1 -0
- package/dist/embed/client.d.ts +85 -0
- package/dist/embed/client.d.ts.map +1 -0
- package/dist/embed/client.js +255 -0
- package/dist/embed/client.js.map +1 -0
- package/dist/embed/host.d.ts +112 -0
- package/dist/embed/host.d.ts.map +1 -0
- package/dist/embed/host.js +278 -0
- package/dist/embed/host.js.map +1 -0
- package/dist/embed/index.d.ts +4 -0
- package/dist/embed/index.d.ts.map +1 -0
- package/dist/embed/index.js +4 -0
- package/dist/embed/protocol.d.ts +137 -0
- package/dist/embed/protocol.d.ts.map +1 -0
- package/dist/embed/protocol.js +161 -0
- package/dist/embed/protocol.js.map +1 -0
- package/dist/features/index.d.ts +66 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +157 -0
- package/dist/features/index.js.map +1 -0
- package/dist/features/skin.d.ts +9 -0
- package/dist/features/skin.d.ts.map +1 -0
- package/dist/features/skin.js +43 -0
- package/dist/features/skin.js.map +1 -0
- package/dist/features/terrain.d.ts +66 -0
- package/dist/features/terrain.d.ts.map +1 -0
- package/dist/features/terrain.js +291 -0
- package/dist/features/terrain.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/interaction/builtin-intents.d.ts +9 -0
- package/dist/interaction/builtin-intents.d.ts.map +1 -0
- package/dist/interaction/builtin-intents.js +61 -0
- package/dist/interaction/builtin-intents.js.map +1 -0
- package/dist/interaction/catalog.d.ts +24 -0
- package/dist/interaction/catalog.d.ts.map +1 -0
- package/dist/interaction/catalog.js +20 -0
- package/dist/interaction/catalog.js.map +1 -0
- package/dist/interaction/controller.d.ts +137 -0
- package/dist/interaction/controller.d.ts.map +1 -0
- package/dist/interaction/controller.js +215 -0
- package/dist/interaction/controller.js.map +1 -0
- package/dist/interaction/index.d.ts +14 -0
- package/dist/interaction/index.d.ts.map +1 -0
- package/dist/interaction/index.js +13 -0
- package/dist/interaction/input.d.ts +26 -0
- package/dist/interaction/input.d.ts.map +1 -0
- package/dist/interaction/input.js +33 -0
- package/dist/interaction/input.js.map +1 -0
- package/dist/interaction/intents.d.ts +80 -0
- package/dist/interaction/intents.d.ts.map +1 -0
- package/dist/interaction/keyboard.d.ts +26 -0
- package/dist/interaction/keyboard.d.ts.map +1 -0
- package/dist/interaction/keyboard.js +32 -0
- package/dist/interaction/keyboard.js.map +1 -0
- package/dist/interaction/picking.d.ts +33 -0
- package/dist/interaction/picking.d.ts.map +1 -0
- package/dist/interaction/picking.js +35 -0
- package/dist/interaction/picking.js.map +1 -0
- package/dist/interaction/pipeline.d.ts +45 -0
- package/dist/interaction/pipeline.d.ts.map +1 -0
- package/dist/interaction/pipeline.js +26 -0
- package/dist/interaction/pipeline.js.map +1 -0
- package/dist/interaction/placement.d.ts +76 -0
- package/dist/interaction/placement.d.ts.map +1 -0
- package/dist/interaction/placement.js +238 -0
- package/dist/interaction/placement.js.map +1 -0
- package/dist/interaction/selection-tool.d.ts +29 -0
- package/dist/interaction/selection-tool.d.ts.map +1 -0
- package/dist/interaction/selection-tool.js +190 -0
- package/dist/interaction/selection-tool.js.map +1 -0
- package/dist/interaction/steps.d.ts +62 -0
- package/dist/interaction/steps.d.ts.map +1 -0
- package/dist/interaction/steps.js +126 -0
- package/dist/interaction/steps.js.map +1 -0
- package/dist/interaction/store.d.ts +10 -0
- package/dist/interaction/store.d.ts.map +1 -0
- package/dist/interaction/store.js +28 -0
- package/dist/interaction/store.js.map +1 -0
- package/dist/interaction/tools.d.ts +26 -0
- package/dist/interaction/tools.d.ts.map +1 -0
- package/dist/interaction/tools.js +41 -0
- package/dist/interaction/tools.js.map +1 -0
- package/dist/model/assets.d.ts +36 -0
- package/dist/model/assets.d.ts.map +1 -0
- package/dist/model/assets.js +18 -0
- package/dist/model/assets.js.map +1 -0
- package/dist/model/committer.d.ts +66 -0
- package/dist/model/committer.d.ts.map +1 -0
- package/dist/model/committer.js +38 -0
- package/dist/model/committer.js.map +1 -0
- package/dist/model/core-patches.d.ts +2 -0
- package/dist/model/core-patches.d.ts.map +1 -0
- package/dist/model/core-patches.js +193 -0
- package/dist/model/core-patches.js.map +1 -0
- package/dist/model/define-map.d.ts +52 -0
- package/dist/model/define-map.d.ts.map +1 -0
- package/dist/model/define-map.js +66 -0
- package/dist/model/define-map.js.map +1 -0
- package/dist/model/definition.d.ts +57 -0
- package/dist/model/definition.d.ts.map +1 -0
- package/dist/model/definition.js +6 -0
- package/dist/model/definition.js.map +1 -0
- package/dist/model/errors.d.ts +21 -0
- package/dist/model/errors.d.ts.map +1 -0
- package/dist/model/errors.js +14 -0
- package/dist/model/errors.js.map +1 -0
- package/dist/model/history.d.ts +43 -0
- package/dist/model/history.d.ts.map +1 -0
- package/dist/model/history.js +141 -0
- package/dist/model/history.js.map +1 -0
- package/dist/model/index.d.ts +14 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/index.js +14 -0
- package/dist/model/json.d.ts +15 -0
- package/dist/model/json.d.ts.map +1 -0
- package/dist/model/json.js +35 -0
- package/dist/model/json.js.map +1 -0
- package/dist/model/model.d.ts +68 -0
- package/dist/model/model.d.ts.map +1 -0
- package/dist/model/model.js +170 -0
- package/dist/model/model.js.map +1 -0
- package/dist/model/parse.d.ts +24 -0
- package/dist/model/parse.d.ts.map +1 -0
- package/dist/model/parse.js +165 -0
- package/dist/model/parse.js.map +1 -0
- package/dist/model/patches.d.ts +100 -0
- package/dist/model/patches.d.ts.map +1 -0
- package/dist/model/patches.js +21 -0
- package/dist/model/patches.js.map +1 -0
- package/dist/model/result.d.ts +22 -0
- package/dist/model/result.d.ts.map +1 -0
- package/dist/model/result.js +10 -0
- package/dist/model/result.js.map +1 -0
- package/dist/model/source.d.ts +52 -0
- package/dist/model/source.d.ts.map +1 -0
- package/dist/model/source.js +75 -0
- package/dist/model/source.js.map +1 -0
- package/dist/protocol/index.d.ts +92 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +106 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/remote/committer.d.ts +43 -0
- package/dist/remote/committer.d.ts.map +1 -0
- package/dist/remote/committer.js +206 -0
- package/dist/remote/committer.js.map +1 -0
- package/dist/remote/index.d.ts +4 -0
- package/dist/remote/index.d.ts.map +1 -0
- package/dist/remote/index.js +4 -0
- package/dist/remote/join.d.ts +30 -0
- package/dist/remote/join.d.ts.map +1 -0
- package/dist/remote/join.js +36 -0
- package/dist/remote/join.js.map +1 -0
- package/dist/remote/memory-transport.d.ts +20 -0
- package/dist/remote/memory-transport.d.ts.map +1 -0
- package/dist/remote/memory-transport.js +48 -0
- package/dist/remote/memory-transport.js.map +1 -0
- package/dist/renderer/effects.d.ts +52 -0
- package/dist/renderer/effects.d.ts.map +1 -0
- package/dist/renderer/effects.js +111 -0
- package/dist/renderer/effects.js.map +1 -0
- package/dist/renderer/feature.d.ts +79 -0
- package/dist/renderer/feature.d.ts.map +1 -0
- package/dist/renderer/feature.js +50 -0
- package/dist/renderer/feature.js.map +1 -0
- package/dist/renderer/geometry.d.ts +59 -0
- package/dist/renderer/geometry.d.ts.map +1 -0
- package/dist/renderer/geometry.js +114 -0
- package/dist/renderer/geometry.js.map +1 -0
- package/dist/renderer/index.d.ts +7 -0
- package/dist/renderer/index.d.ts.map +1 -0
- package/dist/renderer/index.js +7 -0
- package/dist/renderer/scene.d.ts +77 -0
- package/dist/renderer/scene.d.ts.map +1 -0
- package/dist/renderer/scene.js +125 -0
- package/dist/renderer/scene.js.map +1 -0
- package/dist/renderer/textures.d.ts +20 -0
- package/dist/renderer/textures.d.ts.map +1 -0
- package/dist/renderer/textures.js +61 -0
- package/dist/renderer/textures.js.map +1 -0
- package/dist/renderer/view.d.ts +139 -0
- package/dist/renderer/view.d.ts.map +1 -0
- package/dist/renderer/view.js +349 -0
- package/dist/renderer/view.js.map +1 -0
- package/dist/rpgjs/shared.d.ts +41 -0
- package/dist/rpgjs/shared.d.ts.map +1 -0
- package/dist/rpgjs/shared.js +77 -0
- package/dist/rpgjs/shared.js.map +1 -0
- package/dist/server/index.d.ts +64 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +205 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/storage.d.ts +16 -0
- package/dist/server/storage.d.ts.map +1 -0
- package/dist/server/storage.js +52 -0
- package/dist/server/storage.js.map +1 -0
- package/dist/skins/default.d.ts +27 -0
- package/dist/skins/default.d.ts.map +1 -0
- package/dist/skins/default.js +144 -0
- package/dist/skins/default.js.map +1 -0
- package/dist/skins/frames.d.ts +19 -0
- package/dist/skins/frames.d.ts.map +1 -0
- package/dist/skins/frames.js +49 -0
- package/dist/skins/frames.js.map +1 -0
- package/dist/skins/game.d.ts +21 -0
- package/dist/skins/game.d.ts.map +1 -0
- package/dist/skins/game.js +21 -0
- package/dist/skins/game.js.map +1 -0
- package/dist/skins/index.d.ts +5 -0
- package/dist/skins/index.d.ts.map +1 -0
- package/dist/skins/index.js +5 -0
- package/dist/skins/types.d.ts +46 -0
- package/dist/skins/types.d.ts.map +1 -0
- package/dist/skins/types.js +12 -0
- package/dist/skins/types.js.map +1 -0
- package/dist/terrain/data.d.ts +10 -0
- package/dist/terrain/data.d.ts.map +1 -0
- package/dist/terrain/data.js +83 -0
- package/dist/terrain/data.js.map +1 -0
- package/dist/terrain/define.d.ts +72 -0
- package/dist/terrain/define.d.ts.map +1 -0
- package/dist/terrain/define.js +91 -0
- package/dist/terrain/define.js.map +1 -0
- package/dist/terrain/editing.d.ts +54 -0
- package/dist/terrain/editing.d.ts.map +1 -0
- package/dist/terrain/editing.js +169 -0
- package/dist/terrain/editing.js.map +1 -0
- package/dist/terrain/index.d.ts +10 -0
- package/dist/terrain/index.d.ts.map +1 -0
- package/dist/terrain/index.js +10 -0
- package/dist/terrain/kernel.d.ts +57 -0
- package/dist/terrain/kernel.d.ts.map +1 -0
- package/dist/terrain/kernel.js +198 -0
- package/dist/terrain/kernel.js.map +1 -0
- package/dist/terrain/mask.d.ts +37 -0
- package/dist/terrain/mask.d.ts.map +1 -0
- package/dist/terrain/mask.js +244 -0
- package/dist/terrain/mask.js.map +1 -0
- package/dist/terrain/patches.d.ts +32 -0
- package/dist/terrain/patches.d.ts.map +1 -0
- package/dist/terrain/patches.js +135 -0
- package/dist/terrain/patches.js.map +1 -0
- package/dist/terrain/rle.d.ts +4 -0
- package/dist/terrain/rle.d.ts.map +1 -0
- package/dist/terrain/rle.js +68 -0
- package/dist/terrain/rle.js.map +1 -0
- package/dist/terrain/scheduler.d.ts +46 -0
- package/dist/terrain/scheduler.d.ts.map +1 -0
- package/dist/terrain/scheduler.js +148 -0
- package/dist/terrain/scheduler.js.map +1 -0
- package/dist/terrain/types.d.ts +110 -0
- package/dist/terrain/types.d.ts.map +1 -0
- package/dist/terrain/types.js +11 -0
- package/dist/terrain/types.js.map +1 -0
- package/dist/terrain-render/backend.d.ts +66 -0
- package/dist/terrain-render/backend.d.ts.map +1 -0
- package/dist/terrain-render/backend.js +96 -0
- package/dist/terrain-render/backend.js.map +1 -0
- package/dist/terrain-render/host.d.ts +21 -0
- package/dist/terrain-render/host.d.ts.map +1 -0
- package/dist/terrain-render/host.js +62 -0
- package/dist/terrain-render/host.js.map +1 -0
- package/dist/terrain-render/index.d.ts +3 -0
- package/dist/terrain-render/index.d.ts.map +1 -0
- package/dist/terrain-render/index.js +3 -0
- package/dist/terrain-render/worker.d.ts +2 -0
- package/dist/terrain-render/worker.d.ts.map +1 -0
- package/dist/terrain-render/worker.js +34 -0
- package/dist/terrain-render/worker.js.map +1 -0
- package/package.json +124 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (C) 2026 by Samuel Ronce
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,342 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@rpgjs/map-builder`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Experimental, private.** The API changes until the Studio integration
|
|
4
|
+
> validates it. See [`MAP_BUILDER.md`](../../MAP_BUILDER.md) for the design.
|
|
5
|
+
|
|
6
|
+
A modifiable map: one model, changed only by patches, usable by a game, an
|
|
7
|
+
editor or both.
|
|
8
|
+
|
|
9
|
+
| Entry | What it gives | Needs PixiJS |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| `@rpgjs/map-builder/model` | The model, patches, history, sources. Runs in plain Node, without a DOM. | no |
|
|
12
|
+
| `@rpgjs/map-builder/renderer` | `createMapView`: draws a model with PixiJS, updates it patch by patch. | yes (peer dependency) |
|
|
13
|
+
| `@rpgjs/map-builder/features` | `elements()`, `events()`, `selection()`, `placement()`, `history()`. | yes (through the skins) |
|
|
14
|
+
| `@rpgjs/map-builder/interaction` | The headless layer: state, tools, intents, pipelines, catalog. Runs on a server, to validate with the same rules. | no |
|
|
15
|
+
| `@rpgjs/map-builder/skins` | How the interaction looks: `defaultSkin`, `gameSkin`, and the contract to write your own. | yes |
|
|
16
|
+
| `@rpgjs/map-builder/protocol` | The messages between a client and the authority of a map, validated. | no |
|
|
17
|
+
| `@rpgjs/map-builder/authority` | The authority over a map: decides, tells everybody, catches clients up, persists. Runs on a server. | no |
|
|
18
|
+
| `@rpgjs/map-builder/remote` | The client side: a model that asks the authority and follows everybody's changes. | no |
|
|
19
|
+
| `@rpgjs/map-builder/server` | RPGJS: one authority per map, colliders, events. | no (needs `@rpgjs/server`) |
|
|
20
|
+
| `@rpgjs/map-builder/client` | RPGJS: joins the map, view, prediction colliders. | yes (needs `@rpgjs/client`) |
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { createMapModel, defineAssets, defineMap, el } from "@rpgjs/map-builder/model";
|
|
24
|
+
|
|
25
|
+
const assets = defineAssets({ "tree-oak": { src: "/img/tree-oak.png" } });
|
|
26
|
+
|
|
27
|
+
// A map written in code. References to assets are checked by the compiler.
|
|
28
|
+
const forest = defineMap(assets, {
|
|
29
|
+
id: "forest",
|
|
30
|
+
size: [2560, 1920], // pixels
|
|
31
|
+
elements: [el("tree-oak", { id: "t1", x: 128, y: 96, data: { hits: 5 } })],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const model = createMapModel(forest);
|
|
35
|
+
|
|
36
|
+
model.subscribe(({ patches }) => console.log("changed", patches));
|
|
37
|
+
|
|
38
|
+
model.apply({ type: "element.move", id: "t1", x: 160, y: 96 });
|
|
39
|
+
|
|
40
|
+
// A batch is atomic and gets a single revision.
|
|
41
|
+
model.apply([
|
|
42
|
+
{ type: "element.remove", id: "t1" },
|
|
43
|
+
{ type: "event.add", event: { id: "wood-1", kind: "loot", x: 160, y: 96 } },
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
model.history.undo(); // restores the tree and removes the loot, in one step
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Drawing a map
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { createMapView } from "@rpgjs/map-builder/renderer";
|
|
53
|
+
import { elements, events } from "@rpgjs/map-builder/features";
|
|
54
|
+
|
|
55
|
+
const view = createMapView({
|
|
56
|
+
model,
|
|
57
|
+
assets,
|
|
58
|
+
features: [elements(), events()], // imported objects, so unused ones are tree-shaken
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await view.mount(document.getElementById("game")!); // creates a PixiJS application…
|
|
62
|
+
// …or host `view.root` in any PixiJS stage you already have:
|
|
63
|
+
app.stage.addChild(view.root);
|
|
64
|
+
|
|
65
|
+
model.apply({ type: "element.move", id: "t1", x: 160, y: 96 }); // only t1 is updated
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The view follows the model whatever its committer: it listens to the changes
|
|
69
|
+
that were applied, and describes again only the elements a patch touched.
|
|
70
|
+
|
|
71
|
+
- **Depth.** Elements are drawn back to front by a sort key: the bottom of the
|
|
72
|
+
hitbox (or of the drawing without one) plus `depth.z`, as the Studio renderer
|
|
73
|
+
does. `depth.anchorY` replaces the bottom with an explicit offset.
|
|
74
|
+
- **Assets.** `AssetDescriptor` has an optional `frame` (an asset cut out of a
|
|
75
|
+
tileset), a `size`, and a `hitbox` in source pixels. An asset that declares
|
|
76
|
+
neither `frame` nor `size` is measured once its image has loaded.
|
|
77
|
+
- **Culling.** `view.setViewport(rect)` hides what is outside the camera, with a
|
|
78
|
+
margin (64 px by default). `null` shows everything.
|
|
79
|
+
- **Hooks.** `resolveElementSprite` changes how an element is drawn (Studio uses
|
|
80
|
+
it for its drawing rules), `onBeforeRenderElement` adjusts the result,
|
|
81
|
+
`onMissingAsset` reports unknown assets.
|
|
82
|
+
- **Effects.** Short visual changes that never touch the model, so they are not
|
|
83
|
+
saved, undoable nor replayed to late joiners:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const handle = view.effects.play("t1", "shake", { intensity: 4 });
|
|
87
|
+
await handle.finished; // "completed" | "cancelled" | "target-removed" | …
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`shake` and `blink` are built in; add yours with `effects: { lift }` or
|
|
91
|
+
`view.effects.register`. Time only moves on `view.tick(deltaMs)`, which `mount`
|
|
92
|
+
calls every frame, so effects are deterministic.
|
|
93
|
+
|
|
94
|
+
## Editing, and playing, on the same map
|
|
95
|
+
|
|
96
|
+
The same view serves a game and an editor. What changes is the list of features,
|
|
97
|
+
the active modes and the rules.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const catalog = defineCatalog([
|
|
101
|
+
{ id: "oak", sprite: "tree-oak", pipeline: [/* rules of this item */] },
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
const view = createMapView({
|
|
105
|
+
model,
|
|
106
|
+
assets,
|
|
107
|
+
features: [
|
|
108
|
+
elements({ pipeline: { move: [snapToGrid(16)] } }), // rules for every move, whoever asks
|
|
109
|
+
events(),
|
|
110
|
+
selection({ drag: { live: true }, remove: true }),
|
|
111
|
+
placement({ catalog, pipeline: [snapToGrid(32), withinMap(), requireFree()] }),
|
|
112
|
+
history(),
|
|
113
|
+
],
|
|
114
|
+
modes: ["edit"],
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
view.placement.start("oak"); // a ghost follows the pointer; typed on the catalog ids
|
|
118
|
+
view.selection.current; // what is selected
|
|
119
|
+
view.history.undo();
|
|
120
|
+
view.submit({ type: "element.move", id: "t1", x: 160, y: 96 }); // an intent, not a patch
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Features are imported objects, and each one that has an API exposes it on the view
|
|
124
|
+
under its id: `view.placement`, `view.selection`, `view.history`. The view is typed
|
|
125
|
+
by the list you give: without `placement()`, `view.placement` does not exist.
|
|
126
|
+
|
|
127
|
+
### Intents, pipelines, rejections
|
|
128
|
+
|
|
129
|
+
A user action is an **intent**. It goes through a **pipeline** of steps, then
|
|
130
|
+
becomes patches, which the committer applies. A step may modify the intent, refuse it
|
|
131
|
+
or pass it on, in the style of Koa:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const rules: PipelineStep<"element.place">[] = [
|
|
135
|
+
snapToGrid(32),
|
|
136
|
+
maxCount("tree-oak", 5),
|
|
137
|
+
(intent, ctx, next) => (ctx.actor.inventory.wood >= 2 ? next() : ctx.reject("not-enough-wood")),
|
|
138
|
+
];
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Steps are synchronous and pure, so **the same pipeline runs on the client**, to
|
|
142
|
+
preview with the ghost, **and on a server**, to validate. A refusal is a value:
|
|
143
|
+
`view.submit(...)` returns `{ ok: false, rejection: { code: "occupied", … } }` and
|
|
144
|
+
`interaction.state.get().lastRejection` keeps it, so the UI can say why.
|
|
145
|
+
|
|
146
|
+
Provided steps: `snapToGrid`, `withinMap`, `requireFree`, `maxCount`, `minDistance`.
|
|
147
|
+
|
|
148
|
+
### Modes
|
|
149
|
+
|
|
150
|
+
Tools belong to modes (`play`, `edit`, `inspect`). Several modes can be active at
|
|
151
|
+
once, and a tool can be gated on a modifier key, which is how a player plays while
|
|
152
|
+
Alt turns the same click into editing:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
view.interaction.addTool(playTool({ types: ["event"], onInteract: (target) => talkTo(target) }));
|
|
156
|
+
view.interaction.setModes(["play", "edit"]);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The interaction layer is **headless**: it publishes `interaction.state` (hovered,
|
|
160
|
+
selection, drag, rubber band, ghost, last rejection) and draws nothing. Skins read
|
|
161
|
+
that state:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
selection({ skin: { selection: myGlow } }); // replace one part, keep the others
|
|
165
|
+
placement({ catalog, skin: gameSkin({ ghost: { valid: 0x66ff66, invalid: 0xff5555 }, pulse: true }) });
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Keyboard (Escape cancels, Delete removes) is opt-in: `view.mount(host, { keyboard: true })`
|
|
169
|
+
or `bindKeyboard(view.interaction, window)`.
|
|
170
|
+
|
|
171
|
+
### Adding an intent
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
declare module "@rpgjs/map-builder/interaction" {
|
|
175
|
+
interface IntentRegistry {
|
|
176
|
+
"tilling.till": { readonly x: number; readonly y: number };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Multiplayer
|
|
182
|
+
|
|
183
|
+
One **authority** holds the truth of a map. Clients **ask** with intents, the authority
|
|
184
|
+
decides with the same pipelines the clients use to preview, and **tells everybody** what
|
|
185
|
+
became true, in the same order. None of this depends on a framework: connect it to
|
|
186
|
+
whatever carries messages. (`@rpgjs/map-builder/server` and `/client` do it for RPGJS.)
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
// server
|
|
190
|
+
const authority = createMapAuthority({
|
|
191
|
+
definition,
|
|
192
|
+
assets, // sizes and hitboxes come from here
|
|
193
|
+
pipeline: { move: [ownerOnly({ actor: (who) => who.id }), protectedZone([square])] },
|
|
194
|
+
placement: { catalog, pipeline: [snapToGrid(32), requireFree(), rateLimit({ max: 5, windowMs: 1000 })], stamp: (who) => ({ owner: who.id }) },
|
|
195
|
+
persist: { store, id: "village" }, // snapshots and a log; restoreMapAuthority brings it back
|
|
196
|
+
});
|
|
197
|
+
const connection = authority.connect({ id, actor: player, send: (message) => socket.send(message) });
|
|
198
|
+
socket.onMessage((raw) => connection.receive(raw)); // anything invalid is ignored
|
|
199
|
+
|
|
200
|
+
authority.apply({ type: "element.remove", id: "tree-3" }); // the game itself: a lever, a quest
|
|
201
|
+
authority.effect("tree-3", "shake"); // shown to everybody, never kept
|
|
202
|
+
|
|
203
|
+
// client
|
|
204
|
+
const remote = await joinRemoteMap({ transport }); // { send(message), onMessage(listener) }
|
|
205
|
+
const view = createMapView({ model: remote.model, assets, features: [elements(), placement({ catalog })] });
|
|
206
|
+
bindRemoteEffects(remote, view.effects);
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
- **The client never decides.** Its model changes when the authority says so. `submit`
|
|
210
|
+
and `placement` return a promise that resolves with the decision; a refusal carries the
|
|
211
|
+
reason (`"occupied"`, `"not-owner"`, `"rate-limit"`…). Patches are only accepted from
|
|
212
|
+
clients the authority trusts (`trustPatches`), which is what lets an editor undo.
|
|
213
|
+
- **The authority gives the ids.** A client places something without an id and reads the one
|
|
214
|
+
it was given, so two players can never choose the same.
|
|
215
|
+
- **Late clients catch up.** The authority keeps a log of recent changes: a client that is a
|
|
216
|
+
little behind gets just those, one that is too far behind gets the map. A hole in the
|
|
217
|
+
revisions makes a client ask again by itself.
|
|
218
|
+
- **Persistence.** A `MapStore` keeps a snapshot, written every so many revisions or a
|
|
219
|
+
moment after the last change, and, if it can `append`, every change in between.
|
|
220
|
+
`restoreMapAuthority` loads the snapshot and replays the log, and refuses a log with a hole.
|
|
221
|
+
- **Rules for a shared world.** `ownerOnly`, `rateLimit` (a preview does not count),
|
|
222
|
+
`protectedZone`, next to `snapToGrid`, `requireFree`, `maxCount`… The same steps run on
|
|
223
|
+
the client to preview and on the authority to decide.
|
|
224
|
+
- `connectInMemory(authority)` links a client and an authority with no network: for tests,
|
|
225
|
+
demos, and single-player games that still want the rules.
|
|
226
|
+
|
|
227
|
+
## In an RPGJS game
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
// server.ts
|
|
231
|
+
import { createServer } from "@rpgjs/server";
|
|
232
|
+
import { provideMapBuilder, getMapAuthority } from "@rpgjs/map-builder/server";
|
|
233
|
+
|
|
234
|
+
export default createServer({
|
|
235
|
+
providers: [
|
|
236
|
+
provideMapBuilder({
|
|
237
|
+
assets, // a server has no image to measure: declare `size` or `frame`
|
|
238
|
+
maps: { forest }, // by RPGJS map id; a function or a MapSource work too
|
|
239
|
+
authority: { placement: { catalog, pipeline: [snapToGrid(32), requireFree()] } },
|
|
240
|
+
persist: (map) => ({ store: roomStorageMapStore(map) }), // optional
|
|
241
|
+
}),
|
|
242
|
+
],
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// anywhere in game code: a lever
|
|
246
|
+
getMapAuthority(player.getCurrentMap())?.apply([{ type: "element.remove", id: "gate" }]);
|
|
247
|
+
|
|
248
|
+
// client.ts
|
|
249
|
+
import { startGame } from "@rpgjs/client";
|
|
250
|
+
import { provideMapBuilder, getMapBuilder } from "@rpgjs/map-builder/client";
|
|
251
|
+
|
|
252
|
+
startGame({
|
|
253
|
+
providers: [
|
|
254
|
+
provideMapBuilder({
|
|
255
|
+
assets,
|
|
256
|
+
features: [elements(), selection(), placement({ catalog })],
|
|
257
|
+
onJoin: (handle) => onKey("b", () => handle.view.placement.start("oak")),
|
|
258
|
+
}),
|
|
259
|
+
],
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
- **One authority per map**, created when the map loads and restored from the store if there is
|
|
264
|
+
one. A player is connected to the map they are on and to no other; what they send is checked
|
|
265
|
+
against the map it is about.
|
|
266
|
+
- **Colliders**: each element with a hitbox gets a static collider in the physics of the server
|
|
267
|
+
and of the client (prediction), one namespace per element, so building only touches the
|
|
268
|
+
collider that changed. They are put back whenever the physics is rebuilt.
|
|
269
|
+
- **Events** of the map become RPGJS dynamic events (`builder:<id>`), drawn with their `graphic`.
|
|
270
|
+
Give them behaviour with `events: { define: (event) => ({ hooks: { onAction } }) }`. They are
|
|
271
|
+
recreated when RPGJS wipes them on a map update. The client does not use `events()` for them,
|
|
272
|
+
since RPGJS already draws events.
|
|
273
|
+
- **Drawing**: pass the view to the event layer of the map, so elements and characters are sorted
|
|
274
|
+
in depth together: `<EventLayerComponent pixiChildren={getMapBuilder(engine).pixiChildren} />`.
|
|
275
|
+
`handle.attachTo(container)` adds it anywhere else, without that sorting.
|
|
276
|
+
- Standalone and MMORPG work the same: only `socket.emit`, `player.on` and `map.broadcast` are used.
|
|
277
|
+
|
|
278
|
+
### In a CanvasEngine scene
|
|
279
|
+
|
|
280
|
+
The view is a PixiJS container, which is what CanvasEngine renders to. Host it
|
|
281
|
+
the way other raw PixiJS objects are hosted:
|
|
282
|
+
|
|
283
|
+
```ce
|
|
284
|
+
<Container />
|
|
285
|
+
|
|
286
|
+
<script>
|
|
287
|
+
import { mount, tick } from "canvasengine";
|
|
288
|
+
import { attachMapView } from "@rpgjs/map-builder/renderer";
|
|
289
|
+
|
|
290
|
+
mount((element) => attachMapView(element.componentInstance, view));
|
|
291
|
+
tick(({ deltaTime }) => view.tick(deltaTime));
|
|
292
|
+
</script>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## What is in it
|
|
296
|
+
|
|
297
|
+
| | |
|
|
298
|
+
| --- | --- |
|
|
299
|
+
| `defineAssets`, `defineMap`, `el`, `event` | Describe a map in code. The result is a plain, serializable `MapDefinition`. |
|
|
300
|
+
| `parseMapDefinition` | Validates, migrates and copies raw JSON. Unknown fields are preserved. |
|
|
301
|
+
| `MapSource`: `codeMapSource`, `jsonMapSource`, `httpMapSource` | Where maps come from. `loadRegion` streams large maps. |
|
|
302
|
+
| `AssetResolver` | Turns `"tree-oak"` into an image descriptor. |
|
|
303
|
+
| `createMapModel` | State, revisions, patches, batches, history, `subscribe`. |
|
|
304
|
+
| Patches | `element.add/remove/move/update/data`, `event.add/remove/move`, `extension.set`. Serializable and invertible. |
|
|
305
|
+
| `localCommitter` | Applies changes immediately, and saves them through `persist`. |
|
|
306
|
+
| `createMapView`, `elements()`, `events()` | Draw the model with PixiJS, incrementally. |
|
|
307
|
+
| `defineMapFeature` | Declares a feature with an id, its dependencies and what it draws. Built-in and third-party features use the same contract. |
|
|
308
|
+
|
|
309
|
+
## Rejections are values
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
const result = model.apply({ type: "element.move", id: "nope", x: 1, y: 1 });
|
|
313
|
+
if (!result.ok) result.rejection; // { code: "not-found", message, patchIndex: 0 }
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Adding a patch type
|
|
317
|
+
|
|
318
|
+
A feature extends the registry, so its patches are checked everywhere:
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
declare module "@rpgjs/map-builder/model" {
|
|
322
|
+
interface PatchRegistry {
|
|
323
|
+
"tilling.till": { readonly x: number; readonly y: number };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
createMapModel(definition, {
|
|
328
|
+
patchHandlers: [
|
|
329
|
+
definePatchHandlers({
|
|
330
|
+
"tilling.till": { apply: (state, patch) => patchOk(/* new state */ state, /* inverse patches */ []) },
|
|
331
|
+
}),
|
|
332
|
+
],
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Scripts
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
pnpm --filter @rpgjs/map-builder test # unit tests, in Node
|
|
340
|
+
pnpm --filter @rpgjs/map-builder test:types # strict type check, including the type tests
|
|
341
|
+
pnpm --filter @rpgjs/map-builder build
|
|
342
|
+
```
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { AssetResolver } from '../model/assets';
|
|
2
|
+
import { MapDefinition } from '../model/definition';
|
|
3
|
+
import { MapChange, MapModel } from '../model/model';
|
|
4
|
+
import { PatchHandlers, MapPatch } from '../model/patches';
|
|
5
|
+
import { ApplyFailure, ApplyResult } from '../model/result';
|
|
6
|
+
import { Catalog } from '../interaction/catalog';
|
|
7
|
+
import { InteractionController } from '../interaction/controller';
|
|
8
|
+
import { MapIntent } from '../interaction/intents';
|
|
9
|
+
import { PipelineStep } from '../interaction/pipeline';
|
|
10
|
+
import { PlacementIntentOptions } from '../interaction/placement';
|
|
11
|
+
import { EffectTargetMessage, ServerMessage } from '../protocol';
|
|
12
|
+
import { MapViewHooks } from '../renderer/scene';
|
|
13
|
+
import { MapStore, PatchEntry } from './store';
|
|
14
|
+
/** A client connected to the authority. The authority does not care how: `send` is all it needs. */
|
|
15
|
+
export interface AuthoritySession {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
/** Who is acting: whatever the game decides, handed to the steps of the pipelines. */
|
|
18
|
+
readonly actor: unknown;
|
|
19
|
+
send(message: ServerMessage): void;
|
|
20
|
+
}
|
|
21
|
+
export interface AuthorityConnection {
|
|
22
|
+
readonly session: AuthoritySession;
|
|
23
|
+
/** Gives the authority what the client sent. Anything that is not a valid message is ignored. */
|
|
24
|
+
receive(raw: unknown): void;
|
|
25
|
+
disconnect(): void;
|
|
26
|
+
}
|
|
27
|
+
export interface PersistOptions {
|
|
28
|
+
readonly store: MapStore;
|
|
29
|
+
/** Key of the map in the store. */
|
|
30
|
+
readonly id: string;
|
|
31
|
+
/** Write a snapshot every this many revisions. Defaults to 50. */
|
|
32
|
+
readonly snapshotEvery?: number;
|
|
33
|
+
/** Write a snapshot this long after the last change. Defaults to 1000. */
|
|
34
|
+
readonly saveDelayMs?: number;
|
|
35
|
+
/** Replaces `setTimeout`, so tests control time. Returns the function that cancels it. */
|
|
36
|
+
readonly schedule?: (run: () => void, delayMs: number) => () => void;
|
|
37
|
+
/**
|
|
38
|
+
* The store already holds a snapshot of the state the authority starts from, so it does
|
|
39
|
+
* not write one first. `restoreMapAuthority` sets it. Otherwise the authority writes the
|
|
40
|
+
* starting snapshot itself, so that the changes it appends have something to follow.
|
|
41
|
+
*/
|
|
42
|
+
readonly baseSaved?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface MapAuthorityOptions<C extends Catalog<string> = Catalog<string>> {
|
|
45
|
+
readonly definition: MapDefinition;
|
|
46
|
+
/** Revision the map starts at. Defaults to 0. */
|
|
47
|
+
readonly revision?: number;
|
|
48
|
+
/**
|
|
49
|
+
* What the map is made of. Sizes and hitboxes come from here, so assets must declare
|
|
50
|
+
* a `size` or a `frame`: a server has no image to measure.
|
|
51
|
+
*/
|
|
52
|
+
readonly assets: AssetResolver;
|
|
53
|
+
readonly hooks?: MapViewHooks;
|
|
54
|
+
/** Rules for every move and removal, whoever asks. The same steps the clients use to preview. */
|
|
55
|
+
readonly pipeline?: {
|
|
56
|
+
readonly move?: readonly PipelineStep<"element.move">[];
|
|
57
|
+
readonly remove?: readonly PipelineStep<"element.remove">[];
|
|
58
|
+
};
|
|
59
|
+
/** What can be placed, and the rules for it. */
|
|
60
|
+
readonly placement?: PlacementIntentOptions<C>;
|
|
61
|
+
/** Handlers of the patch types features add (`terrainPatchHandlers`): the authority must know them to apply them. */
|
|
62
|
+
readonly patchHandlers?: readonly PatchHandlers[];
|
|
63
|
+
/** Registers more intents and pipelines, e.g. those of a custom feature. */
|
|
64
|
+
readonly setup?: (controller: InteractionController) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Whether this actor may send patches as they are (an editor, an undo). Never by default:
|
|
67
|
+
* a client asks with intents, and the authority decides.
|
|
68
|
+
*/
|
|
69
|
+
readonly trustPatches?: (actor: unknown) => boolean;
|
|
70
|
+
/** Who made a change, in the words other players see. */
|
|
71
|
+
readonly author?: (actor: unknown) => string | undefined;
|
|
72
|
+
/** Changes kept to catch late clients up without sending the whole map. Defaults to 512. */
|
|
73
|
+
readonly log?: number;
|
|
74
|
+
readonly persist?: PersistOptions;
|
|
75
|
+
/** Called after every change, whoever made it: the place to keep a physics world in step. */
|
|
76
|
+
readonly onChange?: (change: MapChange) => void;
|
|
77
|
+
readonly onError?: (error: unknown) => void;
|
|
78
|
+
/** Changes to replay on top of `definition`, in order: what a store gives back after a restart. */
|
|
79
|
+
readonly replay?: readonly PatchEntry[];
|
|
80
|
+
}
|
|
81
|
+
export interface MapAuthority {
|
|
82
|
+
/** The truth. Never change it directly: use `apply`, so everybody hears about it. */
|
|
83
|
+
readonly model: MapModel<ApplyResult>;
|
|
84
|
+
readonly revision: number;
|
|
85
|
+
readonly interaction: InteractionController;
|
|
86
|
+
/** How many clients are connected. */
|
|
87
|
+
readonly connections: number;
|
|
88
|
+
connect(session: AuthoritySession): AuthorityConnection;
|
|
89
|
+
/**
|
|
90
|
+
* A change the game itself makes (a lever, a quest). It is trusted: no pipeline. Everybody
|
|
91
|
+
* connected hears about it.
|
|
92
|
+
*/
|
|
93
|
+
apply(patches: MapPatch | readonly MapPatch[]): ApplyResult;
|
|
94
|
+
/** Runs intents through the pipelines on behalf of an actor, e.g. a non-player character. */
|
|
95
|
+
submit(intents: MapIntent | readonly MapIntent[], actor?: unknown): ApplyResult | ApplyFailure;
|
|
96
|
+
/** Shows a short effect to everybody connected. It is not kept. */
|
|
97
|
+
effect(target: EffectTargetMessage, name: string, params?: Readonly<Record<string, unknown>>): void;
|
|
98
|
+
/** The map as it is now, as a message. */
|
|
99
|
+
snapshot(): ServerMessage;
|
|
100
|
+
/** Writes what is pending to the store. */
|
|
101
|
+
flush(): Promise<void>;
|
|
102
|
+
/** Flushes, then stops. */
|
|
103
|
+
destroy(): Promise<void>;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The authority over one map: it holds the truth, decides what clients may do, tells
|
|
107
|
+
* everybody what became true, catches late clients up and keeps the map between
|
|
108
|
+
* restarts. It knows nothing of any framework: connect it to whatever carries messages.
|
|
109
|
+
*/
|
|
110
|
+
export declare function createMapAuthority<C extends Catalog<string> = Catalog<string>>(options: MapAuthorityOptions<C>): MapAuthority;
|
|
111
|
+
/**
|
|
112
|
+
* Brings a map back from a store, or starts it from `definition` when the store has
|
|
113
|
+
* nothing yet. Changes written after the last snapshot are replayed.
|
|
114
|
+
*/
|
|
115
|
+
export declare function restoreMapAuthority<C extends Catalog<string> = Catalog<string>>(options: MapAuthorityOptions<C> & {
|
|
116
|
+
readonly persist: PersistOptions;
|
|
117
|
+
}): Promise<MapAuthority>;
|
|
118
|
+
//# sourceMappingURL=authority.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authority.d.ts","sourceRoot":"","sources":["../../src/authority/authority.ts"],"names":[],"mappings":"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"}
|