littlejsengine 1.4.6 → 1.4.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.
@@ -492,7 +492,7 @@ const wave = (frequency=1, amplitude=1, t=time)=> amplitude/2 * (1 - Math.cos(t*
492
492
  * @param {Number} t - time in seconds
493
493
  * @return {String}
494
494
  * @memberof Utilities */
495
- const formatTime = (t)=> (t/60|0)+':'+(t%60<10?'0':'')+(t%60|0);
495
+ const formatTime = (t)=> (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0);
496
496
 
497
497
  ///////////////////////////////////////////////////////////////////////////////
498
498
 
@@ -516,7 +516,7 @@ const randInt = (a=1, b=0)=> rand(a,b)|0;
516
516
  /** Randomly returns either -1 or 1
517
517
  * @return {Number}
518
518
  * @memberof Random */
519
- const randSign = ()=> (rand(2)|0) * 2 - 1;
519
+ const randSign = ()=> randInt(2) * 2 - 1;
520
520
 
521
521
  /** Returns a random Vector2 within a circular shape
522
522
  * @param {Number} [radius=1]
@@ -540,10 +540,15 @@ const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
540
540
  const randColor = (cA = new Color, cB = new Color(0,0,0,1), linear)=>
541
541
  linear ? cA.lerp(cB, rand()) : new Color(rand(cA.r,cB.r),rand(cA.g,cB.g),rand(cA.b,cB.b),rand(cA.a,cB.a));
542
542
 
543
- /** The seed used by the randSeeded function, should not be 0
543
+ /** Seed used by the randSeeded function
544
544
  * @memberof Random */
545
545
  let randSeed = 1;
546
546
 
547
+ /** Set seed used by the randSeeded function, should not be 0
548
+ * @param {Number} seed
549
+ * @memberof Random */
550
+ const setRandSeed = (seed)=> randSeed = seed;
551
+
547
552
  /** Returns a seeded random value between the two values passed in using randSeed
548
553
  * @param {Number} [valueA=1]
549
554
  * @param {Number} [valueB=0]
@@ -571,6 +576,14 @@ const randSeeded = (a=1, b=0)=>
571
576
  */
572
577
  const vec2 = (x=0, y)=> x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y);
573
578
 
579
+ /**
580
+ * Check if object is a valid Vector2
581
+ * @param {Vector2} vector
582
+ * @return {Boolean}
583
+ * @memberof Utilities
584
+ */
585
+ const isVector2 = (v)=> !isNaN(v.x) && !isNaN(v.y);
586
+
574
587
  /**
575
588
  * 2D Vector object with vector math library
576
589
  * <br> - Functions do not change this so they can be chained together
@@ -600,27 +613,27 @@ class Vector2
600
613
  /** Returns a copy of this vector plus the vector passed in
601
614
  * @param {Vector2} vector
602
615
  * @return {Vector2} */
603
- add(v) { ASSERT(v.x!=undefined); return new Vector2(this.x + v.x, this.y + v.y); }
616
+ add(v) { ASSERT(isVector2(v)); return new Vector2(this.x + v.x, this.y + v.y); }
604
617
 
605
618
  /** Returns a copy of this vector minus the vector passed in
606
619
  * @param {Vector2} vector
607
620
  * @return {Vector2} */
608
- subtract(v) { ASSERT(v.x!=undefined); return new Vector2(this.x - v.x, this.y - v.y); }
621
+ subtract(v) { ASSERT(isVector2(v)); return new Vector2(this.x - v.x, this.y - v.y); }
609
622
 
610
623
  /** Returns a copy of this vector times the vector passed in
611
624
  * @param {Vector2} vector
612
625
  * @return {Vector2} */
613
- multiply(v) { ASSERT(v.x!=undefined); return new Vector2(this.x * v.x, this.y * v.y); }
626
+ multiply(v) { ASSERT(isVector2(v)); return new Vector2(this.x * v.x, this.y * v.y); }
614
627
 
615
628
  /** Returns a copy of this vector divided by the vector passed in
616
629
  * @param {Vector2} vector
617
630
  * @return {Vector2} */
618
- divide(v) { ASSERT(v.x!=undefined); return new Vector2(this.x / v.x, this.y / v.y); }
631
+ divide(v) { ASSERT(isVector2(v)); return new Vector2(this.x / v.x, this.y / v.y); }
619
632
 
620
633
  /** Returns a copy of this vector scaled by the vector passed in
621
634
  * @param {Number} scale
622
635
  * @return {Vector2} */
623
- scale(s) { ASSERT(s.x==undefined); return new Vector2(this.x * s, this.y * s); }
636
+ scale(s) { ASSERT(!isVector2(s)); return new Vector2(this.x * s, this.y * s); }
624
637
 
625
638
  /** Returns the length of this vector
626
639
  * @return {Number} */
@@ -653,12 +666,12 @@ class Vector2
653
666
  /** Returns the dot product of this and the vector passed in
654
667
  * @param {Vector2} vector
655
668
  * @return {Number} */
656
- dot(v) { ASSERT(v.x!=undefined); return this.x*v.x + this.y*v.y; }
669
+ dot(v) { ASSERT(isVector2(v)); return this.x*v.x + this.y*v.y; }
657
670
 
658
671
  /** Returns the cross product of this and the vector passed in
659
672
  * @param {Vector2} vector
660
673
  * @return {Number} */
661
- cross(v) { ASSERT(v.x!=undefined); return this.x*v.y - this.y*v.x; }
674
+ cross(v) { ASSERT(isVector2(v)); return this.x*v.y - this.y*v.x; }
662
675
 
663
676
  /** Returns the angle of this vector, up is angle 0
664
677
  * @return {Number} */
@@ -694,7 +707,7 @@ class Vector2
694
707
  * @param {Vector2} vector
695
708
  * @param {Number} percent
696
709
  * @return {Vector2} */
697
- lerp(v, p) { ASSERT(v.x!=undefined); return this.add(v.subtract(this).scale(clamp(p))); }
710
+ lerp(v, p) { ASSERT(isVector2(v)); return this.add(v.subtract(this).scale(clamp(p))); }
698
711
 
699
712
  /** Returns true if this vector is within the bounds of an array size passed in
700
713
  * @param {Vector2} arraySize
@@ -705,7 +718,7 @@ class Vector2
705
718
  * @param {float} digits - precision to display
706
719
  * @return {String} */
707
720
  toString(digits=3)
708
- { return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`; }
721
+ { if (debug) { return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`; }}
709
722
  }
710
723
 
711
724
  ///////////////////////////////////////////////////////////////////////////////
@@ -943,7 +956,7 @@ class Timer
943
956
 
944
957
  /** Returns this timer expressed as a string
945
958
  * @return {String} */
946
- toString() { if (debug) { return this.unset() ? 'unset' : Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ); } }
959
+ toString() { if (debug) { return this.unset() ? 'unset' : Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ); }}
947
960
 
948
961
  /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
949
962
  * @return {Number} */
@@ -956,6 +969,20 @@ class Timer
956
969
 
957
970
  'use strict';
958
971
 
