1681-engine 0.5.9 → 0.6.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 (210) hide show
  1. package/CHANGELOG.md +92 -1
  2. package/README.md +15 -6
  3. package/dist/1681-engine.js +588 -465
  4. package/dist/ai/behaviorTree.js +198 -0
  5. package/dist/ai/buildTree.js +54 -0
  6. package/dist/ambient/life/Birds.js +73 -0
  7. package/dist/ambient/life/Butterflies.js +43 -0
  8. package/dist/ambient/life/ChimneySmoke.js +50 -0
  9. package/dist/ambient/life/Drift.js +75 -0
  10. package/dist/ambient/life/Fireflies.js +52 -0
  11. package/dist/ambient/life/lifeMotion.js +49 -0
  12. package/dist/assets/AssetLoader.js +115 -70
  13. package/dist/assets/Prepainter.js +61 -0
  14. package/dist/assets/workerPainter.js +39 -0
  15. package/dist/audio/spatial.js +118 -0
  16. package/dist/cjs/electron/electronLike.d.ts +55 -0
  17. package/dist/cjs/electron/electronLike.js +7 -0
  18. package/dist/cjs/electron/index.d.ts +14 -0
  19. package/dist/cjs/electron/index.js +20 -0
  20. package/dist/cjs/electron/lockdown.d.ts +33 -0
  21. package/dist/cjs/electron/lockdown.js +69 -0
  22. package/dist/cjs/electron/lockdownRules.d.ts +28 -0
  23. package/dist/cjs/electron/lockdownRules.js +61 -0
  24. package/dist/cjs/electron/sessionStore.d.ts +40 -0
  25. package/dist/cjs/electron/sessionStore.js +77 -0
  26. package/dist/cjs/electron/shellId.d.ts +22 -0
  27. package/dist/cjs/electron/shellId.js +31 -0
  28. package/dist/cjs/electron/webFiles.d.ts +13 -0
  29. package/dist/cjs/electron/webFiles.js +52 -0
  30. package/dist/cjs/electron/webPatcher.d.ts +83 -0
  31. package/dist/cjs/electron/webPatcher.js +223 -0
  32. package/dist/cjs/electron/webPointers.d.ts +19 -0
  33. package/dist/cjs/electron/webPointers.js +35 -0
  34. package/dist/cjs/package.json +1 -0
  35. package/dist/cjs/updates/index.d.ts +8 -0
  36. package/dist/cjs/updates/index.js +21 -0
  37. package/dist/cjs/updates/manifest.d.ts +73 -0
  38. package/dist/cjs/updates/manifest.js +67 -0
  39. package/dist/cjs/updates/pickUpdate.d.ts +42 -0
  40. package/dist/cjs/updates/pickUpdate.js +52 -0
  41. package/dist/cjs/updates/webPlan.d.ts +20 -0
  42. package/dist/cjs/updates/webPlan.js +34 -0
  43. package/dist/dialogue/DialogueRunner.js +173 -0
  44. package/dist/dialogue/expr.js +137 -0
  45. package/dist/dialogue/validateDialogue.js +52 -0
  46. package/dist/ecs/prefab.js +94 -0
  47. package/dist/electron/lockdown.js +31 -0
  48. package/dist/electron/lockdownRules.js +42 -0
  49. package/dist/electron/sessionStore.js +48 -0
  50. package/dist/electron/shellId.js +18 -0
  51. package/dist/electron/webFiles.js +36 -0
  52. package/dist/electron/webPatcher.js +153 -0
  53. package/dist/electron/webPointers.js +25 -0
  54. package/dist/electron.js +19 -0
  55. package/dist/input/Actions.js +58 -27
  56. package/dist/input/InputCapture.js +98 -0
  57. package/dist/input/Keymap.js +32 -26
  58. package/dist/input/haptics.js +50 -0
  59. package/dist/input/inputTape.js +68 -0
  60. package/dist/mobile/backButton.js +40 -0
  61. package/dist/mobile/capacitor.js +48 -0
  62. package/dist/mobile/screen.js +31 -0
  63. package/dist/mobile.js +12 -0
  64. package/dist/nav/flowField.js +58 -0
  65. package/dist/nav/steering.js +77 -0
  66. package/dist/net/ApiClient.js +96 -0
  67. package/dist/net/InterpolationBuffer.js +30 -23
  68. package/dist/net/LagCompensator.js +100 -0
  69. package/dist/net/Room.js +122 -43
  70. package/dist/net/RoomHost.js +13 -4
  71. package/dist/net/SpatialHash.js +66 -0
  72. package/dist/pixel/lifeArt.js +83 -0
  73. package/dist/pixel.js +63 -58
  74. package/dist/react/ConfirmButton.js +24 -0
  75. package/dist/react/baseCss.js +1 -0
  76. package/dist/react/labels.js +7 -6
  77. package/dist/react/useJsonSelector.js +13 -0
  78. package/dist/react.js +73 -68
  79. package/dist/tilemap/TilemapView.js +57 -32
  80. package/dist/tilemap/autotile.js +103 -0
  81. package/dist/tilemap/collision.js +29 -18
  82. package/dist/tilemap/ldtk.js +21 -0
  83. package/dist/tilemap/ldtkParse.js +191 -0
  84. package/dist/types/ai/behaviorTree.d.ts +133 -0
  85. package/dist/types/ai/behaviorTree.d.ts.map +1 -0
  86. package/dist/types/ai/buildTree.d.ts +55 -0
  87. package/dist/types/ai/buildTree.d.ts.map +1 -0
  88. package/dist/types/ambient/life/Birds.d.ts +50 -0
  89. package/dist/types/ambient/life/Birds.d.ts.map +1 -0
  90. package/dist/types/ambient/life/Butterflies.d.ts +32 -0
  91. package/dist/types/ambient/life/Butterflies.d.ts.map +1 -0
  92. package/dist/types/ambient/life/ChimneySmoke.d.ts +37 -0
  93. package/dist/types/ambient/life/ChimneySmoke.d.ts.map +1 -0
  94. package/dist/types/ambient/life/Drift.d.ts +58 -0
  95. package/dist/types/ambient/life/Drift.d.ts.map +1 -0
  96. package/dist/types/ambient/life/Fireflies.d.ts +43 -0
  97. package/dist/types/ambient/life/Fireflies.d.ts.map +1 -0
  98. package/dist/types/ambient/life/lifeMotion.d.ts +63 -0
  99. package/dist/types/ambient/life/lifeMotion.d.ts.map +1 -0
  100. package/dist/types/assets/AssetLoader.d.ts +20 -0
  101. package/dist/types/assets/AssetLoader.d.ts.map +1 -1
  102. package/dist/types/assets/Prepainter.d.ts +64 -0
  103. package/dist/types/assets/Prepainter.d.ts.map +1 -0
  104. package/dist/types/assets/workerPainter.d.ts +21 -0
  105. package/dist/types/assets/workerPainter.d.ts.map +1 -0
  106. package/dist/types/audio/spatial.d.ts +107 -0
  107. package/dist/types/audio/spatial.d.ts.map +1 -0
  108. package/dist/types/dialogue/DialogueRunner.d.ts +78 -0
  109. package/dist/types/dialogue/DialogueRunner.d.ts.map +1 -0
  110. package/dist/types/dialogue/expr.d.ts +12 -0
  111. package/dist/types/dialogue/expr.d.ts.map +1 -0
  112. package/dist/types/dialogue/script.d.ts +78 -0
  113. package/dist/types/dialogue/script.d.ts.map +1 -0
  114. package/dist/types/dialogue/validateDialogue.d.ts +21 -0
  115. package/dist/types/dialogue/validateDialogue.d.ts.map +1 -0
  116. package/dist/types/ecs/prefab.d.ts +84 -0
  117. package/dist/types/ecs/prefab.d.ts.map +1 -0
  118. package/dist/types/electron/electronLike.d.ts +56 -0
  119. package/dist/types/electron/electronLike.d.ts.map +1 -0
  120. package/dist/types/electron/index.d.ts +15 -0
  121. package/dist/types/electron/index.d.ts.map +1 -0
  122. package/dist/types/electron/lockdown.d.ts +34 -0
  123. package/dist/types/electron/lockdown.d.ts.map +1 -0
  124. package/dist/types/electron/lockdownRules.d.ts +29 -0
  125. package/dist/types/electron/lockdownRules.d.ts.map +1 -0
  126. package/dist/types/electron/sessionStore.d.ts +41 -0
  127. package/dist/types/electron/sessionStore.d.ts.map +1 -0
  128. package/dist/types/electron/shellId.d.ts +23 -0
  129. package/dist/types/electron/shellId.d.ts.map +1 -0
  130. package/dist/types/electron/webFiles.d.ts +14 -0
  131. package/dist/types/electron/webFiles.d.ts.map +1 -0
  132. package/dist/types/electron/webPatcher.d.ts +84 -0
  133. package/dist/types/electron/webPatcher.d.ts.map +1 -0
  134. package/dist/types/electron/webPointers.d.ts +20 -0
  135. package/dist/types/electron/webPointers.d.ts.map +1 -0
  136. package/dist/types/index.d.ts +30 -3
  137. package/dist/types/index.d.ts.map +1 -1
  138. package/dist/types/input/Actions.d.ts +14 -0
  139. package/dist/types/input/Actions.d.ts.map +1 -1
  140. package/dist/types/input/InputCapture.d.ts +53 -0
  141. package/dist/types/input/InputCapture.d.ts.map +1 -0
  142. package/dist/types/input/Keymap.d.ts +12 -6
  143. package/dist/types/input/Keymap.d.ts.map +1 -1
  144. package/dist/types/input/haptics.d.ts +42 -0
  145. package/dist/types/input/haptics.d.ts.map +1 -0
  146. package/dist/types/input/inputTape.d.ts +66 -0
  147. package/dist/types/input/inputTape.d.ts.map +1 -0
  148. package/dist/types/mobile/backButton.d.ts +30 -0
  149. package/dist/types/mobile/backButton.d.ts.map +1 -0
  150. package/dist/types/mobile/capacitor.d.ts +62 -0
  151. package/dist/types/mobile/capacitor.d.ts.map +1 -0
  152. package/dist/types/mobile/index.d.ts +4 -0
  153. package/dist/types/mobile/index.d.ts.map +1 -0
  154. package/dist/types/mobile/screen.d.ts +27 -0
  155. package/dist/types/mobile/screen.d.ts.map +1 -0
  156. package/dist/types/nav/flowField.d.ts +31 -0
  157. package/dist/types/nav/flowField.d.ts.map +1 -0
  158. package/dist/types/nav/steering.d.ts +47 -0
  159. package/dist/types/nav/steering.d.ts.map +1 -0
  160. package/dist/types/net/ApiClient.d.ts +74 -0
  161. package/dist/types/net/ApiClient.d.ts.map +1 -0
  162. package/dist/types/net/InterpolationBuffer.d.ts +5 -0
  163. package/dist/types/net/InterpolationBuffer.d.ts.map +1 -1
  164. package/dist/types/net/LagCompensator.d.ts +70 -0
  165. package/dist/types/net/LagCompensator.d.ts.map +1 -0
  166. package/dist/types/net/Room.d.ts +72 -3
  167. package/dist/types/net/Room.d.ts.map +1 -1
  168. package/dist/types/net/RoomHost.d.ts +8 -3
  169. package/dist/types/net/RoomHost.d.ts.map +1 -1
  170. package/dist/types/net/SpatialHash.d.ts +25 -0
  171. package/dist/types/net/SpatialHash.d.ts.map +1 -0
  172. package/dist/types/pixel/index.d.ts +1 -0
  173. package/dist/types/pixel/index.d.ts.map +1 -1
  174. package/dist/types/pixel/lifeArt.d.ts +63 -0
  175. package/dist/types/pixel/lifeArt.d.ts.map +1 -0
  176. package/dist/types/react/ConfirmButton.d.ts +26 -0
  177. package/dist/types/react/ConfirmButton.d.ts.map +1 -0
  178. package/dist/types/react/baseCss.d.ts +1 -1
  179. package/dist/types/react/baseCss.d.ts.map +1 -1
  180. package/dist/types/react/index.d.ts +2 -0
  181. package/dist/types/react/index.d.ts.map +1 -1
  182. package/dist/types/react/labels.d.ts +2 -0
  183. package/dist/types/react/labels.d.ts.map +1 -1
  184. package/dist/types/react/useJsonSelector.d.ts +13 -0
  185. package/dist/types/react/useJsonSelector.d.ts.map +1 -0
  186. package/dist/types/tilemap/TilemapView.d.ts +17 -0
  187. package/dist/types/tilemap/TilemapView.d.ts.map +1 -1
  188. package/dist/types/tilemap/autotile.d.ts +95 -0
  189. package/dist/types/tilemap/autotile.d.ts.map +1 -0
  190. package/dist/types/tilemap/collision.d.ts +8 -0
  191. package/dist/types/tilemap/collision.d.ts.map +1 -1
  192. package/dist/types/tilemap/ldtk.d.ts +97 -0
  193. package/dist/types/tilemap/ldtk.d.ts.map +1 -0
  194. package/dist/types/tilemap/ldtkParse.d.ts +26 -0
  195. package/dist/types/tilemap/ldtkParse.d.ts.map +1 -0
  196. package/dist/types/ui/ScrollView.d.ts.map +1 -1
  197. package/dist/types/updates/index.d.ts +9 -0
  198. package/dist/types/updates/index.d.ts.map +1 -0
  199. package/dist/types/updates/manifest.d.ts +74 -0
  200. package/dist/types/updates/manifest.d.ts.map +1 -0
  201. package/dist/types/updates/pickUpdate.d.ts +43 -0
  202. package/dist/types/updates/pickUpdate.d.ts.map +1 -0
  203. package/dist/types/updates/webPlan.d.ts +21 -0
  204. package/dist/types/updates/webPlan.d.ts.map +1 -0
  205. package/dist/ui/ScrollView.js +10 -8
  206. package/dist/updates/manifest.js +35 -0
  207. package/dist/updates/pickUpdate.js +27 -0
  208. package/dist/updates/webPlan.js +14 -0
  209. package/dist/updates.js +15 -0
  210. package/package.json +35 -3
