littlejsengine 1.5.2 → 1.6.4

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.
Files changed (72) hide show
  1. package/README.md +155 -33
  2. package/{engine/index.d.ts → build/littlejs.d.ts} +14 -6
  3. package/{engine/engine.all.module.js → build/littlejs.esm.js} +110 -99
  4. package/build/littlejs.esm.min.js +1 -0
  5. package/{engine/engine.all.js → build/littlejs.js} +102 -97
  6. package/build/littlejs.min.js +1 -0
  7. package/{engine/engine.all.release.js → build/littlejs.release.js} +95 -95
  8. package/examples/breakout/game.js +5 -0
  9. package/examples/breakout/gameObjects.js +41 -48
  10. package/examples/breakout/index.html +5 -4
  11. package/examples/breakoutTutorial/README.md +514 -0
  12. package/examples/breakoutTutorial/game.js +181 -0
  13. package/examples/breakoutTutorial/images/1.png +0 -0
  14. package/examples/breakoutTutorial/images/10.png +0 -0
  15. package/examples/breakoutTutorial/images/11.png +0 -0
  16. package/examples/breakoutTutorial/images/2.png +0 -0
  17. package/examples/breakoutTutorial/images/3.png +0 -0
  18. package/examples/breakoutTutorial/images/4.png +0 -0
  19. package/examples/breakoutTutorial/images/5.png +0 -0
  20. package/examples/breakoutTutorial/images/6.png +0 -0
  21. package/examples/breakoutTutorial/images/7.png +0 -0
  22. package/examples/breakoutTutorial/images/8.png +0 -0
  23. package/examples/breakoutTutorial/images/9.png +0 -0
  24. package/examples/breakoutTutorial/index.html +10 -0
  25. package/examples/electron/build.bat +52 -0
  26. package/{electron → examples/electron}/electron.js +18 -21
  27. package/{game.js → examples/electron/game.js} +1 -1
  28. package/examples/electron/index.html +2 -0
  29. package/examples/electron/package.json +20 -0
  30. package/examples/empty/game.js +11 -1
  31. package/examples/empty/index.html +2 -2
  32. package/examples/module/game.js +1 -1
  33. package/examples/module/index.html +3 -2
  34. package/examples/particles/index.html +3 -2
  35. package/examples/platformer/gameLevel.js +1 -1
  36. package/examples/platformer/gamePlayer.js +1 -1
  37. package/examples/platformer/index.html +8 -7
  38. package/examples/puzzle/index.html +4 -3
  39. package/{build.bat → examples/starter/build.bat} +17 -17
  40. package/examples/starter/game.js +111 -0
  41. package/examples/starter/index.html +27 -0
  42. package/examples/starter/tiles.png +0 -0
  43. package/examples/stress/index.html +3 -2
  44. package/examples/typescript/game.js +89 -89
  45. package/examples/typescript/game.ts +11 -11
  46. package/examples/typescript/index.html +3 -2
  47. package/package.json +4 -4
  48. package/{engine → src}/engine.js +1 -2
  49. package/{engine → src}/engineAudio.js +11 -11
  50. package/src/engineBuild.bat +132 -0
  51. package/{engine → src}/engineDebug.js +8 -2
  52. package/{engine → src}/engineDraw.js +19 -15
  53. package/{engine → src}/engineExport.js +8 -2
  54. package/{engine → src}/engineInput.js +54 -58
  55. package/{engine → src}/engineMedals.js +1 -1
  56. package/{engine → src}/engineObject.js +4 -4
  57. package/{engine → src}/engineRelease.js +1 -0
  58. package/{engine → src}/engineUtilities.js +4 -4
  59. package/electron/electronBuild.bat +0 -32
  60. package/electron/electronSetup.bat +0 -2
  61. package/electron/package.json +0 -21
  62. package/engine/engine.all.min.js +0 -1
  63. package/engine/engine.all.module.min.js +0 -1
  64. package/engine/engineBuild.bat +0 -127
  65. package/index.html +0 -26
  66. /package/{tiles.png → examples/electron/tiles.png} +0 -0
  67. /package/{favicon.png → examples/favicon.png} +0 -0
  68. /package/{engine → src}/engineFont.png +0 -0
  69. /package/{engine → src}/engineParticles.js +0 -0
  70. /package/{engine → src}/engineSettings.js +0 -0
  71. /package/{engine → src}/engineTileLayer.js +0 -0
  72. /package/{engine → src}/engineWebGL.js +0 -0
@@ -45,6 +45,12 @@ let showWatermark = 1;
45
45
  * @memberof Debug */
46
46
  let godMode = 0;
47
47
 
48
+ /** Key code used to toggle debug mode, Esc by default
49
+ * @type {Boolean}
50
+ * @default
51
+ * @memberof Debug */
52
+ let debugKey = 27;
53
+
48
54
  // Engine internal variables not exposed to documentation
49
55
  let debugPrimitives = [], debugOverlay = 0, debugPhysics = 0, debugRaycast = 0,
50
56
  debugParticles = 0, debugGamepads = 0, debugMedals = 0, debugTakeScreenshot, downloadLink;
@@ -166,7 +172,7 @@ const debugUpdate = ()=>
166
172
  if (!debug)
167
173
  return;
168
174
 
169
- if (keyWasPressed(192)) // ~
175
+ if (keyWasPressed(debugKey)) // Esc
170
176
  debugOverlay = !debugOverlay;
171
177
  if (debugOverlay)
172
178
  {
@@ -351,7 +357,7 @@ const debugRender = ()=>
351
357
  overlayContext.fillText('Time: ' + formatTime(time), x, y += h);
352
358
  overlayContext.fillText('---------', x, y += h);
353
359
  overlayContext.fillStyle = '#f00';
354
- overlayContext.fillText('~: Debug Overlay', x, y += h);
360
+ overlayContext.fillText('ESC: Debug Overlay', x, y += h);
355
361
  overlayContext.fillStyle = debugPhysics ? '#f00' : '#fff';
356
362
  overlayContext.fillText('1: Debug Physics', x, y += h);
357
363
  overlayContext.fillStyle = debugParticles ? '#f00' : '#fff';
@@ -741,7 +747,7 @@ class Vector2
741
747
  * @return {Color}
742
748
  * @memberof Utilities
743
749
  */
744
- const colorRGBA = (r, g, b, a)=> new Color(r, g, b, a);
750
+ const rgb = (r, g, b, a)=> new Color(r, g, b, a);
745
751
 
746
752
  /**
747
753
  * Create a color object with HSLA values
@@ -752,7 +758,7 @@ const colorRGBA = (r, g, b, a)=> new Color(r, g, b, a);
752
758
  * @return {Color}
753
759
  * @memberof Utilities
754
760
  */
755
- const colorHSLA = (h, s, l, a)=> new Color().setHSLA(h, s, l, a);
761
+ const hsl = (h, s, l, a)=> new Color().setHSLA(h, s, l, a);
756
762
 
757
763
  /**
758
764
  * Color object (red, green, blue, alpha) with some helpful functions
@@ -760,8 +766,8 @@ const colorHSLA = (h, s, l, a)=> new Color().setHSLA(h, s, l, a);
760
766
  * let a = new Color; // white
761
767
  * let b = new Color(1, 0, 0); // red
762
768
  * let c = new Color(0, 0, 0, 0); // transparent black
763
- * let d = colorRGBA(0, 0, 1); // blue using rgb color
764
- * let e = colorHSLA(.3, 1, .5); // green using hsl color
769
+ * let d = RGB(0, 0, 1); // blue using rgb color
770
+ * let e = HSL(.3, 1, .5); // green using hsl color
765
771
  */
766
772
  class Color
767
773
  {
@@ -1336,6 +1342,7 @@ class EngineObject
1336
1342
  this.velocity.y += gravity * this.gravityScale;
1337
1343
  this.pos.x += this.velocity.x *= this.damping;
1338
1344
  this.pos.y += this.velocity.y *= this.damping;
1345
+ this.angle += this.angleVelocity *= this.angleDamping;
1339
1346
 
1340
1347
  // physics sanity checks
1341
1348
  ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
@@ -1392,6 +1399,7 @@ class EngineObject
1392
1399
  const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity; // prefer to push up if small delta
1393
1400
  const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
1394
1401
  const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
1402
+ const elasticity = max(this.elasticity, o.elasticity);
1395
1403
 
1396
1404
  if (smallStepUp | isBlockedY | !isBlockedX) // resolve y collision
1397
1405
  {
@@ -1404,7 +1412,7 @@ class EngineObject
1404
1412
  this.groundObject = o;
1405
1413
 
1406
1414
  // bounce if other object is fixed or grounded
1407
- this.velocity.y *= -this.elasticity;
1415
+ this.velocity.y *= -elasticity;
1408
1416
  }
1409
1417
  else if (o.mass)
1410
1418
  {
@@ -1418,7 +1426,6 @@ class EngineObject
1418
1426
  + this.velocity.y * 2 * this.mass / (this.mass + o.mass);
1419
1427
 
1420
1428
  // lerp betwen elastic or inelastic based on elasticity
1421
- const elasticity = max(this.elasticity, o.elasticity);
1422
1429
  this.velocity.y = lerp(elasticity, inelastic, elastic0);
1423
1430
  o.velocity.y = lerp(elasticity, inelastic, elastic1);
1424
1431
  }
@@ -1439,12 +1446,11 @@ class EngineObject
1439
1446
  + this.velocity.x * 2 * this.mass / (this.mass + o.mass);
1440
1447
 
1441
1448
  // lerp betwen elastic or inelastic based on elasticity
1442
- const elasticity = max(this.elasticity, o.elasticity);
1443
1449
  this.velocity.x = lerp(elasticity, inelastic, elastic0);
1444
1450
  o.velocity.x = lerp(elasticity, inelastic, elastic1);
1445
1451
  }
1446
1452
  else // bounce if other object is fixed
1447
- this.velocity.x *= -this.elasticity;
1453
+ this.velocity.x *= -elasticity;
1448
1454
  }
1449
1455
  debugOverlay && debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f0f');
1450
1456
  }
@@ -1826,6 +1832,23 @@ function setBlendMode(additive, useWebGL=glEnable)
1826
1832
  mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
1827
1833
  }
1828
1834
 
