littlejsengine 1.3.5 → 1.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  LittleJS is a super lightweight JavaScript game engine with a fast hybrid rendering system. It provides everything necessary to make amazing games, including rendering, physics, particles, sound effects, music, input handling, and debug tools. LittleJS is designed to be extremely small yet powerful enough for a variety of applications, from game jams to commercial releases. The code is very well documented with a variety of examples to get you started. Choo-Choo! 🚂
6
6
 
7
- ## [LittleJS Documentation](https://killedbyapixel.github.io/LittleJS/docs) - [LittleJS Trailer](https://youtu.be/chuBzGjv7Ms) - [LittleJS Trello](https://trello.com/b/E9zf1Xak/littlejs)
7
+ ## [LittleJS Documentation](https://killedbyapixel.github.io/LittleJS/docs) - [LittleJS Trailer](https://youtu.be/chuBzGjv7Ms) - [LittleJS Discord](https://discord.gg/zb7hcGkyZe)
8
8
 
9
9
  ![LittleJS Screenshot](examples/screenshot.jpg)
10
10
 
@@ -29,7 +29,8 @@ LittleJS is a super lightweight JavaScript game engine with a fast hybrid render
29
29
  - Debug tools and debug rendering system
30
30
  - [Full documentation](https://killedbyapixel.github.io/LittleJS/docs) automatically generated from the source code block tags with [JSDoc](https://github.com/jsdoc/jsdoc)
31
31
  - Build system automatically combines everything, minifies, and removes unused code
32
- - For size coding competitions like [js13kGames](https://js13kgames.com/), starter project builds to a 7KB zip with [Roadroller](https://github.com/lifthrasiir/roadroller)
32
+ - For size coding competitions like [js13kGames](https://js13kgames.com/), starter project builds to a 7KB zip file
33
+ - (https://github.com/lifthrasiir/roadroller)
33
34
  - Easily build a Windows executable with [Electron](https://www.electronjs.org/) for distribution on platforms like Steam
34
35
  - Open Source with the [MIT license](https://github.com/KilledByAPixel/LittleJS/blob/main/LICENSE) so it can be used for anything you want
35
36
 
@@ -41,11 +42,13 @@ To easily include LittleJS in your game, you can use one of the 3 pre-built js f
41
42
  - [engine.all.release.js](https://github.com/KilledByAPixel/LittleJS/blob/main/engine/engine.all.release.js) - The engine optimized for release builds
42
43
  - [engine.all.min.js](https://github.com/KilledByAPixel/LittleJS/blob/main/engine/engine.all.min.js) - The engine in release mode and minified
43
44
 
44
- The hello world example includes a batch file [build.bat](https://github.com/KilledByAPixel/LittleJS/blob/main/build.bat) that compresses everything into a tiny zip file using Google Closure, UglifyJS, Roadroller, and ECT. You must run buildSetup.bat to install the necessary npm dependencies.
45
+ The hello world example includes a batch file [build.bat](https://github.com/KilledByAPixel/LittleJS/blob/main/build.bat) that compresses everything into a tiny zip file using Google Closure, UglifyJS, Roadroller, and ECT. You must run [buildSetup.bat](https://github.com/KilledByAPixel/LittleJS/blob/main/buildSetup.bat) to install the necessary npm dependencies.
45
46
 
46
47
  ## Games Made With LittleJS
47
48
 
48
- - [Space Huggers](https://github.com/KilledByAPixel/SpaceHuggers) - A more developed version of the platformer example
49
+ - [Space Huggers](https://www.newgrounds.com/portal/view/819609) - Run and gun platformer with procedural levels
50
+ - [Space Huggers - JS13k Edition](https://github.com/KilledByAPixel/SpaceHuggers) - Original js13k game with source code
51
+ - [Isletopia](https://store.steampowered.com/app/1861260/Isletopia) - Relaxing strategy game of greenifying barren islands
49
52
  - Send me your games!
50
53
 
51
54
  ![LittleJS Logo](favicon.png)
@@ -804,6 +804,34 @@ class Color
804
804
  return this;
805
805
  }
806
806
 
807
+ /** Returns this color expressed in hsla format
808
+ * @return {Array} */
809
+ getHSLA()
810
+ {
811
+ const r = this.r;
812
+ const g = this.g;
813
+ const b = this.b;
814
+ const a = this.a;
815
+ const max = Math.max(r, g, b);
816
+ const min = Math.min(r, g, b);
817
+ const l = (max + min) / 2;
818
+
819
+ let h = 0, s = 0;
820
+ if (max != min)
821
+ {
822
+ let d = max - min;
823
+ s = l > .5 ? d / (2 - max - min) : d / (max + min);
824
+ if (r == max)
825
+ h = (g - b) / d + (g < b ? 6 : 0);
826
+ else if (g == max)
827
+ h = (b - r) / d + 2;
828
+ else if (b == max)
829
+ h = (r - g) / d + 4;
830
+ }
831
+
832
+ return [h / 6, s, l, a];
833
+ }
834
+
807
835
  /** Returns a new color that has each component randomly adjusted
808
836
  * @param {Number} [amount=.05]
809
837
  * @param {Number} [alphaAmount=0]
@@ -835,42 +863,6 @@ class Color
835
863
  return (this.r*255|0) + (this.g*255<<8) + (this.b*255<<16) + (this.a*255<<24);
836
864
  }
837
865
 
838
- /** Returns this color expressed as a hex code
839
- * @return {String} */
840
- hex()
841
- {
842
- const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
843
- return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b);
844
- }
845
-
846
- /** Returns this color expressed in hsla format
847
- * @return {Array} */
848
- getHSLA()
849
- {
850
- const r = this.r;
851
- const g = this.g;
852
- const b = this.b;
853
- const a = this.a;
854
- const max = Math.max(r, g, b);
855
- const min = Math.min(r, g, b);
856
- const l = (max + min) / 2;
857
-
858
- let h = 0, s = 0;
859
- if (max != min)
860
- {
861
- let d = max - min;
862
- s = l > .5 ? d / (2 - max - min) : d / (max + min);
863
- if (r == max)
864
- h = (g - b) / d + (g < b ? 6 : 0);
865
- else if (g == max)
866
- h = (b - r) / d + 2;
867
- else if (b == max)
868
- h = (r - g) / d + 4;
869
- }
870
-
871
- return [h / 6, s, l, a];
872
- }
873
-
874
866
  /** Set this color from a hex code
875
867
  * @param {String} hex - html hex code
876
868
  * @return {Color} */
@@ -884,6 +876,14 @@ class Color
884
876
  ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1);
885
877
  return this;
886
878
  }
879
+
880
+ /** Returns this color expressed as a hex code
881
+ * @return {String} */
882
+ getHex()
883
+ {
884
+ const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
885
+ return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b);
886
+ }
887
887
  }
888
888
 
889
889
  ///////////////////////////////////////////////////////////////////////////////
@@ -950,7 +950,8 @@ class Timer
950
950
  * @memberof Settings */
951
951
  let canvasMaxSize = vec2(1920, 1200);
952
952
 
953
- /** Fixed size of the canvas, if enabled cavnvas size never changes
953
+ /** Fixed size of the canvas, if enabled canvas size never changes
954
+ * - you may also need to set mainCanvasSize if using screen space coords in startup
954
955
  * @type {Vector2}
955
956
  * @default
956
957
  * @memberof Settings */
@@ -1585,18 +1586,26 @@ let overlayContext;
1585
1586
  let mainCanvasSize = vec2();
1586
1587
 
1587
1588
  /** Convert from screen to world space coordinates
1589
+ * - if calling outside of render, you may need to manually set mainCanvasSize
1588
1590
  * @param {Vector2} screenPos
1589
1591
  * @return {Vector2}
1590
1592
  * @memberof Draw */
1591
1593
  const screenToWorld = (screenPos)=>
1592
- screenPos.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos);
1594
+ {
1595
+ ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
1596
+ return screenPos.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos);
1597
+ }
1593
1598
 
1594
1599
  /** Convert from world to screen space coordinates
1600
+ * - if calling outside of render, you may need to manually set mainCanvasSize
1595
1601
  * @param {Vector2} worldPos
1596
1602
  * @return {Vector2}
1597
1603
  * @memberof Draw */
1598
1604
  const worldToScreen = (worldPos)=>
1599
- worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
1605
+ {
1606
+ ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
1607
+ return worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
1608
+ }
1600
1609
 
1601
1610
  /** Draw textured tile centered in world space, with color applied if using WebGL
1602
1611
  * @param {Vector2} pos - Center of the tile in world space
@@ -2074,7 +2083,7 @@ const mouseToScreen = (mousePos)=>
2074
2083
  return vec2(); // fix bug that can occur if user clicks before page loads
2075
2084
 
2076
2085
  const rect = mainCanvas.getBoundingClientRect();
2077
- return mainCanvasSize.multiply(
2086
+ return vec2(mainCanvas.width, mainCanvas.height).multiply(
2078
2087
  vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
2079
2088
  }
2080
2089
 
@@ -4095,7 +4104,7 @@ gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
4095
4104
  const engineName = 'LittleJS';
4096
4105
 
4097
4106
  /** Version of engine */
4098
- const engineVersion = '1.3.5';
4107
+ const engineVersion = '1.3.8';
4099
4108
 
4100
4109
  /** Frames per second to update objects
4101
4110
  * @default */
@@ -4186,6 +4195,33 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4186
4195
  timeReal += frameTimeDeltaMS / 1e3;
4187
4196
  frameTimeBufferMS = min(frameTimeBufferMS + !paused * frameTimeDeltaMS, 50); // clamp incase of slow framerate
4188
4197
 
4198
+ if (canvasFixedSize.x)
4199
+ {
4200
+ // clear set fixed size
4201
+ overlayCanvas.width = mainCanvas.width = canvasFixedSize.x;
4202
+ overlayCanvas.height = mainCanvas.height = canvasFixedSize.y;
4203
+
4204
+ // fit to window by adding space on top or bottom if necessary
4205
+ const aspect = innerWidth / innerHeight;
4206
+ const fixedAspect = mainCanvas.width / mainCanvas.height;
4207
+ mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
4208
+ mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
4209
+ if (glCanvas)
4210
+ {
4211
+ glCanvas.style.width = mainCanvas.style.width;
4212
+ glCanvas.style.height = mainCanvas.style.height;
4213
+ }
4214
+ }
4215
+ else
4216
+ {
4217
+ // clear and set size to same as window
4218
+ overlayCanvas.width = mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4219
+ overlayCanvas.height = mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4220
+ }
4221
+
4222
+ // save canvas size
4223
+ mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
4224
+
4189
4225
  if (paused)
4190
4226
  {
4191
4227
  // do post update even when paused
@@ -4222,30 +4258,6 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4222
4258
  // add the time smoothing back in
4223
4259
  frameTimeBufferMS += deltaSmooth;
4224
4260
  }
4225
-
4226
- if (canvasFixedSize.x)
4227
- {
4228
- // clear set fixed size
4229
- mainCanvas.width = canvasFixedSize.x;
4230
- mainCanvas.height = canvasFixedSize.y;
4231
-
4232
- // fit to window by adding space on top or bottom if necessary
4233
- const aspect = innerWidth / innerHeight;
4234
- const fixedAspect = mainCanvas.width / mainCanvas.height;
4235
- mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
4236
- mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
4237
- if (glCanvas)
4238
- {
4239
- glCanvas.style.width = mainCanvas.style.width;
4240
- glCanvas.style.height = mainCanvas.style.height;
4241
- }
4242
- }
4243
- else
4244
- {
4245
- // clear and set size to same as window
4246
- mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4247
- mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4248
- }
4249
4261
 
4250
4262
  // render sort then render while removing destroyed objects
4251
4263
  enginePreRender();
@@ -4285,8 +4297,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4285
4297
  // called by engine to setup render system
4286
4298
  function enginePreRender()
4287
4299
  {
4288
- // save canvas size and clear canvases
4289
- mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
4300
+ // save canvas size
4301
+ mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
4290
4302
 
4291
4303
  // disable smoothing for pixel art
4292
4304
  mainContext.imageSmoothingEnabled = !cavasPixelated;
@@ -1 +1 @@
1
- const debug=0,showWatermark=0,godMode=0,debugOverlay=0,debugPhysics=0,debugParticles=0,debugRaycast=0,debugGamepads=0,debugMedals=0,ASSERT=()=>{},debugInit=()=>{},debugUpdate=()=>{},debugRender=()=>{},debugRect=()=>{},debugCircle=()=>{},debugPoint=()=>{},debugLine=()=>{},debugAABB=()=>{},debugText=()=>{},debugClear=()=>{},debugSaveCanvas=()=>{};"use strict";const PI=Math.PI,abs=a=>0>a?-a:a,min=(a,b)=>a<b?a:b,max=(a,b)=>a>b?a:b,sign=a=>0>a?-1:1,mod=(a,b=1)=>(a%b+b)%b,clamp=(a,b=0,c=1)=>a<b?b:a>c?c:a,percent=(a,b=0,c=1)=>c-b?clamp((a-b)/(c-b)):0,lerp=(a,b=0,c=1)=>b+clamp(a)*(c-b),smoothStep=a=>a*a*(3-2*a),nearestPowerOfTwo=a=>2**Math.ceil(Math.log2(a)),isOverlapping=(a,b,c,d)=>2*abs(a.x-c.x)<b.x+d.x&2*abs(a.y-c.y)<b.y+d.y,wave=(a=1,b=1,c=time)=>b/2*(1-Math.cos(c*a*2*PI)),formatTime=a=>(a/60|0)+":"+(10>a%60?"0":"")+(a%60|0),rand=(a=1,b=0)=>b+(a-b)*Math.random(),randInt=(a=1,b=0)=>rand(a,b)|0,randSign=()=>2*(rand(2)|0)-1,randInCircle=(a=1,b=0)=>0<a?randVector(a*rand(b/a,1)**.5):new Vector2,randVector=(a=1)=>(new Vector2).setAngle(rand(2*PI),a),randColor=(a=new Color,b=new Color(0,0,0,1),c)=>c?a.lerp(b,rand()):new Color(rand(a.r,b.r),rand(a.g,b.g),rand(a.b,b.b),rand(a.a,b.a));let randSeed=1;const randSeeded=(a=1,b=0)=>{randSeed^=randSeed<<13;randSeed^=randSeed>>>17;randSeed^=randSeed<<5;return b+(a-b)*abs(randSeed%1e9)/1e9},vec2=(a=0,b)=>void 0==a.x?new Vector2(a,void 0==b?a:b):new Vector2(a.x,a.y);class Vector2{constructor(a=0,b=0){this.x=a;this.y=b}copy(){return new Vector2(this.x,this.y)}add(a){ASSERT(void 0!=a.x);return new Vector2(this.x+a.x,this.y+a.y)}subtract(a){ASSERT(void 0!=a.x);return new Vector2(this.x-a.x,this.y-a.y)}multiply(a){ASSERT(void 0!=a.x);return new Vector2(this.x*a.x,this.y*a.y)}divide(a){ASSERT(void 0!=a.x);return new Vector2(this.x/a.x,this.y/a.y)}scale(a){ASSERT(void 0==a.x);return new Vector2(this.x*a,this.y*a)}length(){return this.lengthSquared()**.5}lengthSquared(){return this.x**2+this.y**2}distance(a){return this.distanceSquared(a)**.5}distanceSquared(a){return(this.x-a.x)**2+(this.y-a.y)**2}normalize(a=1){const b=this.length();return b?this.scale(a/b):new Vector2(0,a)}clampLength(a=1){const b=this.length();return b>a?this.scale(a/b):this}dot(a){ASSERT(void 0!=a.x);return this.x*a.x+this.y*a.y}cross(a){ASSERT(void 0!=a.x);return this.x*a.y-this.y*a.x}angle(){return Math.atan2(this.x,this.y)}setAngle(a=0,b=1){this.x=b*Math.sin(a);this.y=b*Math.cos(a);return this}rotate(a){const b=Math.cos(a);a=Math.sin(a);return new Vector2(this.x*b-this.y*a,this.x*a+this.y*b)}direction(){return abs(this.x)>abs(this.y)?0>this.x?3:1:0>this.y?2:0}invert(){return new Vector2(this.y,-this.x)}floor(){return new Vector2(Math.floor(this.x),Math.floor(this.y))}area(){return abs(this.x*this.y)}lerp(a,b){ASSERT(void 0!=a.x);return this.add(a.subtract(this).scale(clamp(b)))}arrayCheck(a){return 0<=this.x&&0<=this.y&&this.x<a.x&&this.y<a.y}toString(a=3){return`(${(0>this.x?"":" ")+this.x.toFixed(a)},${(0>this.y?"":" ")+this.y.toFixed(a)} )`}}class Color{constructor(a=1,b=1,c=1,d=1){this.r=a;this.g=b;this.b=c;this.a=d}copy(){return new Color(this.r,this.g,this.b,this.a)}add(a){return new Color(this.r+a.r,this.g+a.g,this.b+a.b,this.a+a.a)}subtract(a){return new Color(this.r-a.r,this.g-a.g,this.b-a.b,this.a-a.a)}multiply(a){return new Color(this.r*a.r,this.g*a.g,this.b*a.b,this.a*a.a)}divide(a){return new Color(this.r/a.r,this.g/a.g,this.b/a.b,this.a/a.a)}scale(a,b=a){return new Color(this.r*a,this.g*a,this.b*a,this.a*b)}clamp(){return new Color(clamp(this.r),clamp(this.g),clamp(this.b),clamp(this.a))}lerp(a,b){return this.add(a.subtract(this).scale(clamp(b)))}setHSLA(a=0,b=0,c=1,d=1){b=.5>c?c*(1+b):c+b-c*b;c=2*c-b;const e=(f,h,g)=>(g=(g%1+1)%1)<1/6?f+6*(h-f)*g:.5>g?h:g<2/3?f+(h-f)*(2/3-g)*6:f;this.r=e(c,b,a+1/3);this.g=e(c,b,a);this.b=e(c,b,a-1/3);this.a=d;return this}mutate(a=.05,b=0){return new Color(this.r+rand(a,-a),this.g+rand(a,-a),this.b+rand(a,-a),this.a+rand(b,-b)).clamp()}toString(){ASSERT(0<=this.r&&1>=this.r&&0<=this.g&&1>=this.g&&0<=this.b&&1>=this.b&&0<=this.a&&1>=this.a);return`rgb(${255*this.r|0},${255*this.g|0},${255*this.b|0},${this.a})`}rgbaInt(){ASSERT(0<=this.r&&1>=this.r&&0<=this.g&&1>=this.g&&0<=this.b&&1>=this.b&&0<=this.a&&1>=this.a);return(255*this.r|0)+(255*this.g<<8)+(255*this.b<<16)+(255*this.a<<24)}hex(){const a=b=>(16>(b=255*b|0)?"0":"")+b.toString(16);return"#"+a(this.r)+a(this.g)+a(this.b)}getHSLA(){const a=this.r,b=this.g,c=this.b,d=this.a,e=Math.max(a,b,c),f=Math.min(a,b,c),h=(e+f)/2;let g=0,k=0;if(e!=f){let m=e-f;k=.5<h?m/(2-e-f):m/(e+f);a==e?g=(b-c)/m+(b<c?6:0):b==e?g=(c-a)/m+2:c==e&&(g=(a-b)/m+4)}return[g/6,k,h,d]}setHex(a){this.r=parseInt(a.slice(1,3),16)/255;this.g=parseInt(a.slice(3,5),16)/255;this.b=parseInt(a.slice(5,7),16)/255;this.a=1;ASSERT(0<=this.r&&1>=this.r&&0<=this.g&&1>=this.g&&0<=this.b&&1>=this.b);return this}}class Timer{constructor(a){this.time=void 0==a?void 0:time+a;this.setTime=a}set(a=0){this.time=time+a;this.setTime=a}unset(){this.time=void 0}isSet(){return void 0!=this.time}active(){return time<=this.time}elapsed(){return time>this.time}get(){return this.isSet()?time-this.time:0}getPercent(){return this.isSet()?percent(this.time-time,this.setTime,0):0}toString(){if(debug)return this.unset()?"unset":Math.abs(this.get())+" seconds "+(0>this.get()?"before":"after")}}"use strict";let canvasMaxSize=vec2(1920,1200),canvasFixedSize=vec2(),cavasPixelated=1,fontDefault="arial",tileSizeDefault=vec2(16),tileFixBleedScale=.3,objectDefaultSize=vec2(1),enablePhysicsSolver=1,objectDefaultMass=1,objectDefaultDamping=.99,objectDefaultAngleDamping=.99,objectDefaultElasticity=0,objectDefaultFriction=.8,objectMaxSpeed=1,gravity=0,particleEmitRateScale=1,cameraPos=vec2(),cameraScale=max(tileSizeDefault.x,tileSizeDefault.y),glEnable=1,glOverlay=1,gamepadsEnable=1,gamepadDirectionEmulateStick=1,inputWASDEmulateDirection=1,touchGamepadEnable=0,touchGamepadAnalog=1,touchGamepadSize=80,touchGamepadAlpha=.3,vibrateEnable=1,soundVolume=.5,soundEnable=1,soundDefaultRange=30,soundDefaultTaper=.7,medalDisplayTime=5,medalDisplaySlideTime=.5,medalDisplayWidth=640,medalDisplayHeight=80,medalDisplayIconSize=50;"use strict";const engineName="LittleJS",engineVersion="1.3.5",frameRate=60,timeDelta=1/frameRate;let engineObjects=[],engineObjectsCollide=[],frame=0,time=0,timeReal=0,paused=0,frameTimeLastMS=0,frameTimeBufferMS=0,tileImageSize,tileImageFixBleed,averageFPS,drawCount;const styleBody="margin:0;overflow:hidden;background:#000;touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none",styleCanvas="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)";function engineInit(a,b,c,d,e,f){tileImage.onerror=tileImage.onload=()=>{tileImageFixBleed=vec2(tileFixBleedScale).divide(tileImageSize=vec2(tileImage.width,tileImage.height));debug&&(tileImage.onload=()=>ASSERT(1));document.body.style=styleBody;document.body.appendChild(mainCanvas=document.createElement("canvas"));mainContext=mainCanvas.getContext("2d");mainCanvas.style=styleCanvas;debugInit();glInit();document.body.appendChild(overlayCanvas=document.createElement("canvas"));overlayContext=overlayCanvas.getContext("2d");overlayCanvas.style=styleCanvas;a();touchGamepadCreate();h()};const h=(g=0)=>{var k=g-frameTimeLastMS;frameTimeLastMS=g;if(debug||showWatermark)averageFPS=lerp(.05,averageFPS||0,1e3/(k||1));debug&&(k*=keyIsDown(107)?5:keyIsDown(109)?.2:1);timeReal+=k/1e3;frameTimeBufferMS=min(frameTimeBufferMS+!paused*k,50);if(paused)inputUpdate(),debugUpdate(),c(),inputUpdatePost();else{g=0;0>frameTimeBufferMS&&-9<frameTimeBufferMS&&(g=frameTimeBufferMS,frameTimeBufferMS=0);for(;0<=frameTimeBufferMS;frameTimeBufferMS-=1e3/frameRate)inputUpdate(),b(),engineObjectsUpdate(),debugUpdate(),c(),inputUpdatePost();frameTimeBufferMS+=g}canvasFixedSize.x?(mainCanvas.width=canvasFixedSize.x,mainCanvas.height=canvasFixedSize.y,g=innerWidth/innerHeight,k=mainCanvas.width/mainCanvas.height,mainCanvas.style.width=overlayCanvas.style.width=g<k?"100%":"",mainCanvas.style.height=overlayCanvas.style.height=g<k?"":"100%",glCanvas&&(glCanvas.style.width=mainCanvas.style.width,glCanvas.style.height=mainCanvas.style.height)):(mainCanvas.width=min(innerWidth,canvasMaxSize.x),mainCanvas.height=min(innerHeight,canvasMaxSize.y));enginePreRender();d();engineObjects.sort((n,p)=>n.renderOrder-p.renderOrder);for(var m of engineObjects)m.destroyed||m.render();e();medalsRender();touchGamepadRender();debugRender();glCopyToContext(mainContext);showWatermark&&(overlayContext.textAlign="right",overlayContext.textBaseline="top",overlayContext.font="1em monospace",overlayContext.fillStyle="#000",m=engineName+" v"+engineVersion+" / "+drawCount+" / "+engineObjects.length+" / "+averageFPS.toFixed(1)+" "+(glEnable?"GL":"2D"),overlayContext.fillText(m,mainCanvas.width-3,3),overlayContext.fillStyle="#fff",overlayContext.fillText(m,mainCanvas.width-2,2),drawCount=0);requestAnimationFrame(h)};f?tileImage.src=f:tileImage.onload()}function enginePreRender(){mainCanvasSize=vec2(overlayCanvas.width=mainCanvas.width,overlayCanvas.height=mainCanvas.height);mainContext.imageSmoothingEnabled=!cavasPixelated;glPreRender(mainCanvas.width,mainCanvas.height,cameraPos.x,cameraPos.y,cameraScale)}function engineObjectsUpdate(){engineObjectsCollide=engineObjects.filter(b=>b.collideSolidObjects);const a=b=>{if(!b.destroyed){b.update();for(const c of b.children)a(c)}};for(const b of engineObjects)b.parent||a(b);engineObjects=engineObjects.filter(b=>!b.destroyed);time=++frame/frameRate}function engineObjectsDestroy(){for(const a of engineObjects)a.parent||a.destroy();engineObjects=engineObjects.filter(a=>!a.destroyed)}function engineObjectsCallback(a,b,c,d=engineObjects){if(a)if(void 0!=b.x)for(const e of d)isOverlapping(a,b,e.pos,e.size)&&c(e);else{b*=b;for(const e of d)a.distanceSquared(e.pos)<b&&c(e)}else for(const e of d)c(e)}"use strict";class EngineObject{constructor(a=vec2(),b=objectDefaultSize,c=-1,d=tileSizeDefault,e=0,f,h=0){ASSERT(a&&void 0!=a.x&&void 0!=b.x);this.pos=a.copy();this.size=b;this.drawSize;this.tileIndex=c;this.tileSize=d;this.angle=e;this.color=f;this.additiveColor;this.mass=objectDefaultMass;this.damping=objectDefaultDamping;this.angleDamping=objectDefaultAngleDamping;this.elasticity=objectDefaultElasticity;this.friction=objectDefaultFriction;this.gravityScale=1;this.renderOrder=h;this.velocity=new Vector2;this.angleVelocity=0;this.spawnTime=time;this.children=[];this.collideTiles=1;engineObjects.push(this)}update(){var a=this.parent;if(a)this.pos=this.localPos.multiply(vec2(a.getMirrorSign(),1)).rotate(-a.angle).add(a.pos),this.angle=a.getMirrorSign()*this.localAngle+a.angle;else if(this.velocity.x=clamp(this.velocity.x,-objectMaxSpeed,objectMaxSpeed),this.velocity.y=clamp(this.velocity.y,-objectMaxSpeed,objectMaxSpeed),a=this.pos.copy(),this.pos.x+=this.velocity.x*=this.damping,this.pos.y+=this.velocity.y=this.damping*this.velocity.y+gravity*this.gravityScale,this.angle+=this.angleVelocity*=this.angleDamping,ASSERT(0<=this.angleDamping&&1>=this.angleDamping),ASSERT(0<=this.damping&&1>=this.damping),enablePhysicsSolver&&this.mass){var b=0>this.velocity.y;if(this.groundObject){var c=this.groundObject.velocity?this.groundObject.velocity.x:0;this.velocity.x=c+(this.velocity.x-c)*this.friction;this.groundObject=0}if(this.collideSolidObjects)for(var d of engineObjectsCollide)if(!(!this.isSolid&!d.isSolid||d.destroyed||d.parent||d==this||!isOverlapping(this.pos,this.size,d.pos,d.size)||!this.collideWithObject(d)|!d.collideWithObject(this)))if(isOverlapping(a,this.size,d.pos,d.size)){c=a.subtract(d.pos);var e=c.length();c=.01>e?randVector(.001):c.scale(.001/e);this.velocity=this.velocity.add(c);d.mass&&(d.velocity=d.velocity.subtract(c));debugOverlay&&debugPhysics&&debugAABB(this.pos,this.size,d.pos,d.size,"#f00")}else{c=this.size.add(d.size);e=2*(a.y-d.pos.y)>c.y+gravity;var f=2*abs(a.y-d.pos.y)<c.y,h=2*abs(a.x-d.pos.x)<c.x;if(e||h||!f)if(this.pos.y=d.pos.y+(c.y/2+.001)*sign(a.y-d.pos.y),d.groundObject&&b||!d.mass)b&&(this.groundObject=d),this.velocity.y*=-this.elasticity;else if(d.mass){const g=(this.mass*this.velocity.y+d.mass*d.velocity.y)/(this.mass+d.mass),k=this.velocity.y*(this.mass-d.mass)/(this.mass+d.mass)+2*d.velocity.y*d.mass/(this.mass+d.mass),m=d.velocity.y*(d.mass-this.mass)/(this.mass+d.mass)+2*this.velocity.y*this.mass/(this.mass+d.mass),n=max(this.elasticity,d.elasticity);this.velocity.y=lerp(n,g,k);d.velocity.y=lerp(n,g,m)}e||!f&&h||(this.pos.x=d.pos.x+(c.x/2+.001)*sign(a.x-d.pos.x),d.mass?(c=(this.mass*this.velocity.x+d.mass*d.velocity.x)/(this.mass+d.mass),e=this.velocity.x*(this.mass-d.mass)/(this.mass+d.mass)+2*d.velocity.x*d.mass/(this.mass+d.mass),f=d.velocity.x*(d.mass-this.mass)/(this.mass+d.mass)+2*this.velocity.x*this.mass/(this.mass+d.mass),h=max(this.elasticity,d.elasticity),this.velocity.x=lerp(h,c,e),d.velocity.x=lerp(h,c,f)):this.velocity.x*=-this.elasticity);debugOverlay&&debugPhysics&&debugAABB(this.pos,this.size,d.pos,d.size,"#f0f")}if(this.collideTiles&&tileCollisionTest(this.pos,this.size,this)&&!tileCollisionTest(a,this.size,this)){c=tileCollisionTest(new Vector2(a.x,this.pos.y),this.size,this);d=tileCollisionTest(new Vector2(this.pos.x,a.y),this.size,this);if(c||!d)this.groundObject=b,this.velocity.y*=-this.elasticity,b=(a.y-this.size.y/2|0)-(a.y-this.size.y/2),0>b&&b>this.damping*this.velocity.y+gravity*this.gravityScale&&(this.velocity.y=this.damping?(b-gravity*this.gravityScale)/this.damping:0),this.pos.y=a.y;d&&(this.pos.x=a.x,this.velocity.x*=-this.elasticity)}}}render(){drawTile(this.pos,this.drawSize||this.size,this.tileIndex,this.tileSize,this.color,this.angle,this.mirror,this.additiveColor)}destroy(){if(!this.destroyed){this.destroyed=1;this.parent&&this.parent.removeChild(this);for(const a of this.children)a.destroy(a.parent=0)}}collideWithTile(a,b){return 0<a}collideWithTileRaycast(a,b){return 0<a}collideWithObject(a){return 1}getAliveTime(){return time-this.spawnTime}applyAcceleration(a){this.mass&&(this.velocity=this.velocity.add(a))}applyForce(a){this.applyAcceleration(a.scale(1/this.mass))}getMirrorSign(){return this.mirror?-1:1}addChild(a,b=vec2(),c=0){ASSERT(!a.parent&&!this.children.includes(a));this.children.push(a);a.parent=this;a.localPos=b.copy();a.localAngle=c}removeChild(a){ASSERT(a.parent==this&&this.children.includes(a));this.children.splice(this.children.indexOf(a),1);a.parent=0}setCollision(a=0,b=0,c=1){ASSERT(a||!b);this.collideSolidObjects=a;this.isSolid=b;this.collideTiles=c}toString(){if(debug){let a="type = "+this.constructor.name;if(this.pos.x||this.pos.y)a+="\npos = "+this.pos;if(this.velocity.x||this.velocity.y)a+="\nvelocity = "+this.velocity;if(this.size.x||this.size.y)a+="\nsize = "+this.size;this.angle&&(a+="\nangle = "+this.angle.toFixed(3));this.color&&(a+="\ncolor = "+this.color);return a}}}"use strict";const tileImage=new Image;let mainCanvas,mainContext,overlayCanvas,overlayContext,mainCanvasSize=vec2();const screenToWorld=a=>a.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos),worldToScreen=a=>a.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));function drawTile(a,b=vec2(1),c=-1,d=tileSizeDefault,e=new Color,f=0,h,g=new Color(0,0,0,0),k=glEnable){showWatermark&&++drawCount;if(glEnable&&k)if(0>c||!tileImage.width)glDraw(a.x,a.y,b.x,b.y,f,0,0,0,0,0,e.rgbaInt());else{var m=tileImageSize.x/d.x|0;k=d.x/tileImageSize.x;const n=d.y/tileImageSize.y,p=c%m*k;m=(c/m|0)*n;glDraw(a.x,a.y,h?-b.x:b.x,b.y,f,p+tileImageFixBleed.x,m+tileImageFixBleed.y,p-tileImageFixBleed.x+k,m-tileImageFixBleed.y+n,e.rgbaInt(),g.rgbaInt())}else drawCanvas2D(a,b,f,h,n=>{if(0>c)n.fillStyle=e,n.fillRect(-.5,-.5,1,1);else{var p=tileImageSize.x/d.x|0;const l=c%p*d.x+tileFixBleedScale;p=(c/p|0)*d.y+tileFixBleedScale;const u=d.x-2*tileFixBleedScale,v=d.y-2*tileFixBleedScale;n.globalAlpha=e.a;n.drawImage(tileImage,l,p,u,v,-.5,-.5,1,1)}})}function drawRect(a,b,c,d,e){drawTile(a,b,-1,tileSizeDefault,c,d,0,0,e)}function drawTileScreenSpace(a,b=vec2(1),c,d,e,f,h,g,k){drawTile(screenToWorld(a),b.scale(1/cameraScale),c,d,e,f,h,g,k)}function drawRectScreenSpace(a,b,c,d,e){drawTileScreenSpace(a,b,-1,tileSizeDefault,c,d,0,0,e)}function drawLine(a,b,c=.1,d,e){b=vec2((b.x-a.x)/2,(b.y-a.y)/2);c=vec2(c,2*b.length());drawRect(a.add(b),c,d,b.angle(),0,0,e)}function drawCanvas2D(a,b,c,d,e,f=mainContext){a=worldToScreen(a);b=b.scale(cameraScale);f.save();f.translate(a.x+.5|0,a.y-.5|0);f.rotate(c);f.scale(d?-b.x:b.x,b.y);e(f);f.restore()}function setBlendMode(a,b=glEnable){glEnable&&b?glSetBlendMode(a):mainContext.globalCompositeOperation=a?"lighter":"source-over"}function drawTextScreen(a,b,c=1,d=new Color,e=0,f=new Color(0,0,0),h="center",g=fontDefault){overlayContext.fillStyle=d;overlayContext.lineWidth=e;overlayContext.strokeStyle=f;overlayContext.textAlign=h;overlayContext.font=c+"px "+g;overlayContext.textBaseline="middle";overlayContext.lineJoin="round";b=b.copy();(a+"").split("\n").forEach(k=>{e&&overlayContext.strokeText(k,b.x,b.y);overlayContext.fillText(k,b.x,b.y);b.y+=c})}function drawText(a,b,c=1,d,e,f,h,g){drawTextScreen(a,worldToScreen(b),c*cameraScale,d,e*cameraScale,f,h,g)}let engineFontImage;class FontImage{constructor(a,b=vec2(8),c=vec2(0,1),d=0,e=overlayContext){a||engineFontImage||(engineFontImage=new Image,engineFontImage.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC");this.image=a||engineFontImage;this.tileSize=b;this.paddingSize=c;this.startTileIndex=d;this.context=e}drawTextScreen(a,b,c=4,d){const e=this.context;e.save();e.imageSmoothingEnabled=!cavasPixelated;const f=this.tileSize,h=f.add(this.paddingSize).scale(c),g=this.image.width/this.tileSize.x|0;a.split("\n").forEach((k,m)=>{const n=d?k.length*f.x*c/2|0:0;for(let u=k.length;u--;){var p=k[u].charCodeAt();if(32>p||127<p)p=127;var l=this.startTileIndex+p-32;p=l%g;l=l/g|0;const v=b.add(vec2(u,m).multiply(h));e.drawImage(this.image,p*f.x,l*f.y,f.x,f.y,v.x-n,v.y,f.x*c,f.y*c)}});e.restore()}drawText(a,b,c=1,d){this.drawTextScreen(a,worldToScreen(b).floor(),c*cameraScale|0,d)}}const isFullscreen=()=>document.fullscreenElement;function toggleFullscreen(){isFullscreen()?document.exitFullscreen?document.exitFullscreen():document.mozCancelFullScreen&&document.mozCancelFullScreen():document.body.webkitRequestFullScreen?document.body.webkitRequestFullScreen():document.body.mozRequestFullScreen&&document.body.mozRequestFullScreen()}"use strict";const keyIsDown=(a,b=0)=>inputData[b]&&inputData[b][a]&1?1:0,keyWasPressed=(a,b=0)=>inputData[b]&&inputData[b][a]&2?1:0,keyWasReleased=(a,b=0)=>inputData[b]&&inputData[b][a]&4?1:0,clearInput=()=>inputData=[[]],mouseIsDown=keyIsDown,mouseWasPressed=keyWasPressed,mouseWasReleased=keyWasReleased;let mousePos=vec2(),mousePosScreen=vec2(),mouseWheel=0,isUsingGamepad=0,preventDefaultInput=0;const gamepadIsDown=(a,b=0)=>keyIsDown(a,b+1),gamepadWasPressed=(a,b=0)=>keyWasPressed(a,b+1),gamepadWasReleased=(a,b=0)=>keyWasReleased(a,b+1),gamepadStick=(a,b=0)=>stickData[b]?stickData[b][a]||vec2():vec2();let inputData=[[]];function inputUpdate(){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 mainCanvasSize.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.getGamepads&&(document.hasFocus()||debug))for(a=navigator.getGamepads(),b=a.length;b--;){var d=a[b];const h=inputData[b+1]||(inputData[b+1]=[]);c=stickData[b]||(stickData[b]=[]);if(d){var e=g=>.3<g?percent(g,.3,.8):-.3>g?-percent(-g,.3,.8):0;for(var f=0;f<d.axes.length-1;f+=2)c[f>>1]=vec2(e(d.axes[f]),e(-d.axes[f+1])).clampLength();for(e=d.buttons.length;e--;)f=d.buttons[e],h[e]=f.pressed?1+2*!gamepadIsDown(e,b):4*gamepadIsDown(e,b),isUsingGamepad|=!b&&f.pressed,touchGamepadEnable&&touchGamepadTimer.unset();gamepadDirectionEmulateStick&&(d=vec2(gamepadIsDown(15,b)-gamepadIsDown(14,b),gamepadIsDown(12,b)-gamepadIsDown(13,b)),d.lengthSquared()&&(c[0]=d.clampLength()))}}}const vibrate=a=>vibrateEnable&&Navigator.vibrate&&Navigator.vibrate(a),vibrateStop=()=>vibrate(0),isTouchDevice=void 0!==window.ontouchstart;if(isTouchDevice){let a,b;ontouchstart=ontouchmove=ontouchend=c=>{c.button=0;const d=c.touches.length;d?(b||zzfx(0,b=1),c.x=c.touches[0].clientX,c.y=c.touches[0].clientY,a?onmousemove(c):onmousedown(c)):a&&onmouseup(c);a=d;return!c.cancelable}}let touchGamepadTimer=new Timer,touchGamepadButtons=[],touchGamepadStick=vec2();function touchGamepadCreate(){touchGamepadEnable&&isTouchDevice&&(ontouchstart=ontouchmove=ontouchend=a=>{if(touchGamepadEnable){touchGamepadStick=vec2();touchGamepadButtons=[];if(a.touches.length&&(touchGamepadTimer.isSet()||zzfx(0),isUsingGamepad=1,touchGamepadTimer.set(),paused)){touchGamepadButtons[9]=1;return}var b=vec2(touchGamepadSize,mainCanvasSize.y-touchGamepadSize),c=mainCanvasSize.subtract(vec2(touchGamepadSize,touchGamepadSize)),d=mainCanvasSize.scale(.5);for(const e of a.touches)a=mouseToScreen(vec2(e.clientX,e.clientY)),a.distance(b)<touchGamepadSize?touchGamepadAnalog?touchGamepadStick=a.subtract(b).scale(2/touchGamepadSize).clampLength():(a=a.subtract(b).angle(),touchGamepadStick.setAngle((4*a/PI+8.5|0)*PI/4)):a.distance(c)<touchGamepadSize?(a=a.subtract(c).direction(),touchGamepadButtons[a]=1):a.distance(d)<touchGamepadSize&&(touchGamepadButtons[9]=1)}})}function touchGamepadRender(){if(touchGamepadEnable&&touchGamepadTimer.isSet()){var a=percent(touchGamepadTimer.get(),4,3);if(a&&!paused){overlayContext.save();overlayContext.globalAlpha=a*touchGamepadAlpha;overlayContext.strokeStyle="#fff";overlayContext.lineWidth=3;overlayContext.fillStyle=0<touchGamepadStick.lengthSquared()?"#fff":"#000";overlayContext.beginPath();a=vec2(touchGamepadSize,mainCanvasSize.y-touchGamepadSize);if(touchGamepadAnalog)overlayContext.arc(a.x,a.y,touchGamepadSize/2,0,9),overlayContext.fill();else for(var b=10;b--;){var c=b*PI/4;overlayContext.arc(a.x,a.y,.6*touchGamepadSize,c+PI/8,c+PI/8);b%2&&overlayContext.arc(a.x,a.y,.33*touchGamepadSize,c,c);1==b&&overlayContext.fill()}overlayContext.stroke();a=vec2(mainCanvasSize.x-touchGamepadSize,mainCanvasSize.y-touchGamepadSize);for(b=4;b--;)c=a.add((new Vector2).setAngle(b*PI/2,touchGamepadSize/2)),overlayContext.fillStyle=touchGamepadButtons[b]?"#fff":"#000",overlayContext.beginPath(),overlayContext.arc(c.x,c.y,touchGamepadSize/4,0,9),overlayContext.fill(),overlayContext.stroke();overlayContext.restore()}}}"use strict";class Sound{constructor(a,b=soundDefaultRange,c=soundDefaultTaper){soundEnable&&(this.range=b,this.taper=c,this.randomness=a[1]||0,a[1]=0,this.cachedSamples=zzfxG(...a))}play(a,b=1,c=1,d=1){if(soundEnable){var e=0;if(a){if(e=this.range){const f=cameraPos.distanceSquared(a);if(f>e*e)return;b*=percent(f**.5,e,e*this.taper)}e=2*worldToScreen(a).x/mainCanvas.width-1}a=c+c*this.randomness*d*rand(-1,1);return playSamples([this.cachedSamples],b,a,e)}}playNote(a,b,c=1){if(soundEnable)return this.play(b,c,2**(a/12),0)}}class Music{constructor(a){soundEnable&&(this.cachedSamples=zzfxM(...a))}play(a=1,b=1){if(soundEnable)return playSamples(this.cachedSamples,a,1,0,b)}}function playAudioFile(a,b=1,c=1){if(soundEnable)return a=new Audio(a),a.volume=soundVolume*b,a.loop=c,a.play(),a}function speak(a,b="",c=1,d=1,e=1){if(soundEnable&&speechSynthesis)return a=new SpeechSynthesisUtterance(a),a.lang=b,a.volume=2*c*soundVolume,a.rate=d,a.pitch=e,speechSynthesis.speak(a),a}const speakStop=()=>speechSynthesis&&speechSynthesis.cancel(),getNoteFrequency=(a,b=220)=>b*2**(a/12);let audioContext;function playSamples(a,b=1,c=1,d=0,e=0){if(soundEnable&&(audioContext||(audioContext=new(window.AudioContext||webkitAudioContext)),audioContext.resume(),"running"==audioContext.state)){var f=audioContext.createBuffer(a.length,a[0].length,zzfxR),h=audioContext.createBufferSource();a.forEach((g,k)=>f.getChannelData(k).set(g));h.buffer=f;h.playbackRate.value=c;h.loop=e;a=audioContext.createGain();a.gain.value=soundVolume*b;a.connect(audioContext.destination);(window.StereoPannerNode?h.connect(new StereoPannerNode(audioContext,{pan:clamp(d,-1,1)})):h).connect(a);h.start();return h}}const zzfx=(...a)=>playSamples([zzfxG(...a)]),zzfxR=44100;function zzfxG(a=1,b=.05,c=220,d=0,e=0,f=.1,h=0,g=1,k=0,m=0,n=0,p=0,l=0,u=0,v=0,A=0,r=0,B=1,y=0,C=0){let t=2*PI,F=k*=500*t/zzfxR/zzfxR,z=[];b=c*=(1+b*rand(-1,1))*t/zzfxR;let w=0,D=0,q=0,x=1,H=0,I=0,E=0,J,G;d=d*zzfxR+9;y*=zzfxR;e*=zzfxR;f*=zzfxR;r*=zzfxR;m*=500*t/zzfxR**3;v*=t/zzfxR;n*=t/zzfxR;p*=zzfxR;l=l*zzfxR|0;for(G=d+y+e+f+r|0;q<G;z[q++]=E)++I%(100*A|0)||(E=h?1<h?2<h?3<h?Math.sin((w%t)**3):max(min(Math.tan(w),1),-1):1-(2*w/t%2+2)%2:1-4*abs(Math.round(w/t)-w/t):Math.sin(w),E=(l?1-C+C*Math.sin(t*q/l):1)*sign(E)*abs(E)**g*a*soundVolume*(q<d?q/d:q<d+y?1-(q-d)/y*(1-B):q<d+y+e?B:q<G-r?(G-q-r)/f*B:0),E=r?E/2+(r>q?0:(q<G-r?1:(G-q)/r)*z[q-r|0]/2):E),J=(c+=k+=m)*Math.cos(v*D++),w+=J-J*u*(1-1e9*(Math.sin(q)+1)%2),x&&++x>p&&(c+=n,b+=n,x=0),!l||++H%l||(c=b,k=F,x=x||1);return z}function zzfxM(a,b,c,d=125){let e,f,h,g,k,m,n,p,l,u,v,A,r,B=0,y,C=[],t=[],F=[],z=0,w=0,D=1,q={},x=zzfxR/d*60>>2;for(;D;z++)C=[D=p=A=0],c.forEach((H,I)=>{n=b[H][z]||[0,0,0];D|=!!b[H][z];y=A+(b[H][0].length-2-!p)*x;r=I==c.length-1;f=2;for(g=A;f<n.length+r;p=++f){k=n[f];l=f==n.length+r-1&&r||u!=(n[0]||0)|k|0;for(h=0;h<x&&p;h++>x-99&&l?v+=(1>v)/99:0)m=(1-v)*C[B++]/2||0,t[g]=(t[g]||0)-m*w+m,F[g]=(F[g++]||0)+m*w+m;k&&(v=k%1,w=n[1]||0,k|=0)&&(C=q[[u=n[B=0]||0,k]]=q[[u,k]]||(e=[...a[u]],e[2]*=2**((k-12)/12),0<k?zzfxG(...e):[]))}A=y});return[t,F]}"use strict";let tileCollision=[],tileCollisionSize=vec2();function initTileCollision(a){tileCollisionSize=a;tileCollision=[];for(a=tileCollision.length=tileCollisionSize.area();a--;)tileCollision[a]=0}const setTileCollisionData=(a,b=0)=>a.arrayCheck(tileCollisionSize)&&(tileCollision[(a.y|0)*tileCollisionSize.x+a.x|0]=b),getTileCollisionData=a=>a.arrayCheck(tileCollisionSize)?tileCollision[(a.y|0)*tileCollisionSize.x+a.x|0]:0;function tileCollisionTest(a,b=vec2(),c){const d=max(a.x-b.x/2|0,0);var e=max(a.y-b.y/2|0,0);const f=min(a.x+b.x/2,tileCollisionSize.x);for(a=min(a.y+b.y/2,tileCollisionSize.y);e<a;++e)for(b=d;b<f;++b){const h=tileCollision[e*tileCollisionSize.x+b];if(h&&(!c||c.collideWithTile(h,new Vector2(b,e))))return 1}}function tileCollisionRaycast(a,b,c){a=a.floor();b=b.floor();var d=b.subtract(a);const e=abs(d.x),f=-abs(d.y),h=sign(d.x);d=sign(d.y);let g=e+f;for(let m=a.x,n=a.y;;){var k=getTileCollisionData(vec2(m,n));if(k&&(c?c.collideWithTileRaycast(k,new Vector2(m,n)):0<k))return debugRaycast&&debugLine(a,b,"#f00",.02,1),debugRaycast&&debugPoint(new Vector2(m+.5,n+.5),"#ff0",1),new Vector2(m+.5,n+.5);if(m==b.x&n==b.y)break;k=2*g;k>=f&&(g+=f,m+=h);k<=e&&(g+=e,n+=d)}debugRaycast&&debugLine(a,b,"#00f",.02,1)}class TileLayerData{constructor(a,b=0,c=0,d=new Color){this.tile=a;this.direction=b;this.mirror=c;this.color=d}clear(){this.tile=this.direction=this.mirror=0;color=new Color}}class TileLayer extends EngineObject{constructor(a,b=tileCollisionSize,c=tileSizeDefault,d=vec2(1),e=0){super(a,b,-1,c,0,void 0,e);this.canvas=document.createElement("canvas");this.context=this.canvas.getContext("2d");this.scale=d;this.isOverlay;this.data=[];for(a=this.size.area();a--;)this.data.push(new TileLayerData)}setData(a,b,c){a.arrayCheck(this.size)&&(this.data[(a.y|0)*this.size.x+a.x|0]=b,c&&this.drawTileData(a))}getData(a){return a.arrayCheck(this.size)&&this.data[(a.y|0)*this.size.x+a.x|0]}update(){}render(){ASSERT(mainContext!=this.context);glEnable&&!glOverlay&&!this.isOverlay&&glCopyToContext(mainContext);const a=worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));(this.isOverlay?overlayContext:mainContext).drawImage(this.canvas,a.x,a.y,cameraScale*this.size.x*this.scale.x,cameraScale*this.size.y*this.scale.y)}redraw(){this.redrawStart(1);this.drawAllTileData();this.redrawEnd()}redrawStart(a=0){a&&(this.canvas.width=this.size.x*this.tileSize.x,this.canvas.height=this.size.y*this.tileSize.y);this.savedRenderSettings=[mainCanvas,mainContext,cameraPos,cameraScale];mainCanvas=this.canvas;mainContext=this.context;cameraPos=this.size.scale(.5);cameraScale=this.tileSize.x;enginePreRender()}redrawEnd(){ASSERT(mainContext==this.context);glCopyToContext(mainContext,1);[mainCanvas,mainContext,cameraPos,cameraScale]=this.savedRenderSettings}drawTileData(a){const b=a.floor().add(this.pos).add(vec2(.5));this.drawCanvas2D(b,vec2(1),0,0,c=>c.clearRect(-.5,-.5,1,1));a=this.getData(a);void 0!=a.tile&&(ASSERT(mainContext==this.context),drawTile(b,vec2(1),a.tile,this.tileSize,a.color,a.direction*PI/2,a.mirror))}drawAllTileData(){for(let a=this.size.x;a--;)for(let b=this.size.y;b--;)this.drawTileData(vec2(a,b))}drawCanvas2D(a,b,c=0,d,e){const f=this.context;f.save();a=a.subtract(this.pos).multiply(this.tileSize);b=b.multiply(this.tileSize);f.translate(a.x,this.canvas.height-a.y);f.rotate(c);f.scale(d?-b.x:b.x,b.y);e(f);f.restore()}drawTile(a,b=vec2(1),c=-1,d=tileSizeDefault,e=new Color,f,h){this.drawCanvas2D(a,b,f,h,g=>{if(0>c)g.fillStyle=e,g.fillRect(-.5,-.5,1,1);else{const k=tileImage.width/d.x;g.globalAlpha=e.a;g.drawImage(tileImage,c%k*d.x,(c/k|0)*d.x,d.x,d.y,-.5,-.5,1,1)}})}drawRect(a,b,c,d){this.drawTile(a,b,-1,0,c,d)}}"use strict";class ParticleEmitter extends EngineObject{constructor(a,b,c=0,d=0,e=100,f=PI,h=-1,g=tileSizeDefault,k=new Color,m=new Color,n=new Color(1,1,1,0),p=new Color(1,1,1,0),l=.5,u=.1,v=1,A=.1,r=.05,B=1,y=1,C=0,t=PI,F=.1,z=.2,w,D,q=1,x=D?1e9:0){super(a,new Vector2,h,g,b,void 0,x);this.emitSize=c;this.emitTime=d;this.emitRate=e;this.emitConeAngle=f;this.colorStartA=k;this.colorStartB=m;this.colorEndA=n;this.colorEndB=p;this.randomColorLinear=q;this.particleTime=l;this.sizeStart=u;this.sizeEnd=v;this.speed=A;this.angleSpeed=r;this.damping=B;this.angleDamping=y;this.gravityScale=C;this.particleConeAngle=t;this.fadeRate=F;this.randomness=z;this.collideTiles=w;this.additive=D;this.emitTimeBuffer=this.trailScale=0}update(){this.parent&&super.update();if(!this.emitTime||this.getAliveTime()<=this.emitTime){if(this.emitRate*particleEmitRateScale){const a=1/this.emitRate/particleEmitRateScale;for(this.emitTimeBuffer+=timeDelta;0<this.emitTimeBuffer;this.emitTimeBuffer-=a)this.emitParticle()}}else this.destroy();debugParticles&&debugRect(this.pos,vec2(this.emitSize),"#0f0",0,this.angle)}emitParticle(){var a=void 0!=this.emitSize.x?new Vector2(rand(-.5,.5),rand(-.5,.5)).multiply(this.emitSize).rotate(this.angle):randInCircle(.5*this.emitSize);a=new Particle(this.pos.add(a),this.tileIndex,this.tileSize,this.angle+rand(this.particleConeAngle,-this.particleConeAngle));const b=this.randomness;var c=n=>n+n*rand(b,-b);const d=c(this.particleTime),e=c(this.sizeStart),f=c(this.sizeEnd),h=c(this.speed);c=c(this.angleSpeed)*randSign();const g=rand(this.emitConeAngle,-this.emitConeAngle),k=randColor(this.colorStartA,this.colorStartB,this.randomColorLinear),m=randColor(this.colorEndA,this.colorEndB,this.randomColorLinear);a.colorStart=k;a.colorEndDelta=m.subtract(k);a.velocity=(new Vector2).setAngle(this.angle+g,h);a.angleVelocity=c;a.lifeTime=d;a.sizeStart=e;a.sizeEndDelta=f-e;a.fadeRate=this.fadeRate;a.damping=this.damping;a.angleDamping=this.angleDamping;a.elasticity=this.elasticity;a.friction=this.friction;a.gravityScale=this.gravityScale;a.collideTiles=this.collideTiles;a.additive=this.additive;a.renderOrder=this.renderOrder;a.trailScale=this.trailScale;a.mirror=.5>rand();a.destroyCallback=this.particleDestroyCallback;this.particleCreateCallback&&this.particleCreateCallback(a);return a}render(){}}class Particle extends EngineObject{constructor(a,b,c,d){super(a,new Vector2,b,c,d)}render(){const a=min((time-this.spawnTime)/this.lifeTime,1);var b=this.sizeStart+a*this.sizeEndDelta;b=new Vector2(b,b);var c=this.fadeRate/2;c=new Color(this.colorStart.r+a*this.colorEndDelta.r,this.colorStart.g+a*this.colorEndDelta.g,this.colorStart.b+a*this.colorEndDelta.b,(this.colorStart.a+a*this.colorEndDelta.a)*(a<c?a/c:a>1-c?(1-a)/c:1));this.additive&&setBlendMode(1);if(this.trailScale){var d=this.velocity.length();const e=this.velocity.scale(1/d);d*=this.trailScale;b.y=max(b.x,d);this.angle=e.angle();drawTile(this.pos.add(e.multiply(vec2(0,-d/2))),b,this.tileIndex,this.tileSize,c,this.angle,this.mirror)}else drawTile(this.pos,b,this.tileIndex,this.tileSize,c,this.angle,this.mirror);this.additive&&setBlendMode();debugParticles&&debugRect(this.pos,b,"#f005",0,this.angle);1==a&&(this.color=c,this.size=b,this.destroyCallback&&this.destroyCallback(this),this.destroyed=1)}}"use strict";const medals=[];let medalsPreventUnlock,newgrounds,medalsDisplayQueue=[],medalsSaveName,medalsDisplayTimeLast;function medalsInit(a){medalsSaveName=a;debugMedals||medals.forEach(b=>localStorage[b.storageKey()])}class Medal{constructor(a,b,c="",d="🏆",e){ASSERT(0<=a&&!medals[a]);medals[this.id=a]=this;this.name=b;this.description=c;this.icon=d;this.image=new Image;e&&(this.image.src=e)}unlock(){medalsPreventUnlock||this.unlocked||(ASSERT(medalsSaveName),localStorage[this.storageKey()]=this.unlocked=1,medalsDisplayQueue.push(this),newgrounds&&newgrounds.unlockMedal(this.id),localStorage["OS13kTrophy,"+this.icon+","+medalsSaveName+","+this.name]=this.description)}render(a=0){const b=overlayContext,c=min(medalDisplayWidth,mainCanvas.width),d=overlayCanvas.width-c;a*=-medalDisplayHeight;b.save();b.beginPath();b.fillStyle="#ddd";b.fill(b.rect(d,a,c,medalDisplayHeight));b.strokeStyle="#000";b.lineWidth=3;b.stroke();b.clip();this.renderIcon(d+15+medalDisplayIconSize/2,a+medalDisplayHeight/2);b.textAlign="left";b.font="38px "+fontDefault;b.fillText(this.name,d+medalDisplayIconSize+30,a+28);b.font="24px "+fontDefault;b.fillText(this.description,d+medalDisplayIconSize+30,a+60);b.restore()}renderIcon(a,b,c=medalDisplayIconSize){const d=overlayContext;d.fillStyle="#000";d.textAlign="center";d.textBaseline="middle";d.font=.7*c+"px "+fontDefault;this.image.src?d.drawImage(this.image,a-c/2,b-c/2,c,c):d.fillText(this.icon,a,b)}storageKey(){return medalsSaveName+"_"+this.id}}function medalsRender(){if(medalsDisplayQueue.length){var a=medalsDisplayQueue[0],b=timeReal-medalsDisplayTimeLast;if(medalsDisplayTimeLast)if(b>medalDisplayTime)medalsDisplayQueue.shift(medalsDisplayTimeLast=0);else{const c=medalDisplayTime-medalDisplaySlideTime;a.render(b<medalDisplaySlideTime?1-b/medalDisplaySlideTime:b>c?(b-c)/medalDisplaySlideTime:0)}else medalsDisplayTimeLast=timeReal}}class Newgrounds{constructor(a,b){ASSERT(!newgrounds&&a);this.app_id=a;this.cipher=b;this.host=location?location.hostname:"";b&&(this.cryptoJS=CryptoJS());this.session_id=new URL(location.href).searchParams.get("ngio_session_id")||0;if(0!=this.session_id){this.medals=(a=this.call("Medal.getList"))?a.result.data.medals:[];debugMedals&&console.log(this.medals);for(var c of this.medals)if(a=medals[c.id])a.image.src=c.icon,a.name=c.name,a.description=c.description,a.unlocked=c.unlocked,a.difficulty=c.difficulty,a.value=c.value,a.value&&(a.description=a.description+" ("+a.value+")");this.scoreboards=(c=this.call("ScoreBoard.getBoards"))?c.result.data.scoreboards:[];debugMedals&&console.log(this.scoreboards);setInterval(()=>this.call("Gateway.ping",0,1),3e5)}}unlockMedal(a){return this.call("Medal.unlock",{id:a},1)}postScore(a,b){return this.call("ScoreBoard.postScore",{id:a,value:b},1)}getScores(a,b=0,c=0,d=0,e=10){return this.call("ScoreBoard.getScores",{id:a,user:b,social:c,skip:d,limit:e})}logView(){return this.call("App.logView",{host:this.host},1)}call(a,b=0,c=0){a={component:a,parameters:b};if(this.cipher){b=this.cryptoJS;var d=b.enc.Base64.parse(this.cipher);const e=b.lib.WordArray.random(16);d=b.AES.encrypt(JSON.stringify(a),d,{iv:e});a.secure=b.enc.Base64.stringify(e.concat(d.ciphertext));a.parameters=0}b={app_id:this.app_id,session_id:this.session_id,call:a};a=new FormData;a.append("input",JSON.stringify(b));b=new XMLHttpRequest;b.open("POST","https://newgrounds.io/gateway_v3.php",!debugMedals&&c);b.send(a);debugMedals&&console.log(b.responseText);return b.responseText&&JSON.parse(b.responseText)}}const CryptoJS=()=>eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",..."]charCodeAtUinyxpf","for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h<t,o=o%h+(i?t:h-t)*(o>>17)-!i*t,f.map((n,x)=>(U=r[n]+=(i*h/2-r[n]<<13)/((C[n]+=C[n]<5)+1/20)>>13,a[x]+=y[x]*(i-t/h))),p=p*2+i)for(f='010202103203210431053105410642065206541'.split(t=0).map((n,x)=>(U=0,[...n].map((n,x)=>(U=U*997+(c[e-n]|0)|0)),h*32-1&U*997+p+!!A*129)*12+x);o<h*32;o=o*64|M.charCodeAt(d++)&63);for(C=String.fromCharCode(...c);r=/[\0-#?@\\\\~]/.exec(C);)with(C.split(r))C=join(shift());return C")([],[],131072,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(32768),new Uint8Array(51e6),0,0,0,0));"use strict";let glCanvas,glContext,glTileTexture,glActiveTexture,glShader,glPositionData,glColorData,glBatchCount,glBatchAdditive,glAdditive;function glInit(){if(glEnable){glCanvas=document.createElement("canvas");glContext=glCanvas.getContext("webgl",{antialias:!1});glCanvas.style=styleCanvas;glTileTexture=glCreateTexture(tileImage);glOverlay&&document.body.appendChild(glCanvas);glShader=glCreateProgram("precision highp float;uniform mat4 m;attribute vec2 p,t;attribute vec4 c,a;varying vec2 v;varying vec4 d,e;void main(){gl_Position=m*vec4(p,1,1);v=t;d=c;e=a;}","precision highp float;varying vec2 v;varying vec4 d,e;uniform sampler2D s;void main(){gl_FragColor=texture2D(s,v)*d+e;}");var a=new ArrayBuffer(gl_MAX_BATCH*gl_VERTICES_PER_QUAD*gl_VERTEX_BYTE_STRIDE);glCreateBuffer(gl_ARRAY_BUFFER,a.byteLength,gl_DYNAMIC_DRAW);glPositionData=new Float32Array(a);glColorData=new Uint32Array(a);var b=glBatchCount=0;a=(c,d,e,f,h=0)=>{c=glContext.getAttribLocation(glShader,c);glContext.enableVertexAttribArray(c);glContext.vertexAttribPointer(c,f,d,h,gl_VERTEX_BYTE_STRIDE,b);b+=f*e};a("p",gl_FLOAT,4,2);a("t",gl_FLOAT,4,2);a("c",gl_UNSIGNED_BYTE,1,4,1);a("a",gl_UNSIGNED_BYTE,1,4,1)}}function glSetBlendMode(a){glEnable&&(glAdditive=a)}function glSetTexture(a=glTileTexture){glEnable&&(a!=glActiveTexture&&glFlush(),glContext.bindTexture(gl_TEXTURE_2D,glActiveTexture=a))}function glCompileShader(a,b){if(glEnable){b=glContext.createShader(b);glContext.shaderSource(b,a);glContext.compileShader(b);if(debug&&!glContext.getShaderParameter(b,gl_COMPILE_STATUS))throw glContext.getShaderInfoLog(b);return b}}function glCreateProgram(a,b){if(glEnable){var c=glContext.createProgram();glContext.attachShader(c,glCompileShader(a,gl_VERTEX_SHADER));glContext.attachShader(c,glCompileShader(b,gl_FRAGMENT_SHADER));glContext.linkProgram(c);if(debug&&!glContext.getProgramParameter(c,gl_LINK_STATUS))throw glContext.getProgramInfoLog(c);return c}}function glCreateBuffer(a,b,c){if(glEnable){var d=glContext.createBuffer();glContext.bindBuffer(a,d);glContext.bufferData(a,b,c);return d}}function glCreateTexture(a){if(glEnable&&a&&a.width){var b=glContext.createTexture();glContext.bindTexture(gl_TEXTURE_2D,b);glContext.texImage2D(gl_TEXTURE_2D,0,gl_RGBA,gl_RGBA,gl_UNSIGNED_BYTE,a);glContext.texParameteri(gl_TEXTURE_2D,gl_TEXTURE_MIN_FILTER,cavasPixelated?gl_NEAREST:gl_LINEAR);glContext.texParameteri(gl_TEXTURE_2D,gl_TEXTURE_MAG_FILTER,cavasPixelated?gl_NEAREST:gl_LINEAR);glContext.texParameteri(gl_TEXTURE_2D,gl_TEXTURE_WRAP_S,gl_CLAMP_TO_EDGE);glContext.texParameteri(gl_TEXTURE_2D,gl_TEXTURE_WRAP_T,gl_CLAMP_TO_EDGE);return b}}function glPreRender(a,b,c,d,e){glEnable&&(glContext.viewport(0,0,glCanvas.width=a,glCanvas.height=b),glContext.clear(gl_COLOR_BUFFER_BIT),glContext.bindTexture(gl_TEXTURE_2D,glActiveTexture=glTileTexture),glContext.useProgram(glShader),glSetBlendMode(),a=2*e/a,b=2*e/b,glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader,"m"),0,new Float32Array([a,0,0,0,0,b,0,0,1,1,-1,1,-1-a*c,-1-b*d,0,0])))}function glFlush(){if(glEnable&&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(glEnable&&glBatchCount||b)glFlush(),glOverlay&&!b||a.drawImage(glCanvas,0,0)}function glDraw(a,b,c,d,e,f,h,g,k,m=4294967295,n=0){if(glEnable){glBatchCount!=gl_MAX_BATCH&&glBatchAdditive==glAdditive||glFlush();var p=Math.cos(e)/2,l=Math.sin(e)/2;e=p*c;p*=d;c*=l;d*=l;l=glBatchCount++*gl_VERTICES_PER_QUAD*gl_INDICIES_PER_VERT;glPositionData[l++]=a-e-d;glPositionData[l++]=b-p+c;glPositionData[l++]=f;glPositionData[l++]=k;glColorData[l++]=m;glColorData[l++]=n;glPositionData[l++]=a+e+d;glPositionData[l++]=b+p-c;glPositionData[l++]=g;glPositionData[l++]=h;glColorData[l++]=m;glColorData[l++]=n;glPositionData[l++]=a-e+d;glPositionData[l++]=b+p+c;glPositionData[l++]=f;glPositionData[l++]=h;glColorData[l++]=m;glColorData[l++]=n;glPositionData[l++]=a-e-d;glPositionData[l++]=b-p+c;glPositionData[l++]=f;glPositionData[l++]=k;glColorData[l++]=m;glColorData[l++]=n;glPositionData[l++]=a+e-d;glPositionData[l++]=b-p-c;glPositionData[l++]=g;glPositionData[l++]=k;glColorData[l++]=m;glColorData[l++]=n;glPositionData[l++]=a+e+d;glPositionData[l++]=b+p-c;glPositionData[l++]=g;glPositionData[l++]=h;glColorData[l++]=m;glColorData[l++]=n}}const gl_ONE=1,gl_TRIANGLES=4,gl_SRC_ALPHA=770,gl_ONE_MINUS_SRC_ALPHA=771,gl_BLEND=3042,gl_TEXTURE_2D=3553,gl_UNSIGNED_BYTE=5121,gl_FLOAT=5126,gl_RGBA=6408,gl_NEAREST=9728,gl_LINEAR=9729,gl_TEXTURE_MAG_FILTER=10240,gl_TEXTURE_MIN_FILTER=10241,gl_TEXTURE_WRAP_S=10242,gl_TEXTURE_WRAP_T=10243,gl_COLOR_BUFFER_BIT=16384,gl_CLAMP_TO_EDGE=33071,gl_ARRAY_BUFFER=34962,gl_DYNAMIC_DRAW=35048,gl_FRAGMENT_SHADER=35632,gl_VERTEX_SHADER=35633,gl_COMPILE_STATUS=35713,gl_LINK_STATUS=35714,gl_VERTICES_PER_QUAD=6,gl_INDICIES_PER_VERT=6,gl_MAX_BATCH=65536,gl_VERTEX_BYTE_STRIDE=24;
1
+ let showWatermark=0,godMode=0;const debug=0,debugOverlay=0,debugPhysics=0,debugParticles=0,debugRaycast=0,debugGamepads=0,debugMedals=0,ASSERT=()=>{},debugInit=()=>{},debugUpdate=()=>{},debugRender=()=>{},debugRect=()=>{},debugCircle=()=>{},debugPoint=()=>{},debugLine=()=>{},debugAABB=()=>{},debugText=()=>{},debugClear=()=>{},debugSaveCanvas=()=>{};"use strict";const PI=Math.PI,abs=a=>0>a?-a:a,min=(a,b)=>a<b?a:b,max=(a,b)=>a>b?a:b,sign=a=>0>a?-1:1,mod=(a,b=1)=>(a%b+b)%b,clamp=(a,b=0,c=1)=>a<b?b:a>c?c:a,percent=(a,b=0,c=1)=>c-b?clamp((a-b)/(c-b)):0,lerp=(a,b=0,c=1)=>b+clamp(a)*(c-b),smoothStep=a=>a*a*(3-2*a),nearestPowerOfTwo=a=>2**Math.ceil(Math.log2(a)),isOverlapping=(a,b,c,d)=>2*abs(a.x-c.x)<b.x+d.x&2*abs(a.y-c.y)<b.y+d.y,wave=(a=1,b=1,c=time)=>b/2*(1-Math.cos(c*a*2*PI)),formatTime=a=>(a/60|0)+":"+(10>a%60?"0":"")+(a%60|0),rand=(a=1,b=0)=>b+(a-b)*Math.random(),randInt=(a=1,b=0)=>rand(a,b)|0,randSign=()=>2*(rand(2)|0)-1,randInCircle=(a=1,b=0)=>0<a?randVector(a*rand(b/a,1)**.5):new Vector2,randVector=(a=1)=>(new Vector2).setAngle(rand(2*PI),a),randColor=(a=new Color,b=new Color(0,0,0,1),c)=>c?a.lerp(b,rand()):new Color(rand(a.r,b.r),rand(a.g,b.g),rand(a.b,b.b),rand(a.a,b.a));let randSeed=1;const randSeeded=(a=1,b=0)=>{randSeed^=randSeed<<13;randSeed^=randSeed>>>17;randSeed^=randSeed<<5;return b+(a-b)*abs(randSeed%1e9)/1e9},vec2=(a=0,b)=>void 0==a.x?new Vector2(a,void 0==b?a:b):new Vector2(a.x,a.y);class Vector2{constructor(a=0,b=0){this.x=a;this.y=b}copy(){return new Vector2(this.x,this.y)}add(a){ASSERT(void 0!=a.x);return new Vector2(this.x+a.x,this.y+a.y)}subtract(a){ASSERT(void 0!=a.x);return new Vector2(this.x-a.x,this.y-a.y)}multiply(a){ASSERT(void 0!=a.x);return new Vector2(this.x*a.x,this.y*a.y)}divide(a){ASSERT(void 0!=a.x);return new Vector2(this.x/a.x,this.y/a.y)}scale(a){ASSERT(void 0==a.x);return new Vector2(this.x*a,this.y*a)}length(){return this.lengthSquared()**.5}lengthSquared(){return this.x**2+this.y**2}distance(a){return this.distanceSquared(a)**.5}distanceSquared(a){return(this.x-a.x)**2+(this.y-a.y)**2}normalize(a=1){const b=this.length();return b?this.scale(a/b):new Vector2(0,a)}clampLength(a=1){const b=this.length();return b>a?this.scale(a/b):this}dot(a){ASSERT(void 0!=a.x);return this.x*a.x+this.y*a.y}cross(a){ASSERT(void 0!=a.x);return this.x*a.y-this.y*a.x}angle(){return Math.atan2(this.x,this.y)}setAngle(a=0,b=1){this.x=b*Math.sin(a);this.y=b*Math.cos(a);return this}rotate(a){const b=Math.cos(a);a=Math.sin(a);return new Vector2(this.x*b-this.y*a,this.x*a+this.y*b)}direction(){return abs(this.x)>abs(this.y)?0>this.x?3:1:0>this.y?2:0}invert(){return new Vector2(this.y,-this.x)}floor(){return new Vector2(Math.floor(this.x),Math.floor(this.y))}area(){return abs(this.x*this.y)}lerp(a,b){ASSERT(void 0!=a.x);return this.add(a.subtract(this).scale(clamp(b)))}arrayCheck(a){return 0<=this.x&&0<=this.y&&this.x<a.x&&this.y<a.y}toString(a=3){return`(${(0>this.x?"":" ")+this.x.toFixed(a)},${(0>this.y?"":" ")+this.y.toFixed(a)} )`}}class Color{constructor(a=1,b=1,c=1,d=1){this.r=a;this.g=b;this.b=c;this.a=d}copy(){return new Color(this.r,this.g,this.b,this.a)}add(a){return new Color(this.r+a.r,this.g+a.g,this.b+a.b,this.a+a.a)}subtract(a){return new Color(this.r-a.r,this.g-a.g,this.b-a.b,this.a-a.a)}multiply(a){return new Color(this.r*a.r,this.g*a.g,this.b*a.b,this.a*a.a)}divide(a){return new Color(this.r/a.r,this.g/a.g,this.b/a.b,this.a/a.a)}scale(a,b=a){return new Color(this.r*a,this.g*a,this.b*a,this.a*b)}clamp(){return new Color(clamp(this.r),clamp(this.g),clamp(this.b),clamp(this.a))}lerp(a,b){return this.add(a.subtract(this).scale(clamp(b)))}setHSLA(a=0,b=0,c=1,d=1){b=.5>c?c*(1+b):c+b-c*b;c=2*c-b;const e=(f,h,g)=>(g=(g%1+1)%1)<1/6?f+6*(h-f)*g:.5>g?h:g<2/3?f+(h-f)*(2/3-g)*6:f;this.r=e(c,b,a+1/3);this.g=e(c,b,a);this.b=e(c,b,a-1/3);this.a=d;return this}getHSLA(){const a=this.r,b=this.g,c=this.b,d=this.a,e=Math.max(a,b,c),f=Math.min(a,b,c),h=(e+f)/2;let g=0,k=0;if(e!=f){let m=e-f;k=.5<h?m/(2-e-f):m/(e+f);a==e?g=(b-c)/m+(b<c?6:0):b==e?g=(c-a)/m+2:c==e&&(g=(a-b)/m+4)}return[g/6,k,h,d]}mutate(a=.05,b=0){return new Color(this.r+rand(a,-a),this.g+rand(a,-a),this.b+rand(a,-a),this.a+rand(b,-b)).clamp()}toString(){ASSERT(0<=this.r&&1>=this.r&&0<=this.g&&1>=this.g&&0<=this.b&&1>=this.b&&0<=this.a&&1>=this.a);return`rgb(${255*this.r|0},${255*this.g|0},${255*this.b|0},${this.a})`}rgbaInt(){ASSERT(0<=this.r&&1>=this.r&&0<=this.g&&1>=this.g&&0<=this.b&&1>=this.b&&0<=this.a&&1>=this.a);return(255*this.r|0)+(255*this.g<<8)+(255*this.b<<16)+(255*this.a<<24)}setHex(a){this.r=parseInt(a.slice(1,3),16)/255;this.g=parseInt(a.slice(3,5),16)/255;this.b=parseInt(a.slice(5,7),16)/255;this.a=1;ASSERT(0<=this.r&&1>=this.r&&0<=this.g&&1>=this.g&&0<=this.b&&1>=this.b);return this}getHex(){const a=b=>(16>(b=255*b|0)?"0":"")+b.toString(16);return"#"+a(this.r)+a(this.g)+a(this.b)}}class Timer{constructor(a){this.time=void 0==a?void 0:time+a;this.setTime=a}set(a=0){this.time=time+a;this.setTime=a}unset(){this.time=void 0}isSet(){return void 0!=this.time}active(){return time<=this.time}elapsed(){return time>this.time}get(){return this.isSet()?time-this.time:0}getPercent(){return this.isSet()?percent(this.time-time,this.setTime,0):0}toString(){if(debug)return this.unset()?"unset":Math.abs(this.get())+" seconds "+(0>this.get()?"before":"after")}}"use strict";let canvasMaxSize=vec2(1920,1200),canvasFixedSize=vec2(),cavasPixelated=1,fontDefault="arial",tileSizeDefault=vec2(16),tileFixBleedScale=.3,objectDefaultSize=vec2(1),enablePhysicsSolver=1,objectDefaultMass=1,objectDefaultDamping=.99,objectDefaultAngleDamping=.99,objectDefaultElasticity=0,objectDefaultFriction=.8,objectMaxSpeed=1,gravity=0,particleEmitRateScale=1,cameraPos=vec2(),cameraScale=max(tileSizeDefault.x,tileSizeDefault.y),glEnable=1,glOverlay=1,gamepadsEnable=1,gamepadDirectionEmulateStick=1,inputWASDEmulateDirection=1,touchGamepadEnable=0,touchGamepadAnalog=1,touchGamepadSize=80,touchGamepadAlpha=.3,vibrateEnable=1,soundVolume=.5,soundEnable=1,soundDefaultRange=30,soundDefaultTaper=.7,medalDisplayTime=5,medalDisplaySlideTime=.5,medalDisplayWidth=640,medalDisplayHeight=80,medalDisplayIconSize=50;"use strict";const engineName="LittleJS",engineVersion="1.3.8",frameRate=60,timeDelta=1/frameRate;let engineObjects=[],engineObjectsCollide=[],frame=0,time=0,timeReal=0,paused=0,frameTimeLastMS=0,frameTimeBufferMS=0,tileImageSize,tileImageFixBleed,averageFPS,drawCount;const styleBody="margin:0;overflow:hidden;background:#000;touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none",styleCanvas="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)";function engineInit(a,b,c,d,e,f){tileImage.onerror=tileImage.onload=()=>{tileImageFixBleed=vec2(tileFixBleedScale).divide(tileImageSize=vec2(tileImage.width,tileImage.height));debug&&(tileImage.onload=()=>ASSERT(1));document.body.style=styleBody;document.body.appendChild(mainCanvas=document.createElement("canvas"));mainContext=mainCanvas.getContext("2d");mainCanvas.style=styleCanvas;debugInit();glInit();document.body.appendChild(overlayCanvas=document.createElement("canvas"));overlayContext=overlayCanvas.getContext("2d");overlayCanvas.style=styleCanvas;a();touchGamepadCreate();h()};const h=(g=0)=>{var k=g-frameTimeLastMS;frameTimeLastMS=g;if(debug||showWatermark)averageFPS=lerp(.05,averageFPS||0,1e3/(k||1));debug&&(k*=keyIsDown(107)?5:keyIsDown(109)?.2:1);timeReal+=k/1e3;frameTimeBufferMS=min(frameTimeBufferMS+!paused*k,50);canvasFixedSize.x?(overlayCanvas.width=mainCanvas.width=canvasFixedSize.x,overlayCanvas.height=mainCanvas.height=canvasFixedSize.y,g=innerWidth/innerHeight,k=mainCanvas.width/mainCanvas.height,mainCanvas.style.width=overlayCanvas.style.width=g<k?"100%":"",mainCanvas.style.height=overlayCanvas.style.height=g<k?"":"100%",glCanvas&&(glCanvas.style.width=mainCanvas.style.width,glCanvas.style.height=mainCanvas.style.height)):(overlayCanvas.width=mainCanvas.width=min(innerWidth,canvasMaxSize.x),overlayCanvas.height=mainCanvas.height=min(innerHeight,canvasMaxSize.y));mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height);if(paused)inputUpdate(),debugUpdate(),c(),inputUpdatePost();else{g=0;0>frameTimeBufferMS&&-9<frameTimeBufferMS&&(g=frameTimeBufferMS,frameTimeBufferMS=0);for(;0<=frameTimeBufferMS;frameTimeBufferMS-=1e3/frameRate)inputUpdate(),b(),engineObjectsUpdate(),debugUpdate(),c(),inputUpdatePost();frameTimeBufferMS+=g}enginePreRender();d();engineObjects.sort((n,p)=>n.renderOrder-p.renderOrder);for(var m of engineObjects)m.destroyed||m.render();e();medalsRender();touchGamepadRender();debugRender();glCopyToContext(mainContext);showWatermark&&(overlayContext.textAlign="right",overlayContext.textBaseline="top",overlayContext.font="1em monospace",overlayContext.fillStyle="#000",m=engineName+" v"+engineVersion+" / "+drawCount+" / "+engineObjects.length+" / "+averageFPS.toFixed(1)+" "+(glEnable?"GL":"2D"),overlayContext.fillText(m,mainCanvas.width-3,3),overlayContext.fillStyle="#fff",overlayContext.fillText(m,mainCanvas.width-2,2),drawCount=0);requestAnimationFrame(h)};f?tileImage.src=f:tileImage.onload()}function enginePreRender(){mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height);mainContext.imageSmoothingEnabled=!cavasPixelated;glPreRender(mainCanvas.width,mainCanvas.height,cameraPos.x,cameraPos.y,cameraScale)}function engineObjectsUpdate(){engineObjectsCollide=engineObjects.filter(b=>b.collideSolidObjects);const a=b=>{if(!b.destroyed){b.update();for(const c of b.children)a(c)}};for(const b of engineObjects)b.parent||a(b);engineObjects=engineObjects.filter(b=>!b.destroyed);time=++frame/frameRate}function engineObjectsDestroy(){for(const a of engineObjects)a.parent||a.destroy();engineObjects=engineObjects.filter(a=>!a.destroyed)}function engineObjectsCallback(a,b,c,d=engineObjects){if(a)if(void 0!=b.x)for(const e of d)isOverlapping(a,b,e.pos,e.size)&&c(e);else{b*=b;for(const e of d)a.distanceSquared(e.pos)<b&&c(e)}else for(const e of d)c(e)}"use strict";class EngineObject{constructor(a=vec2(),b=objectDefaultSize,c=-1,d=tileSizeDefault,e=0,f,h=0){ASSERT(a&&void 0!=a.x&&void 0!=b.x);this.pos=a.copy();this.size=b;this.drawSize;this.tileIndex=c;this.tileSize=d;this.angle=e;this.color=f;this.additiveColor;this.mass=objectDefaultMass;this.damping=objectDefaultDamping;this.angleDamping=objectDefaultAngleDamping;this.elasticity=objectDefaultElasticity;this.friction=objectDefaultFriction;this.gravityScale=1;this.renderOrder=h;this.velocity=new Vector2;this.angleVelocity=0;this.spawnTime=time;this.children=[];this.collideTiles=1;engineObjects.push(this)}update(){var a=this.parent;if(a)this.pos=this.localPos.multiply(vec2(a.getMirrorSign(),1)).rotate(-a.angle).add(a.pos),this.angle=a.getMirrorSign()*this.localAngle+a.angle;else if(this.velocity.x=clamp(this.velocity.x,-objectMaxSpeed,objectMaxSpeed),this.velocity.y=clamp(this.velocity.y,-objectMaxSpeed,objectMaxSpeed),a=this.pos.copy(),this.pos.x+=this.velocity.x*=this.damping,this.pos.y+=this.velocity.y=this.damping*this.velocity.y+gravity*this.gravityScale,this.angle+=this.angleVelocity*=this.angleDamping,ASSERT(0<=this.angleDamping&&1>=this.angleDamping),ASSERT(0<=this.damping&&1>=this.damping),enablePhysicsSolver&&this.mass){var b=0>this.velocity.y;if(this.groundObject){var c=this.groundObject.velocity?this.groundObject.velocity.x:0;this.velocity.x=c+(this.velocity.x-c)*this.friction;this.groundObject=0}if(this.collideSolidObjects)for(var d of engineObjectsCollide)if(!(!this.isSolid&!d.isSolid||d.destroyed||d.parent||d==this||!isOverlapping(this.pos,this.size,d.pos,d.size)||!this.collideWithObject(d)|!d.collideWithObject(this)))if(isOverlapping(a,this.size,d.pos,d.size)){c=a.subtract(d.pos);var e=c.length();c=.01>e?randVector(.001):c.scale(.001/e);this.velocity=this.velocity.add(c);d.mass&&(d.velocity=d.velocity.subtract(c));debugOverlay&&debugPhysics&&debugAABB(this.pos,this.size,d.pos,d.size,"#f00")}else{c=this.size.add(d.size);e=2*(a.y-d.pos.y)>c.y+gravity;var f=2*abs(a.y-d.pos.y)<c.y,h=2*abs(a.x-d.pos.x)<c.x;if(e||h||!f)if(this.pos.y=d.pos.y+(c.y/2+.001)*sign(a.y-d.pos.y),d.groundObject&&b||!d.mass)b&&(this.groundObject=d),this.velocity.y*=-this.elasticity;else if(d.mass){const g=(this.mass*this.velocity.y+d.mass*d.velocity.y)/(this.mass+d.mass),k=this.velocity.y*(this.mass-d.mass)/(this.mass+d.mass)+2*d.velocity.y*d.mass/(this.mass+d.mass),m=d.velocity.y*(d.mass-this.mass)/(this.mass+d.mass)+2*this.velocity.y*this.mass/(this.mass+d.mass),n=max(this.elasticity,d.elasticity);this.velocity.y=lerp(n,g,k);d.velocity.y=lerp(n,g,m)}e||!f&&h||(this.pos.x=d.pos.x+(c.x/2+.001)*sign(a.x-d.pos.x),d.mass?(c=(this.mass*this.velocity.x+d.mass*d.velocity.x)/(this.mass+d.mass),e=this.velocity.x*(this.mass-d.mass)/(this.mass+d.mass)+2*d.velocity.x*d.mass/(this.mass+d.mass),f=d.velocity.x*(d.mass-this.mass)/(this.mass+d.mass)+2*this.velocity.x*this.mass/(this.mass+d.mass),h=max(this.elasticity,d.elasticity),this.velocity.x=lerp(h,c,e),d.velocity.x=lerp(h,c,f)):this.velocity.x*=-this.elasticity);debugOverlay&&debugPhysics&&debugAABB(this.pos,this.size,d.pos,d.size,"#f0f")}if(this.collideTiles&&tileCollisionTest(this.pos,this.size,this)&&!tileCollisionTest(a,this.size,this)){c=tileCollisionTest(new Vector2(a.x,this.pos.y),this.size,this);d=tileCollisionTest(new Vector2(this.pos.x,a.y),this.size,this);if(c||!d)this.groundObject=b,this.velocity.y*=-this.elasticity,b=(a.y-this.size.y/2|0)-(a.y-this.size.y/2),0>b&&b>this.damping*this.velocity.y+gravity*this.gravityScale&&(this.velocity.y=this.damping?(b-gravity*this.gravityScale)/this.damping:0),this.pos.y=a.y;d&&(this.pos.x=a.x,this.velocity.x*=-this.elasticity)}}}render(){drawTile(this.pos,this.drawSize||this.size,this.tileIndex,this.tileSize,this.color,this.angle,this.mirror,this.additiveColor)}destroy(){if(!this.destroyed){this.destroyed=1;this.parent&&this.parent.removeChild(this);for(const a of this.children)a.destroy(a.parent=0)}}collideWithTile(a,b){return 0<a}collideWithTileRaycast(a,b){return 0<a}collideWithObject(a){return 1}getAliveTime(){return time-this.spawnTime}applyAcceleration(a){this.mass&&(this.velocity=this.velocity.add(a))}applyForce(a){this.applyAcceleration(a.scale(1/this.mass))}getMirrorSign(){return this.mirror?-1:1}addChild(a,b=vec2(),c=0){ASSERT(!a.parent&&!this.children.includes(a));this.children.push(a);a.parent=this;a.localPos=b.copy();a.localAngle=c}removeChild(a){ASSERT(a.parent==this&&this.children.includes(a));this.children.splice(this.children.indexOf(a),1);a.parent=0}setCollision(a=0,b=0,c=1){ASSERT(a||!b);this.collideSolidObjects=a;this.isSolid=b;this.collideTiles=c}toString(){if(debug){let a="type = "+this.constructor.name;if(this.pos.x||this.pos.y)a+="\npos = "+this.pos;if(this.velocity.x||this.velocity.y)a+="\nvelocity = "+this.velocity;if(this.size.x||this.size.y)a+="\nsize = "+this.size;this.angle&&(a+="\nangle = "+this.angle.toFixed(3));this.color&&(a+="\ncolor = "+this.color);return a}}}"use strict";const tileImage=new Image;let mainCanvas,mainContext,overlayCanvas,overlayContext,mainCanvasSize=vec2();const screenToWorld=a=>{ASSERT(mainCanvasSize.x&&mainCanvasSize.y,"mainCanvasSize is invalid");return a.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos)},worldToScreen=a=>{ASSERT(mainCanvasSize.x&&mainCanvasSize.y,"mainCanvasSize is invalid");return a.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5))};function drawTile(a,b=vec2(1),c=-1,d=tileSizeDefault,e=new Color,f=0,h,g=new Color(0,0,0,0),k=glEnable){showWatermark&&++drawCount;if(glEnable&&k)if(0>c||!tileImage.width)glDraw(a.x,a.y,b.x,b.y,f,0,0,0,0,0,e.rgbaInt());else{var m=tileImageSize.x/d.x|0;k=d.x/tileImageSize.x;const n=d.y/tileImageSize.y,p=c%m*k;m=(c/m|0)*n;glDraw(a.x,a.y,h?-b.x:b.x,b.y,f,p+tileImageFixBleed.x,m+tileImageFixBleed.y,p-tileImageFixBleed.x+k,m-tileImageFixBleed.y+n,e.rgbaInt(),g.rgbaInt())}else drawCanvas2D(a,b,f,h,n=>{if(0>c)n.fillStyle=e,n.fillRect(-.5,-.5,1,1);else{var p=tileImageSize.x/d.x|0;const l=c%p*d.x+tileFixBleedScale;p=(c/p|0)*d.y+tileFixBleedScale;const u=d.x-2*tileFixBleedScale,v=d.y-2*tileFixBleedScale;n.globalAlpha=e.a;n.drawImage(tileImage,l,p,u,v,-.5,-.5,1,1)}})}function drawRect(a,b,c,d,e){drawTile(a,b,-1,tileSizeDefault,c,d,0,0,e)}function drawTileScreenSpace(a,b=vec2(1),c,d,e,f,h,g,k){drawTile(screenToWorld(a),b.scale(1/cameraScale),c,d,e,f,h,g,k)}function drawRectScreenSpace(a,b,c,d,e){drawTileScreenSpace(a,b,-1,tileSizeDefault,c,d,0,0,e)}function drawLine(a,b,c=.1,d,e){b=vec2((b.x-a.x)/2,(b.y-a.y)/2);c=vec2(c,2*b.length());drawRect(a.add(b),c,d,b.angle(),0,0,e)}function drawCanvas2D(a,b,c,d,e,f=mainContext){a=worldToScreen(a);b=b.scale(cameraScale);f.save();f.translate(a.x+.5|0,a.y-.5|0);f.rotate(c);f.scale(d?-b.x:b.x,b.y);e(f);f.restore()}function setBlendMode(a,b=glEnable){glEnable&&b?glSetBlendMode(a):mainContext.globalCompositeOperation=a?"lighter":"source-over"}function drawTextScreen(a,b,c=1,d=new Color,e=0,f=new Color(0,0,0),h="center",g=fontDefault){overlayContext.fillStyle=d;overlayContext.lineWidth=e;overlayContext.strokeStyle=f;overlayContext.textAlign=h;overlayContext.font=c+"px "+g;overlayContext.textBaseline="middle";overlayContext.lineJoin="round";b=b.copy();(a+"").split("\n").forEach(k=>{e&&overlayContext.strokeText(k,b.x,b.y);overlayContext.fillText(k,b.x,b.y);b.y+=c})}function drawText(a,b,c=1,d,e,f,h,g){drawTextScreen(a,worldToScreen(b),c*cameraScale,d,e*cameraScale,f,h,g)}let engineFontImage;class FontImage{constructor(a,b=vec2(8),c=vec2(0,1),d=0,e=overlayContext){a||engineFontImage||(engineFontImage=new Image,engineFontImage.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC");this.image=a||engineFontImage;this.tileSize=b;this.paddingSize=c;this.startTileIndex=d;this.context=e}drawTextScreen(a,b,c=4,d){const e=this.context;e.save();e.imageSmoothingEnabled=!cavasPixelated;const f=this.tileSize,h=f.add(this.paddingSize).scale(c),g=this.image.width/this.tileSize.x|0;a.split("\n").forEach((k,m)=>{const n=d?k.length*f.x*c/2|0:0;for(let u=k.length;u--;){var p=k[u].charCodeAt();if(32>p||127<p)p=127;var l=this.startTileIndex+p-32;p=l%g;l=l/g|0;const v=b.add(vec2(u,m).multiply(h));e.drawImage(this.image,p*f.x,l*f.y,f.x,f.y,v.x-n,v.y,f.x*c,f.y*c)}});e.restore()}drawText(a,b,c=1,d){this.drawTextScreen(a,worldToScreen(b).floor(),c*cameraScale|0,d)}}const isFullscreen=()=>document.fullscreenElement;function toggleFullscreen(){isFullscreen()?document.exitFullscreen?document.exitFullscreen():document.mozCancelFullScreen&&document.mozCancelFullScreen():document.body.webkitRequestFullScreen?document.body.webkitRequestFullScreen():document.body.mozRequestFullScreen&&document.body.mozRequestFullScreen()}"use strict";const keyIsDown=(a,b=0)=>inputData[b]&&inputData[b][a]&1?1:0,keyWasPressed=(a,b=0)=>inputData[b]&&inputData[b][a]&2?1:0,keyWasReleased=(a,b=0)=>inputData[b]&&inputData[b][a]&4?1:0,clearInput=()=>inputData=[[]],mouseIsDown=keyIsDown,mouseWasPressed=keyWasPressed,mouseWasReleased=keyWasReleased;let mousePos=vec2(),mousePosScreen=vec2(),mouseWheel=0,isUsingGamepad=0,preventDefaultInput=0;const gamepadIsDown=(a,b=0)=>keyIsDown(a,b+1),gamepadWasPressed=(a,b=0)=>keyWasPressed(a,b+1),gamepadWasReleased=(a,b=0)=>keyWasReleased(a,b+1),gamepadStick=(a,b=0)=>stickData[b]?stickData[b][a]||vec2():vec2();let inputData=[[]];function inputUpdate(){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.getGamepads&&(document.hasFocus()||debug))for(a=navigator.getGamepads(),b=a.length;b--;){var d=a[b];const h=inputData[b+1]||(inputData[b+1]=[]);c=stickData[b]||(stickData[b]=[]);if(d){var e=g=>.3<g?percent(g,.3,.8):-.3>g?-percent(-g,.3,.8):0;for(var f=0;f<d.axes.length-1;f+=2)c[f>>1]=vec2(e(d.axes[f]),e(-d.axes[f+1])).clampLength();for(e=d.buttons.length;e--;)f=d.buttons[e],h[e]=f.pressed?1+2*!gamepadIsDown(e,b):4*gamepadIsDown(e,b),isUsingGamepad|=!b&&f.pressed,touchGamepadEnable&&touchGamepadTimer.unset();gamepadDirectionEmulateStick&&(d=vec2(gamepadIsDown(15,b)-gamepadIsDown(14,b),gamepadIsDown(12,b)-gamepadIsDown(13,b)),d.lengthSquared()&&(c[0]=d.clampLength()))}}}const vibrate=a=>vibrateEnable&&Navigator.vibrate&&Navigator.vibrate(a),vibrateStop=()=>vibrate(0),isTouchDevice=void 0!==window.ontouchstart;if(isTouchDevice){let a,b;ontouchstart=ontouchmove=ontouchend=c=>{c.button=0;const d=c.touches.length;d?(b||zzfx(0,b=1),c.x=c.touches[0].clientX,c.y=c.touches[0].clientY,a?onmousemove(c):onmousedown(c)):a&&onmouseup(c);a=d;return!c.cancelable}}let touchGamepadTimer=new Timer,touchGamepadButtons=[],touchGamepadStick=vec2();function touchGamepadCreate(){touchGamepadEnable&&isTouchDevice&&(ontouchstart=ontouchmove=ontouchend=a=>{if(touchGamepadEnable){touchGamepadStick=vec2();touchGamepadButtons=[];if(a.touches.length&&(touchGamepadTimer.isSet()||zzfx(0),isUsingGamepad=1,touchGamepadTimer.set(),paused)){touchGamepadButtons[9]=1;return}var b=vec2(touchGamepadSize,mainCanvasSize.y-touchGamepadSize),c=mainCanvasSize.subtract(vec2(touchGamepadSize,touchGamepadSize)),d=mainCanvasSize.scale(.5);for(const e of a.touches)a=mouseToScreen(vec2(e.clientX,e.clientY)),a.distance(b)<touchGamepadSize?touchGamepadAnalog?touchGamepadStick=a.subtract(b).scale(2/touchGamepadSize).clampLength():(a=a.subtract(b).angle(),touchGamepadStick.setAngle((4*a/PI+8.5|0)*PI/4)):a.distance(c)<touchGamepadSize?(a=a.subtract(c).direction(),touchGamepadButtons[a]=1):a.distance(d)<touchGamepadSize&&(touchGamepadButtons[9]=1)}})}function touchGamepadRender(){if(touchGamepadEnable&&touchGamepadTimer.isSet()){var a=percent(touchGamepadTimer.get(),4,3);if(a&&!paused){overlayContext.save();overlayContext.globalAlpha=a*touchGamepadAlpha;overlayContext.strokeStyle="#fff";overlayContext.lineWidth=3;overlayContext.fillStyle=0<touchGamepadStick.lengthSquared()?"#fff":"#000";overlayContext.beginPath();a=vec2(touchGamepadSize,mainCanvasSize.y-touchGamepadSize);if(touchGamepadAnalog)overlayContext.arc(a.x,a.y,touchGamepadSize/2,0,9),overlayContext.fill();else for(var b=10;b--;){var c=b*PI/4;overlayContext.arc(a.x,a.y,.6*touchGamepadSize,c+PI/8,c+PI/8);b%2&&overlayContext.arc(a.x,a.y,.33*touchGamepadSize,c,c);1==b&&overlayContext.fill()}overlayContext.stroke();a=vec2(mainCanvasSize.x-touchGamepadSize,mainCanvasSize.y-touchGamepadSize);for(b=4;b--;)c=a.add((new Vector2).setAngle(b*PI/2,touchGamepadSize/2)),overlayContext.fillStyle=touchGamepadButtons[b]?"#fff":"#000",overlayContext.beginPath(),overlayContext.arc(c.x,c.y,touchGamepadSize/4,0,9),overlayContext.fill(),overlayContext.stroke();overlayContext.restore()}}}"use strict";class Sound{constructor(a,b=soundDefaultRange,c=soundDefaultTaper){soundEnable&&(this.range=b,this.taper=c,this.randomness=a[1]||0,a[1]=0,this.cachedSamples=zzfxG(...a))}play(a,b=1,c=1,d=1){if(soundEnable){var e=0;if(a){if(e=this.range){const f=cameraPos.distanceSquared(a);if(f>e*e)return;b*=percent(f**.5,e,e*this.taper)}e=2*worldToScreen(a).x/mainCanvas.width-1}a=c+c*this.randomness*d*rand(-1,1);return playSamples([this.cachedSamples],b,a,e)}}playNote(a,b,c=1){if(soundEnable)return this.play(b,c,2**(a/12),0)}}class Music{constructor(a){soundEnable&&(this.cachedSamples=zzfxM(...a))}play(a=1,b=1){if(soundEnable)return playSamples(this.cachedSamples,a,1,0,b)}}function playAudioFile(a,b=1,c=1){if(soundEnable)return a=new Audio(a),a.volume=soundVolume*b,a.loop=c,a.play(),a}function speak(a,b="",c=1,d=1,e=1){if(soundEnable&&speechSynthesis)return a=new SpeechSynthesisUtterance(a),a.lang=b,a.volume=2*c*soundVolume,a.rate=d,a.pitch=e,speechSynthesis.speak(a),a}const speakStop=()=>speechSynthesis&&speechSynthesis.cancel(),getNoteFrequency=(a,b=220)=>b*2**(a/12);let audioContext;function playSamples(a,b=1,c=1,d=0,e=0){if(soundEnable&&(audioContext||(audioContext=new(window.AudioContext||webkitAudioContext)),audioContext.resume(),"running"==audioContext.state)){var f=audioContext.createBuffer(a.length,a[0].length,zzfxR),h=audioContext.createBufferSource();a.forEach((g,k)=>f.getChannelData(k).set(g));h.buffer=f;h.playbackRate.value=c;h.loop=e;a=audioContext.createGain();a.gain.value=soundVolume*b;a.connect(audioContext.destination);(window.StereoPannerNode?h.connect(new StereoPannerNode(audioContext,{pan:clamp(d,-1,1)})):h).connect(a);h.start();return h}}const zzfx=(...a)=>playSamples([zzfxG(...a)]),zzfxR=44100;function zzfxG(a=1,b=.05,c=220,d=0,e=0,f=.1,h=0,g=1,k=0,m=0,n=0,p=0,l=0,u=0,v=0,A=0,r=0,B=1,y=0,C=0){let t=2*PI,F=k*=500*t/zzfxR/zzfxR,z=[];b=c*=(1+b*rand(-1,1))*t/zzfxR;let w=0,D=0,q=0,x=1,H=0,I=0,E=0,J,G;d=d*zzfxR+9;y*=zzfxR;e*=zzfxR;f*=zzfxR;r*=zzfxR;m*=500*t/zzfxR**3;v*=t/zzfxR;n*=t/zzfxR;p*=zzfxR;l=l*zzfxR|0;for(G=d+y+e+f+r|0;q<G;z[q++]=E)++I%(100*A|0)||(E=h?1<h?2<h?3<h?Math.sin((w%t)**3):max(min(Math.tan(w),1),-1):1-(2*w/t%2+2)%2:1-4*abs(Math.round(w/t)-w/t):Math.sin(w),E=(l?1-C+C*Math.sin(t*q/l):1)*sign(E)*abs(E)**g*a*soundVolume*(q<d?q/d:q<d+y?1-(q-d)/y*(1-B):q<d+y+e?B:q<G-r?(G-q-r)/f*B:0),E=r?E/2+(r>q?0:(q<G-r?1:(G-q)/r)*z[q-r|0]/2):E),J=(c+=k+=m)*Math.cos(v*D++),w+=J-J*u*(1-1e9*(Math.sin(q)+1)%2),x&&++x>p&&(c+=n,b+=n,x=0),!l||++H%l||(c=b,k=F,x=x||1);return z}function zzfxM(a,b,c,d=125){let e,f,h,g,k,m,n,p,l,u,v,A,r,B=0,y,C=[],t=[],F=[],z=0,w=0,D=1,q={},x=zzfxR/d*60>>2;for(;D;z++)C=[D=p=A=0],c.forEach((H,I)=>{n=b[H][z]||[0,0,0];D|=!!b[H][z];y=A+(b[H][0].length-2-!p)*x;r=I==c.length-1;f=2;for(g=A;f<n.length+r;p=++f){k=n[f];l=f==n.length+r-1&&r||u!=(n[0]||0)|k|0;for(h=0;h<x&&p;h++>x-99&&l?v+=(1>v)/99:0)m=(1-v)*C[B++]/2||0,t[g]=(t[g]||0)-m*w+m,F[g]=(F[g++]||0)+m*w+m;k&&(v=k%1,w=n[1]||0,k|=0)&&(C=q[[u=n[B=0]||0,k]]=q[[u,k]]||(e=[...a[u]],e[2]*=2**((k-12)/12),0<k?zzfxG(...e):[]))}A=y});return[t,F]}"use strict";let tileCollision=[],tileCollisionSize=vec2();function initTileCollision(a){tileCollisionSize=a;tileCollision=[];for(a=tileCollision.length=tileCollisionSize.area();a--;)tileCollision[a]=0}const setTileCollisionData=(a,b=0)=>a.arrayCheck(tileCollisionSize)&&(tileCollision[(a.y|0)*tileCollisionSize.x+a.x|0]=b),getTileCollisionData=a=>a.arrayCheck(tileCollisionSize)?tileCollision[(a.y|0)*tileCollisionSize.x+a.x|0]:0;function tileCollisionTest(a,b=vec2(),c){const d=max(a.x-b.x/2|0,0);var e=max(a.y-b.y/2|0,0);const f=min(a.x+b.x/2,tileCollisionSize.x);for(a=min(a.y+b.y/2,tileCollisionSize.y);e<a;++e)for(b=d;b<f;++b){const h=tileCollision[e*tileCollisionSize.x+b];if(h&&(!c||c.collideWithTile(h,new Vector2(b,e))))return 1}}function tileCollisionRaycast(a,b,c){a=a.floor();b=b.floor();var d=b.subtract(a);const e=abs(d.x),f=-abs(d.y),h=sign(d.x);d=sign(d.y);let g=e+f;for(let m=a.x,n=a.y;;){var k=getTileCollisionData(vec2(m,n));if(k&&(c?c.collideWithTileRaycast(k,new Vector2(m,n)):0<k))return debugRaycast&&debugLine(a,b,"#f00",.02,1),debugRaycast&&debugPoint(new Vector2(m+.5,n+.5),"#ff0",1),new Vector2(m+.5,n+.5);if(m==b.x&n==b.y)break;k=2*g;k>=f&&(g+=f,m+=h);k<=e&&(g+=e,n+=d)}debugRaycast&&debugLine(a,b,"#00f",.02,1)}class TileLayerData{constructor(a,b=0,c=0,d=new Color){this.tile=a;this.direction=b;this.mirror=c;this.color=d}clear(){this.tile=this.direction=this.mirror=0;color=new Color}}class TileLayer extends EngineObject{constructor(a,b=tileCollisionSize,c=tileSizeDefault,d=vec2(1),e=0){super(a,b,-1,c,0,void 0,e);this.canvas=document.createElement("canvas");this.context=this.canvas.getContext("2d");this.scale=d;this.isOverlay;this.data=[];for(a=this.size.area();a--;)this.data.push(new TileLayerData)}setData(a,b,c){a.arrayCheck(this.size)&&(this.data[(a.y|0)*this.size.x+a.x|0]=b,c&&this.drawTileData(a))}getData(a){return a.arrayCheck(this.size)&&this.data[(a.y|0)*this.size.x+a.x|0]}update(){}render(){ASSERT(mainContext!=this.context);glEnable&&!glOverlay&&!this.isOverlay&&glCopyToContext(mainContext);const a=worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));(this.isOverlay?overlayContext:mainContext).drawImage(this.canvas,a.x,a.y,cameraScale*this.size.x*this.scale.x,cameraScale*this.size.y*this.scale.y)}redraw(){this.redrawStart(1);this.drawAllTileData();this.redrawEnd()}redrawStart(a=0){a&&(this.canvas.width=this.size.x*this.tileSize.x,this.canvas.height=this.size.y*this.tileSize.y);this.savedRenderSettings=[mainCanvas,mainContext,cameraPos,cameraScale];mainCanvas=this.canvas;mainContext=this.context;cameraPos=this.size.scale(.5);cameraScale=this.tileSize.x;enginePreRender()}redrawEnd(){ASSERT(mainContext==this.context);glCopyToContext(mainContext,1);[mainCanvas,mainContext,cameraPos,cameraScale]=this.savedRenderSettings}drawTileData(a){const b=a.floor().add(this.pos).add(vec2(.5));this.drawCanvas2D(b,vec2(1),0,0,c=>c.clearRect(-.5,-.5,1,1));a=this.getData(a);void 0!=a.tile&&(ASSERT(mainContext==this.context),drawTile(b,vec2(1),a.tile,this.tileSize,a.color,a.direction*PI/2,a.mirror))}drawAllTileData(){for(let a=this.size.x;a--;)for(let b=this.size.y;b--;)this.drawTileData(vec2(a,b))}drawCanvas2D(a,b,c=0,d,e){const f=this.context;f.save();a=a.subtract(this.pos).multiply(this.tileSize);b=b.multiply(this.tileSize);f.translate(a.x,this.canvas.height-a.y);f.rotate(c);f.scale(d?-b.x:b.x,b.y);e(f);f.restore()}drawTile(a,b=vec2(1),c=-1,d=tileSizeDefault,e=new Color,f,h){this.drawCanvas2D(a,b,f,h,g=>{if(0>c)g.fillStyle=e,g.fillRect(-.5,-.5,1,1);else{const k=tileImage.width/d.x;g.globalAlpha=e.a;g.drawImage(tileImage,c%k*d.x,(c/k|0)*d.x,d.x,d.y,-.5,-.5,1,1)}})}drawRect(a,b,c,d){this.drawTile(a,b,-1,0,c,d)}}"use strict";class ParticleEmitter extends EngineObject{constructor(a,b,c=0,d=0,e=100,f=PI,h=-1,g=tileSizeDefault,k=new Color,m=new Color,n=new Color(1,1,1,0),p=new Color(1,1,1,0),l=.5,u=.1,v=1,A=.1,r=.05,B=1,y=1,C=0,t=PI,F=.1,z=.2,w,D,q=1,x=D?1e9:0){super(a,new Vector2,h,g,b,void 0,x);this.emitSize=c;this.emitTime=d;this.emitRate=e;this.emitConeAngle=f;this.colorStartA=k;this.colorStartB=m;this.colorEndA=n;this.colorEndB=p;this.randomColorLinear=q;this.particleTime=l;this.sizeStart=u;this.sizeEnd=v;this.speed=A;this.angleSpeed=r;this.damping=B;this.angleDamping=y;this.gravityScale=C;this.particleConeAngle=t;this.fadeRate=F;this.randomness=z;this.collideTiles=w;this.additive=D;this.emitTimeBuffer=this.trailScale=0}update(){this.parent&&super.update();if(!this.emitTime||this.getAliveTime()<=this.emitTime){if(this.emitRate*particleEmitRateScale){const a=1/this.emitRate/particleEmitRateScale;for(this.emitTimeBuffer+=timeDelta;0<this.emitTimeBuffer;this.emitTimeBuffer-=a)this.emitParticle()}}else this.destroy();debugParticles&&debugRect(this.pos,vec2(this.emitSize),"#0f0",0,this.angle)}emitParticle(){var a=void 0!=this.emitSize.x?new Vector2(rand(-.5,.5),rand(-.5,.5)).multiply(this.emitSize).rotate(this.angle):randInCircle(.5*this.emitSize);a=new Particle(this.pos.add(a),this.tileIndex,this.tileSize,this.angle+rand(this.particleConeAngle,-this.particleConeAngle));const b=this.randomness;var c=n=>n+n*rand(b,-b);const d=c(this.particleTime),e=c(this.sizeStart),f=c(this.sizeEnd),h=c(this.speed);c=c(this.angleSpeed)*randSign();const g=rand(this.emitConeAngle,-this.emitConeAngle),k=randColor(this.colorStartA,this.colorStartB,this.randomColorLinear),m=randColor(this.colorEndA,this.colorEndB,this.randomColorLinear);a.colorStart=k;a.colorEndDelta=m.subtract(k);a.velocity=(new Vector2).setAngle(this.angle+g,h);a.angleVelocity=c;a.lifeTime=d;a.sizeStart=e;a.sizeEndDelta=f-e;a.fadeRate=this.fadeRate;a.damping=this.damping;a.angleDamping=this.angleDamping;a.elasticity=this.elasticity;a.friction=this.friction;a.gravityScale=this.gravityScale;a.collideTiles=this.collideTiles;a.additive=this.additive;a.renderOrder=this.renderOrder;a.trailScale=this.trailScale;a.mirror=.5>rand();a.destroyCallback=this.particleDestroyCallback;this.particleCreateCallback&&this.particleCreateCallback(a);return a}render(){}}class Particle extends EngineObject{constructor(a,b,c,d){super(a,new Vector2,b,c,d)}render(){const a=min((time-this.spawnTime)/this.lifeTime,1);var b=this.sizeStart+a*this.sizeEndDelta;b=new Vector2(b,b);var c=this.fadeRate/2;c=new Color(this.colorStart.r+a*this.colorEndDelta.r,this.colorStart.g+a*this.colorEndDelta.g,this.colorStart.b+a*this.colorEndDelta.b,(this.colorStart.a+a*this.colorEndDelta.a)*(a<c?a/c:a>1-c?(1-a)/c:1));this.additive&&setBlendMode(1);if(this.trailScale){var d=this.velocity.length();const e=this.velocity.scale(1/d);d*=this.trailScale;b.y=max(b.x,d);this.angle=e.angle();drawTile(this.pos.add(e.multiply(vec2(0,-d/2))),b,this.tileIndex,this.tileSize,c,this.angle,this.mirror)}else drawTile(this.pos,b,this.tileIndex,this.tileSize,c,this.angle,this.mirror);this.additive&&setBlendMode();debugParticles&&debugRect(this.pos,b,"#f005",0,this.angle);1==a&&(this.color=c,this.size=b,this.destroyCallback&&this.destroyCallback(this),this.destroyed=1)}}"use strict";const medals=[];let medalsPreventUnlock,newgrounds,medalsDisplayQueue=[],medalsSaveName,medalsDisplayTimeLast;function medalsInit(a){medalsSaveName=a;debugMedals||medals.forEach(b=>localStorage[b.storageKey()])}class Medal{constructor(a,b,c="",d="🏆",e){ASSERT(0<=a&&!medals[a]);medals[this.id=a]=this;this.name=b;this.description=c;this.icon=d;this.image=new Image;e&&(this.image.src=e)}unlock(){medalsPreventUnlock||this.unlocked||(ASSERT(medalsSaveName),localStorage[this.storageKey()]=this.unlocked=1,medalsDisplayQueue.push(this),newgrounds&&newgrounds.unlockMedal(this.id),localStorage["OS13kTrophy,"+this.icon+","+medalsSaveName+","+this.name]=this.description)}render(a=0){const b=overlayContext,c=min(medalDisplayWidth,mainCanvas.width),d=overlayCanvas.width-c;a*=-medalDisplayHeight;b.save();b.beginPath();b.fillStyle="#ddd";b.fill(b.rect(d,a,c,medalDisplayHeight));b.strokeStyle="#000";b.lineWidth=3;b.stroke();b.clip();this.renderIcon(d+15+medalDisplayIconSize/2,a+medalDisplayHeight/2);b.textAlign="left";b.font="38px "+fontDefault;b.fillText(this.name,d+medalDisplayIconSize+30,a+28);b.font="24px "+fontDefault;b.fillText(this.description,d+medalDisplayIconSize+30,a+60);b.restore()}renderIcon(a,b,c=medalDisplayIconSize){const d=overlayContext;d.fillStyle="#000";d.textAlign="center";d.textBaseline="middle";d.font=.7*c+"px "+fontDefault;this.image.src?d.drawImage(this.image,a-c/2,b-c/2,c,c):d.fillText(this.icon,a,b)}storageKey(){return medalsSaveName+"_"+this.id}}function medalsRender(){if(medalsDisplayQueue.length){var a=medalsDisplayQueue[0],b=timeReal-medalsDisplayTimeLast;if(medalsDisplayTimeLast)if(b>medalDisplayTime)medalsDisplayQueue.shift(medalsDisplayTimeLast=0);else{const c=medalDisplayTime-medalDisplaySlideTime;a.render(b<medalDisplaySlideTime?1-b/medalDisplaySlideTime:b>c?(b-c)/medalDisplaySlideTime:0)}else medalsDisplayTimeLast=timeReal}}class Newgrounds{constructor(a,b){ASSERT(!newgrounds&&a);this.app_id=a;this.cipher=b;this.host=location?location.hostname:"";b&&(this.cryptoJS=CryptoJS());this.session_id=new URL(location.href).searchParams.get("ngio_session_id")||0;if(0!=this.session_id){this.medals=(a=this.call("Medal.getList"))?a.result.data.medals:[];debugMedals&&console.log(this.medals);for(var c of this.medals)if(a=medals[c.id])a.image.src=c.icon,a.name=c.name,a.description=c.description,a.unlocked=c.unlocked,a.difficulty=c.difficulty,a.value=c.value,a.value&&(a.description=a.description+" ("+a.value+")");this.scoreboards=(c=this.call("ScoreBoard.getBoards"))?c.result.data.scoreboards:[];debugMedals&&console.log(this.scoreboards);setInterval(()=>this.call("Gateway.ping",0,1),3e5)}}unlockMedal(a){return this.call("Medal.unlock",{id:a},1)}postScore(a,b){return this.call("ScoreBoard.postScore",{id:a,value:b},1)}getScores(a,b=0,c=0,d=0,e=10){return this.call("ScoreBoard.getScores",{id:a,user:b,social:c,skip:d,limit:e})}logView(){return this.call("App.logView",{host:this.host},1)}call(a,b=0,c=0){a={component:a,parameters:b};if(this.cipher){b=this.cryptoJS;var d=b.enc.Base64.parse(this.cipher);const e=b.lib.WordArray.random(16);d=b.AES.encrypt(JSON.stringify(a),d,{iv:e});a.secure=b.enc.Base64.stringify(e.concat(d.ciphertext));a.parameters=0}b={app_id:this.app_id,session_id:this.session_id,call:a};a=new FormData;a.append("input",JSON.stringify(b));b=new XMLHttpRequest;b.open("POST","https://newgrounds.io/gateway_v3.php",!debugMedals&&c);b.send(a);debugMedals&&console.log(b.responseText);return b.responseText&&JSON.parse(b.responseText)}}const CryptoJS=()=>eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",..."]charCodeAtUinyxpf","for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h<t,o=o%h+(i?t:h-t)*(o>>17)-!i*t,f.map((n,x)=>(U=r[n]+=(i*h/2-r[n]<<13)/((C[n]+=C[n]<5)+1/20)>>13,a[x]+=y[x]*(i-t/h))),p=p*2+i)for(f='010202103203210431053105410642065206541'.split(t=0).map((n,x)=>(U=0,[...n].map((n,x)=>(U=U*997+(c[e-n]|0)|0)),h*32-1&U*997+p+!!A*129)*12+x);o<h*32;o=o*64|M.charCodeAt(d++)&63);for(C=String.fromCharCode(...c);r=/[\0-#?@\\\\~]/.exec(C);)with(C.split(r))C=join(shift());return C")([],[],131072,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(32768),new Uint8Array(51e6),0,0,0,0));"use strict";let glCanvas,glContext,glTileTexture,glActiveTexture,glShader,glPositionData,glColorData,glBatchCount,glBatchAdditive,glAdditive;function glInit(){if(glEnable){glCanvas=document.createElement("canvas");glContext=glCanvas.getContext("webgl",{antialias:!1});glCanvas.style=styleCanvas;glTileTexture=glCreateTexture(tileImage);glOverlay&&document.body.appendChild(glCanvas);glShader=glCreateProgram("precision highp float;uniform mat4 m;attribute vec2 p,t;attribute vec4 c,a;varying vec2 v;varying vec4 d,e;void main(){gl_Position=m*vec4(p,1,1);v=t;d=c;e=a;}","precision highp float;varying vec2 v;varying vec4 d,e;uniform sampler2D s;void main(){gl_FragColor=texture2D(s,v)*d+e;}");var a=new ArrayBuffer(gl_MAX_BATCH*gl_VERTICES_PER_QUAD*gl_VERTEX_BYTE_STRIDE);glCreateBuffer(gl_ARRAY_BUFFER,a.byteLength,gl_DYNAMIC_DRAW);glPositionData=new Float32Array(a);glColorData=new Uint32Array(a);var b=glBatchCount=0;a=(c,d,e,f,h=0)=>{c=glContext.getAttribLocation(glShader,c);glContext.enableVertexAttribArray(c);glContext.vertexAttribPointer(c,f,d,h,gl_VERTEX_BYTE_STRIDE,b);b+=f*e};a("p",gl_FLOAT,4,2);a("t",gl_FLOAT,4,2);a("c",gl_UNSIGNED_BYTE,1,4,1);a("a",gl_UNSIGNED_BYTE,1,4,1)}}function glSetBlendMode(a){glEnable&&(glAdditive=a)}function glSetTexture(a=glTileTexture){glEnable&&(a!=glActiveTexture&&glFlush(),glContext.bindTexture(gl_TEXTURE_2D,glActiveTexture=a))}function glCompileShader(a,b){if(glEnable){b=glContext.createShader(b);glContext.shaderSource(b,a);glContext.compileShader(b);if(debug&&!glContext.getShaderParameter(b,gl_COMPILE_STATUS))throw glContext.getShaderInfoLog(b);return b}}function glCreateProgram(a,b){if(glEnable){var c=glContext.createProgram();glContext.attachShader(c,glCompileShader(a,gl_VERTEX_SHADER));glContext.attachShader(c,glCompileShader(b,gl_FRAGMENT_SHADER));glContext.linkProgram(c);if(debug&&!glContext.getProgramParameter(c,gl_LINK_STATUS))throw glContext.getProgramInfoLog(c);return c}}function glCreateBuffer(a,b,c){if(glEnable){var d=glContext.createBuffer();glContext.bindBuffer(a,d);glContext.bufferData(a,b,c);return d}}function glCreateTexture(a){if(glEnable&&a&&a.width){var b=glContext.createTexture();glContext.bindTexture(gl_TEXTURE_2D,b);glContext.texImage2D(gl_TEXTURE_2D,0,gl_RGBA,gl_RGBA,gl_UNSIGNED_BYTE,a);glContext.texParameteri(gl_TEXTURE_2D,gl_TEXTURE_MIN_FILTER,cavasPixelated?gl_NEAREST:gl_LINEAR);glContext.texParameteri(gl_TEXTURE_2D,gl_TEXTURE_MAG_FILTER,cavasPixelated?gl_NEAREST:gl_LINEAR);glContext.texParameteri(gl_TEXTURE_2D,gl_TEXTURE_WRAP_S,gl_CLAMP_TO_EDGE);glContext.texParameteri(gl_TEXTURE_2D,gl_TEXTURE_WRAP_T,gl_CLAMP_TO_EDGE);return b}}function glPreRender(a,b,c,d,e){glEnable&&(glContext.viewport(0,0,glCanvas.width=a,glCanvas.height=b),glContext.clear(gl_COLOR_BUFFER_BIT),glContext.bindTexture(gl_TEXTURE_2D,glActiveTexture=glTileTexture),glContext.useProgram(glShader),glSetBlendMode(),a=2*e/a,b=2*e/b,glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader,"m"),0,new Float32Array([a,0,0,0,0,b,0,0,1,1,-1,1,-1-a*c,-1-b*d,0,0])))}function glFlush(){if(glEnable&&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(glEnable&&glBatchCount||b)glFlush(),glOverlay&&!b||a.drawImage(glCanvas,0,0)}function glDraw(a,b,c,d,e,f,h,g,k,m=4294967295,n=0){if(glEnable){glBatchCount!=gl_MAX_BATCH&&glBatchAdditive==glAdditive||glFlush();var p=Math.cos(e)/2,l=Math.sin(e)/2;e=p*c;p*=d;c*=l;d*=l;l=glBatchCount++*gl_VERTICES_PER_QUAD*gl_INDICIES_PER_VERT;glPositionData[l++]=a-e-d;glPositionData[l++]=b-p+c;glPositionData[l++]=f;glPositionData[l++]=k;glColorData[l++]=m;glColorData[l++]=n;glPositionData[l++]=a+e+d;glPositionData[l++]=b+p-c;glPositionData[l++]=g;glPositionData[l++]=h;glColorData[l++]=m;glColorData[l++]=n;glPositionData[l++]=a-e+d;glPositionData[l++]=b+p+c;glPositionData[l++]=f;glPositionData[l++]=h;glColorData[l++]=m;glColorData[l++]=n;glPositionData[l++]=a-e-d;glPositionData[l++]=b-p+c;glPositionData[l++]=f;glPositionData[l++]=k;glColorData[l++]=m;glColorData[l++]=n;glPositionData[l++]=a+e-d;glPositionData[l++]=b-p-c;glPositionData[l++]=g;glPositionData[l++]=k;glColorData[l++]=m;glColorData[l++]=n;glPositionData[l++]=a+e+d;glPositionData[l++]=b+p-c;glPositionData[l++]=g;glPositionData[l++]=h;glColorData[l++]=m;glColorData[l++]=n}}const gl_ONE=1,gl_TRIANGLES=4,gl_SRC_ALPHA=770,gl_ONE_MINUS_SRC_ALPHA=771,gl_BLEND=3042,gl_TEXTURE_2D=3553,gl_UNSIGNED_BYTE=5121,gl_FLOAT=5126,gl_RGBA=6408,gl_NEAREST=9728,gl_LINEAR=9729,gl_TEXTURE_MAG_FILTER=10240,gl_TEXTURE_MIN_FILTER=10241,gl_TEXTURE_WRAP_S=10242,gl_TEXTURE_WRAP_T=10243,gl_COLOR_BUFFER_BIT=16384,gl_CLAMP_TO_EDGE=33071,gl_ARRAY_BUFFER=34962,gl_DYNAMIC_DRAW=35048,gl_FRAGMENT_SHADER=35632,gl_VERTEX_SHADER=35633,gl_COMPILE_STATUS=35713,gl_LINK_STATUS=35714,gl_VERTICES_PER_QUAD=6,gl_INDICIES_PER_VERT=6,gl_MAX_BATCH=65536,gl_VERTEX_BYTE_STRIDE=24;
@@ -8,9 +8,9 @@
8
8
 
9
9
  'use strict';
10
10
 
11
+ let showWatermark = 0;
12
+ let godMode = 0;
11
13
  const debug = 0;
12
- const showWatermark = 0;
13
- const godMode = 0;
14
14
  const debugOverlay = 0;
15
15
  const debugPhysics = 0;
16
16
  const debugParticles = 0;
@@ -442,6 +442,34 @@ class Color
442
442
  return this;
443
443
  }
444
444
 
445
+ /** Returns this color expressed in hsla format
446
+ * @return {Array} */
447
+ getHSLA()
448
+ {
449
+ const r = this.r;
450
+ const g = this.g;
451
+ const b = this.b;
452
+ const a = this.a;
453
+ const max = Math.max(r, g, b);
454
+ const min = Math.min(r, g, b);
455
+ const l = (max + min) / 2;
456
+
457
+ let h = 0, s = 0;
458
+ if (max != min)
459
+ {
460
+ let d = max - min;
461
+ s = l > .5 ? d / (2 - max - min) : d / (max + min);
462
+ if (r == max)
463
+ h = (g - b) / d + (g < b ? 6 : 0);
464
+ else if (g == max)
465
+ h = (b - r) / d + 2;
466
+ else if (b == max)
467
+ h = (r - g) / d + 4;
468
+ }
469
+
470
+ return [h / 6, s, l, a];
471
+ }
472
+
445
473
  /** Returns a new color that has each component randomly adjusted
446
474
  * @param {Number} [amount=.05]
447
475
  * @param {Number} [alphaAmount=0]
@@ -473,42 +501,6 @@ class Color
473
501
  return (this.r*255|0) + (this.g*255<<8) + (this.b*255<<16) + (this.a*255<<24);
474
502
  }
475
503
 
476
- /** Returns this color expressed as a hex code
477
- * @return {String} */
478
- hex()
479
- {
480
- const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
481
- return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b);
482
- }
483
-
484
- /** Returns this color expressed in hsla format
485
- * @return {Array} */
486
- getHSLA()
487
- {
488
- const r = this.r;
489
- const g = this.g;
490
- const b = this.b;
491
- const a = this.a;
492
- const max = Math.max(r, g, b);
493
- const min = Math.min(r, g, b);
494
- const l = (max + min) / 2;
495
-
496
- let h = 0, s = 0;
497
- if (max != min)
498
- {
499
- let d = max - min;
500
- s = l > .5 ? d / (2 - max - min) : d / (max + min);
501
- if (r == max)
502
- h = (g - b) / d + (g < b ? 6 : 0);
503
- else if (g == max)
504
- h = (b - r) / d + 2;
505
- else if (b == max)
506
- h = (r - g) / d + 4;
507
- }
508
-
509
- return [h / 6, s, l, a];
510
- }
511
-
512
504
  /** Set this color from a hex code
513
505
  * @param {String} hex - html hex code
514
506
  * @return {Color} */
@@ -522,6 +514,14 @@ class Color
522
514
  ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1);
523
515
  return this;
524
516
  }
517
+
518
+ /** Returns this color expressed as a hex code
519
+ * @return {String} */
520
+ getHex()
521
+ {
522
+ const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
523
+ return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b);
524
+ }
525
525
  }