972
+ ///////////////////////////////////////////////////////////////////////////////
973
+ // Camera settings
974
+
975
+ /** Position of camera in world space
976
+ * @type {Vector2}
977
+ * @default
978
+ * @memberof Settings */
979
+ let cameraPos = vec2();
980
+
981
+ /** Scale of camera in world space
982
+ * @default
983
+ * @memberof Settings */
984
+ let cameraScale = 16;
985
+
959
986
  ///////////////////////////////////////////////////////////////////////////////
960
987
  // Display settings
961
988
 
@@ -982,6 +1009,19 @@ let cavasPixelated = 1;
982
1009
  * @memberof Settings */
983
1010
  let fontDefault = 'arial';
984
1011
 
1012
+ ///////////////////////////////////////////////////////////////////////////////
1013
+ // WebGL settings
1014
+
1015
+ /** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
1016
+ * @default
1017
+ * @memberof Settings */
1018
+ let glEnable = 1;
1019
+
1020
+ /** Fixes slow rendering in some browsers by not compositing the WebGL canvas
1021
+ * @default
1022
+ * @memberof Settings */
1023
+ let glOverlay = 1;
1024
+
985
1025
  ///////////////////////////////////////////////////////////////////////////////
986
1026
  // Tile sheet settings
987
1027
 
@@ -1050,33 +1090,6 @@ let gravity = 0;
1050
1090
  * @memberof Settings */
1051
1091
  let particleEmitRateScale = 1;
1052
1092
 
1053
- ///////////////////////////////////////////////////////////////////////////////
1054
- // Camera settings
1055
-
1056
- /** Position of camera in world space
1057
- * @type {Vector2}
1058
- * @default
1059
- * @memberof Settings */
1060
- let cameraPos = vec2();
1061
-
1062
- /** Scale of camera in world space
1063
- * @default
1064
- * @memberof Settings */
1065
- let cameraScale = max(tileSizeDefault.x, tileSizeDefault.y);
1066
-
1067
- ///////////////////////////////////////////////////////////////////////////////
1068
- // WebGL settings
1069
-
1070
- /** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
1071
- * @default
1072
- * @memberof Settings */
1073
- let glEnable = 1;
1074
-
1075
- /** Fixes slow rendering in some browsers by not compositing the WebGL canvas
1076
- * @default
1077
- * @memberof Settings */
1078
- let glOverlay = 1;
1079
-
1080
1093
  ///////////////////////////////////////////////////////////////////////////////
1081
1094
  // Input settings
1082
1095
 
@@ -1110,7 +1123,7 @@ let touchGamepadAnalog = 1;
1110
1123
  /** Size of virutal gamepad for touch devices in pixels
1111
1124
  * @default
1112
1125
  * @memberof Settings */
1113
- let touchGamepadSize = 80;
1126
+ let touchGamepadSize = 99;
1114
1127
 
1115
1128
  /** Transparency of touch gamepad overlay
1116
1129
  * @default
@@ -1217,7 +1230,7 @@ class EngineObject
1217
1230
  constructor(pos=vec2(), size=objectDefaultSize, tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
1218
1231
  {
1219
1232
  // set passed in params
1220
- ASSERT(pos && pos.x != undefined && size.x != undefined); // ensure pos and size are vec2s
1233
+ ASSERT(isVector2(pos) && isVector2(size)); // ensure pos and size are vec2s
1221
1234
 
1222
1235
  /** @property {Vector2} - World space position of the object */
1223
1236
  this.pos = pos.copy();
@@ -1307,7 +1320,7 @@ class EngineObject
1307
1320
  if (this.collideSolidObjects)
