littlejsengine 1.18.8 → 1.18.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -4
- package/dist/littlejs.d.ts +67 -3
- package/dist/littlejs.esm.js +262 -23
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +257 -23
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +253 -21
- package/package.json +1 -1
- package/plugins/medalSystem.js +2 -1
- package/plugins/newgrounds.js +7 -0
- package/plugins/pathFinder.js +35 -6
- package/plugins/tweenSystem.js +8 -0
- package/src/engine.js +3 -2
- package/src/engineAudio.js +3 -2
- package/src/engineDebug.js +4 -2
- package/src/engineDraw.js +89 -9
- package/src/engineExport.js +5 -0
- package/src/engineUtilities.js +43 -0
- package/src/engineWebGL.js +63 -1
package/dist/littlejs.release.js
CHANGED
|
@@ -35,7 +35,7 @@ const engineName = 'LittleJS';
|
|
|
35
35
|
* @type {string}
|
|
36
36
|
* @default
|
|
37
37
|
* @memberof Engine */
|
|
38
|
-
const engineVersion = '1.18.
|
|
38
|
+
const engineVersion = '1.18.12';
|
|
39
39
|
|
|
40
40
|
/** Frames per second to update
|
|
41
41
|
* @type {number}
|
|
@@ -207,7 +207,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
207
207
|
const combinedScale = timeScale * debugScale;
|
|
208
208
|
frameTimeDeltaMS *= combinedScale;
|
|
209
209
|
frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
|
|
210
|
-
if (
|
|
210
|
+
if (combinedScale <= 1)
|
|
211
211
|
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp min framerate
|
|
212
212
|
|
|
213
213
|
let wasUpdated = false;
|
|
@@ -294,6 +294,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
294
294
|
glFlush();
|
|
295
295
|
debugRenderPost();
|
|
296
296
|
drawCount = 0;
|
|
297
|
+
primitiveCount = 0;
|
|
297
298
|
}
|
|
298
299
|
}
|
|
299
300
|
|
|
@@ -1769,6 +1770,7 @@ const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
|
1769
1770
|
* - File saving (text, canvas, data URLs)
|
|
1770
1771
|
* - Native share dialog support
|
|
1771
1772
|
* - Local storage save data management
|
|
1773
|
+
* - Gradient noise (1D and 2D)
|
|
1772
1774
|
* @namespace Utilities
|
|
1773
1775
|
*/
|
|
1774
1776
|
|
|
@@ -1970,6 +1972,48 @@ function writeSaveData(saveName, saveData)
|
|
|
1970
1972
|
{
|
|
1971
1973
|
ASSERT(isStringLike(saveName), 'saveData requires saveName string');
|
|
1972
1974
|
localStorage[saveName] = JSON.stringify(saveData);
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1978
|
+
|
|
1979
|
+
// Deterministic well-distributed hash of an integer lattice index to [0, 1).
|
|
1980
|
+
// Murmur3 finalizer — adjacent integers produce uncorrelated outputs.
|
|
1981
|
+
function noiseHash(i)
|
|
1982
|
+
{
|
|
1983
|
+
let h = (i | 0) ^ 0x9e3779b9;
|
|
1984
|
+
h = Math.imul(h ^ (h >>> 16), 0x85ebca6b);
|
|
1985
|
+
h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35);
|
|
1986
|
+
h ^= h >>> 16;
|
|
1987
|
+
return (h >>> 0) / 2**32;
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
/** 1D gradient noise — returns a smooth value in [0, 1] for any real x.
|
|
1991
|
+
* Integer inputs land on deterministic lattice values; non-integer inputs
|
|
1992
|
+
* are interpolated with smoothStep for C1 continuity.
|
|
1993
|
+
* @param {number} x
|
|
1994
|
+
* @return {number}
|
|
1995
|
+
* @memberof Utilities */
|
|
1996
|
+
function noise1D(x)
|
|
1997
|
+
{
|
|
1998
|
+
const i = floor(x);
|
|
1999
|
+
return lerp(noiseHash(i), noiseHash(i + 1), smoothStep(x - i));
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
/** 2D gradient noise — returns a smooth value in [0, 1] for any real (x, y).
|
|
2003
|
+
* @param {number} x
|
|
2004
|
+
* @param {number} y
|
|
2005
|
+
* @return {number}
|
|
2006
|
+
* @memberof Utilities */
|
|
2007
|
+
function noise2D(x, y)
|
|
2008
|
+
{
|
|
2009
|
+
const ix = floor(x), iy = floor(y);
|
|
2010
|
+
const fx = smoothStep(x - ix), fy = smoothStep(y - iy);
|
|
2011
|
+
// large prime decorrelates neighboring rows
|
|
2012
|
+
const h = (a, b) => noiseHash(a + b * 374761393);
|
|
2013
|
+
return lerp(
|
|
2014
|
+
lerp(h(ix, iy ), h(ix + 1, iy ), fx),
|
|
2015
|
+
lerp(h(ix, iy + 1), h(ix + 1, iy + 1), fx),
|
|
2016
|
+
fy);
|
|
1973
2017
|
}
|
|
1974
2018
|
/**
|
|
1975
2019
|
* LittleJS Engine Settings
|
|
@@ -3176,6 +3220,12 @@ let textureInfos = [];
|
|
|
3176
3220
|
* @memberof Draw */
|
|
3177
3221
|
let drawCount;
|
|
3178
3222
|
|
|
3223
|
+
/** Keeps track of how many primitives were drawn each frame for debugging
|
|
3224
|
+
* A single draw call can render many primitives (e.g. a WebGL sprite batch).
|
|
3225
|
+
* @type {number}
|
|
3226
|
+
* @memberof Draw */
|
|
3227
|
+
let primitiveCount;
|
|
3228
|
+
|
|
3179
3229
|
// internal predicates for tint short-circuiting in canvas2D draw paths
|
|
3180
3230
|
// isWhite ignores alpha because alpha is applied via globalAlpha, not multiply
|
|
3181
3231
|
// isBlack includes alpha so additive colors that only contribute alpha are not skipped
|
|
@@ -3416,19 +3466,19 @@ function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
|
3416
3466
|
}
|
|
3417
3467
|
else
|
|
3418
3468
|
{
|
|
3419
|
-
//
|
|
3420
|
-
//
|
|
3421
|
-
//
|
|
3422
|
-
// color.add(additiveColor) on line ~337.
|
|
3469
|
+
// untextured: glDrawUntextured picks the optimal path (poly
|
|
3470
|
+
// tristrip if already in poly mode, otherwise instanced with
|
|
3471
|
+
// uvs/rgba zeroed). Color+additive are folded together to match
|
|
3472
|
+
// the Canvas2D path's color.add(additiveColor) on line ~337.
|
|
3423
3473
|
const combined = additiveColor ? color.add(additiveColor) : color;
|
|
3424
|
-
|
|
3425
|
-
0, combined.rgbaInt());
|
|
3474
|
+
glDrawUntextured(pos.x, pos.y, size.x, size.y, angle, combined.rgbaInt());
|
|
3426
3475
|
}
|
|
3427
3476
|
}
|
|
3428
3477
|
else
|
|
3429
3478
|
{
|
|
3430
3479
|
// normal canvas 2D rendering method (slower)
|
|
3431
3480
|
++drawCount;
|
|
3481
|
+
++primitiveCount;
|
|
3432
3482
|
size = new Vector2(size.x, -size.y); // flip upside down sprites
|
|
3433
3483
|
drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
3434
3484
|
{
|
|
@@ -3468,13 +3518,13 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
3468
3518
|
* @param {Vector2} pos
|
|
3469
3519
|
* @param {Vector2} [size=vec2(1)]
|
|
3470
3520
|
* @param {Color} [colorTop=WHITE]
|
|
3471
|
-
* @param {Color} [colorBottom=
|
|
3521
|
+
* @param {Color} [colorBottom=CLEAR_WHITE]
|
|
3472
3522
|
* @param {number} [angle]
|
|
3473
3523
|
* @param {boolean} [useWebGL=glEnable]
|
|
3474
3524
|
* @param {boolean} [screenSpace]
|
|
3475
3525
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
3476
3526
|
* @memberof Draw */
|
|
3477
|
-
function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=
|
|
3527
|
+
function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=CLEAR_WHITE, angle=0, useWebGL=glEnable, screenSpace=false, context)
|
|
3478
3528
|
{
|
|
3479
3529
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
3480
3530
|
ASSERT(isVector2(size), 'size must be a vec2');
|
|
@@ -3514,6 +3564,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
3514
3564
|
{
|
|
3515
3565
|
// normal canvas 2D rendering method (slower)
|
|
3516
3566
|
++drawCount;
|
|
3567
|
+
++primitiveCount;
|
|
3517
3568
|
size = new Vector2(size.x, -size.y); // fix upside down sprites
|
|
3518
3569
|
drawCanvas2D(pos, size, angle, false, (context)=>
|
|
3519
3570
|
{
|
|
@@ -3576,8 +3627,9 @@ function drawTextureWrapped(pos, size, wrapCount, texture=0, color=WHITE,
|
|
|
3576
3627
|
return;
|
|
3577
3628
|
}
|
|
3578
3629
|
|
|
3579
|
-
// Canvas2D path — increment
|
|
3630
|
+
// Canvas2D path — increment counts here (WebGL counts via glFlush)
|
|
3580
3631
|
++drawCount;
|
|
3632
|
+
++primitiveCount;
|
|
3581
3633
|
|
|
3582
3634
|
if (!screenSpace)
|
|
3583
3635
|
{
|
|
@@ -3651,6 +3703,7 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
3651
3703
|
{
|
|
3652
3704
|
// normal canvas 2D rendering method (slower)
|
|
3653
3705
|
++drawCount;
|
|
3706
|
+
++primitiveCount;
|
|
3654
3707
|
drawCanvas2D(pos, vec2(1), angle, false, (context)=>
|
|
3655
3708
|
{
|
|
3656
3709
|
context.strokeStyle = color.toString();
|
|
@@ -3831,6 +3884,77 @@ function drawCircle(pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, useW
|
|
|
3831
3884
|
drawEllipse(pos, vec2(size), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context);
|
|
3832
3885
|
}
|
|
3833
3886
|
|
|
3887
|
+
/** Draw a circle filled with a radial gradient from the center to the rim
|
|
3888
|
+
* - Best when batched with other untextured polys
|
|
3889
|
+
* - If drawing mostly textured sprites, bake the gradient into a texture and use drawTile instead
|
|
3890
|
+
* - Stacking gradients at the exact same position may show a faint vertical artifact
|
|
3891
|
+
* @param {Vector2} pos
|
|
3892
|
+
* @param {number} [size=1] - Diameter
|
|
3893
|
+
* @param {Color} [colorInner=WHITE]
|
|
3894
|
+
* @param {Color} [colorOuter=CLEAR_WHITE]
|
|
3895
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
3896
|
+
* @param {boolean} [screenSpace]
|
|
3897
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
3898
|
+
* @memberof Draw */
|
|
3899
|
+
let drawCircleGradientOffset = 0;
|
|
3900
|
+
function drawCircleGradient(pos, size=1, colorInner=WHITE, colorOuter=CLEAR_WHITE, useWebGL=glEnable, screenSpace=false, context)
|
|
3901
|
+
{
|
|
3902
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
3903
|
+
ASSERT(isNumber(size), 'size must be a number');
|
|
3904
|
+
ASSERT(isColor(colorInner) && isColor(colorOuter), 'color is invalid');
|
|
3905
|
+
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3906
|
+
|
|
3907
|
+
if (headlessMode) return;
|
|
3908
|
+
|
|
3909
|
+
if (useWebGL && glEnable)
|
|
3910
|
+
{
|
|
3911
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3912
|
+
if (screenSpace)
|
|
3913
|
+
{
|
|
3914
|
+
// convert to world space
|
|
3915
|
+
pos = screenToWorld(pos);
|
|
3916
|
+
size /= cameraScale;
|
|
3917
|
+
}
|
|
3918
|
+
// fan as tristrip; rotate the boundary vertex by one slice per call
|
|
3919
|
+
// so back-to-back gradients at the same position have their hole
|
|
3920
|
+
// (from gpu edge-rule on the boundary line-degen) at different rim
|
|
3921
|
+
// verts and don't visibly stack
|
|
3922
|
+
const sides = glCircleSides;
|
|
3923
|
+
const radius = size/2;
|
|
3924
|
+
const innerInt = colorInner.rgbaInt();
|
|
3925
|
+
const outerInt = colorOuter.rgbaInt();
|
|
3926
|
+
const offset = drawCircleGradientOffset++;
|
|
3927
|
+
const startA = (offset%sides)/sides*PI*2;
|
|
3928
|
+
const points = [vec2(pos.x + sin(startA)*radius, pos.y + cos(startA)*radius)];
|
|
3929
|
+
const colors = [outerInt];
|
|
3930
|
+
for (let i=sides; i--;)
|
|
3931
|
+
{
|
|
3932
|
+
const a = ((i+offset)%sides)/sides*PI*2;
|
|
3933
|
+
points.push(pos);
|
|
3934
|
+
colors.push(innerInt);
|
|
3935
|
+
points.push(vec2(pos.x + sin(a)*radius, pos.y + cos(a)*radius));
|
|
3936
|
+
colors.push(outerInt);
|
|
3937
|
+
}
|
|
3938
|
+
glDrawColoredPoints(points, colors);
|
|
3939
|
+
}
|
|
3940
|
+
else
|
|
3941
|
+
{
|
|
3942
|
+
// normal canvas 2D rendering method (slower)
|
|
3943
|
+
++drawCount;
|
|
3944
|
+
++primitiveCount;
|
|
3945
|
+
drawCanvas2D(pos, vec2(size), 0, false, (context)=>
|
|
3946
|
+
{
|
|
3947
|
+
const gradient = context.createRadialGradient(0, 0, 0, 0, 0, .5);
|
|
3948
|
+
gradient.addColorStop(0, colorInner.toString());
|
|
3949
|
+
gradient.addColorStop(1, colorOuter.toString());
|
|
3950
|
+
context.fillStyle = gradient;
|
|
3951
|
+
context.beginPath();
|
|
3952
|
+
context.ellipse(0, 0, .5, .5, 0, 0, 9);
|
|
3953
|
+
context.fill();
|
|
3954
|
+
}, screenSpace, context);
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3834
3958
|
/**
|
|
3835
3959
|
* @callback Canvas2DDrawFunction - A function that draws to a 2D canvas context
|
|
3836
3960
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
@@ -5676,14 +5800,15 @@ class SoundInstance
|
|
|
5676
5800
|
|
|
5677
5801
|
/** Speak text with passed in settings
|
|
5678
5802
|
* @param {string} text - The text to speak
|
|
5679
|
-
* @param {string} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
|
|
5680
5803
|
* @param {number} [volume] - How much to scale volume by
|
|
5681
5804
|
* @param {number} [rate] - How quickly to speak
|
|
5682
5805
|
* @param {number} [pitch] - How much to change the pitch by
|
|
5806
|
+
* @param {string} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
|
|
5683
5807
|
* @return {SpeechSynthesisUtterance} - The utterance that was spoken
|
|
5684
5808
|
* @memberof Audio */
|
|
5685
|
-
function speak(text,
|
|
5809
|
+
function speak(text, volume=1, rate=1, pitch=1, language='')
|
|
5686
5810
|
{
|
|
5811
|
+
ASSERT(typeof volume !== 'string', 'speak() signature changed: language is now the last parameter, after pitch');
|
|
5687
5812
|
if (!soundEnable || headlessMode) return;
|
|
5688
5813
|
if (!speechSynthesis) return;
|
|
5689
5814
|
|
|
@@ -7676,7 +7801,8 @@ function glFlush()
|
|
|
7676
7801
|
glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, glBatchCount);
|
|
7677
7802
|
else
|
|
7678
7803
|
glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP, 0, 4, glBatchCount);
|
|
7679
|
-
drawCount
|
|
7804
|
+
++drawCount;
|
|
7805
|
+
primitiveCount += glBatchCount;
|
|
7680
7806
|
glBatchCount = 0;
|
|
7681
7807
|
}
|
|
7682
7808
|
glBatchAdditive = glAdditive;
|
|
@@ -7737,6 +7863,67 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
|
|
|
7737
7863
|
glPositionData[offset++] = angle;
|
|
7738
7864
|
}
|
|
7739
7865
|
|
|
7866
|
+
/** Add an untextured rect to the gl draw list
|
|
7867
|
+
* Picks the optimal path: if already in poly mode, emits a tristrip rect
|
|
7868
|
+
* so it batches with surrounding polys; otherwise uses the instanced path
|
|
7869
|
+
* with uvs and rgba zeroed so the color falls through the additive slot.
|
|
7870
|
+
* @param {number} x
|
|
7871
|
+
* @param {number} y
|
|
7872
|
+
* @param {number} sizeX
|
|
7873
|
+
* @param {number} sizeY
|
|
7874
|
+
* @param {number} angle
|
|
7875
|
+
* @param {number} rgba - color as 32-bit integer
|
|
7876
|
+
* @memberof WebGL */
|
|
7877
|
+
function glDrawUntextured(x, y, sizeX, sizeY, angle, rgba)
|
|
7878
|
+
{
|
|
7879
|
+
if (glPolyMode)
|
|
7880
|
+
{
|
|
7881
|
+
// batch with surrounding polys as a 4-vertex tristrip rect
|
|
7882
|
+
const vertCount = 6; // 4 corners + 2 degenerate verts
|
|
7883
|
+
if (glBatchCount+vertCount >= gl_MAX_POLY_VERTEXES || glBatchAdditive !== glAdditive)
|
|
7884
|
+
glFlush();
|
|
7885
|
+
|
|
7886
|
+
// compute rotated corners in world space (matches glDrawPointsTransform rotation)
|
|
7887
|
+
const hx = sizeX*.5, hy = sizeY*.5;
|
|
7888
|
+
const c = cos(angle), s = sin(angle);
|
|
7889
|
+
const chx = c*hx, shx = s*hx, chy = c*hy, shy = s*hy;
|
|
7890
|
+
const x0 = x - chx - shy, y0 = y + shx - chy; // (-hx,-hy)
|
|
7891
|
+
const x1 = x + chx - shy, y1 = y - shx - chy; // ( hx,-hy)
|
|
7892
|
+
const x2 = x - chx + shy, y2 = y + shx + chy; // (-hx, hy)
|
|
7893
|
+
const x3 = x + chx + shy, y3 = y - shx + chy; // ( hx, hy)
|
|
7894
|
+
|
|
7895
|
+
// write tristrip with leading/trailing degenerate verts
|
|
7896
|
+
let offset = glBatchCount * gl_INDICES_PER_POLY_VERTEX;
|
|
7897
|
+
glPositionData[offset++] = x0; glPositionData[offset++] = y0; glColorData[offset++] = rgba;
|
|
7898
|
+
glPositionData[offset++] = x0; glPositionData[offset++] = y0; glColorData[offset++] = rgba;
|
|
7899
|
+
glPositionData[offset++] = x1; glPositionData[offset++] = y1; glColorData[offset++] = rgba;
|
|
7900
|
+
glPositionData[offset++] = x2; glPositionData[offset++] = y2; glColorData[offset++] = rgba;
|
|
7901
|
+
glPositionData[offset++] = x3; glPositionData[offset++] = y3; glColorData[offset++] = rgba;
|
|
7902
|
+
glPositionData[offset++] = x3; glPositionData[offset++] = y3; glColorData[offset++] = rgba;
|
|
7903
|
+
glBatchCount += vertCount;
|
|
7904
|
+
return;
|
|
7905
|
+
}
|
|
7906
|
+
|
|
7907
|
+
// instanced path: zero uvs and rgba so the texture contribution is killed,
|
|
7908
|
+
// then carry the real color in the additive slot
|
|
7909
|
+
if (glBatchCount >= gl_MAX_INSTANCES || glBatchAdditive !== glAdditive)
|
|
7910
|
+
glFlush();
|
|
7911
|
+
glSetInstancedMode();
|
|
7912
|
+
|
|
7913
|
+
let offset = glBatchCount++ * gl_INDICES_PER_INSTANCE;
|
|
7914
|
+
glPositionData[offset++] = x;
|
|
7915
|
+
glPositionData[offset++] = y;
|
|
7916
|
+
glPositionData[offset++] = sizeX;
|
|
7917
|
+
glPositionData[offset++] = sizeY;
|
|
7918
|
+
glPositionData[offset++] = 0;
|
|
7919
|
+
glPositionData[offset++] = 0;
|
|
7920
|
+
glPositionData[offset++] = 0;
|
|
7921
|
+
glPositionData[offset++] = 0;
|
|
7922
|
+
glColorData[offset++] = 0;
|
|
7923
|
+
glColorData[offset++] = rgba;
|
|
7924
|
+
glPositionData[offset++] = angle;
|
|
7925
|
+
}
|
|
7926
|
+
|
|
7740
7927
|
/** Transform and add a polygon to the gl draw list
|
|
7741
7928
|
* @param {Array<Vector2>} points - Array of Vector2 points
|
|
7742
7929
|
* @param {number} rgba - Color of the polygon as a 32-bit integer
|
|
@@ -8373,7 +8560,8 @@ class Medal
|
|
|
8373
8560
|
/** @property {boolean} - Is the medal unlocked? */
|
|
8374
8561
|
this.unlocked = false;
|
|
8375
8562
|
|
|
8376
|
-
|
|
8563
|
+
/** @property {HTMLImageElement|undefined} - Source image for the medal icon, if any */
|
|
8564
|
+
this.image = undefined;
|
|
8377
8565
|
if (src)
|
|
8378
8566
|
(this.image = new Image).src = src;
|
|
8379
8567
|
|
|
@@ -8536,13 +8724,18 @@ class NewgroundsPlugin
|
|
|
8536
8724
|
ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
|
|
8537
8725
|
|
|
8538
8726
|
newgrounds = this; // set global newgrounds object
|
|
8727
|
+
/** @property {string} - The newgrounds App ID */
|
|
8539
8728
|
this.app_id = app_id;
|
|
8729
|
+
/** @property {string|undefined} - AES-128/Base64 encryption key, if any */
|
|
8540
8730
|
this.cipher = cipher;
|
|
8731
|
+
/** @property {Object|undefined} - CryptoJS instance used when cipher is set */
|
|
8541
8732
|
this.cryptoJS = cryptoJS;
|
|
8733
|
+
/** @property {string} - Hostname used when logging views */
|
|
8542
8734
|
this.host = location ? location.hostname : '';
|
|
8543
8735
|
|
|
8544
8736
|
// get session id from url search params
|
|
8545
8737
|
const url = new URL(location.href);
|
|
8738
|
+
/** @property {string|null} - Newgrounds session id from the URL (null when not logged in) */
|
|
8546
8739
|
this.session_id = url.searchParams.get('ngio_session_id');
|
|
8547
8740
|
|
|
8548
8741
|
if (!this.session_id)
|
|
@@ -8550,6 +8743,7 @@ class NewgroundsPlugin
|
|
|
8550
8743
|
|
|
8551
8744
|
// get medals
|
|
8552
8745
|
const medalsResult = this.call('Medal.getList');
|
|
8746
|
+
/** @property {Array} - Medals fetched from Newgrounds (empty until session is active) */
|
|
8553
8747
|
this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
|
|
8554
8748
|
debugMedals && LOG(this.medals);
|
|
8555
8749
|
for (const newgroundsMedal of this.medals)
|
|
@@ -8573,6 +8767,7 @@ class NewgroundsPlugin
|
|
|
8573
8767
|
|
|
8574
8768
|
// get scoreboards
|
|
8575
8769
|
const scoreboardResult = this.call('ScoreBoard.getBoards');
|
|
8770
|
+
/** @property {Array} - Scoreboards fetched from Newgrounds */
|
|
8576
8771
|
this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
|
|
8577
8772
|
debugMedals && LOG(this.scoreboards);
|
|
8578
8773
|
|
|
@@ -12885,13 +13080,21 @@ class Tween
|
|
|
12885
13080
|
}
|
|
12886
13081
|
ASSERT(isNumber(duration) && duration > 0, 'Tween duration must be > 0');
|
|
12887
13082
|
|
|
13083
|
+
/** @property {function(number|Vector2|Color):void} - Called with the interpolated value each frame */
|
|
12888
13084
|
this.callback = callback;
|
|
13085
|
+
/** @property {number|Vector2|Color} - Starting value */
|
|
12889
13086
|
this.start = start;
|
|
13087
|
+
/** @property {number|Vector2|Color} - Ending value */
|
|
12890
13088
|
this.end = end;
|
|
13089
|
+
/** @property {number} - Total duration in seconds */
|
|
12891
13090
|
this.duration = duration;
|
|
13091
|
+
/** @property {number} - Remaining time in seconds (counts down from duration to 0) */
|
|
12892
13092
|
this.life = duration;
|
|
13093
|
+
/** @property {function(number):number} - Easing curve mapping [0,1] -> [0,1] */
|
|
12893
13094
|
this.ease = options.ease || Ease.LINEAR;
|
|
13095
|
+
/** @property {boolean} - If true, advance even when the game is paused */
|
|
12894
13096
|
this.useRealTime = !!options.useRealTime;
|
|
13097
|
+
/** @property {boolean} - If true, stop advancing until cleared */
|
|
12895
13098
|
this.paused = !!options.paused;
|
|
12896
13099
|
|
|
12897
13100
|
/** @private completion callback set by then(), loop(), pingPong(). */
|
|
@@ -13424,7 +13627,9 @@ class PathFinder
|
|
|
13424
13627
|
// .size + .getCollisionData.
|
|
13425
13628
|
if (isVector2(source))
|
|
13426
13629
|
{
|
|
13630
|
+
/** @property {Vector2} - Grid dimensions in tiles */
|
|
13427
13631
|
this.size = source.floor();
|
|
13632
|
+
/** @property {TileCollisionLayer|undefined} - Tile layer driving walkability, if any */
|
|
13428
13633
|
this.tileLayer = undefined;
|
|
13429
13634
|
}
|
|
13430
13635
|
else
|
|
@@ -13436,13 +13641,18 @@ class PathFinder
|
|
|
13436
13641
|
}
|
|
13437
13642
|
|
|
13438
13643
|
// Tunables (public, freely re-assignable).
|
|
13644
|
+
/** @property {number} - A* heuristic multiplier (1 = admissible, higher = greedier) */
|
|
13439
13645
|
this.heuristicWeight = 1;
|
|
13440
|
-
|
|
13646
|
+
/** @property {number} - Maximum A* expansions before giving up */
|
|
13647
|
+
this.maxLoop = 1e3;
|
|
13648
|
+
/** @property {boolean} - If true, post-process paths with two-pass smoothing */
|
|
13441
13649
|
this.smoothPath = true;
|
|
13650
|
+
/** @property {boolean} - If true, draw debug visualization during findPath */
|
|
13442
13651
|
this.debug = false;
|
|
13443
|
-
|
|
13652
|
+
/** @property {number} - Debug primitive lifetime in seconds (0 disables drawing) */
|
|
13653
|
+
this.debugTime = 1;
|
|
13444
13654
|
|
|
13445
|
-
|
|
13655
|
+
/** @property {Array<PathFinderNode>} - Flat row-major array of size.x*size.y nodes */
|
|
13446
13656
|
this.nodes = new Array(this.size.x * this.size.y);
|
|
13447
13657
|
for (let y = 0; y < this.size.y; ++y)
|
|
13448
13658
|
for (let x = 0; x < this.size.x; ++x)
|
|
@@ -13616,9 +13826,12 @@ class PathFinder
|
|
|
13616
13826
|
// Best path so far through neighbor — record it.
|
|
13617
13827
|
neighbor.parent = current;
|
|
13618
13828
|
neighbor.g = tentativeG;
|
|
13619
|
-
|
|
13620
|
-
|
|
13621
|
-
|
|
13829
|
+
// Octile heuristic — tightest admissible distance for an
|
|
13830
|
+
// 8-connected grid with cardinal cost 1 and diagonal cost √2.
|
|
13831
|
+
const adx = abs(endNode.pos.x - neighbor.pos.x);
|
|
13832
|
+
const ady = abs(endNode.pos.y - neighbor.pos.y);
|
|
13833
|
+
const h = max(adx, ady) + (Math.SQRT2 - 1) * min(adx, ady);
|
|
13834
|
+
neighbor.f = neighbor.g + h * this.heuristicWeight;
|
|
13622
13835
|
}
|
|
13623
13836
|
}
|
|
13624
13837
|
|
|
@@ -13900,6 +14113,24 @@ class PathFinder
|
|
|
13900
14113
|
path.push(original[original.length - 1]);
|
|
13901
14114
|
}
|
|
13902
14115
|
|
|
14116
|
+
/** Drop any middle node that lies exactly on the line through its two
|
|
14117
|
+
* neighbors. Backstop for the smoothing passes — the corners pass
|
|
14118
|
+
* intentionally keeps truly-straight runs, and the string-pulling pass
|
|
14119
|
+
* checks collinearity against the original path, not the in-progress
|
|
14120
|
+
* result, so it can leave 3+ collinear nodes in some edge cases.
|
|
14121
|
+
* @param {PathFinderNode[]} path
|
|
14122
|
+
* @private */
|
|
14123
|
+
dropCollinearNodes(path)
|
|
14124
|
+
{
|
|
14125
|
+
for (let i = path.length - 2; i >= 1; --i)
|
|
14126
|
+
{
|
|
14127
|
+
const a = path[i - 1], b = path[i], c = path[i + 1];
|
|
14128
|
+
if ((b.pos.x - a.pos.x) * (c.pos.y - a.pos.y) ===
|
|
14129
|
+
(b.pos.y - a.pos.y) * (c.pos.x - a.pos.x))
|
|
14130
|
+
path.splice(i, 1);
|
|
14131
|
+
}
|
|
14132
|
+
}
|
|
14133
|
+
|
|
13903
14134
|
/** Lookup helper: true when the node at tile coords (x, y) is in-bounds
|
|
13904
14135
|
* and clear (walkable, zero-cost). Used by isLineClear's hot path.
|
|
13905
14136
|
* @param {number} x
|
|
@@ -14067,6 +14298,7 @@ class PathFinder
|
|
|
14067
14298
|
{
|
|
14068
14299
|
this.smoothPathCorners(nodePath);
|
|
14069
14300
|
this.smoothPathStringPull(nodePath);
|
|
14301
|
+
this.dropCollinearNodes(nodePath);
|
|
14070
14302
|
}
|
|
14071
14303
|
|
|
14072
14304
|
// Convert to world-space Vector2 path. Return copies, not live node
|
package/package.json
CHANGED
package/plugins/medalSystem.js
CHANGED
|
@@ -147,7 +147,8 @@ class Medal
|
|
|
147
147
|
/** @property {boolean} - Is the medal unlocked? */
|
|
148
148
|
this.unlocked = false;
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
/** @property {HTMLImageElement|undefined} - Source image for the medal icon, if any */
|
|
151
|
+
this.image = undefined;
|
|
151
152
|
if (src)
|
|
152
153
|
(this.image = new Image).src = src;
|
|
153
154
|
|
package/plugins/newgrounds.js
CHANGED
|
@@ -63,13 +63,18 @@ class NewgroundsPlugin
|
|
|
63
63
|
ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
|
|
64
64
|
|
|
65
65
|
newgrounds = this; // set global newgrounds object
|
|
66
|
+
/** @property {string} - The newgrounds App ID */
|
|
66
67
|
this.app_id = app_id;
|
|
68
|
+
/** @property {string|undefined} - AES-128/Base64 encryption key, if any */
|
|
67
69
|
this.cipher = cipher;
|
|
70
|
+
/** @property {Object|undefined} - CryptoJS instance used when cipher is set */
|
|
68
71
|
this.cryptoJS = cryptoJS;
|
|
72
|
+
/** @property {string} - Hostname used when logging views */
|
|
69
73
|
this.host = location ? location.hostname : '';
|
|
70
74
|
|
|
71
75
|
// get session id from url search params
|
|
72
76
|
const url = new URL(location.href);
|
|
77
|
+
/** @property {string|null} - Newgrounds session id from the URL (null when not logged in) */
|
|
73
78
|
this.session_id = url.searchParams.get('ngio_session_id');
|
|
74
79
|
|
|
75
80
|
if (!this.session_id)
|
|
@@ -77,6 +82,7 @@ class NewgroundsPlugin
|
|
|
77
82
|
|
|
78
83
|
// get medals
|
|
79
84
|
const medalsResult = this.call('Medal.getList');
|
|
85
|
+
/** @property {Array} - Medals fetched from Newgrounds (empty until session is active) */
|
|
80
86
|
this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
|
|
81
87
|
debugMedals && LOG(this.medals);
|
|
82
88
|
for (const newgroundsMedal of this.medals)
|
|
@@ -100,6 +106,7 @@ class NewgroundsPlugin
|
|
|
100
106
|
|
|
101
107
|
// get scoreboards
|
|
102
108
|
const scoreboardResult = this.call('ScoreBoard.getBoards');
|
|
109
|
+
/** @property {Array} - Scoreboards fetched from Newgrounds */
|
|
103
110
|
this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
|
|
104
111
|
debugMedals && LOG(this.scoreboards);
|
|
105
112
|
|
package/plugins/pathFinder.js
CHANGED
|
@@ -94,7 +94,9 @@ class PathFinder
|
|
|
94
94
|
// .size + .getCollisionData.
|
|
95
95
|
if (isVector2(source))
|
|
96
96
|
{
|
|
97
|
+
/** @property {Vector2} - Grid dimensions in tiles */
|
|
97
98
|
this.size = source.floor();
|
|
99
|
+
/** @property {TileCollisionLayer|undefined} - Tile layer driving walkability, if any */
|
|
98
100
|
this.tileLayer = undefined;
|
|
99
101
|
}
|
|
100
102
|
else
|
|
@@ -106,13 +108,18 @@ class PathFinder
|
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
// Tunables (public, freely re-assignable).
|
|
111
|
+
/** @property {number} - A* heuristic multiplier (1 = admissible, higher = greedier) */
|
|
109
112
|
this.heuristicWeight = 1;
|
|
110
|
-
|
|
113
|
+
/** @property {number} - Maximum A* expansions before giving up */
|
|
114
|
+
this.maxLoop = 1e3;
|
|
115
|
+
/** @property {boolean} - If true, post-process paths with two-pass smoothing */
|
|
111
116
|
this.smoothPath = true;
|
|
117
|
+
/** @property {boolean} - If true, draw debug visualization during findPath */
|
|
112
118
|
this.debug = false;
|
|
113
|
-
|
|
119
|
+
/** @property {number} - Debug primitive lifetime in seconds (0 disables drawing) */
|
|
120
|
+
this.debugTime = 1;
|
|
114
121
|
|
|
115
|
-
|
|
122
|
+
/** @property {Array<PathFinderNode>} - Flat row-major array of size.x*size.y nodes */
|
|
116
123
|
this.nodes = new Array(this.size.x * this.size.y);
|
|
117
124
|
for (let y = 0; y < this.size.y; ++y)
|
|
118
125
|
for (let x = 0; x < this.size.x; ++x)
|
|
@@ -286,9 +293,12 @@ class PathFinder
|
|
|
286
293
|
// Best path so far through neighbor — record it.
|
|
287
294
|
neighbor.parent = current;
|
|
288
295
|
neighbor.g = tentativeG;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
296
|
+
// Octile heuristic — tightest admissible distance for an
|
|
297
|
+
// 8-connected grid with cardinal cost 1 and diagonal cost √2.
|
|
298
|
+
const adx = abs(endNode.pos.x - neighbor.pos.x);
|
|
299
|
+
const ady = abs(endNode.pos.y - neighbor.pos.y);
|
|
300
|
+
const h = max(adx, ady) + (Math.SQRT2 - 1) * min(adx, ady);
|
|
301
|
+
neighbor.f = neighbor.g + h * this.heuristicWeight;
|
|
292
302
|
}
|
|
293
303
|
}
|
|
294
304
|
|
|
@@ -570,6 +580,24 @@ class PathFinder
|
|
|
570
580
|
path.push(original[original.length - 1]);
|
|
571
581
|
}
|
|
572
582
|
|
|
583
|
+
/** Drop any middle node that lies exactly on the line through its two
|
|
584
|
+
* neighbors. Backstop for the smoothing passes — the corners pass
|
|
585
|
+
* intentionally keeps truly-straight runs, and the string-pulling pass
|
|
586
|
+
* checks collinearity against the original path, not the in-progress
|
|
587
|
+
* result, so it can leave 3+ collinear nodes in some edge cases.
|
|
588
|
+
* @param {PathFinderNode[]} path
|
|
589
|
+
* @private */
|
|
590
|
+
dropCollinearNodes(path)
|
|
591
|
+
{
|
|
592
|
+
for (let i = path.length - 2; i >= 1; --i)
|
|
593
|
+
{
|
|
594
|
+
const a = path[i - 1], b = path[i], c = path[i + 1];
|
|
595
|
+
if ((b.pos.x - a.pos.x) * (c.pos.y - a.pos.y) ===
|
|
596
|
+
(b.pos.y - a.pos.y) * (c.pos.x - a.pos.x))
|
|
597
|
+
path.splice(i, 1);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
573
601
|
/** Lookup helper: true when the node at tile coords (x, y) is in-bounds
|
|
574
602
|
* and clear (walkable, zero-cost). Used by isLineClear's hot path.
|
|
575
603
|
* @param {number} x
|
|
@@ -737,6 +765,7 @@ class PathFinder
|
|
|
737
765
|
{
|
|
738
766
|
this.smoothPathCorners(nodePath);
|
|
739
767
|
this.smoothPathStringPull(nodePath);
|
|
768
|
+
this.dropCollinearNodes(nodePath);
|
|
740
769
|
}
|
|
741
770
|
|
|
742
771
|
// Convert to world-space Vector2 path. Return copies, not live node
|
package/plugins/tweenSystem.js
CHANGED
|
@@ -63,13 +63,21 @@ class Tween
|
|
|
63
63
|
}
|
|
64
64
|
ASSERT(isNumber(duration) && duration > 0, 'Tween duration must be > 0');
|
|
65
65
|
|
|
66
|
+
/** @property {function(number|Vector2|Color):void} - Called with the interpolated value each frame */
|
|
66
67
|
this.callback = callback;
|
|
68
|
+
/** @property {number|Vector2|Color} - Starting value */
|
|
67
69
|
this.start = start;
|
|
70
|
+
/** @property {number|Vector2|Color} - Ending value */
|
|
68
71
|
this.end = end;
|
|
72
|
+
/** @property {number} - Total duration in seconds */
|
|
69
73
|
this.duration = duration;
|
|
74
|
+
/** @property {number} - Remaining time in seconds (counts down from duration to 0) */
|
|
70
75
|
this.life = duration;
|
|
76
|
+
/** @property {function(number):number} - Easing curve mapping [0,1] -> [0,1] */
|
|
71
77
|
this.ease = options.ease || Ease.LINEAR;
|
|
78
|
+
/** @property {boolean} - If true, advance even when the game is paused */
|
|
72
79
|
this.useRealTime = !!options.useRealTime;
|
|
80
|
+
/** @property {boolean} - If true, stop advancing until cleared */
|
|
73
81
|
this.paused = !!options.paused;
|
|
74
82
|
|
|
75
83
|
/** @private completion callback set by then(), loop(), pingPong(). */
|
package/src/engine.js
CHANGED
|
@@ -32,7 +32,7 @@ const engineName = 'LittleJS';
|
|
|
32
32
|
* @type {string}
|
|
33
33
|
* @default
|
|
34
34
|
* @memberof Engine */
|
|
35
|
-
const engineVersion = '1.18.
|
|
35
|
+
const engineVersion = '1.18.12';
|
|
36
36
|
|
|
37
37
|
/** Frames per second to update
|
|
38
38
|
* @type {number}
|
|
@@ -204,7 +204,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
204
204
|
const combinedScale = timeScale * debugScale;
|
|
205
205
|
frameTimeDeltaMS *= combinedScale;
|
|
206
206
|
frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
|
|
207
|
-
if (
|
|
207
|
+
if (combinedScale <= 1)
|
|
208
208
|
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp min framerate
|
|
209
209
|
|
|
210
210
|
let wasUpdated = false;
|
|
@@ -291,6 +291,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
291
291
|
glFlush();
|
|
292
292
|
debugRenderPost();
|
|
293
293
|
drawCount = 0;
|
|
294
|
+
primitiveCount = 0;
|
|
294
295
|
}
|
|
295
296
|
}
|
|
296
297
|
|