@@ -1,35 +1,44 @@
1
- import { sound as u } from "../audio/SoundEngine.js";
2
- import { parseTiled as f } from "../tilemap/tiled.js";
3
- import { collectAtlasPages as l, duplicateFrames as w, parseAtlas as p } from "./textureAtlas.js";
4
- function c(r, t) {
1
+ import { sound as f } from "../audio/SoundEngine.js";
2
+ import { ldtkExternalLevels as w, parseLdtk as m } from "../tilemap/ldtkParse.js";
3
+ import { parseTiled as p } from "../tilemap/tiled.js";
4
+ import { collectAtlasPages as g, duplicateFrames as x, parseAtlas as v } from "./textureAtlas.js";
5
+ function l(r) {
6
+ return r instanceof Error ? r.message : String(r);
7
+ }
8
+ function h(r, t) {
5
9
  if (/^[a-z][a-z0-9+.-]*:/i.test(r) || r.startsWith("/")) return r;
6
- const a = (t.slice(0, t.lastIndexOf("/") + 1) + r).split("/"), s = [];
7
- for (const i of a)
8
- i === ".." && s.length && s[s.length - 1] !== ".." && s[s.length - 1] !== "" ? s.pop() : i !== "." && s.push(i);
9
- return s.join("/");
10
+ const s = (t.slice(0, t.lastIndexOf("/") + 1) + r).split("/"), a = [];
11
+ for (const o of s)
12
+ o === ".." && a.length && a[a.length - 1] !== ".." && a[a.length - 1] !== "" ? a.pop() : o !== "." && a.push(o);
13
+ return a.join("/");
14
+ }
15
+ function A(r, t, e) {
16
+ const s = r;
17
+ return [...s.levels ?? [], ...(s.worlds ?? []).flatMap((o) => o.levels ?? [])].some((o) => o.externalRelPath === t && o.identifier !== void 0 && e.has(o.identifier));
10
18
  }
11
- async function m(r) {
19
+ async function $(r) {
12
20
  const t = await createImageBitmap(r), e = document.createElement("canvas");
13
21
  e.width = t.width, e.height = t.height;
14
- const a = e.getContext("2d");
15
- if (!a) throw new Error("AssetLoader: no 2D context (out of canvas memory?)");
16
- return a.drawImage(t, 0, 0), t.close(), e;
22
+ const s = e.getContext("2d");
23
+ if (!s) throw new Error("AssetLoader: no 2D context (out of canvas memory?)");
24
+ return s.drawImage(t, 0, 0), t.close(), e;
17
25
  }
18
- async function g(r, t, e) {
26
+ async function b(r, t, e) {
19
27
  if (typeof FontFace > "u" || typeof document > "u" || !document.fonts)
20
28
  throw new Error(`AssetLoader: this browser cannot load the font "${r}" (no FontFace)`);
21
- const a = new FontFace(r, t, e);
22
- await a.load(), document.fonts.add(a);
29
+ const s = new FontFace(r, t, e);
30
+ await s.load(), document.fonts.add(s);
23
31
  }
24
- class $ {
32
+ class B {
25
33
  constructor(t, e = {}) {
26
- this.textures = t, this.options = e, this.fetcher = e.fetch ?? ((a) => fetch(a)), this.toCanvas = e.toCanvas ?? m;
34
+ this.textures = t, this.options = e, this.fetcher = e.fetch ?? ((s) => fetch(s)), this.toCanvas = e.toCanvas ?? $;
27
35
  }
28
36
  textures;
29
37
  options;
30
38
  queue = [];
31
39
  data = /* @__PURE__ */ new Map();
32
40
  maps = /* @__PURE__ */ new Map();
41
+ ldtks = /* @__PURE__ */ new Map();
33
42
  atlases = /* @__PURE__ */ new Map();
34
43
  /** Decoded audio by key; a key with bytes only waits for a context (`decodeAudio`). */
35
44
  audioBuffers = /* @__PURE__ */ new Map();
@@ -41,13 +50,13 @@ class $ {
41
50
  done = 0;
42
51
  image(t, e) {
43
52
  return this.add(t, async () => {
44
- this.textures.addCanvas(t, await this.toCanvas(await this.blob(e)));
53
+ this.textures.addCanvas(t, await this.canvas(e));
45
54
  });
46
55
  }
47
56
  /** An image cut into equal frames named 0, 1, 2…, left to right, then row by row. */
48
- spritesheet(t, e, a) {
57
+ spritesheet(t, e, s) {
49
58
  return this.add(t, async () => {
50
- this.textures.addSpriteSheet(t, await this.toCanvas(await this.blob(e)), a);
59
+ this.textures.addSpriteSheet(t, await this.canvas(e), s);
51
60
  });
52
61
  }
53
62
  json(t, e) {
@@ -61,14 +70,33 @@ class $ {
61
70
  });
62
71
  }
63
72
  /** A Tiled JSON map, then every tileset image it names (resolved beside the map file). */
64
- tilemap(t, e, a = {}) {
73
+ tilemap(t, e, s = {}) {
65
74
  return this.add(t, async () => {
66
- const s = f(await (await this.request(e)).json());
67
- this.maps.set(t, s);
68
- const i = a.textureKey ?? ((n) => n.name);
69
- for (const n of s.tilesets) this.image(i(n), c(n.image, e));
75
+ const a = p(await (await this.request(e)).json());
76
+ this.maps.set(t, a);
77
+ const o = s.textureKey ?? ((i) => i.name);
78
+ for (const i of a.tilesets) this.image(o(i), h(i.image, e));
70
79
  });
71
80
  }
81
+ /**
82
+ * An LDtk project: the `.ldtk` file, the `.ldtkl` file of each level it saves apart (resolved beside the project),
83
+ * then every tileset image (resolved beside the project, as LDtk writes `relPath`). Counts as one asset, plus one
84
+ * per tileset image. Read it back with `ldtkProject(key)`; each level's `map` goes to `TilemapView` as it is.
85
+ */
86
+ ldtk(t, e, s = {}) {
87
+ return this.add(t, async () => {
88
+ const a = await (await this.request(e)).json(), o = s.levels ? new Set(s.levels) : null, i = w(a).filter((d) => !o || A(a, d, o)), c = await Promise.all(i.map(async (d) => [d, await (await this.request(h(d, e))).json()])), n = m(a, { levels: Object.fromEntries(c) });
89
+ this.ldtks.set(t, n);
90
+ const u = s.textureKey ?? ((d) => d.name);
91
+ for (const d of n.tilesets) this.image(u(d), h(d.image, e));
92
+ });
93
+ }
94
+ /** A loaded LDtk project. */
95
+ ldtkProject(t) {
96
+ const e = this.ldtks.get(t);
97
+ if (!e) throw new Error(`AssetLoader: no LDtk project loaded as "${t}"`);
98
+ return e;
99
+ }
72
100
  /**
73
101
  * A TexturePacker (or Pixi) JSON atlas, hash or array format: its page image, and for a multipack atlas every page
74
102
  * named in `meta.related_multi_packs` (each resolved beside the file naming it). The first page is the texture
@@ -77,27 +105,32 @@ class $ {
77
105
  */
78
106
  atlas(t, e) {
79
107
  return this.add(t, async () => {
80
- const a = this.textures.addAtlas?.bind(this.textures);
81
- if (!a) throw new Error(`AssetLoader: the texture sink cannot take atlases (no addAtlas) for "${t}"`);
82
- const s = await l(
108
+ const s = this.textures.addAtlas?.bind(this.textures);
109
+ if (!s) throw new Error(`AssetLoader: the texture sink cannot take atlases (no addAtlas) for "${t}"`);
110
+ const a = await g(
83
111
  e,
84
- async (o) => p(await (await this.request(o)).json(), o),
85
- c
86
- ), i = await Promise.all(s.map((o) => this.blob(c(o.page.image, o.url)).then((h) => this.toCanvas(h)))), n = w(s.map((o) => o.page));
87
- n.length && console.warn(`[assets] atlas "${t}" names ${n.length} frame(s) on more than one page: ${n.slice(0, 5).join(", ")}`), a(t, s.map((o, h) => ({ canvas: i[h], frames: o.page.frames })));
88
- const d = {};
89
- for (const o of s) Object.assign(d, o.page.animations);
90
- this.atlases.set(t, { frames: [...new Set(s.flatMap((o) => o.page.frames.map((h) => h.name)))], animations: d, pages: s.length });
112
+ async (n) => v(await (await this.request(n)).json(), n),
113
+ h
114
+ ), o = await Promise.all(a.map((n) => this.canvas(h(n.page.image, n.url)))), i = x(a.map((n) => n.page));
115
+ i.length && console.warn(`[assets] atlas "${t}" names ${i.length} frame(s) on more than one page: ${i.slice(0, 5).join(", ")}`), s(t, a.map((n, u) => ({ canvas: o[u], frames: n.page.frames })));
116
+ const c = {};
117
+ for (const n of a) Object.assign(c, n.page.animations);
118
+ this.atlases.set(t, { frames: [...new Set(a.flatMap((n) => n.page.frames.map((u) => u.name)))], animations: c, pages: a.length });
91
119
  });
92
120
  }
93
121
  /**
94
122
  * A web font (woff2, woff, ttf) under `family`, added to `document.fonts` once loaded so canvas text and the DOM can
95
123
  * use it at once — load fonts before the first `Text` is drawn, or it measures with the fallback.
96
124
  */
97
- font(t, e, a) {
98
- const s = this.options.addFont ?? g;
125
+ font(t, e, s) {
126
+ const a = this.options.addFont ?? b;
99
127
  return this.add(t, async () => {
100
- await s(t, await this.bytes(e), a);
128
+ const o = await this.bytes(e);
129
+ try {
130
+ await a(t, o, s);
131
+ } catch (i) {
132
+ throw new Error(`AssetLoader: could not load font "${t}" from ${this.full(e)}: ${l(i)}`);
133
+ }
101
134
  });
102
135
  }
103
136
  /**
@@ -107,10 +140,10 @@ class $ {
107
140
  */
108
141
  audio(t, e) {
109
142
  return this.add(t, async () => {
110
- const a = await this.bytes(e);
143
+ const s = await this.bytes(e);
111
144
  this.audioBuffers.delete(t), this.decoding.delete(t);
112
- const s = (this.options.audioContext ?? (() => u.audioContext))();
113
- s ? this.audioBuffers.set(t, await this.decode(s, a, e)) : this.audioBytes.set(t, a);
145
+ const a = (this.options.audioContext ?? (() => f.audioContext))();
146
+ a ? this.audioBuffers.set(t, await this.decode(a, s, e)) : this.audioBytes.set(t, s);
114
147
  });
115
148
  }
116
149
  /** A loaded sound, decoded; null while it waits for an audio context (`decodeAudio`). */
@@ -122,21 +155,21 @@ class $ {
122
155
  }
123
156
  /** A loaded sound, decoding its kept bytes through `ctx` (default: the loader's context) the first time. */
124
157
  decodeAudio(t, e) {
125
- const a = this.audioBuffers.get(t);
126
- if (a) return Promise.resolve(a);
127
- const s = this.decoding.get(t);
128
- if (s) return s;
129
- const i = this.audioBytes.get(t);
130
- if (!i) return Promise.reject(new Error(`AssetLoader: no audio loaded as "${t}"`));
131
- const n = e ?? (this.options.audioContext ?? (() => u.audioContext))();
132
- if (!n) return Promise.reject(new Error(`AssetLoader: no audio context to decode "${t}" yet`));
133
- const d = this.decode(n, i, t).then(
134
- (o) => (this.audioBuffers.set(t, o), this.audioBytes.delete(t), this.decoding.delete(t), o),
135
- (o) => {
136
- throw this.decoding.delete(t), o;
158
+ const s = this.audioBuffers.get(t);
159
+ if (s) return Promise.resolve(s);
160
+ const a = this.decoding.get(t);
161
+ if (a) return a;
162
+ const o = this.audioBytes.get(t);
163
+ if (!o) return Promise.reject(new Error(`AssetLoader: no audio loaded as "${t}"`));
164
+ const i = e ?? (this.options.audioContext ?? (() => f.audioContext))();
165
+ if (!i) return Promise.reject(new Error(`AssetLoader: no audio context to decode "${t}" yet`));
166
+ const c = this.decode(i, o, t).then(
167
+ (n) => (this.audioBuffers.set(t, n), this.audioBytes.delete(t), this.decoding.delete(t), n),
168
+ (n) => {
169
+ throw this.decoding.delete(t), n;
137
170
  }
138
171
  );
139
- return this.decoding.set(t, d), d;
172
+ return this.decoding.set(t, c), c;
140
173
  }
141
174
  /** What a loaded atlas holds: its frame names and animations. */
142
175
  getAtlas(t) {
@@ -146,11 +179,11 @@ class $ {
146
179
  }
147
180
  /** Runs everything queued; `onProgress` after each asset. Safe to call again after queueing more. */
148
181
  async load(t) {
149
- const e = Math.max(1, this.options.concurrency ?? 6), a = async () => {
150
- for (let s = this.queue.shift(); s; s = this.queue.shift())
151
- await s.run(), this.done++, t?.({ done: this.done, total: this.total, key: s.key });
182
+ const e = Math.max(1, this.options.concurrency ?? 6), s = async () => {
183
+ for (let a = this.queue.shift(); a; a = this.queue.shift())
184
+ await a.run(), this.done++, t?.({ done: this.done, total: this.total, key: a.key });
152
185
  };
153
- for (; this.queue.length; ) await Promise.all(Array.from({ length: e }, a));
186
+ for (; this.queue.length; ) await Promise.all(Array.from({ length: e }, s));
154
187
  }
155
188
  /** A loaded JSON value. */
156
189
  getJson(t) {
@@ -170,33 +203,45 @@ class $ {
170
203
  add(t, e) {
171
204
  return this.queue.push({ key: t, run: e }), this.total++, this;
172
205
  }
206
+ full(t) {
207
+ return h(t, this.options.baseUrl ?? "");
208
+ }
173
209
  async request(t) {
174
- const e = c(t, this.options.baseUrl ?? "");
175
- let a;
210
+ const e = this.full(t);
211
+ let s;
176
212
  try {
177
- a = await this.fetcher(e);
178
- } catch (s) {
179
- throw new Error(`AssetLoader: could not fetch ${e}: ${s instanceof Error ? s.message : String(s)}`);
213
+ s = await this.fetcher(e);
214
+ } catch (a) {
215
+ throw new Error(`AssetLoader: could not fetch ${e}: ${l(a)}`);
180
216
  }
181
- if (!a.ok) throw new Error(`AssetLoader: ${e} answered ${a.status}`);
182
- return a;
217
+ if (!s.ok) throw new Error(`AssetLoader: ${e} answered ${s.status}`);
218
+ return s;
183
219
  }
184
220
  async blob(t) {
185
221
  return (await this.request(t)).blob();
186
222
  }
223
+ /** An image file decoded into a canvas; a file that is not an image fails naming its URL. */
224
+ async canvas(t) {
225
+ const e = await this.blob(t);
226
+ try {
227
+ return await this.toCanvas(e);
228
+ } catch (s) {
229
+ throw new Error(`AssetLoader: could not decode image ${this.full(t)}: ${l(s)}`);
230
+ }
231
+ }
187
232
  async bytes(t) {
188
233
  const e = await this.request(t);
189
234
  return typeof e.arrayBuffer == "function" ? e.arrayBuffer() : (await e.blob()).arrayBuffer();
190
235
  }
191
- async decode(t, e, a) {
236
+ async decode(t, e, s) {
192
237
  try {
193
238
  return await t.decodeAudioData(e.slice(0));
194
- } catch (s) {
195
- throw new Error(`AssetLoader: could not decode audio ${a}: ${s instanceof Error ? s.message : String(s)}`);
239
+ } catch (a) {
240
+ throw new Error(`AssetLoader: could not decode audio ${s}: ${l(a)}`);
196
241
  }
197
242
  }
198
243
  }
199
244
  export {
200
- $ as AssetLoader,
201
- c as resolveUrl
245
+ B as AssetLoader,
246
+ h as resolveUrl
202
247
  };
@@ -0,0 +1,61 @@
1
+ function d({ w: o, h: t, pixels: e }) {
2
+ if (typeof document > "u") return null;
3
+ const s = document.createElement("canvas");
4
+ s.width = o, s.height = t;
5
+ const n = s.getContext("2d");
6
+ return n ? (n.putImageData(new ImageData(new Uint8ClampedArray(e), o, t), 0, 0), s) : null;
7
+ }
8
+ class h {
9
+ constructor(t) {
10
+ this.options = t, this.key = t.key ?? ((e) => e);
11
+ }
12
+ options;
13
+ pending = /* @__PURE__ */ new Set();
14
+ /** Ids whose painting is held as a texture: the current one and its neighbours. */
15
+ held = /* @__PURE__ */ new Set();
16
+ key;
17
+ destroyed = !1;
18
+ /**
19
+ * Stands on `current`: its texture (painted by the caller on arrival, or ahead) and its neighbours' are kept, every
20
+ * other one this prepainter held is freed, and the neighbours not yet painted are started.
21
+ */
22
+ enter(t, e) {
23
+ const s = this.options.id, n = s(t), r = /* @__PURE__ */ new Set([n, ...e.map(s)]);
24
+ for (const i of this.held)
25
+ r.has(i) || (this.options.textures.remove(this.key(i)), this.held.delete(i));
26
+ this.held.add(n);
27
+ for (const i of e) this.prepare(i);
28
+ }
29
+ /** Whether `id` is being painted right now (so a caller may wait for it a moment rather than paint it again). */
30
+ painting(t) {
31
+ return this.pending.has(t);
32
+ }
33
+ /** Ids held as textures now. */
34
+ get holding() {
35
+ return this.held;
36
+ }
37
+ /** Starts painting `item` unless it is painted or under way. */
38
+ prepare(t) {
39
+ const e = this.options.id(t), s = this.options.textures.exists(this.key(e));
40
+ s && this.held.add(e);
41
+ const n = this.options.painter;
42
+ s || this.pending.has(e) || !n || this.destroyed || (this.pending.add(e), n.paint(t, e).then(
43
+ (r) => this.receive(e, r),
44
+ () => this.pending.delete(e)
45
+ ));
46
+ }
47
+ /** Stops painting; textures already made are left to the registry. */
48
+ destroy() {
49
+ this.destroyed = !0, this.options.painter?.dispose?.(), this.pending.clear();
50
+ }
51
+ receive(t, e) {
52
+ if (this.pending.delete(t), this.destroyed) return;
53
+ const s = this.key(t);
54
+ if (this.options.textures.exists(s)) return;
55
+ const n = (this.options.canvas ?? d)(e);
56
+ n && (this.options.textures.addCanvas(s, n), this.held.add(t), this.options.onPainted?.(t));
57
+ }
58
+ }
59
+ export {
60
+ h as Prepainter
61
+ };
@@ -0,0 +1,39 @@
1
+ function w(i) {
2
+ let n;
3
+ const o = /* @__PURE__ */ new Map(), c = (r) => {
4
+ n?.terminate(), n = null;
5
+ for (const t of o.values()) for (const e of t) e.reject(new Error(r));
6
+ o.clear();
7
+ }, f = () => {
8
+ if (n !== void 0) return n;
9
+ try {
10
+ const r = i.create();
11
+ r.onmessage = (t) => {
12
+ const e = t.data, s = o.get(e.id);
13
+ if (s) {
14
+ o.delete(e.id);
15
+ for (const a of s)
16
+ "error" in e ? a.reject(new Error(e.error)) : a.resolve(e);
17
+ }
18
+ }, r.onerror = () => c("the painter worker failed"), n = r;
19
+ } catch {
20
+ n = null;
21
+ }
22
+ return n;
23
+ };
24
+ return {
25
+ paint(r, t) {
26
+ const e = f();
27
+ return e ? new Promise((s, a) => {
28
+ const l = o.get(t);
29
+ l ? l.push({ resolve: s, reject: a }) : (o.set(t, [{ resolve: s, reject: a }]), e.postMessage(i.message ? i.message(r, t) : { id: t, item: r }));
30
+ }) : Promise.reject(new Error("no painter worker"));
31
+ },
32
+ dispose() {
33
+ c("disposed");
34
+ }
35
+ };
36
+ }
37
+ export {
38
+ w as workerPainter
39
+ };
@@ -0,0 +1,118 @@
1
+ const l = [18e3, 1800];
2
+ function f(c, t, e = {}) {
3
+ const i = e.range ?? 480, s = t.x - c.x, n = Math.hypot(s, t.y - c.y), r = e.maxPan ?? 0.8, h = Math.max(-r, Math.min(r, s / (e.panWidth ?? i / 2))), o = e.muffle === void 0 ? l : e.muffle, a = Math.min(1, n / i), u = o ? o[0] * Math.pow(o[1] / o[0], a) : 2e4;
4
+ return n >= i ? { audible: !1, gain: 0, pan: h, cutoffHz: u } : { audible: !0, gain: Math.pow(1 - a, e.rolloff ?? 1.5), pan: h, cutoffHz: u };
5
+ }
6
+ const m = 0.05;
7
+ class d {
8
+ constructor(t, e = t.destination) {
9
+ this.context = t, this.output = t.createGain(), this.output.connect(e);
10
+ }
11
+ context;
12
+ /** Every emitter's gain goes through this: the game's volume setting. */
13
+ output;
14
+ emitters = /* @__PURE__ */ new Set();
15
+ here = { x: 0, y: 0 };
16
+ /** The listener's place as of the last `update`. */
17
+ get listener() {
18
+ return this.here;
19
+ }
20
+ /** How many emitters are alive (playing or waiting out of range). */
21
+ get size() {
22
+ return this.emitters.size;
23
+ }
24
+ /** A sound at (x, y), looping by default. Starts at once when in range of the listener. */
25
+ emitter(t, e, i, s = {}) {
26
+ const n = new p(this, t, e, i, s);
27
+ return this.emitters.add(n), n.refresh(this.here, !1), n;
28
+ }
29
+ /** A one-shot at (x, y), dropped when it ends; skipped (and done at once) when out of range. */
30
+ play(t, e, i, s = {}) {
31
+ return this.emitter(t, e, i, { ...s, loop: !1 });
32
+ }
33
+ /** Moves the listener and re-mixes every emitter. Call once a frame. */
34
+ update(t = this.here) {
35
+ this.here = { x: t.x, y: t.y };
36
+ for (const e of this.emitters) e.refresh(this.here, !0);
37
+ }
38
+ /** Stops every emitter. */
39
+ stopAll(t = 0) {
40
+ for (const e of [...this.emitters]) e.stop(t);
41
+ }
42
+ destroy() {
43
+ this.stopAll(), this.output.disconnect();
44
+ }
45
+ /** @internal */
46
+ forget(t) {
47
+ this.emitters.delete(t);
48
+ }
49
+ }
50
+ class p {
51
+ constructor(t, e, i, s, n) {
52
+ this.owner = t, this.buffer = e, this.x = i, this.y = s, this.options = n;
53
+ const r = t.context;
54
+ this.volume = n.volume ?? 1, this.rate = n.playbackRate ?? 1, this.origin = r.currentTime, this.filter = r.createBiquadFilter(), this.filter.type = "lowpass", this.gain = r.createGain(), this.panner = r.createStereoPanner(), this.filter.connect(this.gain).connect(this.panner).connect(t.output);
55
+ }
56
+ owner;
57
+ buffer;
58
+ x;
59
+ y;
60
+ options;
61
+ volume;
62
+ done = !1;
63
+ source = null;
64
+ gain;
65
+ panner;
66
+ filter;
67
+ /** When the loop would have begun, had it never stopped: where a restart picks it up. */
68
+ origin;
69
+ rate;
70
+ get playing() {
71
+ return this.source !== null;
72
+ }
73
+ refresh(t, e) {
74
+ if (this.done) return;
75
+ const i = f(t, this, this.options), s = this.options.loop ?? !0;
76
+ if (!i.audible) {
77
+ if (!s) return this.stop();
78
+ this.halt();
79
+ return;
80
+ }
81
+ const n = this.owner.context, r = n.currentTime, h = (o, a) => e && this.source ? o.setTargetAtTime(a, r, m) : o.setValueAtTime(a, r);
82
+ h(this.gain.gain, i.gain * Math.max(0, Math.min(1, this.volume))), h(this.panner.pan, i.pan), h(this.filter.frequency, Math.min(i.cutoffHz, n.sampleRate / 2)), this.source || this.begin(s);
83
+ }
84
+ stop(t = 0) {
85
+ if (this.done) return;
86
+ this.done = !0, this.owner.forget(this);
87
+ const e = this.source, i = this.owner.context;
88
+ if (e && t > 0) {
89
+ const s = i.currentTime + t / 1e3;
90
+ this.gain.gain.setValueAtTime(this.gain.gain.value, i.currentTime), this.gain.gain.linearRampToValueAtTime(0, s), e.stop(s), e.onended = () => this.release();
91
+ } else
92
+ this.halt(), this.release();
93
+ }
94
+ begin(t) {
95
+ const e = this.owner.context, i = e.createBufferSource();
96
+ i.buffer = this.buffer, i.loop = t, i.playbackRate.value = this.rate, i.connect(this.filter);
97
+ const s = t ? (e.currentTime - this.origin) * this.rate % this.buffer.duration : 0;
98
+ i.start(e.currentTime, s), t || (i.onended = () => this.stop()), this.source = i;
99
+ }
100
+ halt() {
101
+ const t = this.source;
102
+ if (t) {
103
+ this.source = null, t.onended = null;
104
+ try {
105
+ t.stop();
106
+ } catch {
107
+ }
108
+ t.disconnect();
109
+ }
110
+ }
111
+ release() {
112
+ this.filter.disconnect(), this.gain.disconnect(), this.panner.disconnect();
113
+ }
114
+ }
115
+ export {
116
+ d as SpatialAudio,
117
+ f as spatialMix
118
+ };
@@ -0,0 +1,55 @@
1
+ /** `electron.safeStorage`. */
2
+ export interface SafeStorageLike {
3
+ isEncryptionAvailable(): boolean;
4
+ /** Linux only: `basic_text` means a hard-coded key, which is no encryption at all. */
5
+ getSelectedStorageBackend?(): string;
6
+ encryptString(plainText: string): Uint8Array;
7
+ decryptString(encrypted: Buffer): string;
8
+ }
9
+ /** `electron.ipcMain`. */
10
+ export interface IpcMainLike {
11
+ handle(channel: string, listener: (event: any, ...args: any[]) => unknown): void;
12
+ removeHandler?(channel: string): void;
13
+ }
14
+ /** `electron.shell`. */
15
+ export interface ShellLike {
16
+ openExternal(url: string): Promise<void>;
17
+ }
18
+ /** What `before-input-event` hands a listener (`Electron.Input`). */
19
+ export interface InputLike {
20
+ type: string;
21
+ key: string;
22
+ control: boolean;
23
+ meta: boolean;
24
+ shift: boolean;
25
+ alt: boolean;
26
+ }
27
+ /** An event a listener may cancel. */
28
+ export interface PreventableLike {
29
+ preventDefault(): void;
30
+ }
31
+ /** `electron.Session`, for the permission handlers. */
32
+ export interface SessionLike {
33
+ setPermissionRequestHandler(handler: ((webContents: any, permission: any, callback: (granted: boolean) => void, details: any) => void) | null): void;
34
+ setPermissionCheckHandler(handler: ((webContents: any, permission: any, requestingOrigin: string, details: any) => boolean) | null): void;
35
+ }
36
+ /** `electron.WebContents`. */
37
+ export interface WebContentsLike {
38
+ setWindowOpenHandler(handler: (details: {
39
+ url: string;
40
+ }) => any): void;
41
+ on(event: any, listener: (...args: any[]) => void): unknown;
42
+ toggleDevTools(): void;
43
+ session?: SessionLike;
44
+ }
45
+ /** `electron.BrowserWindow`, as far as a lockdown needs it. */
46
+ export interface WindowLike {
47
+ webContents: WebContentsLike;
48
+ }
49
+ /** `electron.app`, as far as the debugger refusal needs it. */
50
+ export interface AppLike {
51
+ isPackaged: boolean;
52
+ commandLine: {
53
+ hasSwitch(name: string): boolean;
54
+ };
55
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /*
3
+ * The few parts of Electron's main-process API this module touches, written as structural types so the engine never
4
+ * imports or depends on `electron`: a game passes its own `app`, `safeStorage`, `ipcMain`, `shell` and windows, and
5
+ * a test passes fakes. Each is a subset of Electron's own type, so Electron's objects fit them as they are.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `1681-engine/electron`: the main-process side of a desktop build on Electron — file-by-file web patching from a
3
+ * release store, the shell's identity, an OS-encrypted session store and a window lockdown. Electron is an optional
4
+ * peer: nothing here imports it, the game hands in its own `app`, `safeStorage`, `ipcMain`, `shell` and windows. Runs
5
+ * in the main process only (it uses Node's `fs` and `crypto`); the pure rules it shares with browsers and phones are
6
+ * `1681-engine/updates`. Importable from ESM (`import`) and from a CommonJS main process (`require`).
7
+ */
8
+ export type { SafeStorageLike, IpcMainLike, ShellLike, InputLike, PreventableLike, SessionLike, WebContentsLike, WindowLike, AppLike } from './electronLike';
9
+ export { shellId, type ShellIdOptions } from './shellId';
10
+ export { WebPatcher, type WebPatcherOptions } from './webPatcher';
11
+ export type { WebPointer, PointerName } from './webPointers';
12
+ export { SessionStore, registerSessionStore, type SessionStoreOptions } from './sessionStore';
13
+ export { isAllowedExternal, isAllowedNavigation, isDevToolsKey, debuggerRequested, DEBUG_SWITCHES, type ExternalPolicy } from './lockdownRules';
14
+ export { lockDownWindow, denyPermissions, refuseDebugLaunch, type LockdownOptions } from './lockdown';
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.refuseDebugLaunch = exports.denyPermissions = exports.lockDownWindow = exports.DEBUG_SWITCHES = exports.debuggerRequested = exports.isDevToolsKey = exports.isAllowedNavigation = exports.isAllowedExternal = exports.registerSessionStore = exports.SessionStore = exports.WebPatcher = exports.shellId = void 0;
4
+ var shellId_1 = require("./shellId");
5
+ Object.defineProperty(exports, "shellId", { enumerable: true, get: function () { return shellId_1.shellId; } });
6
+ var webPatcher_1 = require("./webPatcher");
7
+ Object.defineProperty(exports, "WebPatcher", { enumerable: true, get: function () { return webPatcher_1.WebPatcher; } });
8
+ var sessionStore_1 = require("./sessionStore");
9
+ Object.defineProperty(exports, "SessionStore", { enumerable: true, get: function () { return sessionStore_1.SessionStore; } });
10
+ Object.defineProperty(exports, "registerSessionStore", { enumerable: true, get: function () { return sessionStore_1.registerSessionStore; } });
11
+ var lockdownRules_1 = require("./lockdownRules");
12
+ Object.defineProperty(exports, "isAllowedExternal", { enumerable: true, get: function () { return lockdownRules_1.isAllowedExternal; } });
13
+ Object.defineProperty(exports, "isAllowedNavigation", { enumerable: true, get: function () { return lockdownRules_1.isAllowedNavigation; } });
14
+ Object.defineProperty(exports, "isDevToolsKey", { enumerable: true, get: function () { return lockdownRules_1.isDevToolsKey; } });
15
+ Object.defineProperty(exports, "debuggerRequested", { enumerable: true, get: function () { return lockdownRules_1.debuggerRequested; } });
16
+ Object.defineProperty(exports, "DEBUG_SWITCHES", { enumerable: true, get: function () { return lockdownRules_1.DEBUG_SWITCHES; } });
17
+ var lockdown_1 = require("./lockdown");
18
+ Object.defineProperty(exports, "lockDownWindow", { enumerable: true, get: function () { return lockdown_1.lockDownWindow; } });
19
+ Object.defineProperty(exports, "denyPermissions", { enumerable: true, get: function () { return lockdown_1.denyPermissions; } });
20
+ Object.defineProperty(exports, "refuseDebugLaunch", { enumerable: true, get: function () { return lockdown_1.refuseDebugLaunch; } });
@@ -0,0 +1,33 @@
1
+ import type { AppLike, SessionLike, ShellLike, WindowLike } from './electronLike';
2
+ import { type ExternalPolicy } from './lockdownRules';
3
+ /** How {@link lockDownWindow} treats a window. */
4
+ export interface LockdownOptions {
5
+ /** `electron.shell`, which opens an allowed link in the system browser. */
6
+ shell: ShellLike;
7
+ /** The links that may leave for the system browser. */
8
+ external: ExternalPolicy;
9
+ /** `app.isPackaged`: a packaged build refuses the DevTools keys. */
10
+ packaged: boolean;
11
+ /** A dev run's page origin, allowed for navigation; leave unset in a packaged build whatever the environment says. */
12
+ devServerUrl?: string;
13
+ /** In a dev run, this key toggles DevTools. Default `F12`; null for none. */
14
+ devToolsKey?: string | null;
15
+ /** Where refusals are reported. Default `console.log`. */
16
+ log?: (...args: unknown[]) => void;
17
+ }
18
+ /**
19
+ * Keeps a window on the game: a new window is never opened (an allowed link goes to the system browser instead, any
20
+ * other is refused), the window never navigates or redirects away from the game's own page, and a packaged build
21
+ * refuses the DevTools keys (set `webPreferences.devTools: !app.isPackaged` too: the keys are only the visible way in).
22
+ */
23
+ export declare function lockDownWindow(win: WindowLike, o: LockdownOptions): void;
24
+ /**
25
+ * Answers every permission request and check on `session` (camera, microphone, notifications, geolocation…) with no,
26
+ * but those in `allow`. Chromium's default in Electron is yes to almost everything, which a game never needs.
27
+ */
28
+ export declare function denyPermissions(session: SessionLike, allow?: readonly string[]): void;
29
+ /**
30
+ * A packaged app started with a debugger switch quits before it opens a window. Call it first thing in the main
31
+ * process; `exit` defaults to `process.exit` — not `app.exit`, so nothing after the call may run.
32
+ */
33
+ export declare function refuseDebugLaunch(app: AppLike, argv?: readonly string[], exit?: (code: number) => void): boolean;