526
526
 
527
527
  ///////////////////////////////////////////////////////////////////////////////
@@ -588,7 +588,8 @@ class Timer
588
588
  * @memberof Settings */
589
589
  let canvasMaxSize = vec2(1920, 1200);
590
590
 
591
- /** Fixed size of the canvas, if enabled cavnvas size never changes
591
+ /** Fixed size of the canvas, if enabled canvas size never changes
592
+ * - you may also need to set mainCanvasSize if using screen space coords in startup
592
593
  * @type {Vector2}
593
594
  * @default
594
595
  * @memberof Settings */
@@ -818,7 +819,7 @@ let medalDisplayIconSize = 50;
818
819
  const engineName = 'LittleJS';
819
820
 
820
821
  /** Version of engine */
821
- const engineVersion = '1.3.5';
822
+ const engineVersion = '1.3.8';
822
823
 
823
824
  /** Frames per second to update objects
824
825
  * @default */
@@ -909,6 +910,33 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
909
910
  timeReal += frameTimeDeltaMS / 1e3;
910
911
  frameTimeBufferMS = min(frameTimeBufferMS + !paused * frameTimeDeltaMS, 50); // clamp incase of slow framerate
911
912
 
913
+ if (canvasFixedSize.x)
914
+ {
915
+ // clear set fixed size
916
+ overlayCanvas.width = mainCanvas.width = canvasFixedSize.x;
917
+ overlayCanvas.height = mainCanvas.height = canvasFixedSize.y;
918
+
919
+ // fit to window by adding space on top or bottom if necessary
920
+ const aspect = innerWidth / innerHeight;
921
+ const fixedAspect = mainCanvas.width / mainCanvas.height;
922
+ mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
923
+ mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
924
+ if (glCanvas)
925
+ {
926
+ glCanvas.style.width = mainCanvas.style.width;
927
+ glCanvas.style.height = mainCanvas.style.height;
928
+ }
929
+ }
930
+ else
931
+ {
932
+ // clear and set size to same as window
933
+ overlayCanvas.width = mainCanvas.width = min(innerWidth, canvasMaxSize.x);
934
+ overlayCanvas.height = mainCanvas.height = min(innerHeight, canvasMaxSize.y);
935
+ }
936
+
937
+ // save canvas size
938
+ mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
939
+
912
940
  if (paused)
