littlejsengine 1.4.6 → 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # ![LittleJS Logo](favicon.png) LittleJS - The Tiny JavaScript Game Engine That Can
2
+
3
+ ## All aboard!
4
+
5
+ LittleJS is a super lightweight JavaScript game engine with a fast hybrid rendering system. It provides a comprehensive set of tools including rendering, physics, particles, sound effects, music, input handling, and debug tools. The code is very clean and well documented with a variety of examples to get you started. Choo-Choo! 🚂
6
+ <div align="center">
7
+
8
+ ## [Demo](https://killedbyapixel.github.io/LittleJS) | [Docs](https://killedbyapixel.github.io/LittleJS/docs) | [Trailer](https://youtu.be/chuBzGjv7Ms) | [Discord](https://discord.gg/zb7hcGkyZe)
9
+ </div>
10
+
11
+ ![LittleJS Screenshot](examples/screenshot.jpg)
12
+
13
+ ## Examples
14
+
15
+ ### [Hello World](https://killedbyapixel.github.io/LittleJS/) - Clean project with only a few things to get you started
16
+ ### [Puzzle Game](https://killedbyapixel.github.io/LittleJS/examples/puzzle) - Match 3 puzzle game with HD rendering and high score tracking
17
+ ### [Platformer](https://killedbyapixel.github.io/LittleJS/examples/platformer) - Platformer/shooter with procedural generation and destruction
18
+ ### [Breakout](https://killedbyapixel.github.io/LittleJS/examples/breakout) - Breakout game with post processing effect
19
+ ### [Stress Test](https://killedbyapixel.github.io/LittleJS/examples/stress) - Max sprite/object test and music system demo
20
+ ### [Particle System Designer](https://killedbyapixel.github.io/LittleJS/examples/particles) - Particle system editor and visualizer
21
+
22
+ ## Features
23
+
24
+ - Very small footprint with no dependencies
25
+ - Can update and render 10,000+ objects at 60fps, often many times more
26
+ - Object oriented system with fast 2D physics and collision handling for axis aligned boxes
27
+ - Positional audio effects with [ZzFX](https://killedbyapixel.github.io/ZzFX/) and music with [ZzFXM](https://keithclark.github.io/ZzFXM/), mp3s, or wavs
28
+ - Input processing system with keyboard, mouse, gamepad, and touch support
29
+ - Particle effects system (particle editor/designer in progress)
30
+ - Medal system tracks and displays achievements with Newgrounds and OS13k integration
31
+ - Several easy to understand example projects you can build on
32
+ - Apply [Shadertoy](https://www.shadertoy.com) compatible shaders for post processinge effects
33
+ - Debug tools and debug rendering system
34
+ - [Full documentation](https://killedbyapixel.github.io/LittleJS/docs) automatically generated from the source code block tags with [JSDoc](https://github.com/jsdoc/jsdoc)
35
+ - Build system automatically combines everything, minifies, and removes unused code
36
+ - For size coding competitions like [js13kGames](https://js13kgames.com/), starter project builds to a 7KB zip file
37
+ - Easily build a Windows executable with [Electron](https://www.electronjs.org/) for distribution on platforms like Steam
38
+ - Open Source with the [MIT license](https://github.com/KilledByAPixel/LittleJS/blob/main/LICENSE) so it can be used for anything you want
39
+ - Can be imported as a JavaScript module and includes [an example module project](https://killedbyapixel.github.io/LittleJS/examples/module)
40
+
41
+ ## Builds
42
+
43
+ To easily include LittleJS in your game, you can use one of the 3 pre-built js files. These are also built automatically by the build scripts.
44
+
45
+ - [engine.all.js](https://github.com/KilledByAPixel/LittleJS/blob/main/engine/engine.all.js) - The full game engine with debug mode available
46
+ - [engine.all.release.js](https://github.com/KilledByAPixel/LittleJS/blob/main/engine/engine.all.release.js) - The engine optimized for release builds
47
+ - [engine.all.min.js](https://github.com/KilledByAPixel/LittleJS/blob/main/engine/engine.all.min.js) - The engine in release mode and minified
48
+
49
+ The hello world example includes a batch file [build.bat](https://github.com/KilledByAPixel/LittleJS/blob/main/build.bat) that compresses everything into a tiny zip file using Google Closure, UglifyJS, Roadroller, and ECT. You must run [buildSetup.bat](https://github.com/KilledByAPixel/LittleJS/blob/main/buildSetup.bat) to install the necessary npm dependencies.
50
+
51
+ ## Debugging
52
+
53
+ Debug builds of LittleJS have a special menu that can be opened by pressing ~, the tilde key.
54
+
55
+ - ~: Debug Overlay
56
+ - 1: Debug Physics
57
+ - 2: Debug Particles
58
+ - 3: Debug Gamepads
59
+ - 4: God Mode
60
+ - 5: Save Screenshot
61
+
62
+ ## Games Made With LittleJS
63
+
64
+ - [Space Huggers](https://www.newgrounds.com/portal/view/819609) - Run and gun platformer with procedural levels
65
+ - [Undergrowth](https://undergrowth.squidband.uk/) - An interactive music videogame for the band Squid
66
+ - [Space Huggers - JS13K Edition](https://github.com/KilledByAPixel/SpaceHuggers) - Original js13k game with source code
67
+ - [Isletopia](https://store.steampowered.com/app/1861260/Isletopia) - Relaxing strategy game of greenifying barren islands
68
+ - [Dead Again](https://js13kgames.com/entries/dead-again) - Top down survial horror by [sanojian & repsej](https://github.com/sanojian/js13k_2022)
69
+ - [Hel's Trial](https://js13kgames.com/entries/hels-trial) - Turn based RPG by [Sebastian Dorn](https://github.com/sebadorn/js13k-2022-death)
70
+ - [Bit Butcher](https://js13kgames.com/entries/bit-butcher) - Survival crafting game by [Deathray Games](https://github.com/deathraygames/bit-butcher)
71
+ - [Necrotic Commander](https://js13kgames.com/entries/necrotic-commander) - Tower defense game by [Daniel Jeffery](https://github.com/d-jeffery/NecroticCommander)
72
+ - [Boxing up Bamboo](https://patrickgh3.itch.io/boxing-up-bamboo) - A challenging puzzle game by [Patrick Traynor](https://cwpat.me/about)
73
+ - Send me your games!
74
+
75
+ ![LittleJS Logo](favicon.png)
@@ -1110,7 +1110,7 @@ let touchGamepadAnalog = 1;
1110
1110
  /** Size of virutal gamepad for touch devices in pixels
1111
1111
  * @default
1112
1112
  * @memberof Settings */
1113
- let touchGamepadSize = 80;
1113
+ let touchGamepadSize = 99;
1114
1114
 
1115
1115
  /** Transparency of touch gamepad overlay
1116
1116
  * @default
@@ -2190,6 +2190,10 @@ const isTouchDevice = window.ontouchstart !== undefined;
2190
2190
  // try to enable touch mouse
2191
2191
  if (isTouchDevice)
2192
2192
  {
2193
+ // override mouse events
2194
+ const mouseDown = onmousedown, mouseUp = onmouseup, mouseMove = onmousemove;
2195
+ onmousedown = onmouseup = onmousemove = (e)=> 0;
2196
+
2193
2197
  // handle all touch events the same way
2194
2198
  let wasTouching, hadTouch;
2195
2199
  ontouchstart = ontouchmove = ontouchend = (e)=>
@@ -2206,10 +2210,10 @@ if (isTouchDevice)
2206
2210
  // set event pos and pass it along
2207
2211
  e.x = e.touches[0].clientX;
2208
2212
  e.y = e.touches[0].clientY;
2209
- wasTouching ? onmousemove(e) : onmousedown(e);
2213
+ wasTouching ? mouseMove(e) : mouseDown(e);
2210
2214
  }
2211
2215
  else if (wasTouching)
2212
- onmouseup(e);
2216
+ mouseUp(e);
2213
2217
 
2214
2218
  // set was touching
2215
2219
  wasTouching = touching;
@@ -3812,7 +3816,6 @@ function glInit()
3812
3816
  // create the canvas and tile texture
3813
3817
  glCanvas = document.createElement('canvas');
3814
3818
  glContext = glCanvas.getContext('webgl', {antialias: false});
3815
- glCanvas.style = styleCanvas;
3816
3819
  glTileTexture = glCreateTexture(tileImage);
3817
3820
 
3818
3821
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
@@ -4209,7 +4212,7 @@ gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
4209
4212
  const engineName = 'LittleJS';
4210
4213
 
4211
4214
  /** Version of engine */
4212
- const engineVersion = '1.4.6';
4215
+ const engineVersion = '1.4.7';
4213
4216
 
4214
4217
  /** Frames per second to update objects
4215
4218
  * @default */
@@ -4238,15 +4241,11 @@ let timeReal = 0;
4238
4241
  let paused = 0;
4239
4242
 
4240
4243
  // Engine internal variables not exposed to documentation
4241
- let frameTimeLastMS = 0, frameTimeBufferMS = 0, tileImageSize, tileImageFixBleed;
4244
+ let tileImageSize, tileImageFixBleed;
4242
4245
 
4243
4246
  // Engine stat tracking, if showWatermark is true
4244
4247
  let averageFPS, drawCount;
4245
4248
 
4246
- // css text used for elements created by engine
4247
- const styleBody = 'margin:0;overflow:hidden;background:#000';
4248
- const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
4249
-
4250
4249
  ///////////////////////////////////////////////////////////////////////////////
4251
4250
 
4252
4251
  /** Start up LittleJS engine with your callback functions
@@ -4266,11 +4265,16 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4266
4265
  tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
4267
4266
  debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
4268
4267
 
4268
+ // setup css
4269
+ const styleBody = 'margin:0;overflow:hidden;background:#000' + // fill the window
4270
+ ';touch-action:none' + // prevent mobile pinch to resize
4271
+ ';user-select:none' + // prevent mobile hold to select
4272
+ ';-webkit-user-select:none;-moz-user-select:none'; // compatibility for mobile
4273
+
4269
4274
  // setup html
4270
4275
  document.body.style = styleBody;
4271
4276
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
4272
4277
  mainContext = mainCanvas.getContext('2d');
4273
- mainCanvas.style = styleCanvas;
4274
4278
 
4275
4279
  // init stuff and start engine
4276
4280
  debugInit();
@@ -4279,13 +4283,19 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4279
4283
  // create overlay canvas for hud to appear above gl canvas
4280
4284
  document.body.appendChild(overlayCanvas = document.createElement('canvas'));
4281
4285
  overlayContext = overlayCanvas.getContext('2d');
4282
- overlayCanvas.style = styleCanvas;
4286
+
4287
+ // set canvas style to fill the window
4288
+ const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
4289
+ (glCanvas||mainCanvas).style = overlayCanvas.style = mainCanvas.style = styleCanvas;
4283
4290
 
4284
4291
  gameInit();
4285
4292
  touchGamepadCreate();
4286
4293
  engineUpdate();
4287
4294
  };
4288
4295
 
4296
+ // frame time tracking
4297
+ let frameTimeLastMS = 0, frameTimeBufferMS = 0;
4298
+
4289
4299
  // main update loop
4290
4300
  const engineUpdate = (frameTimeMS=0)=>
4291
4301
  {
@@ -1 +1 @@
1
- let showWatermark=0,godMode=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*(rand(2)|0)-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 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);class Vector2{constructor(a=0,b=0){this.x=a;this.y=b}copy(){return new Vector2(this.x,this.y)}add(a){ASSERT(void 0!=a.x);return new Vector2(this.x+a.x,this.y+a.y)}subtract(a){ASSERT(void 0!=a.x);return new Vector2(this.x-a.x,this.y-a.y)}multiply(a){ASSERT(void 0!=a.x);return new Vector2(this.x*a.x,this.y*a.y)}divide(a){ASSERT(void 0!=a.x);return new Vector2(this.x/a.x,this.y/a.y)}scale(a){ASSERT(void 0==a.x);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(void 0!=a.x);return this.x*a.x+this.y*a.y}cross(a){ASSERT(void 0!=a.x);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(void 0!=a.x);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){return`(${(0>this.x?"":" ")+this.x.toFixed(a)},${(0>this.y?"":" ")+this.y.toFixed(a)} )`}}const colorRGBA=(a,b,c,d)=>new Color(a,b,c,d),colorHSLA=(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,k,g)=>(g=(g%1+1)%1)<1/6?f+6*(k-f)*g:.5>g?k:g<2/3?f+(k-f)*(2/3-g)*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),k=(e+f)/2;let g=0,h=0;if(e!=f){let n=e-f;h=.5<k?n/(2-e-f):n/(e+f);a==e?g=(b-c)/n+(b<c?6:0):b==e?g=(c-a)/n+2:c==e&&(g=(a-b)/n+4)}return[g/6,h,k,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 canvasMaxSize=vec2(1920,1200),canvasFixedSize=vec2(),cavasPixelated=1,fontDefault="arial",tileSizeDefault=vec2(16),tileFixBleedScale=.3,objectDefaultSize=vec2(1),enablePhysicsSolver=1,objectDefaultMass=1,objectDefaultDamping=.99,objectDefaultAngleDamping=.99,objectDefaultElasticity=0,objectDefaultFriction=.8,objectMaxSpeed=1,gravity=0,particleEmitRateScale=1,cameraPos=vec2(),cameraScale=max(tileSizeDefault.x,tileSizeDefault.y),glEnable=1,glOverlay=1,gamepadsEnable=1,gamepadDirectionEmulateStick=1,inputWASDEmulateDirection=1,touchGamepadEnable=0,touchGamepadAnalog=1,touchGamepadSize=80,touchGamepadAlpha=.3,vibrateEnable=1,soundVolume=.5,soundEnable=1,soundDefaultRange=30,soundDefaultTaper=.7,medalDisplayTime=5,medalDisplaySlideTime=.5,medalDisplayWidth=640,medalDisplayHeight=80,medalDisplayIconSize=50;"use strict";const engineName="LittleJS",engineVersion="1.4.6",frameRate=60,timeDelta=1/frameRate;let engineObjects=[],engineObjectsCollide=[],frame=0,time=0,timeReal=0,paused=0,frameTimeLastMS=0,frameTimeBufferMS=0,tileImageSize,tileImageFixBleed,averageFPS,drawCount;const styleBody="margin:0;overflow:hidden;background:#000",styleCanvas="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)";function engineInit(a,b,c,d,e,f){tileImage.onerror=tileImage.onload=()=>{tileImageFixBleed=vec2(tileFixBleedScale).divide(tileImageSize=vec2(tileImage.width,tileImage.height));debug&&(tileImage.onload=()=>ASSERT(1));document.body.style=styleBody;document.body.appendChild(mainCanvas=document.createElement("canvas"));mainContext=mainCanvas.getContext("2d");mainCanvas.style=styleCanvas;debugInit();glEnable&&glInit();document.body.appendChild(overlayCanvas=document.createElement("canvas"));overlayContext=overlayCanvas.getContext("2d");overlayCanvas.style=styleCanvas;a();touchGamepadCreate();k()};const k=(g=0)=>{var h=g-frameTimeLastMS;frameTimeLastMS=g;if(debug||showWatermark)averageFPS=lerp(.05,averageFPS||0,1e3/(h||1));g=debug&&keyIsDown(107);const n=debug&&keyIsDown(109);debug&&(h*=g?5:n?.2:1);timeReal+=h/1e3;frameTimeBufferMS+=!paused*h;g||(frameTimeBufferMS=min(frameTimeBufferMS,50));canvasFixedSize.x?(overlayCanvas.width=mainCanvas.width=canvasFixedSize.x,overlayCanvas.height=mainCanvas.height=canvasFixedSize.y,h=innerWidth/innerHeight,g=mainCanvas.width/mainCanvas.height,mainCanvas.style.width=overlayCanvas.style.width=h<g?"100%":"",mainCanvas.style.height=overlayCanvas.style.height=h<g?"":"100%",glCanvas&&(glCanvas.style.width=mainCanvas.style.width,glCanvas.style.height=mainCanvas.style.height)):(overlayCanvas.width=mainCanvas.width=min(innerWidth,canvasMaxSize.x),overlayCanvas.height=mainCanvas.height=min(innerHeight,canvasMaxSize.y));mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height);if(paused)inputUpdate(),debugUpdate(),c(),inputUpdatePost();else{h=0;0>frameTimeBufferMS&&-9<frameTimeBufferMS&&(h=frameTimeBufferMS,frameTimeBufferMS=0);for(;0<=frameTimeBufferMS;frameTimeBufferMS-=1e3/frameRate)inputUpdate(),b(),engineObjectsUpdate(),debugUpdate(),c(),inputUpdatePost();frameTimeBufferMS+=h}enginePreRender();d();engineObjects.sort((p,l)=>p.renderOrder-l.renderOrder);for(var m of engineObjects)m.destroyed||m.render();e();glRenderPostProcess();medalsRender();touchGamepadRender();debugRender();glEnable&&glCopyToContext(mainContext);showWatermark&&(overlayContext.textAlign="right",overlayContext.textBaseline="top",overlayContext.font="1em monospace",overlayContext.fillStyle="#000",m=engineName+" v"+engineVersion+" / "+drawCount+" / "+engineObjects.length+" / "+averageFPS.toFixed(1)+" "+(glEnable?"GL":"2D"),overlayContext.fillText(m,mainCanvas.width-3,3),overlayContext.fillStyle="#fff",overlayContext.fillText(m,mainCanvas.width-2,2),drawCount=0);requestAnimationFrame(k)};f?tileImage.src=f:tileImage.onload()}function enginePreRender(){mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height);mainContext.imageSmoothingEnabled=!cavasPixelated;glEnable&&glPreRender(mainCanvas.width,mainCanvas.height,cameraPos.x,cameraPos.y,cameraScale)}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=objectDefaultSize,c=-1,d=tileSizeDefault,e=0,f,k=0){ASSERT(a&&void 0!=a.x&&void 0!=b.x);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=k;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.pos.x+=this.velocity.x*=this.damping,this.pos.y+=this.velocity.y=this.damping*this.velocity.y+gravity*this.gravityScale,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||!isOverlapping(this.pos,this.size,d.pos,d.size)||!this.collideWithObject(d)|!d.collideWithObject(this)))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")}else{c=this.size.add(d.size);e=2*(a.y-d.pos.y)>c.y+gravity;var f=2*abs(a.y-d.pos.y)<c.y,k=2*abs(a.x-d.pos.x)<c.x;if(e||k||!f)if(this.pos.y=d.pos.y+(c.y/2+.001)*sign(a.y-d.pos.y),d.groundObject&&b||!d.mass)b&&(this.groundObject=d),this.velocity.y*=-this.elasticity;else if(d.mass){const g=(this.mass*this.velocity.y+d.mass*d.velocity.y)/(this.mass+d.mass),h=this.velocity.y*(this.mass-d.mass)/(this.mass+d.mass)+2*d.velocity.y*d.mass/(this.mass+d.mass),n=d.velocity.y*(d.mass-this.mass)/(this.mass+d.mass)+2*this.velocity.y*this.mass/(this.mass+d.mass),m=max(this.elasticity,d.elasticity);this.velocity.y=lerp(m,g,h);d.velocity.y=lerp(m,g,n)}e||!f&&k||(this.pos.x=d.pos.x+(c.x/2+.001)*sign(a.x-d.pos.x),d.mass?(c=(this.mass*this.velocity.x+d.mass*d.velocity.x)/(this.mass+d.mass),e=this.velocity.x*(this.mass-d.mass)/(this.mass+d.mass)+2*d.velocity.x*d.mass/(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),k=max(this.elasticity,d.elasticity),this.velocity.x=lerp(k,c,e),d.velocity.x=lerp(k,c,f)):this.velocity.x*=-this.elasticity);debugOverlay&&debugPhysics&&debugAABB(this.pos,this.size,d.pos,d.size,"#f0f")}if(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);if(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";const tileImage=new Image;let mainCanvas,mainContext,overlayCanvas,overlayContext,mainCanvasSize=vec2();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,k,g=new Color(0,0,0,0),h=glEnable){showWatermark&&++drawCount;if(glEnable&&h)if(0>c||!tileImage.width)glDraw(a.x,a.y,b.x,b.y,f,0,0,0,0,0,e.rgbaInt());else{var n=tileImageSize.x/d.x|0;h=d.x/tileImageSize.x;const m=d.y/tileImageSize.y,p=c%n*h;n=(c/n|0)*m;glDraw(a.x,a.y,k?-b.x:b.x,b.y,f,p+tileImageFixBleed.x,n+tileImageFixBleed.y,p-tileImageFixBleed.x+h,n-tileImageFixBleed.y+m,e.rgbaInt(),g.rgbaInt())}else drawCanvas2D(a,b,f,k,m=>{if(0>c)m.fillStyle=e,m.fillRect(-.5,-.5,1,1);else{var p=tileImageSize.x/d.x|0;const l=c%p*d.x+tileFixBleedScale;p=(c/p|0)*d.y+tileFixBleedScale;const u=d.x-2*tileFixBleedScale,v=d.y-2*tileFixBleedScale;m.globalAlpha=e.a;m.drawImage(tileImage,l,p,u,v,-.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,k,g,h){drawTile(screenToWorld(a),b.scale(1/cameraScale),c,d,e,f,k,g,h)}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 drawTextScreen(a,b,c=1,d=new Color,e=0,f=new Color(0,0,0),k="center",g=fontDefault,h=overlayContext){h.fillStyle=d;h.lineWidth=e;h.strokeStyle=f;h.textAlign=k;h.font=c+"px "+g;h.textBaseline="middle";h.lineJoin="round";b=b.copy();(a+"").split("\n").forEach(n=>{e&&h.strokeText(n,b.x,b.y);h.fillText(n,b.x,b.y);b.y+=c})}function drawText(a,b,c=1,d,e,f,k,g){drawTextScreen(a,worldToScreen(b),c*cameraScale,d,e*cameraScale,f,k,g,mainContext)}let engineFontImage;class FontImage{constructor(a,b=vec2(8),c=vec2(0,1),d=0,e=overlayContext){a||engineFontImage||(engineFontImage=new Image,engineFontImage.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,k=f.add(this.paddingSize).scale(c),g=this.image.width/this.tileSize.x|0;(a+"").split("\n").forEach((h,n)=>{const m=d?h.length*f.x*c/2|0:0;for(let u=h.length;u--;){var p=h[u].charCodeAt();if(32>p||127<p)p=127;var l=this.startTileIndex+p-32;p=l%g;l=l/g|0;const v=b.add(vec2(u,n).multiply(k));e.drawImage(this.image,p*f.x,l*f.y,f.x,f.y,v.x-m,v.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.mozCancelFullScreen&&document.mozCancelFullScreen():document.body.webkitRequestFullScreen?document.body.webkitRequestFullScreen():document.body.mozRequestFullScreen&&document.body.mozRequestFullScreen()}"use strict";const keyIsDown=(a,b=0)=>inputData[b]&&inputData[b][a]&1?1:0,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][remapKeyCode(a.keyCode)]=3),preventDefaultInput&&a.preventDefault())};onkeyup=a=>{debug&&a.target!=document.body||(inputData[0][remapKeyCode(a.keyCode)]=4)};const remapKeyCode=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 k=inputData[b+1]||(inputData[b+1]=[]);c=stickData[b]||(stickData[b]=[]);if(d){var e=g=>.3<g?percent(g,.3,.8):-.3>g?-percent(-g,.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],k[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;ontouchstart=ontouchmove=ontouchend=c=>{c.button=0;const d=c.touches.length;d?(b||zzfx(0,b=1),c.x=c.touches[0].clientX,c.y=c.touches[0].clientY,a?onmousemove(c):onmousedown(c)):a&&onmouseup(c);a=d;return!0}}let touchGamepadTimer=new Timer,touchGamepadButtons=[],touchGamepadStick=vec2();function touchGamepadCreate(){touchGamepadEnable&&isTouchDevice&&(ontouchstart=ontouchmove=ontouchend=a=>{if(touchGamepadEnable){touchGamepadStick=vec2();touchGamepadButtons=[];if(a.touches.length&&(touchGamepadTimer.isSet()||zzfx(0),isUsingGamepad=1,touchGamepadTimer.set(),paused)){touchGamepadButtons[9]=1;return}var b=vec2(touchGamepadSize,mainCanvasSize.y-touchGamepadSize),c=mainCanvasSize.subtract(vec2(touchGamepadSize,touchGamepadSize)),d=mainCanvasSize.scale(.5);for(const e of a.touches)a=mouseToScreen(vec2(e.clientX,e.clientY)),a.distance(b)<touchGamepadSize?touchGamepadAnalog?touchGamepadStick=a.subtract(b).scale(2/touchGamepadSize).clampLength():(a=a.subtract(b).angle(),touchGamepadStick.setAngle((4*a/PI+8.5|0)*PI/4)):a.distance(c)<touchGamepadSize?(a=a.subtract(c).direction(),touchGamepadButtons[a]=1):a.distance(d)<touchGamepadSize&&(touchGamepadButtons[9]=1)}})}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=0;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=1){if(soundEnable)return this.play(b,c,2**(a/12),0)}}class Music{constructor(a){soundEnable&&(this.cachedSamples=zzfxM(...a))}play(a=1,b=1){if(soundEnable)return playSamples(this.cachedSamples,a,1,0,b)}}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(window.AudioContext||webkitAudioContext),audioContext.resume(),"running"==audioContext.state)){var f=audioContext.createBuffer(a.length,a[0].length,zzfxR),k=audioContext.createBufferSource();a.forEach((g,h)=>f.getChannelData(h).set(g));k.buffer=f;k.playbackRate.value=c;k.loop=e;a=audioContext.createGain();a.gain.value=soundVolume*b;a.connect(audioContext.destination);(window.StereoPannerNode?k.connect(new StereoPannerNode(audioContext,{pan:clamp(d,-1,1)})):k).connect(a);k.start();return k}}const zzfx=(...a)=>playSamples([zzfxG(...a)]),zzfxR=44100;function zzfxG(a=1,b=.05,c=220,d=0,e=0,f=.1,k=0,g=1,h=0,n=0,m=0,p=0,l=0,u=0,v=0,A=0,r=0,B=1,y=0,C=0){let t=2*PI,F=h*=500*t/zzfxR/zzfxR,z=[];b=c*=(1+b*rand(-1,1))*t/zzfxR;let w=0,D=0,q=0,x=1,G=0,I=0,E=0,J,H;d=d*zzfxR+9;y*=zzfxR;e*=zzfxR;f*=zzfxR;r*=zzfxR;n*=500*t/zzfxR**3;v*=t/zzfxR;m*=t/zzfxR;p*=zzfxR;l=l*zzfxR|0;for(H=d+y+e+f+r|0;q<H;z[q++]=E)++I%(100*A|0)||(E=k?1<k?2<k?3<k?Math.sin((w%t)**3):max(min(Math.tan(w),1),-1):1-(2*w/t%2+2)%2:1-4*abs(Math.round(w/t)-w/t):Math.sin(w),E=(l?1-C+C*Math.sin(t*q/l):1)*sign(E)*abs(E)**g*a*soundVolume*(q<d?q/d:q<d+y?1-(q-d)/y*(1-B):q<d+y+e?B:q<H-r?(H-q-r)/f*B:0),E=r?E/2+(r>q?0:(q<H-r?1:(H-q)/r)*z[q-r|0]/2):E),J=(c+=h+=n)*Math.cos(v*D++),w+=J-J*u*(1-1e9*(Math.sin(q)+1)%2),x&&++x>p&&(c+=m,b+=m,x=0),!l||++G%l||(c=b,h=F,x=x||1);return z}function zzfxM(a,b,c,d=125){let e,f,k,g,h,n,m,p,l,u,v,A,r,B=0,y,C=[],t=[],F=[],z=0,w=0,D=1,q={},x=zzfxR/d*60>>2;for(;D;z++)C=[D=p=A=0],c.forEach((G,I)=>{m=b[G][z]||[0,0,0];D|=!!b[G][z];y=A+(b[G][0].length-2-!p)*x;r=I==c.length-1;f=2;for(g=A;f<m.length+r;p=++f){h=m[f];l=f==m.length+r-1&&r||u!=(m[0]||0)|h|0;for(k=0;k<x&&p;k++>x-99&&l?v+=(1>v)/99:0)n=(1-v)*C[B++]/2||0,t[g]=(t[g]||0)-n*w+n,F[g]=(F[g++]||0)+n*w+n;h&&(v=h%1,w=m[1]||0,h|=0)&&(C=q[[u=m[B=0]||0,h]]=q[[u,h]]||(e=[...a[u]],e[2]*=2**((h-12)/12),0<h?zzfxG(...e):[]))}A=y});return[t,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 k=tileCollision[e*tileCollisionSize.x+b];if(k&&(!c||c.collideWithTile(k,new Vector2(b,e))))return 1}}function tileCollisionRaycast(a,b,c){a=a.floor();b=b.floor();var d=b.subtract(a);const e=abs(d.x),f=-abs(d.y),k=sign(d.x);d=sign(d.y);let g=e+f;for(let n=a.x,m=a.y;;){var h=getTileCollisionData(vec2(n,m));if(h&&(c?c.collideWithTileRaycast(h,new Vector2(n,m)):0<h))return debugRaycast&&debugLine(a,b,"#f00",.02,1),debugRaycast&&debugPoint(new Vector2(n+.5,m+.5),"#ff0",1),new Vector2(n+.5,m+.5);if(n==b.x&m==b.y)break;h=2*g;h>=f&&(g+=f,n+=k);h<=e&&(g+=e,m+=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){a&&(this.canvas.width=this.size.x*this.tileSize.x,this.canvas.height=this.size.y*this.tileSize.y);this.savedRenderSettings=[mainCanvas,mainContext,cameraPos,cameraScale];mainCanvas=this.canvas;mainContext=this.context;cameraPos=this.size.scale(.5);cameraScale=this.tileSize.x;enginePreRender()}redrawEnd(){ASSERT(mainContext==this.context);glEnable&&glCopyToContext(mainContext,1);[mainCanvas,mainContext,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,k){this.drawCanvas2D(a,b,f,k,g=>{if(0>c)g.fillStyle=e,g.fillRect(-.5,-.5,1,1);else{const h=tileImage.width/d.x;g.globalAlpha=e.a;g.drawImage(tileImage,c%h*d.x,(c/h|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,k=-1,g=tileSizeDefault,h=new Color,n=new Color,m=new Color(1,1,1,0),p=new Color(1,1,1,0),l=.5,u=.1,v=1,A=.1,r=.05,B=1,y=1,C=0,t=PI,F=.1,z=.2,w,D,q=1,x=D?1e9:0,G){super(a,new Vector2,k,g,b,void 0,x);this.emitSize=c;this.emitTime=d;this.emitRate=e;this.emitConeAngle=f;this.colorStartA=h;this.colorStartB=n;this.colorEndA=m;this.colorEndB=p;this.randomColorLinear=q;this.particleTime=l;this.sizeStart=u;this.sizeEnd=v;this.speed=A;this.angleSpeed=r;this.damping=B;this.angleDamping=y;this.gravityScale=C;this.particleConeAngle=t;this.fadeRate=F;this.randomness=z;this.collideTiles=w;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(.5*this.emitSize),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),k=d(this.speed);d=d(this.angleSpeed)*randSign();var g=rand(this.emitConeAngle,-this.emitConeAngle);const h=randColor(this.colorStartA,this.colorStartB,this.randomColorLinear),n=randColor(this.colorEndA,this.colorEndB,this.randomColorLinear);g=this.localSpace?g:this.angle+g;a.colorStart=h;a.colorEndDelta=n.subtract(h);a.velocity=(new Vector2).setAngle(g,k);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=.5>rand();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 k=e*this.trailScale;b.y=max(b.x,k);e=f.angle();drawTile(d.add(f.multiply(vec2(0,-k/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 medalsPreventUnlock,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;this.image=new Image;e&&(this.image.src=e)}unlock(){medalsPreventUnlock||this.unlocked||(ASSERT(medalsSaveName),localStorage[this.storageKey()]=this.unlocked=1,medalsDisplayQueue.push(this),newgrounds&&newgrounds.unlockMedal(this.id),localStorage["OS13kTrophy,"+this.icon+","+medalsSaveName+","+this.name]=this.description)}render(a=0){const b=overlayContext,c=min(medalDisplayWidth,mainCanvas.width),d=overlayCanvas.width-c;a*=-medalDisplayHeight;b.save();b.beginPath();b.fillStyle="#ddd";b.fill(b.rect(d,a,c,medalDisplayHeight));b.strokeStyle="#000";b.lineWidth=3;b.stroke();b.clip();this.renderIcon(d+15+medalDisplayIconSize/2,a+medalDisplayHeight/2);b.textAlign="left";b.font="38px "+fontDefault;b.fillText(this.name,d+medalDisplayIconSize+30,a+28);b.font="24px "+fontDefault;b.fillText(this.description,d+medalDisplayIconSize+30,a+60);b.restore()}renderIcon(a,b,c=medalDisplayIconSize){const d=overlayContext;d.fillStyle="#000";d.textAlign="center";d.textBaseline="middle";d.font=.7*c+"px "+fontDefault;this.image.src?d.drawImage(this.image,a-c/2,b-c/2,c,c):d.fillText(this.icon,a,b)}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=CryptoJS());this.session_id=new URL(location.href).searchParams.get("ngio_session_id")||0;if(0!=this.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.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)}}const CryptoJS=()=>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,glVertexData,glPositionData,glColorData,glBatchCount,glBatchAdditive,glAdditive;function glInit(){glCanvas=document.createElement("canvas");glContext=glCanvas.getContext("webgl",{antialias:!1});glCanvas.style=styleCanvas;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 vec2 v;varying vec4 d,e;void main(){gl_Position=m*vec4(p,1,1);v=t;d=c;e=a;}","precision highp float;varying vec2 v;varying vec4 d,e;uniform sampler2D s;void main(){gl_FragColor=texture2D(s,v)*d+e;}");glVertexData=new ArrayBuffer(gl_MAX_BATCH*gl_VERTICES_PER_QUAD*gl_VERTEX_BYTE_STRIDE);glArrayBuffer=glContext.createBuffer();glPositionData=new Float32Array(glVertexData);glColorData=new Uint32Array(glVertexData);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);glContext.texParameteri(gl_TEXTURE_2D,gl_TEXTURE_MIN_FILTER,cavasPixelated?gl_NEAREST:gl_LINEAR);glContext.texParameteri(gl_TEXTURE_2D,gl_TEXTURE_MAG_FILTER,cavasPixelated?gl_NEAREST:gl_LINEAR);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(a,b,c,d,e){glContext.viewport(0,0,glCanvas.width=a,glCanvas.height=b);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,glVertexData.byteLength,gl_DYNAMIC_DRAW);glSetBlendMode();let f=0;const k=(g,h,n,m,p=0)=>{g=glContext.getAttribLocation(glShader,g);glContext.enableVertexAttribArray(g);glContext.vertexAttribPointer(g,m,h,p,gl_VERTEX_BYTE_STRIDE,f);f+=m*n};k("p",gl_FLOAT,4,2);k("t",gl_FLOAT,4,2);k("c",gl_UNSIGNED_BYTE,1,4,1);k("a",gl_UNSIGNED_BYTE,1,4,1);a=2*e/a;b=2*e/b;glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader,"m"),0,new Float32Array([a,0,0,0,0,b,0,0,1,1,-1,1,-1-a*c,-1-b*d,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,k,g,h,n=4294967295,m=0){glBatchCount!=gl_MAX_BATCH&&glBatchAdditive==glAdditive||glFlush();var p=Math.cos(e)/2,l=Math.sin(e)/2;e=p*c;p*=d;c*=l;d*=l;l=glBatchCount++*gl_VERTICES_PER_QUAD*gl_INDICIES_PER_VERT;glPositionData[l++]=a-e-d;glPositionData[l++]=b-p+c;glPositionData[l++]=f;glPositionData[l++]=h;glColorData[l++]=n;glColorData[l++]=m;glPositionData[l++]=a+e+d;glPositionData[l++]=b+p-c;glPositionData[l++]=g;glPositionData[l++]=k;glColorData[l++]=n;glColorData[l++]=m;glPositionData[l++]=a-e+d;glPositionData[l++]=b+p+c;glPositionData[l++]=f;glPositionData[l++]=k;glColorData[l++]=n;glColorData[l++]=m;glPositionData[l++]=a-e-d;glPositionData[l++]=b-p+c;glPositionData[l++]=f;glPositionData[l++]=h;glColorData[l++]=n;glColorData[l++]=m;glPositionData[l++]=a+e-d;glPositionData[l++]=b-p-c;glPositionData[l++]=g;glPositionData[l++]=h;glColorData[l++]=n;glColorData[l++]=m;glPositionData[l++]=a+e+d;glPositionData[l++]=b+p-c;glPositionData[l++]=g;glPositionData[l++]=k;glColorData[l++]=n;glColorData[l++]=m}let glPostShader,glPostArrayBuffer,glPostTexture;function glInitPostProcess(a){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();mainCanvas.style.visibility="hidden"}function glRenderPostProcess(){if(glPostShader){var a=mainCanvas.width,b=mainCanvas.height;glEnable?(glFlush(),mainContext.drawImage(glCanvas,0,0)):glContext.viewport(0,0,glCanvas.width=a,glCanvas.height=b);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 c=glContext.getAttribLocation(glPostShader,"p");glContext.enableVertexAttribArray(c);glContext.vertexAttribPointer(c,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"),a,b,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;
1
+ let showWatermark=0,godMode=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*(rand(2)|0)-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 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);class Vector2{constructor(a=0,b=0){this.x=a;this.y=b}copy(){return new Vector2(this.x,this.y)}add(a){ASSERT(void 0!=a.x);return new Vector2(this.x+a.x,this.y+a.y)}subtract(a){ASSERT(void 0!=a.x);return new Vector2(this.x-a.x,this.y-a.y)}multiply(a){ASSERT(void 0!=a.x);return new Vector2(this.x*a.x,this.y*a.y)}divide(a){ASSERT(void 0!=a.x);return new Vector2(this.x/a.x,this.y/a.y)}scale(a){ASSERT(void 0==a.x);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(void 0!=a.x);return this.x*a.x+this.y*a.y}cross(a){ASSERT(void 0!=a.x);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(void 0!=a.x);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){return`(${(0>this.x?"":" ")+this.x.toFixed(a)},${(0>this.y?"":" ")+this.y.toFixed(a)} )`}}const colorRGBA=(a,b,c,d)=>new Color(a,b,c,d),colorHSLA=(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,g)=>(g=(g%1+1)%1)<1/6?f+6*(h-f)*g:.5>g?h:g<2/3?f+(h-f)*(2/3-g)*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 g=0,k=0;if(e!=f){let n=e-f;k=.5<h?n/(2-e-f):n/(e+f);a==e?g=(b-c)/n+(b<c?6:0):b==e?g=(c-a)/n+2:c==e&&(g=(a-b)/n+4)}return[g/6,k,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 canvasMaxSize=vec2(1920,1200),canvasFixedSize=vec2(),cavasPixelated=1,fontDefault="arial",tileSizeDefault=vec2(16),tileFixBleedScale=.3,objectDefaultSize=vec2(1),enablePhysicsSolver=1,objectDefaultMass=1,objectDefaultDamping=.99,objectDefaultAngleDamping=.99,objectDefaultElasticity=0,objectDefaultFriction=.8,objectMaxSpeed=1,gravity=0,particleEmitRateScale=1,cameraPos=vec2(),cameraScale=max(tileSizeDefault.x,tileSizeDefault.y),glEnable=1,glOverlay=1,gamepadsEnable=1,gamepadDirectionEmulateStick=1,inputWASDEmulateDirection=1,touchGamepadEnable=0,touchGamepadAnalog=1,touchGamepadSize=99,touchGamepadAlpha=.3,vibrateEnable=1,soundVolume=.5,soundEnable=1,soundDefaultRange=30,soundDefaultTaper=.7,medalDisplayTime=5,medalDisplaySlideTime=.5,medalDisplayWidth=640,medalDisplayHeight=80,medalDisplayIconSize=50;"use strict";const engineName="LittleJS",engineVersion="1.4.7",frameRate=60,timeDelta=1/frameRate;let engineObjects=[],engineObjectsCollide=[],frame=0,time=0,timeReal=0,paused=0,tileImageSize,tileImageFixBleed,averageFPS,drawCount;function engineInit(a,b,c,d,e,f){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;-moz-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=overlayCanvas.style=mainCanvas.style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)";a();touchGamepadCreate();k()};let h=0,g=0;const k=(n=0)=>{var m=n-h;h=n;if(debug||showWatermark)averageFPS=lerp(.05,averageFPS||0,1e3/(m||1));n=debug&&keyIsDown(107);const p=debug&&keyIsDown(109);debug&&(m*=n?5:p?.2:1);timeReal+=m/1e3;g+=!paused*m;n||(g=min(g,50));canvasFixedSize.x?(overlayCanvas.width=mainCanvas.width=canvasFixedSize.x,overlayCanvas.height=mainCanvas.height=canvasFixedSize.y,m=innerWidth/innerHeight,n=mainCanvas.width/mainCanvas.height,mainCanvas.style.width=overlayCanvas.style.width=m<n?"100%":"",mainCanvas.style.height=overlayCanvas.style.height=m<n?"":"100%",glCanvas&&(glCanvas.style.width=mainCanvas.style.width,glCanvas.style.height=mainCanvas.style.height)):(overlayCanvas.width=mainCanvas.width=min(innerWidth,canvasMaxSize.x),overlayCanvas.height=mainCanvas.height=min(innerHeight,canvasMaxSize.y));mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height);if(paused)inputUpdate(),debugUpdate(),c(),inputUpdatePost();else{m=0;0>g&&-9<g&&(m=g,g=0);for(;0<=g;g-=1e3/frameRate)inputUpdate(),b(),engineObjectsUpdate(),debugUpdate(),c(),inputUpdatePost();g+=m}enginePreRender();d();engineObjects.sort((r,t)=>r.renderOrder-t.renderOrder);for(var l of engineObjects)l.destroyed||l.render();e();glRenderPostProcess();medalsRender();touchGamepadRender();debugRender();glEnable&&glCopyToContext(mainContext);showWatermark&&(overlayContext.textAlign="right",overlayContext.textBaseline="top",overlayContext.font="1em monospace",overlayContext.fillStyle="#000",l=engineName+" v"+engineVersion+" / "+drawCount+" / "+engineObjects.length+" / "+averageFPS.toFixed(1)+" "+(glEnable?"GL":"2D"),overlayContext.fillText(l,mainCanvas.width-3,3),overlayContext.fillStyle="#fff",overlayContext.fillText(l,mainCanvas.width-2,2),drawCount=0);requestAnimationFrame(k)};f?tileImage.src=f:tileImage.onload()}function enginePreRender(){mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height);mainContext.imageSmoothingEnabled=!cavasPixelated;glEnable&&glPreRender(mainCanvas.width,mainCanvas.height,cameraPos.x,cameraPos.y,cameraScale)}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=objectDefaultSize,c=-1,d=tileSizeDefault,e=0,f,h=0){ASSERT(a&&void 0!=a.x&&void 0!=b.x);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.pos.x+=this.velocity.x*=this.damping,this.pos.y+=this.velocity.y=this.damping*this.velocity.y+gravity*this.gravityScale,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||!isOverlapping(this.pos,this.size,d.pos,d.size)||!this.collideWithObject(d)|!d.collideWithObject(this)))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")}else{c=this.size.add(d.size);e=2*(a.y-d.pos.y)>c.y+gravity;var f=2*abs(a.y-d.pos.y)<c.y,h=2*abs(a.x-d.pos.x)<c.x;if(e||h||!f)if(this.pos.y=d.pos.y+(c.y/2+.001)*sign(a.y-d.pos.y),d.groundObject&&b||!d.mass)b&&(this.groundObject=d),this.velocity.y*=-this.elasticity;else if(d.mass){const g=(this.mass*this.velocity.y+d.mass*d.velocity.y)/(this.mass+d.mass),k=this.velocity.y*(this.mass-d.mass)/(this.mass+d.mass)+2*d.velocity.y*d.mass/(this.mass+d.mass),n=d.velocity.y*(d.mass-this.mass)/(this.mass+d.mass)+2*this.velocity.y*this.mass/(this.mass+d.mass),m=max(this.elasticity,d.elasticity);this.velocity.y=lerp(m,g,k);d.velocity.y=lerp(m,g,n)}e||!f&&h||(this.pos.x=d.pos.x+(c.x/2+.001)*sign(a.x-d.pos.x),d.mass?(c=(this.mass*this.velocity.x+d.mass*d.velocity.x)/(this.mass+d.mass),e=this.velocity.x*(this.mass-d.mass)/(this.mass+d.mass)+2*d.velocity.x*d.mass/(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),h=max(this.elasticity,d.elasticity),this.velocity.x=lerp(h,c,e),d.velocity.x=lerp(h,c,f)):this.velocity.x*=-this.elasticity);debugOverlay&&debugPhysics&&debugAABB(this.pos,this.size,d.pos,d.size,"#f0f")}if(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);if(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";const tileImage=new Image;let mainCanvas,mainContext,overlayCanvas,overlayContext,mainCanvasSize=vec2();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,g=new Color(0,0,0,0),k=glEnable){showWatermark&&++drawCount;if(glEnable&&k)if(0>c||!tileImage.width)glDraw(a.x,a.y,b.x,b.y,f,0,0,0,0,0,e.rgbaInt());else{var n=tileImageSize.x/d.x|0;k=d.x/tileImageSize.x;const m=d.y/tileImageSize.y,p=c%n*k;n=(c/n|0)*m;glDraw(a.x,a.y,h?-b.x:b.x,b.y,f,p+tileImageFixBleed.x,n+tileImageFixBleed.y,p-tileImageFixBleed.x+k,n-tileImageFixBleed.y+m,e.rgbaInt(),g.rgbaInt())}else drawCanvas2D(a,b,f,h,m=>{if(0>c)m.fillStyle=e,m.fillRect(-.5,-.5,1,1);else{var p=tileImageSize.x/d.x|0;const l=c%p*d.x+tileFixBleedScale;p=(c/p|0)*d.y+tileFixBleedScale;const r=d.x-2*tileFixBleedScale,t=d.y-2*tileFixBleedScale;m.globalAlpha=e.a;m.drawImage(tileImage,l,p,r,t,-.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,g,k){drawTile(screenToWorld(a),b.scale(1/cameraScale),c,d,e,f,h,g,k)}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 drawTextScreen(a,b,c=1,d=new Color,e=0,f=new Color(0,0,0),h="center",g=fontDefault,k=overlayContext){k.fillStyle=d;k.lineWidth=e;k.strokeStyle=f;k.textAlign=h;k.font=c+"px "+g;k.textBaseline="middle";k.lineJoin="round";b=b.copy();(a+"").split("\n").forEach(n=>{e&&k.strokeText(n,b.x,b.y);k.fillText(n,b.x,b.y);b.y+=c})}function drawText(a,b,c=1,d,e,f,h,g){drawTextScreen(a,worldToScreen(b),c*cameraScale,d,e*cameraScale,f,h,g,mainContext)}let engineFontImage;class FontImage{constructor(a,b=vec2(8),c=vec2(0,1),d=0,e=overlayContext){a||engineFontImage||(engineFontImage=new Image,engineFontImage.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),g=this.image.width/this.tileSize.x|0;(a+"").split("\n").forEach((k,n)=>{const m=d?k.length*f.x*c/2|0:0;for(let r=k.length;r--;){var p=k[r].charCodeAt();if(32>p||127<p)p=127;var l=this.startTileIndex+p-32;p=l%g;l=l/g|0;const t=b.add(vec2(r,n).multiply(h));e.drawImage(this.image,p*f.x,l*f.y,f.x,f.y,t.x-m,t.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.mozCancelFullScreen&&document.mozCancelFullScreen():document.body.webkitRequestFullScreen?document.body.webkitRequestFullScreen():document.body.mozRequestFullScreen&&document.body.mozRequestFullScreen()}"use strict";const keyIsDown=(a,b=0)=>inputData[b]&&inputData[b][a]&1?1:0,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][remapKeyCode(a.keyCode)]=3),preventDefaultInput&&a.preventDefault())};onkeyup=a=>{debug&&a.target!=document.body||(inputData[0][remapKeyCode(a.keyCode)]=4)};const remapKeyCode=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=g=>.3<g?percent(g,.3,.8):-.3>g?-percent(-g,.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){const a=onmousedown,b=onmouseup,c=onmousemove;onmousedown=onmouseup=onmousemove=f=>0;let d,e;ontouchstart=ontouchmove=ontouchend=f=>{f.button=0;const h=f.touches.length;h?(e||zzfx(0,e=1),f.x=f.touches[0].clientX,f.y=f.touches[0].clientY,d?c(f):a(f)):d&&b(f);d=h;return!0}}let touchGamepadTimer=new Timer,touchGamepadButtons=[],touchGamepadStick=vec2();function touchGamepadCreate(){touchGamepadEnable&&isTouchDevice&&(ontouchstart=ontouchmove=ontouchend=a=>{if(touchGamepadEnable){touchGamepadStick=vec2();touchGamepadButtons=[];if(a.touches.length&&(touchGamepadTimer.isSet()||zzfx(0),isUsingGamepad=1,touchGamepadTimer.set(),paused)){touchGamepadButtons[9]=1;return}var b=vec2(touchGamepadSize,mainCanvasSize.y-touchGamepadSize),c=mainCanvasSize.subtract(vec2(touchGamepadSize,touchGamepadSize)),d=mainCanvasSize.scale(.5);for(const e of a.touches)a=mouseToScreen(vec2(e.clientX,e.clientY)),a.distance(b)<touchGamepadSize?touchGamepadAnalog?touchGamepadStick=a.subtract(b).scale(2/touchGamepadSize).clampLength():(a=a.subtract(b).angle(),touchGamepadStick.setAngle((4*a/PI+8.5|0)*PI/4)):a.distance(c)<touchGamepadSize?(a=a.subtract(c).direction(),touchGamepadButtons[a]=1):a.distance(d)<touchGamepadSize&&(touchGamepadButtons[9]=1)}})}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=0;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=1){if(soundEnable)return this.play(b,c,2**(a/12),0)}}class Music{constructor(a){soundEnable&&(this.cachedSamples=zzfxM(...a))}play(a=1,b=1){if(soundEnable)return playSamples(this.cachedSamples,a,1,0,b)}}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(window.AudioContext||webkitAudioContext),audioContext.resume(),"running"==audioContext.state)){var f=audioContext.createBuffer(a.length,a[0].length,zzfxR),h=audioContext.createBufferSource();a.forEach((g,k)=>f.getChannelData(k).set(g));h.buffer=f;h.playbackRate.value=c;h.loop=e;a=audioContext.createGain();a.gain.value=soundVolume*b;a.connect(audioContext.destination);(window.StereoPannerNode?h.connect(new StereoPannerNode(audioContext,{pan:clamp(d,-1,1)})):h).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,g=1,k=0,n=0,m=0,p=0,l=0,r=0,t=0,A=0,u=0,B=1,y=0,C=0){let v=2*PI,F=k*=500*v/zzfxR/zzfxR,z=[];b=c*=(1+b*rand(-1,1))*v/zzfxR;let w=0,D=0,q=0,x=1,G=0,I=0,E=0,J,H;d=d*zzfxR+9;y*=zzfxR;e*=zzfxR;f*=zzfxR;u*=zzfxR;n*=500*v/zzfxR**3;t*=v/zzfxR;m*=v/zzfxR;p*=zzfxR;l=l*zzfxR|0;for(H=d+y+e+f+u|0;q<H;z[q++]=E)++I%(100*A|0)||(E=h?1<h?2<h?3<h?Math.sin((w%v)**3):max(min(Math.tan(w),1),-1):1-(2*w/v%2+2)%2:1-4*abs(Math.round(w/v)-w/v):Math.sin(w),E=(l?1-C+C*Math.sin(v*q/l):1)*sign(E)*abs(E)**g*a*soundVolume*(q<d?q/d:q<d+y?1-(q-d)/y*(1-B):q<d+y+e?B:q<H-u?(H-q-u)/f*B:0),E=u?E/2+(u>q?0:(q<H-u?1:(H-q)/u)*z[q-u|0]/2):E),J=(c+=k+=n)*Math.cos(t*D++),w+=J-J*r*(1-1e9*(Math.sin(q)+1)%2),x&&++x>p&&(c+=m,b+=m,x=0),!l||++G%l||(c=b,k=F,x=x||1);return z}function zzfxM(a,b,c,d=125){let e,f,h,g,k,n,m,p,l,r,t,A,u,B=0,y,C=[],v=[],F=[],z=0,w=0,D=1,q={},x=zzfxR/d*60>>2;for(;D;z++)C=[D=p=A=0],c.forEach((G,I)=>{m=b[G][z]||[0,0,0];D|=!!b[G][z];y=A+(b[G][0].length-2-!p)*x;u=I==c.length-1;f=2;for(g=A;f<m.length+u;p=++f){k=m[f];l=f==m.length+u-1&&u||r!=(m[0]||0)|k|0;for(h=0;h<x&&p;h++>x-99&&l?t+=(1>t)/99:0)n=(1-t)*C[B++]/2||0,v[g]=(v[g]||0)-n*w+n,F[g]=(F[g++]||0)+n*w+n;k&&(t=k%1,w=m[1]||0,k|=0)&&(C=q[[r=m[B=0]||0,k]]=q[[r,k]]||(e=[...a[r]],e[2]*=2**((k-12)/12),0<k?zzfxG(...e):[]))}A=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){a=a.floor();b=b.floor();var d=b.subtract(a);const e=abs(d.x),f=-abs(d.y),h=sign(d.x);d=sign(d.y);let g=e+f;for(let n=a.x,m=a.y;;){var k=getTileCollisionData(vec2(n,m));if(k&&(c?c.collideWithTileRaycast(k,new Vector2(n,m)):0<k))return debugRaycast&&debugLine(a,b,"#f00",.02,1),debugRaycast&&debugPoint(new Vector2(n+.5,m+.5),"#ff0",1),new Vector2(n+.5,m+.5);if(n==b.x&m==b.y)break;k=2*g;k>=f&&(g+=f,n+=h);k<=e&&(g+=e,m+=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){a&&(this.canvas.width=this.size.x*this.tileSize.x,this.canvas.height=this.size.y*this.tileSize.y);this.savedRenderSettings=[mainCanvas,mainContext,cameraPos,cameraScale];mainCanvas=this.canvas;mainContext=this.context;cameraPos=this.size.scale(.5);cameraScale=this.tileSize.x;enginePreRender()}redrawEnd(){ASSERT(mainContext==this.context);glEnable&&glCopyToContext(mainContext,1);[mainCanvas,mainContext,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,g=>{if(0>c)g.fillStyle=e,g.fillRect(-.5,-.5,1,1);else{const k=tileImage.width/d.x;g.globalAlpha=e.a;g.drawImage(tileImage,c%k*d.x,(c/k|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,g=tileSizeDefault,k=new Color,n=new Color,m=new Color(1,1,1,0),p=new Color(1,1,1,0),l=.5,r=.1,t=1,A=.1,u=.05,B=1,y=1,C=0,v=PI,F=.1,z=.2,w,D,q=1,x=D?1e9:0,G){super(a,new Vector2,h,g,b,void 0,x);this.emitSize=c;this.emitTime=d;this.emitRate=e;this.emitConeAngle=f;this.colorStartA=k;this.colorStartB=n;this.colorEndA=m;this.colorEndB=p;this.randomColorLinear=q;this.particleTime=l;this.sizeStart=r;this.sizeEnd=t;this.speed=A;this.angleSpeed=u;this.damping=B;this.angleDamping=y;this.gravityScale=C;this.particleConeAngle=v;this.fadeRate=F;this.randomness=z;this.collideTiles=w;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(.5*this.emitSize),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 g=rand(this.emitConeAngle,-this.emitConeAngle);const k=randColor(this.colorStartA,this.colorStartB,this.randomColorLinear),n=randColor(this.colorEndA,this.colorEndB,this.randomColorLinear);g=this.localSpace?g:this.angle+g;a.colorStart=k;a.colorEndDelta=n.subtract(k);a.velocity=(new Vector2).setAngle(g,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=.5>rand();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 medalsPreventUnlock,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;this.image=new Image;e&&(this.image.src=e)}unlock(){medalsPreventUnlock||this.unlocked||(ASSERT(medalsSaveName),localStorage[this.storageKey()]=this.unlocked=1,medalsDisplayQueue.push(this),newgrounds&&newgrounds.unlockMedal(this.id),localStorage["OS13kTrophy,"+this.icon+","+medalsSaveName+","+this.name]=this.description)}render(a=0){const b=overlayContext,c=min(medalDisplayWidth,mainCanvas.width),d=overlayCanvas.width-c;a*=-medalDisplayHeight;b.save();b.beginPath();b.fillStyle="#ddd";b.fill(b.rect(d,a,c,medalDisplayHeight));b.strokeStyle="#000";b.lineWidth=3;b.stroke();b.clip();this.renderIcon(d+15+medalDisplayIconSize/2,a+medalDisplayHeight/2);b.textAlign="left";b.font="38px "+fontDefault;b.fillText(this.name,d+medalDisplayIconSize+30,a+28);b.font="24px "+fontDefault;b.fillText(this.description,d+medalDisplayIconSize+30,a+60);b.restore()}renderIcon(a,b,c=medalDisplayIconSize){const d=overlayContext;d.fillStyle="#000";d.textAlign="center";d.textBaseline="middle";d.font=.7*c+"px "+fontDefault;this.image.src?d.drawImage(this.image,a-c/2,b-c/2,c,c):d.fillText(this.icon,a,b)}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=CryptoJS());this.session_id=new URL(location.href).searchParams.get("ngio_session_id")||0;if(0!=this.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.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)}}const CryptoJS=()=>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,glVertexData,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 vec2 v;varying vec4 d,e;void main(){gl_Position=m*vec4(p,1,1);v=t;d=c;e=a;}","precision highp float;varying vec2 v;varying vec4 d,e;uniform sampler2D s;void main(){gl_FragColor=texture2D(s,v)*d+e;}");glVertexData=new ArrayBuffer(gl_MAX_BATCH*gl_VERTICES_PER_QUAD*gl_VERTEX_BYTE_STRIDE);glArrayBuffer=glContext.createBuffer();glPositionData=new Float32Array(glVertexData);glColorData=new Uint32Array(glVertexData);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);glContext.texParameteri(gl_TEXTURE_2D,gl_TEXTURE_MIN_FILTER,cavasPixelated?gl_NEAREST:gl_LINEAR);glContext.texParameteri(gl_TEXTURE_2D,gl_TEXTURE_MAG_FILTER,cavasPixelated?gl_NEAREST:gl_LINEAR);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(a,b,c,d,e){glContext.viewport(0,0,glCanvas.width=a,glCanvas.height=b);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,glVertexData.byteLength,gl_DYNAMIC_DRAW);glSetBlendMode();let f=0;const h=(g,k,n,m,p=0)=>{g=glContext.getAttribLocation(glShader,g);glContext.enableVertexAttribArray(g);glContext.vertexAttribPointer(g,m,k,p,gl_VERTEX_BYTE_STRIDE,f);f+=m*n};h("p",gl_FLOAT,4,2);h("t",gl_FLOAT,4,2);h("c",gl_UNSIGNED_BYTE,1,4,1);h("a",gl_UNSIGNED_BYTE,1,4,1);a=2*e/a;b=2*e/b;glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader,"m"),0,new Float32Array([a,0,0,0,0,b,0,0,1,1,-1,1,-1-a*c,-1-b*d,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,g,k,n=4294967295,m=0){glBatchCount!=gl_MAX_BATCH&&glBatchAdditive==glAdditive||glFlush();var p=Math.cos(e)/2,l=Math.sin(e)/2;e=p*c;p*=d;c*=l;d*=l;l=glBatchCount++*gl_VERTICES_PER_QUAD*gl_INDICIES_PER_VERT;glPositionData[l++]=a-e-d;glPositionData[l++]=b-p+c;glPositionData[l++]=f;glPositionData[l++]=k;glColorData[l++]=n;glColorData[l++]=m;glPositionData[l++]=a+e+d;glPositionData[l++]=b+p-c;glPositionData[l++]=g;glPositionData[l++]=h;glColorData[l++]=n;glColorData[l++]=m;glPositionData[l++]=a-e+d;glPositionData[l++]=b+p+c;glPositionData[l++]=f;glPositionData[l++]=h;glColorData[l++]=n;glColorData[l++]=m;glPositionData[l++]=a-e-d;glPositionData[l++]=b-p+c;glPositionData[l++]=f;glPositionData[l++]=k;glColorData[l++]=n;glColorData[l++]=m;glPositionData[l++]=a+e-d;glPositionData[l++]=b-p-c;glPositionData[l++]=g;glPositionData[l++]=k;glColorData[l++]=n;glColorData[l++]=m;glPositionData[l++]=a+e+d;glPositionData[l++]=b+p-c;glPositionData[l++]=g;glPositionData[l++]=h;glColorData[l++]=n;glColorData[l++]=m}let glPostShader,glPostArrayBuffer,glPostTexture;function glInitPostProcess(a){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();mainCanvas.style.visibility="hidden"}function glRenderPostProcess(){if(glPostShader){var a=mainCanvas.width,b=mainCanvas.height;glEnable?(glFlush(),mainContext.drawImage(glCanvas,0,0)):glContext.viewport(0,0,glCanvas.width=a,glCanvas.height=b);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 c=glContext.getAttribLocation(glPostShader,"p");glContext.enableVertexAttribArray(c);glContext.vertexAttribPointer(c,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"),a,b,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;
@@ -1110,7 +1110,7 @@ let touchGamepadAnalog = 1;
1110
1110
  /** Size of virutal gamepad for touch devices in pixels
1111
1111
  * @default
1112
1112
  * @memberof Settings */
1113
- let touchGamepadSize = 80;
1113
+ let touchGamepadSize = 99;
1114
1114
 
1115
1115
  /** Transparency of touch gamepad overlay
1116
1116
  * @default
@@ -2190,6 +2190,10 @@ const isTouchDevice = window.ontouchstart !== undefined;
2190
2190
  // try to enable touch mouse
2191
2191
  if (isTouchDevice)
2192
2192
  {
2193
+ // override mouse events
2194
+ const mouseDown = onmousedown, mouseUp = onmouseup, mouseMove = onmousemove;
2195
+ onmousedown = onmouseup = onmousemove = (e)=> 0;
2196
+
2193
2197
  // handle all touch events the same way
2194
2198
  let wasTouching, hadTouch;
2195
2199
  ontouchstart = ontouchmove = ontouchend = (e)=>
@@ -2206,10 +2210,10 @@ if (isTouchDevice)
2206
2210
  // set event pos and pass it along
2207
2211
  e.x = e.touches[0].clientX;
2208
2212
  e.y = e.touches[0].clientY;
2209
- wasTouching ? onmousemove(e) : onmousedown(e);
2213
+ wasTouching ? mouseMove(e) : mouseDown(e);
2210
2214
  }
2211
2215
  else if (wasTouching)
2212
- onmouseup(e);
2216
+ mouseUp(e);
2213
2217
 
2214
2218
  // set was touching
2215
2219
  wasTouching = touching;
@@ -3812,7 +3816,6 @@ function glInit()
3812
3816
  // create the canvas and tile texture
3813
3817
  glCanvas = document.createElement('canvas');
3814
3818
  glContext = glCanvas.getContext('webgl', {antialias: false});
3815
- glCanvas.style = styleCanvas;
3816
3819
  glTileTexture = glCreateTexture(tileImage);
3817
3820
 
3818
3821
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
@@ -4209,7 +4212,7 @@ gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
4209
4212
  const engineName = 'LittleJS';
4210
4213
 
4211
4214
  /** Version of engine */
4212
- const engineVersion = '1.4.6';
4215
+ const engineVersion = '1.4.7';
4213
4216
 
4214
4217
  /** Frames per second to update objects
4215
4218
  * @default */
@@ -4238,15 +4241,11 @@ let timeReal = 0;
4238
4241
  let paused = 0;
4239
4242
 
4240
4243
  // Engine internal variables not exposed to documentation
4241
- let frameTimeLastMS = 0, frameTimeBufferMS = 0, tileImageSize, tileImageFixBleed;
4244
+ let tileImageSize, tileImageFixBleed;
4242
4245
 
4243
4246
  // Engine stat tracking, if showWatermark is true
4244
4247
  let averageFPS, drawCount;
4245
4248
 
4246
- // css text used for elements created by engine
4247
- const styleBody = 'margin:0;overflow:hidden;background:#000';
4248
- const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
4249
-
4250
4249
  ///////////////////////////////////////////////////////////////////////////////
4251
4250
 
4252
4251
  /** Start up LittleJS engine with your callback functions
@@ -4266,11 +4265,16 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4266
4265
  tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
4267
4266
  debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
4268
4267
 
4268
+ // setup css
4269
+ const styleBody = 'margin:0;overflow:hidden;background:#000' + // fill the window
4270
+ ';touch-action:none' + // prevent mobile pinch to resize
4271
+ ';user-select:none' + // prevent mobile hold to select
4272
+ ';-webkit-user-select:none;-moz-user-select:none'; // compatibility for mobile
4273
+
4269
4274
  // setup html
4270
4275
  document.body.style = styleBody;
4271
4276
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
4272
4277
  mainContext = mainCanvas.getContext('2d');
4273
- mainCanvas.style = styleCanvas;
4274
4278
 
4275
4279
  // init stuff and start engine
4276
4280
  debugInit();
@@ -4279,13 +4283,19 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4279
4283
  // create overlay canvas for hud to appear above gl canvas
4280
4284
  document.body.appendChild(overlayCanvas = document.createElement('canvas'));
4281
4285
  overlayContext = overlayCanvas.getContext('2d');
4282
- overlayCanvas.style = styleCanvas;
4286
+
4287
+ // set canvas style to fill the window
4288
+ const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
4289
+ (glCanvas||mainCanvas).style = overlayCanvas.style = mainCanvas.style = styleCanvas;
4283
4290
 
4284
4291
  gameInit();
4285
4292
  touchGamepadCreate();
4286
4293
  engineUpdate();
4287
4294
  };
4288
4295
 
4296
+ // frame time tracking
4297
+ let frameTimeLastMS = 0, frameTimeBufferMS = 0;
4298
+
4289
4299
  // main update loop
4290
4300
  const engineUpdate = (frameTimeMS=0)=>
4291
4301
  {
@@ -4799,11 +4809,8 @@ export {
4799
4809
  time,
4800
4810
  timeReal,
4801
4811
  paused,
4802
- //frameTimeLastMS,
4803
- //averageFPS,
4804
- //drawCount,
4805
- //styleBody,
4806
- //styleCanvas,
4812
+ averageFPS,
4813
+ drawCount,
4807
4814
  engineInit,
4808
4815
  //enginePreRender,
4809
4816
  //engineObjectsUpdate,
@@ -755,7 +755,7 @@ let touchGamepadAnalog = 1;
755
755
  /** Size of virutal gamepad for touch devices in pixels
756
756
  * @default
757
757
  * @memberof Settings */
758
- let touchGamepadSize = 80;
758
+ let touchGamepadSize = 99;
759
759
 
760
760
  /** Transparency of touch gamepad overlay
761
761
  * @default
@@ -841,7 +841,7 @@ let medalDisplayIconSize = 50;
841
841
  const engineName = 'LittleJS';
842
842
 
843
843
  /** Version of engine */
844
- const engineVersion = '1.4.6';
844
+ const engineVersion = '1.4.7';
845
845
 
846
846
  /** Frames per second to update objects
847
847
  * @default */
@@ -870,15 +870,11 @@ let timeReal = 0;
870
870
  let paused = 0;
871
871
 
872
872
  // Engine internal variables not exposed to documentation
873
- let frameTimeLastMS = 0, frameTimeBufferMS = 0, tileImageSize, tileImageFixBleed;
873
+ let tileImageSize, tileImageFixBleed;
874
874
 
875
875
  // Engine stat tracking, if showWatermark is true
876
876
  let averageFPS, drawCount;
877
877
 
878
- // css text used for elements created by engine
879
- const styleBody = 'margin:0;overflow:hidden;background:#000';
880
- const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
881
-
882
878
  ///////////////////////////////////////////////////////////////////////////////
883
879
 
884
880
  /** Start up LittleJS engine with your callback functions
@@ -898,11 +894,16 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
898
894
  tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
899
895
  debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
900
896
 
897
+ // setup css
898
+ const styleBody = 'margin:0;overflow:hidden;background:#000' + // fill the window
899
+ ';touch-action:none' + // prevent mobile pinch to resize
900
+ ';user-select:none' + // prevent mobile hold to select
901
+ ';-webkit-user-select:none;-moz-user-select:none'; // compatibility for mobile
902
+
901
903
  // setup html
902
904
  document.body.style = styleBody;
903
905
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
904
906
  mainContext = mainCanvas.getContext('2d');
905
- mainCanvas.style = styleCanvas;
906
907
 
907
908
  // init stuff and start engine
908
909
  debugInit();
@@ -911,13 +912,19 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
911
912
  // create overlay canvas for hud to appear above gl canvas
912
913
  document.body.appendChild(overlayCanvas = document.createElement('canvas'));
913
914
  overlayContext = overlayCanvas.getContext('2d');
914
- overlayCanvas.style = styleCanvas;
915
+
916
+ // set canvas style to fill the window
917
+ const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
918
+ (glCanvas||mainCanvas).style = overlayCanvas.style = mainCanvas.style = styleCanvas;
915
919
 
916
920
  gameInit();
917
921
  touchGamepadCreate();
918
922
  engineUpdate();
919
923
  };
920
924
 
925
+ // frame time tracking
926
+ let frameTimeLastMS = 0, frameTimeBufferMS = 0;
927
+
921
928
  // main update loop
922
929
  const engineUpdate = (frameTimeMS=0)=>
923
930
  {
@@ -2126,6 +2133,10 @@ const isTouchDevice = window.ontouchstart !== undefined;
2126
2133
  // try to enable touch mouse
2127
2134
  if (isTouchDevice)
2128
2135
  {
2136
+ // override mouse events
2137
+ const mouseDown = onmousedown, mouseUp = onmouseup, mouseMove = onmousemove;
2138
+ onmousedown = onmouseup = onmousemove = (e)=> 0;
2139
+
2129
2140
  // handle all touch events the same way
2130
2141
  let wasTouching, hadTouch;
2131
2142
  ontouchstart = ontouchmove = ontouchend = (e)=>
@@ -2142,10 +2153,10 @@ if (isTouchDevice)
2142
2153
  // set event pos and pass it along
2143
2154
  e.x = e.touches[0].clientX;
2144
2155
  e.y = e.touches[0].clientY;
2145
- wasTouching ? onmousemove(e) : onmousedown(e);
2156
+ wasTouching ? mouseMove(e) : mouseDown(e);
2146
2157
  }
2147
2158
  else if (wasTouching)
2148
- onmouseup(e);
2159
+ mouseUp(e);
2149
2160
 
2150
2161
  // set was touching
2151
2162
  wasTouching = touching;
@@ -3748,7 +3759,6 @@ function glInit()
3748
3759
  // create the canvas and tile texture
3749
3760
  glCanvas = document.createElement('canvas');
3750
3761
  glContext = glCanvas.getContext('webgl', {antialias: false});
3751
- glCanvas.style = styleCanvas;
3752
3762
  glTileTexture = glCreateTexture(tileImage);
3753
3763
 
3754
3764
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
@@ -755,7 +755,7 @@ let touchGamepadAnalog = 1;
755
755
  /** Size of virutal gamepad for touch devices in pixels
756
756
  * @default
757
757
  * @memberof Settings */
758
- let touchGamepadSize = 80;
758
+ let touchGamepadSize = 99;
759
759
 
760
760
  /** Transparency of touch gamepad overlay
761
761
  * @default
@@ -841,7 +841,7 @@ let medalDisplayIconSize = 50;
841
841
  const engineName = 'LittleJS';
842
842
 
843
843
  /** Version of engine */
844
- const engineVersion = '1.4.6';
844
+ const engineVersion = '1.4.7';
845
845
 
846
846
  /** Frames per second to update objects
847
847
  * @default */
@@ -870,15 +870,11 @@ let timeReal = 0;
870
870
  let paused = 0;
871
871
 
872
872
  // Engine internal variables not exposed to documentation
873
- let frameTimeLastMS = 0, frameTimeBufferMS = 0, tileImageSize, tileImageFixBleed;
873
+ let tileImageSize, tileImageFixBleed;
874
874
 
875
875
  // Engine stat tracking, if showWatermark is true
876
876
  let averageFPS, drawCount;
877
877
 
878
- // css text used for elements created by engine
879
- const styleBody = 'margin:0;overflow:hidden;background:#000';
880
- const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
881
-
882
878
  ///////////////////////////////////////////////////////////////////////////////
883
879
 
884
880
  /** Start up LittleJS engine with your callback functions
@@ -898,11 +894,16 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
898
894
  tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
899
895
  debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
900
896
 
897
+ // setup css
898
+ const styleBody = 'margin:0;overflow:hidden;background:#000' + // fill the window
899
+ ';touch-action:none' + // prevent mobile pinch to resize
900
+ ';user-select:none' + // prevent mobile hold to select
901
+ ';-webkit-user-select:none;-moz-user-select:none'; // compatibility for mobile
902
+
901
903
  // setup html
902
904
  document.body.style = styleBody;
903
905
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
904
906
  mainContext = mainCanvas.getContext('2d');
905
- mainCanvas.style = styleCanvas;
906
907
 
907
908
  // init stuff and start engine
908
909
  debugInit();
@@ -911,13 +912,19 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
911
912
  // create overlay canvas for hud to appear above gl canvas
912
913
  document.body.appendChild(overlayCanvas = document.createElement('canvas'));
913
914
  overlayContext = overlayCanvas.getContext('2d');
914
- overlayCanvas.style = styleCanvas;
915
+
916
+ // set canvas style to fill the window
917
+ const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
918
+ (glCanvas||mainCanvas).style = overlayCanvas.style = mainCanvas.style = styleCanvas;
915
919
 
916
920
  gameInit();
917
921
  touchGamepadCreate();
918
922
  engineUpdate();
919
923
  };
920
924
 
925
+ // frame time tracking
926
+ let frameTimeLastMS = 0, frameTimeBufferMS = 0;
927
+
921
928
  // main update loop
922
929
  const engineUpdate = (frameTimeMS=0)=>
923
930
  {
@@ -2126,6 +2133,10 @@ const isTouchDevice = window.ontouchstart !== undefined;
2126
2133
  // try to enable touch mouse
2127
2134
  if (isTouchDevice)
2128
2135
  {
2136
+ // override mouse events
2137
+ const mouseDown = onmousedown, mouseUp = onmouseup, mouseMove = onmousemove;
2138
+ onmousedown = onmouseup = onmousemove = (e)=> 0;
2139
+
2129
2140
  // handle all touch events the same way
2130
2141
  let wasTouching, hadTouch;
2131
2142
  ontouchstart = ontouchmove = ontouchend = (e)=>
@@ -2142,10 +2153,10 @@ if (isTouchDevice)
2142
2153
  // set event pos and pass it along
2143
2154
  e.x = e.touches[0].clientX;
2144
2155
  e.y = e.touches[0].clientY;
2145
- wasTouching ? onmousemove(e) : onmousedown(e);
2156
+ wasTouching ? mouseMove(e) : mouseDown(e);
2146
2157
  }
2147
2158
  else if (wasTouching)
2148
- onmouseup(e);
2159
+ mouseUp(e);
2149
2160
 
2150
2161
  // set was touching
2151
2162
  wasTouching = touching;
@@ -3748,7 +3759,6 @@ function glInit()
3748
3759
  // create the canvas and tile texture
3749
3760
  glCanvas = document.createElement('canvas');
3750
3761
  glContext = glCanvas.getContext('webgl', {antialias: false});
3751
- glCanvas.style = styleCanvas;
3752
3762
  glTileTexture = glCreateTexture(tileImage);
3753
3763
 
3754
3764
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
@@ -4444,11 +4454,8 @@ export {
4444
4454
  time,
4445
4455
  timeReal,
4446
4456
  paused,
4447
- //frameTimeLastMS,
4448
- //averageFPS,
4449
- //drawCount,
4450
- //styleBody,
4451
- //styleCanvas,
4457
+ averageFPS,
4458
+ drawCount,
4452
4459
  engineInit,
4453
4460
  //enginePreRender,
4454
4461
  //engineObjectsUpdate,
package/engine/engine.js CHANGED
@@ -22,7 +22,7 @@
22
22
  const engineName = 'LittleJS';
23
23
 
24
24
  /** Version of engine */
25
- const engineVersion = '1.4.6';
25
+ const engineVersion = '1.4.7';
26
26
 
27
27
  /** Frames per second to update objects
28
28
  * @default */
@@ -51,15 +51,11 @@ let timeReal = 0;
51
51
  let paused = 0;
52
52
 
53
53
  // Engine internal variables not exposed to documentation
54
- let frameTimeLastMS = 0, frameTimeBufferMS = 0, tileImageSize, tileImageFixBleed;
54
+ let tileImageSize, tileImageFixBleed;
55
55
 
56
56
  // Engine stat tracking, if showWatermark is true
57
57
  let averageFPS, drawCount;
58
58
 
59
- // css text used for elements created by engine
60
- const styleBody = 'margin:0;overflow:hidden;background:#000';
61
- const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
62
-
63
59
  ///////////////////////////////////////////////////////////////////////////////
64
60
 
65
61
  /** Start up LittleJS engine with your callback functions
@@ -79,11 +75,16 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
79
75
  tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
80
76
  debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
81
77
 
78
+ // setup css
79
+ const styleBody = 'margin:0;overflow:hidden;background:#000' + // fill the window
80
+ ';touch-action:none' + // prevent mobile pinch to resize
81
+ ';user-select:none' + // prevent mobile hold to select
82
+ ';-webkit-user-select:none;-moz-user-select:none'; // compatibility for mobile
83
+
82
84
  // setup html
83
85
  document.body.style = styleBody;
84
86
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
85
87
  mainContext = mainCanvas.getContext('2d');
86
- mainCanvas.style = styleCanvas;
87
88
 
88
89
  // init stuff and start engine
89
90
  debugInit();
@@ -92,13 +93,19 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
92
93
  // create overlay canvas for hud to appear above gl canvas
93
94
  document.body.appendChild(overlayCanvas = document.createElement('canvas'));
94
95
  overlayContext = overlayCanvas.getContext('2d');
95
- overlayCanvas.style = styleCanvas;
96
+
97
+ // set canvas style to fill the window
98
+ const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
99
+ (glCanvas||mainCanvas).style = overlayCanvas.style = mainCanvas.style = styleCanvas;
96
100
 
97
101
  gameInit();
98
102
  touchGamepadCreate();
99
103
  engineUpdate();
100
104
  };
101
105
 
106
+ // frame time tracking
107
+ let frameTimeLastMS = 0, frameTimeBufferMS = 0;
108
+
102
109
  // main update loop
103
110
  const engineUpdate = (frameTimeMS=0)=>
104
111
  {
@@ -320,11 +320,8 @@ export {
320
320
  time,
321
321
  timeReal,
322
322
  paused,
323
- //frameTimeLastMS,
324
- //averageFPS,
325
- //drawCount,
326
- //styleBody,
327
- //styleCanvas,
323
+ averageFPS,
324
+ drawCount,
328
325
  engineInit,
329
326
  //enginePreRender,
330
327
  //engineObjectsUpdate,
Binary file
@@ -253,6 +253,10 @@ const isTouchDevice = window.ontouchstart !== undefined;
253
253
  // try to enable touch mouse
254
254
  if (isTouchDevice)
255
255
  {
256
+ // override mouse events
257
+ const mouseDown = onmousedown, mouseUp = onmouseup, mouseMove = onmousemove;
258
+ onmousedown = onmouseup = onmousemove = (e)=> 0;
259
+
256
260
  // handle all touch events the same way
257
261
  let wasTouching, hadTouch;
258
262
  ontouchstart = ontouchmove = ontouchend = (e)=>
@@ -269,10 +273,10 @@ if (isTouchDevice)
269
273
  // set event pos and pass it along
270
274
  e.x = e.touches[0].clientX;
271
275
  e.y = e.touches[0].clientY;
272
- wasTouching ? onmousemove(e) : onmousedown(e);
276
+ wasTouching ? mouseMove(e) : mouseDown(e);
273
277
  }
274
278
  else if (wasTouching)
275
- onmouseup(e);
279
+ mouseUp(e);
276
280
 
277
281
  // set was touching
278
282
  wasTouching = touching;
@@ -159,7 +159,7 @@ let touchGamepadAnalog = 1;
159
159
  /** Size of virutal gamepad for touch devices in pixels
160
160
  * @default
161
161
  * @memberof Settings */
162
- let touchGamepadSize = 80;
162
+ let touchGamepadSize = 99;
163
163
 
164
164
  /** Transparency of touch gamepad overlay
165
165
  * @default
@@ -37,7 +37,6 @@ function glInit()
37
37
  // create the canvas and tile texture
38
38
  glCanvas = document.createElement('canvas');
39
39
  glContext = glCanvas.getContext('webgl', {antialias: false});
40
- glCanvas.style = styleCanvas;
41
40
  glTileTexture = glCreateTexture(tileImage);
42
41
 
43
42
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
package/engine/index.d.ts CHANGED
@@ -1664,7 +1664,7 @@ declare const gl_VERTEX_BYTE_STRIDE: number;
1664
1664
  /** Name of engine */
1665
1665
  declare const engineName: "LittleJS";
1666
1666
  /** Version of engine */
1667
- declare const engineVersion: "1.4.6";
1667
+ declare const engineVersion: "1.4.7";
1668
1668
  /** Frames per second to update objects
1669
1669
  * @default */
1670
1670
  declare const frameRate: 60;
@@ -1683,11 +1683,7 @@ declare let time: number;
1683
1683
  declare let timeReal: number;
1684
1684
  /** Is the game paused? Causes time and objects to not be updated. */
1685
1685
  declare let paused: number;
1686
- declare let frameTimeLastMS: number;
1687
- declare let frameTimeBufferMS: number;
1688
1686
  declare let tileImageSize: any;
1689
1687
  declare let tileImageFixBleed: any;
1690
1688
  declare let averageFPS: any;
1691
1689
  declare let drawCount: any;
1692
- declare const styleBody: "margin:0;overflow:hidden;background:#000";
1693
- declare const styleCanvas: "position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)";
@@ -5,6 +5,6 @@
5
5
  <link rel=icon type=image/png href=../../favicon.png>
6
6
  </head><body>
7
7
 
8
- <script src=../../engine/engine.all.js?118></script>
9
- <script src=gameObjects.js?118></script>
10
- <script src=game.js?118></script>
8
+ <script src=../../engine/engine.all.js?120></script>
9
+ <script src=gameObjects.js?120></script>
10
+ <script src=game.js?120></script>
@@ -6,4 +6,4 @@
6
6
  </head><body>
7
7
 
8
8
  <!-- Add your game scripts here -->
9
- <script src=game.js?118 type=module></script>
9
+ <script src=game.js?120 type=module></script>
@@ -15,18 +15,18 @@ button
15
15
  </style>
16
16
  </head><body>
17
17
 
18
- <script src=../../engine/engineDebug.js?118></script>
19
- <script src=../../engine/engineUtilities.js?118></script>
20
- <script src=../../engine/engineSettings.js?118></script>
21
- <script src=../../engine/engineObject.js?118></script>
22
- <script src=../../engine/engineDraw.js?118></script>
23
- <script src=../../engine/engineInput.js?118></script>
24
- <script src=../../engine/engineAudio.js?118></script>
25
- <script src=../../engine/engineTileLayer.js?118></script>
26
- <script src=../../engine/engineParticles.js?118></script>
27
- <script src=../../engine/engineMedals.js?118></script>
28
- <script src=../../engine/engineWebGL.js?118></script>
29
- <script src=../../engine/engine.js?118></script>
18
+ <script src=../../engine/engineDebug.js?120></script>
19
+ <script src=../../engine/engineUtilities.js?120></script>
20
+ <script src=../../engine/engineSettings.js?120></script>
21
+ <script src=../../engine/engineObject.js?120></script>
22
+ <script src=../../engine/engineDraw.js?120></script>
23
+ <script src=../../engine/engineInput.js?120></script>
24
+ <script src=../../engine/engineAudio.js?120></script>
25
+ <script src=../../engine/engineTileLayer.js?120></script>
26
+ <script src=../../engine/engineParticles.js?120></script>
27
+ <script src=../../engine/engineMedals.js?120></script>
28
+ <script src=../../engine/engineWebGL.js?120></script>
29
+ <script src=../../engine/engine.js?120></script>
30
30
  <script>
31
31
 
32
32
  'use strict';
@@ -5,9 +5,9 @@
5
5
  <link rel=icon type=image/png href=../../favicon.png>
6
6
  </head><body>
7
7
 
8
- <script src=../../engine/engine.all.js?118></script>
9
- <script src=gameObjects.js?118></script>
10
- <script src=gamePlayer.js?118></script>
11
- <script src=gameEffects.js?118></script>
12
- <script src=gameLevel.js?118></script>
13
- <script src=game.js?118></script>
8
+ <script src=../../engine/engine.all.js?120></script>
9
+ <script src=gameObjects.js?120></script>
10
+ <script src=gamePlayer.js?120></script>
11
+ <script src=gameEffects.js?120></script>
12
+ <script src=gameLevel.js?120></script>
13
+ <script src=game.js?120></script>
@@ -5,5 +5,5 @@
5
5
  <link rel=icon type=image/png href=../../favicon.png>
6
6
  </head><body>
7
7
 
8
- <script src=../../engine/engine.all.js?118></script>
9
- <script src=game.js?118></script>
8
+ <script src=../../engine/engine.all.js?120></script>
9
+ <script src=game.js?120></script>
@@ -5,7 +5,7 @@
5
5
  <link rel=icon type=image/png href=../../favicon.png>
6
6
  </head><body>
7
7
 
8
- <script src=../../engine/engine.all.min.js?118></script>
8
+ <script src=../../engine/engine.all.min.js?120></script>
9
9
  <script>
10
10
 
11
11
  /*
package/index.html CHANGED
@@ -6,21 +6,21 @@
6
6
  </head><body>
7
7
 
8
8
  <!-- LittleJS engine scripts -->
9
- <script src=engine/engineDebug.js?118></script>
10
- <script src=engine/engineUtilities.js?118></script>
11
- <script src=engine/engineSettings.js?118></script>
12
- <script src=engine/engineObject.js?118></script>
13
- <script src=engine/engineDraw.js?118></script>
14
- <script src=engine/engineInput.js?118></script>
15
- <script src=engine/engineAudio.js?118></script>
16
- <script src=engine/engineTileLayer.js?118></script>
17
- <script src=engine/engineParticles.js?118></script>
18
- <script src=engine/engineMedals.js?118></script>
19
- <script src=engine/engineWebGL.js?118></script>
20
- <script src=engine/engine.js?118></script>
9
+ <script src=engine/engineDebug.js?120></script>
10
+ <script src=engine/engineUtilities.js?120></script>
11
+ <script src=engine/engineSettings.js?120></script>
12
+ <script src=engine/engineObject.js?120></script>
13
+ <script src=engine/engineDraw.js?120></script>
14
+ <script src=engine/engineInput.js?120></script>
15
+ <script src=engine/engineAudio.js?120></script>
16
+ <script src=engine/engineTileLayer.js?120></script>
17
+ <script src=engine/engineParticles.js?120></script>
18
+ <script src=engine/engineMedals.js?120></script>
19
+ <script src=engine/engineWebGL.js?120></script>
20
+ <script src=engine/engine.js?120></script>
21
21
 
22
22
  <!-- You can also include all engine scripts like this... -->
23
23
  <!-- <script src=engine/engine.all.js></script> -->
24
24
 
25
25
  <!-- Add your game scripts here -->
26
- <script src=game.js?118></script>
26
+ <script src=game.js?120></script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "littlejsengine",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
4
4
  "description": "LittleJS - Tiny and Fast HTML5 Game Engine",
5
5
  "main": "engine/engine.all.module.js",
6
6
  "repository": {