1681-engine 0.5.10 → 0.6.1

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 (98) hide show
  1. package/CHANGELOG.md +61 -1
  2. package/README.md +12 -6
  3. package/dist/1681-engine.js +588 -522
  4. package/dist/ai/behaviorTree.js +198 -0
  5. package/dist/ai/buildTree.js +54 -0
  6. package/dist/audio/spatial.js +118 -0
  7. package/dist/cjs/electron/webPatcher.d.ts +4 -1
  8. package/dist/cjs/electron/webPatcher.js +9 -2
  9. package/dist/core/Camera.js +10 -0
  10. package/dist/core/ObjectFactory.js +23 -21
  11. package/dist/core/PixelGrid.js +47 -0
  12. package/dist/core/SharpText.js +72 -0
  13. package/dist/core/Stage.js +48 -31
  14. package/dist/core/gridPlacement.js +14 -0
  15. package/dist/dialogue/DialogueRunner.js +173 -0
  16. package/dist/dialogue/expr.js +137 -0
  17. package/dist/dialogue/validateDialogue.js +52 -0
  18. package/dist/ecs/prefab.js +94 -0
  19. package/dist/electron/webPatcher.js +16 -10
  20. package/dist/input/Actions.js +58 -27
  21. package/dist/input/InputCapture.js +98 -0
  22. package/dist/input/Keymap.js +32 -26
  23. package/dist/input/inputTape.js +68 -0
  24. package/dist/mobile/backButton.js +40 -0
  25. package/dist/mobile/capacitor.js +48 -0
  26. package/dist/mobile/screen.js +31 -0
  27. package/dist/mobile.js +12 -0
  28. package/dist/nav/flowField.js +58 -0
  29. package/dist/nav/steering.js +77 -0
  30. package/dist/net/InterpolationBuffer.js +30 -23
  31. package/dist/net/LagCompensator.js +100 -0
  32. package/dist/net/Room.js +122 -43
  33. package/dist/net/RoomHost.js +13 -4
  34. package/dist/net/SpatialHash.js +66 -0
  35. package/dist/types/ai/behaviorTree.d.ts +133 -0
  36. package/dist/types/ai/behaviorTree.d.ts.map +1 -0
  37. package/dist/types/ai/buildTree.d.ts +55 -0
  38. package/dist/types/ai/buildTree.d.ts.map +1 -0
  39. package/dist/types/audio/spatial.d.ts +107 -0
  40. package/dist/types/audio/spatial.d.ts.map +1 -0
  41. package/dist/types/core/Camera.d.ts +8 -0
  42. package/dist/types/core/Camera.d.ts.map +1 -1
  43. package/dist/types/core/ObjectFactory.d.ts.map +1 -1
  44. package/dist/types/core/PixelGrid.d.ts +30 -0
  45. package/dist/types/core/PixelGrid.d.ts.map +1 -0
  46. package/dist/types/core/SharpText.d.ts +32 -0
  47. package/dist/types/core/SharpText.d.ts.map +1 -0
  48. package/dist/types/core/Stage.d.ts +14 -0
  49. package/dist/types/core/Stage.d.ts.map +1 -1
  50. package/dist/types/core/gridPlacement.d.ts +20 -0
  51. package/dist/types/core/gridPlacement.d.ts.map +1 -0
  52. package/dist/types/dialogue/DialogueRunner.d.ts +78 -0
  53. package/dist/types/dialogue/DialogueRunner.d.ts.map +1 -0
  54. package/dist/types/dialogue/expr.d.ts +12 -0
  55. package/dist/types/dialogue/expr.d.ts.map +1 -0
  56. package/dist/types/dialogue/script.d.ts +78 -0
  57. package/dist/types/dialogue/script.d.ts.map +1 -0
  58. package/dist/types/dialogue/validateDialogue.d.ts +21 -0
  59. package/dist/types/dialogue/validateDialogue.d.ts.map +1 -0
  60. package/dist/types/ecs/prefab.d.ts +84 -0
  61. package/dist/types/ecs/prefab.d.ts.map +1 -0
  62. package/dist/types/electron/webPatcher.d.ts +4 -1
  63. package/dist/types/electron/webPatcher.d.ts.map +1 -1
  64. package/dist/types/index.d.ts +18 -1
  65. package/dist/types/index.d.ts.map +1 -1
  66. package/dist/types/input/Actions.d.ts +14 -0
  67. package/dist/types/input/Actions.d.ts.map +1 -1
  68. package/dist/types/input/InputCapture.d.ts +53 -0
  69. package/dist/types/input/InputCapture.d.ts.map +1 -0
  70. package/dist/types/input/Keymap.d.ts +12 -6
  71. package/dist/types/input/Keymap.d.ts.map +1 -1
  72. package/dist/types/input/inputTape.d.ts +66 -0
  73. package/dist/types/input/inputTape.d.ts.map +1 -0
  74. package/dist/types/mobile/backButton.d.ts +30 -0
  75. package/dist/types/mobile/backButton.d.ts.map +1 -0
  76. package/dist/types/mobile/capacitor.d.ts +62 -0
  77. package/dist/types/mobile/capacitor.d.ts.map +1 -0
  78. package/dist/types/mobile/index.d.ts +4 -0
  79. package/dist/types/mobile/index.d.ts.map +1 -0
  80. package/dist/types/mobile/screen.d.ts +27 -0
  81. package/dist/types/mobile/screen.d.ts.map +1 -0
  82. package/dist/types/nav/flowField.d.ts +31 -0
  83. package/dist/types/nav/flowField.d.ts.map +1 -0
  84. package/dist/types/nav/steering.d.ts +47 -0
  85. package/dist/types/nav/steering.d.ts.map +1 -0
  86. package/dist/types/net/InterpolationBuffer.d.ts +5 -0
  87. package/dist/types/net/InterpolationBuffer.d.ts.map +1 -1
  88. package/dist/types/net/LagCompensator.d.ts +70 -0
  89. package/dist/types/net/LagCompensator.d.ts.map +1 -0
  90. package/dist/types/net/Room.d.ts +72 -3
  91. package/dist/types/net/Room.d.ts.map +1 -1
  92. package/dist/types/net/RoomHost.d.ts +8 -3
  93. package/dist/types/net/RoomHost.d.ts.map +1 -1
  94. package/dist/types/net/SpatialHash.d.ts +25 -0
  95. package/dist/types/net/SpatialHash.d.ts.map +1 -0
  96. package/dist/types/ui/ScrollView.d.ts.map +1 -1
  97. package/dist/ui/ScrollView.js +10 -8
  98. package/package.json +5 -1
