@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.
Files changed (287) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +341 -2
  3. package/dist/authority/authority.d.ts +118 -0
  4. package/dist/authority/authority.d.ts.map +1 -0
  5. package/dist/authority/authority.js +289 -0
  6. package/dist/authority/authority.js.map +1 -0
  7. package/dist/authority/index.d.ts +3 -0
  8. package/dist/authority/index.d.ts.map +1 -0
  9. package/dist/authority/index.js +3 -0
  10. package/dist/authority/store.d.ts +36 -0
  11. package/dist/authority/store.d.ts.map +1 -0
  12. package/dist/authority/store.js +35 -0
  13. package/dist/authority/store.js.map +1 -0
  14. package/dist/client/index.d.ts +72 -0
  15. package/dist/client/index.d.ts.map +1 -0
  16. package/dist/client/index.js +200 -0
  17. package/dist/client/index.js.map +1 -0
  18. package/dist/embed/client.d.ts +85 -0
  19. package/dist/embed/client.d.ts.map +1 -0
  20. package/dist/embed/client.js +255 -0
  21. package/dist/embed/client.js.map +1 -0
  22. package/dist/embed/host.d.ts +112 -0
  23. package/dist/embed/host.d.ts.map +1 -0
  24. package/dist/embed/host.js +278 -0
  25. package/dist/embed/host.js.map +1 -0
  26. package/dist/embed/index.d.ts +4 -0
  27. package/dist/embed/index.d.ts.map +1 -0
  28. package/dist/embed/index.js +4 -0
  29. package/dist/embed/protocol.d.ts +137 -0
  30. package/dist/embed/protocol.d.ts.map +1 -0
  31. package/dist/embed/protocol.js +161 -0
  32. package/dist/embed/protocol.js.map +1 -0
  33. package/dist/features/index.d.ts +66 -0
  34. package/dist/features/index.d.ts.map +1 -0
  35. package/dist/features/index.js +157 -0
  36. package/dist/features/index.js.map +1 -0
  37. package/dist/features/skin.d.ts +9 -0
  38. package/dist/features/skin.d.ts.map +1 -0
  39. package/dist/features/skin.js +43 -0
  40. package/dist/features/skin.js.map +1 -0
  41. package/dist/features/terrain.d.ts +66 -0
  42. package/dist/features/terrain.d.ts.map +1 -0
  43. package/dist/features/terrain.js +291 -0
  44. package/dist/features/terrain.js.map +1 -0
  45. package/dist/index.d.ts +9 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +51 -0
  48. package/dist/interaction/builtin-intents.d.ts +9 -0
  49. package/dist/interaction/builtin-intents.d.ts.map +1 -0
  50. package/dist/interaction/builtin-intents.js +61 -0
  51. package/dist/interaction/builtin-intents.js.map +1 -0
  52. package/dist/interaction/catalog.d.ts +24 -0
  53. package/dist/interaction/catalog.d.ts.map +1 -0
  54. package/dist/interaction/catalog.js +20 -0
  55. package/dist/interaction/catalog.js.map +1 -0
  56. package/dist/interaction/controller.d.ts +137 -0
  57. package/dist/interaction/controller.d.ts.map +1 -0
  58. package/dist/interaction/controller.js +215 -0
  59. package/dist/interaction/controller.js.map +1 -0
  60. package/dist/interaction/index.d.ts +14 -0
  61. package/dist/interaction/index.d.ts.map +1 -0
  62. package/dist/interaction/index.js +13 -0
  63. package/dist/interaction/input.d.ts +26 -0
  64. package/dist/interaction/input.d.ts.map +1 -0
  65. package/dist/interaction/input.js +33 -0
  66. package/dist/interaction/input.js.map +1 -0
  67. package/dist/interaction/intents.d.ts +80 -0
  68. package/dist/interaction/intents.d.ts.map +1 -0
  69. package/dist/interaction/keyboard.d.ts +26 -0
  70. package/dist/interaction/keyboard.d.ts.map +1 -0
  71. package/dist/interaction/keyboard.js +32 -0
  72. package/dist/interaction/keyboard.js.map +1 -0
  73. package/dist/interaction/picking.d.ts +33 -0
  74. package/dist/interaction/picking.d.ts.map +1 -0
  75. package/dist/interaction/picking.js +35 -0
  76. package/dist/interaction/picking.js.map +1 -0
  77. package/dist/interaction/pipeline.d.ts +45 -0
  78. package/dist/interaction/pipeline.d.ts.map +1 -0
  79. package/dist/interaction/pipeline.js +26 -0
  80. package/dist/interaction/pipeline.js.map +1 -0
  81. package/dist/interaction/placement.d.ts +76 -0
  82. package/dist/interaction/placement.d.ts.map +1 -0
  83. package/dist/interaction/placement.js +238 -0
  84. package/dist/interaction/placement.js.map +1 -0
  85. package/dist/interaction/selection-tool.d.ts +29 -0
  86. package/dist/interaction/selection-tool.d.ts.map +1 -0
  87. package/dist/interaction/selection-tool.js +190 -0
  88. package/dist/interaction/selection-tool.js.map +1 -0
  89. package/dist/interaction/steps.d.ts +62 -0
  90. package/dist/interaction/steps.d.ts.map +1 -0
  91. package/dist/interaction/steps.js +126 -0
  92. package/dist/interaction/steps.js.map +1 -0
  93. package/dist/interaction/store.d.ts +10 -0
  94. package/dist/interaction/store.d.ts.map +1 -0
  95. package/dist/interaction/store.js +28 -0
  96. package/dist/interaction/store.js.map +1 -0
  97. package/dist/interaction/tools.d.ts +26 -0
  98. package/dist/interaction/tools.d.ts.map +1 -0
  99. package/dist/interaction/tools.js +41 -0
  100. package/dist/interaction/tools.js.map +1 -0
  101. package/dist/model/assets.d.ts +36 -0
  102. package/dist/model/assets.d.ts.map +1 -0
  103. package/dist/model/assets.js +18 -0
  104. package/dist/model/assets.js.map +1 -0
  105. package/dist/model/committer.d.ts +66 -0
  106. package/dist/model/committer.d.ts.map +1 -0
  107. package/dist/model/committer.js +38 -0
  108. package/dist/model/committer.js.map +1 -0
  109. package/dist/model/core-patches.d.ts +2 -0
  110. package/dist/model/core-patches.d.ts.map +1 -0
  111. package/dist/model/core-patches.js +193 -0
  112. package/dist/model/core-patches.js.map +1 -0
  113. package/dist/model/define-map.d.ts +52 -0
  114. package/dist/model/define-map.d.ts.map +1 -0
  115. package/dist/model/define-map.js +66 -0
  116. package/dist/model/define-map.js.map +1 -0
  117. package/dist/model/definition.d.ts +57 -0
  118. package/dist/model/definition.d.ts.map +1 -0
  119. package/dist/model/definition.js +6 -0
  120. package/dist/model/definition.js.map +1 -0
  121. package/dist/model/errors.d.ts +21 -0
  122. package/dist/model/errors.d.ts.map +1 -0
  123. package/dist/model/errors.js +14 -0
  124. package/dist/model/errors.js.map +1 -0
  125. package/dist/model/history.d.ts +43 -0
  126. package/dist/model/history.d.ts.map +1 -0
  127. package/dist/model/history.js +141 -0
  128. package/dist/model/history.js.map +1 -0
  129. package/dist/model/index.d.ts +14 -0
  130. package/dist/model/index.d.ts.map +1 -0
  131. package/dist/model/index.js +14 -0
  132. package/dist/model/json.d.ts +15 -0
  133. package/dist/model/json.d.ts.map +1 -0
  134. package/dist/model/json.js +35 -0
  135. package/dist/model/json.js.map +1 -0
  136. package/dist/model/model.d.ts +68 -0
  137. package/dist/model/model.d.ts.map +1 -0
  138. package/dist/model/model.js +170 -0
  139. package/dist/model/model.js.map +1 -0
  140. package/dist/model/parse.d.ts +24 -0
  141. package/dist/model/parse.d.ts.map +1 -0
  142. package/dist/model/parse.js +165 -0
  143. package/dist/model/parse.js.map +1 -0
  144. package/dist/model/patches.d.ts +100 -0
  145. package/dist/model/patches.d.ts.map +1 -0
  146. package/dist/model/patches.js +21 -0
  147. package/dist/model/patches.js.map +1 -0
  148. package/dist/model/result.d.ts +22 -0
  149. package/dist/model/result.d.ts.map +1 -0
  150. package/dist/model/result.js +10 -0
  151. package/dist/model/result.js.map +1 -0
  152. package/dist/model/source.d.ts +52 -0
  153. package/dist/model/source.d.ts.map +1 -0
  154. package/dist/model/source.js +75 -0
  155. package/dist/model/source.js.map +1 -0
  156. package/dist/protocol/index.d.ts +92 -0
  157. package/dist/protocol/index.d.ts.map +1 -0
  158. package/dist/protocol/index.js +106 -0
  159. package/dist/protocol/index.js.map +1 -0
  160. package/dist/remote/committer.d.ts +43 -0
  161. package/dist/remote/committer.d.ts.map +1 -0
  162. package/dist/remote/committer.js +206 -0
  163. package/dist/remote/committer.js.map +1 -0
  164. package/dist/remote/index.d.ts +4 -0
  165. package/dist/remote/index.d.ts.map +1 -0
  166. package/dist/remote/index.js +4 -0
  167. package/dist/remote/join.d.ts +30 -0
  168. package/dist/remote/join.d.ts.map +1 -0
  169. package/dist/remote/join.js +36 -0
  170. package/dist/remote/join.js.map +1 -0
  171. package/dist/remote/memory-transport.d.ts +20 -0
  172. package/dist/remote/memory-transport.d.ts.map +1 -0
  173. package/dist/remote/memory-transport.js +48 -0
  174. package/dist/remote/memory-transport.js.map +1 -0
  175. package/dist/renderer/effects.d.ts +52 -0
  176. package/dist/renderer/effects.d.ts.map +1 -0
  177. package/dist/renderer/effects.js +111 -0
  178. package/dist/renderer/effects.js.map +1 -0
  179. package/dist/renderer/feature.d.ts +79 -0
  180. package/dist/renderer/feature.d.ts.map +1 -0
  181. package/dist/renderer/feature.js +50 -0
  182. package/dist/renderer/feature.js.map +1 -0
  183. package/dist/renderer/geometry.d.ts +59 -0
  184. package/dist/renderer/geometry.d.ts.map +1 -0
  185. package/dist/renderer/geometry.js +114 -0
  186. package/dist/renderer/geometry.js.map +1 -0
  187. package/dist/renderer/index.d.ts +7 -0
  188. package/dist/renderer/index.d.ts.map +1 -0
  189. package/dist/renderer/index.js +7 -0
  190. package/dist/renderer/scene.d.ts +77 -0
  191. package/dist/renderer/scene.d.ts.map +1 -0
  192. package/dist/renderer/scene.js +125 -0
  193. package/dist/renderer/scene.js.map +1 -0
  194. package/dist/renderer/textures.d.ts +20 -0
  195. package/dist/renderer/textures.d.ts.map +1 -0
  196. package/dist/renderer/textures.js +61 -0
  197. package/dist/renderer/textures.js.map +1 -0
  198. package/dist/renderer/view.d.ts +139 -0
  199. package/dist/renderer/view.d.ts.map +1 -0
  200. package/dist/renderer/view.js +349 -0
  201. package/dist/renderer/view.js.map +1 -0
  202. package/dist/rpgjs/shared.d.ts +41 -0
  203. package/dist/rpgjs/shared.d.ts.map +1 -0
  204. package/dist/rpgjs/shared.js +77 -0
  205. package/dist/rpgjs/shared.js.map +1 -0
  206. package/dist/server/index.d.ts +64 -0
  207. package/dist/server/index.d.ts.map +1 -0
  208. package/dist/server/index.js +205 -0
  209. package/dist/server/index.js.map +1 -0
  210. package/dist/server/storage.d.ts +16 -0
  211. package/dist/server/storage.d.ts.map +1 -0
  212. package/dist/server/storage.js +52 -0
  213. package/dist/server/storage.js.map +1 -0
  214. package/dist/skins/default.d.ts +27 -0
  215. package/dist/skins/default.d.ts.map +1 -0
  216. package/dist/skins/default.js +144 -0
  217. package/dist/skins/default.js.map +1 -0
  218. package/dist/skins/frames.d.ts +19 -0
  219. package/dist/skins/frames.d.ts.map +1 -0
  220. package/dist/skins/frames.js +49 -0
  221. package/dist/skins/frames.js.map +1 -0
  222. package/dist/skins/game.d.ts +21 -0
  223. package/dist/skins/game.d.ts.map +1 -0
  224. package/dist/skins/game.js +21 -0
  225. package/dist/skins/game.js.map +1 -0
  226. package/dist/skins/index.d.ts +5 -0
  227. package/dist/skins/index.d.ts.map +1 -0
  228. package/dist/skins/index.js +5 -0
  229. package/dist/skins/types.d.ts +46 -0
  230. package/dist/skins/types.d.ts.map +1 -0
  231. package/dist/skins/types.js +12 -0
  232. package/dist/skins/types.js.map +1 -0
  233. package/dist/terrain/data.d.ts +10 -0
  234. package/dist/terrain/data.d.ts.map +1 -0
  235. package/dist/terrain/data.js +83 -0
  236. package/dist/terrain/data.js.map +1 -0
  237. package/dist/terrain/define.d.ts +72 -0
  238. package/dist/terrain/define.d.ts.map +1 -0
  239. package/dist/terrain/define.js +91 -0
  240. package/dist/terrain/define.js.map +1 -0
  241. package/dist/terrain/editing.d.ts +54 -0
  242. package/dist/terrain/editing.d.ts.map +1 -0
  243. package/dist/terrain/editing.js +169 -0
  244. package/dist/terrain/editing.js.map +1 -0
  245. package/dist/terrain/index.d.ts +10 -0
  246. package/dist/terrain/index.d.ts.map +1 -0
  247. package/dist/terrain/index.js +10 -0
  248. package/dist/terrain/kernel.d.ts +57 -0
  249. package/dist/terrain/kernel.d.ts.map +1 -0
  250. package/dist/terrain/kernel.js +198 -0
  251. package/dist/terrain/kernel.js.map +1 -0
  252. package/dist/terrain/mask.d.ts +37 -0
  253. package/dist/terrain/mask.d.ts.map +1 -0
  254. package/dist/terrain/mask.js +244 -0
  255. package/dist/terrain/mask.js.map +1 -0
  256. package/dist/terrain/patches.d.ts +32 -0
  257. package/dist/terrain/patches.d.ts.map +1 -0
  258. package/dist/terrain/patches.js +135 -0
  259. package/dist/terrain/patches.js.map +1 -0
  260. package/dist/terrain/rle.d.ts +4 -0
  261. package/dist/terrain/rle.d.ts.map +1 -0
  262. package/dist/terrain/rle.js +68 -0
  263. package/dist/terrain/rle.js.map +1 -0
  264. package/dist/terrain/scheduler.d.ts +46 -0
  265. package/dist/terrain/scheduler.d.ts.map +1 -0
  266. package/dist/terrain/scheduler.js +148 -0
  267. package/dist/terrain/scheduler.js.map +1 -0
  268. package/dist/terrain/types.d.ts +110 -0
  269. package/dist/terrain/types.d.ts.map +1 -0
  270. package/dist/terrain/types.js +11 -0
  271. package/dist/terrain/types.js.map +1 -0
  272. package/dist/terrain-render/backend.d.ts +66 -0
  273. package/dist/terrain-render/backend.d.ts.map +1 -0
  274. package/dist/terrain-render/backend.js +96 -0
  275. package/dist/terrain-render/backend.js.map +1 -0
  276. package/dist/terrain-render/host.d.ts +21 -0
  277. package/dist/terrain-render/host.d.ts.map +1 -0
  278. package/dist/terrain-render/host.js +62 -0
  279. package/dist/terrain-render/host.js.map +1 -0
  280. package/dist/terrain-render/index.d.ts +3 -0
  281. package/dist/terrain-render/index.d.ts.map +1 -0
  282. package/dist/terrain-render/index.js +3 -0
  283. package/dist/terrain-render/worker.d.ts +2 -0
  284. package/dist/terrain-render/worker.d.ts.map +1 -0
  285. package/dist/terrain-render/worker.js +34 -0
  286. package/dist/terrain-render/worker.js.map +1 -0
  287. package/package.json +124 -3