913
941
  {
914
942
  // do post update even when paused
@@ -945,30 +973,6 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
945
973
  // add the time smoothing back in
946
974
  frameTimeBufferMS += deltaSmooth;
947
975
  }
948
-
949
- if (canvasFixedSize.x)
950
- {
951
- // clear set fixed size
952
- mainCanvas.width = canvasFixedSize.x;
953
- mainCanvas.height = canvasFixedSize.y;
954
-
955
- // fit to window by adding space on top or bottom if necessary
956
- const aspect = innerWidth / innerHeight;
957
- const fixedAspect = mainCanvas.width / mainCanvas.height;
958
- mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
959
- mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
960
- if (glCanvas)
961
- {
962
- glCanvas.style.width = mainCanvas.style.width;
963
- glCanvas.style.height = mainCanvas.style.height;
964
- }
965
- }
966
- else
967
- {
968
- // clear and set size to same as window
969
- mainCanvas.width = min(innerWidth, canvasMaxSize.x);
970
- mainCanvas.height = min(innerHeight, canvasMaxSize.y);
971
- }
972
976
 
973
977
  // render sort then render while removing destroyed objects
974
978
  enginePreRender();
@@ -1008,8 +1012,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
1008
1012
  // called by engine to setup render system
1009
1013
  function enginePreRender()
