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
|
@@ -24846,7 +24846,7 @@ function shouldInterruptAfterDeadline(deadline) {
|
|
|
24846
24846
|
var import_sucrase2 = __toESM(require_dist2(), 1);
|
|
24847
24847
|
|
|
24848
24848
|
// ../core/dist/version.js
|
|
24849
|
-
var ENGINE_VERSION = "0.9.
|
|
24849
|
+
var ENGINE_VERSION = "0.9.2";
|
|
24850
24850
|
|
|
24851
24851
|
// ../core/dist/home.js
|
|
24852
24852
|
import { existsSync, renameSync, symlinkSync } from "node:fs";
|
|
@@ -28475,6 +28475,170 @@ function impulseFor(height, gravity, terminalFall) {
|
|
|
28475
28475
|
return impulseUpTo(height, gravity, terminalFall);
|
|
28476
28476
|
}
|
|
28477
28477
|
|
|
28478
|
+
// ../core/dist/script/ads.js
|
|
28479
|
+
var AD_KINDS = ["break", "reward"];
|
|
28480
|
+
var NO_ADS_YET = { pending: null, lastBreak: null, lastPress: null, lastPlay: null, played: false, scene: null, ready: AD_KINDS, banner: null };
|
|
28481
|
+
var PLACEMENT = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
28482
|
+
var PLACEMENT_WORDS = "a placement is named with 1 to 40 lowercase letters, digits and hyphens, like level-end or extra-life";
|
|
28483
|
+
var adsOf = (kept) => kept ?? NO_ADS_YET;
|
|
28484
|
+
var usesAds = (sources) => sources.some((source) => /\bads\s*\.\s*(break|reward|ready|banner)\b/.test(source));
|
|
28485
|
+
var BREAK_GAP_TICKS = 3600;
|
|
28486
|
+
var REWARD_PRESS_TICKS = 60;
|
|
28487
|
+
var PLAY_PRESS_TICKS = 30;
|
|
28488
|
+
var SCENE_SETTLE_TICKS = 120;
|
|
28489
|
+
var BANNER_EDGE_PX = 8;
|
|
28490
|
+
var AD_WORDS = {
|
|
28491
|
+
launch: "a break before the player's first input is refused: AdMob forbids ads at launch",
|
|
28492
|
+
backToBack: "a break less than 60 seconds after the last one is refused: AdMob forbids ads back to back",
|
|
28493
|
+
unchosen: "a reward the player did not choose is refused: AdMob rewards only an ad the player asks for, with a press just before",
|
|
28494
|
+
showing: "an ad is already showing",
|
|
28495
|
+
children: "this game is made for children: it shows no ads",
|
|
28496
|
+
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",
|
|
28497
|
+
banner: (name) => `a banner beside the game's button '${name}': AdMob forbids ads where players tap; keep buttons away from the banner's edge`
|
|
28498
|
+
};
|
|
28499
|
+
function adRefusal(ads, kind, now, off) {
|
|
28500
|
+
if (off !== null)
|
|
28501
|
+
return off;
|
|
28502
|
+
if (ads.pending !== null)
|
|
28503
|
+
return AD_WORDS.showing;
|
|
28504
|
+
if (kind === "break") {
|
|
28505
|
+
if (!ads.played)
|
|
28506
|
+
return AD_WORDS.launch;
|
|
28507
|
+
if (ads.lastBreak !== null && now - ads.lastBreak < BREAK_GAP_TICKS)
|
|
28508
|
+
return AD_WORDS.backToBack;
|
|
28509
|
+
return null;
|
|
28510
|
+
}
|
|
28511
|
+
return ads.lastPress !== null && now - ads.lastPress <= REWARD_PRESS_TICKS ? null : AD_WORDS.unchosen;
|
|
28512
|
+
}
|
|
28513
|
+
var SIX = ["left", "right", "up", "down", "jump", "action"];
|
|
28514
|
+
var playButtons = (input) => (input.buttons ?? []).filter((name) => name !== "click" && !name.startsWith("click:"));
|
|
28515
|
+
var playHeld = (input) => SIX.some((button) => input[button]) || playButtons(input).length > 0;
|
|
28516
|
+
var playPressed = (input, before2) => SIX.some((button) => input[button] && !before2[button]) || playButtons(input).some((name) => !playButtons(before2).includes(name));
|
|
28517
|
+
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 !== "";
|
|
28518
|
+
|
|
28519
|
+
// ../core/dist/script/online.js
|
|
28520
|
+
var ONLINE_STATES = ["online", "resting", "off"];
|
|
28521
|
+
var PUSH_RESULTS = ["granted", "denied", "unavailable"];
|
|
28522
|
+
var CALL_RESULTS = ["ok", "error", "busy", "resting", "refused"];
|
|
28523
|
+
var ONLINE_LIMITS = {
|
|
28524
|
+
asksPerTick: 32,
|
|
28525
|
+
dataBytes: 8192,
|
|
28526
|
+
callName: /^[a-zA-Z][a-zA-Z0-9_]{0,39}$/,
|
|
28527
|
+
room: /^[a-z0-9][a-z0-9-]{0,39}$/,
|
|
28528
|
+
/** A file a game's scripts upload, as JSON; the screen's picture is the page's, capped by the server's size. */
|
|
28529
|
+
fileBytes: 1e6,
|
|
28530
|
+
/** What a download gives a game, as JSON. */
|
|
28531
|
+
downloadBytes: 1e6
|
|
28532
|
+
};
|
|
28533
|
+
var TOO_MANY_ASKS = `a tick asks the game's server at most ${String(ONLINE_LIMITS.asksPerTick)} times: spread the calls over ticks`;
|
|
28534
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28535
|
+
var jsonBytes = (data) => {
|
|
28536
|
+
try {
|
|
28537
|
+
const text2 = JSON.stringify(data);
|
|
28538
|
+
return text2 === void 0 ? null : new TextEncoder().encode(text2).length;
|
|
28539
|
+
} catch {
|
|
28540
|
+
return null;
|
|
28541
|
+
}
|
|
28542
|
+
};
|
|
28543
|
+
var dataProblem = (data) => {
|
|
28544
|
+
const bytes = jsonBytes(data === void 0 ? null : data);
|
|
28545
|
+
if (bytes === null)
|
|
28546
|
+
return "online data is plain data: numbers, strings, booleans, null, lists and objects of them";
|
|
28547
|
+
return bytes > ONLINE_LIMITS.dataBytes ? `online data is at most 8,192 bytes as JSON, and this is ${String(bytes)}` : null;
|
|
28548
|
+
};
|
|
28549
|
+
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)}`;
|
|
28550
|
+
var uploadProblem = (ask) => {
|
|
28551
|
+
const file = "file" in ask && ask["file"] !== void 0;
|
|
28552
|
+
if (file === (ask["screen"] === true))
|
|
28553
|
+
return "an upload is a file or the screen: give file data, or screen: true, not both";
|
|
28554
|
+
if (!file)
|
|
28555
|
+
return null;
|
|
28556
|
+
const bytes = jsonBytes(ask["file"]);
|
|
28557
|
+
if (bytes === null)
|
|
28558
|
+
return "a file is plain data: numbers, strings, booleans, null, lists and objects of them";
|
|
28559
|
+
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;
|
|
28560
|
+
};
|
|
28561
|
+
function askProblem(ask) {
|
|
28562
|
+
const kinds = "an online ask is a call, join, leave, send, upload, download or askPush";
|
|
28563
|
+
if (!isObject(ask))
|
|
28564
|
+
return kinds;
|
|
28565
|
+
switch (ask["kind"]) {
|
|
28566
|
+
case "call":
|
|
28567
|
+
return nameProblem(ask["name"]) ?? dataProblem(ask["data"]);
|
|
28568
|
+
case "upload":
|
|
28569
|
+
return nameProblem(ask["name"]) ?? dataProblem(ask["data"]) ?? uploadProblem(ask);
|
|
28570
|
+
case "download":
|
|
28571
|
+
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://";
|
|
28572
|
+
case "askPush":
|
|
28573
|
+
return null;
|
|
28574
|
+
case "join":
|
|
28575
|
+
case "leave":
|
|
28576
|
+
case "send":
|
|
28577
|
+
if (typeof ask["room"] !== "string" || !ONLINE_LIMITS.room.test(ask["room"])) {
|
|
28578
|
+
return `a room's name is 1 to 40 lowercase letters, digits and hyphens, like lobby-1, not ${JSON.stringify(ask["room"])}`;
|
|
28579
|
+
}
|
|
28580
|
+
return ask["kind"] === "send" ? dataProblem(ask["data"]) : null;
|
|
28581
|
+
default:
|
|
28582
|
+
return kinds;
|
|
28583
|
+
}
|
|
28584
|
+
}
|
|
28585
|
+
var usesOnline = (sources) => sources.some((source) => /\bonline\s*\.\s*(call|join|leave|send|state|upload|uploadScreen|download|askPush)\b/.test(source));
|
|
28586
|
+
function isOnlineHeard(value) {
|
|
28587
|
+
if (!isObject(value))
|
|
28588
|
+
return false;
|
|
28589
|
+
switch (value["kind"]) {
|
|
28590
|
+
case "answer":
|
|
28591
|
+
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");
|
|
28592
|
+
case "message":
|
|
28593
|
+
return typeof value["room"] === "string" && value["data"] !== void 0;
|
|
28594
|
+
case "joined":
|
|
28595
|
+
case "left":
|
|
28596
|
+
case "full":
|
|
28597
|
+
return typeof value["room"] === "string";
|
|
28598
|
+
case "upload":
|
|
28599
|
+
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");
|
|
28600
|
+
case "download":
|
|
28601
|
+
return typeof value["id"] === "number" && Number.isInteger(value["id"]) && CALL_RESULTS.includes(value["result"]) && (value["reason"] === void 0 || typeof value["reason"] === "string");
|
|
28602
|
+
case "push":
|
|
28603
|
+
return PUSH_RESULTS.includes(value["result"]);
|
|
28604
|
+
case "pushOpened":
|
|
28605
|
+
return value["data"] !== void 0;
|
|
28606
|
+
default:
|
|
28607
|
+
return false;
|
|
28608
|
+
}
|
|
28609
|
+
}
|
|
28610
|
+
|
|
28611
|
+
// ../core/dist/script/purchases.js
|
|
28612
|
+
var PURCHASE_RESULTS = ["granted", "pending", "cancelled", "unavailable", "refused"];
|
|
28613
|
+
var PRODUCT_ID = /^[a-z0-9][a-z0-9_.]{0,39}$/;
|
|
28614
|
+
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28615
|
+
function purchaseAskProblem(ask) {
|
|
28616
|
+
if (!isObject2(ask))
|
|
28617
|
+
return "a purchase ask is a buy of a product or a restore";
|
|
28618
|
+
if (ask["kind"] === "restore")
|
|
28619
|
+
return null;
|
|
28620
|
+
if (ask["kind"] !== "buy")
|
|
28621
|
+
return "a purchase ask is a buy of a product or a restore";
|
|
28622
|
+
const id = ask["id"];
|
|
28623
|
+
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)}`;
|
|
28624
|
+
}
|
|
28625
|
+
var isProduct = (value) => isObject2(value) && typeof value["id"] === "string" && typeof value["price"] === "string" && typeof value["name"] === "string";
|
|
28626
|
+
function isPurchaseHeard(value) {
|
|
28627
|
+
if (!isObject2(value))
|
|
28628
|
+
return false;
|
|
28629
|
+
switch (value["kind"]) {
|
|
28630
|
+
case "products":
|
|
28631
|
+
return Array.isArray(value["list"]) && value["list"].every(isProduct);
|
|
28632
|
+
case "bought":
|
|
28633
|
+
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");
|
|
28634
|
+
case "restored":
|
|
28635
|
+
return Array.isArray(value["ids"]) && value["ids"].every((id) => typeof id === "string");
|
|
28636
|
+
default:
|
|
28637
|
+
return false;
|
|
28638
|
+
}
|
|
28639
|
+
}
|
|
28640
|
+
var usesPurchases = (sources) => sources.some((source) => /\bpurchases\s*\.\s*(buy|restore|products)\b/.test(source));
|
|
28641
|
+
|
|
28478
28642
|
// ../core/dist/script/runtime.js
|
|
28479
28643
|
var import_sucrase = __toESM(require_dist2(), 1);
|
|
28480
28644
|
|
|
@@ -29378,6 +29542,33 @@ const __soundHow = (options, label) => {
|
|
|
29378
29542
|
}
|
|
29379
29543
|
return out
|
|
29380
29544
|
}
|
|
29545
|
+
/** Online (the online services spec): asked of the game's server through the page; what comes back is heard on a later
|
|
29546
|
+
* tick as an 'online' event. */
|
|
29547
|
+
const __online = Object.freeze({
|
|
29548
|
+
call(name, data) { return __call('onlineCall', { name, data: data === undefined ? null : data }) },
|
|
29549
|
+
join(room) { __call('onlineJoin', { room }) },
|
|
29550
|
+
leave(room) { __call('onlineLeave', { room }) },
|
|
29551
|
+
send(room, data) { __call('onlineSend', { room, data: data === undefined ? null : data }) },
|
|
29552
|
+
upload(name, file, data) { return __call('onlineUpload', { name, file: file === undefined ? null : file, data: data === undefined ? null : data }) },
|
|
29553
|
+
uploadScreen(name, data) { return __call('onlineUpload', { name, screen: true, data: data === undefined ? null : data }) },
|
|
29554
|
+
download(url) { return __call('onlineDownload', { url }) },
|
|
29555
|
+
askPush() { __call('onlineAskPush', {}) },
|
|
29556
|
+
get state() { return __call('onlineState', {}) },
|
|
29557
|
+
})
|
|
29558
|
+
/** Purchases (the online services spec, part 3): asked of the page, which asks the store; answered on a later tick as a
|
|
29559
|
+
* 'purchase' event. */
|
|
29560
|
+
const __purchases = Object.freeze({
|
|
29561
|
+
get products() { return __frozen(__revived(__call('purchaseProducts', {}))) },
|
|
29562
|
+
buy(id) { __call('purchaseBuy', { id }) },
|
|
29563
|
+
restore() { __call('purchaseRestore', {}) },
|
|
29564
|
+
})
|
|
29565
|
+
/** Ads (the ads spec): asked of the page, answered on a later tick as an 'ad' event. */
|
|
29566
|
+
const __ads = Object.freeze({
|
|
29567
|
+
break(placement) { __call('adBreak', { placement }) },
|
|
29568
|
+
reward(placement) { __call('adReward', { placement }) },
|
|
29569
|
+
ready(kind) { return __call('adReady', { kind }) === true },
|
|
29570
|
+
banner(position) { __call('adBanner', { position: position === undefined ? null : position }) },
|
|
29571
|
+
})
|
|
29381
29572
|
const __sound = {
|
|
29382
29573
|
play(name, options) { __call('sound', Object.assign({ name }, __soundHow(options, 'sound.play'))) },
|
|
29383
29574
|
loop(name, options) { return __call('soundLoop', Object.assign({ name }, __soundHow(options, 'sound.loop'))) },
|
|
@@ -29630,6 +29821,9 @@ const __game = {
|
|
|
29630
29821
|
get vars() { return __vars },
|
|
29631
29822
|
get turns() { return __turns },
|
|
29632
29823
|
get ui() { return __ui },
|
|
29824
|
+
get ads() { return __ads },
|
|
29825
|
+
get online() { return __online },
|
|
29826
|
+
get purchases() { return __purchases },
|
|
29633
29827
|
/** The game script's own fields, read only; null without one. */
|
|
29634
29828
|
get script() { return __frozen(__revived(__call('gameScript', null))) },
|
|
29635
29829
|
get input() { return __input },
|
|
@@ -29691,6 +29885,7 @@ const __game = {
|
|
|
29691
29885
|
restart(options) { __call('restart', { fade: __num(__opt(options).fade, null) }) },
|
|
29692
29886
|
flash(colour, seconds) { __call('screen', { flash: { color: __color(colour, 0xffffff), seconds: seconds === undefined ? 0.2 : seconds } }) },
|
|
29693
29887
|
fade(alpha, seconds, colour) { __call('screen', { fade: { alpha, seconds, color: __color(colour, 0x000000) } }) },
|
|
29888
|
+
openPrivacy() { __call('openPrivacy', null) },
|
|
29694
29889
|
save(key, value) { __call('save', { key, value: value === undefined ? null : value }) },
|
|
29695
29890
|
load(key) { return __call('load', { key }) },
|
|
29696
29891
|
get camera() { return __camera },
|
|
@@ -30130,7 +30325,7 @@ var FAILURES_BEFORE_PAUSE = 10;
|
|
|
30130
30325
|
var MEMORY_LIMIT = 32 * 1024 * 1024;
|
|
30131
30326
|
var INTERRUPTS_PER_CALL = 1e3;
|
|
30132
30327
|
var INTERRUPTS_PER_PHASE = 2e3;
|
|
30133
|
-
var STACK_BYTES =
|
|
30328
|
+
var STACK_BYTES = 128 * 1024;
|
|
30134
30329
|
var GAME_KEY = "__game";
|
|
30135
30330
|
var PRELUDE = `'use strict'
|
|
30136
30331
|
;(function () {
|
|
@@ -30821,7 +31016,8 @@ function build(sources, skipped) {
|
|
|
30821
31016
|
return { fields: fieldsOf(text2), error: null, ...wantReturn ? { returned } : {} };
|
|
30822
31017
|
} catch (error) {
|
|
30823
31018
|
healthy = false;
|
|
30824
|
-
|
|
31019
|
+
const hostStack = error instanceof RangeError && /call stack/i.test(error.message);
|
|
31020
|
+
return { ...failed(hostStack ? messageOf({ message: "stack overflow" }) : `the script broke the interpreter and was stopped: ${error instanceof Error ? error.message : String(error)}`), runaway: true };
|
|
30825
31021
|
} finally {
|
|
30826
31022
|
for (const handle of handles)
|
|
30827
31023
|
handle.dispose();
|
|
@@ -30893,6 +31089,7 @@ function mixAt(level, real) {
|
|
|
30893
31089
|
return level.from;
|
|
30894
31090
|
return level.from + (level.to - level.from) * (real - level.start) / level.ticks;
|
|
30895
31091
|
}
|
|
31092
|
+
var NO_PURCHASES_YET = { products: [], pending: null };
|
|
30896
31093
|
var START_CLOCK = { real: 0, rate: 60, owed: 0, paused: false, stopUntil: 0, latched: [] };
|
|
30897
31094
|
var ticking = /* @__PURE__ */ new Map();
|
|
30898
31095
|
var drawing = /* @__PURE__ */ new Map();
|
|
@@ -30921,7 +31118,7 @@ function runtimeOf(sources, draw = false) {
|
|
|
30921
31118
|
function hasScripts(scripts) {
|
|
30922
31119
|
return scripts.sources.game !== null || Object.keys(scripts.sources.kinds).length > 0;
|
|
30923
31120
|
}
|
|
30924
|
-
function initialScripts(given, kinds, seed = 1, saved = {}, audio, fonts) {
|
|
31121
|
+
function initialScripts(given, kinds, seed = 1, saved = {}, audio, fonts, adsOff) {
|
|
30925
31122
|
const sources = {
|
|
30926
31123
|
kinds: { ...given.kinds },
|
|
30927
31124
|
game: given.game,
|
|
@@ -30967,7 +31164,14 @@ function initialScripts(given, kinds, seed = 1, saved = {}, audio, fonts) {
|
|
|
30967
31164
|
sequences: [],
|
|
30968
31165
|
clock: START_CLOCK,
|
|
30969
31166
|
...audio === void 0 ? {} : { audio },
|
|
30970
|
-
...fonts === void 0 ? {} : { fonts }
|
|
31167
|
+
...fonts === void 0 ? {} : { fonts },
|
|
31168
|
+
// A game whose scripts mention ads keeps them from the first tick, its presses known before it first asks.
|
|
31169
|
+
...usesAds([...Object.values(sources.kinds), sources.game ?? "", ...Object.values(sources.modules ?? {})]) ? { ads: NO_ADS_YET } : {},
|
|
31170
|
+
...adsOff === void 0 ? {} : { adsOff },
|
|
31171
|
+
// A game whose scripts mention online keeps its state and call count from the first tick; any other never has them.
|
|
31172
|
+
...usesOnline([...Object.values(sources.kinds), sources.game ?? "", ...Object.values(sources.modules ?? {})]) ? { onlineState: "off", nextCall: 1 } : {},
|
|
31173
|
+
// A game whose scripts mention purchases keeps its products from the first tick; any other never has them.
|
|
31174
|
+
...usesPurchases([...Object.values(sources.kinds), sources.game ?? "", ...Object.values(sources.modules ?? {})]) ? { purchases: NO_PURCHASES_YET } : {}
|
|
30971
31175
|
};
|
|
30972
31176
|
}
|
|
30973
31177
|
|
|
@@ -31276,7 +31480,7 @@ function numericProps(props) {
|
|
|
31276
31480
|
}
|
|
31277
31481
|
function load(ast, options = {}) {
|
|
31278
31482
|
const palette2 = paletteOf(ast);
|
|
31279
|
-
const scripts = options.scripts === void 0 ? null : initialScripts(options.scripts, new Set(ast.entities.map((e) => e.name)), options.seed, options.saved, options.audio, options.fonts);
|
|
31483
|
+
const scripts = options.scripts === void 0 ? null : initialScripts(options.scripts, new Set(ast.entities.map((e) => e.name)), options.seed, options.saved, options.audio, options.fonts, options.adsOff);
|
|
31280
31484
|
const defs = [];
|
|
31281
31485
|
const defIndexOf = /* @__PURE__ */ new Map();
|
|
31282
31486
|
const kindDefs = /* @__PURE__ */ new Map();
|
|
@@ -31780,6 +31984,71 @@ interface Mixer {
|
|
|
31780
31984
|
}
|
|
31781
31985
|
|
|
31782
31986
|
/** game.turns: whose turn it is, in an order of objects. */
|
|
31987
|
+
/** Ads, for a game in Google Play and the App Store, from its maker's own AdMob account (the guide's publish topic).
|
|
31988
|
+
* The page answers a break or a reward on a later tick, and every script hears it as onEvent('ad', { kind, placement,
|
|
31989
|
+
* result, reason? }): result is 'shown', 'rewarded', 'closed', 'unavailable' or 'refused'. Give a reward only on
|
|
31990
|
+
* 'rewarded'. In the editor an ad is a placeholder card; in browser play every ad is 'unavailable'. */
|
|
31991
|
+
interface Ads {
|
|
31992
|
+
/** A full-screen break at a natural pause, like a level's end: placement names it, like 'level-end'. Refused before
|
|
31993
|
+
* the player's first input, and less than 60 seconds after the last break. */
|
|
31994
|
+
break(placement: string): void
|
|
31995
|
+
/** An ad the player chose to watch for a reward, like 'extra-life': ask it right after the player's press on the
|
|
31996
|
+
* game's own offer. Refused with no press in the last second. */
|
|
31997
|
+
reward(placement: string): void
|
|
31998
|
+
/** 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. */
|
|
31999
|
+
ready(kind: 'break' | 'reward'): boolean
|
|
32000
|
+
/** A banner at the top or bottom of the screen, which the game's view makes room for; null hides it. */
|
|
32001
|
+
banner(position: 'top' | 'bottom' | null): void
|
|
32002
|
+
}
|
|
32003
|
+
|
|
32004
|
+
/** Real-money purchases in the game's store app (the guide's purchases topic): the products purchases.json declares,
|
|
32005
|
+
* bought through Apple's and Google's own in-app purchases. Each answer reaches every script on a later tick as
|
|
32006
|
+
* onEvent('purchase', heard), where heard is { kind: 'products', list }, { kind: 'bought', id, result, data?, reason?,
|
|
32007
|
+
* test? } or { kind: 'restored', ids }. Give what was bought only on result 'granted' (data is what the game's server
|
|
32008
|
+
* answered from onPurchase); 'pending' is not paid yet, and its answer comes again once it is. In a browser and a web
|
|
32009
|
+
* export, every buy is 'unavailable'. */
|
|
32010
|
+
interface Purchases {
|
|
32011
|
+
/** The products as the store shows them to this player: the price in their money, the name in their language; empty
|
|
32012
|
+
* until the store answers, and in a browser. */
|
|
32013
|
+
readonly products: readonly { readonly id: string; readonly price: string; readonly name: string }[]
|
|
32014
|
+
/** Buys a product by its id, like 'coins_100': the store's own sheet asks the player. One at a time: a second buy
|
|
32015
|
+
* while one waits for its answer is heard 'refused'. */
|
|
32016
|
+
buy(id: string): void
|
|
32017
|
+
/** Asks the store for the unlocks and subscriptions this player owns, heard as { kind: 'restored', ids }: Apple asks for
|
|
32018
|
+
* a Restore purchases button wherever unlocks are sold. */
|
|
32019
|
+
restore(): void
|
|
32020
|
+
}
|
|
32021
|
+
|
|
32022
|
+
/** The game's own server, for a game whose Online tab turned it on (the guide's online topic). Each ask goes to the
|
|
32023
|
+
* server through the page; what comes back reaches every script on a later tick as onEvent('online', heard), where
|
|
32024
|
+
* heard is { kind: 'answer', id, name, result, data?, reason? } (result 'ok', 'error', 'busy', 'resting' or 'refused'),
|
|
32025
|
+
* { kind: 'message', room, data }, or { kind: 'joined' | 'left' | 'full', room }. A tick never waits on the network. */
|
|
32026
|
+
interface Online {
|
|
32027
|
+
/** Asks the server's call by name, like 'postScore', with plain data (at most 8,192 bytes as JSON); answers the call's
|
|
32028
|
+
* number, which its answer carries. */
|
|
32029
|
+
call(name: string, data?: Plain): number
|
|
32030
|
+
/** Joins a live room by name (lowercase letters, digits and hyphens, like 'lobby-1'); heard as 'joined', or 'full'. */
|
|
32031
|
+
join(room: string): void
|
|
32032
|
+
leave(room: string): void
|
|
32033
|
+
/** Sends plain data to a room's code, which decides what everyone hears. */
|
|
32034
|
+
send(room: string, data: Plain): void
|
|
32035
|
+
/** 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,
|
|
32036
|
+
* which gets { size, type, data } and answers what ctx.files.uploadUrl gives; heard as { kind: 'upload', id, name,
|
|
32037
|
+
* result, reason? }. Answers the upload's number. */
|
|
32038
|
+
upload(call: string, file: Plain, data?: Plain): number
|
|
32039
|
+
/** Uploads a picture of the game's screen (a PNG) the same way. */
|
|
32040
|
+
uploadScreen(call: string, data?: Plain): number
|
|
32041
|
+
/** Reads a file at an address the game's server gave (ctx.files.downloadUrl): heard as { kind: 'download', id, result,
|
|
32042
|
+
* data?, reason? }, its data the file's JSON (at most 1,000,000 bytes). Answers the download's number. */
|
|
32043
|
+
download(url: string): number
|
|
32044
|
+
/** Asks the player whether the game may send them notifications, where it can (a phone app with push on): heard as
|
|
32045
|
+
* { kind: 'push', result: 'granted' | 'denied' | 'unavailable' }. Never required to play, never rewarded. A
|
|
32046
|
+
* notification the player tapped to open the game is heard as { kind: 'pushOpened', data }. */
|
|
32047
|
+
askPush(): void
|
|
32048
|
+
/** 'online', 'resting' (the server is off or paused, or its wallet is empty) or 'off' (no connection yet). */
|
|
32049
|
+
readonly state: 'online' | 'resting' | 'off'
|
|
32050
|
+
}
|
|
32051
|
+
|
|
31783
32052
|
interface Turns {
|
|
31784
32053
|
/** Begins an order of these objects: the first's turn, round 1. */
|
|
31785
32054
|
begin(things: Thing[]): void
|
|
@@ -32153,6 +32422,15 @@ interface Game {
|
|
|
32153
32422
|
/** The game's UI elements: placed, hovered, pressed and clicked by the engine, drawn by the game. Kept through a goTo,
|
|
32154
32423
|
* gone with a restart. */
|
|
32155
32424
|
readonly ui: Ui
|
|
32425
|
+
/** Breaks, rewards and banners from the game's maker's own AdMob account. */
|
|
32426
|
+
readonly ads: Ads
|
|
32427
|
+
/** The game's own server: calls and live rooms. */
|
|
32428
|
+
readonly online: Online
|
|
32429
|
+
/** Real-money purchases in the game's store app. */
|
|
32430
|
+
readonly purchases: Purchases
|
|
32431
|
+
/** Opens the game's privacy policy, as a game on the app stores must from inside itself: call it when the player
|
|
32432
|
+
* taps the game's own Privacy button. The page knows where the policy is; with none yet, nothing opens. */
|
|
32433
|
+
openPrivacy(): void
|
|
32156
32434
|
/** Kept across restarts and levels, and between plays in the Game tab (in that
|
|
32157
32435
|
* browser). A playtest starts with nothing saved. */
|
|
32158
32436
|
save(key: string, value: Plain): void
|
|
@@ -46900,9 +47178,68 @@ function keyOf2(input) {
|
|
|
46900
47178
|
const wheel = input.wheel === void 0 || input.wheel === 0 ? "" : `~${String(input.wheel)}`;
|
|
46901
47179
|
const sticks = input.sticks === void 0 || input.sticks.length === 0 ? "" : `&${input.sticks.map((s) => `${String(s.lx)},${String(s.ly)},${String(s.rx)},${String(s.ry)}`).join("/")}`;
|
|
46902
47180
|
const touches = input.touches === void 0 || input.touches.length === 0 ? "" : `!${input.touches.map((t) => `${String(t.x)},${String(t.y)}`).join(";")}`;
|
|
47181
|
+
const ready = readyKey(input.adsReady);
|
|
47182
|
+
const answer = input.adAnswer === void 0 ? "" : `$${ANSWER_LETTERS[input.adAnswer]}`;
|
|
47183
|
+
const online = onlineKey(input);
|
|
46903
47184
|
const language = input.language === void 0 ? "" : `=${input.language}`;
|
|
46904
47185
|
const typed = input.typed === void 0 || input.typed === "" ? "" : `"${escapeTyped(input.typed)}"`;
|
|
46905
|
-
return `${letters}${own2}${aim}${screen}${size}${wheel}${sticks}${touches}${language}${typed}`;
|
|
47186
|
+
return `${letters}${own2}${aim}${screen}${size}${wheel}${sticks}${touches}${ready}${answer}${online}${language}${typed}`;
|
|
47187
|
+
}
|
|
47188
|
+
function onlineKey(input) {
|
|
47189
|
+
if (input.online === void 0 && input.onlineState === void 0 && input.purchases === void 0)
|
|
47190
|
+
return "";
|
|
47191
|
+
const json = JSON.stringify({
|
|
47192
|
+
...input.online === void 0 ? {} : { h: input.online },
|
|
47193
|
+
...input.onlineState === void 0 ? {} : { s: input.onlineState },
|
|
47194
|
+
...input.purchases === void 0 ? {} : { p: input.purchases }
|
|
47195
|
+
});
|
|
47196
|
+
let text2 = "";
|
|
47197
|
+
for (const byte of new TextEncoder().encode(json))
|
|
47198
|
+
text2 += String.fromCharCode(byte);
|
|
47199
|
+
return `>${btoa(text2).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")}`;
|
|
47200
|
+
}
|
|
47201
|
+
function onlineOf(text2) {
|
|
47202
|
+
if (!/^[A-Za-z0-9_-]+$/.test(text2))
|
|
47203
|
+
return null;
|
|
47204
|
+
try {
|
|
47205
|
+
const padded = text2.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - text2.length % 4) % 4);
|
|
47206
|
+
const bytes = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
|
|
47207
|
+
const read2 = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
47208
|
+
if (typeof read2 !== "object" || read2 === null || Array.isArray(read2))
|
|
47209
|
+
return null;
|
|
47210
|
+
const { h, s, p } = read2;
|
|
47211
|
+
if (h !== void 0 && (!Array.isArray(h) || !h.every(isOnlineHeard)))
|
|
47212
|
+
return null;
|
|
47213
|
+
if (s !== void 0 && !ONLINE_STATES.includes(s))
|
|
47214
|
+
return null;
|
|
47215
|
+
if (p !== void 0 && (!Array.isArray(p) || !p.every(isPurchaseHeard)))
|
|
47216
|
+
return null;
|
|
47217
|
+
if (h === void 0 && s === void 0 && p === void 0)
|
|
47218
|
+
return null;
|
|
47219
|
+
return {
|
|
47220
|
+
...h === void 0 ? {} : { online: h },
|
|
47221
|
+
...s === void 0 ? {} : { onlineState: s },
|
|
47222
|
+
...p === void 0 ? {} : { purchases: p }
|
|
47223
|
+
};
|
|
47224
|
+
} catch {
|
|
47225
|
+
return null;
|
|
47226
|
+
}
|
|
47227
|
+
}
|
|
47228
|
+
var ANSWER_LETTERS = { shown: "s", rewarded: "r", closed: "c", unavailable: "u" };
|
|
47229
|
+
var ANSWER_OF = { s: "shown", r: "rewarded", c: "closed", u: "unavailable" };
|
|
47230
|
+
var READY_LETTERS = [["b", "break"], ["w", "reward"]];
|
|
47231
|
+
function readyKey(ready) {
|
|
47232
|
+
if (ready === void 0)
|
|
47233
|
+
return "";
|
|
47234
|
+
return `#${READY_LETTERS.filter(([, kind]) => ready.includes(kind)).map(([letter]) => letter).join("")}`;
|
|
47235
|
+
}
|
|
47236
|
+
function answerOf(text2) {
|
|
47237
|
+
return text2.length === 1 ? ANSWER_OF[text2] ?? null : null;
|
|
47238
|
+
}
|
|
47239
|
+
function readyOf(text2) {
|
|
47240
|
+
if (![...text2].every((ch) => READY_LETTERS.some(([letter]) => letter === ch)))
|
|
47241
|
+
return null;
|
|
47242
|
+
return READY_LETTERS.filter(([letter]) => text2.includes(letter)).map(([, kind]) => kind);
|
|
46906
47243
|
}
|
|
46907
47244
|
var TYPED_ESCAPES = { "\\": "\\\\", '"': '\\"', " ": "\\s", "\b": "\\b", "\n": "\\n" };
|
|
46908
47245
|
var UNESCAPED = { "\\": "\\", '"': '"', s: " ", b: "\b", n: "\n" };
|
|
@@ -46961,8 +47298,17 @@ function inputOf(run2) {
|
|
|
46961
47298
|
const spoken = quote === -1 ? run2 : run2.slice(0, quote);
|
|
46962
47299
|
const typed = quote === -1 ? null : typedOf(run2.slice(quote + 1, -1));
|
|
46963
47300
|
const languageAt = spoken.indexOf("=");
|
|
46964
|
-
const
|
|
47301
|
+
const heard = languageAt === -1 ? spoken : spoken.slice(0, languageAt);
|
|
46965
47302
|
const language = languageAt === -1 ? null : spoken.slice(languageAt + 1);
|
|
47303
|
+
const onlineAt = heard.indexOf(">");
|
|
47304
|
+
const answered = onlineAt === -1 ? heard : heard.slice(0, onlineAt);
|
|
47305
|
+
const online = onlineAt === -1 ? null : onlineOf(heard.slice(onlineAt + 1));
|
|
47306
|
+
const answerAt = answered.indexOf("$");
|
|
47307
|
+
const readied = answerAt === -1 ? answered : answered.slice(0, answerAt);
|
|
47308
|
+
const answer = answerAt === -1 ? null : answerOf(answered.slice(answerAt + 1));
|
|
47309
|
+
const readyAt = readied.indexOf("#");
|
|
47310
|
+
const fingered = readyAt === -1 ? readied : readied.slice(0, readyAt);
|
|
47311
|
+
const ready = readyAt === -1 ? null : readyOf(readied.slice(readyAt + 1));
|
|
46966
47312
|
const touchesAt = fingered.indexOf("!");
|
|
46967
47313
|
const full = touchesAt === -1 ? fingered : fingered.slice(0, touchesAt);
|
|
46968
47314
|
const touches = touchesAt === -1 ? null : touchesOf(fingered.slice(touchesAt + 1));
|
|
@@ -46986,6 +47332,9 @@ function inputOf(run2) {
|
|
|
46986
47332
|
...wheel === null ? {} : { wheel },
|
|
46987
47333
|
...sticks === null ? {} : { sticks },
|
|
46988
47334
|
...touches === null ? {} : { touches },
|
|
47335
|
+
...ready === null ? {} : { adsReady: ready },
|
|
47336
|
+
...answer === null ? {} : { adAnswer: answer },
|
|
47337
|
+
...online ?? {},
|
|
46989
47338
|
...language === null ? {} : { language },
|
|
46990
47339
|
...typed === null ? {} : { typed }
|
|
46991
47340
|
};
|
|
@@ -47216,13 +47565,43 @@ function scan(text2, chars) {
|
|
|
47216
47565
|
}
|
|
47217
47566
|
languageKey = `=${language}`;
|
|
47218
47567
|
}
|
|
47219
|
-
const
|
|
47220
|
-
const
|
|
47568
|
+
const onlineSign = rawFingered.indexOf(">");
|
|
47569
|
+
const rawAnswered = onlineSign === -1 ? rawFingered : rawFingered.slice(0, onlineSign);
|
|
47570
|
+
let onlineKeyed = "";
|
|
47571
|
+
if (onlineSign !== -1) {
|
|
47572
|
+
const online = onlineOf(rawFingered.slice(onlineSign + 1));
|
|
47573
|
+
if (online === null) {
|
|
47574
|
+
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);
|
|
47575
|
+
}
|
|
47576
|
+
onlineKeyed = onlineKey(online);
|
|
47577
|
+
}
|
|
47578
|
+
const answerSign = rawAnswered.indexOf("$");
|
|
47579
|
+
const rawReadied = answerSign === -1 ? rawAnswered : rawAnswered.slice(0, answerSign);
|
|
47580
|
+
let answerKey = "";
|
|
47581
|
+
if (answerSign !== -1) {
|
|
47582
|
+
const answer = answerOf(rawAnswered.slice(answerSign + 1));
|
|
47583
|
+
if (answer === null) {
|
|
47584
|
+
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);
|
|
47585
|
+
}
|
|
47586
|
+
answerKey = `$${ANSWER_LETTERS[answer]}`;
|
|
47587
|
+
}
|
|
47588
|
+
const readySign = rawReadied.indexOf("#");
|
|
47589
|
+
const rawTouched = readySign === -1 ? rawReadied : rawReadied.slice(0, readySign);
|
|
47590
|
+
let readyKeyed = "";
|
|
47591
|
+
if (readySign !== -1) {
|
|
47592
|
+
const ready = readyOf(rawReadied.slice(readySign + 1));
|
|
47593
|
+
if (ready === null) {
|
|
47594
|
+
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);
|
|
47595
|
+
}
|
|
47596
|
+
readyKeyed = readyKey(ready);
|
|
47597
|
+
}
|
|
47598
|
+
const touchesSign = rawTouched.indexOf("!");
|
|
47599
|
+
const rawFull = touchesSign === -1 ? rawTouched : rawTouched.slice(0, touchesSign);
|
|
47221
47600
|
let touchesKey = "";
|
|
47222
47601
|
if (touchesSign !== -1) {
|
|
47223
|
-
const touches = touchesOf(
|
|
47602
|
+
const touches = touchesOf(rawTouched.slice(touchesSign + 1));
|
|
47224
47603
|
if (touches === null) {
|
|
47225
|
-
throw new TrackError(`run ${index} has the touches '${
|
|
47604
|
+
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);
|
|
47226
47605
|
}
|
|
47227
47606
|
touchesKey = `!${touches.map((t) => `${String(t.x)},${String(t.y)}`).join(";")}`;
|
|
47228
47607
|
}
|
|
@@ -47308,7 +47687,7 @@ function scan(text2, chars) {
|
|
|
47308
47687
|
aimKey = `@${String(aim.x / AIM_PER_TILE)},${String(aim.y / AIM_PER_TILE)}`;
|
|
47309
47688
|
}
|
|
47310
47689
|
const lettered = rawButtons.toLowerCase().replace(/[+|]/g, "");
|
|
47311
|
-
const buttons = lettered === "" && (ownKey !== "" || aimKey !== "" || pointKey !== "" || sizeKey !== "" || wheelKey !== "" || sticksKey !== "" || touchesKey !== "" || languageKey !== "" || typedKey !== "") ? "-" : lettered;
|
|
47690
|
+
const buttons = lettered === "" && (ownKey !== "" || aimKey !== "" || pointKey !== "" || sizeKey !== "" || wheelKey !== "" || sticksKey !== "" || touchesKey !== "" || readyKeyed !== "" || answerKey !== "" || onlineKeyed !== "" || languageKey !== "" || typedKey !== "") ? "-" : lettered;
|
|
47312
47691
|
if (buttons === "") {
|
|
47313
47692
|
throw new TrackError(`run ${index} has a count but no buttons: '${token}'`, index, at2);
|
|
47314
47693
|
}
|
|
@@ -47323,7 +47702,7 @@ function scan(text2, chars) {
|
|
|
47323
47702
|
}
|
|
47324
47703
|
key = keyOf2(inputOf(buttons));
|
|
47325
47704
|
}
|
|
47326
|
-
key += ownKey + aimKey + pointKey + sizeKey + wheelKey + sticksKey + touchesKey + languageKey + typedKey;
|
|
47705
|
+
key += ownKey + aimKey + pointKey + sizeKey + wheelKey + sticksKey + touchesKey + readyKeyed + answerKey + onlineKeyed + languageKey + typedKey;
|
|
47327
47706
|
if (!/^[1-9][0-9]*$/.test(rawCount)) {
|
|
47328
47707
|
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);
|
|
47329
47708
|
}
|
|
@@ -48848,9 +49227,10 @@ function pressedMeanwhile(input, latched) {
|
|
|
48848
49227
|
function quiet(scripts) {
|
|
48849
49228
|
if (scripts === null)
|
|
48850
49229
|
return null;
|
|
48851
|
-
if (scripts.errors.length === 0 && scripts.warnings.length === 0 && scripts.logs.length === 0 && scripts.sounds.length === 0)
|
|
49230
|
+
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)
|
|
48852
49231
|
return scripts;
|
|
48853
|
-
|
|
49232
|
+
const { privacy: _privacy, ad: _ad, online: _online, purchase: _purchase, ...rest } = scripts;
|
|
49233
|
+
return { ...rest, errors: [], warnings: [], logs: [], sounds: [] };
|
|
48854
49234
|
}
|
|
48855
49235
|
function heldTick(state, input) {
|
|
48856
49236
|
const scripts = quiet(state.scripts);
|
|
@@ -49086,6 +49466,7 @@ function ownBodyWith(was, args) {
|
|
|
49086
49466
|
var LOGS_PER_TICK = 50;
|
|
49087
49467
|
var LOG_CHARS = 500;
|
|
49088
49468
|
var EVENTS_PER_TICK = 256;
|
|
49469
|
+
var AD_EVENTS_PER_TICK = 16;
|
|
49089
49470
|
var TIMERS_PER_OWNER = 64;
|
|
49090
49471
|
var TWEENS_PER_OWNER = 64;
|
|
49091
49472
|
var SEQUENCES_PER_OWNER = 16;
|
|
@@ -49100,7 +49481,7 @@ var OWN_AUDIO = /^own:[a-z0-9][a-z0-9-]{0,39}$/;
|
|
|
49100
49481
|
var LIB_AUDIO = /^lib:[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/;
|
|
49101
49482
|
var MOST_BURSTS = 64;
|
|
49102
49483
|
var MOST_DEFERRED = 1024;
|
|
49103
|
-
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"]);
|
|
49484
|
+
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"]);
|
|
49104
49485
|
function screenOf2(input) {
|
|
49105
49486
|
const size = input.screenSize ?? SCREEN_DEFAULT;
|
|
49106
49487
|
return { screen: input.screenPointer ?? null, size: { width: size.w, height: size.h } };
|
|
@@ -49169,7 +49550,7 @@ function fromUnit(raw, unit, what) {
|
|
|
49169
49550
|
return fromInt(Math.round(value));
|
|
49170
49551
|
}
|
|
49171
49552
|
}
|
|
49172
|
-
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"]);
|
|
49553
|
+
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"]);
|
|
49173
49554
|
function sidesOf(normal) {
|
|
49174
49555
|
switch (normal) {
|
|
49175
49556
|
case "a_above_b":
|
|
@@ -49281,6 +49662,20 @@ var Turn = class {
|
|
|
49281
49662
|
language;
|
|
49282
49663
|
camera;
|
|
49283
49664
|
sounds;
|
|
49665
|
+
/** game.openPrivacy() was called this tick (the store publishing spec). */
|
|
49666
|
+
privacy;
|
|
49667
|
+
/** The ad asked this tick, and what the game keeps of its ads, absent until it has any (the ads spec). */
|
|
49668
|
+
ad;
|
|
49669
|
+
ads;
|
|
49670
|
+
/** What the scripts asked of the game's server this tick; whether it is online; the next call's number, absent for a
|
|
49671
|
+
* game that never mentions online (the online services spec). */
|
|
49672
|
+
online;
|
|
49673
|
+
onlineState;
|
|
49674
|
+
nextCall;
|
|
49675
|
+
/** What the scripts asked of the store this tick, and what the game keeps of its purchases, absent for a game that
|
|
49676
|
+
* never mentions them (the online services spec, part 3). */
|
|
49677
|
+
purchase;
|
|
49678
|
+
purchases;
|
|
49284
49679
|
music;
|
|
49285
49680
|
/** Sounds playing round, and the mixer, replaced whole when changed
|
|
49286
49681
|
* (sub-project 9). */
|
|
@@ -49341,6 +49736,14 @@ var Turn = class {
|
|
|
49341
49736
|
this.saved = scripts.saved;
|
|
49342
49737
|
this.camera = scripts.camera;
|
|
49343
49738
|
this.sounds = fresh ? [] : [...scripts.sounds];
|
|
49739
|
+
this.privacy = !fresh && scripts.privacy === true;
|
|
49740
|
+
this.ad = fresh ? void 0 : scripts.ad;
|
|
49741
|
+
this.ads = scripts.ads;
|
|
49742
|
+
this.online = fresh || scripts.online === void 0 ? void 0 : [...scripts.online];
|
|
49743
|
+
this.onlineState = scripts.onlineState;
|
|
49744
|
+
this.nextCall = scripts.nextCall;
|
|
49745
|
+
this.purchase = fresh ? void 0 : scripts.purchase;
|
|
49746
|
+
this.purchases = scripts.purchases;
|
|
49344
49747
|
this.music = scripts.music;
|
|
49345
49748
|
this.loops = scripts.loops;
|
|
49346
49749
|
this.mixer = scripts.mixer;
|
|
@@ -49486,6 +49889,14 @@ var Turn = class {
|
|
|
49486
49889
|
logs: this.logs.length === 0 ? [] : [...this.logs],
|
|
49487
49890
|
warnings: this.warnings.length,
|
|
49488
49891
|
sounds: this.sounds.length,
|
|
49892
|
+
privacy: this.privacy,
|
|
49893
|
+
ad: this.ad,
|
|
49894
|
+
ads: this.ads,
|
|
49895
|
+
online: this.online === void 0 ? void 0 : [...this.online],
|
|
49896
|
+
onlineState: this.onlineState,
|
|
49897
|
+
nextCall: this.nextCall,
|
|
49898
|
+
purchase: this.purchase,
|
|
49899
|
+
purchases: this.purchases,
|
|
49489
49900
|
bursts: this.bursts.length === 0 ? [] : [...this.bursts],
|
|
49490
49901
|
screen: this.screen,
|
|
49491
49902
|
waiting: this.waiting,
|
|
@@ -49539,6 +49950,14 @@ var Turn = class {
|
|
|
49539
49950
|
this.logs.splice(0, this.logs.length, ...was.logs);
|
|
49540
49951
|
this.warnings.length = was.warnings;
|
|
49541
49952
|
this.sounds.length = was.sounds;
|
|
49953
|
+
this.privacy = was.privacy;
|
|
49954
|
+
this.ad = was.ad;
|
|
49955
|
+
this.ads = was.ads;
|
|
49956
|
+
this.online = was.online;
|
|
49957
|
+
this.onlineState = was.onlineState;
|
|
49958
|
+
this.nextCall = was.nextCall;
|
|
49959
|
+
this.purchase = was.purchase;
|
|
49960
|
+
this.purchases = was.purchases;
|
|
49542
49961
|
this.bursts = was.bursts;
|
|
49543
49962
|
this.screen = was.screen;
|
|
49544
49963
|
this.waiting = was.waiting;
|
|
@@ -50810,6 +51229,76 @@ var Turn = class {
|
|
|
50810
51229
|
// kind's script to read; null for a game without one.
|
|
50811
51230
|
case "gameScript":
|
|
50812
51231
|
return this.scripts.sources.game === null ? null : this.game ?? {};
|
|
51232
|
+
// The game's privacy policy (the store publishing spec): asked of the page, which knows where it is.
|
|
51233
|
+
// Ads (the ads spec): asked of the page, which answers on a later tick, as input.
|
|
51234
|
+
// Online (the online services spec): asked of the page, which carries it to the game's server; what comes back
|
|
51235
|
+
// is heard on a later tick, as input.
|
|
51236
|
+
case "onlineCall": {
|
|
51237
|
+
const id = this.nextCall ?? 1;
|
|
51238
|
+
this.askOnline({ kind: "call", id, name: args.name, data: args.data ?? null });
|
|
51239
|
+
this.nextCall = id + 1;
|
|
51240
|
+
return id;
|
|
51241
|
+
}
|
|
51242
|
+
case "onlineJoin":
|
|
51243
|
+
case "onlineLeave":
|
|
51244
|
+
this.askOnline({ kind: op === "onlineJoin" ? "join" : "leave", room: args.room });
|
|
51245
|
+
return null;
|
|
51246
|
+
case "onlineSend":
|
|
51247
|
+
this.askOnline({ kind: "send", room: args.room, data: args.data ?? null });
|
|
51248
|
+
return null;
|
|
51249
|
+
// Part 2: an upload through one of the game's calls, a download, and asking for push; numbered with the calls.
|
|
51250
|
+
case "onlineUpload":
|
|
51251
|
+
case "onlineDownload": {
|
|
51252
|
+
const id = this.nextCall ?? 1;
|
|
51253
|
+
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 } });
|
|
51254
|
+
this.nextCall = id + 1;
|
|
51255
|
+
return id;
|
|
51256
|
+
}
|
|
51257
|
+
case "onlineAskPush":
|
|
51258
|
+
this.askOnline({ kind: "askPush" });
|
|
51259
|
+
return null;
|
|
51260
|
+
case "onlineState":
|
|
51261
|
+
return this.onlineState ?? "off";
|
|
51262
|
+
// Purchases (the online services spec, part 3): asked of the page, which asks the store; the answer is heard on a
|
|
51263
|
+
// later tick, as input.
|
|
51264
|
+
case "purchaseBuy":
|
|
51265
|
+
this.askPurchase({ kind: "buy", id: args.id });
|
|
51266
|
+
return null;
|
|
51267
|
+
case "purchaseRestore":
|
|
51268
|
+
this.askPurchase({ kind: "restore" });
|
|
51269
|
+
return null;
|
|
51270
|
+
case "purchaseProducts":
|
|
51271
|
+
return (this.purchases ?? NO_PURCHASES_YET).products;
|
|
51272
|
+
case "adBreak":
|
|
51273
|
+
case "adReward": {
|
|
51274
|
+
const placement = args.placement;
|
|
51275
|
+
if (typeof placement !== "string" || !PLACEMENT.test(placement))
|
|
51276
|
+
throw new Error(`${op === "adBreak" ? "ads.break" : "ads.reward"} was given ${JSON.stringify(placement)}: ${PLACEMENT_WORDS}`);
|
|
51277
|
+
this.askAd({ kind: op === "adBreak" ? "break" : "reward", placement });
|
|
51278
|
+
return null;
|
|
51279
|
+
}
|
|
51280
|
+
case "adReady": {
|
|
51281
|
+
const kind = args.kind;
|
|
51282
|
+
if (!AD_KINDS.includes(kind))
|
|
51283
|
+
throw new Error(`ads.ready takes 'break' or 'reward', not ${JSON.stringify(kind)}`);
|
|
51284
|
+
return this.scripts.adsOff === void 0 && adsOf(this.ads).ready.includes(kind);
|
|
51285
|
+
}
|
|
51286
|
+
case "adBanner": {
|
|
51287
|
+
const position = args.position;
|
|
51288
|
+
if (position !== null && position !== "top" && position !== "bottom")
|
|
51289
|
+
throw new Error(`ads.banner takes 'top', 'bottom' or null, not ${JSON.stringify(position)}`);
|
|
51290
|
+
if (position !== null && this.scripts.adsOff !== void 0) {
|
|
51291
|
+
this.warn(this.scripts.adsOff);
|
|
51292
|
+
return null;
|
|
51293
|
+
}
|
|
51294
|
+
this.ads = { ...adsOf(this.ads), banner: position };
|
|
51295
|
+
if (position !== null && this.ui !== void 0)
|
|
51296
|
+
this.bannerBeside(position, this.ui);
|
|
51297
|
+
return null;
|
|
51298
|
+
}
|
|
51299
|
+
case "openPrivacy":
|
|
51300
|
+
this.privacy = true;
|
|
51301
|
+
return null;
|
|
50813
51302
|
case "save": {
|
|
50814
51303
|
this.saved = { ...this.saved, [text(args.key, "save: key")]: args.value ?? null };
|
|
50815
51304
|
return null;
|
|
@@ -51390,8 +51879,132 @@ var Turn = class {
|
|
|
51390
51879
|
this.setDriven(id, { ...this.driven.get(id) ?? { x: false, y: false }, facing: true });
|
|
51391
51880
|
return this.row(e);
|
|
51392
51881
|
}
|
|
51882
|
+
/** An ad asked of the page for this tick, and kept until its answer comes (the ads spec); refused at once, in the
|
|
51883
|
+
* words of the rule it breaks, when it breaks one the engine can tell exactly, and warned of when a break comes in
|
|
51884
|
+
* the middle of play. */
|
|
51885
|
+
askAd(ask) {
|
|
51886
|
+
const ads = adsOf(this.ads);
|
|
51887
|
+
const refused = adRefusal(ads, ask.kind, this.real, this.scripts.adsOff ?? null);
|
|
51888
|
+
if (refused !== null) {
|
|
51889
|
+
this.warn(refused);
|
|
51890
|
+
this.hearAd({ kind: ask.kind, placement: ask.placement, result: "refused", reason: refused });
|
|
51891
|
+
return;
|
|
51892
|
+
}
|
|
51893
|
+
if (ask.kind === "break" && !this.clock.paused) {
|
|
51894
|
+
const playing = playHeld(this.input) || ads.lastPlay !== null && this.real - ads.lastPlay <= PLAY_PRESS_TICKS;
|
|
51895
|
+
const settled = ads.scene === null || this.real - ads.scene.at > SCENE_SETTLE_TICKS;
|
|
51896
|
+
if (playing && settled)
|
|
51897
|
+
this.warn(AD_WORDS.midPlay);
|
|
51898
|
+
}
|
|
51899
|
+
this.ad = ask;
|
|
51900
|
+
this.ads = { ...ads, pending: ask };
|
|
51901
|
+
}
|
|
51902
|
+
/** A banner beside one of the game's own buttons, warned of: a button within a few pixels of the banner's edge. */
|
|
51903
|
+
bannerBeside(position, ui) {
|
|
51904
|
+
const screen = screenOf2(this.input).size;
|
|
51905
|
+
for (const [name, box] of layoutUi(ui, screen)) {
|
|
51906
|
+
if (ui.elements[name]?.kind !== "button")
|
|
51907
|
+
continue;
|
|
51908
|
+
const beside = position === "top" ? box.y <= BANNER_EDGE_PX : box.y + box.h >= screen.height - BANNER_EDGE_PX;
|
|
51909
|
+
if (beside)
|
|
51910
|
+
this.warn(AD_WORDS.banner(name));
|
|
51911
|
+
}
|
|
51912
|
+
}
|
|
51913
|
+
/** What the tick's input says of ads: the kinds ready, and the page's answer to the ad pending, heard as an 'ad'
|
|
51914
|
+
* event; an answer with none pending is let go (the ads spec). */
|
|
51915
|
+
takeAdInput() {
|
|
51916
|
+
const { adAnswer, adsReady } = this.input;
|
|
51917
|
+
if (this.ads !== void 0) {
|
|
51918
|
+
const before2 = this.state.prevInput;
|
|
51919
|
+
const pressed = anyPressed(this.input, before2) || (this.ui?.clicked.length ?? 0) > 0;
|
|
51920
|
+
const played = playPressed(this.input, before2);
|
|
51921
|
+
const key = `${String(this.state.level)}:${String(this.scripts.loads)}`;
|
|
51922
|
+
const scene = this.ads.scene?.key === key ? this.ads.scene : { key, at: this.real };
|
|
51923
|
+
if (pressed || played || scene !== this.ads.scene) {
|
|
51924
|
+
this.ads = {
|
|
51925
|
+
...this.ads,
|
|
51926
|
+
scene,
|
|
51927
|
+
...pressed ? { played: true, lastPress: this.real } : {},
|
|
51928
|
+
...played ? { lastPlay: this.real } : {}
|
|
51929
|
+
};
|
|
51930
|
+
}
|
|
51931
|
+
}
|
|
51932
|
+
if (adsReady !== void 0)
|
|
51933
|
+
this.ads = { ...adsOf(this.ads), ready: AD_KINDS.filter((kind) => adsReady.includes(kind)) };
|
|
51934
|
+
const pending = this.ads?.pending ?? null;
|
|
51935
|
+
if (adAnswer === void 0 || pending === null)
|
|
51936
|
+
return;
|
|
51937
|
+
this.ads = { ...adsOf(this.ads), pending: null, ...pending.kind === "break" && adAnswer === "shown" ? { lastBreak: this.real } : {} };
|
|
51938
|
+
this.hearAd({ kind: pending.kind, placement: pending.placement, result: adAnswer });
|
|
51939
|
+
}
|
|
51940
|
+
/** An ask of the game's server, refused in words when it is not one, or past a tick's asks. */
|
|
51941
|
+
askOnline(ask) {
|
|
51942
|
+
const problem = askProblem(ask);
|
|
51943
|
+
if (problem !== null)
|
|
51944
|
+
throw new Error(problem);
|
|
51945
|
+
const asked = this.online ?? [];
|
|
51946
|
+
if (asked.length >= ONLINE_LIMITS.asksPerTick)
|
|
51947
|
+
throw new Error(TOO_MANY_ASKS);
|
|
51948
|
+
this.online = [...asked, ask];
|
|
51949
|
+
}
|
|
51950
|
+
/** What the page brought back from the game's server, and whether it is online, for a game that keeps online: each
|
|
51951
|
+
* heard as an 'online' event. A game that never mentions online ignores it. */
|
|
51952
|
+
takeOnlineInput() {
|
|
51953
|
+
if (this.nextCall === void 0)
|
|
51954
|
+
return;
|
|
51955
|
+
const { online, onlineState } = this.input;
|
|
51956
|
+
if (onlineState !== void 0)
|
|
51957
|
+
this.onlineState = onlineState;
|
|
51958
|
+
for (const heard of online ?? [])
|
|
51959
|
+
this.hearHost("online", heard);
|
|
51960
|
+
}
|
|
51961
|
+
/** 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
|
|
51962
|
+
* buy while another waits for its answer heard at once as refused. */
|
|
51963
|
+
askPurchase(ask) {
|
|
51964
|
+
const problem = purchaseAskProblem(ask);
|
|
51965
|
+
if (problem !== null)
|
|
51966
|
+
throw new Error(problem);
|
|
51967
|
+
if (this.purchase !== void 0)
|
|
51968
|
+
throw new Error("game.purchases asks the store once a tick: ask again on a later tick");
|
|
51969
|
+
const purchases = this.purchases ?? NO_PURCHASES_YET;
|
|
51970
|
+
if (ask.kind === "buy" && purchases.pending !== null) {
|
|
51971
|
+
this.hearHost("purchase", { kind: "bought", id: ask.id, result: "refused", reason: "a purchase is already in progress: wait for its answer" });
|
|
51972
|
+
return;
|
|
51973
|
+
}
|
|
51974
|
+
this.purchase = ask;
|
|
51975
|
+
if (ask.kind === "buy")
|
|
51976
|
+
this.purchases = { ...purchases, pending: ask.id };
|
|
51977
|
+
}
|
|
51978
|
+
/** What the store and the game's server answered of purchases, for a game that keeps them: the products kept, and each
|
|
51979
|
+
* heard as a 'purchase' event; the buy pending let go once its answer comes. A game that never mentions purchases
|
|
51980
|
+
* ignores it. */
|
|
51981
|
+
takePurchaseInput() {
|
|
51982
|
+
if (this.purchases === void 0)
|
|
51983
|
+
return;
|
|
51984
|
+
for (const heard of this.input.purchases ?? []) {
|
|
51985
|
+
if (heard.kind === "products")
|
|
51986
|
+
this.purchases = { ...this.purchases, products: heard.list };
|
|
51987
|
+
if (heard.kind === "bought" && heard.id === this.purchases.pending)
|
|
51988
|
+
this.purchases = { ...this.purchases, pending: null };
|
|
51989
|
+
this.hearHost("purchase", heard);
|
|
51990
|
+
}
|
|
51991
|
+
}
|
|
51992
|
+
/** An 'ad' event every onEvent hears on the next tick, as an emitted one is. */
|
|
51993
|
+
hearAd(data) {
|
|
51994
|
+
this.hearHost("ad", data);
|
|
51995
|
+
}
|
|
51996
|
+
/** An event from the page (an ad's answer, what the game heard online), heard past a tick's emitted events, up to its
|
|
51997
|
+
* own few (the final review of ads). */
|
|
51998
|
+
hearHost(name, data) {
|
|
51999
|
+
if (this.emitted.length >= EVENTS_PER_TICK + AD_EVENTS_PER_TICK)
|
|
52000
|
+
return;
|
|
52001
|
+
if (this.emitted.length >= EVENTS_PER_TICK && this.emitted.filter((one) => one.name === name).length >= AD_EVENTS_PER_TICK)
|
|
52002
|
+
return;
|
|
52003
|
+
this.emitted.push({ name, data: { ...data }, at: this.events.length });
|
|
52004
|
+
this.events.push(name);
|
|
52005
|
+
}
|
|
51393
52006
|
scriptsState() {
|
|
51394
|
-
const { starting: _was, loops: _loops, mixer: _mixer, typing: _typing, ui: _ui, ...before2 } = this.scripts;
|
|
52007
|
+
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;
|
|
51395
52008
|
return {
|
|
51396
52009
|
...before2,
|
|
51397
52010
|
fields: this.fields,
|
|
@@ -51417,6 +52030,14 @@ var Turn = class {
|
|
|
51417
52030
|
...this.mixer === void 0 ? {} : { mixer: this.mixer },
|
|
51418
52031
|
camera: this.camera,
|
|
51419
52032
|
sounds: this.sounds,
|
|
52033
|
+
...this.privacy ? { privacy: true } : {},
|
|
52034
|
+
...this.ad === void 0 ? {} : { ad: this.ad },
|
|
52035
|
+
...this.ads === void 0 ? {} : { ads: this.ads },
|
|
52036
|
+
...this.online === void 0 || this.online.length === 0 ? {} : { online: this.online },
|
|
52037
|
+
...this.onlineState === void 0 ? {} : { onlineState: this.onlineState },
|
|
52038
|
+
...this.nextCall === void 0 ? {} : { nextCall: this.nextCall },
|
|
52039
|
+
...this.purchase === void 0 ? {} : { purchase: this.purchase },
|
|
52040
|
+
...this.purchases === void 0 ? {} : { purchases: this.purchases },
|
|
51420
52041
|
music: this.music,
|
|
51421
52042
|
bursts: this.bursts,
|
|
51422
52043
|
deferred: this.deferred,
|
|
@@ -51484,6 +52105,9 @@ function beginScripts(state, input) {
|
|
|
51484
52105
|
if (scripts === null || !hasScripts(scripts))
|
|
51485
52106
|
return null;
|
|
51486
52107
|
const turn = new Turn({ ...state, events: [] }, scripts, input, state.contacts, state.step, true, scripts.driven);
|
|
52108
|
+
turn.takeAdInput();
|
|
52109
|
+
turn.takeOnlineInput();
|
|
52110
|
+
turn.takePurchaseInput();
|
|
51487
52111
|
turn.bursts = turn.bursts.filter((b) => turn.now - b.step < b.life * 60);
|
|
51488
52112
|
dueChange(turn);
|
|
51489
52113
|
for (const late of scripts.deferred) {
|
|
@@ -51588,6 +52212,9 @@ function pausedScripts(state, input) {
|
|
|
51588
52212
|
if (scripts === null)
|
|
51589
52213
|
throw new Error("a game without scripts cannot be paused");
|
|
51590
52214
|
const turn = new Turn({ ...state, events: [] }, scripts, input, state.contacts, state.step, true, scripts.driven);
|
|
52215
|
+
turn.takeAdInput();
|
|
52216
|
+
turn.takeOnlineInput();
|
|
52217
|
+
turn.takePurchaseInput();
|
|
51591
52218
|
dueChange(turn);
|
|
51592
52219
|
turn.call(null, "whilePaused", [DT]);
|
|
51593
52220
|
if (turn.clock.paused && (turn.paused.includes(0) || !(turn.runtime.has(null) && turn.runtime.hasHook(null, "whilePaused")))) {
|
|
@@ -51596,7 +52223,8 @@ function pausedScripts(state, input) {
|
|
|
51596
52223
|
}
|
|
51597
52224
|
heardClicks(turn);
|
|
51598
52225
|
const heard = closeScripts(turn.scriptsState(), turn.events).inbox;
|
|
51599
|
-
const
|
|
52226
|
+
const all = [...scripts.inbox, ...heard];
|
|
52227
|
+
const result = turn.result([...all.slice(0, EVENTS_PER_TICK), ...all.slice(EVENTS_PER_TICK).filter((one) => one.name === "ad" || one.name === "online").slice(0, AD_EVENTS_PER_TICK)]);
|
|
51600
52228
|
const held = result.state.scripts;
|
|
51601
52229
|
if (held === null)
|
|
51602
52230
|
return result;
|