1308
1321
  {
1309
1322
  // check collisions against solid objects
1310
- const epsilon = 1e-3; // necessary to push slightly outside of the collision
1323
+ const epsilon = .001; // necessary to push slightly outside of the collision
1311
1324
  for (const o of engineObjectsCollide)
1312
1325
  {
1313
1326
  // non solid objects don't collide with eachother
@@ -1570,11 +1583,6 @@ class EngineObject
1570
1583
 
1571
1584
  'use strict';
1572
1585
 
1573
- /** Tile sheet for batch rendering system
1574
- * @type {Image}
1575
- * @memberof Draw */
1576
- const tileImage = new Image();
1577
-
1578
1586
  /** The primary 2D canvas visible to the user
1579
1587
  * @type {HTMLCanvasElement}
1580
1588
  * @memberof Draw */
@@ -1600,6 +1608,14 @@ let overlayContext;
1600
1608
  * @memberof Draw */
1601
1609
  let mainCanvasSize = vec2();
1602
1610
 
1611
+ /** Tile sheet for batch rendering system
1612
+ * @type {Image}
1613
+ * @memberof Draw */
1614
+ const tileImage = new Image;
1615
+
1616
+ // Engine internal variables not exposed to documentation
1617
+ let tileImageSize, tileImageFixBleed, drawCount;
1618
+
1603
1619
  /** Convert from screen to world space coordinates
1604
1620
  * - if calling outside of render, you may need to manually set mainCanvasSize
1605
1621
  * @param {Vector2} screenPos
@@ -1624,7 +1640,7 @@ const worldToScreen = (worldPos)=>
1624
1640
 
1625
1641
  /** Draw textured tile centered in world space, with color applied if using WebGL
1626
1642
  * @param {Vector2} pos - Center of the tile in world space
1627
- * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space, width and height
1643
+ * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space
1628
1644
  * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
1629
1645
  * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
1630
1646
  * @param {Color} [color=new Color(1,1,1)] - Color to modulate with
@@ -1843,10 +1859,10 @@ class FontImage
1843
1859
  */
1844
1860
  constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
1845
1861
  {
1846
- if (!image && !engineFontImage)
1862
+ if (!engineFontImage)
1847
1863
  {
1848
1864
  // load default font image
1849
- engineFontImage = new Image();
1865
+ engineFontImage = new Image;
1850
1866
  engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
1851
1867
  }
1852
1868
 
@@ -1913,7 +1929,7 @@ class FontImage
1913
1929
  /** Returns true if fullscreen mode is active
1914
1930
  * @return {Boolean}
1915
1931
  * @memberof Draw */
1916
- const isFullscreen =()=> document.fullscreenElement;
1932
+ const isFullscreen = ()=> document.fullscreenElement;
1917
1933
 
1918
1934
  /** Toggle fullsceen mode
1919
1935
  * @memberof Draw */
@@ -1923,16 +1939,9 @@ function toggleFullscreen()
1923
1939
  {
1924
1940
  if (document.exitFullscreen)
1925
1941
  document.exitFullscreen();
1926
- else if (document.mozCancelFullScreen)
1927
- document.mozCancelFullScreen();
1928
- }
1929
- else
1930
- {
1931
- if (document.body.webkitRequestFullScreen)
1932
- document.body.webkitRequestFullScreen();
1933
- else if (document.body.mozRequestFullScreen)
1934
- document.body.mozRequestFullScreen();
1935
1942
  }
1943
+ else if (document.body.requestFullscreen)
1944
+ document.body.requestFullscreen();
1936
1945
  }
1937
1946
 
1938
1947
  /**
@@ -1951,7 +1960,7 @@ function toggleFullscreen()
1951
1960
  * @param {Number} [device=0]
1952
1961
  * @return {Boolean}
1953
1962
  * @memberof Input */
1954
- const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1 ? 1 : 0;
1963
+ const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1;
1955
1964
 
1956
1965
  /** Returns true if device key was pressed this frame
1957
1966
  * @param {Number} key
@@ -2190,32 +2199,41 @@ const isTouchDevice = window.ontouchstart !== undefined;
2190
2199
  // try to enable touch mouse
2191
2200
  if (isTouchDevice)
2192
2201
  {
2193
- // handle all touch events the same way
2194
- let wasTouching, hadTouch;
2195
- ontouchstart = ontouchmove = ontouchend = (e)=>
2202
+ // override mouse events
2203
+ let wasTouching, mouseDown = onmousedown, mouseUp = onmouseup;
2204
+ onmousedown = onmouseup = ()=> 0;
2205
+
2206
+ // setup touch input
2207
+ ontouchstart = (e)=>
2196
2208
  {
2197
- e.button = 0; // all touches are left click
2209
+ // fix mobile audio, force it to play a sound on first touch
2210
+ zzfx(0);
2198
2211
 
2199
- // check if touching and pass to mouse events
2200
- const touching = e.touches.length;
2201
- if (touching)
2212
+ // handle all touch events the same way
2213
+ ontouchstart = ontouchmove = ontouchend = (e)=>
2202
2214
  {
2203
- // fix mobile audio, force it to play a sound on first touch
2204
- hadTouch || zzfx(0, hadTouch=1);
2215
+ e.button = 0; // all touches are left click
2205
2216
 
2206
- // set event pos and pass it along
2207
- e.x = e.touches[0].clientX;
2208
- e.y = e.touches[0].clientY;
2209
- wasTouching ? onmousemove(e) : onmousedown(e);
2210
- }
2211
- else if (wasTouching)
2212
- onmouseup(e);
2217
+ // check if touching and pass to mouse events
2218
+ const touching = e.touches.length;
2219
+ if (touching)
2220
+ {
2221
+ // set event pos and pass it along
2222
+ e.x = e.touches[0].clientX;
2223
+ e.y = e.touches[0].clientY;
2224
+ wasTouching ? onmousemove(e) : mouseDown(e);
2225
+ }
2226
+ else if (wasTouching)
2227
+ mouseUp(e);
2228
+
2229
+ // set was touching
2230
+ wasTouching = touching;
2213
2231
 
2214
- // set was touching
2215
- wasTouching = touching;
2232
+ // must return true so the document will get focus
2233
+ return true;
2234
+ }
2216
2235
 
2217
- // must return true so the document will get focus
2218
- return true;
2236
+ return ontouchstart(e);
2219
2237
  }
2220
2238
  }
2221
2239
 
@@ -2223,7 +2241,7 @@ if (isTouchDevice)
2223
2241
  // touch gamepad, virtual on screen gamepad emulator for touch devices
2224
2242
 
2225
2243
  // touch input internal variables
2226
- let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
2244
+ let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
2227
2245
 
2228
2246
  // create the touch gamepad, called automatically by the engine
2229
2247
  function touchGamepadCreate()
@@ -2231,65 +2249,73 @@ function touchGamepadCreate()
2231
2249
  if (!touchGamepadEnable || !isTouchDevice)
2232
2250
  return;
2233
2251
 
2234
- ontouchstart = ontouchmove = ontouchend = (e)=>
2252
+ // touch input internal variables
2253
+ touchGamepadButtons = [];
2254
+ touchGamepadStick = vec2();
2255
+
2256
+ // setup touch input
2257
+ ontouchstart = (e)=>
2235
2258
  {
2236
- if (!touchGamepadEnable)
2237
- return;
2259
+ // fix mobile audio, force it to play a sound on first touch
2260
+ zzfx(0);
2238
2261
 
2239
- // clear touch gamepad input
2240
- touchGamepadStick = vec2();
2241
- touchGamepadButtons = [];
2242
-
2243
- const touching = e.touches.length;
2244
- if (touching)
2262
+ ontouchstart = ontouchmove = ontouchend = (e)=>
2245
2263
  {
2246
- touchGamepadTimer.isSet() || zzfx(0) ; // fix mobile audio, force it to play a sound the first time
2247
-
2248
- // set that gamepad is active
2249
- isUsingGamepad = 1;
2250
- touchGamepadTimer.set();
2251
-
2252
- if (paused)
2264
+ // clear touch gamepad input
2265
+ touchGamepadStick = vec2();
2266
+ touchGamepadButtons = [];
2267
+
2268
+ const touching = e.touches.length;
2269
+ if (touching)
2253
2270
  {
2254
- // touch anywhere to press start when paused
2255
- touchGamepadButtons[9] = 1;
2256
- return;
2271
+ // set that gamepad is active
2272
+ isUsingGamepad = 1;
2273
+ touchGamepadTimer.set();
2274
+
2275
+ if (paused)
2276
+ {
2277
+ // touch anywhere to press start when paused
2278
+ touchGamepadButtons[9] = 1;
2279
+ return;
2280
+ }
2257
2281
  }
2258
- }
2259
2282
 
2260
- // get center of left and right sides
2261
- const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2262
- const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
2263
- const startCenter = mainCanvasSize.scale(.5);
2283
+ // get center of left and right sides
2284
+ const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2285
+ const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
2286
+ const startCenter = mainCanvasSize.scale(.5);
2264
2287
 
2265
- // check each touch point
2266
- for (const touch of e.touches)
2267
- {
2268
- const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
2269
- if (touchPos.distance(stickCenter) < touchGamepadSize)
2288
+ // check each touch point
2289
+ for (const touch of e.touches)
2270
2290
  {
2271
- // virtual analog stick
2272
- if (touchGamepadAnalog)
2273
- touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
2274
- else
2291
+ const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
2292
+ if (touchPos.distance(stickCenter) < touchGamepadSize)
2275
2293
  {
2276
- // 8 way dpad
2277
- const angle = touchPos.subtract(stickCenter).angle();
2278
- touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
2294
+ // virtual analog stick
2295
+ if (touchGamepadAnalog)
2296
+ touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
2297
+ else
2298
+ {
2299
+ // 8 way dpad
2300
+ const angle = touchPos.subtract(stickCenter).angle();
2301
+ touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
2302
+ }
2303
+ }
2304
+ else if (touchPos.distance(buttonCenter) < touchGamepadSize)
2305
+ {
2306
+ // virtual face buttons
2307
+ const button = touchPos.subtract(buttonCenter).direction();
2308
+ touchGamepadButtons[button] = 1;
2309
+ }
2310
+ else if (touchPos.distance(startCenter) < touchGamepadSize)
2311
+ {
2312
+ // virtual start button in center
2313
+ touchGamepadButtons[9] = 1;
2279
2314
  }
2280
- }
2281
- else if (touchPos.distance(buttonCenter) < touchGamepadSize)
2282
- {
2283
- // virtual face buttons
2284
- const button = touchPos.subtract(buttonCenter).direction();
2285
- touchGamepadButtons[button] = 1;
2286
- }
2287
- else if (touchPos.distance(startCenter) < touchGamepadSize)
2288
- {
2289
- // virtual start button in center
2290
- touchGamepadButtons[9] = 1;
2291
2315
  }
2292
2316
  }
2317
+
2318
+ return ontouchstart(e);
2293
2319
  }
2294
2320
  }
2295
2321
 
@@ -2407,7 +2433,7 @@ class Sound
2407
2433
  {
2408
2434
  if (!soundEnable) return;
2409
2435
 
2410
- let pan = 0;
2436
+ let pan;
2411
2437
  if (pos)
2412
2438
  {
2413
2439
  const range = this.range;
@@ -2437,7 +2463,7 @@ class Sound
2437
2463
  * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
2438
2464
  * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
2439
2465
  */
2440
- playNote(semitoneOffset, pos, volume=1)
2466
+ playNote(semitoneOffset, pos, volume)
2441
2467
  {
2442
2468
  if (!soundEnable) return;
2443
2469
 
@@ -2492,7 +2518,7 @@ class Music
2492
2518
  * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
2493
2519
  * @return {AudioBufferSourceNode} - The audio node, can be used to stop sound later
2494
2520
  */
2495
- play(volume = 1, loop = 1)
2521
+ play(volume, loop = 1)
2496
2522
  {
2497
2523
  if (!soundEnable) return;
2498
2524
 
@@ -2574,7 +2600,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
2574
2600
 
2575
2601
  // create audio context
2576
2602
  if (!audioContext)
2577
- audioContext = new (window.AudioContext||webkitAudioContext);
2603
+ audioContext = new AudioContext;
2578
2604
 
2579
2605
  // fix stalled audio
2580
2606
  audioContext.resume();
@@ -2593,18 +2619,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
2593
2619
  source.playbackRate.value = rate;
2594
2620
  source.loop = loop;
2595
2621
 
2596
- // create and connect gain node (createGain is more widley spported then GainNode construtor)
2622
+ // create and connect gain node (createGain is more widely spported then GainNode construtor)
2597
2623
  const gainNode = audioContext.createGain();
2598
2624
  gainNode.gain.value = soundVolume*volume;
2599
2625
  gainNode.connect(audioContext.destination);
2600
2626
 
2601
- // connect source to gain
2602
- (
2603
- window.StereoPannerNode ? // create pan node if possible
2604
- source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
2605
- : source
2606
- )
2607
- .connect(gainNode);
2627
+ // connect source to stereo panner and gain
2628
+ source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)})).connect(gainNode);
2608
2629
 
