@skewedaspect/sage 0.9.4 → 0.9.6

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.
@@ -103,7 +103,6 @@ export declare class GameLevel extends Level {
103
103
  * and fix PBR material falloff for compatibility.
104
104
  */
105
105
  private _setupClustering;
106
- private _toColor3;
107
106
  /**
108
107
  * Configure and enable the Havok physics plugin with the level's gravity settings.
109
108
  */
@@ -112,21 +111,10 @@ export declare class GameLevel extends Level {
112
111
  * Walk all transform nodes and meshes, normalizing glTF metadata and dispatching to property handlers.
113
112
  */
114
113
  private _processNodeProperties;
115
- /**
116
- * Get normalized metadata from a node, handling glTF extras.
117
- *
118
- * Babylon.js stores glTF custom properties in node.metadata.gltf.extras,
119
- * so we need to extract them for easier access.
120
- */
121
- private _getNormalizedMetadata;
122
114
  /**
123
115
  * Check a node's metadata for spawn/entity markers and run any registered property handlers.
124
116
  */
125
117
  private _processNodeMetadata;
126
- /**
127
- * Run registered property handlers on a node.
128
- */
129
- private _runPropertyHandlers;
130
118
  /**
131
119
  * Match collected spawn points against the level config's spawn definitions and instantiate entities.
132
120
  */
@@ -144,11 +132,6 @@ export declare class GameLevel extends Level {
144
132
  * Create an entity from a spawn definition, inheriting position/rotation/scaling from the spawn point.
145
133
  */
146
134
  private _spawnEntity;
147
- /**
148
- * Build or import a mesh from the entity definition and attach it to the entity.
149
- * Supports primitive shapes (box/sphere/capsule/cylinder) and GLB/GLTF file paths.
150
- */
151
- private _createEntityMesh;
152
135
  /**
153
136
  * Create game entities for all nodes marked with the 'entity' metadata property.
154
137
  */
@@ -68,9 +68,18 @@ export declare class MouseInputPlugin {
68
68
  */
69
69
  private _handlePointerMove;
70
70
  /**
71
- * Handle mouse wheel events
71
+ * Handle mouse wheel events.
72
+ *
73
+ * The wheel is a pulse, not a level: the browser reports a delta per notch and never reports "stopped scrolling".
74
+ * So each event notifies twice — once carrying the deltas, once carrying zeros. Without that trailing zero,
75
+ * `emitOnChange` value bindings swallow every notch after the first (consecutive notches report identical deltas),
76
+ * trigger bindings never see a falling edge to re-arm on, and `getState()` latches the last scroll forever.
72
77
  */
73
78
  private _handleMouseWheel;
79
+ /**
80
+ * Notify subscribers of the current wheel state, attributed to the wheel event that produced it
81
+ */
82
+ private _notifyWheelState;
74
83
  /**
75
84
  * Notify subscribers of device connected event
76
85
  */
@@ -1,9 +1,23 @@
1
- import { Scene } from '@babylonjs/core';
1
+ import { Color3, Quaternion, Scene, TransformNode, Vector3 } from '@babylonjs/core';
2
2
  import { ClusteredLightContainer } from '@babylonjs/core/Lights/Clustered/clusteredLightContainer';
3
3
  import { LoggerInterface } from "../interfaces/logger.d.ts";
4
- import { LevelConfig, LevelContext, LevelInstance, PropertyHandler } from "../interfaces/level.d.ts";
4
+ import { ColorConfig, LevelConfig, LevelContext, LevelInstance, PropertyHandler } from "../interfaces/level.d.ts";
5
+ import { GameEntity } from "./entity.d.ts";
5
6
  import { GameEngine } from "./gameEngine.d.ts";
6
7
  import { OutlineManager } from "../managers/outline.d.ts";
8
+ /**
9
+ * Transform and visibility overrides applied to a mesh built by `Level.createEntityMesh()`.
10
+ */
11
+ export interface CreateEntityMeshOptions {
12
+ /** World position for the entity's root node. Left at the node default when omitted. */
13
+ position?: Vector3;
14
+ /** World rotation for the entity's root node. Left at the node default when omitted. */
15
+ rotation?: Quaternion;
16
+ /** Scaling for the entity's root node. Left at the node default when omitted. */
17
+ scaling?: Vector3;
18
+ /** When false, the node is disabled immediately on creation so nothing ever renders. Default true. */
19
+ enabled?: boolean;
20
+ }
7
21
  /**
8
22
  * Abstract base class for game levels.
9
23
  *
@@ -47,11 +61,34 @@ export declare abstract class Level implements LevelInstance {
47
61
  * @param message
48
62
  */
49
63
  protected $emitProgress(progress: number, message: string): void;
64
+ protected _toColor3(color: ColorConfig): Color3;
65
+ /**
66
+ * Get normalized metadata from a node, handling glTF extras.
67
+ *
68
+ * Babylon.js stores glTF custom properties in node.metadata.gltf.extras,
69
+ * so we need to extract them for easier access.
70
+ */
71
+ protected _getNormalizedMetadata(node: TransformNode): Record<string, unknown> | null;
72
+ /**
73
+ * Run registered property handlers on a node.
74
+ */
75
+ protected _runPropertyHandlers(node: TransformNode): Promise<void>;
50
76
  /**
51
77
  * Load and create the scene for this level.
52
78
  * Progress events will be emitted during loading.
53
79
  */
54
80
  load(): Promise<Scene>;
81
+ /**
82
+ * Build or import a mesh from the entity definition and attach it to the entity.
83
+ * Supports primitive shapes (box/sphere/capsule/cylinder) and GLB/GLTF file paths.
84
+ *
85
+ * @param entity - The entity whose definition supplies the mesh spec
86
+ * @param options - Transform overrides and initial enabled state
87
+ *
88
+ * @returns The root node the mesh was built under, or null if the level has no scene, the definition has no
89
+ * mesh spec, or the GLB import produced no meshes.
90
+ */
91
+ createEntityMesh(entity: GameEntity, options?: CreateEntityMeshOptions): Promise<TransformNode | null>;
55
92
  /**
56
93
  * Abstract method for building the scene content.
57
94
  * Concrete levels must implement this to create their specific content.
@@ -18,6 +18,11 @@ export interface CreateEntityOptions<State extends object = object> {
18
18
  tags?: string[];
19
19
  /** Scene node to attach the entity to. */
20
20
  node?: TransformNode;
21
+ /**
22
+ * Build the definition's mesh via the current level ('disabled' loads it setEnabled(false)).
23
+ * Mutually exclusive with `node`.
24
+ */
25
+ mesh?: 'enabled' | 'disabled';
21
26
  }
22
27
  export declare class GameEntityManager implements Disposable {
23
28
  /** The event bus for the entity manager. */
@@ -85,6 +90,18 @@ export declare class GameEntityManager implements Disposable {
85
90
  */
86
91
  private _registerEntityActions;
87
92
  $frameUpdate(dt: number): void;
93
+ /**
94
+ * Deep-clone a definition's defaultState for a new instance.
95
+ *
96
+ * A shallow spread would hand every instance of a type the same nested arrays and objects — mutate one
97
+ * entity's inventory slots and you mutate its neighbor's. Definitions must keep defaultState as plain,
98
+ * structured-cloneable data.
99
+ */
100
+ private _cloneDefaultState;
101
+ /**
102
+ * Honor the `mesh` create option by building the definition's mesh on the current level.
103
+ */
104
+ private _buildDefinitionMesh;
88
105
  /**
89
106
  * Registers a new entity definition.
90
107
  * Also registers any actions declared in the definition with the binding manager.
@@ -49,12 +49,22 @@ export declare class LevelManager implements Disposable {
49
49
  private _propertyHandlers;
50
50
  /** The currently active level */
51
51
  private _currentLevel;
52
+ /** The level currently inside load(), if any */
53
+ private _loadingLevel;
52
54
  /** Unsubscribe functions for event subscriptions */
53
55
  private _eventUnsubscribers;
54
56
  /**
55
57
  * Gets the currently active level
56
58
  */
57
59
  get currentLevel(): Level | null;
60
+ /**
61
+ * Gets the level currently being loaded, if any.
62
+ *
63
+ * During a transition, `currentLevel` still points at the outgoing level while the incoming one builds its
64
+ * scene — anything that needs "the level work is happening in right now" (dynamic entity mesh creation during
65
+ * spawn processing, for instance) must prefer this over `currentLevel`.
66
+ */
67
+ get loadingLevel(): Level | null;
58
68
  /**
59
69
  * Gets the registered property handlers
60
70
  */
package/dist/sage.es.js CHANGED
@@ -59,7 +59,7 @@ var he = class {
59
59
  configurable: !0
60
60
  });
61
61
  }
62
- }, k = "0.9.4", A = class {
62
+ }, k = "0.9.6", A = class {
63
63
  canvas;
64
64
  renderEngine;
65
65
  physics;
@@ -1606,6 +1606,26 @@ var Ae = ke(16), G = class {
1606
1606
  this._log.trace(`Updating ${this.entities.size} entities with dt=${e}`);
1607
1607
  for (let t of this.entities.values()) t.parent || t.$update(e);
1608
1608
  }
1609
+ _cloneDefaultState(e) {
1610
+ try {
1611
+ return structuredClone(e.defaultState);
1612
+ } catch (t) {
1613
+ throw Error(`Entity type '${e.type}' has a defaultState that cannot be structured-cloned. defaultState must be plain data (no functions, class instances, or DOM references).`, { cause: t });
1614
+ }
1615
+ }
1616
+ async _buildDefinitionMesh(e, t) {
1617
+ if (!t.mesh) return;
1618
+ if (t.node) {
1619
+ this._log.warn(`Ignoring 'mesh' option for entity ${e.id}: 'node' was supplied, and the two are exclusive.`);
1620
+ return;
1621
+ }
1622
+ let { levelManager: n } = this._engine.managers, r = n.loadingLevel ?? n.currentLevel;
1623
+ if (!r?.isLoaded) {
1624
+ this._log.warn(`Cannot build a mesh for entity ${e.id}: no level is loaded.`);
1625
+ return;
1626
+ }
1627
+ await r.createEntityMesh(e, { enabled: t.mesh === "enabled" });
1628
+ }
1609
1629
  registerEntityDefinition(e) {
1610
1630
  this._log.debug(`Registering entity definition: ${e.type}`);
1611
1631
  let t = e.extends ? this._resolveInheritance(e) : e;
@@ -1626,16 +1646,16 @@ var Ae = ke(16), G = class {
1626
1646
  if (r && r.length > 0) {
1627
1647
  this._log.trace(`Recycling entity from pool for type: ${e}`);
1628
1648
  let i = r.pop();
1629
- if (i.$reset(n.defaultState, n.tags ?? []), i.$setEntityManager(this), t.initialState && (i.state = {
1649
+ if (i.$reset(this._cloneDefaultState(n), n.tags ?? []), i.$setEntityManager(this), t.initialState && (i.state = {
1630
1650
  ...i.state,
1631
1651
  ...t.initialState
1632
1652
  }), t.tags) for (let e of t.tags) i.$addTag(e);
1633
- return t.node && i.$attachToNode(t.node, this._engine), this.entities.set(i.id, i), this._indexEntity(i), this._log.debug(`Recycled entity ${i.id} from pool`), i;
1653
+ return t.node && i.$attachToNode(t.node, this._engine), this.entities.set(i.id, i), this._indexEntity(i), await this._buildDefinitionMesh(i, t), this._log.debug(`Recycled entity ${i.id} from pool`), i;
1634
1654
  }
1635
1655
  }
1636
1656
  this._log.trace(`Using entity definition with ${n.behaviors?.length || 0} behaviors`);
1637
1657
  let r = {
1638
- ...n.defaultState,
1658
+ ...this._cloneDefaultState(n),
1639
1659
  ...t.initialState
1640
1660
  };
1641
1661
  if (n.onBeforeCreate) {
@@ -1660,7 +1680,7 @@ var Ae = ke(16), G = class {
1660
1680
  });
1661
1681
  a.$addChild(n);
1662
1682
  }
1663
- return a;
1683
+ return await this._buildDefinitionMesh(a, t), a;
1664
1684
  }
1665
1685
  async destroyEntity(e) {
1666
1686
  this._log.debug(`Destroying entity: ${e}`);
@@ -1670,7 +1690,7 @@ var Ae = ke(16), G = class {
1670
1690
  if (n?.poolable) {
1671
1691
  this._log.trace(`Pooling entity ${e} instead of destroying`);
1672
1692
  for (let e of [...t.children]) await this.destroyEntity(e.id);
1673
- t.parent && t.parent.$removeChild(t), this._unindexEntity(t), t.$reset(n.defaultState, n.tags ?? []);
1693
+ t.parent && t.parent.$removeChild(t), this._unindexEntity(t), t.$reset(this._cloneDefaultState(n), n.tags ?? []);
1674
1694
  let r = this._pools.get(t.type);
1675
1695
  r || (r = [], this._pools.set(t.type, r)), r.push(t), this.entities.delete(e), this._log.debug(`Entity ${e} pooled`);
1676
1696
  return;
@@ -1912,6 +1932,21 @@ var ze = class {
1912
1932
  }
1913
1933
  });
1914
1934
  }
1935
+ _toColor3(e) {
1936
+ return new r(e.r, e.g, e.b);
1937
+ }
1938
+ _getNormalizedMetadata(e) {
1939
+ if (!e.metadata) return null;
1940
+ let t = e.metadata?.gltf?.extras;
1941
+ return t ? { ...t } : e.metadata;
1942
+ }
1943
+ async _runPropertyHandlers(e) {
1944
+ for (let [t, n] of this.propertyHandlers) if (t in e.metadata) try {
1945
+ await n(e, e.metadata[t], this, this.gameEngine);
1946
+ } catch (n) {
1947
+ this._log.error(`Error in property handler '${t}' for node '${e.name}':`, n);
1948
+ }
1949
+ }
1915
1950
  async load() {
1916
1951
  if (this._scene) return this._log.warn(`Level ${this.name} is already loaded`), this._scene;
1917
1952
  this._log.info(`Loading level: ${this.name}`), this._log.time(`level-${this.name}-load`);
@@ -1935,6 +1970,74 @@ var ze = class {
1935
1970
  }), e;
1936
1971
  }
1937
1972
  }
1973
+ async createEntityMesh(e, t = {}) {
1974
+ if (!this._scene) return null;
1975
+ let n = this.gameEngine.managers.entityManager.getDefinition(e.type);
1976
+ if (!n?.mesh) return null;
1977
+ let r = n.mesh, i = this._scene, a = new D(`entity-${e.id}`, i);
1978
+ t.enabled === !1 && a.setEnabled(!1);
1979
+ let o;
1980
+ switch (r.source) {
1981
+ case "box":
1982
+ o = [b.CreateBox(`${e.type}-mesh`, {
1983
+ size: r.params?.size ?? 1,
1984
+ width: r.params?.width,
1985
+ height: r.params?.height,
1986
+ depth: r.params?.depth
1987
+ }, i)];
1988
+ break;
1989
+ case "sphere":
1990
+ o = [b.CreateSphere(`${e.type}-mesh`, {
1991
+ diameter: r.params?.diameter ?? 1,
1992
+ segments: r.params?.segments ?? 16
1993
+ }, i)];
1994
+ break;
1995
+ case "capsule":
1996
+ o = [b.CreateCapsule(`${e.type}-mesh`, {
1997
+ height: r.params?.height ?? 1.8,
1998
+ radius: r.params?.radius ?? .4
1999
+ }, i)];
2000
+ break;
2001
+ case "cylinder":
2002
+ o = [b.CreateCylinder(`${e.type}-mesh`, {
2003
+ height: r.params?.height ?? 1,
2004
+ diameter: r.params?.diameter ?? 1
2005
+ }, i)];
2006
+ break;
2007
+ default: {
2008
+ let e = await this.gameEngine.engines.sceneEngine.importMeshes([], r.source, i);
2009
+ if (e.meshes.length === 0) return this._log.warn(`No meshes loaded from: ${r.source}`), a.dispose(), null;
2010
+ o = e.meshes;
2011
+ for (let t of e.transformNodes) t.parent ||= a;
2012
+ break;
2013
+ }
2014
+ }
2015
+ for (let e of o) e.parent ||= a;
2016
+ t.position && a.position.copyFrom(t.position), t.rotation && (a.rotationQuaternion = t.rotation.clone()), t.scaling && a.scaling.copyFrom(t.scaling), r.scale && (typeof r.scale == "number" ? a.scaling.setAll(r.scale) : a.scaling.set(r.scale.x, r.scale.y, r.scale.z));
2017
+ let s = r.material;
2018
+ if (s?.color) {
2019
+ let t = s.color;
2020
+ if (s.type === "pbr") {
2021
+ let n = new S(`${e.type}-material`, i);
2022
+ n.albedoColor = this._toColor3(t), s.emissive && (n.emissiveColor = this._toColor3(s.emissive)), n.metallic = s.metallic ?? 0, n.roughness = s.roughness ?? 1;
2023
+ for (let e of o) e.material = n;
2024
+ } else {
2025
+ let n = new E(`${e.type}-material`, i);
2026
+ n.diffuseColor = this._toColor3(t), s.emissive && (n.emissiveColor = this._toColor3(s.emissive));
2027
+ for (let e of o) e.material = n;
2028
+ }
2029
+ }
2030
+ if (r.visible === !1) for (let e of o) e.isVisible = !1;
2031
+ let c = a.getDescendants(!1).filter((e) => e instanceof D);
2032
+ for (let e of c) {
2033
+ let t = this._getNormalizedMetadata(e);
2034
+ t && Object.keys(t).length > 0 && (e.metadata = {
2035
+ ...e.metadata,
2036
+ ...t
2037
+ }, await this._runPropertyHandlers(e));
2038
+ }
2039
+ return this.gameEngine.managers.entityManager.attachToNode(e, a), a;
2040
+ }
1938
2041
  async $dispose() {
1939
2042
  if (this._scene) {
1940
2043
  this._log.info(`Disposing level: ${this.name}`);
@@ -2163,9 +2266,6 @@ var ze = class {
2163
2266
  t.horizontalTiles !== void 0 && (r.horizontalTiles = t.horizontalTiles), t.verticalTiles !== void 0 && (r.verticalTiles = t.verticalTiles), t.depthSlices !== void 0 && (r.depthSlices = t.depthSlices), t.maxRange !== void 0 && (r.maxRange = t.maxRange), this.clusteredLights = r, this._log.debug(`Clustered lighting enabled: ${n.length} lights, ${r.horizontalTiles}x${r.verticalTiles} tiles, ${r.depthSlices} depth slices`);
2164
2267
  for (let t of e.materials) t instanceof S && (t.useGLTFLightFalloff = !0);
2165
2268
  }
2166
- _toColor3(e) {
2167
- return new r(e.r, e.g, e.b);
2168
- }
2169
2269
  async _enablePhysics(e) {
2170
2270
  let t = this._config.physics, n = new O(0, -9.81, 0);
2171
2271
  typeof t == "object" && t.gravity && (n = new O(t.gravity.x, t.gravity.y, t.gravity.z));
@@ -2183,11 +2283,6 @@ var ze = class {
2183
2283
  }
2184
2284
  this._log.debug(`Processed ${t.length} nodes, found ${this._spawnPoints.length} spawn points and ${this._entityNodes.length} entity nodes`);
2185
2285
  }
2186
- _getNormalizedMetadata(e) {
2187
- if (!e.metadata) return null;
2188
- let t = e.metadata?.gltf?.extras;
2189
- return t ? { ...t } : e.metadata;
2190
- }
2191
2286
  async _processNodeMetadata(e) {
2192
2287
  if ("spawn" in e.metadata) {
2193
2288
  let { position: t, rotation: n, scaling: r } = Re(e);
@@ -2204,13 +2299,6 @@ var ze = class {
2204
2299
  node: e
2205
2300
  }), await this._runPropertyHandlers(e);
2206
2301
  }
2207
- async _runPropertyHandlers(e) {
2208
- for (let [t, n] of this.propertyHandlers) if (t in e.metadata) try {
2209
- await n(e, e.metadata[t], this, this.gameEngine);
2210
- } catch (n) {
2211
- this._log.error(`Error in property handler '${t}' for node '${e.name}':`, n);
2212
- }
2213
- }
2214
2302
  async _processSpawnPoints() {
2215
2303
  for (let e of this._spawnPoints) {
2216
2304
  let t = this._config.spawns?.[e.name];
@@ -2240,76 +2328,11 @@ var ze = class {
2240
2328
  tags: e.tags,
2241
2329
  initialState: n
2242
2330
  });
2243
- return await this._createEntityMesh(r, t), r;
2244
- }
2245
- async _createEntityMesh(e, t) {
2246
- if (!this._scene) return;
2247
- let n = this.gameEngine.managers.entityManager.getDefinition(e.type);
2248
- if (!n?.mesh) return;
2249
- let r = n.mesh, i = this._scene, a = new D(`entity-${e.id}`, i), o;
2250
- switch (r.source) {
2251
- case "box":
2252
- o = [b.CreateBox(`${e.type}-mesh`, {
2253
- size: r.params?.size ?? 1,
2254
- width: r.params?.width,
2255
- height: r.params?.height,
2256
- depth: r.params?.depth
2257
- }, i)];
2258
- break;
2259
- case "sphere":
2260
- o = [b.CreateSphere(`${e.type}-mesh`, {
2261
- diameter: r.params?.diameter ?? 1,
2262
- segments: r.params?.segments ?? 16
2263
- }, i)];
2264
- break;
2265
- case "capsule":
2266
- o = [b.CreateCapsule(`${e.type}-mesh`, {
2267
- height: r.params?.height ?? 1.8,
2268
- radius: r.params?.radius ?? .4
2269
- }, i)];
2270
- break;
2271
- case "cylinder":
2272
- o = [b.CreateCylinder(`${e.type}-mesh`, {
2273
- height: r.params?.height ?? 1,
2274
- diameter: r.params?.diameter ?? 1
2275
- }, i)];
2276
- break;
2277
- default: {
2278
- let e = await this.gameEngine.engines.sceneEngine.importMeshes([], r.source, i);
2279
- if (e.meshes.length === 0) {
2280
- this._log.warn(`No meshes loaded from: ${r.source}`), a.dispose();
2281
- return;
2282
- }
2283
- o = e.meshes;
2284
- for (let t of e.transformNodes) t.parent ||= a;
2285
- break;
2286
- }
2287
- }
2288
- for (let e of o) e.parent ||= a;
2289
- a.position.copyFrom(t.position), a.rotationQuaternion = t.rotation.clone(), a.scaling.copyFrom(t.scaling), r.scale && (typeof r.scale == "number" ? a.scaling.setAll(r.scale) : a.scaling.set(r.scale.x, r.scale.y, r.scale.z));
2290
- let s = r.material;
2291
- if (s?.color) {
2292
- let t = s.color;
2293
- if (s.type === "pbr") {
2294
- let n = new S(`${e.type}-material`, i);
2295
- n.albedoColor = this._toColor3(t), s.emissive && (n.emissiveColor = this._toColor3(s.emissive)), n.metallic = s.metallic ?? 0, n.roughness = s.roughness ?? 1;
2296
- for (let e of o) e.material = n;
2297
- } else {
2298
- let n = new E(`${e.type}-material`, i);
2299
- n.diffuseColor = this._toColor3(t), s.emissive && (n.emissiveColor = this._toColor3(s.emissive));
2300
- for (let e of o) e.material = n;
2301
- }
2302
- }
2303
- if (r.visible === !1) for (let e of o) e.isVisible = !1;
2304
- let c = a.getDescendants(!1).filter((e) => e instanceof D);
2305
- for (let e of c) {
2306
- let t = this._getNormalizedMetadata(e);
2307
- t && Object.keys(t).length > 0 && (e.metadata = {
2308
- ...e.metadata,
2309
- ...t
2310
- }, await this._runPropertyHandlers(e));
2311
- }
2312
- this.gameEngine.managers.entityManager.attachToNode(e, a);
2331
+ return await this.createEntityMesh(r, {
2332
+ position: t.position,
2333
+ rotation: t.rotation,
2334
+ scaling: t.scaling
2335
+ }), r;
2313
2336
  }
2314
2337
  async _processEntityNodes() {
2315
2338
  for (let e of this._entityNodes) await this._processEntityNode(e);
@@ -2381,10 +2404,14 @@ var ze = class {
2381
2404
  _loadedLevels = /* @__PURE__ */ new Map();
2382
2405
  _propertyHandlers = /* @__PURE__ */ new Map();
2383
2406
  _currentLevel = null;
2407
+ _loadingLevel = null;
2384
2408
  _eventUnsubscribers = [];
2385
2409
  get currentLevel() {
2386
2410
  return this._currentLevel;
2387
2411
  }
2412
+ get loadingLevel() {
2413
+ return this._loadingLevel;
2414
+ }
2388
2415
  get propertyHandlers() {
2389
2416
  return this._propertyHandlers;
2390
2417
  }
@@ -2458,7 +2485,13 @@ var ze = class {
2458
2485
  if (n) return this._log.warn(`Level '${t.name}' is already loaded`), n;
2459
2486
  this._log.debug(`Loading level: ${t.name}`);
2460
2487
  let r = this._createLevelInstance(t);
2461
- return await r.load(), this._loadedLevels.set(t.name, r), r;
2488
+ this._loadingLevel = r;
2489
+ try {
2490
+ await r.load();
2491
+ } finally {
2492
+ this._loadingLevel = null;
2493
+ }
2494
+ return this._loadedLevels.set(t.name, r), r;
2462
2495
  }
2463
2496
  async activateLevel(e) {
2464
2497
  let t = await this.loadLevel(e);
@@ -2879,7 +2912,15 @@ var ze = class {
2879
2912
  deltaY: e.deltaY,
2880
2913
  deltaZ: e.deltaZ,
2881
2914
  deltaMode: e.deltaMode
2882
- }, this._notifyInputChanged({
2915
+ }, this._notifyWheelState(e), this._wheelState = {
2916
+ deltaX: 0,
2917
+ deltaY: 0,
2918
+ deltaZ: 0,
2919
+ deltaMode: 0
2920
+ }, this._notifyWheelState(e);
2921
+ }
2922
+ _notifyWheelState(e) {
2923
+ this._notifyInputChanged({
2883
2924
  type: "mouse",
2884
2925
  event: e,
2885
2926
  buttons: { ...this._buttonState },