gameMechanics.js 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353
  1. /***************************************************************
  2. * LInE - Free Education, Private Data - http://www.usp.br/line
  3. *
  4. * This file handles all the game mechanics.
  5. **************************************************************/
  6. /**
  7. * Variable that handles game mechanics.
  8. *
  9. * @namespace
  10. */
  11. const game = {
  12. image: {}, // [Not directly used] Holds cached reference to media.
  13. sprite: {}, // [Not directly used] Holds cached reference to media.
  14. audio: {}, // Holds cached reference to media - game.audio.<name>.play() plays that audio once.
  15. lang: {}, // Holds language dictionary in a key-value format - game.lang.<key> returns <value>.
  16. loadedCur: 0, // [Not directly used] CURRENT number of cached media (on current state)
  17. loadedMax: 0, // [Not directly used] EXPECTED number of cached media (on current state)
  18. loadManager: {
  19. // [Not directly used] <mediaCategory> : [ <isLoading?> , <#CurrentlyCached> ]
  20. lang: [false, 0],
  21. audio: [false, 0],
  22. image: [false, 0],
  23. sprite: [false, 0],
  24. },
  25. /**
  26. * Handles game states. <br>
  27. *
  28. * When a state is associated with an object
  29. * it can accessed using game.state.start('state name'). <br>
  30. * That way the object can use the especial functions: preload(), create() and update();
  31. * that behave according to the following rules: <br>
  32. * - preload() : first function to run when a state is called. Loads media. Runs only once. (is optional) <br>
  33. * - create() : called right after preload(). Where the main code goes. Runs only once. (must exist) <br>
  34. * - update() : called right after create(). Is iteratively called by 'game loop' until going to next state. (is optional)
  35. *
  36. * @namespace
  37. */
  38. state: {
  39. // [Not directly used] List of game states.
  40. list: [],
  41. // [Not directly used]
  42. name: undefined,
  43. /**
  44. * Create new state. <br>
  45. *
  46. * After a state is created, the object associated with that state
  47. * can be called using game.state.start('state name')
  48. *
  49. * @param {string} name state name
  50. * @param {object} obj object that should be called when accessing the state
  51. */
  52. add: function (name, obj) {
  53. game.state.list[name] = obj;
  54. },
  55. /**
  56. * Start new state.
  57. *
  58. * Will look for the state's preload() to load the files for the current state.
  59. * If there is no preload, will call create().
  60. *
  61. * @param {string} name state name
  62. */
  63. start: function (name) {
  64. document.body.style.cursor = 'auto'; // Set cursor to default
  65. game.loop.stop(); // Stop last game loop
  66. game.event.clear(); // Clears last event queue
  67. game.animation.clear(); // Clears last animation queue
  68. game.loadedCur = 0; // Clears last state's number of loaded media
  69. game.loadedMax = 0; // Clears last state's expected loaded media
  70. game.state.name = name; // Updates state name
  71. self = game.state.list[name]; // Updates self to current state
  72. if (self.preload) {
  73. game.render.clear(); // Clears render queue
  74. // IF there's media to be loaded, creates progress bar
  75. game.add.geom.rect(
  76. 0,
  77. 0,
  78. context.canvas.width,
  79. context.canvas.height,
  80. colors.white,
  81. 0,
  82. colors.blueBg,
  83. 1
  84. );
  85. self.progressBar = game.add.geom.rect(
  86. context.canvas.width / 2,
  87. context.canvas.height / 2,
  88. 20,
  89. 20,
  90. undefined,
  91. 0,
  92. colors.white
  93. );
  94. self.progressBar.anchor(0.5, 0.5);
  95. // Calls state's preload() to load the state's media
  96. self.preload();
  97. } else {
  98. game.state.create();
  99. }
  100. },
  101. /**
  102. * [Not directly used] Encapsulate create() function in the current state.
  103. */
  104. create: function () {
  105. game.render.clear(); // Clears render queue, removing 'progress bar' if preload() was called
  106. if (!self.create) {
  107. console.error(
  108. "Game error: The state called does not have a 'create' function. Unable to continue."
  109. );
  110. } else {
  111. self.create(); // Calls create()
  112. game.render.all(); // After create() ends, renders media on canvas
  113. if (self.restart && self.restart == true) {
  114. // If needed, restart state
  115. game.state.start(game.state.name);
  116. } else {
  117. if (self.update) game.loop.start(self); // Calls update() if it exists
  118. }
  119. }
  120. },
  121. },
  122. /**
  123. * Loads media files to cache. <br>
  124. *
  125. * IMPORTANT: Must ONLY be used inside the function preload(),
  126. * as it calls create() after all media is cached.
  127. *
  128. * @see /js/globals.js for the list of media urls (var url)
  129. *
  130. * @namespace
  131. */
  132. load: {
  133. /**
  134. * Loads language file to cache using Fetch API and
  135. * saves its content as dictionary on game.lang.
  136. *
  137. * @param {string} url url for the selected language
  138. */
  139. lang: function (url) {
  140. game.loadManager.lang[0] = true;
  141. game.loadManager.lang[1] = 0;
  142. game.lang = {}; // Clear previously loaded language
  143. const init = { mode: 'same-origin' };
  144. fetch(url, init)
  145. .then((response) => {
  146. return response.text();
  147. })
  148. .then((text) => {
  149. let msg = text.split('\n');
  150. game.loadedMax += msg.length - 1;
  151. msg.forEach((cur) => {
  152. try {
  153. let msg = cur.split('=');
  154. game.lang[msg[0].trim()] = msg[1].trim();
  155. } catch (Error) {
  156. if (debugMode) console.log('Sintax error fixed');
  157. }
  158. game.load.finishedOneMediaElement(msg.length - 1, 'lang');
  159. });
  160. });
  161. },
  162. /**
  163. * Loads audio files to cache using Fetch API
  164. * saves references in game.audio.
  165. *
  166. * @param {string[]} urls audio urls for the current state
  167. */
  168. audio: function (urls) {
  169. game.loadManager.audio[0] = true;
  170. game.loadManager.audio[1] = 0;
  171. urls = this.getUncachedUrls(urls, game.audio);
  172. if (urls.length == 0) {
  173. game.load.finishedOneMediaElement(0, 'audio');
  174. } else {
  175. game.loadedMax += urls.length - 1;
  176. const init = { mode: 'same-origin' };
  177. urls.forEach((cur) => {
  178. fetch(cur[1][1], init)
  179. .then((response) => response.blob())
  180. .then((myBlob) => {
  181. game.audio[cur[0]] = new Audio(URL.createObjectURL(myBlob));
  182. game.load.finishedOneMediaElement(urls.length - 1, 'audio');
  183. });
  184. });
  185. }
  186. },
  187. /**
  188. * Loads image files to cache using HTMLImageElement
  189. * saves references in game.image.
  190. *
  191. * @param {string[]} urls image urls for the current state
  192. */
  193. image: function (urls) {
  194. game.loadManager.image[0] = true;
  195. game.loadManager.image[1] = 0;
  196. urls = this.getUncachedUrls(urls, game.image);
  197. if (urls.length == 0) {
  198. game.load.finishedOneMediaElement(0, 'image');
  199. } else {
  200. game.loadedMax += urls.length - 1;
  201. urls.forEach((cur) => {
  202. const img = new Image();
  203. img.onload = () => {
  204. game.image[cur[0]] = img;
  205. game.load.finishedOneMediaElement(urls.length - 1, 'image');
  206. };
  207. img.src = cur[1];
  208. });
  209. }
  210. },
  211. /**
  212. * Loads image files that contains spritesheets to cache using HTMLImageElement
  213. * saves references in game.sprite.
  214. *
  215. * @param {string[]} urls spritesheet urls for the current state
  216. */
  217. sprite: function (urls) {
  218. game.loadManager.sprite[0] = true;
  219. game.loadManager.sprite[1] = 0;
  220. urls = this.getUncachedUrls(urls, game.sprite);
  221. if (urls.length == 0) {
  222. game.load.finishedOneMediaElement(0, 'sprite');
  223. } else {
  224. game.loadedMax += urls.length - 1;
  225. urls.forEach((cur) => {
  226. const img = new Image();
  227. img.onload = () => {
  228. game.sprite[cur[0]] = img;
  229. game.load.finishedOneMediaElement(urls.length - 1, 'sprite');
  230. };
  231. img.src = cur[1];
  232. img.frames = cur[2];
  233. });
  234. }
  235. },
  236. /** [Not directly used] Removes the urls that are already in the cache.
  237. *
  238. * @param {string[]} urls array of urls
  239. * @param {object} media media category
  240. *
  241. * @returns {string[]} array of uncached urls
  242. */
  243. getUncachedUrls: function (urls, media) {
  244. const newUrls = [];
  245. urls.forEach((cur) => {
  246. if (media[cur[0]] == undefined) newUrls.push(cur);
  247. });
  248. return newUrls;
  249. },
  250. /** [Not directly used] Informs ONE media file was loaded to cache. <br>
  251. *
  252. * After ALL FILES of the SAME CATEGORY are cached, calls game.load.finishedOneMediaType()
  253. *
  254. * @param {number} lastIndex last index of the media array (to check if is finished)
  255. * @param {String} mediaType media category (to update the cached files from that category)
  256. */
  257. finishedOneMediaElement: function (lastIndex, mediaType) {
  258. // Updates progress bar
  259. if (lastIndex != 0) {
  260. self.progressBar.width = (200 / game.loadedMax) * game.loadedCur;
  261. game.render.all();
  262. game.loadedCur++;
  263. }
  264. // If reached last index of current media array
  265. if (lastIndex == game.loadManager[mediaType][1]) {
  266. // Resets load manager
  267. game.loadManager[mediaType][0] = false;
  268. game.loadManager[mediaType][1] = 0;
  269. // Informs
  270. game.load.finishedOneMediaType();
  271. } else {
  272. // Updates
  273. game.loadManager[mediaType][1]++;
  274. }
  275. },
  276. /** [Not directly used] Informs ALL MEDIA files from the SAME CATEGORY are cached. <br>
  277. *
  278. * After ALL CATEGORIES of media are cached, calls create() via game.state. <br>
  279. * ATTENTION: Do not call create() directly.
  280. */
  281. finishedOneMediaType: function () {
  282. // Checks if finished loading ALL media categories
  283. let endPreload = true;
  284. for (let key in game.loadManager) {
  285. if (game.loadManager[key][0] == true) {
  286. endPreload = false;
  287. break;
  288. }
  289. }
  290. // If flag doesnt change, all media is cached
  291. if (endPreload) {
  292. game.state.create();
  293. }
  294. },
  295. },
  296. /**
  297. * Adds new media to the 'media queue' (game.render.queue). <br>
  298. *
  299. * All queued media will be rendered on canvas when game.render.all() is called.
  300. *
  301. * @namespace
  302. */
  303. add: {
  304. /**
  305. * Adds image to media queue.
  306. *
  307. * @param {number} x x coordinate for the figure
  308. * @param {number} y y coordinate for the figure
  309. * @param {string} img name of the cached image
  310. * @param {undefined|number} scale scale for the image (default = 1)
  311. * @param {undefined|number} alpha level of transparency, from 0 (invisible) to 1 (100% visible) (default = 1)
  312. *
  313. * @returns {object} a reference to the created image.
  314. */
  315. image: function (x, y, img, scale, alpha) {
  316. if (x == undefined || y == undefined || img == undefined)
  317. console.error('Game error: missing parameters.');
  318. else if (game.image[img] == undefined)
  319. console.error('Game error: image not found in cache: ' + img + '.');
  320. else {
  321. const med = {
  322. typeOfMedia: 'image',
  323. name: img,
  324. x: x || game.add.default.x,
  325. y: y || game.add.default.y,
  326. _xWithAnchor: x || game.add.default._xWithAnchor,
  327. _yWithAnchor: y || game.add.default._yWithAnchor,
  328. xAnchor: game.add.default.xAnchor,
  329. yAnchor: game.add.default.yAnchor,
  330. shadow: game.add.default.shadow,
  331. shadowColor: game.add.default.shadowColor,
  332. shadowBlur: game.add.default.shadowBlur,
  333. alpha: alpha != undefined ? alpha : game.add.default.alpha,
  334. scale: scale || game.add.default.scale,
  335. width: game.image[img].width,
  336. height: game.image[img].height,
  337. anchor: function (xAnchor, yAnchor) {
  338. this.xAnchor = xAnchor;
  339. this.yAnchor = yAnchor;
  340. },
  341. get xWithAnchor() {
  342. return this.x - this.width * this.scale * this.xAnchor;
  343. },
  344. get yWithAnchor() {
  345. return this.y - this.height * this.scale * this.yAnchor;
  346. },
  347. };
  348. med.originalScale = med.scale;
  349. game.render.queue.push(med);
  350. return med;
  351. }
  352. },
  353. /**
  354. * Adds spritesheet to media queue. <br>
  355. * A spritesheet is an image that can be cropped to show only one 'frame' at a time.
  356. *
  357. * @param {number} x x coordinate for the figure
  358. * @param {number} y Y coordinate for the figure
  359. * @param {string} img name of the cached spritesheet
  360. * @param {undefined|number} curFrame current frame (default = 0)
  361. * @param {undefined|number} scale scale for the spritesheet (default = 1)
  362. * @param {undefined|number} alpha level of transparency, from 0 (invisible) to 1 (100% visible) (default = 1)
  363. *
  364. * @returns {object} a reference to the created sprite.
  365. */
  366. sprite: function (x, y, img, curFrame, scale, alpha) {
  367. if (x == undefined || y == undefined || img == undefined)
  368. console.error('Game error: missing parameters.');
  369. else if (game.sprite[img] == undefined)
  370. console.error('Game error: sprite not found in cache: ' + img + '.');
  371. else {
  372. const med = {
  373. typeOfMedia: 'sprite',
  374. name: img,
  375. x: x || game.add.default.x,
  376. y: y || game.add.default.y,
  377. _xWithAnchor: x || game.add.default._xWithAnchor,
  378. _yWithAnchor: y || game.add.default._yWithAnchor,
  379. xAnchor: game.add.default.xAnchor,
  380. yAnchor: game.add.default.yAnchor,
  381. shadow: game.add.default.shadow,
  382. shadowColor: game.add.default.shadowColor,
  383. shadowBlur: game.add.default.shadowBlur,
  384. alpha: alpha != undefined ? alpha : game.add.default.alpha,
  385. scale: scale || game.add.default.scale,
  386. width: game.sprite[img].width / game.sprite[img].frames, // Frame width
  387. height: game.sprite[img].height, // Frame height
  388. curFrame: curFrame || 0,
  389. anchor: function (xAnchor, yAnchor) {
  390. this.xAnchor = xAnchor;
  391. this.yAnchor = yAnchor;
  392. },
  393. get xWithAnchor() {
  394. return this.x - this.width * this.scale * this.xAnchor;
  395. },
  396. get yWithAnchor() {
  397. return this.y - this.height * this.scale * this.yAnchor;
  398. },
  399. };
  400. med.originalScale = med.scale;
  401. game.render.queue.push(med);
  402. return med;
  403. }
  404. },
  405. /**
  406. * Adds text to media queue.
  407. *
  408. * @param {number} x x coordinate for the figure
  409. * @param {number} y y coordinate for the figure
  410. * @param {string} text text to be displayed on screen
  411. * @param {object} style object containing font, color and align for the text
  412. *
  413. * @returns {object} a reference to the created text.
  414. */
  415. text: function (x, y, text, style) {
  416. if (
  417. x == undefined ||
  418. y == undefined ||
  419. text == undefined ||
  420. style == undefined
  421. )
  422. console.error('Game error: missing parameters.');
  423. else {
  424. const med = {
  425. typeOfMedia: 'text',
  426. name: text,
  427. x: x || game.add.default.x,
  428. y: y || game.add.default.y,
  429. _xWithAnchor: x || game.add.default._xWithAnchor,
  430. _yWithAnchor: y || game.add.default._yWithAnchor,
  431. xAnchor: game.add.default.xAnchor,
  432. yAnchor: game.add.default.yAnchor,
  433. shadow: game.add.default.shadow,
  434. shadowColor: game.add.default.shadowColor,
  435. shadowBlur: game.add.default.shadowBlur,
  436. alpha: game.add.default.alpha,
  437. font: style.font || game.add.default.font,
  438. fill: style.fill || game.add.default.fill,
  439. align: style.align || game.add.default.align,
  440. anchor: function () {
  441. console.error("Game error: there's no anchor for text.");
  442. },
  443. set style(style) {
  444. this.font = style.font;
  445. this.fill = style.fill;
  446. this.align = style.align;
  447. },
  448. get xWithAnchor() {
  449. return this.x;
  450. },
  451. get yWithAnchor() {
  452. return this.y;
  453. },
  454. };
  455. game.render.queue.push(med);
  456. return med;
  457. }
  458. },
  459. /**
  460. * Adds geometric shapes.
  461. * @namespace
  462. */
  463. geom: {
  464. /**
  465. * Adds rectangle to media queue.
  466. *
  467. * @param {number} x x coordinate for top left corner of the rectangle
  468. * @param {number} y y coordinate for top left corner of the rectangle
  469. * @param {number} width rectangle width (default = 50)
  470. * @param {undefined|number} height rectangle height (default = 50)
  471. * @param {undefined|string} lineColor stroke color (default = black)
  472. * @param {undefined|number} lineWidth stroke width (default = 1px)
  473. * @param {undefined|string} fillColor fill color (default = no fill)
  474. * @param {undefined|number} alpha level of transparency, from 0 (invisible) to 1 (100% visible)) (default = 1)
  475. *
  476. * @returns {object} a reference to the created rectangle.
  477. */
  478. rect: function (
  479. x,
  480. y,
  481. width,
  482. height,
  483. lineColor,
  484. lineWidth,
  485. fillColor,
  486. alpha
  487. ) {
  488. if (x == undefined || y == undefined || width == undefined)
  489. console.error('Game error: missing parameters.');
  490. else {
  491. const med = {
  492. typeOfMedia: 'rect',
  493. x: x || game.add.default.x,
  494. y: y || game.add.default.y,
  495. _xWithAnchor: x || game.add.default._xWithAnchor,
  496. _yWithAnchor: y || game.add.default._yWithAnchor,
  497. xAnchor: game.add.default.xAnchor,
  498. yAnchor: game.add.default.yAnchor,
  499. shadow: game.add.default.shadow,
  500. shadowColor: game.add.default.shadowColor,
  501. shadowBlur: game.add.default.shadowBlur,
  502. alpha: alpha != undefined ? alpha : game.add.default.alpha,
  503. scale: game.add.default.scale,
  504. width: 0,
  505. height: 0,
  506. lineColor: lineColor || game.add.default.lineColor,
  507. lineWidth: 0,
  508. fillColor: fillColor || game.add.default.fillColor,
  509. anchor: function (xAnchor, yAnchor) {
  510. this.xAnchor = xAnchor;
  511. this.yAnchor = yAnchor;
  512. },
  513. get xWithAnchor() {
  514. return this.x - this.width * this.scale * this.xAnchor;
  515. },
  516. get yWithAnchor() {
  517. return this.y - this.height * this.scale * this.yAnchor;
  518. },
  519. };
  520. med.originalScale = med.scale;
  521. if (width != 0) {
  522. med.width = width || game.add.default.width;
  523. }
  524. if (height != 0) {
  525. med.height = height || width || game.add.default.height;
  526. }
  527. if (lineWidth != 0) {
  528. med.lineWidth = lineWidth || game.add.default.lineWidth;
  529. }
  530. game.render.queue.push(med);
  531. return med;
  532. }
  533. },
  534. /**
  535. * Adds circle to media queue.
  536. *
  537. * @param {number} x x coordinate for the circle center
  538. * @param {number} y y coordinate for the circle center
  539. * @param {number} diameter circle diameter (default = 50)
  540. * @param {undefined|string} lineColor stroke color (default = black)
  541. * @param {undefined|number} lineWidth stroke width (default = 1px)
  542. * @param {undefined|string} fillColor fill color (default = no fill)
  543. * @param {undefined|number} alpha level of transparency, from 0 (invisible) to 1 (100% visible)) (default = 1)
  544. *
  545. * @returns {object} a reference to the created circle.
  546. */
  547. circle: function (
  548. x,
  549. y,
  550. diameter,
  551. lineColor,
  552. lineWidth,
  553. fillColor,
  554. alpha
  555. ) {
  556. if (x == undefined || y == undefined || diameter == undefined)
  557. console.error('Game error: missing parameters.');
  558. else {
  559. const med = {
  560. typeOfMedia: 'arc',
  561. x: x || game.add.default.x,
  562. y: y || game.add.default.y,
  563. _xWithAnchor: x || game.add.default._xWithAnchor,
  564. _yWithAnchor: y || game.add.default._yWithAnchor,
  565. xAnchor: game.add.default.xAnchor,
  566. yAnchor: game.add.default.yAnchor,
  567. shadow: game.add.default.shadow,
  568. shadowColor: game.add.default.shadowColor,
  569. shadowBlur: game.add.default.shadowBlur,
  570. alpha: alpha != undefined ? alpha : game.add.default.alpha,
  571. scale: game.add.default.scale,
  572. diameter: 0,
  573. width: 0,
  574. height: 0,
  575. angleStart: 0,
  576. angleEnd: 2 * Math.PI,
  577. anticlockwise: game.add.default.anticlockwise,
  578. lineColor: lineColor || game.add.default.lineColor,
  579. lineWidth: 0,
  580. fillColor: fillColor || game.add.default.fillColor,
  581. anchor: function (xAnchor, yAnchor) {
  582. this.xAnchor = xAnchor;
  583. this.yAnchor = yAnchor;
  584. },
  585. get xWithAnchor() {
  586. return this.x - this.width * this.scale * this.xAnchor;
  587. },
  588. get yWithAnchor() {
  589. return this.y - this.height * this.scale * this.yAnchor;
  590. },
  591. };
  592. med.originalScale = med.scale;
  593. if (diameter != 0) {
  594. med.diameter = diameter || game.add.default.diameter;
  595. med.width = med.height = med.diameter;
  596. }
  597. if (lineWidth != 0) {
  598. med.lineWidth = lineWidth || game.add.default.lineWidth;
  599. }
  600. game.render.queue.push(med);
  601. return med;
  602. }
  603. },
  604. /**
  605. * Adds arc to media queue.
  606. *
  607. * @param {number} x x coordinate for the arc center
  608. * @param {number} y y coordinate for the arc center
  609. * @param {number} diameter arc diameter
  610. * @param {number} angleStart angle to start the arc
  611. * @param {number} angleEnd angle to end the arc
  612. * @param {undefined|boolean} anticlockwise if true, arc is created anticlockwise (default = false)
  613. * @param {undefined|string} lineColor stroke color (default = black)
  614. * @param {undefined|number} lineWidth stroke width (default = 1px)
  615. * @param {undefined|string} fillColor fill color (default = no fill)
  616. * @param {undefined|number} alpha level of transparency, from 0 (invisible) to 1 (100% visible)) (default = 1)
  617. *
  618. * @returns {object} a reference to the created arc.
  619. */
  620. arc: function (
  621. x,
  622. y,
  623. diameter,
  624. angleStart,
  625. angleEnd,
  626. anticlockwise,
  627. lineColor,
  628. lineWidth,
  629. fillColor,
  630. alpha
  631. ) {
  632. if (
  633. x == undefined ||
  634. y == undefined ||
  635. diameter == undefined ||
  636. angleStart == undefined ||
  637. angleEnd == undefined
  638. )
  639. console.error('Game error: missing parameters.');
  640. else {
  641. const med = {
  642. typeOfMedia: 'arc',
  643. x: x || game.add.default.x,
  644. y: y || game.add.default.y,
  645. _xWithAnchor: x || game.add.default._xWithAnchor,
  646. _yWithAnchor: y || game.add.default._yWithAnchor,
  647. xAnchor: game.add.default.xAnchor,
  648. yAnchor: game.add.default.yAnchor,
  649. shadow: game.add.default.shadow,
  650. shadowColor: game.add.default.shadowColor,
  651. shadowBlur: game.add.default.shadowBlur,
  652. alpha: alpha != undefined ? alpha : game.add.default.alpha,
  653. scale: game.add.default.scale,
  654. diameter: 0,
  655. width: 0,
  656. height: 0,
  657. angleStart: angleStart || 0,
  658. angleEnd: angleEnd || 2 * Math.PI,
  659. anticlockwise: anticlockwise || game.add.default.anticlockwise,
  660. lineColor: lineColor || game.add.default.lineColor,
  661. lineWidth: 0,
  662. fillColor: fillColor || game.add.default.fillColor,
  663. anchor: function (xAnchor, yAnchor) {
  664. this.xAnchor = xAnchor;
  665. this.yAnchor = yAnchor;
  666. },
  667. get xWithAnchor() {
  668. return this.x - this.width * this.scale * this.xAnchor;
  669. },
  670. get yWithAnchor() {
  671. return this.y - this.height * this.scale * this.yAnchor;
  672. },
  673. };
  674. med.originalScale = med.scale;
  675. if (diameter != 0) {
  676. med.diameter = diameter || game.add.default.diameter;
  677. med.width = med.height = med.diameter;
  678. }
  679. if (lineWidth != 0) {
  680. med.lineWidth = lineWidth || game.add.default.lineWidth;
  681. }
  682. game.render.queue.push(med);
  683. return med;
  684. }
  685. },
  686. },
  687. /**
  688. * [Not directly used] Default values for the media properties.
  689. */
  690. default: {
  691. // Used in: all types of media.
  692. x: 0,
  693. y: 0,
  694. _xWithAnchor: 0,
  695. _yWithAnchor: 0,
  696. xAnchor: 0,
  697. yAnchor: 0,
  698. shadow: false,
  699. shadowColor: '#0075c5',
  700. shadowBlur: 20,
  701. alpha: 1,
  702. // Used in: image, sprite, square, circle.
  703. scale: 1,
  704. // Used in: text.
  705. font: '14px Arial,sans-serif',
  706. fill: '#000',
  707. align: 'center',
  708. // Used in: square, circle (image and sprite have width and height, but do not have default values).
  709. width: 50,
  710. height: 50,
  711. lineColor: '#000',
  712. lineWidth: 1,
  713. fillColor: 0, // No fill
  714. // Used in: circle.
  715. diameter: 50,
  716. anticlockwise: false,
  717. },
  718. },
  719. /**
  720. * Renders media on current screen. <br<
  721. * It uses properties of html canvas to draw media on screen during game loop.
  722. *
  723. * @namespace
  724. */
  725. render: {
  726. // [Not directly used] Media queue to be rendered on the current state.
  727. queue: [],
  728. /** [Not directly used] Renders image on canvas.
  729. *
  730. * @param {object} cur current media in media queue
  731. */
  732. image: function (cur) {
  733. const x = cur.xWithAnchor,
  734. y = cur.yWithAnchor;
  735. // Rotation
  736. if (cur.rotate && cur.rotate != 0) {
  737. context.save();
  738. context.translate(cur.x, cur.y);
  739. context.rotate((cur.rotate * Math.PI) / 180);
  740. context.translate(-cur.x, -cur.y);
  741. }
  742. // Alpha
  743. context.globalAlpha = cur.alpha;
  744. // Shadow
  745. context.shadowBlur = cur.shadow ? cur.shadowBlur : 0;
  746. context.shadowColor = cur.shadowColor;
  747. // Image
  748. context.drawImage(
  749. game.image[cur.name],
  750. x,
  751. y,
  752. cur.width * cur.scale,
  753. cur.height * cur.scale
  754. );
  755. // End
  756. context.shadowBlur = 0;
  757. context.globalAlpha = 1;
  758. if (cur.rotate && cur.rotate != 0) context.restore();
  759. },
  760. /** [Not directly used] Renders spritesheet on canvas.
  761. *
  762. * @param {object} cur current media in media queue
  763. */
  764. sprite: function (cur) {
  765. const x = cur.xWithAnchor,
  766. y = cur.yWithAnchor;
  767. // Rotation
  768. if (cur.rotate && cur.rotate != 0) {
  769. context.save();
  770. context.translate(cur.x, cur.y);
  771. context.rotate((cur.rotate * Math.PI) / 180);
  772. context.translate(-cur.x, -cur.y);
  773. }
  774. // Alpha
  775. context.globalAlpha = cur.alpha;
  776. // Shadow
  777. context.shadowBlur = cur.shadow ? cur.shadowBlur : 0;
  778. context.shadowColor = cur.shadowColor;
  779. // Image
  780. context.drawImage(
  781. game.sprite[cur.name],
  782. cur.width * cur.curFrame,
  783. 0,
  784. cur.width,
  785. cur.height,
  786. x,
  787. y,
  788. cur.width * cur.scale,
  789. cur.height * cur.scale
  790. );
  791. // End
  792. context.shadowBlur = 0;
  793. context.globalAlpha = 1;
  794. if (cur.rotate && cur.rotate != 0) context.restore();
  795. },
  796. /** [Not directly used] Renders text on canvas.
  797. *
  798. * @param {object} cur current media in media queue
  799. */
  800. text: function (cur) {
  801. const x = cur.xWithAnchor,
  802. y = cur.yWithAnchor;
  803. // Rotation
  804. if (cur.rotate && cur.rotate != 0) {
  805. context.save();
  806. context.translate(cur.x, cur.y);
  807. context.rotate((cur.rotate * Math.PI) / 180);
  808. context.translate(-cur.x, -cur.y);
  809. }
  810. // Alpha
  811. context.globalAlpha = cur.alpha;
  812. // Shadow
  813. context.shadowBlur = cur.shadow ? cur.shadowBlur : 0;
  814. context.shadowColor = cur.shadowColor;
  815. // Font style
  816. context.font = cur.font;
  817. context.textAlign = cur.align;
  818. context.fillStyle = cur.fill;
  819. // Text
  820. context.fillText(cur.name, x, y);
  821. // End
  822. context.shadowBlur = 0;
  823. context.globalAlpha = 1;
  824. if (cur.rotate && cur.rotate != 0) context.restore();
  825. },
  826. /** [Not directly used] Renders geometric shapes on canvas.
  827. *
  828. * @namespace
  829. */
  830. geom: {
  831. /**
  832. * Renders rectangle on canvas.
  833. *
  834. * @param {object} cur current media in media queue
  835. */
  836. rect: function (cur) {
  837. const x = cur.xWithAnchor,
  838. y = cur.yWithAnchor;
  839. // Rotation
  840. if (cur.rotate && cur.rotate != 0) {
  841. context.save();
  842. context.translate(cur.x, cur.y);
  843. context.rotate((cur.rotate * Math.PI) / 180);
  844. context.translate(-cur.x, -cur.y);
  845. }
  846. // Alpha
  847. context.globalAlpha = cur.alpha;
  848. // Shadow
  849. context.shadowBlur = cur.shadow ? cur.shadowBlur : 0;
  850. context.shadowColor = cur.shadowColor;
  851. // Fill
  852. if (cur.fillColor != 0) {
  853. context.fillStyle = cur.fillColor;
  854. context.fillRect(x, y, cur.width * cur.scale, cur.height * cur.scale);
  855. }
  856. // Stroke
  857. if (cur.lineWidth != 0) {
  858. context.strokeStyle = cur.lineColor;
  859. context.lineWidth = cur.lineWidth;
  860. context.strokeRect(
  861. x,
  862. y,
  863. cur.width * cur.scale,
  864. cur.height * cur.scale
  865. );
  866. }
  867. // End
  868. context.shadowBlur = 0;
  869. context.globalAlpha = 1;
  870. if (cur.rotate && cur.rotate != 0) context.restore();
  871. },
  872. /**
  873. * Renders arc on canvas (arc or circle).
  874. *
  875. * @param {object} cur current media in media queue
  876. */
  877. arc: function (cur) {
  878. const x = cur.xWithAnchor,
  879. y = cur.yWithAnchor;
  880. // Rotation
  881. if (cur.rotate && cur.rotate != 0) {
  882. context.save();
  883. context.translate(cur.x, cur.y);
  884. context.rotate((cur.rotate * Math.PI) / 180);
  885. context.translate(-cur.x, -cur.y);
  886. }
  887. // Alpha
  888. context.globalAlpha = cur.alpha;
  889. // Shadow
  890. context.shadowBlur = cur.shadow ? cur.shadowBlur : 0;
  891. context.shadowColor = cur.shadowColor;
  892. // Fill info
  893. if (cur.fillColor != 0) context.fillStyle = cur.fillColor;
  894. // Stroke info
  895. if (cur.lineWidth != 0) {
  896. context.strokeStyle = cur.lineColor;
  897. context.lineWidth = cur.lineWidth;
  898. }
  899. // Path
  900. context.beginPath();
  901. if (cur.angleEnd != 2 * Math.PI) context.lineTo(x, y);
  902. context.arc(
  903. x,
  904. y,
  905. (cur.diameter / 2) * cur.scale,
  906. cur.angleStart,
  907. cur.angleEnd,
  908. cur.anticlockwise
  909. );
  910. if (cur.angleEnd != 2 * Math.PI) context.lineTo(x, y);
  911. // End
  912. if (cur.fillColor != 0) context.fill();
  913. if (cur.lineWidth != 0) context.stroke();
  914. context.shadowBlur = 0;
  915. context.globalAlpha = 1;
  916. if (cur.rotate && cur.rotate != 0) context.restore();
  917. },
  918. },
  919. /**
  920. * Renders all queued media on screen. Called repeatedly by the game loop.
  921. */
  922. all: function () {
  923. game.render.queue.forEach((cur) => {
  924. switch (cur.typeOfMedia) {
  925. case 'image':
  926. this.image(cur);
  927. break;
  928. case 'sprite':
  929. this.sprite(cur);
  930. break;
  931. case 'text':
  932. this.text(cur);
  933. break;
  934. case 'rect':
  935. this.geom.rect(cur);
  936. break;
  937. case 'arc':
  938. this.geom.arc(cur);
  939. break;
  940. }
  941. });
  942. },
  943. /**
  944. * Clears media queue (used when changing states).
  945. */
  946. clear: function () {
  947. game.render.queue = [];
  948. },
  949. },
  950. /**
  951. * Math functions.
  952. *
  953. * @namespace
  954. */
  955. math: {
  956. /**
  957. * Returns a random integer in a range (inclusive for min and max).
  958. *
  959. * @param {number} min smaller integer
  960. * @param {number} max larger integer
  961. *
  962. * @returns {number} random integer in range
  963. */
  964. randomInRange: function (min, max) {
  965. min = Math.ceil(min);
  966. max = Math.floor(max);
  967. return Math.floor(Math.random() * (max - min + 1) + min);
  968. },
  969. /**
  970. * Returns a random divisor for a given number.
  971. *
  972. * @param {number} number number
  973. *
  974. * @returns {number} random divisor for that number
  975. */
  976. randomDivisor: function (number) {
  977. const validDivisors = [];
  978. // If 'number' can be divided by 'i', add to list of 'validDivisors'
  979. for (let i = 2; i < number; i++) {
  980. if (number % i == 0) validDivisors.push(i);
  981. }
  982. const randIndex = game.math.randomInRange(0, validDivisors.length - 1);
  983. return validDivisors[randIndex];
  984. },
  985. /**
  986. * Converts degree to radian.
  987. *
  988. * @param {number} degree number in degrees
  989. *
  990. * @returns {number} its radian equivalent
  991. */
  992. degreeToRad: function (degree) {
  993. return (degree * Math.PI) / 180;
  994. },
  995. /**
  996. * Returns distance from the center of an icon to mouse/pointer (radius).
  997. *
  998. * @param {number} xMouse mouse x coordinate
  999. * @param {number} xIcon icon x coordinate
  1000. * @param {number} yMouse mouse y coordinate
  1001. * @param {number} yIcon icon y coordinate
  1002. *
  1003. * @returns {number} distance between the two icons
  1004. */
  1005. distanceToPointer: function (xMouse, xIcon, yMouse, yIcon) {
  1006. const a = Math.max(xMouse, xIcon) - Math.min(xMouse, xIcon);
  1007. const b = Math.max(yMouse, yIcon) - Math.min(yMouse, yIcon);
  1008. return Math.sqrt(a * a + b * b);
  1009. },
  1010. /**
  1011. * Checks if pointer/mouse is over (rectangular) icon.
  1012. *
  1013. * @param {number} xMouse contains the mouse x coordinate
  1014. * @param {number} yMouse contains the mouse y coordinate
  1015. * @param {object} icon icon
  1016. *
  1017. * @returns {boolean} true if cursor is over icon
  1018. */
  1019. isOverIcon: function (xMouse, yMouse, icon) {
  1020. const x = xMouse,
  1021. y = yMouse,
  1022. cur = icon;
  1023. return (
  1024. y >= cur.yWithAnchor &&
  1025. y <= cur.yWithAnchor + cur.height * cur.scale &&
  1026. x >= cur.xWithAnchor &&
  1027. x <= cur.xWithAnchor + cur.width * cur.scale
  1028. );
  1029. },
  1030. /**
  1031. * Get mouse position coordinates
  1032. *
  1033. * @param {object} mouseEvent
  1034. * @returns {object} x and y mouse coordinates
  1035. */
  1036. getMouse: function (mouseEvent) {
  1037. const c = context.canvas.getBoundingClientRect();
  1038. const canvas_scale = context.canvas.width / parseFloat(c.width);
  1039. return {
  1040. x: (mouseEvent.clientX - c.left) * canvas_scale,
  1041. y: (mouseEvent.clientY - c.top) * canvas_scale,
  1042. };
  1043. },
  1044. /**
  1045. * Calculate spacing for icons on the menu screen
  1046. *
  1047. * @param {number} width width of the desirable part of the screen
  1048. * @param {number} numberOfIcons number or icons to be put on the screen
  1049. *
  1050. * @returns {number} correct spacing between icons
  1051. */
  1052. getOffset: function (width, numberOfIcons) {
  1053. return width / (numberOfIcons + 1);
  1054. },
  1055. /**
  1056. * Converts a given time in seconds (number) to the format HH:MM:SS (string)
  1057. *
  1058. * @param {number} s time in seconds
  1059. *
  1060. * @returns {string} time in the format HH:MM:SS
  1061. */
  1062. convertTime: function (s) {
  1063. let h = 0,
  1064. m = 0;
  1065. if (s > 1200) {
  1066. h = s / 1200;
  1067. s = s % 1200;
  1068. }
  1069. if (s > 60) {
  1070. m = s / 60;
  1071. s = s % 60;
  1072. }
  1073. h = '' + h;
  1074. m = '' + m;
  1075. s = '' + s;
  1076. if (h.length < 2) h = '0' + h;
  1077. if (m.length < 2) m = '0' + m;
  1078. if (s.length < 2) s = '0' + s;
  1079. return h + ':' + m + ':' + s;
  1080. },
  1081. },
  1082. /**
  1083. * Timer used to get the time spent to complete a game.
  1084. *
  1085. * @namespace
  1086. */
  1087. timer: {
  1088. // [Not directly used] Start time.
  1089. _start: 0,
  1090. // [Not directly used] End time.
  1091. end: 0,
  1092. // Elapsed time.
  1093. elapsed: 0,
  1094. /**
  1095. * Reset values and start timer.
  1096. */
  1097. start: function () {
  1098. game.timer._start = game.timer.end = game.timer.elapsed = 0;
  1099. game.timer._start = new Date().getTime();
  1100. },
  1101. /**
  1102. * Stop timer and set elapsed time.
  1103. */
  1104. stop: function () {
  1105. if (game.timer._start != 0 && game.timer.end == 0) {
  1106. // If timer has started but not finished
  1107. game.timer.end = new Date().getTime();
  1108. game.timer.elapsed = Math.floor(
  1109. (game.timer.end - game.timer._start) / 1000
  1110. );
  1111. }
  1112. },
  1113. },
  1114. /**
  1115. * Handles pointer events. <br>
  1116. *
  1117. * @namespace
  1118. */
  1119. event: {
  1120. // [Not directly used] List of events in current state.
  1121. list: [],
  1122. /**
  1123. * Adds new event to current state.
  1124. *
  1125. * @param {string} name event name, can be: 'click' or 'mousemove'
  1126. * @param {function} func function to be called when event is triggered
  1127. */
  1128. add: function (name, func) {
  1129. context.canvas.addEventListener(name, func);
  1130. game.event.list.push([name, func]);
  1131. },
  1132. /** [Not directly used] Clears list of events. Called before moving to new state.
  1133. */
  1134. clear: function () {
  1135. game.event.list.forEach((cur) => {
  1136. context.canvas.removeEventListener(cur[0], cur[1]);
  1137. });
  1138. game.event.list = [];
  1139. },
  1140. },
  1141. /** [Not directly used] Handles 'game loop'. <br>
  1142. *
  1143. * After the media queue is filled in create(), the 'game loop' starts.
  1144. * It calls update() iteratively, re-rendering the screen every time. <br>
  1145. *
  1146. * The 'game loop' is stoped by leaving the current state.
  1147. *
  1148. * @namespace
  1149. */
  1150. loop: {
  1151. // [Not directly used] Holds animation event.
  1152. id: undefined,
  1153. // [Not directly used] State that called the loop.
  1154. curState: undefined,
  1155. // [Not directly used] Loop status, can be: 'on', 'ending' or 'off'.
  1156. status: 'off',
  1157. // [Not directly used]
  1158. waitingToStart: undefined,
  1159. // [Not directly used]
  1160. startTime: 0,
  1161. // [Not directly used] 1000: 1 second | 60: expected frames per second.
  1162. duration: 1000 / 60,
  1163. /** [Not directly used] Starts game loop.
  1164. *
  1165. * @param {object} state current state
  1166. */
  1167. start: function (state) {
  1168. if (game.loop.status == 'off') {
  1169. game.loop.curState = state;
  1170. game.loop.startTime = new Date().getTime();
  1171. game.loop.status = 'on';
  1172. game.loop.id = requestAnimationFrame(game.loop.run);
  1173. } else {
  1174. // If 'game.loop.status' is either 'on' or 'ending'
  1175. game.loop.waitingToStart = state;
  1176. if (game.loop.status == 'on') game.loop.stop();
  1177. }
  1178. },
  1179. /**
  1180. * [Not directly used] Stops game loop.
  1181. */
  1182. stop: function () {
  1183. if (game.loop.status == 'on') game.loop.status = 'ending';
  1184. },
  1185. /**
  1186. * [Not directly used] Executes game loop.
  1187. *
  1188. * This code will run on each iteration of the game loop.
  1189. */
  1190. run: function () {
  1191. if (game.loop.status != 'on') {
  1192. game.loop.clear();
  1193. } else {
  1194. const timestamp = new Date().getTime();
  1195. const runtime = timestamp - game.loop.startTime;
  1196. if (runtime >= game.loop.duration) {
  1197. // Calls state's update()
  1198. game.loop.curState.update();
  1199. // Updates state's animation
  1200. game.animation.run();
  1201. }
  1202. game.loop.id = requestAnimationFrame(game.loop.run); // Loop
  1203. }
  1204. },
  1205. /**
  1206. * [Not directly used] Resets game loop values.
  1207. */
  1208. clear: function () {
  1209. if (game.loop.id != undefined) {
  1210. cancelAnimationFrame(game.loop.id); // Cancel animation event
  1211. game.loop.id = undefined; // Clears object that holds animation event
  1212. game.loop.curState = undefined; // Clears object that holds current state
  1213. game.loop.status = 'off'; // Inform animation must end (read in game.loop.run())
  1214. }
  1215. if (game.loop.waitingToStart != undefined) {
  1216. const temp = game.loop.waitingToStart;
  1217. game.loop.waitingToStart = undefined;
  1218. game.loop.start(temp);
  1219. }
  1220. },
  1221. },
  1222. /**
  1223. * Handles spritesheet animation. <br>
  1224. * It iterates through the spritesheet frames inside the animation queue.
  1225. * Called by game loop.
  1226. *
  1227. * @namespace
  1228. */
  1229. animation: {
  1230. // [Not directly used] Animation queue for the current state.
  1231. queue: [],
  1232. // [Not directly used]
  1233. count: 0,
  1234. /**
  1235. * Plays animation.
  1236. *
  1237. * @param {string} name animation name (identifier)
  1238. */
  1239. play: function (name) {
  1240. let newAnimation;
  1241. // Gets first object in the 'render queue' with that animation name
  1242. for (let i in game.render.queue) {
  1243. if (
  1244. game.render.queue[i].animation != undefined &&
  1245. game.render.queue[i].animation[0] == name
  1246. ) {
  1247. newAnimation = game.render.queue[i];
  1248. break;
  1249. }
  1250. }
  1251. // If found, saves object in the 'animation queue'
  1252. if (newAnimation != undefined) game.animation.queue.push(newAnimation);
  1253. },
  1254. /**
  1255. * Stops animation.
  1256. *
  1257. * @param {string} name animation name
  1258. */
  1259. stop: function (name) {
  1260. // Removes all with that name from the 'animation queue'
  1261. game.animation.queue.forEach((cur) => {
  1262. if (cur.animation[0] == name) {
  1263. game.animation.queue.splice(cur, 1);
  1264. }
  1265. });
  1266. },
  1267. /**
  1268. * [Not directly used] Executes animation.
  1269. */
  1270. run: function () {
  1271. game.animation.queue.forEach((character) => {
  1272. if (
  1273. !character.animation[2] ||
  1274. game.animation.count % character.animation[2] == 0
  1275. ) {
  1276. const i = character.animation[1].indexOf(character.curFrame);
  1277. if (i == -1) {
  1278. // Frame not found
  1279. if (debugMode)
  1280. console.error('Game error: animation frame not found.');
  1281. } else if (i < character.animation[1].length - 1) {
  1282. // Go to next frame
  1283. character.curFrame = character.animation[1][i + 1];
  1284. } else {
  1285. character.curFrame = character.animation[1][0]; // If last frame, restart
  1286. }
  1287. }
  1288. });
  1289. game.animation.count++;
  1290. },
  1291. /**
  1292. * [Not directly used] Clears animation queue.
  1293. */
  1294. clear: function () {
  1295. // Resets animation counter
  1296. game.animation.count = 0;
  1297. // Clears property 'animation' from objects in game.render.queue
  1298. game.render.queue.forEach((cur) => {
  1299. if (cur.animation != undefined) {
  1300. delete cur.animation;
  1301. }
  1302. });
  1303. // Clears animation queue
  1304. game.animation.queue = [];
  1305. },
  1306. },
  1307. };