2609
2630
  // play and return sound
2610
2631
  source.start();
@@ -2722,10 +2743,8 @@ function zzfxG
2722
2743
  * @memberof Audio */
2723
2744
  function zzfxM(instruments, patterns, sequence, BPM = 125)
2724
2745
  {
2746
+ let i, j, k;
2725
2747
  let instrumentParameters;
2726
- let i;
2727
- let j;
2728
- let k;
2729
2748
  let note;
2730
2749
  let sample;
2731
2750
  let patternChannel;
@@ -2895,14 +2914,11 @@ function tileCollisionRaycast(posStart, posEnd, object)
2895
2914
  {
2896
2915
  // test if a ray collides with tiles from start to end
2897
2916
  // todo: a way to get the exact hit point, it must still register as inside the hit tile
2898
- posStart = posStart.floor();
2899
- posEnd = posEnd.floor();
2900
- const posDelta = posEnd.subtract(posStart);
2917
+ const posDelta = (posEnd = posEnd.floor()).subtract(posStart = posStart.floor());
2901
2918
  const dx = abs(posDelta.x), dy = -abs(posDelta.y);
2902
2919
  const sx = sign(posDelta.x), sy = sign(posDelta.y);
2903
- let e = dx + dy;
2904
2920
 
2905
- for (let x = posStart.x, y = posStart.y;;)
2921
+ for (let x = posStart.x, y = posStart.y, e = dx + dy;;)
2906
2922
  {
2907
2923
  const tileData = getTileCollisionData(vec2(x,y));
2908
2924
  if (tileData && (object ? object.collideWithTileRaycast(tileData, new Vector2(x, y)) : tileData > 0))
@@ -2994,7 +3010,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
2994
3010
  // init tile data
2995
3011
  this.data = [];
2996
3012
  for (let j = this.size.area(); j--;)
2997
- this.data.push(new TileLayerData());
3013
+ this.data.push(new TileLayerData);
2998
3014
  }
2999
3015
 
3000
3016
  /** Set data at a given position in the array
@@ -3050,21 +3066,23 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3050
3066
  * @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
3051
3067
  redrawStart(clear = 0)
3052
3068
  {
3053
- if (clear)
3054
- {
3055
- // clear and set size
3056
- this.canvas.width = this.size.x * this.tileSize.x;
3057
- this.canvas.height = this.size.y * this.tileSize.y;
3058
- }
3059
-
3060
3069
  // save current render settings
3061
- this.savedRenderSettings = [mainCanvas, mainContext, cameraPos, cameraScale];
3070
+ this.savedRenderSettings = [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale];
3062
3071
 
3063
- // use normal rendering system to render the tiles
3072
+ // hack: use normal rendering system to render the tiles
3064
3073
  mainCanvas = this.canvas;
3065
3074
  mainContext = this.context;
3066
3075
  cameraPos = this.size.scale(.5);
3067
3076
  cameraScale = this.tileSize.x;
3077
+
3078
+ if (clear)
3079
+ {
3080
+ // clear and set size
3081
+ mainCanvas.width = this.size.x * this.tileSize.x;
3082
+ mainCanvas.height = this.size.y * this.tileSize.y;
3083
+ }
3084
+
3085
+ // begin a new render for the tile canvas
3068
3086
  enginePreRender();
3069
3087
  }
3070
3088
 
@@ -3076,7 +3094,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3076
3094
  //debugSaveCanvas(this.canvas);
3077
3095
 
3078
3096
  // set stuff back to normal
3079
- [mainCanvas, mainContext, cameraPos, cameraScale] = this.savedRenderSettings;
3097
+ [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale] = this.savedRenderSettings;
3080
3098
  }
3081
3099
 
3082
3100
  /** Draw the tile at a given position
@@ -3317,7 +3335,7 @@ class ParticleEmitter extends EngineObject
3317
3335
  this.parent && super.update();
3318
3336
 
3319
3337
  // update emitter
3320
- if (!this.emitTime || this.getAliveTime() <= this.emitTime)
3338
+ if (!this.emitTime | this.getAliveTime() <= this.emitTime)
3321
3339
  {
3322
3340
  // emit particles
3323
3341
  if (this.emitRate * particleEmitRateScale)
@@ -3339,9 +3357,9 @@ class ParticleEmitter extends EngineObject
3339
3357
  {
3340
3358
  // spawn a particle
3341
3359
  let pos = this.emitSize.x != undefined ? // check if vec2 was used for size
3342
- (new Vector2(rand(-.5,.5), rand(-.5,.5)))
3343
- .multiply(this.emitSize).rotate(this.angle) // box emitter
3344
- : randInCircle(this.emitSize * .5); // circle emitter
3360
+ (new Vector2(rand(-1,1), rand(-1,1)))
3361
+ .multiply(this.emitSize/2).rotate(this.angle) // box emitter
3362
+ : randInCircle(this.emitSize/2); // circle emitter
3345
3363
  let angle = rand(this.particleConeAngle, -this.particleConeAngle);
3346
3364
  if (!this.localSpace)
3347
3365
  {
@@ -3384,7 +3402,7 @@ class ParticleEmitter extends EngineObject
3384
3402
  particle.additive = this.additive;
3385
3403
  particle.renderOrder = this.renderOrder;
3386
3404
  particle.trailScale = this.trailScale;
3387
- particle.mirror = rand()<.5;
3405
+ particle.mirror = randInt(2);
3388
3406
  particle.localSpaceEmitter = this.localSpace && this;
3389
3407
 
3390
3408
  // setup callbacks for particles
@@ -3413,7 +3431,8 @@ class Particle extends EngineObject
3413
3431
  * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
3414
3432
  * @param {Number} [angle=0] - Angle to rotate the particle
3415
3433
  */
3416
- constructor(pos, tileIndex, tileSize, angle) { super(pos, new Vector2, tileIndex, tileSize, angle); }
3434
+ constructor(pos, tileIndex, tileSize, angle)
3435
+ { super(pos, new Vector2, tileIndex, tileSize, angle); }
3417
3436
 
3418
3437
  /** Render the particle, automatically called each frame, sorted by renderOrder */
3419
3438
  render()
@@ -3533,7 +3552,7 @@ class Medal
3533
3552
  this.name = name;
3534
3553
  this.description = description;
3535
3554
  this.icon = icon;
3536
- this.image = new Image();
3555
+ this.image = new Image;
3537
3556
  if (src)
3538
3557
  this.image.src = src;
3539
3558
  }
@@ -3548,10 +3567,7 @@ class Medal
3548
3567
  ASSERT(medalsSaveName); // save name must be set
3549
3568
  localStorage[this.storageKey()] = this.unlocked = 1;
3550
3569
  medalsDisplayQueue.push(this);
3551
-
3552
- // save for newgrounds and OS13K
3553
3570
  newgrounds && newgrounds.unlockMedal(this.id);
3554
- localStorage['OS13kTrophy,' + this.icon + ',' + medalsSaveName + ',' + this.name] = this.description;
3555
3571
  }
