littlejsengine 1.24.2 → 1.25.0

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/EDITOR.md +2 -2
  3. package/FAQ.md +6 -0
  4. package/REFERENCE.md +86 -41
  5. package/dist/littlejs.d.ts +600 -323
  6. package/dist/littlejs.esm.js +2412 -1797
  7. package/dist/littlejs.esm.min.js +38 -24
  8. package/dist/littlejs.js +2410 -1797
  9. package/dist/littlejs.min.js +37 -23
  10. package/dist/littlejs.release.js +2393 -1826
  11. package/package.json +1 -1
  12. package/plugins/box2d.js +41 -28
  13. package/plugins/drawUtilities.js +374 -380
  14. package/plugins/gltf.js +331 -55
  15. package/plugins/lightSystem.js +2 -2
  16. package/plugins/math3d.js +1013 -1014
  17. package/plugins/newgrounds.js +1 -4
  18. package/plugins/particleEffects.js +3 -4
  19. package/plugins/pathFinder.js +52 -28
  20. package/plugins/pluginExport.js +2 -0
  21. package/plugins/postProcess.js +196 -65
  22. package/plugins/render3d.js +8 -6
  23. package/plugins/render3dEditor.js +3 -3
  24. package/plugins/render3dExtras.js +31 -10
  25. package/plugins/render3dLevel.js +15 -4
  26. package/plugins/textureSheet.js +15 -7
  27. package/plugins/tweenSystem.js +43 -38
  28. package/plugins/uiSystem.js +25 -20
  29. package/src/engine.js +39 -21
  30. package/src/engineAudio.js +13 -9
  31. package/src/engineBuild.mjs +49 -2
  32. package/src/engineDebug.js +3 -2
  33. package/src/engineDraw.js +20 -18
  34. package/src/engineEditor.js +75 -23
  35. package/src/engineInput.js +10 -10
  36. package/src/engineMath.js +3 -6
  37. package/src/engineObject.js +8 -9
  38. package/src/engineParticles.js +8 -7
  39. package/src/engineRelease.js +17 -10
  40. package/src/engineTileLayer.js +33 -14
  41. package/src/engineUtilities.js +15 -2
  42. package/src/engineWebGL.js +34 -12
@@ -25,7 +25,7 @@ let newgrounds;
25
25
  const newgroundsUnlocksToResend = new Set; // pending medals whose request did not reach the server
26
26
  const newgroundsUnlocksRefused = new Set; // medals the server refused this visit, asked again they answer no unsent
27
27
  const newgroundsSecureComponents = ['Medal.unlock', 'ScoreBoard.postScore']; // the calls encrypted with a cipher
28
- const newgroundsSessionErrors = [104, 110, 111]; // expired session, login required, session cancelled
28
+ const newgroundsSessionErrors = [104, 110, 111]; // expired session, login required, session canceled
29
29
  const newgroundsTimeoutMS = 15e3; // how long a request may take before it fails
30
30
 
31
31
  // whether the server answered that the session is gone, as opposed to a request that failed on the way
@@ -181,9 +181,6 @@ class NewgroundsPlugin
181
181
  this.ready = this.init();
182
182
  }
183
183
 
184
- /** @deprecated since 1.20, the view is logged when the plugin starts, so this does nothing */
185
- logView() {}
186
-
187
184
  /** Log the view, check the session, fetch the medals and scoreboards, then keep the session alive; the constructor runs it once
188
185
  * @private */
189
186
  async init()
@@ -162,7 +162,6 @@ function particleEffectSanitize(raw)
162
162
  const text = typeof raw?.name === 'string' ? raw.name.replace(/[\x00-\x1f\x7f]+/g, ' ').trim() : '';
163
163
  const name = text ? text.slice(0, 60).trim() : 'Effect'; // no space left where it was cut
164
164
  const input = raw?.settings && typeof raw.settings === 'object' ? {...raw.settings} : {};
165
- input.collideLevel ??= input.collideTiles; // its name before 1.20
166
165
  // a library saved before shapes names its tile and no shape, it keeps its tile
167
166
  if (input.shape === undefined && isNumber(input.tileIndex))
168
167
  input.shape = '';
@@ -212,10 +211,10 @@ function particleEffectSanitize(raw)
212
211
  }
213
212
 
214
213
  /** A copy of an effect with its four colors turned around the color wheel and their saturation scaled; lightness and
215
- * alpha stay, and grey and white have no hue to turn
214
+ * alpha stay, and gray and white have no hue to turn
216
215
  * @param {Object} effect
217
216
  * @param {number} [hue] - How far around the wheel, 1 is all the way
218
- * @param {number} [saturation] - Multiplies the saturation, 0 is grey, clamped to 1
217
+ * @param {number} [saturation] - Multiplies the saturation, 0 is gray, clamped to 1
219
218
  * @return {Object}
220
219
  * @memberof ParticleEffects */
221
220
  function particleEffectRecolor(effect, hue=0, saturation=1)
@@ -505,7 +504,7 @@ function particleEffectResolve(nameOrEffect, options)
505
504
  * @param {Object} [options] - What to change for this play, each left out when not wanted:
506
505
  * scale grows the whole effect, the built-ins fit a one unit object at 1;
507
506
  * hue turns its colors around the color wheel, 1 is all the way;
508
- * saturation multiplies its saturation, 0 is grey;
507
+ * saturation multiplies its saturation, 0 is gray;
509
508
  * angle is its direction, 0 is up, the effect's own angle when not given;
510
509
  * tileInfo, a TileInfo or a TextureInfo, is the game's own art to draw with in place of the effect's shape,
511
510
  * tinted by its colors, a whole texture drawn as one tile;
@@ -26,7 +26,8 @@ const PATHFINDER_TILE_VEC = vec2(1);
26
26
  * @memberof PathFinding */
27
27
  class PathFinderNode
28
28
  {
29
- /** @param {number} x - Tile x
29
+ /** Make the node of a grid cell, as PathFinder does for each
30
+ * @param {number} x - Tile x
30
31
  * @param {number} y - Tile y */
31
32
  constructor(x, y)
32
33
  {
@@ -66,7 +67,8 @@ class PathFinderNode
66
67
  /** Reset per-search state and walkability (called by buildNodeData). */
67
68
  reset() { this.walkable = false; this.cost = 0; this.resetSearch(); }
68
69
 
69
- /** True if walkable and not blocked by cost. */
70
+ /** True if walkable and not blocked by cost.
71
+ * @return {boolean} */
70
72
  isClear()
71
73
  {
72
74
  return this.walkable && this.cost === 0;
@@ -88,7 +90,8 @@ class PathFinderNode
88
90
  */
89
91
  class PathFinder
90
92
  {
91
- /** @param {TileCollisionLayer|Vector2} source - Either a TileCollisionLayer
93
+ /** Make a path finder over a tile layer or a grid
94
+ * @param {TileCollisionLayer|Vector2} source - Either a TileCollisionLayer
92
95
  * (size and walkability auto-derived) or a Vector2 grid size (user
93
96
  * overrides isWalkable). */
94
97
  constructor(source)
@@ -157,7 +160,7 @@ class PathFinder
157
160
  * the instance or via a subclass.
158
161
  * @param {number} x - Tile x
159
162
  * @param {number} y - Tile y
160
- * @returns {boolean} */
163
+ * @return {boolean} */
161
164
  isWalkable(x, y)
162
165
  {
163
166
  if (!this.tileLayer) return true;
@@ -168,7 +171,7 @@ class PathFinder
168
171
  * Override to add cost-weighted terrain (mud, swamp, etc).
169
172
  * @param {number} x - Tile x
170
173
  * @param {number} y - Tile y
171
- * @returns {number} */
174
+ * @return {number} */
172
175
  getCost(x, y)
173
176
  {
174
177
  return 0;
@@ -177,7 +180,7 @@ class PathFinder
177
180
  /** Get the node at tile coords, or null if out of bounds.
178
181
  * @param {number} x
179
182
  * @param {number} y
180
- * @returns {PathFinderNode|null} */
183
+ * @return {PathFinderNode|null} */
181
184
  getNode(x, y)
182
185
  {
183
186
  if (x < 0 || y < 0 || x >= this.size.x || y >= this.size.y) return null;
@@ -186,7 +189,7 @@ class PathFinder
186
189
 
187
190
  /** Convert a world-space position to integer tile coords (no clamping).
188
191
  * @param {Vector2} worldPos
189
- * @returns {Vector2} */
192
+ * @return {Vector2} */
190
193
  worldToTile(worldPos)
191
194
  {
192
195
  const ox = this.tileLayer ? this.tileLayer.pos.x : 0;
@@ -197,7 +200,7 @@ class PathFinder
197
200
  /** Convert integer tile coords to the world-space center of that tile.
198
201
  * @param {number} x
199
202
  * @param {number} y
200
- * @returns {Vector2} */
203
+ * @return {Vector2} */
201
204
  tileToWorld(x, y)
202
205
  {
203
206
  const ox = this.tileLayer ? this.tileLayer.pos.x : 0;
@@ -241,7 +244,7 @@ class PathFinder
241
244
  * reached; false on disconnected goal or maxLoop exhaustion, which sets searchGaveUp.
242
245
  * @param {PathFinderNode} startNode
243
246
  * @param {PathFinderNode} endNode
244
- * @returns {boolean}
247
+ * @return {boolean}
245
248
  * @private */
246
249
  aStarSearch(startNode, endNode)
247
250
  {
@@ -385,9 +388,9 @@ class PathFinder
385
388
  * unchanged walkability, pass `rebuild=false` and call `buildNodeData()`
386
389
  * once externally to avoid redundant work.
387
390
  * @param {Vector2} worldPos
388
- * @param {number} [searchRange=10] - Max box-radius in tiles
389
- * @param {boolean} [rebuild=true] - Whether to call buildNodeData first
390
- * @returns {PathFinderNode|null} */
391
+ * @param {number} [searchRange] - Max box-radius in tiles
392
+ * @param {boolean} [rebuild] - Whether to call buildNodeData first
393
+ * @return {PathFinderNode|null} */
391
394
  getNearestClearNode(worldPos, searchRange = 10, rebuild = true)
392
395
  {
393
396
  ASSERT(isVector2(worldPos), 'worldPos must be a Vector2');
@@ -550,20 +553,36 @@ class PathFinder
550
553
  {
551
554
  if (path.length <= 2) return;
552
555
 
553
- // Greedy: from each kept node, jump to the furthest node with a clear
554
- // line to it, or else the next node. Every segment is one isLineClear
556
+ // Greedy: from each kept node, jump to the furthest corner with a clear
557
+ // line to it, then on along the straight run past it as far as one is
558
+ // clear, or else the next node. Every segment is one isLineClear
555
559
  // accepted or one the path already had, so none can cross a wall.
556
- const original = path.slice();
560
+ // Corners first, so a winding path costs a check per corner and not
561
+ // per cell of it.
562
+ const original = path.slice(), last = original.length - 1;
563
+ const corners = [];
564
+ for (let i = 1; i < last; ++i)
565
+ {
566
+ const a = original[i - 1].pos, b = original[i].pos, c = original[i + 1].pos;
567
+ if ((b.x - a.x) * (c.y - a.y) !== (b.y - a.y) * (c.x - a.x))
568
+ corners.push(i);
569
+ }
570
+ corners.push(last);
557
571
  path.length = 0;
558
572
  path.push(original[0]);
559
- for (let k = 0; k < original.length - 1;)
573
+ for (let k = 0; k < last;)
560
574
  {
561
- let j = original.length - 1;
575
+ let j = k + 1;
562
576
  if (original[k].isClear()) // isLineClear needs both ends clear
563
- while (j > k + 1 && !(original[j].isClear() && this.isLineClear(original[k].pos, original[j].pos)))
564
- --j;
565
- else
566
- j = k + 1;
577
+ {
578
+ const clear = (i)=> original[i].isClear() && this.isLineClear(original[k].pos, original[i].pos);
579
+ let c = corners.length - 1, next = last + 1; // next is the corner past the one found
580
+ while (c >= 0 && corners[c] > k + 1 && !clear(corners[c]))
581
+ next = corners[c--];
582
+ j = c >= 0 && corners[c] > k + 1 ? corners[c] : k + 1;
583
+ for (let i = next - 1; i > j; --i)
584
+ if (clear(i)) { j = i; break; }
585
+ }
567
586
  path.push(original[j]);
568
587
  k = j;
569
588
  }
@@ -578,20 +597,25 @@ class PathFinder
578
597
  * @private */
579
598
  dropCollinearNodes(path)
580
599
  {
581
- for (let i = path.length - 2; i >= 1; --i)
600
+ // one pass, a node kept only where the way turns from the last one kept
601
+ if (path.length < 3) return;
602
+ let kept = 1;
603
+ for (let i = 1; i < path.length - 1; ++i)
582
604
  {
583
- const a = path[i - 1], b = path[i], c = path[i + 1];
584
- if ((b.pos.x - a.pos.x) * (c.pos.y - a.pos.y) ===
605
+ const a = path[kept - 1], b = path[i], c = path[i + 1];
606
+ if ((b.pos.x - a.pos.x) * (c.pos.y - a.pos.y) !==
585
607
  (b.pos.y - a.pos.y) * (c.pos.x - a.pos.x))
586
- path.splice(i, 1);
608
+ path[kept++] = b;
587
609
  }
610
+ path[kept++] = path[path.length - 1];
611
+ path.length = kept;
588
612
  }
589
613
 
590
614
  /** Lookup helper: true when the node at tile coords (x, y) is in-bounds
591
615
  * and clear (walkable, zero-cost). Used by isLineClear's hot path.
592
616
  * @param {number} x
593
617
  * @param {number} y
594
- * @returns {boolean}
618
+ * @return {boolean}
595
619
  * @private */
596
620
  isNodeClear(x, y)
597
621
  {
@@ -608,7 +632,7 @@ class PathFinder
608
632
  * CheckLine() in pathFinding.cpp.
609
633
  * @param {Vector2} startPos - Tile coords
610
634
  * @param {Vector2} endPos - Tile coords
611
- * @returns {boolean}
635
+ * @return {boolean}
612
636
  * @private */
613
637
  isLineClear(startPos, endPos)
614
638
  {
@@ -731,7 +755,7 @@ class PathFinder
731
755
  * @param {Vector2} startPos - World-space start
732
756
  * @param {Vector2} endPos - World-space end
733
757
  * @param {boolean} [rebuild] - Whether to call buildNodeData first
734
- * @returns {Vector2[]} */
758
+ * @return {Vector2[]} */
735
759
  findPath(startPos, endPos, rebuild = true)
736
760
  {
737
761
  ASSERT(isVector2(startPos) && isVector2(endPos), 'findPath needs Vector2 endpoints');
@@ -37,6 +37,8 @@ export
37
37
  postProcessCurve,
38
38
  postProcessChromatic,
39
39
  postProcessOutline,
40
+ postProcessTiltShift,
41
+ postProcessDepthOfField,
40
42
  postProcessTV,
41
43
 
42
44
  // Light System