littlejsengine 1.24.0 → 1.24.1
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/CHANGELOG.md +28 -1
- package/README.md +1 -1
- package/REFERENCE.md +7 -4
- package/dist/littlejs.d.ts +29 -18
- package/dist/littlejs.esm.js +117 -53
- package/dist/littlejs.esm.min.js +3 -3
- package/dist/littlejs.js +117 -53
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +96 -45
- package/package.json +1 -1
- package/plugins/box2d.js +1 -1
- package/plugins/render3d.js +3 -2
- package/plugins/render3dEditor.js +4 -2
- package/plugins/render3dExtras.js +9 -6
- package/plugins/render3dVoxels.js +15 -8
- package/src/engine.js +1 -1
- package/src/engineEditor.js +17 -6
- package/src/engineSettings.js +5 -1
- package/src/engineTileLayer.js +62 -26
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@ What changed in each release, newest first. Releases before 1.20.0 are on the
|
|
|
7
7
|
|
|
8
8
|
Nothing yet.
|
|
9
9
|
|
|
10
|
+
## 1.24.1 - 2026-10-02
|
|
11
|
+
|
|
12
|
+
### New Changes
|
|
13
|
+
|
|
14
|
+
- A VoxelMap block type can be `doubleSided`, its faces shown from inside the block too, so the surface of water shows from under it
|
|
15
|
+
- 3D Voxels: the player goes under water and walks on the bottom, in place of floating at the surface
|
|
16
|
+
- `tileLayersLoad`'s `collisionLayer` may be a layer's name
|
|
17
|
+
- `light.addFlare` takes the flare's color too
|
|
18
|
+
- LDtk: an IntGrid layer is always a hidden layer of its values under its own name, its rule tiles in layers over it named with tiles, so `tileLayersLoad(map, tileInfo, 0, 'Collisions')` is solid where the values say, whatever the tiles stack or fade; a project of several worlds loads; a project with no tileset entry keeps its tiles
|
|
19
|
+
- The example browser has a new look, in the dark colors of the docs site: a slim top bar in place of the title, the demo at 16:9, and the example list as rows under their headings. A handle between the code and its write-up drags to give one the other's space, by touch and the arrow keys too, and is remembered; a double click puts the quarter back. On a phone the example's name, between Prev and Next, opens a full screen picker with the search. The Screenshot button is gone
|
|
20
|
+
|
|
21
|
+
### Fixes
|
|
22
|
+
|
|
23
|
+
- Box2D: `addRegularPoly` with a side count that is not a whole number no longer hangs, as the engine's own polygons do not since 1.23.1
|
|
24
|
+
- The level editor's tile palette no longer asserts on a Tiled tileset with a margin or a spacing
|
|
25
|
+
- LDtk: a see-through tile keeps its opacity, in a layer of its own; a layer whose tileset is another size is left out with a warning, in place of being read as stacks; a missing level says so in release builds too
|
|
26
|
+
- A Tiled tileset's margin and spacing count from where the game's sheet starts, so a sheet inside an atlas keeps its place
|
|
27
|
+
- 2D level editor: applying an autosave of another size leaves an object layer it does not know about as the file has it, and an autosave whose tiles do not fit its size no longer resizes the map
|
|
28
|
+
- A light's lens flare is hidden by a room or a level mesh around it, seen from outside: only a lamp, a mesh around the light no wider than half its radius, is passed by
|
|
29
|
+
- Example browser: Enter on a focused example selects it, a heading named in a write-up is no longer a link that does nothing, blocked or broken saved preferences no longer stop the page, and the handle drags with the main button only
|
|
30
|
+
- 3D level editor: a broken autosave no longer blocks every edit until Drop, and one older than the last edits, which storage had no room for, says so as the 2D editor does
|
|
31
|
+
|
|
10
32
|
## 1.24.0 - 2026-10-02
|
|
11
33
|
|
|
12
34
|
### New Demos
|
|
@@ -23,7 +45,7 @@ Nothing yet.
|
|
|
23
45
|
- Lens flares in the 3D level editor: a level's Light has a `lensFlare` property, and the Scene box has a Lens flare checkbox for the sun's, saved in the level's scene block
|
|
24
46
|
- Lens flares have three more shapes, `hex`, `streak` and `star`; `flare.shapes` says what the ghosts are picked from, `glowSize` and `ghostSize` scale its parts, and an element may be a tile of the game's own (`tileInfo`), turned (`angle`) and wider than tall
|
|
25
47
|
- 3D picking is triangle accurate: `render3D.pick`, `engineObjectsRaycast3D` and clicks in the 3D level editor hit a mesh on its triangles, not the box around it
|
|
26
|
-
- LDtk levels load: `tileLayersFromLDtk(ldtk, level)` makes a level of an LDtk project a Tiled map for `tileLayersLoad` and `objectLayersLoad`, with its tile, auto and IntGrid layers, its entities as objects and their fields as properties
|
|
48
|
+
- LDtk levels load: `tileLayersFromLDtk(ldtk, level)` makes a level of an LDtk project a Tiled map for `tileLayersLoad` and `objectLayersLoad`, with its tile, auto and IntGrid layers, tiles stacked in a cell and see-through tiles, its entities as objects and their fields as properties
|
|
27
49
|
- A Tiled tileset with a margin or a spacing, a sheet with gaps between its tiles, is read where its tiles are
|
|
28
50
|
- The 3D Example has lens flares, on the sun and on each orb
|
|
29
51
|
- CHANGELOG.md lists what changed in each release, and the site's root goes to the example browser
|
|
@@ -64,6 +86,11 @@ Nothing yet.
|
|
|
64
86
|
- A `Sound` made while sound is off counts as loaded, so a loading screen that waits for sounds finishes
|
|
65
87
|
- A tween callback that throws no longer leaves the tween system allocating on every update
|
|
66
88
|
- The build and the docs build leave `dist/` and `docs/` untouched when a step fails
|
|
89
|
+
- A `Timer` set to a time below zero reads as done
|
|
90
|
+
- `UIVideo` lets go of its video when it is destroyed
|
|
91
|
+
- A texture atlas tag with no frames no longer drops the groups after it
|
|
92
|
+
- `setAdditiveBlendMode` works in headless mode
|
|
93
|
+
- The release build's `levelEditor` has `keys`, `buttons` and `tools`, as the debug one does
|
|
67
94
|
|
|
68
95
|
## 1.23.0 - 2026-10-01
|
|
69
96
|
|
package/README.md
CHANGED
|
@@ -105,7 +105,7 @@ The code is very clean and well documented with many examples to get you started
|
|
|
105
105
|
|
|
106
106
|
## How To Use LittleJS
|
|
107
107
|
|
|
108
|
-
To get started download the latest
|
|
108
|
+
To get started download the latest release from GitHub, the engine's builds and source as npm has them (the examples are in the repository), or install via npm:
|
|
109
109
|
|
|
110
110
|
```
|
|
111
111
|
npm install littlejsengine
|
package/REFERENCE.md
CHANGED
|
@@ -717,7 +717,8 @@ tileCollisionRaycast(posStart, posEnd, object, normal, solidOnly=true) // Where
|
|
|
717
717
|
// or undefined; a normal vec2 passed in is set to the surface's
|
|
718
718
|
tileCollisionLayers // List of all tile collision layers
|
|
719
719
|
tileLayersLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLayer, draw=true) // collisionLayer is the index
|
|
720
|
-
// of the layer that gets collision, no tile when no
|
|
720
|
+
// or the name of the layer that gets collision, no tile when no
|
|
721
|
+
// image is loaded
|
|
721
722
|
// Load tile layers from exported data, Tiled flips and turns included;
|
|
722
723
|
// groups are flattened and layer indices count that flat list,
|
|
723
724
|
// hidden layers load with collision but are not drawn; a
|
|
@@ -725,8 +726,10 @@ tileLayersLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLayer, draw
|
|
|
725
726
|
// its tiles are
|
|
726
727
|
tileLayersFromLDtk(ldtk, level=0) // A Tiled map of an LDtk level, by index or identifier, to give
|
|
727
728
|
// tileLayersLoad and objectLayersLoad: its layers bottom first, an
|
|
728
|
-
// IntGrid layer
|
|
729
|
-
//
|
|
729
|
+
// IntGrid layer a hidden layer of its values under its name, for
|
|
730
|
+
// collisionLayer, its rule tiles over it as name tiles, tiles
|
|
731
|
+
// stacked in a cell in layers above, named (2), (3), and a
|
|
732
|
+
// see-through tile in a layer of its opacity; its entities
|
|
730
733
|
// objects with their fields as properties
|
|
731
734
|
objectLayersAddType(name, make, defaults={}, tileInfo) // Name a type of object in a Tiled map: a class made
|
|
732
735
|
// with new make(pos), or an arrow function called make(pos),
|
|
@@ -1755,7 +1758,7 @@ flare.elements = [{at: .5, size: .1, color: hsl(.6,1,.6,.3), shape: 'disc'}] //
|
|
|
1755
1758
|
// tileInfo for a tile of your own; angle turns it
|
|
1756
1759
|
flare.visible // how much of the sun shows, 0 to 1, eased over fadeTime, to read; what is not
|
|
1757
1760
|
// see through hides it, flare.occlusion = false turns that off
|
|
1758
|
-
light.addFlare(size, count, intensity, saturation) // gives a Light3D a flare of its own and returns it: at the
|
|
1761
|
+
light.addFlare(size, count, intensity, saturation, color) // gives a Light3D a flare of its own and returns it: at the
|
|
1759
1762
|
// light, in its color, smaller from farther than its radius, hidden by what is
|
|
1760
1763
|
// in front of it, a spotlight's only from inside its beam
|
|
1761
1764
|
light.flare // the light's LensFlare3D, its child, or undefined; set one of your own, and
|
package/dist/littlejs.d.ts
CHANGED
|
@@ -4282,30 +4282,35 @@ declare module "littlejsengine" {
|
|
|
4282
4282
|
* @param {Object} tileMapData - Level data from exported data
|
|
4283
4283
|
* @param {TileInfo} [tileInfo] - Default tile info (used for size and texture), tile() by default, none when no image is loaded
|
|
4284
4284
|
* @param {number} [renderOrder] - Render order of the top layer
|
|
4285
|
-
* @param {number}
|
|
4285
|
+
* @param {number|string} [collisionLayer] - Layer to use for collision if any, by its index or its name
|
|
4286
4286
|
* @param {boolean} [draw] - Should the layer be drawn automatically
|
|
4287
4287
|
* @return {Array<TileCollisionLayer>}
|
|
4288
4288
|
* @memberof TileLayers */
|
|
4289
|
-
export function tileLayersLoad(tileMapData: any, tileInfo?: TileInfo, renderOrder?: number, collisionLayer?: number, draw?: boolean): Array<TileCollisionLayer>;
|
|
4289
|
+
export function tileLayersLoad(tileMapData: any, tileInfo?: TileInfo, renderOrder?: number, collisionLayer?: number | string, draw?: boolean): Array<TileCollisionLayer>;
|
|
4290
4290
|
/**
|
|
4291
4291
|
* Make a Tiled map of a level of an LDtk project, to load with tileLayersLoad and objectLayersLoad
|
|
4292
|
-
* - Each Tiles, AutoLayer and IntGrid layer is a tile layer, the bottom one first as in Tiled
|
|
4293
|
-
*
|
|
4294
|
-
*
|
|
4295
|
-
*
|
|
4296
|
-
* - An IntGrid layer
|
|
4292
|
+
* - Each Tiles, AutoLayer and IntGrid layer is a tile layer, the bottom one first as in Tiled; where LDtk stacks
|
|
4293
|
+
* tiles in a cell, an edge over a fill, each tile over another goes in a layer of its own just above, named with
|
|
4294
|
+
* (2), (3) and so on, and a tile LDtk draws see-through goes in one of its own with that opacity, times the
|
|
4295
|
+
* layer's, named with it, like Ground .25
|
|
4296
|
+
* - An IntGrid layer is a hidden layer of its values under its own name, for collision, whatever tiles its rules
|
|
4297
|
+
* make, which are layers over it named with tiles, like Collisions tiles; pass the name as collisionLayer, since
|
|
4298
|
+
* the stacked and see-through layers change the indices from level to level:
|
|
4299
|
+
* tileLayersLoad(map, tile(0, 16), 0, 'Collisions')
|
|
4297
4300
|
* - An Entities layer is an object layer: an entity's name is its type for objectLayersAddType, it is placed at
|
|
4298
4301
|
* its middle, and its Int, Float, Bool, String, Color and FilePath fields are its properties (an enum is a string)
|
|
4299
4302
|
* - The tileset is the first tile layer's, with its padding and spacing; give tileLayersLoad a tile info of its image
|
|
4300
4303
|
* - The level is in the project file (not saved as separate level files), its layers of one grid size; a layer of
|
|
4301
|
-
* another grid size or another tileset is left out, with a warning in debug builds
|
|
4304
|
+
* another grid size or another tileset is left out, with a warning in debug builds, and a project with no tileset
|
|
4305
|
+
* keeps its tiles for the tile info the game gives; a project of several worlds has its levels counted in order
|
|
4306
|
+
* - Layer offsets are not read
|
|
4302
4307
|
* - The level editor edits the map this returns, and saves it as a Tiled map
|
|
4303
4308
|
* @param {Object} ldtk - The LDtk project, its JSON
|
|
4304
4309
|
* @param {number|string} [level] - Which level, by its index or its identifier
|
|
4305
4310
|
* @return {Object} - A Tiled map: width, height, tilewidth, tileheight, tilesets and layers
|
|
4306
4311
|
* @example
|
|
4307
4312
|
* const map = tileLayersFromLDtk(await fetchJSON('world.ldtk'), 'Level_0');
|
|
4308
|
-
* const layers = tileLayersLoad(map, tile(0, 16), 0,
|
|
4313
|
+
* const layers = tileLayersLoad(map, tile(0, 16), 0, 'Collisions'); // its IntGrid layer is solid
|
|
4309
4314
|
* objectLayersLoad(map);
|
|
4310
4315
|
* @memberof TileLayers */
|
|
4311
4316
|
export function tileLayersFromLDtk(ldtk: any, level?: number | string): any;
|
|
@@ -9721,11 +9726,12 @@ declare module "littlejsengine" {
|
|
|
9721
9726
|
/** @type {Array<Vector3>} */
|
|
9722
9727
|
chunkCenters: Array<Vector3>;
|
|
9723
9728
|
chunksChanged: Set<any>;
|
|
9724
|
-
/** @type {Array<{faces: Array<number>, seeThrough: boolean, transparent: boolean}|undefined>} */
|
|
9729
|
+
/** @type {Array<{faces: Array<number>, seeThrough: boolean, transparent: boolean, doubleSided: boolean}|undefined>} */
|
|
9725
9730
|
blockTypes: Array<{
|
|
9726
9731
|
faces: Array<number>;
|
|
9727
9732
|
seeThrough: boolean;
|
|
9728
9733
|
transparent: boolean;
|
|
9734
|
+
doubleSided: boolean;
|
|
9729
9735
|
} | undefined>;
|
|
9730
9736
|
tiles: Map<any, any>;
|
|
9731
9737
|
/** The block type at a cell, 0 for empty or outside the map
|
|
@@ -9753,25 +9759,28 @@ declare module "littlejsengine" {
|
|
|
9753
9759
|
* @param {number} type - 1 to 255
|
|
9754
9760
|
* @param {number|Array<number>|{top?: number, side: number, bottom?: number}} faces - A tile index for every face,
|
|
9755
9761
|
* six in the order +x, -x, +y, -y, +z, -z, or the side's with the top and bottom's, which default to the side's
|
|
9756
|
-
* @param {{seeThrough?: boolean, transparent?: boolean}} [options] - seeThrough for holes
|
|
9757
|
-
* leaves, so the blocks beside it keep their faces; transparent to blend, like glass or
|
|
9758
|
-
* transparent stage, and see-through too
|
|
9762
|
+
* @param {{seeThrough?: boolean, transparent?: boolean, doubleSided?: boolean}} [options] - seeThrough for holes
|
|
9763
|
+
* in its texture, like leaves, so the blocks beside it keep their faces; transparent to blend, like glass or
|
|
9764
|
+
* water, drawn in the transparent stage, and see-through too; doubleSided for faces seen from inside the
|
|
9765
|
+
* block as well, like water, whose surface then shows from under it */
|
|
9759
9766
|
setBlockType(type: number, faces: number | Array<number> | {
|
|
9760
9767
|
top?: number;
|
|
9761
9768
|
side: number;
|
|
9762
9769
|
bottom?: number;
|
|
9763
|
-
}, { seeThrough, transparent }?: {
|
|
9770
|
+
}, { seeThrough, transparent, doubleSided }?: {
|
|
9764
9771
|
seeThrough?: boolean;
|
|
9765
9772
|
transparent?: boolean;
|
|
9773
|
+
doubleSided?: boolean;
|
|
9766
9774
|
}): void;
|
|
9767
9775
|
/** A block type's faces and how it is seen through
|
|
9768
9776
|
* @param {number} type
|
|
9769
|
-
* @return {{faces: Array<number>, seeThrough: boolean, transparent: boolean}}
|
|
9777
|
+
* @return {{faces: Array<number>, seeThrough: boolean, transparent: boolean, doubleSided: boolean}}
|
|
9770
9778
|
* @ignore */
|
|
9771
9779
|
blockType(type: number): {
|
|
9772
9780
|
faces: Array<number>;
|
|
9773
9781
|
seeThrough: boolean;
|
|
9774
9782
|
transparent: boolean;
|
|
9783
|
+
doubleSided: boolean;
|
|
9775
9784
|
};
|
|
9776
9785
|
/** Build every chunk again, after changing data directly or ambientOcclusion */
|
|
9777
9786
|
rebuild(): void;
|
|
@@ -9883,8 +9892,9 @@ declare module "littlejsengine" {
|
|
|
9883
9892
|
* @param {number} [count] - How many ghosts there are, besides the glow at the light
|
|
9884
9893
|
* @param {number} [intensity] - How bright it is
|
|
9885
9894
|
* @param {number} [saturation] - How colorful the ghosts are
|
|
9895
|
+
* @param {Color} [color] - Tints the flare, with the light's own color
|
|
9886
9896
|
* @return {LensFlare3D} - The flare, to change: light.addFlare().shapes = ['hex'] */
|
|
9887
|
-
addFlare(size?: number, count?: number, intensity?: number, saturation?: number): LensFlare3D;
|
|
9897
|
+
addFlare(size?: number, count?: number, intensity?: number, saturation?: number, color?: Color): LensFlare3D;
|
|
9888
9898
|
set flare(arg: LensFlare3D);
|
|
9889
9899
|
/** The light's lens flare, undefined for none: addFlare makes it, or set a LensFlare3D of your own, and
|
|
9890
9900
|
* destroying the flare takes it away
|
|
@@ -10209,7 +10219,7 @@ declare module "littlejsengine" {
|
|
|
10209
10219
|
* with the sun's own color; shapes says what its ghosts are, glowSize and ghostSize how big its parts are; or
|
|
10210
10220
|
* give it elements of your own, which may be tiles of the game's
|
|
10211
10221
|
* - visible is how much of the sun shows, 0 to 1, eased over fadeTime, there for a game to read; it is 0 while the
|
|
10212
|
-
* sun is off the screen, where nothing is tested
|
|
10222
|
+
* sun is too far off the screen for the flare to show, a third of the screen past its edge, where nothing is tested
|
|
10213
10223
|
* - What hides the sun is found with a ray from the camera, against the level and every object that is not see
|
|
10214
10224
|
* through, each on the triangles of its mesh, see render3D.pick; turn it off with occlusion
|
|
10215
10225
|
* - It needs WebGL, and it draws nothing in the shadow of renderAfter2D
|
|
@@ -10252,7 +10262,8 @@ declare module "littlejsengine" {
|
|
|
10252
10262
|
elements: Array<LensFlareElement> | undefined;
|
|
10253
10263
|
/** @property {Light3D|undefined} - A light the flare is of in place of the sun, a lamp or a spotlight: the
|
|
10254
10264
|
* flare is at the light and in its color, smaller from farther than the light reaches, hidden by what is
|
|
10255
|
-
* in front of the light,
|
|
10265
|
+
* in front of the light but its lamp, a mesh around it no wider than half its radius, and a spotlight's
|
|
10266
|
+
* shows from inside its beam only; a DirectionalLight3D's is far
|
|
10256
10267
|
* away where it shines from, like the sun's; the flare is destroyed when its light is; light.addFlare
|
|
10257
10268
|
* sets this
|
|
10258
10269
|
* @type {Light3D|undefined} */
|
package/dist/littlejs.esm.js
CHANGED
|
@@ -35,7 +35,7 @@ const engineName = 'LittleJS';
|
|
|
35
35
|
* @type {string}
|
|
36
36
|
* @default
|
|
37
37
|
* @memberof Engine */
|
|
38
|
-
const engineVersion = '1.24.
|
|
38
|
+
const engineVersion = '1.24.1';
|
|
39
39
|
|
|
40
40
|
/** Frames per second to update
|
|
41
41
|
* @type {number}
|
|
@@ -4084,7 +4084,11 @@ function setGLEnable(enable)
|
|
|
4084
4084
|
/** Set how many sided polygons to use when drawing circles and ellipses with WebGL
|
|
4085
4085
|
* @param {number} sides
|
|
4086
4086
|
* @memberof Settings */
|
|
4087
|
-
function setGLCircleSides(sides)
|
|
4087
|
+
function setGLCircleSides(sides)
|
|
4088
|
+
{
|
|
4089
|
+
ASSERT(isNumber(sides) && sides >= 3, 'circles need at least 3 sides');
|
|
4090
|
+
glCircleSides = sides;
|
|
4091
|
+
}
|
|
4088
4092
|
|
|
4089
4093
|
/** Set default size of tiles in pixels
|
|
4090
4094
|
* @param {Vector2} size
|
|
@@ -9606,7 +9610,7 @@ const tileLayersTiledFlips = [[0,0], [3,1], [2,1], [3,0], [0,1], [1,0], [2,0], [
|
|
|
9606
9610
|
* @param {Object} tileMapData - Level data from exported data
|
|
9607
9611
|
* @param {TileInfo} [tileInfo] - Default tile info (used for size and texture), tile() by default, none when no image is loaded
|
|
9608
9612
|
* @param {number} [renderOrder] - Render order of the top layer
|
|
9609
|
-
* @param {number}
|
|
9613
|
+
* @param {number|string} [collisionLayer] - Layer to use for collision if any, by its index or its name
|
|
9610
9614
|
* @param {boolean} [draw] - Should the layer be drawn automatically
|
|
9611
9615
|
* @return {Array<TileCollisionLayer>}
|
|
9612
9616
|
* @memberof TileLayers */
|
|
@@ -9638,7 +9642,8 @@ function tileLayersLoad(tileMapData, tileInfo=tileLayerDefaultTile(), renderOrde
|
|
|
9638
9642
|
const margin = tileset.margin || 0, spacing = tileset.spacing || 0, size = tileInfo.size;
|
|
9639
9643
|
const width = tileInfo.textureInfo?.size.x || 0;
|
|
9640
9644
|
const columns = tileset.columns || max(1, floor((width - margin*2 + spacing) / (size.x + spacing)));
|
|
9641
|
-
|
|
9645
|
+
const origin = tileInfo.pos.subtract(vec2(tileInfo.padding)); // where the sheet starts, in an atlas too
|
|
9646
|
+
tileInfo = new TileInfo(origin.add(vec2(margin)), size, tileInfo.textureInfo, spacing / 2, tileInfo.bleed, columns);
|
|
9642
9647
|
}
|
|
9643
9648
|
|
|
9644
9649
|
// flatten group layers in order, a group's color and visibility carry to the layers inside it
|
|
@@ -9669,6 +9674,14 @@ function tileLayersLoad(tileMapData, tileInfo=tileLayerDefaultTile(), renderOrde
|
|
|
9669
9674
|
};
|
|
9670
9675
|
addLayers(tileMapData.layers, WHITE, true);
|
|
9671
9676
|
|
|
9677
|
+
// a layer named for collision is the first tile layer of that name, which stays the same when layers are added
|
|
9678
|
+
if (typeof collisionLayer === 'string')
|
|
9679
|
+
{
|
|
9680
|
+
const name = collisionLayer;
|
|
9681
|
+
collisionLayer = layers.findIndex((l)=> l.color && l.dataLayer.name === name);
|
|
9682
|
+
ASSERT(collisionLayer >= 0, 'no tile layer named ' + name);
|
|
9683
|
+
}
|
|
9684
|
+
|
|
9672
9685
|
// create tile layers and fill with data
|
|
9673
9686
|
const tileLayers = [];
|
|
9674
9687
|
const levelSize = vec2(tileMapData.width, tileMapData.height);
|
|
@@ -9724,31 +9737,38 @@ const tileLayersLDtkTypes = {Int: 'int', Float: 'float', Bool: 'bool', String: '
|
|
|
9724
9737
|
|
|
9725
9738
|
/**
|
|
9726
9739
|
* Make a Tiled map of a level of an LDtk project, to load with tileLayersLoad and objectLayersLoad
|
|
9727
|
-
* - Each Tiles, AutoLayer and IntGrid layer is a tile layer, the bottom one first as in Tiled
|
|
9728
|
-
*
|
|
9729
|
-
*
|
|
9730
|
-
*
|
|
9731
|
-
* - An IntGrid layer
|
|
9740
|
+
* - Each Tiles, AutoLayer and IntGrid layer is a tile layer, the bottom one first as in Tiled; where LDtk stacks
|
|
9741
|
+
* tiles in a cell, an edge over a fill, each tile over another goes in a layer of its own just above, named with
|
|
9742
|
+
* (2), (3) and so on, and a tile LDtk draws see-through goes in one of its own with that opacity, times the
|
|
9743
|
+
* layer's, named with it, like Ground .25
|
|
9744
|
+
* - An IntGrid layer is a hidden layer of its values under its own name, for collision, whatever tiles its rules
|
|
9745
|
+
* make, which are layers over it named with tiles, like Collisions tiles; pass the name as collisionLayer, since
|
|
9746
|
+
* the stacked and see-through layers change the indices from level to level:
|
|
9747
|
+
* tileLayersLoad(map, tile(0, 16), 0, 'Collisions')
|
|
9732
9748
|
* - An Entities layer is an object layer: an entity's name is its type for objectLayersAddType, it is placed at
|
|
9733
9749
|
* its middle, and its Int, Float, Bool, String, Color and FilePath fields are its properties (an enum is a string)
|
|
9734
9750
|
* - The tileset is the first tile layer's, with its padding and spacing; give tileLayersLoad a tile info of its image
|
|
9735
9751
|
* - The level is in the project file (not saved as separate level files), its layers of one grid size; a layer of
|
|
9736
|
-
* another grid size or another tileset is left out, with a warning in debug builds
|
|
9752
|
+
* another grid size or another tileset is left out, with a warning in debug builds, and a project with no tileset
|
|
9753
|
+
* keeps its tiles for the tile info the game gives; a project of several worlds has its levels counted in order
|
|
9754
|
+
* - Layer offsets are not read
|
|
9737
9755
|
* - The level editor edits the map this returns, and saves it as a Tiled map
|
|
9738
9756
|
* @param {Object} ldtk - The LDtk project, its JSON
|
|
9739
9757
|
* @param {number|string} [level] - Which level, by its index or its identifier
|
|
9740
9758
|
* @return {Object} - A Tiled map: width, height, tilewidth, tileheight, tilesets and layers
|
|
9741
9759
|
* @example
|
|
9742
9760
|
* const map = tileLayersFromLDtk(await fetchJSON('world.ldtk'), 'Level_0');
|
|
9743
|
-
* const layers = tileLayersLoad(map, tile(0, 16), 0,
|
|
9761
|
+
* const layers = tileLayersLoad(map, tile(0, 16), 0, 'Collisions'); // its IntGrid layer is solid
|
|
9744
9762
|
* objectLayersLoad(map);
|
|
9745
9763
|
* @memberof TileLayers */
|
|
9746
9764
|
function tileLayersFromLDtk(ldtk, level=0)
|
|
9747
9765
|
{
|
|
9748
|
-
const levels = ldtk?.levels || [];
|
|
9766
|
+
const levels = ldtk?.levels?.length ? ldtk.levels : (ldtk?.worlds || []).flatMap((w)=> w.levels || []);
|
|
9749
9767
|
const data = typeof level === 'string' ? levels.find((l)=> l.identifier === level) : levels[level];
|
|
9750
|
-
|
|
9751
|
-
|
|
9768
|
+
if (!data)
|
|
9769
|
+
throw new Error('tileLayersFromLDtk: no level ' + level);
|
|
9770
|
+
if (!isArray(data.layerInstances))
|
|
9771
|
+
throw new Error('tileLayersFromLDtk: level ' + data.identifier + ' has no layers, levels saved as separate files are not read');
|
|
9752
9772
|
const instances = data.layerInstances;
|
|
9753
9773
|
const tiled = (l)=> l.__type !== 'Entities';
|
|
9754
9774
|
const first = instances.find((l)=> tiled(l) && l.__tilesetDefUid != undefined) || instances.find(tiled) || instances[0];
|
|
@@ -9788,33 +9808,53 @@ function tileLayersFromLDtk(ldtk, level=0)
|
|
|
9788
9808
|
continue;
|
|
9789
9809
|
}
|
|
9790
9810
|
const tiles = [...(instance.autoLayerTiles || []), ...(instance.gridTiles || [])];
|
|
9791
|
-
const
|
|
9792
|
-
|
|
9793
|
-
if (!
|
|
9811
|
+
const ownTiles = !tiles.length || !tileset ||
|
|
9812
|
+
instance.__tilesetDefUid === tileset.uid && tileset.tileGridSize === grid;
|
|
9813
|
+
if (instance.__gridSize !== grid || instance.__cWid !== width || instance.__cHei !== height || !ownTiles)
|
|
9794
9814
|
{
|
|
9795
9815
|
debug && console.warn(`tileLayersFromLDtk: layer ${name} has another grid or tileset, left out`);
|
|
9796
9816
|
continue;
|
|
9797
9817
|
}
|
|
9798
|
-
|
|
9799
|
-
|
|
9818
|
+
// an IntGrid layer's values, hidden, under its own name, for collision whatever its tiles do
|
|
9819
|
+
const empty = ()=> new Array(width * height).fill(0), opacity = instance.__opacity ?? 1;
|
|
9820
|
+
const values = instance.__type === 'IntGrid';
|
|
9821
|
+
if (values)
|
|
9822
|
+
{
|
|
9823
|
+
const data = empty();
|
|
9824
|
+
(instance.intGridCsv || []).forEach((value, k)=> k < data.length && (data[k] = value));
|
|
9825
|
+
map.layers.push({id, name, type: 'tilelayer', width, height, data, opacity, visible: false, x: 0, y: 0});
|
|
9826
|
+
if (!tiles.length) continue;
|
|
9827
|
+
}
|
|
9828
|
+
|
|
9829
|
+
// a layer for each depth in a cell and each opacity, the first tiles drawn solid first
|
|
9830
|
+
const tilesName = values ? name + ' tiles' : name;
|
|
9831
|
+
const stacks = [{depth: 0, alpha: 1, data: empty()}], stack = (depth, alpha)=>
|
|
9832
|
+
{
|
|
9833
|
+
let s = stacks.find((s)=> s.depth === depth && s.alpha === alpha);
|
|
9834
|
+
s || stacks.push(s = {depth, alpha, data: empty()});
|
|
9835
|
+
return s.data;
|
|
9836
|
+
};
|
|
9800
9837
|
{
|
|
9801
|
-
// a tile's place in pixels, its tile in the sheet,
|
|
9802
|
-
// Tiled's two top bits; LDtk lists them in the order it draws them, so the first in a
|
|
9803
|
-
// bottom one and each after it goes a layer higher
|
|
9838
|
+
// a tile's place in pixels, its tile in the sheet, its flips, bit 0 across and bit 1 down, which are
|
|
9839
|
+
// Tiled's two top bits, and its opacity; LDtk lists them in the order it draws them, so the first in a
|
|
9840
|
+
// cell is the bottom one and each after it goes a layer higher
|
|
9804
9841
|
const depth = new Uint16Array(width * height);
|
|
9805
9842
|
for (const t of tiles)
|
|
9806
9843
|
{
|
|
9807
9844
|
const x = floor(t.px[0] / grid), y = floor(t.px[1] / grid), cell = x + y * width;
|
|
9808
9845
|
if (x >= 0 && x < width && y >= 0 && y < height)
|
|
9809
|
-
(
|
|
9846
|
+
stack(depth[cell]++, round(clamp(t.a ?? 1) * 1e3) / 1e3)[cell] =
|
|
9810
9847
|
(t.t + 1 | (t.f & 1 ? 0x80000000 : 0) | (t.f & 2 ? 0x40000000 : 0)) >>> 0;
|
|
9811
9848
|
}
|
|
9812
9849
|
}
|
|
9813
|
-
|
|
9814
|
-
|
|
9815
|
-
const
|
|
9816
|
-
|
|
9817
|
-
|
|
9850
|
+
const visible = instance.visible !== false;
|
|
9851
|
+
stacks.sort((a, b)=> a.depth - b.depth || b.alpha - a.alpha);
|
|
9852
|
+
for (const {depth, alpha, data} of stacks)
|
|
9853
|
+
{
|
|
9854
|
+
const layerName = tilesName + (depth ? ` (${depth + 1})` : '') + (alpha < 1 ? ' ' + String(alpha).replace(/^0/, '') : '');
|
|
9855
|
+
map.layers.push({id: values || depth || alpha < 1 ? map.nextlayerid++ : id, name: layerName, type: 'tilelayer',
|
|
9856
|
+
width, height, data, opacity: opacity * alpha, visible, x: 0, y: 0});
|
|
9857
|
+
}
|
|
9818
9858
|
}
|
|
9819
9859
|
return map;
|
|
9820
9860
|
}
|
|
@@ -17375,7 +17415,7 @@ class Box2dObject extends EngineObject
|
|
|
17375
17415
|
|
|
17376
17416
|
const points = [];
|
|
17377
17417
|
const radius = diameter/2;
|
|
17378
|
-
for (let i=sides; i
|
|
17418
|
+
for (let i=sides; i-- > 0;) // a count that is not a whole number still ends
|
|
17379
17419
|
points.push(vec2(radius,0).rotate((i+.5)/sides*PI*2));
|
|
17380
17420
|
return this.addPoly(points, density, friction, restitution, isSensor);
|
|
17381
17421
|
}
|
|
@@ -27843,10 +27883,11 @@ class Light3D extends EngineObject3D
|
|
|
27843
27883
|
* @param {number} [count] - How many ghosts there are, besides the glow at the light
|
|
27844
27884
|
* @param {number} [intensity] - How bright it is
|
|
27845
27885
|
* @param {number} [saturation] - How colorful the ghosts are
|
|
27886
|
+
* @param {Color} [color] - Tints the flare, with the light's own color
|
|
27846
27887
|
* @return {LensFlare3D} - The flare, to change: light.addFlare().shapes = ['hex'] */
|
|
27847
|
-
addFlare(size, count, intensity, saturation)
|
|
27888
|
+
addFlare(size, count, intensity, saturation, color)
|
|
27848
27889
|
{
|
|
27849
|
-
const flare = new LensFlare3D(size, count, intensity, saturation);
|
|
27890
|
+
const flare = new LensFlare3D(size, count, intensity, saturation, color);
|
|
27850
27891
|
this.flare = flare;
|
|
27851
27892
|
return flare;
|
|
27852
27893
|
}
|
|
@@ -29494,7 +29535,7 @@ function render3DFlareTile(shape)
|
|
|
29494
29535
|
* with the sun's own color; shapes says what its ghosts are, glowSize and ghostSize how big its parts are; or
|
|
29495
29536
|
* give it elements of your own, which may be tiles of the game's
|
|
29496
29537
|
* - visible is how much of the sun shows, 0 to 1, eased over fadeTime, there for a game to read; it is 0 while the
|
|
29497
|
-
* sun is off the screen, where nothing is tested
|
|
29538
|
+
* sun is too far off the screen for the flare to show, a third of the screen past its edge, where nothing is tested
|
|
29498
29539
|
* - What hides the sun is found with a ray from the camera, against the level and every object that is not see
|
|
29499
29540
|
* through, each on the triangles of its mesh, see render3D.pick; turn it off with occlusion
|
|
29500
29541
|
* - It needs WebGL, and it draws nothing in the shadow of renderAfter2D
|
|
@@ -29541,7 +29582,8 @@ class LensFlare3D extends EngineObject3D
|
|
|
29541
29582
|
this.elements = undefined;
|
|
29542
29583
|
/** @property {Light3D|undefined} - A light the flare is of in place of the sun, a lamp or a spotlight: the
|
|
29543
29584
|
* flare is at the light and in its color, smaller from farther than the light reaches, hidden by what is
|
|
29544
|
-
* in front of the light,
|
|
29585
|
+
* in front of the light but its lamp, a mesh around it no wider than half its radius, and a spotlight's
|
|
29586
|
+
* shows from inside its beam only; a DirectionalLight3D's is far
|
|
29545
29587
|
* away where it shines from, like the sun's; the flare is destroyed when its light is; light.addFlare
|
|
29546
29588
|
* sets this
|
|
29547
29589
|
* @type {Light3D|undefined} */
|
|
@@ -29681,13 +29723,15 @@ class LensFlare3D extends EngineObject3D
|
|
|
29681
29723
|
if (map.raycast(ray, reach, (type)=> !map.blockType(type).seeThrough))
|
|
29682
29724
|
return true;
|
|
29683
29725
|
// a light's lamp, the mesh the light is inside, does not hide it: one whose box, in its own space, the light
|
|
29684
|
-
// is in
|
|
29726
|
+
// is in, and no wider than half the light's reach, since a room or a whole level has a box around its lights
|
|
29727
|
+
// too, and its walls do hide them from outside
|
|
29685
29728
|
const lamp = (o)=>
|
|
29686
29729
|
{
|
|
29687
29730
|
if (!this.light || reach === Infinity || !o.mesh || o instanceof HeightMap) return false;
|
|
29688
|
-
const matrix = render3DObjectMatrix(o);
|
|
29689
|
-
|
|
29690
|
-
|
|
29731
|
+
const matrix = render3DObjectMatrix(o), b = o.mesh.bounds || o.mesh.getBounds();
|
|
29732
|
+
const side = max(b.max.x - b.min.x, b.max.y - b.min.y, b.max.z - b.min.z) * render3DMaxStretch(matrix.m);
|
|
29733
|
+
if (!matrix.determinant() || side > this.light.radius / 2) return false;
|
|
29734
|
+
const p = matrix.copy().invert().transformPoint(source.pos);
|
|
29691
29735
|
return p.x >= b.min.x && p.x <= b.max.x && p.y >= b.min.y && p.y <= b.max.y &&
|
|
29692
29736
|
p.z >= b.min.z && p.z <= b.max.z;
|
|
29693
29737
|
};
|
|
@@ -30324,7 +30368,7 @@ class VoxelMap extends EngineObject3D
|
|
|
30324
30368
|
/** @type {Array<Vector3>} */
|
|
30325
30369
|
this.chunkCenters = []; // a chunk mesh's points are around its center, which it is drawn at
|
|
30326
30370
|
this.chunksChanged = new Set;
|
|
30327
|
-
/** @type {Array<{faces: Array<number>, seeThrough: boolean, transparent: boolean}|undefined>} */
|
|
30371
|
+
/** @type {Array<{faces: Array<number>, seeThrough: boolean, transparent: boolean, doubleSided: boolean}|undefined>} */
|
|
30328
30372
|
this.blockTypes = [];
|
|
30329
30373
|
this.tiles = new Map; // tile index to the uvs of its corners
|
|
30330
30374
|
|
|
@@ -30396,27 +30440,29 @@ class VoxelMap extends EngineObject3D
|
|
|
30396
30440
|
* @param {number} type - 1 to 255
|
|
30397
30441
|
* @param {number|Array<number>|{top?: number, side: number, bottom?: number}} faces - A tile index for every face,
|
|
30398
30442
|
* six in the order +x, -x, +y, -y, +z, -z, or the side's with the top and bottom's, which default to the side's
|
|
30399
|
-
* @param {{seeThrough?: boolean, transparent?: boolean}} [options] - seeThrough for holes
|
|
30400
|
-
* leaves, so the blocks beside it keep their faces; transparent to blend, like glass or
|
|
30401
|
-
* transparent stage, and see-through too
|
|
30402
|
-
|
|
30443
|
+
* @param {{seeThrough?: boolean, transparent?: boolean, doubleSided?: boolean}} [options] - seeThrough for holes
|
|
30444
|
+
* in its texture, like leaves, so the blocks beside it keep their faces; transparent to blend, like glass or
|
|
30445
|
+
* water, drawn in the transparent stage, and see-through too; doubleSided for faces seen from inside the
|
|
30446
|
+
* block as well, like water, whose surface then shows from under it */
|
|
30447
|
+
setBlockType(type, faces, {seeThrough=false, transparent=false, doubleSided=false}={})
|
|
30403
30448
|
{
|
|
30404
30449
|
ASSERT(type >= 1 && type <= 255 && type % 1 === 0, 'a block type is a whole number from 1 to 255', type);
|
|
30405
30450
|
const f = /** @type {any} */ (faces);
|
|
30406
30451
|
const list = isNumber(f) ? [f, f, f, f, f, f] : isArray(f) ? f :
|
|
30407
30452
|
[f.side, f.side, f.top ?? f.side, f.bottom ?? f.side, f.side, f.side];
|
|
30408
30453
|
ASSERT(list.length === 6 && list.every(isNumber), 'faces is a tile index, six of them, or {top, side, bottom}');
|
|
30409
|
-
this.blockTypes[type] = {faces: list, seeThrough: seeThrough || transparent, transparent};
|
|
30454
|
+
this.blockTypes[type] = {faces: list, seeThrough: seeThrough || transparent, transparent, doubleSided};
|
|
30410
30455
|
this.rebuild();
|
|
30411
30456
|
}
|
|
30412
30457
|
|
|
30413
30458
|
/** A block type's faces and how it is seen through
|
|
30414
30459
|
* @param {number} type
|
|
30415
|
-
* @return {{faces: Array<number>, seeThrough: boolean, transparent: boolean}}
|
|
30460
|
+
* @return {{faces: Array<number>, seeThrough: boolean, transparent: boolean, doubleSided: boolean}}
|
|
30416
30461
|
* @ignore */
|
|
30417
30462
|
blockType(type)
|
|
30418
30463
|
{
|
|
30419
|
-
return this.blockTypes[type] ||= {faces: [type, type, type, type, type, type], seeThrough: false, transparent: false
|
|
30464
|
+
return this.blockTypes[type] ||= {faces: [type, type, type, type, type, type], seeThrough: false, transparent: false,
|
|
30465
|
+
doubleSided: false};
|
|
30420
30466
|
}
|
|
30421
30467
|
|
|
30422
30468
|
/** Build every chunk again, after changing data directly or ambientOcclusion */
|
|
@@ -30486,6 +30532,11 @@ class VoxelMap extends EngineObject3D
|
|
|
30486
30532
|
colors[k] = ao ? RENDER3D_VOXEL_SHADES[shade[i]] : WHITE;
|
|
30487
30533
|
}
|
|
30488
30534
|
mesh.addStrip(points, normal, uvs, colors);
|
|
30535
|
+
|
|
30536
|
+
// a double sided block shows the face from inside too, its corners the other way round
|
|
30537
|
+
if (block.doubleSided)
|
|
30538
|
+
mesh.addStrip([points[1], points[0], points[3], points[2]], normal.scale(-1),
|
|
30539
|
+
[uvs[1], uvs[0], uvs[3], uvs[2]], [colors[1], colors[0], colors[3], colors[2]]);
|
|
30489
30540
|
}
|
|
30490
30541
|
}
|
|
30491
30542
|
this.chunkMeshes[index]?.dispose();
|
|
@@ -34276,8 +34327,8 @@ function editorMapRestore(map)
|
|
|
34276
34327
|
if ((saved.width ?? map.width) === map.width && (saved.height ?? map.height) === map.height &&
|
|
34277
34328
|
editorSameData(saved.layers, data) && editorSameData(saved.objects ?? [], objects))
|
|
34278
34329
|
editorDiscardPending(record);
|
|
34279
|
-
else if (!sameFile || !
|
|
34280
|
-
!editorCopyData(data, saved.layers))
|
|
34330
|
+
else if (!sameFile || !editorSavedFits(saved, data.length, saved.width ?? map.width, saved.height ?? map.height) ||
|
|
34331
|
+
!editorResizeMap(map, saved.width ?? map.width, saved.height ?? map.height) || !editorCopyData(data, saved.layers))
|
|
34281
34332
|
{
|
|
34282
34333
|
record.pending = saved;
|
|
34283
34334
|
console.warn(`LittleJS editor: ${record.fileName} changed since its autosaved edits, ` +
|
|
@@ -34576,6 +34627,11 @@ function editorCopyData(data, saved)
|
|
|
34576
34627
|
return true;
|
|
34577
34628
|
}
|
|
34578
34629
|
|
|
34630
|
+
// are an autosave's tiles a layer for each of the map's, each of its own size, so the map is resized only for tiles
|
|
34631
|
+
// that fit it
|
|
34632
|
+
const editorSavedFits = (saved, count, width, height)=> isArray(saved.layers) && saved.layers.length === count &&
|
|
34633
|
+
saved.layers.every((layer)=> isArray(layer) && layer.length === width * height);
|
|
34634
|
+
|
|
34579
34635
|
// remember a map's tile data and objects, or forget them when they are back to the file
|
|
34580
34636
|
function editorAutosave(record)
|
|
34581
34637
|
{
|
|
@@ -34604,7 +34660,7 @@ function editorPaintData(record, data, objects, width=record.map.width, height=r
|
|
|
34604
34660
|
{
|
|
34605
34661
|
if (!editorHas('onRestart')) return;
|
|
34606
34662
|
const before = editorMapSnapshot(record);
|
|
34607
|
-
editorSetMapSnapshot(record, {width, height, layers: data, objects});
|
|
34663
|
+
editorSetMapSnapshot(record, {width, height, layers: data, objects}, keepUnknown);
|
|
34608
34664
|
editorMapChanged(record, before);
|
|
34609
34665
|
return;
|
|
34610
34666
|
}
|
|
@@ -34721,13 +34777,15 @@ function editorMapSnapshot(record)
|
|
|
34721
34777
|
return editorObjectsCopy({width: map.width, height: map.height, layers: editorTileLayerData(map.layers), objects});
|
|
34722
34778
|
}
|
|
34723
34779
|
|
|
34724
|
-
// give a map a size, tiles and objects, the game's Restart hook makes its layers again
|
|
34725
|
-
|
|
34780
|
+
// give a map a size, tiles and objects, the game's Restart hook makes its layers again; keepUnknown leaves an object
|
|
34781
|
+
// layer past the snapshot's as it is, as applying an autosave from before the file had it does
|
|
34782
|
+
function editorSetMapSnapshot(record, snapshot, keepUnknown=false)
|
|
34726
34783
|
{
|
|
34727
34784
|
const map = record.map;
|
|
34728
34785
|
editorResizeMap(map, snapshot.width, snapshot.height);
|
|
34729
34786
|
editorCopyData(editorTileLayerData(map.layers), snapshot.layers);
|
|
34730
34787
|
editorRestoreObjects(map, snapshot);
|
|
34788
|
+
if (!keepUnknown)
|
|
34731
34789
|
for (const group of editorObjectGroups(map.layers).slice(snapshot.objects.length))
|
|
34732
34790
|
group.objects = []; // an Objects layer the editor made since
|
|
34733
34791
|
editorClearSelections();
|
|
@@ -35887,9 +35945,13 @@ function editorPaletteTiles(layer)
|
|
|
35887
35945
|
if (!image) return [];
|
|
35888
35946
|
if (layer.palette?.image === image) return layer.palette.tiles;
|
|
35889
35947
|
|
|
35890
|
-
|
|
35948
|
+
// a sheet read by its columns, a tileset with a margin or a spacing, is read with frame(), which asserts the
|
|
35949
|
+
// frame is inside the image, so a row past the image's end is found before its frame is made
|
|
35950
|
+
const tiles = [], base = live.tileInfo, columns = base.columns;
|
|
35891
35951
|
for (let i = 0; i < 4096; ++i)
|
|
35892
35952
|
{
|
|
35953
|
+
if (columns && base.pos.y + (i / columns | 0) * (base.size.y + base.padding*2) + base.size.y > image.height)
|
|
35954
|
+
break;
|
|
35893
35955
|
const t = editorTileInfo(live, i);
|
|
35894
35956
|
if (t.pos.x + t.size.x > image.width || t.pos.y + t.size.y > image.height) break;
|
|
35895
35957
|
tiles.push({tile: i, tileInfo: t});
|
|
@@ -37178,9 +37240,11 @@ function editor3DLevelLoaded(level)
|
|
|
37178
37240
|
level.objects = editor3DCopy(saved.objects);
|
|
37179
37241
|
for (const name of editor3DLevelPartNames)
|
|
37180
37242
|
parts[name] ? level[name] = editor3DCopy(parts[name]) : delete level[name];
|
|
37243
|
+
saved.stale && console.warn(`LittleJS editor: the edits brought back to ${record.fileName} are older than ` +
|
|
37244
|
+
'the last ones, which storage had no room for');
|
|
37181
37245
|
}
|
|
37182
|
-
else if (saved)
|
|
37183
|
-
record.pending = saved;
|
|
37246
|
+
else if (saved && isArray(saved.objects))
|
|
37247
|
+
record.pending = saved; // one that is broken is passed by, and the next autosave writes over it
|
|
37184
37248
|
}
|
|
37185
37249
|
|
|
37186
37250
|
// called by level3DLoad for each object, with what its type made
|