@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,32 @@
1
+ //#region src/interaction/keyboard.ts
2
+ var isTyping = (target) => {
3
+ const element = target;
4
+ const tag = element?.tagName?.toUpperCase();
5
+ return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || element?.isContentEditable === true;
6
+ };
7
+ var toKeyInput = (event) => ({
8
+ key: event.key,
9
+ modifiers: {
10
+ shift: !!event.shiftKey,
11
+ ctrl: !!event.ctrlKey,
12
+ alt: !!event.altKey,
13
+ meta: !!event.metaKey
14
+ }
15
+ });
16
+ /**
17
+ * Gives key presses to the interaction layer: Escape cancels, Delete removes.
18
+ * Keys typed in a text field are left alone, and a key a tool took is not passed
19
+ * on to the browser. Returns the function that stops listening.
20
+ */
21
+ function bindKeyboard(controller, target) {
22
+ const listener = (event) => {
23
+ if (isTyping(event.target)) return;
24
+ if (controller.feedKey(toKeyInput(event))) event.preventDefault?.();
25
+ };
26
+ target.addEventListener("keydown", listener);
27
+ return () => target.removeEventListener("keydown", listener);
28
+ }
29
+ //#endregion
30
+ export { bindKeyboard, toKeyInput };
31
+
32
+ //# sourceMappingURL=keyboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyboard.js","names":[],"sources":["../../src/interaction/keyboard.ts"],"sourcesContent":["import type { Unsubscribe } from \"../model/result\";\nimport type { InteractionController } from \"./controller\";\nimport type { KeyInput } from \"./input\";\n\n/** The part of a `KeyboardEvent` that is used. */\nexport interface KeyEventLike {\n readonly key: string;\n readonly shiftKey?: boolean;\n readonly ctrlKey?: boolean;\n readonly altKey?: boolean;\n readonly metaKey?: boolean;\n readonly target?: unknown;\n preventDefault?(): void;\n}\n\n/** Anything that emits key events: `window`, `document`, an element. */\nexport interface KeyTarget {\n addEventListener(type: \"keydown\", listener: (event: KeyEventLike) => void): void;\n removeEventListener(type: \"keydown\", listener: (event: KeyEventLike) => void): void;\n}\n\nconst isTyping = (target: unknown): boolean => {\n const element = target as { tagName?: string; isContentEditable?: boolean } | null | undefined;\n const tag = element?.tagName?.toUpperCase();\n return tag === \"INPUT\" || tag === \"TEXTAREA\" || tag === \"SELECT\" || element?.isContentEditable === true;\n};\n\nexport const toKeyInput = (event: KeyEventLike): KeyInput => ({\n key: event.key,\n modifiers: { shift: !!event.shiftKey, ctrl: !!event.ctrlKey, alt: !!event.altKey, meta: !!event.metaKey },\n});\n\n/**\n * Gives key presses to the interaction layer: Escape cancels, Delete removes.\n * Keys typed in a text field are left alone, and a key a tool took is not passed\n * on to the browser. Returns the function that stops listening.\n */\nexport function bindKeyboard(controller: Pick<InteractionController, \"feedKey\">, target: KeyTarget): Unsubscribe {\n const listener = (event: KeyEventLike): void => {\n if (isTyping(event.target)) return;\n if (controller.feedKey(toKeyInput(event))) event.preventDefault?.();\n };\n target.addEventListener(\"keydown\", listener);\n return () => target.removeEventListener(\"keydown\", listener);\n}\n"],"mappings":";AAqBA,IAAM,YAAY,WAA6B;CAC7C,MAAM,UAAU;CAChB,MAAM,MAAM,SAAS,SAAS,YAAY;CAC1C,OAAO,QAAQ,WAAW,QAAQ,cAAc,QAAQ,YAAY,SAAS,sBAAsB;AACrG;AAEA,IAAa,cAAc,WAAmC;CAC5D,KAAK,MAAM;CACX,WAAW;EAAE,OAAO,CAAC,CAAC,MAAM;EAAU,MAAM,CAAC,CAAC,MAAM;EAAS,KAAK,CAAC,CAAC,MAAM;EAAQ,MAAM,CAAC,CAAC,MAAM;CAAQ;AAC1G;;;;;;AAOA,SAAgB,aAAa,YAAoD,QAAgC;CAC/G,MAAM,YAAY,UAA8B;EAC9C,IAAI,SAAS,MAAM,MAAM,GAAG;EAC5B,IAAI,WAAW,QAAQ,WAAW,KAAK,CAAC,GAAG,MAAM,iBAAiB;CACpE;CACA,OAAO,iBAAiB,WAAW,QAAQ;CAC3C,aAAa,OAAO,oBAAoB,WAAW,QAAQ;AAC7D"}
@@ -0,0 +1,33 @@
1
+ import { Rect } from '../renderer/geometry';
2
+ import { SceneNode } from '../renderer/scene';
3
+ /** An element or an event on the map. */
4
+ export interface TargetRef {
5
+ readonly type: "element" | "event";
6
+ readonly id: string;
7
+ }
8
+ export declare const targetKey: (target: TargetRef) => string;
9
+ export declare const sameTarget: (a: TargetRef, b: TargetRef) => boolean;
10
+ export declare const targetOf: (node: SceneNode) => TargetRef;
11
+ export interface Point {
12
+ readonly x: number;
13
+ readonly y: number;
14
+ }
15
+ export interface PickOptions {
16
+ /**
17
+ * What counts as being under the pointer: the drawn `"bounds"` (default), or
18
+ * only the `"hitbox"`, for dense maps where sprites overlap a lot.
19
+ */
20
+ readonly area?: "bounds" | "hitbox";
21
+ /** Only consider these kinds of target. */
22
+ readonly types?: readonly TargetRef["type"][];
23
+ }
24
+ /**
25
+ * Every node under `point`, front first. Front means drawn last: the highest
26
+ * sort key, and among equals the one that appears later in the list.
27
+ */
28
+ export declare function pickNodes(nodes: readonly SceneNode[], point: Point, options?: PickOptions): SceneNode[];
29
+ /** The node drawn in front under `point`, if any. */
30
+ export declare const pickNode: (nodes: readonly SceneNode[], point: Point, options?: PickOptions) => SceneNode | undefined;
31
+ /** Nodes whose bounds touch `area`, for a rubber-band selection. */
32
+ export declare function nodesIn(nodes: readonly SceneNode[], area: Rect, types?: readonly TargetRef["type"][]): SceneNode[];
33
+ //# sourceMappingURL=picking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"picking.d.ts","sourceRoot":"","sources":["../../src/interaction/picking.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,yCAAyC;AACzC,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC;IACnC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,SAAS,GAAI,QAAQ,SAAS,KAAG,MAAwC,CAAC;AACvF,eAAO,MAAM,UAAU,GAAI,GAAG,SAAS,EAAE,GAAG,SAAS,KAAG,OAA6C,CAAC;AACtG,eAAO,MAAM,QAAQ,GAAI,MAAM,SAAS,KAAG,SAA+C,CAAC;AAE3F,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACpC,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;CAC/C;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,WAAgB,GAAG,SAAS,EAAE,CAW3G;AAED,qDAAqD;AACrD,eAAO,MAAM,QAAQ,GAAI,OAAO,SAAS,SAAS,EAAE,EAAE,OAAO,KAAK,EAAE,UAAU,WAAW,KAAG,SAAS,GAAG,SACnE,CAAC;AAEtC,oEAAoE;AACpE,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,SAAS,SAAS,CAAC,MAAM,CAAC,EAAE,GAAG,SAAS,EAAE,CAWlH"}
@@ -0,0 +1,35 @@
1
+ //#region src/interaction/picking.ts
2
+ var targetKey = (target) => `${target.type}s/${target.id}`;
3
+ var sameTarget = (a, b) => a.type === b.type && a.id === b.id;
4
+ var targetOf = (node) => ({
5
+ type: node.type,
6
+ id: node.id
7
+ });
8
+ var contains = (rect, point) => point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
9
+ /**
10
+ * Every node under `point`, front first. Front means drawn last: the highest
11
+ * sort key, and among equals the one that appears later in the list.
12
+ */
13
+ function pickNodes(nodes, point, options = {}) {
14
+ const area = options.area ?? "bounds";
15
+ return nodes.map((node, order) => ({
16
+ node,
17
+ order
18
+ })).filter(({ node }) => {
19
+ if (options.types && !options.types.includes(node.type)) return false;
20
+ const rect = area === "hitbox" ? node.hitbox : node.bounds;
21
+ return rect !== void 0 && contains(rect, point);
22
+ }).sort((a, b) => b.node.sortKey - a.node.sortKey || b.order - a.order).map(({ node }) => node);
23
+ }
24
+ /** The node drawn in front under `point`, if any. */
25
+ var pickNode = (nodes, point, options) => pickNodes(nodes, point, options)[0];
26
+ /** Nodes whose bounds touch `area`, for a rubber-band selection. */
27
+ function nodesIn(nodes, area, types) {
28
+ const right = area.x + area.width;
29
+ const bottom = area.y + area.height;
30
+ return nodes.filter((node) => (!types || types.includes(node.type)) && node.bounds.x <= right && area.x <= node.bounds.x + node.bounds.width && node.bounds.y <= bottom && area.y <= node.bounds.y + node.bounds.height);
31
+ }
32
+ //#endregion
33
+ export { nodesIn, pickNode, pickNodes, sameTarget, targetKey, targetOf };
34
+
35
+ //# sourceMappingURL=picking.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"picking.js","names":[],"sources":["../../src/interaction/picking.ts"],"sourcesContent":["import type { Rect } from \"../renderer/geometry\";\nimport type { SceneNode } from \"../renderer/scene\";\n\n/** An element or an event on the map. */\nexport interface TargetRef {\n readonly type: \"element\" | \"event\";\n readonly id: string;\n}\n\nexport const targetKey = (target: TargetRef): string => `${target.type}s/${target.id}`;\nexport const sameTarget = (a: TargetRef, b: TargetRef): boolean => a.type === b.type && a.id === b.id;\nexport const targetOf = (node: SceneNode): TargetRef => ({ type: node.type, id: node.id });\n\nexport interface Point {\n readonly x: number;\n readonly y: number;\n}\n\nconst contains = (rect: Rect, point: Point): boolean =>\n point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;\n\nexport interface PickOptions {\n /**\n * What counts as being under the pointer: the drawn `\"bounds\"` (default), or\n * only the `\"hitbox\"`, for dense maps where sprites overlap a lot.\n */\n readonly area?: \"bounds\" | \"hitbox\";\n /** Only consider these kinds of target. */\n readonly types?: readonly TargetRef[\"type\"][];\n}\n\n/**\n * Every node under `point`, front first. Front means drawn last: the highest\n * sort key, and among equals the one that appears later in the list.\n */\nexport function pickNodes(nodes: readonly SceneNode[], point: Point, options: PickOptions = {}): SceneNode[] {\n const area = options.area ?? \"bounds\";\n return nodes\n .map((node, order) => ({ node, order }))\n .filter(({ node }) => {\n if (options.types && !options.types.includes(node.type)) return false;\n const rect = area === \"hitbox\" ? node.hitbox : node.bounds;\n return rect !== undefined && contains(rect, point);\n })\n .sort((a, b) => b.node.sortKey - a.node.sortKey || b.order - a.order)\n .map(({ node }) => node);\n}\n\n/** The node drawn in front under `point`, if any. */\nexport const pickNode = (nodes: readonly SceneNode[], point: Point, options?: PickOptions): SceneNode | undefined =>\n pickNodes(nodes, point, options)[0];\n\n/** Nodes whose bounds touch `area`, for a rubber-band selection. */\nexport function nodesIn(nodes: readonly SceneNode[], area: Rect, types?: readonly TargetRef[\"type\"][]): SceneNode[] {\n const right = area.x + area.width;\n const bottom = area.y + area.height;\n return nodes.filter(\n (node) =>\n (!types || types.includes(node.type)) &&\n node.bounds.x <= right &&\n area.x <= node.bounds.x + node.bounds.width &&\n node.bounds.y <= bottom &&\n area.y <= node.bounds.y + node.bounds.height,\n );\n}\n"],"mappings":";AASA,IAAa,aAAa,WAA8B,GAAG,OAAO,KAAK,IAAI,OAAO;AAClF,IAAa,cAAc,GAAc,MAA0B,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE;AACnG,IAAa,YAAY,UAAgC;CAAE,MAAM,KAAK;CAAM,IAAI,KAAK;AAAG;AAOxF,IAAM,YAAY,MAAY,UAC5B,MAAM,KAAK,KAAK,KAAK,MAAM,KAAK,KAAK,IAAI,KAAK,SAAS,MAAM,KAAK,KAAK,KAAK,MAAM,KAAK,KAAK,IAAI,KAAK;;;;;AAgBvG,SAAgB,UAAU,OAA6B,OAAc,UAAuB,CAAC,GAAgB;CAC3G,MAAM,OAAO,QAAQ,QAAQ;CAC7B,OAAO,MACJ,KAAK,MAAM,WAAW;EAAE;EAAM;CAAM,EAAE,CAAC,CACvC,QAAQ,EAAE,WAAW;EACpB,IAAI,QAAQ,SAAS,CAAC,QAAQ,MAAM,SAAS,KAAK,IAAI,GAAG,OAAO;EAChE,MAAM,OAAO,SAAS,WAAW,KAAK,SAAS,KAAK;EACpD,OAAO,SAAS,KAAA,KAAa,SAAS,MAAM,KAAK;CACnD,CAAC,CAAC,CACD,MAAM,GAAG,MAAM,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,QAAQ,EAAE,KAAK,CAAC,CACpE,KAAK,EAAE,WAAW,IAAI;AAC3B;;AAGA,IAAa,YAAY,OAA6B,OAAc,YAClE,UAAU,OAAO,OAAO,OAAO,CAAC,CAAC;;AAGnC,SAAgB,QAAQ,OAA6B,MAAY,OAAmD;CAClH,MAAM,QAAQ,KAAK,IAAI,KAAK;CAC5B,MAAM,SAAS,KAAK,IAAI,KAAK;CAC7B,OAAO,MAAM,QACV,UACE,CAAC,SAAS,MAAM,SAAS,KAAK,IAAI,MACnC,KAAK,OAAO,KAAK,SACjB,KAAK,KAAK,KAAK,OAAO,IAAI,KAAK,OAAO,SACtC,KAAK,OAAO,KAAK,UACjB,KAAK,KAAK,KAAK,OAAO,IAAI,KAAK,OAAO,MAC1C;AACF"}
@@ -0,0 +1,45 @@
1
+ import { AssetResolver } from '../model/assets';
2
+ import { MapState } from '../model/definition';
3
+ import { SceneNode } from '../renderer/scene';
4
+ import { Footprint, IntentOf, IntentType, Rejection } from './intents';
5
+ export type PipelineAccepted<K extends IntentType> = {
6
+ readonly ok: true;
7
+ readonly intent: IntentOf<K>;
8
+ };
9
+ /** A rejection carries the intent as it was when it was refused, e.g. already snapped to the grid. */
10
+ export type PipelineRejected<K extends IntentType> = Rejection & {
11
+ readonly intent?: IntentOf<K>;
12
+ };
13
+ export type PipelineResult<K extends IntentType = IntentType> = PipelineAccepted<K> | PipelineRejected<K>;
14
+ export interface PipelineContext {
15
+ readonly state: MapState;
16
+ readonly assets: AssetResolver;
17
+ /**
18
+ * What can get in the way: what is drawn right now, with the sizes and hitboxes the
19
+ * player sees, minus the elements the intents are moving or removing.
20
+ */
21
+ readonly nodes: readonly SceneNode[];
22
+ /** Everything that is drawn, including what the intents are moving or removing. */
23
+ readonly scene: readonly SceneNode[];
24
+ /** Who is acting. Whatever the game decides: a player, a user. */
25
+ readonly actor: unknown;
26
+ /**
27
+ * True when the pipeline only previews (a ghost following the pointer) and nothing
28
+ * will happen. Steps with a side effect, such as `rateLimit`, must not act then.
29
+ */
30
+ readonly preview: boolean;
31
+ /** The space the intent would take, or `undefined` when it does not apply. */
32
+ footprint(intent: IntentOf<IntentType>): Footprint | undefined;
33
+ /** Refuses the intent: `return ctx.reject("not-enough-wood")`. */
34
+ reject(reason: string, message?: string): Rejection;
35
+ }
36
+ /**
37
+ * One link of the chain, in the style of Koa. It may modify the intent
38
+ * (`next({ ...intent, x: 32 })`), refuse it (`ctx.reject(reason)`) or pass it on (`next()`).
39
+ */
40
+ export type PipelineStep<K extends IntentType = IntentType> = (intent: IntentOf<K>, context: PipelineContext, next: (intent?: IntentOf<K>) => PipelineResult<K>) => PipelineResult<K>;
41
+ /** Runs `steps` in order. Pure and synchronous, so the same chain can preview on the client and validate on the server. */
42
+ export declare function runPipeline<K extends IntentType>(steps: readonly PipelineStep<K>[], intent: IntentOf<K>, context: PipelineContext): PipelineResult<K>;
43
+ /** Chains several steps into one, to give a catalog item or a game a reusable rule set. */
44
+ export declare const compose: <K extends IntentType>(...steps: readonly PipelineStep<K>[]) => PipelineStep<K>;
45
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/interaction/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE5E,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,UAAU,IAAI;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAA;CAAE,CAAC;AACzG,sGAAsG;AACtG,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,UAAU,IAAI,SAAS,GAAG;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAA;CAAE,CAAC;AACnG,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IAAI,gBAAgB,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;AAE1G,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;IACrC,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;IACrC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,8EAA8E;IAC9E,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,SAAS,GAAG,SAAS,CAAC;IAC/D,kEAAkE;IAClE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACrD;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IAAI,CAC5D,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EACnB,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,cAAc,CAAC,CAAC,CAAC,KAC9C,cAAc,CAAC,CAAC,CAAC,CAAC;AAEvB,2HAA2H;AAC3H,wBAAgB,WAAW,CAAC,CAAC,SAAS,UAAU,EAC9C,KAAK,EAAE,SAAS,YAAY,CAAC,CAAC,CAAC,EAAE,EACjC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EACnB,OAAO,EAAE,eAAe,GACvB,cAAc,CAAC,CAAC,CAAC,CAQnB;AAED,2FAA2F;AAC3F,eAAO,MAAM,OAAO,GACjB,CAAC,SAAS,UAAU,EAAE,GAAG,OAAO,SAAS,YAAY,CAAC,CAAC,CAAC,EAAE,KAAG,YAAY,CAAC,CAAC,CAI3E,CAAC"}
@@ -0,0 +1,26 @@
1
+ //#region src/interaction/pipeline.ts
2
+ /** Runs `steps` in order. Pure and synchronous, so the same chain can preview on the client and validate on the server. */
3
+ function runPipeline(steps, intent, context) {
4
+ const dispatch = (index, current) => {
5
+ const step = steps[index];
6
+ if (!step) return {
7
+ ok: true,
8
+ intent: current
9
+ };
10
+ const result = step(current, context, (replacement) => dispatch(index + 1, replacement ?? current));
11
+ return result.ok || result.intent !== void 0 ? result : {
12
+ ...result,
13
+ intent: current
14
+ };
15
+ };
16
+ return dispatch(0, intent);
17
+ }
18
+ /** Chains several steps into one, to give a catalog item or a game a reusable rule set. */
19
+ var compose = (...steps) => (intent, context, next) => {
20
+ const result = runPipeline(steps, intent, context);
21
+ return result.ok ? next(result.intent) : result;
22
+ };
23
+ //#endregion
24
+ export { compose, runPipeline };
25
+
26
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","names":[],"sources":["../../src/interaction/pipeline.ts"],"sourcesContent":["import type { AssetResolver } from \"../model/assets\";\nimport type { MapState } from \"../model/definition\";\nimport type { SceneNode } from \"../renderer/scene\";\nimport type { Footprint, IntentOf, IntentType, Rejection } from \"./intents\";\n\nexport type PipelineAccepted<K extends IntentType> = { readonly ok: true; readonly intent: IntentOf<K> };\n/** A rejection carries the intent as it was when it was refused, e.g. already snapped to the grid. */\nexport type PipelineRejected<K extends IntentType> = Rejection & { readonly intent?: IntentOf<K> };\nexport type PipelineResult<K extends IntentType = IntentType> = PipelineAccepted<K> | PipelineRejected<K>;\n\nexport interface PipelineContext {\n readonly state: MapState;\n readonly assets: AssetResolver;\n /**\n * What can get in the way: what is drawn right now, with the sizes and hitboxes the\n * player sees, minus the elements the intents are moving or removing.\n */\n readonly nodes: readonly SceneNode[];\n /** Everything that is drawn, including what the intents are moving or removing. */\n readonly scene: readonly SceneNode[];\n /** Who is acting. Whatever the game decides: a player, a user. */\n readonly actor: unknown;\n /**\n * True when the pipeline only previews (a ghost following the pointer) and nothing\n * will happen. Steps with a side effect, such as `rateLimit`, must not act then.\n */\n readonly preview: boolean;\n /** The space the intent would take, or `undefined` when it does not apply. */\n footprint(intent: IntentOf<IntentType>): Footprint | undefined;\n /** Refuses the intent: `return ctx.reject(\"not-enough-wood\")`. */\n reject(reason: string, message?: string): Rejection;\n}\n\n/**\n * One link of the chain, in the style of Koa. It may modify the intent\n * (`next({ ...intent, x: 32 })`), refuse it (`ctx.reject(reason)`) or pass it on (`next()`).\n */\nexport type PipelineStep<K extends IntentType = IntentType> = (\n intent: IntentOf<K>,\n context: PipelineContext,\n next: (intent?: IntentOf<K>) => PipelineResult<K>,\n) => PipelineResult<K>;\n\n/** Runs `steps` in order. Pure and synchronous, so the same chain can preview on the client and validate on the server. */\nexport function runPipeline<K extends IntentType>(\n steps: readonly PipelineStep<K>[],\n intent: IntentOf<K>,\n context: PipelineContext,\n): PipelineResult<K> {\n const dispatch = (index: number, current: IntentOf<K>): PipelineResult<K> => {\n const step = steps[index];\n if (!step) return { ok: true, intent: current };\n const result = step(current, context, (replacement) => dispatch(index + 1, replacement ?? current));\n return result.ok || result.intent !== undefined ? result : { ...result, intent: current };\n };\n return dispatch(0, intent);\n}\n\n/** Chains several steps into one, to give a catalog item or a game a reusable rule set. */\nexport const compose =\n <K extends IntentType>(...steps: readonly PipelineStep<K>[]): PipelineStep<K> =>\n (intent, context, next) => {\n const result = runPipeline(steps, intent, context);\n return result.ok ? next(result.intent) : result;\n };\n"],"mappings":";;AA4CA,SAAgB,YACd,OACA,QACA,SACmB;CACnB,MAAM,YAAY,OAAe,YAA4C;EAC3E,MAAM,OAAO,MAAM;EACnB,IAAI,CAAC,MAAM,OAAO;GAAE,IAAI;GAAM,QAAQ;EAAQ;EAC9C,MAAM,SAAS,KAAK,SAAS,UAAU,gBAAgB,SAAS,QAAQ,GAAG,eAAe,OAAO,CAAC;EAClG,OAAO,OAAO,MAAM,OAAO,WAAW,KAAA,IAAY,SAAS;GAAE,GAAG;GAAQ,QAAQ;EAAQ;CAC1F;CACA,OAAO,SAAS,GAAG,MAAM;AAC3B;;AAGA,IAAa,WACY,GAAG,WACzB,QAAQ,SAAS,SAAS;CACzB,MAAM,SAAS,YAAY,OAAO,QAAQ,OAAO;CACjD,OAAO,OAAO,KAAK,KAAK,OAAO,MAAM,IAAI;AAC3C"}
@@ -0,0 +1,76 @@
1
+ import { JsonObject } from '../model/json';
2
+ import { Unsubscribe, ApplyResult, MaybePromise } from '../model/result';
3
+ import { describeElement } from '../renderer/scene';
4
+ import { Catalog, CatalogId, CatalogItem } from './catalog';
5
+ import { InteractionController } from './controller';
6
+ import { IntentDefinition } from './intents';
7
+ import { PipelineStep } from './pipeline';
8
+ export type PlacementAnchor = "top-left" | "center" | "bottom-center";
9
+ export interface PlacementOptions<C extends Catalog<string>> {
10
+ readonly catalog: C;
11
+ /** Rules every item goes through, before its own. Typically `[snapToGrid(32), withinMap(), requireFree()]`. */
12
+ readonly pipeline?: readonly PipelineStep<"element.place">[];
13
+ /** Which point of the item follows the pointer. Defaults to `"center"`. */
14
+ readonly anchor?: PlacementAnchor;
15
+ /** Stay in placement mode after an item was placed. Defaults to false. */
16
+ readonly keepPlacing?: boolean;
17
+ /** Ids of what gets placed. Defaults to `<catalog id>-<n>`. */
18
+ readonly idFactory?: (item: CatalogItem, taken: ReadonlySet<string>) => string;
19
+ /** Modes in which the tool is active. Defaults to `["play", "edit"]`: builders place in play. */
20
+ readonly modes?: readonly string[];
21
+ /**
22
+ * Data written on every element placed, from whoever places it: `(actor) => ({ owner: actor.id })`.
23
+ * Merged over the data of the catalog item.
24
+ */
25
+ readonly stamp?: (actor: unknown) => JsonObject | undefined;
26
+ }
27
+ export interface PlacedEvent {
28
+ readonly catalogId: string;
29
+ readonly id: string;
30
+ readonly x: number;
31
+ readonly y: number;
32
+ }
33
+ export interface RejectedEvent {
34
+ readonly catalogId: string;
35
+ /** Why: the reason of the step that refused, e.g. `"occupied"`. */
36
+ readonly reason: string;
37
+ readonly message: string;
38
+ }
39
+ export interface PlacementApi<Id extends string = string> {
40
+ readonly catalog: Catalog<Id>;
41
+ /** The item being placed, if any. */
42
+ readonly active: Id | null;
43
+ /** Starts placing: a ghost follows the pointer, valid or not, until a click or `cancel()`. */
44
+ start(id: Id): void;
45
+ cancel(): void;
46
+ onPlaced(listener: (event: PlacedEvent) => void): Unsubscribe;
47
+ onRejected(listener: (event: RejectedEvent) => void): Unsubscribe;
48
+ }
49
+ /** What the intent needs, with or without a tool: it is also what a server registers. */
50
+ export type PlacementIntentOptions<C extends Catalog<string>> = Pick<PlacementOptions<C>, "catalog" | "pipeline" | "idFactory" | "stamp">;
51
+ export interface PlacementCore {
52
+ readonly definition: IntentDefinition<"element.place">;
53
+ itemOf(id: string): CatalogItem | undefined;
54
+ /** What an item would look like at a position: its bounds and hitbox, as the renderer describes them. */
55
+ probe(item: CatalogItem, x: number, y: number): ReturnType<typeof describeElement>;
56
+ dispose(): void;
57
+ }
58
+ /**
59
+ * The `element.place` intent and the rules it goes through: those of the feature, then
60
+ * those of the catalog item. No tool and no ghost, so a server can register it too.
61
+ *
62
+ * Ids are given when the intent is resolved, numbered per item and never reused. A
63
+ * client that talks to an authority sends the intent without an id and reads the one
64
+ * it was given in the result: two clients can never choose the same.
65
+ */
66
+ export declare function registerPlacementIntent<C extends Catalog<string>>(controller: Pick<InteractionController<MaybePromise<ApplyResult>>, "registerIntent" | "usePipeline" | "describeContext">, options: PlacementIntentOptions<C>): PlacementCore;
67
+ /**
68
+ * Placing things of a catalog: the `element.place` intent, the ghost that
69
+ * previews it through the same pipeline that validates it, and the tool that
70
+ * drives both. Headless.
71
+ */
72
+ export declare function createPlacement<C extends Catalog<string>>(controller: InteractionController<MaybePromise<ApplyResult>>, options: PlacementOptions<C>): {
73
+ readonly api: PlacementApi<CatalogId<C>>;
74
+ dispose(): void;
75
+ };
76
+ //# sourceMappingURL=placement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placement.d.ts","sourceRoot":"","sources":["../../src/interaction/placement.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,238 @@
1
+ import { describeElement } from "../renderer/scene.js";
2
+ import { runPipeline } from "./pipeline.js";
3
+ //#region src/interaction/placement.ts
4
+ var offsetOf = (anchor, width, height) => anchor === "center" ? {
5
+ x: -width / 2,
6
+ y: -height / 2
7
+ } : anchor === "bottom-center" ? {
8
+ x: -width / 2,
9
+ y: -height
10
+ } : {
11
+ x: 0,
12
+ y: 0
13
+ };
14
+ /**
15
+ * The `element.place` intent and the rules it goes through: those of the feature, then
16
+ * those of the catalog item. No tool and no ghost, so a server can register it too.
17
+ *
18
+ * Ids are given when the intent is resolved, numbered per item and never reused. A
19
+ * client that talks to an authority sends the intent without an id and reads the one
20
+ * it was given in the result: two clients can never choose the same.
21
+ */
22
+ function registerPlacementIntent(controller, options) {
23
+ const { catalog } = options;
24
+ const counters = /* @__PURE__ */ new Map();
25
+ const itemOf = (id) => catalog.get(id);
26
+ const probe = (item, x, y) => describeElement({
27
+ id: "__placement__",
28
+ kind: item.sprite,
29
+ x,
30
+ y
31
+ }, controller.describeContext);
32
+ const nextId = (item, taken) => {
33
+ if (options.idFactory) return options.idFactory(item, taken);
34
+ let n = counters.get(item.id) ?? 0;
35
+ let id;
36
+ do {
37
+ n += 1;
38
+ id = `${item.id}-${n}`;
39
+ } while (taken.has(id));
40
+ counters.set(item.id, n);
41
+ return id;
42
+ };
43
+ const definition = {
44
+ resolve(intent, { state, actor }) {
45
+ const item = itemOf(intent.catalogId);
46
+ if (!item) return {
47
+ ok: false,
48
+ reason: "unknown-catalog-item",
49
+ message: `"${intent.catalogId}" is not in the catalog`
50
+ };
51
+ const id = intent.id ?? nextId(item, new Set(state.elements.map((element) => element.id)));
52
+ const data = {
53
+ ...item.data,
54
+ ...options.stamp?.(actor)
55
+ };
56
+ return {
57
+ type: "element.add",
58
+ element: {
59
+ id,
60
+ kind: item.sprite,
61
+ x: intent.x,
62
+ y: intent.y,
63
+ ...Object.keys(data).length > 0 ? { data } : {}
64
+ }
65
+ };
66
+ },
67
+ footprint(intent) {
68
+ const item = itemOf(intent.catalogId);
69
+ const node = item ? probe(item, intent.x, intent.y) : void 0;
70
+ return node ? {
71
+ bounds: node.bounds,
72
+ ...node.hitbox ? { hitbox: node.hitbox } : {}
73
+ } : void 0;
74
+ }
75
+ };
76
+ const itemRules = (intent, context, next) => {
77
+ const rules = itemOf(intent.catalogId)?.pipeline;
78
+ if (!rules || rules.length === 0) return next();
79
+ const result = runPipeline(rules, intent, context);
80
+ return result.ok ? next(result.intent) : result;
81
+ };
82
+ const stops = [controller.registerIntent("element.place", definition), controller.usePipeline("element.place", [...options.pipeline ?? [], itemRules])];
83
+ return {
84
+ definition,
85
+ itemOf,
86
+ probe,
87
+ dispose: () => stops.forEach((stop) => stop())
88
+ };
89
+ }
90
+ /**
91
+ * Placing things of a catalog: the `element.place` intent, the ghost that
92
+ * previews it through the same pipeline that validates it, and the tool that
93
+ * drives both. Headless.
94
+ */
95
+ function createPlacement(controller, options) {
96
+ const { catalog } = options;
97
+ const anchor = options.anchor ?? "center";
98
+ const placedListeners = /* @__PURE__ */ new Set();
99
+ const rejectedListeners = /* @__PURE__ */ new Set();
100
+ const core = registerPlacementIntent(controller, options);
101
+ const { itemOf, probe, definition } = core;
102
+ const stops = [core.dispose];
103
+ const ghostAt = (id, point) => {
104
+ const item = itemOf(id);
105
+ if (!item) return;
106
+ const size = probe(item, 0, 0)?.bounds;
107
+ const shift = size ? offsetOf(anchor, size.width, size.height) : {
108
+ x: 0,
109
+ y: 0
110
+ };
111
+ const wanted = {
112
+ type: "element.place",
113
+ catalogId: id,
114
+ x: point.x + shift.x,
115
+ y: point.y + shift.y
116
+ };
117
+ const result = controller.preview(wanted);
118
+ const final = result.intent ?? wanted;
119
+ const footprint = definition.footprint?.(final, {
120
+ state: controller.model.state,
121
+ assets: controller.assets,
122
+ describe: controller.describeContext,
123
+ actor: void 0
124
+ });
125
+ controller.state.update({ ghost: {
126
+ catalogId: id,
127
+ kind: item.sprite,
128
+ x: final.x,
129
+ y: final.y,
130
+ valid: result.ok,
131
+ ...!result.ok ? { reason: result.reason } : {},
132
+ ...footprint ? {
133
+ bounds: footprint.bounds,
134
+ ...footprint.hitbox ? { hitbox: footprint.hitbox } : {}
135
+ } : {}
136
+ } });
137
+ };
138
+ const stopPlacing = () => controller.state.update({
139
+ placing: null,
140
+ ghost: null
141
+ });
142
+ const tool = {
143
+ id: "placement",
144
+ modes: options.modes ?? ["play", "edit"],
145
+ priority: 100,
146
+ pointerDown(input) {
147
+ const id = controller.state.get().placing;
148
+ if (id === null) return false;
149
+ if (input.button === 2) {
150
+ stopPlacing();
151
+ return true;
152
+ }
153
+ if (input.button !== 0) return false;
154
+ if (!itemOf(id)) return true;
155
+ ghostAt(id, input);
156
+ const ghost = controller.state.get().ghost;
157
+ if (!ghost) return true;
158
+ const result = controller.submit({
159
+ type: "element.place",
160
+ catalogId: id,
161
+ x: ghost.x,
162
+ y: ghost.y
163
+ });
164
+ const settle = (outcome) => {
165
+ if (outcome.ok) {
166
+ const added = outcome.patches.find((patch) => patch.type === "element.add");
167
+ const placedId = added && "element" in added ? added.element.id : "";
168
+ for (const listener of [...placedListeners]) listener({
169
+ catalogId: id,
170
+ id: placedId,
171
+ x: ghost.x,
172
+ y: ghost.y
173
+ });
174
+ if (!options.keepPlacing) stopPlacing();
175
+ } else {
176
+ const event = {
177
+ catalogId: id,
178
+ reason: outcome.rejection.code,
179
+ message: outcome.rejection.message
180
+ };
181
+ for (const listener of [...rejectedListeners]) listener(event);
182
+ }
183
+ };
184
+ if (typeof result.then === "function") result.then(settle);
185
+ else settle(result);
186
+ return true;
187
+ },
188
+ pointerMove(input) {
189
+ const id = controller.state.get().placing;
190
+ if (id === null) return false;
191
+ ghostAt(id, input);
192
+ return false;
193
+ },
194
+ key(input) {
195
+ if (input.key === "Escape" && controller.state.get().placing !== null) {
196
+ stopPlacing();
197
+ return true;
198
+ }
199
+ return false;
200
+ },
201
+ cancel: stopPlacing
202
+ };
203
+ stops.push(controller.addTool(tool));
204
+ return {
205
+ api: {
206
+ catalog,
207
+ get active() {
208
+ return controller.state.get().placing;
209
+ },
210
+ start(id) {
211
+ if (!catalog.has(id)) throw new Error(`"${id}" is not in the catalog`);
212
+ controller.state.update({
213
+ placing: id,
214
+ ghost: null
215
+ });
216
+ if (controller.pointer) ghostAt(id, controller.pointer);
217
+ },
218
+ cancel: stopPlacing,
219
+ onPlaced(listener) {
220
+ placedListeners.add(listener);
221
+ return () => placedListeners.delete(listener);
222
+ },
223
+ onRejected(listener) {
224
+ rejectedListeners.add(listener);
225
+ return () => rejectedListeners.delete(listener);
226
+ }
227
+ },
228
+ dispose() {
229
+ stops.forEach((stop) => stop());
230
+ placedListeners.clear();
231
+ rejectedListeners.clear();
232
+ }
233
+ };
234
+ }
235
+ //#endregion
236
+ export { createPlacement, registerPlacementIntent };
237
+
238
+ //# sourceMappingURL=placement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placement.js","names":[],"sources":["../../src/interaction/placement.ts"],"sourcesContent":["import type { JsonObject } from \"../model/json\";\nimport type { Unsubscribe } from \"../model/result\";\nimport type { ApplyResult, MaybePromise } from \"../model/result\";\nimport { describeElement } from \"../renderer/scene\";\nimport type { Catalog, CatalogId, CatalogItem } from \"./catalog\";\nimport type { InteractionController, Tool } from \"./controller\";\nimport type { KeyInput, PointerInput } from \"./input\";\nimport type { Footprint, IntentDefinition } from \"./intents\";\nimport { runPipeline, type PipelineStep } from \"./pipeline\";\nimport type { Point } from \"./picking\";\n\nexport type PlacementAnchor = \"top-left\" | \"center\" | \"bottom-center\";\n\nexport interface PlacementOptions<C extends Catalog<string>> {\n readonly catalog: C;\n /** Rules every item goes through, before its own. Typically `[snapToGrid(32), withinMap(), requireFree()]`. */\n readonly pipeline?: readonly PipelineStep<\"element.place\">[];\n /** Which point of the item follows the pointer. Defaults to `\"center\"`. */\n readonly anchor?: PlacementAnchor;\n /** Stay in placement mode after an item was placed. Defaults to false. */\n readonly keepPlacing?: boolean;\n /** Ids of what gets placed. Defaults to `<catalog id>-<n>`. */\n readonly idFactory?: (item: CatalogItem, taken: ReadonlySet<string>) => string;\n /** Modes in which the tool is active. Defaults to `[\"play\", \"edit\"]`: builders place in play. */\n readonly modes?: readonly string[];\n /**\n * Data written on every element placed, from whoever places it: `(actor) => ({ owner: actor.id })`.\n * Merged over the data of the catalog item.\n */\n readonly stamp?: (actor: unknown) => JsonObject | undefined;\n}\n\nexport interface PlacedEvent {\n readonly catalogId: string;\n readonly id: string;\n readonly x: number;\n readonly y: number;\n}\n\nexport interface RejectedEvent {\n readonly catalogId: string;\n /** Why: the reason of the step that refused, e.g. `\"occupied\"`. */\n readonly reason: string;\n readonly message: string;\n}\n\nexport interface PlacementApi<Id extends string = string> {\n readonly catalog: Catalog<Id>;\n /** The item being placed, if any. */\n readonly active: Id | null;\n /** Starts placing: a ghost follows the pointer, valid or not, until a click or `cancel()`. */\n start(id: Id): void;\n cancel(): void;\n onPlaced(listener: (event: PlacedEvent) => void): Unsubscribe;\n onRejected(listener: (event: RejectedEvent) => void): Unsubscribe;\n}\n\nconst offsetOf = (anchor: PlacementAnchor, width: number, height: number): Point =>\n anchor === \"center\" ? { x: -width / 2, y: -height / 2 } : anchor === \"bottom-center\" ? { x: -width / 2, y: -height } : { x: 0, y: 0 };\n\n/** What the intent needs, with or without a tool: it is also what a server registers. */\nexport type PlacementIntentOptions<C extends Catalog<string>> = Pick<PlacementOptions<C>, \"catalog\" | \"pipeline\" | \"idFactory\" | \"stamp\">;\n\nexport interface PlacementCore {\n readonly definition: IntentDefinition<\"element.place\">;\n itemOf(id: string): CatalogItem | undefined;\n /** What an item would look like at a position: its bounds and hitbox, as the renderer describes them. */\n probe(item: CatalogItem, x: number, y: number): ReturnType<typeof describeElement>;\n dispose(): void;\n}\n\n/**\n * The `element.place` intent and the rules it goes through: those of the feature, then\n * those of the catalog item. No tool and no ghost, so a server can register it too.\n *\n * Ids are given when the intent is resolved, numbered per item and never reused. A\n * client that talks to an authority sends the intent without an id and reads the one\n * it was given in the result: two clients can never choose the same.\n */\nexport function registerPlacementIntent<C extends Catalog<string>>(\n controller: Pick<InteractionController<MaybePromise<ApplyResult>>, \"registerIntent\" | \"usePipeline\" | \"describeContext\">,\n options: PlacementIntentOptions<C>,\n): PlacementCore {\n const { catalog } = options;\n const counters = new Map<string, number>();\n const itemOf = (id: string): CatalogItem | undefined => catalog.get(id);\n\n const probe = (item: CatalogItem, x: number, y: number) =>\n describeElement({ id: \"__placement__\", kind: item.sprite, x, y }, controller.describeContext);\n\n const nextId = (item: CatalogItem, taken: ReadonlySet<string>): string => {\n if (options.idFactory) return options.idFactory(item, taken);\n let n = counters.get(item.id) ?? 0;\n let id: string;\n do {\n n += 1;\n id = `${item.id}-${n}`;\n } while (taken.has(id));\n counters.set(item.id, n);\n return id;\n };\n\n const definition: IntentDefinition<\"element.place\"> = {\n resolve(intent, { state, actor }) {\n const item = itemOf(intent.catalogId);\n if (!item) return { ok: false, reason: \"unknown-catalog-item\", message: `\"${intent.catalogId}\" is not in the catalog` };\n const id = intent.id ?? nextId(item, new Set(state.elements.map((element) => element.id)));\n const data = { ...item.data, ...options.stamp?.(actor) };\n return {\n type: \"element.add\",\n element: { id, kind: item.sprite, x: intent.x, y: intent.y, ...(Object.keys(data).length > 0 ? { data } : {}) },\n };\n },\n footprint(intent): Footprint | undefined {\n const item = itemOf(intent.catalogId);\n const node = item ? probe(item, intent.x, intent.y) : undefined;\n return node ? { bounds: node.bounds, ...(node.hitbox ? { hitbox: node.hitbox } : {}) } : undefined;\n },\n };\n\n // The rules of the item come after those of the feature, on every placement.\n const itemRules: PipelineStep<\"element.place\"> = (intent, context, next) => {\n const rules = itemOf(intent.catalogId)?.pipeline;\n if (!rules || rules.length === 0) return next();\n const result = runPipeline(rules, intent, context);\n return result.ok ? next(result.intent) : result;\n };\n\n const stops: Unsubscribe[] = [\n controller.registerIntent(\"element.place\", definition),\n controller.usePipeline(\"element.place\", [...(options.pipeline ?? []), itemRules]),\n ];\n\n return { definition, itemOf, probe, dispose: () => stops.forEach((stop) => stop()) };\n}\n\n/**\n * Placing things of a catalog: the `element.place` intent, the ghost that\n * previews it through the same pipeline that validates it, and the tool that\n * drives both. Headless.\n */\nexport function createPlacement<C extends Catalog<string>>(\n controller: InteractionController<MaybePromise<ApplyResult>>,\n options: PlacementOptions<C>,\n): { readonly api: PlacementApi<CatalogId<C>>; dispose(): void } {\n const { catalog } = options;\n const anchor = options.anchor ?? \"center\";\n const placedListeners = new Set<(event: PlacedEvent) => void>();\n const rejectedListeners = new Set<(event: RejectedEvent) => void>();\n const core = registerPlacementIntent(controller, options);\n const { itemOf, probe, definition } = core;\n const stops: Unsubscribe[] = [core.dispose];\n\n const ghostAt = (id: string, point: Point): void => {\n const item = itemOf(id);\n if (!item) return;\n const size = probe(item, 0, 0)?.bounds;\n const shift = size ? offsetOf(anchor, size.width, size.height) : { x: 0, y: 0 };\n const wanted = { type: \"element.place\" as const, catalogId: id, x: point.x + shift.x, y: point.y + shift.y };\n const result = controller.preview(wanted);\n const final = result.intent ?? wanted;\n const footprint = definition.footprint?.(final, {\n state: controller.model.state,\n assets: controller.assets,\n describe: controller.describeContext,\n actor: undefined,\n });\n controller.state.update({\n ghost: {\n catalogId: id,\n kind: item.sprite,\n x: final.x,\n y: final.y,\n valid: result.ok,\n ...(!result.ok ? { reason: result.reason } : {}),\n ...(footprint ? { bounds: footprint.bounds, ...(footprint.hitbox ? { hitbox: footprint.hitbox } : {}) } : {}),\n },\n });\n };\n\n const stopPlacing = (): void => controller.state.update({ placing: null, ghost: null });\n\n const tool: Tool = {\n id: \"placement\",\n modes: options.modes ?? [\"play\", \"edit\"],\n // Placing comes before selecting: while an item is in hand, a click places it.\n priority: 100,\n pointerDown(input: PointerInput) {\n const id = controller.state.get().placing;\n if (id === null) return false;\n if (input.button === 2) {\n stopPlacing();\n return true;\n }\n if (input.button !== 0) return false;\n\n if (!itemOf(id)) return true;\n ghostAt(id, input);\n const ghost = controller.state.get().ghost;\n if (!ghost) return true;\n\n // No id is sent: whoever resolves the intent gives one, and it comes back in the result.\n const result = controller.submit({ type: \"element.place\", catalogId: id, x: ghost.x, y: ghost.y });\n const settle = (outcome: ApplyResult): void => {\n if (outcome.ok) {\n const added = outcome.patches.find((patch) => patch.type === \"element.add\");\n const placedId = added && \"element\" in added ? added.element.id : \"\";\n for (const listener of [...placedListeners]) listener({ catalogId: id, id: placedId, x: ghost.x, y: ghost.y });\n if (!options.keepPlacing) stopPlacing();\n } else {\n const event = { catalogId: id, reason: outcome.rejection.code, message: outcome.rejection.message };\n for (const listener of [...rejectedListeners]) listener(event);\n }\n };\n if (typeof (result as Promise<unknown>).then === \"function\") void (result as Promise<ApplyResult>).then(settle);\n else settle(result as ApplyResult);\n return true;\n },\n pointerMove(input: PointerInput) {\n const id = controller.state.get().placing;\n if (id === null) return false;\n ghostAt(id, input);\n return false;\n },\n key(input: KeyInput) {\n if (input.key === \"Escape\" && controller.state.get().placing !== null) {\n stopPlacing();\n return true;\n }\n return false;\n },\n cancel: stopPlacing,\n };\n stops.push(controller.addTool(tool));\n\n const api: PlacementApi<CatalogId<C>> = {\n catalog: catalog as unknown as Catalog<CatalogId<C>>,\n get active() {\n return controller.state.get().placing as CatalogId<C> | null;\n },\n start(id) {\n if (!catalog.has(id)) throw new Error(`\"${id}\" is not in the catalog`);\n controller.state.update({ placing: id, ghost: null });\n if (controller.pointer) ghostAt(id, controller.pointer);\n },\n cancel: stopPlacing,\n onPlaced(listener) {\n placedListeners.add(listener);\n return () => placedListeners.delete(listener);\n },\n onRejected(listener) {\n rejectedListeners.add(listener);\n return () => rejectedListeners.delete(listener);\n },\n };\n\n return {\n api,\n dispose() {\n stops.forEach((stop) => stop());\n placedListeners.clear();\n rejectedListeners.clear();\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { Tool } from './controller';
2
+ import { KeyInput, PointerInput } from './input';
3
+ import { PickOptions, TargetRef } from './picking';
4
+ export interface SelectionToolOptions {
5
+ readonly modes?: readonly string[];
6
+ /** What can be selected. Defaults to elements and events. */
7
+ readonly types?: readonly TargetRef["type"][];
8
+ readonly area?: PickOptions["area"];
9
+ /**
10
+ * Dragging selected elements. `false` turns it off. With `live`, the model
11
+ * follows the pointer (one undo step); without, it changes on release.
12
+ */
13
+ readonly drag?: false | {
14
+ readonly live?: boolean;
15
+ readonly threshold?: number;
16
+ };
17
+ /** Select with a rubber band on empty space. Defaults to true. */
18
+ readonly marquee?: boolean;
19
+ /** `Delete` and `Backspace` remove the selected elements. Defaults to false. */
20
+ readonly remove?: boolean;
21
+ readonly when?: (input: PointerInput | KeyInput) => boolean;
22
+ readonly priority?: number;
23
+ }
24
+ /**
25
+ * Hover, click and rubber-band selection, dragging and deleting. Headless: it
26
+ * only reads and writes the interaction state and submits intents.
27
+ */
28
+ export declare function selectionTool(options?: SelectionToolOptions): Tool;
29
+ //# sourceMappingURL=selection-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection-tool.d.ts","sourceRoot":"","sources":["../../src/interaction/selection-tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAyB,IAAI,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEtD,OAAO,EAAiC,KAAK,WAAW,EAAc,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAExG,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;IAC9C,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACpC;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,GAAG;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjF,kEAAkE;IAClE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,GAAG,QAAQ,KAAK,OAAO,CAAC;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAsBD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,IAAI,CA2KtE"}