3556
3572
 
3557
3573
  /** Render a medal
@@ -3567,20 +3583,19 @@ class Medal
3567
3583
  // draw containing rect and clip to that region
3568
3584
  context.save();
3569
3585
  context.beginPath();
3570
- context.fillStyle = '#ddd'
3571
- context.fill(context.rect(x, y, width, medalDisplayHeight));
3572
- context.strokeStyle = '#000';
3586
+ context.fillStyle = new Color(.9,.9,.9);
3587
+ context.strokeStyle = new Color(0,0,0);
3573
3588
  context.lineWidth = 3;
3589
+ context.fill(context.rect(x, y, width, medalDisplayHeight));
3574
3590
  context.stroke();
3575
3591
  context.clip();
3576
3592
 
3577
3593
  // draw the icon and text
3578
- this.renderIcon(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2);
3579
- context.textAlign = 'left';
3580
- context.font = '38px '+ fontDefault;
3581
- context.fillText(this.name, x+medalDisplayIconSize+30, y+28);
3582
- context.font = '24px '+ fontDefault;
3583
- context.fillText(this.description, x+medalDisplayIconSize+30, y+60);
3594
+ this.renderIcon(vec2(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2));
3595
+ const pos = vec2(x+medalDisplayIconSize+30, y+28);
3596
+ drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, 0, 'left');
3597
+ pos.y += 32;
3598
+ drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, 0, 'left');
3584
3599
  context.restore();
3585
3600
  }
3586
3601
 
@@ -3589,18 +3604,13 @@ class Medal
3589
3604
  * @param {Number} y - Screen space Y position
3590
3605
  * @param {Number} [size=medalDisplayIconSize] - Screen space size
3591
3606
  */
3592
- renderIcon(x, y, size=medalDisplayIconSize)
3607
+ renderIcon(pos, size=medalDisplayIconSize)
3593
3608
  {
3594
3609
  // draw the image or icon
3595
- const context = overlayContext;
3596
- context.fillStyle = '#000';
3597
- context.textAlign = 'center';
3598
- context.textBaseline = 'middle';
3599
- context.font = size*.7 + 'px '+ fontDefault;
3600
- if (this.image.src)
3601
- context.drawImage(this.image, x-size/2, y-size/2, size, size);
3610
+ if (this.image)
3611
+ overlayContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
3602
3612
  else
3603
- context.fillText(this.icon, x, y); // show icon if there is no image
3613
+ drawTextScreen(this.icon, pos, size*.7, new Color(0,0,0));
3604
3614
  }
3605
3615
 
3606
3616
  // Get local storage key used by the medal
@@ -3658,13 +3668,21 @@ class Newgrounds
3658
3668
  constructor(app_id, cipher)
3659
3669
  {
3660
3670
  ASSERT(!newgrounds && app_id);
3671
+
3672
+ // create an instance of CryptoJS for encrypted calls
3673
+ if (cipher)
3674
+ {
3675
+ ///////////////////////////////////////////////////////////////////////////////
3676
+ // Crypto-JS - https://github.com/brix/crypto-js [The MIT License (MIT)]
3677
+ // Copyright (c) 2009-2013 Jeff Mott Copyright (c) 2013-2016 Evan Vosberg
3678
+
3679
+ this.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")([],[],1<<17,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(1<<15),new Uint8Array(51e6),0,0,0,0));
3680
+ }
3681
+
3661
3682
  this.app_id = app_id;
3662
3683
  this.cipher = cipher;
3663
3684
  this.host = location ? location.hostname : '';
3664
3685
 
3665
- // create an instance of CryptoJS for encrypted calls
3666
- cipher && (this.cryptoJS = CryptoJS());
3667
-
3668
3686
  // get session id from url search params
3669
3687
  const url = new URL(location.href);
3670
3688
  this.session_id = url.searchParams.get('ngio_session_id') || 0;
@@ -3767,12 +3785,6 @@ class Newgrounds
3767
3785
  return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
3768
3786
  }
3769
3787
  }