@@ -0,0 +1,198 @@
1
+ class k {
2
+ constructor(t) {
3
+ this.root = t;
4
+ }
5
+ root;
6
+ /** A new agent on this tree, with memory of its own. */
7
+ agent() {
8
+ return new p(this.root);
9
+ }
10
+ }
11
+ class p {
12
+ constructor(t) {
13
+ this.root = t;
14
+ }
15
+ root;
16
+ now = 0;
17
+ memory = /* @__PURE__ */ new Map();
18
+ kept = /* @__PURE__ */ new Map();
19
+ /** Nodes that answered 'running' last time they ticked: the ones a halt must reach. */
20
+ open = /* @__PURE__ */ new Set();
21
+ last = /* @__PURE__ */ new Map();
22
+ seen = /* @__PURE__ */ new Map();
23
+ /** Ticks the tree once at time `now` (ms) and returns the root's answer. */
24
+ update(t, e) {
25
+ this.now = e, this.seen = /* @__PURE__ */ new Map();
26
+ const n = this.tick(this.root, t);
27
+ return this.last = this.seen, n;
28
+ }
29
+ /** Halts whatever is running and forgets all memory: the agent starts the tree afresh (it died, it was stunned). */
30
+ reset(t) {
31
+ this.halt(this.root, t), this.memory.clear(), this.kept.clear(), this.open.clear(), this.last.clear();
32
+ }
33
+ /** Whether a node is running for this agent now. */
34
+ isRunning(t) {
35
+ return this.open.has(t);
36
+ }
37
+ state(t, e) {
38
+ return this.memory.has(t) || this.memory.set(t, e()), this.memory.get(t);
39
+ }
40
+ lasting(t, e) {
41
+ return this.kept.has(t) || this.kept.set(t, e()), this.kept.get(t);
42
+ }
43
+ tick(t, e) {
44
+ const n = t.tick(e, this);
45
+ return this.seen.set(t, n), n === "running" ? this.open.add(t) : (this.open.delete(t), this.memory.delete(t)), n;
46
+ }
47
+ halt(t, e) {
48
+ if (this.open.has(t)) {
49
+ for (const n of t.children) this.halt(n, e);
50
+ t.halt?.(e, this), this.open.delete(t), this.memory.delete(t);
51
+ }
52
+ }
53
+ /** The tree as it ran on the last tick, depth first: for a debug overlay. */
54
+ trace() {
55
+ const t = [], e = (n, r) => {
56
+ t.push({ depth: r, kind: n.kind, name: n.name, status: this.last.get(n) ?? null });
57
+ for (const o of n.children) e(o, r + 1);
58
+ };
59
+ return e(this.root, 0), t;
60
+ }
61
+ }
62
+ function m(s) {
63
+ return s.map((t) => `${" ".repeat(t.depth)}${t.name ?? t.kind} ${t.status ?? "·"}`).join(`
64
+ `);
65
+ }
66
+ const u = (s, t, e, n = {}) => ({
67
+ kind: s,
68
+ children: t,
69
+ tick: e,
70
+ ...n
71
+ });
72
+ function w(...s) {
73
+ const t = u("sequence", s, (e, n) => {
74
+ const r = n.state(t, () => ({ i: 0 }));
75
+ for (; r.i < s.length; r.i++) {
76
+ const o = n.tick(s[r.i], e);
77
+ if (o !== "success") return o;
78
+ }
79
+ return "success";
80
+ });
81
+ return t;
82
+ }
83
+ function y(...s) {
84
+ const t = u("selector", s, (e, n) => {
85
+ const r = n.state(t, () => ({ i: 0 }));
86
+ for (; r.i < s.length; r.i++) {
87
+ const o = n.tick(s[r.i], e);
88
+ if (o !== "failure") return o;
89
+ }
90
+ return "failure";
91
+ });
92
+ return t;
93
+ }
94
+ function v(...s) {
95
+ return g("reactive-sequence", s, "success");
96
+ }
97
+ function M(...s) {
98
+ return g("reactive-selector", s, "failure");
99
+ }
100
+ function g(s, t, e) {
101
+ return u(s, t, (n, r) => {
102
+ for (let o = 0; o < t.length; o++) {
103
+ const i = r.tick(t[o], n);
104
+ if (i !== e) {
105
+ for (let c = o + 1; c < t.length; c++) r.halt(t[c], n);
106
+ return i;
107
+ }
108
+ }
109
+ return e;
110
+ });
111
+ }
112
+ function b(s, ...t) {
113
+ const e = Math.min(t.length, s.succeed ?? t.length), n = Math.min(t.length, s.fail ?? 1), r = u("parallel", t, (o, i) => {
114
+ const c = i.state(r, () => /* @__PURE__ */ new Map());
115
+ for (let a = 0; a < t.length; a++) {
116
+ if (c.has(a)) continue;
117
+ const h = i.tick(t[a], o);
118
+ h !== "running" && c.set(a, h);
119
+ }
120
+ let f = 0, l = 0;
121
+ for (const a of c.values()) a === "success" ? f++ : l++;
122
+ if (f < e && l < n) return "running";
123
+ for (const a of t) i.halt(a, o);
124
+ return f >= e ? "success" : "failure";
125
+ });
126
+ return r;
127
+ }
128
+ function q(s) {
129
+ return u("invert", [s], (t, e) => {
130
+ const n = e.tick(s, t);
131
+ return n === "success" ? "failure" : n === "failure" ? "success" : "running";
132
+ });
133
+ }
134
+ function A(s) {
135
+ return u("succeed", [s], (t, e) => e.tick(s, t) === "running" ? "running" : "success");
136
+ }
137
+ function S(s, t) {
138
+ const e = u("repeat", [t], (n, r) => {
139
+ const o = r.state(e, () => ({ n: 0 })), i = r.tick(t, n);
140
+ return i !== "success" ? i : (o.n++, o.n >= s ? "success" : "running");
141
+ });
142
+ return e;
143
+ }
144
+ function $(s, t) {
145
+ const e = u("retry", [t], (n, r) => {
146
+ const o = r.state(e, () => ({ n: 0 })), i = r.tick(t, n);
147
+ return i !== "failure" ? i : (o.n++, o.n >= s ? "failure" : "running");
148
+ });
149
+ return e;
150
+ }
151
+ function j(s, t) {
152
+ const e = u("cooldown", [t], (n, r) => {
153
+ const o = r.lasting(e, () => ({ readyAt: -1 / 0 }));
154
+ if (r.now < o.readyAt) return "failure";
155
+ const i = r.tick(t, n);
156
+ return i !== "running" && (o.readyAt = r.now + s), i;
157
+ });
158
+ return e;
159
+ }
160
+ function B(s, t) {
161
+ const e = u("timeout", [t], (n, r) => {
162
+ const o = r.state(e, () => ({ began: r.now }));
163
+ return r.now - o.began >= s ? (r.halt(t, n), "failure") : r.tick(t, n);
164
+ });
165
+ return e;
166
+ }
167
+ function H(s) {
168
+ const t = u("wait", [], (e, n) => {
169
+ const r = n.state(t, () => ({ began: n.now }));
170
+ return n.now - r.began >= s ? "success" : "running";
171
+ });
172
+ return t;
173
+ }
174
+ function T(s, t) {
175
+ return u("condition", [], (e) => t(e) ? "success" : "failure", { name: s });
176
+ }
177
+ function F(s, t, e = {}) {
178
+ return u("action", [], (n, r) => t(n, r.now), { name: s, halt: e.onHalt ? (n) => e.onHalt(n) : void 0 });
179
+ }
180
+ export {
181
+ k as BehaviorTree,
182
+ p as BtAgent,
183
+ F as action,
184
+ T as condition,
185
+ j as cooldown,
186
+ m as formatTrace,
187
+ q as invert,
188
+ b as parallel,
189
+ M as reactiveSelector,
190
+ v as reactiveSequence,
191
+ S as repeat,
192
+ $ as retry,
193
+ y as selector,
194
+ w as sequence,
195
+ A as succeed,
196
+ B as timeout,
197
+ H as wait
198
+ };
@@ -0,0 +1,54 @@
1
+ import { action as m, condition as s, wait as h, timeout as $, cooldown as y, retry as b, repeat as w, succeed as q, invert as v, parallel as S, reactiveSelector as k, reactiveSequence as A, selector as H, sequence as T } from "./behaviorTree.js";
2
+ function x(e, t) {
3
+ return l(e, t, "$", []);
4
+ }
5
+ function l(e, t, u, c) {
6
+ const r = (n) => {
7
+ throw new Error(`buildTree: ${u}: ${n}`);
8
+ };
9
+ if (!e || typeof e != "object") return r("not a node");
10
+ const a = (n) => !Array.isArray(n) || n.length === 0 ? r("needs children") : n.map((d, f) => l(d, t, `${u}.children[${f}]`, c)), i = (n) => l(n, t, `${u}.child`, c), o = (n, d) => typeof n == "number" && n >= 0 ? n : r(`${d} must be a number of at least 0`);
11
+ switch (e.type) {
12
+ case "sequence":
13
+ return T(...a(e.children));
14
+ case "selector":
15
+ return H(...a(e.children));
16
+ case "reactiveSequence":
17
+ return A(...a(e.children));
18
+ case "reactiveSelector":
19
+ return k(...a(e.children));
20
+ case "parallel":
21
+ return S({ succeed: e.succeed, fail: e.fail }, ...a(e.children));
22
+ case "invert":
23
+ return v(i(e.child));
24
+ case "succeed":
25
+ return q(i(e.child));
26
+ case "repeat":
27
+ return w(o(e.times, "times"), i(e.child));
28
+ case "retry":
29
+ return b(o(e.times, "times"), i(e.child));
30
+ case "cooldown":
31
+ return y(o(e.ms, "ms"), i(e.child));
32
+ case "timeout":
33
+ return $(o(e.ms, "ms"), i(e.child));
34
+ case "wait":
35
+ return h(o(e.ms, "ms"));
36
+ case "condition": {
37
+ const n = t.conditions?.[e.name];
38
+ return n ? s(e.name, n) : r(`no condition '${e.name}'`);
39
+ }
40
+ case "action": {
41
+ const n = t.actions?.[e.name];
42
+ return n ? typeof n == "function" ? m(e.name, n) : m(e.name, n.run, { onHalt: n.onHalt }) : r(`no action '${e.name}'`);
43
+ }
44
+ case "tree": {
45
+ const n = t.trees?.[e.name];
46
+ return n ? c.includes(e.name) ? r(`tree '${e.name}' contains itself`) : l(n, t, `${u}<${e.name}>`, [...c, e.name]) : r(`no tree '${e.name}'`);
47
+ }
48
+ default:
49
+ return r(`unknown type '${e.type}'`);
50
+ }
51
+ }
52
+ export {
53
+ x as buildTree
54
+ };
@@ -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
+ };
@@ -47,6 +47,7 @@ export declare class WebPatcher {
47
47
  private loaded;
48
48
  /** The tidy a start began: a build waits for it, or it could delete the `.part` folder being filled. */
49
49
  private tidying;
50
+ private tidyChain;
50
51
  constructor(options: WebPatcherOptions);
51
52
  /** This shell's identity. */
52
53
  get shell(): string;
@@ -75,6 +76,8 @@ export declare class WebPatcher {
75
76
  private promote;
76
77
  /** A pointer this shell may load: its own shell, newer than the installed build, its folder whole. */
77
78
  private usable;
78
- /** Removes every patched web but the active, pending and previous one, and any half-made folder. */
79
+ /** Removes every patched web but the active, pending and previous one, and any half-made folder. A call made while
80
+ * one is running waits for it and then runs once more, so two never delete the same folder at once. */
79
81
  tidy(): Promise<void>;
82
+ private tidyOnce;
80
83
  }
@@ -27,6 +27,7 @@ class WebPatcher {
27
27
  loaded = null;
28
28
  /** The tidy a start began: a build waits for it, or it could delete the `.part` folder being filled. */
29
29
  tidying = Promise.resolve();
30
+ tidyChain = Promise.resolve();
30
31
  constructor(options) {
31
32
  this.options = options;
32
33
  this.o = {
@@ -197,8 +198,14 @@ class WebPatcher {
197
198
  usable(p) {
198
199
  return !!p && p.shell === this.shell && (0, manifest_1.isNewer)(p.version, this.o.appVersion) && (0, node_fs_1.existsSync)((0, node_path_1.join)(this.o.root, p.version, this.o.entry));
199
200
  }
200
- /** Removes every patched web but the active, pending and previous one, and any half-made folder. */
201
- async tidy() {
201
+ /** Removes every patched web but the active, pending and previous one, and any half-made folder. A call made while
202
+ * one is running waits for it and then runs once more, so two never delete the same folder at once. */
203
+ tidy() {
204
+ const next = this.tidyChain.then(() => this.tidyOnce());
205
+ this.tidyChain = next;
206
+ return next;
207
+ }
208
+ async tidyOnce() {
202
209
  try {
203
210
  const names = ['active', 'pending', 'previous'];
204
211
  const keep = new Set([...names.map((n) => this.pointers.read(n)?.version), this.loaded?.version].filter((v) => !!v));
@@ -12,6 +12,8 @@ class g {
12
12
  resolution = 1;
13
13
  /** Lands the view on whole backing pixels (pixel art); off, the world moves smoothly between them. */
14
14
  roundPixels = !1;
15
+ /** The world is drawn on its own pixel grid (`Stage.pixelPerfect`): `snapRoot` lands on whole world pixels. Set by the stage. */
16
+ pixelGrid = !1;
15
17
  scrollX = 0;
16
18
  scrollY = 0;
17
19
  width = 0;
@@ -42,6 +44,14 @@ class g {
42
44
  const s = this.zoom * this.resolution;
43
45
  return Math.round(t * s) / s;
44
46
  }
47
+ /**
48
+ * Where an object's root lands (a body, and whatever is laid over one): a whole world pixel while the world is drawn
49
+ * on its own grid (`Stage.pixelPerfect`), else `snap`. One rule for every root, so a body, what it holds and what
50
+ * points at it fall on the same pixel; children keep whole-pixel offsets from it.
51
+ */
52
+ snapRoot(t) {
53
+ return this.pixelGrid ? Math.round(t) : this.snap(t);
54
+ }
45
55
  setBounds(t, s, h, e) {
46
56
  return this.bounds = { x: t, y: s, width: h, height: e }, this;
47
57
  }
@@ -1,45 +1,47 @@
1
- import { BitmapLabel as m } from "./BitmapLabel.js";
2
- import { Graphics as u } from "./Graphics.js";
3
- import { Group as g } from "./Group.js";
4
- import { Image as p } from "./Image.js";
5
- import { Rectangle as l } from "./Rectangle.js";
6
- import { Text as x } from "./Text.js";
1
+ import { BitmapLabel as g } from "./BitmapLabel.js";
2
+ import { Graphics as p } from "./Graphics.js";
3
+ import { Group as u } from "./Group.js";
4
+ import { Image as l } from "./Image.js";
5
+ import { Rectangle as x } from "./Rectangle.js";
6
+ import { Text as f } from "./Text.js";
7
7
  class L {
8
8
  constructor(t) {
9
9
  this.stage = t;
10
10
  }
11
11
  stage;
12
- image(t, e, r, a) {
13
- return this.attach(new p(this.stage.textures, t, e, r, a));
12
+ image(t, e, r, s) {
13
+ return this.attach(new l(this.stage.textures, t, e, r, s));
14
14
  }
15
15
  /**
16
16
  * A copy of `src` as it is drawn right now (afterimages, clones). A live texture, repainted every frame, is
17
17
  * snapshotted first, and the snapshot is freed with the copy.
18
18
  */
19
19
  still(t, e, r) {
20
- const a = this.stage.textures;
21
- if (!a.isLive(r.textureKey)) return this.image(t, e, r.textureKey, r.frameName);
22
- const s = a.snapshot(r.textureKey, r.frameName), i = this.image(t, e, s, "0");
23
- return i.once("destroyed", () => a.remove(s)), i;
20
+ const s = this.stage.textures;
21
+ if (!s.isLive(r.textureKey)) return this.image(t, e, r.textureKey, r.frameName);
22
+ const a = s.snapshot(r.textureKey, r.frameName), i = this.image(t, e, a, "0");
23
+ return i.once("destroyed", () => s.remove(a)), i;
24
24
  }
25
25
  graphics(t) {
26
- const e = this.attach(new u());
26
+ const e = this.attach(new p());
27
27
  return e.position.set(t?.x ?? 0, t?.y ?? 0), e;
28
28
  }
29
29
  container(t = 0, e = 0, r = []) {
30
- const a = this.attach(new g());
31
- return a.position.set(t, e), a.add(r), a;
30
+ const s = this.attach(new u());
31
+ return s.position.set(t, e), s.add(r), s;
32
32
  }
33
- rectangle(t, e, r, a, s, i = 1) {
34
- const n = this.attach(new l(r, a, s, i));
33
+ rectangle(t, e, r, s, a, i = 1) {
34
+ const n = this.attach(new x(r, s, a, i));
35
35
  return n.position.set(t, e), n;
36
36
  }
37
- text(t, e, r, a) {
38
- return this.attach(new x(t, e, r, a));
37
+ text(t, e, r, s) {
38
+ const a = this.attach(new f(t, e, r, s));
39
+ return this.stage.keepSharp(a), a;
39
40
  }
40
41
  /** A bitmap-font label (`BitmapLabel`): cheap to change, for text rewritten many times a second. */
41
- label(t, e, r, a, s, i, n, o, h, c = "") {
42
- return this.attach(new m(t, e, r, a, s, i, n, o, h, c));
42
+ label(t, e, r, s, a, i, n, h, c, m = "") {
43
+ const o = this.attach(new g(t, e, r, s, a, i, n, h, c, m));
44
+ return this.stage.keepSharp(o), o;
43
45
  }
44
46
  /** Puts an object made elsewhere on the world layer, as the factory's own are (a `PixelLayer`, a custom class). */
45
47
  existing(t) {
@@ -0,0 +1,47 @@
1
+ import { Matrix as n, Sprite as l, RenderTexture as a } from "pixi.js";
2
+ import { gridPlacement as d } from "./gridPlacement.js";
3
+ import { SharpText as x } from "./SharpText.js";
4
+ class m {
5
+ constructor(e, t) {
6
+ this.app = e, this.world = t, this.text = new x(t);
7
+ }
8
+ app;
9
+ world;
10
+ texture = null;
11
+ sprite = null;
12
+ text;
13
+ transform = new n();
14
+ on = !1;
15
+ get enabled() {
16
+ return this.on;
17
+ }
18
+ /** Once a frame, after the camera wrote the world's transform and before Pixi draws the screen. */
19
+ render(e, t) {
20
+ if (t !== this.on && (t ? this.enable(e) : this.disable(e)), !t) return;
21
+ const i = e.zoom || 1, s = this.world.position, r = d(s.x, s.y, i, this.app.renderer.resolution, e.width, e.height), o = this.fit(r.width, r.height);
22
+ this.transform.set(1, 0, 0, 1, -r.tx, -r.ty), this.app.renderer.render({ container: this.world, target: o, clear: !0, clearColor: [0, 0, 0, 0], transform: this.transform });
23
+ const h = this.sprite;
24
+ h.scale.set(i), h.position.set(r.x, r.y), this.text.sync(s, i);
25
+ }
26
+ destroy(e) {
27
+ this.disable(e), this.text.destroy();
28
+ }
29
+ enable(e) {
30
+ this.on = !0, e.pixelGrid = !0;
31
+ const t = this.app.stage, i = Math.max(0, t.children.indexOf(this.world));
32
+ t.removeChild(this.world), this.sprite = new l(), t.addChildAt(this.sprite, i), t.addChildAt(this.text.layer, i + 1);
33
+ }
34
+ disable(e) {
35
+ if (!this.on) return;
36
+ this.on = !1, e.pixelGrid = !1, this.text.release();
37
+ const t = this.app.stage, i = Math.max(0, this.sprite ? t.children.indexOf(this.sprite) : 0);
38
+ t.removeChild(this.text.layer), this.sprite?.destroy(), this.sprite = null, this.texture?.destroy(!0), this.texture = null, this.world.destroyed || t.addChildAt(this.world, i);
39
+ }
40
+ /** The texture at exactly this size, made again only when the screen or the zoom changed. */
41
+ fit(e, t) {
42
+ return this.texture && this.texture.width === e && this.texture.height === t ? this.texture : (this.texture?.destroy(!0), this.texture = a.create({ width: e, height: t, resolution: 1, scaleMode: "nearest", antialias: !1 }), this.sprite.texture = this.texture, this.texture);
43
+ }
44
+ }
45
+ export {
46
+ m as PixelGrid
47
+ };
@@ -0,0 +1,72 @@
1
+ import { Container as n, Matrix as d } from "pixi.js";
2
+ class c {
3
+ constructor(e) {
4
+ this.world = e;
5
+ }
6
+ world;
7
+ layer = new n({ sortableChildren: !0 });
8
+ labels = /* @__PURE__ */ new Set();
9
+ m = new d();
10
+ /** Labels tracked since the last sweep for destroyed ones: off, nothing else sweeps them. */
11
+ sinceSweep = 0;
12
+ track(e) {
13
+ this.labels.add({ obj: e, home: null, wrap: null }), ++this.sinceSweep >= 256 && this.prune();
14
+ }
15
+ /** Lays the layer over the world (`origin`: where the world's origin lands on screen) and every label on its home. */
16
+ sync(e, r) {
17
+ this.layer.position.set(e.x, e.y), this.layer.scale.set(r);
18
+ for (const t of this.labels) {
19
+ const s = t.obj;
20
+ if (s.destroyed) {
21
+ this.drop(t);
22
+ continue;
23
+ }
24
+ s.parent && s.parent !== t.wrap && (t.home = s.parent, t.wrap ??= this.layer.addChild(new n()), t.wrap.addChild(s));
25
+ const o = t.home, i = t.wrap;
26
+ if (!(!o || !i)) {
27
+ if (o.destroyed) {
28
+ s.destroy(), this.drop(t);
29
+ continue;
30
+ }
31
+ this.place(o, i, s);
32
+ }
33
+ }
34
+ }
35
+ /** Every label back in its home (the grid switched off), the stand-ins gone. */
36
+ release() {
37
+ for (const e of this.labels) {
38
+ const { obj: r, home: t, wrap: s } = e;
39
+ if (r.destroyed) {
40
+ this.drop(e);
41
+ continue;
42
+ }
43
+ t && s && r.parent === s && (t.destroyed ? r.destroy() : t.addChild(r)), s?.destroy(), e.wrap = null, e.home = null;
44
+ }
45
+ }
46
+ /** Forgets labels the game destroyed while nothing synced (the grid off). */
47
+ prune() {
48
+ this.sinceSweep = 0;
49
+ for (const e of this.labels) e.obj.destroyed && this.drop(e);
50
+ }
51
+ destroy() {
52
+ this.release(), this.labels.clear(), this.layer.destroy({ children: !0 });
53
+ }
54
+ place(e, r, t) {
55
+ const s = this.m.identity();
56
+ let o = !0, i = 1, a = e;
57
+ for (let l = e; l !== this.world; l = l.parent) {
58
+ if (!l) {
59
+ o = !1;
60
+ break;
61
+ }
62
+ (!l.visible || !l.renderable) && (o = !1), i *= l.alpha, l.updateLocalTransform(), s.prepend(l.localTransform), a = l;
63
+ }
64
+ r.visible = o, o && (r.setFromMatrix(s), r.alpha = i, r.zIndex = e === this.world ? t.zIndex : a.zIndex);
65
+ }
66
+ drop(e) {
67
+ e.wrap?.destroy(), this.labels.delete(e);
68
+ }
69
+ }
70
+ export {
71
+ c as SharpText
72
+ };