@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,21 @@
1
+ import { SKIN_COLORS, framesSkin, ghostLayer } from "./default.js";
2
+ //#region src/skins/game.ts
3
+ /**
4
+ * A skin for players rather than editors: a glowing outline instead of a
5
+ * technical frame, and a ghost that can pulse and take the colours of the game.
6
+ */
7
+ function gameSkin(options = {}) {
8
+ return framesSkin({
9
+ color: options.color ?? 16436245,
10
+ outline: options.outline ?? "glow"
11
+ }, ghostLayer({
12
+ valid: options.ghost?.valid ?? SKIN_COLORS.valid,
13
+ invalid: options.ghost?.invalid ?? SKIN_COLORS.invalid,
14
+ alpha: options.ghost?.alpha ?? .7,
15
+ pulse: options.pulse ?? false
16
+ }));
17
+ }
18
+ //#endregion
19
+ export { gameSkin };
20
+
21
+ //# sourceMappingURL=game.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game.js","names":[],"sources":["../../src/skins/game.ts"],"sourcesContent":["import { SKIN_COLORS, framesSkin, ghostLayer } from \"./default\";\nimport type { Skin } from \"./types\";\n\nexport interface GameSkinOptions {\n /** Colours of the ghost of the item being placed. */\n readonly ghost?: { readonly valid?: number; readonly invalid?: number; readonly alpha?: number };\n /** `\"glow\"` (default) gives selections a soft halo, `\"line\"` a plain frame. */\n readonly outline?: \"line\" | \"glow\";\n /** Colour of selections and hovers. */\n readonly color?: number;\n /** Makes the ghost pulse. */\n readonly pulse?: boolean;\n}\n\n/**\n * A skin for players rather than editors: a glowing outline instead of a\n * technical frame, and a ghost that can pulse and take the colours of the game.\n */\nexport function gameSkin(options: GameSkinOptions = {}): Skin {\n return framesSkin(\n { color: options.color ?? 0xfacc15, outline: options.outline ?? \"glow\" },\n ghostLayer({\n valid: options.ghost?.valid ?? SKIN_COLORS.valid,\n invalid: options.ghost?.invalid ?? SKIN_COLORS.invalid,\n alpha: options.ghost?.alpha ?? 0.7,\n pulse: options.pulse ?? false,\n }),\n );\n}\n"],"mappings":";;;;;;AAkBA,SAAgB,SAAS,UAA2B,CAAC,GAAS;CAC5D,OAAO,WACL;EAAE,OAAO,QAAQ,SAAS;EAAU,SAAS,QAAQ,WAAW;CAAO,GACvE,WAAW;EACT,OAAO,QAAQ,OAAO,SAAS,YAAY;EAC3C,SAAS,QAAQ,OAAO,WAAW,YAAY;EAC/C,OAAO,QAAQ,OAAO,SAAS;EAC/B,OAAO,QAAQ,SAAS;CAC1B,CAAC,CACH;AACF"}
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export { drawFrames, frameLayer, nodeOf, type DrawnFrame } from './frames';
3
+ export { defaultSkin, framesSkin, ghostLayer, SKIN_COLORS, type FrameLookOptions, type GhostLookOptions } from './default';
4
+ export { gameSkin, type GameSkinOptions } from './game';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/skins/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC3H,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,QAAQ,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { drawFrames, frameLayer, nodeOf } from "./frames.js";
2
+ import { SKIN_COLORS, defaultSkin, framesSkin, ghostLayer } from "./default.js";
3
+ import { defineSkin, mergeSkin } from "./types.js";
4
+ import { gameSkin } from "./game.js";
5
+ export { SKIN_COLORS, defaultSkin, defineSkin, drawFrames, frameLayer, framesSkin, gameSkin, ghostLayer, mergeSkin, nodeOf };
@@ -0,0 +1,46 @@
1
+ import { Container } from 'pixi.js';
2
+ import { AssetResolver } from '../model/assets';
3
+ import { InteractionSnapshot } from '../interaction/controller';
4
+ import { SceneNode } from '../renderer/scene';
5
+ import { TextureStore } from '../renderer/textures';
6
+ /** What a skin layer can reach to draw itself. */
7
+ export interface SkinContext {
8
+ /** Draw into this container: it sits in the overlay stage, above the map. */
9
+ readonly layer: Container;
10
+ readonly textures: TextureStore;
11
+ readonly assets: AssetResolver;
12
+ /** What is drawn on the map right now: where things are, how big they are. */
13
+ nodes(): readonly SceneNode[];
14
+ /** Call when something async finished (an image loaded) and the layer should draw again. */
15
+ requestUpdate(): void;
16
+ }
17
+ /** One visual of the interaction layer. It reads the published state and draws it; it never changes anything. */
18
+ export interface SkinLayer {
19
+ /** Called whenever the interaction state or the map changes. */
20
+ update(snapshot: InteractionSnapshot): void;
21
+ /** Called every frame with the time elapsed, for animations. */
22
+ tick?(deltaMs: number): void;
23
+ destroy(): void;
24
+ }
25
+ export type SkinLayerFactory = (context: SkinContext) => SkinLayer;
26
+ /**
27
+ * How the interaction layer looks. Every part is optional and replaceable on its
28
+ * own, so a game can restyle the ghost and keep the default selection.
29
+ */
30
+ export interface Skin {
31
+ /** Frames around what is selected. */
32
+ readonly selection?: SkinLayerFactory;
33
+ /** What is under the pointer. */
34
+ readonly hover?: SkinLayerFactory;
35
+ /** Where the dragged things would land, valid or not. */
36
+ readonly drag?: SkinLayerFactory;
37
+ /** The rubber band of a selection in progress. */
38
+ readonly marquee?: SkinLayerFactory;
39
+ /** The item being placed, following the pointer. */
40
+ readonly ghost?: SkinLayerFactory;
41
+ }
42
+ /** Checks a skin at compile time. */
43
+ export declare const defineSkin: (skin: Skin) => Skin;
44
+ /** A skin is a set of parts: `override` replaces the parts it defines and keeps the others of `base`. */
45
+ export declare const mergeSkin: (base: Skin, override: Skin | undefined) => Skin;
46
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/skins/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,kDAAkD;AAClD,MAAM,WAAW,WAAW;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,8EAA8E;IAC9E,KAAK,IAAI,SAAS,SAAS,EAAE,CAAC;IAC9B,4FAA4F;IAC5F,aAAa,IAAI,IAAI,CAAC;CACvB;AAED,iHAAiH;AACjH,MAAM,WAAW,SAAS;IACxB,gEAAgE;IAChE,MAAM,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC5C,gEAAgE;IAChE,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,SAAS,CAAC;AAEnE;;;GAGG;AACH,MAAM,WAAW,IAAI;IACnB,sCAAsC;IACtC,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;IACtC,iCAAiC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,yDAAyD;IACzD,QAAQ,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACjC,kDAAkD;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAC;IACpC,oDAAoD;IACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;CACnC;AAED,qCAAqC;AACrC,eAAO,MAAM,UAAU,GAAI,MAAM,IAAI,KAAG,IAAY,CAAC;AAErD,yGAAyG;AACzG,eAAO,MAAM,SAAS,GAAI,MAAM,IAAI,EAAE,UAAU,IAAI,GAAG,SAAS,KAAG,IAAkC,CAAC"}
@@ -0,0 +1,12 @@
1
+ //#region src/skins/types.ts
2
+ /** Checks a skin at compile time. */
3
+ var defineSkin = (skin) => skin;
4
+ /** A skin is a set of parts: `override` replaces the parts it defines and keeps the others of `base`. */
5
+ var mergeSkin = (base, override) => ({
6
+ ...base,
7
+ ...override
8
+ });
9
+ //#endregion
10
+ export { defineSkin, mergeSkin };
11
+
12
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../src/skins/types.ts"],"sourcesContent":["import type { Container } from \"pixi.js\";\nimport type { AssetResolver } from \"../model/assets\";\nimport type { InteractionSnapshot } from \"../interaction/controller\";\nimport type { SceneNode } from \"../renderer/scene\";\nimport type { TextureStore } from \"../renderer/textures\";\n\n/** What a skin layer can reach to draw itself. */\nexport interface SkinContext {\n /** Draw into this container: it sits in the overlay stage, above the map. */\n readonly layer: Container;\n readonly textures: TextureStore;\n readonly assets: AssetResolver;\n /** What is drawn on the map right now: where things are, how big they are. */\n nodes(): readonly SceneNode[];\n /** Call when something async finished (an image loaded) and the layer should draw again. */\n requestUpdate(): void;\n}\n\n/** One visual of the interaction layer. It reads the published state and draws it; it never changes anything. */\nexport interface SkinLayer {\n /** Called whenever the interaction state or the map changes. */\n update(snapshot: InteractionSnapshot): void;\n /** Called every frame with the time elapsed, for animations. */\n tick?(deltaMs: number): void;\n destroy(): void;\n}\n\nexport type SkinLayerFactory = (context: SkinContext) => SkinLayer;\n\n/**\n * How the interaction layer looks. Every part is optional and replaceable on its\n * own, so a game can restyle the ghost and keep the default selection.\n */\nexport interface Skin {\n /** Frames around what is selected. */\n readonly selection?: SkinLayerFactory;\n /** What is under the pointer. */\n readonly hover?: SkinLayerFactory;\n /** Where the dragged things would land, valid or not. */\n readonly drag?: SkinLayerFactory;\n /** The rubber band of a selection in progress. */\n readonly marquee?: SkinLayerFactory;\n /** The item being placed, following the pointer. */\n readonly ghost?: SkinLayerFactory;\n}\n\n/** Checks a skin at compile time. */\nexport const defineSkin = (skin: Skin): Skin => skin;\n\n/** A skin is a set of parts: `override` replaces the parts it defines and keeps the others of `base`. */\nexport const mergeSkin = (base: Skin, override: Skin | undefined): Skin => ({ ...base, ...override });\n"],"mappings":";;AA+CA,IAAa,cAAc,SAAqB;;AAGhD,IAAa,aAAa,MAAY,cAAsC;CAAE,GAAG;CAAM,GAAG;AAAS"}
@@ -0,0 +1,10 @@
1
+ import { MapIssue } from '../model/errors';
2
+ import { MapState } from '../model/definition';
3
+ import { TerrainData } from './types';
4
+ /** Problems of a terrain extension value, with their paths. Empty when it is valid. */
5
+ export declare function terrainIssues(value: unknown, path?: string): MapIssue[];
6
+ /** The terrain of a state. `undefined` when the map has none; throws when what it has is invalid. */
7
+ export declare function readTerrain(state: MapState): TerrainData | undefined;
8
+ /** Same, without throwing: for code that decides what to draw. */
9
+ export declare function tryReadTerrain(state: MapState): TerrainData | undefined;
10
+ //# sourceMappingURL=data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../src/terrain/data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAA8C,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAIvF,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,SAAuB,GAAG,QAAQ,EAAE,CAgDrF;AAED,qGAAqG;AACrG,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,CAMpE;AAED,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,CAMvE"}
@@ -0,0 +1,83 @@
1
+ import { isFiniteNumberValue, isJsonValue, isPlainObject } from "../model/json.js";
2
+ import { MapDefinitionError } from "../model/errors.js";
3
+ import { TERRAIN_EXTENSION_ID } from "./types.js";
4
+ //#region src/terrain/data.ts
5
+ var MODES = /* @__PURE__ */ new Set([
6
+ "hard",
7
+ "fade",
8
+ "nine-slice",
9
+ "water",
10
+ "custom"
11
+ ]);
12
+ /** Problems of a terrain extension value, with their paths. Empty when it is valid. */
13
+ function terrainIssues(value, path = "extensions.terrain") {
14
+ const issues = [];
15
+ const bad = (where, message) => issues.push({
16
+ path: `${path}${where}`,
17
+ message
18
+ });
19
+ if (!isPlainObject(value) || !isJsonValue(value)) return [{
20
+ path,
21
+ message: "must be a JSON object"
22
+ }];
23
+ if (value.version !== 1) bad(".version", `must be 1`);
24
+ const ids = /* @__PURE__ */ new Set();
25
+ if (!Array.isArray(value.materials)) bad(".materials", "must be an array");
26
+ else {
27
+ if (value.materials.length > 65534) bad(".materials", "too many materials");
28
+ value.materials.forEach((material, index) => {
29
+ if (!isPlainObject(material)) return bad(`.materials[${index}]`, "must be an object");
30
+ if (typeof material.id !== "string" || material.id.length === 0) bad(`.materials[${index}].id`, "must be a non-empty string");
31
+ else if (ids.has(material.id)) bad(`.materials[${index}].id`, `"${material.id}" is used twice`);
32
+ else ids.add(material.id);
33
+ if (typeof material.texture !== "string" || material.texture.length === 0) bad(`.materials[${index}].texture`, "must be an asset reference");
34
+ if (material.mode !== void 0 && (!isPlainObject(material.mode) || !MODES.has(String(material.mode.type)))) bad(`.materials[${index}].mode`, "must be a render mode");
35
+ });
36
+ }
37
+ if (value.transitions !== void 0) if (!Array.isArray(value.transitions)) bad(".transitions", "must be an array");
38
+ else value.transitions.forEach((rule, index) => {
39
+ if (!isPlainObject(rule) || !ids.has(String(rule.from)) || !ids.has(String(rule.to))) bad(`.transitions[${index}]`, "must link two materials of the palette");
40
+ });
41
+ const mask = value.mask;
42
+ if (!isPlainObject(mask)) bad(".mask", "must be an object");
43
+ else {
44
+ for (const field of [
45
+ "scale",
46
+ "width",
47
+ "height",
48
+ "chunk"
49
+ ]) if (!isFiniteNumberValue(mask[field]) || mask[field] <= 0) bad(`.mask.${field}`, "must be a positive number");
50
+ for (const field of [
51
+ "width",
52
+ "height",
53
+ "chunk"
54
+ ]) if (isFiniteNumberValue(mask[field]) && !Number.isInteger(mask[field])) bad(`.mask.${field}`, "must be an integer");
55
+ if (!isFiniteNumberValue(mask.fill) || !Number.isInteger(mask.fill) || mask.fill < 0 || mask.fill > 65535) bad(".mask.fill", "must be a material index or 65535");
56
+ else if (mask.fill !== 65535 && Array.isArray(value.materials) && mask.fill >= value.materials.length) bad(".mask.fill", "points past the palette");
57
+ if (!isPlainObject(mask.chunks)) bad(".mask.chunks", "must be an object");
58
+ else for (const [chunkKey, encoded] of Object.entries(mask.chunks)) if (!/^\d+,\d+$/.test(chunkKey) || typeof encoded !== "string") bad(`.mask.chunks["${chunkKey}"]`, "must be a string under a `col,row` key");
59
+ }
60
+ if (value.morphology !== void 0 && (!Array.isArray(value.morphology) || !value.morphology.every((item) => isPlainObject(item) && typeof item.id === "string"))) bad(".morphology", "must be an array of features with an id");
61
+ if (value.water !== void 0 && !isPlainObject(value.water)) bad(".water", "must be an object");
62
+ return issues;
63
+ }
64
+ /** The terrain of a state. `undefined` when the map has none; throws when what it has is invalid. */
65
+ function readTerrain(state) {
66
+ const value = state.extensions?.[TERRAIN_EXTENSION_ID];
67
+ if (value === void 0) return void 0;
68
+ const issues = terrainIssues(value);
69
+ if (issues.length > 0) throw new MapDefinitionError(issues);
70
+ return value;
71
+ }
72
+ /** Same, without throwing: for code that decides what to draw. */
73
+ function tryReadTerrain(state) {
74
+ try {
75
+ return readTerrain(state);
76
+ } catch {
77
+ return;
78
+ }
79
+ }
80
+ //#endregion
81
+ export { readTerrain, terrainIssues, tryReadTerrain };
82
+
83
+ //# sourceMappingURL=data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data.js","names":[],"sources":["../../src/terrain/data.ts"],"sourcesContent":["import type { MapIssue } from \"../model/errors\";\nimport { MapDefinitionError } from \"../model/errors\";\nimport type { MapState } from \"../model/definition\";\nimport { isFiniteNumberValue, isJsonValue, isPlainObject, type JsonValue } from \"../model/json\";\nimport { TERRAIN_DATA_VERSION, TERRAIN_EXTENSION_ID, type TerrainData } from \"./types\";\n\nconst MODES = new Set([\"hard\", \"fade\", \"nine-slice\", \"water\", \"custom\"]);\n\n/** Problems of a terrain extension value, with their paths. Empty when it is valid. */\nexport function terrainIssues(value: unknown, path = \"extensions.terrain\"): MapIssue[] {\n const issues: MapIssue[] = [];\n const bad = (where: string, message: string) => issues.push({ path: `${path}${where}`, message });\n if (!isPlainObject(value) || !isJsonValue(value)) return [{ path, message: \"must be a JSON object\" }];\n if (value.version !== TERRAIN_DATA_VERSION) bad(\".version\", `must be ${TERRAIN_DATA_VERSION}`);\n\n const ids = new Set<string>();\n if (!Array.isArray(value.materials)) bad(\".materials\", \"must be an array\");\n else {\n if (value.materials.length > 0xfffe) bad(\".materials\", \"too many materials\");\n value.materials.forEach((material: unknown, index: number) => {\n if (!isPlainObject(material)) return bad(`.materials[${index}]`, \"must be an object\");\n if (typeof material.id !== \"string\" || material.id.length === 0) bad(`.materials[${index}].id`, \"must be a non-empty string\");\n else if (ids.has(material.id)) bad(`.materials[${index}].id`, `\"${material.id}\" is used twice`);\n else ids.add(material.id);\n if (typeof material.texture !== \"string\" || material.texture.length === 0) bad(`.materials[${index}].texture`, \"must be an asset reference\");\n if (material.mode !== undefined && (!isPlainObject(material.mode) || !MODES.has(String(material.mode.type)))) bad(`.materials[${index}].mode`, \"must be a render mode\");\n });\n }\n\n if (value.transitions !== undefined) {\n if (!Array.isArray(value.transitions)) bad(\".transitions\", \"must be an array\");\n else value.transitions.forEach((rule: unknown, index: number) => {\n if (!isPlainObject(rule) || !ids.has(String(rule.from)) || !ids.has(String(rule.to))) bad(`.transitions[${index}]`, \"must link two materials of the palette\");\n });\n }\n\n const mask = value.mask;\n if (!isPlainObject(mask)) bad(\".mask\", \"must be an object\");\n else {\n for (const field of [\"scale\", \"width\", \"height\", \"chunk\"] as const) {\n if (!isFiniteNumberValue(mask[field]) || (mask[field] as number) <= 0) bad(`.mask.${field}`, \"must be a positive number\");\n }\n for (const field of [\"width\", \"height\", \"chunk\"] as const) {\n if (isFiniteNumberValue(mask[field]) && !Number.isInteger(mask[field])) bad(`.mask.${field}`, \"must be an integer\");\n }\n if (!isFiniteNumberValue(mask.fill) || !Number.isInteger(mask.fill) || mask.fill < 0 || mask.fill > 0xffff) bad(\".mask.fill\", \"must be a material index or 65535\");\n else if (mask.fill !== 0xffff && Array.isArray(value.materials) && mask.fill >= value.materials.length) bad(\".mask.fill\", \"points past the palette\");\n if (!isPlainObject(mask.chunks)) bad(\".mask.chunks\", \"must be an object\");\n else for (const [chunkKey, encoded] of Object.entries(mask.chunks)) {\n if (!/^\\d+,\\d+$/.test(chunkKey) || typeof encoded !== \"string\") bad(`.mask.chunks[\"${chunkKey}\"]`, \"must be a string under a `col,row` key\");\n }\n }\n if (value.morphology !== undefined && (!Array.isArray(value.morphology) || !value.morphology.every((item: unknown) => isPlainObject(item) && typeof item.id === \"string\"))) {\n bad(\".morphology\", \"must be an array of features with an id\");\n }\n if (value.water !== undefined && !isPlainObject(value.water)) bad(\".water\", \"must be an object\");\n return issues;\n}\n\n/** The terrain of a state. `undefined` when the map has none; throws when what it has is invalid. */\nexport function readTerrain(state: MapState): TerrainData | undefined {\n const value = state.extensions?.[TERRAIN_EXTENSION_ID] as JsonValue | undefined;\n if (value === undefined) return undefined;\n const issues = terrainIssues(value);\n if (issues.length > 0) throw new MapDefinitionError(issues);\n return value as unknown as TerrainData;\n}\n\n/** Same, without throwing: for code that decides what to draw. */\nexport function tryReadTerrain(state: MapState): TerrainData | undefined {\n try {\n return readTerrain(state);\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ import { TerrainBuilder } from '../model/define-map';
2
+ import { TerrainData, TerrainMaterial, TerrainMode, TerrainShape, TerrainTransition } from './types';
3
+ export interface MaterialInput {
4
+ readonly texture: string;
5
+ readonly mode?: TerrainMode;
6
+ readonly priority?: number;
7
+ readonly collision?: boolean;
8
+ readonly specialType?: string;
9
+ readonly renderTileSize?: number;
10
+ readonly renderingStyle?: "pixel-art" | "hd-2d";
11
+ }
12
+ type Shapes = {
13
+ readonly [id: string]: MaterialInput;
14
+ };
15
+ type Id<M extends Shapes> = Extract<keyof M, string>;
16
+ /** One step of the recipe that writes the mask. Later steps paint over earlier ones. */
17
+ export interface PaintOp<I extends string = string> {
18
+ readonly material: I | null;
19
+ readonly shape: TerrainShape;
20
+ }
21
+ export interface LayersOptions {
22
+ /** World pixels per mask pixel. Defaults to 1: pixel-perfect. */
23
+ readonly scale?: number;
24
+ /** Side of a mask chunk, in mask pixels. Defaults to 256. */
25
+ readonly chunk?: number;
26
+ readonly transitions?: readonly TerrainTransition[];
27
+ readonly water?: TerrainData["water"];
28
+ /** Holes and walls, as the kernel takes them. */
29
+ readonly morphology?: TerrainData["morphology"];
30
+ }
31
+ export interface TerrainDefinition<I extends string> {
32
+ readonly materials: readonly TerrainMaterial[];
33
+ /** The recipe that writes the mask. Pass it to `defineMap({ terrain })`. */
34
+ layers(ops: readonly PaintOp<I>[], options?: LayersOptions): TerrainBuilder;
35
+ /** The terrain of a map of this size, from a recipe. */
36
+ build(size: readonly [number, number], ops: readonly PaintOp<I>[], options?: LayersOptions): TerrainData;
37
+ }
38
+ /** Ways to write the mask: shapes painted with a material. */
39
+ export declare const paint: {
40
+ readonly fill: <I extends string>(material: I) => PaintOp<I>;
41
+ readonly circle: <I extends string>(material: I, options: {
42
+ x: number;
43
+ y: number;
44
+ radius: number;
45
+ }) => PaintOp<I>;
46
+ readonly rect: <I extends string>(material: I, options: {
47
+ x: number;
48
+ y: number;
49
+ width: number;
50
+ height: number;
51
+ }) => PaintOp<I>;
52
+ readonly stroke: <I extends string>(material: I, options: {
53
+ points: readonly (readonly [number, number])[];
54
+ radius: number;
55
+ }) => PaintOp<I>;
56
+ /** Erases: no material under the shape. */
57
+ readonly erase: (shape: TerrainShape) => PaintOp<never>;
58
+ };
59
+ /**
60
+ * Declares the materials of a terrain once, so that the recipe that paints them is checked by the compiler.
61
+ *
62
+ * ```ts
63
+ * const terrain = defineTerrain({
64
+ * grass: { texture: "grass", mode: { type: "fade", width: 18 } },
65
+ * water: { texture: "water", mode: { type: "water", foam: true }, collision: true },
66
+ * });
67
+ * defineMap(assets, { id: "forest", size: [2560, 1920], terrain: terrain.layers([paint.fill("grass"), paint.circle("water", { x: 800, y: 600, radius: 180 })]) });
68
+ * ```
69
+ */
70
+ export declare function defineTerrain<const M extends Shapes>(materials: M): TerrainDefinition<Id<M>>;
71
+ export {};
72
+ //# sourceMappingURL=define.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../../src/terrain/define.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ import { MapDefinitionError } from "../model/errors.js";
2
+ import "./types.js";
3
+ import { createMask, paintShape } from "./mask.js";
4
+ //#region src/terrain/define.ts
5
+ /** Ways to write the mask: shapes painted with a material. */
6
+ var paint = {
7
+ fill: (material) => ({
8
+ material,
9
+ shape: { type: "fill" }
10
+ }),
11
+ circle: (material, options) => ({
12
+ material,
13
+ shape: {
14
+ type: "circle",
15
+ ...options
16
+ }
17
+ }),
18
+ rect: (material, options) => ({
19
+ material,
20
+ shape: {
21
+ type: "rect",
22
+ ...options
23
+ }
24
+ }),
25
+ stroke: (material, options) => ({
26
+ material,
27
+ shape: {
28
+ type: "stroke",
29
+ points: options.points,
30
+ radius: options.radius
31
+ }
32
+ }),
33
+ /** Erases: no material under the shape. */
34
+ erase: (shape) => ({
35
+ material: null,
36
+ shape
37
+ })
38
+ };
39
+ /**
40
+ * Declares the materials of a terrain once, so that the recipe that paints them is checked by the compiler.
41
+ *
42
+ * ```ts
43
+ * const terrain = defineTerrain({
44
+ * grass: { texture: "grass", mode: { type: "fade", width: 18 } },
45
+ * water: { texture: "water", mode: { type: "water", foam: true }, collision: true },
46
+ * });
47
+ * defineMap(assets, { id: "forest", size: [2560, 1920], terrain: terrain.layers([paint.fill("grass"), paint.circle("water", { x: 800, y: 600, radius: 180 })]) });
48
+ * ```
49
+ */
50
+ function defineTerrain(materials) {
51
+ const list = Object.entries(materials).map(([id, input]) => ({
52
+ id,
53
+ ...input
54
+ }));
55
+ const build = (size, ops, options = {}) => {
56
+ const scale = options.scale ?? 1;
57
+ if (!(scale > 0) || !Number.isFinite(scale)) throw new MapDefinitionError([{
58
+ path: "terrain.scale",
59
+ message: "must be a positive number"
60
+ }]);
61
+ let mask = createMask(size[0], size[1], {
62
+ scale,
63
+ ...options.chunk !== void 0 ? { chunk: options.chunk } : {}
64
+ });
65
+ ops.forEach((op, index) => {
66
+ const value = op.material === null ? 65535 : list.findIndex((material) => material.id === op.material);
67
+ if (value < 0) throw new MapDefinitionError([{
68
+ path: `terrain.layers[${index}]`,
69
+ message: `material "${String(op.material)}" is not declared`
70
+ }]);
71
+ mask = paintShape(mask, op.shape, value).mask;
72
+ });
73
+ return {
74
+ version: 1,
75
+ materials: list,
76
+ ...options.transitions ? { transitions: options.transitions } : {},
77
+ mask,
78
+ ...options.water ? { water: options.water } : {},
79
+ ...options.morphology ? { morphology: options.morphology } : {}
80
+ };
81
+ };
82
+ return {
83
+ materials: list,
84
+ build,
85
+ layers: (ops, options) => ({ toExtension: (size) => build(size, ops, options) })
86
+ };
87
+ }
88
+ //#endregion
89
+ export { defineTerrain, paint };
90
+
91
+ //# sourceMappingURL=define.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define.js","names":[],"sources":["../../src/terrain/define.ts"],"sourcesContent":["import type { JsonValue } from \"../model/json\";\nimport type { TerrainBuilder } from \"../model/define-map\";\nimport { MapDefinitionError } from \"../model/errors\";\nimport { createMask, paintShape } from \"./mask\";\nimport { TERRAIN_DATA_VERSION, type TerrainData, type TerrainMaterial, type TerrainMode, type TerrainShape, type TerrainTransition } from \"./types\";\n\nexport interface MaterialInput {\n readonly texture: string;\n readonly mode?: TerrainMode;\n readonly priority?: number;\n readonly collision?: boolean;\n readonly specialType?: string;\n readonly renderTileSize?: number;\n readonly renderingStyle?: \"pixel-art\" | \"hd-2d\";\n}\n\ntype Shapes = { readonly [id: string]: MaterialInput };\ntype Id<M extends Shapes> = Extract<keyof M, string>;\n\n/** One step of the recipe that writes the mask. Later steps paint over earlier ones. */\nexport interface PaintOp<I extends string = string> {\n readonly material: I | null;\n readonly shape: TerrainShape;\n}\n\nexport interface LayersOptions {\n /** World pixels per mask pixel. Defaults to 1: pixel-perfect. */\n readonly scale?: number;\n /** Side of a mask chunk, in mask pixels. Defaults to 256. */\n readonly chunk?: number;\n readonly transitions?: readonly TerrainTransition[];\n readonly water?: TerrainData[\"water\"];\n /** Holes and walls, as the kernel takes them. */\n readonly morphology?: TerrainData[\"morphology\"];\n}\n\nexport interface TerrainDefinition<I extends string> {\n readonly materials: readonly TerrainMaterial[];\n /** The recipe that writes the mask. Pass it to `defineMap({ terrain })`. */\n layers(ops: readonly PaintOp<I>[], options?: LayersOptions): TerrainBuilder;\n /** The terrain of a map of this size, from a recipe. */\n build(size: readonly [number, number], ops: readonly PaintOp<I>[], options?: LayersOptions): TerrainData;\n}\n\n/** Ways to write the mask: shapes painted with a material. */\nexport const paint = {\n fill: <I extends string>(material: I): PaintOp<I> => ({ material, shape: { type: \"fill\" } }),\n circle: <I extends string>(material: I, options: { x: number; y: number; radius: number }): PaintOp<I> => ({ material, shape: { type: \"circle\", ...options } }),\n rect: <I extends string>(material: I, options: { x: number; y: number; width: number; height: number }): PaintOp<I> => ({ material, shape: { type: \"rect\", ...options } }),\n stroke: <I extends string>(material: I, options: { points: readonly (readonly [number, number])[]; radius: number }): PaintOp<I> => ({ material, shape: { type: \"stroke\", points: options.points, radius: options.radius } }),\n /** Erases: no material under the shape. */\n erase: (shape: TerrainShape): PaintOp<never> => ({ material: null, shape }),\n} as const;\n\n/**\n * Declares the materials of a terrain once, so that the recipe that paints them is checked by the compiler.\n *\n * ```ts\n * const terrain = defineTerrain({\n * grass: { texture: \"grass\", mode: { type: \"fade\", width: 18 } },\n * water: { texture: \"water\", mode: { type: \"water\", foam: true }, collision: true },\n * });\n * defineMap(assets, { id: \"forest\", size: [2560, 1920], terrain: terrain.layers([paint.fill(\"grass\"), paint.circle(\"water\", { x: 800, y: 600, radius: 180 })]) });\n * ```\n */\nexport function defineTerrain<const M extends Shapes>(materials: M): TerrainDefinition<Id<M>> {\n const list: TerrainMaterial[] = Object.entries(materials).map(([id, input]) => ({ id, ...input }));\n const build = (size: readonly [number, number], ops: readonly PaintOp<Id<M>>[], options: LayersOptions = {}): TerrainData => {\n const scale = options.scale ?? 1;\n if (!(scale > 0) || !Number.isFinite(scale)) throw new MapDefinitionError([{ path: \"terrain.scale\", message: \"must be a positive number\" }]);\n let mask = createMask(size[0], size[1], { scale, ...(options.chunk !== undefined ? { chunk: options.chunk } : {}) });\n ops.forEach((op, index) => {\n const value = op.material === null ? 0xffff : list.findIndex((material) => material.id === op.material);\n if (value < 0) throw new MapDefinitionError([{ path: `terrain.layers[${index}]`, message: `material \"${String(op.material)}\" is not declared` }]);\n mask = paintShape(mask, op.shape, value).mask;\n });\n return {\n version: TERRAIN_DATA_VERSION,\n materials: list,\n ...(options.transitions ? { transitions: options.transitions } : {}),\n mask,\n ...(options.water ? { water: options.water } : {}),\n ...(options.morphology ? { morphology: options.morphology } : {}),\n };\n };\n return {\n materials: list,\n build,\n layers: (ops, options) => ({ toExtension: (size) => build(size, ops, options) as unknown as JsonValue }),\n };\n}\n"],"mappings":";;;;;AA6CA,IAAa,QAAQ;CACnB,OAAyB,cAA6B;EAAE;EAAU,OAAO,EAAE,MAAM,OAAO;CAAE;CAC1F,SAA2B,UAAa,aAAmE;EAAE;EAAU,OAAO;GAAE,MAAM;GAAU,GAAG;EAAQ;CAAE;CAC7J,OAAyB,UAAa,aAAkF;EAAE;EAAU,OAAO;GAAE,MAAM;GAAQ,GAAG;EAAQ;CAAE;CACxK,SAA2B,UAAa,aAA6F;EAAE;EAAU,OAAO;GAAE,MAAM;GAAU,QAAQ,QAAQ;GAAQ,QAAQ,QAAQ;EAAO;CAAE;;CAE3N,QAAQ,WAAyC;EAAE,UAAU;EAAM;CAAM;AAC3E;;;;;;;;;;;;AAaA,SAAgB,cAAsC,WAAwC;CAC5F,MAAM,OAA0B,OAAO,QAAQ,SAAS,CAAC,CAAC,KAAK,CAAC,IAAI,YAAY;EAAE;EAAI,GAAG;CAAM,EAAE;CACjG,MAAM,SAAS,MAAiC,KAAgC,UAAyB,CAAC,MAAmB;EAC3H,MAAM,QAAQ,QAAQ,SAAS;EAC/B,IAAI,EAAE,QAAQ,MAAM,CAAC,OAAO,SAAS,KAAK,GAAG,MAAM,IAAI,mBAAmB,CAAC;GAAE,MAAM;GAAiB,SAAS;EAA4B,CAAC,CAAC;EAC3I,IAAI,OAAO,WAAW,KAAK,IAAI,KAAK,IAAI;GAAE;GAAO,GAAI,QAAQ,UAAU,KAAA,IAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;EAAG,CAAC;EACnH,IAAI,SAAS,IAAI,UAAU;GACzB,MAAM,QAAQ,GAAG,aAAa,OAAO,QAAS,KAAK,WAAW,aAAa,SAAS,OAAO,GAAG,QAAQ;GACtG,IAAI,QAAQ,GAAG,MAAM,IAAI,mBAAmB,CAAC;IAAE,MAAM,kBAAkB,MAAM;IAAI,SAAS,aAAa,OAAO,GAAG,QAAQ,EAAE;GAAmB,CAAC,CAAC;GAChJ,OAAO,WAAW,MAAM,GAAG,OAAO,KAAK,CAAC,CAAC;EAC3C,CAAC;EACD,OAAO;GACL,SAAA;GACA,WAAW;GACX,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,YAAY,IAAI,CAAC;GAClE;GACA,GAAI,QAAQ,QAAQ,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;GAChD,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;EACjE;CACF;CACA,OAAO;EACL,WAAW;EACX;EACA,SAAS,KAAK,aAAa,EAAE,cAAc,SAAS,MAAM,MAAM,KAAK,OAAO,EAA0B;CACxG;AACF"}
@@ -0,0 +1,54 @@
1
+ import { Unsubscribe } from '../model/result';
2
+ import { InteractionController, Tool } from '../interaction/controller';
3
+ import { IntentDefinition } from '../interaction/intents';
4
+ import { PipelineStep } from '../interaction/pipeline';
5
+ import { TerrainShape } from './types';
6
+ declare module "../interaction/intents" {
7
+ interface IntentRegistry {
8
+ /** Paint a shape of the terrain with a material, or erase it with `null`. */
9
+ "terrain.paint": {
10
+ readonly shape: TerrainShape;
11
+ readonly material: string | null;
12
+ };
13
+ }
14
+ }
15
+ /** The `terrain.paint` intent: one patch, after the rules of the pipeline. */
16
+ export declare const terrainPaintIntent: IntentDefinition<"terrain.paint">;
17
+ /** Registers the intent, unless a feature already did. */
18
+ export declare function registerTerrainIntents(controller: InteractionController): Unsubscribe;
19
+ /**
20
+ * Only paint over certain materials: `requireTerrain(["grass", "dirt"])` lets a game till grass and nothing else.
21
+ * Checks every pixel the shape would cover. Pixels outside the map count as wrong ground.
22
+ */
23
+ export declare function requireTerrain(materials: readonly string[]): PipelineStep<"terrain.paint">;
24
+ /** Refuses shapes that reach outside the map. Without it they are clipped. */
25
+ export declare function withinTerrain(): PipelineStep<"terrain.paint">;
26
+ /** Keeps a brush from being bigger than a game wants. */
27
+ export declare function maxRadius(limit: number): PipelineStep<"terrain.paint">;
28
+ export interface BrushOptions {
29
+ /** Material to paint, or `null` to erase. */
30
+ readonly material: string | null;
31
+ /** Radius in world pixels. */
32
+ readonly radius: number;
33
+ /** Distance between two points of a stroke, in world pixels. Defaults to a third of the radius. */
34
+ readonly spacing?: number;
35
+ }
36
+ export interface BrushApi {
37
+ /** Starts painting with the pointer: pressing paints, dragging paints a line, releasing ends the stroke. */
38
+ start(options: BrushOptions): void;
39
+ stop(): void;
40
+ readonly active: BrushOptions | null;
41
+ }
42
+ export interface BrushTool {
43
+ readonly tool: Tool;
44
+ readonly api: BrushApi;
45
+ }
46
+ /**
47
+ * The tool that paints. Each press-drag-release is one undo step, however many patches it made. It only
48
+ * submits intents: whatever rules the pipeline has, and wherever the model commits (here or on an authority),
49
+ * apply as for anything else.
50
+ */
51
+ export declare function terrainBrush(options?: {
52
+ readonly modes?: readonly string[];
53
+ }): BrushTool;
54
+ //# sourceMappingURL=editing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editing.d.ts","sourceRoot":"","sources":["../../src/terrain/editing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,qBAAqB,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AAE7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAI5D,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAEzD,OAAO,QAAQ,wBAAwB,CAAC;IACtC,UAAU,cAAc;QACtB,6EAA6E;QAC7E,eAAe,EAAE;YAAE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;YAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE,CAAC;KACrF;CACF;AAED,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,EAAE,gBAAgB,CAAC,eAAe,CAKhE,CAAC;AAEF,0DAA0D;AAC1D,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,qBAAqB,GAAG,WAAW,CAErF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,YAAY,CAAC,eAAe,CAAC,CAsB1F;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,IAAI,YAAY,CAAC,eAAe,CAAC,CAa7D;AAED,yDAAyD;AACzD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAAC,eAAe,CAAC,CAMtE;AAED,MAAM,WAAW,YAAY;IAC3B,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mGAAmG;IACnG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,QAAQ;IACvB,4GAA4G;IAC5G,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC,IAAI,IAAI,IAAI,CAAC;IACb,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC;CACxB;AAID;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAO,GAAG,SAAS,CAuD5F"}
@@ -0,0 +1,169 @@
1
+ import "./types.js";
2
+ import { tryReadTerrain } from "./data.js";
3
+ import { covers, readRect, shapeBounds } from "./mask.js";
4
+ import { paintCost } from "./patches.js";
5
+ //#region src/terrain/editing.ts
6
+ /** The `terrain.paint` intent: one patch, after the rules of the pipeline. */
7
+ var terrainPaintIntent = { resolve(intent, { state }) {
8
+ if (!tryReadTerrain(state)) return {
9
+ ok: false,
10
+ reason: "no-terrain",
11
+ message: "This map has no terrain"
12
+ };
13
+ return {
14
+ type: "terrain.paint",
15
+ shape: intent.shape,
16
+ material: intent.material
17
+ };
18
+ } };
19
+ /** Registers the intent, unless a feature already did. */
20
+ function registerTerrainIntents(controller) {
21
+ return controller.hasIntent("terrain.paint") ? () => void 0 : controller.registerIntent("terrain.paint", terrainPaintIntent);
22
+ }
23
+ /**
24
+ * Only paint over certain materials: `requireTerrain(["grass", "dirt"])` lets a game till grass and nothing else.
25
+ * Checks every pixel the shape would cover. Pixels outside the map count as wrong ground.
26
+ */
27
+ function requireTerrain(materials) {
28
+ return (intent, context, next) => {
29
+ const terrain = tryReadTerrain(context.state);
30
+ if (!terrain) return context.reject("no-terrain", "This map has no terrain");
31
+ const allowed = new Set(materials.map((id) => terrain.materials.findIndex((material) => material.id === id)).filter((index) => index >= 0));
32
+ const bounds = shapeBounds(terrain.mask, intent.shape);
33
+ if (!bounds) return context.reject("outside-map", "Nothing of this lies on the map");
34
+ if (paintCost(terrain.mask, intent.shape) > 5e7) return context.reject("too-large", "This is too large to paint at once");
35
+ const values = readRect(terrain.mask, bounds);
36
+ for (let y = bounds.y; y < bounds.y + bounds.height; y += 1) for (let x = bounds.x; x < bounds.x + bounds.width; x += 1) {
37
+ if (!covers(terrain.mask, intent.shape, x, y)) continue;
38
+ const value = values[(y - bounds.y) * bounds.width + (x - bounds.x)];
39
+ if (value === 65535 || !allowed.has(value)) return context.reject("wrong-ground", `Can only be painted over ${materials.join(", ")}`);
40
+ }
41
+ return next();
42
+ };
43
+ }
44
+ /** Refuses shapes that reach outside the map. Without it they are clipped. */
45
+ function withinTerrain() {
46
+ return (intent, context, next) => {
47
+ const terrain = tryReadTerrain(context.state);
48
+ if (!terrain) return context.reject("no-terrain", "This map has no terrain");
49
+ const shape = intent.shape;
50
+ const width = terrain.mask.width * terrain.mask.scale;
51
+ const height = terrain.mask.height * terrain.mask.scale;
52
+ const box = shape.type === "fill" ? {
53
+ x0: 0,
54
+ y0: 0,
55
+ x1: width,
56
+ y1: height
57
+ } : shape.type === "circle" ? {
58
+ x0: shape.x - shape.radius,
59
+ y0: shape.y - shape.radius,
60
+ x1: shape.x + shape.radius,
61
+ y1: shape.y + shape.radius
62
+ } : shape.type === "rect" ? {
63
+ x0: shape.x,
64
+ y0: shape.y,
65
+ x1: shape.x + shape.width,
66
+ y1: shape.y + shape.height
67
+ } : {
68
+ x0: Math.min(...shape.points.map((p) => p[0])) - shape.radius,
69
+ y0: Math.min(...shape.points.map((p) => p[1])) - shape.radius,
70
+ x1: Math.max(...shape.points.map((p) => p[0])) + shape.radius,
71
+ y1: Math.max(...shape.points.map((p) => p[1])) + shape.radius
72
+ };
73
+ if (box.x0 < 0 || box.y0 < 0 || box.x1 > width || box.y1 > height) return context.reject("outside-map", "This reaches outside the map");
74
+ return next();
75
+ };
76
+ }
77
+ /** Keeps a brush from being bigger than a game wants. */
78
+ function maxRadius(limit) {
79
+ return (intent, _context, next) => {
80
+ const shape = intent.shape;
81
+ if ((shape.type === "circle" || shape.type === "stroke") && shape.radius > limit) return next({
82
+ ...intent,
83
+ shape: {
84
+ ...shape,
85
+ radius: limit
86
+ }
87
+ });
88
+ return next();
89
+ };
90
+ }
91
+ var strokes = 0;
92
+ /**
93
+ * The tool that paints. Each press-drag-release is one undo step, however many patches it made. It only
94
+ * submits intents: whatever rules the pipeline has, and wherever the model commits (here or on an authority),
95
+ * apply as for anything else.
96
+ */
97
+ function terrainBrush(options = {}) {
98
+ let brush = null;
99
+ let last = null;
100
+ let key = "";
101
+ const paintAt = (controller, shape) => {
102
+ if (!brush) return;
103
+ controller.submit({
104
+ type: "terrain.paint",
105
+ shape,
106
+ material: brush.material
107
+ }, { coalesce: key });
108
+ };
109
+ const end = () => {
110
+ last = null;
111
+ key = "";
112
+ };
113
+ return {
114
+ tool: {
115
+ id: "terrain-brush",
116
+ modes: options.modes ?? ["edit"],
117
+ priority: 90,
118
+ pointerDown(input, controller) {
119
+ if (!brush || input.button !== 0) return false;
120
+ strokes += 1;
121
+ key = `terrain-stroke-${strokes}`;
122
+ last = {
123
+ x: input.x,
124
+ y: input.y
125
+ };
126
+ paintAt(controller, {
127
+ type: "circle",
128
+ x: input.x,
129
+ y: input.y,
130
+ radius: brush.radius
131
+ });
132
+ return true;
133
+ },
134
+ pointerMove(input, controller) {
135
+ if (!brush || !last) return false;
136
+ const spacing = brush.spacing ?? Math.max(1, brush.radius / 3);
137
+ if (Math.hypot(input.x - last.x, input.y - last.y) < spacing) return true;
138
+ paintAt(controller, {
139
+ type: "stroke",
140
+ points: [[last.x, last.y], [input.x, input.y]],
141
+ radius: brush.radius
142
+ });
143
+ last = {
144
+ x: input.x,
145
+ y: input.y
146
+ };
147
+ return true;
148
+ },
149
+ pointerUp: end,
150
+ cancel: end
151
+ },
152
+ api: {
153
+ start(next) {
154
+ brush = next;
155
+ },
156
+ stop() {
157
+ brush = null;
158
+ end();
159
+ },
160
+ get active() {
161
+ return brush;
162
+ }
163
+ }
164
+ };
165
+ }
166
+ //#endregion
167
+ export { maxRadius, registerTerrainIntents, requireTerrain, terrainBrush, terrainPaintIntent, withinTerrain };
168
+
169
+ //# sourceMappingURL=editing.js.map