3770
-
3771
- ///////////////////////////////////////////////////////////////////////////////
3772
- // Crypto-JS - https://github.com/brix/crypto-js [The MIT License (MIT)]
3773
- // Copyright (c) 2009-2013 Jeff Mott Copyright (c) 2013-2016 Evan Vosberg
3774
-
3775
- 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")([],[],1<<17,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(1<<15),new Uint8Array(51e6),0,0,0,0));
3776
3788
  /**
3777
3789
  * LittleJS WebGL Interface
3778
3790
  * <br> - All webgl used by the engine is wrapped up here
@@ -3802,7 +3814,7 @@ let glContext;
3802
3814
  let glTileTexture;
3803
3815
 
3804
3816
  // WebGL internal variables not exposed to documentation
3805
- let glActiveTexture, glShader, glArrayBuffer, glVertexData, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
3817
+ let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
3806
3818
 
3807
3819
  ///////////////////////////////////////////////////////////////////////////////
3808
3820
 
@@ -3812,7 +3824,6 @@ function glInit()
3812
3824
  // create the canvas and tile texture
3813
3825
  glCanvas = document.createElement('canvas');
3814
3826
  glContext = glCanvas.getContext('webgl', {antialias: false});
3815
- glCanvas.style = styleCanvas;
3816
3827
  glTileTexture = glCreateTexture(tileImage);
3817
3828
 
3818
3829
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
@@ -3824,27 +3835,25 @@ function glInit()
3824
3835
  'uniform mat4 m;'+ // transform matrix
3825
3836
  'attribute vec2 p,t;'+ // position, uv
3826
3837
  'attribute vec4 c,a;'+ // color, additiveColor
3827
- 'varying vec2 v;'+ // return uv
3828
- 'varying vec4 d,e;'+ // return color, additiveColor
3838
+ 'varying vec4 v,d,e;'+ // return uv, color, additiveColor
3829
3839
  'void main(){'+ // shader entry point
3830
3840
  'gl_Position=m*vec4(p,1,1);'+ // transform position
3831
- 'v=t;d=c;e=a;'+ // pass stuff to fragment shader
3841
+ 'v=vec4(t,p);d=c;e=a;'+ // pass stuff to fragment shader
3832
3842
  '}' // end of shader
3833
3843
  ,
3834
- 'precision highp float;'+ // use highp for better accuracy
3835
- 'varying vec2 v;'+ // uv
3836
- 'varying vec4 d,e;'+ // color, additiveColor
3837
- 'uniform sampler2D s;'+ // texture
3838
- 'void main(){'+ // shader entry point
3839
- 'gl_FragColor=texture2D(s,v)*d+e;'+ // modulate texture by color plus additive
3840
- '}' // end of shader
3844
+ 'precision highp float;'+ // use highp for better accuracy
3845
+ 'varying vec4 v,d,e;'+ // uv, color, additiveColor
3846
+ 'uniform sampler2D s;'+ // texture
3847
+ 'void main(){'+ // shader entry point
3848
+ 'gl_FragColor=texture2D(s,v.xy)*d+e;'+ // modulate texture by color plus additive
3849
+ '}' // end of shader
3841
3850
  );
3842
3851
 
3843
3852
  // init buffers
3844
- glVertexData = new ArrayBuffer(gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE);
3853
+ const vertexData = new ArrayBuffer(gl_VERTEX_BUFFER_SIZE);
3845
3854
  glArrayBuffer = glContext.createBuffer();
3846
- glPositionData = new Float32Array(glVertexData);
3847
- glColorData = new Uint32Array(glVertexData);
3855
+ glPositionData = new Float32Array(vertexData);
3856
+ glColorData = new Uint32Array(vertexData);
3848
3857
  glBatchCount = 0;
3849
3858
  }
3850
3859
 
@@ -3919,18 +3928,19 @@ function glCreateTexture(image)
3919
3928
  image && image.width && glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
3920
3929
 
3921
3930
  // use point filtering for pixelated rendering
3922
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, cavasPixelated ? gl_NEAREST : gl_LINEAR);
3923
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, cavasPixelated ? gl_NEAREST : gl_LINEAR);
3931
+ const filter = cavasPixelated ? gl_NEAREST : gl_LINEAR;
3932
+ glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
3933
+ glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
3924
3934
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
3925
3935
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE);
3926
3936
  return texture;
3927
3937
  }
3928
3938
 
3929
3939
  // called automatically by engine before render
3930
- function glPreRender(width, height, cameraX, cameraY, cameraScale)
3940
+ function glPreRender()
3931
3941
  {
3932
3942
  // clear and set to same size as main canvas
3933
- glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
3943
+ glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
3934
3944
  glContext.clear(gl_COLOR_BUFFER_BIT);
3935
3945
 
3936
3946
  // set up the shader
@@ -3938,7 +3948,7 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
3938
3948
  glContext.activeTexture(gl_TEXTURE0);
3939
3949
  glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
3940
3950
  glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
3941
- glContext.bufferData(gl_ARRAY_BUFFER, glVertexData.byteLength, gl_DYNAMIC_DRAW);
3951
+ glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
3942
3952
  glSetBlendMode();
3943
3953
 
3944
3954
  // set vertex attributes
@@ -3956,14 +3966,14 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
3956
3966
  initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
3957
3967
 
3958
3968
  // build the transform matrix
3959
- const sx = 2 * cameraScale / width;
3960
- const sy = 2 * cameraScale / height;
3969
+ const sx = 2 * cameraScale / mainCanvas.width;
3970
+ const sy = 2 * cameraScale / mainCanvas.height;
3961
3971
  glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
3962
3972
  new Float32Array([
3963
3973
  sx, 0, 0, 0,
3964
3974
  0, sy, 0, 0,
3965
3975
  1, 1, -1, 1,
3966
- -1-sx*cameraX, -1-sy*cameraY, 0, 0
3976
+ -1-sx*cameraPos.x, -1-sy*cameraPos.y, 0, 0
3967
3977
  ])
3968
3978
  );
3969
3979
  }
@@ -4011,10 +4021,10 @@ function glCopyToContext(context, forceDraw)
4011
4021
  * @param uv0Y
4012
4022
  * @param uv1X
4013
4023
  * @param uv1Y
4014
- * @param [rgba=0xffffffff]
4024
+ * @param rgba
4015
4025
  * @param [rgbaAdditive=0]
4016
4026
  * @memberof WebGL */
4017
- function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffffff, rgbaAdditive=0)
4027
+ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
4018
4028
  {
4019
4029
  // flush if there is no room for more verts or if different blend mode
4020
4030
  if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
@@ -4025,43 +4035,16 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffff
4025
4035
  const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
4026
4036
 
4027
4037
  // setup 2 triangles to form a quad
4028
- let offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT;
4029
-
4030
- // vertex 0
4031
- glPositionData[offset++] = x - cx - sy;
4032
- glPositionData[offset++] = y - cy + sx;
4033
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
4034
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
4035
-
4036
- // vertex 1
4037
- glPositionData[offset++] = x + cx + sy;
4038
- glPositionData[offset++] = y + cy - sx;
4039
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
4040
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
4041
-
4042
- // vertex 2
4043
- glPositionData[offset++] = x - cx + sy;
4044
- glPositionData[offset++] = y + cy + sx;
4045
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
4046
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
4047
-
4048
- // vertex 0
4049
- glPositionData[offset++] = x - cx - sy;
4050
- glPositionData[offset++] = y - cy + sx;
4051
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
4052
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
4053
-
4054
- // vertex 3
4055
- glPositionData[offset++] = x + cx - sy;
4056
- glPositionData[offset++] = y - cy - sx;
4057
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
4058
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
4059
-
4060
- // vertex 1
4061
- glPositionData[offset++] = x + cx + sy;
4062
- glPositionData[offset++] = y + cy - sx;
4063
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
4064
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
4038
+ for(let i=6, offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT; i--;)
4039
+ {
4040
+ const a = i-4&&i>1, b = i-5&&i-2&&i-1;
4041
+ glPositionData[offset++] = x + (a?-cx:cx) + (b?sy:-sy);
4042
+ glPositionData[offset++] = y + (b?cy:-cy) + (a?sx:-sx);
4043
+ glPositionData[offset++] = a ? uv0X : uv1X;
4044
+ glPositionData[offset++] = b ? uv0Y : uv1Y;
4045
+ glColorData[offset++] = rgba;
4046
+ glColorData[offset++] = rgbaAdditive;
4047
+ }
4065
4048
  }