1010
1014
  {
1011
- // save canvas size and clear canvases
1012
- mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
1015
+ // save canvas size
1016
+ mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
1013
1017
 
1014
1018
  // disable smoothing for pixel art
1015
1019
  mainContext.imageSmoothingEnabled = !cavasPixelated;
@@ -1507,18 +1511,26 @@ let overlayContext;
1507
1511
  let mainCanvasSize = vec2();
1508
1512
 
1509
1513
  /** Convert from screen to world space coordinates
1514
+ * - if calling outside of render, you may need to manually set mainCanvasSize
1510
1515
  * @param {Vector2} screenPos
1511
1516
  * @return {Vector2}
1512
1517
  * @memberof Draw */
1513
1518
  const screenToWorld = (screenPos)=>
1514
- screenPos.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos);
1519
+ {
1520
+ ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
1521
+ return screenPos.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos);
1522
+ }
1515
1523
 
1516
1524
  /** Convert from world to screen space coordinates
1525
+ * - if calling outside of render, you may need to manually set mainCanvasSize
1517
1526
  * @param {Vector2} worldPos
1518
1527
  * @return {Vector2}
1519
1528
  * @memberof Draw */
1520
1529
  const worldToScreen = (worldPos)=>
1521
- worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
1530
+ {
1531
+ ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
1532
+ return worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
1533
+ }
1522
1534
 
