@fruit-pie/quicksave 0.1.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 (3) hide show
  1. package/README.md +8 -0
  2. package/dist/index.js +1184 -0
  3. package/package.json +35 -0
package/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # Quicksave
2
+
3
+ Extra save you can place wherever you want (on the ground) between 2 actual saves for accessibility purposes.
4
+
5
+ ## Keys
6
+
7
+ - **X** to place a quicksave (you must be on the ground and have used an actual save at least once)
8
+ - **C** to remove a quicksave if you changed your mind
package/dist/index.js ADDED
@@ -0,0 +1,1184 @@
1
+ //#region node_modules/@fruit-pie/sdk/dist/index.js
2
+ var e = function(e) {
3
+ let { manifest: t, hostApi: n, prefix: r, objects: i, internalReceive: a, internalCreate: o, normalizeEventName: s, context: c, queue: l, scratch: u } = e, d = /* @__PURE__ */ new Map();
4
+ for (let e of i) for (let n of Object.keys(e.receive ?? {})) {
5
+ let r = s(n), i = d.get(r);
6
+ if (i) throw Error(`mod ${t.id}: both ${i} and ${e.name} receive ${JSON.stringify(r)}. They would race for each message; give it to one of them.`);
7
+ d.set(r, e.name);
8
+ }
9
+ for (let e of Object.keys(a)) d.set(e, `${r}inbox`);
10
+ let f = (e, t) => `while (ds_list_size(${l(e)}) > 0) {
11
+ ${u("pl")} = ds_list_find_value(${l(e)}, 0);
12
+ ds_list_delete(${l(e)}, 0);
13
+ ${t}
14
+ }`;
15
+ if (d.size > 0) {
16
+ let e = [...d.keys()], t = e.map((e, t) => `${t === 0 ? "if" : "else if"} (${u("ev")} == "${e}") ds_list_add(${l(e)}, ${u("pl")});`).join("\n"), i = Object.entries(a).map(([e, t]) => f(e, t)).join("\n");
17
+ n.resource.create({
18
+ kind: "object",
19
+ name: `${r}inbox`,
20
+ persistent: !0,
21
+ visible: !1,
22
+ depth: 0,
23
+ events: {
24
+ create: e.map((e) => `${l(e)} = ds_list_create();`).join("\n") + o,
25
+ step: i || void 0,
26
+ beginStep: `${n.gml.receive(u("m"))}
27
+ while (${u("m")} != "") {
28
+ ${u("at")} = string_pos(" ", ${u("m")});
29
+ if (${u("at")} > 1) {
30
+ ${u("ev")} = string_copy(${u("m")}, 1, ${u("at")} - 1);
31
+ ${u("pl")} = string_delete(${u("m")}, 1, ${u("at")});
32
+ ${t}
33
+ }
34
+ ${n.gml.receive(u("m"))}
35
+ }`
36
+ }
37
+ });
38
+ }
39
+ for (let e of i) {
40
+ let t = (t) => t?.(c(e)), i = [Object.entries(e.receive ?? {}).map(([t, n]) => f(s(t), n({
41
+ ...c(e),
42
+ payload: u("pl")
43
+ }))).join("\n"), t(e.events?.step)].filter(Boolean).join("\n"), a = {};
44
+ for (let [n, r] of [
45
+ ["create", t(e.events?.create)],
46
+ ["beginStep", t(e.events?.beginStep)],
47
+ ["step", i || void 0],
48
+ ["endStep", t(e.events?.endStep)],
49
+ ["draw", t(e.events?.draw)],
50
+ ["destroy", t(e.events?.destroy)]
51
+ ]) r && (a[n] = r);
52
+ n.resource.create({
53
+ kind: "object",
54
+ name: `${r}o_${e.name}`,
55
+ depth: e.depth,
56
+ persistent: e.persistent,
57
+ visible: e.visible,
58
+ events: a
59
+ });
60
+ }
61
+ }, t = function(e) {
62
+ let t = -1, n = (e) => {
63
+ switch (e.k) {
64
+ case "arg":
65
+ e.index !== null && e.index < 16 && (t = Math.max(t, e.index));
66
+ break;
67
+ case "field":
68
+ n(e.of);
69
+ break;
70
+ case "index":
71
+ n(e.of), e.at.forEach(n);
72
+ break;
73
+ case "op":
74
+ e.args.forEach(n);
75
+ break;
76
+ case "call": e.args.forEach(n);
77
+ }
78
+ }, r = (e) => {
79
+ for (let t of e) t.k === "assign" ? (n(t.target), n(t.value)) : t.k === "call" ? n(t.call) : t.k === "if" ? (n(t.cond), r(t.then), r(t.else)) : t.k === "with" ? (n(t.target), r(t.body)) : t.k === "loop" ? r(t.body) : t.k === "switch" ? (n(t.value), t.bodies.forEach(r)) : t.k === "exit" && t.value && n(t.value);
80
+ };
81
+ return r(e), t;
82
+ }, n = /^[A-Za-z][A-Za-z0-9_-]*$/u, r = /^[A-Za-z][A-Za-z0-9_]*$/u, i = /^[A-Za-z0-9_.-]+$/u, a = (e) => {
83
+ let t = "";
84
+ for (let n = 0; n < e.length; n += 8192) t += String.fromCharCode(...e.subarray(n, n + 8192));
85
+ return btoa(t).replace(/=+$/u, "");
86
+ }, o = function(e) {
87
+ e.hooks.forEach(({ script: n, wrap: r }, i) => {
88
+ let a = `${e.prefix}__orig${i}_${n}`, o = t(e.content.scripts.find((e) => e.name === n)?.body ?? []) + 1, s = `${e.prefix}__fwd${i}`, c = `global.${e.prefix}__hc${i}`, l = `global.${e.prefix}__ha${i}`, u = `global.${e.prefix}__hi${i}`, d = Array.from({ length: 17 }, (e, t) => {
89
+ let n = Array.from({ length: t }, (e, t) => `${l}[${t}]`).join(", ");
90
+ return ` case ${t}: return ${a}(${n});`;
91
+ }).join("\n");
92
+ e.hostApi.resource.create({
93
+ kind: "script",
94
+ name: s,
95
+ source: `switch (${c}) {\n${d}\n}\nreturn 0;`
96
+ });
97
+ let f = r({
98
+ engine: e.hostApi.target.engine,
99
+ original: {
100
+ call: (...e) => e.length === 0 ? `${s}()` : `${a}(${e.join(", ")})`,
101
+ arg: (e) => e >= 0 && e < 16 ? `argument${e}` : `argument[${e}]`,
102
+ argumentCount: "argument_count"
103
+ },
104
+ send: e.send,
105
+ call: e.call
106
+ });
107
+ e.hostApi.resource.create({
108
+ kind: "hook",
109
+ script: n,
110
+ original: a,
111
+ source: `
112
+ ${c} = argument_count;
113
+ ${u} = 0;
114
+ while (${u} < argument_count) {
115
+ ${l}[${u}] = argument[${u}];
116
+ ${u} += 1;
117
+ }
118
+ ${o > 0 ? `if (0) { ${l}[${o - 1}] = argument${o - 1}; }\n` : ""}${f}`
119
+ });
120
+ });
121
+ };
122
+ function s(e) {
123
+ switch (e.k) {
124
+ case "assign": return e.target.k === "var" ? `an assignment to ${e.target.name}` : "an assignment";
125
+ case "call": return `a call to ${e.call.name}`;
126
+ case "exit": return e.value ? "a return" : "an exit";
127
+ default: return `a ${e.k} statement`;
128
+ }
129
+ }
130
+ function c(e, t) {
131
+ let n = t.at.at;
132
+ if (n === void 0) throw Error(`mod ${e}: cannot insert at ${s(t.at)} -- this game's engine could not place it, so there is nothing to insert around. Statements without a place include the parts of a \`for\` header and temporaries a decompiler invented.`);
133
+ if (t.place !== "before" && t.place !== "after") throw Error(`mod ${e}: an insertion's place is "before" or "after", not ${JSON.stringify(t.place)}`);
134
+ if (t.place === "after" && t.at.k === "exit") throw Error(`mod ${e}: nothing follows ${s(t.at)}, so there is no "after" to insert at. Use "before" to run something on the way out.`);
135
+ return n;
136
+ }
137
+ var l = function(e) {
138
+ let t = /* @__PURE__ */ new Map();
139
+ for (let n of e.inserts) {
140
+ let r = n.body({
141
+ engine: e.hostApi.target.engine,
142
+ send: e.send,
143
+ call: e.call
144
+ }), i = t.get(r);
145
+ i === void 0 && (i = `${e.prefix}__ins${t.size}`, t.set(r, i), e.hostApi.resource.create({
146
+ kind: "script",
147
+ name: i,
148
+ source: r
149
+ })), e.hostApi.resource.create({
150
+ kind: "insert",
151
+ at: n.at,
152
+ place: n.place,
153
+ call: i
154
+ });
155
+ }
156
+ }, u = function(e) {
157
+ for (let t of e.scripts) {
158
+ let n = {};
159
+ (t.args ?? []).forEach((i, a) => {
160
+ if (!r.test(i)) throw Error(`mod ${e.manifest.id}: script ${JSON.stringify(t.name)} has an argument called ${JSON.stringify(i)}, which is not a usable name`);
161
+ n[i] = `argument${a}`;
162
+ }), e.hostApi.resource.create({
163
+ kind: "script",
164
+ name: `${e.prefix}s_${t.name}`,
165
+ source: t.body({
166
+ engine: e.hostApi.target.engine,
167
+ a: n,
168
+ send: e.send,
169
+ call: e.call
170
+ })
171
+ });
172
+ }
173
+ }, d = 1200, f = 15e3, p = (e) => e[0] === 137 && e[1] === 80 ? {
174
+ extension: "png",
175
+ type: "image/png"
176
+ } : e[0] === 255 && e[1] === 216 ? {
177
+ extension: "jpg",
178
+ type: "image/jpeg"
179
+ } : e[0] === 66 && e[1] === 77 ? {
180
+ extension: "bmp",
181
+ type: "image/bmp"
182
+ } : null, m = class extends Error {
183
+ constructor() {
184
+ super("superseded by a newer load after the game restarted");
185
+ }
186
+ }, h = async function(e) {
187
+ if (typeof e != "string") return e instanceof Uint8Array ? e : new Uint8Array(e);
188
+ if (e.startsWith("data:")) {
189
+ let t = e.indexOf(",");
190
+ if (t < 0) throw Error("that data: URI has no comma in it");
191
+ let n = e.slice(0, t), r = e.slice(t + 1);
192
+ if (!n.endsWith(";base64")) return new TextEncoder().encode(decodeURIComponent(r));
193
+ let i = atob(r), a = new Uint8Array(i.length);
194
+ for (let e = 0; e < i.length; e += 1) a[e] = i.charCodeAt(e);
195
+ return a;
196
+ }
197
+ let t = await fetch(e, { cache: "no-store" });
198
+ if (!t.ok) throw Error(`${e}: ${t.status}`);
199
+ return new Uint8Array(await t.arrayBuffer());
200
+ }, g = async function() {
201
+ return await import(
202
+ /* @vite-ignore */
203
+ "electron"
204
+ );
205
+ }, _ = async function(e, t) {
206
+ if (typeof OffscreenCanvas == "function" && typeof createImageBitmap == "function") {
207
+ let n = await Promise.all(e.map((e, t) => {
208
+ let n = p(e);
209
+ if (!n) throw Error(`frame ${t + 1} is not a PNG, JPEG or BMP`);
210
+ return createImageBitmap(new Blob([e], { type: n.type }));
211
+ })), r = n[0];
212
+ for (let [e, t] of n.entries()) if (t.width !== r.width || t.height !== r.height) throw Error(`frame ${e + 1} is ${t.width}x${t.height} but frame 1 is ${r.width}x${r.height}; every frame of a sprite is the same size`);
213
+ let i = t ? Math.min(1, t / Math.max(r.width, r.height)) : 1, a = Math.max(1, Math.round(r.width * i)), o = Math.max(1, Math.round(r.height * i)), s = new OffscreenCanvas(a * n.length, o), c = s.getContext("2d");
214
+ return n.forEach((e, t) => {
215
+ c.drawImage(e, t * a, 0, a, o), e.close();
216
+ }), new Uint8Array(await (await s.convertToBlob({ type: "image/png" })).arrayBuffer());
217
+ }
218
+ let { nativeImage: n } = await g(), r = e.map((e, t) => {
219
+ let r = n.createFromBuffer(e);
220
+ if (r.getSize().width === 0) throw Error(`frame ${t + 1} could not be decoded`);
221
+ return r;
222
+ }), i = r[0].getSize();
223
+ for (let [e, t] of r.entries()) {
224
+ let n = t.getSize();
225
+ if (n.width !== i.width || n.height !== i.height) throw Error(`frame ${e + 1} is ${n.width}x${n.height} but frame 1 is ${i.width}x${i.height}; every frame of a sprite is the same size`);
226
+ }
227
+ let a = t ? Math.min(1, t / Math.max(i.width, i.height)) : 1, o = Math.max(1, Math.round(i.width * a)), s = Math.max(1, Math.round(i.height * a)), c = o * r.length, l = new Uint8Array(c * s * 4);
228
+ return r.forEach((e, t) => {
229
+ let n = (a < 1 ? e.resize({
230
+ width: o,
231
+ height: s
232
+ }) : e).toBitmap();
233
+ for (let e = 0; e < s; e += 1) l.set(n.subarray(e * o * 4, (e + 1) * o * 4), (e * c + t * o) * 4);
234
+ }), new Uint8Array(n.createFromBitmap(l, {
235
+ width: c,
236
+ height: s
237
+ }).toPNG());
238
+ }, v = async function(e, t, n) {
239
+ if (typeof OffscreenCanvas == "function" && typeof createImageBitmap == "function") {
240
+ let r = await createImageBitmap(new Blob([e], { type: t })), i = n ? Math.min(1, n / Math.max(r.width, r.height)) : 1, a = Math.max(1, Math.round(r.width * i)), o = Math.max(1, Math.round(r.height * i)), s = new OffscreenCanvas(a, o);
241
+ return s.getContext("2d").drawImage(r, 0, 0, a, o), r.close(), new Uint8Array(await (await s.convertToBlob({ type: "image/png" })).arrayBuffer());
242
+ }
243
+ let { nativeImage: r } = await g(), i = r.createFromBuffer(e), a = i.getSize(), o = n ? Math.min(1, n / Math.max(a.width, a.height)) : 1;
244
+ return o < 1 && (i = i.resize({
245
+ width: Math.max(1, Math.round(a.width * o)),
246
+ height: Math.max(1, Math.round(a.height * o))
247
+ })), new Uint8Array(i.toPNG());
248
+ }, y = {
249
+ begin: "__spr_begin",
250
+ part: "__spr_part",
251
+ end: "__spr_end",
252
+ ack: "__spr_ack",
253
+ ok: "__spr_ok",
254
+ failed: "__spr_failed",
255
+ hello: "__spr_hello"
256
+ }, b = function(e) {
257
+ let t = null, n = Promise.resolve(), r = new Promise((t) => {
258
+ e.hostApi.on("start", t);
259
+ }), i = async function(t, n) {
260
+ let r = Array.isArray(t) ? t : [t];
261
+ if (r.length === 0) throw Error("there is no picture in that list");
262
+ let i = await Promise.all(r.map((e) => h(e))), a = n.frames ?? 1;
263
+ if (i.length > 1) return {
264
+ bytes: await _(i, n.maxSize),
265
+ extension: "png",
266
+ frames: i.length * a
267
+ };
268
+ let o = i[0], s = p(o);
269
+ if (!s) throw Error("those bytes are not a PNG, JPEG or BMP");
270
+ let c = e.hostApi.target.engine === "gms" && s.extension !== "png", l = n.maxSize !== void 0;
271
+ return !c && !l ? {
272
+ bytes: o,
273
+ extension: s.extension,
274
+ frames: a
275
+ } : {
276
+ bytes: await v(o, s.type, n.maxSize),
277
+ extension: "png",
278
+ frames: a
279
+ };
280
+ }, o = function(n, r, i) {
281
+ let { bytes: o, extension: s, frames: c } = i, l = a(o), u = [];
282
+ for (let e = 0; e < l.length; e += d) u.push(l.slice(e, e + d));
283
+ return new Promise((i, a) => {
284
+ let l = 0, d, p = (r, o, s = !1) => {
285
+ if (clearTimeout(d), t = null, r) {
286
+ if (s) {
287
+ a(r);
288
+ return;
289
+ }
290
+ console.warn(`[${e.manifest.id}] sprite ${JSON.stringify(n)} failed: ${r.message}`), a(r);
291
+ } else i(o);
292
+ }, h = () => {
293
+ clearTimeout(d), d = setTimeout(() => p(/* @__PURE__ */ Error(`the game stopped answering after ${l} of ${u.length} chunks`)), f);
294
+ };
295
+ t = {
296
+ ack: () => {
297
+ l < u.length ? e.sendInternal(y.part, u[l++]) : e.sendInternal(y.end), h();
298
+ },
299
+ done: (e) => p(null, e),
300
+ failed: (e) => p(Error(e)),
301
+ abandon: () => p(new m(), void 0, !0)
302
+ };
303
+ let g = r.origin ?? {
304
+ x: 0,
305
+ y: 0
306
+ };
307
+ e.sendInternal(y.begin, `${n} ${o.length} ${s} ${c} ${g.x} ${g.y}`), h();
308
+ });
309
+ };
310
+ return {
311
+ loadSprite: function(e, t, a, s = () => !0) {
312
+ let c = async () => {
313
+ let n = await i(a, t);
314
+ if (await r, !s()) throw new m();
315
+ return o(e, t, n);
316
+ }, l = n.then(c, c);
317
+ return n = l.catch(() => {}), l;
318
+ },
319
+ ack: () => t?.ack(),
320
+ done: (e) => t?.done(e),
321
+ failed: (e) => t?.failed(e),
322
+ abandon: () => t?.abandon()
323
+ };
324
+ }, x = function(e) {
325
+ let { hostApi: t, prefix: n, sprites: r, payload: i, sendInternalGml: a } = e;
326
+ if (r.length === 0) return {};
327
+ let o = (e) => `global.${n}__${e}`, s = `${n}__sprfield`, c = `${n}__spropen`, l = `${n}__sprwrite`, u = `${n}__sprclose`, d = t.target.engine === "gm8" ? {
328
+ scratch: "temp_directory + \"\\\"",
329
+ open: `${o("sprfn")} = argument0;
330
+ ${o("sprfh")} = file_bin_open(argument1, 1);
331
+ file_bin_close(${o("sprfh")});`,
332
+ chunkStart: `${o("sprfh")} = file_bin_open(${o("sprfile")}, 2);
333
+ file_bin_seek(${o("sprfh")}, file_bin_size(${o("sprfh")}));`,
334
+ write: (e) => `file_bin_write_byte(${o("sprfh")}, ${e});`,
335
+ chunkEnd: `file_bin_close(${o("sprfh")});`,
336
+ close: `${o("sprfn")} = argument0;`
337
+ } : {
338
+ scratch: "\"\"",
339
+ open: `${o("sprfn")} = argument1;
340
+ ${o("sprbuf")} = buffer_create(argument0, buffer_fixed, 1);`,
341
+ chunkStart: "",
342
+ write: (e) => `buffer_write(${o("sprbuf")}, buffer_u8, ${e});`,
343
+ chunkEnd: "",
344
+ close: `buffer_save(${o("sprbuf")}, argument0); buffer_delete(${o("sprbuf")});`
345
+ };
346
+ t.resource.create({
347
+ kind: "script",
348
+ name: s,
349
+ source: `${o("fs")} = argument0;
350
+ ${o("fn")} = 1;
351
+ while (${o("fn")} < argument1) {
352
+ ${o("fat")} = string_pos(" ", ${o("fs")});
353
+ if (${o("fat")} == 0) return "";
354
+ ${o("fs")} = string_delete(${o("fs")}, 1, ${o("fat")});
355
+ ${o("fn")} += 1;
356
+ }
357
+ ${o("fat")} = string_pos(" ", ${o("fs")});
358
+ if (${o("fat")} > 0) ${o("fs")} = string_copy(${o("fs")}, 1, ${o("fat")} - 1);
359
+ return ${o("fs")};`
360
+ }), t.resource.create({
361
+ kind: "script",
362
+ name: c,
363
+ source: d.open
364
+ }), t.resource.create({
365
+ kind: "script",
366
+ name: u,
367
+ source: d.close
368
+ }), t.resource.create({
369
+ kind: "script",
370
+ name: l,
371
+ source: `${o("ws")} = argument0;
372
+ ${d.chunkStart}
373
+ ${o("wn")} = string_length(${o("ws")});
374
+ ${o("wi")} = 1;
375
+ while (${o("wi")} <= ${o("wn")}) {
376
+ ${o("wl")} = ${o("wn")} - ${o("wi")} + 1;
377
+ ${o("w0")} = ${o("b64")}[ord(string_char_at(${o("ws")}, ${o("wi")}))];
378
+ ${o("w1")} = ${o("b64")}[ord(string_char_at(${o("ws")}, ${o("wi")} + 1))];
379
+ ${d.write(`(${o("w0")} << 2) | (${o("w1")} >> 4)`)}
380
+ if (${o("wl")} >= 3) {
381
+ ${o("w2")} = ${o("b64")}[ord(string_char_at(${o("ws")}, ${o("wi")} + 2))];
382
+ ${d.write(`((${o("w1")} & 15) << 4) | (${o("w2")} >> 2)`)}
383
+ if (${o("wl")} >= 4) {
384
+ ${o("w3")} = ${o("b64")}[ord(string_char_at(${o("ws")}, ${o("wi")} + 3))];
385
+ ${d.write(`((${o("w2")} & 3) << 6) | ${o("w3")}`)}
386
+ }
387
+ }
388
+ ${o("wi")} += 4;
389
+ }
390
+ ${d.chunkEnd}`
391
+ });
392
+ let f = r.map(({ name: e }) => `if (${o("sprslot")} == "${e}") {
393
+ if (is_real(global.${n}spr_${e}) && global.${n}spr_${e} >= 0) sprite_delete(global.${n}spr_${e});
394
+ global.${n}spr_${e} = ${o("sprnew")};
395
+ }`).join(" else ");
396
+ return {
397
+ [y.begin]: `${o("sprslot")} = ${s}(${i}, 1);
398
+ ${o("sprlen")} = real(${s}(${i}, 2));
399
+ ${o("sprfile")} = ${d.scratch} + "fpsprite." + ${s}(${i}, 3);
400
+ ${o("sprimgs")} = real(${s}(${i}, 4));
401
+ ${o("sprox")} = real(${s}(${i}, 5));
402
+ ${o("sproy")} = real(${s}(${i}, 6));
403
+ ${c}(${o("sprlen")}, ${o("sprfile")});
404
+ ${a(y.ack)}`,
405
+ [y.part]: `if (${o("sprfile")} != "") {
406
+ ${l}(${i});
407
+ ${a(y.ack)}
408
+ }`,
409
+ [y.end]: `if (${o("sprfile")} != "") {
410
+ ${u}(${o("sprfile")});
411
+ ${o("sprnew")} = sprite_add(${o("sprfile")}, ${o("sprimgs")}, false, false, ${o("sprox")}, ${o("sproy")});
412
+ file_delete(${o("sprfile")});
413
+ if (is_real(${o("sprnew")}) && ${o("sprnew")} >= 0) {
414
+ ${f}
415
+ ${a(y.ok, `${o("sprslot")} + " " + string(sprite_get_width(${o("sprnew")})) + " " + string(sprite_get_height(${o("sprnew")}))`)}
416
+ } else {
417
+ ${a(y.failed, `${o("sprslot")} + " sprite_add answered " + string(${o("sprnew")})`)}
418
+ }
419
+ ${o("sprfile")} = "";
420
+ }`
421
+ };
422
+ }, S = function(e) {
423
+ let { prefix: t, sprites: n, sendInternalGml: r } = e;
424
+ if (n.length === 0) return "";
425
+ let i = (e) => `global.${t}__${e}`;
426
+ return `
427
+ ${n.map(({ name: e }) => `global.${t}spr_${e} = -1;`).join("\n")}
428
+ ${i("sprfile")} = "";
429
+ ${i("b64a")} = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
430
+ ${i("b64i")} = 0;
431
+ while (${i("b64i")} < 64) {
432
+ ${i("b64")}[ord(string_char_at(${i("b64a")}, ${i("b64i")} + 1))] = ${i("b64i")};
433
+ ${i("b64i")} += 1;
434
+ }
435
+ ${r(y.hello)}`;
436
+ }, C = function(e) {
437
+ let { manifest: t, sprites: n, shown: r, spriteLoader: i, handlers: a } = e;
438
+ if (n.length === 0) return;
439
+ let o = 0;
440
+ a.set(y.hello, [() => {
441
+ if (o += 1, o === 1) return;
442
+ i.abandon();
443
+ let e = o, a = () => e === o;
444
+ for (let [e, o] of r) {
445
+ let r = n.find((t) => t.name === e)?.options ?? {};
446
+ i.loadSprite(e, r, o, a).catch((n) => {
447
+ n instanceof m || console.warn(`[${t.id}] sprite ${JSON.stringify(e)} failed to come back after a restart:`, n);
448
+ });
449
+ }
450
+ }]), a.set(y.ack, [() => i.ack()]), a.set(y.ok, [(e) => {
451
+ let [, t, n] = e.split(" ");
452
+ i.done({
453
+ width: Number(t),
454
+ height: Number(n)
455
+ });
456
+ }]), a.set(y.failed, [(e) => {
457
+ let t = e.indexOf(" ");
458
+ i.failed(t < 0 ? "the game refused the picture" : e.slice(t + 1));
459
+ }]);
460
+ }, w = function(e) {
461
+ let t = (e.split(".").pop() ?? e).replaceAll(/[^A-Za-z0-9]/gu, "").slice(0, 12), n = 2166136261;
462
+ for (let t = 0; t < e.length; t += 1) n = Math.imul(n ^ e.charCodeAt(t), 16777619);
463
+ return `m${t}_${((n >>> 0) % 65536).toString(16).padStart(4, "0")}_`;
464
+ }, T = function(t, a) {
465
+ let s = {
466
+ ...t,
467
+ apiVersion: 2
468
+ };
469
+ return {
470
+ manifest: s,
471
+ activate(t) {
472
+ let d = w(s.id), f = [], p = [], m = [], h = [], g = [], _ = /* @__PURE__ */ new Map(), v = !0, y = (e) => {
473
+ if (!n.test(e)) throw Error(`mod ${s.id}: ${JSON.stringify(e)} is not a usable event name (letters, digits, - and _, starting with a letter)`);
474
+ return e.replaceAll("-", "_");
475
+ }, T = (e) => `${d}__${e}`, E = (e) => {
476
+ let t = {};
477
+ for (let n of e.vars ?? []) {
478
+ if (!r.test(n)) throw Error(`mod ${s.id}: ${JSON.stringify(n)} is not a usable variable name (letters, digits and _, starting with a letter)`);
479
+ t[n] = `${d}v_${n}`;
480
+ }
481
+ return t;
482
+ }, D = (e) => `global.${d}q_${e}`, O = (e, n = "") => t.send(`${e} ${n}`), k = (e, n) => t.gml.send(n ? `"${e} " + (${n})` : `"${e} "`), A = (e, t) => k(y(e), t), j = (e, ...t) => {
483
+ if (!p.some((t) => t.name === e)) throw Error(`mod ${s.id}: call(${JSON.stringify(e)}) names a script this mod never declared. It has: ${p.map((e) => e.name).join(", ") || "none"}`);
484
+ return `${d}s_${e}(${t.join(", ")})`;
485
+ }, M = (e) => ({
486
+ engine: t.target.engine,
487
+ v: E(e),
488
+ send: A,
489
+ call: j
490
+ }), N = b({
491
+ hostApi: t,
492
+ manifest: s,
493
+ sendInternal: O
494
+ }), P = /* @__PURE__ */ new Map(), F = t.game;
495
+ a({
496
+ engine: t.target.engine,
497
+ content: F,
498
+ resource: {
499
+ object: { create(e) {
500
+ if (!v) throw Error(`mod ${s.id}: objects can only be declared while the mod is built`);
501
+ f.push(e);
502
+ } },
503
+ script: {
504
+ create(e) {
505
+ if (!v) throw Error(`mod ${s.id}: scripts can only be declared while the mod is built`);
506
+ if (!r.test(e.name)) throw Error(`mod ${s.id}: ${JSON.stringify(e.name)} is not a usable script name (letters, digits and _, starting with a letter)`);
507
+ if (p.some((t) => t.name === e.name)) throw Error(`mod ${s.id}: two scripts are called ${JSON.stringify(e.name)}`);
508
+ p.push(e);
509
+ },
510
+ hook(e, t) {
511
+ if (!v) throw Error(`mod ${s.id}: hooks can only be declared while the mod is built`);
512
+ h.push({
513
+ script: e,
514
+ wrap: t
515
+ });
516
+ },
517
+ insert(e) {
518
+ if (!v) throw Error(`mod ${s.id}: insertions can only be declared while the mod is built`);
519
+ g.push({
520
+ at: c(s.id, e),
521
+ place: e.place,
522
+ body: e.body
523
+ });
524
+ }
525
+ },
526
+ sprite: { create(e, t = {}) {
527
+ if (!v) throw Error(`mod ${s.id}: sprites can only be declared while the mod is built`);
528
+ if (!r.test(e)) throw Error(`mod ${s.id}: ${JSON.stringify(e)} is not a usable sprite name (letters, digits and _, starting with a letter)`);
529
+ if (m.some((t) => t.name === e)) throw Error(`mod ${s.id}: two sprites are called ${JSON.stringify(e)}`);
530
+ return m.push({
531
+ name: e,
532
+ options: t
533
+ }), {
534
+ id: `global.${d}spr_${e}`,
535
+ load: (n) => (P.set(e, n), N.loadSprite(e, t, n))
536
+ };
537
+ } }
538
+ },
539
+ on(e, t) {
540
+ let n = y(e), r = _.get(n);
541
+ r ? r.push(t) : _.set(n, [t]);
542
+ },
543
+ onStart(e) {
544
+ t.on("start", e);
545
+ },
546
+ scratchPath(e) {
547
+ if (!i.test(e)) throw Error(`mod ${s.id}: ${JSON.stringify(e)} is not a usable scratch file name (letters, digits, _ . and -)`);
548
+ return t.target.engine === "gm8" ? `temp_directory + "\\${d}${e}"` : `"${d}${e}"`;
549
+ },
550
+ send(e, n = "") {
551
+ t.send(`${y(e)} ${n}`);
552
+ }
553
+ }), v = !1, l({
554
+ manifest: s,
555
+ inserts: g,
556
+ prefix: d,
557
+ hostApi: t,
558
+ send: A,
559
+ call: j
560
+ }), u({
561
+ manifest: s,
562
+ scripts: p,
563
+ hostApi: t,
564
+ prefix: d,
565
+ send: A,
566
+ call: j
567
+ }), o({
568
+ manifest: s,
569
+ hooks: h,
570
+ content: F,
571
+ hostApi: t,
572
+ prefix: d,
573
+ send: A,
574
+ call: j
575
+ }), e({
576
+ manifest: s,
577
+ hostApi: t,
578
+ prefix: d,
579
+ objects: f,
580
+ internalReceive: x({
581
+ hostApi: t,
582
+ prefix: d,
583
+ sprites: m,
584
+ payload: T("pl"),
585
+ sendInternalGml: k
586
+ }),
587
+ internalCreate: S({
588
+ prefix: d,
589
+ sprites: m,
590
+ sendInternalGml: k
591
+ }),
592
+ normalizeEventName: y,
593
+ context: M,
594
+ queue: D,
595
+ scratch: T
596
+ }), C({
597
+ manifest: s,
598
+ sprites: m,
599
+ shown: P,
600
+ spriteLoader: N,
601
+ handlers: _
602
+ }), t.on("message", (e) => {
603
+ let t = e.indexOf(" "), n = t < 0 ? e : e.slice(0, t), r = t < 0 ? "" : e.slice(t + 1), i = _.get(n);
604
+ if (!i) {
605
+ console.warn(`[${s.id}] no handler for ${JSON.stringify(n)}`);
606
+ return;
607
+ }
608
+ for (let e of i) new Promise((t) => {
609
+ t(e(r));
610
+ }).catch((e) => {
611
+ console.error(`[${s.id}] handling ${n} failed:`, e);
612
+ });
613
+ });
614
+ }
615
+ };
616
+ }, E = /* @__PURE__ */ new Set([
617
+ "file_bin_open",
618
+ "file_text_open_read",
619
+ "file_text_open_write",
620
+ "file_text_open_append"
621
+ ]), D = /* @__PURE__ */ new Set([
622
+ "file_bin_read_byte",
623
+ "file_text_read_real",
624
+ "file_text_read_string",
625
+ "buffer_read"
626
+ ]), O = /* @__PURE__ */ new Set([
627
+ "file_bin_write_byte",
628
+ "file_text_write_real",
629
+ "file_text_write_string",
630
+ "buffer_write"
631
+ ]), k = (e) => JSON.stringify(e ?? null), A = function(e) {
632
+ return e.k === "var" && (e.scope === "local" || e.scope === "self") ? `${e.scope}:${e.name}` : null;
633
+ }, j = function(e, t) {
634
+ if (e.k === "asset" && e.kind === "object") return e.name;
635
+ if (e.k === "number" && Number.isInteger(e.value) && e.value >= 0 && e.value < 1e5) return `(${e.value})`;
636
+ let n = A(e);
637
+ return n ? t.aliases.get(n) ?? null : null;
638
+ }, M = class {
639
+ facts;
640
+ summaries;
641
+ current = null;
642
+ constructor(e, t) {
643
+ this.summaries = t, this.facts = {
644
+ script: e,
645
+ vars: /* @__PURE__ */ new Map(),
646
+ aliases: /* @__PURE__ */ new Map(),
647
+ argAliases: /* @__PURE__ */ new Map(),
648
+ handles: /* @__PURE__ */ new Map(),
649
+ writes: [],
650
+ positionReads: [],
651
+ roomGotos: [],
652
+ globalAssigns: [],
653
+ counted: /* @__PURE__ */ new Set(),
654
+ calls: /* @__PURE__ */ new Set(),
655
+ returns: /* @__PURE__ */ new Set(),
656
+ globalReads: /* @__PURE__ */ new Set()
657
+ };
658
+ }
659
+ tags(e, t) {
660
+ let n = /* @__PURE__ */ new Set(), r = (e) => this.tags(e, t).forEach((e) => n.add(e));
661
+ switch (e.k) {
662
+ case "arg":
663
+ e.index !== null && n.add(`arg:${e.index}`);
664
+ break;
665
+ case "var":
666
+ if (e.scope === "builtin" && e.name === "room") n.add("room");
667
+ else if (e.scope === "builtin" && t && (e.name === "x" || e.name === "y")) n.add(`${e.name}:${t}`);
668
+ else if (e.scope === "global") n.add(`read:global:${e.name}`), this.facts.globalReads.add(e.name);
669
+ else {
670
+ let t = A(e);
671
+ this.facts.vars.get(t ?? "")?.forEach((e) => n.add(e));
672
+ }
673
+ break;
674
+ case "field": {
675
+ let t = j(e.of, this.facts);
676
+ t && (e.name === "x" || e.name === "y") ? n.add(`${e.name}:${t}`) : r(e.of);
677
+ break;
678
+ }
679
+ case "index":
680
+ r(e.of);
681
+ break;
682
+ case "op":
683
+ e.args.forEach(r);
684
+ break;
685
+ case "call": {
686
+ let t = this.readChannel(e);
687
+ t && n.add(t.startsWith("handle:") ? t : `read:${t}`);
688
+ for (let t of this.summaries.get(e.name)?.returns ?? []) {
689
+ let r = t.handle === null ? t.channel : this.handleChannel(e.args[t.handle]);
690
+ r && n.add(r.startsWith("handle:") ? r : `read:${r}`);
691
+ }
692
+ e.args.forEach(r);
693
+ break;
694
+ }
695
+ }
696
+ return n;
697
+ }
698
+ handleChannel(e) {
699
+ if (!e) return null;
700
+ if (e.k === "arg" && e.index !== null) return `handle:${e.index}`;
701
+ let t = A(e);
702
+ return t && this.facts.argAliases.has(t) ? `handle:${this.facts.argAliases.get(t)}` : t && this.facts.handles.get(t) || "file:?";
703
+ }
704
+ readChannel(e) {
705
+ let [t, n, r] = e.args;
706
+ return D.has(e.name) && t ? this.handleChannel(t) : e.name.startsWith("ini_read_") ? `ini:${k(t)}/${k(n)}` : /^registry_read_.*_ext$/u.test(e.name) ? `registry:${k(t)}/${k(n)}` : e.name.startsWith("registry_read_") ? `registry:${k(t)}` : e.name === "ds_map_find_value" ? `map:${k(n)}` : null;
707
+ }
708
+ writeOf(e) {
709
+ let [t, n, r] = e.args;
710
+ if (O.has(e.name) && t) {
711
+ let i = e.name === "buffer_write" ? r : n;
712
+ return i ? {
713
+ channel: this.handleChannel(t),
714
+ value: i
715
+ } : null;
716
+ }
717
+ return e.name.startsWith("ini_write_") && r ? {
718
+ channel: `ini:${k(t)}/${k(n)}`,
719
+ value: r
720
+ } : /^registry_write_.*_ext$/u.test(e.name) && r ? {
721
+ channel: `registry:${k(t)}/${k(n)}`,
722
+ value: r
723
+ } : e.name.startsWith("registry_write_") && n ? {
724
+ channel: `registry:${k(t)}`,
725
+ value: n
726
+ } : (e.name === "ds_map_add" || e.name === "ds_map_replace") && r ? {
727
+ channel: `map:${k(n)}`,
728
+ value: r
729
+ } : null;
730
+ }
731
+ block(e, t, n) {
732
+ for (let r of e) this.statement(r, t, n);
733
+ }
734
+ statement(e, t, n) {
735
+ switch (this.current = e, e.k) {
736
+ case "assign":
737
+ this.assign(e.target, e.op, e.value, t, n);
738
+ break;
739
+ case "call":
740
+ this.call(e.call, t, n);
741
+ break;
742
+ case "if":
743
+ this.tags(e.cond, n), this.expressionCalls(e.cond, t, n), this.block(e.then, !0, n), this.block(e.else, !0, n);
744
+ break;
745
+ case "with": {
746
+ let t = j(e.target, this.facts);
747
+ this.block(e.body, !0, t ?? n);
748
+ break;
749
+ }
750
+ case "loop":
751
+ this.block(e.body, !0, n);
752
+ break;
753
+ case "switch":
754
+ this.tags(e.value, n);
755
+ for (let t of e.bodies) this.block(t, !0, n);
756
+ break;
757
+ case "exit": e.value && (this.expressionCalls(e.value, t, n), this.tags(e.value, n).forEach((e) => this.facts.returns.add(e)));
758
+ }
759
+ }
760
+ expressionCalls(e, t, n) {
761
+ e.k === "call" ? this.call(e, t, n) : e.k === "op" ? e.args.forEach((e) => this.expressionCalls(e, t, n)) : (e.k === "field" || e.k === "index") && this.expressionCalls(e.of, t, n);
762
+ }
763
+ call(e, t, n) {
764
+ e.kind === "script" && this.facts.calls.add(e.name);
765
+ let r = this.writeOf(e);
766
+ r && this.facts.writes.push({
767
+ channel: r.channel,
768
+ tags: this.tags(r.value, n),
769
+ stmt: this.current
770
+ });
771
+ for (let t of this.summaries.get(e.name)?.writes ?? []) {
772
+ let r = e.args[t.value], i = t.handle === null ? t.channel : this.handleChannel(e.args[t.handle]);
773
+ r && i && this.facts.writes.push({
774
+ channel: i,
775
+ tags: this.tags(r, n),
776
+ stmt: this.current
777
+ });
778
+ }
779
+ if (e.name === "room_goto" && e.args[0] && this.facts.roomGotos.push(this.tags(e.args[0], n)), e.name === "instance_create" && e.args.length >= 3) {
780
+ let t = j(e.args[2], this.facts);
781
+ if (t) {
782
+ let r = this.tags(e.args[0], n);
783
+ this.tags(e.args[1], n).forEach((e) => r.add(e)), this.facts.positionReads.push({
784
+ object: t,
785
+ tags: r
786
+ });
787
+ }
788
+ }
789
+ e.args.forEach((e) => this.expressionCalls(e, t, n));
790
+ }
791
+ assign(e, t, n, r, i) {
792
+ this.expressionCalls(n, r, i);
793
+ let a = this.tags(n, i);
794
+ if (n.k === "call" && E.has(n.name)) {
795
+ let t = A(e);
796
+ t && this.facts.handles.set(t, `file:${k(n.args[0])}`);
797
+ }
798
+ let o = A(e);
799
+ if (o) {
800
+ let e = this.facts.vars.get(o) ?? /* @__PURE__ */ new Set();
801
+ a.forEach((t) => e.add(t)), this.facts.vars.set(o, e);
802
+ let t = j(n, this.facts);
803
+ t && n.k !== "number" && this.facts.aliases.set(o, t), n.k === "arg" && n.index !== null && this.facts.argAliases.set(o, n.index);
804
+ return;
805
+ }
806
+ if (e.k === "var" && e.scope === "global") {
807
+ t === "=" ? (this.facts.globalAssigns.push({
808
+ name: e.name,
809
+ tags: a,
810
+ topLevel: !r
811
+ }), this.facts.writes.push({
812
+ channel: `global:${e.name}`,
813
+ tags: a,
814
+ stmt: this.current
815
+ })) : (t === "+=" || t === "-=") && this.facts.counted.add(e.name);
816
+ return;
817
+ }
818
+ if (e.k === "index" && e.of.k === "var" && e.of.scope === "global") {
819
+ t === "=" ? this.facts.writes.push({
820
+ channel: `global:${e.of.name}`,
821
+ tags: a,
822
+ stmt: this.current
823
+ }) : (t === "+=" || t === "-=") && this.facts.counted.add(e.of.name);
824
+ return;
825
+ }
826
+ let s = e.k === "field" && (e.name === "x" || e.name === "y") ? j(e.of, this.facts) : e.k === "var" && e.scope === "builtin" && (e.name === "x" || e.name === "y") ? i : null;
827
+ s && this.facts.positionReads.push({
828
+ object: s,
829
+ tags: a
830
+ });
831
+ }
832
+ }, N = function(e, t, n) {
833
+ let r = new M(e, n);
834
+ r.block(t, !1, null);
835
+ let i = new M(e, n);
836
+ return i.facts.vars = r.facts.vars, i.facts.aliases = r.facts.aliases, i.facts.argAliases = r.facts.argAliases, i.facts.handles = r.facts.handles, i.block(t, !1, null), i.facts;
837
+ }, P = function(e) {
838
+ return [...e].filter((e) => e.startsWith("read:")).map((e) => e.slice(5));
839
+ }, F = function(e, t) {
840
+ return [...e].filter((e) => e.startsWith(`${t}:`)).map((e) => e.slice(2));
841
+ }, I = function(e, t) {
842
+ return [...e].filter((e) => e.startsWith(t)).map((e) => Number(e.slice(t.length)));
843
+ }, L = function(e) {
844
+ let t = [];
845
+ for (let n of e.writes) {
846
+ let e = n.channel.startsWith("handle:") ? Number(n.channel.slice(7)) : null;
847
+ if (!n.channel.startsWith("global:")) for (let r of I(n.tags, "arg:")) t.push({
848
+ value: r,
849
+ handle: e,
850
+ channel: e === null ? n.channel : null
851
+ });
852
+ }
853
+ return {
854
+ writes: t,
855
+ returns: [...I(e.returns, "handle:").map((e) => ({
856
+ handle: e,
857
+ channel: null
858
+ })), ...P(e.returns).filter((e) => !e.startsWith("global:")).map((e) => ({
859
+ handle: null,
860
+ channel: e
861
+ }))]
862
+ };
863
+ }, R = 50, z = function(e) {
864
+ let t = /* @__PURE__ */ new Map(), n = [], r = "";
865
+ for (let i = 0; i < R; i += 1) {
866
+ let i = t;
867
+ n = e.scripts.map((e) => N(e.name, e.body, i)), t = new Map(n.map((e) => [e.script, L(e)]));
868
+ let a = JSON.stringify([...t]);
869
+ if (a === r) break;
870
+ r = a;
871
+ }
872
+ return n;
873
+ }, B = function(e, t, n) {
874
+ let r = /* @__PURE__ */ new Set(), i = /* @__PURE__ */ new Set(), a = (e) => {
875
+ switch (e.k) {
876
+ case "var":
877
+ e.scope === "global" && r.add(e.name);
878
+ break;
879
+ case "field":
880
+ a(e.of);
881
+ break;
882
+ case "index":
883
+ a(e.of), e.at.forEach(a);
884
+ break;
885
+ case "op":
886
+ e.args.forEach(a);
887
+ break;
888
+ case "call": e.kind === "script" && i.add(e.name), e.args.forEach(a);
889
+ }
890
+ }, o = (e) => {
891
+ for (let t of e) switch (t.k) {
892
+ case "assign":
893
+ a(t.target), a(t.value);
894
+ break;
895
+ case "call":
896
+ a(t.call);
897
+ break;
898
+ case "if":
899
+ a(t.cond), o(t.then), o(t.else);
900
+ break;
901
+ case "with":
902
+ a(t.target), o(t.body);
903
+ break;
904
+ case "loop":
905
+ o(t.body);
906
+ break;
907
+ case "switch":
908
+ a(t.value), t.bodies.forEach(o);
909
+ break;
910
+ case "exit": t.value && a(t.value);
911
+ }
912
+ };
913
+ o(e);
914
+ for (let e of i) {
915
+ if (n.has(e)) continue;
916
+ let r = t.scripts.find((t) => t.name === e);
917
+ r && o(r.body);
918
+ }
919
+ return r;
920
+ }, V = function(e) {
921
+ if (e.scripts.length === 0) return {
922
+ ok: !1,
923
+ error: "this game's code could not be read"
924
+ };
925
+ let t = z(e), n = t.flatMap((e) => {
926
+ let t = /* @__PURE__ */ new Set();
927
+ e.writes.forEach((e) => e.tags.forEach((e) => t.add(e)));
928
+ let n = F(t, "x"), r = new Set(F(t, "y")), i = n.filter((e) => r.has(e));
929
+ if (!t.has("room") || i.length === 0) return [];
930
+ let a = e.writes.filter((e) => e.tags.has("room") || [...e.tags].some((e) => /^[xy]:/u.test(e))).map((e) => e.channel);
931
+ return [{
932
+ facts: e,
933
+ players: i,
934
+ channels: [...new Set(a)]
935
+ }];
936
+ }), r = t.flatMap((e) => {
937
+ let t = e.positionReads.filter((e) => P(e.tags).length > 0), n = e.roomGotos.filter((e) => P(e).length > 0);
938
+ if (t.length === 0 || n.length === 0) return [];
939
+ let r = /* @__PURE__ */ new Set();
940
+ return t.forEach((e) => P(e.tags).forEach((e) => r.add(e))), n.forEach((e) => P(e).forEach((e) => r.add(e))), [{
941
+ facts: e,
942
+ players: [...new Set(t.map((e) => e.object))],
943
+ channels: [...r]
944
+ }];
945
+ }), i = n.filter((e) => r.some((t) => t.channels.some((t) => e.channels.includes(t)))), a = r.filter((e) => i.some((t) => t.channels.some((t) => e.channels.includes(t)))), o = new Set(a.map((e) => e.facts.script)), s = a.filter((e) => ![...e.facts.calls].some((t) => t !== e.facts.script && o.has(t)));
946
+ if (i.length === 0 || s.length === 0) return {
947
+ ok: !1,
948
+ error: n.length === 0 ? "no script stores the room and a player's position" : r.length === 0 ? "no script puts a stored position back into a player and changes room" : "no save routine and load routine use the same storage"
949
+ };
950
+ let c = [.../* @__PURE__ */ new Set([...i.flatMap((e) => e.players), ...s.flatMap((e) => e.players)])], l = new Set(c), u = /* @__PURE__ */ new Set([...i.map((e) => e.facts.script), ...s.map((e) => e.facts.script)]), d = /* @__PURE__ */ new Set();
951
+ t.filter((e) => !u.has(e.script)).forEach((e) => e.counted.forEach((e) => d.add(e)));
952
+ for (let t of e.objects) for (let e of Object.values(t.events)) e && N(t.name, e, /* @__PURE__ */ new Map()).counted.forEach((e) => d.add(e));
953
+ let f = new Set(s.flatMap((e) => e.facts.globalAssigns.filter((e) => e.topLevel && P(e.tags).length > 0 && !d.has(e.name)).map((e) => e.name))), p = i.flatMap((e) => e.facts.writes).filter((e) => [...e.tags].some((e) => /^[xy]:/u.test(e)) && e.stmt?.at !== void 0), m = [...new Map(p.map((e) => [e.stmt.at, e.stmt])).values()];
954
+ if (m.length === 0) return {
955
+ ok: !1,
956
+ error: "no save routine stores the position at a line that can be addressed"
957
+ };
958
+ let h = /* @__PURE__ */ new Set();
959
+ for (let e of i) for (let t of e.facts.globalReads) h.add(t);
960
+ let g = [...f].filter((e) => h.has(e)), _ = /* @__PURE__ */ new Set();
961
+ for (let t of e.objects) l.has(t.name) && t.events.create && B(t.events.create, e, u).forEach((e) => _.add(e));
962
+ return {
963
+ ok: !0,
964
+ value: {
965
+ players: c,
966
+ positionWrites: m,
967
+ loads: s.map((e) => e.facts.script),
968
+ carried: g,
969
+ createReads: g.filter((e) => _.has(e))
970
+ }
971
+ };
972
+ }, H = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABWElEQVR4nM2VS3LCMAyGvew9ePXBIXoJLpI7dF9KIVDgGqw5RfPY9CY144CKKiJHsglh8U2UzNj/ZyESY/Z7AzwYcxNwJg23N4IXyLKsAi/I8/wPd18URQWu4b4sy4tAeJYkiUwAh9IaaBJwV0ywQN3p67qAQ+np1QL0tFwHfAJRHQAJ/BPQ1tP6qgJ1MwDXugHEdfQQdvY3xBLOFNsGwu5zfy8iCP+1byp+vl/FuP3T6cBu0/F/CY2AJjBKwFrDbsIhF3iRCWAJHGQnkwpaN4mAwEYj4LgIPeGrvQKL50CBhlCpwFojgMOlApyEWAAWgEAorMD8SSYAi7Qd2DUIfHUu8PnYnsAuVkDz4gmdAbUAne7YDqxmI7kAlfDVvvDWBHCoVGA5G+oE6CxwodJvwfIjQOCaX8MogRjOAgNeoG1cuJNgBRbvPYb+memRVMwxFII77QChyj4ABNYZj7JHZ24AAAAASUVORK5CYII=", U = 8, W = {
973
+ scale: .85,
974
+ alpha: .6,
975
+ swing: 8,
976
+ pace: 800
977
+ }, G = {
978
+ state: "global.quicksave_state",
979
+ room: "global.quicksave_room",
980
+ x: "global.quicksave_x",
981
+ y: "global.quicksave_y",
982
+ markerX: "global.quicksave_marker_x",
983
+ markerY: "global.quicksave_marker_y",
984
+ hasAnchor: "global.quicksave_has_anchor",
985
+ anchorX: "global.quicksave_anchor_x",
986
+ anchorY: "global.quicksave_anchor_y",
987
+ applying: "global.quicksave_applying",
988
+ carried: (e) => `global.quicksave_carried${e}`
989
+ }, K = function(e, t) {
990
+ let { players: n, carried: r, createReads: i, positionWrites: a, loads: o } = t, s = e.scratchPath("state.txt"), c = e.resource.sprite.create("save", { origin: {
991
+ x: 16,
992
+ y: 16
993
+ } });
994
+ c.load([H]), e.resource.script.create({
995
+ name: "player",
996
+ body: () => `global.quicksave_found = noone;
997
+ ${n.map((e) => `with (${e}) {
998
+ if (global.quicksave_found == noone || id > global.quicksave_found) global.quicksave_found = id;
999
+ }`).join("\n")}
1000
+ return global.quicksave_found;`
1001
+ }), e.resource.script.create({
1002
+ name: "grounded",
1003
+ args: ["who"],
1004
+ body: ({ a: e }) => `global.quicksave_grounded = 0;
1005
+ with (${e.who}) {
1006
+ if (vspeed == 0 && (!place_free(x, y + 1) || !place_free(x, y - 1))) global.quicksave_grounded = 1;
1007
+ };
1008
+ return global.quicksave_grounded;`
1009
+ });
1010
+ let l = (e) => `if (is_string(${e})) {
1011
+ file_text_write_string(qs_file, "s"); file_text_writeln(qs_file); file_text_write_string(qs_file, ${e});
1012
+ } else {
1013
+ file_text_write_string(qs_file, "r"); file_text_writeln(qs_file); file_text_write_real(qs_file, ${e});
1014
+ }
1015
+ file_text_writeln(qs_file);`, u = (e) => `qs_kind = file_text_read_string(qs_file); file_text_readln(qs_file);
1016
+ if (qs_kind == "s") ${e} = file_text_read_string(qs_file); else ${e} = file_text_read_real(qs_file);
1017
+ file_text_readln(qs_file);`, d = [
1018
+ G.state,
1019
+ G.room,
1020
+ G.x,
1021
+ G.y,
1022
+ G.markerX,
1023
+ G.markerY,
1024
+ G.hasAnchor,
1025
+ G.anchorX,
1026
+ G.anchorY
1027
+ ];
1028
+ e.resource.script.create({
1029
+ name: "persist",
1030
+ body: () => `var qs_file;
1031
+ if (${G.state} == 0) {
1032
+ if (file_exists(${s})) file_delete(${s});
1033
+ exit;
1034
+ }
1035
+ qs_file = file_text_open_write(${s});
1036
+ ${d.map((e) => `file_text_write_real(qs_file, ${e}); file_text_writeln(qs_file);`).join("\n")}
1037
+ ${r.map((e, t) => l(G.carried(t))).join("\n")}
1038
+ file_text_close(qs_file);`
1039
+ }), e.resource.script.create({
1040
+ name: "reset",
1041
+ body: () => `${[
1042
+ ...d,
1043
+ G.applying,
1044
+ ...r.map((e, t) => G.carried(t))
1045
+ ].map((e) => `${e} = 0;`).join("\n")}`
1046
+ }), e.resource.script.create({
1047
+ name: "restore",
1048
+ body: ({ call: e }) => `var qs_file, qs_kind;
1049
+ ${e("reset")};
1050
+ if (!file_exists(${s})) exit;
1051
+ qs_file = file_text_open_read(${s});
1052
+ ${d.map((e) => `${e} = file_text_read_real(qs_file); file_text_readln(qs_file);`).join("\n")}
1053
+ ${r.map((e, t) => u(G.carried(t))).join("\n")}
1054
+ file_text_close(qs_file);`
1055
+ });
1056
+ for (let t of a) e.resource.script.insert({
1057
+ at: t,
1058
+ place: "after",
1059
+ body: ({ call: e, send: t }) => `
1060
+ var qs_player;
1061
+ if (!${G.applying}) {
1062
+ qs_player = ${e("player")};
1063
+ if (qs_player != noone) {
1064
+ ${G.anchorX} = qs_player.x;
1065
+ ${G.anchorY} = qs_player.y;
1066
+ ${G.hasAnchor} = 1;
1067
+ }
1068
+ if (${G.state} != 0) {
1069
+ ${G.state} = 0;
1070
+ ${t("cleared", "\"a real save\"")}
1071
+ }
1072
+ ${e("persist")};
1073
+ }
1074
+ `
1075
+ });
1076
+ let f = i.map((e) => `if (global.${e} != ${G.carried(r.indexOf(e))}) qs_rebuild = 1;`).join("\n ");
1077
+ for (let t of o) e.resource.script.hook(t, ({ original: e, call: n, send: i }) => `
1078
+ var qs_result, qs_player, qs_near, qs_rebuild;
1079
+ qs_result = ${e.call()};
1080
+ qs_player = ${n("player")};
1081
+ if (qs_player != noone) {
1082
+ qs_near = ${G.hasAnchor}
1083
+ && abs(qs_player.x - ${G.anchorX}) <= ${U}
1084
+ && abs(qs_player.y - ${G.anchorY}) <= ${U};
1085
+ if (${G.state} == 1 && qs_near) {
1086
+ ${G.applying} = 1;
1087
+ qs_rebuild = 0;
1088
+ ${f}
1089
+ ${r.map((e, t) => `global.${e} = ${G.carried(t)};`).join("\n ")}
1090
+ room_goto(${G.room});
1091
+ with (qs_player) {
1092
+ if (qs_rebuild) event_perform(ev_create, 0);
1093
+ x = ${G.x};
1094
+ y = ${G.y};
1095
+ xprevious = x;
1096
+ yprevious = y;
1097
+ hspeed = 0;
1098
+ vspeed = 0;
1099
+ }
1100
+ ${G.applying} = 0;
1101
+ ${i("restored", `"by ${t}"`)}
1102
+ } else {
1103
+ if (${G.state} == 1) ${i("dropped", "\"the game loaded a different save\"")}
1104
+ ${G.state} = 0;
1105
+ ${G.anchorX} = qs_player.x;
1106
+ ${G.anchorY} = qs_player.y;
1107
+ ${G.hasAnchor} = 1;
1108
+ }
1109
+ ${n("persist")};
1110
+ }
1111
+ return qs_result;
1112
+ `);
1113
+ e.resource.object.create({
1114
+ name: "quicksave",
1115
+ vars: ["standing", "who"],
1116
+ events: {
1117
+ create: ({ v: e, call: t }) => `
1118
+ ${t("restore")};
1119
+ ${e.standing} = 0;
1120
+ `,
1121
+ step: ({ v: e, call: t, send: n }) => `
1122
+ ${e.who} = ${t("player")};
1123
+ // One behind the player: the only depth that means the same thing in every game.
1124
+ if (${e.who} != noone) depth = ${e.who}.depth + 1;
1125
+ // Two steps, so bumping a ceiling mid-jump is not standing on it.
1126
+ if (${e.who} != noone && ${t("grounded", e.who)}) ${e.standing} += 1;
1127
+ else ${e.standing} = 0;
1128
+
1129
+ if (keyboard_check_pressed(ord("X"))) {
1130
+ if (${G.state} != 0) {
1131
+ ${n("refused", "\"a quicksave is already placed, or was cancelled until the next respawn\"")}
1132
+ } else if (!${G.hasAnchor}) {
1133
+ ${n("refused", "\"the game has not saved or loaded yet\"")}
1134
+ } else if (${e.standing} < 2) {
1135
+ ${n("refused", "\"not standing on anything\"")}
1136
+ } else {
1137
+ ${G.room} = room;
1138
+ ${G.x} = ${e.who}.x;
1139
+ ${G.y} = ${e.who}.y;
1140
+ ${G.markerX} = (${e.who}.bbox_left + ${e.who}.bbox_right) / 2;
1141
+ ${G.markerY} = (${e.who}.bbox_top + ${e.who}.bbox_bottom) / 2;
1142
+ ${r.map((e, t) => `${G.carried(t)} = global.${e};`).join("\n ")}
1143
+ ${G.state} = 1;
1144
+ ${t("persist")};
1145
+ ${n("placed", `room_get_name(room) + " " + string(${G.x}) + "," + string(${G.y})`)}
1146
+ }
1147
+ }
1148
+
1149
+ if (keyboard_check_pressed(ord("C")) && ${G.state} == 1) {
1150
+ ${G.state} = 2;
1151
+ ${t("persist")};
1152
+ ${n("cancelled")}
1153
+ }
1154
+ `,
1155
+ draw: () => `
1156
+ if (${G.state} == 1 && room == ${G.room} && ${c.id} >= 0) {
1157
+ draw_sprite_ext(${c.id}, 0, ${G.markerX}, ${G.markerY}, ${W.scale}, ${W.scale},
1158
+ ${W.swing} * sin(current_time / ${W.pace}), c_white, ${W.alpha});
1159
+ }
1160
+ `
1161
+ },
1162
+ receive: { fresh: ({ call: e }) => `
1163
+ ${e("reset")};
1164
+ ${e("persist")};
1165
+ ` }
1166
+ }), e.onStart(() => e.send("fresh"));
1167
+ for (let t of [
1168
+ "placed",
1169
+ "cancelled",
1170
+ "restored",
1171
+ "cleared",
1172
+ "dropped",
1173
+ "refused"
1174
+ ]) e.on(t, (e) => console.log(`[quicksave] ${t}${e ? `: ${e}` : ""}`));
1175
+ }, q = T({
1176
+ id: "fruit-pie.quicksave",
1177
+ name: "Quicksave",
1178
+ version: "1.0.0"
1179
+ }, (e) => {
1180
+ let t = V(e.content);
1181
+ t.ok ? K(e, t.value) : console.warn(`[quicksave] this game is not supported: ${t.error}`);
1182
+ });
1183
+ //#endregion
1184
+ export { q as default };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@fruit-pie/quicksave",
3
+ "version": "0.1.0",
4
+ "description": "Save anywhere between two of the game's saves, without touching its save files.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "dev": "concurrently -k \"vite build --watch\" \"vite preview\"",
9
+ "typecheck": "tsc --noEmit",
10
+ "prepublishOnly": "vite build",
11
+ "test": "node --test \"src/**/*.test.ts\""
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "fruitPieMod": {
20
+ "manifestId": "fruit-pie.quicksave",
21
+ "name": "Quicksave",
22
+ "entry": "dist/index.js",
23
+ "apiVersion": 2
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^24.13.4",
27
+ "concurrently": "^10.0.5",
28
+ "typescript": "^7.0.2",
29
+ "vite": "^8.3.0"
30
+ },
31
+ "dependencies": {
32
+ "@fruit-pie/detect": "^1.0.0",
33
+ "@fruit-pie/sdk": "^1.1.0"
34
+ }
35
+ }