From 00a9ba0544a08b002ca4cbd01d8682a7597315c8 Mon Sep 17 00:00:00 2001 From: Frank Force Date: Thu, 29 Jun 2023 15:59:51 -0500 Subject: [PATCH] typescript support remove add Music.stop() and Music.isPlaying() add typescript example folder some fixes for type issues --- build.bat | 10 +- engine/engine.all.js | 39 +- engine/engine.all.min.js | 2 +- engine/engine.all.module.js | 39 +- engine/engine.all.module.min.js | 2 +- engine/engine.all.release.js | 31 +- engine/engine.js | 2 +- engine/engineAudio.js | 15 +- engine/engineBuild.bat | 16 +- engine/engineDebug.js | 8 +- engine/engineDraw.js | 2 +- engine/engineInput.js | 2 +- engine/engineObject.js | 8 +- engine/engineParticles.js | 2 +- engine/index.d.ts | 3261 ++++++++++++++--------------- examples/breakout/index.html | 2 +- examples/empty/index.html | 5 +- examples/module/game.js | 5 +- examples/module/index.html | 2 +- examples/particles/index.html | 2 +- examples/platformer/index.html | 2 +- examples/puzzle/index.html | 2 +- examples/stress/index.html | 2 +- examples/typescript/build.bat | 14 + examples/typescript/game.js | 89 + examples/typescript/game.ts | 117 ++ examples/typescript/index.html | 9 + examples/typescript/tiles.png | Bin 0 -> 2436 bytes examples/typescript/tsconfig.json | 8 + index.html | 2 +- package.json | 2 +- 31 files changed, 1953 insertions(+), 1749 deletions(-) create mode 100644 examples/typescript/build.bat create mode 100644 examples/typescript/game.js create mode 100644 examples/typescript/game.ts create mode 100644 examples/typescript/index.html create mode 100644 examples/typescript/tiles.png create mode 100644 examples/typescript/tsconfig.json diff --git a/build.bat b/build.bat index f0cc2878..dd10d826 100644 --- a/build.bat +++ b/build.bat @@ -39,6 +39,7 @@ if %ERRORLEVEL% NEQ 0 ( pause exit /b %ERRORLEVEL% ) +del temp_%BUILD_FILENAME% rem more minification with uglify or terser (they both do about the same) call npx uglifyjs -o %BUILD_FILENAME% --compress --mangle -- %BUILD_FILENAME% @@ -49,8 +50,7 @@ if %ERRORLEVEL% NEQ 0 ( ) rem roadroller compresses the code better then zip -copy %BUILD_FILENAME% roadroller_%BUILD_FILENAME% -call npx roadroller roadroller_%BUILD_FILENAME% -o roadroller_%BUILD_FILENAME% +call npx roadroller %BUILD_FILENAME% -o %BUILD_FILENAME% if %ERRORLEVEL% NEQ 0 ( pause exit /b %ERRORLEVEL% @@ -58,14 +58,12 @@ if %ERRORLEVEL% NEQ 0 ( rem build the html, you can add html header and footers here rem type ..\header.html >> index.html -echo ^^^ >> index.html -type roadroller_%BUILD_FILENAME% >> index.html +echo ^^ >> index.html +type %BUILD_FILENAME% >> index.html echo ^ >> index.html rem delete intermediate files del %BUILD_FILENAME% -del temp_%BUILD_FILENAME% -del roadroller_%BUILD_FILENAME% rem zip the result, ect is recommended call ..\node_modules\ect-bin\vendor\win32\ect.exe -9 -strip -zip ..\%NAME%.zip index.html tiles.png diff --git a/engine/engine.all.js b/engine/engine.all.js index 317232fe..c2eca06f 100644 --- a/engine/engine.all.js +++ b/engine/engine.all.js @@ -59,9 +59,9 @@ const ASSERT = enableAsserts ? (...assert)=> console.assert(...assert) : ()=>{}; * @param {String} [color='#fff'] * @param {Number} [time=0] * @param {Number} [angle=0] - * @param {Boolean} [fill=0] + * @param {Boolean} [fill=false] * @memberof Debug */ -const debugRect = (pos, size=vec2(), color='#fff', time=0, angle=0, fill=0)=> +const debugRect = (pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)=> { ASSERT(typeof color == 'string'); // pass in regular html strings as colors debugPrimitives.push({pos, size:vec2(size), color, time:new Timer(time), angle, fill}); @@ -72,9 +72,9 @@ const debugRect = (pos, size=vec2(), color='#fff', time=0, angle=0, fill=0)=> * @param {Number} [radius=0] * @param {String} [color='#fff'] * @param {Number} [time=0] - * @param {Boolean} [fill=0] + * @param {Boolean} [fill=false] * @memberof Debug */ -const debugCircle = (pos, radius=0, color='#fff', time=0, fill=0)=> +const debugCircle = (pos, radius=0, color='#fff', time=0, fill=false)=> { ASSERT(typeof color == 'string'); // pass in regular html strings as colors debugPrimitives.push({pos, size:radius, color, time:new Timer(time), angle:0, fill}); @@ -1528,9 +1528,9 @@ class EngineObject } /** Set how this object collides - * @param {boolean} [collideSolidObjects=1] - Does it collide with solid objects - * @param {boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers) - * @param {boolean} [collideTiles=1] - Does it collide with the tile collision */ + * @param {Boolean} [collideSolidObjects=1] - Does it collide with solid objects + * @param {Boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers) + * @param {Boolean} [collideTiles=1] - Does it collide with the tile collision */ setCollision(collideSolidObjects=1, isSolid=1, collideTiles=1) { ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide @@ -1540,6 +1540,8 @@ class EngineObject this.collideTiles = collideTiles; } + /** Returns string containg info about this object for debugging + * @return {String} */ toString() { if (debug) @@ -1609,7 +1611,7 @@ let overlayContext; let mainCanvasSize = vec2(); /** Tile sheet for batch rendering system - * @type {Image} + * @type {CanvasImageSource} * @memberof Draw */ const tileImage = new Image; @@ -2190,7 +2192,7 @@ const vibrateStop = ()=> vibrate(0); // Touch input /** True if a touch device has been detected - * @const {boolean} + * @const {Boolean} * @memberof Input */ const isTouchDevice = window.ontouchstart !== undefined; @@ -2520,8 +2522,21 @@ class Music { if (!soundEnable) return; - return playSamples(this.cachedSamples, volume, 1, 0, loop); + this.source = playSamples(this.cachedSamples, volume, 1, 0, loop); } + + /** Stop the music */ + stop() + { + if (this.source) + this.source.stop(); + this.source = 0; + } + + /** Check if music is playing + * @return {Boolean} + */ + isPlaying() { return this.source; } } /** Play an mp3 or wav audio from a local file or url @@ -3212,7 +3227,7 @@ class ParticleEmitter extends EngineObject * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0 * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter * @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied - * @param {Number} [tileSize=tileSizeDefault] - Tile size for particles + * @param {Vector2} [tileSize=tileSizeDefault] - Tile size for particles * @param {Color} [colorStartA=new Color(1,1,1)] - Color at start of life 1, randomized between start colors * @param {Color} [colorStartB=new Color(1,1,1)] - Color at start of life 2, randomized between start colors * @param {Color} [colorEndA=new Color(1,1,1,0)] - Color at end of life 1, randomized between end colors @@ -4190,7 +4205,7 @@ gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STR const engineName = 'LittleJS'; /** Version of engine */ -const engineVersion = '1.4.9'; +const engineVersion = '1.5.0'; /** Frames per second to update objects * @default */ diff --git a/engine/engine.all.min.js b/engine/engine.all.min.js index 3620e955..93e9f17a 100644 --- a/engine/engine.all.min.js +++ b/engine/engine.all.min.js @@ -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)=>aa>b?a:b,sign=a=>0>a?-1:1,mod=(a,b=1)=>(a%b+b)%b,clamp=(a,b=0,c=1)=>ac?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/2*(1-Math.cos(c*a*2*PI)),formatTime=a=>(a/60|0)+":"+(10>a%60?"0":"")+(a%60|0),rand=(a=1,b=0)=>b+(a-b)*Math.random(),randInt=(a=1,b=0)=>rand(a,b)|0,randSign=()=>2*randInt(2)-1,randInCircle=(a=1,b=0)=>0(new Vector2).setAngle(rand(2*PI),a),randColor=(a=new Color,b=new Color(0,0,0,1),c)=>c?a.lerp(b,rand()):new Color(rand(a.r,b.r),rand(a.g,b.g),rand(a.b,b.b),rand(a.a,b.a));let randSeed=1;const setRandSeed=a=>randSeed=a,randSeeded=(a=1,b=0)=>{randSeed^=randSeed<<13;randSeed^=randSeed>>>17;randSeed^=randSeed<<5;return b+(a-b)*abs(randSeed%1e9)/1e9},vec2=(a=0,b)=>void 0==a.x?new Vector2(a,void 0==b?a:b):new Vector2(a.x,a.y),isVector2=a=>!isNaN(a.x)&&!isNaN(a.y);class Vector2{constructor(a=0,b=0){this.x=a;this.y=b}copy(){return new Vector2(this.x,this.y)}add(a){ASSERT(isVector2(a));return new Vector2(this.x+a.x,this.y+a.y)}subtract(a){ASSERT(isVector2(a));return new Vector2(this.x-a.x,this.y-a.y)}multiply(a){ASSERT(isVector2(a));return new Vector2(this.x*a.x,this.y*a.y)}divide(a){ASSERT(isVector2(a));return new Vector2(this.x/a.x,this.y/a.y)}scale(a){ASSERT(!isVector2(a));return new Vector2(this.x*a,this.y*a)}length(){return this.lengthSquared()**.5}lengthSquared(){return this.x**2+this.y**2}distance(a){return this.distanceSquared(a)**.5}distanceSquared(a){return(this.x-a.x)**2+(this.y-a.y)**2}normalize(a=1){const b=this.length();return b?this.scale(a/b):new Vector2(0,a)}clampLength(a=1){const b=this.length();return b>a?this.scale(a/b):this}dot(a){ASSERT(isVector2(a));return this.x*a.x+this.y*a.y}cross(a){ASSERT(isVector2(a));return this.x*a.y-this.y*a.x}angle(){return Math.atan2(this.x,this.y)}setAngle(a=0,b=1){this.x=b*Math.sin(a);this.y=b*Math.cos(a);return this}rotate(a){const b=Math.cos(a);a=Math.sin(a);return new Vector2(this.x*b-this.y*a,this.x*a+this.y*b)}direction(){return abs(this.x)>abs(this.y)?0>this.x?3:1:0>this.y?2:0}invert(){return new Vector2(this.y,-this.x)}floor(){return new Vector2(Math.floor(this.x),Math.floor(this.y))}area(){return abs(this.x*this.y)}lerp(a,b){ASSERT(isVector2(a));return this.add(a.subtract(this).scale(clamp(b)))}arrayCheck(a){return 0<=this.x&&0<=this.y&&this.xthis.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,g,k)=>(k=(k%1+1)%1)<1/6?f+6*(g-f)*k:.5>k?g:k<2/3?f+(g-f)*(2/3-k)*6:f;this.r=e(c,b,a+1/3);this.g=e(c,b,a);this.b=e(c,b,a-1/3);this.a=d;return this}getHSLA(){const a=clamp(this.r),b=clamp(this.g),c=clamp(this.b),d=clamp(this.a),e=Math.max(a,b,c),f=Math.min(a,b,c),g=(e+f)/2;let k=0,h=0;if(e!=f){let l=e-f;h=.5(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=7this.time}get(){return this.isSet()?time-this.time:0}getPercent(){return this.isSet()?percent(this.time-time,this.setTime,0):0}toString(){if(debug)return this.unset()?"unset":Math.abs(this.get())+" seconds "+(0>this.get()?"before":"after")}valueOf(){return this.get()}}"use strict";let cameraPos=vec2(),cameraScale=32,canvasMaxSize=vec2(1920,1200),canvasFixedSize=vec2(),cavasPixelated=1,fontDefault="arial",glEnable=1,glOverlay=1,tileSizeDefault=vec2(16),tileFixBleedScale=.3,objectDefaultSize=vec2(1),enablePhysicsSolver=1,objectDefaultMass=1,objectDefaultDamping=.99,objectDefaultAngleDamping=.99,objectDefaultElasticity=0,objectDefaultFriction=.8,objectMaxSpeed=1,gravity=0,particleEmitRateScale=1,gamepadsEnable=1,gamepadDirectionEmulateStick=1,inputWASDEmulateDirection=1,touchGamepadEnable=0,touchGamepadAnalog=1,touchGamepadSize=99,touchGamepadAlpha=.3,vibrateEnable=1,soundEnable=1,soundVolume=.5,soundDefaultRange=40,soundDefaultTaper=.7,medalDisplayTime=5,medalDisplaySlideTime=.5,medalDisplaySize=vec2(640,80),medalDisplayIconSize=50,medalsPreventUnlock;"use strict";const engineName="LittleJS",engineVersion="1.4.9",frameRate=60,timeDelta=1/frameRate;let engineObjects=[],engineObjectsCollide=[],frame=0,time=0,timeReal=0,paused=0;function setPaused(a){paused=a}function engineInit(a,b,c,d,e,f){function g(m=0){var n=m-k;k=m;if(debug||showWatermark)l=lerp(.05,l,1e3/(n||1));m=debug&&keyIsDown(107);const p=debug&&keyIsDown(109);debug&&(n*=m?5:p?.2:1);timeReal+=n/1e3;h+=!paused*n;m||(h=min(h,50));canvasFixedSize.x?(overlayCanvas.width=mainCanvas.width=canvasFixedSize.x,overlayCanvas.height=mainCanvas.height=canvasFixedSize.y,n=innerWidth/innerHeight,m=mainCanvas.width/mainCanvas.height,(glCanvas||mainCanvas).style.width=mainCanvas.style.width=overlayCanvas.style.width=nh&&-9r.renderOrder-w.renderOrder);for(var q of engineObjects)q.destroyed||q.render();e();glRenderPostProcess();medalsRender();touchGamepadRender();debugRender();glEnable&&glCopyToContext(mainContext);showWatermark&&(overlayContext.textAlign="right",overlayContext.textBaseline="top",overlayContext.font="1em monospace",overlayContext.fillStyle="#000",q=engineName+" v"+engineVersion+" / "+drawCount+" / "+engineObjects.length+" / "+l.toFixed(1)+(glEnable?" GL":" 2D"),overlayContext.fillText(q,mainCanvas.width-3,3),overlayContext.fillStyle="#fff",overlayContext.fillText(q,mainCanvas.width-2,2),drawCount=0);requestAnimationFrame(g)}tileImage.onerror=tileImage.onload=()=>{tileImageFixBleed=vec2(tileFixBleedScale).divide(tileImageSize=vec2(tileImage.width,tileImage.height));debug&&(tileImage.onload=()=>ASSERT(1));document.body.style="margin:0;overflow:hidden;background:#000;touch-action:none;user-select:none;-webkit-user-select:none";document.body.appendChild(mainCanvas=document.createElement("canvas"));mainContext=mainCanvas.getContext("2d");debugInit();glEnable&&glInit();document.body.appendChild(overlayCanvas=document.createElement("canvas"));overlayContext=overlayCanvas.getContext("2d");(glCanvas||mainCanvas).style=mainCanvas.style=overlayCanvas.style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)";a();touchGamepadCreate();g()};let k=0,h,l;f?tileImage.src=f:tileImage.onload()}function enginePreRender(){mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height);mainContext.imageSmoothingEnabled=!cavasPixelated;glEnable&&glPreRender()}function engineObjectsUpdate(){engineObjectsCollide=engineObjects.filter(b=>b.collideSolidObjects);const a=b=>{if(!b.destroyed){b.update();for(const c of b.children)a(c)}};for(const b of engineObjects)b.parent||a(b);engineObjects=engineObjects.filter(b=>!b.destroyed);time=++frame/frameRate}function engineObjectsDestroy(){for(const a of engineObjects)a.parent||a.destroy();engineObjects=engineObjects.filter(a=>!a.destroyed)}function engineObjectsCallback(a,b,c,d=engineObjects){if(a)if(void 0!=b.x)for(const e of d)isOverlapping(a,b,e.pos,e.size)&&c(e);else{b*=b;for(const e of d)a.distanceSquared(e.pos)=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)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{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,g,k=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 l=tileImageSize.x/d.x|0;h=d.x/tileImageSize.x;const m=d.y/tileImageSize.y,n=c%l*h;l=(c/l|0)*m;glDraw(a.x,a.y,g?-b.x:b.x,b.y,f,n+tileImageFixBleed.x,l+tileImageFixBleed.y,n-tileImageFixBleed.x+h,l-tileImageFixBleed.y+m,e.rgbaInt(),k.rgbaInt())}else drawCanvas2D(a,b,f,g,m=>{if(0>c)m.fillStyle=e,m.fillRect(-.5,-.5,1,1);else{var n=tileImageSize.x/d.x|0;const p=c%n*d.x+tileFixBleedScale;n=(c/n|0)*d.y+tileFixBleedScale;const q=d.x-2*tileFixBleedScale,r=d.y-2*tileFixBleedScale;m.globalAlpha=e.a;m.drawImage(tileImage,p,n,q,r,-.5,-.5,1,1)}})}function drawRect(a,b,c,d,e){drawTile(a,b,-1,tileSizeDefault,c,d,0,0,e)}function drawTileScreenSpace(a,b=vec2(1),c,d,e,f,g,k,h){drawTile(screenToWorld(a),b.scale(1/cameraScale),c,d,e,f,g,k,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),g="center",k=fontDefault,h=overlayContext){h.fillStyle=d;h.lineWidth=e;h.strokeStyle=f;h.textAlign=g;h.font=c+"px "+k;h.textBaseline="middle";h.lineJoin="round";b=b.copy();(a+"").split("\n").forEach(l=>{e&&h.strokeText(l,b.x,b.y);h.fillText(l,b.x,b.y);b.y+=c})}function drawText(a,b,c=1,d,e,f,g,k){drawTextScreen(a,worldToScreen(b),c*cameraScale,d,e*cameraScale,f,g,k,mainContext)}let engineFontImage;class FontImage{constructor(a,b=vec2(8),c=vec2(0,1),d=0,e=overlayContext){engineFontImage||((engineFontImage=new Image).src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC");this.image=a||engineFontImage;this.tileSize=b;this.paddingSize=c;this.startTileIndex=d;this.context=e}drawTextScreen(a,b,c=4,d){const e=this.context;e.save();e.imageSmoothingEnabled=!cavasPixelated;const f=this.tileSize,g=f.add(this.paddingSize).scale(c),k=this.image.width/this.tileSize.x|0;(a+"").split("\n").forEach((h,l)=>{const m=d?h.length*f.x*c/2|0:0;for(let q=h.length;q--;){var n=h[q].charCodeAt();if(32>n||127document.fullscreenElement;function toggleFullscreen(){isFullscreen()?document.exitFullscreen&&document.exitFullscreen():document.body.requestFullscreen&&document.body.requestFullscreen()}"use strict";const keyIsDown=(a,b=0)=>inputData[b]&&inputData[b][a]&1,keyWasPressed=(a,b=0)=>inputData[b]&&inputData[b][a]&2?1:0,keyWasReleased=(a,b=0)=>inputData[b]&&inputData[b][a]&4?1:0,clearInput=()=>inputData=[[]],mouseIsDown=keyIsDown,mouseWasPressed=keyWasPressed,mouseWasReleased=keyWasReleased;let mousePos=vec2(),mousePosScreen=vec2(),mouseWheel=0,isUsingGamepad=0,preventDefaultInput=0;const gamepadIsDown=(a,b=0)=>keyIsDown(a,b+1),gamepadWasPressed=(a,b=0)=>keyWasPressed(a,b+1),gamepadWasReleased=(a,b=0)=>keyWasReleased(a,b+1),gamepadStick=(a,b=0)=>stickData[b]?stickData[b][a]||vec2():vec2();let inputData=[[]];function inputUpdate(){isTouchDevice||document.hasFocus()||clearInput();mousePos=screenToWorld(mousePosScreen);gamepadsUpdate()}function inputUpdatePost(){for(const a of inputData)for(const b in a)a[b]&=1;mouseWheel=0}onkeydown=a=>{debug&&a.target!=document.body||(a.repeat||(inputData[isUsingGamepad=0][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 g=inputData[b+1]||(inputData[b+1]=[]);c=stickData[b]||(stickData[b]=[]);if(d){var e=k=>.3k?-percent(-k,.3,.8):0;for(var f=0;f>1]=vec2(e(d.axes[f]),e(-d.axes[f+1])).clampLength();for(e=d.buttons.length;e--;)f=d.buttons[e],g[e]=f.pressed?1+2*!gamepadIsDown(e,b):4*gamepadIsDown(e,b),isUsingGamepad|=!b&&f.pressed,touchGamepadEnable&&touchGamepadTimer.unset();gamepadDirectionEmulateStick&&(d=vec2(gamepadIsDown(15,b)-gamepadIsDown(14,b),gamepadIsDown(12,b)-gamepadIsDown(13,b)),d.lengthSquared()&&(c[0]=d.clampLength()))}}}const vibrate=a=>vibrateEnable&&navigator&&navigator.vibrate&&navigator.vibrate(a),vibrateStop=()=>vibrate(0),isTouchDevice=void 0!==window.ontouchstart;if(isTouchDevice){let a,b=onmousedown,c=onmouseup;onmousedown=onmouseup=()=>0;ontouchstart=d=>{zzfx(0);ontouchstart=ontouchmove=ontouchend=e=>{e.button=0;const f=e.touches.length;f?(e.x=e.touches[0].clientX,e.y=e.touches[0].clientY,a?onmousemove(e):b(e)):a&&c(e);a=f;return!0};return ontouchstart(d)}}let touchGamepadTimer=new Timer,touchGamepadButtons,touchGamepadStick;function touchGamepadCreate(){touchGamepadEnable&&isTouchDevice&&(touchGamepadButtons=[],touchGamepadStick=vec2(),ontouchstart=a=>{zzfx(0);ontouchstart=ontouchmove=ontouchend=b=>{touchGamepadStick=vec2();touchGamepadButtons=[];if(b.touches.length&&(isUsingGamepad=1,touchGamepadTimer.set(),paused)){touchGamepadButtons[9]=1;return}const c=vec2(touchGamepadSize,mainCanvasSize.y-touchGamepadSize),d=mainCanvasSize.subtract(vec2(touchGamepadSize,touchGamepadSize)),e=mainCanvasSize.scale(.5);for(const f of b.touches)b=mouseToScreen(vec2(f.clientX,f.clientY)),b.distance(c)e*e)return;b*=percent(f**.5,e,e*this.taper)}e=2*worldToScreen(a).x/mainCanvas.width-1}a=c+c*this.randomness*d*rand(-1,1);return playSamples([this.cachedSamples],b,a,e)}}playNote(a,b,c){if(soundEnable)return this.play(b,c,2**(a/12),0)}}class Music{constructor(a){soundEnable&&(this.cachedSamples=zzfxM(...a))}play(a,b=1){if(soundEnable)return 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 AudioContext,audioContext.resume(),"running"==audioContext.state)){var f=audioContext.createBuffer(a.length,a[0].length,zzfxR),g=audioContext.createBufferSource();a.forEach((k,h)=>f.getChannelData(h).set(k));g.buffer=f;g.playbackRate.value=c;g.loop=e;a=audioContext.createGain();a.gain.value=soundVolume*b;a.connect(audioContext.destination);g.connect(new StereoPannerNode(audioContext,{pan:clamp(d,-1,1)})).connect(a);g.start();return g}}const zzfx=(...a)=>playSamples([zzfxG(...a)]),zzfxR=44100;function zzfxG(a=1,b=.05,c=220,d=0,e=0,f=.1,g=0,k=1,h=0,l=0,m=0,n=0,p=0,q=0,r=0,w=0,u=0,B=1,z=0,C=0){let v=2*PI,F=h*=500*v/zzfxR/zzfxR,A=[];b=c*=(1+b*rand(-1,1))*v/zzfxR;let x=0,D=0,t=0,y=1,G=0,I=0,E=0,J,H;d=d*zzfxR+9;z*=zzfxR;e*=zzfxR;f*=zzfxR;u*=zzfxR;l*=500*v/zzfxR**3;r*=v/zzfxR;m*=v/zzfxR;n*=zzfxR;p=p*zzfxR|0;for(H=d+z+e+f+u|0;tt?0:(tn&&(c+=m,b+=m,y=0),!p||++G%p||(c=b,h=F,y=y||1);return A}function zzfxM(a,b,c,d=125){let e,f,g,k,h,l,m,n,p,q,r,w,u,B=0,z,C=[],v=[],F=[],A=0,x=0,D=1,t={},y=zzfxR/d*60>>2;for(;D;A++)C=[D=n=w=0],c.forEach((G,I)=>{m=b[G][A]||[0,0,0];D|=!!b[G][A];z=w+(b[G][0].length-2-!n)*y;u=I==c.length-1;e=2;for(g=w;ey-99&&p?r+=(1>r)/99:0)l=(1-r)*C[B++]/2||0,v[g]=(v[g]||0)-l*x+l,F[g]=(F[g++]||0)+l*x+l;h&&(r=h%1,x=m[1]||0,h|=0)&&(C=t[[q=m[B=0]||0,h]]=t[[q,h]]||(k=[...a[q]],k[2]*=2**((h-12)/12),0a.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=f&&(m+=f,h+=g);k<=e&&(m+=e,l+=d)}debugRaycast&&debugLine(a,b,"#00f",.02,1)}class TileLayerData{constructor(a,b=0,c=0,d=new Color){this.tile=a;this.direction=b;this.mirror=c;this.color=d}clear(){this.tile=this.direction=this.mirror=0;color=new Color}}class TileLayer extends EngineObject{constructor(a,b=tileCollisionSize,c=tileSizeDefault,d=vec2(1),e=0){super(a,b,-1,c,0,void 0,e);this.canvas=document.createElement("canvas");this.context=this.canvas.getContext("2d");this.scale=d;this.isOverlay;this.data=[];for(a=this.size.area();a--;)this.data.push(new TileLayerData)}setData(a,b,c){a.arrayCheck(this.size)&&(this.data[(a.y|0)*this.size.x+a.x|0]=b,c&&this.drawTileData(a))}getData(a){return a.arrayCheck(this.size)&&this.data[(a.y|0)*this.size.x+a.x|0]}update(){}render(){ASSERT(mainContext!=this.context);glEnable&&!glOverlay&&!this.isOverlay&&glCopyToContext(mainContext);const a=worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));(this.isOverlay?overlayContext:mainContext).drawImage(this.canvas,a.x,a.y,cameraScale*this.size.x*this.scale.x,cameraScale*this.size.y*this.scale.y)}redraw(){this.redrawStart(1);this.drawAllTileData();this.redrawEnd()}redrawStart(a=0){this.savedRenderSettings=[mainCanvas,mainContext,mainCanvasSize,cameraPos,cameraScale];mainCanvas=this.canvas;mainContext=this.context;cameraPos=this.size.scale(.5);cameraScale=this.tileSize.x;a&&(mainCanvas.width=this.size.x*this.tileSize.x,mainCanvas.height=this.size.y*this.tileSize.y);enginePreRender()}redrawEnd(){ASSERT(mainContext==this.context);glEnable&&glCopyToContext(mainContext,1);[mainCanvas,mainContext,mainCanvasSize,cameraPos,cameraScale]=this.savedRenderSettings}drawTileData(a){const b=a.floor().add(this.pos).add(vec2(.5));this.drawCanvas2D(b,vec2(1),0,0,c=>c.clearRect(-.5,-.5,1,1));a=this.getData(a);void 0!=a.tile&&(ASSERT(mainContext==this.context),drawTile(b,vec2(1),a.tile,this.tileSize,a.color,a.direction*PI/2,a.mirror))}drawAllTileData(){for(let a=this.size.x;a--;)for(let b=this.size.y;b--;)this.drawTileData(vec2(a,b))}drawCanvas2D(a,b,c=0,d,e){const f=this.context;f.save();a=a.subtract(this.pos).multiply(this.tileSize);b=b.multiply(this.tileSize);f.translate(a.x,this.canvas.height-a.y);f.rotate(c);f.scale(d?-b.x:b.x,b.y);e(f);f.restore()}drawTile(a,b=vec2(1),c=-1,d=tileSizeDefault,e=new Color,f,g){this.drawCanvas2D(a,b,f,g,k=>{if(0>c)k.fillStyle=e,k.fillRect(-.5,-.5,1,1);else{const h=tileImage.width/d.x;k.globalAlpha=e.a;k.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,g=-1,k=tileSizeDefault,h=new Color,l=new Color,m=new Color(1,1,1,0),n=new Color(1,1,1,0),p=.5,q=.1,r=1,w=.1,u=.05,B=1,z=1,C=0,v=PI,F=.1,A=.2,x,D,t=1,y=D?1e9:0,G){super(a,new Vector2,g,k,b,void 0,y);this.emitSize=c;this.emitTime=d;this.emitRate=e;this.emitConeAngle=f;this.colorStartA=h;this.colorStartB=l;this.colorEndA=m;this.colorEndB=n;this.randomColorLinear=t;this.particleTime=p;this.sizeStart=q;this.sizeEnd=r;this.speed=w;this.angleSpeed=u;this.damping=B;this.angleDamping=z;this.gravityScale=C;this.particleConeAngle=v;this.fadeRate=F;this.randomness=A;this.collideTiles=x;this.additive=D;this.localSpace=G;this.emitTimeBuffer=this.trailScale=0}update(){this.parent&&super.update();if(!this.emitTime|this.getAliveTime()<=this.emitTime){if(this.emitRate*particleEmitRateScale){const a=1/this.emitRate/particleEmitRateScale;for(this.emitTimeBuffer+=timeDelta;0m+m*rand(c,-c);b=d(this.particleTime);const e=d(this.sizeStart),f=d(this.sizeEnd),g=d(this.speed);d=d(this.angleSpeed)*randSign();var k=rand(this.emitConeAngle,-this.emitConeAngle);const h=randColor(this.colorStartA,this.colorStartB,this.randomColorLinear),l=randColor(this.colorEndA,this.colorEndB,this.randomColorLinear);k=this.localSpace?k:this.angle+k;a.colorStart=h;a.colorEndDelta=l.subtract(h);a.velocity=(new Vector2).setAngle(k,g);a.angleVelocity=d;a.lifeTime=b;a.sizeStart=e;a.sizeEndDelta=f-e;a.fadeRate=this.fadeRate;a.damping=this.damping;a.angleDamping=this.angleDamping;a.elasticity=this.elasticity;a.friction=this.friction;a.gravityScale=this.gravityScale;a.collideTiles=this.collideTiles;a.additive=this.additive;a.renderOrder=this.renderOrder;a.trailScale=this.trailScale;a.mirror=randInt(2);a.localSpaceEmitter=this.localSpace&&this;a.destroyCallback=this.particleDestroyCallback;this.particleCreateCallback&&this.particleCreateCallback(a);return a}render(){}}class Particle extends EngineObject{constructor(a,b,c,d){super(a,new Vector2,b,c,d)}render(){const a=min((time-this.spawnTime)/this.lifeTime,1);var b=this.sizeStart+a*this.sizeEndDelta;b=new Vector2(b,b);var c=this.fadeRate/2;c=new Color(this.colorStart.r+a*this.colorEndDelta.r,this.colorStart.g+a*this.colorEndDelta.g,this.colorStart.b+a*this.colorEndDelta.b,(this.colorStart.a+a*this.colorEndDelta.a)*(a1-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 g=e*this.trailScale;b.y=max(b.x,g);e=f.angle();drawTile(d.add(f.multiply(vec2(0,-g/2))),b,this.tileIndex,this.tileSize,c,e,this.mirror)}else drawTile(d,b,this.tileIndex,this.tileSize,c,e,this.mirror);this.additive&&setBlendMode();debugParticles&&debugRect(d,b,"#f005",0,e);1==a&&(this.color=c,this.size=b,this.destroyCallback&&this.destroyCallback(this),this.destroyed=1)}}"use strict";const medals=[];let medalsDisplayQueue=[],medalsSaveName,medalsDisplayTimeLast;function medalsInit(a){medalsSaveName=a;debugMedals||medals.forEach(b=>b.unlocked=localStorage[b.storageKey()]|0)}class Medal{constructor(a,b,c="",d="🏆",e){ASSERT(0<=a&&!medals[a]);medals[this.id=a]=this;this.name=b;this.description=c;this.icon=d;e&&((this.image=new Image).src=e)}unlock(){medalsPreventUnlock||this.unlocked||(ASSERT(medalsSaveName),localStorage[this.storageKey()]=this.unlocked=1,medalsDisplayQueue.push(this),newgrounds&&newgrounds.unlockMedal(this.id))}render(a=0){const b=overlayContext;var c=min(medalDisplaySize.x,mainCanvas.width);const d=overlayCanvas.width-c;a*=-medalDisplaySize.y;b.save();b.beginPath();b.fillStyle=new Color(.9,.9,.9);b.strokeStyle=new Color(0,0,0);b.lineWidth=3;b.fill(b.rect(d,a,c,medalDisplaySize.y));b.stroke();b.clip();this.renderIcon(vec2(d+15+medalDisplayIconSize/2,a+medalDisplaySize.y/2));c=vec2(d+medalDisplayIconSize+30,a+28);drawTextScreen(this.name,c,38,new Color(0,0,0),0,0,"left");c.y+=32;drawTextScreen(this.description,c,24,new Color(0,0,0),0,0,"left");b.restore()}renderIcon(a,b=medalDisplayIconSize){this.image?overlayContext.drawImage(this.image,a.x-b/2,a.y-b/2,b,b):drawTextScreen(this.icon,a,.7*b,new Color(0,0,0))}storageKey(){return medalsSaveName+"_"+this.id}}function medalsRender(){if(medalsDisplayQueue.length){var a=medalsDisplayQueue[0],b=timeReal-medalsDisplayTimeLast;if(medalsDisplayTimeLast)if(b>medalDisplayTime)medalsDisplayQueue.shift(medalsDisplayTimeLast=0);else{const c=medalDisplayTime-medalDisplaySlideTime;a.render(bc?(b-c)/medalDisplaySlideTime:0)}else medalsDisplayTimeLast=timeReal}}let newgrounds;function newgroundsInit(a,b){newgrounds=new Newgrounds(a,b)}class Newgrounds{constructor(a,b){ASSERT(!newgrounds&&a);this.app_id=a;this.cipher=b;this.host=location?location.hostname:"";b&&(this.cryptoJS=this.CryptoJS());if(this.session_id=new URL(location.href).searchParams.get("ngio_session_id")){this.medals=(a=this.call("Medal.getList"))?a.result.data.medals:[];debugMedals&&console.log(this.medals);for(var c of this.medals)if(a=medals[c.id])a.image=new Image,a.image.src=c.icon,a.name=c.name,a.description=c.description,a.unlocked=c.unlocked,a.difficulty=c.difficulty,a.value=c.value,a.value&&(a.description=a.description+" ("+a.value+")");this.scoreboards=(c=this.call("ScoreBoard.getBoards"))?c.result.data.scoreboards:[];debugMedals&&console.log(this.scoreboards);setInterval(()=>this.call("Gateway.ping",0,1),3e5)}}unlockMedal(a){return this.call("Medal.unlock",{id:a},1)}postScore(a,b){return this.call("ScoreBoard.postScore",{id:a,value:b},1)}getScores(a,b=0,c=0,d=0,e=10){return this.call("ScoreBoard.getScores",{id:a,user:b,social:c,skip:d,limit:e})}logView(){return this.call("App.logView",{host:this.host},1)}call(a,b=0,c=0){a={component:a,parameters:b};if(this.cipher){b=this.cryptoJS;var d=b.enc.Base64.parse(this.cipher);const e=b.lib.WordArray.random(16);d=b.AES.encrypt(JSON.stringify(a),d,{iv:e});a.secure=b.enc.Base64.stringify(e.concat(d.ciphertext));a.parameters=0}b={app_id:this.app_id,session_id:this.session_id,call:a};a=new FormData;a.append("input",JSON.stringify(b));b=new XMLHttpRequest;b.open("POST","https://newgrounds.io/gateway_v3.php",!debugMedals&&c);b.send(a);debugMedals&&console.log(b.responseText);return b.responseText&&JSON.parse(b.responseText)}CryptoJS(){return eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",..."]charCodeAtUinyxpf","for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h>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{d=glContext.getAttribLocation(glShader,d);glContext.enableVertexAttribArray(d);glContext.vertexAttribPointer(d,g,e,k,gl_VERTEX_BYTE_STRIDE,a);a+=g*f};b("p",gl_FLOAT,4,2);b("t",gl_FLOAT,4,2);b("c",gl_UNSIGNED_BYTE,1,4,1);b("a",gl_UNSIGNED_BYTE,1,4,1);b=2*cameraScale/mainCanvas.width;const c=2*cameraScale/mainCanvas.height;glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader,"m"),0,new Float32Array([b,0,0,0,0,c,0,0,1,1,-1,1,-1-b*cameraPos.x,-1-c*cameraPos.y,0,0]))}function glFlush(){if(glBatchCount){var a=glBatchAdditive?gl_ONE:gl_ONE_MINUS_SRC_ALPHA;glContext.blendFuncSeparate(gl_SRC_ALPHA,a,gl_ONE,a);glContext.enable(gl_BLEND);glContext.bufferSubData(gl_ARRAY_BUFFER,0,glPositionData.subarray(0,glBatchCount*gl_VERTICES_PER_QUAD*gl_INDICIES_PER_VERT));glContext.drawArrays(gl_TRIANGLES,0,glBatchCount*gl_VERTICES_PER_QUAD);glBatchCount=0;glBatchAdditive=glAdditive}}function glCopyToContext(a,b){if(glBatchCount||b)glFlush(),glOverlay&&!b||a.drawImage(glCanvas,0,0)}function glDraw(a,b,c,d,e,f,g,k,h,l,m=0){glBatchCount!=gl_MAX_BATCH&&glBatchAdditive==glAdditive||glFlush();var n=Math.cos(e)/2,p=Math.sin(e)/2;e=n*c;n*=d;c*=p;d*=p;for(let q=6,r=glBatchCount++*gl_VERTICES_PER_QUAD*gl_INDICIES_PER_VERT;q--;){p=q-4&&1{},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)=>aa>b?a:b,sign=a=>0>a?-1:1,mod=(a,b=1)=>(a%b+b)%b,clamp=(a,b=0,c=1)=>ac?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/2*(1-Math.cos(c*a*2*PI)),formatTime=a=>(a/60|0)+":"+(10>a%60?"0":"")+(a%60|0),rand=(a=1,b=0)=>b+(a-b)*Math.random(),randInt=(a=1,b=0)=>rand(a,b)|0,randSign=()=>2*randInt(2)-1,randInCircle=(a=1,b=0)=>0(new Vector2).setAngle(rand(2*PI),a),randColor=(a=new Color,b=new Color(0,0,0,1),c)=>c?a.lerp(b,rand()):new Color(rand(a.r,b.r),rand(a.g,b.g),rand(a.b,b.b),rand(a.a,b.a));let randSeed=1;const setRandSeed=a=>randSeed=a,randSeeded=(a=1,b=0)=>{randSeed^=randSeed<<13;randSeed^=randSeed>>>17;randSeed^=randSeed<<5;return b+(a-b)*abs(randSeed%1e9)/1e9},vec2=(a=0,b)=>void 0==a.x?new Vector2(a,void 0==b?a:b):new Vector2(a.x,a.y),isVector2=a=>!isNaN(a.x)&&!isNaN(a.y);class Vector2{constructor(a=0,b=0){this.x=a;this.y=b}copy(){return new Vector2(this.x,this.y)}add(a){ASSERT(isVector2(a));return new Vector2(this.x+a.x,this.y+a.y)}subtract(a){ASSERT(isVector2(a));return new Vector2(this.x-a.x,this.y-a.y)}multiply(a){ASSERT(isVector2(a));return new Vector2(this.x*a.x,this.y*a.y)}divide(a){ASSERT(isVector2(a));return new Vector2(this.x/a.x,this.y/a.y)}scale(a){ASSERT(!isVector2(a));return new Vector2(this.x*a,this.y*a)}length(){return this.lengthSquared()**.5}lengthSquared(){return this.x**2+this.y**2}distance(a){return this.distanceSquared(a)**.5}distanceSquared(a){return(this.x-a.x)**2+(this.y-a.y)**2}normalize(a=1){const b=this.length();return b?this.scale(a/b):new Vector2(0,a)}clampLength(a=1){const b=this.length();return b>a?this.scale(a/b):this}dot(a){ASSERT(isVector2(a));return this.x*a.x+this.y*a.y}cross(a){ASSERT(isVector2(a));return this.x*a.y-this.y*a.x}angle(){return Math.atan2(this.x,this.y)}setAngle(a=0,b=1){this.x=b*Math.sin(a);this.y=b*Math.cos(a);return this}rotate(a){const b=Math.cos(a);a=Math.sin(a);return new Vector2(this.x*b-this.y*a,this.x*a+this.y*b)}direction(){return abs(this.x)>abs(this.y)?0>this.x?3:1:0>this.y?2:0}invert(){return new Vector2(this.y,-this.x)}floor(){return new Vector2(Math.floor(this.x),Math.floor(this.y))}area(){return abs(this.x*this.y)}lerp(a,b){ASSERT(isVector2(a));return this.add(a.subtract(this).scale(clamp(b)))}arrayCheck(a){return 0<=this.x&&0<=this.y&&this.xthis.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,g,k)=>(k=(k%1+1)%1)<1/6?f+6*(g-f)*k:.5>k?g:k<2/3?f+(g-f)*(2/3-k)*6:f;this.r=e(c,b,a+1/3);this.g=e(c,b,a);this.b=e(c,b,a-1/3);this.a=d;return this}getHSLA(){const a=clamp(this.r),b=clamp(this.g),c=clamp(this.b),d=clamp(this.a),e=Math.max(a,b,c),f=Math.min(a,b,c),g=(e+f)/2;let k=0,h=0;if(e!=f){let l=e-f;h=.5(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=7this.time}get(){return this.isSet()?time-this.time:0}getPercent(){return this.isSet()?percent(this.time-time,this.setTime,0):0}toString(){if(debug)return this.unset()?"unset":Math.abs(this.get())+" seconds "+(0>this.get()?"before":"after")}valueOf(){return this.get()}}"use strict";let cameraPos=vec2(),cameraScale=32,canvasMaxSize=vec2(1920,1200),canvasFixedSize=vec2(),cavasPixelated=1,fontDefault="arial",glEnable=1,glOverlay=1,tileSizeDefault=vec2(16),tileFixBleedScale=.3,objectDefaultSize=vec2(1),enablePhysicsSolver=1,objectDefaultMass=1,objectDefaultDamping=.99,objectDefaultAngleDamping=.99,objectDefaultElasticity=0,objectDefaultFriction=.8,objectMaxSpeed=1,gravity=0,particleEmitRateScale=1,gamepadsEnable=1,gamepadDirectionEmulateStick=1,inputWASDEmulateDirection=1,touchGamepadEnable=0,touchGamepadAnalog=1,touchGamepadSize=99,touchGamepadAlpha=.3,vibrateEnable=1,soundEnable=1,soundVolume=.5,soundDefaultRange=40,soundDefaultTaper=.7,medalDisplayTime=5,medalDisplaySlideTime=.5,medalDisplaySize=vec2(640,80),medalDisplayIconSize=50,medalsPreventUnlock;"use strict";const engineName="LittleJS",engineVersion="1.5.0",frameRate=60,timeDelta=1/frameRate;let engineObjects=[],engineObjectsCollide=[],frame=0,time=0,timeReal=0,paused=0;function setPaused(a){paused=a}function engineInit(a,b,c,d,e,f){function g(m=0){var n=m-k;k=m;if(debug||showWatermark)l=lerp(.05,l,1e3/(n||1));m=debug&&keyIsDown(107);const p=debug&&keyIsDown(109);debug&&(n*=m?5:p?.2:1);timeReal+=n/1e3;h+=!paused*n;m||(h=min(h,50));canvasFixedSize.x?(overlayCanvas.width=mainCanvas.width=canvasFixedSize.x,overlayCanvas.height=mainCanvas.height=canvasFixedSize.y,n=innerWidth/innerHeight,m=mainCanvas.width/mainCanvas.height,(glCanvas||mainCanvas).style.width=mainCanvas.style.width=overlayCanvas.style.width=nh&&-9r.renderOrder-w.renderOrder);for(var q of engineObjects)q.destroyed||q.render();e();glRenderPostProcess();medalsRender();touchGamepadRender();debugRender();glEnable&&glCopyToContext(mainContext);showWatermark&&(overlayContext.textAlign="right",overlayContext.textBaseline="top",overlayContext.font="1em monospace",overlayContext.fillStyle="#000",q=engineName+" v"+engineVersion+" / "+drawCount+" / "+engineObjects.length+" / "+l.toFixed(1)+(glEnable?" GL":" 2D"),overlayContext.fillText(q,mainCanvas.width-3,3),overlayContext.fillStyle="#fff",overlayContext.fillText(q,mainCanvas.width-2,2),drawCount=0);requestAnimationFrame(g)}tileImage.onerror=tileImage.onload=()=>{tileImageFixBleed=vec2(tileFixBleedScale).divide(tileImageSize=vec2(tileImage.width,tileImage.height));debug&&(tileImage.onload=()=>ASSERT(1));document.body.style="margin:0;overflow:hidden;background:#000;touch-action:none;user-select:none;-webkit-user-select:none";document.body.appendChild(mainCanvas=document.createElement("canvas"));mainContext=mainCanvas.getContext("2d");debugInit();glEnable&&glInit();document.body.appendChild(overlayCanvas=document.createElement("canvas"));overlayContext=overlayCanvas.getContext("2d");(glCanvas||mainCanvas).style=mainCanvas.style=overlayCanvas.style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)";a();touchGamepadCreate();g()};let k=0,h,l;f?tileImage.src=f:tileImage.onload()}function enginePreRender(){mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height);mainContext.imageSmoothingEnabled=!cavasPixelated;glEnable&&glPreRender()}function engineObjectsUpdate(){engineObjectsCollide=engineObjects.filter(b=>b.collideSolidObjects);const a=b=>{if(!b.destroyed){b.update();for(const c of b.children)a(c)}};for(const b of engineObjects)b.parent||a(b);engineObjects=engineObjects.filter(b=>!b.destroyed);time=++frame/frameRate}function engineObjectsDestroy(){for(const a of engineObjects)a.parent||a.destroy();engineObjects=engineObjects.filter(a=>!a.destroyed)}function engineObjectsCallback(a,b,c,d=engineObjects){if(a)if(void 0!=b.x)for(const e of d)isOverlapping(a,b,e.pos,e.size)&&c(e);else{b*=b;for(const e of d)a.distanceSquared(e.pos)=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)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{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,g,k=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 l=tileImageSize.x/d.x|0;h=d.x/tileImageSize.x;const m=d.y/tileImageSize.y,n=c%l*h;l=(c/l|0)*m;glDraw(a.x,a.y,g?-b.x:b.x,b.y,f,n+tileImageFixBleed.x,l+tileImageFixBleed.y,n-tileImageFixBleed.x+h,l-tileImageFixBleed.y+m,e.rgbaInt(),k.rgbaInt())}else drawCanvas2D(a,b,f,g,m=>{if(0>c)m.fillStyle=e,m.fillRect(-.5,-.5,1,1);else{var n=tileImageSize.x/d.x|0;const p=c%n*d.x+tileFixBleedScale;n=(c/n|0)*d.y+tileFixBleedScale;const q=d.x-2*tileFixBleedScale,r=d.y-2*tileFixBleedScale;m.globalAlpha=e.a;m.drawImage(tileImage,p,n,q,r,-.5,-.5,1,1)}})}function drawRect(a,b,c,d,e){drawTile(a,b,-1,tileSizeDefault,c,d,0,0,e)}function drawTileScreenSpace(a,b=vec2(1),c,d,e,f,g,k,h){drawTile(screenToWorld(a),b.scale(1/cameraScale),c,d,e,f,g,k,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),g="center",k=fontDefault,h=overlayContext){h.fillStyle=d;h.lineWidth=e;h.strokeStyle=f;h.textAlign=g;h.font=c+"px "+k;h.textBaseline="middle";h.lineJoin="round";b=b.copy();(a+"").split("\n").forEach(l=>{e&&h.strokeText(l,b.x,b.y);h.fillText(l,b.x,b.y);b.y+=c})}function drawText(a,b,c=1,d,e,f,g,k){drawTextScreen(a,worldToScreen(b),c*cameraScale,d,e*cameraScale,f,g,k,mainContext)}let engineFontImage;class FontImage{constructor(a,b=vec2(8),c=vec2(0,1),d=0,e=overlayContext){engineFontImage||((engineFontImage=new Image).src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC");this.image=a||engineFontImage;this.tileSize=b;this.paddingSize=c;this.startTileIndex=d;this.context=e}drawTextScreen(a,b,c=4,d){const e=this.context;e.save();e.imageSmoothingEnabled=!cavasPixelated;const f=this.tileSize,g=f.add(this.paddingSize).scale(c),k=this.image.width/this.tileSize.x|0;(a+"").split("\n").forEach((h,l)=>{const m=d?h.length*f.x*c/2|0:0;for(let q=h.length;q--;){var n=h[q].charCodeAt();if(32>n||127document.fullscreenElement;function toggleFullscreen(){isFullscreen()?document.exitFullscreen&&document.exitFullscreen():document.body.requestFullscreen&&document.body.requestFullscreen()}"use strict";const keyIsDown=(a,b=0)=>inputData[b]&&inputData[b][a]&1,keyWasPressed=(a,b=0)=>inputData[b]&&inputData[b][a]&2?1:0,keyWasReleased=(a,b=0)=>inputData[b]&&inputData[b][a]&4?1:0,clearInput=()=>inputData=[[]],mouseIsDown=keyIsDown,mouseWasPressed=keyWasPressed,mouseWasReleased=keyWasReleased;let mousePos=vec2(),mousePosScreen=vec2(),mouseWheel=0,isUsingGamepad=0,preventDefaultInput=0;const gamepadIsDown=(a,b=0)=>keyIsDown(a,b+1),gamepadWasPressed=(a,b=0)=>keyWasPressed(a,b+1),gamepadWasReleased=(a,b=0)=>keyWasReleased(a,b+1),gamepadStick=(a,b=0)=>stickData[b]?stickData[b][a]||vec2():vec2();let inputData=[[]];function inputUpdate(){isTouchDevice||document.hasFocus()||clearInput();mousePos=screenToWorld(mousePosScreen);gamepadsUpdate()}function inputUpdatePost(){for(const a of inputData)for(const b in a)a[b]&=1;mouseWheel=0}onkeydown=a=>{debug&&a.target!=document.body||(a.repeat||(inputData[isUsingGamepad=0][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 g=inputData[b+1]||(inputData[b+1]=[]);c=stickData[b]||(stickData[b]=[]);if(d){var e=k=>.3k?-percent(-k,.3,.8):0;for(var f=0;f>1]=vec2(e(d.axes[f]),e(-d.axes[f+1])).clampLength();for(e=d.buttons.length;e--;)f=d.buttons[e],g[e]=f.pressed?1+2*!gamepadIsDown(e,b):4*gamepadIsDown(e,b),isUsingGamepad|=!b&&f.pressed,touchGamepadEnable&&touchGamepadTimer.unset();gamepadDirectionEmulateStick&&(d=vec2(gamepadIsDown(15,b)-gamepadIsDown(14,b),gamepadIsDown(12,b)-gamepadIsDown(13,b)),d.lengthSquared()&&(c[0]=d.clampLength()))}}}const vibrate=a=>vibrateEnable&&navigator&&navigator.vibrate&&navigator.vibrate(a),vibrateStop=()=>vibrate(0),isTouchDevice=void 0!==window.ontouchstart;if(isTouchDevice){let a,b=onmousedown,c=onmouseup;onmousedown=onmouseup=()=>0;ontouchstart=d=>{zzfx(0);ontouchstart=ontouchmove=ontouchend=e=>{e.button=0;const f=e.touches.length;f?(e.x=e.touches[0].clientX,e.y=e.touches[0].clientY,a?onmousemove(e):b(e)):a&&c(e);a=f;return!0};return ontouchstart(d)}}let touchGamepadTimer=new Timer,touchGamepadButtons,touchGamepadStick;function touchGamepadCreate(){touchGamepadEnable&&isTouchDevice&&(touchGamepadButtons=[],touchGamepadStick=vec2(),ontouchstart=a=>{zzfx(0);ontouchstart=ontouchmove=ontouchend=b=>{touchGamepadStick=vec2();touchGamepadButtons=[];if(b.touches.length&&(isUsingGamepad=1,touchGamepadTimer.set(),paused)){touchGamepadButtons[9]=1;return}const c=vec2(touchGamepadSize,mainCanvasSize.y-touchGamepadSize),d=mainCanvasSize.subtract(vec2(touchGamepadSize,touchGamepadSize)),e=mainCanvasSize.scale(.5);for(const f of b.touches)b=mouseToScreen(vec2(f.clientX,f.clientY)),b.distance(c)e*e)return;b*=percent(f**.5,e,e*this.taper)}e=2*worldToScreen(a).x/mainCanvas.width-1}a=c+c*this.randomness*d*rand(-1,1);return playSamples([this.cachedSamples],b,a,e)}}playNote(a,b,c){if(soundEnable)return this.play(b,c,2**(a/12),0)}}class Music{constructor(a){soundEnable&&(this.cachedSamples=zzfxM(...a))}play(a,b=1){soundEnable&&(this.source=playSamples(this.cachedSamples,a,1,0,b))}stop(){this.source&&this.source.stop();this.source=0}isPlaying(){return this.source}}function playAudioFile(a,b=1,c=1){if(soundEnable)return a=new Audio(a),a.volume=soundVolume*b,a.loop=c,a.play(),a}function speak(a,b="",c=1,d=1,e=1){if(soundEnable&&speechSynthesis)return a=new SpeechSynthesisUtterance(a),a.lang=b,a.volume=2*c*soundVolume,a.rate=d,a.pitch=e,speechSynthesis.speak(a),a}const speakStop=()=>speechSynthesis&&speechSynthesis.cancel(),getNoteFrequency=(a,b=220)=>b*2**(a/12);let audioContext;function playSamples(a,b=1,c=1,d=0,e=0){if(soundEnable&&(audioContext||=new AudioContext,audioContext.resume(),"running"==audioContext.state)){var f=audioContext.createBuffer(a.length,a[0].length,zzfxR),g=audioContext.createBufferSource();a.forEach((k,h)=>f.getChannelData(h).set(k));g.buffer=f;g.playbackRate.value=c;g.loop=e;a=audioContext.createGain();a.gain.value=soundVolume*b;a.connect(audioContext.destination);g.connect(new StereoPannerNode(audioContext,{pan:clamp(d,-1,1)})).connect(a);g.start();return g}}const zzfx=(...a)=>playSamples([zzfxG(...a)]),zzfxR=44100;function zzfxG(a=1,b=.05,c=220,d=0,e=0,f=.1,g=0,k=1,h=0,l=0,m=0,n=0,p=0,q=0,r=0,w=0,u=0,B=1,z=0,C=0){let v=2*PI,F=h*=500*v/zzfxR/zzfxR,A=[];b=c*=(1+b*rand(-1,1))*v/zzfxR;let x=0,D=0,t=0,y=1,G=0,I=0,E=0,J,H;d=d*zzfxR+9;z*=zzfxR;e*=zzfxR;f*=zzfxR;u*=zzfxR;l*=500*v/zzfxR**3;r*=v/zzfxR;m*=v/zzfxR;n*=zzfxR;p=p*zzfxR|0;for(H=d+z+e+f+u|0;tt?0:(tn&&(c+=m,b+=m,y=0),!p||++G%p||(c=b,h=F,y=y||1);return A}function zzfxM(a,b,c,d=125){let e,f,g,k,h,l,m,n,p,q,r,w,u,B=0,z,C=[],v=[],F=[],A=0,x=0,D=1,t={},y=zzfxR/d*60>>2;for(;D;A++)C=[D=n=w=0],c.forEach((G,I)=>{m=b[G][A]||[0,0,0];D|=!!b[G][A];z=w+(b[G][0].length-2-!n)*y;u=I==c.length-1;e=2;for(g=w;ey-99&&p?r+=(1>r)/99:0)l=(1-r)*C[B++]/2||0,v[g]=(v[g]||0)-l*x+l,F[g]=(F[g++]||0)+l*x+l;h&&(r=h%1,x=m[1]||0,h|=0)&&(C=t[[q=m[B=0]||0,h]]=t[[q,h]]||(k=[...a[q]],k[2]*=2**((h-12)/12),0a.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=f&&(m+=f,h+=g);k<=e&&(m+=e,l+=d)}debugRaycast&&debugLine(a,b,"#00f",.02,1)}class TileLayerData{constructor(a,b=0,c=0,d=new Color){this.tile=a;this.direction=b;this.mirror=c;this.color=d}clear(){this.tile=this.direction=this.mirror=0;color=new Color}}class TileLayer extends EngineObject{constructor(a,b=tileCollisionSize,c=tileSizeDefault,d=vec2(1),e=0){super(a,b,-1,c,0,void 0,e);this.canvas=document.createElement("canvas");this.context=this.canvas.getContext("2d");this.scale=d;this.isOverlay;this.data=[];for(a=this.size.area();a--;)this.data.push(new TileLayerData)}setData(a,b,c){a.arrayCheck(this.size)&&(this.data[(a.y|0)*this.size.x+a.x|0]=b,c&&this.drawTileData(a))}getData(a){return a.arrayCheck(this.size)&&this.data[(a.y|0)*this.size.x+a.x|0]}update(){}render(){ASSERT(mainContext!=this.context);glEnable&&!glOverlay&&!this.isOverlay&&glCopyToContext(mainContext);const a=worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));(this.isOverlay?overlayContext:mainContext).drawImage(this.canvas,a.x,a.y,cameraScale*this.size.x*this.scale.x,cameraScale*this.size.y*this.scale.y)}redraw(){this.redrawStart(1);this.drawAllTileData();this.redrawEnd()}redrawStart(a=0){this.savedRenderSettings=[mainCanvas,mainContext,mainCanvasSize,cameraPos,cameraScale];mainCanvas=this.canvas;mainContext=this.context;cameraPos=this.size.scale(.5);cameraScale=this.tileSize.x;a&&(mainCanvas.width=this.size.x*this.tileSize.x,mainCanvas.height=this.size.y*this.tileSize.y);enginePreRender()}redrawEnd(){ASSERT(mainContext==this.context);glEnable&&glCopyToContext(mainContext,1);[mainCanvas,mainContext,mainCanvasSize,cameraPos,cameraScale]=this.savedRenderSettings}drawTileData(a){const b=a.floor().add(this.pos).add(vec2(.5));this.drawCanvas2D(b,vec2(1),0,0,c=>c.clearRect(-.5,-.5,1,1));a=this.getData(a);void 0!=a.tile&&(ASSERT(mainContext==this.context),drawTile(b,vec2(1),a.tile,this.tileSize,a.color,a.direction*PI/2,a.mirror))}drawAllTileData(){for(let a=this.size.x;a--;)for(let b=this.size.y;b--;)this.drawTileData(vec2(a,b))}drawCanvas2D(a,b,c=0,d,e){const f=this.context;f.save();a=a.subtract(this.pos).multiply(this.tileSize);b=b.multiply(this.tileSize);f.translate(a.x,this.canvas.height-a.y);f.rotate(c);f.scale(d?-b.x:b.x,b.y);e(f);f.restore()}drawTile(a,b=vec2(1),c=-1,d=tileSizeDefault,e=new Color,f,g){this.drawCanvas2D(a,b,f,g,k=>{if(0>c)k.fillStyle=e,k.fillRect(-.5,-.5,1,1);else{const h=tileImage.width/d.x;k.globalAlpha=e.a;k.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,g=-1,k=tileSizeDefault,h=new Color,l=new Color,m=new Color(1,1,1,0),n=new Color(1,1,1,0),p=.5,q=.1,r=1,w=.1,u=.05,B=1,z=1,C=0,v=PI,F=.1,A=.2,x,D,t=1,y=D?1e9:0,G){super(a,new Vector2,g,k,b,void 0,y);this.emitSize=c;this.emitTime=d;this.emitRate=e;this.emitConeAngle=f;this.colorStartA=h;this.colorStartB=l;this.colorEndA=m;this.colorEndB=n;this.randomColorLinear=t;this.particleTime=p;this.sizeStart=q;this.sizeEnd=r;this.speed=w;this.angleSpeed=u;this.damping=B;this.angleDamping=z;this.gravityScale=C;this.particleConeAngle=v;this.fadeRate=F;this.randomness=A;this.collideTiles=x;this.additive=D;this.localSpace=G;this.emitTimeBuffer=this.trailScale=0}update(){this.parent&&super.update();if(!this.emitTime|this.getAliveTime()<=this.emitTime){if(this.emitRate*particleEmitRateScale){const a=1/this.emitRate/particleEmitRateScale;for(this.emitTimeBuffer+=timeDelta;0m+m*rand(c,-c);b=d(this.particleTime);const e=d(this.sizeStart),f=d(this.sizeEnd),g=d(this.speed);d=d(this.angleSpeed)*randSign();var k=rand(this.emitConeAngle,-this.emitConeAngle);const h=randColor(this.colorStartA,this.colorStartB,this.randomColorLinear),l=randColor(this.colorEndA,this.colorEndB,this.randomColorLinear);k=this.localSpace?k:this.angle+k;a.colorStart=h;a.colorEndDelta=l.subtract(h);a.velocity=(new Vector2).setAngle(k,g);a.angleVelocity=d;a.lifeTime=b;a.sizeStart=e;a.sizeEndDelta=f-e;a.fadeRate=this.fadeRate;a.damping=this.damping;a.angleDamping=this.angleDamping;a.elasticity=this.elasticity;a.friction=this.friction;a.gravityScale=this.gravityScale;a.collideTiles=this.collideTiles;a.additive=this.additive;a.renderOrder=this.renderOrder;a.trailScale=this.trailScale;a.mirror=randInt(2);a.localSpaceEmitter=this.localSpace&&this;a.destroyCallback=this.particleDestroyCallback;this.particleCreateCallback&&this.particleCreateCallback(a);return a}render(){}}class Particle extends EngineObject{constructor(a,b,c,d){super(a,new Vector2,b,c,d)}render(){const a=min((time-this.spawnTime)/this.lifeTime,1);var b=this.sizeStart+a*this.sizeEndDelta;b=new Vector2(b,b);var c=this.fadeRate/2;c=new Color(this.colorStart.r+a*this.colorEndDelta.r,this.colorStart.g+a*this.colorEndDelta.g,this.colorStart.b+a*this.colorEndDelta.b,(this.colorStart.a+a*this.colorEndDelta.a)*(a1-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 g=e*this.trailScale;b.y=max(b.x,g);e=f.angle();drawTile(d.add(f.multiply(vec2(0,-g/2))),b,this.tileIndex,this.tileSize,c,e,this.mirror)}else drawTile(d,b,this.tileIndex,this.tileSize,c,e,this.mirror);this.additive&&setBlendMode();debugParticles&&debugRect(d,b,"#f005",0,e);1==a&&(this.color=c,this.size=b,this.destroyCallback&&this.destroyCallback(this),this.destroyed=1)}}"use strict";const medals=[];let medalsDisplayQueue=[],medalsSaveName,medalsDisplayTimeLast;function medalsInit(a){medalsSaveName=a;debugMedals||medals.forEach(b=>b.unlocked=localStorage[b.storageKey()]|0)}class Medal{constructor(a,b,c="",d="🏆",e){ASSERT(0<=a&&!medals[a]);medals[this.id=a]=this;this.name=b;this.description=c;this.icon=d;e&&((this.image=new Image).src=e)}unlock(){medalsPreventUnlock||this.unlocked||(ASSERT(medalsSaveName),localStorage[this.storageKey()]=this.unlocked=1,medalsDisplayQueue.push(this),newgrounds&&newgrounds.unlockMedal(this.id))}render(a=0){const b=overlayContext;var c=min(medalDisplaySize.x,mainCanvas.width);const d=overlayCanvas.width-c;a*=-medalDisplaySize.y;b.save();b.beginPath();b.fillStyle=new Color(.9,.9,.9);b.strokeStyle=new Color(0,0,0);b.lineWidth=3;b.fill(b.rect(d,a,c,medalDisplaySize.y));b.stroke();b.clip();this.renderIcon(vec2(d+15+medalDisplayIconSize/2,a+medalDisplaySize.y/2));c=vec2(d+medalDisplayIconSize+30,a+28);drawTextScreen(this.name,c,38,new Color(0,0,0),0,0,"left");c.y+=32;drawTextScreen(this.description,c,24,new Color(0,0,0),0,0,"left");b.restore()}renderIcon(a,b=medalDisplayIconSize){this.image?overlayContext.drawImage(this.image,a.x-b/2,a.y-b/2,b,b):drawTextScreen(this.icon,a,.7*b,new Color(0,0,0))}storageKey(){return medalsSaveName+"_"+this.id}}function medalsRender(){if(medalsDisplayQueue.length){var a=medalsDisplayQueue[0],b=timeReal-medalsDisplayTimeLast;if(medalsDisplayTimeLast)if(b>medalDisplayTime)medalsDisplayQueue.shift(medalsDisplayTimeLast=0);else{const c=medalDisplayTime-medalDisplaySlideTime;a.render(bc?(b-c)/medalDisplaySlideTime:0)}else medalsDisplayTimeLast=timeReal}}let newgrounds;function newgroundsInit(a,b){newgrounds=new Newgrounds(a,b)}class Newgrounds{constructor(a,b){ASSERT(!newgrounds&&a);this.app_id=a;this.cipher=b;this.host=location?location.hostname:"";b&&(this.cryptoJS=this.CryptoJS());if(this.session_id=new URL(location.href).searchParams.get("ngio_session_id")){this.medals=(a=this.call("Medal.getList"))?a.result.data.medals:[];debugMedals&&console.log(this.medals);for(var c of this.medals)if(a=medals[c.id])a.image=new Image,a.image.src=c.icon,a.name=c.name,a.description=c.description,a.unlocked=c.unlocked,a.difficulty=c.difficulty,a.value=c.value,a.value&&(a.description=a.description+" ("+a.value+")");this.scoreboards=(c=this.call("ScoreBoard.getBoards"))?c.result.data.scoreboards:[];debugMedals&&console.log(this.scoreboards);setInterval(()=>this.call("Gateway.ping",0,1),3e5)}}unlockMedal(a){return this.call("Medal.unlock",{id:a},1)}postScore(a,b){return this.call("ScoreBoard.postScore",{id:a,value:b},1)}getScores(a,b=0,c=0,d=0,e=10){return this.call("ScoreBoard.getScores",{id:a,user:b,social:c,skip:d,limit:e})}logView(){return this.call("App.logView",{host:this.host},1)}call(a,b=0,c=0){a={component:a,parameters:b};if(this.cipher){b=this.cryptoJS;var d=b.enc.Base64.parse(this.cipher);const e=b.lib.WordArray.random(16);d=b.AES.encrypt(JSON.stringify(a),d,{iv:e});a.secure=b.enc.Base64.stringify(e.concat(d.ciphertext));a.parameters=0}b={app_id:this.app_id,session_id:this.session_id,call:a};a=new FormData;a.append("input",JSON.stringify(b));b=new XMLHttpRequest;b.open("POST","https://newgrounds.io/gateway_v3.php",!debugMedals&&c);b.send(a);debugMedals&&console.log(b.responseText);return b.responseText&&JSON.parse(b.responseText)}CryptoJS(){return eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",..."]charCodeAtUinyxpf","for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h>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{d=glContext.getAttribLocation(glShader,d);glContext.enableVertexAttribArray(d);glContext.vertexAttribPointer(d,g,e,k,gl_VERTEX_BYTE_STRIDE,a);a+=g*f};b("p",gl_FLOAT,4,2);b("t",gl_FLOAT,4,2);b("c",gl_UNSIGNED_BYTE,1,4,1);b("a",gl_UNSIGNED_BYTE,1,4,1);b=2*cameraScale/mainCanvas.width;const c=2*cameraScale/mainCanvas.height;glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader,"m"),0,new Float32Array([b,0,0,0,0,c,0,0,1,1,-1,1,-1-b*cameraPos.x,-1-c*cameraPos.y,0,0]))}function glFlush(){if(glBatchCount){var a=glBatchAdditive?gl_ONE:gl_ONE_MINUS_SRC_ALPHA;glContext.blendFuncSeparate(gl_SRC_ALPHA,a,gl_ONE,a);glContext.enable(gl_BLEND);glContext.bufferSubData(gl_ARRAY_BUFFER,0,glPositionData.subarray(0,glBatchCount*gl_VERTICES_PER_QUAD*gl_INDICIES_PER_VERT));glContext.drawArrays(gl_TRIANGLES,0,glBatchCount*gl_VERTICES_PER_QUAD);glBatchCount=0;glBatchAdditive=glAdditive}}function glCopyToContext(a,b){if(glBatchCount||b)glFlush(),glOverlay&&!b||a.drawImage(glCanvas,0,0)}function glDraw(a,b,c,d,e,f,g,k,h,l,m=0){glBatchCount!=gl_MAX_BATCH&&glBatchAdditive==glAdditive||glFlush();var n=Math.cos(e)/2,p=Math.sin(e)/2;e=n*c;n*=d;c*=p;d*=p;for(let q=6,r=glBatchCount++*gl_VERTICES_PER_QUAD*gl_INDICIES_PER_VERT;q--;){p=q-4&&1 console.assert(...assert) : ()=>{}; * @param {String} [color='#fff'] * @param {Number} [time=0] * @param {Number} [angle=0] - * @param {Boolean} [fill=0] + * @param {Boolean} [fill=false] * @memberof Debug */ -const debugRect = (pos, size=vec2(), color='#fff', time=0, angle=0, fill=0)=> +const debugRect = (pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)=> { ASSERT(typeof color == 'string'); // pass in regular html strings as colors debugPrimitives.push({pos, size:vec2(size), color, time:new Timer(time), angle, fill}); @@ -72,9 +72,9 @@ const debugRect = (pos, size=vec2(), color='#fff', time=0, angle=0, fill=0)=> * @param {Number} [radius=0] * @param {String} [color='#fff'] * @param {Number} [time=0] - * @param {Boolean} [fill=0] + * @param {Boolean} [fill=false] * @memberof Debug */ -const debugCircle = (pos, radius=0, color='#fff', time=0, fill=0)=> +const debugCircle = (pos, radius=0, color='#fff', time=0, fill=false)=> { ASSERT(typeof color == 'string'); // pass in regular html strings as colors debugPrimitives.push({pos, size:radius, color, time:new Timer(time), angle:0, fill}); @@ -1528,9 +1528,9 @@ class EngineObject } /** Set how this object collides - * @param {boolean} [collideSolidObjects=1] - Does it collide with solid objects - * @param {boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers) - * @param {boolean} [collideTiles=1] - Does it collide with the tile collision */ + * @param {Boolean} [collideSolidObjects=1] - Does it collide with solid objects + * @param {Boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers) + * @param {Boolean} [collideTiles=1] - Does it collide with the tile collision */ setCollision(collideSolidObjects=1, isSolid=1, collideTiles=1) { ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide @@ -1540,6 +1540,8 @@ class EngineObject this.collideTiles = collideTiles; } + /** Returns string containg info about this object for debugging + * @return {String} */ toString() { if (debug) @@ -1609,7 +1611,7 @@ let overlayContext; let mainCanvasSize = vec2(); /** Tile sheet for batch rendering system - * @type {Image} + * @type {CanvasImageSource} * @memberof Draw */ const tileImage = new Image; @@ -2190,7 +2192,7 @@ const vibrateStop = ()=> vibrate(0); // Touch input /** True if a touch device has been detected - * @const {boolean} + * @const {Boolean} * @memberof Input */ const isTouchDevice = window.ontouchstart !== undefined; @@ -2520,8 +2522,21 @@ class Music { if (!soundEnable) return; - return playSamples(this.cachedSamples, volume, 1, 0, loop); + this.source = playSamples(this.cachedSamples, volume, 1, 0, loop); } + + /** Stop the music */ + stop() + { + if (this.source) + this.source.stop(); + this.source = 0; + } + + /** Check if music is playing + * @return {Boolean} + */ + isPlaying() { return this.source; } } /** Play an mp3 or wav audio from a local file or url @@ -3212,7 +3227,7 @@ class ParticleEmitter extends EngineObject * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0 * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter * @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied - * @param {Number} [tileSize=tileSizeDefault] - Tile size for particles + * @param {Vector2} [tileSize=tileSizeDefault] - Tile size for particles * @param {Color} [colorStartA=new Color(1,1,1)] - Color at start of life 1, randomized between start colors * @param {Color} [colorStartB=new Color(1,1,1)] - Color at start of life 2, randomized between start colors * @param {Color} [colorEndA=new Color(1,1,1,0)] - Color at end of life 1, randomized between end colors @@ -4190,7 +4205,7 @@ gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STR const engineName = 'LittleJS'; /** Version of engine */ -const engineVersion = '1.4.9'; +const engineVersion = '1.5.0'; /** Frames per second to update objects * @default */ diff --git a/engine/engine.all.module.min.js b/engine/engine.all.module.min.js index c389fe65..4bec8a33 100644 --- a/engine/engine.all.module.min.js +++ b/engine/engine.all.module.min.js @@ -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)=>aa>b?a:b,sign=a=>0>a?-1:1,mod=(a,b=1)=>(a%b+b)%b,clamp=(a,b=0,c=1)=>ac?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/2*(1-Math.cos(c*a*2*PI)),formatTime=a=>(a/60|0)+":"+(10>a%60?"0":"")+(a%60|0),rand=(a=1,b=0)=>b+(a-b)*Math.random(),randInt=(a=1,b=0)=>rand(a,b)|0,randSign=()=>2*randInt(2)-1,randInCircle=(a=1,b=0)=>0(new Vector2).setAngle(rand(2*PI),a),randColor=(a=new Color,b=new Color(0,0,0,1),c)=>c?a.lerp(b,rand()):new Color(rand(a.r,b.r),rand(a.g,b.g),rand(a.b,b.b),rand(a.a,b.a));let randSeed=1;const setRandSeed=a=>randSeed=a,randSeeded=(a=1,b=0)=>{randSeed^=randSeed<<13;randSeed^=randSeed>>>17;randSeed^=randSeed<<5;return b+(a-b)*abs(randSeed%1e9)/1e9},vec2=(a=0,b)=>void 0==a.x?new Vector2(a,void 0==b?a:b):new Vector2(a.x,a.y),isVector2=a=>!isNaN(a.x)&&!isNaN(a.y);class Vector2{constructor(a=0,b=0){this.x=a;this.y=b}copy(){return new Vector2(this.x,this.y)}add(a){ASSERT(isVector2(a));return new Vector2(this.x+a.x,this.y+a.y)}subtract(a){ASSERT(isVector2(a));return new Vector2(this.x-a.x,this.y-a.y)}multiply(a){ASSERT(isVector2(a));return new Vector2(this.x*a.x,this.y*a.y)}divide(a){ASSERT(isVector2(a));return new Vector2(this.x/a.x,this.y/a.y)}scale(a){ASSERT(!isVector2(a));return new Vector2(this.x*a,this.y*a)}length(){return this.lengthSquared()**.5}lengthSquared(){return this.x**2+this.y**2}distance(a){return this.distanceSquared(a)**.5}distanceSquared(a){return(this.x-a.x)**2+(this.y-a.y)**2}normalize(a=1){const b=this.length();return b?this.scale(a/b):new Vector2(0,a)}clampLength(a=1){const b=this.length();return b>a?this.scale(a/b):this}dot(a){ASSERT(isVector2(a));return this.x*a.x+this.y*a.y}cross(a){ASSERT(isVector2(a));return this.x*a.y-this.y*a.x}angle(){return Math.atan2(this.x,this.y)}setAngle(a=0,b=1){this.x=b*Math.sin(a);this.y=b*Math.cos(a);return this}rotate(a){const b=Math.cos(a);a=Math.sin(a);return new Vector2(this.x*b-this.y*a,this.x*a+this.y*b)}direction(){return abs(this.x)>abs(this.y)?0>this.x?3:1:0>this.y?2:0}invert(){return new Vector2(this.y,-this.x)}floor(){return new Vector2(Math.floor(this.x),Math.floor(this.y))}area(){return abs(this.x*this.y)}lerp(a,b){ASSERT(isVector2(a));return this.add(a.subtract(this).scale(clamp(b)))}arrayCheck(a){return 0<=this.x&&0<=this.y&&this.xthis.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,g,k)=>(k=(k%1+1)%1)<1/6?f+6*(g-f)*k:.5>k?g:k<2/3?f+(g-f)*(2/3-k)*6:f;this.r=e(c,b,a+1/3);this.g=e(c,b,a);this.b=e(c,b,a-1/3);this.a=d;return this}getHSLA(){const a=clamp(this.r),b=clamp(this.g),c=clamp(this.b),d=clamp(this.a),e=Math.max(a,b,c),f=Math.min(a,b,c),g=(e+f)/2;let k=0,h=0;if(e!=f){let l=e-f;h=.5(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=7this.time}get(){return this.isSet()?time-this.time:0}getPercent(){return this.isSet()?percent(this.time-time,this.setTime,0):0}toString(){if(debug)return this.unset()?"unset":Math.abs(this.get())+" seconds "+(0>this.get()?"before":"after")}valueOf(){return this.get()}}"use strict";let cameraPos=vec2(),cameraScale=32,canvasMaxSize=vec2(1920,1200),canvasFixedSize=vec2(),cavasPixelated=1,fontDefault="arial",glEnable=1,glOverlay=1,tileSizeDefault=vec2(16),tileFixBleedScale=.3,objectDefaultSize=vec2(1),enablePhysicsSolver=1,objectDefaultMass=1,objectDefaultDamping=.99,objectDefaultAngleDamping=.99,objectDefaultElasticity=0,objectDefaultFriction=.8,objectMaxSpeed=1,gravity=0,particleEmitRateScale=1,gamepadsEnable=1,gamepadDirectionEmulateStick=1,inputWASDEmulateDirection=1,touchGamepadEnable=0,touchGamepadAnalog=1,touchGamepadSize=99,touchGamepadAlpha=.3,vibrateEnable=1,soundEnable=1,soundVolume=.5,soundDefaultRange=40,soundDefaultTaper=.7,medalDisplayTime=5,medalDisplaySlideTime=.5,medalDisplaySize=vec2(640,80),medalDisplayIconSize=50,medalsPreventUnlock;"use strict";const engineName="LittleJS",engineVersion="1.4.9",frameRate=60,timeDelta=1/frameRate;let engineObjects=[],engineObjectsCollide=[],frame=0,time=0,timeReal=0,paused=0;function setPaused(a){paused=a}function engineInit(a,b,c,d,e,f){function g(m=0){var n=m-k;k=m;if(debug||showWatermark)l=lerp(.05,l,1e3/(n||1));m=debug&&keyIsDown(107);const p=debug&&keyIsDown(109);debug&&(n*=m?5:p?.2:1);timeReal+=n/1e3;h+=!paused*n;m||(h=min(h,50));canvasFixedSize.x?(overlayCanvas.width=mainCanvas.width=canvasFixedSize.x,overlayCanvas.height=mainCanvas.height=canvasFixedSize.y,n=innerWidth/innerHeight,m=mainCanvas.width/mainCanvas.height,(glCanvas||mainCanvas).style.width=mainCanvas.style.width=overlayCanvas.style.width=nh&&-9r.renderOrder-w.renderOrder);for(var q of engineObjects)q.destroyed||q.render();e();glRenderPostProcess();medalsRender();touchGamepadRender();debugRender();glEnable&&glCopyToContext(mainContext);showWatermark&&(overlayContext.textAlign="right",overlayContext.textBaseline="top",overlayContext.font="1em monospace",overlayContext.fillStyle="#000",q=engineName+" v"+engineVersion+" / "+drawCount+" / "+engineObjects.length+" / "+l.toFixed(1)+(glEnable?" GL":" 2D"),overlayContext.fillText(q,mainCanvas.width-3,3),overlayContext.fillStyle="#fff",overlayContext.fillText(q,mainCanvas.width-2,2),drawCount=0);requestAnimationFrame(g)}tileImage.onerror=tileImage.onload=()=>{tileImageFixBleed=vec2(tileFixBleedScale).divide(tileImageSize=vec2(tileImage.width,tileImage.height));debug&&(tileImage.onload=()=>ASSERT(1));document.body.style="margin:0;overflow:hidden;background:#000;touch-action:none;user-select:none;-webkit-user-select:none";document.body.appendChild(mainCanvas=document.createElement("canvas"));mainContext=mainCanvas.getContext("2d");debugInit();glEnable&&glInit();document.body.appendChild(overlayCanvas=document.createElement("canvas"));overlayContext=overlayCanvas.getContext("2d");(glCanvas||mainCanvas).style=mainCanvas.style=overlayCanvas.style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)";a();touchGamepadCreate();g()};let k=0,h,l;f?tileImage.src=f:tileImage.onload()}function enginePreRender(){mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height);mainContext.imageSmoothingEnabled=!cavasPixelated;glEnable&&glPreRender()}function engineObjectsUpdate(){engineObjectsCollide=engineObjects.filter(b=>b.collideSolidObjects);const a=b=>{if(!b.destroyed){b.update();for(const c of b.children)a(c)}};for(const b of engineObjects)b.parent||a(b);engineObjects=engineObjects.filter(b=>!b.destroyed);time=++frame/frameRate}function engineObjectsDestroy(){for(const a of engineObjects)a.parent||a.destroy();engineObjects=engineObjects.filter(a=>!a.destroyed)}function engineObjectsCallback(a,b,c,d=engineObjects){if(a)if(void 0!=b.x)for(const e of d)isOverlapping(a,b,e.pos,e.size)&&c(e);else{b*=b;for(const e of d)a.distanceSquared(e.pos)=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)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{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,g,k=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 l=tileImageSize.x/d.x|0;h=d.x/tileImageSize.x;const m=d.y/tileImageSize.y,n=c%l*h;l=(c/l|0)*m;glDraw(a.x,a.y,g?-b.x:b.x,b.y,f,n+tileImageFixBleed.x,l+tileImageFixBleed.y,n-tileImageFixBleed.x+h,l-tileImageFixBleed.y+m,e.rgbaInt(),k.rgbaInt())}else drawCanvas2D(a,b,f,g,m=>{if(0>c)m.fillStyle=e,m.fillRect(-.5,-.5,1,1);else{var n=tileImageSize.x/d.x|0;const p=c%n*d.x+tileFixBleedScale;n=(c/n|0)*d.y+tileFixBleedScale;const q=d.x-2*tileFixBleedScale,r=d.y-2*tileFixBleedScale;m.globalAlpha=e.a;m.drawImage(tileImage,p,n,q,r,-.5,-.5,1,1)}})}function drawRect(a,b,c,d,e){drawTile(a,b,-1,tileSizeDefault,c,d,0,0,e)}function drawTileScreenSpace(a,b=vec2(1),c,d,e,f,g,k,h){drawTile(screenToWorld(a),b.scale(1/cameraScale),c,d,e,f,g,k,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),g="center",k=fontDefault,h=overlayContext){h.fillStyle=d;h.lineWidth=e;h.strokeStyle=f;h.textAlign=g;h.font=c+"px "+k;h.textBaseline="middle";h.lineJoin="round";b=b.copy();(a+"").split("\n").forEach(l=>{e&&h.strokeText(l,b.x,b.y);h.fillText(l,b.x,b.y);b.y+=c})}function drawText(a,b,c=1,d,e,f,g,k){drawTextScreen(a,worldToScreen(b),c*cameraScale,d,e*cameraScale,f,g,k,mainContext)}let engineFontImage;class FontImage{constructor(a,b=vec2(8),c=vec2(0,1),d=0,e=overlayContext){engineFontImage||((engineFontImage=new Image).src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC");this.image=a||engineFontImage;this.tileSize=b;this.paddingSize=c;this.startTileIndex=d;this.context=e}drawTextScreen(a,b,c=4,d){const e=this.context;e.save();e.imageSmoothingEnabled=!cavasPixelated;const f=this.tileSize,g=f.add(this.paddingSize).scale(c),k=this.image.width/this.tileSize.x|0;(a+"").split("\n").forEach((h,l)=>{const m=d?h.length*f.x*c/2|0:0;for(let q=h.length;q--;){var n=h[q].charCodeAt();if(32>n||127document.fullscreenElement;function toggleFullscreen(){isFullscreen()?document.exitFullscreen&&document.exitFullscreen():document.body.requestFullscreen&&document.body.requestFullscreen()}"use strict";const keyIsDown=(a,b=0)=>inputData[b]&&inputData[b][a]&1,keyWasPressed=(a,b=0)=>inputData[b]&&inputData[b][a]&2?1:0,keyWasReleased=(a,b=0)=>inputData[b]&&inputData[b][a]&4?1:0,clearInput=()=>inputData=[[]],mouseIsDown=keyIsDown,mouseWasPressed=keyWasPressed,mouseWasReleased=keyWasReleased;let mousePos=vec2(),mousePosScreen=vec2(),mouseWheel=0,isUsingGamepad=0,preventDefaultInput=0;const gamepadIsDown=(a,b=0)=>keyIsDown(a,b+1),gamepadWasPressed=(a,b=0)=>keyWasPressed(a,b+1),gamepadWasReleased=(a,b=0)=>keyWasReleased(a,b+1),gamepadStick=(a,b=0)=>stickData[b]?stickData[b][a]||vec2():vec2();let inputData=[[]];function inputUpdate(){isTouchDevice||document.hasFocus()||clearInput();mousePos=screenToWorld(mousePosScreen);gamepadsUpdate()}function inputUpdatePost(){for(const a of inputData)for(const b in a)a[b]&=1;mouseWheel=0}onkeydown=a=>{debug&&a.target!=document.body||(a.repeat||(inputData[isUsingGamepad=0][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 g=inputData[b+1]||(inputData[b+1]=[]);c=stickData[b]||(stickData[b]=[]);if(d){var e=k=>.3k?-percent(-k,.3,.8):0;for(var f=0;f>1]=vec2(e(d.axes[f]),e(-d.axes[f+1])).clampLength();for(e=d.buttons.length;e--;)f=d.buttons[e],g[e]=f.pressed?1+2*!gamepadIsDown(e,b):4*gamepadIsDown(e,b),isUsingGamepad|=!b&&f.pressed,touchGamepadEnable&&touchGamepadTimer.unset();gamepadDirectionEmulateStick&&(d=vec2(gamepadIsDown(15,b)-gamepadIsDown(14,b),gamepadIsDown(12,b)-gamepadIsDown(13,b)),d.lengthSquared()&&(c[0]=d.clampLength()))}}}const vibrate=a=>vibrateEnable&&navigator&&navigator.vibrate&&navigator.vibrate(a),vibrateStop=()=>vibrate(0),isTouchDevice=void 0!==window.ontouchstart;if(isTouchDevice){let a,b=onmousedown,c=onmouseup;onmousedown=onmouseup=()=>0;ontouchstart=d=>{zzfx(0);ontouchstart=ontouchmove=ontouchend=e=>{e.button=0;const f=e.touches.length;f?(e.x=e.touches[0].clientX,e.y=e.touches[0].clientY,a?onmousemove(e):b(e)):a&&c(e);a=f;return!0};return ontouchstart(d)}}let touchGamepadTimer=new Timer,touchGamepadButtons,touchGamepadStick;function touchGamepadCreate(){touchGamepadEnable&&isTouchDevice&&(touchGamepadButtons=[],touchGamepadStick=vec2(),ontouchstart=a=>{zzfx(0);ontouchstart=ontouchmove=ontouchend=b=>{touchGamepadStick=vec2();touchGamepadButtons=[];if(b.touches.length&&(isUsingGamepad=1,touchGamepadTimer.set(),paused)){touchGamepadButtons[9]=1;return}const c=vec2(touchGamepadSize,mainCanvasSize.y-touchGamepadSize),d=mainCanvasSize.subtract(vec2(touchGamepadSize,touchGamepadSize)),e=mainCanvasSize.scale(.5);for(const f of b.touches)b=mouseToScreen(vec2(f.clientX,f.clientY)),b.distance(c)e*e)return;b*=percent(f**.5,e,e*this.taper)}e=2*worldToScreen(a).x/mainCanvas.width-1}a=c+c*this.randomness*d*rand(-1,1);return playSamples([this.cachedSamples],b,a,e)}}playNote(a,b,c){if(soundEnable)return this.play(b,c,2**(a/12),0)}}class Music{constructor(a){soundEnable&&(this.cachedSamples=zzfxM(...a))}play(a,b=1){if(soundEnable)return 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 AudioContext,audioContext.resume(),"running"==audioContext.state)){var f=audioContext.createBuffer(a.length,a[0].length,zzfxR),g=audioContext.createBufferSource();a.forEach((k,h)=>f.getChannelData(h).set(k));g.buffer=f;g.playbackRate.value=c;g.loop=e;a=audioContext.createGain();a.gain.value=soundVolume*b;a.connect(audioContext.destination);g.connect(new StereoPannerNode(audioContext,{pan:clamp(d,-1,1)})).connect(a);g.start();return g}}const zzfx=(...a)=>playSamples([zzfxG(...a)]),zzfxR=44100;function zzfxG(a=1,b=.05,c=220,d=0,e=0,f=.1,g=0,k=1,h=0,l=0,m=0,n=0,p=0,q=0,r=0,w=0,u=0,B=1,z=0,C=0){let v=2*PI,F=h*=500*v/zzfxR/zzfxR,A=[];b=c*=(1+b*rand(-1,1))*v/zzfxR;let x=0,D=0,t=0,y=1,G=0,I=0,E=0,J,H;d=d*zzfxR+9;z*=zzfxR;e*=zzfxR;f*=zzfxR;u*=zzfxR;l*=500*v/zzfxR**3;r*=v/zzfxR;m*=v/zzfxR;n*=zzfxR;p=p*zzfxR|0;for(H=d+z+e+f+u|0;tt?0:(tn&&(c+=m,b+=m,y=0),!p||++G%p||(c=b,h=F,y=y||1);return A}function zzfxM(a,b,c,d=125){let e,f,g,k,h,l,m,n,p,q,r,w,u,B=0,z,C=[],v=[],F=[],A=0,x=0,D=1,t={},y=zzfxR/d*60>>2;for(;D;A++)C=[D=n=w=0],c.forEach((G,I)=>{m=b[G][A]||[0,0,0];D|=!!b[G][A];z=w+(b[G][0].length-2-!n)*y;u=I==c.length-1;e=2;for(g=w;ey-99&&p?r+=(1>r)/99:0)l=(1-r)*C[B++]/2||0,v[g]=(v[g]||0)-l*x+l,F[g]=(F[g++]||0)+l*x+l;h&&(r=h%1,x=m[1]||0,h|=0)&&(C=t[[q=m[B=0]||0,h]]=t[[q,h]]||(k=[...a[q]],k[2]*=2**((h-12)/12),0a.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=f&&(m+=f,h+=g);k<=e&&(m+=e,l+=d)}debugRaycast&&debugLine(a,b,"#00f",.02,1)}class TileLayerData{constructor(a,b=0,c=0,d=new Color){this.tile=a;this.direction=b;this.mirror=c;this.color=d}clear(){this.tile=this.direction=this.mirror=0;color=new Color}}class TileLayer extends EngineObject{constructor(a,b=tileCollisionSize,c=tileSizeDefault,d=vec2(1),e=0){super(a,b,-1,c,0,void 0,e);this.canvas=document.createElement("canvas");this.context=this.canvas.getContext("2d");this.scale=d;this.isOverlay;this.data=[];for(a=this.size.area();a--;)this.data.push(new TileLayerData)}setData(a,b,c){a.arrayCheck(this.size)&&(this.data[(a.y|0)*this.size.x+a.x|0]=b,c&&this.drawTileData(a))}getData(a){return a.arrayCheck(this.size)&&this.data[(a.y|0)*this.size.x+a.x|0]}update(){}render(){ASSERT(mainContext!=this.context);glEnable&&!glOverlay&&!this.isOverlay&&glCopyToContext(mainContext);const a=worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));(this.isOverlay?overlayContext:mainContext).drawImage(this.canvas,a.x,a.y,cameraScale*this.size.x*this.scale.x,cameraScale*this.size.y*this.scale.y)}redraw(){this.redrawStart(1);this.drawAllTileData();this.redrawEnd()}redrawStart(a=0){this.savedRenderSettings=[mainCanvas,mainContext,mainCanvasSize,cameraPos,cameraScale];mainCanvas=this.canvas;mainContext=this.context;cameraPos=this.size.scale(.5);cameraScale=this.tileSize.x;a&&(mainCanvas.width=this.size.x*this.tileSize.x,mainCanvas.height=this.size.y*this.tileSize.y);enginePreRender()}redrawEnd(){ASSERT(mainContext==this.context);glEnable&&glCopyToContext(mainContext,1);[mainCanvas,mainContext,mainCanvasSize,cameraPos,cameraScale]=this.savedRenderSettings}drawTileData(a){const b=a.floor().add(this.pos).add(vec2(.5));this.drawCanvas2D(b,vec2(1),0,0,c=>c.clearRect(-.5,-.5,1,1));a=this.getData(a);void 0!=a.tile&&(ASSERT(mainContext==this.context),drawTile(b,vec2(1),a.tile,this.tileSize,a.color,a.direction*PI/2,a.mirror))}drawAllTileData(){for(let a=this.size.x;a--;)for(let b=this.size.y;b--;)this.drawTileData(vec2(a,b))}drawCanvas2D(a,b,c=0,d,e){const f=this.context;f.save();a=a.subtract(this.pos).multiply(this.tileSize);b=b.multiply(this.tileSize);f.translate(a.x,this.canvas.height-a.y);f.rotate(c);f.scale(d?-b.x:b.x,b.y);e(f);f.restore()}drawTile(a,b=vec2(1),c=-1,d=tileSizeDefault,e=new Color,f,g){this.drawCanvas2D(a,b,f,g,k=>{if(0>c)k.fillStyle=e,k.fillRect(-.5,-.5,1,1);else{const h=tileImage.width/d.x;k.globalAlpha=e.a;k.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,g=-1,k=tileSizeDefault,h=new Color,l=new Color,m=new Color(1,1,1,0),n=new Color(1,1,1,0),p=.5,q=.1,r=1,w=.1,u=.05,B=1,z=1,C=0,v=PI,F=.1,A=.2,x,D,t=1,y=D?1e9:0,G){super(a,new Vector2,g,k,b,void 0,y);this.emitSize=c;this.emitTime=d;this.emitRate=e;this.emitConeAngle=f;this.colorStartA=h;this.colorStartB=l;this.colorEndA=m;this.colorEndB=n;this.randomColorLinear=t;this.particleTime=p;this.sizeStart=q;this.sizeEnd=r;this.speed=w;this.angleSpeed=u;this.damping=B;this.angleDamping=z;this.gravityScale=C;this.particleConeAngle=v;this.fadeRate=F;this.randomness=A;this.collideTiles=x;this.additive=D;this.localSpace=G;this.emitTimeBuffer=this.trailScale=0}update(){this.parent&&super.update();if(!this.emitTime|this.getAliveTime()<=this.emitTime){if(this.emitRate*particleEmitRateScale){const a=1/this.emitRate/particleEmitRateScale;for(this.emitTimeBuffer+=timeDelta;0m+m*rand(c,-c);b=d(this.particleTime);const e=d(this.sizeStart),f=d(this.sizeEnd),g=d(this.speed);d=d(this.angleSpeed)*randSign();var k=rand(this.emitConeAngle,-this.emitConeAngle);const h=randColor(this.colorStartA,this.colorStartB,this.randomColorLinear),l=randColor(this.colorEndA,this.colorEndB,this.randomColorLinear);k=this.localSpace?k:this.angle+k;a.colorStart=h;a.colorEndDelta=l.subtract(h);a.velocity=(new Vector2).setAngle(k,g);a.angleVelocity=d;a.lifeTime=b;a.sizeStart=e;a.sizeEndDelta=f-e;a.fadeRate=this.fadeRate;a.damping=this.damping;a.angleDamping=this.angleDamping;a.elasticity=this.elasticity;a.friction=this.friction;a.gravityScale=this.gravityScale;a.collideTiles=this.collideTiles;a.additive=this.additive;a.renderOrder=this.renderOrder;a.trailScale=this.trailScale;a.mirror=randInt(2);a.localSpaceEmitter=this.localSpace&&this;a.destroyCallback=this.particleDestroyCallback;this.particleCreateCallback&&this.particleCreateCallback(a);return a}render(){}}class Particle extends EngineObject{constructor(a,b,c,d){super(a,new Vector2,b,c,d)}render(){const a=min((time-this.spawnTime)/this.lifeTime,1);var b=this.sizeStart+a*this.sizeEndDelta;b=new Vector2(b,b);var c=this.fadeRate/2;c=new Color(this.colorStart.r+a*this.colorEndDelta.r,this.colorStart.g+a*this.colorEndDelta.g,this.colorStart.b+a*this.colorEndDelta.b,(this.colorStart.a+a*this.colorEndDelta.a)*(a1-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 g=e*this.trailScale;b.y=max(b.x,g);e=f.angle();drawTile(d.add(f.multiply(vec2(0,-g/2))),b,this.tileIndex,this.tileSize,c,e,this.mirror)}else drawTile(d,b,this.tileIndex,this.tileSize,c,e,this.mirror);this.additive&&setBlendMode();debugParticles&&debugRect(d,b,"#f005",0,e);1==a&&(this.color=c,this.size=b,this.destroyCallback&&this.destroyCallback(this),this.destroyed=1)}}"use strict";const medals=[];let medalsDisplayQueue=[],medalsSaveName,medalsDisplayTimeLast;function medalsInit(a){medalsSaveName=a;debugMedals||medals.forEach(b=>b.unlocked=localStorage[b.storageKey()]|0)}class Medal{constructor(a,b,c="",d="🏆",e){ASSERT(0<=a&&!medals[a]);medals[this.id=a]=this;this.name=b;this.description=c;this.icon=d;e&&((this.image=new Image).src=e)}unlock(){medalsPreventUnlock||this.unlocked||(ASSERT(medalsSaveName),localStorage[this.storageKey()]=this.unlocked=1,medalsDisplayQueue.push(this),newgrounds&&newgrounds.unlockMedal(this.id))}render(a=0){const b=overlayContext;var c=min(medalDisplaySize.x,mainCanvas.width);const d=overlayCanvas.width-c;a*=-medalDisplaySize.y;b.save();b.beginPath();b.fillStyle=new Color(.9,.9,.9);b.strokeStyle=new Color(0,0,0);b.lineWidth=3;b.fill(b.rect(d,a,c,medalDisplaySize.y));b.stroke();b.clip();this.renderIcon(vec2(d+15+medalDisplayIconSize/2,a+medalDisplaySize.y/2));c=vec2(d+medalDisplayIconSize+30,a+28);drawTextScreen(this.name,c,38,new Color(0,0,0),0,0,"left");c.y+=32;drawTextScreen(this.description,c,24,new Color(0,0,0),0,0,"left");b.restore()}renderIcon(a,b=medalDisplayIconSize){this.image?overlayContext.drawImage(this.image,a.x-b/2,a.y-b/2,b,b):drawTextScreen(this.icon,a,.7*b,new Color(0,0,0))}storageKey(){return medalsSaveName+"_"+this.id}}function medalsRender(){if(medalsDisplayQueue.length){var a=medalsDisplayQueue[0],b=timeReal-medalsDisplayTimeLast;if(medalsDisplayTimeLast)if(b>medalDisplayTime)medalsDisplayQueue.shift(medalsDisplayTimeLast=0);else{const c=medalDisplayTime-medalDisplaySlideTime;a.render(bc?(b-c)/medalDisplaySlideTime:0)}else medalsDisplayTimeLast=timeReal}}let newgrounds;function newgroundsInit(a,b){newgrounds=new Newgrounds(a,b)}class Newgrounds{constructor(a,b){ASSERT(!newgrounds&&a);this.app_id=a;this.cipher=b;this.host=location?location.hostname:"";b&&(this.cryptoJS=this.CryptoJS());if(this.session_id=new URL(location.href).searchParams.get("ngio_session_id")){this.medals=(a=this.call("Medal.getList"))?a.result.data.medals:[];debugMedals&&console.log(this.medals);for(var c of this.medals)if(a=medals[c.id])a.image=new Image,a.image.src=c.icon,a.name=c.name,a.description=c.description,a.unlocked=c.unlocked,a.difficulty=c.difficulty,a.value=c.value,a.value&&(a.description=a.description+" ("+a.value+")");this.scoreboards=(c=this.call("ScoreBoard.getBoards"))?c.result.data.scoreboards:[];debugMedals&&console.log(this.scoreboards);setInterval(()=>this.call("Gateway.ping",0,1),3e5)}}unlockMedal(a){return this.call("Medal.unlock",{id:a},1)}postScore(a,b){return this.call("ScoreBoard.postScore",{id:a,value:b},1)}getScores(a,b=0,c=0,d=0,e=10){return this.call("ScoreBoard.getScores",{id:a,user:b,social:c,skip:d,limit:e})}logView(){return this.call("App.logView",{host:this.host},1)}call(a,b=0,c=0){a={component:a,parameters:b};if(this.cipher){b=this.cryptoJS;var d=b.enc.Base64.parse(this.cipher);const e=b.lib.WordArray.random(16);d=b.AES.encrypt(JSON.stringify(a),d,{iv:e});a.secure=b.enc.Base64.stringify(e.concat(d.ciphertext));a.parameters=0}b={app_id:this.app_id,session_id:this.session_id,call:a};a=new FormData;a.append("input",JSON.stringify(b));b=new XMLHttpRequest;b.open("POST","https://newgrounds.io/gateway_v3.php",!debugMedals&&c);b.send(a);debugMedals&&console.log(b.responseText);return b.responseText&&JSON.parse(b.responseText)}CryptoJS(){return eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",..."]charCodeAtUinyxpf","for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h>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{d=glContext.getAttribLocation(glShader,d);glContext.enableVertexAttribArray(d);glContext.vertexAttribPointer(d,g,e,k,gl_VERTEX_BYTE_STRIDE,a);a+=g*f};b("p",gl_FLOAT,4,2);b("t",gl_FLOAT,4,2);b("c",gl_UNSIGNED_BYTE,1,4,1);b("a",gl_UNSIGNED_BYTE,1,4,1);b=2*cameraScale/mainCanvas.width;const c=2*cameraScale/mainCanvas.height;glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader,"m"),0,new Float32Array([b,0,0,0,0,c,0,0,1,1,-1,1,-1-b*cameraPos.x,-1-c*cameraPos.y,0,0]))}function glFlush(){if(glBatchCount){var a=glBatchAdditive?gl_ONE:gl_ONE_MINUS_SRC_ALPHA;glContext.blendFuncSeparate(gl_SRC_ALPHA,a,gl_ONE,a);glContext.enable(gl_BLEND);glContext.bufferSubData(gl_ARRAY_BUFFER,0,glPositionData.subarray(0,glBatchCount*gl_VERTICES_PER_QUAD*gl_INDICIES_PER_VERT));glContext.drawArrays(gl_TRIANGLES,0,glBatchCount*gl_VERTICES_PER_QUAD);glBatchCount=0;glBatchAdditive=glAdditive}}function glCopyToContext(a,b){if(glBatchCount||b)glFlush(),glOverlay&&!b||a.drawImage(glCanvas,0,0)}function glDraw(a,b,c,d,e,f,g,k,h,l,m=0){glBatchCount!=gl_MAX_BATCH&&glBatchAdditive==glAdditive||glFlush();var n=Math.cos(e)/2,p=Math.sin(e)/2;e=n*c;n*=d;c*=p;d*=p;for(let q=6,r=glBatchCount++*gl_VERTICES_PER_QUAD*gl_INDICIES_PER_VERT;q--;){p=q-4&&1cameraPos=v;const setCameraScale=v=>cameraScale=v;const setCanvasMaxSize=v=>canvasMaxSize=v;const setCanvasFixedSize=v=>canvasFixedSize=v;const setCavasPixelated=v=>cavasPixelated=v;const setFontDefault=v=>fontDefault=v;const setGlEnable=v=>glEnable=v;const setGlOverlay=v=>glOverlay=v;const setTileSizeDefault=v=>tileSizeDefault=v;const setTileFixBleedScale=v=>tileFixBleedScale=v;const setObjectDefaultSize=v=>objectDefaultSize=v;const setEnablePhysicsSolver=v=>enablePhysicsSolver=v;const setObjectDefaultMass=v=>objectDefaultMass=v;const setObjectDefaultDamping=v=>objectDefaultDamping=v;const setObjectDefaultAngleDamping=v=>objectDefaultAngleDamping=v;const setObjectDefaultElasticity=v=>objectDefaultElasticity=v;const setObjectDefaultFriction=v=>objectDefaultFriction=v;const setObjectMaxSpeed=v=>objectMaxSpeed=v;const setGravity=v=>gravity=v;const setParticleEmitRateScale=v=>particleEmitRateScale=v;const setGamepadsEnable=v=>gamepadsEnable=v;const setGamepadDirectionEmulateStick=v=>gamepadDirectionEmulateStick=v;const setInputWASDEmulateDirection=v=>inputWASDEmulateDirection=v;const setTouchGamepadEnable=v=>touchGamepadEnable=v;const setTouchGamepadAnalog=v=>touchGamepadAnalog=v;const setTouchGamepadSize=v=>touchGamepadSize=v;const setTouchGamepadAlpha=v=>touchGamepadAlpha=v;const setVibrateEnable=v=>vibrateEnable=v;const setSoundEnable=v=>soundEnable=v;const setSoundVolume=v=>soundVolume=v;const setSoundDefaultRange=v=>soundDefaultRange=v;const setSoundDefaultTaper=v=>soundDefaultTaper=v;const setMedalDisplayTime=v=>medalDisplayTime=v;const setMedalDisplaySlideTime=v=>medalDisplaySlideTime=v;const setMedalDisplaySize=v=>medalDisplaySize=v;const setMedalDisplayIconSize=v=>medalDisplayIconSize=v;const setMedalsPreventUnlock=v=>medalsPreventUnlock=v;export{setCameraPos,setCameraScale,setCanvasMaxSize,setCanvasFixedSize,setCavasPixelated,setFontDefault,setGlEnable,setGlOverlay,setTileSizeDefault,setTileFixBleedScale,setObjectDefaultSize,setEnablePhysicsSolver,setObjectDefaultMass,setObjectDefaultDamping,setObjectDefaultAngleDamping,setObjectDefaultElasticity,setObjectDefaultFriction,setObjectMaxSpeed,setGravity,setParticleEmitRateScale,setGamepadsEnable,setGamepadDirectionEmulateStick,setInputWASDEmulateDirection,setTouchGamepadEnable,setTouchGamepadAnalog,setTouchGamepadSize,setTouchGamepadAlpha,setVibrateEnable,setSoundEnable,setSoundVolume,setSoundDefaultRange,setSoundDefaultTaper,setMedalDisplayTime,setMedalDisplaySlideTime,setMedalDisplaySize,setMedalDisplayIconSize,setMedalsPreventUnlock,canvasMaxSize,canvasFixedSize,cavasPixelated,fontDefault,tileSizeDefault,tileFixBleedScale,objectDefaultSize,enablePhysicsSolver,objectDefaultMass,objectDefaultDamping,objectDefaultAngleDamping,objectDefaultElasticity,objectDefaultFriction,objectMaxSpeed,gravity,particleEmitRateScale,cameraPos,cameraScale,glEnable,glOverlay,gamepadsEnable,gamepadDirectionEmulateStick,inputWASDEmulateDirection,touchGamepadEnable,touchGamepadAnalog,touchGamepadSize,touchGamepadAlpha,vibrateEnable,soundEnable,soundVolume,soundDefaultRange,soundDefaultTaper,medalDisplayTime,medalDisplaySlideTime,medalDisplaySize,medalDisplayIconSize,debug,showWatermark,godMode,debugRect,debugCircle,debugPoint,debugLine,debugAABB,debugText,debugClear,debugSaveCanvas,PI,abs,min,max,sign,mod,clamp,percent,lerp,smoothStep,nearestPowerOfTwo,isOverlapping,wave,formatTime,rand,randInt,randSign,randInCircle,randVector,randColor,randSeed,setRandSeed,randSeeded,Vector2,Color,Timer,vec2,colorRGBA,colorHSLA,EngineObject,tileImage,mainCanvas,mainContext,overlayCanvas,overlayContext,mainCanvasSize,screenToWorld,worldToScreen,drawTile,drawRect,drawTileScreenSpace,drawRectScreenSpace,drawLine,drawCanvas2D,setBlendMode,drawTextScreen,drawText,engineFontImage,FontImage,isFullscreen,toggleFullscreen,keyIsDown,keyWasPressed,keyWasReleased,clearInput,mouseIsDown,mouseWasPressed,mouseWasReleased,mousePos,mousePosScreen,mouseWheel,isUsingGamepad,preventDefaultInput,gamepadIsDown,gamepadWasPressed,gamepadWasReleased,gamepadStick,mouseToScreen,gamepadsUpdate,vibrate,vibrateStop,isTouchDevice,Sound,Music,playAudioFile,speak,speakStop,getNoteFrequency,audioContext,playSamples,zzfx,tileCollision,tileCollisionSize,initTileCollision,setTileCollisionData,getTileCollisionData,tileCollisionTest,tileCollisionRaycast,TileLayerData,TileLayer,ParticleEmitter,Particle,medals,medalsPreventUnlock,medalsInit,newgroundsInit,Medal,Newgrounds,glCanvas,glContext,glSetBlendMode,glSetTexture,glCompileShader,glCreateProgram,glCreateTexture,glInitPostProcess,engineName,engineVersion,frameRate,timeDelta,engineObjects,frame,time,timeReal,paused,setPaused,engineInit,engineObjectsUpdate,engineObjectsDestroy,engineObjectsCallback}; \ No newline at end of file +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)=>aa>b?a:b,sign=a=>0>a?-1:1,mod=(a,b=1)=>(a%b+b)%b,clamp=(a,b=0,c=1)=>ac?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/2*(1-Math.cos(c*a*2*PI)),formatTime=a=>(a/60|0)+":"+(10>a%60?"0":"")+(a%60|0),rand=(a=1,b=0)=>b+(a-b)*Math.random(),randInt=(a=1,b=0)=>rand(a,b)|0,randSign=()=>2*randInt(2)-1,randInCircle=(a=1,b=0)=>0(new Vector2).setAngle(rand(2*PI),a),randColor=(a=new Color,b=new Color(0,0,0,1),c)=>c?a.lerp(b,rand()):new Color(rand(a.r,b.r),rand(a.g,b.g),rand(a.b,b.b),rand(a.a,b.a));let randSeed=1;const setRandSeed=a=>randSeed=a,randSeeded=(a=1,b=0)=>{randSeed^=randSeed<<13;randSeed^=randSeed>>>17;randSeed^=randSeed<<5;return b+(a-b)*abs(randSeed%1e9)/1e9},vec2=(a=0,b)=>void 0==a.x?new Vector2(a,void 0==b?a:b):new Vector2(a.x,a.y),isVector2=a=>!isNaN(a.x)&&!isNaN(a.y);class Vector2{constructor(a=0,b=0){this.x=a;this.y=b}copy(){return new Vector2(this.x,this.y)}add(a){ASSERT(isVector2(a));return new Vector2(this.x+a.x,this.y+a.y)}subtract(a){ASSERT(isVector2(a));return new Vector2(this.x-a.x,this.y-a.y)}multiply(a){ASSERT(isVector2(a));return new Vector2(this.x*a.x,this.y*a.y)}divide(a){ASSERT(isVector2(a));return new Vector2(this.x/a.x,this.y/a.y)}scale(a){ASSERT(!isVector2(a));return new Vector2(this.x*a,this.y*a)}length(){return this.lengthSquared()**.5}lengthSquared(){return this.x**2+this.y**2}distance(a){return this.distanceSquared(a)**.5}distanceSquared(a){return(this.x-a.x)**2+(this.y-a.y)**2}normalize(a=1){const b=this.length();return b?this.scale(a/b):new Vector2(0,a)}clampLength(a=1){const b=this.length();return b>a?this.scale(a/b):this}dot(a){ASSERT(isVector2(a));return this.x*a.x+this.y*a.y}cross(a){ASSERT(isVector2(a));return this.x*a.y-this.y*a.x}angle(){return Math.atan2(this.x,this.y)}setAngle(a=0,b=1){this.x=b*Math.sin(a);this.y=b*Math.cos(a);return this}rotate(a){const b=Math.cos(a);a=Math.sin(a);return new Vector2(this.x*b-this.y*a,this.x*a+this.y*b)}direction(){return abs(this.x)>abs(this.y)?0>this.x?3:1:0>this.y?2:0}invert(){return new Vector2(this.y,-this.x)}floor(){return new Vector2(Math.floor(this.x),Math.floor(this.y))}area(){return abs(this.x*this.y)}lerp(a,b){ASSERT(isVector2(a));return this.add(a.subtract(this).scale(clamp(b)))}arrayCheck(a){return 0<=this.x&&0<=this.y&&this.xthis.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,g,k)=>(k=(k%1+1)%1)<1/6?f+6*(g-f)*k:.5>k?g:k<2/3?f+(g-f)*(2/3-k)*6:f;this.r=e(c,b,a+1/3);this.g=e(c,b,a);this.b=e(c,b,a-1/3);this.a=d;return this}getHSLA(){const a=clamp(this.r),b=clamp(this.g),c=clamp(this.b),d=clamp(this.a),e=Math.max(a,b,c),f=Math.min(a,b,c),g=(e+f)/2;let k=0,h=0;if(e!=f){let l=e-f;h=.5(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=7this.time}get(){return this.isSet()?time-this.time:0}getPercent(){return this.isSet()?percent(this.time-time,this.setTime,0):0}toString(){if(debug)return this.unset()?"unset":Math.abs(this.get())+" seconds "+(0>this.get()?"before":"after")}valueOf(){return this.get()}}"use strict";let cameraPos=vec2(),cameraScale=32,canvasMaxSize=vec2(1920,1200),canvasFixedSize=vec2(),cavasPixelated=1,fontDefault="arial",glEnable=1,glOverlay=1,tileSizeDefault=vec2(16),tileFixBleedScale=.3,objectDefaultSize=vec2(1),enablePhysicsSolver=1,objectDefaultMass=1,objectDefaultDamping=.99,objectDefaultAngleDamping=.99,objectDefaultElasticity=0,objectDefaultFriction=.8,objectMaxSpeed=1,gravity=0,particleEmitRateScale=1,gamepadsEnable=1,gamepadDirectionEmulateStick=1,inputWASDEmulateDirection=1,touchGamepadEnable=0,touchGamepadAnalog=1,touchGamepadSize=99,touchGamepadAlpha=.3,vibrateEnable=1,soundEnable=1,soundVolume=.5,soundDefaultRange=40,soundDefaultTaper=.7,medalDisplayTime=5,medalDisplaySlideTime=.5,medalDisplaySize=vec2(640,80),medalDisplayIconSize=50,medalsPreventUnlock;"use strict";const engineName="LittleJS",engineVersion="1.5.0",frameRate=60,timeDelta=1/frameRate;let engineObjects=[],engineObjectsCollide=[],frame=0,time=0,timeReal=0,paused=0;function setPaused(a){paused=a}function engineInit(a,b,c,d,e,f){function g(m=0){var n=m-k;k=m;if(debug||showWatermark)l=lerp(.05,l,1e3/(n||1));m=debug&&keyIsDown(107);const p=debug&&keyIsDown(109);debug&&(n*=m?5:p?.2:1);timeReal+=n/1e3;h+=!paused*n;m||(h=min(h,50));canvasFixedSize.x?(overlayCanvas.width=mainCanvas.width=canvasFixedSize.x,overlayCanvas.height=mainCanvas.height=canvasFixedSize.y,n=innerWidth/innerHeight,m=mainCanvas.width/mainCanvas.height,(glCanvas||mainCanvas).style.width=mainCanvas.style.width=overlayCanvas.style.width=nh&&-9r.renderOrder-w.renderOrder);for(var q of engineObjects)q.destroyed||q.render();e();glRenderPostProcess();medalsRender();touchGamepadRender();debugRender();glEnable&&glCopyToContext(mainContext);showWatermark&&(overlayContext.textAlign="right",overlayContext.textBaseline="top",overlayContext.font="1em monospace",overlayContext.fillStyle="#000",q=engineName+" v"+engineVersion+" / "+drawCount+" / "+engineObjects.length+" / "+l.toFixed(1)+(glEnable?" GL":" 2D"),overlayContext.fillText(q,mainCanvas.width-3,3),overlayContext.fillStyle="#fff",overlayContext.fillText(q,mainCanvas.width-2,2),drawCount=0);requestAnimationFrame(g)}tileImage.onerror=tileImage.onload=()=>{tileImageFixBleed=vec2(tileFixBleedScale).divide(tileImageSize=vec2(tileImage.width,tileImage.height));debug&&(tileImage.onload=()=>ASSERT(1));document.body.style="margin:0;overflow:hidden;background:#000;touch-action:none;user-select:none;-webkit-user-select:none";document.body.appendChild(mainCanvas=document.createElement("canvas"));mainContext=mainCanvas.getContext("2d");debugInit();glEnable&&glInit();document.body.appendChild(overlayCanvas=document.createElement("canvas"));overlayContext=overlayCanvas.getContext("2d");(glCanvas||mainCanvas).style=mainCanvas.style=overlayCanvas.style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)";a();touchGamepadCreate();g()};let k=0,h,l;f?tileImage.src=f:tileImage.onload()}function enginePreRender(){mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height);mainContext.imageSmoothingEnabled=!cavasPixelated;glEnable&&glPreRender()}function engineObjectsUpdate(){engineObjectsCollide=engineObjects.filter(b=>b.collideSolidObjects);const a=b=>{if(!b.destroyed){b.update();for(const c of b.children)a(c)}};for(const b of engineObjects)b.parent||a(b);engineObjects=engineObjects.filter(b=>!b.destroyed);time=++frame/frameRate}function engineObjectsDestroy(){for(const a of engineObjects)a.parent||a.destroy();engineObjects=engineObjects.filter(a=>!a.destroyed)}function engineObjectsCallback(a,b,c,d=engineObjects){if(a)if(void 0!=b.x)for(const e of d)isOverlapping(a,b,e.pos,e.size)&&c(e);else{b*=b;for(const e of d)a.distanceSquared(e.pos)=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)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{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,g,k=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 l=tileImageSize.x/d.x|0;h=d.x/tileImageSize.x;const m=d.y/tileImageSize.y,n=c%l*h;l=(c/l|0)*m;glDraw(a.x,a.y,g?-b.x:b.x,b.y,f,n+tileImageFixBleed.x,l+tileImageFixBleed.y,n-tileImageFixBleed.x+h,l-tileImageFixBleed.y+m,e.rgbaInt(),k.rgbaInt())}else drawCanvas2D(a,b,f,g,m=>{if(0>c)m.fillStyle=e,m.fillRect(-.5,-.5,1,1);else{var n=tileImageSize.x/d.x|0;const p=c%n*d.x+tileFixBleedScale;n=(c/n|0)*d.y+tileFixBleedScale;const q=d.x-2*tileFixBleedScale,r=d.y-2*tileFixBleedScale;m.globalAlpha=e.a;m.drawImage(tileImage,p,n,q,r,-.5,-.5,1,1)}})}function drawRect(a,b,c,d,e){drawTile(a,b,-1,tileSizeDefault,c,d,0,0,e)}function drawTileScreenSpace(a,b=vec2(1),c,d,e,f,g,k,h){drawTile(screenToWorld(a),b.scale(1/cameraScale),c,d,e,f,g,k,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),g="center",k=fontDefault,h=overlayContext){h.fillStyle=d;h.lineWidth=e;h.strokeStyle=f;h.textAlign=g;h.font=c+"px "+k;h.textBaseline="middle";h.lineJoin="round";b=b.copy();(a+"").split("\n").forEach(l=>{e&&h.strokeText(l,b.x,b.y);h.fillText(l,b.x,b.y);b.y+=c})}function drawText(a,b,c=1,d,e,f,g,k){drawTextScreen(a,worldToScreen(b),c*cameraScale,d,e*cameraScale,f,g,k,mainContext)}let engineFontImage;class FontImage{constructor(a,b=vec2(8),c=vec2(0,1),d=0,e=overlayContext){engineFontImage||((engineFontImage=new Image).src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC");this.image=a||engineFontImage;this.tileSize=b;this.paddingSize=c;this.startTileIndex=d;this.context=e}drawTextScreen(a,b,c=4,d){const e=this.context;e.save();e.imageSmoothingEnabled=!cavasPixelated;const f=this.tileSize,g=f.add(this.paddingSize).scale(c),k=this.image.width/this.tileSize.x|0;(a+"").split("\n").forEach((h,l)=>{const m=d?h.length*f.x*c/2|0:0;for(let q=h.length;q--;){var n=h[q].charCodeAt();if(32>n||127document.fullscreenElement;function toggleFullscreen(){isFullscreen()?document.exitFullscreen&&document.exitFullscreen():document.body.requestFullscreen&&document.body.requestFullscreen()}"use strict";const keyIsDown=(a,b=0)=>inputData[b]&&inputData[b][a]&1,keyWasPressed=(a,b=0)=>inputData[b]&&inputData[b][a]&2?1:0,keyWasReleased=(a,b=0)=>inputData[b]&&inputData[b][a]&4?1:0,clearInput=()=>inputData=[[]],mouseIsDown=keyIsDown,mouseWasPressed=keyWasPressed,mouseWasReleased=keyWasReleased;let mousePos=vec2(),mousePosScreen=vec2(),mouseWheel=0,isUsingGamepad=0,preventDefaultInput=0;const gamepadIsDown=(a,b=0)=>keyIsDown(a,b+1),gamepadWasPressed=(a,b=0)=>keyWasPressed(a,b+1),gamepadWasReleased=(a,b=0)=>keyWasReleased(a,b+1),gamepadStick=(a,b=0)=>stickData[b]?stickData[b][a]||vec2():vec2();let inputData=[[]];function inputUpdate(){isTouchDevice||document.hasFocus()||clearInput();mousePos=screenToWorld(mousePosScreen);gamepadsUpdate()}function inputUpdatePost(){for(const a of inputData)for(const b in a)a[b]&=1;mouseWheel=0}onkeydown=a=>{debug&&a.target!=document.body||(a.repeat||(inputData[isUsingGamepad=0][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 g=inputData[b+1]||(inputData[b+1]=[]);c=stickData[b]||(stickData[b]=[]);if(d){var e=k=>.3k?-percent(-k,.3,.8):0;for(var f=0;f>1]=vec2(e(d.axes[f]),e(-d.axes[f+1])).clampLength();for(e=d.buttons.length;e--;)f=d.buttons[e],g[e]=f.pressed?1+2*!gamepadIsDown(e,b):4*gamepadIsDown(e,b),isUsingGamepad|=!b&&f.pressed,touchGamepadEnable&&touchGamepadTimer.unset();gamepadDirectionEmulateStick&&(d=vec2(gamepadIsDown(15,b)-gamepadIsDown(14,b),gamepadIsDown(12,b)-gamepadIsDown(13,b)),d.lengthSquared()&&(c[0]=d.clampLength()))}}}const vibrate=a=>vibrateEnable&&navigator&&navigator.vibrate&&navigator.vibrate(a),vibrateStop=()=>vibrate(0),isTouchDevice=void 0!==window.ontouchstart;if(isTouchDevice){let a,b=onmousedown,c=onmouseup;onmousedown=onmouseup=()=>0;ontouchstart=d=>{zzfx(0);ontouchstart=ontouchmove=ontouchend=e=>{e.button=0;const f=e.touches.length;f?(e.x=e.touches[0].clientX,e.y=e.touches[0].clientY,a?onmousemove(e):b(e)):a&&c(e);a=f;return!0};return ontouchstart(d)}}let touchGamepadTimer=new Timer,touchGamepadButtons,touchGamepadStick;function touchGamepadCreate(){touchGamepadEnable&&isTouchDevice&&(touchGamepadButtons=[],touchGamepadStick=vec2(),ontouchstart=a=>{zzfx(0);ontouchstart=ontouchmove=ontouchend=b=>{touchGamepadStick=vec2();touchGamepadButtons=[];if(b.touches.length&&(isUsingGamepad=1,touchGamepadTimer.set(),paused)){touchGamepadButtons[9]=1;return}const c=vec2(touchGamepadSize,mainCanvasSize.y-touchGamepadSize),d=mainCanvasSize.subtract(vec2(touchGamepadSize,touchGamepadSize)),e=mainCanvasSize.scale(.5);for(const f of b.touches)b=mouseToScreen(vec2(f.clientX,f.clientY)),b.distance(c)e*e)return;b*=percent(f**.5,e,e*this.taper)}e=2*worldToScreen(a).x/mainCanvas.width-1}a=c+c*this.randomness*d*rand(-1,1);return playSamples([this.cachedSamples],b,a,e)}}playNote(a,b,c){if(soundEnable)return this.play(b,c,2**(a/12),0)}}class Music{constructor(a){soundEnable&&(this.cachedSamples=zzfxM(...a))}play(a,b=1){soundEnable&&(this.source=playSamples(this.cachedSamples,a,1,0,b))}stop(){this.source&&this.source.stop();this.source=0}isPlaying(){return this.source}}function playAudioFile(a,b=1,c=1){if(soundEnable)return a=new Audio(a),a.volume=soundVolume*b,a.loop=c,a.play(),a}function speak(a,b="",c=1,d=1,e=1){if(soundEnable&&speechSynthesis)return a=new SpeechSynthesisUtterance(a),a.lang=b,a.volume=2*c*soundVolume,a.rate=d,a.pitch=e,speechSynthesis.speak(a),a}const speakStop=()=>speechSynthesis&&speechSynthesis.cancel(),getNoteFrequency=(a,b=220)=>b*2**(a/12);let audioContext;function playSamples(a,b=1,c=1,d=0,e=0){if(soundEnable&&(audioContext||=new AudioContext,audioContext.resume(),"running"==audioContext.state)){var f=audioContext.createBuffer(a.length,a[0].length,zzfxR),g=audioContext.createBufferSource();a.forEach((k,h)=>f.getChannelData(h).set(k));g.buffer=f;g.playbackRate.value=c;g.loop=e;a=audioContext.createGain();a.gain.value=soundVolume*b;a.connect(audioContext.destination);g.connect(new StereoPannerNode(audioContext,{pan:clamp(d,-1,1)})).connect(a);g.start();return g}}const zzfx=(...a)=>playSamples([zzfxG(...a)]),zzfxR=44100;function zzfxG(a=1,b=.05,c=220,d=0,e=0,f=.1,g=0,k=1,h=0,l=0,m=0,n=0,p=0,q=0,r=0,w=0,u=0,B=1,z=0,C=0){let v=2*PI,F=h*=500*v/zzfxR/zzfxR,A=[];b=c*=(1+b*rand(-1,1))*v/zzfxR;let x=0,D=0,t=0,y=1,G=0,I=0,E=0,J,H;d=d*zzfxR+9;z*=zzfxR;e*=zzfxR;f*=zzfxR;u*=zzfxR;l*=500*v/zzfxR**3;r*=v/zzfxR;m*=v/zzfxR;n*=zzfxR;p=p*zzfxR|0;for(H=d+z+e+f+u|0;tt?0:(tn&&(c+=m,b+=m,y=0),!p||++G%p||(c=b,h=F,y=y||1);return A}function zzfxM(a,b,c,d=125){let e,f,g,k,h,l,m,n,p,q,r,w,u,B=0,z,C=[],v=[],F=[],A=0,x=0,D=1,t={},y=zzfxR/d*60>>2;for(;D;A++)C=[D=n=w=0],c.forEach((G,I)=>{m=b[G][A]||[0,0,0];D|=!!b[G][A];z=w+(b[G][0].length-2-!n)*y;u=I==c.length-1;e=2;for(g=w;ey-99&&p?r+=(1>r)/99:0)l=(1-r)*C[B++]/2||0,v[g]=(v[g]||0)-l*x+l,F[g]=(F[g++]||0)+l*x+l;h&&(r=h%1,x=m[1]||0,h|=0)&&(C=t[[q=m[B=0]||0,h]]=t[[q,h]]||(k=[...a[q]],k[2]*=2**((h-12)/12),0a.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=f&&(m+=f,h+=g);k<=e&&(m+=e,l+=d)}debugRaycast&&debugLine(a,b,"#00f",.02,1)}class TileLayerData{constructor(a,b=0,c=0,d=new Color){this.tile=a;this.direction=b;this.mirror=c;this.color=d}clear(){this.tile=this.direction=this.mirror=0;color=new Color}}class TileLayer extends EngineObject{constructor(a,b=tileCollisionSize,c=tileSizeDefault,d=vec2(1),e=0){super(a,b,-1,c,0,void 0,e);this.canvas=document.createElement("canvas");this.context=this.canvas.getContext("2d");this.scale=d;this.isOverlay;this.data=[];for(a=this.size.area();a--;)this.data.push(new TileLayerData)}setData(a,b,c){a.arrayCheck(this.size)&&(this.data[(a.y|0)*this.size.x+a.x|0]=b,c&&this.drawTileData(a))}getData(a){return a.arrayCheck(this.size)&&this.data[(a.y|0)*this.size.x+a.x|0]}update(){}render(){ASSERT(mainContext!=this.context);glEnable&&!glOverlay&&!this.isOverlay&&glCopyToContext(mainContext);const a=worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));(this.isOverlay?overlayContext:mainContext).drawImage(this.canvas,a.x,a.y,cameraScale*this.size.x*this.scale.x,cameraScale*this.size.y*this.scale.y)}redraw(){this.redrawStart(1);this.drawAllTileData();this.redrawEnd()}redrawStart(a=0){this.savedRenderSettings=[mainCanvas,mainContext,mainCanvasSize,cameraPos,cameraScale];mainCanvas=this.canvas;mainContext=this.context;cameraPos=this.size.scale(.5);cameraScale=this.tileSize.x;a&&(mainCanvas.width=this.size.x*this.tileSize.x,mainCanvas.height=this.size.y*this.tileSize.y);enginePreRender()}redrawEnd(){ASSERT(mainContext==this.context);glEnable&&glCopyToContext(mainContext,1);[mainCanvas,mainContext,mainCanvasSize,cameraPos,cameraScale]=this.savedRenderSettings}drawTileData(a){const b=a.floor().add(this.pos).add(vec2(.5));this.drawCanvas2D(b,vec2(1),0,0,c=>c.clearRect(-.5,-.5,1,1));a=this.getData(a);void 0!=a.tile&&(ASSERT(mainContext==this.context),drawTile(b,vec2(1),a.tile,this.tileSize,a.color,a.direction*PI/2,a.mirror))}drawAllTileData(){for(let a=this.size.x;a--;)for(let b=this.size.y;b--;)this.drawTileData(vec2(a,b))}drawCanvas2D(a,b,c=0,d,e){const f=this.context;f.save();a=a.subtract(this.pos).multiply(this.tileSize);b=b.multiply(this.tileSize);f.translate(a.x,this.canvas.height-a.y);f.rotate(c);f.scale(d?-b.x:b.x,b.y);e(f);f.restore()}drawTile(a,b=vec2(1),c=-1,d=tileSizeDefault,e=new Color,f,g){this.drawCanvas2D(a,b,f,g,k=>{if(0>c)k.fillStyle=e,k.fillRect(-.5,-.5,1,1);else{const h=tileImage.width/d.x;k.globalAlpha=e.a;k.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,g=-1,k=tileSizeDefault,h=new Color,l=new Color,m=new Color(1,1,1,0),n=new Color(1,1,1,0),p=.5,q=.1,r=1,w=.1,u=.05,B=1,z=1,C=0,v=PI,F=.1,A=.2,x,D,t=1,y=D?1e9:0,G){super(a,new Vector2,g,k,b,void 0,y);this.emitSize=c;this.emitTime=d;this.emitRate=e;this.emitConeAngle=f;this.colorStartA=h;this.colorStartB=l;this.colorEndA=m;this.colorEndB=n;this.randomColorLinear=t;this.particleTime=p;this.sizeStart=q;this.sizeEnd=r;this.speed=w;this.angleSpeed=u;this.damping=B;this.angleDamping=z;this.gravityScale=C;this.particleConeAngle=v;this.fadeRate=F;this.randomness=A;this.collideTiles=x;this.additive=D;this.localSpace=G;this.emitTimeBuffer=this.trailScale=0}update(){this.parent&&super.update();if(!this.emitTime|this.getAliveTime()<=this.emitTime){if(this.emitRate*particleEmitRateScale){const a=1/this.emitRate/particleEmitRateScale;for(this.emitTimeBuffer+=timeDelta;0m+m*rand(c,-c);b=d(this.particleTime);const e=d(this.sizeStart),f=d(this.sizeEnd),g=d(this.speed);d=d(this.angleSpeed)*randSign();var k=rand(this.emitConeAngle,-this.emitConeAngle);const h=randColor(this.colorStartA,this.colorStartB,this.randomColorLinear),l=randColor(this.colorEndA,this.colorEndB,this.randomColorLinear);k=this.localSpace?k:this.angle+k;a.colorStart=h;a.colorEndDelta=l.subtract(h);a.velocity=(new Vector2).setAngle(k,g);a.angleVelocity=d;a.lifeTime=b;a.sizeStart=e;a.sizeEndDelta=f-e;a.fadeRate=this.fadeRate;a.damping=this.damping;a.angleDamping=this.angleDamping;a.elasticity=this.elasticity;a.friction=this.friction;a.gravityScale=this.gravityScale;a.collideTiles=this.collideTiles;a.additive=this.additive;a.renderOrder=this.renderOrder;a.trailScale=this.trailScale;a.mirror=randInt(2);a.localSpaceEmitter=this.localSpace&&this;a.destroyCallback=this.particleDestroyCallback;this.particleCreateCallback&&this.particleCreateCallback(a);return a}render(){}}class Particle extends EngineObject{constructor(a,b,c,d){super(a,new Vector2,b,c,d)}render(){const a=min((time-this.spawnTime)/this.lifeTime,1);var b=this.sizeStart+a*this.sizeEndDelta;b=new Vector2(b,b);var c=this.fadeRate/2;c=new Color(this.colorStart.r+a*this.colorEndDelta.r,this.colorStart.g+a*this.colorEndDelta.g,this.colorStart.b+a*this.colorEndDelta.b,(this.colorStart.a+a*this.colorEndDelta.a)*(a1-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 g=e*this.trailScale;b.y=max(b.x,g);e=f.angle();drawTile(d.add(f.multiply(vec2(0,-g/2))),b,this.tileIndex,this.tileSize,c,e,this.mirror)}else drawTile(d,b,this.tileIndex,this.tileSize,c,e,this.mirror);this.additive&&setBlendMode();debugParticles&&debugRect(d,b,"#f005",0,e);1==a&&(this.color=c,this.size=b,this.destroyCallback&&this.destroyCallback(this),this.destroyed=1)}}"use strict";const medals=[];let medalsDisplayQueue=[],medalsSaveName,medalsDisplayTimeLast;function medalsInit(a){medalsSaveName=a;debugMedals||medals.forEach(b=>b.unlocked=localStorage[b.storageKey()]|0)}class Medal{constructor(a,b,c="",d="🏆",e){ASSERT(0<=a&&!medals[a]);medals[this.id=a]=this;this.name=b;this.description=c;this.icon=d;e&&((this.image=new Image).src=e)}unlock(){medalsPreventUnlock||this.unlocked||(ASSERT(medalsSaveName),localStorage[this.storageKey()]=this.unlocked=1,medalsDisplayQueue.push(this),newgrounds&&newgrounds.unlockMedal(this.id))}render(a=0){const b=overlayContext;var c=min(medalDisplaySize.x,mainCanvas.width);const d=overlayCanvas.width-c;a*=-medalDisplaySize.y;b.save();b.beginPath();b.fillStyle=new Color(.9,.9,.9);b.strokeStyle=new Color(0,0,0);b.lineWidth=3;b.fill(b.rect(d,a,c,medalDisplaySize.y));b.stroke();b.clip();this.renderIcon(vec2(d+15+medalDisplayIconSize/2,a+medalDisplaySize.y/2));c=vec2(d+medalDisplayIconSize+30,a+28);drawTextScreen(this.name,c,38,new Color(0,0,0),0,0,"left");c.y+=32;drawTextScreen(this.description,c,24,new Color(0,0,0),0,0,"left");b.restore()}renderIcon(a,b=medalDisplayIconSize){this.image?overlayContext.drawImage(this.image,a.x-b/2,a.y-b/2,b,b):drawTextScreen(this.icon,a,.7*b,new Color(0,0,0))}storageKey(){return medalsSaveName+"_"+this.id}}function medalsRender(){if(medalsDisplayQueue.length){var a=medalsDisplayQueue[0],b=timeReal-medalsDisplayTimeLast;if(medalsDisplayTimeLast)if(b>medalDisplayTime)medalsDisplayQueue.shift(medalsDisplayTimeLast=0);else{const c=medalDisplayTime-medalDisplaySlideTime;a.render(bc?(b-c)/medalDisplaySlideTime:0)}else medalsDisplayTimeLast=timeReal}}let newgrounds;function newgroundsInit(a,b){newgrounds=new Newgrounds(a,b)}class Newgrounds{constructor(a,b){ASSERT(!newgrounds&&a);this.app_id=a;this.cipher=b;this.host=location?location.hostname:"";b&&(this.cryptoJS=this.CryptoJS());if(this.session_id=new URL(location.href).searchParams.get("ngio_session_id")){this.medals=(a=this.call("Medal.getList"))?a.result.data.medals:[];debugMedals&&console.log(this.medals);for(var c of this.medals)if(a=medals[c.id])a.image=new Image,a.image.src=c.icon,a.name=c.name,a.description=c.description,a.unlocked=c.unlocked,a.difficulty=c.difficulty,a.value=c.value,a.value&&(a.description=a.description+" ("+a.value+")");this.scoreboards=(c=this.call("ScoreBoard.getBoards"))?c.result.data.scoreboards:[];debugMedals&&console.log(this.scoreboards);setInterval(()=>this.call("Gateway.ping",0,1),3e5)}}unlockMedal(a){return this.call("Medal.unlock",{id:a},1)}postScore(a,b){return this.call("ScoreBoard.postScore",{id:a,value:b},1)}getScores(a,b=0,c=0,d=0,e=10){return this.call("ScoreBoard.getScores",{id:a,user:b,social:c,skip:d,limit:e})}logView(){return this.call("App.logView",{host:this.host},1)}call(a,b=0,c=0){a={component:a,parameters:b};if(this.cipher){b=this.cryptoJS;var d=b.enc.Base64.parse(this.cipher);const e=b.lib.WordArray.random(16);d=b.AES.encrypt(JSON.stringify(a),d,{iv:e});a.secure=b.enc.Base64.stringify(e.concat(d.ciphertext));a.parameters=0}b={app_id:this.app_id,session_id:this.session_id,call:a};a=new FormData;a.append("input",JSON.stringify(b));b=new XMLHttpRequest;b.open("POST","https://newgrounds.io/gateway_v3.php",!debugMedals&&c);b.send(a);debugMedals&&console.log(b.responseText);return b.responseText&&JSON.parse(b.responseText)}CryptoJS(){return eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",..."]charCodeAtUinyxpf","for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h>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{d=glContext.getAttribLocation(glShader,d);glContext.enableVertexAttribArray(d);glContext.vertexAttribPointer(d,g,e,k,gl_VERTEX_BYTE_STRIDE,a);a+=g*f};b("p",gl_FLOAT,4,2);b("t",gl_FLOAT,4,2);b("c",gl_UNSIGNED_BYTE,1,4,1);b("a",gl_UNSIGNED_BYTE,1,4,1);b=2*cameraScale/mainCanvas.width;const c=2*cameraScale/mainCanvas.height;glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader,"m"),0,new Float32Array([b,0,0,0,0,c,0,0,1,1,-1,1,-1-b*cameraPos.x,-1-c*cameraPos.y,0,0]))}function glFlush(){if(glBatchCount){var a=glBatchAdditive?gl_ONE:gl_ONE_MINUS_SRC_ALPHA;glContext.blendFuncSeparate(gl_SRC_ALPHA,a,gl_ONE,a);glContext.enable(gl_BLEND);glContext.bufferSubData(gl_ARRAY_BUFFER,0,glPositionData.subarray(0,glBatchCount*gl_VERTICES_PER_QUAD*gl_INDICIES_PER_VERT));glContext.drawArrays(gl_TRIANGLES,0,glBatchCount*gl_VERTICES_PER_QUAD);glBatchCount=0;glBatchAdditive=glAdditive}}function glCopyToContext(a,b){if(glBatchCount||b)glFlush(),glOverlay&&!b||a.drawImage(glCanvas,0,0)}function glDraw(a,b,c,d,e,f,g,k,h,l,m=0){glBatchCount!=gl_MAX_BATCH&&glBatchAdditive==glAdditive||glFlush();var n=Math.cos(e)/2,p=Math.sin(e)/2;e=n*c;n*=d;c*=p;d*=p;for(let q=6,r=glBatchCount++*gl_VERTICES_PER_QUAD*gl_INDICIES_PER_VERT;q--;){p=q-4&&1cameraPos=v;const setCameraScale=v=>cameraScale=v;const setCanvasMaxSize=v=>canvasMaxSize=v;const setCanvasFixedSize=v=>canvasFixedSize=v;const setCavasPixelated=v=>cavasPixelated=v;const setFontDefault=v=>fontDefault=v;const setGlEnable=v=>glEnable=v;const setGlOverlay=v=>glOverlay=v;const setTileSizeDefault=v=>tileSizeDefault=v;const setTileFixBleedScale=v=>tileFixBleedScale=v;const setObjectDefaultSize=v=>objectDefaultSize=v;const setEnablePhysicsSolver=v=>enablePhysicsSolver=v;const setObjectDefaultMass=v=>objectDefaultMass=v;const setObjectDefaultDamping=v=>objectDefaultDamping=v;const setObjectDefaultAngleDamping=v=>objectDefaultAngleDamping=v;const setObjectDefaultElasticity=v=>objectDefaultElasticity=v;const setObjectDefaultFriction=v=>objectDefaultFriction=v;const setObjectMaxSpeed=v=>objectMaxSpeed=v;const setGravity=v=>gravity=v;const setParticleEmitRateScale=v=>particleEmitRateScale=v;const setGamepadsEnable=v=>gamepadsEnable=v;const setGamepadDirectionEmulateStick=v=>gamepadDirectionEmulateStick=v;const setInputWASDEmulateDirection=v=>inputWASDEmulateDirection=v;const setTouchGamepadEnable=v=>touchGamepadEnable=v;const setTouchGamepadAnalog=v=>touchGamepadAnalog=v;const setTouchGamepadSize=v=>touchGamepadSize=v;const setTouchGamepadAlpha=v=>touchGamepadAlpha=v;const setVibrateEnable=v=>vibrateEnable=v;const setSoundEnable=v=>soundEnable=v;const setSoundVolume=v=>soundVolume=v;const setSoundDefaultRange=v=>soundDefaultRange=v;const setSoundDefaultTaper=v=>soundDefaultTaper=v;const setMedalDisplayTime=v=>medalDisplayTime=v;const setMedalDisplaySlideTime=v=>medalDisplaySlideTime=v;const setMedalDisplaySize=v=>medalDisplaySize=v;const setMedalDisplayIconSize=v=>medalDisplayIconSize=v;const setMedalsPreventUnlock=v=>medalsPreventUnlock=v;export{setCameraPos,setCameraScale,setCanvasMaxSize,setCanvasFixedSize,setCavasPixelated,setFontDefault,setGlEnable,setGlOverlay,setTileSizeDefault,setTileFixBleedScale,setObjectDefaultSize,setEnablePhysicsSolver,setObjectDefaultMass,setObjectDefaultDamping,setObjectDefaultAngleDamping,setObjectDefaultElasticity,setObjectDefaultFriction,setObjectMaxSpeed,setGravity,setParticleEmitRateScale,setGamepadsEnable,setGamepadDirectionEmulateStick,setInputWASDEmulateDirection,setTouchGamepadEnable,setTouchGamepadAnalog,setTouchGamepadSize,setTouchGamepadAlpha,setVibrateEnable,setSoundEnable,setSoundVolume,setSoundDefaultRange,setSoundDefaultTaper,setMedalDisplayTime,setMedalDisplaySlideTime,setMedalDisplaySize,setMedalDisplayIconSize,setMedalsPreventUnlock,canvasMaxSize,canvasFixedSize,cavasPixelated,fontDefault,tileSizeDefault,tileFixBleedScale,objectDefaultSize,enablePhysicsSolver,objectDefaultMass,objectDefaultDamping,objectDefaultAngleDamping,objectDefaultElasticity,objectDefaultFriction,objectMaxSpeed,gravity,particleEmitRateScale,cameraPos,cameraScale,glEnable,glOverlay,gamepadsEnable,gamepadDirectionEmulateStick,inputWASDEmulateDirection,touchGamepadEnable,touchGamepadAnalog,touchGamepadSize,touchGamepadAlpha,vibrateEnable,soundEnable,soundVolume,soundDefaultRange,soundDefaultTaper,medalDisplayTime,medalDisplaySlideTime,medalDisplaySize,medalDisplayIconSize,debug,showWatermark,godMode,debugRect,debugCircle,debugPoint,debugLine,debugAABB,debugText,debugClear,debugSaveCanvas,PI,abs,min,max,sign,mod,clamp,percent,lerp,smoothStep,nearestPowerOfTwo,isOverlapping,wave,formatTime,rand,randInt,randSign,randInCircle,randVector,randColor,randSeed,setRandSeed,randSeeded,Vector2,Color,Timer,vec2,colorRGBA,colorHSLA,EngineObject,tileImage,mainCanvas,mainContext,overlayCanvas,overlayContext,mainCanvasSize,screenToWorld,worldToScreen,drawTile,drawRect,drawTileScreenSpace,drawRectScreenSpace,drawLine,drawCanvas2D,setBlendMode,drawTextScreen,drawText,engineFontImage,FontImage,isFullscreen,toggleFullscreen,keyIsDown,keyWasPressed,keyWasReleased,clearInput,mouseIsDown,mouseWasPressed,mouseWasReleased,mousePos,mousePosScreen,mouseWheel,isUsingGamepad,preventDefaultInput,gamepadIsDown,gamepadWasPressed,gamepadWasReleased,gamepadStick,mouseToScreen,gamepadsUpdate,vibrate,vibrateStop,isTouchDevice,Sound,Music,playAudioFile,speak,speakStop,getNoteFrequency,audioContext,playSamples,zzfx,tileCollision,tileCollisionSize,initTileCollision,setTileCollisionData,getTileCollisionData,tileCollisionTest,tileCollisionRaycast,TileLayerData,TileLayer,ParticleEmitter,Particle,medals,medalsPreventUnlock,medalsInit,newgroundsInit,Medal,Newgrounds,glCanvas,glContext,glSetBlendMode,glSetTexture,glCompileShader,glCreateProgram,glCreateTexture,glInitPostProcess,engineName,engineVersion,frameRate,timeDelta,engineObjects,frame,time,timeReal,paused,setPaused,engineInit,engineObjectsUpdate,engineObjectsDestroy,engineObjectsCallback}; \ No newline at end of file diff --git a/engine/engine.all.release.js b/engine/engine.all.release.js index 113608e8..65509095 100644 --- a/engine/engine.all.release.js +++ b/engine/engine.all.release.js @@ -855,7 +855,7 @@ let medalsPreventUnlock; const engineName = 'LittleJS'; /** Version of engine */ -const engineVersion = '1.4.9'; +const engineVersion = '1.5.0'; /** Frames per second to update objects * @default */ @@ -1462,9 +1462,9 @@ class EngineObject } /** Set how this object collides - * @param {boolean} [collideSolidObjects=1] - Does it collide with solid objects - * @param {boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers) - * @param {boolean} [collideTiles=1] - Does it collide with the tile collision */ + * @param {Boolean} [collideSolidObjects=1] - Does it collide with solid objects + * @param {Boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers) + * @param {Boolean} [collideTiles=1] - Does it collide with the tile collision */ setCollision(collideSolidObjects=1, isSolid=1, collideTiles=1) { ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide @@ -1474,6 +1474,8 @@ class EngineObject this.collideTiles = collideTiles; } + /** Returns string containg info about this object for debugging + * @return {String} */ toString() { if (debug) @@ -1543,7 +1545,7 @@ let overlayContext; let mainCanvasSize = vec2(); /** Tile sheet for batch rendering system - * @type {Image} + * @type {CanvasImageSource} * @memberof Draw */ const tileImage = new Image; @@ -2124,7 +2126,7 @@ const vibrateStop = ()=> vibrate(0); // Touch input /** True if a touch device has been detected - * @const {boolean} + * @const {Boolean} * @memberof Input */ const isTouchDevice = window.ontouchstart !== undefined; @@ -2454,8 +2456,21 @@ class Music { if (!soundEnable) return; - return playSamples(this.cachedSamples, volume, 1, 0, loop); + this.source = playSamples(this.cachedSamples, volume, 1, 0, loop); } + + /** Stop the music */ + stop() + { + if (this.source) + this.source.stop(); + this.source = 0; + } + + /** Check if music is playing + * @return {Boolean} + */ + isPlaying() { return this.source; } } /** Play an mp3 or wav audio from a local file or url @@ -3146,7 +3161,7 @@ class ParticleEmitter extends EngineObject * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0 * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter * @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied - * @param {Number} [tileSize=tileSizeDefault] - Tile size for particles + * @param {Vector2} [tileSize=tileSizeDefault] - Tile size for particles * @param {Color} [colorStartA=new Color(1,1,1)] - Color at start of life 1, randomized between start colors * @param {Color} [colorStartB=new Color(1,1,1)] - Color at start of life 2, randomized between start colors * @param {Color} [colorEndA=new Color(1,1,1,0)] - Color at end of life 1, randomized between end colors diff --git a/engine/engine.js b/engine/engine.js index 1a5f4652..f5ab25ae 100644 --- a/engine/engine.js +++ b/engine/engine.js @@ -23,7 +23,7 @@ const engineName = 'LittleJS'; /** Version of engine */ -const engineVersion = '1.4.9'; +const engineVersion = '1.5.0'; /** Frames per second to update objects * @default */ diff --git a/engine/engineAudio.js b/engine/engineAudio.js index ef2d7843..30d52343 100644 --- a/engine/engineAudio.js +++ b/engine/engineAudio.js @@ -146,8 +146,21 @@ class Music { if (!soundEnable) return; - return playSamples(this.cachedSamples, volume, 1, 0, loop); + this.source = playSamples(this.cachedSamples, volume, 1, 0, loop); } + + /** Stop the music */ + stop() + { + if (this.source) + this.source.stop(); + this.source = 0; + } + + /** Check if music is playing + * @return {Boolean} + */ + isPlaying() { return this.source; } } /** Play an mp3 or wav audio from a local file or url diff --git a/engine/engineBuild.bat b/engine/engineBuild.bat index b5232964..2bed2de9 100644 --- a/engine/engineBuild.bat +++ b/engine/engineBuild.bat @@ -93,14 +93,6 @@ if %ERRORLEVEL% NEQ 0 ( exit /b %ERRORLEVEL% ) -rem --- BUILD TYPESCRIPT DEFINITIONS --- - -call npx tsc engine.all.js --declaration --allowJs --emitDeclarationOnly --outFile index.d.ts -if %ERRORLEVEL% NEQ 0 ( - pause - exit /b %ERRORLEVEL% -) - rem --- BUILD ENGINE MODULE --- set OUTPUT_FILENAME=engine.all.module.js @@ -121,6 +113,14 @@ echo.>> %OUTPUT_FILENAME% rem lightly minify with uglify call npx uglifyjs -o %OUTPUT_FILENAME% -- %OUTPUT_FILENAME% +if %ERRORLEVEL% NEQ 0 ( + pause + exit /b %ERRORLEVEL% +) + +rem --- BUILD TYPESCRIPT DEFINITIONS --- + +call npx tsc engine.all.module.js --declaration --allowJs --emitDeclarationOnly --outFile index.d.ts if %ERRORLEVEL% NEQ 0 ( pause exit /b %ERRORLEVEL% diff --git a/engine/engineDebug.js b/engine/engineDebug.js index f4d11b7b..71baee2b 100644 --- a/engine/engineDebug.js +++ b/engine/engineDebug.js @@ -59,9 +59,9 @@ const ASSERT = enableAsserts ? (...assert)=> console.assert(...assert) : ()=>{}; * @param {String} [color='#fff'] * @param {Number} [time=0] * @param {Number} [angle=0] - * @param {Boolean} [fill=0] + * @param {Boolean} [fill=false] * @memberof Debug */ -const debugRect = (pos, size=vec2(), color='#fff', time=0, angle=0, fill=0)=> +const debugRect = (pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)=> { ASSERT(typeof color == 'string'); // pass in regular html strings as colors debugPrimitives.push({pos, size:vec2(size), color, time:new Timer(time), angle, fill}); @@ -72,9 +72,9 @@ const debugRect = (pos, size=vec2(), color='#fff', time=0, angle=0, fill=0)=> * @param {Number} [radius=0] * @param {String} [color='#fff'] * @param {Number} [time=0] - * @param {Boolean} [fill=0] + * @param {Boolean} [fill=false] * @memberof Debug */ -const debugCircle = (pos, radius=0, color='#fff', time=0, fill=0)=> +const debugCircle = (pos, radius=0, color='#fff', time=0, fill=false)=> { ASSERT(typeof color == 'string'); // pass in regular html strings as colors debugPrimitives.push({pos, size:radius, color, time:new Timer(time), angle:0, fill}); diff --git a/engine/engineDraw.js b/engine/engineDraw.js index 0ed573a4..f4e51afe 100644 --- a/engine/engineDraw.js +++ b/engine/engineDraw.js @@ -48,7 +48,7 @@ let overlayContext; let mainCanvasSize = vec2(); /** Tile sheet for batch rendering system - * @type {Image} + * @type {CanvasImageSource} * @memberof Draw */ const tileImage = new Image; diff --git a/engine/engineInput.js b/engine/engineInput.js index 65dece4b..62d13b20 100644 --- a/engine/engineInput.js +++ b/engine/engineInput.js @@ -246,7 +246,7 @@ const vibrateStop = ()=> vibrate(0); // Touch input /** True if a touch device has been detected - * @const {boolean} + * @const {Boolean} * @memberof Input */ const isTouchDevice = window.ontouchstart !== undefined; diff --git a/engine/engineObject.js b/engine/engineObject.js index 7f59c388..ce2f6846 100644 --- a/engine/engineObject.js +++ b/engine/engineObject.js @@ -341,9 +341,9 @@ class EngineObject } /** Set how this object collides - * @param {boolean} [collideSolidObjects=1] - Does it collide with solid objects - * @param {boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers) - * @param {boolean} [collideTiles=1] - Does it collide with the tile collision */ + * @param {Boolean} [collideSolidObjects=1] - Does it collide with solid objects + * @param {Boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers) + * @param {Boolean} [collideTiles=1] - Does it collide with the tile collision */ setCollision(collideSolidObjects=1, isSolid=1, collideTiles=1) { ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide @@ -353,6 +353,8 @@ class EngineObject this.collideTiles = collideTiles; } + /** Returns string containg info about this object for debugging + * @return {String} */ toString() { if (debug) diff --git a/engine/engineParticles.js b/engine/engineParticles.js index 5687800d..5a05edee 100644 --- a/engine/engineParticles.js +++ b/engine/engineParticles.js @@ -34,7 +34,7 @@ class ParticleEmitter extends EngineObject * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0 * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter * @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied - * @param {Number} [tileSize=tileSizeDefault] - Tile size for particles + * @param {Vector2} [tileSize=tileSizeDefault] - Tile size for particles * @param {Color} [colorStartA=new Color(1,1,1)] - Color at start of life 1, randomized between start colors * @param {Color} [colorStartB=new Color(1,1,1)] - Color at start of life 2, randomized between start colors * @param {Color} [colorEndA=new Color(1,1,1,0)] - Color at end of life 1, randomized between end colors diff --git a/engine/index.d.ts b/engine/index.d.ts index b7f3cbda..2cbd6cd2 100644 --- a/engine/index.d.ts +++ b/engine/index.d.ts @@ -1,1700 +1,1615 @@ -/** Draw textured tile centered in world space, with color applied if using WebGL - * @param {Vector2} pos - Center of the tile in world space - * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space - * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured - * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels - * @param {Color} [color=new Color(1,1,1)] - Color to modulate with - * @param {Number} [angle=0] - Angle to rotate by - * @param {Boolean} [mirror=0] - If true image is flipped along the Y axis - * @param {Color} [additiveColor=new Color(0,0,0,0)] - Additive color to be applied - * @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering - * @memberof Draw */ -declare function drawTile(pos: Vector2, size?: Vector2, tileIndex?: number, tileSize?: Vector2, color?: Color, angle?: number, mirror?: boolean, additiveColor?: Color, useWebGL?: boolean): void; -/** Draw colored rect centered on pos - * @param {Vector2} pos - * @param {Vector2} [size=new Vector2(1,1)] - * @param {Color} [color=new Color(1,1,1)] - * @param {Number} [angle=0] - * @param {Boolean} [useWebGL=glEnable] - * @memberof Draw */ -declare function drawRect(pos: Vector2, size?: Vector2, color?: Color, angle?: number, useWebGL?: boolean): void; -/** Draw textured tile centered on pos in screen space - * @param {Vector2} pos - Center of the tile - * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile - * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured - * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels - * @param {Color} [color=new Color] - * @param {Number} [angle=0] - * @param {Boolean} [mirror=0] - * @param {Color} [additiveColor=new Color(0,0,0,0)] - * @param {Boolean} [useWebGL=glEnable] - * @memberof Draw */ -declare function drawTileScreenSpace(pos: Vector2, size?: Vector2, tileIndex?: number, tileSize?: Vector2, color?: Color, angle?: number, mirror?: boolean, additiveColor?: Color, useWebGL?: boolean): void; -/** Draw colored rectangle in screen space - * @param {Vector2} pos - * @param {Vector2} [size=new Vector2(1,1)] - * @param {Color} [color=new Color(1,1,1)] - * @param {Number} [angle=0] - * @param {Boolean} [useWebGL=glEnable] - * @memberof Draw */ -declare function drawRectScreenSpace(pos: Vector2, size?: Vector2, color?: Color, angle?: number, useWebGL?: boolean): void; -/** Draw colored line between two points - * @param {Vector2} posA - * @param {Vector2} posB - * @param {Number} [thickness=.1] - * @param {Color} [color=new Color(1,1,1)] - * @param {Boolean} [useWebGL=glEnable] - * @memberof Draw */ -declare function drawLine(posA: Vector2, posB: Vector2, thickness?: number, color?: Color, useWebGL?: boolean): void; -/** Draw directly to a 2d canvas context in world space - * @param {Vector2} pos - * @param {Vector2} size - * @param {Number} angle - * @param {Boolean} mirror - * @param {Function} drawFunction - * @param {CanvasRenderingContext2D} [context=mainContext] - * @memberof Draw */ -declare function drawCanvas2D(pos: Vector2, size: Vector2, angle: number, mirror: boolean, drawFunction: Function, context?: CanvasRenderingContext2D): void; -/** Enable normal or additive blend mode - * @param {Boolean} [additive=0] - * @param {Boolean} [useWebGL=glEnable] - * @memberof Draw */ -declare function setBlendMode(additive?: boolean, useWebGL?: boolean): void; -/** Draw text on overlay canvas in screen space - * Automatically splits new lines into rows - * @param {String} text - * @param {Vector2} pos - * @param {Number} [size=1] - * @param {Color} [color=new Color(1,1,1)] - * @param {Number} [lineWidth=0] - * @param {Color} [lineColor=new Color(0,0,0)] - * @param {String} [textAlign='center'] - * @memberof Draw */ -declare function drawTextScreen(text: string, pos: Vector2, size?: number, color?: Color, lineWidth?: number, lineColor?: Color, textAlign?: string, font?: string, context?: CanvasRenderingContext2D): void; -/** Draw text on overlay canvas in world space - * Automatically splits new lines into rows - * @param {String} text - * @param {Vector2} pos - * @param {Number} [size=1] - * @param {Color} [color=new Color(1,1,1)] - * @param {Number} [lineWidth=0] - * @param {Color} [lineColor=new Color(0,0,0)] - * @param {String} [textAlign='center'] - * @memberof Draw */ -declare function drawText(text: string, pos: Vector2, size?: number, color?: Color, lineWidth?: number, lineColor?: Color, textAlign?: string, font: any): void; -/** Toggle fullsceen mode - * @memberof Draw */ -declare function toggleFullscreen(): void; -declare function inputUpdate(): void; -declare function inputUpdatePost(): void; -declare function gamepadsUpdate(): void; -declare function touchGamepadCreate(): void; -declare function touchGamepadRender(): void; -/** Play an mp3 or wav audio from a local file or url - * @param {String} url - Location of sound file to play - * @param {Number} [volume=1] - How much to scale volume by - * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end - * @return {HTMLAudioElement} - The audio element for this sound - * @memberof Audio */ -declare function playAudioFile(url: string, volume?: number, loop?: boolean): HTMLAudioElement; -/** Speak text with passed in settings - * @param {String} text - The text to speak - * @param {String} [language] - The language/accent to use (examples: en, it, ru, ja, zh) - * @param {Number} [volume=1] - How much to scale volume by - * @param {Number} [rate=1] - How quickly to speak - * @param {Number} [pitch=1] - How much to change the pitch by - * @return {SpeechSynthesisUtterance} - The utterance that was spoken - * @memberof Audio */ -declare function speak(text: string, language?: string, volume?: number, rate?: number, pitch?: number): SpeechSynthesisUtterance; -/** Play cached audio samples with given settings - * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback) - * @param {Number} [volume=1] - How much to scale volume by - * @param {Number} [rate=1] - The playback rate to use - * @param {Number} [pan=0] - How much to apply stereo panning - * @param {Boolean} [loop=0] - True if the sound should loop when it reaches the end - * @return {AudioBufferSourceNode} - The audio node of the sound played - * @memberof Audio */ -declare function playSamples(sampleChannels: any[], volume?: number, rate?: number, pan?: number, loop?: boolean): AudioBufferSourceNode; -/** Generate samples for a ZzFX sound - * @memberof Audio */ -declare function zzfxG(volume?: number, randomness?: number, frequency?: number, attack?: number, sustain?: number, release?: number, shape?: number, shapeCurve?: number, slide?: number, deltaSlide?: number, pitchJump?: number, pitchJumpTime?: number, repeatTime?: number, noise?: number, modulation?: number, bitCrush?: number, delay?: number, sustainVolume?: number, decay?: number, tremolo?: number): number[]; -/** Generate samples for a ZzFM song with given parameters - * @param {Array} instruments - Array of ZzFX sound paramaters - * @param {Array} patterns - Array of pattern data - * @param {Array} sequence - Array of pattern indexes - * @param {Number} [BPM=125] - Playback speed of the song in BPM - * @returns {Array} - Left and right channel sample data - * @memberof Audio */ -declare function zzfxM(instruments: any[], patterns: any[], sequence: any[], BPM?: number): any[]; -/** Clear and initialize tile collision - * @param {Vector2} size - * @memberof TileCollision */ -declare function initTileCollision(size: Vector2): void; -/** Check if collision with another object should occur - * @param {Vector2} pos - * @param {Vector2} [size=new Vector2(1,1)] - * @param {EngineObject} [object] - * @return {Boolean} - * @memberof TileCollision */ -declare function tileCollisionTest(pos: Vector2, size?: Vector2, object?: EngineObject): boolean; -/** Return the center of tile if any that is hit (this does not return the exact hit point) - * @param {Vector2} posStart - * @param {Vector2} posEnd - * @param {EngineObject} [object] - * @return {Vector2} - * @memberof TileCollision */ -declare function tileCollisionRaycast(posStart: Vector2, posEnd: Vector2, object?: EngineObject): Vector2; -/** Initialize medals with a save name used for storage - *
- Call this after creating all medals - *
- Checks if medals are unlocked - * @param {String} saveName - * @memberof Medals */ -declare function medalsInit(saveName: string): void; -declare function medalsRender(): void; -/** This can used to enable Newgrounds functionality - * @param {Number} app_id - The newgrounds App ID - * @param {String} [cipher] - The encryption Key (AES-128/Base64) - * @memberof Medals */ -declare function newgroundsInit(app_id: number, cipher?: string): void; -declare function glInit(): void; -/** Set the WebGl blend mode, normally you should call setBlendMode instead - * @param {Boolean} [additive=0] - * @memberof WebGL */ -declare function glSetBlendMode(additive?: boolean): void; -/** Set the WebGl texture, not normally necessary unless multiple tile sheets are used - *
- This may also flush the gl buffer resulting in more draw calls and worse performance - * @param {WebGLTexture} [texture=glTileTexture] - * @memberof WebGL */ -declare function glSetTexture(texture?: WebGLTexture): void; -/** Compile WebGL shader of the given type, will throw errors if in debug mode - * @param {String} source - * @param type - * @return {WebGLShader} - * @memberof WebGL */ -declare function glCompileShader(source: string, type: any): WebGLShader; -/** Create WebGL program with given shaders - * @param {WebGLShader} vsSource - * @param {WebGLShader} fsSource - * @return {WebGLProgram} - * @memberof WebGL */ -declare function glCreateProgram(vsSource: WebGLShader, fsSource: WebGLShader): WebGLProgram; -/** Create WebGL texture from an image and set the texture settings - * @param {Image} image - * @return {WebGLTexture} - * @memberof WebGL */ -declare function glCreateTexture(image: new (width?: number, height?: number) => HTMLImageElement): WebGLTexture; -declare function glPreRender(): void; -/** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary - * @memberof WebGL */ -declare function glFlush(): void; -/** Draw any sprites still in the buffer, copy to main canvas and clear - * @param {CanvasRenderingContext2D} context - * @param {Boolean} [forceDraw=0] - * @memberof WebGL */ -declare function glCopyToContext(context: CanvasRenderingContext2D, forceDraw?: boolean): void; -/** Add a sprite to the gl draw list, used by all gl draw functions - * @param x - * @param y - * @param sizeX - * @param sizeY - * @param angle - * @param uv0X - * @param uv0Y - * @param uv1X - * @param uv1Y - * @param rgba - * @param [rgbaAdditive=0] - * @memberof WebGL */ -declare function glDraw(x: any, y: any, sizeX: any, sizeY: any, angle: any, uv0X: any, uv0Y: any, uv1X: any, uv1Y: any, rgba: any, rgbaAdditive?: number): void; -/** Set up a post processing shader - * @param {String} shaderCode - * @memberof WebGL */ -declare function glInitPostProcess(shaderCode: string): void; -declare function glRenderPostProcess(): void; -/** Set if game is paused - * @param {Boolean} paused - */ -declare function setPaused(_paused: any): void; -/** Start up LittleJS engine with your callback functions - * @param {Function} gameInit - Called once after the engine starts up, setup the game - * @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state - * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render - * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects - * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects - * @param {String} [tileImageSource] - Tile image to use, everything starts when the image is finished loading - */ -declare function engineInit(gameInit: Function, gameUpdate: Function, gameUpdatePost: Function, gameRender: Function, gameRenderPost: Function, tileImageSource?: string): void; -declare function enginePreRender(): void; -/** Update each engine object, remove destroyed objects, and update time */ -declare function engineObjectsUpdate(): void; -/** Destroy and remove all objects */ -declare function engineObjectsDestroy(): void; -/** Triggers a callback for each object within a given area - * @param {Vector2} [pos] - Center of test area - * @param {Number} [size] - Radius of circle if float, rectangle size if Vector2 - * @param {Function} [callbackFunction] - Calls this function on every object that passes the test - * @param {Array} [objects=engineObjects] - List of objects to check */ -declare function engineObjectsCallback(pos?: Vector2, size?: number, callbackFunction?: Function, objects?: any[]): void; -/** True if debug is enabled - * @default - * @memberof Debug */ -declare const debug: 1; -/** True if asserts are enaled - * @default - * @memberof Debug */ -declare const enableAsserts: 1; -/** Size to render debug points by default - * @default - * @memberof Debug */ -declare const debugPointSize: 0.5; -/** True if watermark with FPS should be down, false in release builds - * @default - * @memberof Debug */ -declare let showWatermark: number; -/** True if god mode is enabled, handle this however you want - * @default - * @memberof Debug */ -declare let godMode: number; -declare let debugPrimitives: any[]; -declare let debugOverlay: number; -declare let debugPhysics: number; -declare let debugRaycast: number; -declare let debugParticles: number; -declare let debugGamepads: number; -declare let debugMedals: number; -declare let debugTakeScreenshot: any; -declare let downloadLink: any; -declare function ASSERT(...assert: any[]): void; -/** Draw a debug rectangle in world space - * @param {Vector2} pos - * @param {Vector2} [size=new Vector2()] - * @param {String} [color='#fff'] - * @param {Number} [time=0] - * @param {Number} [angle=0] - * @param {Boolean} [fill=0] - * @memberof Debug */ -declare function debugRect(pos: Vector2, size?: Vector2, color?: string, time?: number, angle?: number, fill?: boolean): void; -/** Draw a debug circle in world space - * @param {Vector2} pos - * @param {Number} [radius=0] - * @param {String} [color='#fff'] - * @param {Number} [time=0] - * @param {Boolean} [fill=0] - * @memberof Debug */ -declare function debugCircle(pos: Vector2, radius?: number, color?: string, time?: number, fill?: boolean): void; -/** Draw a debug point in world space - * @param {Vector2} pos - * @param {String} [color='#fff'] - * @param {Number} [time=0] - * @param {Number} [angle=0] - * @memberof Debug */ -declare function debugPoint(pos: Vector2, color?: string, time?: number, angle?: number): void; -/** Draw a debug line in world space - * @param {Vector2} posA - * @param {Vector2} posB - * @param {String} [color='#fff'] - * @param {Number} [thickness=.1] - * @param {Number} [time=0] - * @memberof Debug */ -declare function debugLine(posA: Vector2, posB: Vector2, color?: string, thickness?: number, time?: number): void; -/** Draw a debug axis aligned bounding box in world space - * @param {Vector2} posA - * @param {Vector2} sizeA - * @param {Vector2} posB - * @param {Vector2} sizeB - * @param {String} [color='#fff'] - * @memberof Debug */ -declare function debugAABB(pA: any, sA: any, pB: any, sB: any, color?: string): void; -/** Draw a debug axis aligned bounding box in world space - * @param {String} text - * @param {Vector2} pos - * @param {Number} [size=1] - * @param {String} [color='#fff'] - * @param {Number} [time=0] - * @param {Number} [angle=0] - * @param {String} [font='monospace'] - * @memberof Debug */ -declare function debugText(text: string, pos: Vector2, size?: number, color?: string, time?: number, angle?: number, font?: string): void; -/** Clear all debug primitives in the list - * @memberof Debug */ -declare function debugClear(): any[]; -/** Save a canvas to disk - * @param {HTMLCanvasElement} canvas - * @param {String} [filename] - * @memberof Debug */ -declare function debugSaveCanvas(canvas: HTMLCanvasElement, filename?: string): void; -declare function debugInit(): void; -declare function debugUpdate(): void; -declare function debugRender(): void; -/** A shortcut to get Math.PI - * @const - * @memberof Utilities */ -declare const PI: number; -/** Returns absoulte value of value passed in - * @param {Number} value - * @return {Number} - * @memberof Utilities */ -declare function abs(a: any): number; -/** Returns lowest of two values passed in - * @param {Number} valueA - * @param {Number} valueB - * @return {Number} - * @memberof Utilities */ -declare function min(a: any, b: any): number; -/** Returns highest of two values passed in - * @param {Number} valueA - * @param {Number} valueB - * @return {Number} - * @memberof Utilities */ -declare function max(a: any, b: any): number; -/** Returns the sign of value passed in (also returns 1 if 0) - * @param {Number} value - * @return {Number} - * @memberof Utilities */ -declare function sign(a: any): number; -/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected - * @param {Number} dividend - * @param {Number} [divisor=1] - * @return {Number} - * @memberof Utilities */ -declare function mod(a: any, b?: number): number; -/** Clamps the value beween max and min - * @param {Number} value - * @param {Number} [min=0] - * @param {Number} [max=1] - * @return {Number} - * @memberof Utilities */ -declare function clamp(v: any, min?: number, max?: number): number; -/** Returns what percentage the value is between max and min - * @param {Number} value - * @param {Number} [min=0] - * @param {Number} [max=1] - * @return {Number} - * @memberof Utilities */ -declare function percent(v: any, min?: number, max?: number): number; -/** Linearly interpolates the percent value between max and min - * @param {Number} percent - * @param {Number} [min=0] - * @param {Number} [max=1] - * @return {Number} - * @memberof Utilities */ -declare function lerp(p: any, min?: number, max?: number): number; -/** Applies smoothstep function to the percentage value - * @param {Number} value - * @return {Number} - * @memberof Utilities */ -declare function smoothStep(p: any): number; -/** Returns the nearest power of two not less then the value - * @param {Number} value - * @return {Number} - * @memberof Utilities */ -declare function nearestPowerOfTwo(v: any): number; -/** Returns true if two axis aligned bounding boxes are overlapping - * @param {Vector2} pointA - Center of box A - * @param {Vector2} sizeA - Size of box A - * @param {Vector2} pointB - Center of box B - * @param {Vector2} [sizeB] - Size of box B - * @return {Boolean} - True if overlapping - * @memberof Utilities */ -declare function isOverlapping(pA: any, sA: any, pB: any, sB: any): boolean; -/** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default - * @param {Number} [frequency=1] - Frequency of the wave in Hz - * @param {Number} [amplitude=1] - Amplitude (max height) of the wave - * @param {Number} [t=time] - Value to use for time of the wave - * @return {Number} - Value waving between 0 and amplitude - * @memberof Utilities */ -declare function wave(frequency?: number, amplitude?: number, t?: number): number; -/** Formats seconds to mm:ss style for display purposes - * @param {Number} t - time in seconds - * @return {String} - * @memberof Utilities */ -declare function formatTime(t: number): string; -/** Random global functions - * @namespace Random */ -/** Returns a random value between the two values passed in - * @param {Number} [valueA=1] - * @param {Number} [valueB=0] - * @return {Number} - * @memberof Random */ -declare function rand(a?: number, b?: number): number; -/** Returns a floored random value the two values passed in - * @param {Number} [valueA=1] - * @param {Number} [valueB=0] - * @return {Number} - * @memberof Random */ -declare function randInt(a?: number, b?: number): number; -/** Randomly returns either -1 or 1 - * @return {Number} - * @memberof Random */ -declare function randSign(): number; -/** Returns a random Vector2 within a circular shape - * @param {Number} [radius=1] - * @param {Number} [minRadius=0] - * @return {Vector2} - * @memberof Random */ -declare function randInCircle(radius?: number, minRadius?: number): Vector2; -/** Returns a random Vector2 with the passed in length - * @param {Number} [length=1] - * @return {Vector2} - * @memberof Random */ -declare function randVector(length?: number): Vector2; -/** Returns a random color between the two passed in colors, combine components if linear - * @param {Color} [colorA=new Color(1,1,1,1)] - * @param {Color} [colorB=new Color(0,0,0,1)] - * @param {Boolean} [linear] - * @return {Color} - * @memberof Random */ -declare function randColor(cA?: Color, cB?: Color, linear?: boolean): Color; -/** Seed used by the randSeeded function - * @memberof Random */ -declare let randSeed: number; -/** Set seed used by the randSeeded function, should not be 0 - * @param {Number} seed - * @memberof Random */ -declare function setRandSeed(seed: number): number; -/** Returns a seeded random value between the two values passed in using randSeed - * @param {Number} [valueA=1] - * @param {Number} [valueB=0] - * @return {Number} - * @memberof Random */ -declare function randSeeded(a?: number, b?: number): number; -/** - * Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar - * @param {Number} [x=0] - * @param {Number} [y=0] - * @return {Vector2} - * @example - * let a = vec2(0, 1); // vector with coordinates (0, 1) - * let b = vec2(a); // copy a into b - * a = vec2(5); // set a to (5, 5) - * b = vec2(); // set b to (0, 0) - * @memberof Utilities - */ -declare function vec2(x?: number, y?: number): Vector2; -/** - * Check if object is a valid Vector2 - * @param {Vector2} vector - * @return {Boolean} - * @memberof Utilities - */ -declare function isVector2(v: any): boolean; -/** - * 2D Vector object with vector math library - *
- Functions do not change this so they can be chained together - * @example - * let a = new Vector2(2, 3); // vector with coordinates (2, 3) - * let b = new Vector2; // vector with coordinates (0, 0) - * let c = vec2(4, 2); // use the vec2 function to make a Vector2 - * let d = a.add(b).scale(5); // operators can be chained - */ -declare class Vector2 { - /** Create a 2D vector with the x and y passed in, can also be created with vec2() - * @param {Number} [x=0] - X axis location - * @param {Number} [y=0] - Y axis location */ - constructor(x?: number, y?: number); - /** @property {Number} - X axis location */ - x: number; - /** @property {Number} - Y axis location */ - y: number; - /** Returns a new vector that is a copy of this - * @return {Vector2} */ - copy(): Vector2; - /** Returns a copy of this vector plus the vector passed in - * @param {Vector2} vector - * @return {Vector2} */ - add(v: any): Vector2; - /** Returns a copy of this vector minus the vector passed in - * @param {Vector2} vector - * @return {Vector2} */ - subtract(v: any): Vector2; - /** Returns a copy of this vector times the vector passed in - * @param {Vector2} vector - * @return {Vector2} */ - multiply(v: any): Vector2; - /** Returns a copy of this vector divided by the vector passed in - * @param {Vector2} vector - * @return {Vector2} */ - divide(v: any): Vector2; - /** Returns a copy of this vector scaled by the vector passed in - * @param {Number} scale - * @return {Vector2} */ - scale(s: any): Vector2; - /** Returns the length of this vector - * @return {Number} */ - length(): number; - /** Returns the length of this vector squared - * @return {Number} */ - lengthSquared(): number; - /** Returns the distance from this vector to vector passed in - * @param {Vector2} vector - * @return {Number} */ - distance(v: any): number; - /** Returns the distance squared from this vector to vector passed in - * @param {Vector2} vector - * @return {Number} */ - distanceSquared(v: any): number; - /** Returns a new vector in same direction as this one with the length passed in - * @param {Number} [length=1] - * @return {Vector2} */ - normalize(length?: number): Vector2; - /** Returns a new vector clamped to length passed in - * @param {Number} [length=1] - * @return {Vector2} */ - clampLength(length?: number): Vector2; - /** Returns the dot product of this and the vector passed in - * @param {Vector2} vector - * @return {Number} */ - dot(v: any): number; - /** Returns the cross product of this and the vector passed in - * @param {Vector2} vector - * @return {Number} */ - cross(v: any): number; - /** Returns the angle of this vector, up is angle 0 - * @return {Number} */ - angle(): number; - /** Sets this vector with angle and length passed in - * @param {Number} [angle=0] - * @param {Number} [length=1] */ - setAngle(a?: number, length?: number): Vector2; - /** Returns copy of this vector rotated by the angle passed in - * @param {Number} angle - * @return {Vector2} */ - rotate(a: any): Vector2; - /** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3) - * @return {Number} */ - direction(): number; - /** Returns a copy of this vector that has been inverted - * @return {Vector2} */ - invert(): Vector2; - /** Returns a copy of this vector with each axis floored - * @return {Vector2} */ - floor(): Vector2; - /** Returns the area this vector covers as a rectangle - * @return {Number} */ - area(): number; - /** Returns a new vector that is p percent between this and the vector passed in - * @param {Vector2} vector - * @param {Number} percent - * @return {Vector2} */ - lerp(v: any, p: any): Vector2; - /** Returns true if this vector is within the bounds of an array size passed in - * @param {Vector2} arraySize - * @return {Boolean} */ - arrayCheck(arraySize: Vector2): boolean; - /** Returns this vector expressed as a string - * @param {float} digits - precision to display - * @return {String} */ - toString(digits?: float): string; -} -/** - * Create a color object with RGBA values - * @param {Number} [r=1] - * @param {Number} [g=1] - * @param {Number} [b=1] - * @param {Number} [a=1] - * @return {Color} - * @memberof Utilities - */ -declare function colorRGBA(r?: number, g?: number, b?: number, a?: number): Color; -/** - * Create a color object with HSLA values - * @param {Number} [h=0] - * @param {Number} [s=0] - * @param {Number} [l=1] - * @param {Number} [a=1] - * @return {Color} - * @memberof Utilities - */ -declare function colorHSLA(h?: number, s?: number, l?: number, a?: number): Color; -/** - * Color object (red, green, blue, alpha) with some helpful functions - * @example - * let a = new Color; // white - * let b = new Color(1, 0, 0); // red - * let c = new Color(0, 0, 0, 0); // transparent black - */ -declare class Color { - /** Create a color with the components passed in, white by default - * @param {Number} [red=1] - * @param {Number} [green=1] - * @param {Number} [blue=1] - * @param {Number} [alpha=1] */ - constructor(r?: number, g?: number, b?: number, a?: number); - /** @property {Number} - Red */ - r: number; - /** @property {Number} - Green */ - g: number; - /** @property {Number} - Blue */ - b: number; - /** @property {Number} - Alpha */ - a: number; - /** Returns a new color that is a copy of this - * @return {Color} */ - copy(): Color; - /** Returns a copy of this color plus the color passed in - * @param {Color} color - * @return {Color} */ - add(c: any): Color; - /** Returns a copy of this color minus the color passed in - * @param {Color} color - * @return {Color} */ - subtract(c: any): Color; - /** Returns a copy of this color times the color passed in - * @param {Color} color - * @return {Color} */ - multiply(c: any): Color; - /** Returns a copy of this color divided by the color passed in - * @param {Color} color - * @return {Color} */ - divide(c: any): Color; - /** Returns a copy of this color scaled by the value passed in, alpha can be scaled separately - * @param {Number} scale - * @param {Number} [alphaScale=scale] - * @return {Color} */ - scale(s: any, a?: any): Color; - /** Returns a copy of this color clamped to the valid range between 0 and 1 - * @return {Color} */ - clamp(): Color; - /** Returns a new color that is p percent between this and the color passed in - * @param {Color} color - * @param {Number} percent - * @return {Color} */ - lerp(c: any, p: any): Color; - /** Sets this color given a hue, saturation, lightness, and alpha - * @param {Number} [hue=0] - * @param {Number} [saturation=0] - * @param {Number} [lightness=1] - * @param {Number} [alpha=1] - * @return {Color} */ - setHSLA(h?: number, s?: number, l?: number, a?: number): Color; - /** Returns this color expressed in hsla format - * @return {Array} */ - getHSLA(): any[]; - /** Returns a new color that has each component randomly adjusted - * @param {Number} [amount=.05] - * @param {Number} [alphaAmount=0] - * @return {Color} */ - mutate(amount?: number, alphaAmount?: number): Color; - /** Returns this color expressed as a hex color code - * @param {Boolean} [useAlpha=1] - if alpha should be included in result - * @return {String} */ - toString(useAlpha?: boolean): string; - /** Set this color from a hex code - * @param {String} hex - html hex code - * @return {Color} */ - setHex(hex: string): Color; - /** Returns this color expressed as 32 bit RGBA value - * @return {Number} */ - rgbaInt(): number; -} -/** - * Timer object tracks how long has passed since it was set - * @example - * let a = new Timer; // creates a timer that is not set - * a.set(3); // sets the timer to 3 seconds - * - * let b = new Timer(1); // creates a timer with 1 second left - * b.unset(); // unsets the timer - */ -declare class Timer { - /** Create a timer object set time passed in - * @param {Number} [timeLeft] - How much time left before the timer elapses in seconds */ - constructor(timeLeft?: number); - time: number; - setTime: number; - /** Set the timer with seconds passed in - * @param {Number} [timeLeft=0] - How much time left before the timer is elapsed in seconds */ - set(timeLeft?: number): void; - /** Unset the timer */ - unset(): void; - /** Returns true if set - * @return {Boolean} */ - isSet(): boolean; - /** Returns true if set and has not elapsed - * @return {Boolean} */ - active(): boolean; - /** Returns true if set and elapsed - * @return {Boolean} */ - elapsed(): boolean; - /** Get how long since elapsed, returns 0 if not set (returns negative if currently active) - * @return {Number} */ - get(): number; - /** Get percentage elapsed based on time it was set to, returns 0 if not set - * @return {Number} */ - getPercent(): number; - /** Returns this timer expressed as a string - * @return {String} */ - toString(): string; - /** Get how long since elapsed, returns 0 if not set (returns negative if currently active) - * @return {Number} */ - valueOf(): number; -} -/** Position of camera in world space - * @type {Vector2} - * @default - * @memberof Settings */ -declare let cameraPos: Vector2; -/** Scale of camera in world space - * @default - * @memberof Settings */ -declare let cameraScale: number; -/** The max size of the canvas, centered if window is larger - * @type {Vector2} - * @default - * @memberof Settings */ -declare let canvasMaxSize: Vector2; -/** Fixed size of the canvas, if enabled canvas size never changes - * - you may also need to set mainCanvasSize if using screen space coords in startup - * @type {Vector2} - * @default - * @memberof Settings */ -declare let canvasFixedSize: Vector2; -/** Disables anti aliasing for pixel art if true - * @default - * @memberof Settings */ -declare let cavasPixelated: number; -/** Default font used for text rendering - * @default - * @memberof Settings */ -declare let fontDefault: string; -/** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled) - * @default - * @memberof Settings */ -declare let glEnable: number; -/** Fixes slow rendering in some browsers by not compositing the WebGL canvas - * @default - * @memberof Settings */ -declare let glOverlay: number; -/** Default size of tiles in pixels - * @type {Vector2} - * @default - * @memberof Settings */ -declare let tileSizeDefault: Vector2; -/** Prevent tile bleeding from neighbors in pixels - * @default - * @memberof Settings */ -declare let tileFixBleedScale: number; -/** Default size of objects - * @type {Vector2} - * @default - * @memberof Settings */ -declare let objectDefaultSize: Vector2; -/** Enable physics solver for collisions between objects - * @default - * @memberof Settings */ -declare let enablePhysicsSolver: number; -/** Default object mass for collison calcuations (how heavy objects are) - * @default - * @memberof Settings */ -declare let objectDefaultMass: number; -/** How much to slow velocity by each frame (0-1) - * @default - * @memberof Settings */ -declare let objectDefaultDamping: number; -/** How much to slow angular velocity each frame (0-1) - * @default - * @memberof Settings */ -declare let objectDefaultAngleDamping: number; -/** How much to bounce when a collision occurs (0-1) - * @default - * @memberof Settings */ -declare let objectDefaultElasticity: number; -/** How much to slow when touching (0-1) - * @default - * @memberof Settings */ -declare let objectDefaultFriction: number; -/** Clamp max speed to avoid fast objects missing collisions - * @default - * @memberof Settings */ -declare let objectMaxSpeed: number; -/** How much gravity to apply to objects along the Y axis, negative is down - * @default - * @memberof Settings */ -declare let gravity: number; -/** Scales emit rate of particles, useful for low graphics mode (0 disables particle emitters) - * @default - * @memberof Settings */ -declare let particleEmitRateScale: number; -/** Should gamepads be allowed - * @default - * @memberof Settings */ -declare let gamepadsEnable: number; -/** If true, the dpad input is also routed to the left analog stick (for better accessability) - * @default - * @memberof Settings */ -declare let gamepadDirectionEmulateStick: number; -/** If true the WASD keys are also routed to the direction keys (for better accessability) - * @default - * @memberof Settings */ -declare let inputWASDEmulateDirection: number; -/** True if touch gamepad should appear on mobile devices - *
- Supports left analog stick, 4 face buttons and start button (button 9) - *
- Must be set by end of gameInit to be activated - * @default - * @memberof Settings */ -declare let touchGamepadEnable: number; -/** True if touch gamepad should be analog stick or false to use if 8 way dpad - * @default - * @memberof Settings */ -declare let touchGamepadAnalog: number; -/** Size of virutal gamepad for touch devices in pixels - * @default - * @memberof Settings */ -declare let touchGamepadSize: number; -/** Transparency of touch gamepad overlay - * @default - * @memberof Settings */ -declare let touchGamepadAlpha: number; -/** Allow vibration hardware if it exists - * @default - * @memberof Settings */ -declare let vibrateEnable: number; -/** All audio code can be disabled and removed from build - * @default - * @memberof Settings */ -declare let soundEnable: number; -/** Volume scale to apply to all sound, music and speech - * @default - * @memberof Settings */ -declare let soundVolume: number; -/** Default range where sound no longer plays - * @default - * @memberof Settings */ -declare let soundDefaultRange: number; -/** Default range percent to start tapering off sound (0-1) - * @default - * @memberof Settings */ -declare let soundDefaultTaper: number; -/** How long to show medals for in seconds - * @default - * @memberof Settings */ -declare let medalDisplayTime: number; -/** How quickly to slide on/off medals in seconds - * @default - * @memberof Settings */ -declare let medalDisplaySlideTime: number; -/** Size of medal display - * @default - * @memberof Settings */ -declare let medalDisplaySize: Vector2; -/** Size of icon in medal display - * @default - * @memberof Settings */ -declare let medalDisplayIconSize: number; -/** Set to stop medals from being unlockable (like if cheats are enabled) - * @default - * @memberof Settings */ -declare let medalsPreventUnlock: any; -/** - * LittleJS Object Base Object Class - *
- Base object class used by the engine - *
- Automatically adds self to object list - *
- Will be updated and rendered each frame - *
- Renders as a sprite from a tilesheet by default - *
- Can have color and addtive color applied - *
- 2d Physics and collision system - *
- Sorted by renderOrder - *
- Objects can have children attached - *
- Parents are updated before children, and set child transform - *
- Call destroy() to get rid of objects - *
- *
The physics system used by objects is simple and fast with some caveats... - *
- Collision uses the axis aligned size, the object's rotation angle is only for rendering - *
- Objects are guaranteed to not intersect tile collision from physics - *
- If an object starts or is moved inside tile collision, it will not collide with that tile - *
- Collision for objects can be set to be solid to block other objects - *
- Objects may get pushed into overlapping other solid objects, if so they will push away - *
- Solid objects are more performance intensive and should be used sparingly - * @example - * // create an engine object, normally you would first extend the class with your own - * const pos = vec2(2,3); - * const object = new EngineObject(pos); - */ -declare class EngineObject { - /** Create an engine object and adds it to the list of objects - * @param {Vector2} [position=new Vector2()] - World space position of the object - * @param {Vector2} [size=objectDefaultSize] - World space size of the object - * @param {Number} [tileIndex=-1] - Tile to use to render object (-1 is untextured) - * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels - * @param {Number} [angle=0] - Angle the object is rotated by - * @param {Color} [color] - Color to apply to tile when rendered - * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered - */ - constructor(pos?: Vector2, size?: Vector2, tileIndex?: number, tileSize?: Vector2, angle?: number, color?: Color, renderOrder?: number); - /** @property {Vector2} - World space position of the object */ - pos: Vector2; - /** @property {Vector2} - World space width and height of the object */ - size: Vector2; - /** @property {Number} - Tile to use to render object (-1 is untextured) */ - tileIndex: number; - /** @property {Vector2} - Size of tile in source pixels */ - tileSize: Vector2; - /** @property {Number} - Angle to rotate the object */ - angle: number; - /** @property {Color} - Color to apply when rendered */ - color: Color; - /** @property {Number} [mass=objectDefaultMass] - How heavy the object is, static if 0 */ - mass: number; - /** @property {Number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */ - damping: number; - /** @property {Number} [angleDamping=objectDefaultAngleDamping] - How much to slow down rotation each frame (0-1) */ - angleDamping: number; - /** @property {Number} [elasticity=objectDefaultElasticity] - How bouncy the object is when colliding (0-1) */ - elasticity: number; - /** @property {Number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */ - friction: number; - /** @property {Number} [gravityScale=1] - How much to scale gravity by for this object */ - gravityScale: number; - /** @property {Number} [renderOrder=0] - Objects are sorted by render order */ - renderOrder: number; - /** @property {Vector2} [velocity=new Vector2()] - Velocity of the object */ - velocity: Vector2; - /** @property {Number} [angleVelocity=0] - Angular velocity of the object */ - angleVelocity: number; - spawnTime: number; - children: any[]; - collideTiles: number; - /** Update the object transform and physics, called automatically by engine once each frame */ - update(): void; - groundObject: any; - /** Render the object, draws a tile by default, automatically called each frame, sorted by renderOrder */ - render(): void; - /** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */ - destroy(): void; - destroyed: number; - /** Called to check if a tile collision should be resolved - * @param {Number} tileData - the value of the tile at the position - * @param {Vector2} pos - tile where the collision occured - * @return {Boolean} - true if the collision should be resolved */ - collideWithTile(tileData: number, pos: Vector2): boolean; - /** Called to check if a tile raycast hit - * @param {Number} tileData - the value of the tile at the position - * @param {Vector2} pos - tile where the raycast is - * @return {Boolean} - true if the raycast should hit */ - collideWithTileRaycast(tileData: number, pos: Vector2): boolean; - /** Called to check if a object collision should be resolved - * @param {EngineObject} object - the object to test against - * @return {Boolean} - true if the collision should be resolved - */ - collideWithObject(o: any): boolean; - /** How long since the object was created - * @return {Number} */ - getAliveTime(): number; - /** Apply acceleration to this object (adjust velocity, not affected by mass) - * @param {Vector2} acceleration */ - applyAcceleration(a: any): void; - /** Apply force to this object (adjust velocity, affected by mass) - * @param {Vector2} force */ - applyForce(force: Vector2): void; - /** Get the direction of the mirror - * @return {Number} -1 if this.mirror is true, or 1 if not mirrored */ - getMirrorSign(): number; - /** Attaches a child to this with a given local transform - * @param {EngineObject} child - * @param {Vector2} [localPos=new Vector2] - * @param {Number} [localAngle=0] */ - addChild(child: EngineObject, localPos?: Vector2, localAngle?: number): void; - /** Removes a child from this one - * @param {EngineObject} child */ - removeChild(child: EngineObject): void; - /** Set how this object collides - * @param {boolean} [collideSolidObjects=1] - Does it collide with solid objects - * @param {boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers) - * @param {boolean} [collideTiles=1] - Does it collide with the tile collision */ - setCollision(collideSolidObjects?: boolean, isSolid?: boolean, collideTiles?: boolean): void; - collideSolidObjects: boolean; - isSolid: boolean; - toString(): string; -} -/** The primary 2D canvas visible to the user - * @type {HTMLCanvasElement} - * @memberof Draw */ -declare let mainCanvas: HTMLCanvasElement; -/** 2d context for mainCanvas - * @type {CanvasRenderingContext2D} - * @memberof Draw */ -declare let mainContext: CanvasRenderingContext2D; -/** A canvas that appears on top of everything the same size as mainCanvas - * @type {HTMLCanvasElement} - * @memberof Draw */ -declare let overlayCanvas: HTMLCanvasElement; -/** 2d context for overlayCanvas - * @type {CanvasRenderingContext2D} - * @memberof Draw */ -declare let overlayContext: CanvasRenderingContext2D; -/** The size of the main canvas (and other secondary canvases) - * @type {Vector2} - * @memberof Draw */ -declare let mainCanvasSize: Vector2; -/** Tile sheet for batch rendering system - * @type {Image} - * @memberof Draw */ -declare const tileImage: new (width?: number, height?: number) => HTMLImageElement; -declare let tileImageSize: any; -declare let tileImageFixBleed: any; -declare let drawCount: any; -/** Convert from screen to world space coordinates - * - if calling outside of render, you may need to manually set mainCanvasSize - * @param {Vector2} screenPos - * @return {Vector2} - * @memberof Draw */ -declare function screenToWorld(screenPos: Vector2): Vector2; -/** Convert from world to screen space coordinates - * - if calling outside of render, you may need to manually set mainCanvasSize - * @param {Vector2} worldPos - * @return {Vector2} - * @memberof Draw */ -declare function worldToScreen(worldPos: Vector2): Vector2; -/** - * Font Image Object - Draw text on a 2D canvas by using characters in an image - *
- 96 characters (from space to tilde) are stored in an image - *
- Uses a default 8x8 font if none is supplied - *
- You can also use fonts from the main tile sheet - * @example - * // use built in font - * const font = new ImageFont; - * - * // draw text - * font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50)); - */ -declare let engineFontImage: any; -declare class FontImage { - /** Create an image font - * @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used - * @param {Vector2} [tileSize=vec2(8)] - Size of the font source tiles - * @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters - * @param {Number} [startTileIndex=0] - Tile index in image where font starts - * @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to +declare module "engine.all.module" { + /** + * LittleJS Module Export + *
- Export engine as a module with extra functions where necessary */ - constructor(image?: HTMLImageElement, tileSize?: Vector2, paddingSize?: Vector2, startTileIndex?: number, context?: CanvasRenderingContext2D); - image: any; - tileSize: Vector2; - paddingSize: Vector2; - startTileIndex: number; - context: CanvasRenderingContext2D; - /** Draw text in screen space using the image font - * @param {String} text + export function setCameraPos(v: any): any; + export function setCameraScale(v: any): any; + export function setCanvasMaxSize(v: any): any; + export function setCanvasFixedSize(v: any): any; + export function setCavasPixelated(v: any): any; + export function setFontDefault(v: any): any; + export function setGlEnable(v: any): any; + export function setGlOverlay(v: any): any; + export function setTileSizeDefault(v: any): any; + export function setTileFixBleedScale(v: any): any; + export function setObjectDefaultSize(v: any): any; + export function setEnablePhysicsSolver(v: any): any; + export function setObjectDefaultMass(v: any): any; + export function setObjectDefaultDamping(v: any): any; + export function setObjectDefaultAngleDamping(v: any): any; + export function setObjectDefaultElasticity(v: any): any; + export function setObjectDefaultFriction(v: any): any; + export function setObjectMaxSpeed(v: any): any; + export function setGravity(v: any): any; + export function setParticleEmitRateScale(v: any): any; + export function setGamepadsEnable(v: any): any; + export function setGamepadDirectionEmulateStick(v: any): any; + export function setInputWASDEmulateDirection(v: any): any; + export function setTouchGamepadEnable(v: any): any; + export function setTouchGamepadAnalog(v: any): any; + export function setTouchGamepadSize(v: any): any; + export function setTouchGamepadAlpha(v: any): any; + export function setVibrateEnable(v: any): any; + export function setSoundEnable(v: any): any; + export function setSoundVolume(v: any): any; + export function setSoundDefaultRange(v: any): any; + export function setSoundDefaultTaper(v: any): any; + export function setMedalDisplayTime(v: any): any; + export function setMedalDisplaySlideTime(v: any): any; + export function setMedalDisplaySize(v: any): any; + export function setMedalDisplayIconSize(v: any): any; + export function setMedalsPreventUnlock(v: any): any; + /** The max size of the canvas, centered if window is larger + * @type {Vector2} + * @default + * @memberof Settings */ + export let canvasMaxSize: Vector2; + /** Fixed size of the canvas, if enabled canvas size never changes + * - you may also need to set mainCanvasSize if using screen space coords in startup + * @type {Vector2} + * @default + * @memberof Settings */ + export let canvasFixedSize: Vector2; + /** Disables anti aliasing for pixel art if true + * @default + * @memberof Settings */ + export let cavasPixelated: number; + /** Default font used for text rendering + * @default + * @memberof Settings */ + export let fontDefault: string; + /** Default size of tiles in pixels + * @type {Vector2} + * @default + * @memberof Settings */ + export let tileSizeDefault: Vector2; + /** Prevent tile bleeding from neighbors in pixels + * @default + * @memberof Settings */ + export let tileFixBleedScale: number; + /** Default size of objects + * @type {Vector2} + * @default + * @memberof Settings */ + export let objectDefaultSize: Vector2; + /** Enable physics solver for collisions between objects + * @default + * @memberof Settings */ + export let enablePhysicsSolver: number; + /** Default object mass for collison calcuations (how heavy objects are) + * @default + * @memberof Settings */ + export let objectDefaultMass: number; + /** How much to slow velocity by each frame (0-1) + * @default + * @memberof Settings */ + export let objectDefaultDamping: number; + /** How much to slow angular velocity each frame (0-1) + * @default + * @memberof Settings */ + export let objectDefaultAngleDamping: number; + /** How much to bounce when a collision occurs (0-1) + * @default + * @memberof Settings */ + export let objectDefaultElasticity: number; + /** How much to slow when touching (0-1) + * @default + * @memberof Settings */ + export let objectDefaultFriction: number; + /** Clamp max speed to avoid fast objects missing collisions + * @default + * @memberof Settings */ + export let objectMaxSpeed: number; + /** How much gravity to apply to objects along the Y axis, negative is down + * @default + * @memberof Settings */ + export let gravity: number; + /** Scales emit rate of particles, useful for low graphics mode (0 disables particle emitters) + * @default + * @memberof Settings */ + export let particleEmitRateScale: number; + /** Position of camera in world space + * @type {Vector2} + * @default + * @memberof Settings */ + export let cameraPos: Vector2; + /** Scale of camera in world space + * @default + * @memberof Settings */ + export let cameraScale: number; + /** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled) + * @default + * @memberof Settings */ + export let glEnable: number; + /** Fixes slow rendering in some browsers by not compositing the WebGL canvas + * @default + * @memberof Settings */ + export let glOverlay: number; + /** Should gamepads be allowed + * @default + * @memberof Settings */ + export let gamepadsEnable: number; + /** If true, the dpad input is also routed to the left analog stick (for better accessability) + * @default + * @memberof Settings */ + export let gamepadDirectionEmulateStick: number; + /** If true the WASD keys are also routed to the direction keys (for better accessability) + * @default + * @memberof Settings */ + export let inputWASDEmulateDirection: number; + /** True if touch gamepad should appear on mobile devices + *
- Supports left analog stick, 4 face buttons and start button (button 9) + *
- Must be set by end of gameInit to be activated + * @default + * @memberof Settings */ + export let touchGamepadEnable: number; + /** True if touch gamepad should be analog stick or false to use if 8 way dpad + * @default + * @memberof Settings */ + export let touchGamepadAnalog: number; + /** Size of virutal gamepad for touch devices in pixels + * @default + * @memberof Settings */ + export let touchGamepadSize: number; + /** Transparency of touch gamepad overlay + * @default + * @memberof Settings */ + export let touchGamepadAlpha: number; + /** Allow vibration hardware if it exists + * @default + * @memberof Settings */ + export let vibrateEnable: number; + /** All audio code can be disabled and removed from build + * @default + * @memberof Settings */ + export let soundEnable: number; + /** Volume scale to apply to all sound, music and speech + * @default + * @memberof Settings */ + export let soundVolume: number; + /** Default range where sound no longer plays + * @default + * @memberof Settings */ + export let soundDefaultRange: number; + /** Default range percent to start tapering off sound (0-1) + * @default + * @memberof Settings */ + export let soundDefaultTaper: number; + /** How long to show medals for in seconds + * @default + * @memberof Settings */ + export let medalDisplayTime: number; + /** How quickly to slide on/off medals in seconds + * @default + * @memberof Settings */ + export let medalDisplaySlideTime: number; + /** Size of medal display + * @default + * @memberof Settings */ + export let medalDisplaySize: Vector2; + /** Size of icon in medal display + * @default + * @memberof Settings */ + export let medalDisplayIconSize: number; + /** True if debug is enabled + * @default + * @memberof Debug */ + export const debug: 1; + /** True if watermark with FPS should be down, false in release builds + * @default + * @memberof Debug */ + export let showWatermark: number; + /** True if god mode is enabled, handle this however you want + * @default + * @memberof Debug */ + export let godMode: number; + /** Draw a debug rectangle in world space * @param {Vector2} pos - * @param {Number} [scale=4] - * @param {Boolean} [center] - */ - drawTextScreen(text: string, pos: Vector2, scale?: number, center?: boolean): void; - /** Draw text in world space using the image font + * @param {Vector2} [size=new Vector2()] + * @param {String} [color='#fff'] + * @param {Number} [time=0] + * @param {Number} [angle=0] + * @param {Boolean} [fill=false] + * @memberof Debug */ + export function debugRect(pos: Vector2, size?: Vector2, color?: string, time?: number, angle?: number, fill?: boolean): void; + /** Draw a debug circle in world space + * @param {Vector2} pos + * @param {Number} [radius=0] + * @param {String} [color='#fff'] + * @param {Number} [time=0] + * @param {Boolean} [fill=false] + * @memberof Debug */ + export function debugCircle(pos: Vector2, radius?: number, color?: string, time?: number, fill?: boolean): void; + /** Draw a debug point in world space + * @param {Vector2} pos + * @param {String} [color='#fff'] + * @param {Number} [time=0] + * @param {Number} [angle=0] + * @memberof Debug */ + export function debugPoint(pos: Vector2, color?: string, time?: number, angle?: number): void; + /** Draw a debug line in world space + * @param {Vector2} posA + * @param {Vector2} posB + * @param {String} [color='#fff'] + * @param {Number} [thickness=.1] + * @param {Number} [time=0] + * @memberof Debug */ + export function debugLine(posA: Vector2, posB: Vector2, color?: string, thickness?: number, time?: number): void; + /** Draw a debug axis aligned bounding box in world space + * @param {Vector2} posA + * @param {Vector2} sizeA + * @param {Vector2} posB + * @param {Vector2} sizeB + * @param {String} [color='#fff'] + * @memberof Debug */ + export function debugAABB(pA: any, sA: any, pB: any, sB: any, color?: string): void; + /** Draw a debug axis aligned bounding box in world space * @param {String} text * @param {Vector2} pos - * @param {Number} [scale=.25] - * @param {Boolean} [center] + * @param {Number} [size=1] + * @param {String} [color='#fff'] + * @param {Number} [time=0] + * @param {Number} [angle=0] + * @param {String} [font='monospace'] + * @memberof Debug */ + export function debugText(text: string, pos: Vector2, size?: number, color?: string, time?: number, angle?: number, font?: string): void; + /** Clear all debug primitives in the list + * @memberof Debug */ + export function debugClear(): any[]; + /** Save a canvas to disk + * @param {HTMLCanvasElement} canvas + * @param {String} [filename] + * @memberof Debug */ + export function debugSaveCanvas(canvas: HTMLCanvasElement, filename?: string): void; + /** A shortcut to get Math.PI + * @const + * @memberof Utilities */ + export const PI: number; + /** Returns absoulte value of value passed in + * @param {Number} value + * @return {Number} + * @memberof Utilities */ + export function abs(a: any): number; + /** Returns lowest of two values passed in + * @param {Number} valueA + * @param {Number} valueB + * @return {Number} + * @memberof Utilities */ + export function min(a: any, b: any): number; + /** Returns highest of two values passed in + * @param {Number} valueA + * @param {Number} valueB + * @return {Number} + * @memberof Utilities */ + export function max(a: any, b: any): number; + /** Returns the sign of value passed in (also returns 1 if 0) + * @param {Number} value + * @return {Number} + * @memberof Utilities */ + export function sign(a: any): number; + /** Returns first parm modulo the second param, but adjusted so negative numbers work as expected + * @param {Number} dividend + * @param {Number} [divisor=1] + * @return {Number} + * @memberof Utilities */ + export function mod(a: any, b?: number): number; + /** Clamps the value beween max and min + * @param {Number} value + * @param {Number} [min=0] + * @param {Number} [max=1] + * @return {Number} + * @memberof Utilities */ + export function clamp(v: any, min?: number, max?: number): number; + /** Returns what percentage the value is between max and min + * @param {Number} value + * @param {Number} [min=0] + * @param {Number} [max=1] + * @return {Number} + * @memberof Utilities */ + export function percent(v: any, min?: number, max?: number): number; + /** Linearly interpolates the percent value between max and min + * @param {Number} percent + * @param {Number} [min=0] + * @param {Number} [max=1] + * @return {Number} + * @memberof Utilities */ + export function lerp(p: any, min?: number, max?: number): number; + /** Applies smoothstep function to the percentage value + * @param {Number} value + * @return {Number} + * @memberof Utilities */ + export function smoothStep(p: any): number; + /** Returns the nearest power of two not less then the value + * @param {Number} value + * @return {Number} + * @memberof Utilities */ + export function nearestPowerOfTwo(v: any): number; + /** Returns true if two axis aligned bounding boxes are overlapping + * @param {Vector2} pointA - Center of box A + * @param {Vector2} sizeA - Size of box A + * @param {Vector2} pointB - Center of box B + * @param {Vector2} [sizeB] - Size of box B + * @return {Boolean} - True if overlapping + * @memberof Utilities */ + export function isOverlapping(pA: any, sA: any, pB: any, sB: any): boolean; + /** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default + * @param {Number} [frequency=1] - Frequency of the wave in Hz + * @param {Number} [amplitude=1] - Amplitude (max height) of the wave + * @param {Number} [t=time] - Value to use for time of the wave + * @return {Number} - Value waving between 0 and amplitude + * @memberof Utilities */ + export function wave(frequency?: number, amplitude?: number, t?: number): number; + /** Formats seconds to mm:ss style for display purposes + * @param {Number} t - time in seconds + * @return {String} + * @memberof Utilities */ + export function formatTime(t: number): string; + /** Random global functions + * @namespace Random */ + /** Returns a random value between the two values passed in + * @param {Number} [valueA=1] + * @param {Number} [valueB=0] + * @return {Number} + * @memberof Random */ + export function rand(a?: number, b?: number): number; + /** Returns a floored random value the two values passed in + * @param {Number} [valueA=1] + * @param {Number} [valueB=0] + * @return {Number} + * @memberof Random */ + export function randInt(a?: number, b?: number): number; + /** Randomly returns either -1 or 1 + * @return {Number} + * @memberof Random */ + export function randSign(): number; + /** Returns a random Vector2 within a circular shape + * @param {Number} [radius=1] + * @param {Number} [minRadius=0] + * @return {Vector2} + * @memberof Random */ + export function randInCircle(radius?: number, minRadius?: number): Vector2; + /** Returns a random Vector2 with the passed in length + * @param {Number} [length=1] + * @return {Vector2} + * @memberof Random */ + export function randVector(length?: number): Vector2; + /** Returns a random color between the two passed in colors, combine components if linear + * @param {Color} [colorA=new Color(1,1,1,1)] + * @param {Color} [colorB=new Color(0,0,0,1)] + * @param {Boolean} [linear] + * @return {Color} + * @memberof Random */ + export function randColor(cA?: Color, cB?: Color, linear?: boolean): Color; + /** Seed used by the randSeeded function + * @memberof Random */ + export let randSeed: number; + /** Set seed used by the randSeeded function, should not be 0 + * @param {Number} seed + * @memberof Random */ + export function setRandSeed(seed: number): number; + /** Returns a seeded random value between the two values passed in using randSeed + * @param {Number} [valueA=1] + * @param {Number} [valueB=0] + * @return {Number} + * @memberof Random */ + export function randSeeded(a?: number, b?: number): number; + /** + * 2D Vector object with vector math library + *
- Functions do not change this so they can be chained together + * @example + * let a = new Vector2(2, 3); // vector with coordinates (2, 3) + * let b = new Vector2; // vector with coordinates (0, 0) + * let c = vec2(4, 2); // use the vec2 function to make a Vector2 + * let d = a.add(b).scale(5); // operators can be chained */ - drawText(text: string, pos: Vector2, scale?: number, center?: boolean): void; -} -/** Returns true if fullscreen mode is active - * @return {Boolean} - * @memberof Draw */ -declare function isFullscreen(): boolean; -/** Returns true if device key is down - * @param {Number} key - * @param {Number} [device=0] - * @return {Boolean} - * @memberof Input */ -declare function keyIsDown(key: number, device?: number): boolean; -/** Returns true if device key was pressed this frame - * @param {Number} key - * @param {Number} [device=0] - * @return {Boolean} - * @memberof Input */ -declare function keyWasPressed(key: number, device?: number): boolean; -/** Returns true if device key was released this frame - * @param {Number} key - * @param {Number} [device=0] - * @return {Boolean} - * @memberof Input */ -declare function keyWasReleased(key: number, device?: number): boolean; -/** Clears all input - * @memberof Input */ -declare function clearInput(): any[][]; -/** Returns true if device key is down - * @param {Number} key - * @param {Number} [device=0] - * @return {Boolean} - * @memberof Input */ -declare function mouseIsDown(key: number, device?: number): boolean; -/** Returns true if device key was pressed this frame - * @param {Number} key - * @param {Number} [device=0] - * @return {Boolean} - * @memberof Input */ -declare function mouseWasPressed(key: number, device?: number): boolean; -/** Returns true if device key was released this frame - * @param {Number} key - * @param {Number} [device=0] - * @return {Boolean} - * @memberof Input */ -declare function mouseWasReleased(key: number, device?: number): boolean; -/** Mouse pos in world space - * @type {Vector2} - * @memberof Input */ -declare let mousePos: Vector2; -/** Mouse pos in screen space - * @type {Vector2} - * @memberof Input */ -declare let mousePosScreen: Vector2; -/** Mouse wheel delta this frame - * @memberof Input */ -declare let mouseWheel: number; -/** Returns true if user is using gamepad (has more recently pressed a gamepad button) - * @memberof Input */ -declare let isUsingGamepad: number; -/** Prevents input continuing to the default browser handling (false by default) - * @memberof Input */ -declare let preventDefaultInput: number; -/** Returns true if gamepad button is down - * @param {Number} button - * @param {Number} [gamepad=0] - * @return {Boolean} - * @memberof Input */ -declare function gamepadIsDown(button: number, gamepad?: number): boolean; -/** Returns true if gamepad button was pressed - * @param {Number} button - * @param {Number} [gamepad=0] - * @return {Boolean} - * @memberof Input */ -declare function gamepadWasPressed(button: number, gamepad?: number): boolean; -/** Returns true if gamepad button was released - * @param {Number} button - * @param {Number} [gamepad=0] - * @return {Boolean} - * @memberof Input */ -declare function gamepadWasReleased(button: number, gamepad?: number): boolean; -/** Returns gamepad stick value - * @param {Number} stick - * @param {Number} [gamepad=0] - * @return {Vector2} - * @memberof Input */ -declare function gamepadStick(stick: number, gamepad?: number): Vector2; -declare let inputData: any[][]; -declare function remapKeyCode(c: any): any; -declare function mouseToScreen(mousePos: any): Vector2; -declare const stickData: any[]; -/** Pulse the vibration hardware if it exists - * @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array - * @memberof Input */ -declare function vibrate(pattern?: number): boolean; -/** Cancel any ongoing vibration - * @memberof Input */ -declare function vibrateStop(): boolean; -/** True if a touch device has been detected - * @const {boolean} - * @memberof Input */ -declare const isTouchDevice: boolean; -declare let touchGamepadTimer: Timer; -declare let touchGamepadButtons: any; -declare let touchGamepadStick: any; -/** - * Sound Object - Stores a zzfx sound for later use and can be played positionally - *
- *
Create sounds using the ZzFX Sound Designer. - * @example - * // create a sound - * const sound_example = new Sound([.5,.5]); - * - * // play the sound - * sound_example.play(); - */ -declare class Sound { - /** Create a sound object and cache the zzfx samples for later use - * @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5] - * @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away - * @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off + export class Vector2 { + /** Create a 2D vector with the x and y passed in, can also be created with vec2() + * @param {Number} [x=0] - X axis location + * @param {Number} [y=0] - Y axis location */ + constructor(x?: number, y?: number); + /** @property {Number} - X axis location */ + x: number; + /** @property {Number} - Y axis location */ + y: number; + /** Returns a new vector that is a copy of this + * @return {Vector2} */ + copy(): Vector2; + /** Returns a copy of this vector plus the vector passed in + * @param {Vector2} vector + * @return {Vector2} */ + add(v: any): Vector2; + /** Returns a copy of this vector minus the vector passed in + * @param {Vector2} vector + * @return {Vector2} */ + subtract(v: any): Vector2; + /** Returns a copy of this vector times the vector passed in + * @param {Vector2} vector + * @return {Vector2} */ + multiply(v: any): Vector2; + /** Returns a copy of this vector divided by the vector passed in + * @param {Vector2} vector + * @return {Vector2} */ + divide(v: any): Vector2; + /** Returns a copy of this vector scaled by the vector passed in + * @param {Number} scale + * @return {Vector2} */ + scale(s: any): Vector2; + /** Returns the length of this vector + * @return {Number} */ + length(): number; + /** Returns the length of this vector squared + * @return {Number} */ + lengthSquared(): number; + /** Returns the distance from this vector to vector passed in + * @param {Vector2} vector + * @return {Number} */ + distance(v: any): number; + /** Returns the distance squared from this vector to vector passed in + * @param {Vector2} vector + * @return {Number} */ + distanceSquared(v: any): number; + /** Returns a new vector in same direction as this one with the length passed in + * @param {Number} [length=1] + * @return {Vector2} */ + normalize(length?: number): Vector2; + /** Returns a new vector clamped to length passed in + * @param {Number} [length=1] + * @return {Vector2} */ + clampLength(length?: number): Vector2; + /** Returns the dot product of this and the vector passed in + * @param {Vector2} vector + * @return {Number} */ + dot(v: any): number; + /** Returns the cross product of this and the vector passed in + * @param {Vector2} vector + * @return {Number} */ + cross(v: any): number; + /** Returns the angle of this vector, up is angle 0 + * @return {Number} */ + angle(): number; + /** Sets this vector with angle and length passed in + * @param {Number} [angle=0] + * @param {Number} [length=1] */ + setAngle(a?: number, length?: number): Vector2; + /** Returns copy of this vector rotated by the angle passed in + * @param {Number} angle + * @return {Vector2} */ + rotate(a: any): Vector2; + /** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3) + * @return {Number} */ + direction(): number; + /** Returns a copy of this vector that has been inverted + * @return {Vector2} */ + invert(): Vector2; + /** Returns a copy of this vector with each axis floored + * @return {Vector2} */ + floor(): Vector2; + /** Returns the area this vector covers as a rectangle + * @return {Number} */ + area(): number; + /** Returns a new vector that is p percent between this and the vector passed in + * @param {Vector2} vector + * @param {Number} percent + * @return {Vector2} */ + lerp(v: any, p: any): Vector2; + /** Returns true if this vector is within the bounds of an array size passed in + * @param {Vector2} arraySize + * @return {Boolean} */ + arrayCheck(arraySize: Vector2): boolean; + /** Returns this vector expressed as a string + * @param {float} digits - precision to display + * @return {String} */ + toString(digits?: float): string; + } + /** + * Color object (red, green, blue, alpha) with some helpful functions + * @example + * let a = new Color; // white + * let b = new Color(1, 0, 0); // red + * let c = new Color(0, 0, 0, 0); // transparent black */ - constructor(zzfxSound: any[], range?: number, taper?: number); - /** @property {Number} - World space max range of sound, will not play if camera is farther away */ - range: number; - /** @property {Number} - At what percentage of range should it start tapering off */ - taper: number; - randomness: any; - cachedSamples: number[]; - /** Play the sound - * @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null - * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade) - * @param {Number} [pitch=1] - How much to scale pitch by (also adjusted by this.randomness) - * @param {Number} [randomnessScale=1] - How much to scale randomness - * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later + export class Color { + /** Create a color with the components passed in, white by default + * @param {Number} [red=1] + * @param {Number} [green=1] + * @param {Number} [blue=1] + * @param {Number} [alpha=1] */ + constructor(r?: number, g?: number, b?: number, a?: number); + /** @property {Number} - Red */ + r: number; + /** @property {Number} - Green */ + g: number; + /** @property {Number} - Blue */ + b: number; + /** @property {Number} - Alpha */ + a: number; + /** Returns a new color that is a copy of this + * @return {Color} */ + copy(): Color; + /** Returns a copy of this color plus the color passed in + * @param {Color} color + * @return {Color} */ + add(c: any): Color; + /** Returns a copy of this color minus the color passed in + * @param {Color} color + * @return {Color} */ + subtract(c: any): Color; + /** Returns a copy of this color times the color passed in + * @param {Color} color + * @return {Color} */ + multiply(c: any): Color; + /** Returns a copy of this color divided by the color passed in + * @param {Color} color + * @return {Color} */ + divide(c: any): Color; + /** Returns a copy of this color scaled by the value passed in, alpha can be scaled separately + * @param {Number} scale + * @param {Number} [alphaScale=scale] + * @return {Color} */ + scale(s: any, a?: any): Color; + /** Returns a copy of this color clamped to the valid range between 0 and 1 + * @return {Color} */ + clamp(): Color; + /** Returns a new color that is p percent between this and the color passed in + * @param {Color} color + * @param {Number} percent + * @return {Color} */ + lerp(c: any, p: any): Color; + /** Sets this color given a hue, saturation, lightness, and alpha + * @param {Number} [hue=0] + * @param {Number} [saturation=0] + * @param {Number} [lightness=1] + * @param {Number} [alpha=1] + * @return {Color} */ + setHSLA(h?: number, s?: number, l?: number, a?: number): Color; + /** Returns this color expressed in hsla format + * @return {Array} */ + getHSLA(): any[]; + /** Returns a new color that has each component randomly adjusted + * @param {Number} [amount=.05] + * @param {Number} [alphaAmount=0] + * @return {Color} */ + mutate(amount?: number, alphaAmount?: number): Color; + /** Returns this color expressed as a hex color code + * @param {Boolean} [useAlpha=1] - if alpha should be included in result + * @return {String} */ + toString(useAlpha?: boolean): string; + /** Set this color from a hex code + * @param {String} hex - html hex code + * @return {Color} */ + setHex(hex: string): Color; + /** Returns this color expressed as 32 bit RGBA value + * @return {Number} */ + rgbaInt(): number; + } + /** + * Timer object tracks how long has passed since it was set + * @example + * let a = new Timer; // creates a timer that is not set + * a.set(3); // sets the timer to 3 seconds + * + * let b = new Timer(1); // creates a timer with 1 second left + * b.unset(); // unsets the timer */ - play(pos?: Vector2, volume?: number, pitch?: number, randomnessScale?: number): AudioBufferSourceNode; - /** Play the sound as a note with a semitone offset - * @param {Number} semitoneOffset - How many semitones to offset pitch - * @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null - * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade) - * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later + export class Timer { + /** Create a timer object set time passed in + * @param {Number} [timeLeft] - How much time left before the timer elapses in seconds */ + constructor(timeLeft?: number); + time: number; + setTime: number; + /** Set the timer with seconds passed in + * @param {Number} [timeLeft=0] - How much time left before the timer is elapsed in seconds */ + set(timeLeft?: number): void; + /** Unset the timer */ + unset(): void; + /** Returns true if set + * @return {Boolean} */ + isSet(): boolean; + /** Returns true if set and has not elapsed + * @return {Boolean} */ + active(): boolean; + /** Returns true if set and elapsed + * @return {Boolean} */ + elapsed(): boolean; + /** Get how long since elapsed, returns 0 if not set (returns negative if currently active) + * @return {Number} */ + get(): number; + /** Get percentage elapsed based on time it was set to, returns 0 if not set + * @return {Number} */ + getPercent(): number; + /** Returns this timer expressed as a string + * @return {String} */ + toString(): string; + /** Get how long since elapsed, returns 0 if not set (returns negative if currently active) + * @return {Number} */ + valueOf(): number; + } + /** + * Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar + * @param {Number} [x=0] + * @param {Number} [y=0] + * @return {Vector2} + * @example + * let a = vec2(0, 1); // vector with coordinates (0, 1) + * let b = vec2(a); // copy a into b + * a = vec2(5); // set a to (5, 5) + * b = vec2(); // set b to (0, 0) + * @memberof Utilities */ - playNote(semitoneOffset: number, pos?: Vector2, volume?: number): AudioBufferSourceNode; -} -/** - * Music Object - Stores a zzfx music track for later use - *
- *
Create music with the ZzFXM tracker. - * @example - * // create some music - * const music_example = new Music( - * [ - * [ // instruments - * [,0,400] // simple note - * ], - * [ // patterns - * [ // pattern 1 - * [ // channel 0 - * 0, -1, // instrument 0, left speaker - * 1, 0, 9, 1 // channel notes - * ], - * [ // channel 1 - * 0, 1, // instrument 1, right speaker - * 0, 12, 17, -1 // channel notes - * ] - * ], - * ], - * [0, 0, 0, 0], // sequence, play pattern 0 four times - * 90 // BPM - * ]); - * - * // play the music - * music_example.play(); - */ -declare class Music { - /** Create a music object and cache the zzfx music samples for later use - * @param {Array} zzfxMusic - Array of zzfx music parameters + export function vec2(x?: number, y?: number): Vector2; + /** + * Create a color object with RGBA values + * @param {Number} [r=1] + * @param {Number} [g=1] + * @param {Number} [b=1] + * @param {Number} [a=1] + * @return {Color} + * @memberof Utilities */ - constructor(zzfxMusic: any[]); - cachedSamples: any[]; - /** Play the music - * @param {Number} [volume=1] - How much to scale volume by - * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end - * @return {AudioBufferSourceNode} - The audio node, can be used to stop sound later + export function colorRGBA(r?: number, g?: number, b?: number, a?: number): Color; + /** + * Create a color object with HSLA values + * @param {Number} [h=0] + * @param {Number} [s=0] + * @param {Number} [l=1] + * @param {Number} [a=1] + * @return {Color} + * @memberof Utilities */ - play(volume?: number, loop?: boolean): AudioBufferSourceNode; -} -/** Stop all queued speech - * @memberof Audio */ -declare function speakStop(): void; -/** Get frequency of a note on a musical scale - * @param {Number} semitoneOffset - How many semitones away from the root note - * @param {Number} [rootNoteFrequency=220] - Frequency at semitone offset 0 - * @return {Number} - The frequency of the note - * @memberof Audio */ -declare function getNoteFrequency(semitoneOffset: number, rootFrequency?: number): number; -/** Audio context used by the engine - * @memberof Audio */ -declare let audioContext: any; -/** Generate and play a ZzFX sound - *
- *
Create sounds using the ZzFX Sound Designer. - * @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5] - * @return {Array} - Array of audio samples - * @memberof Audio */ -declare function zzfx(...zzfxSound: any[]): any[]; -/** Sample rate used for all ZzFX sounds - * @default 44100 - * @memberof Audio */ -declare const zzfxR: 44100; -/** The tile collision layer array, use setTileCollisionData and getTileCollisionData to access - * @memberof TileCollision */ -declare let tileCollision: any[]; -/** Size of the tile collision layer - * @type {Vector2} - * @memberof TileCollision */ -declare let tileCollisionSize: Vector2; -/** Set tile collision data - * @param {Vector2} pos - * @param {Number} [data=0] - * @memberof TileCollision */ -declare function setTileCollisionData(pos: Vector2, data?: number): number; -/** Get tile collision data - * @param {Vector2} pos - * @return {Number} - * @memberof TileCollision */ -declare function getTileCollisionData(pos: Vector2): number; -/** - * Tile layer data object stores info about how to render a tile - * @example - * // create tile layer data with tile index 0 and random orientation and color - * const tileIndex = 0; - * const direction = randInt(4) - * const mirror = randInt(2); - * const color = randColor(); - * const data = new TileLayerData(tileIndex, direction, mirror, color); - */ -declare class TileLayerData { - /** Create a tile layer data object, one for each tile in a TileLayer - * @param {Number} [tile] - The tile to use, untextured if undefined - * @param {Number} [direction=0] - Integer direction of tile, in 90 degree increments - * @param {Boolean} [mirror=0] - If the tile should be mirrored along the x axis - * @param {Color} [color=new Color(1,1,1)] - Color of the tile */ - constructor(tile?: number, direction?: number, mirror?: boolean, color?: Color); - /** @property {Number} - The tile to use, untextured if undefined */ - tile: number; - /** @property {Number} - Integer direction of tile, in 90 degree increments */ - direction: number; - /** @property {Boolean} - If the tile should be mirrored along the x axis */ - mirror: boolean; - /** @property {Color} - Color of the tile */ - color: Color; - /** Set this tile to clear, it will not be rendered */ - clear(): void; -} -/** - * Tile layer object - cached rendering system for tile layers - *
- Each Tile layer is rendered to an off screen canvas - *
- To allow dynamic modifications, layers are rendered using canvas 2d - *
- Some devices like mobile phones are limited to 4k texture resolution - *
- So with 16x16 tiles this limits layers to 256x256 on mobile devices - * @extends EngineObject - * @example - * // create tile collision and visible tile layer - * initTileCollision(vec2(200,100)); - * const tileLayer = new TileLayer(); - */ -declare class TileLayer extends EngineObject { - /** Create a tile layer object - * @param {Vector2} [position=new Vector2()] - World space position - * @param {Vector2} [size=tileCollisionSize] - World space size - * @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels - * @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered - * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered - */ - constructor(pos: any, size?: Vector2, tileSize?: Vector2, scale?: Vector2, renderOrder?: number); - /** @property {HTMLCanvasElement} - The canvas used by this tile layer */ - canvas: HTMLCanvasElement; - /** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */ - context: CanvasRenderingContext2D; - /** @property {Vector2} - How much to scale this layer when rendered */ - scale: Vector2; - data: TileLayerData[]; - /** Set data at a given position in the array - * @param {Vector2} position - Local position in array - * @param {TileLayerData} data - Data to set - * @param {Boolean} [redraw=0] - Force the tile to redraw if true */ - setData(layerPos: any, data: TileLayerData, redraw?: boolean): void; - /** Get data at a given position in the array - * @param {Vector2} layerPos - Local position in array - * @return {TileLayerData} */ - getData(layerPos: Vector2): TileLayerData; - /** Draw all the tile data to an offscreen canvas - * - This may be slow in some browsers - */ - redraw(): void; - /** Call to start the redraw process - * @param {Boolean} [clear=0] - Should it clear the canvas before drawing */ - redrawStart(clear?: boolean): void; - savedRenderSettings: (number | HTMLCanvasElement | CanvasRenderingContext2D | Vector2)[]; - /** Call to end the redraw process */ - redrawEnd(): void; - /** Draw the tile at a given position - * @param {Vector2} layerPos */ - drawTileData(layerPos: Vector2): void; - /** Draw all the tiles in this layer */ - drawAllTileData(): void; - /** Draw directly to the 2D canvas in world space (bipass webgl) - * @param {Vector2} pos - * @param {Vector2} size - * @param {Number} [angle=0] - * @param {Boolean} [mirror=0] - * @param {Function} drawFunction */ - drawCanvas2D(pos: Vector2, size: Vector2, angle?: number, mirror?: boolean, drawFunction: Function): void; - /** Draw a tile directly onto the layer canvas + export function colorHSLA(h?: number, s?: number, l?: number, a?: number): Color; + /** + * LittleJS Object Base Object Class + *
- Base object class used by the engine + *
- Automatically adds self to object list + *
- Will be updated and rendered each frame + *
- Renders as a sprite from a tilesheet by default + *
- Can have color and addtive color applied + *
- 2d Physics and collision system + *
- Sorted by renderOrder + *
- Objects can have children attached + *
- Parents are updated before children, and set child transform + *
- Call destroy() to get rid of objects + *
+ *
The physics system used by objects is simple and fast with some caveats... + *
- Collision uses the axis aligned size, the object's rotation angle is only for rendering + *
- Objects are guaranteed to not intersect tile collision from physics + *
- If an object starts or is moved inside tile collision, it will not collide with that tile + *
- Collision for objects can be set to be solid to block other objects + *
- Objects may get pushed into overlapping other solid objects, if so they will push away + *
- Solid objects are more performance intensive and should be used sparingly + * @example + * // create an engine object, normally you would first extend the class with your own + * const pos = vec2(2,3); + * const object = new EngineObject(pos); + */ + export class EngineObject { + /** Create an engine object and adds it to the list of objects + * @param {Vector2} [position=new Vector2()] - World space position of the object + * @param {Vector2} [size=objectDefaultSize] - World space size of the object + * @param {Number} [tileIndex=-1] - Tile to use to render object (-1 is untextured) + * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels + * @param {Number} [angle=0] - Angle the object is rotated by + * @param {Color} [color] - Color to apply to tile when rendered + * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered + */ + constructor(pos?: Vector2, size?: Vector2, tileIndex?: number, tileSize?: Vector2, angle?: number, color?: Color, renderOrder?: number); + /** @property {Vector2} - World space position of the object */ + pos: Vector2; + /** @property {Vector2} - World space width and height of the object */ + size: Vector2; + /** @property {Number} - Tile to use to render object (-1 is untextured) */ + tileIndex: number; + /** @property {Vector2} - Size of tile in source pixels */ + tileSize: Vector2; + /** @property {Number} - Angle to rotate the object */ + angle: number; + /** @property {Color} - Color to apply when rendered */ + color: Color; + /** @property {Number} [mass=objectDefaultMass] - How heavy the object is, static if 0 */ + mass: number; + /** @property {Number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */ + damping: number; + /** @property {Number} [angleDamping=objectDefaultAngleDamping] - How much to slow down rotation each frame (0-1) */ + angleDamping: number; + /** @property {Number} [elasticity=objectDefaultElasticity] - How bouncy the object is when colliding (0-1) */ + elasticity: number; + /** @property {Number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */ + friction: number; + /** @property {Number} [gravityScale=1] - How much to scale gravity by for this object */ + gravityScale: number; + /** @property {Number} [renderOrder=0] - Objects are sorted by render order */ + renderOrder: number; + /** @property {Vector2} [velocity=new Vector2()] - Velocity of the object */ + velocity: Vector2; + /** @property {Number} [angleVelocity=0] - Angular velocity of the object */ + angleVelocity: number; + spawnTime: number; + children: any[]; + collideTiles: number; + /** Update the object transform and physics, called automatically by engine once each frame */ + update(): void; + groundObject: any; + /** Render the object, draws a tile by default, automatically called each frame, sorted by renderOrder */ + render(): void; + /** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */ + destroy(): void; + destroyed: number; + /** Called to check if a tile collision should be resolved + * @param {Number} tileData - the value of the tile at the position + * @param {Vector2} pos - tile where the collision occured + * @return {Boolean} - true if the collision should be resolved */ + collideWithTile(tileData: number, pos: Vector2): boolean; + /** Called to check if a tile raycast hit + * @param {Number} tileData - the value of the tile at the position + * @param {Vector2} pos - tile where the raycast is + * @return {Boolean} - true if the raycast should hit */ + collideWithTileRaycast(tileData: number, pos: Vector2): boolean; + /** Called to check if a object collision should be resolved + * @param {EngineObject} object - the object to test against + * @return {Boolean} - true if the collision should be resolved + */ + collideWithObject(o: any): boolean; + /** How long since the object was created + * @return {Number} */ + getAliveTime(): number; + /** Apply acceleration to this object (adjust velocity, not affected by mass) + * @param {Vector2} acceleration */ + applyAcceleration(a: any): void; + /** Apply force to this object (adjust velocity, affected by mass) + * @param {Vector2} force */ + applyForce(force: Vector2): void; + /** Get the direction of the mirror + * @return {Number} -1 if this.mirror is true, or 1 if not mirrored */ + getMirrorSign(): number; + /** Attaches a child to this with a given local transform + * @param {EngineObject} child + * @param {Vector2} [localPos=new Vector2] + * @param {Number} [localAngle=0] */ + addChild(child: EngineObject, localPos?: Vector2, localAngle?: number): void; + /** Removes a child from this one + * @param {EngineObject} child */ + removeChild(child: EngineObject): void; + /** Set how this object collides + * @param {Boolean} [collideSolidObjects=1] - Does it collide with solid objects + * @param {Boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers) + * @param {Boolean} [collideTiles=1] - Does it collide with the tile collision */ + setCollision(collideSolidObjects?: boolean, isSolid?: boolean, collideTiles?: boolean): void; + collideSolidObjects: boolean; + isSolid: boolean; + /** Returns string containg info about this object for debugging + * @return {String} */ + toString(): string; + } + /** Tile sheet for batch rendering system + * @type {CanvasImageSource} + * @memberof Draw */ + export const tileImage: CanvasImageSource; + /** The primary 2D canvas visible to the user + * @type {HTMLCanvasElement} + * @memberof Draw */ + export let mainCanvas: HTMLCanvasElement; + /** 2d context for mainCanvas + * @type {CanvasRenderingContext2D} + * @memberof Draw */ + export let mainContext: CanvasRenderingContext2D; + /** A canvas that appears on top of everything the same size as mainCanvas + * @type {HTMLCanvasElement} + * @memberof Draw */ + export let overlayCanvas: HTMLCanvasElement; + /** 2d context for overlayCanvas + * @type {CanvasRenderingContext2D} + * @memberof Draw */ + export let overlayContext: CanvasRenderingContext2D; + /** The size of the main canvas (and other secondary canvases) + * @type {Vector2} + * @memberof Draw */ + export let mainCanvasSize: Vector2; + /** Convert from screen to world space coordinates + * - if calling outside of render, you may need to manually set mainCanvasSize + * @param {Vector2} screenPos + * @return {Vector2} + * @memberof Draw */ + export function screenToWorld(screenPos: Vector2): Vector2; + /** Convert from world to screen space coordinates + * - if calling outside of render, you may need to manually set mainCanvasSize + * @param {Vector2} worldPos + * @return {Vector2} + * @memberof Draw */ + export function worldToScreen(worldPos: Vector2): Vector2; + /** Draw textured tile centered in world space, with color applied if using WebGL + * @param {Vector2} pos - Center of the tile in world space + * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space + * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured + * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels + * @param {Color} [color=new Color(1,1,1)] - Color to modulate with + * @param {Number} [angle=0] - Angle to rotate by + * @param {Boolean} [mirror=0] - If true image is flipped along the Y axis + * @param {Color} [additiveColor=new Color(0,0,0,0)] - Additive color to be applied + * @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering + * @memberof Draw */ + export function drawTile(pos: Vector2, size?: Vector2, tileIndex?: number, tileSize?: Vector2, color?: Color, angle?: number, mirror?: boolean, additiveColor?: Color, useWebGL?: boolean): void; + /** Draw colored rect centered on pos * @param {Vector2} pos * @param {Vector2} [size=new Vector2(1,1)] - * @param {Number} [tileIndex=-1] - * @param {Vector2} [tileSize=tileSizeDefault] * @param {Color} [color=new Color(1,1,1)] * @param {Number} [angle=0] - * @param {Boolean} [mirror=0] */ - drawTile(pos: Vector2, size?: Vector2, tileIndex?: number, tileSize?: Vector2, color?: Color, angle?: number, mirror?: boolean): void; - /** Draw a rectangle directly onto the layer canvas + * @param {Boolean} [useWebGL=glEnable] + * @memberof Draw */ + export function drawRect(pos: Vector2, size?: Vector2, color?: Color, angle?: number, useWebGL?: boolean): void; + /** Draw textured tile centered on pos in screen space + * @param {Vector2} pos - Center of the tile + * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile + * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured + * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels + * @param {Color} [color=new Color] + * @param {Number} [angle=0] + * @param {Boolean} [mirror=0] + * @param {Color} [additiveColor=new Color(0,0,0,0)] + * @param {Boolean} [useWebGL=glEnable] + * @memberof Draw */ + export function drawTileScreenSpace(pos: Vector2, size?: Vector2, tileIndex?: number, tileSize?: Vector2, color?: Color, angle?: number, mirror?: boolean, additiveColor?: Color, useWebGL?: boolean): void; + /** Draw colored rectangle in screen space * @param {Vector2} pos * @param {Vector2} [size=new Vector2(1,1)] * @param {Color} [color=new Color(1,1,1)] - * @param {Number} [angle=0] */ - drawRect(pos: Vector2, size?: Vector2, color?: Color, angle?: number): void; -} -/** - * Particle Emitter - Spawns particles with the given settings - * @extends EngineObject - * @example - * // create a particle emitter - * let pos = vec2(2,3); - * let particleEmiter = new ParticleEmitter - * ( - * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone - * 0, vec2(16), // tileIndex, tileSize - * new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB - * new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB - * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed - * .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate, - * .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder - * ); - */ -declare class ParticleEmitter extends EngineObject { - /** Create a particle system with the given settings - * @param {Vector2} position - World space position of the emitter - * @param {Number} [angle=0] - Angle to emit the particles - * @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect) - * @param {Number} [emitTime=0] - How long to stay alive (0 is forever) - * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0 - * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter - * @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied - * @param {Number} [tileSize=tileSizeDefault] - Tile size for particles - * @param {Color} [colorStartA=new Color(1,1,1)] - Color at start of life 1, randomized between start colors - * @param {Color} [colorStartB=new Color(1,1,1)] - Color at start of life 2, randomized between start colors - * @param {Color} [colorEndA=new Color(1,1,1,0)] - Color at end of life 1, randomized between end colors - * @param {Color} [colorEndB=new Color(1,1,1,0)] - Color at end of life 2, randomized between end colors - * @param {Number} [particleTime=.5] - How long particles live - * @param {Number} [sizeStart=.1] - How big are particles at start - * @param {Number} [sizeEnd=1] - How big are particles at end - * @param {Number} [speed=.1] - How fast are particles when spawned - * @param {Number} [angleSpeed=.05] - How fast are particles rotating - * @param {Number} [damping=1] - How much to dampen particle speed - * @param {Number} [angleDamping=1] - How much to dampen particle angular speed - * @param {Number} [gravityScale=0] - How much does gravity effect particles - * @param {Number} [particleConeAngle=PI] - Cone for start particle angle - * @param {Number} [fadeRate=.1] - How quick to fade in particles at start/end in percent of life - * @param {Number} [randomness=.2] - Apply extra randomness percent - * @param {Boolean} [collideTiles=0] - Do particles collide against tiles - * @param {Boolean} [additive=0] - Should particles use addtive blend - * @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component - * @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default) - * @param {Boolean} [localSpace=0] - Should it be in local space of emitter (world space is default) + * @param {Number} [angle=0] + * @param {Boolean} [useWebGL=glEnable] + * @memberof Draw */ + export function drawRectScreenSpace(pos: Vector2, size?: Vector2, color?: Color, angle?: number, useWebGL?: boolean): void; + /** Draw colored line between two points + * @param {Vector2} posA + * @param {Vector2} posB + * @param {Number} [thickness=.1] + * @param {Color} [color=new Color(1,1,1)] + * @param {Boolean} [useWebGL=glEnable] + * @memberof Draw */ + export function drawLine(posA: Vector2, posB: Vector2, thickness?: number, color?: Color, useWebGL?: boolean): void; + /** Draw directly to a 2d canvas context in world space + * @param {Vector2} pos + * @param {Vector2} size + * @param {Number} angle + * @param {Boolean} mirror + * @param {Function} drawFunction + * @param {CanvasRenderingContext2D} [context=mainContext] + * @memberof Draw */ + export function drawCanvas2D(pos: Vector2, size: Vector2, angle: number, mirror: boolean, drawFunction: Function, context?: CanvasRenderingContext2D): void; + /** Enable normal or additive blend mode + * @param {Boolean} [additive=0] + * @param {Boolean} [useWebGL=glEnable] + * @memberof Draw */ + export function setBlendMode(additive?: boolean, useWebGL?: boolean): void; + /** Draw text on overlay canvas in screen space + * Automatically splits new lines into rows + * @param {String} text + * @param {Vector2} pos + * @param {Number} [size=1] + * @param {Color} [color=new Color(1,1,1)] + * @param {Number} [lineWidth=0] + * @param {Color} [lineColor=new Color(0,0,0)] + * @param {String} [textAlign='center'] + * @memberof Draw */ + export function drawTextScreen(text: string, pos: Vector2, size?: number, color?: Color, lineWidth?: number, lineColor?: Color, textAlign?: string, font?: string, context?: CanvasRenderingContext2D): void; + /** Draw text on overlay canvas in world space + * Automatically splits new lines into rows + * @param {String} text + * @param {Vector2} pos + * @param {Number} [size=1] + * @param {Color} [color=new Color(1,1,1)] + * @param {Number} [lineWidth=0] + * @param {Color} [lineColor=new Color(0,0,0)] + * @param {String} [textAlign='center'] + * @memberof Draw */ + export function drawText(text: string, pos: Vector2, size?: number, color?: Color, lineWidth?: number, lineColor?: Color, textAlign?: string, font: any): void; + /** + * Font Image Object - Draw text on a 2D canvas by using characters in an image + *
- 96 characters (from space to tilde) are stored in an image + *
- Uses a default 8x8 font if none is supplied + *
- You can also use fonts from the main tile sheet + * @example + * // use built in font + * const font = new ImageFont; + * + * // draw text + * font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50)); */ - constructor(pos: any, angle?: number, emitSize?: number, emitTime?: number, emitRate?: number, emitConeAngle?: number, tileIndex?: number, tileSize?: number, colorStartA?: Color, colorStartB?: Color, colorEndA?: Color, colorEndB?: Color, particleTime?: number, sizeStart?: number, sizeEnd?: number, speed?: number, angleSpeed?: number, damping?: number, angleDamping?: number, gravityScale?: number, particleConeAngle?: number, fadeRate?: number, randomness?: number, collideTiles?: boolean, additive?: boolean, randomColorLinear?: boolean, renderOrder?: number, localSpace?: boolean); - /** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */ - emitSize: number; - /** @property {Number} - How long to stay alive (0 is forever) */ - emitTime: number; - /** @property {Number} - How many particles per second to spawn, does not emit if 0 */ - emitRate: number; - /** @property {Number} - Local angle to apply velocity to particles from emitter */ - emitConeAngle: number; - /** @property {Color} - Color at start of life 1, randomized between start colors */ - colorStartA: Color; - /** @property {Color} - Color at start of life 2, randomized between start colors */ - colorStartB: Color; - /** @property {Color} - Color at end of life 1, randomized between end colors */ - colorEndA: Color; - /** @property {Color} - Color at end of life 2, randomized between end colors */ - colorEndB: Color; - /** @property {Boolean} - Should color be randomized linearly or across each component */ - randomColorLinear: boolean; - /** @property {Number} - How long particles live */ - particleTime: number; - /** @property {Number} - How big are particles at start */ - sizeStart: number; - /** @property {Number} - How big are particles at end */ - sizeEnd: number; - /** @property {Number} - How fast are particles when spawned */ - speed: number; - /** @property {Number} - How fast are particles rotating */ - angleSpeed: number; - /** @property {Number} - Cone for start particle angle */ - particleConeAngle: number; - /** @property {Number} - How quick to fade in particles at start/end in percent of life */ - fadeRate: number; - /** @property {Number} - Apply extra randomness percent */ - randomness: number; - /** @property {Number} - Do particles collide against tiles */ - collideTiles: boolean; - /** @property {Number} - Should particles use addtive blend */ - additive: boolean; - /** @property {Boolean} - Should it be in local space of emitter */ - localSpace: boolean; - /** @property {Number} - If set the partile is drawn as a trail, stretched in the drection of velocity */ - trailScale: number; - emitTimeBuffer: number; - /** Spawn one particle - * @return {Particle} */ - emitParticle(): Particle; -} -/** - * Particle Object - Created automatically by Particle Emitters - * @extends EngineObject - */ -declare class Particle extends EngineObject { + export let engineFontImage: any; + export class FontImage { + /** Create an image font + * @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used + * @param {Vector2} [tileSize=vec2(8)] - Size of the font source tiles + * @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters + * @param {Number} [startTileIndex=0] - Tile index in image where font starts + * @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to + */ + constructor(image?: HTMLImageElement, tileSize?: Vector2, paddingSize?: Vector2, startTileIndex?: number, context?: CanvasRenderingContext2D); + image: any; + tileSize: Vector2; + paddingSize: Vector2; + startTileIndex: number; + context: CanvasRenderingContext2D; + /** Draw text in screen space using the image font + * @param {String} text + * @param {Vector2} pos + * @param {Number} [scale=4] + * @param {Boolean} [center] + */ + drawTextScreen(text: string, pos: Vector2, scale?: number, center?: boolean): void; + /** Draw text in world space using the image font + * @param {String} text + * @param {Vector2} pos + * @param {Number} [scale=.25] + * @param {Boolean} [center] + */ + drawText(text: string, pos: Vector2, scale?: number, center?: boolean): void; + } + /** Returns true if fullscreen mode is active + * @return {Boolean} + * @memberof Draw */ + export function isFullscreen(): boolean; + /** Toggle fullsceen mode + * @memberof Draw */ + export function toggleFullscreen(): void; + /** Returns true if device key is down + * @param {Number} key + * @param {Number} [device=0] + * @return {Boolean} + * @memberof Input */ + export function keyIsDown(key: number, device?: number): boolean; + /** Returns true if device key was pressed this frame + * @param {Number} key + * @param {Number} [device=0] + * @return {Boolean} + * @memberof Input */ + export function keyWasPressed(key: number, device?: number): boolean; + /** Returns true if device key was released this frame + * @param {Number} key + * @param {Number} [device=0] + * @return {Boolean} + * @memberof Input */ + export function keyWasReleased(key: number, device?: number): boolean; + /** Clears all input + * @memberof Input */ + export function clearInput(): any[][]; + /** Returns true if device key is down + * @param {Number} key + * @param {Number} [device=0] + * @return {Boolean} + * @memberof Input */ + export function mouseIsDown(key: number, device?: number): boolean; + /** Returns true if device key was pressed this frame + * @param {Number} key + * @param {Number} [device=0] + * @return {Boolean} + * @memberof Input */ + export function mouseWasPressed(key: number, device?: number): boolean; + /** Returns true if device key was released this frame + * @param {Number} key + * @param {Number} [device=0] + * @return {Boolean} + * @memberof Input */ + export function mouseWasReleased(key: number, device?: number): boolean; + /** Mouse pos in world space + * @type {Vector2} + * @memberof Input */ + export let mousePos: Vector2; + /** Mouse pos in screen space + * @type {Vector2} + * @memberof Input */ + export let mousePosScreen: Vector2; + /** Mouse wheel delta this frame + * @memberof Input */ + export let mouseWheel: number; + /** Returns true if user is using gamepad (has more recently pressed a gamepad button) + * @memberof Input */ + export let isUsingGamepad: number; + /** Prevents input continuing to the default browser handling (false by default) + * @memberof Input */ + export let preventDefaultInput: number; + /** Returns true if gamepad button is down + * @param {Number} button + * @param {Number} [gamepad=0] + * @return {Boolean} + * @memberof Input */ + export function gamepadIsDown(button: number, gamepad?: number): boolean; + /** Returns true if gamepad button was pressed + * @param {Number} button + * @param {Number} [gamepad=0] + * @return {Boolean} + * @memberof Input */ + export function gamepadWasPressed(button: number, gamepad?: number): boolean; + /** Returns true if gamepad button was released + * @param {Number} button + * @param {Number} [gamepad=0] + * @return {Boolean} + * @memberof Input */ + export function gamepadWasReleased(button: number, gamepad?: number): boolean; + /** Returns gamepad stick value + * @param {Number} stick + * @param {Number} [gamepad=0] + * @return {Vector2} + * @memberof Input */ + export function gamepadStick(stick: number, gamepad?: number): Vector2; + export function mouseToScreen(mousePos: any): Vector2; + export function gamepadsUpdate(): void; + /** Pulse the vibration hardware if it exists + * @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array + * @memberof Input */ + export function vibrate(pattern?: number): boolean; + /** Cancel any ongoing vibration + * @memberof Input */ + export function vibrateStop(): boolean; + /** True if a touch device has been detected + * @const {Boolean} + * @memberof Input */ + export const isTouchDevice: boolean; /** - * Create a particle with the given settings - * @param {Vector2} position - World space position of the particle - * @param {Number} [tileIndex=-1] - Tile to use to render, untextured if -1 - * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels - * @param {Number} [angle=0] - Angle to rotate the particle + * Sound Object - Stores a zzfx sound for later use and can be played positionally + *
+ *
Create sounds using the ZzFX Sound Designer. + * @example + * // create a sound + * const sound_example = new Sound([.5,.5]); + * + * // play the sound + * sound_example.play(); */ - constructor(pos: any, tileIndex?: number, tileSize?: Vector2, angle?: number); -} -/** List of all medals - * @memberof Medals */ -declare const medals: any[]; -declare let medalsDisplayQueue: any[]; -declare let medalsSaveName: any; -declare let medalsDisplayTimeLast: any; -/** - * Medal Object - Tracks an unlockable medal - * @example - * // create a medal - * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️'); - * - * // initialize medals - * medalsInit('Example Game'); - * - * // unlock the medal - * medal_example.unlock(); - */ -declare class Medal { - /** Create an medal object and adds it to the list of medals - * @param {Number} id - The unique identifier of the medal - * @param {String} name - Name of the medal - * @param {String} [description] - Description of the medal - * @param {String} [icon='🏆'] - Icon for the medal - * @param {String} [src] - Image location for the medal + export class Sound { + /** Create a sound object and cache the zzfx samples for later use + * @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5] + * @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away + * @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off + */ + constructor(zzfxSound: any[], range?: number, taper?: number); + /** @property {Number} - World space max range of sound, will not play if camera is farther away */ + range: number; + /** @property {Number} - At what percentage of range should it start tapering off */ + taper: number; + randomness: any; + cachedSamples: number[]; + /** Play the sound + * @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null + * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade) + * @param {Number} [pitch=1] - How much to scale pitch by (also adjusted by this.randomness) + * @param {Number} [randomnessScale=1] - How much to scale randomness + * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later + */ + play(pos?: Vector2, volume?: number, pitch?: number, randomnessScale?: number): AudioBufferSourceNode; + /** Play the sound as a note with a semitone offset + * @param {Number} semitoneOffset - How many semitones to offset pitch + * @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null + * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade) + * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later + */ + playNote(semitoneOffset: number, pos?: Vector2, volume?: number): AudioBufferSourceNode; + } + /** + * Music Object - Stores a zzfx music track for later use + *
+ *
Create music with the ZzFXM tracker. + * @example + * // create some music + * const music_example = new Music( + * [ + * [ // instruments + * [,0,400] // simple note + * ], + * [ // patterns + * [ // pattern 1 + * [ // channel 0 + * 0, -1, // instrument 0, left speaker + * 1, 0, 9, 1 // channel notes + * ], + * [ // channel 1 + * 0, 1, // instrument 1, right speaker + * 0, 12, 17, -1 // channel notes + * ] + * ], + * ], + * [0, 0, 0, 0], // sequence, play pattern 0 four times + * 90 // BPM + * ]); + * + * // play the music + * music_example.play(); + */ + export class Music { + /** Create a music object and cache the zzfx music samples for later use + * @param {Array} zzfxMusic - Array of zzfx music parameters + */ + constructor(zzfxMusic: any[]); + cachedSamples: any[]; + /** Play the music + * @param {Number} [volume=1] - How much to scale volume by + * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end + * @return {AudioBufferSourceNode} - The audio node, can be used to stop sound later + */ + play(volume?: number, loop?: boolean): AudioBufferSourceNode; + source: number | AudioBufferSourceNode; + /** Stop the music */ + stop(): void; + /** Check if music is playing + * @return {Boolean} + */ + isPlaying(): boolean; + } + /** Play an mp3 or wav audio from a local file or url + * @param {String} url - Location of sound file to play + * @param {Number} [volume=1] - How much to scale volume by + * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end + * @return {HTMLAudioElement} - The audio element for this sound + * @memberof Audio */ + export function playAudioFile(url: string, volume?: number, loop?: boolean): HTMLAudioElement; + /** Speak text with passed in settings + * @param {String} text - The text to speak + * @param {String} [language] - The language/accent to use (examples: en, it, ru, ja, zh) + * @param {Number} [volume=1] - How much to scale volume by + * @param {Number} [rate=1] - How quickly to speak + * @param {Number} [pitch=1] - How much to change the pitch by + * @return {SpeechSynthesisUtterance} - The utterance that was spoken + * @memberof Audio */ + export function speak(text: string, language?: string, volume?: number, rate?: number, pitch?: number): SpeechSynthesisUtterance; + /** Stop all queued speech + * @memberof Audio */ + export function speakStop(): void; + /** Get frequency of a note on a musical scale + * @param {Number} semitoneOffset - How many semitones away from the root note + * @param {Number} [rootNoteFrequency=220] - Frequency at semitone offset 0 + * @return {Number} - The frequency of the note + * @memberof Audio */ + export function getNoteFrequency(semitoneOffset: number, rootFrequency?: number): number; + /** Audio context used by the engine + * @memberof Audio */ + export let audioContext: any; + /** Play cached audio samples with given settings + * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback) + * @param {Number} [volume=1] - How much to scale volume by + * @param {Number} [rate=1] - The playback rate to use + * @param {Number} [pan=0] - How much to apply stereo panning + * @param {Boolean} [loop=0] - True if the sound should loop when it reaches the end + * @return {AudioBufferSourceNode} - The audio node of the sound played + * @memberof Audio */ + export function playSamples(sampleChannels: any[], volume?: number, rate?: number, pan?: number, loop?: boolean): AudioBufferSourceNode; + /** Generate and play a ZzFX sound + *
+ *
Create sounds using the ZzFX Sound Designer. + * @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5] + * @return {Array} - Array of audio samples + * @memberof Audio */ + export function zzfx(...zzfxSound: any[]): any[]; + /** The tile collision layer array, use setTileCollisionData and getTileCollisionData to access + * @memberof TileCollision */ + export let tileCollision: any[]; + /** Size of the tile collision layer + * @type {Vector2} + * @memberof TileCollision */ + export let tileCollisionSize: Vector2; + /** Clear and initialize tile collision + * @param {Vector2} size + * @memberof TileCollision */ + export function initTileCollision(size: Vector2): void; + /** Set tile collision data + * @param {Vector2} pos + * @param {Number} [data=0] + * @memberof TileCollision */ + export function setTileCollisionData(pos: Vector2, data?: number): number; + /** Get tile collision data + * @param {Vector2} pos + * @return {Number} + * @memberof TileCollision */ + export function getTileCollisionData(pos: Vector2): number; + /** Check if collision with another object should occur + * @param {Vector2} pos + * @param {Vector2} [size=new Vector2(1,1)] + * @param {EngineObject} [object] + * @return {Boolean} + * @memberof TileCollision */ + export function tileCollisionTest(pos: Vector2, size?: Vector2, object?: EngineObject): boolean; + /** Return the center of tile if any that is hit (this does not return the exact hit point) + * @param {Vector2} posStart + * @param {Vector2} posEnd + * @param {EngineObject} [object] + * @return {Vector2} + * @memberof TileCollision */ + export function tileCollisionRaycast(posStart: Vector2, posEnd: Vector2, object?: EngineObject): Vector2; + /** + * Tile layer data object stores info about how to render a tile + * @example + * // create tile layer data with tile index 0 and random orientation and color + * const tileIndex = 0; + * const direction = randInt(4) + * const mirror = randInt(2); + * const color = randColor(); + * const data = new TileLayerData(tileIndex, direction, mirror, color); + */ + export class TileLayerData { + /** Create a tile layer data object, one for each tile in a TileLayer + * @param {Number} [tile] - The tile to use, untextured if undefined + * @param {Number} [direction=0] - Integer direction of tile, in 90 degree increments + * @param {Boolean} [mirror=0] - If the tile should be mirrored along the x axis + * @param {Color} [color=new Color(1,1,1)] - Color of the tile */ + constructor(tile?: number, direction?: number, mirror?: boolean, color?: Color); + /** @property {Number} - The tile to use, untextured if undefined */ + tile: number; + /** @property {Number} - Integer direction of tile, in 90 degree increments */ + direction: number; + /** @property {Boolean} - If the tile should be mirrored along the x axis */ + mirror: boolean; + /** @property {Color} - Color of the tile */ + color: Color; + /** Set this tile to clear, it will not be rendered */ + clear(): void; + } + /** + * Tile layer object - cached rendering system for tile layers + *
- Each Tile layer is rendered to an off screen canvas + *
- To allow dynamic modifications, layers are rendered using canvas 2d + *
- Some devices like mobile phones are limited to 4k texture resolution + *
- So with 16x16 tiles this limits layers to 256x256 on mobile devices + * @extends EngineObject + * @example + * // create tile collision and visible tile layer + * initTileCollision(vec2(200,100)); + * const tileLayer = new TileLayer(); */ - constructor(id: number, name: string, description?: string, icon?: string, src?: string); - id: number; - name: string; - description: string; - icon: string; - image: HTMLImageElement; - /** Unlocks a medal if not already unlocked */ - unlock(): void; - unlocked: number; - /** Render a medal - * @param {Number} [hidePercent=0] - How much to slide the medal off screen + export class TileLayer extends EngineObject { + /** Create a tile layer object + * @param {Vector2} [position=new Vector2()] - World space position + * @param {Vector2} [size=tileCollisionSize] - World space size + * @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels + * @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered + * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered + */ + constructor(pos: any, size?: Vector2, tileSize?: Vector2, scale?: Vector2, renderOrder?: number); + /** @property {HTMLCanvasElement} - The canvas used by this tile layer */ + canvas: HTMLCanvasElement; + /** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */ + context: CanvasRenderingContext2D; + /** @property {Vector2} - How much to scale this layer when rendered */ + scale: Vector2; + data: TileLayerData[]; + /** Set data at a given position in the array + * @param {Vector2} position - Local position in array + * @param {TileLayerData} data - Data to set + * @param {Boolean} [redraw=0] - Force the tile to redraw if true */ + setData(layerPos: any, data: TileLayerData, redraw?: boolean): void; + /** Get data at a given position in the array + * @param {Vector2} layerPos - Local position in array + * @return {TileLayerData} */ + getData(layerPos: Vector2): TileLayerData; + /** Draw all the tile data to an offscreen canvas + * - This may be slow in some browsers + */ + redraw(): void; + /** Call to start the redraw process + * @param {Boolean} [clear=0] - Should it clear the canvas before drawing */ + redrawStart(clear?: boolean): void; + savedRenderSettings: (number | HTMLCanvasElement | CanvasRenderingContext2D | Vector2)[]; + /** Call to end the redraw process */ + redrawEnd(): void; + /** Draw the tile at a given position + * @param {Vector2} layerPos */ + drawTileData(layerPos: Vector2): void; + /** Draw all the tiles in this layer */ + drawAllTileData(): void; + /** Draw directly to the 2D canvas in world space (bipass webgl) + * @param {Vector2} pos + * @param {Vector2} size + * @param {Number} [angle=0] + * @param {Boolean} [mirror=0] + * @param {Function} drawFunction */ + drawCanvas2D(pos: Vector2, size: Vector2, angle?: number, mirror?: boolean, drawFunction: Function): void; + /** Draw a tile directly onto the layer canvas + * @param {Vector2} pos + * @param {Vector2} [size=new Vector2(1,1)] + * @param {Number} [tileIndex=-1] + * @param {Vector2} [tileSize=tileSizeDefault] + * @param {Color} [color=new Color(1,1,1)] + * @param {Number} [angle=0] + * @param {Boolean} [mirror=0] */ + drawTile(pos: Vector2, size?: Vector2, tileIndex?: number, tileSize?: Vector2, color?: Color, angle?: number, mirror?: boolean): void; + /** Draw a rectangle directly onto the layer canvas + * @param {Vector2} pos + * @param {Vector2} [size=new Vector2(1,1)] + * @param {Color} [color=new Color(1,1,1)] + * @param {Number} [angle=0] */ + drawRect(pos: Vector2, size?: Vector2, color?: Color, angle?: number): void; + } + /** + * Particle Emitter - Spawns particles with the given settings + * @extends EngineObject + * @example + * // create a particle emitter + * let pos = vec2(2,3); + * let particleEmiter = new ParticleEmitter + * ( + * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone + * 0, vec2(16), // tileIndex, tileSize + * new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB + * new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB + * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed + * .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate, + * .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder + * ); */ - render(hidePercent?: number): void; - /** Render the icon for a medal - * @param {Number} x - Screen space X position - * @param {Number} y - Screen space Y position - * @param {Number} [size=medalDisplayIconSize] - Screen space size + export class ParticleEmitter extends EngineObject { + /** Create a particle system with the given settings + * @param {Vector2} position - World space position of the emitter + * @param {Number} [angle=0] - Angle to emit the particles + * @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect) + * @param {Number} [emitTime=0] - How long to stay alive (0 is forever) + * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0 + * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter + * @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied + * @param {Vector2} [tileSize=tileSizeDefault] - Tile size for particles + * @param {Color} [colorStartA=new Color(1,1,1)] - Color at start of life 1, randomized between start colors + * @param {Color} [colorStartB=new Color(1,1,1)] - Color at start of life 2, randomized between start colors + * @param {Color} [colorEndA=new Color(1,1,1,0)] - Color at end of life 1, randomized between end colors + * @param {Color} [colorEndB=new Color(1,1,1,0)] - Color at end of life 2, randomized between end colors + * @param {Number} [particleTime=.5] - How long particles live + * @param {Number} [sizeStart=.1] - How big are particles at start + * @param {Number} [sizeEnd=1] - How big are particles at end + * @param {Number} [speed=.1] - How fast are particles when spawned + * @param {Number} [angleSpeed=.05] - How fast are particles rotating + * @param {Number} [damping=1] - How much to dampen particle speed + * @param {Number} [angleDamping=1] - How much to dampen particle angular speed + * @param {Number} [gravityScale=0] - How much does gravity effect particles + * @param {Number} [particleConeAngle=PI] - Cone for start particle angle + * @param {Number} [fadeRate=.1] - How quick to fade in particles at start/end in percent of life + * @param {Number} [randomness=.2] - Apply extra randomness percent + * @param {Boolean} [collideTiles=0] - Do particles collide against tiles + * @param {Boolean} [additive=0] - Should particles use addtive blend + * @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component + * @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default) + * @param {Boolean} [localSpace=0] - Should it be in local space of emitter (world space is default) + */ + constructor(pos: any, angle?: number, emitSize?: number, emitTime?: number, emitRate?: number, emitConeAngle?: number, tileIndex?: number, tileSize?: Vector2, colorStartA?: Color, colorStartB?: Color, colorEndA?: Color, colorEndB?: Color, particleTime?: number, sizeStart?: number, sizeEnd?: number, speed?: number, angleSpeed?: number, damping?: number, angleDamping?: number, gravityScale?: number, particleConeAngle?: number, fadeRate?: number, randomness?: number, collideTiles?: boolean, additive?: boolean, randomColorLinear?: boolean, renderOrder?: number, localSpace?: boolean); + /** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */ + emitSize: number; + /** @property {Number} - How long to stay alive (0 is forever) */ + emitTime: number; + /** @property {Number} - How many particles per second to spawn, does not emit if 0 */ + emitRate: number; + /** @property {Number} - Local angle to apply velocity to particles from emitter */ + emitConeAngle: number; + /** @property {Color} - Color at start of life 1, randomized between start colors */ + colorStartA: Color; + /** @property {Color} - Color at start of life 2, randomized between start colors */ + colorStartB: Color; + /** @property {Color} - Color at end of life 1, randomized between end colors */ + colorEndA: Color; + /** @property {Color} - Color at end of life 2, randomized between end colors */ + colorEndB: Color; + /** @property {Boolean} - Should color be randomized linearly or across each component */ + randomColorLinear: boolean; + /** @property {Number} - How long particles live */ + particleTime: number; + /** @property {Number} - How big are particles at start */ + sizeStart: number; + /** @property {Number} - How big are particles at end */ + sizeEnd: number; + /** @property {Number} - How fast are particles when spawned */ + speed: number; + /** @property {Number} - How fast are particles rotating */ + angleSpeed: number; + /** @property {Number} - Cone for start particle angle */ + particleConeAngle: number; + /** @property {Number} - How quick to fade in particles at start/end in percent of life */ + fadeRate: number; + /** @property {Number} - Apply extra randomness percent */ + randomness: number; + /** @property {Number} - Do particles collide against tiles */ + collideTiles: boolean; + /** @property {Number} - Should particles use addtive blend */ + additive: boolean; + /** @property {Boolean} - Should it be in local space of emitter */ + localSpace: boolean; + /** @property {Number} - If set the partile is drawn as a trail, stretched in the drection of velocity */ + trailScale: number; + emitTimeBuffer: number; + /** Spawn one particle + * @return {Particle} */ + emitParticle(): Particle; + } + /** + * Particle Object - Created automatically by Particle Emitters + * @extends EngineObject */ - renderIcon(pos: any, size?: number): void; - storageKey(): string; -} -declare let newgrounds: any; -/** - * Newgrounds API wrapper object - * @example - * // create a newgrounds object, replace the app id and cipher with your own - * const app_id = '53123:1ZuSTQ9l'; - * const cipher = 'enF0vGH@Mj/FRASKL23Q=='; - * newgrounds = new Newgrounds(app_id, cipher); - */ -declare class Newgrounds { - /** Create a newgrounds object + export class Particle extends EngineObject { + /** + * Create a particle with the given settings + * @param {Vector2} position - World space position of the particle + * @param {Number} [tileIndex=-1] - Tile to use to render, untextured if -1 + * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels + * @param {Number} [angle=0] - Angle to rotate the particle + */ + constructor(pos: any, tileIndex?: number, tileSize?: Vector2, angle?: number); + } + /** List of all medals + * @memberof Medals */ + export const medals: any[]; + /** Set to stop medals from being unlockable (like if cheats are enabled) + * @default + * @memberof Settings */ + export let medalsPreventUnlock: any; + /** Initialize medals with a save name used for storage + *
- Call this after creating all medals + *
- Checks if medals are unlocked + * @param {String} saveName + * @memberof Medals */ + export function medalsInit(saveName: string): void; + /** This can used to enable Newgrounds functionality * @param {Number} app_id - The newgrounds App ID - * @param {String} [cipher] - The encryption Key (AES-128/Base64) */ - constructor(app_id: number, cipher?: string); - app_id: number; - cipher: string; - host: string; - cryptoJS: any; - session_id: string; - medals: any; - scoreboards: any; - /** Send message to unlock a medal by id - * @param {Number} id - The medal id */ - unlockMedal(id: number): any; - /** Send message to post score - * @param {Number} id - The scoreboard id - * @param {Number} value - The score value */ - postScore(id: number, value: number): any; - /** Get scores from a scoreboard - * @param {Number} id - The scoreboard id - * @param {String} [user=0] - A user's id or name - * @param {Number} [social=0] - If true, only social scores will be loaded - * @param {Number} [skip=0] - Number of scores to skip before start - * @param {Number} [limit=10] - Number of scores to include in the list - * @return {Object} - The response JSON object + * @param {String} [cipher] - The encryption Key (AES-128/Base64) + * @memberof Medals */ + export function newgroundsInit(app_id: number, cipher?: string): void; + /** + * Medal Object - Tracks an unlockable medal + * @example + * // create a medal + * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️'); + * + * // initialize medals + * medalsInit('Example Game'); + * + * // unlock the medal + * medal_example.unlock(); + */ + export class Medal { + /** Create an medal object and adds it to the list of medals + * @param {Number} id - The unique identifier of the medal + * @param {String} name - Name of the medal + * @param {String} [description] - Description of the medal + * @param {String} [icon='🏆'] - Icon for the medal + * @param {String} [src] - Image location for the medal + */ + constructor(id: number, name: string, description?: string, icon?: string, src?: string); + id: number; + name: string; + description: string; + icon: string; + image: HTMLImageElement; + /** Unlocks a medal if not already unlocked */ + unlock(): void; + unlocked: number; + /** Render a medal + * @param {Number} [hidePercent=0] - How much to slide the medal off screen + */ + render(hidePercent?: number): void; + /** Render the icon for a medal + * @param {Number} x - Screen space X position + * @param {Number} y - Screen space Y position + * @param {Number} [size=medalDisplayIconSize] - Screen space size + */ + renderIcon(pos: any, size?: number): void; + storageKey(): string; + } + /** + * Newgrounds API wrapper object + * @example + * // create a newgrounds object, replace the app id and cipher with your own + * const app_id = '53123:1ZuSTQ9l'; + * const cipher = 'enF0vGH@Mj/FRASKL23Q=='; + * newgrounds = new Newgrounds(app_id, cipher); + */ + export class Newgrounds { + /** Create a newgrounds object + * @param {Number} app_id - The newgrounds App ID + * @param {String} [cipher] - The encryption Key (AES-128/Base64) */ + constructor(app_id: number, cipher?: string); + app_id: number; + cipher: string; + host: string; + cryptoJS: any; + session_id: string; + medals: any; + scoreboards: any; + /** Send message to unlock a medal by id + * @param {Number} id - The medal id */ + unlockMedal(id: number): any; + /** Send message to post score + * @param {Number} id - The scoreboard id + * @param {Number} value - The score value */ + postScore(id: number, value: number): any; + /** Get scores from a scoreboard + * @param {Number} id - The scoreboard id + * @param {String} [user=0] - A user's id or name + * @param {Number} [social=0] - If true, only social scores will be loaded + * @param {Number} [skip=0] - Number of scores to skip before start + * @param {Number} [limit=10] - Number of scores to include in the list + * @return {Object} - The response JSON object + */ + getScores(id: number, user?: string, social?: number, skip?: number, limit?: number): any; + /** Send message to log a view */ + logView(): any; + /** Send a message to call a component of the Newgrounds API + * @param {String} component - Name of the component + * @param {Object} [parameters=0] - Parameters to use for call + * @param {Boolean} [async=0] - If true, don't wait for response before continuing (avoid stall) + * @return {Object} - The response JSON object + */ + call(component: string, parameters?: any, async?: boolean): any; + CryptoJS(): any; + } + /** The WebGL canvas which appears above the main canvas and below the overlay canvas + * @type {HTMLCanvasElement} + * @memberof WebGL */ + export let glCanvas: HTMLCanvasElement; + /** 2d context for glCanvas + * @type {WebGLRenderingContext} + * @memberof WebGL */ + export let glContext: WebGLRenderingContext; + /** Set the WebGl blend mode, normally you should call setBlendMode instead + * @param {Boolean} [additive=0] + * @memberof WebGL */ + export function glSetBlendMode(additive?: boolean): void; + /** Set the WebGl texture, not normally necessary unless multiple tile sheets are used + *
- This may also flush the gl buffer resulting in more draw calls and worse performance + * @param {WebGLTexture} [texture=glTileTexture] + * @memberof WebGL */ + export function glSetTexture(texture?: WebGLTexture): void; + /** Compile WebGL shader of the given type, will throw errors if in debug mode + * @param {String} source + * @param type + * @return {WebGLShader} + * @memberof WebGL */ + export function glCompileShader(source: string, type: any): WebGLShader; + /** Create WebGL program with given shaders + * @param {WebGLShader} vsSource + * @param {WebGLShader} fsSource + * @return {WebGLProgram} + * @memberof WebGL */ + export function glCreateProgram(vsSource: WebGLShader, fsSource: WebGLShader): WebGLProgram; + /** Create WebGL texture from an image and set the texture settings + * @param {Image} image + * @return {WebGLTexture} + * @memberof WebGL */ + export function glCreateTexture(image: new (width?: number, height?: number) => HTMLImageElement): WebGLTexture; + /** Set up a post processing shader + * @param {String} shaderCode + * @memberof WebGL */ + export function glInitPostProcess(shaderCode: string): void; + /** Name of engine */ + export const engineName: "LittleJS"; + /** Version of engine */ + export const engineVersion: "1.5.0"; + /** Frames per second to update objects + * @default */ + export const frameRate: 60; + /** How many seconds each frame lasts, engine uses a fixed time step + * @default 1/60 */ + export const timeDelta: number; + /** Array containing all engine objects */ + export let engineObjects: any[]; + /** Current update frame, used to calculate time */ + export let frame: number; + /** Current engine time since start in seconds, derived from frame */ + export let time: number; + /** Actual clock time since start in seconds (not affected by pause or frame rate clamping) */ + export let timeReal: number; + /** Is the game paused? Causes time and objects to not be updated */ + export let paused: number; + /** Set if game is paused + * @param {Boolean} paused */ - getScores(id: number, user?: string, social?: number, skip?: number, limit?: number): any; - /** Send message to log a view */ - logView(): any; - /** Send a message to call a component of the Newgrounds API - * @param {String} component - Name of the component - * @param {Object} [parameters=0] - Parameters to use for call - * @param {Boolean} [async=0] - If true, don't wait for response before continuing (avoid stall) - * @return {Object} - The response JSON object + export function setPaused(_paused: any): void; + /** Start up LittleJS engine with your callback functions + * @param {Function} gameInit - Called once after the engine starts up, setup the game + * @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state + * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render + * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects + * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects + * @param {String} [tileImageSource] - Tile image to use, everything starts when the image is finished loading */ - call(component: string, parameters?: any, async?: boolean): any; - CryptoJS(): any; + export function engineInit(gameInit: Function, gameUpdate: Function, gameUpdatePost: Function, gameRender: Function, gameRenderPost: Function, tileImageSource?: string): void; + /** Update each engine object, remove destroyed objects, and update time */ + export function engineObjectsUpdate(): void; + /** Destroy and remove all objects */ + export function engineObjectsDestroy(): void; + /** Triggers a callback for each object within a given area + * @param {Vector2} [pos] - Center of test area + * @param {Number} [size] - Radius of circle if float, rectangle size if Vector2 + * @param {Function} [callbackFunction] - Calls this function on every object that passes the test + * @param {Array} [objects=engineObjects] - List of objects to check */ + export function engineObjectsCallback(pos?: Vector2, size?: number, callbackFunction?: Function, objects?: any[]): void; } -/** The WebGL canvas which appears above the main canvas and below the overlay canvas - * @type {HTMLCanvasElement} - * @memberof WebGL */ -declare let glCanvas: HTMLCanvasElement; -/** 2d context for glCanvas - * @type {WebGLRenderingContext} - * @memberof WebGL */ -declare let glContext: WebGLRenderingContext; -/** Main tile sheet texture automatically loaded by engine - * @type {WebGLTexture} - * @memberof WebGL */ -declare let glTileTexture: WebGLTexture; -declare let glActiveTexture: any; -declare let glShader: any; -declare let glArrayBuffer: any; -declare let glPositionData: any; -declare let glColorData: any; -declare let glBatchCount: any; -declare let glBatchAdditive: any; -declare let glAdditive: any; -declare let glPostShader: any; -declare let glPostArrayBuffer: any; -declare let glPostTexture: any; -declare const gl_ONE: 1; -declare const gl_TRIANGLES: 4; -declare const gl_SRC_ALPHA: 770; -declare const gl_ONE_MINUS_SRC_ALPHA: 771; -declare const gl_BLEND: 3042; -declare const gl_TEXTURE_2D: 3553; -declare const gl_UNSIGNED_BYTE: 5121; -declare const gl_BYTE: 5120; -declare const gl_FLOAT: 5126; -declare const gl_RGBA: 6408; -declare const gl_NEAREST: 9728; -declare const gl_LINEAR: 9729; -declare const gl_TEXTURE_MAG_FILTER: 10240; -declare const gl_TEXTURE_MIN_FILTER: 10241; -declare const gl_TEXTURE_WRAP_S: 10242; -declare const gl_TEXTURE_WRAP_T: 10243; -declare const gl_COLOR_BUFFER_BIT: 16384; -declare const gl_CLAMP_TO_EDGE: 33071; -declare const gl_TEXTURE0: 33984; -declare const gl_TEXTURE1: 33985; -declare const gl_ARRAY_BUFFER: 34962; -declare const gl_STATIC_DRAW: 35044; -declare const gl_DYNAMIC_DRAW: 35048; -declare const gl_FRAGMENT_SHADER: 35632; -declare const gl_VERTEX_SHADER: 35633; -declare const gl_COMPILE_STATUS: 35713; -declare const gl_LINK_STATUS: 35714; -declare const gl_UNPACK_FLIP_Y_WEBGL: 37440; -declare const gl_VERTICES_PER_QUAD: 6; -declare const gl_INDICIES_PER_VERT: 6; -declare const gl_MAX_BATCH: number; -declare const gl_VERTEX_BYTE_STRIDE: number; -declare const gl_VERTEX_BUFFER_SIZE: number; -/** Name of engine */ -declare const engineName: "LittleJS"; -/** Version of engine */ -declare const engineVersion: "1.4.9"; -/** Frames per second to update objects - * @default */ -declare const frameRate: 60; -/** How many seconds each frame lasts, engine uses a fixed time step - * @default 1/60 */ -declare const timeDelta: number; -/** Array containing all engine objects */ -declare let engineObjects: any[]; -/** Array containing only objects that are set to collide with other objects this frame (for optimization) */ -declare let engineObjectsCollide: any[]; -/** Current update frame, used to calculate time */ -declare let frame: number; -/** Current engine time since start in seconds, derived from frame */ -declare let time: number; -/** Actual clock time since start in seconds (not affected by pause or frame rate clamping) */ -declare let timeReal: number; -/** Is the game paused? Causes time and objects to not be updated */ -declare let paused: number; diff --git a/examples/breakout/index.html b/examples/breakout/index.html index a9b8fbc1..f2b140df 100644 --- a/examples/breakout/index.html +++ b/examples/breakout/index.html @@ -1,4 +1,4 @@ - + diff --git a/examples/empty/index.html b/examples/empty/index.html index 04403515..e0a5bd05 100644 --- a/examples/empty/index.html +++ b/examples/empty/index.html @@ -1,7 +1,4 @@ - - - - + diff --git a/examples/module/game.js b/examples/module/game.js index 624c7cff..ecbef107 100644 --- a/examples/module/game.js +++ b/examples/module/game.js @@ -6,10 +6,7 @@ // import module import * as LittleJS from '../../engine/engine.all.module.js'; -const Vector2 = LittleJS.Vector2; -const Color = LittleJS.Color; -const Timer = LittleJS.Timer; -const vec2 = LittleJS.vec2; +const {Vector2, Color, Timer, vec2} = LittleJS; // sound effects const sound_click = new LittleJS.Sound([1,.5]); diff --git a/examples/module/index.html b/examples/module/index.html index b5079bc5..70fe4387 100644 --- a/examples/module/index.html +++ b/examples/module/index.html @@ -1,4 +1,4 @@ - + diff --git a/examples/particles/index.html b/examples/particles/index.html index 6c4ae60f..895eee2c 100644 --- a/examples/particles/index.html +++ b/examples/particles/index.html @@ -1,4 +1,4 @@ - + diff --git a/examples/platformer/index.html b/examples/platformer/index.html index 1c925d88..0d0c3a3a 100644 --- a/examples/platformer/index.html +++ b/examples/platformer/index.html @@ -1,4 +1,4 @@ - + diff --git a/examples/puzzle/index.html b/examples/puzzle/index.html index d7f14d66..6cf13043 100644 --- a/examples/puzzle/index.html +++ b/examples/puzzle/index.html @@ -1,4 +1,4 @@ - + diff --git a/examples/stress/index.html b/examples/stress/index.html index ea99f45c..b1b38338 100644 --- a/examples/stress/index.html +++ b/examples/stress/index.html @@ -1,4 +1,4 @@ - + diff --git a/examples/typescript/build.bat b/examples/typescript/build.bat new file mode 100644 index 00000000..0ae79401 --- /dev/null +++ b/examples/typescript/build.bat @@ -0,0 +1,14 @@ +rem TypeScript build script for LittleJS by Frank Force + +set BUILD_FOLDER=build + +call npx tsc +if %ERRORLEVEL% NEQ 0 ( + pause + exit /b %ERRORLEVEL% +) + +rem copy output js file and remove build folder + +copy %BUILD_FOLDER%\examples\typescript\game.js game.js +rmdir /s /q %BUILD_FOLDER% \ No newline at end of file diff --git a/examples/typescript/game.js b/examples/typescript/game.js new file mode 100644 index 00000000..1332f858 --- /dev/null +++ b/examples/typescript/game.js @@ -0,0 +1,89 @@ +/* + LittleJS Hello World Starter Game +*/ +'use strict'; +// import module +import * as LittleJS from '../../engine/engine.all.module.js'; +const { Vector2, Color, Timer, vec2 } = LittleJS; +// sound effects +const sound_click = new LittleJS.Sound([1, .5]); +// medals +const medal_example = new LittleJS.Medal(0, 'Example Medal', 'Welcome to LittleJS!'); +LittleJS.medalsInit('Hello World'); +// game variables +let particleEmiter; +/////////////////////////////////////////////////////////////////////////////// +function gameInit() { + // create tile collision and visible tile layer + LittleJS.initTileCollision(vec2(32, 16)); + const pos = vec2(); + const tileLayer = new LittleJS.TileLayer(pos, LittleJS.tileCollisionSize); + // get level data from the tiles image + const imageLevelDataRow = 1; + const mainContext = LittleJS.mainContext; + mainContext.drawImage(LittleJS.tileImage, 0, 0); + for (pos.x = LittleJS.tileCollisionSize.x; pos.x--;) + for (pos.y = LittleJS.tileCollisionSize.y; pos.y--;) { + const imageData = mainContext.getImageData(pos.x, 16 * (imageLevelDataRow + 1) - pos.y - 1, 1, 1).data; + if (imageData[0]) { + const tileIndex = 1; + const direction = LittleJS.randInt(4); + const mirror = LittleJS.randInt(2) ? true : false; + const color = LittleJS.randColor(); + const data = new LittleJS.TileLayerData(tileIndex, direction, mirror, color); + tileLayer.setData(pos, data); + LittleJS.setTileCollisionData(pos, 1); + } + } + tileLayer.redraw(); + // move camera to center of collision + LittleJS.setCameraPos(LittleJS.tileCollisionSize.scale(.5)); + // enable gravity + LittleJS.setGravity(-.01); + // create particle emitter + const center = LittleJS.tileCollisionSize.scale(.5).add(vec2(0, 9)); + particleEmiter = new LittleJS.ParticleEmitter(center, 0, // emitPos, emitAngle + 1, 0, 500, Math.PI, // emitSize, emitTime, emitRate, emiteCone + 0, vec2(16), // tileIndex, tileSize + new Color(1, 1, 1), new Color(0, 0, 0), // colorStartA, colorStartB + new Color(1, 1, 1, 0), new Color(0, 0, 0, 0), // colorEndA, colorEndB + 2, .2, .2, .1, .05, // time, sizeStart, sizeEnd, speed, angleSpeed + .99, 1, 1, Math.PI, // damping, angleDamping, gravityScale, cone + .05, .5, true, true // fadeRate, randomness, collide, additive + ); + particleEmiter.elasticity = .3; // bounce when it collides + particleEmiter.trailScale = 2; // stretch in direction of motion +} +/////////////////////////////////////////////////////////////////////////////// +function gameUpdate() { + if (LittleJS.mouseWasPressed(0)) { + // play sound when mouse is pressed + sound_click.play(LittleJS.mousePos); + // change particle color and set to fade out + particleEmiter.colorStartA = new Color; + particleEmiter.colorStartB = LittleJS.randColor(); + particleEmiter.colorEndA = particleEmiter.colorStartA.scale(1, 0); + particleEmiter.colorEndB = particleEmiter.colorStartB.scale(1, 0); + // unlock medals + medal_example.unlock(); + } + // move particles to mouse location if on screen + if (LittleJS.mousePosScreen.x) + particleEmiter.pos = LittleJS.mousePos; +} +/////////////////////////////////////////////////////////////////////////////// +function gameUpdatePost() { +} +/////////////////////////////////////////////////////////////////////////////// +function gameRender() { + // draw a grey square in the background without using webgl + LittleJS.drawRect(LittleJS.cameraPos, LittleJS.tileCollisionSize.add(vec2(5)), new Color(.2, .2, .2), 0, false); +} +/////////////////////////////////////////////////////////////////////////////// +function gameRenderPost() { + // draw to overlay canvas for hud rendering + LittleJS.drawTextScreen('LittleJS with TypeScript', vec2(LittleJS.mainCanvasSize.x / 2, 80), 80); +} +/////////////////////////////////////////////////////////////////////////////// +// Startup LittleJS Engine +LittleJS.engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, 'tiles.png'); diff --git a/examples/typescript/game.ts b/examples/typescript/game.ts new file mode 100644 index 00000000..f4260a3c --- /dev/null +++ b/examples/typescript/game.ts @@ -0,0 +1,117 @@ +/* + LittleJS Hello World Starter Game +*/ + +'use strict'; + +// import module +import * as LittleJS from '../../engine/engine.all.module.js'; +const {Vector2, Color, Timer, vec2} = LittleJS; + +// sound effects +const sound_click = new LittleJS.Sound([1,.5]); + +// medals +const medal_example = new LittleJS.Medal(0, 'Example Medal', 'Welcome to LittleJS!'); +LittleJS.medalsInit('Hello World'); + +// game variables +let particleEmiter; + +/////////////////////////////////////////////////////////////////////////////// +function gameInit() +{ + // create tile collision and visible tile layer + LittleJS.initTileCollision(vec2(32, 16)); + const pos = vec2(); + const tileLayer = new LittleJS.TileLayer(pos, LittleJS.tileCollisionSize); + + // get level data from the tiles image + const imageLevelDataRow = 1; + const mainContext = LittleJS.mainContext; + mainContext.drawImage(LittleJS.tileImage, 0, 0); + for (pos.x = LittleJS.tileCollisionSize.x; pos.x--;) + for (pos.y = LittleJS.tileCollisionSize.y; pos.y--;) + { + const imageData = mainContext.getImageData(pos.x, 16*(imageLevelDataRow+1)-pos.y-1, 1, 1).data; + if (imageData[0]) + { + const tileIndex = 1; + const direction = LittleJS.randInt(4) + const mirror = LittleJS.randInt(2) ? true : false; + const color = LittleJS.randColor(); + const data = new LittleJS.TileLayerData(tileIndex, direction, mirror, color); + tileLayer.setData(pos, data); + LittleJS.setTileCollisionData(pos, 1); + } + } + tileLayer.redraw(); + + // move camera to center of collision + LittleJS.setCameraPos(LittleJS.tileCollisionSize.scale(.5)); + + // enable gravity + LittleJS.setGravity(-.01); + + // create particle emitter + const center = LittleJS.tileCollisionSize.scale(.5).add(vec2(0,9)); + particleEmiter = new LittleJS.ParticleEmitter( + center, 0, // emitPos, emitAngle + 1, 0, 500, Math.PI, // emitSize, emitTime, emitRate, emiteCone + 0, vec2(16), // tileIndex, tileSize + new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB + new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB + 2, .2, .2, .1, .05, // time, sizeStart, sizeEnd, speed, angleSpeed + .99, 1, 1, Math.PI, // damping, angleDamping, gravityScale, cone + .05, .5, true, true // fadeRate, randomness, collide, additive + ); + particleEmiter.elasticity = .3; // bounce when it collides + particleEmiter.trailScale = 2; // stretch in direction of motion +} + +/////////////////////////////////////////////////////////////////////////////// +function gameUpdate() +{ + if (LittleJS.mouseWasPressed(0)) + { + // play sound when mouse is pressed + sound_click.play(LittleJS.mousePos); + + // change particle color and set to fade out + particleEmiter.colorStartA = new Color; + particleEmiter.colorStartB = LittleJS.randColor(); + particleEmiter.colorEndA = particleEmiter.colorStartA.scale(1,0); + particleEmiter.colorEndB = particleEmiter.colorStartB.scale(1,0); + + // unlock medals + medal_example.unlock(); + } + + // move particles to mouse location if on screen + if (LittleJS.mousePosScreen.x) + particleEmiter.pos = LittleJS.mousePos; +} + +/////////////////////////////////////////////////////////////////////////////// +function gameUpdatePost() +{ + +} + +/////////////////////////////////////////////////////////////////////////////// +function gameRender() +{ + // draw a grey square in the background without using webgl + LittleJS.drawRect(LittleJS.cameraPos, LittleJS.tileCollisionSize.add(vec2(5)), new Color(.2,.2,.2), 0, false); +} + +/////////////////////////////////////////////////////////////////////////////// +function gameRenderPost() +{ + // draw to overlay canvas for hud rendering + LittleJS.drawTextScreen('LittleJS with TypeScript', vec2(LittleJS.mainCanvasSize.x/2, 80), 80); +} + +/////////////////////////////////////////////////////////////////////////////// +// Startup LittleJS Engine +LittleJS.engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, 'tiles.png'); \ No newline at end of file diff --git a/examples/typescript/index.html b/examples/typescript/index.html new file mode 100644 index 00000000..70fe4387 --- /dev/null +++ b/examples/typescript/index.html @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/examples/typescript/tiles.png b/examples/typescript/tiles.png new file mode 100644 index 0000000000000000000000000000000000000000..8b4a3606729082d7ce5ea150cb77883e091adedc GIT binary patch literal 2436 zcmds&`!~~%AIIODyUn#Wm$0Fd+fZ(W<}#PL&aDrPtoB7{n9InehN2|5Fd?myL@pK9 z6z!8{}Ms z0RU*X1p(rsyA}{r69NFjj3ghwIIq(Q$mp1;kg#*X$hh;-!N}mmu-#?i#P`4Olml8& zekjZzTsV|5zig>x(Y@-uMhcoFddy%~70e>1-Xg1Xuv5)DKT{ukT5gK2x~q1g|8S+3 z%Y3H$9LKw{EQYy2%i?W)OiZFE)zgbPVo{s5ndH>Kc%jZwLQ=iM;Ui zv8Bn(CITM(n=l~YSrM1kn;Kw3cPYuEBY7yJ(s2+opcl$s(b>9CoGs6s2gQV_G4DZZ zUlW40bc`PkSI4(xi2CTg7#}_?BOBjkTcIlxEduwr(~t747VcJlA~wVtNzd;&)PiR4cFy=9QX%Nj(OO zuBRU*5PAjbcXivhN&+k)pS33(eQ>MiApm9^e+Ozy$C=6>$T(tXAr}pm9i<<2nU)rH zD%L8R(>PO6kumtM-)vleaPaJroDaepvW_!T)y_ix_Yd(oF08DwY`CvU{Pil|@~bfK zBe53P24*5TK6Z0?L4UvVS-(Wg=-J{rq*!>%f=PINJvn^5#^yvPKeJ#UFzM?=eRAHZ z2h`6U+3f`fWX#dW$F&E`*v~F>yp*duALtYlheC_B#mO}BXEM}*FgVqCWJFL}* zDr}Y!&JX@LGAU;B%?Dbdi*nG+_20S7xSDN)QWrW;GwODe>EKZ_RF|4(qG5kJx3ePi z20W_7bFAoXszyr@_MaY$DR~mi=;_#-;C=L4IMeLNf|hHXMNt0_>*I(XczhN5Ezhmq z_uI@cZ9{raoneM(p}p5~$SiI3(pKL8;|ffv6}g_a!%8y0^iAW@=(~k8Y3c7=%3I!n zeCkwV>&GN9G2+Iy5LlQLD*BmwfkU?!eXmK~noX?dWnZbeWxs1?()}I((H!Yj6dO?HA?CCca%e}KBtLc-@DSKdVT^)x9BT()0~(EJH@FO++v zITl-7Ak~;K@#I8W7(Kr!B)^Su%!2P+aKXZ|*<&Fh1lpX*JzPrx`%Qz+Rf-K*a!@B< z$!n4g*!Eu4rV_JZt>pyG@bPi7BGgW~N1veOB}B2G;iv+e*Uv9p9S^T$(bq*p0GWE! z`CNZ;2r!`0dW?6;^slwF#SCNiiGo_7mkBaVxP(JzZ98}G%fC`5n$COu5ltF-30v0Z z;)fXYD4S2|9U_88k)AFXbLKV;4O+cRDANCoN#5nz=(XVUA$CY~{6=ulqXl>wD`_Br>B z?)l1wT5`s$Qc_-CTh?1@Lp#1KG*g<|bM;GUq~Gg(uSd#D2F~9$f9+goo8oR6IV5rw z!9+dhcQLt5tRr>~2TeRwG~m}%^%4wqm2}8O=8tNW=OO+y*Z{ubo%8h;UQdu>UxaR0 z!-%qtii;CcLhf+*p?hG6+=PjjYZce>^E86O$Hw%`>0}fTLB2KlD(IgGKCj-tv@GsM zx)J>wyA|z_#tWqTGrlhoCnGC?ozN-gi0wKl!si=nNq7hXBs9rl$K!8oQhQYiTr3UR zQ^0S&WD80}--~(!%3xS8l}$4KIqHs}Lf6B8iJF@WcV!o~^HzA3104eV$igR?K157! zKmoXRH(Er`xHw{VeOJHio?qNGiD)PPSO9?R`BNaEtU_^jC>Doxvlp9(2n(AliOL@{ z1ORY57GvYH8#|M3k>xm9V9Lxlw=lAdi1{jF?izb(!%(k{G%y@l_WT9v+)OB6tTa1I zkSZ@JjkYZbDOGZvs~9P6IA(*vfZ;2KhihgrvWQB$t6mTqp|nh~CE}Yt+pX@6PAry zdPti5_@x^Al{r4W9>&-aFrrj-%0aF-g)q+A_83&g%Ms)LgX2NBHwx`WjLRiYZyU3q z0K6y`MZ0r67ppzrO8Q}%RuR}HSnZt+xjAhwOz40kMSZ}-9!NQIBJEQb<>I6oS}{HR zB(?pSnmm8LBly54^q0$|lR!-s*j$0DfWN9eNfpth#?5c@j|2)e0cc^c1i>#19US;e z#>@*8_oEF}(+i+s+>n%LO$mBR^O!(?%)nQcb*mrn0h(5Y4L491!VdzYY@#&|loL83 z^DSZh8|&k9fTijS2=>##R0}77b85{K#5(4t3@!=2_?rRR(+Ki&0soX410KbT0#1Lv d;s04ai-^u@k)Ta|i!Xo9#M--K8f*#a{{=Ps`=tN? literal 0 HcmV?d00001 diff --git a/examples/typescript/tsconfig.json b/examples/typescript/tsconfig.json new file mode 100644 index 00000000..d33872be --- /dev/null +++ b/examples/typescript/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "allowJs": true, + "module": "es2020", + "target": "es2020", + "outDir": "build" + } +} \ No newline at end of file diff --git a/index.html b/index.html index 8b64ea61..8c77566f 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ - + diff --git a/package.json b/package.json index c5779ebc..5f17ba64 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "littlejsengine", - "version": "1.4.9", + "version": "1.5.0", "description": "LittleJS - Tiny and Fast HTML5 Game Engine", "main": "engine/engine.all.module.js", "repository": {