1835
+ /** Draw text on overlay canvas in world space
1836
+ * Automatically splits new lines into rows
1837
+ * @param {String} text
1838
+ * @param {Vector2} pos
1839
+ * @param {Number} [size=1]
1840
+ * @param {Color} [color=Color()]
1841
+ * @param {Number} [lineWidth=0]
1842
+ * @param {Color} [lineColor=Color(0,0,0)]
1843
+ * @param {String} [textAlign='center']
1844
+ * @param {String} [font=fontDefault]
1845
+ * @param {CanvasRenderingContext2D} [context=overlayContext]
1846
+ * @memberof Draw */
1847
+ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context)
1848
+ {
1849
+ drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context);
1850
+ }
1851
+
1829
1852
  /** Draw text on overlay canvas in screen space
1830
1853
  * Automatically splits new lines into rows
1831
1854
  * @param {String} text
@@ -1835,6 +1858,8 @@ function setBlendMode(additive, useWebGL=glEnable)
1835
1858
  * @param {Number} [lineWidth=0]
1836
1859
  * @param {Color} [lineColor=Color(0,0,0)]
1837
1860
  * @param {String} [textAlign='center']
1861
+ * @param {String} [font=fontDefault]
1862
+ * @param {CanvasRenderingContext2D} [context=overlayContext]
1838
1863
  * @memberof Draw */
1839
1864
  function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
1840
1865
  {
@@ -1855,21 +1880,6 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
1855
1880
  });
1856
1881
  }
1857
1882
 
1858
- /** Draw text on overlay canvas in world space
1859
- * Automatically splits new lines into rows
1860
- * @param {String} text
1861
- * @param {Vector2} pos
1862
- * @param {Number} [size=1]
1863
- * @param {Color} [color=Color()]
1864
- * @param {Number} [lineWidth=0]
1865
- * @param {Color} [lineColor=Color(0,0,0)]
1866
- * @param {String} [textAlign='center']
1867
- * @memberof Draw */
1868
- function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font)
1869
- {
1870
- drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, mainContext);
1871
- }
1872
-
1873
1883
  ///////////////////////////////////////////////////////////////////////////////
1874
1884
 
1875
1885
  let engineFontImage;
@@ -2123,15 +2133,15 @@ function inputUpdatePost()
2123
2133
  onkeydown = (e)=>
2124
2134
  {
2125
2135
  if (debug && e.target != document.body) return;
2126
- e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
2136
+ e.repeat || (inputData[isUsingGamepad = 0][remapKey(e.which)] = 3);
2127
2137
  preventDefaultInput && e.preventDefault();
2128
2138
  }
2129
2139
  onkeyup = (e)=>
2130
2140
  {
2131
2141
  if (debug && e.target != document.body) return;
2132
- inputData[0][remapKeyCode(e.keyCode)] = 4;
2142
+ inputData[0][remapKey(e.which)] = 4;
2133
2143
  }
2134
- const remapKeyCode = (c)=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
2144
+ const remapKey = (c)=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
2135
2145
 
2136
2146
  ///////////////////////////////////////////////////////////////////////////////
2137
2147
  // Mouse event handlers
@@ -2273,6 +2283,9 @@ if (isTouchDevice)
2273
2283
  return true;
2274
2284
  }
2275
2285
 
2286
+ // try to create touch game pad
2287
+ touchGamepadEnable && touchGamepadCreate();
2288
+
2276
2289
  return ontouchstart(e);
2277
2290
  }
2278
2291
  }
@@ -2286,76 +2299,69 @@ let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
2286
2299
  // create the touch gamepad, called automatically by the engine
2287
2300
  function touchGamepadCreate()
2288
2301
  {
2289
- if (!touchGamepadEnable || !isTouchDevice)
2290
- return;
2291
-
2292
2302
  // touch input internal variables
2293
2303
  touchGamepadButtons = [];
2294
2304
  touchGamepadStick = vec2();
2295
2305
 
2296
- // setup touch input
2297
- ontouchstart = (e)=>
2306
+ let touchHandler = ontouchstart;
2307
+ ontouchstart = ontouchmove = ontouchend = (e)=>
2298
2308
  {
2299
- // fix mobile audio, force it to play a sound on first touch
2300
- zzfx(0);
2301
-
2302
- ontouchstart = ontouchmove = ontouchend = (e)=>
2309
+ // clear touch gamepad input
2310
+ touchGamepadStick = vec2();
2311
+ touchGamepadButtons = [];
2312
+
2313
+ const touching = e.touches.length;
2314
+ if (touching)
2303
2315
  {
2304
- // clear touch gamepad input
2305
- touchGamepadStick = vec2();
2306
- touchGamepadButtons = [];
2307
-
2308
- const touching = e.touches.length;
2309
- if (touching)
2316
+ touchGamepadTimer.set();
2317
+ if (paused)
2310
2318
  {
2311
- // set that gamepad is active
2312
- isUsingGamepad = 1;
2313
- touchGamepadTimer.set();
2314
-
2315
- if (paused)
2316
- {
2317
- // touch anywhere to press start when paused
2318
- touchGamepadButtons[9] = 1;
2319
- return;
2320
- }
2319
+ // touch anywhere to press start when paused
2320
+ touchGamepadButtons[9] = 1;
2321
+ return;
2321
2322
  }
2323
+ }
2322
2324
 
2323
- // get center of left and right sides
2324
- const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2325
- const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
2326
- const startCenter = mainCanvasSize.scale(.5);
2325
+ // get center of left and right sides
2326
+ const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2327
+ const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
2328
+ const startCenter = mainCanvasSize.scale(.5);
2327
2329
 
2328
- // check each touch point
2329
- for (const touch of e.touches)
2330
+ // check each touch point
2331
+ for (const touch of e.touches)
2332
+ {
2333
+ const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
2334
+ if (touchPos.distance(stickCenter) < touchGamepadSize)
2330
2335
  {
2331
- const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
2332
- if (touchPos.distance(stickCenter) < touchGamepadSize)
2333
- {
2334
- // virtual analog stick
2335
- if (touchGamepadAnalog)
2336
- touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
2337
- else
2338
- {
2339
- // 8 way dpad
2340
- const angle = touchPos.subtract(stickCenter).angle();
2341
- touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
2342
- }
2343
- }
2344
- else if (touchPos.distance(buttonCenter) < touchGamepadSize)
2345
- {
2346
- // virtual face buttons
2347
- const button = touchPos.subtract(buttonCenter).direction();
2348
- touchGamepadButtons[button] = 1;
2349
- }
2350
- else if (touchPos.distance(startCenter) < touchGamepadSize)
2336
+ // virtual analog stick
2337
+ if (touchGamepadAnalog)
2338
+ touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
2339
+ else
2351
2340
  {
2352
- // virtual start button in center
2353
- touchGamepadButtons[9] = 1;
2341
+ // 8 way dpad
2342
+ const angle = touchPos.subtract(stickCenter).angle();
2343
+ touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
2354
2344
  }
2355
2345
  }
2346
+ else if (touchPos.distance(buttonCenter) < touchGamepadSize)
2347
+ {
2348
+ // virtual face buttons
2349
+ const button = touchPos.subtract(buttonCenter).direction();
2350
+ touchGamepadButtons[button] = 1;
2351
+ }
2352
+ else if (touchPos.distance(startCenter) < touchGamepadSize)
2353
+ {
2354
+ // virtual start button in center
2355
+ touchGamepadButtons[9] = 1;
2356
+ }
2356
2357
  }
2357
2358
 
2358
- return ontouchstart(e);
2359
+ // call default touch handler and set to using gamepad
2360
+ touchHandler(e);
2361
+ isUsingGamepad = 1;
2362
+
2363
+ // must return true so the document will get focus
2364
+ return true;
2359
2365
  }
2360
2366
  }
2361
2367
 
@@ -2381,7 +2387,7 @@ function touchGamepadRender()
2381
2387
  overlayContext.beginPath();
2382
2388
 
2383
2389
  const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2384
- if (touchGamepadAnalog)
2390
+ if (touchGamepadAnalog) // draw circle shaped gamepad
2385
2391
  {
2386
2392
  overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
2387
2393
  overlayContext.fill();
@@ -2686,7 +2692,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
2686
2692
  }
2687
2693
 
2688
2694
  ///////////////////////////////////////////////////////////////////////////////
2689
- // ZzFXMicro - Zuper Zmall Zound Zynth - v1.1.8 by Frank Force
2695
+ // ZzFXMicro - Zuper Zmall Zound Zynth - v1.2.0 by Frank Force
2690
2696
 
2691
2697
  /** Generate and play a ZzFX sound
2692
2698
  * <br>
@@ -2712,11 +2718,11 @@ function zzfxG
2712
2718
  bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0
2713
2719
  )
2714
2720
  {
2715
- // init parameters
2721
+ // locals
2716
2722
  let PI2 = PI*2, startSlide = slide *= 500 * PI2 / zzfxR / zzfxR, b=[],
2717
2723
  startFrequency = frequency *= (1 + randomness*rand(-1,1)) * PI2 / zzfxR,
2718
- t=0, tm=0, i=0, j=1, r=0, c=0, s=0, f, length;
2719
-
2724
+ t=0, tm=0, i=0, j=1, r=0, c=0, s=0, f, length
2725
+
2720
2726
  // scale by sample rate
2721
2727
  attack = attack * zzfxR + 9; // minimum attack to prevent pop
2722
2728
  decay *= zzfxR;
@@ -2766,18 +2772,18 @@ function zzfxG
2766
2772
  Math.cos(modulation*tm++); // modulation
2767
2773
  t += f - f*noise*(1 - (Math.sin(i)+1)*1e9%2); // noise
2768
2774
 
2769
- if (j && ++j > pitchJumpTime) // pitch jump
2775
+ if (j && ++j > pitchJumpTime) // pitch jump
2770
2776
  {
2771
- frequency += pitchJump; // apply pitch jump
2772
- startFrequency += pitchJump; // also apply to start
2773
- j = 0; // reset pitch jump time
2777
+ frequency += pitchJump; // apply pitch jump
2778
+ startFrequency += pitchJump; // also apply to start
2779
+ j = 0; // reset pitch jump time
2774
2780
  }
2775
2781
 
2776
2782
  if (repeatTime && !(++r % repeatTime)) // repeat
2777
2783
  {
2778
- frequency = startFrequency; // reset frequency
2779
- slide = startSlide; // reset slide
2780
- j = j || 1; // reset pitch jump time
2784
+ frequency = startFrequency; // reset frequency
2785
+ slide = startSlide; // reset slide
2786
+ j ||= 1; // reset pitch jump time
2781
2787
  }
2782
2788
  }
2783
2789
 
@@ -3545,7 +3551,7 @@ class Particle extends EngineObject
3545
3551
  * LittleJS Medal System
3546
3552
  * <br> - Tracks and displays medals
3547
3553
  * <br> - Saves medals to local storage
3548
- * <br> - Newgrounds and OS13k integration
3554
+ * <br> - Newgrounds integration
3549
3555
  * @namespace Medals
3550
3556
  */