1523
1535
  /** Draw textured tile centered in world space, with color applied if using WebGL
1524
1536
  * @param {Vector2} pos - Center of the tile in world space
@@ -1996,7 +2008,7 @@ const mouseToScreen = (mousePos)=>
1996
2008
  return vec2(); // fix bug that can occur if user clicks before page loads
1997
2009
 
1998
2010
  const rect = mainCanvas.getBoundingClientRect();
1999
- return mainCanvasSize.multiply(
2011
+ return vec2(mainCanvas.width, mainCanvas.height).multiply(
2000
2012
  vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
2001
2013
  }
2002
2014
 
package/engine/engine.js CHANGED
@@ -22,7 +22,7 @@
22
22
  const engineName = 'LittleJS';
23
23
 
24
24
  /** Version of engine */
25
- const engineVersion = '1.3.5';
25
+ const engineVersion = '1.3.8';
26
26
 
27
27
  /** Frames per second to update objects
28
28
  * @default */
@@ -113,6 +113,33 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
113
113
  timeReal += frameTimeDeltaMS / 1e3;
114
114
  frameTimeBufferMS = min(frameTimeBufferMS + !paused * frameTimeDeltaMS, 50); // clamp incase of slow framerate
115
115
 
116
+ if (canvasFixedSize.x)
117
+ {
118
+ // clear set fixed size
119
+ overlayCanvas.width = mainCanvas.width = canvasFixedSize.x;
120
+ overlayCanvas.height = mainCanvas.height = canvasFixedSize.y;
121
+
122
+ // fit to window by adding space on top or bottom if necessary
123
+ const aspect = innerWidth / innerHeight;
124
+ const fixedAspect = mainCanvas.width / mainCanvas.height;
125
+ mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
126
+ mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
127
+ if (glCanvas)
128
+ {
129
+ glCanvas.style.width = mainCanvas.style.width;
130
+ glCanvas.style.height = mainCanvas.style.height;
131
+ }
132
+ }
133
+ else
134
+ {
135
+ // clear and set size to same as window
136
+ overlayCanvas.width = mainCanvas.width = min(innerWidth, canvasMaxSize.x);
137
+ overlayCanvas.height = mainCanvas.height = min(innerHeight, canvasMaxSize.y);
138
+ }
139
+
140
+ // save canvas size
141
+ mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
142
+
116
143
  if (paused)
