thrunium 1.2.0-rc.2 → 1.2.0-rc.3

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/README.md CHANGED
@@ -64,7 +64,10 @@ from `game.json` (`entry`, else the first of `game.ts`, `game.js`, `src/game.ts`
64
64
  `src/game.js`; the title is `name`, else the folder name). A hand-written
65
65
  `index.html` is used as written, and having both it and `entry` is an error.
66
66
  `import './style.css'` from game code is bundled and linked, with `url()` fonts and
67
- images; remote `@import` stays remote.
67
+ images; remote `@import` stays remote. A generated page takes a title, description,
68
+ favicon, link-preview image, address and language from `page` in `game.json`, and
69
+ the contents of an optional `head.html` and `body.html` (see the "Dev server and
70
+ builds" guide). None of these mix with a hand-written `index.html`.
68
71
 
69
72
  `dev` never uses the CDN. For a project with a module entry
70
73
  (a generated page, or `<script type="module" src=...>` importing `thrunium`), `build` has two engine
package/dist/cli.mjs CHANGED
@@ -155,20 +155,28 @@ function hasEngineTag(html) {
155
155
  function escapeHtml(text) {
156
156
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
157
157
  }
158
- function generatePage(title, entry) {
158
+ function generatePage(title, entry, opts = {}) {
159
+ const head = (opts.headTags ?? []).map((tag) => ` ${tag}
160
+ `).join("") + (opts.headExtra ? `${HEAD_PLACEHOLDER}
161
+ ` : "");
162
+ const body = opts.bodyExtra ? `${BODY_PLACEHOLDER}
163
+ ` : "";
159
164
  return `<!DOCTYPE html>
160
- <html lang="en">
165
+ <html lang="${escapeHtml(opts.lang ?? "en")}">
161
166
  <head>
162
167
  <meta charset="utf-8">
163
168
  <meta name="viewport" content="width=device-width, initial-scale=1">
164
169
  <title>${escapeHtml(title)}</title>
165
- </head>
170
+ ${head}</head>
166
171
  <body style="margin: 0; padding: 0;">
167
- <script type="module" src="${escapeHtml(entry)}"></script>
172
+ ${body} <script type="module" src="${escapeHtml(entry)}"></script>
168
173
  </body>
169
174
  </html>
170
175
  `;
171
176
  }
177
+ function fillExtras(html, extras) {
178
+ return html.replace(HEAD_PLACEHOLDER, () => extras.head).replace(BODY_PLACEHOLDER, () => extras.body);
179
+ }
172
180
  function transformHtml(html, opts) {
173
181
  let out = html;
174
182
  const hasHead = /<\/head\s*>/i.test(html);
@@ -198,23 +206,98 @@ function transformHtml(html, opts) {
198
206
  }
199
207
  return out;
200
208
  }
201
- var SCRIPT_TAG, ATTRIBUTE, ENGINE_SRC;
209
+ var SCRIPT_TAG, ATTRIBUTE, ENGINE_SRC, HEAD_PLACEHOLDER, BODY_PLACEHOLDER;
202
210
  var init_html = __esm({
203
211
  "cli/html.ts"() {
204
212
  "use strict";
205
213
  SCRIPT_TAG = /<script\b[^>]*>/gi;
206
214
  ATTRIBUTE = /([^\s"'<>\/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
207
215
  ENGINE_SRC = /(^|\/)thrunium(\.min)?\.js([?#]|$)/;
216
+ HEAD_PLACEHOLDER = "<!--thrunium:head-->";
217
+ BODY_PLACEHOLDER = "<!--thrunium:body-->";
218
+ }
219
+ });
220
+
221
+ // cli/page.ts
222
+ import { existsSync as existsSync2, statSync } from "node:fs";
223
+ import { isAbsolute, join as join2, normalize } from "node:path";
224
+ function checkAsset(root, field, value) {
225
+ if (HTTP_URL.test(value)) return value;
226
+ const problem = (why) => new UserError(`"page.${field}" in game.json must be an http(s) URL or a relative path to a file in the project (${why}): ${value}`);
227
+ if (/^[a-z][a-z0-9+.-]*:/i.test(value) || value.startsWith("//")) throw problem("unsupported address");
228
+ if (/[?#]/.test(value)) throw problem("no ? or # suffix");
229
+ if (isAbsolute(value) || normalize(value).split(/[\\/]/).includes("..")) throw problem("outside the project");
230
+ const path = join2(root, value);
231
+ if (!existsSync2(path) || !statSync(path).isFile()) throw problem("file not found");
232
+ return value.replace(/^(\.\/)+/, "");
233
+ }
234
+ function readPageSettings(root, value) {
235
+ if (value === void 0) return {};
236
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
237
+ throw new UserError('"page" in game.json must be an object.');
238
+ }
239
+ const raw = value;
240
+ const settings = {};
241
+ for (const field of FIELDS) {
242
+ const v = raw[field];
243
+ if (v === void 0) continue;
244
+ if (typeof v !== "string") throw new UserError(`"page.${field}" in game.json must be a string.`);
245
+ if (v !== "") settings[field] = v;
246
+ }
247
+ if (settings.icon !== void 0) settings.icon = checkAsset(root, "icon", settings.icon);
248
+ if (settings.image !== void 0) settings.image = checkAsset(root, "image", settings.image);
249
+ if (settings.url !== void 0 && !(HTTP_URL.test(settings.url) && URL.canParse(settings.url))) {
250
+ throw new UserError(`"page.url" in game.json must be an absolute http(s) URL: ${settings.url}`);
251
+ }
252
+ return settings;
253
+ }
254
+ function pageHeadTags(settings, title) {
255
+ const { description, icon, url } = settings;
256
+ const image = settings.image !== void 0 && url !== void 0 && !HTTP_URL.test(settings.image) ? new URL(settings.image, url).href : settings.image;
257
+ const attr = escapeHtml;
258
+ const tags = [];
259
+ if (description !== void 0) tags.push(`<meta name="description" content="${attr(description)}">`);
260
+ if (url !== void 0) tags.push(`<link rel="canonical" href="${attr(url)}">`);
261
+ if (icon !== void 0) {
262
+ const ext = /\.[^./?#]+$/.exec(icon)?.[0].toLowerCase() ?? "";
263
+ const type = ICON_TYPES[ext];
264
+ tags.push(`<link rel="icon" href="${attr(icon)}"${type ? ` type="${type}"` : ""}>`);
265
+ }
266
+ if (description !== void 0 || image !== void 0 || url !== void 0) {
267
+ tags.push('<meta property="og:type" content="website">');
268
+ tags.push(`<meta property="og:title" content="${attr(title)}">`);
269
+ if (description !== void 0) tags.push(`<meta property="og:description" content="${attr(description)}">`);
270
+ if (image !== void 0) tags.push(`<meta property="og:image" content="${attr(image)}">`);
271
+ if (url !== void 0) tags.push(`<meta property="og:url" content="${attr(url)}">`);
272
+ tags.push(`<meta name="twitter:card" content="${image !== void 0 ? "summary_large_image" : "summary"}">`);
273
+ }
274
+ return tags;
275
+ }
276
+ var FIELDS, HTTP_URL, ICON_TYPES;
277
+ var init_page = __esm({
278
+ "cli/page.ts"() {
279
+ "use strict";
280
+ init_errors();
281
+ init_html();
282
+ FIELDS = ["title", "description", "icon", "image", "url", "lang"];
283
+ HTTP_URL = /^https?:\/\//i;
284
+ ICON_TYPES = {
285
+ ".png": "image/png",
286
+ ".svg": "image/svg+xml",
287
+ ".ico": "image/x-icon",
288
+ ".gif": "image/gif",
289
+ ".webp": "image/webp"
290
+ };
208
291
  }
209
292
  });
210
293
 
211
294
  // cli/project.ts
212
- import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "node:fs";
213
- import { basename, isAbsolute, join as join2, normalize, resolve } from "node:path";
295
+ import { existsSync as existsSync3, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
296
+ import { basename, isAbsolute as isAbsolute2, join as join3, normalize as normalize2, resolve } from "node:path";
214
297
  function resolveEngineMode(root, flag) {
215
298
  if (flag) return flag;
216
- const manifest = join2(root, "package.json");
217
- if (!existsSync2(manifest)) return "cdn";
299
+ const manifest = join3(root, "package.json");
300
+ if (!existsSync3(manifest)) return "cdn";
218
301
  let pkg;
219
302
  try {
220
303
  pkg = JSON.parse(readFileSync2(manifest, "utf8"));
@@ -227,11 +310,11 @@ function resolveEngineMode(root, flag) {
227
310
  throw new UserError(`Invalid "thrunium.engine" in package.json: ${JSON.stringify(value)}. Use "cdn" or "bundle".`);
228
311
  }
229
312
  function isFile(path) {
230
- return existsSync2(path) && statSync(path).isFile();
313
+ return existsSync3(path) && statSync2(path).isFile();
231
314
  }
232
315
  function readGameJson(root) {
233
- const file = join2(root, "game.json");
234
- if (!existsSync2(file)) return null;
316
+ const file = join3(root, "game.json");
317
+ if (!existsSync3(file)) return null;
235
318
  let json;
236
319
  try {
237
320
  json = JSON.parse(readFileSync2(file, "utf8"));
@@ -241,51 +324,77 @@ function readGameJson(root) {
241
324
  if (typeof json !== "object" || json === null || Array.isArray(json)) {
242
325
  throw new UserError("game.json must contain a JSON object.");
243
326
  }
244
- const { name, entry } = json;
327
+ const { name, entry, page } = json;
245
328
  if (name !== void 0 && typeof name !== "string") throw new UserError('"name" in game.json must be a string.');
246
329
  if (entry !== void 0 && typeof entry !== "string") throw new UserError('"entry" in game.json must be a string.');
247
- return { name, entry };
330
+ return { name, entry, page };
248
331
  }
249
332
  function validateEntry(root, entry) {
250
- const inside = !isAbsolute(entry) && !normalize(entry).split(/[\\/]/).includes("..");
333
+ const inside = !isAbsolute2(entry) && !normalize2(entry).split(/[\\/]/).includes("..");
251
334
  if (!inside) throw new UserError(`"entry" in game.json must be a relative path inside the project: ${entry}`);
252
335
  if (!ENTRY_EXTENSIONS.test(entry)) {
253
336
  throw new UserError(`"entry" in game.json must end in .ts, .js, .mts or .mjs: ${entry}`);
254
337
  }
255
- if (!isFile(join2(root, entry))) throw new UserError(`"entry" in game.json points to ${entry}, which does not exist.`);
338
+ if (!isFile(join3(root, entry))) throw new UserError(`"entry" in game.json points to ${entry}, which does not exist.`);
256
339
  }
257
- function hasEntryKey(file) {
340
+ function hasKey(file, key) {
258
341
  try {
259
342
  const json = JSON.parse(readFileSync2(file, "utf8"));
260
- return typeof json === "object" && json !== null && !Array.isArray(json) && "entry" in json;
343
+ return typeof json === "object" && json !== null && !Array.isArray(json) && key in json;
261
344
  } catch {
262
345
  return false;
263
346
  }
264
347
  }
348
+ function readExtra(root, file) {
349
+ const path = join3(root, file);
350
+ return isFile(path) ? readFileSync2(path, "utf8").replace(/^\uFEFF/, "").trimEnd() : "";
351
+ }
265
352
  function resolvePage(root) {
266
- const indexPath = join2(root, "index.html");
267
- if (existsSync2(indexPath)) {
268
- if (hasEntryKey(join2(root, "game.json"))) {
353
+ const indexPath = join3(root, "index.html");
354
+ if (existsSync3(indexPath)) {
355
+ const gameJson = join3(root, "game.json");
356
+ if (hasKey(gameJson, "entry")) {
269
357
  throw new UserError('game.json has "entry", but index.html also exists and is used instead. Remove one of them.');
270
358
  }
271
- return { html: readFileSync2(indexPath, "utf8"), htmlSource: "file" };
359
+ if (hasKey(gameJson, "page")) {
360
+ throw new UserError(
361
+ 'game.json has "page", but index.html also exists and is used instead. Move the settings into index.html or delete it.'
362
+ );
363
+ }
364
+ for (const file of ["head.html", "body.html"]) {
365
+ if (isFile(join3(root, file))) {
366
+ throw new UserError(
367
+ `${file} exists, but index.html also exists and is used instead. Move its contents into index.html or delete it.`
368
+ );
369
+ }
370
+ }
371
+ return { html: readFileSync2(indexPath, "utf8"), htmlSource: "file", extras: { head: "", body: "" } };
272
372
  }
273
373
  const game = readGameJson(root);
274
374
  let entry = game?.entry;
275
375
  if (entry !== void 0) validateEntry(root, entry);
276
- else entry = DEFAULT_ENTRIES.find((candidate) => isFile(join2(root, candidate)));
376
+ else entry = DEFAULT_ENTRIES.find((candidate) => isFile(join3(root, candidate)));
277
377
  if (entry === void 0) {
278
378
  throw new UserError(
279
379
  `No game found: add game.ts or game.js (or set "entry" in game.json), or an index.html. Looked for: ${DEFAULT_ENTRIES.join(", ")}.`
280
380
  );
281
381
  }
282
- return { html: generatePage(game?.name ?? basename(resolve(root)), entry), htmlSource: "generated" };
382
+ const settings = readPageSettings(root, game?.page);
383
+ const title = settings.title ?? game?.name ?? basename(resolve(root));
384
+ const extras = { head: readExtra(root, "head.html"), body: readExtra(root, "body.html") };
385
+ const html = generatePage(title, entry, {
386
+ lang: settings.lang,
387
+ headTags: pageHeadTags(settings, title),
388
+ headExtra: extras.head !== "",
389
+ bodyExtra: extras.body !== ""
390
+ });
391
+ return { html, htmlSource: "generated", extras };
283
392
  }
284
393
  function loadProject(root, engineFlag) {
285
- const { html, htmlSource } = resolvePage(root);
394
+ const { html, htmlSource, extras } = resolvePage(root);
286
395
  const entries = findModuleEntries(html);
287
396
  for (const entry of entries) {
288
- if (!existsSync2(join2(root, entry))) {
397
+ if (!existsSync3(join3(root, entry))) {
289
398
  throw new UserError(`Entry file ${entry} (from ${htmlSource === "file" ? "index.html" : "game.json"}) does not exist.`);
290
399
  }
291
400
  }
@@ -293,7 +402,7 @@ function loadProject(root, engineFlag) {
293
402
  if (usesModules && hasEngineTag(html)) {
294
403
  throw new UserError("Remove the Thrunium <script> tag from index.html; thrunium adds it for you.");
295
404
  }
296
- return { root, html, htmlSource, entries, usesModules, engineMode: resolveEngineMode(root, engineFlag) };
405
+ return { root, html, htmlSource, extras, entries, usesModules, engineMode: resolveEngineMode(root, engineFlag) };
297
406
  }
298
407
  function thruniumPlugin() {
299
408
  return {
@@ -334,6 +443,7 @@ var init_project = __esm({
334
443
  init_engine();
335
444
  init_errors();
336
445
  init_html();
446
+ init_page();
337
447
  DEFAULT_ENTRIES = ["game.ts", "game.js", "src/game.ts", "src/game.js"];
338
448
  ENTRY_EXTENSIONS = /\.(ts|js|mts|mjs)$/;
339
449
  ASSET_EXTENSIONS = [".woff", ".woff2", ".ttf", ".otf", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp"];
@@ -381,11 +491,11 @@ __export(dev_exports, {
381
491
  startDev: () => startDev
382
492
  });
383
493
  import { spawn } from "node:child_process";
384
- import { existsSync as existsSync3, readdirSync, realpathSync, statSync as statSync2, watch } from "node:fs";
494
+ import { existsSync as existsSync4, readdirSync, realpathSync, statSync as statSync3, watch } from "node:fs";
385
495
  import { readFile } from "node:fs/promises";
386
496
  import { createServer } from "node:http";
387
497
  import { isIP } from "node:net";
388
- import { basename as basename2, extname, isAbsolute as isAbsolute2, join as join3, normalize as normalize2, relative, resolve as resolve2, sep } from "node:path";
498
+ import { basename as basename2, extname, isAbsolute as isAbsolute3, join as join4, normalize as normalize3, relative, resolve as resolve2, sep } from "node:path";
389
499
  import { parseArgs } from "node:util";
390
500
  import { context as esbuildContext, formatMessages } from "esbuild";
391
501
  function openBrowser(url) {
@@ -423,10 +533,10 @@ async function listen(server, port, host) {
423
533
  async function startDev(opts) {
424
534
  const root = resolve2(opts.root);
425
535
  const host = opts.host ?? "localhost";
426
- const outDir = join3(root, "dist");
536
+ const outDir = join4(root, "dist");
427
537
  const realRoot = realpathSync(root);
428
538
  const outTmp = outDir + ".tmp";
429
- const virtualOut = join3(root, ".thrunium-dev");
539
+ const virtualOut = join4(root, ".thrunium-dev");
430
540
  loadProject(root);
431
541
  const clients = /* @__PURE__ */ new Set();
432
542
  let lastError = null;
@@ -541,12 +651,15 @@ data: ${JSON.stringify(data)}
541
651
  const project = loadProject(root);
542
652
  const entryMap = new Map(project.entries.map((e, i) => [e, `${BUNDLE_PREFIX}${i}.js`]));
543
653
  const styles = project.entries.flatMap((_, i) => bundles.has(`${i}.css`) ? [`${BUNDLE_PREFIX}${i}.css`] : []);
544
- const html = transformHtml(project.html, {
545
- engineTag: project.usesModules ? ENGINE_TAG : null,
546
- entryMap,
547
- inject: RELOAD_CLIENT,
548
- styles
549
- });
654
+ const html = fillExtras(
655
+ transformHtml(project.html, {
656
+ engineTag: project.usesModules ? ENGINE_TAG : null,
657
+ entryMap,
658
+ inject: RELOAD_CLIENT,
659
+ styles
660
+ }),
661
+ project.extras
662
+ );
550
663
  res.writeHead(200, { ...noCache, "content-type": MIME[".html"] }).end(html);
551
664
  return;
552
665
  }
@@ -565,15 +678,15 @@ data: ${JSON.stringify(data)}
565
678
  res.writeHead(200, { ...noCache, "content-type": type }).end(contents);
566
679
  return;
567
680
  }
568
- const file = normalize2(join3(root, pathname));
681
+ const file = normalize3(join4(root, pathname));
569
682
  const rel = relative(root, file);
570
683
  if (/[\\\0]/.test(pathname) || // backslashes act as separators on Windows; NUL is never valid
571
- rel.split(/[\\/]/).some((part) => part.startsWith(".")) || rel === "" || rel === ".." || rel.startsWith(".." + sep) || isAbsolute2(rel) || !existsSync3(file) || !statSync2(file).isFile()) {
684
+ rel.split(/[\\/]/).some((part) => part.startsWith(".")) || rel === "" || rel === ".." || rel.startsWith(".." + sep) || isAbsolute3(rel) || !existsSync4(file) || !statSync3(file).isFile()) {
572
685
  res.writeHead(404).end();
573
686
  return;
574
687
  }
575
688
  const realRel = relative(realRoot, realpathSync(file));
576
- if (realRel === ".." || realRel.startsWith(".." + sep) || isAbsolute2(realRel)) {
689
+ if (realRel === ".." || realRel.startsWith(".." + sep) || isAbsolute3(realRel)) {
577
690
  res.writeHead(404).end();
578
691
  return;
579
692
  }
@@ -585,7 +698,7 @@ data: ${JSON.stringify(data)}
585
698
  for (const dir of [outDir, outTmp]) if (abs === dir || abs.startsWith(dir + sep)) return true;
586
699
  if (owned.has(abs)) return true;
587
700
  try {
588
- return statSync2(abs).isDirectory();
701
+ return statSync3(abs).isDirectory();
589
702
  } catch {
590
703
  return false;
591
704
  }
@@ -593,15 +706,10 @@ data: ${JSON.stringify(data)}
593
706
  let timer;
594
707
  let closed = false;
595
708
  let pending = Promise.resolve();
596
- let indexChanged = false;
597
709
  const flush = async () => {
598
- const checkEntries = indexChanged;
599
- indexChanged = false;
600
710
  try {
601
- if (checkEntries) {
602
- const next = findModuleEntries(loadProject(root).html);
603
- if (next.join("\n") !== entries.join("\n")) await startBundler(next);
604
- }
711
+ const next = findModuleEntries(loadProject(root).html);
712
+ if (next.join("\n") !== entries.join("\n")) await startBundler(next);
605
713
  } catch (err) {
606
714
  broadcastError(err instanceof Error ? err.message : String(err));
607
715
  return;
@@ -620,20 +728,20 @@ data: ${JSON.stringify(data)}
620
728
  };
621
729
  const watchers = /* @__PURE__ */ new Map();
622
730
  const skippedTop = (name) => {
623
- const abs = join3(root, name);
731
+ const abs = join4(root, name);
624
732
  return name === "node_modules" || name.startsWith(".") || abs === outDir || abs === outTmp;
625
733
  };
626
734
  const onWatchError = (path) => (err) => {
627
735
  watchers.get(path)?.close();
628
736
  watchers.delete(path);
629
- if (existsSync3(path)) console.error(`thrunium: file watching stopped for ${path}: ${err.message}`);
737
+ if (existsSync4(path)) console.error(`thrunium: file watching stopped for ${path}: ${err.message}`);
630
738
  };
631
739
  const rescan = () => {
632
740
  if (closed) return;
633
741
  const dirs = /* @__PURE__ */ new Set();
634
742
  try {
635
743
  for (const entry of readdirSync(root, { withFileTypes: true })) {
636
- if (entry.isDirectory() && !skippedTop(entry.name)) dirs.add(join3(root, entry.name));
744
+ if (entry.isDirectory() && !skippedTop(entry.name)) dirs.add(join4(root, entry.name));
637
745
  }
638
746
  } catch {
639
747
  return;
@@ -650,7 +758,7 @@ data: ${JSON.stringify(data)}
650
758
  const w = watch(dir, { recursive: true }, (_event, filename) => {
651
759
  if (!filename) return schedule();
652
760
  const name = filename.toString();
653
- if (name === basename2(dir) && !existsSync3(join3(dir, name))) return;
761
+ if (name === basename2(dir) && !existsSync4(join4(dir, name))) return;
654
762
  if (!ignored(resolve2(dir, name))) schedule();
655
763
  });
656
764
  w.on("error", onWatchError(dir));
@@ -662,9 +770,8 @@ data: ${JSON.stringify(data)}
662
770
  const rootWatcher = watch(root, (event, filename) => {
663
771
  if (event === "rename") rescan();
664
772
  const abs = filename ? resolve2(root, filename.toString()) : root;
665
- const pageInput = !filename || abs === join3(root, "index.html") || abs === join3(root, "game.json");
773
+ const pageInput = !filename || PAGE_INPUTS.some((file) => abs === join4(root, file));
666
774
  if (!pageInput && ignored(abs)) return;
667
- if (pageInput) indexChanged = true;
668
775
  schedule();
669
776
  });
670
777
  rootWatcher.on("error", onWatchError(root));
@@ -741,7 +848,7 @@ ${USAGE}`);
741
848
  });
742
849
  await server.close();
743
850
  }
744
- var USAGE, MIME, ENGINE_TAG, BUNDLE_PREFIX, WATCH_DEBOUNCE_MS, PORT_ATTEMPTS;
851
+ var USAGE, MIME, ENGINE_TAG, BUNDLE_PREFIX, PAGE_INPUTS, WATCH_DEBOUNCE_MS, PORT_ATTEMPTS;
745
852
  var init_dev = __esm({
746
853
  "cli/dev.ts"() {
747
854
  "use strict";
@@ -781,6 +888,7 @@ Options:
781
888
  };
782
889
  ENGINE_TAG = '<script src="/__thrunium/engine/thrunium.js"></script>';
783
890
  BUNDLE_PREFIX = "/__thrunium/bundle/";
891
+ PAGE_INPUTS = ["index.html", "game.json", "head.html", "body.html"];
784
892
  WATCH_DEBOUNCE_MS = 50;
785
893
  PORT_ATTEMPTS = 11;
786
894
  }
@@ -792,14 +900,14 @@ __export(build_exports, {
792
900
  build: () => build,
793
901
  runBuild: () => runBuild
794
902
  });
795
- import { existsSync as existsSync4, mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync3, renameSync, rmSync, statSync as statSync3, writeFileSync, copyFileSync } from "node:fs";
796
- import { dirname as dirname2, isAbsolute as isAbsolute3, join as join4, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
903
+ import { existsSync as existsSync5, mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync3, renameSync, rmSync, statSync as statSync4, writeFileSync, copyFileSync } from "node:fs";
904
+ import { dirname as dirname2, isAbsolute as isAbsolute4, join as join5, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
797
905
  import { parseArgs as parseArgs2 } from "node:util";
798
906
  import { build as esbuild, formatMessages as formatMessages2 } from "esbuild";
799
907
  function listFiles(root, dir, skipDirs) {
800
908
  const files = [];
801
909
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
802
- const abs = join4(dir, entry.name);
910
+ const abs = join5(dir, entry.name);
803
911
  if (entry.name.startsWith(".") || entry.name === "node_modules" || skipDirs.has(abs)) continue;
804
912
  if (entry.isDirectory()) files.push(...listFiles(root, abs, skipDirs));
805
913
  else if (entry.isFile() && !SKIPPED_NAMES.test(entry.name)) files.push(relative2(root, abs));
@@ -810,7 +918,7 @@ async function runBuild(opts) {
810
918
  const root = resolve3(opts.root);
811
919
  const outDir = resolve3(root, opts.outDir ?? "dist");
812
920
  const rel = relative2(root, outDir);
813
- if (rel === "" || rel === ".." || rel.startsWith(".." + sep2) || isAbsolute3(rel)) {
921
+ if (rel === "" || rel === ".." || rel.startsWith(".." + sep2) || isAbsolute4(rel)) {
814
922
  throw new UserError(`The output folder must be inside the project and not the project itself: ${outDir}`);
815
923
  }
816
924
  const project = loadProject(root, opts.engine);
@@ -827,7 +935,7 @@ async function runBuild(opts) {
827
935
  const entryMap = /* @__PURE__ */ new Map();
828
936
  const styles = [];
829
937
  if (project.usesModules) {
830
- const outAbs = join4(root, ".thrunium-out");
938
+ const outAbs = join5(root, ".thrunium-out");
831
939
  const result = await esbuild({
832
940
  ...baseBuildOptions(root),
833
941
  entryPoints: project.entries,
@@ -864,25 +972,26 @@ async function runBuild(opts) {
864
972
  engineTag = '<script src="thrunium.min.js"></script>';
865
973
  written.set("thrunium.min.js", readFileSync3(engineFile("thrunium.min.js")));
866
974
  const map = engineFile("thrunium.min.js") + ".map";
867
- if (existsSync4(map)) written.set("thrunium.min.js.map", readFileSync3(map));
975
+ if (existsSync5(map)) written.set("thrunium.min.js.map", readFileSync3(map));
868
976
  } else {
869
977
  engineTag = cdnScriptTag(engineVersion(), integrity);
870
978
  }
871
979
  }
872
980
  const tmp = outDir + ".tmp";
873
- written.set("index.html", transformHtml(project.html, { engineTag, entryMap, inject: null, styles }));
981
+ written.set("index.html", fillExtras(transformHtml(project.html, { engineTag, entryMap, inject: null, styles }), project.extras));
874
982
  rmSync(tmp, { recursive: true, force: true });
875
983
  try {
876
984
  const skipDirs = /* @__PURE__ */ new Set([outDir, tmp]);
877
985
  for (const file of listFiles(root, root, skipDirs)) {
878
986
  const key = file.split(sep2).join("/");
879
987
  if (written.has(key) || consumed.has(resolve3(root, file))) continue;
880
- const dest = join4(tmp, file);
988
+ if (project.htmlSource === "generated" && PAGE_PARTS.has(key)) continue;
989
+ const dest = join5(tmp, file);
881
990
  mkdirSync(dirname2(dest), { recursive: true });
882
- copyFileSync(join4(root, file), dest);
991
+ copyFileSync(join5(root, file), dest);
883
992
  }
884
993
  for (const [file, contents] of written) {
885
- const dest = join4(tmp, file);
994
+ const dest = join5(tmp, file);
886
995
  mkdirSync(dirname2(dest), { recursive: true });
887
996
  writeFileSync(dest, contents);
888
997
  }
@@ -893,12 +1002,12 @@ async function runBuild(opts) {
893
1002
  throw err;
894
1003
  }
895
1004
  const files = listAll(outDir, outDir).sort();
896
- for (const f of files) console.log(` ${f} ${formatSize(statSync3(join4(outDir, f)).size)}`);
1005
+ for (const f of files) console.log(` ${f} ${formatSize(statSync4(join5(outDir, f)).size)}`);
897
1006
  return { outDir, files };
898
1007
  }
899
1008
  function listAll(base, dir) {
900
1009
  return readdirSync2(dir, { withFileTypes: true }).flatMap(
901
- (e) => e.isDirectory() ? listAll(base, join4(dir, e.name)) : [relative2(base, join4(dir, e.name)).split(sep2).join("/")]
1010
+ (e) => e.isDirectory() ? listAll(base, join5(dir, e.name)) : [relative2(base, join5(dir, e.name)).split(sep2).join("/")]
902
1011
  );
903
1012
  }
904
1013
  function formatSize(bytes) {
@@ -930,7 +1039,7 @@ ${USAGE2}`);
930
1039
  }
931
1040
  await runBuild({ root: process.cwd(), outDir: values.out, engine: values.engine });
932
1041
  }
933
- var USAGE2, SKIPPED_NAMES;
1042
+ var USAGE2, SKIPPED_NAMES, PAGE_PARTS;
934
1043
  var init_build = __esm({
935
1044
  "cli/build.ts"() {
936
1045
  "use strict";
@@ -946,6 +1055,7 @@ Options:
946
1055
  --help, -h Show this help
947
1056
  `;
948
1057
  SKIPPED_NAMES = /^(package\.json|package-lock\.json|npm-shrinkwrap\.json|pnpm-lock\.yaml|pnpm-workspace\.yaml|yarn\.lock|bun\.lockb?|bunfig\.toml|tsconfig.*\.json|.*\.ts)$/;
1058
+ PAGE_PARTS = /* @__PURE__ */ new Set(["head.html", "body.html"]);
949
1059
  }
950
1060
  });
951
1061
 
@@ -960,8 +1070,8 @@ __export(init_exports, {
960
1070
  spawnInstall: () => spawnInstall
961
1071
  });
962
1072
  import { spawn as spawn2 } from "node:child_process";
963
- import { cpSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync4, renameSync as renameSync2, statSync as statSync4, writeFileSync as writeFileSync2 } from "node:fs";
964
- import { basename as basename3, join as join5, resolve as resolve4 } from "node:path";
1073
+ import { cpSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync4, renameSync as renameSync2, statSync as statSync5, writeFileSync as writeFileSync2 } from "node:fs";
1074
+ import { basename as basename3, join as join6, resolve as resolve4 } from "node:path";
965
1075
  import { parseArgs as parseArgs3 } from "node:util";
966
1076
  function detectPackageManager(userAgent) {
967
1077
  const [name, version] = (userAgent?.split(" ")[0] ?? "").split("/");
@@ -983,25 +1093,25 @@ function spawnInstall(cmd, args, cwd) {
983
1093
  }
984
1094
  async function runInit(opts) {
985
1095
  const dir = resolve4(opts.dir);
986
- if (existsSync5(dir)) {
987
- if (!statSync4(dir).isDirectory() || readdirSync3(dir).length > 0) {
1096
+ if (existsSync6(dir)) {
1097
+ if (!statSync5(dir).isDirectory() || readdirSync3(dir).length > 0) {
988
1098
  throw new UserError(`${dir} already exists and is not an empty folder.`);
989
1099
  }
990
1100
  } else {
991
1101
  mkdirSync2(dir, { recursive: true });
992
1102
  }
993
- cpSync(join5(packageRoot(), "templates", opts.template), dir, { recursive: true });
994
- if (opts.pm.name === "pnpm") cpSync(join5(packageRoot(), "templates", "pnpm"), dir, { recursive: true });
995
- renameSync2(join5(dir, "gitignore"), join5(dir, ".gitignore"));
1103
+ cpSync(join6(packageRoot(), "templates", opts.template), dir, { recursive: true });
1104
+ if (opts.pm.name === "pnpm") cpSync(join6(packageRoot(), "templates", "pnpm"), dir, { recursive: true });
1105
+ renameSync2(join6(dir, "gitignore"), join6(dir, ".gitignore"));
996
1106
  const name = basename3(dir).toLowerCase().replace(/[^a-z0-9-]/g, "-");
997
1107
  for (const file of TEXT_FILES) {
998
- const path = join5(dir, file);
1108
+ const path = join6(dir, file);
999
1109
  const text = readFileSync4(path, "utf8").replace(/\{\{name\}\}/g, name).replace(/\{\{version\}\}/g, engineVersion());
1000
1110
  writeFileSync2(path, text);
1001
1111
  }
1002
1112
  const pinPnpm = opts.pm.name === "pnpm" && opts.pm.version;
1003
1113
  if (pinPnpm || opts.pm.name === "npm") {
1004
- const path = join5(dir, "package.json");
1114
+ const path = join6(dir, "package.json");
1005
1115
  const pkg = JSON.parse(readFileSync4(path, "utf8"));
1006
1116
  if (pinPnpm) pkg.packageManager = `pnpm@${opts.pm.version}`;
1007
1117
  if (opts.pm.name === "npm") pkg.allowScripts = { esbuild: true };
package/dist/thrunium.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! THRUNIUM v1.2.0-rc.2 | (c) Infobits ApS | MIT License */
1
+ /*! THRUNIUM v1.2.0-rc.3 | (c) Infobits ApS | MIT License */
2
2
  "use strict";
3
3
  (() => {
4
4
  var __defProp = Object.defineProperty;
@@ -2222,7 +2222,7 @@
2222
2222
  }
2223
2223
 
2224
2224
  // src/index.ts
2225
- T.version = "1.2.0-rc.2";
2225
+ T.version = "1.2.0-rc.3";
2226
2226
  T.pageLoad = pageLoad;
2227
2227
  T.ready = ready;
2228
2228
  T.throwError = throwError;
@@ -1,3 +1,3 @@
1
- /*! THRUNIUM v1.2.0-rc.2 | (c) Infobits ApS | MIT License */
2
- "use strict";(()=>{var H=Object.defineProperty;var _=(s,e)=>{for(var t in e)H(s,t,{get:e[t],enumerable:!0})};window.THRUNIUM||(window.THRUNIUM={});var n=window.THRUNIUM;function b(){n.pageLoaded=!0}function x(s){let e=!1,t=()=>{e||(e=!0,s())};if(document.readyState==="complete"){t();return}document.addEventListener("DOMContentLoaded",t,!1),window.addEventListener("load",t,!1)}function m(s){console.error(s),n.error=!0}function E(){let s=navigator.userAgent||navigator.vendor||"";return/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(s)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(s.substr(0,4))}function L(){return/iPad|iPhone|iPod/.test(navigator.userAgent)&&!("MSStream"in window)}function R(s,e,t=!1){return t?Math.random()*(e-s)+s:Math.floor(Math.random()*(e-s+1))+s}function M(){n.container=null,n.gameObject=null,n.error=!1,n.pageLoaded=!1,n.fontsLoaded=!1,x(b),typeof document!="undefined"&&document.fonts&&document.fonts.ready?document.fonts.ready.then(()=>{n.fontsLoaded=!0}):n.fontsLoaded=!0}var C;C=Symbol.toStringTag;var p=class{constructor(e,t){this[C]="THRUNIUM.Game";this.scale=1;this.fitScale=1;this.delta=0;this.fps=0;this.runSpeed=0;this.lastRun=null;this.gameRunning=!1;this.showFPS=!1;this.namespace="";this.mouseX=0;this.mouseY=0;this.state="startup";this.startState=null;this.states=[];this.backgroundColor="#fff";this.assets=[];this.assetsLoaded=!1;this.loadedAssets=0;this.assetsToLoad=0;this.font="Arial";this.textSize="30px";this.click=()=>{};this.mousedown=()=>{};this.mouseup=()=>{};this.mousemove=()=>{};this.touch=()=>{};this.touchstart=()=>{};this.touchend=()=>{};this.keydown=()=>{};this.keyup=()=>{};this.deviceorientation=()=>{};this.devicemotion=()=>{};this.rezise=()=>{};this._deviceorientation=e=>{this.deviceorientation(e)};this._devicemotion=e=>{this.devicemotion(e)};this._mousetouchmove=e=>{e.preventDefault(),this.getMouseCords(e),this.mousemove(e)};this._touchstart=e=>{e.preventDefault(),this.getMouseCords(e),this.touch(e),this.touchstart(e)};this._touchend=e=>{e.preventDefault(),this.getMouseCords(e),this.click(e),this.touchend(e)};this._resize=e=>{var i,o;this.updateFitScale(),this.updateScale();let t=(o=(i=n.Elem)==null?void 0:i.elemsList)!=null?o:[];for(let r=0;r<t.length;r++)t[r].resize();this.rezise(e)};this.assetLoaded=()=>{this.loadedAssets++};this.initAssetSpriteMap=(e=>function(){let t=e.assets,i=n.Sprite,o=new i(this);t[this.dataset.index]=o,o.setSpriteMap(Number(this.dataset.spriteWidth),Number(this.dataset.spriteHeight)),e.loadedAssets++})(this);this.gameLoop=()=>{if(!this.lastRun){this.lastRun=Date.now(),this.requestFrame(this.gameLoop);return}this.delta=(Date.now()-this.lastRun)/1e3,this.lastRun=Date.now(),this.fps=1/this.delta,this.runSpeed=Math.round(this.fps/60*100)/100,this.ctx.clearRect(0,0,this.width,this.height),this.canvas.style.cursor="default",this.drawRect(0,0,this.width,this.height),this.setFillColor(this.backgroundColor),this.fill(),this.state=="startup"?this.startup():this.states[this.state](),this.showFPS&&this.drawFPS(),this.gameRunning&&!n.error&&this.requestFrame(this.gameLoop)};this.width=e,this.height=t,this.contruct()}getMouseCords(e){let t=e;if(t.clientX===void 0){let a=e;a.touches&&a.touches[0]?t=a.touches[0]:a.changedTouches&&a.changedTouches[0]&&(t=a.changedTouches[0])}let i=this.canvas.getBoundingClientRect(),o=this.canvas.scrollWidth/this.width,r=this.canvas.scrollHeight/this.height;this.mouseX=(t.clientX-i.left)/o,this.mouseY=(t.clientY-i.top)/r}updateScale(){this.scale=window.devicePixelRatio*this.fitScale,this.canvas.width=this.width*this.scale,this.canvas.height=this.height*this.scale,this.ctx.scale(this.scale,this.scale)}updateFitScale(){this.width/this.height>window.innerWidth/window.innerHeight?this.fitScale=window.innerWidth/this.width:this.fitScale=window.innerHeight/this.height;let e=this.width*this.fitScale+"px",t=this.height*this.fitScale+"px";this.canvas.style.width=e,this.canvas.style.height=t;let i=n.container;i.style.width=e,i.style.height=t}contruct(){document.body.style.background="#333";let e=document.createElement("div");e.style.position="relative",e.style.display="block",e.style.margin="auto",n.container=e,n.gameObject=this,document.body.appendChild(e),this.canvas=document.createElement("canvas"),e.appendChild(this.canvas),this.ctx=this.canvas.getContext("2d"),this.canvas.style.display="block",this.canvas.style.margin="auto",this._resize(),window.addEventListener("resize",this._resize,!1),this.ctx.imageSmoothingEnabled=!1}init(){this.assetsLoaded||this.loadAssets(),this._resize(),this.canvas.addEventListener("click",this.click,!1),this.canvas.addEventListener("mousedown",this.mousedown,!1),this.canvas.addEventListener("mouseup",this.mouseup,!1),this.canvas.addEventListener("touchstart",this._touchstart,!1),this.canvas.addEventListener("touchend",this._touchend,!1),document.addEventListener("keydown",this.keydown,!1),document.addEventListener("keyup",this.keyup,!1),this.canvas.addEventListener("touchmove",this._mousetouchmove,!1),this.canvas.addEventListener("mousemove",this._mousetouchmove,!1),window.addEventListener("deviceorientation",this._deviceorientation),window.addEventListener("devicemotion",this._devicemotion)}loadAssets(){this.assetsToLoad=this.assets.length;let e=[],t=e;for(let i=0;i<this.assets.length;i++){let o=this.assets[i],r,a,d=null;if(typeof o=="string")a=o,r=a;else if(typeof o=="object"&&o!==null){let h=o;a=h.src,r=h.index,h.spriteMap!==void 0&&(d=h.spriteMap)}else{console.error("ASSETS - Unknown entry");continue}let c=this.getFileExtension(a);t[r]=null;let l;if(this.isImage(c))l=new Image;else if(this.isAudio(c))l=new Audio;else{console.error("ASSETS - Unknown file type: "+c+" for file "+a);continue}t[r]=l,l.dataset.index=r,d!==null&&(l.dataset.spriteWidth=String(d.width),l.dataset.spriteHeight=String(d.height)),l.onload=d!==null?this.initAssetSpriteMap:this.assetLoaded,l.src=a}this.assets=e,this.assetsLoaded=!0}getFileExtension(e){return/[.]/.exec(e)?/[^.]+$/.exec(e)[0]:void 0}isImage(e){return["jpg","jpeg","png","gif","xbm"].includes(e)}isAudio(e){return["mp3","wav","ogg","aac"].includes(e)}setFillColor(e){this.ctx.fillStyle=e}fill(){this.ctx.fill()}setStrokeColor(e){this.ctx.strokeStyle=e}stroke(){this.ctx.stroke()}drawLine(e,t,i,o,r){this.ctx.beginPath(),r&&(this.ctx.lineWidth=r),this.ctx.moveTo(e,t),this.ctx.lineTo(i,o),this.ctx.stroke(),this.ctx.closePath()}drawText(e,t,i){this.ctx.font=this.textSize+" "+this.font,this.ctx.fillText(e,t,i)}drawRect(e,t,i,o,r){let a=this.ctx;a.beginPath(),r?(a.moveTo(e+r,t),a.lineTo(e+i-r,t),a.quadraticCurveTo(e+i,t,e+i,t+r),a.lineTo(e+i,t+o-r),a.quadraticCurveTo(e+i,t+o,e+i-r,t+o),a.lineTo(e+r,t+o),a.quadraticCurveTo(e,t+o,e,t+o-r),a.lineTo(e,t+r),a.quadraticCurveTo(e,t,e+r,t)):a.rect(e,t,i,o),a.closePath()}drawCircle(e,t,i){this.ctx.beginPath(),this.ctx.arc(e,t,i,0,2*Math.PI),this.ctx.closePath()}drawImage(e,t,i,o=null,r=null){this.ctx.beginPath(),o!==null&&r!==null?this.ctx.drawImage(e,t,i,o,r):this.ctx.drawImage(e,t,i),this.ctx.closePath()}start(){this.gameRunning=!0,this.requestFrame(this.gameLoop)}stop(){this.gameRunning=!1}requestFrame(e){typeof window.requestAnimFrame=="function"?window.requestAnimFrame(e):requestAnimationFrame(e)}startup(){this.drawRect(0,0,this.width,this.height),this.setFillColor("#000"),this.fill(),this.ctx.font="25px Arial",this.ctx.textAlign="center",this.ctx.textBaseline="bottom",this.setFillColor("#fff"),this.ctx.fillText("Loading game",this.width/2,this.height/2-10),this.drawRect(this.width/2-100,this.height/2,200,20,10),this.setStrokeColor("#fff"),this.stroke();let e=200/this.assetsToLoad*this.loadedAssets;e<20&&(e=20),this.drawRect(this.width/2-100,this.height/2,e,20,10),this.setFillColor("#fff"),this.fill(),this.assetsToLoad==this.loadedAssets&&n.pageLoaded&&n.fontsLoaded&&(this._resize(),this.state=this.startState)}drawFPS(){this.ctx.fillStyle="gray",this.ctx.font="normal 16pt Arial",this.ctx.textAlign="left",this.ctx.textBaseline="top",this.ctx.fillText(Math.round(this.fps)+" fps - scale: "+this.scale,5,5)}};var f=class{constructor(e,t){this.x=e,this.y=t}get[Symbol.toStringTag](){return"THRUNIUM.Vector"}clone(){return new n.Vector(this.x,this.y)}equal(e){return this.x==e.x&&this.y==e.y}scale(e){this.x*=e,this.y*=e}scaleX(e){this.x*=e}scaleY(e){this.y*=e}add(e){this.x+=e.x,this.y+=e.y}addX(e){this.x+=e.x}addY(e){this.y+=e.y}subtract(e){this.x-=e.x,this.y-=e.y}subtractX(e){this.x-=e.x}subtractY(e){this.y-=e.y}divide(e){this.x/=e.x,this.y/=e.y}divideX(e){this.x/=e.x}divideY(e){this.y/=e.y}multiply(e){this.x*=e.x,this.y*=e.y}multiplyX(e){this.x*=e.x}multiplyY(e){this.y*=e.y}invert(){this.x*=-1,this.y*=-1}invertX(){this.x*=-1}invertY(){this.y*=-1}normalize(){let e=Math.sqrt(this.x*this.x+this.y*this.y);e!==0&&(this.x/=e,this.y/=e)}limit(e){this.x>e.x&&(this.x=e.x),this.y>e.y&&(this.y=e.y),this.x<e.x*-1&&(this.x=e.x*-1),this.y<e.y*-1&&(this.y=e.y*-1)}limitLength(e){}};var u={isCircle(s){return s.x!==void 0&&s.y!==void 0&&s.radius!==void 0},isRect(s){return s.x!==void 0&&s.y!==void 0&&s.width!==void 0&&s.height!==void 0},fixPos(s){return s.pos===void 0||s.pos.x===void 0&&s.pos.y===void 0||(s.x=s.pos.x,s.y=s.pos.y),s},rect(s,e){let t=u.fixPos(s),i=u.fixPos(e);if(!u.isRect(t)||!u.isRect(i))return m("One of the objects is not defined correctly as a rect"),!1;let[o,r,a,d]=[t.x,t.y,t.width,t.height],[c,l,h,T]=[i.x,i.y,i.width,i.height];return!(o+a/2<c-h/2||o-a/2>c+h/2||r+d/2<l-T/2||r-d/2>l+T/2)},circle(s,e){let t=u.fixPos(s),i=u.fixPos(e);if(!u.isCircle(t)||!u.isCircle(i))return m("One of the objects is not defined correctly as a circle"),!1;let o=t.x-i.x,r=t.y-i.y;return Math.sqrt(o*o+r*r)<t.radius+i.radius},circleRect(s,e){let t=u.fixPos(s),i=u.fixPos(e);if(!u.isCircle(t)||!u.isRect(i))return m("One of the objects is not defined correctly as a circle or rect"),!1;let o=Math.abs(t.x-i.x),r=Math.abs(t.y-i.y),a=i.width/2,d=i.height/2,c=t.radius;if(o>=a+c||r>=d+c)return!1;if(o<a||r<d)return!0;let l=o-a,h=r-d;return l*l+h*h<=c*c}};var P=()=>n.gameObject,q;q=Symbol.toStringTag;var g=class{constructor(e){this[q]="THRUNIUM.Sprite";this.angle=0;this.currentSprite=null;this.animTimeDur=100;this.frameTimeLast=0;this.animLoop=!1;this.animCallback=()=>{};this.image=e,this.fullWidth=e.width,this.fullHeight=e.height,this.spriteWidth=e.width,this.spriteHeight=e.height}setAngle(e){this.angle=e}setLoop(e){this.animLoop=e}setAnimCallback(e){this.animCallback=e}setAnimTime(e){this.animTimeDur=e}setSpriteMap(e,t){this.spriteWidth=e,this.spriteHeight=t}draw(e=0,t,i,o,r){let a=e*this.spriteWidth%this.fullWidth,d=Math.floor(e*this.spriteWidth/this.fullWidth)*this.spriteHeight,c=o===void 0?this.spriteWidth:o,l=r===void 0?this.spriteHeight:r,h=P().ctx;h.beginPath(),h.save(),h.translate(t,i),h.rotate(this.angle*Math.PI/180),h.drawImage(this.image,a,d,this.spriteWidth,this.spriteHeight,-c/2,-l/2,c,l),h.restore(),h.closePath()}drawAnim(e,t,i,o,r,a){var l;this.currentSprite==null&&(this.currentSprite=e);let d=this.animTimeDur/(t-e+1),c=Date.now();c-this.frameTimeLast>d&&(this.currentSprite<t?this.currentSprite++:this.animLoop?this.currentSprite=e:(this.currentSprite=null,this.animCallback()),this.frameTimeLast=c),this.draw((l=this.currentSprite)!=null?l:void 0,i,o,r,a)}};var w=()=>n.gameObject,v=class{constructor(e,t,i){this.style={};this.hoverStyle={};this.hoverOverrides={};this._tag="THRUNIUM.Elem."+e,this.x=t,this.y=i,this.elem=document.createElement(e),this.elem.addEventListener("mouseenter",()=>this.updateStyle(this.hoverStyle),!1),this.elem.addEventListener("mouseleave",()=>this.updateStyle(this.style),!1),this.setStyle("display","block"),this.setStyle("position","absolute"),this.setStyle("color","#000"),this.setStyle("backgroundColor","#fff"),this.setStyle("fontFamily",w().font),this.setStyle("borderRadius","5px"),this.resize(),S.elemsList.push(this),n.container.appendChild(this.elem)}get[Symbol.toStringTag](){return this._tag}setPos(e,t){this.x=e,this.y=t,this.resize()}resize(){let e=w().fitScale;this.setStyle("left",this.x*e+"px"),this.setStyle("top",this.y*e+"px"),this.setStyle("transformOrigin","top left"),this.setStyle("transform","scale("+e+")")}addEventListener(e,t,i){this.elem.addEventListener(e,t,i)}updateStyle(e){for(let t in e)this.elem.style[t]=e[t]}setStyle(e,t){this.style[e]=t,this.hoverOverrides[e]||(this.hoverStyle[e]=t),this.updateStyle(this.style)}setHoverStyle(e,t){this.hoverStyle[e]=t,this.hoverOverrides[e]=!0}show(){this.setStyle("display","block")}hide(){this.setStyle("display","none")}};function F(s,e,t){let i=new v("button",e,t);return i.elem.textContent=s,i.elem.style.border="none",i}function O(s,e,t){let i=w(),o=document.createElement("input");return o.style.position="absolute",o.style.left=s*i.scale*i.fitScale+"px",o.style.top=e*i.scale*i.fitScale+"px",o.style.border="2px solid #000",o.style.borderRadius="5px",o.style.color="#000",o.style.backgroundColor="#fff",o.style.fontFamily=i.font,o.style.fontSize=i.textSize,o.type=t.type?t.type:"text",t.placeholder&&(o.placeholder=t.placeholder),n.container.appendChild(o),o}var S={elemsList:[],Elem:v,Button:F,Input:O};function y(s){var r,a,d,c;let e=(r=s.success)!=null?r:(l=>console.log(l)),t=(a=s.error)!=null?a:(()=>{}),i=(d=s.contentType)!=null?d:"application/x-www-form-urlencoded",o=new XMLHttpRequest;return o.withCredentials=(c=s.withCredentials)!=null?c:!1,o.onreadystatechange=function(){o.readyState===4&&(o.status===200?e(o):t(o))},o.open(s.type,s.url,!0),i!==!1&&o.setRequestHeader("Content-Type",i),o.send(s.data),o}function I(s){return s.replace(/[%;\x00-\x1f\x7f]/g,encodeURIComponent).replace(/^ +| +$/g,e=>e.replace(/ /g,"%20"))}function z(s,e){let t=new Date;t.setTime(t.getTime()+365*24*60*60*1e3);let i="expires="+t.toUTCString();return document.cookie=s+"="+I(String(e))+";"+i+";path="+document.location.pathname,!0}function j(s){let e=s+"=";for(let t of document.cookie.split(";"))if(t=t.replace(/^ +/,""),t.indexOf(e)===0){let i=t.substring(e.length);try{return decodeURIComponent(i)}catch(o){return i}}return null}var A={set:z,get:j};var k={};_(k,{_options:()=>G,_receiveMessage:()=>B,accessRequested:()=>D,loginPopup:()=>W,reqToken:()=>V,reqUserToken:()=>Y,request:()=>K,setReqToken:()=>N,timeout:()=>X,userToken:()=>U});var V=null,U=null,X=null,W=null,D=!1,G={succes:s=>console.log(s),error:s=>console.log(s)};function N(s){n.Storage.reqToken=s}function Y(s){n.Storage._options.succes=s.succes,n.Storage._options.error=s.error,y({url:"https://api.thrungames.com/request/user-token",type:"post",data:"requestToken="+n.Storage.reqToken,succes:function(e){var i,o,r,a;let t=JSON.parse(e.response);t.access=="0"&&!n.Storage.accessRequested?(window.addEventListener("message",n.Storage._receiveMessage,!1),window.parent.postMessage({reqToken:n.Storage.reqToken},document.origin),n.Storage.timeout=setTimeout(function(){let d=screen.width?(screen.width-400)/2:0,l="height=500,width=400,top="+(screen.height?(screen.height-500)/2:0)+",left="+d+",scrollbars=yes,resizable",h="https://api.thrungames.com/user/access?token="+n.Storage.reqToken;n.Storage.loginPopup=window.open(h,"Approval of game",l)},200)):t.access=="1"?(n.Storage.userToken=window.event.data.userToken,(o=(i=n.Storage._options).succes)==null||o.call(i,{type:"userAccepted",event:e})):(a=(r=n.Storage._options).error)==null||a.call(r,{type:"ajaxFailed",event:e})},error:function(e){var t,i;(i=(t=n.Storage._options).error)==null||i.call(t,{type:"ajaxFailed",event:e})}})}function B(s){var t,i,o;let e=window.event;e.origin!=="https://www.thrungames.com/"&&e.origin!=="https://www.thrungames.com/"||(clearTimeout((t=n.Storage.timeout)!=null?t:void 0),n.Storage.accessRequested=!0,e.data.state==1?n.Storage.reqUserToken(n.Storage._options):(o=(i=n.Storage._options).error)==null||o.call(i,{type:"userRejected",event:s}))}function K(s){}n.version="1.2.0-rc.2";n.pageLoad=b;n.ready=x;n.throwError=m;n.mobileAndTabletcheck=E;n.iOScheck=L;n.getRandom=R;M();n.Game=p;n.Vector=f;n.Collider=Object.assign(n.Collider||{},u);n.Sprite=g;n.Elem=Object.assign(n.Elem||{},S);n.ajax=y;n.Storage=Object.assign(n.Storage||{},A,k);window.requestAnimFrame=typeof window.requestAnimationFrame=="function"?window.requestAnimationFrame.bind(window):s=>window.setTimeout(()=>s(Date.now()),1e3/60);})();
1
+ /*! THRUNIUM v1.2.0-rc.3 | (c) Infobits ApS | MIT License */
2
+ "use strict";(()=>{var H=Object.defineProperty;var _=(s,e)=>{for(var t in e)H(s,t,{get:e[t],enumerable:!0})};window.THRUNIUM||(window.THRUNIUM={});var n=window.THRUNIUM;function b(){n.pageLoaded=!0}function x(s){let e=!1,t=()=>{e||(e=!0,s())};if(document.readyState==="complete"){t();return}document.addEventListener("DOMContentLoaded",t,!1),window.addEventListener("load",t,!1)}function m(s){console.error(s),n.error=!0}function E(){let s=navigator.userAgent||navigator.vendor||"";return/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(s)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(s.substr(0,4))}function L(){return/iPad|iPhone|iPod/.test(navigator.userAgent)&&!("MSStream"in window)}function R(s,e,t=!1){return t?Math.random()*(e-s)+s:Math.floor(Math.random()*(e-s+1))+s}function M(){n.container=null,n.gameObject=null,n.error=!1,n.pageLoaded=!1,n.fontsLoaded=!1,x(b),typeof document!="undefined"&&document.fonts&&document.fonts.ready?document.fonts.ready.then(()=>{n.fontsLoaded=!0}):n.fontsLoaded=!0}var C;C=Symbol.toStringTag;var p=class{constructor(e,t){this[C]="THRUNIUM.Game";this.scale=1;this.fitScale=1;this.delta=0;this.fps=0;this.runSpeed=0;this.lastRun=null;this.gameRunning=!1;this.showFPS=!1;this.namespace="";this.mouseX=0;this.mouseY=0;this.state="startup";this.startState=null;this.states=[];this.backgroundColor="#fff";this.assets=[];this.assetsLoaded=!1;this.loadedAssets=0;this.assetsToLoad=0;this.font="Arial";this.textSize="30px";this.click=()=>{};this.mousedown=()=>{};this.mouseup=()=>{};this.mousemove=()=>{};this.touch=()=>{};this.touchstart=()=>{};this.touchend=()=>{};this.keydown=()=>{};this.keyup=()=>{};this.deviceorientation=()=>{};this.devicemotion=()=>{};this.rezise=()=>{};this._deviceorientation=e=>{this.deviceorientation(e)};this._devicemotion=e=>{this.devicemotion(e)};this._mousetouchmove=e=>{e.preventDefault(),this.getMouseCords(e),this.mousemove(e)};this._touchstart=e=>{e.preventDefault(),this.getMouseCords(e),this.touch(e),this.touchstart(e)};this._touchend=e=>{e.preventDefault(),this.getMouseCords(e),this.click(e),this.touchend(e)};this._resize=e=>{var i,o;this.updateFitScale(),this.updateScale();let t=(o=(i=n.Elem)==null?void 0:i.elemsList)!=null?o:[];for(let r=0;r<t.length;r++)t[r].resize();this.rezise(e)};this.assetLoaded=()=>{this.loadedAssets++};this.initAssetSpriteMap=(e=>function(){let t=e.assets,i=n.Sprite,o=new i(this);t[this.dataset.index]=o,o.setSpriteMap(Number(this.dataset.spriteWidth),Number(this.dataset.spriteHeight)),e.loadedAssets++})(this);this.gameLoop=()=>{if(!this.lastRun){this.lastRun=Date.now(),this.requestFrame(this.gameLoop);return}this.delta=(Date.now()-this.lastRun)/1e3,this.lastRun=Date.now(),this.fps=1/this.delta,this.runSpeed=Math.round(this.fps/60*100)/100,this.ctx.clearRect(0,0,this.width,this.height),this.canvas.style.cursor="default",this.drawRect(0,0,this.width,this.height),this.setFillColor(this.backgroundColor),this.fill(),this.state=="startup"?this.startup():this.states[this.state](),this.showFPS&&this.drawFPS(),this.gameRunning&&!n.error&&this.requestFrame(this.gameLoop)};this.width=e,this.height=t,this.contruct()}getMouseCords(e){let t=e;if(t.clientX===void 0){let a=e;a.touches&&a.touches[0]?t=a.touches[0]:a.changedTouches&&a.changedTouches[0]&&(t=a.changedTouches[0])}let i=this.canvas.getBoundingClientRect(),o=this.canvas.scrollWidth/this.width,r=this.canvas.scrollHeight/this.height;this.mouseX=(t.clientX-i.left)/o,this.mouseY=(t.clientY-i.top)/r}updateScale(){this.scale=window.devicePixelRatio*this.fitScale,this.canvas.width=this.width*this.scale,this.canvas.height=this.height*this.scale,this.ctx.scale(this.scale,this.scale)}updateFitScale(){this.width/this.height>window.innerWidth/window.innerHeight?this.fitScale=window.innerWidth/this.width:this.fitScale=window.innerHeight/this.height;let e=this.width*this.fitScale+"px",t=this.height*this.fitScale+"px";this.canvas.style.width=e,this.canvas.style.height=t;let i=n.container;i.style.width=e,i.style.height=t}contruct(){document.body.style.background="#333";let e=document.createElement("div");e.style.position="relative",e.style.display="block",e.style.margin="auto",n.container=e,n.gameObject=this,document.body.appendChild(e),this.canvas=document.createElement("canvas"),e.appendChild(this.canvas),this.ctx=this.canvas.getContext("2d"),this.canvas.style.display="block",this.canvas.style.margin="auto",this._resize(),window.addEventListener("resize",this._resize,!1),this.ctx.imageSmoothingEnabled=!1}init(){this.assetsLoaded||this.loadAssets(),this._resize(),this.canvas.addEventListener("click",this.click,!1),this.canvas.addEventListener("mousedown",this.mousedown,!1),this.canvas.addEventListener("mouseup",this.mouseup,!1),this.canvas.addEventListener("touchstart",this._touchstart,!1),this.canvas.addEventListener("touchend",this._touchend,!1),document.addEventListener("keydown",this.keydown,!1),document.addEventListener("keyup",this.keyup,!1),this.canvas.addEventListener("touchmove",this._mousetouchmove,!1),this.canvas.addEventListener("mousemove",this._mousetouchmove,!1),window.addEventListener("deviceorientation",this._deviceorientation),window.addEventListener("devicemotion",this._devicemotion)}loadAssets(){this.assetsToLoad=this.assets.length;let e=[],t=e;for(let i=0;i<this.assets.length;i++){let o=this.assets[i],r,a,d=null;if(typeof o=="string")a=o,r=a;else if(typeof o=="object"&&o!==null){let h=o;a=h.src,r=h.index,h.spriteMap!==void 0&&(d=h.spriteMap)}else{console.error("ASSETS - Unknown entry");continue}let c=this.getFileExtension(a);t[r]=null;let l;if(this.isImage(c))l=new Image;else if(this.isAudio(c))l=new Audio;else{console.error("ASSETS - Unknown file type: "+c+" for file "+a);continue}t[r]=l,l.dataset.index=r,d!==null&&(l.dataset.spriteWidth=String(d.width),l.dataset.spriteHeight=String(d.height)),l.onload=d!==null?this.initAssetSpriteMap:this.assetLoaded,l.src=a}this.assets=e,this.assetsLoaded=!0}getFileExtension(e){return/[.]/.exec(e)?/[^.]+$/.exec(e)[0]:void 0}isImage(e){return["jpg","jpeg","png","gif","xbm"].includes(e)}isAudio(e){return["mp3","wav","ogg","aac"].includes(e)}setFillColor(e){this.ctx.fillStyle=e}fill(){this.ctx.fill()}setStrokeColor(e){this.ctx.strokeStyle=e}stroke(){this.ctx.stroke()}drawLine(e,t,i,o,r){this.ctx.beginPath(),r&&(this.ctx.lineWidth=r),this.ctx.moveTo(e,t),this.ctx.lineTo(i,o),this.ctx.stroke(),this.ctx.closePath()}drawText(e,t,i){this.ctx.font=this.textSize+" "+this.font,this.ctx.fillText(e,t,i)}drawRect(e,t,i,o,r){let a=this.ctx;a.beginPath(),r?(a.moveTo(e+r,t),a.lineTo(e+i-r,t),a.quadraticCurveTo(e+i,t,e+i,t+r),a.lineTo(e+i,t+o-r),a.quadraticCurveTo(e+i,t+o,e+i-r,t+o),a.lineTo(e+r,t+o),a.quadraticCurveTo(e,t+o,e,t+o-r),a.lineTo(e,t+r),a.quadraticCurveTo(e,t,e+r,t)):a.rect(e,t,i,o),a.closePath()}drawCircle(e,t,i){this.ctx.beginPath(),this.ctx.arc(e,t,i,0,2*Math.PI),this.ctx.closePath()}drawImage(e,t,i,o=null,r=null){this.ctx.beginPath(),o!==null&&r!==null?this.ctx.drawImage(e,t,i,o,r):this.ctx.drawImage(e,t,i),this.ctx.closePath()}start(){this.gameRunning=!0,this.requestFrame(this.gameLoop)}stop(){this.gameRunning=!1}requestFrame(e){typeof window.requestAnimFrame=="function"?window.requestAnimFrame(e):requestAnimationFrame(e)}startup(){this.drawRect(0,0,this.width,this.height),this.setFillColor("#000"),this.fill(),this.ctx.font="25px Arial",this.ctx.textAlign="center",this.ctx.textBaseline="bottom",this.setFillColor("#fff"),this.ctx.fillText("Loading game",this.width/2,this.height/2-10),this.drawRect(this.width/2-100,this.height/2,200,20,10),this.setStrokeColor("#fff"),this.stroke();let e=200/this.assetsToLoad*this.loadedAssets;e<20&&(e=20),this.drawRect(this.width/2-100,this.height/2,e,20,10),this.setFillColor("#fff"),this.fill(),this.assetsToLoad==this.loadedAssets&&n.pageLoaded&&n.fontsLoaded&&(this._resize(),this.state=this.startState)}drawFPS(){this.ctx.fillStyle="gray",this.ctx.font="normal 16pt Arial",this.ctx.textAlign="left",this.ctx.textBaseline="top",this.ctx.fillText(Math.round(this.fps)+" fps - scale: "+this.scale,5,5)}};var f=class{constructor(e,t){this.x=e,this.y=t}get[Symbol.toStringTag](){return"THRUNIUM.Vector"}clone(){return new n.Vector(this.x,this.y)}equal(e){return this.x==e.x&&this.y==e.y}scale(e){this.x*=e,this.y*=e}scaleX(e){this.x*=e}scaleY(e){this.y*=e}add(e){this.x+=e.x,this.y+=e.y}addX(e){this.x+=e.x}addY(e){this.y+=e.y}subtract(e){this.x-=e.x,this.y-=e.y}subtractX(e){this.x-=e.x}subtractY(e){this.y-=e.y}divide(e){this.x/=e.x,this.y/=e.y}divideX(e){this.x/=e.x}divideY(e){this.y/=e.y}multiply(e){this.x*=e.x,this.y*=e.y}multiplyX(e){this.x*=e.x}multiplyY(e){this.y*=e.y}invert(){this.x*=-1,this.y*=-1}invertX(){this.x*=-1}invertY(){this.y*=-1}normalize(){let e=Math.sqrt(this.x*this.x+this.y*this.y);e!==0&&(this.x/=e,this.y/=e)}limit(e){this.x>e.x&&(this.x=e.x),this.y>e.y&&(this.y=e.y),this.x<e.x*-1&&(this.x=e.x*-1),this.y<e.y*-1&&(this.y=e.y*-1)}limitLength(e){}};var u={isCircle(s){return s.x!==void 0&&s.y!==void 0&&s.radius!==void 0},isRect(s){return s.x!==void 0&&s.y!==void 0&&s.width!==void 0&&s.height!==void 0},fixPos(s){return s.pos===void 0||s.pos.x===void 0&&s.pos.y===void 0||(s.x=s.pos.x,s.y=s.pos.y),s},rect(s,e){let t=u.fixPos(s),i=u.fixPos(e);if(!u.isRect(t)||!u.isRect(i))return m("One of the objects is not defined correctly as a rect"),!1;let[o,r,a,d]=[t.x,t.y,t.width,t.height],[c,l,h,T]=[i.x,i.y,i.width,i.height];return!(o+a/2<c-h/2||o-a/2>c+h/2||r+d/2<l-T/2||r-d/2>l+T/2)},circle(s,e){let t=u.fixPos(s),i=u.fixPos(e);if(!u.isCircle(t)||!u.isCircle(i))return m("One of the objects is not defined correctly as a circle"),!1;let o=t.x-i.x,r=t.y-i.y;return Math.sqrt(o*o+r*r)<t.radius+i.radius},circleRect(s,e){let t=u.fixPos(s),i=u.fixPos(e);if(!u.isCircle(t)||!u.isRect(i))return m("One of the objects is not defined correctly as a circle or rect"),!1;let o=Math.abs(t.x-i.x),r=Math.abs(t.y-i.y),a=i.width/2,d=i.height/2,c=t.radius;if(o>=a+c||r>=d+c)return!1;if(o<a||r<d)return!0;let l=o-a,h=r-d;return l*l+h*h<=c*c}};var P=()=>n.gameObject,q;q=Symbol.toStringTag;var g=class{constructor(e){this[q]="THRUNIUM.Sprite";this.angle=0;this.currentSprite=null;this.animTimeDur=100;this.frameTimeLast=0;this.animLoop=!1;this.animCallback=()=>{};this.image=e,this.fullWidth=e.width,this.fullHeight=e.height,this.spriteWidth=e.width,this.spriteHeight=e.height}setAngle(e){this.angle=e}setLoop(e){this.animLoop=e}setAnimCallback(e){this.animCallback=e}setAnimTime(e){this.animTimeDur=e}setSpriteMap(e,t){this.spriteWidth=e,this.spriteHeight=t}draw(e=0,t,i,o,r){let a=e*this.spriteWidth%this.fullWidth,d=Math.floor(e*this.spriteWidth/this.fullWidth)*this.spriteHeight,c=o===void 0?this.spriteWidth:o,l=r===void 0?this.spriteHeight:r,h=P().ctx;h.beginPath(),h.save(),h.translate(t,i),h.rotate(this.angle*Math.PI/180),h.drawImage(this.image,a,d,this.spriteWidth,this.spriteHeight,-c/2,-l/2,c,l),h.restore(),h.closePath()}drawAnim(e,t,i,o,r,a){var l;this.currentSprite==null&&(this.currentSprite=e);let d=this.animTimeDur/(t-e+1),c=Date.now();c-this.frameTimeLast>d&&(this.currentSprite<t?this.currentSprite++:this.animLoop?this.currentSprite=e:(this.currentSprite=null,this.animCallback()),this.frameTimeLast=c),this.draw((l=this.currentSprite)!=null?l:void 0,i,o,r,a)}};var w=()=>n.gameObject,v=class{constructor(e,t,i){this.style={};this.hoverStyle={};this.hoverOverrides={};this._tag="THRUNIUM.Elem."+e,this.x=t,this.y=i,this.elem=document.createElement(e),this.elem.addEventListener("mouseenter",()=>this.updateStyle(this.hoverStyle),!1),this.elem.addEventListener("mouseleave",()=>this.updateStyle(this.style),!1),this.setStyle("display","block"),this.setStyle("position","absolute"),this.setStyle("color","#000"),this.setStyle("backgroundColor","#fff"),this.setStyle("fontFamily",w().font),this.setStyle("borderRadius","5px"),this.resize(),S.elemsList.push(this),n.container.appendChild(this.elem)}get[Symbol.toStringTag](){return this._tag}setPos(e,t){this.x=e,this.y=t,this.resize()}resize(){let e=w().fitScale;this.setStyle("left",this.x*e+"px"),this.setStyle("top",this.y*e+"px"),this.setStyle("transformOrigin","top left"),this.setStyle("transform","scale("+e+")")}addEventListener(e,t,i){this.elem.addEventListener(e,t,i)}updateStyle(e){for(let t in e)this.elem.style[t]=e[t]}setStyle(e,t){this.style[e]=t,this.hoverOverrides[e]||(this.hoverStyle[e]=t),this.updateStyle(this.style)}setHoverStyle(e,t){this.hoverStyle[e]=t,this.hoverOverrides[e]=!0}show(){this.setStyle("display","block")}hide(){this.setStyle("display","none")}};function F(s,e,t){let i=new v("button",e,t);return i.elem.textContent=s,i.elem.style.border="none",i}function O(s,e,t){let i=w(),o=document.createElement("input");return o.style.position="absolute",o.style.left=s*i.scale*i.fitScale+"px",o.style.top=e*i.scale*i.fitScale+"px",o.style.border="2px solid #000",o.style.borderRadius="5px",o.style.color="#000",o.style.backgroundColor="#fff",o.style.fontFamily=i.font,o.style.fontSize=i.textSize,o.type=t.type?t.type:"text",t.placeholder&&(o.placeholder=t.placeholder),n.container.appendChild(o),o}var S={elemsList:[],Elem:v,Button:F,Input:O};function y(s){var r,a,d,c;let e=(r=s.success)!=null?r:(l=>console.log(l)),t=(a=s.error)!=null?a:(()=>{}),i=(d=s.contentType)!=null?d:"application/x-www-form-urlencoded",o=new XMLHttpRequest;return o.withCredentials=(c=s.withCredentials)!=null?c:!1,o.onreadystatechange=function(){o.readyState===4&&(o.status===200?e(o):t(o))},o.open(s.type,s.url,!0),i!==!1&&o.setRequestHeader("Content-Type",i),o.send(s.data),o}function I(s){return s.replace(/[%;\x00-\x1f\x7f]/g,encodeURIComponent).replace(/^ +| +$/g,e=>e.replace(/ /g,"%20"))}function z(s,e){let t=new Date;t.setTime(t.getTime()+365*24*60*60*1e3);let i="expires="+t.toUTCString();return document.cookie=s+"="+I(String(e))+";"+i+";path="+document.location.pathname,!0}function j(s){let e=s+"=";for(let t of document.cookie.split(";"))if(t=t.replace(/^ +/,""),t.indexOf(e)===0){let i=t.substring(e.length);try{return decodeURIComponent(i)}catch(o){return i}}return null}var A={set:z,get:j};var k={};_(k,{_options:()=>G,_receiveMessage:()=>B,accessRequested:()=>D,loginPopup:()=>W,reqToken:()=>V,reqUserToken:()=>Y,request:()=>K,setReqToken:()=>N,timeout:()=>X,userToken:()=>U});var V=null,U=null,X=null,W=null,D=!1,G={succes:s=>console.log(s),error:s=>console.log(s)};function N(s){n.Storage.reqToken=s}function Y(s){n.Storage._options.succes=s.succes,n.Storage._options.error=s.error,y({url:"https://api.thrungames.com/request/user-token",type:"post",data:"requestToken="+n.Storage.reqToken,succes:function(e){var i,o,r,a;let t=JSON.parse(e.response);t.access=="0"&&!n.Storage.accessRequested?(window.addEventListener("message",n.Storage._receiveMessage,!1),window.parent.postMessage({reqToken:n.Storage.reqToken},document.origin),n.Storage.timeout=setTimeout(function(){let d=screen.width?(screen.width-400)/2:0,l="height=500,width=400,top="+(screen.height?(screen.height-500)/2:0)+",left="+d+",scrollbars=yes,resizable",h="https://api.thrungames.com/user/access?token="+n.Storage.reqToken;n.Storage.loginPopup=window.open(h,"Approval of game",l)},200)):t.access=="1"?(n.Storage.userToken=window.event.data.userToken,(o=(i=n.Storage._options).succes)==null||o.call(i,{type:"userAccepted",event:e})):(a=(r=n.Storage._options).error)==null||a.call(r,{type:"ajaxFailed",event:e})},error:function(e){var t,i;(i=(t=n.Storage._options).error)==null||i.call(t,{type:"ajaxFailed",event:e})}})}function B(s){var t,i,o;let e=window.event;e.origin!=="https://www.thrungames.com/"&&e.origin!=="https://www.thrungames.com/"||(clearTimeout((t=n.Storage.timeout)!=null?t:void 0),n.Storage.accessRequested=!0,e.data.state==1?n.Storage.reqUserToken(n.Storage._options):(o=(i=n.Storage._options).error)==null||o.call(i,{type:"userRejected",event:s}))}function K(s){}n.version="1.2.0-rc.3";n.pageLoad=b;n.ready=x;n.throwError=m;n.mobileAndTabletcheck=E;n.iOScheck=L;n.getRandom=R;M();n.Game=p;n.Vector=f;n.Collider=Object.assign(n.Collider||{},u);n.Sprite=g;n.Elem=Object.assign(n.Elem||{},S);n.ajax=y;n.Storage=Object.assign(n.Storage||{},A,k);window.requestAnimFrame=typeof window.requestAnimationFrame=="function"?window.requestAnimationFrame.bind(window):s=>window.setTimeout(()=>s(Date.now()),1e3/60);})();
3
3
  //# sourceMappingURL=thrunium.min.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thrunium",
3
- "version": "1.2.0-rc.2",
3
+ "version": "1.2.0-rc.3",
4
4
  "description": "The official Thrungames game library.",
5
5
  "scripts": {
6
6
  "build": "tsc --noEmit -p tsconfig.build.json && node scripts/build.mjs",