gamesteel 0.4.1 → 0.5.2
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.
- package/LICENSE +2 -2
- package/README.md +2 -2
- package/dist/THIRD-PARTY-NOTICES.txt +70 -0
- package/dist/gamesteel.js +25438 -9896
- package/dist/pkgs/audio/dist/worker.js +648 -20
- package/dist/pkgs/model/dist/worker.js +648 -20
- package/dist/pkgs/paint/dist/worker.js +648 -20
- package/dist/pkgs/studio/dist/exported-player.js +12826 -11619
- package/dist/pkgs/studio/dist/server/playtest-worker.js +2653 -478
- package/dist/resolved/@gamesteel/online/worker/runtime.js +6825 -0
- package/package.json +17 -12
|
@@ -26221,7 +26221,7 @@ function shouldInterruptAfterDeadline(deadline) {
|
|
|
26221
26221
|
var import_sucrase2 = __toESM(require_dist2(), 1);
|
|
26222
26222
|
|
|
26223
26223
|
// ../core/dist/version.js
|
|
26224
|
-
var ENGINE_VERSION = "0.9.
|
|
26224
|
+
var ENGINE_VERSION = "0.9.2";
|
|
26225
26225
|
|
|
26226
26226
|
// ../core/dist/home.js
|
|
26227
26227
|
import { existsSync, renameSync, symlinkSync } from "node:fs";
|
|
@@ -29858,6 +29858,170 @@ function impulseFor(height, gravity, terminalFall) {
|
|
|
29858
29858
|
return impulseUpTo(height, gravity, terminalFall);
|
|
29859
29859
|
}
|
|
29860
29860
|
|
|
29861
|
+
// ../core/dist/script/ads.js
|
|
29862
|
+
var AD_KINDS = ["break", "reward"];
|
|
29863
|
+
var NO_ADS_YET = { pending: null, lastBreak: null, lastPress: null, lastPlay: null, played: false, scene: null, ready: AD_KINDS, banner: null };
|
|
29864
|
+
var PLACEMENT = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
29865
|
+
var PLACEMENT_WORDS = "a placement is named with 1 to 40 lowercase letters, digits and hyphens, like level-end or extra-life";
|
|
29866
|
+
var adsOf = (kept) => kept ?? NO_ADS_YET;
|
|
29867
|
+
var usesAds = (sources) => sources.some((source2) => /\bads\s*\.\s*(break|reward|ready|banner)\b/.test(source2));
|
|
29868
|
+
var BREAK_GAP_TICKS = 3600;
|
|
29869
|
+
var REWARD_PRESS_TICKS = 60;
|
|
29870
|
+
var PLAY_PRESS_TICKS = 30;
|
|
29871
|
+
var SCENE_SETTLE_TICKS = 120;
|
|
29872
|
+
var BANNER_EDGE_PX = 8;
|
|
29873
|
+
var AD_WORDS = {
|
|
29874
|
+
launch: "a break before the player's first input is refused: AdMob forbids ads at launch",
|
|
29875
|
+
backToBack: "a break less than 60 seconds after the last one is refused: AdMob forbids ads back to back",
|
|
29876
|
+
unchosen: "a reward the player did not choose is refused: AdMob rewards only an ad the player asks for, with a press just before",
|
|
29877
|
+
showing: "an ad is already showing",
|
|
29878
|
+
children: "this game is made for children: it shows no ads",
|
|
29879
|
+
midPlay: "a break while the player is in the middle of play: AdMob and Google Play forbid surprise ads during play; put it at a natural pause",
|
|
29880
|
+
banner: (name) => `a banner beside the game's button '${name}': AdMob forbids ads where players tap; keep buttons away from the banner's edge`
|
|
29881
|
+
};
|
|
29882
|
+
function adRefusal(ads, kind, now, off) {
|
|
29883
|
+
if (off !== null)
|
|
29884
|
+
return off;
|
|
29885
|
+
if (ads.pending !== null)
|
|
29886
|
+
return AD_WORDS.showing;
|
|
29887
|
+
if (kind === "break") {
|
|
29888
|
+
if (!ads.played)
|
|
29889
|
+
return AD_WORDS.launch;
|
|
29890
|
+
if (ads.lastBreak !== null && now - ads.lastBreak < BREAK_GAP_TICKS)
|
|
29891
|
+
return AD_WORDS.backToBack;
|
|
29892
|
+
return null;
|
|
29893
|
+
}
|
|
29894
|
+
return ads.lastPress !== null && now - ads.lastPress <= REWARD_PRESS_TICKS ? null : AD_WORDS.unchosen;
|
|
29895
|
+
}
|
|
29896
|
+
var SIX = ["left", "right", "up", "down", "jump", "action"];
|
|
29897
|
+
var playButtons = (input) => (input.buttons ?? []).filter((name) => name !== "click" && !name.startsWith("click:"));
|
|
29898
|
+
var playHeld = (input) => SIX.some((button) => input[button]) || playButtons(input).length > 0;
|
|
29899
|
+
var playPressed = (input, before2) => SIX.some((button) => input[button] && !before2[button]) || playButtons(input).some((name) => !playButtons(before2).includes(name));
|
|
29900
|
+
var anyPressed = (input, before2) => playPressed(input, before2) || (input.buttons ?? []).some((name) => !(before2.buttons ?? []).includes(name)) || (input.touches?.length ?? 0) > (before2.touches?.length ?? 0) || input.typed !== void 0 && input.typed !== "";
|
|
29901
|
+
|
|
29902
|
+
// ../core/dist/script/online.js
|
|
29903
|
+
var ONLINE_STATES = ["online", "resting", "off"];
|
|
29904
|
+
var PUSH_RESULTS = ["granted", "denied", "unavailable"];
|
|
29905
|
+
var CALL_RESULTS = ["ok", "error", "busy", "resting", "refused"];
|
|
29906
|
+
var ONLINE_LIMITS = {
|
|
29907
|
+
asksPerTick: 32,
|
|
29908
|
+
dataBytes: 8192,
|
|
29909
|
+
callName: /^[a-zA-Z][a-zA-Z0-9_]{0,39}$/,
|
|
29910
|
+
room: /^[a-z0-9][a-z0-9-]{0,39}$/,
|
|
29911
|
+
/** A file a game's scripts upload, as JSON; the screen's picture is the page's, capped by the server's size. */
|
|
29912
|
+
fileBytes: 1e6,
|
|
29913
|
+
/** What a download gives a game, as JSON. */
|
|
29914
|
+
downloadBytes: 1e6
|
|
29915
|
+
};
|
|
29916
|
+
var TOO_MANY_ASKS = `a tick asks the game's server at most ${String(ONLINE_LIMITS.asksPerTick)} times: spread the calls over ticks`;
|
|
29917
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29918
|
+
var jsonBytes = (data) => {
|
|
29919
|
+
try {
|
|
29920
|
+
const text3 = JSON.stringify(data);
|
|
29921
|
+
return text3 === void 0 ? null : new TextEncoder().encode(text3).length;
|
|
29922
|
+
} catch {
|
|
29923
|
+
return null;
|
|
29924
|
+
}
|
|
29925
|
+
};
|
|
29926
|
+
var dataProblem = (data) => {
|
|
29927
|
+
const bytes = jsonBytes(data === void 0 ? null : data);
|
|
29928
|
+
if (bytes === null)
|
|
29929
|
+
return "online data is plain data: numbers, strings, booleans, null, lists and objects of them";
|
|
29930
|
+
return bytes > ONLINE_LIMITS.dataBytes ? `online data is at most 8,192 bytes as JSON, and this is ${String(bytes)}` : null;
|
|
29931
|
+
};
|
|
29932
|
+
var nameProblem = (name) => typeof name === "string" && ONLINE_LIMITS.callName.test(name) ? null : `a call's name is a letter, then up to 39 letters, digits and _, like postScore, not ${JSON.stringify(name)}`;
|
|
29933
|
+
var uploadProblem = (ask) => {
|
|
29934
|
+
const file = "file" in ask && ask["file"] !== void 0;
|
|
29935
|
+
if (file === (ask["screen"] === true))
|
|
29936
|
+
return "an upload is a file or the screen: give file data, or screen: true, not both";
|
|
29937
|
+
if (!file)
|
|
29938
|
+
return null;
|
|
29939
|
+
const bytes = jsonBytes(ask["file"]);
|
|
29940
|
+
if (bytes === null)
|
|
29941
|
+
return "a file is plain data: numbers, strings, booleans, null, lists and objects of them";
|
|
29942
|
+
return bytes > ONLINE_LIMITS.fileBytes ? `a file from a game's scripts is at most 1,000,000 bytes as JSON, and this is ${String(bytes)}` : null;
|
|
29943
|
+
};
|
|
29944
|
+
function askProblem(ask) {
|
|
29945
|
+
const kinds = "an online ask is a call, join, leave, send, upload, download or askPush";
|
|
29946
|
+
if (!isObject(ask))
|
|
29947
|
+
return kinds;
|
|
29948
|
+
switch (ask["kind"]) {
|
|
29949
|
+
case "call":
|
|
29950
|
+
return nameProblem(ask["name"]) ?? dataProblem(ask["data"]);
|
|
29951
|
+
case "upload":
|
|
29952
|
+
return nameProblem(ask["name"]) ?? dataProblem(ask["data"]) ?? uploadProblem(ask);
|
|
29953
|
+
case "download":
|
|
29954
|
+
return typeof ask["url"] === "string" && ask["url"].length <= 4096 && /^(https:\/\/|local:)/.test(ask["url"]) ? null : "a download's address is one the game's server gave, starting https://";
|
|
29955
|
+
case "askPush":
|
|
29956
|
+
return null;
|
|
29957
|
+
case "join":
|
|
29958
|
+
case "leave":
|
|
29959
|
+
case "send":
|
|
29960
|
+
if (typeof ask["room"] !== "string" || !ONLINE_LIMITS.room.test(ask["room"])) {
|
|
29961
|
+
return `a room's name is 1 to 40 lowercase letters, digits and hyphens, like lobby-1, not ${JSON.stringify(ask["room"])}`;
|
|
29962
|
+
}
|
|
29963
|
+
return ask["kind"] === "send" ? dataProblem(ask["data"]) : null;
|
|
29964
|
+
default:
|
|
29965
|
+
return kinds;
|
|
29966
|
+
}
|
|
29967
|
+
}
|
|
29968
|
+
var usesOnline = (sources) => sources.some((source2) => /\bonline\s*\.\s*(call|join|leave|send|state|upload|uploadScreen|download|askPush)\b/.test(source2));
|
|
29969
|
+
function isOnlineHeard(value) {
|
|
29970
|
+
if (!isObject(value))
|
|
29971
|
+
return false;
|
|
29972
|
+
switch (value["kind"]) {
|
|
29973
|
+
case "answer":
|
|
29974
|
+
return typeof value["id"] === "number" && Number.isInteger(value["id"]) && typeof value["name"] === "string" && CALL_RESULTS.includes(value["result"]) && (value["reason"] === void 0 || typeof value["reason"] === "string");
|
|
29975
|
+
case "message":
|
|
29976
|
+
return typeof value["room"] === "string" && value["data"] !== void 0;
|
|
29977
|
+
case "joined":
|
|
29978
|
+
case "left":
|
|
29979
|
+
case "full":
|
|
29980
|
+
return typeof value["room"] === "string";
|
|
29981
|
+
case "upload":
|
|
29982
|
+
return typeof value["id"] === "number" && Number.isInteger(value["id"]) && typeof value["name"] === "string" && CALL_RESULTS.includes(value["result"]) && (value["reason"] === void 0 || typeof value["reason"] === "string");
|
|
29983
|
+
case "download":
|
|
29984
|
+
return typeof value["id"] === "number" && Number.isInteger(value["id"]) && CALL_RESULTS.includes(value["result"]) && (value["reason"] === void 0 || typeof value["reason"] === "string");
|
|
29985
|
+
case "push":
|
|
29986
|
+
return PUSH_RESULTS.includes(value["result"]);
|
|
29987
|
+
case "pushOpened":
|
|
29988
|
+
return value["data"] !== void 0;
|
|
29989
|
+
default:
|
|
29990
|
+
return false;
|
|
29991
|
+
}
|
|
29992
|
+
}
|
|
29993
|
+
|
|
29994
|
+
// ../core/dist/script/purchases.js
|
|
29995
|
+
var PURCHASE_RESULTS = ["granted", "pending", "cancelled", "unavailable", "refused"];
|
|
29996
|
+
var PRODUCT_ID = /^[a-z0-9][a-z0-9_.]{0,39}$/;
|
|
29997
|
+
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29998
|
+
function purchaseAskProblem(ask) {
|
|
29999
|
+
if (!isObject2(ask))
|
|
30000
|
+
return "a purchase ask is a buy of a product or a restore";
|
|
30001
|
+
if (ask["kind"] === "restore")
|
|
30002
|
+
return null;
|
|
30003
|
+
if (ask["kind"] !== "buy")
|
|
30004
|
+
return "a purchase ask is a buy of a product or a restore";
|
|
30005
|
+
const id = ask["id"];
|
|
30006
|
+
return typeof id === "string" && PRODUCT_ID.test(id) ? null : `a product id is 1 to 40 lowercase letters, digits, _ and ., starting with a letter or digit, like coins_100, not ${JSON.stringify(id) ?? String(id)}`;
|
|
30007
|
+
}
|
|
30008
|
+
var isProduct = (value) => isObject2(value) && typeof value["id"] === "string" && typeof value["price"] === "string" && typeof value["name"] === "string";
|
|
30009
|
+
function isPurchaseHeard(value) {
|
|
30010
|
+
if (!isObject2(value))
|
|
30011
|
+
return false;
|
|
30012
|
+
switch (value["kind"]) {
|
|
30013
|
+
case "products":
|
|
30014
|
+
return Array.isArray(value["list"]) && value["list"].every(isProduct);
|
|
30015
|
+
case "bought":
|
|
30016
|
+
return typeof value["id"] === "string" && PURCHASE_RESULTS.includes(value["result"]) && (value["reason"] === void 0 || typeof value["reason"] === "string") && (value["test"] === void 0 || typeof value["test"] === "boolean");
|
|
30017
|
+
case "restored":
|
|
30018
|
+
return Array.isArray(value["ids"]) && value["ids"].every((id) => typeof id === "string");
|
|
30019
|
+
default:
|
|
30020
|
+
return false;
|
|
30021
|
+
}
|
|
30022
|
+
}
|
|
30023
|
+
var usesPurchases = (sources) => sources.some((source2) => /\bpurchases\s*\.\s*(buy|restore|products)\b/.test(source2));
|
|
30024
|
+
|
|
29861
30025
|
// ../core/dist/script/runtime.js
|
|
29862
30026
|
var import_sucrase = __toESM(require_dist2(), 1);
|
|
29863
30027
|
|
|
@@ -30761,6 +30925,33 @@ const __soundHow = (options, label) => {
|
|
|
30761
30925
|
}
|
|
30762
30926
|
return out
|
|
30763
30927
|
}
|
|
30928
|
+
/** Online (the online services spec): asked of the game's server through the page; what comes back is heard on a later
|
|
30929
|
+
* tick as an 'online' event. */
|
|
30930
|
+
const __online = Object.freeze({
|
|
30931
|
+
call(name, data) { return __call('onlineCall', { name, data: data === undefined ? null : data }) },
|
|
30932
|
+
join(room) { __call('onlineJoin', { room }) },
|
|
30933
|
+
leave(room) { __call('onlineLeave', { room }) },
|
|
30934
|
+
send(room, data) { __call('onlineSend', { room, data: data === undefined ? null : data }) },
|
|
30935
|
+
upload(name, file, data) { return __call('onlineUpload', { name, file: file === undefined ? null : file, data: data === undefined ? null : data }) },
|
|
30936
|
+
uploadScreen(name, data) { return __call('onlineUpload', { name, screen: true, data: data === undefined ? null : data }) },
|
|
30937
|
+
download(url) { return __call('onlineDownload', { url }) },
|
|
30938
|
+
askPush() { __call('onlineAskPush', {}) },
|
|
30939
|
+
get state() { return __call('onlineState', {}) },
|
|
30940
|
+
})
|
|
30941
|
+
/** Purchases (the online services spec, part 3): asked of the page, which asks the store; answered on a later tick as a
|
|
30942
|
+
* 'purchase' event. */
|
|
30943
|
+
const __purchases = Object.freeze({
|
|
30944
|
+
get products() { return __frozen(__revived(__call('purchaseProducts', {}))) },
|
|
30945
|
+
buy(id) { __call('purchaseBuy', { id }) },
|
|
30946
|
+
restore() { __call('purchaseRestore', {}) },
|
|
30947
|
+
})
|
|
30948
|
+
/** Ads (the ads spec): asked of the page, answered on a later tick as an 'ad' event. */
|
|
30949
|
+
const __ads = Object.freeze({
|
|
30950
|
+
break(placement) { __call('adBreak', { placement }) },
|
|
30951
|
+
reward(placement) { __call('adReward', { placement }) },
|
|
30952
|
+
ready(kind) { return __call('adReady', { kind }) === true },
|
|
30953
|
+
banner(position) { __call('adBanner', { position: position === undefined ? null : position }) },
|
|
30954
|
+
})
|
|
30764
30955
|
const __sound = {
|
|
30765
30956
|
play(name, options) { __call('sound', Object.assign({ name }, __soundHow(options, 'sound.play'))) },
|
|
30766
30957
|
loop(name, options) { return __call('soundLoop', Object.assign({ name }, __soundHow(options, 'sound.loop'))) },
|
|
@@ -31013,6 +31204,9 @@ const __game = {
|
|
|
31013
31204
|
get vars() { return __vars },
|
|
31014
31205
|
get turns() { return __turns },
|
|
31015
31206
|
get ui() { return __ui },
|
|
31207
|
+
get ads() { return __ads },
|
|
31208
|
+
get online() { return __online },
|
|
31209
|
+
get purchases() { return __purchases },
|
|
31016
31210
|
/** The game script's own fields, read only; null without one. */
|
|
31017
31211
|
get script() { return __frozen(__revived(__call('gameScript', null))) },
|
|
31018
31212
|
get input() { return __input },
|
|
@@ -31074,6 +31268,7 @@ const __game = {
|
|
|
31074
31268
|
restart(options) { __call('restart', { fade: __num(__opt(options).fade, null) }) },
|
|
31075
31269
|
flash(colour, seconds) { __call('screen', { flash: { color: __color(colour, 0xffffff), seconds: seconds === undefined ? 0.2 : seconds } }) },
|
|
31076
31270
|
fade(alpha, seconds, colour) { __call('screen', { fade: { alpha, seconds, color: __color(colour, 0x000000) } }) },
|
|
31271
|
+
openPrivacy() { __call('openPrivacy', null) },
|
|
31077
31272
|
save(key, value) { __call('save', { key, value: value === undefined ? null : value }) },
|
|
31078
31273
|
load(key) { return __call('load', { key }) },
|
|
31079
31274
|
get camera() { return __camera },
|
|
@@ -31513,7 +31708,7 @@ var FAILURES_BEFORE_PAUSE = 10;
|
|
|
31513
31708
|
var MEMORY_LIMIT = 32 * 1024 * 1024;
|
|
31514
31709
|
var INTERRUPTS_PER_CALL = 1e3;
|
|
31515
31710
|
var INTERRUPTS_PER_PHASE = 2e3;
|
|
31516
|
-
var STACK_BYTES =
|
|
31711
|
+
var STACK_BYTES = 128 * 1024;
|
|
31517
31712
|
var GAME_KEY = "__game";
|
|
31518
31713
|
var PRELUDE = `'use strict'
|
|
31519
31714
|
;(function () {
|
|
@@ -32204,7 +32399,8 @@ function build(sources, skipped) {
|
|
|
32204
32399
|
return { fields: fieldsOf(text3), error: null, ...wantReturn ? { returned } : {} };
|
|
32205
32400
|
} catch (error2) {
|
|
32206
32401
|
healthy = false;
|
|
32207
|
-
|
|
32402
|
+
const hostStack = error2 instanceof RangeError && /call stack/i.test(error2.message);
|
|
32403
|
+
return { ...failed(hostStack ? messageOf({ message: "stack overflow" }) : `the script broke the interpreter and was stopped: ${error2 instanceof Error ? error2.message : String(error2)}`), runaway: true };
|
|
32208
32404
|
} finally {
|
|
32209
32405
|
for (const handle of handles)
|
|
32210
32406
|
handle.dispose();
|
|
@@ -32276,6 +32472,7 @@ function mixAt(level2, real) {
|
|
|
32276
32472
|
return level2.from;
|
|
32277
32473
|
return level2.from + (level2.to - level2.from) * (real - level2.start) / level2.ticks;
|
|
32278
32474
|
}
|
|
32475
|
+
var NO_PURCHASES_YET = { products: [], pending: null };
|
|
32279
32476
|
var START_CLOCK = { real: 0, rate: 60, owed: 0, paused: false, stopUntil: 0, latched: [] };
|
|
32280
32477
|
var ticking = /* @__PURE__ */ new Map();
|
|
32281
32478
|
var drawing = /* @__PURE__ */ new Map();
|
|
@@ -32304,7 +32501,7 @@ function runtimeOf(sources, draw = false) {
|
|
|
32304
32501
|
function hasScripts(scripts) {
|
|
32305
32502
|
return scripts.sources.game !== null || Object.keys(scripts.sources.kinds).length > 0;
|
|
32306
32503
|
}
|
|
32307
|
-
function initialScripts(given, kinds, seed = 1, saved = {}, audio, fonts) {
|
|
32504
|
+
function initialScripts(given, kinds, seed = 1, saved = {}, audio, fonts, adsOff) {
|
|
32308
32505
|
const sources = {
|
|
32309
32506
|
kinds: { ...given.kinds },
|
|
32310
32507
|
game: given.game,
|
|
@@ -32350,7 +32547,14 @@ function initialScripts(given, kinds, seed = 1, saved = {}, audio, fonts) {
|
|
|
32350
32547
|
sequences: [],
|
|
32351
32548
|
clock: START_CLOCK,
|
|
32352
32549
|
...audio === void 0 ? {} : { audio },
|
|
32353
|
-
...fonts === void 0 ? {} : { fonts }
|
|
32550
|
+
...fonts === void 0 ? {} : { fonts },
|
|
32551
|
+
// A game whose scripts mention ads keeps them from the first tick, its presses known before it first asks.
|
|
32552
|
+
...usesAds([...Object.values(sources.kinds), sources.game ?? "", ...Object.values(sources.modules ?? {})]) ? { ads: NO_ADS_YET } : {},
|
|
32553
|
+
...adsOff === void 0 ? {} : { adsOff },
|
|
32554
|
+
// A game whose scripts mention online keeps its state and call count from the first tick; any other never has them.
|
|
32555
|
+
...usesOnline([...Object.values(sources.kinds), sources.game ?? "", ...Object.values(sources.modules ?? {})]) ? { onlineState: "off", nextCall: 1 } : {},
|
|
32556
|
+
// A game whose scripts mention purchases keeps its products from the first tick; any other never has them.
|
|
32557
|
+
...usesPurchases([...Object.values(sources.kinds), sources.game ?? "", ...Object.values(sources.modules ?? {})]) ? { purchases: NO_PURCHASES_YET } : {}
|
|
32354
32558
|
};
|
|
32355
32559
|
}
|
|
32356
32560
|
|
|
@@ -32659,7 +32863,7 @@ function numericProps(props) {
|
|
|
32659
32863
|
}
|
|
32660
32864
|
function load(ast, options = {}) {
|
|
32661
32865
|
const palette = paletteOf(ast);
|
|
32662
|
-
const scripts = options.scripts === void 0 ? null : initialScripts(options.scripts, new Set(ast.entities.map((e2) => e2.name)), options.seed, options.saved, options.audio, options.fonts);
|
|
32866
|
+
const scripts = options.scripts === void 0 ? null : initialScripts(options.scripts, new Set(ast.entities.map((e2) => e2.name)), options.seed, options.saved, options.audio, options.fonts, options.adsOff);
|
|
32663
32867
|
const defs = [];
|
|
32664
32868
|
const defIndexOf = /* @__PURE__ */ new Map();
|
|
32665
32869
|
const kindDefs = /* @__PURE__ */ new Map();
|
|
@@ -33163,6 +33367,71 @@ interface Mixer {
|
|
|
33163
33367
|
}
|
|
33164
33368
|
|
|
33165
33369
|
/** game.turns: whose turn it is, in an order of objects. */
|
|
33370
|
+
/** Ads, for a game in Google Play and the App Store, from its maker's own AdMob account (the guide's publish topic).
|
|
33371
|
+
* The page answers a break or a reward on a later tick, and every script hears it as onEvent('ad', { kind, placement,
|
|
33372
|
+
* result, reason? }): result is 'shown', 'rewarded', 'closed', 'unavailable' or 'refused'. Give a reward only on
|
|
33373
|
+
* 'rewarded'. In the editor an ad is a placeholder card; in browser play every ad is 'unavailable'. */
|
|
33374
|
+
interface Ads {
|
|
33375
|
+
/** A full-screen break at a natural pause, like a level's end: placement names it, like 'level-end'. Refused before
|
|
33376
|
+
* the player's first input, and less than 60 seconds after the last break. */
|
|
33377
|
+
break(placement: string): void
|
|
33378
|
+
/** An ad the player chose to watch for a reward, like 'extra-life': ask it right after the player's press on the
|
|
33379
|
+
* game's own offer. Refused with no press in the last second. */
|
|
33380
|
+
reward(placement: string): void
|
|
33381
|
+
/** Whether an ad of this kind can play now: show a reward's offer only while it is. Never in a game made for children. */
|
|
33382
|
+
ready(kind: 'break' | 'reward'): boolean
|
|
33383
|
+
/** A banner at the top or bottom of the screen, which the game's view makes room for; null hides it. */
|
|
33384
|
+
banner(position: 'top' | 'bottom' | null): void
|
|
33385
|
+
}
|
|
33386
|
+
|
|
33387
|
+
/** Real-money purchases in the game's store app (the guide's purchases topic): the products purchases.json declares,
|
|
33388
|
+
* bought through Apple's and Google's own in-app purchases. Each answer reaches every script on a later tick as
|
|
33389
|
+
* onEvent('purchase', heard), where heard is { kind: 'products', list }, { kind: 'bought', id, result, data?, reason?,
|
|
33390
|
+
* test? } or { kind: 'restored', ids }. Give what was bought only on result 'granted' (data is what the game's server
|
|
33391
|
+
* answered from onPurchase); 'pending' is not paid yet, and its answer comes again once it is. In a browser and a web
|
|
33392
|
+
* export, every buy is 'unavailable'. */
|
|
33393
|
+
interface Purchases {
|
|
33394
|
+
/** The products as the store shows them to this player: the price in their money, the name in their language; empty
|
|
33395
|
+
* until the store answers, and in a browser. */
|
|
33396
|
+
readonly products: readonly { readonly id: string; readonly price: string; readonly name: string }[]
|
|
33397
|
+
/** Buys a product by its id, like 'coins_100': the store's own sheet asks the player. One at a time: a second buy
|
|
33398
|
+
* while one waits for its answer is heard 'refused'. */
|
|
33399
|
+
buy(id: string): void
|
|
33400
|
+
/** Asks the store for the unlocks and subscriptions this player owns, heard as { kind: 'restored', ids }: Apple asks for
|
|
33401
|
+
* a Restore purchases button wherever unlocks are sold. */
|
|
33402
|
+
restore(): void
|
|
33403
|
+
}
|
|
33404
|
+
|
|
33405
|
+
/** The game's own server, for a game whose Online tab turned it on (the guide's online topic). Each ask goes to the
|
|
33406
|
+
* server through the page; what comes back reaches every script on a later tick as onEvent('online', heard), where
|
|
33407
|
+
* heard is { kind: 'answer', id, name, result, data?, reason? } (result 'ok', 'error', 'busy', 'resting' or 'refused'),
|
|
33408
|
+
* { kind: 'message', room, data }, or { kind: 'joined' | 'left' | 'full', room }. A tick never waits on the network. */
|
|
33409
|
+
interface Online {
|
|
33410
|
+
/** Asks the server's call by name, like 'postScore', with plain data (at most 8,192 bytes as JSON); answers the call's
|
|
33411
|
+
* number, which its answer carries. */
|
|
33412
|
+
call(name: string, data?: Plain): number
|
|
33413
|
+
/** Joins a live room by name (lowercase letters, digits and hyphens, like 'lobby-1'); heard as 'joined', or 'full'. */
|
|
33414
|
+
join(room: string): void
|
|
33415
|
+
leave(room: string): void
|
|
33416
|
+
/** Sends plain data to a room's code, which decides what everyone hears. */
|
|
33417
|
+
send(room: string, data: Plain): void
|
|
33418
|
+
/** Uploads a file (plain data, at most 1,000,000 bytes as JSON) to the game's storage through its server's call by name,
|
|
33419
|
+
* which gets { size, type, data } and answers what ctx.files.uploadUrl gives; heard as { kind: 'upload', id, name,
|
|
33420
|
+
* result, reason? }. Answers the upload's number. */
|
|
33421
|
+
upload(call: string, file: Plain, data?: Plain): number
|
|
33422
|
+
/** Uploads a picture of the game's screen (a PNG) the same way. */
|
|
33423
|
+
uploadScreen(call: string, data?: Plain): number
|
|
33424
|
+
/** Reads a file at an address the game's server gave (ctx.files.downloadUrl): heard as { kind: 'download', id, result,
|
|
33425
|
+
* data?, reason? }, its data the file's JSON (at most 1,000,000 bytes). Answers the download's number. */
|
|
33426
|
+
download(url: string): number
|
|
33427
|
+
/** Asks the player whether the game may send them notifications, where it can (a phone app with push on): heard as
|
|
33428
|
+
* { kind: 'push', result: 'granted' | 'denied' | 'unavailable' }. Never required to play, never rewarded. A
|
|
33429
|
+
* notification the player tapped to open the game is heard as { kind: 'pushOpened', data }. */
|
|
33430
|
+
askPush(): void
|
|
33431
|
+
/** 'online', 'resting' (the server is off or paused, or its wallet is empty) or 'off' (no connection yet). */
|
|
33432
|
+
readonly state: 'online' | 'resting' | 'off'
|
|
33433
|
+
}
|
|
33434
|
+
|
|
33166
33435
|
interface Turns {
|
|
33167
33436
|
/** Begins an order of these objects: the first's turn, round 1. */
|
|
33168
33437
|
begin(things: Thing[]): void
|
|
@@ -33536,6 +33805,15 @@ interface Game {
|
|
|
33536
33805
|
/** The game's UI elements: placed, hovered, pressed and clicked by the engine, drawn by the game. Kept through a goTo,
|
|
33537
33806
|
* gone with a restart. */
|
|
33538
33807
|
readonly ui: Ui
|
|
33808
|
+
/** Breaks, rewards and banners from the game's maker's own AdMob account. */
|
|
33809
|
+
readonly ads: Ads
|
|
33810
|
+
/** The game's own server: calls and live rooms. */
|
|
33811
|
+
readonly online: Online
|
|
33812
|
+
/** Real-money purchases in the game's store app. */
|
|
33813
|
+
readonly purchases: Purchases
|
|
33814
|
+
/** Opens the game's privacy policy, as a game on the app stores must from inside itself: call it when the player
|
|
33815
|
+
* taps the game's own Privacy button. The page knows where the policy is; with none yet, nothing opens. */
|
|
33816
|
+
openPrivacy(): void
|
|
33539
33817
|
/** Kept across restarts and levels, and between plays in the Game tab (in that
|
|
33540
33818
|
* browser). A playtest starts with nothing saved. */
|
|
33541
33819
|
save(key: string, value: Plain): void
|
|
@@ -48283,9 +48561,68 @@ function keyOf2(input) {
|
|
|
48283
48561
|
const wheel = input.wheel === void 0 || input.wheel === 0 ? "" : `~${String(input.wheel)}`;
|
|
48284
48562
|
const sticks = input.sticks === void 0 || input.sticks.length === 0 ? "" : `&${input.sticks.map((s2) => `${String(s2.lx)},${String(s2.ly)},${String(s2.rx)},${String(s2.ry)}`).join("/")}`;
|
|
48285
48563
|
const touches = input.touches === void 0 || input.touches.length === 0 ? "" : `!${input.touches.map((t2) => `${String(t2.x)},${String(t2.y)}`).join(";")}`;
|
|
48564
|
+
const ready = readyKey(input.adsReady);
|
|
48565
|
+
const answer = input.adAnswer === void 0 ? "" : `$${ANSWER_LETTERS[input.adAnswer]}`;
|
|
48566
|
+
const online = onlineKey(input);
|
|
48286
48567
|
const language = input.language === void 0 ? "" : `=${input.language}`;
|
|
48287
48568
|
const typed = input.typed === void 0 || input.typed === "" ? "" : `"${escapeTyped(input.typed)}"`;
|
|
48288
|
-
return `${letters}${own2}${aim}${screen}${size}${wheel}${sticks}${touches}${language}${typed}`;
|
|
48569
|
+
return `${letters}${own2}${aim}${screen}${size}${wheel}${sticks}${touches}${ready}${answer}${online}${language}${typed}`;
|
|
48570
|
+
}
|
|
48571
|
+
function onlineKey(input) {
|
|
48572
|
+
if (input.online === void 0 && input.onlineState === void 0 && input.purchases === void 0)
|
|
48573
|
+
return "";
|
|
48574
|
+
const json = JSON.stringify({
|
|
48575
|
+
...input.online === void 0 ? {} : { h: input.online },
|
|
48576
|
+
...input.onlineState === void 0 ? {} : { s: input.onlineState },
|
|
48577
|
+
...input.purchases === void 0 ? {} : { p: input.purchases }
|
|
48578
|
+
});
|
|
48579
|
+
let text3 = "";
|
|
48580
|
+
for (const byte of new TextEncoder().encode(json))
|
|
48581
|
+
text3 += String.fromCharCode(byte);
|
|
48582
|
+
return `>${btoa(text3).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")}`;
|
|
48583
|
+
}
|
|
48584
|
+
function onlineOf(text3) {
|
|
48585
|
+
if (!/^[A-Za-z0-9_-]+$/.test(text3))
|
|
48586
|
+
return null;
|
|
48587
|
+
try {
|
|
48588
|
+
const padded = text3.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - text3.length % 4) % 4);
|
|
48589
|
+
const bytes = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
|
|
48590
|
+
const read2 = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
48591
|
+
if (typeof read2 !== "object" || read2 === null || Array.isArray(read2))
|
|
48592
|
+
return null;
|
|
48593
|
+
const { h: h2, s: s2, p } = read2;
|
|
48594
|
+
if (h2 !== void 0 && (!Array.isArray(h2) || !h2.every(isOnlineHeard)))
|
|
48595
|
+
return null;
|
|
48596
|
+
if (s2 !== void 0 && !ONLINE_STATES.includes(s2))
|
|
48597
|
+
return null;
|
|
48598
|
+
if (p !== void 0 && (!Array.isArray(p) || !p.every(isPurchaseHeard)))
|
|
48599
|
+
return null;
|
|
48600
|
+
if (h2 === void 0 && s2 === void 0 && p === void 0)
|
|
48601
|
+
return null;
|
|
48602
|
+
return {
|
|
48603
|
+
...h2 === void 0 ? {} : { online: h2 },
|
|
48604
|
+
...s2 === void 0 ? {} : { onlineState: s2 },
|
|
48605
|
+
...p === void 0 ? {} : { purchases: p }
|
|
48606
|
+
};
|
|
48607
|
+
} catch {
|
|
48608
|
+
return null;
|
|
48609
|
+
}
|
|
48610
|
+
}
|
|
48611
|
+
var ANSWER_LETTERS = { shown: "s", rewarded: "r", closed: "c", unavailable: "u" };
|
|
48612
|
+
var ANSWER_OF = { s: "shown", r: "rewarded", c: "closed", u: "unavailable" };
|
|
48613
|
+
var READY_LETTERS = [["b", "break"], ["w", "reward"]];
|
|
48614
|
+
function readyKey(ready) {
|
|
48615
|
+
if (ready === void 0)
|
|
48616
|
+
return "";
|
|
48617
|
+
return `#${READY_LETTERS.filter(([, kind]) => ready.includes(kind)).map(([letter]) => letter).join("")}`;
|
|
48618
|
+
}
|
|
48619
|
+
function answerOf(text3) {
|
|
48620
|
+
return text3.length === 1 ? ANSWER_OF[text3] ?? null : null;
|
|
48621
|
+
}
|
|
48622
|
+
function readyOf(text3) {
|
|
48623
|
+
if (![...text3].every((ch) => READY_LETTERS.some(([letter]) => letter === ch)))
|
|
48624
|
+
return null;
|
|
48625
|
+
return READY_LETTERS.filter(([letter]) => text3.includes(letter)).map(([, kind]) => kind);
|
|
48289
48626
|
}
|
|
48290
48627
|
var TYPED_ESCAPES = { "\\": "\\\\", '"': '\\"', " ": "\\s", "\b": "\\b", "\n": "\\n" };
|
|
48291
48628
|
var UNESCAPED = { "\\": "\\", '"': '"', s: " ", b: "\b", n: "\n" };
|
|
@@ -48344,8 +48681,17 @@ function inputOf(run2) {
|
|
|
48344
48681
|
const spoken = quote === -1 ? run2 : run2.slice(0, quote);
|
|
48345
48682
|
const typed = quote === -1 ? null : typedOf(run2.slice(quote + 1, -1));
|
|
48346
48683
|
const languageAt = spoken.indexOf("=");
|
|
48347
|
-
const
|
|
48684
|
+
const heard = languageAt === -1 ? spoken : spoken.slice(0, languageAt);
|
|
48348
48685
|
const language = languageAt === -1 ? null : spoken.slice(languageAt + 1);
|
|
48686
|
+
const onlineAt = heard.indexOf(">");
|
|
48687
|
+
const answered = onlineAt === -1 ? heard : heard.slice(0, onlineAt);
|
|
48688
|
+
const online = onlineAt === -1 ? null : onlineOf(heard.slice(onlineAt + 1));
|
|
48689
|
+
const answerAt = answered.indexOf("$");
|
|
48690
|
+
const readied = answerAt === -1 ? answered : answered.slice(0, answerAt);
|
|
48691
|
+
const answer = answerAt === -1 ? null : answerOf(answered.slice(answerAt + 1));
|
|
48692
|
+
const readyAt = readied.indexOf("#");
|
|
48693
|
+
const fingered = readyAt === -1 ? readied : readied.slice(0, readyAt);
|
|
48694
|
+
const ready = readyAt === -1 ? null : readyOf(readied.slice(readyAt + 1));
|
|
48349
48695
|
const touchesAt = fingered.indexOf("!");
|
|
48350
48696
|
const full = touchesAt === -1 ? fingered : fingered.slice(0, touchesAt);
|
|
48351
48697
|
const touches = touchesAt === -1 ? null : touchesOf(fingered.slice(touchesAt + 1));
|
|
@@ -48369,6 +48715,9 @@ function inputOf(run2) {
|
|
|
48369
48715
|
...wheel === null ? {} : { wheel },
|
|
48370
48716
|
...sticks === null ? {} : { sticks },
|
|
48371
48717
|
...touches === null ? {} : { touches },
|
|
48718
|
+
...ready === null ? {} : { adsReady: ready },
|
|
48719
|
+
...answer === null ? {} : { adAnswer: answer },
|
|
48720
|
+
...online ?? {},
|
|
48372
48721
|
...language === null ? {} : { language },
|
|
48373
48722
|
...typed === null ? {} : { typed }
|
|
48374
48723
|
};
|
|
@@ -48599,13 +48948,43 @@ function scan(text3, chars) {
|
|
|
48599
48948
|
}
|
|
48600
48949
|
languageKey = `=${language}`;
|
|
48601
48950
|
}
|
|
48602
|
-
const
|
|
48603
|
-
const
|
|
48951
|
+
const onlineSign = rawFingered.indexOf(">");
|
|
48952
|
+
const rawAnswered = onlineSign === -1 ? rawFingered : rawFingered.slice(0, onlineSign);
|
|
48953
|
+
let onlineKeyed = "";
|
|
48954
|
+
if (onlineSign !== -1) {
|
|
48955
|
+
const online = onlineOf(rawFingered.slice(onlineSign + 1));
|
|
48956
|
+
if (online === null) {
|
|
48957
|
+
throw new TrackError(`run ${index} has '${rawFingered.slice(onlineSign, onlineSign + 24)}': what a game heard online rides as > and the base64url of its JSON, as the page wrote it`, index, at2);
|
|
48958
|
+
}
|
|
48959
|
+
onlineKeyed = onlineKey(online);
|
|
48960
|
+
}
|
|
48961
|
+
const answerSign = rawAnswered.indexOf("$");
|
|
48962
|
+
const rawReadied = answerSign === -1 ? rawAnswered : rawAnswered.slice(0, answerSign);
|
|
48963
|
+
let answerKey = "";
|
|
48964
|
+
if (answerSign !== -1) {
|
|
48965
|
+
const answer = answerOf(rawAnswered.slice(answerSign + 1));
|
|
48966
|
+
if (answer === null) {
|
|
48967
|
+
throw new TrackError(`run ${index} has '${rawAnswered.slice(answerSign)}': an ad's answer is $s (shown), $r (rewarded), $c (closed) or $u (unavailable)`, index, at2);
|
|
48968
|
+
}
|
|
48969
|
+
answerKey = `$${ANSWER_LETTERS[answer]}`;
|
|
48970
|
+
}
|
|
48971
|
+
const readySign = rawReadied.indexOf("#");
|
|
48972
|
+
const rawTouched = readySign === -1 ? rawReadied : rawReadied.slice(0, readySign);
|
|
48973
|
+
let readyKeyed = "";
|
|
48974
|
+
if (readySign !== -1) {
|
|
48975
|
+
const ready = readyOf(rawReadied.slice(readySign + 1));
|
|
48976
|
+
if (ready === null) {
|
|
48977
|
+
throw new TrackError(`run ${index} has '${rawReadied.slice(readySign)}': ads ready are # with b (break) and w (reward), or # alone for none, like #bw`, index, at2);
|
|
48978
|
+
}
|
|
48979
|
+
readyKeyed = readyKey(ready);
|
|
48980
|
+
}
|
|
48981
|
+
const touchesSign = rawTouched.indexOf("!");
|
|
48982
|
+
const rawFull = touchesSign === -1 ? rawTouched : rawTouched.slice(0, touchesSign);
|
|
48604
48983
|
let touchesKey = "";
|
|
48605
48984
|
if (touchesSign !== -1) {
|
|
48606
|
-
const touches = touchesOf(
|
|
48985
|
+
const touches = touchesOf(rawTouched.slice(touchesSign + 1));
|
|
48607
48986
|
if (touches === null) {
|
|
48608
|
-
throw new TrackError(`run ${index} has the touches '${
|
|
48987
|
+
throw new TrackError(`run ${index} has the touches '${rawTouched.slice(touchesSign)}', which must be !x,y in whole screen pixels, a semicolon between fingers, one to four, like !120,300;400,310`, index, at2);
|
|
48609
48988
|
}
|
|
48610
48989
|
touchesKey = `!${touches.map((t2) => `${String(t2.x)},${String(t2.y)}`).join(";")}`;
|
|
48611
48990
|
}
|
|
@@ -48691,7 +49070,7 @@ function scan(text3, chars) {
|
|
|
48691
49070
|
aimKey = `@${String(aim.x / AIM_PER_TILE)},${String(aim.y / AIM_PER_TILE)}`;
|
|
48692
49071
|
}
|
|
48693
49072
|
const lettered = rawButtons.toLowerCase().replace(/[+|]/g, "");
|
|
48694
|
-
const buttons = lettered === "" && (ownKey !== "" || aimKey !== "" || pointKey !== "" || sizeKey !== "" || wheelKey !== "" || sticksKey !== "" || touchesKey !== "" || languageKey !== "" || typedKey !== "") ? "-" : lettered;
|
|
49073
|
+
const buttons = lettered === "" && (ownKey !== "" || aimKey !== "" || pointKey !== "" || sizeKey !== "" || wheelKey !== "" || sticksKey !== "" || touchesKey !== "" || readyKeyed !== "" || answerKey !== "" || onlineKeyed !== "" || languageKey !== "" || typedKey !== "") ? "-" : lettered;
|
|
48695
49074
|
if (buttons === "") {
|
|
48696
49075
|
throw new TrackError(`run ${index} has a count but no buttons: '${token}'`, index, at2);
|
|
48697
49076
|
}
|
|
@@ -48706,7 +49085,7 @@ function scan(text3, chars) {
|
|
|
48706
49085
|
}
|
|
48707
49086
|
key = keyOf2(inputOf(buttons));
|
|
48708
49087
|
}
|
|
48709
|
-
key += ownKey + aimKey + pointKey + sizeKey + wheelKey + sticksKey + touchesKey + languageKey + typedKey;
|
|
49088
|
+
key += ownKey + aimKey + pointKey + sizeKey + wheelKey + sticksKey + touchesKey + readyKeyed + answerKey + onlineKeyed + languageKey + typedKey;
|
|
48710
49089
|
if (!/^[1-9][0-9]*$/.test(rawCount)) {
|
|
48711
49090
|
throw new TrackError(`run ${index} has the count '${rawCount}', which must be a whole number from 1 to ${MAX_RUN_COUNT} with no leading zero`, index, at2);
|
|
48712
49091
|
}
|
|
@@ -50231,9 +50610,10 @@ function pressedMeanwhile(input, latched) {
|
|
|
50231
50610
|
function quiet(scripts) {
|
|
50232
50611
|
if (scripts === null)
|
|
50233
50612
|
return null;
|
|
50234
|
-
if (scripts.errors.length === 0 && scripts.warnings.length === 0 && scripts.logs.length === 0 && scripts.sounds.length === 0)
|
|
50613
|
+
if (scripts.errors.length === 0 && scripts.warnings.length === 0 && scripts.logs.length === 0 && scripts.sounds.length === 0 && scripts.privacy !== true && scripts.ad === void 0 && scripts.online === void 0 && scripts.purchase === void 0)
|
|
50235
50614
|
return scripts;
|
|
50236
|
-
|
|
50615
|
+
const { privacy: _privacy, ad: _ad, online: _online, purchase: _purchase, ...rest } = scripts;
|
|
50616
|
+
return { ...rest, errors: [], warnings: [], logs: [], sounds: [] };
|
|
50237
50617
|
}
|
|
50238
50618
|
function heldTick(state, input) {
|
|
50239
50619
|
const scripts = quiet(state.scripts);
|
|
@@ -50469,6 +50849,7 @@ function ownBodyWith(was, args) {
|
|
|
50469
50849
|
var LOGS_PER_TICK = 50;
|
|
50470
50850
|
var LOG_CHARS = 500;
|
|
50471
50851
|
var EVENTS_PER_TICK = 256;
|
|
50852
|
+
var AD_EVENTS_PER_TICK = 16;
|
|
50472
50853
|
var TIMERS_PER_OWNER = 64;
|
|
50473
50854
|
var TWEENS_PER_OWNER = 64;
|
|
50474
50855
|
var SEQUENCES_PER_OWNER = 16;
|
|
@@ -50483,7 +50864,7 @@ var OWN_AUDIO = /^own:[a-z0-9][a-z0-9-]{0,39}$/;
|
|
|
50483
50864
|
var LIB_AUDIO = /^lib:[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/;
|
|
50484
50865
|
var MOST_BURSTS = 64;
|
|
50485
50866
|
var MOST_DEFERRED = 1024;
|
|
50486
|
-
var DRAW_OPS = /* @__PURE__ */ new Set(["thing", "data", "fields", "shown", "find", "findAll", "findIn", "touching", "tileAt", "tileIn", "tileUnder", "raycast", "path", "setting", "load", "random", "log", "drawn", "clock", "running", "measure", "bodyOf", "nextStep", "vars", "shared", "gameScript", "parentOf", "levelSize", "moving", "turns", "mixer", "typing", "t", "language", "languages", "ui", "uiFit"]);
|
|
50867
|
+
var DRAW_OPS = /* @__PURE__ */ new Set(["thing", "data", "fields", "shown", "find", "findAll", "findIn", "touching", "tileAt", "tileIn", "tileUnder", "raycast", "path", "setting", "load", "random", "log", "drawn", "clock", "running", "measure", "bodyOf", "nextStep", "vars", "shared", "gameScript", "parentOf", "levelSize", "moving", "turns", "mixer", "typing", "t", "language", "languages", "ui", "uiFit", "onlineState", "purchaseProducts"]);
|
|
50487
50868
|
function screenOf2(input) {
|
|
50488
50869
|
const size = input.screenSize ?? SCREEN_DEFAULT;
|
|
50489
50870
|
return { screen: input.screenPointer ?? null, size: { width: size.w, height: size.h } };
|
|
@@ -50552,7 +50933,7 @@ function fromUnit(raw, unit, what) {
|
|
|
50552
50933
|
return fromInt(Math.round(value));
|
|
50553
50934
|
}
|
|
50554
50935
|
}
|
|
50555
|
-
var BOT_OPS = /* @__PURE__ */ new Set(["thing", "data", "fields", "shown", "find", "findAll", "findIn", "touching", "tileAt", "tileIn", "tileUnder", "raycast", "path", "setting", "load", "random", "log", "clock", "running", "measure", "bodyOf", "nextStep", "vars", "shared", "gameScript", "parentOf", "levelSize", "moving", "turns", "mixer", "typing", "t", "language", "languages", "ui", "uiFit"]);
|
|
50936
|
+
var BOT_OPS = /* @__PURE__ */ new Set(["thing", "data", "fields", "shown", "find", "findAll", "findIn", "touching", "tileAt", "tileIn", "tileUnder", "raycast", "path", "setting", "load", "random", "log", "clock", "running", "measure", "bodyOf", "nextStep", "vars", "shared", "gameScript", "parentOf", "levelSize", "moving", "turns", "mixer", "typing", "t", "language", "languages", "ui", "uiFit", "onlineState", "purchaseProducts"]);
|
|
50556
50937
|
function sidesOf(normal) {
|
|
50557
50938
|
switch (normal) {
|
|
50558
50939
|
case "a_above_b":
|
|
@@ -50664,6 +51045,20 @@ var Turn = class {
|
|
|
50664
51045
|
language;
|
|
50665
51046
|
camera;
|
|
50666
51047
|
sounds;
|
|
51048
|
+
/** game.openPrivacy() was called this tick (the store publishing spec). */
|
|
51049
|
+
privacy;
|
|
51050
|
+
/** The ad asked this tick, and what the game keeps of its ads, absent until it has any (the ads spec). */
|
|
51051
|
+
ad;
|
|
51052
|
+
ads;
|
|
51053
|
+
/** What the scripts asked of the game's server this tick; whether it is online; the next call's number, absent for a
|
|
51054
|
+
* game that never mentions online (the online services spec). */
|
|
51055
|
+
online;
|
|
51056
|
+
onlineState;
|
|
51057
|
+
nextCall;
|
|
51058
|
+
/** What the scripts asked of the store this tick, and what the game keeps of its purchases, absent for a game that
|
|
51059
|
+
* never mentions them (the online services spec, part 3). */
|
|
51060
|
+
purchase;
|
|
51061
|
+
purchases;
|
|
50667
51062
|
music;
|
|
50668
51063
|
/** Sounds playing round, and the mixer, replaced whole when changed
|
|
50669
51064
|
* (sub-project 9). */
|
|
@@ -50724,6 +51119,14 @@ var Turn = class {
|
|
|
50724
51119
|
this.saved = scripts.saved;
|
|
50725
51120
|
this.camera = scripts.camera;
|
|
50726
51121
|
this.sounds = fresh ? [] : [...scripts.sounds];
|
|
51122
|
+
this.privacy = !fresh && scripts.privacy === true;
|
|
51123
|
+
this.ad = fresh ? void 0 : scripts.ad;
|
|
51124
|
+
this.ads = scripts.ads;
|
|
51125
|
+
this.online = fresh || scripts.online === void 0 ? void 0 : [...scripts.online];
|
|
51126
|
+
this.onlineState = scripts.onlineState;
|
|
51127
|
+
this.nextCall = scripts.nextCall;
|
|
51128
|
+
this.purchase = fresh ? void 0 : scripts.purchase;
|
|
51129
|
+
this.purchases = scripts.purchases;
|
|
50727
51130
|
this.music = scripts.music;
|
|
50728
51131
|
this.loops = scripts.loops;
|
|
50729
51132
|
this.mixer = scripts.mixer;
|
|
@@ -50869,6 +51272,14 @@ var Turn = class {
|
|
|
50869
51272
|
logs: this.logs.length === 0 ? [] : [...this.logs],
|
|
50870
51273
|
warnings: this.warnings.length,
|
|
50871
51274
|
sounds: this.sounds.length,
|
|
51275
|
+
privacy: this.privacy,
|
|
51276
|
+
ad: this.ad,
|
|
51277
|
+
ads: this.ads,
|
|
51278
|
+
online: this.online === void 0 ? void 0 : [...this.online],
|
|
51279
|
+
onlineState: this.onlineState,
|
|
51280
|
+
nextCall: this.nextCall,
|
|
51281
|
+
purchase: this.purchase,
|
|
51282
|
+
purchases: this.purchases,
|
|
50872
51283
|
bursts: this.bursts.length === 0 ? [] : [...this.bursts],
|
|
50873
51284
|
screen: this.screen,
|
|
50874
51285
|
waiting: this.waiting,
|
|
@@ -50922,6 +51333,14 @@ var Turn = class {
|
|
|
50922
51333
|
this.logs.splice(0, this.logs.length, ...was.logs);
|
|
50923
51334
|
this.warnings.length = was.warnings;
|
|
50924
51335
|
this.sounds.length = was.sounds;
|
|
51336
|
+
this.privacy = was.privacy;
|
|
51337
|
+
this.ad = was.ad;
|
|
51338
|
+
this.ads = was.ads;
|
|
51339
|
+
this.online = was.online;
|
|
51340
|
+
this.onlineState = was.onlineState;
|
|
51341
|
+
this.nextCall = was.nextCall;
|
|
51342
|
+
this.purchase = was.purchase;
|
|
51343
|
+
this.purchases = was.purchases;
|
|
50925
51344
|
this.bursts = was.bursts;
|
|
50926
51345
|
this.screen = was.screen;
|
|
50927
51346
|
this.waiting = was.waiting;
|
|
@@ -52193,6 +52612,76 @@ var Turn = class {
|
|
|
52193
52612
|
// kind's script to read; null for a game without one.
|
|
52194
52613
|
case "gameScript":
|
|
52195
52614
|
return this.scripts.sources.game === null ? null : this.game ?? {};
|
|
52615
|
+
// The game's privacy policy (the store publishing spec): asked of the page, which knows where it is.
|
|
52616
|
+
// Ads (the ads spec): asked of the page, which answers on a later tick, as input.
|
|
52617
|
+
// Online (the online services spec): asked of the page, which carries it to the game's server; what comes back
|
|
52618
|
+
// is heard on a later tick, as input.
|
|
52619
|
+
case "onlineCall": {
|
|
52620
|
+
const id = this.nextCall ?? 1;
|
|
52621
|
+
this.askOnline({ kind: "call", id, name: args.name, data: args.data ?? null });
|
|
52622
|
+
this.nextCall = id + 1;
|
|
52623
|
+
return id;
|
|
52624
|
+
}
|
|
52625
|
+
case "onlineJoin":
|
|
52626
|
+
case "onlineLeave":
|
|
52627
|
+
this.askOnline({ kind: op === "onlineJoin" ? "join" : "leave", room: args.room });
|
|
52628
|
+
return null;
|
|
52629
|
+
case "onlineSend":
|
|
52630
|
+
this.askOnline({ kind: "send", room: args.room, data: args.data ?? null });
|
|
52631
|
+
return null;
|
|
52632
|
+
// Part 2: an upload through one of the game's calls, a download, and asking for push; numbered with the calls.
|
|
52633
|
+
case "onlineUpload":
|
|
52634
|
+
case "onlineDownload": {
|
|
52635
|
+
const id = this.nextCall ?? 1;
|
|
52636
|
+
this.askOnline(op === "onlineDownload" ? { kind: "download", id, url: args.url } : { kind: "upload", id, name: args.name, data: args.data ?? null, ...args.screen === true ? { screen: true } : { file: args.file ?? null } });
|
|
52637
|
+
this.nextCall = id + 1;
|
|
52638
|
+
return id;
|
|
52639
|
+
}
|
|
52640
|
+
case "onlineAskPush":
|
|
52641
|
+
this.askOnline({ kind: "askPush" });
|
|
52642
|
+
return null;
|
|
52643
|
+
case "onlineState":
|
|
52644
|
+
return this.onlineState ?? "off";
|
|
52645
|
+
// Purchases (the online services spec, part 3): asked of the page, which asks the store; the answer is heard on a
|
|
52646
|
+
// later tick, as input.
|
|
52647
|
+
case "purchaseBuy":
|
|
52648
|
+
this.askPurchase({ kind: "buy", id: args.id });
|
|
52649
|
+
return null;
|
|
52650
|
+
case "purchaseRestore":
|
|
52651
|
+
this.askPurchase({ kind: "restore" });
|
|
52652
|
+
return null;
|
|
52653
|
+
case "purchaseProducts":
|
|
52654
|
+
return (this.purchases ?? NO_PURCHASES_YET).products;
|
|
52655
|
+
case "adBreak":
|
|
52656
|
+
case "adReward": {
|
|
52657
|
+
const placement = args.placement;
|
|
52658
|
+
if (typeof placement !== "string" || !PLACEMENT.test(placement))
|
|
52659
|
+
throw new Error(`${op === "adBreak" ? "ads.break" : "ads.reward"} was given ${JSON.stringify(placement)}: ${PLACEMENT_WORDS}`);
|
|
52660
|
+
this.askAd({ kind: op === "adBreak" ? "break" : "reward", placement });
|
|
52661
|
+
return null;
|
|
52662
|
+
}
|
|
52663
|
+
case "adReady": {
|
|
52664
|
+
const kind = args.kind;
|
|
52665
|
+
if (!AD_KINDS.includes(kind))
|
|
52666
|
+
throw new Error(`ads.ready takes 'break' or 'reward', not ${JSON.stringify(kind)}`);
|
|
52667
|
+
return this.scripts.adsOff === void 0 && adsOf(this.ads).ready.includes(kind);
|
|
52668
|
+
}
|
|
52669
|
+
case "adBanner": {
|
|
52670
|
+
const position = args.position;
|
|
52671
|
+
if (position !== null && position !== "top" && position !== "bottom")
|
|
52672
|
+
throw new Error(`ads.banner takes 'top', 'bottom' or null, not ${JSON.stringify(position)}`);
|
|
52673
|
+
if (position !== null && this.scripts.adsOff !== void 0) {
|
|
52674
|
+
this.warn(this.scripts.adsOff);
|
|
52675
|
+
return null;
|
|
52676
|
+
}
|
|
52677
|
+
this.ads = { ...adsOf(this.ads), banner: position };
|
|
52678
|
+
if (position !== null && this.ui !== void 0)
|
|
52679
|
+
this.bannerBeside(position, this.ui);
|
|
52680
|
+
return null;
|
|
52681
|
+
}
|
|
52682
|
+
case "openPrivacy":
|
|
52683
|
+
this.privacy = true;
|
|
52684
|
+
return null;
|
|
52196
52685
|
case "save": {
|
|
52197
52686
|
this.saved = { ...this.saved, [text(args.key, "save: key")]: args.value ?? null };
|
|
52198
52687
|
return null;
|
|
@@ -52773,8 +53262,132 @@ var Turn = class {
|
|
|
52773
53262
|
this.setDriven(id, { ...this.driven.get(id) ?? { x: false, y: false }, facing: true });
|
|
52774
53263
|
return this.row(e2);
|
|
52775
53264
|
}
|
|
53265
|
+
/** An ad asked of the page for this tick, and kept until its answer comes (the ads spec); refused at once, in the
|
|
53266
|
+
* words of the rule it breaks, when it breaks one the engine can tell exactly, and warned of when a break comes in
|
|
53267
|
+
* the middle of play. */
|
|
53268
|
+
askAd(ask) {
|
|
53269
|
+
const ads = adsOf(this.ads);
|
|
53270
|
+
const refused = adRefusal(ads, ask.kind, this.real, this.scripts.adsOff ?? null);
|
|
53271
|
+
if (refused !== null) {
|
|
53272
|
+
this.warn(refused);
|
|
53273
|
+
this.hearAd({ kind: ask.kind, placement: ask.placement, result: "refused", reason: refused });
|
|
53274
|
+
return;
|
|
53275
|
+
}
|
|
53276
|
+
if (ask.kind === "break" && !this.clock.paused) {
|
|
53277
|
+
const playing2 = playHeld(this.input) || ads.lastPlay !== null && this.real - ads.lastPlay <= PLAY_PRESS_TICKS;
|
|
53278
|
+
const settled = ads.scene === null || this.real - ads.scene.at > SCENE_SETTLE_TICKS;
|
|
53279
|
+
if (playing2 && settled)
|
|
53280
|
+
this.warn(AD_WORDS.midPlay);
|
|
53281
|
+
}
|
|
53282
|
+
this.ad = ask;
|
|
53283
|
+
this.ads = { ...ads, pending: ask };
|
|
53284
|
+
}
|
|
53285
|
+
/** A banner beside one of the game's own buttons, warned of: a button within a few pixels of the banner's edge. */
|
|
53286
|
+
bannerBeside(position, ui) {
|
|
53287
|
+
const screen = screenOf2(this.input).size;
|
|
53288
|
+
for (const [name, box] of layoutUi(ui, screen)) {
|
|
53289
|
+
if (ui.elements[name]?.kind !== "button")
|
|
53290
|
+
continue;
|
|
53291
|
+
const beside = position === "top" ? box.y <= BANNER_EDGE_PX : box.y + box.h >= screen.height - BANNER_EDGE_PX;
|
|
53292
|
+
if (beside)
|
|
53293
|
+
this.warn(AD_WORDS.banner(name));
|
|
53294
|
+
}
|
|
53295
|
+
}
|
|
53296
|
+
/** What the tick's input says of ads: the kinds ready, and the page's answer to the ad pending, heard as an 'ad'
|
|
53297
|
+
* event; an answer with none pending is let go (the ads spec). */
|
|
53298
|
+
takeAdInput() {
|
|
53299
|
+
const { adAnswer, adsReady } = this.input;
|
|
53300
|
+
if (this.ads !== void 0) {
|
|
53301
|
+
const before2 = this.state.prevInput;
|
|
53302
|
+
const pressed = anyPressed(this.input, before2) || (this.ui?.clicked.length ?? 0) > 0;
|
|
53303
|
+
const played = playPressed(this.input, before2);
|
|
53304
|
+
const key = `${String(this.state.level)}:${String(this.scripts.loads)}`;
|
|
53305
|
+
const scene = this.ads.scene?.key === key ? this.ads.scene : { key, at: this.real };
|
|
53306
|
+
if (pressed || played || scene !== this.ads.scene) {
|
|
53307
|
+
this.ads = {
|
|
53308
|
+
...this.ads,
|
|
53309
|
+
scene,
|
|
53310
|
+
...pressed ? { played: true, lastPress: this.real } : {},
|
|
53311
|
+
...played ? { lastPlay: this.real } : {}
|
|
53312
|
+
};
|
|
53313
|
+
}
|
|
53314
|
+
}
|
|
53315
|
+
if (adsReady !== void 0)
|
|
53316
|
+
this.ads = { ...adsOf(this.ads), ready: AD_KINDS.filter((kind) => adsReady.includes(kind)) };
|
|
53317
|
+
const pending = this.ads?.pending ?? null;
|
|
53318
|
+
if (adAnswer === void 0 || pending === null)
|
|
53319
|
+
return;
|
|
53320
|
+
this.ads = { ...adsOf(this.ads), pending: null, ...pending.kind === "break" && adAnswer === "shown" ? { lastBreak: this.real } : {} };
|
|
53321
|
+
this.hearAd({ kind: pending.kind, placement: pending.placement, result: adAnswer });
|
|
53322
|
+
}
|
|
53323
|
+
/** An ask of the game's server, refused in words when it is not one, or past a tick's asks. */
|
|
53324
|
+
askOnline(ask) {
|
|
53325
|
+
const problem = askProblem(ask);
|
|
53326
|
+
if (problem !== null)
|
|
53327
|
+
throw new Error(problem);
|
|
53328
|
+
const asked = this.online ?? [];
|
|
53329
|
+
if (asked.length >= ONLINE_LIMITS.asksPerTick)
|
|
53330
|
+
throw new Error(TOO_MANY_ASKS);
|
|
53331
|
+
this.online = [...asked, ask];
|
|
53332
|
+
}
|
|
53333
|
+
/** What the page brought back from the game's server, and whether it is online, for a game that keeps online: each
|
|
53334
|
+
* heard as an 'online' event. A game that never mentions online ignores it. */
|
|
53335
|
+
takeOnlineInput() {
|
|
53336
|
+
if (this.nextCall === void 0)
|
|
53337
|
+
return;
|
|
53338
|
+
const { online, onlineState } = this.input;
|
|
53339
|
+
if (onlineState !== void 0)
|
|
53340
|
+
this.onlineState = onlineState;
|
|
53341
|
+
for (const heard of online ?? [])
|
|
53342
|
+
this.hearHost("online", heard);
|
|
53343
|
+
}
|
|
53344
|
+
/** A purchase asked of the page for this tick: a buy of an id not one, or a second ask in a tick, refused in words; a
|
|
53345
|
+
* buy while another waits for its answer heard at once as refused. */
|
|
53346
|
+
askPurchase(ask) {
|
|
53347
|
+
const problem = purchaseAskProblem(ask);
|
|
53348
|
+
if (problem !== null)
|
|
53349
|
+
throw new Error(problem);
|
|
53350
|
+
if (this.purchase !== void 0)
|
|
53351
|
+
throw new Error("game.purchases asks the store once a tick: ask again on a later tick");
|
|
53352
|
+
const purchases = this.purchases ?? NO_PURCHASES_YET;
|
|
53353
|
+
if (ask.kind === "buy" && purchases.pending !== null) {
|
|
53354
|
+
this.hearHost("purchase", { kind: "bought", id: ask.id, result: "refused", reason: "a purchase is already in progress: wait for its answer" });
|
|
53355
|
+
return;
|
|
53356
|
+
}
|
|
53357
|
+
this.purchase = ask;
|
|
53358
|
+
if (ask.kind === "buy")
|
|
53359
|
+
this.purchases = { ...purchases, pending: ask.id };
|
|
53360
|
+
}
|
|
53361
|
+
/** What the store and the game's server answered of purchases, for a game that keeps them: the products kept, and each
|
|
53362
|
+
* heard as a 'purchase' event; the buy pending let go once its answer comes. A game that never mentions purchases
|
|
53363
|
+
* ignores it. */
|
|
53364
|
+
takePurchaseInput() {
|
|
53365
|
+
if (this.purchases === void 0)
|
|
53366
|
+
return;
|
|
53367
|
+
for (const heard of this.input.purchases ?? []) {
|
|
53368
|
+
if (heard.kind === "products")
|
|
53369
|
+
this.purchases = { ...this.purchases, products: heard.list };
|
|
53370
|
+
if (heard.kind === "bought" && heard.id === this.purchases.pending)
|
|
53371
|
+
this.purchases = { ...this.purchases, pending: null };
|
|
53372
|
+
this.hearHost("purchase", heard);
|
|
53373
|
+
}
|
|
53374
|
+
}
|
|
53375
|
+
/** An 'ad' event every onEvent hears on the next tick, as an emitted one is. */
|
|
53376
|
+
hearAd(data) {
|
|
53377
|
+
this.hearHost("ad", data);
|
|
53378
|
+
}
|
|
53379
|
+
/** An event from the page (an ad's answer, what the game heard online), heard past a tick's emitted events, up to its
|
|
53380
|
+
* own few (the final review of ads). */
|
|
53381
|
+
hearHost(name, data) {
|
|
53382
|
+
if (this.emitted.length >= EVENTS_PER_TICK + AD_EVENTS_PER_TICK)
|
|
53383
|
+
return;
|
|
53384
|
+
if (this.emitted.length >= EVENTS_PER_TICK && this.emitted.filter((one2) => one2.name === name).length >= AD_EVENTS_PER_TICK)
|
|
53385
|
+
return;
|
|
53386
|
+
this.emitted.push({ name, data: { ...data }, at: this.events.length });
|
|
53387
|
+
this.events.push(name);
|
|
53388
|
+
}
|
|
52776
53389
|
scriptsState() {
|
|
52777
|
-
const { starting: _was, loops: _loops, mixer: _mixer, typing: _typing, ui: _ui, ...before2 } = this.scripts;
|
|
53390
|
+
const { starting: _was, loops: _loops, mixer: _mixer, typing: _typing, ui: _ui, privacy: _privacy, ad: _ad, ads: _ads, online: _online, onlineState: _onlineState, nextCall: _nextCall, purchase: _purchase, purchases: _purchases, ...before2 } = this.scripts;
|
|
52778
53391
|
return {
|
|
52779
53392
|
...before2,
|
|
52780
53393
|
fields: this.fields,
|
|
@@ -52800,6 +53413,14 @@ var Turn = class {
|
|
|
52800
53413
|
...this.mixer === void 0 ? {} : { mixer: this.mixer },
|
|
52801
53414
|
camera: this.camera,
|
|
52802
53415
|
sounds: this.sounds,
|
|
53416
|
+
...this.privacy ? { privacy: true } : {},
|
|
53417
|
+
...this.ad === void 0 ? {} : { ad: this.ad },
|
|
53418
|
+
...this.ads === void 0 ? {} : { ads: this.ads },
|
|
53419
|
+
...this.online === void 0 || this.online.length === 0 ? {} : { online: this.online },
|
|
53420
|
+
...this.onlineState === void 0 ? {} : { onlineState: this.onlineState },
|
|
53421
|
+
...this.nextCall === void 0 ? {} : { nextCall: this.nextCall },
|
|
53422
|
+
...this.purchase === void 0 ? {} : { purchase: this.purchase },
|
|
53423
|
+
...this.purchases === void 0 ? {} : { purchases: this.purchases },
|
|
52803
53424
|
music: this.music,
|
|
52804
53425
|
bursts: this.bursts,
|
|
52805
53426
|
deferred: this.deferred,
|
|
@@ -52867,6 +53488,9 @@ function beginScripts(state, input) {
|
|
|
52867
53488
|
if (scripts === null || !hasScripts(scripts))
|
|
52868
53489
|
return null;
|
|
52869
53490
|
const turn = new Turn({ ...state, events: [] }, scripts, input, state.contacts, state.step, true, scripts.driven);
|
|
53491
|
+
turn.takeAdInput();
|
|
53492
|
+
turn.takeOnlineInput();
|
|
53493
|
+
turn.takePurchaseInput();
|
|
52870
53494
|
turn.bursts = turn.bursts.filter((b) => turn.now - b.step < b.life * 60);
|
|
52871
53495
|
dueChange(turn);
|
|
52872
53496
|
for (const late of scripts.deferred) {
|
|
@@ -52971,6 +53595,9 @@ function pausedScripts(state, input) {
|
|
|
52971
53595
|
if (scripts === null)
|
|
52972
53596
|
throw new Error("a game without scripts cannot be paused");
|
|
52973
53597
|
const turn = new Turn({ ...state, events: [] }, scripts, input, state.contacts, state.step, true, scripts.driven);
|
|
53598
|
+
turn.takeAdInput();
|
|
53599
|
+
turn.takeOnlineInput();
|
|
53600
|
+
turn.takePurchaseInput();
|
|
52974
53601
|
dueChange(turn);
|
|
52975
53602
|
turn.call(null, "whilePaused", [DT]);
|
|
52976
53603
|
if (turn.clock.paused && (turn.paused.includes(0) || !(turn.runtime.has(null) && turn.runtime.hasHook(null, "whilePaused")))) {
|
|
@@ -52979,7 +53606,8 @@ function pausedScripts(state, input) {
|
|
|
52979
53606
|
}
|
|
52980
53607
|
heardClicks(turn);
|
|
52981
53608
|
const heard = closeScripts(turn.scriptsState(), turn.events).inbox;
|
|
52982
|
-
const
|
|
53609
|
+
const all = [...scripts.inbox, ...heard];
|
|
53610
|
+
const result = turn.result([...all.slice(0, EVENTS_PER_TICK), ...all.slice(EVENTS_PER_TICK).filter((one2) => one2.name === "ad" || one2.name === "online").slice(0, AD_EVENTS_PER_TICK)]);
|
|
52983
53611
|
const held = result.state.scripts;
|
|
52984
53612
|
if (held === null)
|
|
52985
53613
|
return result;
|