117
144
  {
118
145
  // do post update even when paused
@@ -149,30 +176,6 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
149
176
  // add the time smoothing back in
150
177
  frameTimeBufferMS += deltaSmooth;
151
178
  }
152
-
153
- if (canvasFixedSize.x)
154
- {
155
- // clear set fixed size
156
- mainCanvas.width = canvasFixedSize.x;
157
- mainCanvas.height = canvasFixedSize.y;
158
-
159
- // fit to window by adding space on top or bottom if necessary
160
- const aspect = innerWidth / innerHeight;
161
- const fixedAspect = mainCanvas.width / mainCanvas.height;
162
- mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
163
- mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
164
- if (glCanvas)
165
- {
166
- glCanvas.style.width = mainCanvas.style.width;
167
- glCanvas.style.height = mainCanvas.style.height;
168
- }
169
- }
170
- else
171
- {
172
- // clear and set size to same as window
173
- mainCanvas.width = min(innerWidth, canvasMaxSize.x);
174
- mainCanvas.height = min(innerHeight, canvasMaxSize.y);
175
- }
176
179
 
177
180
  // render sort then render while removing destroyed objects
178
181
  enginePreRender();
@@ -212,8 +215,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
212
215
  // called by engine to setup render system
213
216
  function enginePreRender()
