littlejsengine 1.4.5 → 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.
@@ -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.5';
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.5",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.5';
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
  {
@@ -4476,8 +4486,11 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
4476
4486
  pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
4477
4487
  }
4478
4488
  }
4479
-
4480
- // This file contains exports neessary when LittleJS is used as a module
4489
+
4490
+ /**
4491
+ * LittleJS Module Export
4492
+ * <br> - Export engine as a module with extra functions where necessary
4493
+ */
4481
4494
 
4482
4495
  // setters for all variables that devs will need to modify
4483
4496
  const setCameraPos = (v)=> cameraPos = v;
@@ -4519,11 +4532,9 @@ const setMedalDisplayWidth = (v)=> medalDisplayWidth = v;
4519
4532
  const setMedalDisplayHeight = (v)=> medalDisplayHeight = v;
4520
4533
  const setMedalDisplayIconSize = (v)=> medalDisplayIconSize = v;
4521
4534
  const setMedalsPreventUnlock = (v)=> medalsPreventUnlock = v;
4522
- const setDebugPointSize = (v)=> debugPointSize = v;
4523
4535
  const setShowWatermark = (v)=> showWatermark = v;
4524
4536
  const setGodMode = (v)=> godMode = v;
4525
4537
 
4526
- // This is a full list of everything in engine.all
4527
4538
  export {
4528
4539
  // Custom methods
4529
4540
  setCameraPos,
@@ -4565,7 +4576,6 @@ export {
4565
4576
  setMedalDisplayHeight,
4566
4577
  setMedalDisplayIconSize,
4567
4578
  setMedalsPreventUnlock,
4568
- setDebugPointSize,
4569
4579
  setShowWatermark,
4570
4580
  setGodMode,
4571
4581
 
@@ -4610,7 +4620,6 @@ export {
4610
4620
 
4611
4621
  // Globals
4612
4622
  debug,
4613
- debugPointSize,
4614
4623
  showWatermark,
4615
4624
  godMode,
4616
4625
  // Debug
@@ -4651,6 +4660,7 @@ export {
4651
4660
  isOverlapping,
4652
4661
  wave,
4653
4662
  formatTime,
4663
+
4654
4664
  // Random
4655
4665
  rand,
4656
4666
  randInt,
@@ -4661,13 +4671,13 @@ export {
4661
4671
  randSeed,
4662
4672
  randSeeded,
4663
4673
 
4664
- // More utilities
4665
- vec2,
4674
+ // Utility Classes
4666
4675
  Vector2,
4667
- colorRGBA,
4668
- colorHSLA,
4669
4676
  Color,
4670
4677
  Timer,
4678
+ vec2,
4679
+ colorRGBA,
4680
+ colorHSLA,
4671
4681
 
4672
4682
  // Base
4673
4683
  EngineObject,
@@ -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.5';
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.5';
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
@@ -4121,8 +4131,11 @@ gl_VERTICES_PER_QUAD = 6,
4121
4131
  gl_INDICIES_PER_VERT = 6,
4122
4132
  gl_MAX_BATCH = 1<<16,
4123
4133
  gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
4124
-
4125
- // This file contains exports neessary when LittleJS is used as a module
4134
+
4135
+ /**
4136
+ * LittleJS Module Export
4137
+ * <br> - Export engine as a module with extra functions where necessary
4138
+ */
4126
4139
 
4127
4140
  // setters for all variables that devs will need to modify
4128
4141
  const setCameraPos = (v)=> cameraPos = v;
@@ -4164,11 +4177,9 @@ const setMedalDisplayWidth = (v)=> medalDisplayWidth = v;
4164
4177
  const setMedalDisplayHeight = (v)=> medalDisplayHeight = v;
4165
4178
  const setMedalDisplayIconSize = (v)=> medalDisplayIconSize = v;
4166
4179
  const setMedalsPreventUnlock = (v)=> medalsPreventUnlock = v;
4167
- const setDebugPointSize = (v)=> debugPointSize = v;
4168
4180
  const setShowWatermark = (v)=> showWatermark = v;
4169
4181
  const setGodMode = (v)=> godMode = v;
4170
4182
 
4171
- // This is a full list of everything in engine.all
4172
4183
  export {
4173
4184
  // Custom methods
4174
4185
  setCameraPos,
@@ -4210,7 +4221,6 @@ export {
4210
4221
  setMedalDisplayHeight,
4211
4222
  setMedalDisplayIconSize,
4212
4223
  setMedalsPreventUnlock,
4213
- setDebugPointSize,
4214
4224
  setShowWatermark,
4215
4225
  setGodMode,
4216
4226
 
@@ -4255,7 +4265,6 @@ export {
4255
4265
 
4256
4266
  // Globals
4257
4267
  debug,
4258
- debugPointSize,
4259
4268
  showWatermark,
4260
4269
  godMode,
4261
4270
  // Debug
@@ -4296,6 +4305,7 @@ export {
4296
4305
  isOverlapping,
4297
4306
  wave,
4298
4307
  formatTime,
4308
+
4299
4309
  // Random
4300
4310
  rand,
4301
4311
  randInt,
@@ -4306,13 +4316,13 @@ export {
4306
4316
  randSeed,
4307
4317
  randSeeded,
4308
4318
 
4309
- // More utilities
4310
- vec2,
4319
+ // Utility Classes
4311
4320
  Vector2,
4312
- colorRGBA,
4313
- colorHSLA,
4314
4321
  Color,
4315
4322
  Timer,
4323
+ vec2,
4324
+ colorRGBA,
4325
+ colorHSLA,
4316
4326
 
4317
4327
  // Base
4318
4328
  EngineObject,
@@ -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.5';
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
  {
@@ -94,8 +94,27 @@ if %ERRORLEVEL% NEQ 0 (
94
94
  )
95
95
 
96
96
  rem --- BUILD TYPESCRIPT DEFINITIONS ---
97
+
97
98
  call npx tsc engine.all.js --declaration --allowJs --emitDeclarationOnly --outFile index.d.ts
98
99
  if %ERRORLEVEL% NEQ 0 (
99
100
  pause
100
101
  exit /b %ERRORLEVEL%
101
- )
102
+ )
103
+
104
+ rem --- BUILD ENGINE MODULE ---
105
+
106
+ set OUTPUT_FILENAME=engine.all.module.js
107
+ del %OUTPUT_FILENAME%
108
+ type engine.all.js >> %OUTPUT_FILENAME%
109
+ echo.>> %OUTPUT_FILENAME%
110
+ type engineExport.js >> %OUTPUT_FILENAME%
111
+ echo.>> %OUTPUT_FILENAME%
112
+
113
+ rem --- BUILD ENGINE MODULE RELEASE ---
114
+
115
+ set OUTPUT_FILENAME=engine.all.release.module.js
116
+ del %OUTPUT_FILENAME%
117
+ type engine.all.release.js >> %OUTPUT_FILENAME%
118
+ echo.>> %OUTPUT_FILENAME%
119
+ type engineExport.js >> %OUTPUT_FILENAME%
120
+ echo.>> %OUTPUT_FILENAME%
@@ -1,4 +1,7 @@
1
- // This file contains exports neessary when LittleJS is used as a module
1
+ /**
2
+ * LittleJS Module Export
3
+ * <br> - Export engine as a module with extra functions where necessary
4
+ */
2
5
 
3
6
  // setters for all variables that devs will need to modify
4
7
  const setCameraPos = (v)=> cameraPos = v;
@@ -40,11 +43,9 @@ const setMedalDisplayWidth = (v)=> medalDisplayWidth = v;
40
43
  const setMedalDisplayHeight = (v)=> medalDisplayHeight = v;
41
44
  const setMedalDisplayIconSize = (v)=> medalDisplayIconSize = v;
42
45
  const setMedalsPreventUnlock = (v)=> medalsPreventUnlock = v;
43
- const setDebugPointSize = (v)=> debugPointSize = v;
44
46
  const setShowWatermark = (v)=> showWatermark = v;
45
47
  const setGodMode = (v)=> godMode = v;
46
48
 
47
- // This is a full list of everything in engine.all
48
49
  export {
49
50
  // Custom methods
50
51
  setCameraPos,
@@ -86,7 +87,6 @@ export {
86
87
  setMedalDisplayHeight,
87
88
  setMedalDisplayIconSize,
88
89
  setMedalsPreventUnlock,
89
- setDebugPointSize,
90
90
  setShowWatermark,
91
91
  setGodMode,
92
92
 
@@ -131,7 +131,6 @@ export {
131
131
 
132
132
  // Globals
133
133
  debug,
134
- debugPointSize,
135
134
  showWatermark,
136
135
  godMode,
137
136
  // Debug
@@ -172,6 +171,7 @@ export {
172
171
  isOverlapping,
173
172
  wave,
174
173
  formatTime,
174
+
175
175
  // Random
176
176
  rand,
177
177
  randInt,
@@ -182,13 +182,13 @@ export {
182
182
  randSeed,
183
183
  randSeeded,
184
184
 
185
- // More utilities
186
- vec2,
185
+ // Utility Classes
187
186
  Vector2,
188
- colorRGBA,
189
- colorHSLA,
190
187
  Color,
191
188
  Timer,
189
+ vec2,
190
+ colorRGBA,
191
+ colorHSLA,
192
192
 
193
193
  // Base
194
194
  EngineObject,
@@ -320,14 +320,11 @@ 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,
331
328
  engineObjectsDestroy,
332
329
  engineObjectsCallback,
333
- };
330
+ };
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.5";
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.5",
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": {
@@ -23,14 +23,13 @@
23
23
  },
24
24
  "homepage": "https://github.com/KilledByAPixel/LittleJS",
25
25
  "scripts": {
26
- "build:esm": "node --experimental-modules engine/engineBuildModule.mjs",
27
- "build": "build.bat && npm run build:esm && npm run check-filesize"
26
+ "build": "build.bat"
28
27
  },
29
28
  "dependencies": {
30
29
  "ect-bin": "^1.4.1",
31
30
  "google-closure-compiler": "^20221102.0.1",
32
31
  "roadroller": "^2.1.0",
33
- "typescript": "^4.9.4",
32
+ "typescript": "^4.9.5",
34
33
  "uglify-js": "^3.17.4"
35
34
  }
36
35
  }
@@ -1,14 +0,0 @@
1
- import fs from 'fs'; // node
2
-
3
- const files = [
4
- { engine: './engine/engine.all.js', output: './engine/engine.all.module.js' },
5
- { engine: './engine/engine.all.release.js', output: './engine/engine.all.release.module.js' },
6
- ];
7
-
8
- const footerFile = fs.readFileSync('./engine/engineExportModule.js');
9
-
10
- files.forEach(({ engine, output }) => {
11
- const engineFile = fs.readFileSync(engine);
12
- const file = [engineFile, footerFile].join('\n');
13
- fs.writeFileSync(output, file);
14
- });