littlejsengine 1.9.7 → 1.9.9
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/LICENSE +22 -0
- package/README.md +13 -8
- package/dist/littlejs.d.ts +75 -69
- package/dist/littlejs.esm.js +313 -354
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +307 -350
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +244 -319
- package/examples/box2d/game.js +137 -0
- package/examples/box2d/index.html +12 -0
- package/examples/box2d/scenes.js +412 -0
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +3 -3
- package/examples/breakout/index.html +4 -3
- package/examples/breakoutTutorial/index.html +2 -2
- package/examples/electron/build.js +1 -1
- package/examples/electron/index.html +2 -2
- package/examples/js13k/build.js +19 -1
- package/examples/js13k/index.html +13 -13
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +1 -1
- package/examples/platformer/gameEffects.js +2 -2
- package/examples/platformer/gameLevel.js +0 -1
- package/examples/platformer/index.html +8 -8
- package/examples/puzzle/index.html +2 -2
- package/examples/starter/build.js +1 -1
- package/examples/starter/index.html +13 -13
- package/examples/stress/index.html +1 -1
- package/examples/typescript/index.html +1 -1
- package/package.json +1 -1
- package/plugins/Box2D_v2.3.1_min.wasm.js +630 -0
- package/plugins/Box2D_v2.3.1_min.wasm.wasm +0 -0
- package/plugins/box2d.js +879 -0
- package/plugins/newgrounds.js +169 -0
- package/plugins/postProcess.js +102 -0
- package/src/engine.js +48 -29
- package/src/engineAudio.js +20 -12
- package/src/engineBuild.js +1 -1
- package/src/engineDebug.js +68 -33
- package/src/engineDraw.js +1 -1
- package/src/engineExport.js +6 -4
- package/src/engineInput.js +7 -4
- package/src/engineMedals.js +40 -171
- package/src/engineObject.js +33 -2
- package/src/engineRelease.js +5 -2
- package/src/engineSettings.js +14 -2
- package/src/engineUtilities.js +75 -2
- package/src/engineWebGL.js +1 -94
package/dist/littlejs.release.js
CHANGED
|
@@ -26,13 +26,16 @@ function debugInit (){}
|
|
|
26
26
|
function debugUpdate (){}
|
|
27
27
|
function debugRender (){}
|
|
28
28
|
function debugRect (){}
|
|
29
|
+
function debugPoly (){}
|
|
29
30
|
function debugCircle (){}
|
|
30
31
|
function debugPoint (){}
|
|
31
32
|
function debugLine (){}
|
|
32
|
-
function
|
|
33
|
+
function debugOverlap (){}
|
|
33
34
|
function debugText (){}
|
|
34
35
|
function debugClear (){}
|
|
35
|
-
function debugSaveCanvas (){}
|
|
36
|
+
function debugSaveCanvas (){}
|
|
37
|
+
function debugSaveText (){}
|
|
38
|
+
function debugSaveDataURL(){}
|
|
36
39
|
/**
|
|
37
40
|
* LittleJS Utility Classes and Functions
|
|
38
41
|
* - General purpose math library
|
|
@@ -336,7 +339,7 @@ class RandomGenerator
|
|
|
336
339
|
*/
|
|
337
340
|
function vec2(x=0, y)
|
|
338
341
|
{
|
|
339
|
-
return typeof x
|
|
342
|
+
return typeof x == 'number' ?
|
|
340
343
|
new Vector2(x, y == undefined? x : y) :
|
|
341
344
|
new Vector2(x.x, x.y);
|
|
342
345
|
}
|
|
@@ -365,13 +368,19 @@ class Vector2
|
|
|
365
368
|
* @param {Number} [y] - Y axis location */
|
|
366
369
|
constructor(x=0, y=0)
|
|
367
370
|
{
|
|
368
|
-
ASSERT(typeof x
|
|
371
|
+
ASSERT(typeof x == 'number' && typeof y == 'number');
|
|
369
372
|
/** @property {Number} - X axis location */
|
|
370
373
|
this.x = x;
|
|
371
374
|
/** @property {Number} - Y axis location */
|
|
372
375
|
this.y = y;
|
|
373
376
|
}
|
|
374
377
|
|
|
378
|
+
/** Sets values of this vector and returns self
|
|
379
|
+
* @param {Number} [x] - X axis location
|
|
380
|
+
* @param {Number} [y] - Y axis location
|
|
381
|
+
* @return {Vector2} */
|
|
382
|
+
set(x=0, y=0) { this.x=x; this.y=y; return this; }
|
|
383
|
+
|
|
375
384
|
/** Returns a new vector that is a copy of this
|
|
376
385
|
* @return {Vector2} */
|
|
377
386
|
copy() { return new Vector2(this.x, this.y); }
|
|
@@ -623,6 +632,15 @@ class Color
|
|
|
623
632
|
this.a = a;
|
|
624
633
|
}
|
|
625
634
|
|
|
635
|
+
/** Sets values of this color and returns self
|
|
636
|
+
* @param {Number} [r] - red
|
|
637
|
+
* @param {Number} [g] - green
|
|
638
|
+
* @param {Number} [b] - blue
|
|
639
|
+
* @param {Number} [a] - alpha
|
|
640
|
+
* @return {Color} */
|
|
641
|
+
set(r=1, g=1, b=1, a=1)
|
|
642
|
+
{ this.r=r; this.g=g; this.b=b; this.a=a; return this; }
|
|
643
|
+
|
|
626
644
|
/** Returns a new color that is a copy of this
|
|
627
645
|
* @return {Color} */
|
|
628
646
|
copy() { return new Color(this.r, this.g, this.b, this.a); }
|
|
@@ -784,6 +802,64 @@ class Color
|
|
|
784
802
|
}
|
|
785
803
|
}
|
|
786
804
|
|
|
805
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
806
|
+
// default colors
|
|
807
|
+
|
|
808
|
+
/** Color - White
|
|
809
|
+
* @type {Color}
|
|
810
|
+
* @memberof Utilities */
|
|
811
|
+
const WHITE = rgb();
|
|
812
|
+
|
|
813
|
+
/** Color - Black
|
|
814
|
+
* @type {Color}
|
|
815
|
+
* @memberof Utilities */
|
|
816
|
+
const BLACK = rgb(0,0,0);
|
|
817
|
+
|
|
818
|
+
/** Color - Gray
|
|
819
|
+
* @type {Color}
|
|
820
|
+
* @memberof Utilities */
|
|
821
|
+
const GRAY = rgb(.5,.5,.5);
|
|
822
|
+
|
|
823
|
+
/** Color - Red
|
|
824
|
+
* @type {Color}
|
|
825
|
+
* @memberof Utilities */
|
|
826
|
+
const RED = rgb(1,0,0);
|
|
827
|
+
|
|
828
|
+
/** Color - Orange
|
|
829
|
+
* @type {Color}
|
|
830
|
+
* @memberof Utilities */
|
|
831
|
+
const ORANGE = rgb(1,.5,0);
|
|
832
|
+
|
|
833
|
+
/** Color - Yellow
|
|
834
|
+
* @type {Color}
|
|
835
|
+
* @memberof Utilities */
|
|
836
|
+
const YELLOW = rgb(1,1,0);
|
|
837
|
+
|
|
838
|
+
/** Color - Green
|
|
839
|
+
* @type {Color}
|
|
840
|
+
* @memberof Utilities */
|
|
841
|
+
const GREEN = rgb(0,1,0);
|
|
842
|
+
|
|
843
|
+
/** Color - Cyan
|
|
844
|
+
* @type {Color}
|
|
845
|
+
* @memberof Utilities */
|
|
846
|
+
const CYAN = rgb(0,1,1);
|
|
847
|
+
|
|
848
|
+
/** Color - Blue
|
|
849
|
+
* @type {Color}
|
|
850
|
+
* @memberof Utilities */
|
|
851
|
+
const BLUE = rgb(0,0,1);
|
|
852
|
+
|
|
853
|
+
/** Color - Purple
|
|
854
|
+
* @type {Color}
|
|
855
|
+
* @memberof Utilities */
|
|
856
|
+
const PURPLE = rgb(.5,0,1);
|
|
857
|
+
|
|
858
|
+
/** Color - Magenta
|
|
859
|
+
* @type {Color}
|
|
860
|
+
* @memberof Utilities */
|
|
861
|
+
const MAGENTA = rgb(1,0,1);
|
|
862
|
+
|
|
787
863
|
///////////////////////////////////////////////////////////////////////////////
|
|
788
864
|
|
|
789
865
|
/**
|
|
@@ -864,9 +940,9 @@ let cameraScale = 32;
|
|
|
864
940
|
|
|
865
941
|
/** The max size of the canvas, centered if window is larger
|
|
866
942
|
* @type {Vector2}
|
|
867
|
-
* @default Vector2(1920,
|
|
943
|
+
* @default Vector2(1920,1080)
|
|
868
944
|
* @memberof Settings */
|
|
869
|
-
let canvasMaxSize = vec2(1920,
|
|
945
|
+
let canvasMaxSize = vec2(1920, 1080);
|
|
870
946
|
|
|
871
947
|
/** Fixed size of the canvas, if enabled canvas size never changes
|
|
872
948
|
* - you may also need to set mainCanvasSize if using screen space coords in startup
|
|
@@ -1007,6 +1083,13 @@ let gamepadDirectionEmulateStick = true;
|
|
|
1007
1083
|
* @memberof Settings */
|
|
1008
1084
|
let inputWASDEmulateDirection = true;
|
|
1009
1085
|
|
|
1086
|
+
/** True if touch input is enabled for mobile devices
|
|
1087
|
+
* - Touch events will be routed to mouse events
|
|
1088
|
+
* @type {Boolean}
|
|
1089
|
+
* @default
|
|
1090
|
+
* @memberof Settings */
|
|
1091
|
+
let touchInputEnable = true;
|
|
1092
|
+
|
|
1010
1093
|
/** True if touch gamepad should appear on mobile devices
|
|
1011
1094
|
* - Supports left analog stick, 4 face buttons and start button (button 9)
|
|
1012
1095
|
* - Must be set by end of gameInit to be activated
|
|
@@ -1222,6 +1305,11 @@ function setGamepadDirectionEmulateStick(enable) { gamepadDirectionEmulateStick
|
|
|
1222
1305
|
* @memberof Settings */
|
|
1223
1306
|
function setInputWASDEmulateDirection(enable) { inputWASDEmulateDirection = enable; }
|
|
1224
1307
|
|
|
1308
|
+
/** Set if touch input is allowed
|
|
1309
|
+
* @param {Boolean} enable
|
|
1310
|
+
* @memberof Settings */
|
|
1311
|
+
function setTouchInputEnable(enable) { touchInputEnable = enable; }
|
|
1312
|
+
|
|
1225
1313
|
/** Set if touch gamepad should appear on mobile devices
|
|
1226
1314
|
* @param {Boolean} enable
|
|
1227
1315
|
* @memberof Settings */
|
|
@@ -1513,7 +1601,7 @@ class EngineObject
|
|
|
1513
1601
|
if (o.mass) // push away if not fixed
|
|
1514
1602
|
o.velocity = o.velocity.subtract(velocity);
|
|
1515
1603
|
|
|
1516
|
-
debugOverlay && debugPhysics &&
|
|
1604
|
+
debugOverlay && debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
|
|
1517
1605
|
continue;
|
|
1518
1606
|
}
|
|
1519
1607
|
|
|
@@ -1575,7 +1663,7 @@ class EngineObject
|
|
|
1575
1663
|
else // bounce if other object is fixed
|
|
1576
1664
|
this.velocity.x *= -elasticity;
|
|
1577
1665
|
}
|
|
1578
|
-
debugOverlay && debugPhysics &&
|
|
1666
|
+
debugOverlay && debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f0f');
|
|
1579
1667
|
}
|
|
1580
1668
|
}
|
|
1581
1669
|
if (this.collideTiles)
|
|
@@ -1636,6 +1724,22 @@ class EngineObject
|
|
|
1636
1724
|
for (const child of this.children)
|
|
1637
1725
|
child.destroy(child.parent = 0);
|
|
1638
1726
|
}
|
|
1727
|
+
|
|
1728
|
+
/** Convert from local space to world space
|
|
1729
|
+
* @param {Vector2} pos - local space point */
|
|
1730
|
+
localToWorld(pos) { return this.pos.add(pos.rotate(-this.angle)); }
|
|
1731
|
+
|
|
1732
|
+
/** Convert from world space to local space
|
|
1733
|
+
* @param {Vector2} pos - world space point */
|
|
1734
|
+
worldToLocal(pos) { return pos.subtract(this.pos).rotate(this.angle); }
|
|
1735
|
+
|
|
1736
|
+
/** Convert from local space to world space for a vector (rotation only)
|
|
1737
|
+
* @param {Vector2} vec - local space vector */
|
|
1738
|
+
localToWorldVector(vec) { return vec.rotate(this.angle); }
|
|
1739
|
+
|
|
1740
|
+
/** Convert from world space to local space for a vector (rotation only)
|
|
1741
|
+
* @param {Vector2} vec - world space vector */
|
|
1742
|
+
worldToLocalVector(vec) { return vec.rotate(-this.angle); }
|
|
1639
1743
|
|
|
1640
1744
|
/** Called to check if a tile collision should be resolved
|
|
1641
1745
|
* @param {Number} tileData - the value of the tile at the position
|
|
@@ -1722,6 +1826,21 @@ class EngineObject
|
|
|
1722
1826
|
return text;
|
|
1723
1827
|
}
|
|
1724
1828
|
}
|
|
1829
|
+
|
|
1830
|
+
/** Render debug info for this object */
|
|
1831
|
+
renderDebugInfo()
|
|
1832
|
+
{
|
|
1833
|
+
if (debug)
|
|
1834
|
+
{
|
|
1835
|
+
// show object info for debugging
|
|
1836
|
+
const size = vec2(max(this.size.x, .2), max(this.size.y, .2));
|
|
1837
|
+
const color1 = rgb(this.collideTiles?1:0, this.collideSolidObjects?1:0, this.isSolid?1:0, this.parent?.2:.5);
|
|
1838
|
+
const color2 = this.parent ? rgb(1,1,1,.5) : rgb(0,0,0,.8);
|
|
1839
|
+
drawRect(this.pos, size, color1, this.angle, false);
|
|
1840
|
+
drawRect(this.pos, size.scale(.8), color2, this.angle, false);
|
|
1841
|
+
this.parent && drawLine(this.pos, this.parent.pos, .1, rgb(0,0,1,.5), false);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1725
1844
|
}
|
|
1726
1845
|
/**
|
|
1727
1846
|
* LittleJS Drawing System
|
|
@@ -2045,7 +2164,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
|
|
|
2045
2164
|
context.save();
|
|
2046
2165
|
context.translate(pos.x+.5, pos.y+.5);
|
|
2047
2166
|
context.rotate(angle);
|
|
2048
|
-
context.scale(mirror ? -size.x : size.x, size.y);
|
|
2167
|
+
context.scale(mirror ? -size.x : size.x, -size.y);
|
|
2049
2168
|
drawFunction(context);
|
|
2050
2169
|
context.restore();
|
|
2051
2170
|
}
|
|
@@ -2227,6 +2346,7 @@ function toggleFullscreen()
|
|
|
2227
2346
|
* - Tracks keyboard down, pressed, and released
|
|
2228
2347
|
* - Tracks mouse buttons, position, and wheel
|
|
2229
2348
|
* - Tracks multiple analog gamepads
|
|
2349
|
+
* - Touch input is handled as mouse input
|
|
2230
2350
|
* - Virtual gamepad for touch devices
|
|
2231
2351
|
* @namespace Input
|
|
2232
2352
|
*/
|
|
@@ -2360,7 +2480,8 @@ function inputUpdate()
|
|
|
2360
2480
|
if (headlessMode) return;
|
|
2361
2481
|
|
|
2362
2482
|
// clear input when lost focus (prevent stuck keys)
|
|
2363
|
-
isTouchDevice
|
|
2483
|
+
if(!(touchInputEnable && isTouchDevice) && !document.hasFocus())
|
|
2484
|
+
clearInput();
|
|
2364
2485
|
|
|
2365
2486
|
// update mouse world space position
|
|
2366
2487
|
mousePos = screenToWorld(mousePosScreen);
|
|
@@ -2432,7 +2553,7 @@ function inputInit()
|
|
|
2432
2553
|
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2433
2554
|
|
|
2434
2555
|
// init touch input
|
|
2435
|
-
if (isTouchDevice)
|
|
2556
|
+
if (isTouchDevice && touchInputEnable && !headlessMode)
|
|
2436
2557
|
touchInputInit();
|
|
2437
2558
|
}
|
|
2438
2559
|
|
|
@@ -2560,12 +2681,12 @@ function vibrateStop() { vibrate(0); }
|
|
|
2560
2681
|
|
|
2561
2682
|
/** True if a touch device has been detected
|
|
2562
2683
|
* @memberof Input */
|
|
2563
|
-
const isTouchDevice =
|
|
2684
|
+
const isTouchDevice = window.ontouchstart !== undefined;
|
|
2564
2685
|
|
|
2565
2686
|
// touch gamepad internal variables
|
|
2566
2687
|
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
2567
2688
|
|
|
2568
|
-
//
|
|
2689
|
+
// enable touch input mouse passthrough
|
|
2569
2690
|
function touchInputInit()
|
|
2570
2691
|
{
|
|
2571
2692
|
// add non passive touch event listeners
|
|
@@ -2674,6 +2795,7 @@ function touchInputInit()
|
|
|
2674
2795
|
// render the touch gamepad, called automatically by the engine
|
|
2675
2796
|
function touchGamepadRender()
|
|
2676
2797
|
{
|
|
2798
|
+
if (!touchInputEnable || !isTouchDevice || headlessMode) return;
|
|
2677
2799
|
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
2678
2800
|
return;
|
|
2679
2801
|
|
|
@@ -2841,7 +2963,17 @@ class Sound
|
|
|
2841
2963
|
|
|
2842
2964
|
// play the sound
|
|
2843
2965
|
const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
|
|
2844
|
-
|
|
2966
|
+
this.gainNode = audioContext.createGain();
|
|
2967
|
+
return this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate, this.gainNode);
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
/** Set the sound volume
|
|
2971
|
+
* @param {Number} [volume] - How much to scale volume by
|
|
2972
|
+
*/
|
|
2973
|
+
setVolume(volume=1)
|
|
2974
|
+
{
|
|
2975
|
+
if (this.gainNode)
|
|
2976
|
+
this.gainNode.gain.value = volume;
|
|
2845
2977
|
}
|
|
2846
2978
|
|
|
2847
2979
|
/** Stop the last instance of this sound that was played */
|
|
@@ -3029,15 +3161,16 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
3029
3161
|
let audioSuspended = false;
|
|
3030
3162
|
|
|
3031
3163
|
/** Play cached audio samples with given settings
|
|
3032
|
-
* @param {Array}
|
|
3033
|
-
* @param {Number}
|
|
3034
|
-
* @param {Number}
|
|
3035
|
-
* @param {Number}
|
|
3036
|
-
* @param {Boolean}
|
|
3037
|
-
* @param {Number}
|
|
3164
|
+
* @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
|
|
3165
|
+
* @param {Number} [volume] - How much to scale volume by
|
|
3166
|
+
* @param {Number} [rate] - The playback rate to use
|
|
3167
|
+
* @param {Number} [pan] - How much to apply stereo panning
|
|
3168
|
+
* @param {Boolean} [loop] - True if the sound should loop when it reaches the end
|
|
3169
|
+
* @param {Number} [sampleRate=44100] - Sample rate for the sound
|
|
3170
|
+
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing
|
|
3038
3171
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
3039
3172
|
* @memberof Audio */
|
|
3040
|
-
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR)
|
|
3173
|
+
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR, gainNode)
|
|
3041
3174
|
{
|
|
3042
3175
|
if (!soundEnable || headlessMode) return;
|
|
3043
3176
|
|
|
@@ -3063,11 +3196,8 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3063
3196
|
source.playbackRate.value = rate;
|
|
3064
3197
|
source.loop = loop;
|
|
3065
3198
|
|
|
3066
|
-
// set master gain volume
|
|
3067
|
-
setSoundVolume(soundVolume);
|
|
3068
|
-
|
|
3069
3199
|
// create and connect gain node
|
|
3070
|
-
|
|
3200
|
+
gainNode = gainNode || audioContext.createGain();
|
|
3071
3201
|
gainNode.gain.value = volume;
|
|
3072
3202
|
gainNode.connect(audioGainNode);
|
|
3073
3203
|
|
|
@@ -4048,9 +4178,9 @@ class Particle extends EngineObject
|
|
|
4048
4178
|
|
|
4049
4179
|
|
|
4050
4180
|
/** List of all medals
|
|
4051
|
-
* @type {
|
|
4181
|
+
* @type {Object}
|
|
4052
4182
|
* @memberof Medals */
|
|
4053
|
-
const medals =
|
|
4183
|
+
const medals = {};
|
|
4054
4184
|
|
|
4055
4185
|
// Engine internal variables not exposed to documentation
|
|
4056
4186
|
let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
|
|
@@ -4066,9 +4196,46 @@ function medalsInit(saveName)
|
|
|
4066
4196
|
{
|
|
4067
4197
|
// check if medals are unlocked
|
|
4068
4198
|
medalsSaveName = saveName;
|
|
4069
|
-
|
|
4199
|
+
if (!debugMedals)
|
|
4200
|
+
medalsForEach(medal=> medal.unlocked = (localStorage[medal.storageKey()] | 0));
|
|
4201
|
+
|
|
4202
|
+
// engine automatically renders medals
|
|
4203
|
+
engineAddPlugin(undefined, medalsRender);
|
|
4204
|
+
function medalsRender()
|
|
4205
|
+
{
|
|
4206
|
+
if (!medalsDisplayQueue.length)
|
|
4207
|
+
return;
|
|
4208
|
+
|
|
4209
|
+
// update first medal in queue
|
|
4210
|
+
const medal = medalsDisplayQueue[0];
|
|
4211
|
+
const time = timeReal - medalsDisplayTimeLast;
|
|
4212
|
+
if (!medalsDisplayTimeLast)
|
|
4213
|
+
medalsDisplayTimeLast = timeReal;
|
|
4214
|
+
else if (time > medalDisplayTime)
|
|
4215
|
+
{
|
|
4216
|
+
medalsDisplayTimeLast = 0;
|
|
4217
|
+
medalsDisplayQueue.shift();
|
|
4218
|
+
}
|
|
4219
|
+
else
|
|
4220
|
+
{
|
|
4221
|
+
// slide on/off medals
|
|
4222
|
+
const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
|
|
4223
|
+
const hidePercent =
|
|
4224
|
+
time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
|
|
4225
|
+
time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
|
|
4226
|
+
medal.render(hidePercent);
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4070
4229
|
}
|
|
4071
4230
|
|
|
4231
|
+
/** Calls a function for each medal
|
|
4232
|
+
* @param {Function} callback
|
|
4233
|
+
* @memberof Medals */
|
|
4234
|
+
function medalsForEach(callback)
|
|
4235
|
+
{ Object.values(medals).forEach(medal=>callback(medal)); }
|
|
4236
|
+
|
|
4237
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
4238
|
+
|
|
4072
4239
|
/**
|
|
4073
4240
|
* Medal - Tracks an unlockable medal
|
|
4074
4241
|
* @example
|
|
@@ -4113,7 +4280,6 @@ class Medal
|
|
|
4113
4280
|
ASSERT(medalsSaveName, 'save name must be set');
|
|
4114
4281
|
localStorage[this.storageKey()] = this.unlocked = 1;
|
|
4115
4282
|
medalsDisplayQueue.push(this);
|
|
4116
|
-
newgrounds && newgrounds.unlockMedal(this.id);
|
|
4117
4283
|
}
|
|
4118
4284
|
|
|
4119
4285
|
/** Render a medal
|
|
@@ -4161,173 +4327,6 @@ class Medal
|
|
|
4161
4327
|
|
|
4162
4328
|
// Get local storage key used by the medal
|
|
4163
4329
|
storageKey() { return medalsSaveName + '_' + this.id; }
|
|
4164
|
-
}
|
|
4165
|
-
|
|
4166
|
-
// engine automatically renders medals
|
|
4167
|
-
function medalsRender()
|
|
4168
|
-
{
|
|
4169
|
-
if (!medalsDisplayQueue.length)
|
|
4170
|
-
return;
|
|
4171
|
-
|
|
4172
|
-
// update first medal in queue
|
|
4173
|
-
const medal = medalsDisplayQueue[0];
|
|
4174
|
-
const time = timeReal - medalsDisplayTimeLast;
|
|
4175
|
-
if (!medalsDisplayTimeLast)
|
|
4176
|
-
medalsDisplayTimeLast = timeReal;
|
|
4177
|
-
else if (time > medalDisplayTime)
|
|
4178
|
-
{
|
|
4179
|
-
medalsDisplayTimeLast = 0;
|
|
4180
|
-
medalsDisplayQueue.shift();
|
|
4181
|
-
}
|
|
4182
|
-
else
|
|
4183
|
-
{
|
|
4184
|
-
// slide on/off medals
|
|
4185
|
-
const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
|
|
4186
|
-
const hidePercent =
|
|
4187
|
-
time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
|
|
4188
|
-
time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
|
|
4189
|
-
medal.render(hidePercent);
|
|
4190
|
-
}
|
|
4191
|
-
}
|
|
4192
|
-
|
|
4193
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
4194
|
-
|
|
4195
|
-
// global Newgrounds object
|
|
4196
|
-
let newgrounds;
|
|
4197
|
-
|
|
4198
|
-
/** This can used to enable Newgrounds functionality
|
|
4199
|
-
* @param {Number} app_id - The newgrounds App ID
|
|
4200
|
-
* @param {String} [cipher] - The encryption Key (AES-128/Base64)
|
|
4201
|
-
* @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher
|
|
4202
|
-
* @memberof Medals */
|
|
4203
|
-
function newgroundsInit(app_id, cipher, cryptoJS)
|
|
4204
|
-
{ newgrounds = new Newgrounds(app_id, cipher, cryptoJS); }
|
|
4205
|
-
|
|
4206
|
-
/**
|
|
4207
|
-
* Newgrounds API wrapper object
|
|
4208
|
-
* @example
|
|
4209
|
-
* // create a newgrounds object, replace the app id with your own
|
|
4210
|
-
* const app_id = '53123:1ZuSTQ9l';
|
|
4211
|
-
* newgrounds = new Newgrounds(app_id);
|
|
4212
|
-
*/
|
|
4213
|
-
class Newgrounds
|
|
4214
|
-
{
|
|
4215
|
-
/** Create a newgrounds object
|
|
4216
|
-
* @param {Number} app_id - The newgrounds App ID
|
|
4217
|
-
* @param {String} [cipher] - The encryption Key (AES-128/Base64)
|
|
4218
|
-
* @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher */
|
|
4219
|
-
constructor(app_id, cipher, cryptoJS)
|
|
4220
|
-
{
|
|
4221
|
-
ASSERT(!newgrounds && app_id>0, 'there can only be one newgrounds object');
|
|
4222
|
-
ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
|
|
4223
|
-
|
|
4224
|
-
this.app_id = app_id;
|
|
4225
|
-
this.cipher = cipher;
|
|
4226
|
-
this.cryptoJS = cryptoJS;
|
|
4227
|
-
this.host = location ? location.hostname : '';
|
|
4228
|
-
|
|
4229
|
-
// get session id from url search params
|
|
4230
|
-
const url = new URL(location.href);
|
|
4231
|
-
this.session_id = url.searchParams.get('ngio_session_id');
|
|
4232
|
-
|
|
4233
|
-
if (!this.session_id)
|
|
4234
|
-
return; // only use newgrounds when logged in
|
|
4235
|
-
|
|
4236
|
-
// get medals
|
|
4237
|
-
const medalsResult = this.call('Medal.getList');
|
|
4238
|
-
this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
|
|
4239
|
-
debugMedals && console.log(this.medals);
|
|
4240
|
-
for (const newgroundsMedal of this.medals)
|
|
4241
|
-
{
|
|
4242
|
-
const medal = medals[newgroundsMedal['id']];
|
|
4243
|
-
if (medal)
|
|
4244
|
-
{
|
|
4245
|
-
// copy newgrounds medal data
|
|
4246
|
-
medal.image = new Image;
|
|
4247
|
-
medal.image.src = newgroundsMedal['icon'];
|
|
4248
|
-
medal.name = newgroundsMedal['name'];
|
|
4249
|
-
medal.description = newgroundsMedal['description'];
|
|
4250
|
-
medal.unlocked = newgroundsMedal['unlocked'];
|
|
4251
|
-
medal.difficulty = newgroundsMedal['difficulty'];
|
|
4252
|
-
medal.value = newgroundsMedal['value'];
|
|
4253
|
-
|
|
4254
|
-
if (medal.value)
|
|
4255
|
-
medal.description = medal.description + ' (' + medal.value + ')';
|
|
4256
|
-
}
|
|
4257
|
-
}
|
|
4258
|
-
|
|
4259
|
-
// get scoreboards
|
|
4260
|
-
const scoreboardResult = this.call('ScoreBoard.getBoards');
|
|
4261
|
-
this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
|
|
4262
|
-
debugMedals && console.log(this.scoreboards);
|
|
4263
|
-
|
|
4264
|
-
const keepAliveMS = 5 * 60 * 1e3;
|
|
4265
|
-
setInterval(()=>this.call('Gateway.ping', 0, true), keepAliveMS);
|
|
4266
|
-
}
|
|
4267
|
-
|
|
4268
|
-
/** Send message to unlock a medal by id
|
|
4269
|
-
* @param {Number} id - The medal id */
|
|
4270
|
-
unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, true); }
|
|
4271
|
-
|
|
4272
|
-
/** Send message to post score
|
|
4273
|
-
* @param {Number} id - The scoreboard id
|
|
4274
|
-
* @param {Number} value - The score value */
|
|
4275
|
-
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, true); }
|
|
4276
|
-
|
|
4277
|
-
/** Get scores from a scoreboard
|
|
4278
|
-
* @param {Number} id - The scoreboard id
|
|
4279
|
-
* @param {String} [user] - A user's id or name
|
|
4280
|
-
* @param {Number} [social] - If true, only social scores will be loaded
|
|
4281
|
-
* @param {Number} [skip] - Number of scores to skip before start
|
|
4282
|
-
* @param {Number} [limit] - Number of scores to include in the list
|
|
4283
|
-
* @return {Object} - The response JSON object
|
|
4284
|
-
*/
|
|
4285
|
-
getScores(id, user, social=0, skip=0, limit=10)
|
|
4286
|
-
{ return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
|
|
4287
|
-
|
|
4288
|
-
/** Send message to log a view */
|
|
4289
|
-
logView() { return this.call('App.logView', {'host':this.host}, true); }
|
|
4290
|
-
|
|
4291
|
-
/** Send a message to call a component of the Newgrounds API
|
|
4292
|
-
* @param {String} component - Name of the component
|
|
4293
|
-
* @param {Object} [parameters] - Parameters to use for call
|
|
4294
|
-
* @param {Boolean} [async] - If true, don't wait for response before continuing
|
|
4295
|
-
* @return {Object} - The response JSON object
|
|
4296
|
-
*/
|
|
4297
|
-
call(component, parameters, async=false)
|
|
4298
|
-
{
|
|
4299
|
-
const call = {'component':component, 'parameters':parameters};
|
|
4300
|
-
if (this.cipher)
|
|
4301
|
-
{
|
|
4302
|
-
// encrypt using AES-128 Base64 with cryptoJS
|
|
4303
|
-
const cryptoJS = this.cryptoJS;
|
|
4304
|
-
const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
|
|
4305
|
-
const iv = cryptoJS['lib']['WordArray']['random'](16);
|
|
4306
|
-
const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
|
|
4307
|
-
call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
|
|
4308
|
-
call['parameters'] = 0;
|
|
4309
|
-
}
|
|
4310
|
-
|
|
4311
|
-
// build the input object
|
|
4312
|
-
const input =
|
|
4313
|
-
{
|
|
4314
|
-
'app_id': this.app_id,
|
|
4315
|
-
'session_id': this.session_id,
|
|
4316
|
-
'call': call
|
|
4317
|
-
};
|
|
4318
|
-
|
|
4319
|
-
// build post data
|
|
4320
|
-
const formData = new FormData();
|
|
4321
|
-
formData.append('input', JSON.stringify(input));
|
|
4322
|
-
|
|
4323
|
-
// send post data
|
|
4324
|
-
const xmlHttp = new XMLHttpRequest();
|
|
4325
|
-
const url = 'https://newgrounds.io/gateway_v3.php';
|
|
4326
|
-
xmlHttp.open('POST', url, !debugMedals && async);
|
|
4327
|
-
xmlHttp.send(formData);
|
|
4328
|
-
debugMedals && console.log(xmlHttp.responseText);
|
|
4329
|
-
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
4330
|
-
}
|
|
4331
4330
|
}
|
|
4332
4331
|
/**
|
|
4333
4332
|
* LittleJS WebGL Interface
|
|
@@ -4418,7 +4417,7 @@ function glPreRender()
|
|
|
4418
4417
|
|
|
4419
4418
|
// clear and set to same size as main canvas
|
|
4420
4419
|
glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
|
|
4421
|
-
|
|
4420
|
+
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
4422
4421
|
|
|
4423
4422
|
// set up the shader
|
|
4424
4423
|
glContext.useProgram(glShader);
|
|
@@ -4601,99 +4600,6 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
|
|
|
4601
4600
|
glInstanceCount++;
|
|
4602
4601
|
}
|
|
4603
4602
|
|
|
4604
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
4605
|
-
// post processing - can be enabled to pass other canvases through a final shader
|
|
4606
|
-
|
|
4607
|
-
let glPostShader, glPostTexture, glPostIncludeOverlay;
|
|
4608
|
-
|
|
4609
|
-
/** Set up a post processing shader
|
|
4610
|
-
* @param {String} shaderCode
|
|
4611
|
-
* @param {Boolean} includeOverlay
|
|
4612
|
-
* @memberof WebGL */
|
|
4613
|
-
function glInitPostProcess(shaderCode, includeOverlay=false)
|
|
4614
|
-
{
|
|
4615
|
-
ASSERT(!glPostShader, 'can only have 1 post effects shader');
|
|
4616
|
-
if (headlessMode) return;
|
|
4617
|
-
if (!shaderCode) // default shader pass through
|
|
4618
|
-
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
4619
|
-
|
|
4620
|
-
// create the shader
|
|
4621
|
-
glPostShader = glCreateProgram(
|
|
4622
|
-
'#version 300 es\n' + // specify GLSL ES version
|
|
4623
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
4624
|
-
'in vec2 p;'+ // position
|
|
4625
|
-
'void main(){'+ // shader entry point
|
|
4626
|
-
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
4627
|
-
'}' // end of shader
|
|
4628
|
-
,
|
|
4629
|
-
'#version 300 es\n' + // specify GLSL ES version
|
|
4630
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
4631
|
-
'uniform sampler2D iChannel0;'+ // input texture
|
|
4632
|
-
'uniform vec3 iResolution;'+ // size of output texture
|
|
4633
|
-
'uniform float iTime;'+ // time
|
|
4634
|
-
'out vec4 c;'+ // out color
|
|
4635
|
-
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
4636
|
-
'void main(){'+ // shader entry point
|
|
4637
|
-
'mainImage(c,gl_FragCoord.xy);'+ // call post process function
|
|
4638
|
-
'c.a=1.;'+ // always use full alpha
|
|
4639
|
-
'}' // end of shader
|
|
4640
|
-
);
|
|
4641
|
-
|
|
4642
|
-
// create buffer and texture
|
|
4643
|
-
glPostTexture = glCreateTexture(undefined);
|
|
4644
|
-
glPostIncludeOverlay = includeOverlay;
|
|
4645
|
-
|
|
4646
|
-
// hide the original 2d canvas
|
|
4647
|
-
mainCanvas.style.visibility = 'hidden';
|
|
4648
|
-
if (glPostIncludeOverlay)
|
|
4649
|
-
overlayCanvas.style.visibility = 'hidden';
|
|
4650
|
-
}
|
|
4651
|
-
|
|
4652
|
-
// Render the post processing shader, called automatically by the engine
|
|
4653
|
-
function glRenderPostProcess()
|
|
4654
|
-
{
|
|
4655
|
-
if (!glPostShader || headlessMode) return;
|
|
4656
|
-
|
|
4657
|
-
// prepare to render post process shader
|
|
4658
|
-
if (glEnable)
|
|
4659
|
-
{
|
|
4660
|
-
glFlush(); // clear out the buffer
|
|
4661
|
-
mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
|
|
4662
|
-
}
|
|
4663
|
-
else
|
|
4664
|
-
{
|
|
4665
|
-
// set the viewport
|
|
4666
|
-
glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
|
|
4667
|
-
}
|
|
4668
|
-
|
|
4669
|
-
// copy overlay canvas so it will be included in post processing
|
|
4670
|
-
glPostIncludeOverlay && mainContext.drawImage(overlayCanvas, 0, 0);
|
|
4671
|
-
|
|
4672
|
-
// setup shader program to draw one triangle
|
|
4673
|
-
glContext.useProgram(glPostShader);
|
|
4674
|
-
glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
|
|
4675
|
-
glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, 1);
|
|
4676
|
-
glContext.disable(gl_BLEND);
|
|
4677
|
-
|
|
4678
|
-
// set textures, pass in the 2d canvas and gl canvas in separate texture channels
|
|
4679
|
-
glContext.activeTexture(gl_TEXTURE0);
|
|
4680
|
-
glContext.bindTexture(gl_TEXTURE_2D, glPostTexture);
|
|
4681
|
-
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, mainCanvas);
|
|
4682
|
-
|
|
4683
|
-
// set vertex position attribute
|
|
4684
|
-
const vertexByteStride = 8;
|
|
4685
|
-
const pLocation = glContext.getAttribLocation(glPostShader, 'p');
|
|
4686
|
-
glContext.enableVertexAttribArray(pLocation);
|
|
4687
|
-
glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, false, vertexByteStride, 0);
|
|
4688
|
-
|
|
4689
|
-
// set uniforms and draw
|
|
4690
|
-
const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
|
|
4691
|
-
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
4692
|
-
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
4693
|
-
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
4694
|
-
glContext.drawArrays(gl_TRIANGLE_STRIP, 0, 4);
|
|
4695
|
-
}
|
|
4696
|
-
|
|
4697
4603
|
///////////////////////////////////////////////////////////////////////////////
|
|
4698
4604
|
// store gl constants as integers so their name doesn't use space in minifed
|
|
4699
4605
|
const
|
|
@@ -4758,7 +4664,7 @@ const engineName = 'LittleJS';
|
|
|
4758
4664
|
* @type {String}
|
|
4759
4665
|
* @default
|
|
4760
4666
|
* @memberof Engine */
|
|
4761
|
-
const engineVersion = '1.9.
|
|
4667
|
+
const engineVersion = '1.9.9';
|
|
4762
4668
|
|
|
4763
4669
|
/** Frames per second to update
|
|
4764
4670
|
* @type {Number}
|
|
@@ -4812,6 +4718,22 @@ function setPaused(isPaused) { paused = isPaused; }
|
|
|
4812
4718
|
let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
|
|
4813
4719
|
|
|
4814
4720
|
///////////////////////////////////////////////////////////////////////////////
|
|
4721
|
+
// plugin hooks
|
|
4722
|
+
|
|
4723
|
+
const pluginUpdateList = [], pluginRenderList = [];
|
|
4724
|
+
|
|
4725
|
+
/** Add a new update function for a plugin
|
|
4726
|
+
* @param {Function} [updateFunction]
|
|
4727
|
+
* @param {Function} [renderFunction]
|
|
4728
|
+
* @memberof Engine */
|
|
4729
|
+
function engineAddPlugin(updateFunction, renderFunction)
|
|
4730
|
+
{
|
|
4731
|
+
updateFunction && pluginUpdateList.push(updateFunction);
|
|
4732
|
+
renderFunction && pluginRenderList.push(renderFunction);
|
|
4733
|
+
}
|
|
4734
|
+
|
|
4735
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
4736
|
+
// Main engine functions
|
|
4815
4737
|
|
|
4816
4738
|
/** Startup LittleJS engine with your callback functions
|
|
4817
4739
|
* @param {Function} gameInit - Called once after the engine starts up, setup the game
|
|
@@ -4887,6 +4809,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4887
4809
|
// update game and objects
|
|
4888
4810
|
inputUpdate();
|
|
4889
4811
|
gameUpdate();
|
|
4812
|
+
pluginUpdateList.forEach(f=>f());
|
|
4890
4813
|
engineObjectsUpdate();
|
|
4891
4814
|
|
|
4892
4815
|
// do post update
|
|
@@ -4908,8 +4831,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4908
4831
|
for (const o of engineObjects)
|
|
4909
4832
|
o.destroyed || o.render();
|
|
4910
4833
|
gameRenderPost();
|
|
4911
|
-
|
|
4912
|
-
medalsRender();
|
|
4834
|
+
pluginRenderList.forEach(f=>f());
|
|
4913
4835
|
touchGamepadRender();
|
|
4914
4836
|
debugRender();
|
|
4915
4837
|
glCopyToContext(mainContext);
|
|
@@ -4981,10 +4903,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4981
4903
|
const styleBody =
|
|
4982
4904
|
'margin:0;overflow:hidden;' + // fill the window
|
|
4983
4905
|
'background:#000;' + // set background color
|
|
4984
|
-
'
|
|
4985
|
-
'user-select:none;' + // prevent mobile hold to select
|
|
4906
|
+
'user-select:none;' + // prevent hold to select
|
|
4986
4907
|
'-webkit-user-select:none;' + // compatibility for ios
|
|
4987
|
-
'
|
|
4908
|
+
(!touchInputEnable ? '' : // no touch css setttings
|
|
4909
|
+
'touch-action:none;' + // prevent mobile pinch to resize
|
|
4910
|
+
'-webkit-touch-callout:none');// compatibility for ios
|
|
4988
4911
|
document.body.style.cssText = styleBody;
|
|
4989
4912
|
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
4990
4913
|
mainContext = mainCanvas.getContext('2d');
|
|
@@ -5199,21 +5122,23 @@ function drawEngineSplashScreen(t)
|
|
|
5199
5122
|
x.setLineDash([99*p2,99]);
|
|
5200
5123
|
|
|
5201
5124
|
// cab top
|
|
5202
|
-
rect(7,
|
|
5203
|
-
rect(7,
|
|
5204
|
-
rect(25,
|
|
5205
|
-
rect(25,
|
|
5206
|
-
rect(25,
|
|
5125
|
+
rect(7,16,18,-8,color(2,2));
|
|
5126
|
+
rect(7,8,18,4,color(2,3));
|
|
5127
|
+
rect(25,8,8,8,color(2,1));
|
|
5128
|
+
rect(25,8,-18,8);
|
|
5129
|
+
rect(25,8,8,8);
|
|
5207
5130
|
|
|
5208
5131
|
// cab
|
|
5209
|
-
rect(25,
|
|
5210
|
-
rect(11,
|
|
5211
|
-
rect(11,
|
|
5212
|
-
rect(11,
|
|
5213
|
-
rect(
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
rect(
|
|
5132
|
+
rect(25,16,7,23,color());
|
|
5133
|
+
rect(11,39,14,-23,color(1,1));
|
|
5134
|
+
rect(11,16,14,18,color(1,2));
|
|
5135
|
+
rect(11,16,14,8,color(1,3));
|
|
5136
|
+
rect(25,16,-14,24);
|
|
5137
|
+
|
|
5138
|
+
// cab window
|
|
5139
|
+
rect(15,29,6,-9,color(2,2));
|
|
5140
|
+
circle(15,21,5,0,PI/2,color(2,4),1);
|
|
5141
|
+
rect(21,21,-6,9);
|
|
5217
5142
|
|
|
5218
5143
|
// little stack
|
|
5219
5144
|
rect(37,14,9,6,color(3,2));
|
|
@@ -5221,16 +5146,16 @@ function drawEngineSplashScreen(t)
|
|
|
5221
5146
|
rect(37,14,9,6);
|
|
5222
5147
|
|
|
5223
5148
|
// big stack
|
|
5224
|
-
rect(50,20,10,-8,color(0,1))
|
|
5225
|
-
rect(50,20,6.5,-8,color(0,2))
|
|
5226
|
-
rect(50,20,3.5,-8,color(0,3))
|
|
5227
|
-
rect(50,20,10,-8)
|
|
5228
|
-
circle(55,2,11.4,.5,PI-.5,color(3,3))
|
|
5229
|
-
circle(55,2,11.4,.5,PI/2,color(3,2),1)
|
|
5230
|
-
circle(55,2,11.4,.5,PI-.5)
|
|
5231
|
-
rect(45,7,20,-7,color(0,2))
|
|
5232
|
-
rect(45,-1,20,4,color(0,3))
|
|
5233
|
-
rect(45,-1,20,8)
|
|
5149
|
+
rect(50,20,10,-8,color(0,1));
|
|
5150
|
+
rect(50,20,6.5,-8,color(0,2));
|
|
5151
|
+
rect(50,20,3.5,-8,color(0,3));
|
|
5152
|
+
rect(50,20,10,-8);
|
|
5153
|
+
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
5154
|
+
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
5155
|
+
circle(55,2,11.4,.5,PI-.5);
|
|
5156
|
+
rect(45,7,20,-7,color(0,2));
|
|
5157
|
+
rect(45,-1,20,4,color(0,3));
|
|
5158
|
+
rect(45,-1,20,8);
|
|
5234
5159
|
|
|
5235
5160
|
// engine
|
|
5236
5161
|
for (let i=5; i--;)
|