214
217
  {
215
- // save canvas size and clear canvases
216
- mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
218
+ // save canvas size
219
+ mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
217
220
 
218
221
  // disable smoothing for pixel art
219
222
  mainContext.imageSmoothingEnabled = !cavasPixelated;
@@ -53,18 +53,26 @@ let overlayContext;
53
53
  let mainCanvasSize = vec2();
54
54
 
55
55
  /** Convert from screen to world space coordinates
56
+ * - if calling outside of render, you may need to manually set mainCanvasSize
56
57
  * @param {Vector2} screenPos
57
58
  * @return {Vector2}
58
59
  * @memberof Draw */
59
60
  const screenToWorld = (screenPos)=>
60
- screenPos.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos);
61
+ {
62
+ ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
63
+ return screenPos.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos);
64
+ }
61
65
 
62
66
  /** Convert from world to screen space coordinates
67
+ * - if calling outside of render, you may need to manually set mainCanvasSize
63
68
  * @param {Vector2} worldPos
64
69
  * @return {Vector2}
65
70
  * @memberof Draw */
66
71
  const worldToScreen = (worldPos)=>
67
- worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
72
+ {
73
+ ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
74
+ return worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
75
+ }
68
76
 
69
77
  /** Draw textured tile centered in world space, with color applied if using WebGL
70
78
  * @param {Vector2} pos - Center of the tile in world space
@@ -162,7 +162,7 @@ const mouseToScreen = (mousePos)=>
162
162
  return vec2(); // fix bug that can occur if user clicks before page loads
163
163
 
164
164
  const rect = mainCanvas.getBoundingClientRect();
165
- return mainCanvasSize.multiply(
165
+ return vec2(mainCanvas.width, mainCanvas.height).multiply(
166
166
  vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
167
167
  }
168
168
 
@@ -8,9 +8,9 @@
8
8
 
9
9
  'use strict';
10
10
 
11
+ let showWatermark = 0;
12
+ let godMode = 0;
11
13
  const debug = 0;
12
- const showWatermark = 0;
13
- const godMode = 0;
14
14
  const debugOverlay = 0;
15
15
  const debugPhysics = 0;
16
16
  const debugParticles = 0;
@@ -14,7 +14,8 @@
14
14
  * @memberof Settings */