4066
4049
 
4067
4050
  ///////////////////////////////////////////////////////////////////////////////
@@ -4113,14 +4096,13 @@ function glRenderPostProcess()
4113
4096
  return;
4114
4097
 
4115
4098
  // prepare to render post process shader
4116
- const width = mainCanvas.width, height = mainCanvas.height;
4117
4099
  if (glEnable)
4118
4100
  {
4119
4101
  glFlush(); // clear out the buffer
4120
4102
  mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
4121
4103
  }
4122
- else
4123
- glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height); // set viewport
4104
+ else // set viewport
4105
+ glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
4124
4106
 
4125
4107
  // setup shader program to draw one triangle
4126
4108
  glContext.useProgram(glPostShader);
@@ -4144,7 +4126,7 @@ function glRenderPostProcess()
4144
4126
  const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
4145
4127
  glContext.uniform1i(uniformLocation('iChannel0'), 0);
4146
4128
  glContext.uniform1f(uniformLocation('iTime'), time);
4147
- glContext.uniform3f(uniformLocation('iResolution'), width, height, 1);
4129
+ glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
4148
4130
  glContext.drawArrays(gl_TRIANGLES, 0, 3);
4149
4131
  }
4150
4132
 
@@ -4184,7 +4166,8 @@ gl_UNPACK_FLIP_Y_WEBGL = 37440,
4184
4166
  gl_VERTICES_PER_QUAD = 6,
4185
4167
  gl_INDICIES_PER_VERT = 6,
4186
4168
  gl_MAX_BATCH = 1<<16,
4187
- gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
4169
+ gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
4170
+ gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE;
4188
4171
  /*
4189
4172
  LittleJS - The Tiny JavaScript Game Engine That Can!
4190
4173
  MIT License - Copyright 2021 Frank Force
@@ -4200,6 +4183,7 @@ gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
4200
4183
  - Particle effect system
4201
4184
  - Medal system tracks and displays achievements
4202
4185
  - Debug tools and debug rendering system
4186
+ - Post processing effects
4203
4187
  - Call engineInit() to start it up!
4204
4188
  */
4205
4189
 
@@ -4209,7 +4193,7 @@ gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
4209
4193
  const engineName = 'LittleJS';
4210
4194
 
4211
4195
  /** Version of engine */
4212
- const engineVersion = '1.4.6';
4196
+ const engineVersion = '1.4.8';
4213
4197
 
4214
4198
  /** Frames per second to update objects
4215
4199
  * @default */
@@ -4234,18 +4218,13 @@ let time = 0;
4234
4218
  /** Actual clock time since start in seconds (not affected by pause or frame rate clamping) */
4235
4219
  let timeReal = 0;
4236
4220
 
4237
- /** Is the game paused? Causes time and objects to not be updated. */
4221
+ /** Is the game paused? Causes time and objects to not be updated */
4238
4222
  let paused = 0;
4239
4223
 
4240
- // Engine internal variables not exposed to documentation
4241
- let frameTimeLastMS = 0, frameTimeBufferMS = 0, tileImageSize, tileImageFixBleed;
4242
-
4243
- // Engine stat tracking, if showWatermark is true
4244
- let averageFPS, drawCount;
4245
-
4246
- // css text used for elements created by engine
4247
- const styleBody = 'margin:0;overflow:hidden;background:#000';
4248
- const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
4224
+ /** Set if game is paused
4225
+ * @param {Boolean} paused
4226
+ */
4227
+ function setPaused(_paused) { paused = _paused; }
4249
4228
 
4250
4229
  ///////////////////////////////////////////////////////////////////////////////
4251
4230
 
@@ -4267,10 +4246,13 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4267
4246
  debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
4268
4247
 
4269
4248
  // setup html
4249
+ const styleBody = 'margin:0;overflow:hidden;background:#000' + // fill the window
4250
+ ';touch-action:none' + // prevent mobile pinch to resize
4251
+ ';user-select:none' + // prevent mobile hold to select
4252
+ ';-webkit-user-select:none'; // compatibility for ios
4270
4253
  document.body.style = styleBody;
4271
4254
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
4272
4255
  mainContext = mainCanvas.getContext('2d');
4273
- mainCanvas.style = styleCanvas;
4274
4256
 
4275
4257
  // init stuff and start engine
4276
4258
  debugInit();
@@ -4279,21 +4261,27 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4279
4261
  // create overlay canvas for hud to appear above gl canvas
4280
4262
  document.body.appendChild(overlayCanvas = document.createElement('canvas'));
4281
4263
  overlayContext = overlayCanvas.getContext('2d');
4282
- overlayCanvas.style = styleCanvas;
4264
+
4265
+ // set canvas style to fill the window
4266
+ const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
4267
+ (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
4283
4268
 
4284
4269
  gameInit();
4285
4270
  touchGamepadCreate();
4286
4271
  engineUpdate();
4287
4272
  };
4288
4273
 
4274
+ // frame time tracking
4275
+ let frameTimeLastMS = 0, frameTimeBufferMS, averageFPS;
4276
+
4289
4277
  // main update loop
4290
- const engineUpdate = (frameTimeMS=0)=>
4278
+ function engineUpdate(frameTimeMS=0)
4291
4279
  {
4292
4280
  // update time keeping
4293
4281
  let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
4294
4282
  frameTimeLastMS = frameTimeMS;
4295
4283
  if (debug || showWatermark)
4296
- averageFPS = lerp(.05, averageFPS || 0, 1e3/(frameTimeDeltaMS||1));
4284
+ averageFPS = lerp(.05, averageFPS, 1e3/(frameTimeDeltaMS||1));
4297
4285
  const debugSpeedUp = debug && keyIsDown(107); // +
4298
4286
  const debugSpeedDown = debug && keyIsDown(109); // -
4299
4287
  if (debug)
@@ -4305,24 +4293,19 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4305
4293
 
4306
4294
  if (canvasFixedSize.x)
4307
4295
  {
4308
- // clear set fixed size
4296
+ // clear canvas and set fixed size
4309
4297
  overlayCanvas.width = mainCanvas.width = canvasFixedSize.x;
4310
4298
  overlayCanvas.height = mainCanvas.height = canvasFixedSize.y;
4311
4299
 
4312
4300
  // fit to window by adding space on top or bottom if necessary
4313
4301
  const aspect = innerWidth / innerHeight;
4314
4302
  const fixedAspect = mainCanvas.width / mainCanvas.height;
4315
- mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
4316
- mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
4317
- if (glCanvas)
4318
- {
4319
- glCanvas.style.width = mainCanvas.style.width;
4320
- glCanvas.style.height = mainCanvas.style.height;
4321
- }
4303
+ (glCanvas||mainCanvas).style.width = mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
4304
+ (glCanvas||mainCanvas).style.height = mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
4322
4305
  }
4323
4306
  else
4324
4307
  {
4325
- // clear and set size to same as window
4308
+ // clear canvas and set size to same as window
4326
4309
  overlayCanvas.width = mainCanvas.width = min(innerWidth, canvasMaxSize.x);
4327
4310
  overlayCanvas.height = mainCanvas.height = min(innerHeight, canvasMaxSize.y);
4328
4311
  }
@@ -4389,7 +4372,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4389
4372
  overlayContext.fillStyle = '#000';
4390
4373
  const text = engineName + ' ' + 'v' + engineVersion + ' / '
4391
4374
  + drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
4392
- + ' ' + (glEnable ? 'GL' : '2D') ;
4375
+ + (glEnable ? ' GL' : ' 2D') ;
4393
4376
  overlayContext.fillText(text, mainCanvas.width-3, 3);
4394
4377
  overlayContext.fillStyle = '#fff';
4395
4378
  overlayContext.fillText(text, mainCanvas.width-2, 2);
@@ -4403,7 +4386,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4403
4386
  tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
4404
4387
  }
