thrunium 0.0.0-stage → 1.2.0-rc.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.
@@ -0,0 +1,2241 @@
1
+ /*! THRUNIUM v1.2.0-rc.2 | (c) Infobits ApS | MIT License */
2
+ "use strict";
3
+ (() => {
4
+ var __defProp = Object.defineProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+
10
+ // src/namespace.ts
11
+ window.THRUNIUM || (window.THRUNIUM = {});
12
+ var T = window.THRUNIUM;
13
+
14
+ // src/env.ts
15
+ function pageLoad() {
16
+ T.pageLoaded = true;
17
+ }
18
+ function ready(fn) {
19
+ let fired = false;
20
+ const once = () => {
21
+ if (fired) return;
22
+ fired = true;
23
+ fn();
24
+ };
25
+ if (document.readyState === "complete") {
26
+ once();
27
+ return;
28
+ }
29
+ document.addEventListener("DOMContentLoaded", once, false);
30
+ window.addEventListener("load", once, false);
31
+ }
32
+ function throwError(txt) {
33
+ console.error(txt);
34
+ T.error = true;
35
+ }
36
+ function mobileAndTabletcheck() {
37
+ const a = navigator.userAgent || navigator.vendor || "";
38
+ 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(a) || /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(a.substr(0, 4));
39
+ }
40
+ function iOScheck() {
41
+ return /iPad|iPhone|iPod/.test(navigator.userAgent) && !("MSStream" in window);
42
+ }
43
+ function getRandom(min, max, float = false) {
44
+ if (float) return Math.random() * (max - min) + min;
45
+ return Math.floor(Math.random() * (max - min + 1)) + min;
46
+ }
47
+ function initEnv() {
48
+ T.container = null;
49
+ T.gameObject = null;
50
+ T.error = false;
51
+ T.pageLoaded = false;
52
+ T.fontsLoaded = false;
53
+ ready(pageLoad);
54
+ if (typeof document !== "undefined" && document.fonts && document.fonts.ready) {
55
+ void document.fonts.ready.then(() => {
56
+ T.fontsLoaded = true;
57
+ });
58
+ } else {
59
+ T.fontsLoaded = true;
60
+ }
61
+ }
62
+
63
+ // src/game.ts
64
+ var _a;
65
+ _a = Symbol.toStringTag;
66
+ var Game = class {
67
+ /**
68
+ * Creates a game: a `<canvas>` with its 2d context inside a container, the game loop and the standard events. The canvas is appended to the page and scaled to fit the window. Call `init()` and `start()` afterwards. Must be called with `new`.
69
+ * @param width Width of the game in pixels.
70
+ * @param height Height of the game in pixels.
71
+ * @example
72
+ * ```js
73
+ * var game = new THRUNIUM.Game(400, 400);
74
+ * ```
75
+ */
76
+ constructor(width, height) {
77
+ this[_a] = "THRUNIUM.Game";
78
+ /**
79
+ * Density of real canvas pixels per game pixel (device pixel ratio times `fitScale`). Adjusted automatically to keep the game sharp. Read-only.
80
+ *
81
+ * @category Variables
82
+ * @example
83
+ * ```js
84
+ * var game = new THRUNIUM.Game(400, 400);
85
+ * game.scale;
86
+ * ```
87
+ */
88
+ this.scale = 1;
89
+ /**
90
+ * How much the game is scaled to fit the browser window. Adjusted automatically when the window is resized. Read-only.
91
+ *
92
+ * @category Variables
93
+ * @example
94
+ * ```js
95
+ * var game = new THRUNIUM.Game(400, 400);
96
+ * game.fitScale;
97
+ * ```
98
+ */
99
+ this.fitScale = 1;
100
+ /**
101
+ * Seconds elapsed since the previous frame. Updated every game loop; multiply movement by it to keep speed independent of frame rate.
102
+ *
103
+ * @category Variables
104
+ * @example
105
+ * ```js
106
+ * var game = new THRUNIUM.Game(400, 400);
107
+ * game.states['game'] = function() {
108
+ * playerX += 100 * game.delta; // 100 pixels per second
109
+ * };
110
+ * ```
111
+ */
112
+ this.delta = 0;
113
+ /**
114
+ * Frames per second of the game loop. Updated every frame. Read-only: an overwritten value is replaced on the next frame.
115
+ *
116
+ * @category Variables
117
+ * @example
118
+ * ```js
119
+ * var game = new THRUNIUM.Game(400, 400);
120
+ * game.fps;
121
+ * ```
122
+ */
123
+ this.fps = 0;
124
+ // Before 1.2.0 this was the string (fps / 60).toFixed(2); it is now a number.
125
+ /**
126
+ * Current frames per second divided by 60, as a number with two decimals (for example `1`). Use it to correct game speed on devices that run below 60 fps. Updated every frame. Read-only.
127
+ *
128
+ * @category Variables
129
+ * @example
130
+ * ```js
131
+ * var game = new THRUNIUM.Game(400, 400);
132
+ * game.runSpeed;
133
+ * ```
134
+ */
135
+ this.runSpeed = 0;
136
+ /**
137
+ * Unix timestamp in milliseconds of the last time the game loop ran, or `null` before the first frame. Updated every frame. Read-only.
138
+ *
139
+ * @category Variables
140
+ * @example
141
+ * ```js
142
+ * var game = new THRUNIUM.Game(400, 400);
143
+ * game.lastRun;
144
+ * ```
145
+ */
146
+ this.lastRun = null;
147
+ /**
148
+ * Whether the game loop is running. `start()` sets it to `true` and `stop()` to `false`; setting it to `false` yourself ends the loop after the current frame.
149
+ *
150
+ * @category Variables
151
+ * @example
152
+ * ```js
153
+ * var game = new THRUNIUM.Game(400, 400);
154
+ * game.gameRunning = false; // the loop stops
155
+ * ```
156
+ */
157
+ this.gameRunning = false;
158
+ /**
159
+ * Set to `true` to draw an FPS meter in the top-left corner. Default is `false`.
160
+ *
161
+ * @category Variables
162
+ * @example
163
+ * ```js
164
+ * var game = new THRUNIUM.Game(400, 400);
165
+ * game.showFPS = true;
166
+ * ```
167
+ */
168
+ this.showFPS = false;
169
+ /**
170
+ * Free-form name for the game. The engine does not use it, so it is available for your own bookkeeping, for example to prefix saved data. Default is an empty string.
171
+ *
172
+ * @category Variables
173
+ * @example
174
+ * ```js
175
+ * var game = new THRUNIUM.Game(400, 400);
176
+ * game.namespace = 'my-game';
177
+ * ```
178
+ */
179
+ this.namespace = "";
180
+ /**
181
+ * X coordinate of the pointer in game coordinates. Updated when the mouse moves or the screen is touched or clicked. Read-only.
182
+ *
183
+ * @category Variables
184
+ * @example
185
+ * ```js
186
+ * var game = new THRUNIUM.Game(400, 400);
187
+ * game.mouseX;
188
+ * ```
189
+ */
190
+ this.mouseX = 0;
191
+ /**
192
+ * Y coordinate of the pointer in game coordinates. Updated when the mouse moves or the screen is touched or clicked. Read-only.
193
+ *
194
+ * @category Variables
195
+ * @example
196
+ * ```js
197
+ * var game = new THRUNIUM.Game(400, 400);
198
+ * game.mouseY;
199
+ * ```
200
+ */
201
+ this.mouseY = 0;
202
+ /**
203
+ * Name of the state the game loop is currently running (a key of `states`). It starts as the built-in loading screen, `"startup"`, and switches to `startState` once everything has loaded. Assign a new name to change state.
204
+ *
205
+ * @category Variables
206
+ * @example
207
+ * ```js
208
+ * var game = new THRUNIUM.Game(400, 400);
209
+ * game.state = 'game'; // run game.states['game'] each frame
210
+ * ```
211
+ */
212
+ this.state = "startup";
213
+ /**
214
+ * Name of the state to run after the loading screen. Used once, so set it before `init()`. Default is `null`, so you have to set it.
215
+ *
216
+ * @category Variables
217
+ * @example
218
+ * ```js
219
+ * var game = new THRUNIUM.Game(400, 400);
220
+ * game.startState = 'menu';
221
+ * ```
222
+ */
223
+ this.startState = null;
224
+ // Indexed by state name; kept as an array to match 1.0.0.
225
+ /**
226
+ * Your game states: an object of functions keyed by name. The function named by `state` is called every frame. Change `state` to switch between them.
227
+ *
228
+ * @category Variables
229
+ * @example
230
+ * ```js
231
+ * var game = new THRUNIUM.Game(400, 400);
232
+ * game.states['game'] = function() {
233
+ * // your code, run every frame
234
+ * };
235
+ * ```
236
+ */
237
+ this.states = [];
238
+ /**
239
+ * Background color drawn behind every frame. Accepts any CSS color (hex, rgb, rgba, hsl, hsla). Default is `"#fff"`.
240
+ *
241
+ * @category Variables
242
+ * @example
243
+ * ```js
244
+ * var game = new THRUNIUM.Game(400, 400);
245
+ * game.backgroundColor = '#eee';
246
+ * ```
247
+ */
248
+ this.backgroundColor = "#fff";
249
+ /**
250
+ * Assets to load on startup. Set it before `init()`. Each entry is either the path of an image or audio file, which is also the key the loaded asset is stored under, or an object `{ src, index, spriteMap? }` with the file path, the key to store it under, and an optional `spriteMap` of `{ width, height }` that loads an image as a `Sprite` sprite map. Supported images are jpg, jpeg, png, gif and xbm; audio is mp3, wav, ogg and aac. After `init()` the array is replaced by an object-like array keyed by each entry's index, holding the loaded element (or `Sprite` for sprite maps).
251
+ *
252
+ * @category Assets
253
+ * @example
254
+ * ```js
255
+ * var game = new THRUNIUM.Game(400, 400);
256
+ * // as a string: the path is also the key
257
+ * game.assets = ['images/sprite.png'];
258
+ * // the loading screen runs until every asset has loaded, then switches to startState
259
+ * game.startState = 'game';
260
+ * game.states['game'] = function() {
261
+ * game.drawImage(game.assets['images/sprite.png'], 0, 0);
262
+ * };
263
+ * game.init();
264
+ * game.start();
265
+ * ```
266
+ * @example
267
+ * ```js
268
+ * var game = new THRUNIUM.Game(400, 400);
269
+ * // as an object: choose the key, optionally load a sprite map
270
+ * game.assets = [{
271
+ * src: 'images/spritemap.png',
272
+ * index: 'tiles',
273
+ * spriteMap: { width: 40, height: 40 }
274
+ * }];
275
+ * game.startState = 'game';
276
+ * game.states['game'] = function() {
277
+ * // Sprite.draw(frame, x, y): frame 0 centred at (50, 50)
278
+ * game.assets['tiles'].draw(0, 50, 50);
279
+ * };
280
+ * game.init();
281
+ * game.start();
282
+ * ```
283
+ */
284
+ // Typed for strict consumers: entries going in are strings or AssetEntry, and
285
+ // after init() the same array is read by key, which the Record part allows.
286
+ this.assets = [];
287
+ /**
288
+ * Whether `loadAssets()` has run, so the entries of `assets` have been turned into loadable elements. Use `loadedAssets` to see how many have actually finished loading.
289
+ *
290
+ * @category Variables
291
+ * @example
292
+ * ```js
293
+ * var game = new THRUNIUM.Game(400, 400);
294
+ * game.assetsLoaded; // false until init()
295
+ * ```
296
+ */
297
+ this.assetsLoaded = false;
298
+ /**
299
+ * Number of requested assets that have finished loading.
300
+ *
301
+ * @category Variables
302
+ * @example
303
+ * ```js
304
+ * var game = new THRUNIUM.Game(400, 400);
305
+ * game.loadedAssets;
306
+ * ```
307
+ */
308
+ this.loadedAssets = 0;
309
+ /**
310
+ * Number of assets requested for loading. Set when `loadAssets()` runs; the loading screen is done when `loadedAssets` reaches it.
311
+ *
312
+ * @category Variables
313
+ * @example
314
+ * ```js
315
+ * var game = new THRUNIUM.Game(400, 400);
316
+ * game.assetsToLoad;
317
+ * ```
318
+ */
319
+ this.assetsToLoad = 0;
320
+ /**
321
+ * Font family used by `drawText`. Default is `"Arial"`.
322
+ *
323
+ * @category Variables
324
+ * @example
325
+ * ```js
326
+ * var game = new THRUNIUM.Game(400, 400);
327
+ * game.font = 'Courier New';
328
+ * ```
329
+ */
330
+ this.font = "Arial";
331
+ /**
332
+ * Font size used by `drawText`, as a CSS size. Default is `"30px"`.
333
+ *
334
+ * @category Variables
335
+ * @example
336
+ * ```js
337
+ * var game = new THRUNIUM.Game(400, 400);
338
+ * game.textSize = '32px';
339
+ * ```
340
+ */
341
+ this.textSize = "30px";
342
+ // User-assignable events (own properties, no-ops by default).
343
+ /**
344
+ * Called when the user clicks the game, and when a touch ends on it (`touchend` calls `click` just before its own handler). It updates `mouseX` and `mouseY` first. Assign a function to handle it. Handlers are attached by `init()`, so assign them before calling it.
345
+ * @param evt The mouse click, or the touch event when a touch ended.
346
+ *
347
+ * @category Events
348
+ * @example
349
+ * ```js
350
+ * var game = new THRUNIUM.Game(400, 400);
351
+ * game.click = function(e) {
352
+ * // your code here
353
+ * };
354
+ * ```
355
+ */
356
+ this.click = () => {
357
+ };
358
+ /**
359
+ * Called when the user presses a mouse button in the game. It updates `mouseX` and `mouseY` first. Assign a function to handle it. Handlers are attached by `init()`, so assign them before calling it.
360
+ * @param evt The mouse event.
361
+ *
362
+ * @category Events
363
+ * @example
364
+ * ```js
365
+ * var game = new THRUNIUM.Game(400, 400);
366
+ * game.mousedown = function(e) {
367
+ * // your code here
368
+ * };
369
+ * ```
370
+ */
371
+ this.mousedown = () => {
372
+ };
373
+ /**
374
+ * Called when the user releases a mouse button in the game. It updates `mouseX` and `mouseY` first. Assign a function to handle it. Handlers are attached by `init()`, so assign them before calling it.
375
+ * @param evt The mouse event.
376
+ *
377
+ * @category Events
378
+ * @example
379
+ * ```js
380
+ * var game = new THRUNIUM.Game(400, 400);
381
+ * game.mouseup = function(e) {
382
+ * // your code here
383
+ * };
384
+ * ```
385
+ */
386
+ this.mouseup = () => {
387
+ };
388
+ /**
389
+ * Called when the pointer moves over the game. It updates `mouseX` and `mouseY` first. Assign a function to handle it.
390
+ *
391
+ * @category Events
392
+ * @example
393
+ * ```js
394
+ * var game = new THRUNIUM.Game(400, 400);
395
+ * game.mousemove = function(e) {
396
+ * // your code here
397
+ * };
398
+ * ```
399
+ */
400
+ this.mousemove = () => {
401
+ };
402
+ /**
403
+ * Called when the user touches the game with a touchscreen. It updates `mouseX` and `mouseY` first. Assign a function to handle it. The handler is looked up when the event fires, so it can be assigned or changed at any time.
404
+ * @param evt The touch event.
405
+ *
406
+ * @category Events
407
+ * @example
408
+ * ```js
409
+ * var game = new THRUNIUM.Game(400, 400);
410
+ * game.touch = function(e) {
411
+ * // your code here
412
+ * };
413
+ * ```
414
+ */
415
+ this.touch = () => {
416
+ };
417
+ /**
418
+ * Called when the user first touches the game with a touchscreen, after `touch`. It updates `mouseX` and `mouseY` first. Assign a function to handle it. The handler is looked up when the event fires, so it can be assigned or changed at any time.
419
+ * @param evt The touch event.
420
+ *
421
+ * @category Events
422
+ * @example
423
+ * ```js
424
+ * var game = new THRUNIUM.Game(400, 400);
425
+ * game.touchstart = function(e) {
426
+ * // your code here
427
+ * };
428
+ * ```
429
+ */
430
+ this.touchstart = () => {
431
+ };
432
+ /**
433
+ * Called when the user lifts the touch from a touchscreen, after `click`. It updates `mouseX` and `mouseY` first. Assign a function to handle it. The handler is looked up when the event fires, so it can be assigned or changed at any time.
434
+ * @param evt The touch event.
435
+ *
436
+ * @category Events
437
+ * @example
438
+ * ```js
439
+ * var game = new THRUNIUM.Game(400, 400);
440
+ * game.touchend = function(e) {
441
+ * // your code here
442
+ * };
443
+ * ```
444
+ */
445
+ this.touchend = () => {
446
+ };
447
+ /**
448
+ * Called when the user presses a key. Assign a function to handle it; find key codes at keycode.info. Attached by `init()`, so assign it first.
449
+ * @param evt The keyboard event; `evt.key` holds the key.
450
+ *
451
+ * @category Events
452
+ * @example
453
+ * ```js
454
+ * var game = new THRUNIUM.Game(400, 400);
455
+ * game.keydown = function(e) {
456
+ * // your code here
457
+ * console.log(e.keyCode, e.key);
458
+ * };
459
+ * ```
460
+ */
461
+ this.keydown = () => {
462
+ };
463
+ /**
464
+ * Called when the user releases a key. Assign a function to handle it. Attached by `init()`, so assign it first.
465
+ * @param evt The keyboard event; `evt.key` holds the key.
466
+ *
467
+ * @category Events
468
+ * @example
469
+ * ```js
470
+ * var game = new THRUNIUM.Game(400, 400);
471
+ * game.keyup = function(e) {
472
+ * // your code here
473
+ * console.log(e.keyCode, e.key);
474
+ * };
475
+ * ```
476
+ */
477
+ this.keyup = () => {
478
+ };
479
+ /**
480
+ * Called when the orientation of the device changes. Usually only available on phones.
481
+ *
482
+ * @category Events
483
+ * @example
484
+ * ```js
485
+ * var game = new THRUNIUM.Game(400, 400);
486
+ * game.deviceorientation = function(e) {
487
+ * // your code here
488
+ * };
489
+ * ```
490
+ */
491
+ this.deviceorientation = () => {
492
+ };
493
+ /**
494
+ * Called when the device reports motion (acceleration or rotation). Usually only available on phones. Since 1.1.0 this handler is actually attached; 1.0.0 never called it.
495
+ *
496
+ * @category Events
497
+ * @example
498
+ * ```js
499
+ * var game = new THRUNIUM.Game(400, 400);
500
+ * game.devicemotion = function(e) {
501
+ * // your code here
502
+ * };
503
+ * ```
504
+ */
505
+ this.devicemotion = () => {
506
+ };
507
+ /**
508
+ * Called after the game has refitted itself when the window is resized. The name is misspelled on purpose and is kept for compatibility.
509
+ *
510
+ * @category Events
511
+ * @example
512
+ * ```js
513
+ * var game = new THRUNIUM.Game(400, 400);
514
+ * game.rezise = function(e) {
515
+ * // your code here
516
+ * };
517
+ * ```
518
+ */
519
+ this.rezise = () => {
520
+ };
521
+ // Arrow fields so they can be passed straight to addEventListener while
522
+ // always dispatching to the CURRENT user handler.
523
+ /**
524
+ * Internal listener that forwards to `deviceorientation`.
525
+ *
526
+ * @internal
527
+ */
528
+ this._deviceorientation = (evt) => {
529
+ this.deviceorientation(evt);
530
+ };
531
+ /**
532
+ * Internal listener that forwards to `devicemotion`.
533
+ *
534
+ * @internal
535
+ */
536
+ this._devicemotion = (evt) => {
537
+ this.devicemotion(evt);
538
+ };
539
+ /**
540
+ * Internal listener for mouse and touch movement.
541
+ *
542
+ * @internal
543
+ */
544
+ this._mousetouchmove = (evt) => {
545
+ evt.preventDefault();
546
+ this.getMouseCords(evt);
547
+ this.mousemove(evt);
548
+ };
549
+ /**
550
+ * Internal listener for touch start.
551
+ *
552
+ * @internal
553
+ */
554
+ this._touchstart = (evt) => {
555
+ evt.preventDefault();
556
+ this.getMouseCords(evt);
557
+ this.touch(evt);
558
+ this.touchstart(evt);
559
+ };
560
+ /**
561
+ * Internal listener for touch end.
562
+ *
563
+ * @internal
564
+ */
565
+ this._touchend = (evt) => {
566
+ evt.preventDefault();
567
+ this.getMouseCords(evt);
568
+ this.click(evt);
569
+ this.touchend(evt);
570
+ };
571
+ /**
572
+ * Internal window resize listener.
573
+ *
574
+ * @internal
575
+ */
576
+ this._resize = (evt) => {
577
+ var _a3, _b;
578
+ this.updateFitScale();
579
+ this.updateScale();
580
+ const elems = (_b = (_a3 = T.Elem) == null ? void 0 : _a3.elemsList) != null ? _b : [];
581
+ for (let i = 0; i < elems.length; i++) elems[i].resize();
582
+ this.rezise(evt);
583
+ };
584
+ // Arrow field: used as an onload handler, so it must keep `this` = the Game.
585
+ /**
586
+ * Internal onload handler that counts a loaded asset.
587
+ *
588
+ * @internal
589
+ */
590
+ this.assetLoaded = () => {
591
+ this.loadedAssets++;
592
+ };
593
+ // A plain `function` property on purpose: as an onload handler `this` is the
594
+ // image element, and the Game is reached through the captured `self`.
595
+ /**
596
+ * Internal onload handler that turns a loaded sprite-map image into a `Sprite`.
597
+ *
598
+ * @internal
599
+ */
600
+ this.initAssetSpriteMap = /* @__PURE__ */ ((self) => function() {
601
+ const assets = self.assets;
602
+ const Sprite2 = T.Sprite;
603
+ const sprite = new Sprite2(this);
604
+ assets[this.dataset.index] = sprite;
605
+ sprite.setSpriteMap(Number(this.dataset.spriteWidth), Number(this.dataset.spriteHeight));
606
+ self.loadedAssets++;
607
+ })(this);
608
+ // Arrow field so it can be handed straight to requestAnimationFrame.
609
+ /**
610
+ * One frame of the game loop.
611
+ *
612
+ * @internal
613
+ */
614
+ this.gameLoop = () => {
615
+ if (!this.lastRun) {
616
+ this.lastRun = Date.now();
617
+ this.requestFrame(this.gameLoop);
618
+ return;
619
+ }
620
+ this.delta = (Date.now() - this.lastRun) / 1e3;
621
+ this.lastRun = Date.now();
622
+ this.fps = 1 / this.delta;
623
+ this.runSpeed = Math.round(this.fps / 60 * 100) / 100;
624
+ this.ctx.clearRect(0, 0, this.width, this.height);
625
+ this.canvas.style.cursor = "default";
626
+ this.drawRect(0, 0, this.width, this.height);
627
+ this.setFillColor(this.backgroundColor);
628
+ this.fill();
629
+ if (this.state == "startup") this.startup();
630
+ else this.states[this.state]();
631
+ if (this.showFPS) this.drawFPS();
632
+ if (this.gameRunning && !T.error) this.requestFrame(this.gameLoop);
633
+ };
634
+ this.width = width;
635
+ this.height = height;
636
+ this.contruct();
637
+ }
638
+ /**
639
+ * Updates `mouseX` and `mouseY` (in game coordinates) from a mouse or touch event. The engine calls it for you before the mouse and touch handlers run, so you only need it to read the position of an event you handle yourself. Since 1.1.0 it uses this game instead of a global variable named `game`, and handles touch-end events that have no `touches`.
640
+ * @param evt The mouse or touch event to read the position from.
641
+ * @example
642
+ * ```js
643
+ * var game = new THRUNIUM.Game(400, 400);
644
+ * game.canvas.addEventListener('pointerdown', function(e) {
645
+ * game.getMouseCords(e);
646
+ * console.log(game.mouseX, game.mouseY);
647
+ * });
648
+ * ```
649
+ */
650
+ getMouseCords(evt) {
651
+ let point = evt;
652
+ if (point.clientX === void 0) {
653
+ const te = evt;
654
+ if (te.touches && te.touches[0]) point = te.touches[0];
655
+ else if (te.changedTouches && te.changedTouches[0]) point = te.changedTouches[0];
656
+ }
657
+ const rect = this.canvas.getBoundingClientRect();
658
+ const sx = this.canvas.scrollWidth / this.width;
659
+ const sy = this.canvas.scrollHeight / this.height;
660
+ this.mouseX = (point.clientX - rect.left) / sx;
661
+ this.mouseY = (point.clientY - rect.top) / sy;
662
+ }
663
+ /**
664
+ * Sizes the canvas backing store for the device pixel ratio and fit scale.
665
+ *
666
+ * @internal
667
+ */
668
+ updateScale() {
669
+ this.scale = window.devicePixelRatio * this.fitScale;
670
+ this.canvas.width = this.width * this.scale;
671
+ this.canvas.height = this.height * this.scale;
672
+ this.ctx.scale(this.scale, this.scale);
673
+ }
674
+ /**
675
+ * Fits the canvas and container to the window, preserving aspect ratio.
676
+ *
677
+ * @internal
678
+ */
679
+ updateFitScale() {
680
+ if (this.width / this.height > window.innerWidth / window.innerHeight) {
681
+ this.fitScale = window.innerWidth / this.width;
682
+ } else {
683
+ this.fitScale = window.innerHeight / this.height;
684
+ }
685
+ const w = this.width * this.fitScale + "px";
686
+ const h = this.height * this.fitScale + "px";
687
+ this.canvas.style.width = w;
688
+ this.canvas.style.height = h;
689
+ const container = T.container;
690
+ container.style.width = w;
691
+ container.style.height = h;
692
+ }
693
+ /**
694
+ * Creates the container and canvas and hooks the window resize event. The constructor calls it, so you do not need to. The name is misspelled on purpose and is kept for compatibility.
695
+ * @example
696
+ * ```js
697
+ * var game = new THRUNIUM.Game(400, 400);
698
+ * // the constructor has already called game.contruct()
699
+ * ```
700
+ */
701
+ contruct() {
702
+ document.body.style.background = "#333";
703
+ const container = document.createElement("div");
704
+ container.style.position = "relative";
705
+ container.style.display = "block";
706
+ container.style.margin = "auto";
707
+ T.container = container;
708
+ T.gameObject = this;
709
+ document.body.appendChild(container);
710
+ this.canvas = document.createElement("canvas");
711
+ container.appendChild(this.canvas);
712
+ this.ctx = this.canvas.getContext("2d");
713
+ this.canvas.style.display = "block";
714
+ this.canvas.style.margin = "auto";
715
+ this._resize();
716
+ window.addEventListener("resize", this._resize, false);
717
+ this.ctx.imageSmoothingEnabled = false;
718
+ }
719
+ /**
720
+ * Initializes the game: loads `assets`, fits the canvas to the window and attaches the event handlers as they are at this moment. Assign your handlers and `assets` before calling it, and call it before `start()`.
721
+ * @example
722
+ * ```js
723
+ * var game = new THRUNIUM.Game(400, 400);
724
+ * game.startState = 'menu';
725
+ * game.init();
726
+ * ```
727
+ */
728
+ init() {
729
+ if (!this.assetsLoaded) this.loadAssets();
730
+ this._resize();
731
+ this.canvas.addEventListener("click", this.click, false);
732
+ this.canvas.addEventListener("mousedown", this.mousedown, false);
733
+ this.canvas.addEventListener("mouseup", this.mouseup, false);
734
+ this.canvas.addEventListener("touchstart", this._touchstart, false);
735
+ this.canvas.addEventListener("touchend", this._touchend, false);
736
+ document.addEventListener("keydown", this.keydown, false);
737
+ document.addEventListener("keyup", this.keyup, false);
738
+ this.canvas.addEventListener("touchmove", this._mousetouchmove, false);
739
+ this.canvas.addEventListener("mousemove", this._mousetouchmove, false);
740
+ window.addEventListener("deviceorientation", this._deviceorientation);
741
+ window.addEventListener("devicemotion", this._devicemotion);
742
+ }
743
+ /**
744
+ * Creates an `Image` or `Audio` element for every entry of `assets` and starts loading it, then replaces `assets` with an object-like array keyed by each entry's index. `init()` calls it for you, so you rarely need it directly. Unknown entries and file types are logged to the console and skipped.
745
+ *
746
+ * @category Assets
747
+ * @example
748
+ * ```js
749
+ * var game = new THRUNIUM.Game(400, 400);
750
+ * game.assets = ['sounds/jump.wav'];
751
+ * game.loadAssets();
752
+ * game.assets['sounds/jump.wav'].play();
753
+ * ```
754
+ */
755
+ loadAssets() {
756
+ this.assetsToLoad = this.assets.length;
757
+ const tempAssets = [];
758
+ const byIndex = tempAssets;
759
+ for (let i = 0; i < this.assets.length; i++) {
760
+ const entry = this.assets[i];
761
+ let index;
762
+ let src;
763
+ let spriteMap = null;
764
+ if (typeof entry === "string") {
765
+ src = entry;
766
+ index = src;
767
+ } else if (typeof entry === "object" && entry !== null) {
768
+ const spec = entry;
769
+ src = spec.src;
770
+ index = spec.index;
771
+ if (spec.spriteMap !== void 0) spriteMap = spec.spriteMap;
772
+ } else {
773
+ console.error("ASSETS - Unknown entry");
774
+ continue;
775
+ }
776
+ const fileExtension = this.getFileExtension(src);
777
+ byIndex[index] = null;
778
+ let el;
779
+ if (this.isImage(fileExtension)) el = new Image();
780
+ else if (this.isAudio(fileExtension)) el = new Audio();
781
+ else {
782
+ console.error("ASSETS - Unknown file type: " + fileExtension + " for file " + src);
783
+ continue;
784
+ }
785
+ byIndex[index] = el;
786
+ el.dataset.index = index;
787
+ if (spriteMap !== null) {
788
+ el.dataset.spriteWidth = String(spriteMap.width);
789
+ el.dataset.spriteHeight = String(spriteMap.height);
790
+ }
791
+ el.onload = spriteMap !== null ? this.initAssetSpriteMap : this.assetLoaded;
792
+ el.src = src;
793
+ }
794
+ this.assets = tempAssets;
795
+ this.assetsLoaded = true;
796
+ }
797
+ /**
798
+ * Returns the text after the last dot of a file name, or `undefined`.
799
+ *
800
+ * @internal
801
+ */
802
+ getFileExtension(filename) {
803
+ return /[.]/.exec(filename) ? /[^.]+$/.exec(filename)[0] : void 0;
804
+ }
805
+ /**
806
+ * Whether a file extension is a supported image type.
807
+ *
808
+ * @internal
809
+ */
810
+ isImage(ext) {
811
+ return ["jpg", "jpeg", "png", "gif", "xbm"].includes(ext);
812
+ }
813
+ /**
814
+ * Whether a file extension is a supported audio type.
815
+ *
816
+ * @internal
817
+ */
818
+ isAudio(ext) {
819
+ return ["mp3", "wav", "ogg", "aac"].includes(ext);
820
+ }
821
+ // color
822
+ /**
823
+ * Sets the fill color used by `fill()` and `drawText`.
824
+ *
825
+ * @category Drawing
826
+ * @param color Any CSS color: hex, rgb, rgba, hsl, hsla or a name.
827
+ * @example
828
+ * ```js
829
+ * var game = new THRUNIUM.Game(400, 400);
830
+ * game.setFillColor('#f00');
831
+ * game.drawRect(10, 10, 50, 50);
832
+ * game.fill();
833
+ * ```
834
+ */
835
+ setFillColor(color) {
836
+ this.ctx.fillStyle = color;
837
+ }
838
+ /**
839
+ * Fills the current path (for example from `drawRect` or `drawCircle`) with the fill color.
840
+ *
841
+ * @category Drawing
842
+ * @example
843
+ * ```js
844
+ * var game = new THRUNIUM.Game(400, 400);
845
+ * game.setFillColor('#0a0');
846
+ * game.drawCircle(100, 100, 20);
847
+ * game.fill();
848
+ * ```
849
+ */
850
+ fill() {
851
+ this.ctx.fill();
852
+ }
853
+ /**
854
+ * Sets the stroke color used by `stroke()` and `drawLine`.
855
+ *
856
+ * @category Drawing
857
+ * @param color Any CSS color: hex, rgb, rgba, hsl, hsla or a name.
858
+ * @example
859
+ * ```js
860
+ * var game = new THRUNIUM.Game(400, 400);
861
+ * game.setStrokeColor('#00f');
862
+ * game.drawLine(0, 0, 100, 100);
863
+ * ```
864
+ */
865
+ setStrokeColor(color) {
866
+ this.ctx.strokeStyle = color;
867
+ }
868
+ /**
869
+ * Strokes the outline of the current path with the stroke color.
870
+ *
871
+ * @category Drawing
872
+ * @example
873
+ * ```js
874
+ * var game = new THRUNIUM.Game(400, 400);
875
+ * game.setStrokeColor('#000');
876
+ * game.drawRect(10, 10, 50, 50);
877
+ * game.stroke();
878
+ * ```
879
+ */
880
+ stroke() {
881
+ this.ctx.stroke();
882
+ }
883
+ // draw
884
+ /**
885
+ * Draws a line from (x1, y1) to (x2, y2) with the stroke color. When `width` is given it sets the canvas `lineWidth`; like all canvas state it then persists for later strokes until changed (since 1.1.0; 1.0.0 ignored `width`).
886
+ *
887
+ * @category Drawing
888
+ * @param x1 X coordinate of the start.
889
+ * @param y1 Y coordinate of the start.
890
+ * @param x2 X coordinate of the end.
891
+ * @param y2 Y coordinate of the end.
892
+ * @param width Line width in pixels. Leave out to keep the current width.
893
+ * @example
894
+ * ```js
895
+ * var game = new THRUNIUM.Game(400, 400);
896
+ * game.drawLine(100, 100, 200, 200);
897
+ * game.drawLine(100, 200, 200, 100, 4); // 4 pixels wide, and later lines stay 4 wide
898
+ * ```
899
+ */
900
+ drawLine(x1, y1, x2, y2, width) {
901
+ this.ctx.beginPath();
902
+ if (width) this.ctx.lineWidth = width;
903
+ this.ctx.moveTo(x1, y1);
904
+ this.ctx.lineTo(x2, y2);
905
+ this.ctx.stroke();
906
+ this.ctx.closePath();
907
+ }
908
+ /**
909
+ * Draws text at (x, y) using `font` and `textSize` and the fill color.
910
+ *
911
+ * @category Drawing
912
+ * @param text The text to draw.
913
+ * @param x X coordinate of the text.
914
+ * @param y Y coordinate of the text.
915
+ * @example
916
+ * ```js
917
+ * var game = new THRUNIUM.Game(400, 400);
918
+ * game.drawText('Hello World!', 100, 100);
919
+ * ```
920
+ */
921
+ drawText(text, x, y) {
922
+ this.ctx.font = this.textSize + " " + this.font;
923
+ this.ctx.fillText(text, x, y);
924
+ }
925
+ /**
926
+ * Traces a rectangle path, with rounded corners if `radius` is given. It only builds the path: call `fill()` or `stroke()` afterwards to draw it.
927
+ *
928
+ * @category Drawing
929
+ * @param x X coordinate of the left edge.
930
+ * @param y Y coordinate of the top edge.
931
+ * @param width Width of the rectangle.
932
+ * @param height Height of the rectangle.
933
+ * @param radius Corner radius. Leave out for square corners.
934
+ * @example
935
+ * ```js
936
+ * var game = new THRUNIUM.Game(400, 400);
937
+ * game.drawRect(100, 100, 20, 20);
938
+ * game.fill();
939
+ * ```
940
+ */
941
+ drawRect(x, y, width, height, radius) {
942
+ const ctx = this.ctx;
943
+ ctx.beginPath();
944
+ if (radius) {
945
+ ctx.moveTo(x + radius, y);
946
+ ctx.lineTo(x + width - radius, y);
947
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
948
+ ctx.lineTo(x + width, y + height - radius);
949
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
950
+ ctx.lineTo(x + radius, y + height);
951
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
952
+ ctx.lineTo(x, y + radius);
953
+ ctx.quadraticCurveTo(x, y, x + radius, y);
954
+ } else {
955
+ ctx.rect(x, y, width, height);
956
+ }
957
+ ctx.closePath();
958
+ }
959
+ /**
960
+ * Traces a circle path. It only builds the path: call `fill()` or `stroke()` afterwards to draw it.
961
+ *
962
+ * @category Drawing
963
+ * @param x X coordinate of the center.
964
+ * @param y Y coordinate of the center.
965
+ * @param radius Radius of the circle.
966
+ * @example
967
+ * ```js
968
+ * var game = new THRUNIUM.Game(400, 400);
969
+ * game.drawCircle(100, 100, 20);
970
+ * game.fill();
971
+ * ```
972
+ */
973
+ drawCircle(x, y, radius) {
974
+ this.ctx.beginPath();
975
+ this.ctx.arc(x, y, radius, 0, 2 * Math.PI);
976
+ this.ctx.closePath();
977
+ }
978
+ /**
979
+ * Draws an image at (x, y), scaled to `width` by `height` when both are given.
980
+ *
981
+ * @category Drawing
982
+ * @param img The image to draw, for example a loaded entry of `assets`.
983
+ * @param x X coordinate of the left edge.
984
+ * @param y Y coordinate of the top edge.
985
+ * @param width Width to draw at. Needs `height` as well; leave out for the natural size.
986
+ * @param height Height to draw at. Needs `width` as well; leave out for the natural size.
987
+ * @example
988
+ * ```js
989
+ * var game = new THRUNIUM.Game(400, 400);
990
+ * game.assets = ['image.png'];
991
+ * game.init();
992
+ * game.drawImage(game.assets['image.png'], 100, 100);
993
+ * game.drawImage(game.assets['image.png'], 100, 100, 200, 200);
994
+ * ```
995
+ */
996
+ drawImage(img, x, y, width = null, height = null) {
997
+ this.ctx.beginPath();
998
+ if (width !== null && height !== null) {
999
+ this.ctx.drawImage(img, x, y, width, height);
1000
+ } else {
1001
+ this.ctx.drawImage(img, x, y);
1002
+ }
1003
+ this.ctx.closePath();
1004
+ }
1005
+ // loop control
1006
+ /**
1007
+ * Starts the game loop. Each frame it clears the canvas, fills the background and runs the current state.
1008
+ * @example
1009
+ * ```js
1010
+ * var game = new THRUNIUM.Game(400, 400);
1011
+ * game.init();
1012
+ * game.start();
1013
+ * ```
1014
+ */
1015
+ start() {
1016
+ this.gameRunning = true;
1017
+ this.requestFrame(this.gameLoop);
1018
+ }
1019
+ /**
1020
+ * Stops the game loop. Call `start()` to run it again.
1021
+ * @example
1022
+ * ```js
1023
+ * var game = new THRUNIUM.Game(400, 400);
1024
+ * game.stop();
1025
+ * ```
1026
+ */
1027
+ stop() {
1028
+ this.gameRunning = false;
1029
+ }
1030
+ /** Prefers the legacy window.requestAnimFrame alias, else requestAnimationFrame. */
1031
+ requestFrame(cb) {
1032
+ if (typeof window.requestAnimFrame === "function") window.requestAnimFrame(cb);
1033
+ else requestAnimationFrame(cb);
1034
+ }
1035
+ /**
1036
+ * Built-in loading screen shown while `state` is `"startup"`.
1037
+ *
1038
+ * @internal
1039
+ */
1040
+ startup() {
1041
+ this.drawRect(0, 0, this.width, this.height);
1042
+ this.setFillColor("#000");
1043
+ this.fill();
1044
+ this.ctx.font = "25px Arial";
1045
+ this.ctx.textAlign = "center";
1046
+ this.ctx.textBaseline = "bottom";
1047
+ this.setFillColor("#fff");
1048
+ this.ctx.fillText("Loading game", this.width / 2, this.height / 2 - 10);
1049
+ this.drawRect(this.width / 2 - 100, this.height / 2, 200, 20, 10);
1050
+ this.setStrokeColor("#fff");
1051
+ this.stroke();
1052
+ let loaded = 200 / this.assetsToLoad * this.loadedAssets;
1053
+ if (loaded < 20) loaded = 20;
1054
+ this.drawRect(this.width / 2 - 100, this.height / 2, loaded, 20, 10);
1055
+ this.setFillColor("#fff");
1056
+ this.fill();
1057
+ if (this.assetsToLoad == this.loadedAssets && T.pageLoaded && T.fontsLoaded) {
1058
+ this._resize();
1059
+ this.state = this.startState;
1060
+ }
1061
+ }
1062
+ /**
1063
+ * Draws the FPS meter shown when `showFPS` is true.
1064
+ *
1065
+ * @internal
1066
+ */
1067
+ drawFPS() {
1068
+ this.ctx.fillStyle = "gray";
1069
+ this.ctx.font = "normal 16pt Arial";
1070
+ this.ctx.textAlign = "left";
1071
+ this.ctx.textBaseline = "top";
1072
+ this.ctx.fillText(Math.round(this.fps) + " fps - scale: " + this.scale, 5, 5);
1073
+ }
1074
+ };
1075
+
1076
+ // src/vector.ts
1077
+ var Vector = class {
1078
+ /**
1079
+ * Creates a vector. Must be called with `new`.
1080
+ * @param x The x component.
1081
+ * @param y The y component.
1082
+ * @example
1083
+ * ```js
1084
+ * var vector = new THRUNIUM.Vector(10, 20);
1085
+ * ```
1086
+ */
1087
+ constructor(x, y) {
1088
+ this.x = x;
1089
+ this.y = y;
1090
+ }
1091
+ // Makes Object.prototype.toString.call(v) give '[object THRUNIUM.Vector]'.
1092
+ get [Symbol.toStringTag]() {
1093
+ return "THRUNIUM.Vector";
1094
+ }
1095
+ // Built through T.Vector so a game that patches THRUNIUM.Vector gets its own class (1.0.0 behavior).
1096
+ /**
1097
+ * Returns a new vector with the same `x` and `y`. Changing the copy does not change the original.
1098
+ *
1099
+ * @example
1100
+ * ```js
1101
+ * var vector = new THRUNIUM.Vector(10, 20);
1102
+ * var copy = vector.clone();
1103
+ * copy.scale(2);
1104
+ * // vector is still (10, 20), copy is (20, 40)
1105
+ * ```
1106
+ */
1107
+ clone() {
1108
+ return new T.Vector(this.x, this.y);
1109
+ }
1110
+ /**
1111
+ * Returns `true` if both components equal those of `vector`. The comparison is loose (`==`), as in 1.0.0.
1112
+ * @param vector The vector to compare with.
1113
+ * @example
1114
+ * ```js
1115
+ * var a = new THRUNIUM.Vector(10, 20);
1116
+ * var b = new THRUNIUM.Vector(10, 20);
1117
+ * a.equal(b); // true
1118
+ * ```
1119
+ */
1120
+ equal(vector) {
1121
+ return this.x == vector.x && this.y == vector.y;
1122
+ }
1123
+ /**
1124
+ * Multiplies both components by `scale`.
1125
+ * @param scale The factor to multiply by.
1126
+ * @example
1127
+ * ```js
1128
+ * var vector = new THRUNIUM.Vector(10, 20);
1129
+ * vector.scale(2);
1130
+ * // vector is now (20, 40)
1131
+ * ```
1132
+ */
1133
+ scale(scale) {
1134
+ this.x *= scale;
1135
+ this.y *= scale;
1136
+ }
1137
+ /**
1138
+ * Multiplies the x component by `scale`.
1139
+ * @param scale The factor to multiply by.
1140
+ * @example
1141
+ * ```js
1142
+ * var vector = new THRUNIUM.Vector(10, 20);
1143
+ * vector.scaleX(2);
1144
+ * // vector is now (20, 20)
1145
+ * ```
1146
+ */
1147
+ scaleX(scale) {
1148
+ this.x *= scale;
1149
+ }
1150
+ /**
1151
+ * Multiplies the y component by `scale`.
1152
+ * @param scale The factor to multiply by.
1153
+ * @example
1154
+ * ```js
1155
+ * var vector = new THRUNIUM.Vector(10, 20);
1156
+ * vector.scaleY(2);
1157
+ * // vector is now (10, 40)
1158
+ * ```
1159
+ */
1160
+ scaleY(scale) {
1161
+ this.y *= scale;
1162
+ }
1163
+ /**
1164
+ * Adds `vector` to this vector.
1165
+ * @param vector The vector to add.
1166
+ * @example
1167
+ * ```js
1168
+ * var vector = new THRUNIUM.Vector(10, 20);
1169
+ * vector.add(new THRUNIUM.Vector(5, 2));
1170
+ * // vector is now (15, 22)
1171
+ * ```
1172
+ */
1173
+ add(vector) {
1174
+ this.x += vector.x;
1175
+ this.y += vector.y;
1176
+ }
1177
+ /**
1178
+ * Adds the x component of `vector` to this vector's x component.
1179
+ * @param vector The vector whose x component is added.
1180
+ * @example
1181
+ * ```js
1182
+ * var vector = new THRUNIUM.Vector(10, 20);
1183
+ * vector.addX(new THRUNIUM.Vector(5, 2));
1184
+ * // vector is now (15, 20)
1185
+ * ```
1186
+ */
1187
+ addX(vector) {
1188
+ this.x += vector.x;
1189
+ }
1190
+ /**
1191
+ * Adds the y component of `vector` to this vector's y component.
1192
+ * @param vector The vector whose y component is added.
1193
+ * @example
1194
+ * ```js
1195
+ * var vector = new THRUNIUM.Vector(10, 20);
1196
+ * vector.addY(new THRUNIUM.Vector(5, 2));
1197
+ * // vector is now (10, 22)
1198
+ * ```
1199
+ */
1200
+ addY(vector) {
1201
+ this.y += vector.y;
1202
+ }
1203
+ /**
1204
+ * Subtracts `vector` from this vector.
1205
+ * @param vector The vector to subtract.
1206
+ * @example
1207
+ * ```js
1208
+ * var vector = new THRUNIUM.Vector(10, 20);
1209
+ * vector.subtract(new THRUNIUM.Vector(2, 10));
1210
+ * // vector is now (8, 10)
1211
+ * ```
1212
+ */
1213
+ subtract(vector) {
1214
+ this.x -= vector.x;
1215
+ this.y -= vector.y;
1216
+ }
1217
+ /**
1218
+ * Subtracts the x component of `vector` from this vector's x component.
1219
+ * @param vector The vector whose x component is subtracted.
1220
+ * @example
1221
+ * ```js
1222
+ * var vector = new THRUNIUM.Vector(10, 20);
1223
+ * vector.subtractX(new THRUNIUM.Vector(2, 10));
1224
+ * // vector is now (8, 20)
1225
+ * ```
1226
+ */
1227
+ subtractX(vector) {
1228
+ this.x -= vector.x;
1229
+ }
1230
+ /**
1231
+ * Subtracts the y component of `vector` from this vector's y component.
1232
+ * @param vector The vector whose y component is subtracted.
1233
+ * @example
1234
+ * ```js
1235
+ * var vector = new THRUNIUM.Vector(10, 20);
1236
+ * vector.subtractY(new THRUNIUM.Vector(2, 10));
1237
+ * // vector is now (10, 10)
1238
+ * ```
1239
+ */
1240
+ subtractY(vector) {
1241
+ this.y -= vector.y;
1242
+ }
1243
+ /**
1244
+ * Divides each component by the matching component of `vector`.
1245
+ * @param vector The vector to divide by.
1246
+ * @example
1247
+ * ```js
1248
+ * var vector = new THRUNIUM.Vector(10, 20);
1249
+ * vector.divide(new THRUNIUM.Vector(2, 4));
1250
+ * // vector is now (5, 5)
1251
+ * ```
1252
+ */
1253
+ divide(vector) {
1254
+ this.x /= vector.x;
1255
+ this.y /= vector.y;
1256
+ }
1257
+ /**
1258
+ * Divides the x component by the x component of `vector`.
1259
+ * @param vector The vector whose x component is the divisor.
1260
+ * @example
1261
+ * ```js
1262
+ * var vector = new THRUNIUM.Vector(10, 20);
1263
+ * vector.divideX(new THRUNIUM.Vector(2, 4));
1264
+ * // vector is now (5, 20)
1265
+ * ```
1266
+ */
1267
+ divideX(vector) {
1268
+ this.x /= vector.x;
1269
+ }
1270
+ /**
1271
+ * Divides the y component by the y component of `vector`.
1272
+ * @param vector The vector whose y component is the divisor.
1273
+ * @example
1274
+ * ```js
1275
+ * var vector = new THRUNIUM.Vector(10, 20);
1276
+ * vector.divideY(new THRUNIUM.Vector(2, 4));
1277
+ * // vector is now (10, 5)
1278
+ * ```
1279
+ */
1280
+ divideY(vector) {
1281
+ this.y /= vector.y;
1282
+ }
1283
+ /**
1284
+ * Multiplies each component by the matching component of `vector`.
1285
+ * @param vector The vector to multiply by.
1286
+ * @example
1287
+ * ```js
1288
+ * var vector = new THRUNIUM.Vector(10, 20);
1289
+ * vector.multiply(new THRUNIUM.Vector(4, 2));
1290
+ * // vector is now (40, 40)
1291
+ * ```
1292
+ */
1293
+ multiply(vector) {
1294
+ this.x *= vector.x;
1295
+ this.y *= vector.y;
1296
+ }
1297
+ /**
1298
+ * Multiplies the x component by the x component of `vector`.
1299
+ * @param vector The vector whose x component is the factor.
1300
+ * @example
1301
+ * ```js
1302
+ * var vector = new THRUNIUM.Vector(10, 20);
1303
+ * vector.multiplyX(new THRUNIUM.Vector(4, 2));
1304
+ * // vector is now (40, 20)
1305
+ * ```
1306
+ */
1307
+ multiplyX(vector) {
1308
+ this.x *= vector.x;
1309
+ }
1310
+ /**
1311
+ * Multiplies the y component by the y component of `vector`.
1312
+ * @param vector The vector whose y component is the factor.
1313
+ * @example
1314
+ * ```js
1315
+ * var vector = new THRUNIUM.Vector(10, 20);
1316
+ * vector.multiplyY(new THRUNIUM.Vector(4, 2));
1317
+ * // vector is now (10, 40)
1318
+ * ```
1319
+ */
1320
+ multiplyY(vector) {
1321
+ this.y *= vector.y;
1322
+ }
1323
+ /**
1324
+ * Flips the sign of both components, so the vector points the other way.
1325
+ *
1326
+ * @example
1327
+ * ```js
1328
+ * var vector = new THRUNIUM.Vector(10, 20);
1329
+ * vector.invert();
1330
+ * // vector is now (-10, -20)
1331
+ * ```
1332
+ */
1333
+ invert() {
1334
+ this.x *= -1;
1335
+ this.y *= -1;
1336
+ }
1337
+ /**
1338
+ * Flips the sign of the x component.
1339
+ *
1340
+ * @example
1341
+ * ```js
1342
+ * var vector = new THRUNIUM.Vector(10, 20);
1343
+ * vector.invertX();
1344
+ * // vector is now (-10, 20)
1345
+ * ```
1346
+ */
1347
+ invertX() {
1348
+ this.x *= -1;
1349
+ }
1350
+ /**
1351
+ * Flips the sign of the y component.
1352
+ *
1353
+ * @example
1354
+ * ```js
1355
+ * var vector = new THRUNIUM.Vector(10, 20);
1356
+ * vector.invertY();
1357
+ * // vector is now (10, -20)
1358
+ * ```
1359
+ */
1360
+ invertY() {
1361
+ this.y *= -1;
1362
+ }
1363
+ // Bug #8 fix: 1.0.0 computed x/y and y/x, which is not normalization.
1364
+ /**
1365
+ * Scales the vector to length 1, keeping its direction. The zero vector stays (0, 0). Since 1.1.0 this returns a real unit vector; 1.0.0 computed something else.
1366
+ *
1367
+ * @example
1368
+ * ```js
1369
+ * var vector = new THRUNIUM.Vector(3, 4);
1370
+ * vector.normalize();
1371
+ * // vector is now (0.6, 0.8)
1372
+ * ```
1373
+ */
1374
+ normalize() {
1375
+ const length = Math.sqrt(this.x * this.x + this.y * this.y);
1376
+ if (length === 0) return;
1377
+ this.x /= length;
1378
+ this.y /= length;
1379
+ }
1380
+ /**
1381
+ * Clamps each component to the range from `-vector` to `+vector` on that axis: `x` to between `-vector.x` and `vector.x`, `y` to between `-vector.y` and `vector.y`. Useful for capping a velocity. Pass a vector with non-negative components.
1382
+ * @param vector The largest allowed size of each component.
1383
+ * @example
1384
+ * ```js
1385
+ * var vector = new THRUNIUM.Vector(10, 20);
1386
+ * vector.limit(new THRUNIUM.Vector(10, 10));
1387
+ * // vector is now (10, 10)
1388
+ * ```
1389
+ */
1390
+ limit(vector) {
1391
+ if (this.x > vector.x) this.x = vector.x;
1392
+ if (this.y > vector.y) this.y = vector.y;
1393
+ if (this.x < vector.x * -1) this.x = vector.x * -1;
1394
+ if (this.y < vector.y * -1) this.y = vector.y * -1;
1395
+ }
1396
+ /**
1397
+ * Does nothing. It is kept so games written for 1.0.0, where it was also empty, keep working. Use `limit()` to clamp a vector.
1398
+ * @param _len Ignored.
1399
+ * @example
1400
+ * ```js
1401
+ * var vector = new THRUNIUM.Vector(10, 20);
1402
+ * vector.limitLength(5);
1403
+ * // vector is still (10, 20)
1404
+ * ```
1405
+ */
1406
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1407
+ limitLength(_len) {
1408
+ }
1409
+ };
1410
+
1411
+ // src/collider.ts
1412
+ var Collider = {
1413
+ /**
1414
+ * Returns `true` if the object has the `x`, `y` and `radius` of a circle. It does not look at `pos`; call `fixPos()` first for a shape that uses one.
1415
+ * @param circle The object to check.
1416
+ * @example
1417
+ * ```js
1418
+ * THRUNIUM.Collider.isCircle({ x: 100, y: 100, radius: 50 }); // true
1419
+ * THRUNIUM.Collider.isCircle({ x: 100, y: 100 }); // false
1420
+ * ```
1421
+ */
1422
+ isCircle(circle) {
1423
+ return circle.x !== void 0 && circle.y !== void 0 && circle.radius !== void 0;
1424
+ },
1425
+ /**
1426
+ * Returns `true` if the object has the `x`, `y`, `width` and `height` of a rect. It does not look at `pos`; call `fixPos()` first for a shape that uses one.
1427
+ * @param rect The object to check.
1428
+ * @example
1429
+ * ```js
1430
+ * THRUNIUM.Collider.isRect({ x: 100, y: 100, width: 50, height: 50 }); // true
1431
+ * THRUNIUM.Collider.isRect({ x: 100, y: 100, radius: 50 }); // false
1432
+ * ```
1433
+ */
1434
+ isRect(rect) {
1435
+ return rect.x !== void 0 && rect.y !== void 0 && rect.width !== void 0 && rect.height !== void 0;
1436
+ },
1437
+ /**
1438
+ * Copies `pos.x` and `pos.y` onto the shape's own `x` and `y`, and returns the same object. A shape without a `pos` is returned unchanged. The result has the same shape you passed in, so a circle stays a circle and a rect stays a rect. The collision checks call it for you.
1439
+ * @param object The shape to update.
1440
+ * @example
1441
+ * ```js
1442
+ * var rect = { pos: new THRUNIUM.Vector(100, 100), width: 50, height: 50 };
1443
+ * THRUNIUM.Collider.fixPos(rect);
1444
+ * // rect.x is now 100 and rect.y is 100
1445
+ * ```
1446
+ */
1447
+ fixPos(object) {
1448
+ if (object.pos === void 0) return object;
1449
+ if (object.pos.x === void 0 && object.pos.y === void 0) return object;
1450
+ object.x = object.pos.x;
1451
+ object.y = object.pos.y;
1452
+ return object;
1453
+ },
1454
+ /**
1455
+ * Returns `true` if two rects overlap. Rects whose edges just touch count as colliding. Each rect needs `x`, `y`, `width` and `height`, or `pos`, `width` and `height`.
1456
+ * @param _a The first rect.
1457
+ * @param _b The second rect.
1458
+ * @example
1459
+ * ```js
1460
+ * var rect1 = { x: 100, y: 100, width: 50, height: 50 };
1461
+ * var rect2 = { x: 140, y: 140, width: 50, height: 50 };
1462
+ * if (THRUNIUM.Collider.rect(rect1, rect2)) {
1463
+ * console.log('Collision!');
1464
+ * }
1465
+ * ```
1466
+ * @example
1467
+ * ```js
1468
+ * var rect1 = { pos: new THRUNIUM.Vector(100, 100), width: 50, height: 50 };
1469
+ * var rect2 = { pos: new THRUNIUM.Vector(140, 140), width: 50, height: 50 };
1470
+ * if (THRUNIUM.Collider.rect(rect1, rect2)) {
1471
+ * console.log('Collision!');
1472
+ * }
1473
+ * ```
1474
+ */
1475
+ rect(_a3, _b) {
1476
+ const a = Collider.fixPos(_a3);
1477
+ const b = Collider.fixPos(_b);
1478
+ if (!Collider.isRect(a) || !Collider.isRect(b)) {
1479
+ throwError("One of the objects is not defined correctly as a rect");
1480
+ return false;
1481
+ }
1482
+ const [ax, ay, aw, ah] = [a.x, a.y, a.width, a.height];
1483
+ const [bx, by, bw, bh] = [b.x, b.y, b.width, b.height];
1484
+ if (ax + aw / 2 < bx - bw / 2) return false;
1485
+ if (ax - aw / 2 > bx + bw / 2) return false;
1486
+ if (ay + ah / 2 < by - bh / 2) return false;
1487
+ if (ay - ah / 2 > by + bh / 2) return false;
1488
+ return true;
1489
+ },
1490
+ /**
1491
+ * Returns `true` if two circles overlap. Circles that just touch do not count as colliding. Each circle needs `x`, `y` and `radius`, or `pos` and `radius`.
1492
+ * @param _a The first circle.
1493
+ * @param _b The second circle.
1494
+ * @example
1495
+ * ```js
1496
+ * var circle1 = { x: 100, y: 100, radius: 50 };
1497
+ * var circle2 = { x: 140, y: 140, radius: 50 };
1498
+ * if (THRUNIUM.Collider.circle(circle1, circle2)) {
1499
+ * console.log('Collision!');
1500
+ * }
1501
+ * ```
1502
+ * @example
1503
+ * ```js
1504
+ * var circle1 = { pos: new THRUNIUM.Vector(100, 100), radius: 50 };
1505
+ * var circle2 = { pos: new THRUNIUM.Vector(140, 140), radius: 50 };
1506
+ * if (THRUNIUM.Collider.circle(circle1, circle2)) {
1507
+ * console.log('Collision!');
1508
+ * }
1509
+ * ```
1510
+ */
1511
+ circle(_a3, _b) {
1512
+ const a = Collider.fixPos(_a3);
1513
+ const b = Collider.fixPos(_b);
1514
+ if (!Collider.isCircle(a) || !Collider.isCircle(b)) {
1515
+ throwError("One of the objects is not defined correctly as a circle");
1516
+ return false;
1517
+ }
1518
+ const dx = a.x - b.x;
1519
+ const dy = a.y - b.y;
1520
+ return Math.sqrt(dx * dx + dy * dy) < a.radius + b.radius;
1521
+ },
1522
+ /**
1523
+ * Returns `true` if a circle and a rect overlap. A circle that just touches a side of the rect does not count as colliding. The circle needs `x`, `y` and `radius` (or `pos` and `radius`); the rect needs `x`, `y`, `width` and `height` (or `pos`, `width` and `height`).
1524
+ * @param _circle The circle.
1525
+ * @param _rect The rect.
1526
+ * @example
1527
+ * ```js
1528
+ * var circle = { x: 100, y: 100, radius: 50 };
1529
+ * var rect = { x: 140, y: 140, width: 50, height: 50 };
1530
+ * if (THRUNIUM.Collider.circleRect(circle, rect)) {
1531
+ * console.log('Collision!');
1532
+ * }
1533
+ * ```
1534
+ * @example
1535
+ * ```js
1536
+ * var circle = { pos: new THRUNIUM.Vector(100, 100), radius: 50 };
1537
+ * var rect = { pos: new THRUNIUM.Vector(140, 140), width: 50, height: 50 };
1538
+ * if (THRUNIUM.Collider.circleRect(circle, rect)) {
1539
+ * console.log('Collision!');
1540
+ * }
1541
+ * ```
1542
+ */
1543
+ circleRect(_circle, _rect) {
1544
+ const circle = Collider.fixPos(_circle);
1545
+ const rect = Collider.fixPos(_rect);
1546
+ if (!Collider.isCircle(circle) || !Collider.isRect(rect)) {
1547
+ throwError("One of the objects is not defined correctly as a circle or rect");
1548
+ return false;
1549
+ }
1550
+ const distX = Math.abs(circle.x - rect.x);
1551
+ const distY = Math.abs(circle.y - rect.y);
1552
+ const halfW = rect.width / 2;
1553
+ const halfH = rect.height / 2;
1554
+ const r = circle.radius;
1555
+ if (distX >= halfW + r) return false;
1556
+ if (distY >= halfH + r) return false;
1557
+ if (distX < halfW) return true;
1558
+ if (distY < halfH) return true;
1559
+ const dx = distX - halfW;
1560
+ const dy = distY - halfH;
1561
+ return dx * dx + dy * dy <= r * r;
1562
+ }
1563
+ };
1564
+
1565
+ // src/sprite.ts
1566
+ var game = () => T.gameObject;
1567
+ var _a2;
1568
+ _a2 = Symbol.toStringTag;
1569
+ var Sprite = class {
1570
+ /**
1571
+ * Creates a sprite from an image. The image must have finished loading, because its size is read here; until you call `setSpriteMap()` the whole image is one frame. Must be called with `new`, and a `THRUNIUM.Game` must exist before the sprite draws.
1572
+ * @param image A loaded image to use as the sprite or sprite map.
1573
+ * @example
1574
+ * ```js
1575
+ * var game = new THRUNIUM.Game(400, 400);
1576
+ * var image = new Image();
1577
+ * image.onload = function() {
1578
+ * var sprite = new THRUNIUM.Sprite(image);
1579
+ * sprite.draw(0, 200, 200);
1580
+ * };
1581
+ * image.src = 'images/hero.png';
1582
+ * ```
1583
+ */
1584
+ constructor(image) {
1585
+ this[_a2] = "THRUNIUM.Sprite";
1586
+ /**
1587
+ * Rotation in degrees, clockwise, around the centre of the drawn frame. Default is `0`. Set it with `setAngle()`.
1588
+ *
1589
+ * @example
1590
+ * ```js
1591
+ * var game = new THRUNIUM.Game(400, 400);
1592
+ * game.assets = [{ src: 'images/hero.png', index: 'hero', spriteMap: { width: 32, height: 32 } }];
1593
+ * game.startState = 'game';
1594
+ * game.states['game'] = function() {
1595
+ * game.assets['hero'].angle; // 0
1596
+ * };
1597
+ * game.init();
1598
+ * game.start();
1599
+ * ```
1600
+ */
1601
+ this.angle = 0;
1602
+ /**
1603
+ * The frame `drawAnim()` is showing, or `null` when no animation is running. `drawAnim()` updates it; set it to `null` to restart an animation from its first frame.
1604
+ *
1605
+ * @example
1606
+ * ```js
1607
+ * var game = new THRUNIUM.Game(400, 400);
1608
+ * game.assets = [{ src: 'images/hero.png', index: 'hero', spriteMap: { width: 32, height: 32 } }];
1609
+ * game.startState = 'game';
1610
+ * game.states['game'] = function() {
1611
+ * var hero = game.assets['hero'];
1612
+ * hero.setLoop(true);
1613
+ * hero.drawAnim(0, 3, 200, 200);
1614
+ * hero.currentSprite; // 0, 1, 2 or 3
1615
+ * };
1616
+ * game.init();
1617
+ * game.start();
1618
+ * ```
1619
+ */
1620
+ this.currentSprite = null;
1621
+ /**
1622
+ * How long one run of an animation takes, in milliseconds, split evenly over its frames. Default is `100`. Set it with `setAnimTime()`.
1623
+ *
1624
+ * @example
1625
+ * ```js
1626
+ * var game = new THRUNIUM.Game(400, 400);
1627
+ * game.assets = [{ src: 'images/hero.png', index: 'hero', spriteMap: { width: 32, height: 32 } }];
1628
+ * game.startState = 'game';
1629
+ * game.states['game'] = function() {
1630
+ * game.assets['hero'].animTimeDur; // 100
1631
+ * };
1632
+ * game.init();
1633
+ * game.start();
1634
+ * ```
1635
+ */
1636
+ this.animTimeDur = 100;
1637
+ /**
1638
+ * Unix timestamp in milliseconds of when `drawAnim()` last moved to another frame. Starts at `0`.
1639
+ *
1640
+ * @example
1641
+ * ```js
1642
+ * var game = new THRUNIUM.Game(400, 400);
1643
+ * game.assets = [{ src: 'images/hero.png', index: 'hero', spriteMap: { width: 32, height: 32 } }];
1644
+ * game.startState = 'game';
1645
+ * game.states['game'] = function() {
1646
+ * var hero = game.assets['hero'];
1647
+ * hero.drawAnim(0, 3, 200, 200);
1648
+ * hero.frameTimeLast; // e.g. 1767225600000
1649
+ * };
1650
+ * game.init();
1651
+ * game.start();
1652
+ * ```
1653
+ */
1654
+ this.frameTimeLast = 0;
1655
+ /**
1656
+ * Whether `drawAnim()` starts over after the last frame. Default is `false`. Set it with `setLoop()`.
1657
+ *
1658
+ * @example
1659
+ * ```js
1660
+ * var game = new THRUNIUM.Game(400, 400);
1661
+ * game.assets = [{ src: 'images/hero.png', index: 'hero', spriteMap: { width: 32, height: 32 } }];
1662
+ * game.startState = 'game';
1663
+ * game.states['game'] = function() {
1664
+ * game.assets['hero'].animLoop; // false
1665
+ * };
1666
+ * game.init();
1667
+ * game.start();
1668
+ * ```
1669
+ */
1670
+ this.animLoop = false;
1671
+ /**
1672
+ * Function called when a non-looping animation finishes. Default does nothing. Set it with `setAnimCallback()`.
1673
+ *
1674
+ * @example
1675
+ * ```js
1676
+ * var game = new THRUNIUM.Game(400, 400);
1677
+ * game.assets = [{ src: 'images/hero.png', index: 'hero', spriteMap: { width: 32, height: 32 } }];
1678
+ * game.startState = 'game';
1679
+ * game.states['game'] = function() {
1680
+ * game.assets['hero'].animCallback(); // does nothing until you set one
1681
+ * };
1682
+ * game.init();
1683
+ * game.start();
1684
+ * ```
1685
+ */
1686
+ this.animCallback = () => {
1687
+ };
1688
+ this.image = image;
1689
+ this.fullWidth = image.width;
1690
+ this.fullHeight = image.height;
1691
+ this.spriteWidth = image.width;
1692
+ this.spriteHeight = image.height;
1693
+ }
1694
+ /**
1695
+ * Sets the rotation, in degrees clockwise, that the sprite is drawn with from now on.
1696
+ * @param angle The rotation in degrees.
1697
+ * @example
1698
+ * ```js
1699
+ * var game = new THRUNIUM.Game(400, 400);
1700
+ * game.assets = [{ src: 'images/hero.png', index: 'hero', spriteMap: { width: 32, height: 32 } }];
1701
+ * game.startState = 'game';
1702
+ * game.states['game'] = function() {
1703
+ * var hero = game.assets['hero'];
1704
+ * hero.setAngle(hero.angle + 90 * game.delta); // a quarter turn per second
1705
+ * hero.draw(0, 200, 200);
1706
+ * };
1707
+ * game.init();
1708
+ * game.start();
1709
+ * ```
1710
+ */
1711
+ setAngle(angle) {
1712
+ this.angle = angle;
1713
+ }
1714
+ /**
1715
+ * Sets whether `drawAnim()` starts over after the last frame. Without a loop, the animation calls the `setAnimCallback()` function after its last frame, and the next `drawAnim()` call starts it again. Since 1.1.0 `drawAnim()` honours this setting; 1.0.0 ignored it.
1716
+ * @param loop `true` to loop the animation.
1717
+ * @example
1718
+ * ```js
1719
+ * var game = new THRUNIUM.Game(400, 400);
1720
+ * game.assets = [{ src: 'images/hero.png', index: 'hero', spriteMap: { width: 32, height: 32 } }];
1721
+ * game.startState = 'game';
1722
+ * game.states['game'] = function() {
1723
+ * var hero = game.assets['hero'];
1724
+ * hero.setLoop(true);
1725
+ * hero.drawAnim(0, 3, 200, 200);
1726
+ * };
1727
+ * game.init();
1728
+ * game.start();
1729
+ * ```
1730
+ */
1731
+ setLoop(loop) {
1732
+ this.animLoop = loop;
1733
+ }
1734
+ /**
1735
+ * Sets the function called each time a non-looping animation finishes.
1736
+ * @param fun The function to call; it gets no arguments.
1737
+ * @example
1738
+ * ```js
1739
+ * var game = new THRUNIUM.Game(400, 400);
1740
+ * game.assets = [{ src: 'images/explosion.png', index: 'boom', spriteMap: { width: 64, height: 64 } }];
1741
+ * game.startState = 'game';
1742
+ * game.states['game'] = function() {
1743
+ * var boom = game.assets['boom'];
1744
+ * boom.setAnimCallback(function() {
1745
+ * game.state = 'gameover';
1746
+ * });
1747
+ * boom.drawAnim(0, 7, 200, 200);
1748
+ * };
1749
+ * game.states['gameover'] = function() {
1750
+ * game.drawText('Game over', 200, 200);
1751
+ * };
1752
+ * game.init();
1753
+ * game.start();
1754
+ * ```
1755
+ */
1756
+ setAnimCallback(fun) {
1757
+ this.animCallback = fun;
1758
+ }
1759
+ // Bug #6: 1.0.0 assigned to `this.setAnimTime`, overwriting the method itself.
1760
+ /**
1761
+ * Sets how long one run of an animation takes, in milliseconds. The time is split evenly over the frames `drawAnim()` plays. Since 1.1.0 this works; in 1.0.0 it overwrote itself and had no effect.
1762
+ * @param time Duration of the whole animation in milliseconds.
1763
+ * @example
1764
+ * ```js
1765
+ * var game = new THRUNIUM.Game(400, 400);
1766
+ * game.assets = [{ src: 'images/hero.png', index: 'hero', spriteMap: { width: 32, height: 32 } }];
1767
+ * game.startState = 'game';
1768
+ * game.states['game'] = function() {
1769
+ * var hero = game.assets['hero'];
1770
+ * hero.setAnimTime(400); // 4 frames, 100 ms each
1771
+ * hero.setLoop(true);
1772
+ * hero.drawAnim(0, 3, 200, 200);
1773
+ * };
1774
+ * game.init();
1775
+ * game.start();
1776
+ * ```
1777
+ */
1778
+ setAnimTime(time) {
1779
+ this.animTimeDur = time;
1780
+ }
1781
+ /**
1782
+ * Turns the image into a sprite map of frames of `width` x `height` pixels. Frames are numbered from 0, left to right, then row by row. A sprite-map asset has this set already.
1783
+ * @param width Width of one frame in pixels.
1784
+ * @param height Height of one frame in pixels.
1785
+ * @example
1786
+ * ```js
1787
+ * var game = new THRUNIUM.Game(400, 400);
1788
+ * var image = new Image();
1789
+ * image.onload = function() {
1790
+ * var tiles = new THRUNIUM.Sprite(image);
1791
+ * tiles.setSpriteMap(40, 40);
1792
+ * tiles.draw(2, 200, 200); // the third 40 x 40 frame
1793
+ * };
1794
+ * image.src = 'images/tiles.png';
1795
+ * ```
1796
+ */
1797
+ setSpriteMap(width, height) {
1798
+ this.spriteWidth = width;
1799
+ this.spriteHeight = height;
1800
+ }
1801
+ /**
1802
+ * Draws one frame centred on (`x`, `y`), rotated by `angle`. The frame is drawn at its own size unless you pass `width` and `height`.
1803
+ * @param imgNum Number of the frame to draw, counting from 0 left to right, then row by row. Default is `0`.
1804
+ * @param x X coordinate of the centre of the drawn frame.
1805
+ * @param y Y coordinate of the centre of the drawn frame.
1806
+ * @param width Width to draw the frame at. Default is the frame width.
1807
+ * @param height Height to draw the frame at. Default is the frame height.
1808
+ * @example
1809
+ * ```js
1810
+ * var game = new THRUNIUM.Game(400, 400);
1811
+ * game.assets = [{ src: 'images/tiles.png', index: 'tiles', spriteMap: { width: 40, height: 40 } }];
1812
+ * game.startState = 'game';
1813
+ * game.states['game'] = function() {
1814
+ * game.assets['tiles'].draw(0, 50, 50); // frame 0 at its own size
1815
+ * game.assets['tiles'].draw(1, 150, 50, 80, 80); // frame 1 at double size
1816
+ * };
1817
+ * game.init();
1818
+ * game.start();
1819
+ * ```
1820
+ */
1821
+ draw(imgNum = 0, x, y, width, height) {
1822
+ const xImg = imgNum * this.spriteWidth % this.fullWidth;
1823
+ const yImg = Math.floor(imgNum * this.spriteWidth / this.fullWidth) * this.spriteHeight;
1824
+ const w = width === void 0 ? this.spriteWidth : width;
1825
+ const h = height === void 0 ? this.spriteHeight : height;
1826
+ const ctx = game().ctx;
1827
+ ctx.beginPath();
1828
+ ctx.save();
1829
+ ctx.translate(x, y);
1830
+ ctx.rotate(this.angle * Math.PI / 180);
1831
+ ctx.drawImage(this.image, xImg, yImg, this.spriteWidth, this.spriteHeight, -w / 2, -h / 2, w, h);
1832
+ ctx.restore();
1833
+ ctx.closePath();
1834
+ }
1835
+ /**
1836
+ * Plays an animation over the frames `imgFrom` to `imgTo`: call it every frame of your game state and it draws the current frame, moving to the next one when its share of `animTimeDur` has passed. After `imgTo` it starts over when `setLoop(true)` is set; otherwise it calls the `setAnimCallback()` function, and the next call starts again from `imgFrom`. Drawing works as in `draw()`.
1837
+ * @param imgFrom Number of the first frame of the animation.
1838
+ * @param imgTo Number of the last frame of the animation.
1839
+ * @param x X coordinate of the centre of the drawn frame.
1840
+ * @param y Y coordinate of the centre of the drawn frame.
1841
+ * @param width Width to draw the frame at. Default is the frame width.
1842
+ * @param height Height to draw the frame at. Default is the frame height.
1843
+ * @example
1844
+ * ```js
1845
+ * var game = new THRUNIUM.Game(400, 400);
1846
+ * game.assets = [{ src: 'images/hero.png', index: 'hero', spriteMap: { width: 32, height: 32 } }];
1847
+ * game.startState = 'game';
1848
+ * game.states['game'] = function() {
1849
+ * // frames 0 to 3, drawn at double size
1850
+ * game.assets['hero'].drawAnim(0, 3, 200, 200, 64, 64);
1851
+ * };
1852
+ * game.init();
1853
+ * game.start();
1854
+ * ```
1855
+ */
1856
+ drawAnim(imgFrom, imgTo, x, y, width, height) {
1857
+ var _a3;
1858
+ if (this.currentSprite == null) this.currentSprite = imgFrom;
1859
+ const length = this.animTimeDur / (imgTo - imgFrom + 1);
1860
+ const now = Date.now();
1861
+ if (now - this.frameTimeLast > length) {
1862
+ if (this.currentSprite < imgTo) this.currentSprite++;
1863
+ else if (this.animLoop) this.currentSprite = imgFrom;
1864
+ else {
1865
+ this.currentSprite = null;
1866
+ this.animCallback();
1867
+ }
1868
+ this.frameTimeLast = now;
1869
+ }
1870
+ this.draw((_a3 = this.currentSprite) != null ? _a3 : void 0, x, y, width, height);
1871
+ }
1872
+ };
1873
+
1874
+ // src/elem.ts
1875
+ var game2 = () => T.gameObject;
1876
+ var ElemElem = class {
1877
+ /**
1878
+ * Creates an HTML element with the tag `tag` and places it on the game at (`x`, `y`). It starts with a white background, black text, the game's `font` and rounded corners, and is added to `THRUNIUM.Elem.elemsList`. Create the `THRUNIUM.Game` first, because the element is added to the game's container. Must be called with `new`.
1879
+ * @param tag Tag name of the element to create, such as `'div'` or `'button'`.
1880
+ * @param x X coordinate of the element's top-left corner, in game coordinates.
1881
+ * @param y Y coordinate of the element's top-left corner, in game coordinates.
1882
+ * @example
1883
+ * ```js
1884
+ * var game = new THRUNIUM.Game(400, 400);
1885
+ * var label = new THRUNIUM.Elem.Elem('div', 20, 20);
1886
+ * label.elem.textContent = 'Score: 0';
1887
+ * ```
1888
+ */
1889
+ constructor(tag, x, y) {
1890
+ /**
1891
+ * The element's styles, as CSS property names in camelCase (`backgroundColor`) mapped to values. Change them with `setStyle()`, which also applies them; changing this object directly does not update the element.
1892
+ *
1893
+ * @example
1894
+ * ```js
1895
+ * var game = new THRUNIUM.Game(400, 400);
1896
+ * var button = THRUNIUM.Elem.Button('Play', 100, 100);
1897
+ * button.style.backgroundColor; // '#fff'
1898
+ * ```
1899
+ */
1900
+ this.style = {};
1901
+ /**
1902
+ * The styles applied while the mouse is over the element, in the same form as `style`. It follows `style`, except for properties set with `setHoverStyle()`. Change it with `setHoverStyle()`.
1903
+ *
1904
+ * @example
1905
+ * ```js
1906
+ * var game = new THRUNIUM.Game(400, 400);
1907
+ * var button = THRUNIUM.Elem.Button('Play', 100, 100);
1908
+ * button.setHoverStyle('backgroundColor', '#ddd');
1909
+ * button.hoverStyle.backgroundColor; // '#ddd'
1910
+ * ```
1911
+ */
1912
+ this.hoverStyle = {};
1913
+ /** Hover properties set explicitly via setHoverStyle; setStyle must not overwrite these. */
1914
+ this.hoverOverrides = {};
1915
+ this._tag = "THRUNIUM.Elem." + tag;
1916
+ this.x = x;
1917
+ this.y = y;
1918
+ this.elem = document.createElement(tag);
1919
+ this.elem.addEventListener("mouseenter", () => this.updateStyle(this.hoverStyle), false);
1920
+ this.elem.addEventListener("mouseleave", () => this.updateStyle(this.style), false);
1921
+ this.setStyle("display", "block");
1922
+ this.setStyle("position", "absolute");
1923
+ this.setStyle("color", "#000");
1924
+ this.setStyle("backgroundColor", "#fff");
1925
+ this.setStyle("fontFamily", game2().font);
1926
+ this.setStyle("borderRadius", "5px");
1927
+ this.resize();
1928
+ elemApi.elemsList.push(this);
1929
+ T.container.appendChild(this.elem);
1930
+ }
1931
+ get [Symbol.toStringTag]() {
1932
+ return this._tag;
1933
+ }
1934
+ /**
1935
+ * Moves the element so its top-left corner is at (`x`, `y`) in game coordinates.
1936
+ * @param x The new x coordinate.
1937
+ * @param y The new y coordinate.
1938
+ * @example
1939
+ * ```js
1940
+ * var game = new THRUNIUM.Game(400, 400);
1941
+ * var button = THRUNIUM.Elem.Button('Play', 100, 100);
1942
+ * button.setPos(150, 200);
1943
+ * ```
1944
+ */
1945
+ setPos(x, y) {
1946
+ this.x = x;
1947
+ this.y = y;
1948
+ this.resize();
1949
+ }
1950
+ /**
1951
+ * Re-applies the element's position and scale for the game's current `fitScale`. The game calls it for every element in `THRUNIUM.Elem.elemsList` when the window is resized, so you rarely need it. Since 1.1.0 it no longer leaks a global variable or throws in strict mode.
1952
+ *
1953
+ * @example
1954
+ * ```js
1955
+ * var game = new THRUNIUM.Game(400, 400);
1956
+ * var button = THRUNIUM.Elem.Button('Play', 100, 100);
1957
+ * button.resize();
1958
+ * ```
1959
+ */
1960
+ resize() {
1961
+ const fit = game2().fitScale;
1962
+ this.setStyle("left", this.x * fit + "px");
1963
+ this.setStyle("top", this.y * fit + "px");
1964
+ this.setStyle("transformOrigin", "top left");
1965
+ this.setStyle("transform", "scale(" + fit + ")");
1966
+ }
1967
+ /**
1968
+ * Listens for a DOM event on the element, like the element's own `addEventListener`.
1969
+ * @param type Name of the event, such as `'click'`.
1970
+ * @param fun The function to call when the event happens; it gets the event.
1971
+ * @param async Passed on as the third argument of the DOM `addEventListener` (`useCapture`). Despite its name it has nothing to do with asynchronous code; leave it out.
1972
+ * @example
1973
+ * ```js
1974
+ * var game = new THRUNIUM.Game(400, 400);
1975
+ * var button = THRUNIUM.Elem.Button('Press me!', 100, 100);
1976
+ * button.addEventListener('click', function(e) {
1977
+ * // button was clicked
1978
+ * });
1979
+ * ```
1980
+ */
1981
+ addEventListener(type, fun, async) {
1982
+ this.elem.addEventListener(type, fun, async);
1983
+ }
1984
+ /**
1985
+ * Applies a set of styles to the element without storing them in `style` or `hoverStyle`. The element uses it internally to switch between `style` and `hoverStyle`; to change a style for good, use `setStyle()`.
1986
+ * @param styleList CSS property names in camelCase mapped to values.
1987
+ * @example
1988
+ * ```js
1989
+ * var game = new THRUNIUM.Game(400, 400);
1990
+ * var button = THRUNIUM.Elem.Button('Play', 100, 100);
1991
+ * button.updateStyle({ color: '#f00', fontSize: '20px' });
1992
+ * ```
1993
+ */
1994
+ updateStyle(styleList) {
1995
+ for (const name in styleList) {
1996
+ this.elem.style[name] = styleList[name];
1997
+ }
1998
+ }
1999
+ /**
2000
+ * Sets one style of the element and applies it. Unless you have set a hover style for the same property with `setHoverStyle()`, the element keeps it while hovered too.
2001
+ * @param name CSS property name in camelCase, such as `'backgroundColor'`.
2002
+ * @param value The CSS value, such as `'#000'`.
2003
+ * @example
2004
+ * ```js
2005
+ * var game = new THRUNIUM.Game(400, 400);
2006
+ * var button = THRUNIUM.Elem.Button('Play', 100, 100);
2007
+ * button.setStyle('backgroundColor', '#000');
2008
+ * button.setStyle('color', '#fff');
2009
+ * ```
2010
+ */
2011
+ setStyle(name, value) {
2012
+ this.style[name] = value;
2013
+ if (!this.hoverOverrides[name]) this.hoverStyle[name] = value;
2014
+ this.updateStyle(this.style);
2015
+ }
2016
+ /**
2017
+ * Sets a style that applies only while the mouse is over the element; the normal style comes back when the mouse leaves. Since 1.1.0 hover styles work as described; 1.0.0 applied them the wrong way round.
2018
+ * @param name CSS property name in camelCase, such as `'backgroundColor'`.
2019
+ * @param value The CSS value to use while hovered.
2020
+ * @example
2021
+ * ```js
2022
+ * var game = new THRUNIUM.Game(400, 400);
2023
+ * var button = THRUNIUM.Elem.Button('Play', 100, 100);
2024
+ * button.setHoverStyle('backgroundColor', '#ddd');
2025
+ * ```
2026
+ */
2027
+ setHoverStyle(name, value) {
2028
+ this.hoverStyle[name] = value;
2029
+ this.hoverOverrides[name] = true;
2030
+ }
2031
+ /**
2032
+ * Shows the element again after `hide()`.
2033
+ *
2034
+ * @example
2035
+ * ```js
2036
+ * var game = new THRUNIUM.Game(400, 400);
2037
+ * var button = THRUNIUM.Elem.Button('Play', 100, 100);
2038
+ * button.hide();
2039
+ * button.show();
2040
+ * ```
2041
+ */
2042
+ show() {
2043
+ this.setStyle("display", "block");
2044
+ }
2045
+ /**
2046
+ * Hides the element. It stays in place, so `show()` brings it back.
2047
+ *
2048
+ * @example
2049
+ * ```js
2050
+ * var game = new THRUNIUM.Game(400, 400);
2051
+ * var button = THRUNIUM.Elem.Button('Play', 100, 100);
2052
+ * button.addEventListener('click', function() {
2053
+ * button.hide();
2054
+ * });
2055
+ * ```
2056
+ */
2057
+ hide() {
2058
+ this.setStyle("display", "none");
2059
+ }
2060
+ };
2061
+ function Button(txt, x, y) {
2062
+ const button = new ElemElem("button", x, y);
2063
+ button.elem.textContent = txt;
2064
+ button.elem.style.border = "none";
2065
+ return button;
2066
+ }
2067
+ function Input(x, y, options) {
2068
+ const g = game2();
2069
+ const input = document.createElement("input");
2070
+ input.style.position = "absolute";
2071
+ input.style.left = x * g.scale * g.fitScale + "px";
2072
+ input.style.top = y * g.scale * g.fitScale + "px";
2073
+ input.style.border = "2px solid #000";
2074
+ input.style.borderRadius = "5px";
2075
+ input.style.color = "#000";
2076
+ input.style.backgroundColor = "#fff";
2077
+ input.style.fontFamily = g.font;
2078
+ input.style.fontSize = g.textSize;
2079
+ input.type = options.type ? options.type : "text";
2080
+ if (options.placeholder) input.placeholder = options.placeholder;
2081
+ T.container.appendChild(input);
2082
+ return input;
2083
+ }
2084
+ var elemApi = {
2085
+ /**
2086
+ * Every `Elem.Elem` created so far, including buttons, in creation order. The game uses it to reposition them when the window is resized. Inputs from `Input()` are not in it.
2087
+ *
2088
+ * @example
2089
+ * ```js
2090
+ * var game = new THRUNIUM.Game(400, 400);
2091
+ * THRUNIUM.Elem.Button('Play', 100, 100);
2092
+ * THRUNIUM.Elem.elemsList.length; // 1
2093
+ * ```
2094
+ */
2095
+ elemsList: [],
2096
+ Elem: ElemElem,
2097
+ Button,
2098
+ Input
2099
+ };
2100
+
2101
+ // src/ajax.ts
2102
+ function ajax(data) {
2103
+ var _a3, _b, _c, _d;
2104
+ const success = (_a3 = data.success) != null ? _a3 : ((e) => console.log(e));
2105
+ const error = (_b = data.error) != null ? _b : (() => {
2106
+ });
2107
+ const contentType = (_c = data.contentType) != null ? _c : "application/x-www-form-urlencoded";
2108
+ const xhttp = new XMLHttpRequest();
2109
+ xhttp.withCredentials = (_d = data.withCredentials) != null ? _d : false;
2110
+ xhttp.onreadystatechange = function() {
2111
+ if (xhttp.readyState !== 4) return;
2112
+ if (xhttp.status === 200) success(xhttp);
2113
+ else error(xhttp);
2114
+ };
2115
+ xhttp.open(data.type, data.url, true);
2116
+ if (contentType !== false) xhttp.setRequestHeader("Content-Type", contentType);
2117
+ xhttp.send(data.data);
2118
+ return xhttp;
2119
+ }
2120
+
2121
+ // src/storage.ts
2122
+ function escapeValue(value) {
2123
+ return value.replace(/[%;\x00-\x1f\x7f]/g, encodeURIComponent).replace(/^ +| +$/g, (spaces) => spaces.replace(/ /g, "%20"));
2124
+ }
2125
+ function set(name, value) {
2126
+ const date = /* @__PURE__ */ new Date();
2127
+ date.setTime(date.getTime() + 365 * 24 * 60 * 60 * 1e3);
2128
+ const expires = "expires=" + date.toUTCString();
2129
+ document.cookie = name + "=" + escapeValue(String(value)) + ";" + expires + ";path=" + document.location.pathname;
2130
+ return true;
2131
+ }
2132
+ function get(name) {
2133
+ const prefix = name + "=";
2134
+ for (let c of document.cookie.split(";")) {
2135
+ c = c.replace(/^ +/, "");
2136
+ if (c.indexOf(prefix) === 0) {
2137
+ const raw = c.substring(prefix.length);
2138
+ try {
2139
+ return decodeURIComponent(raw);
2140
+ } catch (e) {
2141
+ return raw;
2142
+ }
2143
+ }
2144
+ }
2145
+ return null;
2146
+ }
2147
+ var storage = { set, get };
2148
+
2149
+ // src/storage-token.ts
2150
+ var storage_token_exports = {};
2151
+ __export(storage_token_exports, {
2152
+ _options: () => _options,
2153
+ _receiveMessage: () => _receiveMessage,
2154
+ accessRequested: () => accessRequested,
2155
+ loginPopup: () => loginPopup,
2156
+ reqToken: () => reqToken,
2157
+ reqUserToken: () => reqUserToken,
2158
+ request: () => request,
2159
+ setReqToken: () => setReqToken,
2160
+ timeout: () => timeout,
2161
+ userToken: () => userToken
2162
+ });
2163
+ var reqToken = null;
2164
+ var userToken = null;
2165
+ var timeout = null;
2166
+ var loginPopup = null;
2167
+ var accessRequested = false;
2168
+ var _options = {
2169
+ succes: (e) => console.log(e),
2170
+ error: (e) => console.log(e)
2171
+ };
2172
+ function setReqToken(token) {
2173
+ T.Storage.reqToken = token;
2174
+ }
2175
+ function reqUserToken(options) {
2176
+ T.Storage._options.succes = options.succes;
2177
+ T.Storage._options.error = options.error;
2178
+ ajax({
2179
+ url: "https://api.thrungames.com/request/user-token",
2180
+ type: "post",
2181
+ data: "requestToken=" + T.Storage.reqToken,
2182
+ // 1.0.0 typo `succes` kept: ajax ignores it (part of why the flow is WIP).
2183
+ succes: function(e) {
2184
+ var _a3, _b, _c, _d;
2185
+ const response = JSON.parse(e.response);
2186
+ if (response.access == "0" && !T.Storage.accessRequested) {
2187
+ window.addEventListener("message", T.Storage._receiveMessage, false);
2188
+ window.parent.postMessage({ reqToken: T.Storage.reqToken }, document.origin);
2189
+ T.Storage.timeout = setTimeout(function() {
2190
+ const leftPosition = screen.width ? (screen.width - 400) / 2 : 0;
2191
+ const topPosition = screen.height ? (screen.height - 500) / 2 : 0;
2192
+ const settings = "height=500,width=400,top=" + topPosition + ",left=" + leftPosition + ",scrollbars=yes,resizable";
2193
+ const url = "https://api.thrungames.com/user/access?token=" + T.Storage.reqToken;
2194
+ T.Storage.loginPopup = window.open(url, "Approval of game", settings);
2195
+ }, 200);
2196
+ } else if (response.access == "1") {
2197
+ T.Storage.userToken = window.event.data.userToken;
2198
+ (_b = (_a3 = T.Storage._options).succes) == null ? void 0 : _b.call(_a3, { type: "userAccepted", event: e });
2199
+ } else {
2200
+ (_d = (_c = T.Storage._options).error) == null ? void 0 : _d.call(_c, { type: "ajaxFailed", event: e });
2201
+ }
2202
+ },
2203
+ error: function(e) {
2204
+ var _a3, _b;
2205
+ (_b = (_a3 = T.Storage._options).error) == null ? void 0 : _b.call(_a3, { type: "ajaxFailed", event: e });
2206
+ }
2207
+ });
2208
+ }
2209
+ function _receiveMessage(e) {
2210
+ var _a3, _b, _c;
2211
+ const event = window.event;
2212
+ if (event.origin !== "https://www.thrungames.com/" && event.origin !== "https://www.thrungames.com/") return;
2213
+ clearTimeout((_a3 = T.Storage.timeout) != null ? _a3 : void 0);
2214
+ T.Storage.accessRequested = true;
2215
+ if (event.data.state == 1) {
2216
+ T.Storage.reqUserToken(T.Storage._options);
2217
+ } else {
2218
+ (_c = (_b = T.Storage._options).error) == null ? void 0 : _c.call(_b, { type: "userRejected", event: e });
2219
+ }
2220
+ }
2221
+ function request(_options2) {
2222
+ }
2223
+
2224
+ // src/index.ts
2225
+ T.version = "1.2.0-rc.2";
2226
+ T.pageLoad = pageLoad;
2227
+ T.ready = ready;
2228
+ T.throwError = throwError;
2229
+ T.mobileAndTabletcheck = mobileAndTabletcheck;
2230
+ T.iOScheck = iOScheck;
2231
+ T.getRandom = getRandom;
2232
+ initEnv();
2233
+ T.Game = Game;
2234
+ T.Vector = Vector;
2235
+ T.Collider = Object.assign(T.Collider || {}, Collider);
2236
+ T.Sprite = Sprite;
2237
+ T.Elem = Object.assign(T.Elem || {}, elemApi);
2238
+ T.ajax = ajax;
2239
+ T.Storage = Object.assign(T.Storage || {}, storage, storage_token_exports);
2240
+ window.requestAnimFrame = typeof window.requestAnimationFrame === "function" ? window.requestAnimationFrame.bind(window) : (cb) => window.setTimeout(() => cb(Date.now()), 1e3 / 60);
2241
+ })();