15
15
  let canvasMaxSize = vec2(1920, 1200);
16
16
 
17
- /** Fixed size of the canvas, if enabled cavnvas size never changes
17
+ /** Fixed size of the canvas, if enabled canvas size never changes
18
+ * - you may also need to set mainCanvasSize if using screen space coords in startup
18
19
  * @type {Vector2}
19
20
  * @default
20
21
  * @memberof Settings */
@@ -409,6 +409,34 @@ class Color
409
409
  return this;
410
410
  }
411
411
 
412
+ /** Returns this color expressed in hsla format
413
+ * @return {Array} */
414
+ getHSLA()
415
+ {
416
+ const r = this.r;
417
+ const g = this.g;
418
+ const b = this.b;
419
+ const a = this.a;
420
+ const max = Math.max(r, g, b);
421
+ const min = Math.min(r, g, b);
422
+ const l = (max + min) / 2;
423
+
424
+ let h = 0, s = 0;
425
+ if (max != min)
426
+ {
427
+ let d = max - min;
428
+ s = l > .5 ? d / (2 - max - min) : d / (max + min);
429
+ if (r == max)
430
+ h = (g - b) / d + (g < b ? 6 : 0);
431
+ else if (g == max)
432
+ h = (b - r) / d + 2;
433
+ else if (b == max)
434
+ h = (r - g) / d + 4;
435
+ }
436
+
437
+ return [h / 6, s, l, a];
438
+ }
439
+
412
440
  /** Returns a new color that has each component randomly adjusted
413
441
  * @param {Number} [amount=.05]
414
442
  * @param {Number} [alphaAmount=0]
@@ -440,42 +468,6 @@ class Color
440
468
  return (this.r*255|0) + (this.g*255<<8) + (this.b*255<<16) + (this.a*255<<24);
441
469
  }
442
470
 
443
- /** Returns this color expressed as a hex code
444
- * @return {String} */
445
- hex()
446
- {
447
- const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
448
- return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b);
449
- }
450
-
451
- /** Returns this color expressed in hsla format
452
- * @return {Array} */
453
- getHSLA()
454
- {
455
- const r = this.r;
456
- const g = this.g;
457
- const b = this.b;
458
- const a = this.a;
459
- const max = Math.max(r, g, b);
460
- const min = Math.min(r, g, b);
461
- const l = (max + min) / 2;
462
-
463
- let h = 0, s = 0;
464
- if (max != min)
465
- {
466
- let d = max - min;
467
- s = l > .5 ? d / (2 - max - min) : d / (max + min);
468
- if (r == max)
469
- h = (g - b) / d + (g < b ? 6 : 0);
470
- else if (g == max)
471
- h = (b - r) / d + 2;
472
- else if (b == max)
473
- h = (r - g) / d + 4;
474
- }
475
-
476
- return [h / 6, s, l, a];
477
- }
478
-
479
471
  /** Set this color from a hex code
480
472
  * @param {String} hex - html hex code
481
473
  * @return {Color} */
@@ -489,6 +481,14 @@ class Color
489
481
  ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1);
490
482
  return this;
491
483
  }
484
+
485
+ /** Returns this color expressed as a hex code
486
+ * @return {String} */
487
+ getHex()
488
+ {
489
+ const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
490
+ return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b);
491
+ }
492
492
  }
493
493
 
494
494
  ///////////////////////////////////////////////////////////////////////////////
package/engine/index.d.ts CHANGED
@@ -629,6 +629,9 @@ declare class Color {
629
629
  * @param {Number} [alpha=1]
630
630
  * @return {Color} */
631
631
  setHSLA(h?: number, s?: number, l?: number, a?: number): Color;
632
+ /** Returns this color expressed in hsla format
633
+ * @return {Array} */
634
+ getHSLA(): any[];
632
635
  /** Returns a new color that has each component randomly adjusted
633
636
  * @param {Number} [amount=.05]
634
637
  * @param {Number} [alphaAmount=0]
@@ -640,16 +643,13 @@ declare class Color {
640
643
  /** Returns this color expressed as 32 bit integer RGBA value
641
644
  * @return {Number} */
642
645
  rgbaInt(): number;
643
- /** Returns this color expressed as a hex code
644
- * @return {String} */
645
- hex(): string;
646
- /** Returns this color expressed in hsla format
647
- * @return {Array} */
648
- getHSLA(): any[];
649
646
  /** Set this color from a hex code
650
647
  * @param {String} hex - html hex code
651
648
  * @return {Color} */
652
649
  setHex(hex: string): Color;
650
+ /** Returns this color expressed as a hex code
651
+ * @return {String} */
652
+ getHex(): string;
653
653
  }
654
654
  /**
655
655
  * Timer object tracks how long has passed since it was set
@@ -695,7 +695,8 @@ declare class Timer {
695
695
  * @default
696
696
  * @memberof Settings */
697
697
  declare let canvasMaxSize: Vector2;
698
- /** Fixed size of the canvas, if enabled cavnvas size never changes
698
+ /** Fixed size of the canvas, if enabled canvas size never changes
699
+ * - you may also need to set mainCanvasSize if using screen space coords in startup
699
700
  * @type {Vector2}
700
701
  * @default
701
702
  * @memberof Settings */
@@ -991,11 +992,13 @@ declare let overlayContext: CanvasRenderingContext2D;
991
992
  * @memberof Draw */
992
993
  declare let mainCanvasSize: Vector2;
993
994
  /** Convert from screen to world space coordinates
995
+ * - if calling outside of render, you may need to manually set mainCanvasSize
994
996
  * @param {Vector2} screenPos
995
997
  * @return {Vector2}
996
998
  * @memberof Draw */
997
999
  declare function screenToWorld(screenPos: Vector2): Vector2;
998
1000
  /** Convert from world to screen space coordinates
1001
+ * - if calling outside of render, you may need to manually set mainCanvasSize
999
1002
  * @param {Vector2} worldPos
1000
1003
  * @return {Vector2}
1001
1004
  * @memberof Draw */
@@ -1627,7 +1630,7 @@ declare const gl_VERTEX_BYTE_STRIDE: number;
1627
1630
  /** Name of engine */
1628
1631
  declare const engineName: "LittleJS";
1629
1632
  /** Version of engine */
1630
- declare const engineVersion: "1.3.5";
1633
+ declare const engineVersion: "1.3.8";
1631
1634
  /** Frames per second to update objects
1632
1635
  * @default */
1633
1636
  declare const frameRate: 60;
@@ -5,6 +5,6 @@
5
5
  <link rel=icon type=image/png href=../../favicon.png>
6
6
  </head><body>
7
7
 
8
- <script src=../../engine/engine.all.js?109></script>
9
- <script src=game.js?109></script>
10
- <script src=gameObjects.js?109></script>
8
+ <script src=../../engine/engine.all.js?111></script>
9
+ <script src=game.js?111></script>
10
+ <script src=gameObjects.js?111></script>
@@ -15,18 +15,18 @@ button
15
15
  </style>
16
16
  </head><body>
17
17
 
18
- <script src=../../engine/engineDebug.js?109></script>
19
- <script src=../../engine/engineUtilities.js?109></script>
20
- <script src=../../engine/engineSettings.js?109></script>
21
- <script src=../../engine/engineObject.js?109></script>
22
- <script src=../../engine/engineDraw.js?109></script>
23
- <script src=../../engine/engineInput.js?109></script>
24
- <script src=../../engine/engineAudio.js?109></script>
25
- <script src=../../engine/engineTileLayer.js?109></script>
26
- <script src=../../engine/engineParticles.js?109></script>
27
- <script src=../../engine/engineMedals.js?109></script>
28
- <script src=../../engine/engineWebGL.js?109></script>
29
- <script src=../../engine/engine.js?109></script>
18
+ <script src=../../engine/engineDebug.js?111></script>
19
+ <script src=../../engine/engineUtilities.js?111></script>
20
+ <script src=../../engine/engineSettings.js?111></script>
21
+ <script src=../../engine/engineObject.js?111></script>
22
+ <script src=../../engine/engineDraw.js?111></script>
23
+ <script src=../../engine/engineInput.js?111></script>
24
+ <script src=../../engine/engineAudio.js?111></script>
25
+ <script src=../../engine/engineTileLayer.js?111></script>
26
+ <script src=../../engine/engineParticles.js?111></script>
27
+ <script src=../../engine/engineMedals.js?111></script>
28
+ <script src=../../engine/engineWebGL.js?111></script>
29
+ <script src=../../engine/engine.js?111></script>
30
30
  <script>
31
31
 
32
32
  'use strict';
@@ -103,7 +103,7 @@ function gameInit()
103
103
  if (type == 'color')
104
104
  {
105
105
  const color = particleSystem[name];
106
- defaultValue = color.hex();
106
+ defaultValue = color.getHex();
107
107
  }
108
108
  else if (type == 'alpha')
109
109
  {
@@ -277,7 +277,7 @@ function updateSetting(setting, save=1)
277
277
  if (type == 'color')
278
278
  {
279
279
  const color = particleSystem[name];
280
- input.value = color.hex();
280
+ input.value = color.getHex();
281
281
  }
282
282
  else if (type == 'alpha')
283
283
  {
@@ -5,9 +5,9 @@
5
5
  <link rel=icon type=image/png href=../../favicon.png>
6
6
  </head><body>
7
7
 
8
- <script src=../../engine/engine.all.js?109></script>
9
- <script src=gameObjects.js?109></script>
10
- <script src=gamePlayer.js?109></script>
11
- <script src=gameEffects.js?109></script>
12
- <script src=gameLevel.js?109></script>
8
+ <script src=../../engine/engine.all.js?111></script>
9
+ <script src=gameObjects.js?111></script>
10
+ <script src=gamePlayer.js?111></script>
11
+ <script src=gameEffects.js?111></script>
12
+ <script src=gameLevel.js?111></script>
13
13
  <script src=game.js?103></script>
@@ -5,5 +5,5 @@
5
5
  <link rel=icon type=image/png href=../../favicon.png>
6
6
  </head><body>
7
7
 
8
- <script src=../../engine/engine.all.js?109></script>
9
- <script src=game.js?109></script>
8
+ <script src=../../engine/engine.all.js?111></script>
9
+ <script src=game.js?111></script>
@@ -5,7 +5,7 @@
5
5
  <link rel=icon type=image/png href=../../favicon.png>
6
6
  </head><body>
7
7
 
8
- <script src=../../engine/engine.all.min.js?109></script>
8
+ <script src=../../engine/engine.all.min.js?111></script>
9
9
  <script>
10
10
 
11
11
  /*
package/index.html CHANGED
@@ -6,21 +6,21 @@
6
6
  </head><body>
7
7
 
8
8
  <!-- LittleJS engine scripts -->
9
- <script src=engine/engineDebug.js?109></script>
10
- <script src=engine/engineUtilities.js?109></script>
11
- <script src=engine/engineSettings.js?109></script>
12
- <script src=engine/engineObject.js?109></script>
13
- <script src=engine/engineDraw.js?109></script>
14
- <script src=engine/engineInput.js?109></script>
15
- <script src=engine/engineAudio.js?109></script>
16
- <script src=engine/engineTileLayer.js?109></script>
17
- <script src=engine/engineParticles.js?109></script>
18
- <script src=engine/engineMedals.js?109></script>
19
- <script src=engine/engineWebGL.js?109></script>
20
- <script src=engine/engine.js?109></script>
9
+ <script src=engine/engineDebug.js?111></script>
10
+ <script src=engine/engineUtilities.js?111></script>
11
+ <script src=engine/engineSettings.js?111></script>
12
+ <script src=engine/engineObject.js?111></script>
13
+ <script src=engine/engineDraw.js?111></script>
14
+ <script src=engine/engineInput.js?111></script>
15
+ <script src=engine/engineAudio.js?111></script>
16
+ <script src=engine/engineTileLayer.js?111></script>
17
+ <script src=engine/engineParticles.js?111></script>
18
+ <script src=engine/engineMedals.js?111></script>
19
+ <script src=engine/engineWebGL.js?111></script>
20
+ <script src=engine/engine.js?111></script>
21
21
 
22
22
  <!-- You can also include all engine scripts like this... -->
23
23
  <!-- <script src=engine/engine.all.js></script> -->
24
24
 
25
25
  <!-- Add your game scripts here -->
26
- <script src=game.js?109></script>
26
+ <script src=game.js?111></script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "littlejsengine",
3
- "version": "1.3.5",
3
+ "version": "1.3.8",
4
4
  "description": "LittleJS - Tiny and Fast HTML5 Game Engine",
5
5
  "main": "engine/engine.all.js",
6
6
  "repository": {
@@ -21,5 +21,12 @@
21
21
  "bugs": {
22
22
  "url": "https://github.com/KilledByAPixel/LittleJS/issues"
23
23
  },
24
- "homepage": "https://github.com/KilledByAPixel/LittleJS"
24
+ "homepage": "https://github.com/KilledByAPixel/LittleJS",
25
+ "dependencies": {
26
+ "ect-bin": "^1.4.1",
27
+ "google-closure-compiler": "^20220803.0.0",
28
+ "roadroller": "^2.1.0",
29
+ "typescript": "^4.7.4",
30
+ "uglify-js": "^3.16.3"
31
+ }
25
32
  }