4405
4388
 
4406
- // called by engine to setup render system
4389
+ // Called automatically by engine to setup render system
4407
4390
  function enginePreRender()
4408
4391
  {
4409
4392
  // save canvas size
@@ -4413,12 +4396,10 @@ function enginePreRender()
4413
4396
  mainContext.imageSmoothingEnabled = !cavasPixelated;
4414
4397
 
4415
4398
  // setup gl rendering if enabled
4416
- glEnable && glPreRender(mainCanvas.width, mainCanvas.height, cameraPos.x, cameraPos.y, cameraScale);
4399
+ glEnable && glPreRender();
4417
4400
  }
4418
4401
 
4419
- ///////////////////////////////////////////////////////////////////////////////
4420
-
4421
- /** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
4402
+ /** Update each engine object, remove destroyed objects, and update time */
4422
4403
  function engineObjectsUpdate()
4423
4404
  {
4424
4405
  // get list of solid objects for physics optimzation
@@ -4482,14 +4463,15 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
4482
4463
  * <br> - Export engine as a module with extra functions where necessary
4483
4464
  */
4484
4465
 
4485
- // setters for all variables that devs will need to modify
4466
+ // Setters for all variables that devs will need to modify
4486
4467
  const setCameraPos = (v)=> cameraPos = v;
4487
4468
  const setCameraScale = (v)=> cameraScale = v;
4488
- const setRandSeed = (v)=> randSeed = v;
4489
4469
  const setCanvasMaxSize = (v)=> canvasMaxSize = v;
4490
4470
  const setCanvasFixedSize = (v)=> canvasFixedSize = v;
4491
4471
  const setCavasPixelated = (v)=> cavasPixelated = v;
4492
4472
  const setFontDefault = (v)=> fontDefault = v;
4473
+ const setGlEnable = (v)=> glEnable = v;
4474
+ const setGlOverlay = (v)=> glOverlay = v;
4493
4475
  const setTileSizeDefault = (v)=> tileSizeDefault = v;
4494
4476
  const setTileFixBleedScale = (v)=> tileFixBleedScale = v;
4495
4477
  const setObjectDefaultSize = (v)=> objectDefaultSize = v;
@@ -4502,8 +4484,6 @@ const setObjectDefaultFriction = (v)=> objectDefaultFriction = v;
4502
4484
  const setObjectMaxSpeed = (v)=> objectMaxSpeed = v;
4503
4485
  const setGravity = (v)=> gravity = v;
4504
4486
  const setParticleEmitRateScale = (v)=> particleEmitRateScale = v;
4505
- const setGlEnable = (v)=> glEnable = v;
4506
- const setGlOverlay = (v)=> glOverlay = v;
4507
4487
  const setGamepadsEnable = (v)=> gamepadsEnable = v;
4508
4488
  const setGamepadDirectionEmulateStick = (v)=> gamepadDirectionEmulateStick = v;
4509
4489
  const setInputWASDEmulateDirection = (v)=> inputWASDEmulateDirection = v;
@@ -4522,18 +4502,17 @@ const setMedalDisplayWidth = (v)=> medalDisplayWidth = v;
4522
4502
  const setMedalDisplayHeight = (v)=> medalDisplayHeight = v;
4523
4503
  const setMedalDisplayIconSize = (v)=> medalDisplayIconSize = v;
4524
4504
  const setMedalsPreventUnlock = (v)=> medalsPreventUnlock = v;
4525
- const setShowWatermark = (v)=> showWatermark = v;
4526
- const setGodMode = (v)=> godMode = v;
4527
4505
 
4528
4506
  export {
4529
- // Custom methods
4507
+ // Setters for global variables
4530
4508
  setCameraPos,
4531
4509
  setCameraScale,
4532
- setRandSeed,
4533
4510
  setCanvasMaxSize,
4534
4511
  setCanvasFixedSize,
4535
4512
  setCavasPixelated,
4536
4513
  setFontDefault,
4514
+ setGlEnable,
4515
+ setGlOverlay,
4537
4516
  setTileSizeDefault,
4538
4517
  setTileFixBleedScale,
4539
4518
  setObjectDefaultSize,
@@ -4546,8 +4525,6 @@ export {
4546
4525
  setObjectMaxSpeed,
4547
4526
  setGravity,
4548
4527
  setParticleEmitRateScale,
4549
- setGlEnable,
4550
- setGlOverlay,
4551
4528
  setGamepadsEnable,
4552
4529
  setGamepadDirectionEmulateStick,
4553
4530
  setInputWASDEmulateDirection,
@@ -4566,8 +4543,6 @@ export {
4566
4543
  setMedalDisplayHeight,
4567
4544
  setMedalDisplayIconSize,
4568
4545
  setMedalsPreventUnlock,
4569
- setShowWatermark,
4570
- setGodMode,
4571
4546
 
4572
4547
  // Settings
4573
4548
  canvasMaxSize,
@@ -4659,6 +4634,7 @@ export {
4659
4634
  randVector,
4660
4635
  randColor,
4661
4636
  randSeed,
4637
+ setRandSeed,
4662
4638
  randSeeded,
4663
4639
 
4664
4640
  // Utility Classes
@@ -4723,15 +4699,12 @@ export {
4723
4699
  // onmousemove,
4724
4700
  // onwheel,
4725
4701
  // oncontextmenu,
4726
- mouseToScreen,
4727
4702
  //stickData,
4703
+ mouseToScreen,
4728
4704
  gamepadsUpdate,
4729
4705
  vibrate,
4730
4706
  vibrateStop,
4731
4707
  isTouchDevice,
4732
- //touchGamepadTimer,
4733
- touchGamepadCreate,
4734
- touchGamepadRender,
4735
4708
 
4736
4709
  // Audio
4737
4710
  Sound,
@@ -4799,14 +4772,9 @@ export {
4799
4772
  time,
4800
4773
  timeReal,
4801
4774
  paused,
4802
- //frameTimeLastMS,
4803
- //averageFPS,
4804
- //drawCount,
4805
- //styleBody,
4806
- //styleCanvas,
4775
+ setPaused,
4807
4776
  engineInit,
4808
- //enginePreRender,
4809
- //engineObjectsUpdate,
4777
+ engineObjectsUpdate,
4810
4778
  engineObjectsDestroy,
4811
4779
  engineObjectsCallback,
4812
4780
  };