3551
3557
 
@@ -4269,7 +4275,7 @@ const engineName = 'LittleJS';
4269
4275
  * @type {String}
4270
4276
  * @default
4271
4277
  * @memberof Engine */
4272
- const engineVersion = '1.5.2';
4278
+ const engineVersion = '1.6.4';
4273
4279
 
4274
4280
  /** Frames per second to update objects
4275
4281
  * @type {Number}
@@ -4360,7 +4366,6 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4360
4366
  (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
4361
4367
 
4362
4368
  gameInit();
4363
- touchGamepadCreate();
4364
4369
  engineUpdate();
4365
4370
  };
4366
4371
 
@@ -0,0 +1 @@
1
+ let showWatermark=0,godMode=0,debugKeyCode=0;const debug=0,debugOverlay=0,debugPhysics=0,debugParticles=0,debugRaycast=0,debugGamepads=0,debugMedals=0,ASSERT=()=>{},debugInit=()=>{},debugUpdate=()=>{},debugRender=()=>{},debugRect=()=>{},debugCircle=()=>{},debugPoint=()=>{},debugLine=()=>{},debugAABB=()=>{},debugText=()=>{},debugClear=()=>{},debugSaveCanvas=()=>{};"use strict";const PI=Math.PI,abs=a=>0>a?-a:a,min=(a,b)=>a<b?a:b,max=(a,b)=>a>b?a:b,sign=a=>0>a?-1:1,mod=(a,b=1)=>(a%b+b)%b,clamp=(a,b=0,c=1)=>a<b?b:a>c?c:a,percent=(a,b=0,c=1)=>c-b?clamp((a-b)/(c-b)):0,lerp=(a,b=0,c=1)=>b+clamp(a)*(c-b),smoothStep=a=>a*a*(3-2*a),nearestPowerOfTwo=a=>2**Math.ceil(Math.log2(a)),isOverlapping=(a,b,c,d)=>2*abs(a.x-c.x)<b.x+d.x&&2*abs(a.y-c.y)<b.y+d.y,wave=(a=1,b=1,c=time)=>b/2*(1-Math.cos(c*a*2*PI)),formatTime=a=>(a/60|0)+":"+(10>a%60?"0":"")+(a%60|0),rand=(a=1,b=0)=>b+(a-b)*Math.random(),randInt=(a=1,b=0)=>rand(a,b)|0,randSign=()=>2*randInt(2)-1,randInCircle=(a=1,b=0)=>0<a?randVector(a*rand(b/a,1)**.5):new Vector2,randVector=(a=1)=>(new Vector2).setAngle(rand(2*PI),a),randColor=(a=new Color,b=new Color(0,0,0,1),c)=>c?a.lerp(b,rand()):new Color(rand(a.r,b.r),rand(a.g,b.g),rand(a.b,b.b),rand(a.a,b.a));let randSeed=1;const setRandSeed=a=>randSeed=a,randSeeded=(a=1,b=0)=>{randSeed^=randSeed<<13;randSeed^=randSeed>>>17;randSeed^=randSeed<<5;return b+(a-b)*abs(randSeed%1e9)/1e9},vec2=(a=0,b)=>void 0==a.x?new Vector2(a,void 0==b?a:b):new Vector2(a.x,a.y),isVector2=a=>!isNaN(a.x)&&!isNaN(a.y);class Vector2{constructor(a=0,b=0){this.x=a;this.y=b}copy(){return new Vector2(this.x,this.y)}add(a){ASSERT(isVector2(a));return new Vector2(this.x+a.x,this.y+a.y)}subtract(a){ASSERT(isVector2(a));return new Vector2(this.x-a.x,this.y-a.y)}multiply(a){ASSERT(isVector2(a));return new Vector2(this.x*a.x,this.y*a.y)}divide(a){ASSERT(isVector2(a));return new Vector2(this.x/a.x,this.y/a.y)}scale(a){ASSERT(!isVector2(a));return new Vector2(this.x*a,this.y*a)}length(){return this.lengthSquared()**.5}lengthSquared(){return this.x**2+this.y**2}distance(a){return this.distanceSquared(a)**.5}distanceSquared(a){return(this.x-a.x)**2+(this.y-a.y)**2}normalize(a=1){const b=this.length();return b?this.scale(a/b):new Vector2(0,a)}clampLength(a=1){const b=this.length();return b>a?this.scale(a/b):this}dot(a){ASSERT(isVector2(a));return this.x*a.x+this.y*a.y}cross(a){ASSERT(isVector2(a));return this.x*a.y-this.y*a.x}angle(){return Math.atan2(this.x,this.y)}setAngle(a=0,b=1){this.x=b*Math.sin(a);this.y=b*Math.cos(a);return this}rotate(a){const b=Math.cos(a);a=Math.sin(a);return new Vector2(this.x*b-this.y*a,this.x*a+this.y*b)}direction(){return abs(this.x)>abs(this.y)?0>this.x?3:1:0>this.y?2:0}invert(){return new Vector2(this.y,-this.x)}floor(){return new Vector2(Math.floor(this.x),Math.floor(this.y))}area(){return abs(this.x*this.y)}lerp(a,b){ASSERT(isVector2(a));return this.add(a.subtract(this).scale(clamp(b)))}arrayCheck(a){return 0<=this.x&&0<=this.y&&this.x<a.x&&this.y<a.y}toString(a=3){if(debug)return`(${(0>this.x?"":" ")+this.x.toFixed(a)},${(0>this.y?"":" ")+this.y.toFixed(a)} )`}}const rgb=(a,b,c,d)=>new Color(a,b,c,d),hsl=(a,b,c,d)=>(new Color).setHSLA(a,b,c,d);class Color{constructor(a=1,b=1,c=1,d=1){this.r=a;this.g=b;this.b=c;this.a=d}copy(){return new Color(this.r,this.g,this.b,this.a)}add(a){return new Color(this.r+a.r,this.g+a.g,this.b+a.b,this.a+a.a)}subtract(a){return new Color(this.r-a.r,this.g-a.g,this.b-a.b,this.a-a.a)}multiply(a){return new Color(this.r*a.r,this.g*a.g,this.b*a.b,this.a*a.a)}divide(a){return new Color(this.r/a.r,this.g/a.g,this.b/a.b,this.a/a.a)}scale(a,b=a){return new Color(this.r*a,this.g*a,this.b*a,this.a*b)}clamp(){return new Color(clamp(this.r),clamp(this.g),clamp(this.b),clamp(this.a))}lerp(a,b){return this.add(a.subtract(this).scale(clamp(b)))}setHSLA(a=0,b=0,c=1,d=1){b=.5>c?c*(1+b):c+b-c*b;c=2*c-b;const e=(f,h,k)=>(k=(k%1+1)%1)<1/6?f+6*(h-f)*k:.5>k?h:k<2/3?f+(h-f)*(2/3-k)*6:f;this.r=e(c,b,a+1/3);this.g=e(c,b,a);this.b=e(c,b,a-1/3);this.a=d;return this}getHSLA(){const a=clamp(this.r),b=clamp(this.g),c=clamp(this.b),d=clamp(this.a),e=Math.max(a,b,c),f=Math.min(a,b,c),h=(e+f)/2;let k=0,g=0;if(e!=f){let l=e-f;g=.5<h?l/(2-e-f):l/(e+f);a==e?k=(b-c)/l+(b<c?6:0):b==e?k=(c-a)/l+2:c==e&&(k=(a-b)/l+4)}return[k/6,g,h,d]}mutate(a=.05,b=0){return new Color(this.r+rand(a,-a),this.g+rand(a,-a),this.b+rand(a,-a),this.a+rand(b,-b)).clamp()}toString(a=1){const b=c=>(16>(c=255*c|0)?"0":"")+c.toString(16);return"#"+b(this.r)+b(this.g)+b(this.b)+(a?b(this.a):"")}setHex(a){this.r=clamp(parseInt(a.slice(1,3),16)/255);this.g=clamp(parseInt(a.slice(3,5),16)/255);this.b=clamp(parseInt(a.slice(5,7),16)/255);this.a=7<a.length?clamp(parseInt(a.slice(7,9),16)/255):1;return this}rgbaInt(){const a=255*clamp(this.r)|0,b=(255*clamp(this.g)|0)<<8,c=(255*clamp(this.b)|0)<<16,d=(255*clamp(this.a)|0)<<24;return a+b+c+d}}class Timer{constructor(a){this.time=void 0==a?void 0:time+a;this.setTime=a}set(a=0){this.time=time+a;this.setTime=a}unset(){this.time=void 0}isSet(){return void 0!=this.time}active(){return time<=this.time}elapsed(){return time>this.time}get(){return this.isSet()?time-this.time:0}getPercent(){return this.isSet()?percent(this.time-time,this.setTime,0):0}toString(){if(debug)return this.unset()?"unset":Math.abs(this.get())+" seconds "+(0>this.get()?"before":"after")}valueOf(){return this.get()}}"use strict";let cameraPos=vec2(),cameraScale=32,canvasMaxSize=vec2(1920,1200),canvasFixedSize=vec2(),cavasPixelated=1,fontDefault="arial",glEnable=1,glOverlay=1,tileSizeDefault=vec2(16),tileFixBleedScale=.3,enablePhysicsSolver=1,objectDefaultMass=1,objectDefaultDamping=1,objectDefaultAngleDamping=1,objectDefaultElasticity=0,objectDefaultFriction=.8,objectMaxSpeed=1,gravity=0,particleEmitRateScale=1,gamepadsEnable=1,gamepadDirectionEmulateStick=1,inputWASDEmulateDirection=1,touchGamepadEnable=0,touchGamepadAnalog=1,touchGamepadSize=99,touchGamepadAlpha=.3,vibrateEnable=1,soundEnable=1,soundVolume=.5,soundDefaultRange=40,soundDefaultTaper=.7,medalDisplayTime=5,medalDisplaySlideTime=.5,medalDisplaySize=vec2(640,80),medalDisplayIconSize=50,medalsPreventUnlock;"use strict";const engineName="LittleJS",engineVersion="1.6.4",frameRate=60,timeDelta=1/frameRate;let engineObjects=[],engineObjectsCollide=[],frame=0,time=0,timeReal=0,paused=0;function setPaused(a){paused=a}function engineInit(a,b,c,d,e,f){function h(m=0){var n=m-k;k=m;if(debug||showWatermark)l=lerp(.05,l,1e3/(n||1));m=debug&&keyIsDown(107);const p=debug&&keyIsDown(109);debug&&(n*=m?5:p?.2:1);timeReal+=n/1e3;g+=!paused*n;m||(g=min(g,50));canvasFixedSize.x?(mainCanvas.width=canvasFixedSize.x,mainCanvas.height=canvasFixedSize.y,n=innerWidth/innerHeight,m=mainCanvas.width/mainCanvas.height,(glCanvas||mainCanvas).style.width=mainCanvas.style.width=overlayCanvas.style.width=n<m?"100%":"",(glCanvas||mainCanvas).style.height=mainCanvas.style.height=overlayCanvas.style.height=n<m?"":"100%"):(mainCanvas.width=min(innerWidth,canvasMaxSize.x),mainCanvas.height=min(innerHeight,canvasMaxSize.y));overlayCanvas.width=mainCanvas.width;overlayCanvas.height=mainCanvas.height;mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height);if(paused)inputUpdate(),debugUpdate(),c(),inputUpdatePost();else{n=0;0>g&&-9<g&&(n=g,g=0);for(;0<=g;g-=1e3/frameRate)inputUpdate(),b(),engineObjectsUpdate(),debugUpdate(),c(),inputUpdatePost();g+=n}enginePreRender();d();engineObjects.sort((r,w)=>r.renderOrder-w.renderOrder);for(var q of engineObjects)q.destroyed||q.render();e();glRenderPostProcess();medalsRender();touchGamepadRender();debugRender();glEnable&&glCopyToContext(mainContext);showWatermark&&(overlayContext.textAlign="right",overlayContext.textBaseline="top",overlayContext.font="1em monospace",overlayContext.fillStyle="#000",q=engineName+" v"+engineVersion+" / "+drawCount+" / "+engineObjects.length+" / "+l.toFixed(1)+(glEnable?" GL":" 2D"),overlayContext.fillText(q,mainCanvas.width-3,3),overlayContext.fillStyle="#fff",overlayContext.fillText(q,mainCanvas.width-2,2),drawCount=0);requestAnimationFrame(h)}tileImage.onerror=tileImage.onload=()=>{tileImageFixBleed=vec2(tileFixBleedScale).divide(tileImageSize=vec2(tileImage.width,tileImage.height));debug&&(tileImage.onload=()=>ASSERT(1));document.body.style="margin:0;overflow:hidden;background:#000;touch-action:none;user-select:none;-webkit-user-select:none";document.body.appendChild(mainCanvas=document.createElement("canvas"));mainContext=mainCanvas.getContext("2d");debugInit();glEnable&&glInit();document.body.appendChild(overlayCanvas=document.createElement("canvas"));overlayContext=overlayCanvas.getContext("2d");(glCanvas||mainCanvas).style=mainCanvas.style=overlayCanvas.style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)";a();h()};let k=0,g,l;f?tileImage.src=f:tileImage.onload()}function enginePreRender(){mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height);mainContext.imageSmoothingEnabled=!cavasPixelated;glEnable&&glPreRender()}function engineObjectsUpdate(){engineObjectsCollide=engineObjects.filter(b=>b.collideSolidObjects);const a=b=>{if(!b.destroyed){b.update();for(const c of b.children)a(c)}};for(const b of engineObjects)b.parent||a(b);engineObjects=engineObjects.filter(b=>!b.destroyed);time=++frame/frameRate}function engineObjectsDestroy(){for(const a of engineObjects)a.parent||a.destroy();engineObjects=engineObjects.filter(a=>!a.destroyed)}function engineObjectsCallback(a,b,c,d=engineObjects){if(a)if(void 0!=b.x)for(const e of d)isOverlapping(a,b,e.pos,e.size)&&c(e);else{b*=b;for(const e of d)a.distanceSquared(e.pos)<b&&c(e)}else for(const e of d)c(e)}"use strict";class EngineObject{constructor(a=vec2(),b=vec2(1),c=-1,d=tileSizeDefault,e=0,f,h=0){ASSERT(isVector2(a)&&isVector2(b));this.pos=a.copy();this.size=b;this.drawSize;this.tileIndex=c;this.tileSize=d;this.angle=e;this.color=f;this.additiveColor;this.mass=objectDefaultMass;this.damping=objectDefaultDamping;this.angleDamping=objectDefaultAngleDamping;this.elasticity=objectDefaultElasticity;this.friction=objectDefaultFriction;this.gravityScale=1;this.renderOrder=h;this.velocity=new Vector2;this.angleVelocity=0;this.spawnTime=time;this.children=[];this.collideTiles=1;engineObjects.push(this)}update(){var a=this.parent;if(a)this.pos=this.localPos.multiply(vec2(a.getMirrorSign(),1)).rotate(-a.angle).add(a.pos),this.angle=a.getMirrorSign()*this.localAngle+a.angle;else if(this.velocity.x=clamp(this.velocity.x,-objectMaxSpeed,objectMaxSpeed),this.velocity.y=clamp(this.velocity.y,-objectMaxSpeed,objectMaxSpeed),a=this.pos.copy(),this.velocity.y+=gravity*this.gravityScale,this.pos.x+=this.velocity.x*=this.damping,this.pos.y+=this.velocity.y*=this.damping,this.angle+=this.angleVelocity*=this.angleDamping,ASSERT(0<=this.angleDamping&&1>=this.angleDamping),ASSERT(0<=this.damping&&1>=this.damping),enablePhysicsSolver&&this.mass){var b=0>this.velocity.y;if(this.groundObject){var c=this.groundObject.velocity?this.groundObject.velocity.x:0;this.velocity.x=c+(this.velocity.x-c)*this.friction;this.groundObject=0}if(this.collideSolidObjects)for(var d of engineObjectsCollide){if(!this.isSolid&!d.isSolid||d.destroyed||d.parent||d==this)continue;if(!isOverlapping(this.pos,this.size,d.pos,d.size))continue;if(!this.collideWithObject(d)|!d.collideWithObject(this))continue;if(isOverlapping(a,this.size,d.pos,d.size)){c=a.subtract(d.pos);var e=c.length();c=.01>e?randVector(.001):c.scale(.001/e);this.velocity=this.velocity.add(c);d.mass&&(d.velocity=d.velocity.subtract(c));debugOverlay&&debugPhysics&&debugAABB(this.pos,this.size,d.pos,d.size,"#f00");continue}e=this.size.add(d.size);var f=2*(a.y-d.pos.y)>e.y+gravity;const k=2*abs(a.y-d.pos.y)<e.y;var h=2*abs(a.x-d.pos.x)<e.x;c=max(this.elasticity,d.elasticity);if(f|h|!k)if(this.pos.y=d.pos.y+(e.y/2+.001)*sign(a.y-d.pos.y),d.groundObject&&b||!d.mass)b&&(this.groundObject=d),this.velocity.y*=-c;else if(d.mass){h=(this.mass*this.velocity.y+d.mass*d.velocity.y)/(this.mass+d.mass);const g=d.velocity.y*(d.mass-this.mass)/(this.mass+d.mass)+2*this.velocity.y*this.mass/(this.mass+d.mass);this.velocity.y=lerp(c,h,this.velocity.y*(this.mass-d.mass)/(this.mass+d.mass)+2*d.velocity.y*d.mass/(this.mass+d.mass));d.velocity.y=lerp(c,h,g)}!f&k&&(this.pos.x=d.pos.x+(e.x/2+.001)*sign(a.x-d.pos.x),d.mass?(e=(this.mass*this.velocity.x+d.mass*d.velocity.x)/(this.mass+d.mass),f=d.velocity.x*(d.mass-this.mass)/(this.mass+d.mass)+2*this.velocity.x*this.mass/(this.mass+d.mass),this.velocity.x=lerp(c,e,this.velocity.x*(this.mass-d.mass)/(this.mass+d.mass)+2*d.velocity.x*d.mass/(this.mass+d.mass)),d.velocity.x=lerp(c,e,f)):this.velocity.x*=-c);debugOverlay&&debugPhysics&&debugAABB(this.pos,this.size,d.pos,d.size,"#f0f")}this.collideTiles&&tileCollisionTest(this.pos,this.size,this)&&!tileCollisionTest(a,this.size,this)&&(c=tileCollisionTest(new Vector2(a.x,this.pos.y),this.size,this),d=tileCollisionTest(new Vector2(this.pos.x,a.y),this.size,this),c|!d&&(this.groundObject=b,this.velocity.y*=-this.elasticity,b=(a.y-this.size.y/2|0)-(a.y-this.size.y/2),0>b&&b>this.damping*this.velocity.y+gravity*this.gravityScale&&(this.velocity.y=this.damping?(b-gravity*this.gravityScale)/this.damping:0),this.pos.y=a.y),d&&(this.pos.x=a.x,this.velocity.x*=-this.elasticity))}}render(){drawTile(this.pos,this.drawSize||this.size,this.tileIndex,this.tileSize,this.color,this.angle,this.mirror,this.additiveColor)}destroy(){if(!this.destroyed){this.destroyed=1;this.parent&&this.parent.removeChild(this);for(const a of this.children)a.destroy(a.parent=0)}}collideWithTile(a,b){return 0<a}collideWithTileRaycast(a,b){return 0<a}collideWithObject(a){return 1}getAliveTime(){return time-this.spawnTime}applyAcceleration(a){this.mass&&(this.velocity=this.velocity.add(a))}applyForce(a){this.applyAcceleration(a.scale(1/this.mass))}getMirrorSign(){return this.mirror?-1:1}addChild(a,b=vec2(),c=0){ASSERT(!a.parent&&!this.children.includes(a));this.children.push(a);a.parent=this;a.localPos=b.copy();a.localAngle=c}removeChild(a){ASSERT(a.parent==this&&this.children.includes(a));this.children.splice(this.children.indexOf(a),1);a.parent=0}setCollision(a=1,b=1,c=1){ASSERT(a||!b);this.collideSolidObjects=a;this.isSolid=b;this.collideTiles=c}toString(){if(debug){let a="type = "+this.constructor.name;if(this.pos.x||this.pos.y)a+="\npos = "+this.pos;if(this.velocity.x||this.velocity.y)a+="\nvelocity = "+this.velocity;if(this.size.x||this.size.y)a+="\nsize = "+this.size;this.angle&&(a+="\nangle = "+this.angle.toFixed(3));this.color&&(a+="\ncolor = "+this.color);return a}}}"use strict";let mainCanvas,mainContext,overlayCanvas,overlayContext,mainCanvasSize=vec2();const tileImage=new Image;let tileImageSize,tileImageFixBleed,drawCount;const screenToWorld=a=>{ASSERT(mainCanvasSize.x&&mainCanvasSize.y,"mainCanvasSize is invalid");return a.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos)},worldToScreen=a=>{ASSERT(mainCanvasSize.x&&mainCanvasSize.y,"mainCanvasSize is invalid");return a.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5))};function drawTile(a,b=vec2(1),c=-1,d=tileSizeDefault,e=new Color,f=0,h,k=new Color(0,0,0,0),g=glEnable){showWatermark&&++drawCount;if(glEnable&&g)if(0>c||!tileImage.width)glDraw(a.x,a.y,b.x,b.y,f,0,0,0,0,0,e.rgbaInt());else{var l=tileImageSize.x/d.x|0;g=d.x/tileImageSize.x;const m=d.y/tileImageSize.y,n=c%l*g;l=(c/l|0)*m;glDraw(a.x,a.y,h?-b.x:b.x,b.y,f,n+tileImageFixBleed.x,l+tileImageFixBleed.y,n-tileImageFixBleed.x+g,l-tileImageFixBleed.y+m,e.rgbaInt(),k.rgbaInt())}else drawCanvas2D(a,b,f,h,m=>{if(0>c)m.fillStyle=e,m.fillRect(-.5,-.5,1,1);else{var n=tileImageSize.x/d.x|0;const p=c%n*d.x+tileFixBleedScale;n=(c/n|0)*d.y+tileFixBleedScale;const q=d.x-2*tileFixBleedScale,r=d.y-2*tileFixBleedScale;m.globalAlpha=e.a;m.drawImage(tileImage,p,n,q,r,-.5,-.5,1,1)}})}function drawRect(a,b,c,d,e){drawTile(a,b,-1,tileSizeDefault,c,d,0,0,e)}function drawTileScreenSpace(a,b=vec2(1),c,d,e,f,h,k,g){drawTile(screenToWorld(a),b.scale(1/cameraScale),c,d,e,f,h,k,g)}function drawRectScreenSpace(a,b,c,d,e){drawTileScreenSpace(a,b,-1,tileSizeDefault,c,d,0,0,e)}function drawLine(a,b,c=.1,d,e){b=vec2((b.x-a.x)/2,(b.y-a.y)/2);c=vec2(c,2*b.length());drawRect(a.add(b),c,d,b.angle(),e)}function drawCanvas2D(a,b,c,d,e,f=mainContext){a=worldToScreen(a);b=b.scale(cameraScale);f.save();f.translate(a.x+.5|0,a.y+.5|0);f.rotate(c);f.scale(d?-b.x:b.x,b.y);e(f);f.restore()}function setBlendMode(a,b=glEnable){glEnable&&b?glSetBlendMode(a):mainContext.globalCompositeOperation=a?"lighter":"source-over"}function drawText(a,b,c=1,d,e=0,f,h,k,g){drawTextScreen(a,worldToScreen(b),c*cameraScale,d,e*cameraScale,f,h,k,g)}function drawTextScreen(a,b,c=1,d=new Color,e=0,f=new Color(0,0,0),h="center",k=fontDefault,g=overlayContext){g.fillStyle=d;g.lineWidth=e;g.strokeStyle=f;g.textAlign=h;g.font=c+"px "+k;g.textBaseline="middle";g.lineJoin="round";b=b.copy();(a+"").split("\n").forEach(l=>{e&&g.strokeText(l,b.x,b.y);g.fillText(l,b.x,b.y);b.y+=c})}let engineFontImage;class FontImage{constructor(a,b=vec2(8),c=vec2(0,1),d=0,e=overlayContext){engineFontImage||((engineFontImage=new Image).src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC");this.image=a||engineFontImage;this.tileSize=b;this.paddingSize=c;this.startTileIndex=d;this.context=e}drawTextScreen(a,b,c=4,d){const e=this.context;e.save();e.imageSmoothingEnabled=!cavasPixelated;const f=this.tileSize,h=f.add(this.paddingSize).scale(c),k=this.image.width/this.tileSize.x|0;(a+"").split("\n").forEach((g,l)=>{const m=d?g.length*f.x*c/2|0:0;for(let q=g.length;q--;){var n=g[q].charCodeAt();if(32>n||127<n)n=127;var p=this.startTileIndex+n-32;n=p%k;p=p/k|0;const r=b.add(vec2(q,l).multiply(h));e.drawImage(this.image,n*f.x,p*f.y,f.x,f.y,r.x-m,r.y,f.x*c,f.y*c)}});e.restore()}drawText(a,b,c=1,d){this.drawTextScreen(a,worldToScreen(b).floor(),c*cameraScale|0,d)}}const isFullscreen=()=>document.fullscreenElement;function toggleFullscreen(){isFullscreen()?document.exitFullscreen&&document.exitFullscreen():document.body.requestFullscreen&&document.body.requestFullscreen()}"use strict";const keyIsDown=(a,b=0)=>inputData[b]&&inputData[b][a]&1,keyWasPressed=(a,b=0)=>inputData[b]&&inputData[b][a]&2?1:0,keyWasReleased=(a,b=0)=>inputData[b]&&inputData[b][a]&4?1:0,clearInput=()=>inputData=[[]],mouseIsDown=keyIsDown,mouseWasPressed=keyWasPressed,mouseWasReleased=keyWasReleased;let mousePos=vec2(),mousePosScreen=vec2(),mouseWheel=0,isUsingGamepad=0,preventDefaultInput=0;const gamepadIsDown=(a,b=0)=>keyIsDown(a,b+1),gamepadWasPressed=(a,b=0)=>keyWasPressed(a,b+1),gamepadWasReleased=(a,b=0)=>keyWasReleased(a,b+1),gamepadStick=(a,b=0)=>stickData[b]?stickData[b][a]||vec2():vec2();let inputData=[[]];function inputUpdate(){isTouchDevice||document.hasFocus()||clearInput();mousePos=screenToWorld(mousePosScreen);gamepadsUpdate()}function inputUpdatePost(){for(const a of inputData)for(const b in a)a[b]&=1;mouseWheel=0}onkeydown=a=>{debug&&a.target!=document.body||(a.repeat||(inputData[isUsingGamepad=0][remapKey(a.which)]=3),preventDefaultInput&&a.preventDefault())};onkeyup=a=>{debug&&a.target!=document.body||(inputData[0][remapKey(a.which)]=4)};const remapKey=a=>inputWASDEmulateDirection?87==a?38:83==a?40:65==a?37:68==a?39:a:a;onmousedown=a=>{inputData[isUsingGamepad=0][a.button]=3;onmousemove(a);a.button&&a.preventDefault()};onmouseup=a=>inputData[0][a.button]=inputData[0][a.button]&2|4;onmousemove=a=>mousePosScreen=mouseToScreen(a);onwheel=a=>a.ctrlKey||(mouseWheel=sign(a.deltaY));oncontextmenu=a=>!1;const mouseToScreen=a=>{if(!mainCanvas)return vec2();const b=mainCanvas.getBoundingClientRect();return vec2(mainCanvas.width,mainCanvas.height).multiply(vec2(percent(a.x,b.left,b.right),percent(a.y,b.top,b.bottom)))},stickData=[];function gamepadsUpdate(){if(touchGamepadEnable&&touchGamepadTimer.isSet()){(stickData[0]||(stickData[0]=[]))[0]=vec2(touchGamepadStick.x,-touchGamepadStick.y);var a=inputData[1]||(inputData[1]=[]);for(var b=10;b--;){var c=3==b?2:2==b?3:b;a[c]=touchGamepadButtons[b]?1+2*!gamepadIsDown(c,0):4*gamepadIsDown(c,0)}}if(gamepadsEnable&&navigator&&navigator.getGamepads&&(document.hasFocus()||debug))for(a=navigator.getGamepads(),b=a.length;b--;){var d=a[b];const h=inputData[b+1]||(inputData[b+1]=[]);c=stickData[b]||(stickData[b]=[]);if(d){var e=k=>.3<k?percent(k,.3,.8):-.3>k?-percent(-k,.3,.8):0;for(var f=0;f<d.axes.length-1;f+=2)c[f>>1]=vec2(e(d.axes[f]),e(-d.axes[f+1])).clampLength();for(e=d.buttons.length;e--;)f=d.buttons[e],h[e]=f.pressed?1+2*!gamepadIsDown(e,b):4*gamepadIsDown(e,b),isUsingGamepad|=!b&&f.pressed,touchGamepadEnable&&touchGamepadTimer.unset();gamepadDirectionEmulateStick&&(d=vec2(gamepadIsDown(15,b)-gamepadIsDown(14,b),gamepadIsDown(12,b)-gamepadIsDown(13,b)),d.lengthSquared()&&(c[0]=d.clampLength()))}}}const vibrate=a=>vibrateEnable&&navigator&&navigator.vibrate&&navigator.vibrate(a),vibrateStop=()=>vibrate(0),isTouchDevice=void 0!==window.ontouchstart;if(isTouchDevice){let a,b=onmousedown,c=onmouseup;onmousedown=onmouseup=()=>0;ontouchstart=d=>{zzfx(0);ontouchstart=ontouchmove=ontouchend=e=>{e.button=0;const f=e.touches.length;f?(e.x=e.touches[0].clientX,e.y=e.touches[0].clientY,a?onmousemove(e):b(e)):a&&c(e);a=f;return!0};touchGamepadEnable&&touchGamepadCreate();return ontouchstart(d)}}let touchGamepadTimer=new Timer,touchGamepadButtons,touchGamepadStick;function touchGamepadCreate(){touchGamepadButtons=[];touchGamepadStick=vec2();let a=ontouchstart;ontouchstart=ontouchmove=ontouchend=b=>{touchGamepadStick=vec2();touchGamepadButtons=[];if(b.touches.length&&(touchGamepadTimer.set(),paused)){touchGamepadButtons[9]=1;return}const c=vec2(touchGamepadSize,mainCanvasSize.y-touchGamepadSize),d=mainCanvasSize.subtract(vec2(touchGamepadSize,touchGamepadSize)),e=mainCanvasSize.scale(.5);for(const h of b.touches){var f=mouseToScreen(vec2(h.clientX,h.clientY));f.distance(c)<touchGamepadSize?touchGamepadAnalog?touchGamepadStick=f.subtract(c).scale(2/touchGamepadSize).clampLength():(f=f.subtract(c).angle(),touchGamepadStick.setAngle((4*f/PI+8.5|0)*PI/4)):f.distance(d)<touchGamepadSize?(f=f.subtract(d).direction(),touchGamepadButtons[f]=1):f.distance(e)<touchGamepadSize&&(touchGamepadButtons[9]=1)}a(b);isUsingGamepad=1;return!0}}function touchGamepadRender(){if(touchGamepadEnable&&touchGamepadTimer.isSet()){var a=percent(touchGamepadTimer,4,3);if(a&&!paused){overlayContext.save();overlayContext.globalAlpha=a*touchGamepadAlpha;overlayContext.strokeStyle="#fff";overlayContext.lineWidth=3;overlayContext.fillStyle=0<touchGamepadStick.lengthSquared()?"#fff":"#000";overlayContext.beginPath();a=vec2(touchGamepadSize,mainCanvasSize.y-touchGamepadSize);if(touchGamepadAnalog)overlayContext.arc(a.x,a.y,touchGamepadSize/2,0,9),overlayContext.fill();else for(var b=10;b--;){var c=b*PI/4;overlayContext.arc(a.x,a.y,.6*touchGamepadSize,c+PI/8,c+PI/8);b%2&&overlayContext.arc(a.x,a.y,.33*touchGamepadSize,c,c);1==b&&overlayContext.fill()}overlayContext.stroke();a=vec2(mainCanvasSize.x-touchGamepadSize,mainCanvasSize.y-touchGamepadSize);for(b=4;b--;)c=a.add((new Vector2).setAngle(b*PI/2,touchGamepadSize/2)),overlayContext.fillStyle=touchGamepadButtons[b]?"#fff":"#000",overlayContext.beginPath(),overlayContext.arc(c.x,c.y,touchGamepadSize/4,0,9),overlayContext.fill(),overlayContext.stroke();overlayContext.restore()}}}"use strict";class Sound{constructor(a,b=soundDefaultRange,c=soundDefaultTaper){soundEnable&&(this.range=b,this.taper=c,this.randomness=a[1]||0,a[1]=0,this.cachedSamples=zzfxG(...a))}play(a,b=1,c=1,d=1){if(soundEnable){var e;if(a){if(e=this.range){const f=cameraPos.distanceSquared(a);if(f>e*e)return;b*=percent(f**.5,e,e*this.taper)}e=2*worldToScreen(a).x/mainCanvas.width-1}a=c+c*this.randomness*d*rand(-1,1);return playSamples([this.cachedSamples],b,a,e)}}playNote(a,b,c){if(soundEnable)return this.play(b,c,2**(a/12),0)}}class Music{constructor(a){soundEnable&&(this.cachedSamples=zzfxM(...a))}play(a,b=1){if(soundEnable)return this.source=playSamples(this.cachedSamples,a,1,0,b)}stop(){this.source&&this.source.stop();this.source=0}isPlaying(){return this.source}}function playAudioFile(a,b=1,c=1){if(soundEnable)return a=new Audio(a),a.volume=soundVolume*b,a.loop=c,a.play(),a}function speak(a,b="",c=1,d=1,e=1){if(soundEnable&&speechSynthesis)return a=new SpeechSynthesisUtterance(a),a.lang=b,a.volume=2*c*soundVolume,a.rate=d,a.pitch=e,speechSynthesis.speak(a),a}const speakStop=()=>speechSynthesis&&speechSynthesis.cancel(),getNoteFrequency=(a,b=220)=>b*2**(a/12);let audioContext;function playSamples(a,b=1,c=1,d=0,e=0){if(soundEnable&&(audioContext||=new AudioContext,audioContext.resume(),"running"==audioContext.state)){var f=audioContext.createBuffer(a.length,a[0].length,zzfxR),h=audioContext.createBufferSource();a.forEach((k,g)=>f.getChannelData(g).set(k));h.buffer=f;h.playbackRate.value=c;h.loop=e;a=audioContext.createGain();a.gain.value=soundVolume*b;a.connect(audioContext.destination);h.connect(new StereoPannerNode(audioContext,{pan:clamp(d,-1,1)})).connect(a);h.start();return h}}const zzfx=(...a)=>playSamples([zzfxG(...a)]),zzfxR=44100;function zzfxG(a=1,b=.05,c=220,d=0,e=0,f=.1,h=0,k=1,g=0,l=0,m=0,n=0,p=0,q=0,r=0,w=0,u=0,B=1,y=0,C=0){let v=2*PI,F=g*=500*v/zzfxR/zzfxR,A=[];b=c*=(1+b*rand(-1,1))*v/zzfxR;let x=0,D=0,t=0,z=1,G=0,I=0,E=0,J,H;d=d*zzfxR+9;y*=zzfxR;e*=zzfxR;f*=zzfxR;u*=zzfxR;l*=500*v/zzfxR**3;r*=v/zzfxR;m*=v/zzfxR;n*=zzfxR;p=p*zzfxR|0;for(H=d+y+e+f+u|0;t<H;A[t++]=E)++I%(100*w|0)||(E=h?1<h?2<h?3<h?Math.sin((x%v)**3):max(min(Math.tan(x),1),-1):1-(2*x/v%2+2)%2:1-4*abs(Math.round(x/v)-x/v):Math.sin(x),E=(p?1-C+C*Math.sin(v*t/p):1)*sign(E)*abs(E)**k*a*soundVolume*(t<d?t/d:t<d+y?1-(t-d)/y*(1-B):t<d+y+e?B:t<H-u?(H-t-u)/f*B:0),E=u?E/2+(u>t?0:(t<H-u?1:(H-t)/u)*A[t-u|0]/2):E),J=(c+=g+=l)*Math.cos(r*D++),x+=J-J*q*(1-1e9*(Math.sin(t)+1)%2),z&&++z>n&&(c+=m,b+=m,z=0),!p||++G%p||(c=b,g=F,z||=1);return A}function zzfxM(a,b,c,d=125){let e,f,h,k,g,l,m,n,p,q,r,w,u,B=0,y,C=[],v=[],F=[],A=0,x=0,D=1,t={},z=zzfxR/d*60>>2;for(;D;A++)C=[D=n=w=0],c.forEach((G,I)=>{m=b[G][A]||[0,0,0];D|=!!b[G][A];y=w+(b[G][0].length-2-!n)*z;u=I==c.length-1;e=2;for(h=w;e<m.length+u;n=++e){g=m[e];p=e==m.length+u-1&&u||q!=(m[0]||0)|g|0;for(f=0;f<z&&n;f++>z-99&&p?r+=(1>r)/99:0)l=(1-r)*C[B++]/2||0,v[h]=(v[h]||0)-l*x+l,F[h]=(F[h++]||0)+l*x+l;g&&(r=g%1,x=m[1]||0,g|=0)&&(C=t[[q=m[B=0]||0,g]]=t[[q,g]]||(k=[...a[q]],k[2]*=2**((g-12)/12),0<g?zzfxG(...k):[]))}w=y});return[v,F]}"use strict";let tileCollision=[],tileCollisionSize=vec2();function initTileCollision(a){tileCollisionSize=a;tileCollision=[];for(a=tileCollision.length=tileCollisionSize.area();a--;)tileCollision[a]=0}const setTileCollisionData=(a,b=0)=>a.arrayCheck(tileCollisionSize)&&(tileCollision[(a.y|0)*tileCollisionSize.x+a.x|0]=b),getTileCollisionData=a=>a.arrayCheck(tileCollisionSize)?tileCollision[(a.y|0)*tileCollisionSize.x+a.x|0]:0;function tileCollisionTest(a,b=vec2(),c){const d=max(a.x-b.x/2|0,0);var e=max(a.y-b.y/2|0,0);const f=min(a.x+b.x/2,tileCollisionSize.x);for(a=min(a.y+b.y/2,tileCollisionSize.y);e<a;++e)for(b=d;b<f;++b){const h=tileCollision[e*tileCollisionSize.x+b];if(h&&(!c||c.collideWithTile(h,new Vector2(b,e))))return 1}}function tileCollisionRaycast(a,b,c){var d=(b=b.floor()).subtract(a=a.floor());const e=abs(d.x),f=-abs(d.y),h=sign(d.x);d=sign(d.y);for(let g=a.x,l=a.y,m=e+f;;){var k=getTileCollisionData(vec2(g,l));if(k&&(c?c.collideWithTileRaycast(k,new Vector2(g,l)):0<k))return debugRaycast&&debugLine(a,b,"#f00",.02,1),debugRaycast&&debugPoint(new Vector2(g+.5,l+.5),"#ff0",1),new Vector2(g+.5,l+.5);if(g==b.x&l==b.y)break;k=2*m;k>=f&&(m+=f,g+=h);k<=e&&(m+=e,l+=d)}debugRaycast&&debugLine(a,b,"#00f",.02,1)}class TileLayerData{constructor(a,b=0,c=0,d=new Color){this.tile=a;this.direction=b;this.mirror=c;this.color=d}clear(){this.tile=this.direction=this.mirror=0;color=new Color}}class TileLayer extends EngineObject{constructor(a,b=tileCollisionSize,c=tileSizeDefault,d=vec2(1),e=0){super(a,b,-1,c,0,void 0,e);this.canvas=document.createElement("canvas");this.context=this.canvas.getContext("2d");this.scale=d;this.isOverlay;this.data=[];for(a=this.size.area();a--;)this.data.push(new TileLayerData)}setData(a,b,c){a.arrayCheck(this.size)&&(this.data[(a.y|0)*this.size.x+a.x|0]=b,c&&this.drawTileData(a))}getData(a){return a.arrayCheck(this.size)&&this.data[(a.y|0)*this.size.x+a.x|0]}update(){}render(){ASSERT(mainContext!=this.context);glEnable&&!glOverlay&&!this.isOverlay&&glCopyToContext(mainContext);const a=worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));(this.isOverlay?overlayContext:mainContext).drawImage(this.canvas,a.x,a.y,cameraScale*this.size.x*this.scale.x,cameraScale*this.size.y*this.scale.y)}redraw(){this.redrawStart(1);this.drawAllTileData();this.redrawEnd()}redrawStart(a=0){this.savedRenderSettings=[mainCanvas,mainContext,mainCanvasSize,cameraPos,cameraScale];mainCanvas=this.canvas;mainContext=this.context;cameraPos=this.size.scale(.5);cameraScale=this.tileSize.x;a&&(mainCanvas.width=this.size.x*this.tileSize.x,mainCanvas.height=this.size.y*this.tileSize.y);enginePreRender()}redrawEnd(){ASSERT(mainContext==this.context);glEnable&&glCopyToContext(mainContext,1);[mainCanvas,mainContext,mainCanvasSize,cameraPos,cameraScale]=this.savedRenderSettings}drawTileData(a){const b=a.floor().add(this.pos).add(vec2(.5));this.drawCanvas2D(b,vec2(1),0,0,c=>c.clearRect(-.5,-.5,1,1));a=this.getData(a);void 0!=a.tile&&(ASSERT(mainContext==this.context),drawTile(b,vec2(1),a.tile,this.tileSize,a.color,a.direction*PI/2,a.mirror))}drawAllTileData(){for(let a=this.size.x;a--;)for(let b=this.size.y;b--;)this.drawTileData(vec2(a,b))}drawCanvas2D(a,b,c=0,d,e){const f=this.context;f.save();a=a.subtract(this.pos).multiply(this.tileSize);b=b.multiply(this.tileSize);f.translate(a.x,this.canvas.height-a.y);f.rotate(c);f.scale(d?-b.x:b.x,b.y);e(f);f.restore()}drawTile(a,b=vec2(1),c=-1,d=tileSizeDefault,e=new Color,f,h){this.drawCanvas2D(a,b,f,h,k=>{if(0>c)k.fillStyle=e,k.fillRect(-.5,-.5,1,1);else{const g=tileImage.width/d.x;k.globalAlpha=e.a;k.drawImage(tileImage,c%g*d.x,(c/g|0)*d.y,d.x,d.y,-.5,-.5,1,1)}})}drawRect(a,b,c,d){this.drawTile(a,b,-1,0,c,d)}}"use strict";class ParticleEmitter extends EngineObject{constructor(a,b,c=0,d=0,e=100,f=PI,h=-1,k=tileSizeDefault,g=new Color,l=new Color,m=new Color(1,1,1,0),n=new Color(1,1,1,0),p=.5,q=.1,r=1,w=.1,u=.05,B=1,y=1,C=0,v=PI,F=.1,A=.2,x,D,t=1,z=D?1e9:0,G){super(a,new Vector2,h,k,b,void 0,z);this.emitSize=c;this.emitTime=d;this.emitRate=e;this.emitConeAngle=f;this.colorStartA=g;this.colorStartB=l;this.colorEndA=m;this.colorEndB=n;this.randomColorLinear=t;this.particleTime=p;this.sizeStart=q;this.sizeEnd=r;this.speed=w;this.angleSpeed=u;this.damping=B;this.angleDamping=y;this.gravityScale=C;this.particleConeAngle=v;this.fadeRate=F;this.randomness=A;this.collideTiles=x;this.additive=D;this.localSpace=G;this.emitTimeBuffer=this.trailScale=0}update(){this.parent&&super.update();if(!this.emitTime|this.getAliveTime()<=this.emitTime){if(this.emitRate*particleEmitRateScale){const a=1/this.emitRate/particleEmitRateScale;for(this.emitTimeBuffer+=timeDelta;0<this.emitTimeBuffer;this.emitTimeBuffer-=a)this.emitParticle()}}else this.destroy();debugParticles&&debugRect(this.pos,vec2(this.emitSize),"#0f0",0,this.angle)}emitParticle(){var a=void 0!=this.emitSize.x?new Vector2(rand(-.5,.5),rand(-.5,.5)).multiply(this.emitSize).rotate(this.angle):randInCircle(this.emitSize/2),b=rand(this.particleConeAngle,-this.particleConeAngle);this.localSpace||(a=this.pos.add(a),b+=this.angle);a=new Particle(a,this.tileIndex,this.tileSize,b);const c=this.randomness;var d=m=>m+m*rand(c,-c);b=d(this.particleTime);const e=d(this.sizeStart),f=d(this.sizeEnd),h=d(this.speed);d=d(this.angleSpeed)*randSign();var k=rand(this.emitConeAngle,-this.emitConeAngle);const g=randColor(this.colorStartA,this.colorStartB,this.randomColorLinear),l=randColor(this.colorEndA,this.colorEndB,this.randomColorLinear);k=this.localSpace?k:this.angle+k;a.colorStart=g;a.colorEndDelta=l.subtract(g);a.velocity=(new Vector2).setAngle(k,h);a.angleVelocity=d;a.lifeTime=b;a.sizeStart=e;a.sizeEndDelta=f-e;a.fadeRate=this.fadeRate;a.damping=this.damping;a.angleDamping=this.angleDamping;a.elasticity=this.elasticity;a.friction=this.friction;a.gravityScale=this.gravityScale;a.collideTiles=this.collideTiles;a.additive=this.additive;a.renderOrder=this.renderOrder;a.trailScale=this.trailScale;a.mirror=randInt(2);a.localSpaceEmitter=this.localSpace&&this;a.destroyCallback=this.particleDestroyCallback;this.particleCreateCallback&&this.particleCreateCallback(a);return a}render(){}}class Particle extends EngineObject{constructor(a,b,c,d){super(a,new Vector2,b,c,d)}render(){const a=min((time-this.spawnTime)/this.lifeTime,1);var b=this.sizeStart+a*this.sizeEndDelta;b=new Vector2(b,b);var c=this.fadeRate/2;c=new Color(this.colorStart.r+a*this.colorEndDelta.r,this.colorStart.g+a*this.colorEndDelta.g,this.colorStart.b+a*this.colorEndDelta.b,(this.colorStart.a+a*this.colorEndDelta.a)*(a<c?a/c:a>1-c?(1-a)/c:1));this.additive&&setBlendMode(1);let d=this.pos;var e=this.angle;this.localSpaceEmitter&&(d=this.localSpaceEmitter.pos.add(d.rotate(-this.localSpaceEmitter.angle)),e+=this.localSpaceEmitter.angle);if(this.trailScale){var f=this.velocity;this.localSpaceEmitter&&(f=f.rotate(-this.localSpaceEmitter.angle));e=f.length();f=f.scale(1/e);const h=e*this.trailScale;b.y=max(b.x,h);e=f.angle();drawTile(d.add(f.multiply(vec2(0,-h/2))),b,this.tileIndex,this.tileSize,c,e,this.mirror)}else drawTile(d,b,this.tileIndex,this.tileSize,c,e,this.mirror);this.additive&&setBlendMode();debugParticles&&debugRect(d,b,"#f005",0,e);1==a&&(this.color=c,this.size=b,this.destroyCallback&&this.destroyCallback(this),this.destroyed=1)}}"use strict";const medals=[];let medalsDisplayQueue=[],medalsSaveName,medalsDisplayTimeLast;function medalsInit(a){medalsSaveName=a;debugMedals||medals.forEach(b=>b.unlocked=localStorage[b.storageKey()]|0)}class Medal{constructor(a,b,c="",d="🏆",e){ASSERT(0<=a&&!medals[a]);medals[this.id=a]=this;this.name=b;this.description=c;this.icon=d;e&&((this.image=new Image).src=e)}unlock(){medalsPreventUnlock||this.unlocked||(ASSERT(medalsSaveName),localStorage[this.storageKey()]=this.unlocked=1,medalsDisplayQueue.push(this),newgrounds&&newgrounds.unlockMedal(this.id))}render(a=0){const b=overlayContext;var c=min(medalDisplaySize.x,mainCanvas.width);const d=overlayCanvas.width-c;a*=-medalDisplaySize.y;b.save();b.beginPath();b.fillStyle=new Color(.9,.9,.9);b.strokeStyle=new Color(0,0,0);b.lineWidth=3;b.fill(b.rect(d,a,c,medalDisplaySize.y));b.stroke();b.clip();this.renderIcon(vec2(d+15+medalDisplayIconSize/2,a+medalDisplaySize.y/2));c=vec2(d+medalDisplayIconSize+30,a+28);drawTextScreen(this.name,c,38,new Color(0,0,0),0,0,"left");c.y+=32;drawTextScreen(this.description,c,24,new Color(0,0,0),0,0,"left");b.restore()}renderIcon(a,b=medalDisplayIconSize){this.image?overlayContext.drawImage(this.image,a.x-b/2,a.y-b/2,b,b):drawTextScreen(this.icon,a,.7*b,new Color(0,0,0))}storageKey(){return medalsSaveName+"_"+this.id}}function medalsRender(){if(medalsDisplayQueue.length){var a=medalsDisplayQueue[0],b=timeReal-medalsDisplayTimeLast;if(medalsDisplayTimeLast)if(b>medalDisplayTime)medalsDisplayQueue.shift(medalsDisplayTimeLast=0);else{const c=medalDisplayTime-medalDisplaySlideTime;a.render(b<medalDisplaySlideTime?1-b/medalDisplaySlideTime:b>c?(b-c)/medalDisplaySlideTime:0)}else medalsDisplayTimeLast=timeReal}}let newgrounds;function newgroundsInit(a,b){newgrounds=new Newgrounds(a,b)}class Newgrounds{constructor(a,b){ASSERT(!newgrounds&&a);this.app_id=a;this.cipher=b;this.host=location?location.hostname:"";b&&(this.cryptoJS=this.CryptoJS());if(this.session_id=new URL(location.href).searchParams.get("ngio_session_id")){this.medals=(a=this.call("Medal.getList"))?a.result.data.medals:[];debugMedals&&console.log(this.medals);for(var c of this.medals)if(a=medals[c.id])a.image=new Image,a.image.src=c.icon,a.name=c.name,a.description=c.description,a.unlocked=c.unlocked,a.difficulty=c.difficulty,a.value=c.value,a.value&&(a.description=a.description+" ("+a.value+")");this.scoreboards=(c=this.call("ScoreBoard.getBoards"))?c.result.data.scoreboards:[];debugMedals&&console.log(this.scoreboards);setInterval(()=>this.call("Gateway.ping",0,1),3e5)}}unlockMedal(a){return this.call("Medal.unlock",{id:a},1)}postScore(a,b){return this.call("ScoreBoard.postScore",{id:a,value:b},1)}getScores(a,b=0,c=0,d=0,e=10){return this.call("ScoreBoard.getScores",{id:a,user:b,social:c,skip:d,limit:e})}logView(){return this.call("App.logView",{host:this.host},1)}call(a,b=0,c=0){a={component:a,parameters:b};if(this.cipher){b=this.cryptoJS;var d=b.enc.Base64.parse(this.cipher);const e=b.lib.WordArray.random(16);d=b.AES.encrypt(JSON.stringify(a),d,{iv:e});a.secure=b.enc.Base64.stringify(e.concat(d.ciphertext));a.parameters=0}b={app_id:this.app_id,session_id:this.session_id,call:a};a=new FormData;a.append("input",JSON.stringify(b));b=new XMLHttpRequest;b.open("POST","https://newgrounds.io/gateway_v3.php",!debugMedals&&c);b.send(a);debugMedals&&console.log(b.responseText);return b.responseText&&JSON.parse(b.responseText)}CryptoJS(){return eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",..."]charCodeAtUinyxpf","for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h<t,o=o%h+(i?t:h-t)*(o>>17)-!i*t,f.map((n,x)=>(U=r[n]+=(i*h/2-r[n]<<13)/((C[n]+=C[n]<5)+1/20)>>13,a[x]+=y[x]*(i-t/h))),p=p*2+i)for(f='010202103203210431053105410642065206541'.split(t=0).map((n,x)=>(U=0,[...n].map((n,x)=>(U=U*997+(c[e-n]|0)|0)),h*32-1&U*997+p+!!A*129)*12+x);o<h*32;o=o*64|M.charCodeAt(d++)&63);for(C=String.fromCharCode(...c);r=/[\0-#?@\\\\~]/.exec(C);)with(C.split(r))C=join(shift());return C")([],[],131072,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(32768),new Uint8Array(51e6),0,0,0,0))}}"use strict";let glCanvas,glContext,glTileTexture,glActiveTexture,glShader,glArrayBuffer,glPositionData,glColorData,glBatchCount,glBatchAdditive,glAdditive;function glInit(){glCanvas=document.createElement("canvas");glContext=glCanvas.getContext("webgl",{antialias:!1});glTileTexture=glCreateTexture(tileImage);glOverlay&&document.body.appendChild(glCanvas);glShader=glCreateProgram("precision highp float;uniform mat4 m;attribute vec2 p,t;attribute vec4 c,a;varying vec4 v,d,e;void main(){gl_Position=m*vec4(p,1,1);v=vec4(t,p);d=c;e=a;}","precision highp float;varying vec4 v,d,e;uniform sampler2D s;void main(){gl_FragColor=texture2D(s,v.xy)*d+e;}");const a=new ArrayBuffer(gl_VERTEX_BUFFER_SIZE);glArrayBuffer=glContext.createBuffer();glPositionData=new Float32Array(a);glColorData=new Uint32Array(a);glBatchCount=0}function glSetBlendMode(a){glAdditive=a}function glSetTexture(a=glTileTexture){a!=glActiveTexture&&glFlush();glContext.bindTexture(gl_TEXTURE_2D,glActiveTexture=a)}function glCompileShader(a,b){b=glContext.createShader(b);glContext.shaderSource(b,a);glContext.compileShader(b);if(debug&&!glContext.getShaderParameter(b,gl_COMPILE_STATUS))throw glContext.getShaderInfoLog(b);return b}function glCreateProgram(a,b){const c=glContext.createProgram();glContext.attachShader(c,glCompileShader(a,gl_VERTEX_SHADER));glContext.attachShader(c,glCompileShader(b,gl_FRAGMENT_SHADER));glContext.linkProgram(c);if(debug&&!glContext.getProgramParameter(c,gl_LINK_STATUS))throw glContext.getProgramInfoLog(c);return c}function glCreateTexture(a){const b=glContext.createTexture();glContext.bindTexture(gl_TEXTURE_2D,b);a&&a.width&&glContext.texImage2D(gl_TEXTURE_2D,0,gl_RGBA,gl_RGBA,gl_UNSIGNED_BYTE,a);a=cavasPixelated?gl_NEAREST:gl_LINEAR;glContext.texParameteri(gl_TEXTURE_2D,gl_TEXTURE_MIN_FILTER,a);glContext.texParameteri(gl_TEXTURE_2D,gl_TEXTURE_MAG_FILTER,a);glContext.texParameteri(gl_TEXTURE_2D,gl_TEXTURE_WRAP_S,gl_CLAMP_TO_EDGE);glContext.texParameteri(gl_TEXTURE_2D,gl_TEXTURE_WRAP_T,gl_CLAMP_TO_EDGE);return b}function glPreRender(){glContext.viewport(0,0,glCanvas.width=mainCanvas.width,glCanvas.height=mainCanvas.height);glContext.clear(gl_COLOR_BUFFER_BIT);glContext.useProgram(glShader);glContext.activeTexture(gl_TEXTURE0);glContext.bindTexture(gl_TEXTURE_2D,glActiveTexture=glTileTexture);glContext.bindBuffer(gl_ARRAY_BUFFER,glArrayBuffer);glContext.bufferData(gl_ARRAY_BUFFER,gl_VERTEX_BUFFER_SIZE,gl_DYNAMIC_DRAW);glSetBlendMode();let a=0;var b=(d,e,f,h,k=0)=>{d=glContext.getAttribLocation(glShader,d);glContext.enableVertexAttribArray(d);glContext.vertexAttribPointer(d,h,e,k,gl_VERTEX_BYTE_STRIDE,a);a+=h*f};b("p",gl_FLOAT,4,2);b("t",gl_FLOAT,4,2);b("c",gl_UNSIGNED_BYTE,1,4,1);b("a",gl_UNSIGNED_BYTE,1,4,1);b=2*cameraScale/mainCanvas.width;const c=2*cameraScale/mainCanvas.height;glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader,"m"),0,new Float32Array([b,0,0,0,0,c,0,0,1,1,-1,1,-1-b*cameraPos.x,-1-c*cameraPos.y,0,0]))}function glFlush(){if(glBatchCount){var a=glBatchAdditive?gl_ONE:gl_ONE_MINUS_SRC_ALPHA;glContext.blendFuncSeparate(gl_SRC_ALPHA,a,gl_ONE,a);glContext.enable(gl_BLEND);glContext.bufferSubData(gl_ARRAY_BUFFER,0,glPositionData.subarray(0,glBatchCount*gl_VERTICES_PER_QUAD*gl_INDICIES_PER_VERT));glContext.drawArrays(gl_TRIANGLES,0,glBatchCount*gl_VERTICES_PER_QUAD);glBatchCount=0;glBatchAdditive=glAdditive}}function glCopyToContext(a,b){if(glBatchCount||b)glFlush(),glOverlay&&!b||a.drawImage(glCanvas,0,0)}function glDraw(a,b,c,d,e,f,h,k,g,l,m=0){glBatchCount!=gl_MAX_BATCH&&glBatchAdditive==glAdditive||glFlush();var n=Math.cos(e)/2,p=Math.sin(e)/2;e=n*c;n*=d;c*=p;d*=p;for(let q=6,r=glBatchCount++*gl_VERTICES_PER_QUAD*gl_INDICIES_PER_VERT;q--;){p=q-4&&1<q;const w=q-5&&q-2&&q-1;glPositionData[r++]=a+(p?-e:e)+(w?d:-d);glPositionData[r++]=b+(w?n:-n)+(p?c:-c);glPositionData[r++]=p?f:k;glPositionData[r++]=w?h:g;glColorData[r++]=l;glColorData[r++]=m}}let glPostShader,glPostArrayBuffer,glPostTexture,glPostIncludeOverlay;function glInitPostProcess(a,b){ASSERT(!glPostShader);a||="void mainImage(out vec4 c,vec2 p){c=texture2D(iChannel0,p/iResolution.xy);}";glPostShader=glCreateProgram("precision highp float;attribute vec2 p;void main(){gl_Position=vec4(p,1,1);}","precision highp float;uniform sampler2D iChannel0;uniform vec3 iResolution;uniform float iTime;\n"+a+"\nvoid main(){mainImage(gl_FragColor,gl_FragCoord.xy);gl_FragColor.a=1.;}");glPostArrayBuffer=glContext.createBuffer();glPostTexture=glCreateTexture();glPostIncludeOverlay=b;mainCanvas.style.visibility="hidden"}function glRenderPostProcess(){if(glPostShader){glEnable?(glFlush(),mainContext.drawImage(glCanvas,0,0)):glContext.viewport(0,0,glCanvas.width=mainCanvas.width,glCanvas.height=mainCanvas.height);glPostIncludeOverlay&&(mainContext.drawImage(overlayCanvas,0,0),overlayCanvas.width=mainCanvas.width);glContext.useProgram(glPostShader);glContext.disable(gl_BLEND);glContext.bindBuffer(gl_ARRAY_BUFFER,glPostArrayBuffer);glContext.bufferData(gl_ARRAY_BUFFER,new Float32Array([-3,1,1,-3,1,1]),gl_STATIC_DRAW);glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL,!0);glContext.activeTexture(gl_TEXTURE0);glContext.bindTexture(gl_TEXTURE_2D,glPostTexture);glContext.texImage2D(gl_TEXTURE_2D,0,gl_RGBA,gl_RGBA,gl_UNSIGNED_BYTE,mainCanvas);var a=glContext.getAttribLocation(glPostShader,"p");glContext.enableVertexAttribArray(a);glContext.vertexAttribPointer(a,2,gl_FLOAT,0,8,0);glContext.uniform1i(glContext.getUniformLocation(glPostShader,"iChannel0"),0);glContext.uniform1f(glContext.getUniformLocation(glPostShader,"iTime"),time);glContext.uniform3f(glContext.getUniformLocation(glPostShader,"iResolution"),mainCanvas.width,mainCanvas.height,1);glContext.drawArrays(gl_TRIANGLES,0,3)}}const gl_ONE=1,gl_TRIANGLES=4,gl_SRC_ALPHA=770,gl_ONE_MINUS_SRC_ALPHA=771,gl_BLEND=3042,gl_TEXTURE_2D=3553,gl_UNSIGNED_BYTE=5121,gl_BYTE=5120,gl_FLOAT=5126,gl_RGBA=6408,gl_NEAREST=9728,gl_LINEAR=9729,gl_TEXTURE_MAG_FILTER=10240,gl_TEXTURE_MIN_FILTER=10241,gl_TEXTURE_WRAP_S=10242,gl_TEXTURE_WRAP_T=10243,gl_COLOR_BUFFER_BIT=16384,gl_CLAMP_TO_EDGE=33071,gl_TEXTURE0=33984,gl_TEXTURE1=33985,gl_ARRAY_BUFFER=34962,gl_STATIC_DRAW=35044,gl_DYNAMIC_DRAW=35048,gl_FRAGMENT_SHADER=35632,gl_VERTEX_SHADER=35633,gl_COMPILE_STATUS=35713,gl_LINK_STATUS=35714,gl_UNPACK_FLIP_Y_WEBGL=37440,gl_VERTICES_PER_QUAD=6,gl_INDICIES_PER_VERT=6,gl_MAX_BATCH=65536,gl_VERTEX_BYTE_STRIDE=24,gl_VERTEX_BUFFER_SIZE=gl_MAX_BATCH*gl_VERTICES_PER_QUAD*gl_VERTEX_BYTE_STRIDE;