@@ -0,0 +1,291 @@
1
+ import { defineMapFeature } from "../renderer/feature.js";
2
+ import { tryReadTerrain } from "../terrain/data.js";
3
+ import { readRect, shapeBounds } from "../terrain/mask.js";
4
+ import { dirtyOfPatch, globalOverlayMaterials, renderMargin, widestBlend } from "../terrain/kernel.js";
5
+ import { createTerrainScheduler } from "../terrain/scheduler.js";
6
+ import { registerTerrainIntents, terrainBrush } from "../terrain/editing.js";
7
+ import { Container, Sprite, Texture, TextureSource } from "pixi.js";
8
+ //#region src/features/terrain.ts
9
+ var TERRAIN_FEATURE_ID = "terrain";
10
+ /** Pixels of an image, decoded in the browser. */
11
+ async function decodeRaster(src) {
12
+ const response = await fetch(src);
13
+ if (!response.ok) throw new Error(`Could not load "${src}" (${response.status})`);
14
+ const bitmap = await createImageBitmap(await response.blob());
15
+ const context = (typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(bitmap.width, bitmap.height) : Object.assign(document.createElement("canvas"), {
16
+ width: bitmap.width,
17
+ height: bitmap.height
18
+ })).getContext("2d");
19
+ context.drawImage(bitmap, 0, 0);
20
+ const image = context.getImageData(0, 0, bitmap.width, bitmap.height);
21
+ bitmap.close();
22
+ return {
23
+ width: image.width,
24
+ height: image.height,
25
+ pixels: image.data
26
+ };
27
+ }
28
+ /** Surfaces backed by canvases (nearest filtering, 1:1: the pixels are what the kernel produced). */
29
+ var canvasSurface = (width, height) => {
30
+ const canvas = document.createElement("canvas");
31
+ canvas.width = width;
32
+ canvas.height = height;
33
+ const context = canvas.getContext("2d");
34
+ const texture = Texture.from(canvas);
35
+ texture.source.scaleMode = "nearest";
36
+ return {
37
+ texture,
38
+ put(x, y, w, h, pixels) {
39
+ context.putImageData(new ImageData(pixels, w, h), x, y);
40
+ texture.source.update();
41
+ },
42
+ destroy: () => texture.destroy(true)
43
+ };
44
+ };
45
+ /** Surfaces that only keep their pixels in memory: for tests and tools that read them back. */
46
+ function memorySurface() {
47
+ const store = /* @__PURE__ */ new WeakMap();
48
+ const factory = (width, height) => {
49
+ const pixels = new Uint8ClampedArray(width * height * 4);
50
+ const texture = new Texture({ source: new TextureSource({
51
+ width,
52
+ height
53
+ }) });
54
+ store.set(texture, pixels);
55
+ return {
56
+ texture,
57
+ put(x, y, w, h, source) {
58
+ for (let row = 0; row < h; row += 1) pixels.set(source.subarray(row * w * 4, (row + 1) * w * 4), ((y + row) * width + x) * 4);
59
+ },
60
+ destroy: () => texture.destroy(true)
61
+ };
62
+ };
63
+ return Object.assign(factory, { pixelsOf: (texture) => store.get(texture) });
64
+ }
65
+ /**
66
+ * Draws the painted terrain of the map (`extensions.terrain`): the control mask, the materials and
67
+ * their render modes, rendered by the kernel of `@rpgjs/render-map2d` into chunk textures.
68
+ * Painting changes only the chunks it touches. What is on screen renders first.
69
+ */
70
+ function terrain(options = {}) {
71
+ return defineMapFeature({
72
+ id: TERRAIN_FEATURE_ID,
73
+ setup(host) {
74
+ const chunk = options.chunk ?? 512;
75
+ const makeSurface = options.surface ?? canvasSurface;
76
+ const report = options.onError ?? ((error) => console.warn("[map-builder] terrain:", error));
77
+ const container = new Container();
78
+ container.label = "terrain";
79
+ host.stages.ground.addChild(container);
80
+ const sprites = /* @__PURE__ */ new Map();
81
+ let backend;
82
+ let scheduler;
83
+ let current;
84
+ let grow = 0;
85
+ let margin = 0;
86
+ let viewport = null;
87
+ let generation = 0;
88
+ let destroyed = false;
89
+ let starting = Promise.resolve();
90
+ let phase = "idle";
91
+ let queued = [];
92
+ const images = /* @__PURE__ */ new Map();
93
+ const loadImage = (src) => {
94
+ let promise = images.get(src);
95
+ if (!promise) {
96
+ promise = (options.loadImage ?? decodeRaster)(src);
97
+ images.set(src, promise);
98
+ promise.catch(() => images.delete(src));
99
+ }
100
+ return promise;
101
+ };
102
+ const colsOf = (data) => Math.ceil((host.model.state.size[0] ?? data.mask.width) / chunk);
103
+ const place = (frame) => {
104
+ const data = current;
105
+ if (!data || destroyed) return;
106
+ const col = Math.floor(frame.x / chunk);
107
+ const row = Math.floor(frame.y / chunk);
108
+ const index = row * colsOf(data) + col;
109
+ let entry = sprites.get(index);
110
+ if (!entry) {
111
+ const surface = makeSurface(chunk, chunk);
112
+ const sprite = new Sprite(surface.texture);
113
+ sprite.position.set(col * chunk, row * chunk);
114
+ container.addChild(sprite);
115
+ entry = {
116
+ sprite,
117
+ surface
118
+ };
119
+ sprites.set(index, entry);
120
+ }
121
+ entry.surface.put(frame.x - col * chunk, frame.y - row * chunk, frame.width, frame.height, frame.pixels);
122
+ };
123
+ const sourcesOf = (data) => {
124
+ const out = {};
125
+ for (const material of data.materials) {
126
+ const asset = host.assets.resolve(material.texture);
127
+ if (asset) out[material.id] = {
128
+ src: asset.src,
129
+ ...asset.frame ? { frame: asset.frame } : {}
130
+ };
131
+ else report(/* @__PURE__ */ new Error(`terrain material "${material.id}": unknown asset "${material.texture}"`));
132
+ }
133
+ return out;
134
+ };
135
+ const clearChunks = () => {
136
+ for (const { sprite, surface } of sprites.values()) {
137
+ sprite.destroy();
138
+ surface.destroy();
139
+ }
140
+ sprites.clear();
141
+ };
142
+ /** Gives the backend the terrain as it is now and renders everything. */
143
+ async function begin(first, token) {
144
+ const sources = sourcesOf(first);
145
+ const loaded = {};
146
+ await Promise.all([...new Set(Object.values(sources).map((source) => source.src))].map(async (src) => {
147
+ try {
148
+ loaded[src] = await loadImage(src);
149
+ } catch (error) {
150
+ report(error);
151
+ }
152
+ }));
153
+ if (token !== generation || destroyed) return;
154
+ if (!backend) {
155
+ backend = (typeof options.backend === "function" ? await options.backend() : options.backend) ?? (await import("../terrain-render/backend.js")).inlineTerrainBackend();
156
+ if (token !== generation || destroyed) return;
157
+ }
158
+ const state = host.model.state;
159
+ const data = tryReadTerrain(state);
160
+ if (!data) return;
161
+ const init = {
162
+ data,
163
+ size: state.size,
164
+ tileSize: state.tileSize ?? 48,
165
+ sources,
166
+ images: loaded
167
+ };
168
+ phase = "initializing";
169
+ queued = [];
170
+ await backend.init(init);
171
+ if (token !== generation || destroyed) return;
172
+ for (const change of queued) backend.updateMask(change.rect, change.values);
173
+ queued = [];
174
+ phase = "ready";
175
+ current = data;
176
+ grow = widestBlend(data);
177
+ margin = renderMargin(data);
178
+ scheduler?.destroy();
179
+ clearChunks();
180
+ const used = backend;
181
+ scheduler = createTerrainScheduler({
182
+ size: state.size,
183
+ chunk,
184
+ concurrency: used.concurrency,
185
+ render: (rect) => used.render(rect, margin),
186
+ apply: place,
187
+ onError: (error) => report(error)
188
+ });
189
+ scheduler.setViewport(viewport);
190
+ scheduler.invalidate("all");
191
+ }
192
+ const restart = () => {
193
+ generation += 1;
194
+ const token = generation;
195
+ const data = tryReadTerrain(host.model.state);
196
+ phase = data ? "loading" : "idle";
197
+ if (!data) {
198
+ scheduler?.destroy();
199
+ scheduler = void 0;
200
+ current = void 0;
201
+ clearChunks();
202
+ return;
203
+ }
204
+ starting = begin(data, token).catch(report);
205
+ };
206
+ const onChange = (change) => {
207
+ if (destroyed) return;
208
+ if (change.reset) return restart();
209
+ let rebuild = false;
210
+ const patches = change.patches.filter((patch) => patch.type.startsWith("terrain.") || patch.type === "extension.set" && patch.id === "terrain");
211
+ if (patches.length === 0) return;
212
+ const next = tryReadTerrain(change.state);
213
+ if (!next) return restart();
214
+ for (const patch of patches) if (patch.type === "extension.set" || patch.type === "terrain.materials" || patch.type === "terrain.morphology") rebuild = true;
215
+ if (rebuild || phase === "idle") return restart();
216
+ if (phase === "loading") return;
217
+ let overlay = false;
218
+ const touchesOverlay = (was, now, rect) => {
219
+ const indexes = globalOverlayMaterials(now);
220
+ if (indexes.length === 0) return false;
221
+ const seen = /* @__PURE__ */ new Set([...readRect(was.mask, rect), ...readRect(now.mask, rect)]);
222
+ return indexes.some((index) => seen.has(index));
223
+ };
224
+ for (const patch of patches) {
225
+ const dirty = dirtyOfPatch(patch, next, grow);
226
+ if (dirty === "none") continue;
227
+ if (patch.type === "terrain.paint" || patch.type === "terrain.region") {
228
+ const rect = patch.type === "terrain.region" ? {
229
+ x: patch.x,
230
+ y: patch.y,
231
+ width: patch.width,
232
+ height: patch.height
233
+ } : shapeBounds(next.mask, patch.shape);
234
+ if (rect && phase === "initializing") queued.push({
235
+ rect,
236
+ values: readRect(next.mask, rect)
237
+ });
238
+ else if (rect) backend?.updateMask(rect, readRect(next.mask, rect));
239
+ if (rect && phase === "ready" && current && touchesOverlay(current, next, rect)) overlay = true;
240
+ }
241
+ if (phase === "ready") scheduler?.invalidate(dirty);
242
+ }
243
+ if (phase === "ready") {
244
+ current = next;
245
+ if (overlay) scheduler?.invalidate("all");
246
+ }
247
+ };
248
+ const editable = options.edit === true || options.pipeline !== void 0;
249
+ const brush = editable ? terrainBrush(options.brushModes ? { modes: options.brushModes } : {}) : void 0;
250
+ const stops = [];
251
+ if (editable) {
252
+ stops.push(registerTerrainIntents(host.interaction));
253
+ if (options.pipeline) stops.push(host.interaction.usePipeline("terrain.paint", options.pipeline));
254
+ stops.push(host.interaction.addTool(brush.tool));
255
+ }
256
+ stops.push(host.model.subscribe(onChange), host.onViewport((next) => {
257
+ viewport = next;
258
+ scheduler?.setViewport(next);
259
+ }));
260
+ restart();
261
+ return {
262
+ api: {
263
+ whenIdle: async () => {
264
+ await starting;
265
+ await scheduler?.whenIdle();
266
+ },
267
+ get pending() {
268
+ return scheduler?.pending ?? 0;
269
+ },
270
+ get backend() {
271
+ return backend;
272
+ },
273
+ brush: brush?.api
274
+ },
275
+ dispose() {
276
+ destroyed = true;
277
+ generation += 1;
278
+ for (const stop of stops) stop();
279
+ scheduler?.destroy();
280
+ clearChunks();
281
+ container.destroy();
282
+ backend?.destroy();
283
+ }
284
+ };
285
+ }
286
+ });
287
+ }
288
+ //#endregion
289
+ export { TERRAIN_FEATURE_ID, canvasSurface, decodeRaster, memorySurface, terrain };
290
+
291
+ //# sourceMappingURL=terrain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terrain.js","names":[],"sources":["../../src/features/terrain.ts"],"sourcesContent":["import { Container, Sprite, Texture, TextureSource } from \"pixi.js\";\nimport type { RasterImage } from \"@rpgjs/render-map2d\";\nimport type { InteractionController } from \"../interaction/controller\";\nimport type { PipelineStep } from \"../interaction/pipeline\";\nimport type { MapChange } from \"../model/model\";\nimport type { Unsubscribe } from \"../model/result\";\nimport { defineMapFeature, type MapFeature } from \"../renderer/feature\";\nimport type { TerrainBackend } from \"../terrain-render/backend\";\nimport { readTerrain, tryReadTerrain } from \"../terrain/data\";\nimport { dirtyOfPatch, globalOverlayMaterials, renderMargin, widestBlend, type MaterialSource, type TerrainRenderInit } from \"../terrain/kernel\";\nimport { readRect, shapeBounds } from \"../terrain/mask\";\nimport { createTerrainScheduler, type TerrainFrame, type TerrainScheduler } from \"../terrain/scheduler\";\nimport { registerTerrainIntents, terrainBrush, type BrushApi } from \"../terrain/editing\";\nimport type { MaskRect, TerrainData } from \"../terrain/types\";\n\nexport const TERRAIN_FEATURE_ID = \"terrain\";\n\n/** One chunk of the terrain as pixels: a texture, and a way to write into it. */\nexport interface TerrainSurface {\n readonly texture: Texture;\n /** Writes RGBA pixels at `(x, y)` inside the surface, then makes the change visible. */\n put(x: number, y: number, width: number, height: number, pixels: Uint8ClampedArray): void;\n destroy(): void;\n}\n\nexport type TerrainSurfaceFactory = (width: number, height: number) => TerrainSurface;\n\nexport interface TerrainOptions {\n /**\n * Where the kernel runs. Defaults to the calling thread, which is fine for small maps and tests.\n * For live painting use `workerTerrainBackend` from `@rpgjs/map-builder/terrain-render`.\n */\n readonly backend?: TerrainBackend | (() => TerrainBackend | Promise<TerrainBackend>);\n /** Decodes an image into pixels. Defaults to the browser (`createImageBitmap`). */\n readonly loadImage?: (src: string) => Promise<RasterImage>;\n /** Side of the squares the terrain is rendered in. Defaults to 512. */\n readonly chunk?: number;\n /** How chunk textures are made. Defaults to canvases; replaceable for tests. */\n readonly surface?: TerrainSurfaceFactory;\n /** Receives non-fatal problems (an image that did not load, a render that failed). Defaults to `console.warn`. */\n readonly onError?: (error: unknown) => void;\n /**\n * Make the terrain editable: registers the `terrain.paint` intent and the brush (`view.terrain.brush`).\n * Passing `pipeline` also turns it on.\n */\n readonly edit?: boolean;\n /** Rules every painting goes through, whoever asks: the brush, `view.submit`, a game. */\n readonly pipeline?: readonly PipelineStep<\"terrain.paint\">[];\n /** Modes in which the brush is active. Defaults to `[\"edit\"]`. */\n readonly brushModes?: readonly string[];\n}\n\nexport interface TerrainApi {\n /** Resolves when everything that was asked to be (re)rendered has been. */\n whenIdle(): Promise<void>;\n /** Chunks waiting or being rendered. */\n readonly pending: number;\n /** The backend in use, once there is one. */\n readonly backend: TerrainBackend | undefined;\n /** The brush, when the terrain is editable (`terrain({ edit: true })`). */\n readonly brush: BrushApi | undefined;\n}\n\n/** Pixels of an image, decoded in the browser. */\nexport async function decodeRaster(src: string): Promise<RasterImage> {\n const response = await fetch(src);\n if (!response.ok) throw new Error(`Could not load \"${src}\" (${response.status})`);\n const bitmap = await createImageBitmap(await response.blob());\n const canvas = typeof OffscreenCanvas !== \"undefined\" ? new OffscreenCanvas(bitmap.width, bitmap.height) : Object.assign(document.createElement(\"canvas\"), { width: bitmap.width, height: bitmap.height });\n const context = canvas.getContext(\"2d\") as OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;\n context.drawImage(bitmap, 0, 0);\n const image = context.getImageData(0, 0, bitmap.width, bitmap.height);\n bitmap.close();\n return { width: image.width, height: image.height, pixels: image.data };\n}\n\n/** Surfaces backed by canvases (nearest filtering, 1:1: the pixels are what the kernel produced). */\nexport const canvasSurface: TerrainSurfaceFactory = (width, height) => {\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const context = canvas.getContext(\"2d\")!;\n const texture = Texture.from(canvas);\n texture.source.scaleMode = \"nearest\";\n return {\n texture,\n put(x, y, w, h, pixels) {\n context.putImageData(new ImageData(pixels as Uint8ClampedArray<ArrayBuffer>, w, h), x, y);\n texture.source.update();\n },\n destroy: () => texture.destroy(true),\n };\n};\n\n/** Surfaces that only keep their pixels in memory: for tests and tools that read them back. */\nexport function memorySurface(): TerrainSurfaceFactory & { pixelsOf(texture: Texture): Uint8ClampedArray | undefined } {\n const store = new WeakMap<Texture, Uint8ClampedArray>();\n const factory: TerrainSurfaceFactory = (width, height) => {\n const pixels = new Uint8ClampedArray(width * height * 4);\n const texture = new Texture({ source: new TextureSource({ width, height }) });\n store.set(texture, pixels);\n return {\n texture,\n put(x, y, w, h, source) {\n for (let row = 0; row < h; row += 1) pixels.set(source.subarray(row * w * 4, (row + 1) * w * 4), ((y + row) * width + x) * 4);\n },\n destroy: () => texture.destroy(true),\n };\n };\n return Object.assign(factory, { pixelsOf: (texture: Texture) => store.get(texture) });\n}\n\n/**\n * Draws the painted terrain of the map (`extensions.terrain`): the control mask, the materials and\n * their render modes, rendered by the kernel of `@rpgjs/render-map2d` into chunk textures.\n * Painting changes only the chunks it touches. What is on screen renders first.\n */\nexport function terrain(options: TerrainOptions = {}): MapFeature<\"terrain\", TerrainApi> {\n return defineMapFeature<\"terrain\", TerrainApi>({\n id: TERRAIN_FEATURE_ID,\n setup(host) {\n const chunk = options.chunk ?? 512;\n const makeSurface = options.surface ?? canvasSurface;\n const report = options.onError ?? ((error: unknown) => console.warn(\"[map-builder] terrain:\", error));\n const container = new Container();\n container.label = \"terrain\";\n host.stages.ground.addChild(container);\n\n const sprites = new Map<number, { sprite: Sprite; surface: TerrainSurface }>();\n let backend: TerrainBackend | undefined;\n let scheduler: TerrainScheduler | undefined;\n let current: TerrainData | undefined;\n let grow = 0;\n let margin = 0;\n let viewport: { x: number; y: number; width: number; height: number } | null = null;\n let generation = 0;\n let destroyed = false;\n let starting: Promise<void> = Promise.resolve();\n // \"loading\": images and backend are being prepared, the terrain is read when they are ready.\n // \"initializing\": the backend has its copy of the terrain; changes made since are queued and replayed.\n // \"ready\": changes go straight to the backend.\n let phase: \"idle\" | \"loading\" | \"initializing\" | \"ready\" = \"idle\";\n let queued: Array<{ rect: MaskRect; values: Uint16Array }> = [];\n const images = new Map<string, Promise<RasterImage>>();\n\n const loadImage = (src: string): Promise<RasterImage> => {\n let promise = images.get(src);\n if (!promise) {\n promise = (options.loadImage ?? decodeRaster)(src);\n images.set(src, promise);\n promise.catch(() => images.delete(src));\n }\n return promise;\n };\n\n const colsOf = (data: TerrainData): number => Math.ceil((host.model.state.size[0] ?? data.mask.width) / chunk);\n\n const place = (frame: TerrainFrame): void => {\n const data = current;\n if (!data || destroyed) return;\n const col = Math.floor(frame.x / chunk);\n const row = Math.floor(frame.y / chunk);\n const index = row * colsOf(data) + col;\n let entry = sprites.get(index);\n if (!entry) {\n const surface = makeSurface(chunk, chunk);\n const sprite = new Sprite(surface.texture);\n sprite.position.set(col * chunk, row * chunk);\n container.addChild(sprite);\n entry = { sprite, surface };\n sprites.set(index, entry);\n }\n entry.surface.put(frame.x - col * chunk, frame.y - row * chunk, frame.width, frame.height, frame.pixels);\n };\n\n const sourcesOf = (data: TerrainData): Record<string, MaterialSource> => {\n const out: Record<string, MaterialSource> = {};\n for (const material of data.materials) {\n const asset = host.assets.resolve(material.texture);\n if (asset) out[material.id] = { src: asset.src, ...(asset.frame ? { frame: asset.frame } : {}) };\n else report(new Error(`terrain material \"${material.id}\": unknown asset \"${material.texture}\"`));\n }\n return out;\n };\n\n const clearChunks = (): void => {\n for (const { sprite, surface } of sprites.values()) {\n sprite.destroy();\n surface.destroy();\n }\n sprites.clear();\n };\n\n /** Gives the backend the terrain as it is now and renders everything. */\n async function begin(first: TerrainData, token: number): Promise<void> {\n const sources = sourcesOf(first);\n const loaded: Record<string, RasterImage> = {};\n await Promise.all(\n [...new Set(Object.values(sources).map((source) => source.src))].map(async (src) => {\n try {\n loaded[src] = await loadImage(src);\n } catch (error) {\n report(error);\n }\n }),\n );\n if (token !== generation || destroyed) return;\n if (!backend) {\n const made = typeof options.backend === \"function\" ? await options.backend() : options.backend;\n backend = made ?? (await import(\"../terrain-render/backend\")).inlineTerrainBackend();\n if (token !== generation || destroyed) return;\n }\n // The terrain as it is now, not as it was when the restart began: edits may have come in while loading.\n const state = host.model.state;\n const data = tryReadTerrain(state);\n if (!data) return;\n const init: TerrainRenderInit = { data, size: state.size, tileSize: state.tileSize ?? 48, sources, images: loaded };\n phase = \"initializing\";\n queued = [];\n await backend.init(init);\n if (token !== generation || destroyed) return;\n for (const change of queued) backend.updateMask(change.rect, change.values);\n queued = [];\n phase = \"ready\";\n current = data;\n grow = widestBlend(data);\n margin = renderMargin(data);\n scheduler?.destroy();\n clearChunks();\n const used = backend;\n scheduler = createTerrainScheduler({\n size: state.size,\n chunk,\n concurrency: used.concurrency,\n render: (rect) => used.render(rect, margin),\n apply: place,\n onError: (error) => report(error),\n });\n scheduler.setViewport(viewport);\n scheduler.invalidate(\"all\");\n }\n\n const restart = (): void => {\n generation += 1;\n const token = generation;\n const data = tryReadTerrain(host.model.state);\n phase = data ? \"loading\" : \"idle\";\n if (!data) {\n scheduler?.destroy();\n scheduler = undefined;\n current = undefined;\n clearChunks();\n return;\n }\n starting = begin(data, token).catch(report);\n };\n\n const onChange = (change: MapChange): void => {\n if (destroyed) return;\n if (change.reset) return restart();\n let rebuild = false;\n const patches = change.patches.filter((patch) => patch.type.startsWith(\"terrain.\") || (patch.type === \"extension.set\" && patch.id === \"terrain\"));\n if (patches.length === 0) return;\n const next = tryReadTerrain(change.state);\n if (!next) return restart();\n for (const patch of patches) {\n if (patch.type === \"extension.set\" || patch.type === \"terrain.materials\" || patch.type === \"terrain.morphology\") rebuild = true;\n }\n if (rebuild || phase === \"idle\") return restart();\n // The terrain is read when loading ends: it already includes this change.\n if (phase === \"loading\") return;\n // Mask changes: the backend gets the new values, in order, then the affected chunks render again.\n let overlay = false;\n const touchesOverlay = (was: TerrainData, now: TerrainData, rect: MaskRect): boolean => {\n const indexes = globalOverlayMaterials(now);\n if (indexes.length === 0) return false;\n const seen = new Set<number>([...readRect(was.mask, rect), ...readRect(now.mask, rect)]);\n return indexes.some((index) => seen.has(index));\n };\n for (const patch of patches) {\n const dirty = dirtyOfPatch(patch, next, grow);\n if (dirty === \"none\") continue;\n if (patch.type === \"terrain.paint\" || patch.type === \"terrain.region\") {\n const rect: MaskRect | undefined = patch.type === \"terrain.region\" ? { x: patch.x, y: patch.y, width: patch.width, height: patch.height } : shapeBounds(next.mask, patch.shape);\n if (rect && phase === \"initializing\") queued.push({ rect, values: readRect(next.mask, rect) });\n else if (rect) backend?.updateMask(rect, readRect(next.mask, rect));\n // A preset that follows the whole shape (lane marks of a road) may change anywhere when its material is touched.\n if (rect && phase === \"ready\" && current && touchesOverlay(current, next, rect)) overlay = true;\n }\n if (phase === \"ready\") scheduler?.invalidate(dirty);\n }\n if (phase === \"ready\") {\n current = next;\n if (overlay) scheduler?.invalidate(\"all\");\n }\n };\n\n const editable = options.edit === true || options.pipeline !== undefined;\n const brush = editable ? terrainBrush(options.brushModes ? { modes: options.brushModes } : {}) : undefined;\n const stops: Unsubscribe[] = [];\n if (editable) {\n stops.push(registerTerrainIntents(host.interaction as unknown as InteractionController));\n if (options.pipeline) stops.push(host.interaction.usePipeline(\"terrain.paint\", options.pipeline));\n stops.push(host.interaction.addTool(brush!.tool));\n }\n stops.push(host.model.subscribe(onChange), host.onViewport((next) => { viewport = next; scheduler?.setViewport(next); }));\n restart();\n\n return {\n api: {\n whenIdle: async () => {\n await starting;\n await scheduler?.whenIdle();\n },\n get pending() {\n return scheduler?.pending ?? 0;\n },\n get backend() {\n return backend;\n },\n brush: brush?.api,\n },\n dispose() {\n destroyed = true;\n generation += 1;\n for (const stop of stops) stop();\n scheduler?.destroy();\n clearChunks();\n container.destroy();\n backend?.destroy();\n },\n };\n },\n });\n}\n\nexport { readTerrain };\n"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export * from './model/index';
2
+ export * from './renderer/index';
3
+ export * from './features/index';
4
+ export * from './interaction/index';
5
+ export * from './skins/index';
6
+ export * from './protocol/index';
7
+ export * from './authority/index';
8
+ export * from './remote/index';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,51 @@
1
+ import { cloneJson, isFiniteNumberValue, isJsonObject, isJsonValue, isPlainObject } from "./model/json.js";
2
+ import { MAP_DEFINITION_VERSION } from "./model/definition.js";
3
+ import { MapDefinitionError } from "./model/errors.js";
4
+ import { checkPlaced, defaultMapMigrations, isScale, parseMapDefinition } from "./model/parse.js";
5
+ import { defineAssets } from "./model/assets.js";
6
+ import { defineMap, el, event } from "./model/define-map.js";
7
+ import { definePatchHandlers, patchFail, patchOk } from "./model/patches.js";
8
+ import { chain, isThenable } from "./model/result.js";
9
+ import { createHistory } from "./model/history.js";
10
+ import { localCommitter } from "./model/committer.js";
11
+ import { coreHandlers } from "./model/core-patches.js";
12
+ import { DEFAULT_HISTORY_LIMIT, createMapModel } from "./model/model.js";
13
+ import { MapSourceError, codeMapSource, httpMapSource, jsonMapSource, loadMapRegion, regionOf } from "./model/source.js";
14
+ import "./model/index.js";
15
+ import { grow, intersects, normalizeScale, resolveElementGeometry, unionBounds } from "./renderer/geometry.js";
16
+ import { STAGE_ORDER, describeElement, describeEvent } from "./renderer/scene.js";
17
+ import { blinkEffect, builtInEffects, createEffectRunner, shakeEffect } from "./renderer/effects.js";
18
+ import { MAP_FEATURE_API_VERSION, MapFeatureError, RESERVED_FEATURE_IDS, defineMapFeature, validateFeatures } from "./renderer/feature.js";
19
+ import { createTextureStore, pixiTextureProvider } from "./renderer/textures.js";
20
+ import { bindKeyboard, toKeyInput } from "./interaction/keyboard.js";
21
+ import { nodesIn, pickNode, pickNodes, sameTarget, targetKey, targetOf } from "./interaction/picking.js";
22
+ import { compose, runPipeline } from "./interaction/pipeline.js";
23
+ import { createStore } from "./interaction/store.js";
24
+ import { createInteraction, uniqueTargets as unique } from "./interaction/controller.js";
25
+ import { attachMapView, createMapView } from "./renderer/view.js";
26
+ import "./renderer/index.js";
27
+ import { registerElementIntents } from "./interaction/builtin-intents.js";
28
+ import { createPlacement, registerPlacementIntent } from "./interaction/placement.js";
29
+ import { selectionTool } from "./interaction/selection-tool.js";
30
+ import { drawFrames, frameLayer, nodeOf } from "./skins/frames.js";
31
+ import { SKIN_COLORS, defaultSkin, framesSkin, ghostLayer } from "./skins/default.js";
32
+ import { defineSkin, mergeSkin } from "./skins/types.js";
33
+ import { mountSkinLayers } from "./features/skin.js";
34
+ import { TERRAIN_FEATURE_ID, canvasSurface, decodeRaster, memorySurface, terrain } from "./features/terrain.js";
35
+ import { ELEMENTS_FEATURE_ID, EVENTS_FEATURE_ID, HISTORY_FEATURE_ID, PLACEMENT_FEATURE_ID, SELECTION_FEATURE_ID, elements, events, history, placement, selection } from "./features/index.js";
36
+ import { NO_MODIFIERS, key, pointer } from "./interaction/input.js";
37
+ import { maxCount, minDistance, ownerOnly, protectedZone, rateLimit, requireFree, snapToGrid, withinMap } from "./interaction/steps.js";
38
+ import { defineCatalog } from "./interaction/catalog.js";
39
+ import { hoverTool, playTool } from "./interaction/tools.js";
40
+ import "./interaction/index.js";
41
+ import { gameSkin } from "./skins/game.js";
42
+ import "./skins/index.js";
43
+ import { MAP_BUILDER_EVENT, PROTOCOL_LIMITS, parseClientMessage, parseServerMessage } from "./protocol/index.js";
44
+ import { MapStoreCorruptError, memoryMapStore } from "./authority/store.js";
45
+ import { createMapAuthority, restoreMapAuthority } from "./authority/authority.js";
46
+ import "./authority/index.js";
47
+ import { remoteCommitter } from "./remote/committer.js";
48
+ import { bindRemoteEffects, joinRemoteMap } from "./remote/join.js";
49
+ import { connectInMemory } from "./remote/memory-transport.js";
50
+ import "./remote/index.js";
51
+ export { DEFAULT_HISTORY_LIMIT, ELEMENTS_FEATURE_ID, EVENTS_FEATURE_ID, HISTORY_FEATURE_ID, MAP_BUILDER_EVENT, MAP_DEFINITION_VERSION, MAP_FEATURE_API_VERSION, MapDefinitionError, MapFeatureError, MapSourceError, MapStoreCorruptError, NO_MODIFIERS, PLACEMENT_FEATURE_ID, PROTOCOL_LIMITS, RESERVED_FEATURE_IDS, SELECTION_FEATURE_ID, SKIN_COLORS, STAGE_ORDER, TERRAIN_FEATURE_ID, attachMapView, bindKeyboard, bindRemoteEffects, blinkEffect, builtInEffects, canvasSurface, chain, checkPlaced, cloneJson, codeMapSource, compose, connectInMemory, coreHandlers, createEffectRunner, createHistory, createInteraction, createMapAuthority, createMapModel, createMapView, createPlacement, createStore, createTextureStore, decodeRaster, defaultMapMigrations, defaultSkin, defineAssets, defineCatalog, defineMap, defineMapFeature, definePatchHandlers, defineSkin, describeElement, describeEvent, drawFrames, el, elements, event, events, frameLayer, framesSkin, gameSkin, ghostLayer, grow, history, hoverTool, httpMapSource, intersects, isFiniteNumberValue, isJsonObject, isJsonValue, isPlainObject, isScale, isThenable, joinRemoteMap, jsonMapSource, key, loadMapRegion, localCommitter, maxCount, memoryMapStore, memorySurface, mergeSkin, minDistance, mountSkinLayers, nodeOf, nodesIn, normalizeScale, ownerOnly, parseClientMessage, parseMapDefinition, parseServerMessage, patchFail, patchOk, pickNode, pickNodes, pixiTextureProvider, placement, playTool, pointer, protectedZone, rateLimit, regionOf, registerElementIntents, registerPlacementIntent, remoteCommitter, requireFree, resolveElementGeometry, restoreMapAuthority, runPipeline, sameTarget, selection, selectionTool, shakeEffect, snapToGrid, targetKey, targetOf, terrain, toKeyInput, unionBounds, unique as uniqueTargets, validateFeatures, withinMap };
@@ -0,0 +1,9 @@
1
+ import { InteractionController } from './controller';
2
+ import { Unsubscribe } from '../model/result';
3
+ /**
4
+ * Registers `element.move` and `element.remove`, the intents every editing tool of
5
+ * elements relies on. Those already registered are left alone, so several features
6
+ * can each make sure they are there.
7
+ */
8
+ export declare function registerElementIntents(controller: InteractionController): Unsubscribe;
9
+ //# sourceMappingURL=builtin-intents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtin-intents.d.ts","sourceRoot":"","sources":["../../src/interaction/builtin-intents.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAiCnD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,qBAAqB,GAAG,WAAW,CAKrF"}
@@ -0,0 +1,61 @@
1
+ import { describeElement } from "../renderer/scene.js";
2
+ //#region src/interaction/builtin-intents.ts
3
+ var footprintAt = (element, x, y, context) => {
4
+ if (!element) return void 0;
5
+ const node = describeElement({
6
+ ...element,
7
+ x,
8
+ y
9
+ }, context.describe);
10
+ if (!node) return void 0;
11
+ return {
12
+ bounds: node.bounds,
13
+ ...node.hitbox ? { hitbox: node.hitbox } : {},
14
+ ignore: [{
15
+ type: "element",
16
+ id: element.id
17
+ }]
18
+ };
19
+ };
20
+ var move = {
21
+ resolve(intent, { state }) {
22
+ if (!state.elements.some((element) => element.id === intent.id)) return {
23
+ ok: false,
24
+ reason: "not-found",
25
+ message: `Element "${intent.id}" does not exist`
26
+ };
27
+ return {
28
+ type: "element.move",
29
+ id: intent.id,
30
+ x: intent.x,
31
+ y: intent.y
32
+ };
33
+ },
34
+ footprint: (intent, context) => footprintAt(context.state.elements.find((element) => element.id === intent.id), intent.x, intent.y, context)
35
+ };
36
+ var remove = { resolve(intent, { state }) {
37
+ if (!state.elements.some((element) => element.id === intent.id)) return {
38
+ ok: false,
39
+ reason: "not-found",
40
+ message: `Element "${intent.id}" does not exist`
41
+ };
42
+ return {
43
+ type: "element.remove",
44
+ id: intent.id
45
+ };
46
+ } };
47
+ /**
48
+ * Registers `element.move` and `element.remove`, the intents every editing tool of
49
+ * elements relies on. Those already registered are left alone, so several features
50
+ * can each make sure they are there.
51
+ */
52
+ function registerElementIntents(controller) {
53
+ const stops = [];
54
+ if (!controller.hasIntent("element.move")) stops.push(controller.registerIntent("element.move", move));
55
+ if (!controller.hasIntent("element.remove")) stops.push(controller.registerIntent("element.remove", remove));
56
+ return () => stops.forEach((stop) => stop());
57
+ }
58
+ //#endregion
59
+ export { registerElementIntents };
60
+
61
+ //# sourceMappingURL=builtin-intents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtin-intents.js","names":[],"sources":["../../src/interaction/builtin-intents.ts"],"sourcesContent":["import type { MapElement } from \"../model/definition\";\nimport { describeElement } from \"../renderer/scene\";\nimport type { InteractionController } from \"./controller\";\nimport type { Footprint, IntentContext, IntentDefinition } from \"./intents\";\nimport type { Unsubscribe } from \"../model/result\";\n\nconst footprintAt = (element: MapElement | undefined, x: number, y: number, context: IntentContext): Footprint | undefined => {\n if (!element) return undefined;\n const node = describeElement({ ...element, x, y }, context.describe);\n if (!node) return undefined;\n return {\n bounds: node.bounds,\n ...(node.hitbox ? { hitbox: node.hitbox } : {}),\n // The element does not block itself at its new position.\n ignore: [{ type: \"element\", id: element.id }],\n };\n};\n\nconst move: IntentDefinition<\"element.move\"> = {\n resolve(intent, { state }) {\n if (!state.elements.some((element) => element.id === intent.id)) {\n return { ok: false, reason: \"not-found\", message: `Element \"${intent.id}\" does not exist` };\n }\n return { type: \"element.move\", id: intent.id, x: intent.x, y: intent.y };\n },\n footprint: (intent, context) => footprintAt(context.state.elements.find((element) => element.id === intent.id), intent.x, intent.y, context),\n};\n\nconst remove: IntentDefinition<\"element.remove\"> = {\n resolve(intent, { state }) {\n if (!state.elements.some((element) => element.id === intent.id)) {\n return { ok: false, reason: \"not-found\", message: `Element \"${intent.id}\" does not exist` };\n }\n return { type: \"element.remove\", id: intent.id };\n },\n};\n\n/**\n * Registers `element.move` and `element.remove`, the intents every editing tool of\n * elements relies on. Those already registered are left alone, so several features\n * can each make sure they are there.\n */\nexport function registerElementIntents(controller: InteractionController): Unsubscribe {\n const stops: Unsubscribe[] = [];\n if (!controller.hasIntent(\"element.move\")) stops.push(controller.registerIntent(\"element.move\", move));\n if (!controller.hasIntent(\"element.remove\")) stops.push(controller.registerIntent(\"element.remove\", remove));\n return () => stops.forEach((stop) => stop());\n}\n"],"mappings":";;AAMA,IAAM,eAAe,SAAiC,GAAW,GAAW,YAAkD;CAC5H,IAAI,CAAC,SAAS,OAAO,KAAA;CACrB,MAAM,OAAO,gBAAgB;EAAE,GAAG;EAAS;EAAG;CAAE,GAAG,QAAQ,QAAQ;CACnE,IAAI,CAAC,MAAM,OAAO,KAAA;CAClB,OAAO;EACL,QAAQ,KAAK;EACb,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;EAE7C,QAAQ,CAAC;GAAE,MAAM;GAAW,IAAI,QAAQ;EAAG,CAAC;CAC9C;AACF;AAEA,IAAM,OAAyC;CAC7C,QAAQ,QAAQ,EAAE,SAAS;EACzB,IAAI,CAAC,MAAM,SAAS,MAAM,YAAY,QAAQ,OAAO,OAAO,EAAE,GAC5D,OAAO;GAAE,IAAI;GAAO,QAAQ;GAAa,SAAS,YAAY,OAAO,GAAG;EAAkB;EAE5F,OAAO;GAAE,MAAM;GAAgB,IAAI,OAAO;GAAI,GAAG,OAAO;GAAG,GAAG,OAAO;EAAE;CACzE;CACA,YAAY,QAAQ,YAAY,YAAY,QAAQ,MAAM,SAAS,MAAM,YAAY,QAAQ,OAAO,OAAO,EAAE,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO;AAC7I;AAEA,IAAM,SAA6C,EACjD,QAAQ,QAAQ,EAAE,SAAS;CACzB,IAAI,CAAC,MAAM,SAAS,MAAM,YAAY,QAAQ,OAAO,OAAO,EAAE,GAC5D,OAAO;EAAE,IAAI;EAAO,QAAQ;EAAa,SAAS,YAAY,OAAO,GAAG;CAAkB;CAE5F,OAAO;EAAE,MAAM;EAAkB,IAAI,OAAO;CAAG;AACjD,EACF;;;;;;AAOA,SAAgB,uBAAuB,YAAgD;CACrF,MAAM,QAAuB,CAAC;CAC9B,IAAI,CAAC,WAAW,UAAU,cAAc,GAAG,MAAM,KAAK,WAAW,eAAe,gBAAgB,IAAI,CAAC;CACrG,IAAI,CAAC,WAAW,UAAU,gBAAgB,GAAG,MAAM,KAAK,WAAW,eAAe,kBAAkB,MAAM,CAAC;CAC3G,aAAa,MAAM,SAAS,SAAS,KAAK,CAAC;AAC7C"}
@@ -0,0 +1,24 @@
1
+ import { JsonObject } from '../model/json';
2
+ import { IntentType, PositionedIntentType } from './intents';
3
+ import { PipelineStep } from './pipeline';
4
+ export interface CatalogItem<Id extends string = string> {
5
+ /** What the UI and `placement.start()` call it. */
6
+ readonly id: Id;
7
+ /** The asset drawn once placed (an `AssetResolver` reference). */
8
+ readonly sprite: string;
9
+ /** Initial `data` of the element created. */
10
+ readonly data?: JsonObject;
11
+ /** Rules specific to this item, run after those of the feature. */
12
+ readonly pipeline?: readonly PipelineStep<"element.place">[];
13
+ }
14
+ export interface Catalog<Id extends string = string> {
15
+ readonly ids: readonly Id[];
16
+ readonly items: readonly CatalogItem<Id>[];
17
+ get(id: string): CatalogItem<Id> | undefined;
18
+ has(id: string): id is Id;
19
+ }
20
+ /** What can be placed, as data: Studio brings a rich catalog, a game three trees. Ids are checked by the compiler. */
21
+ export declare function defineCatalog<const Items extends readonly CatalogItem<string>[]>(items: Items): Catalog<Items[number]["id"]>;
22
+ export type CatalogId<C> = C extends Catalog<infer Id> ? Id : never;
23
+ export type { IntentType, PositionedIntentType };
24
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/interaction/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,WAAW,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM;IACrD,mDAAmD;IACnD,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC;IAChB,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,eAAe,CAAC,EAAE,CAAC;CAC9D;AAED,MAAM,WAAW,OAAO,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM;IACjD,QAAQ,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC;IAC3C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;IAC7C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,EAAE,IAAI,EAAE,CAAC;CAC3B;AAED,sHAAsH;AACtH,wBAAgB,aAAa,CAAC,KAAK,CAAC,KAAK,SAAS,SAAS,WAAW,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAa5H;AAED,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;AACpE,YAAY,EAAE,UAAU,EAAE,oBAAoB,EAAE,CAAC"}
@@ -0,0 +1,20 @@
1
+ //#region src/interaction/catalog.ts
2
+ /** What can be placed, as data: Studio brings a rich catalog, a game three trees. Ids are checked by the compiler. */
3
+ function defineCatalog(items) {
4
+ const table = /* @__PURE__ */ new Map();
5
+ for (const item of items) {
6
+ if (typeof item.id !== "string" || item.id.length === 0) throw new Error("A catalog item needs a non-empty id");
7
+ if (table.has(item.id)) throw new Error(`Catalog item "${item.id}" is defined twice`);
8
+ table.set(item.id, item);
9
+ }
10
+ return {
11
+ ids: [...table.keys()],
12
+ items: [...table.values()],
13
+ get: (id) => table.get(id),
14
+ has: (id) => table.has(id)
15
+ };
16
+ }
17
+ //#endregion
18
+ export { defineCatalog };
19
+
20
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","names":[],"sources":["../../src/interaction/catalog.ts"],"sourcesContent":["import type { JsonObject } from \"../model/json\";\nimport type { IntentType, PositionedIntentType } from \"./intents\";\nimport type { PipelineStep } from \"./pipeline\";\n\nexport interface CatalogItem<Id extends string = string> {\n /** What the UI and `placement.start()` call it. */\n readonly id: Id;\n /** The asset drawn once placed (an `AssetResolver` reference). */\n readonly sprite: string;\n /** Initial `data` of the element created. */\n readonly data?: JsonObject;\n /** Rules specific to this item, run after those of the feature. */\n readonly pipeline?: readonly PipelineStep<\"element.place\">[];\n}\n\nexport interface Catalog<Id extends string = string> {\n readonly ids: readonly Id[];\n readonly items: readonly CatalogItem<Id>[];\n get(id: string): CatalogItem<Id> | undefined;\n has(id: string): id is Id;\n}\n\n/** What can be placed, as data: Studio brings a rich catalog, a game three trees. Ids are checked by the compiler. */\nexport function defineCatalog<const Items extends readonly CatalogItem<string>[]>(items: Items): Catalog<Items[number][\"id\"]> {\n const table = new Map<string, CatalogItem<Items[number][\"id\"]>>();\n for (const item of items) {\n if (typeof item.id !== \"string\" || item.id.length === 0) throw new Error(\"A catalog item needs a non-empty id\");\n if (table.has(item.id)) throw new Error(`Catalog item \"${item.id}\" is defined twice`);\n table.set(item.id, item);\n }\n return {\n ids: [...table.keys()],\n items: [...table.values()],\n get: (id) => table.get(id),\n has: (id): id is Items[number][\"id\"] => table.has(id),\n };\n}\n\nexport type CatalogId<C> = C extends Catalog<infer Id> ? Id : never;\nexport type { IntentType, PositionedIntentType };\n"],"mappings":";;AAuBA,SAAgB,cAAkE,OAA4C;CAC5H,MAAM,wBAAQ,IAAI,IAA8C;CAChE,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,OAAO,KAAK,OAAO,YAAY,KAAK,GAAG,WAAW,GAAG,MAAM,IAAI,MAAM,qCAAqC;EAC9G,IAAI,MAAM,IAAI,KAAK,EAAE,GAAG,MAAM,IAAI,MAAM,iBAAiB,KAAK,GAAG,mBAAmB;EACpF,MAAM,IAAI,KAAK,IAAI,IAAI;CACzB;CACA,OAAO;EACL,KAAK,CAAC,GAAG,MAAM,KAAK,CAAC;EACrB,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC;EACzB,MAAM,OAAO,MAAM,IAAI,EAAE;EACzB,MAAM,OAAkC,MAAM,IAAI,EAAE;CACtD;AACF"}