@skewedaspect/sage 0.3.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 (86) hide show
  1. package/LICENSE +21 -0
  2. package/Readme.md +53 -0
  3. package/dist/classes/bindings/toggle.d.ts +122 -0
  4. package/dist/classes/bindings/trigger.d.ts +79 -0
  5. package/dist/classes/bindings/value.d.ts +104 -0
  6. package/dist/classes/entity.d.ts +83 -0
  7. package/dist/classes/eventBus.d.ts +94 -0
  8. package/dist/classes/gameEngine.d.ts +57 -0
  9. package/dist/classes/input/gamepad.d.ts +94 -0
  10. package/dist/classes/input/keyboard.d.ts +66 -0
  11. package/dist/classes/input/mouse.d.ts +80 -0
  12. package/dist/classes/input/readers/gamepad.d.ts +77 -0
  13. package/dist/classes/input/readers/keyboard.d.ts +60 -0
  14. package/dist/classes/input/readers/mouse.d.ts +45 -0
  15. package/dist/classes/loggers/consoleBackend.d.ts +29 -0
  16. package/dist/classes/loggers/nullBackend.d.ts +14 -0
  17. package/dist/engines/scene.d.ts +11 -0
  18. package/dist/interfaces/action.d.ts +20 -0
  19. package/dist/interfaces/binding.d.ts +144 -0
  20. package/dist/interfaces/entity.d.ts +9 -0
  21. package/dist/interfaces/game.d.ts +26 -0
  22. package/dist/interfaces/input.d.ts +181 -0
  23. package/dist/interfaces/logger.d.ts +88 -0
  24. package/dist/managers/binding.d.ts +185 -0
  25. package/dist/managers/entity.d.ts +70 -0
  26. package/dist/managers/game.d.ts +20 -0
  27. package/dist/managers/input.d.ts +56 -0
  28. package/dist/managers/level.d.ts +55 -0
  29. package/dist/sage.d.ts +20 -0
  30. package/dist/sage.es.js +2208 -0
  31. package/dist/sage.es.js.map +1 -0
  32. package/dist/sage.umd.js +2 -0
  33. package/dist/sage.umd.js.map +1 -0
  34. package/dist/utils/capabilities.d.ts +2 -0
  35. package/dist/utils/graphics.d.ts +10 -0
  36. package/dist/utils/logger.d.ts +66 -0
  37. package/dist/utils/physics.d.ts +2 -0
  38. package/dist/utils/version.d.ts +5 -0
  39. package/docs/architecture.md +129 -0
  40. package/docs/behaviors.md +706 -0
  41. package/docs/binding_system.md +820 -0
  42. package/docs/design/input.md +86 -0
  43. package/docs/entity_system.md +538 -0
  44. package/docs/eventbus.md +225 -0
  45. package/docs/getting_started.md +264 -0
  46. package/docs/images/sage_logo.png +0 -0
  47. package/docs/images/sage_logo_shape.png +0 -0
  48. package/docs/overview.md +38 -0
  49. package/docs/physics_system.md +686 -0
  50. package/docs/scene_system.md +513 -0
  51. package/package.json +69 -0
  52. package/src/classes/bindings/toggle.ts +261 -0
  53. package/src/classes/bindings/trigger.ts +211 -0
  54. package/src/classes/bindings/value.ts +227 -0
  55. package/src/classes/entity.ts +256 -0
  56. package/src/classes/eventBus.ts +259 -0
  57. package/src/classes/gameEngine.ts +125 -0
  58. package/src/classes/input/gamepad.ts +388 -0
  59. package/src/classes/input/keyboard.ts +189 -0
  60. package/src/classes/input/mouse.ts +276 -0
  61. package/src/classes/input/readers/gamepad.ts +179 -0
  62. package/src/classes/input/readers/keyboard.ts +123 -0
  63. package/src/classes/input/readers/mouse.ts +133 -0
  64. package/src/classes/loggers/consoleBackend.ts +135 -0
  65. package/src/classes/loggers/nullBackend.ts +51 -0
  66. package/src/engines/scene.ts +112 -0
  67. package/src/images/sage_logo.svg +172 -0
  68. package/src/images/sage_logo_shape.svg +146 -0
  69. package/src/interfaces/action.ts +30 -0
  70. package/src/interfaces/binding.ts +191 -0
  71. package/src/interfaces/entity.ts +21 -0
  72. package/src/interfaces/game.ts +44 -0
  73. package/src/interfaces/input.ts +221 -0
  74. package/src/interfaces/logger.ts +118 -0
  75. package/src/managers/binding.ts +729 -0
  76. package/src/managers/entity.ts +252 -0
  77. package/src/managers/game.ts +111 -0
  78. package/src/managers/input.ts +233 -0
  79. package/src/managers/level.ts +261 -0
  80. package/src/sage.ts +119 -0
  81. package/src/types/global.d.ts +11 -0
  82. package/src/utils/capabilities.ts +16 -0
  83. package/src/utils/graphics.ts +148 -0
  84. package/src/utils/logger.ts +225 -0
  85. package/src/utils/physics.ts +16 -0
  86. package/src/utils/version.ts +11 -0
@@ -0,0 +1,2208 @@
1
+ var I = Object.defineProperty;
2
+ var k = (a, e, t) => e in a ? I(a, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[e] = t;
3
+ var n = (a, e, t) => k(a, typeof e != "symbol" ? e + "" : e, t);
4
+ import { FreeCamera as V, Vector3 as b, HemisphericLight as G, MeshBuilder as D, PhysicsAggregate as E, PhysicsShapeType as C, Scene as L, NullEngine as B, WebGPUEngine as K, Engine as U, HavokPlugin as R } from "@babylonjs/core";
5
+ import z from "@babylonjs/havok";
6
+ const m = "0.3.0";
7
+ class T {
8
+ /**
9
+ * Creates an instance of SkewedAspectGameEngine.
10
+ * @param canvas - The game canvas.
11
+ * @param renderEngine - The rendering engine.
12
+ * @param physics - The physics engine.
13
+ * @param eventBus - The event bus.
14
+ * @param logger - The logging utility.
15
+ * @param engines - The engines used in the game.
16
+ * @param managers - The managers used in the game.
17
+ */
18
+ constructor(e, t, s, i, o, r, c) {
19
+ n(this, "canvas");
20
+ n(this, "renderEngine");
21
+ n(this, "physics");
22
+ n(this, "managers");
23
+ n(this, "engines");
24
+ n(this, "eventBus");
25
+ n(this, "logger");
26
+ n(this, "_log");
27
+ this.canvas = e, this.renderEngine = t, this.physics = s, this.eventBus = i, this.logger = o, this.engines = r, this.managers = c, this._log = o.getLogger("GameEngine");
28
+ }
29
+ //------------------------------------------------------------------------------------------------------------------
30
+ // Public API
31
+ //------------------------------------------------------------------------------------------------------------------
32
+ /**
33
+ * Starts the game engine.
34
+ */
35
+ async start() {
36
+ this._log.info(`Starting SkewedAspect Game Engine (Version: ${m})...`), await this.managers.gameManager.start(this.canvas), this._log.info("Game engine started successfully");
37
+ }
38
+ /**
39
+ * Stops the game engine.
40
+ */
41
+ async stop() {
42
+ this._log.info(`Stopping SkewedAspect Game Engine (Version: ${m})...`), await this.managers.gameManager.stop(), this._log.info("Game engine stopped successfully");
43
+ }
44
+ }
45
+ class W {
46
+ constructor() {
47
+ n(this, "timers");
48
+ this.timers = /* @__PURE__ */ new Map();
49
+ }
50
+ /**
51
+ * Get the CSS style for a specific log level
52
+ */
53
+ getStyleForLevel(e) {
54
+ const t = {
55
+ trace: "color: #999999",
56
+ // Dim gray
57
+ debug: "color: #00AAAA",
58
+ // Cyan
59
+ info: "color: #00AA00",
60
+ // Green
61
+ warn: "color: #AAAA00",
62
+ // Yellow
63
+ error: "color: #AA0000",
64
+ // Red
65
+ timer: "color: #AA00AA",
66
+ // Magenta
67
+ none: "color: inherit"
68
+ // Default
69
+ };
70
+ return t[e] || t.none;
71
+ }
72
+ /**
73
+ * Format a log message with category, timestamp and log level
74
+ */
75
+ formatMessage(e, t) {
76
+ const s = /* @__PURE__ */ new Date(), i = s.getHours() % 12 || 12, o = s.getMinutes().toString().padStart(2, "0"), r = s.getSeconds().toString().padStart(2, "0"), c = s.getHours() >= 12 ? "PM" : "AM", h = `[${i}:${o}:${r} ${c}]`, u = t.toUpperCase();
77
+ return [
78
+ `${h} %c${u}%c (${e}): `,
79
+ // Format string with placeholders
80
+ this.getStyleForLevel(t),
81
+ // Level style
82
+ "",
83
+ // Default style
84
+ ""
85
+ // Reset style
86
+ ];
87
+ }
88
+ trace(e, t, ...s) {
89
+ const [i, o, r, c] = this.formatMessage(e, "trace");
90
+ console.debug(i, o, r, c, t, ...s);
91
+ }
92
+ debug(e, t, ...s) {
93
+ const [i, o, r, c] = this.formatMessage(e, "debug");
94
+ console.debug(i, o, r, c, t, ...s);
95
+ }
96
+ info(e, t, ...s) {
97
+ const [i, o, r, c] = this.formatMessage(e, "info");
98
+ console.info(i, o, r, c, t, ...s);
99
+ }
100
+ warn(e, t, ...s) {
101
+ const [i, o, r, c] = this.formatMessage(e, "warn");
102
+ console.warn(i, o, r, c, t, ...s);
103
+ }
104
+ error(e, t, ...s) {
105
+ const [i, o, r, c] = this.formatMessage(e, "error");
106
+ console.error(i, o, r, c, t, ...s);
107
+ }
108
+ /**
109
+ * Start a timer with the specified label.
110
+ */
111
+ time(e, t) {
112
+ const s = `${e}:${t}`;
113
+ this.timers.set(s, performance.now());
114
+ }
115
+ /**
116
+ * End a timer and log the elapsed time.
117
+ */
118
+ timeEnd(e, t) {
119
+ const s = `${e}:${t}`, i = this.timers.get(s);
120
+ if (i !== void 0) {
121
+ const o = performance.now() - i;
122
+ this.timers.delete(s);
123
+ const [r, c, h, u] = this.formatMessage(e, "timer");
124
+ console.info(`${r} Timer '${t}' completed in ${o.toFixed(2)}ms`, c, h, u);
125
+ } else
126
+ console.warn(`[${e}]: Timer '${t}' does not exist`);
127
+ }
128
+ }
129
+ class P {
130
+ trace(e, t, ...s) {
131
+ }
132
+ debug(e, t, ...s) {
133
+ }
134
+ info(e, t, ...s) {
135
+ }
136
+ warn(e, t, ...s) {
137
+ }
138
+ error(e, t, ...s) {
139
+ }
140
+ time(e, t) {
141
+ }
142
+ timeEnd(e, t) {
143
+ }
144
+ }
145
+ class l {
146
+ constructor(e, t = "none", s = new P()) {
147
+ n(this, "category");
148
+ n(this, "backend");
149
+ n(this, "minLevel");
150
+ this.category = e, this.backend = s, this.minLevel = t;
151
+ }
152
+ /**
153
+ * Update the logger's backend and minimum level
154
+ */
155
+ updateSettings(e, t) {
156
+ this.backend = e, this.minLevel = t;
157
+ }
158
+ trace(e, ...t) {
159
+ this.shouldLog("trace") && this.backend.trace(this.category, e, ...t);
160
+ }
161
+ debug(e, ...t) {
162
+ this.shouldLog("debug") && this.backend.debug(this.category, e, ...t);
163
+ }
164
+ info(e, ...t) {
165
+ this.shouldLog("info") && this.backend.info(this.category, e, ...t);
166
+ }
167
+ warn(e, ...t) {
168
+ this.shouldLog("warn") && this.backend.warn(this.category, e, ...t);
169
+ }
170
+ error(e, ...t) {
171
+ this.shouldLog("error") && this.backend.error(this.category, e, ...t);
172
+ }
173
+ time(e) {
174
+ this.minLevel !== "none" && this.backend.time(this.category, e);
175
+ }
176
+ timeEnd(e) {
177
+ this.minLevel !== "none" && this.backend.timeEnd(this.category, e);
178
+ }
179
+ /**
180
+ * Determine if a message at the given level should be logged based on the current minimum level.
181
+ */
182
+ shouldLog(e) {
183
+ if (this.minLevel === "none")
184
+ return !1;
185
+ switch (this.minLevel) {
186
+ case "trace":
187
+ return !0;
188
+ case "debug":
189
+ return e !== "trace";
190
+ case "info":
191
+ return e === "info" || e === "warn" || e === "error";
192
+ case "warn":
193
+ return e === "warn" || e === "error";
194
+ case "error":
195
+ return e === "error";
196
+ default:
197
+ return !1;
198
+ }
199
+ }
200
+ }
201
+ class F {
202
+ /**
203
+ * Create a new LoggingUtility.
204
+ *
205
+ * @param level - The minimum logging level (defaults to INFO)
206
+ * @param backend - The logging backend to use (defaults to ConsoleBackend)
207
+ */
208
+ constructor(e = "debug", t = new W()) {
209
+ n(this, "backend");
210
+ n(this, "level");
211
+ n(this, "loggers");
212
+ this.backend = t, this.level = e, this.loggers = /* @__PURE__ */ new Map();
213
+ }
214
+ /**
215
+ * Set the logging backend.
216
+ *
217
+ * @param backend - The logging backend to use
218
+ */
219
+ setBackend(e) {
220
+ this.backend = e, this.loggers.forEach((t) => {
221
+ t instanceof l && t.updateSettings(this.backend, this.level);
222
+ });
223
+ }
224
+ /**
225
+ * Set the minimum logging level.
226
+ *
227
+ * @param level - The minimum logging level to display
228
+ */
229
+ setLevel(e) {
230
+ this.level = e, this.loggers.forEach((t) => {
231
+ t instanceof l && t.updateSettings(this.backend, this.level);
232
+ });
233
+ }
234
+ /**
235
+ * Get the current minimum logging level.
236
+ */
237
+ getLevel() {
238
+ return this.level;
239
+ }
240
+ /**
241
+ * Get a logger for the specified category.
242
+ *
243
+ * @param category - The category for this logger (typically class/module name)
244
+ * @returns A Logger instance for the specified category
245
+ */
246
+ getLogger(e) {
247
+ this.loggers.has(e) || this.loggers.set(e, new l(e, this.level, this.backend));
248
+ const t = this.loggers.get(e);
249
+ if (t === void 0)
250
+ throw new Error(`Failed to create logger for category: ${e}`);
251
+ return t;
252
+ }
253
+ }
254
+ class O {
255
+ /**
256
+ * Creates a new GameEventBus instance
257
+ *
258
+ * @param logger - The logging utility to use
259
+ */
260
+ constructor(e) {
261
+ /**
262
+ * For exact event-type matches:
263
+ * - Key = event type string (e.g. "input:keydown")
264
+ * - Value = set of subscriptions for that exact type
265
+ */
266
+ n(this, "directMap", /* @__PURE__ */ new Map());
267
+ /**
268
+ * For pattern-based (wildcard or RegExp) subscriptions.
269
+ */
270
+ n(this, "patternSubs", /* @__PURE__ */ new Set());
271
+ /**
272
+ * Logger instance
273
+ */
274
+ n(this, "_log");
275
+ this._log = (e == null ? void 0 : e.getLogger("EventBus")) || new l("EventBus");
276
+ }
277
+ /**
278
+ * Subscribe with automatic detection:
279
+ * - If `eventTypeOrPattern` is a `RegExp` or a string containing `*`, treat it as a pattern subscription.
280
+ * - Otherwise, treat it as an exact subscription.
281
+ *
282
+ * @template P - The payload shape you want to expect in the callback.
283
+ * @param {string | RegExp} eventTypeOrPattern - The exact event type or a wildcard/RegExp pattern.
284
+ * @param {GameEventCallback<P>} callback - A callback expecting a `GameEvent<P>`.
285
+ * @returns {Unsubscribe} function that, when called, removes this subscription.
286
+ */
287
+ subscribe(e, t) {
288
+ return this._log.trace(`Subscribe request for: ${e.toString()}`), e instanceof RegExp || typeof e == "string" && e.includes("*") ? this.subscribePattern(e, t) : this.subscribeExact(e, t);
289
+ }
290
+ /**
291
+ * Subscribes to an exact event type (e.g. "input:keydown").
292
+ *
293
+ * @template P - The payload shape you want to expect in the callback.
294
+ * @param {string} eventType - The exact string to match.
295
+ * @param {GameEventCallback<P>} callback - A callback expecting a `GameEvent<P>`.
296
+ * @returns {Unsubscribe} function that unsubscribes this exact subscription.
297
+ */
298
+ subscribeExact(e, t) {
299
+ this._log.debug(`Adding exact subscription for: ${e}`);
300
+ let s = this.directMap.get(e);
301
+ s || (s = /* @__PURE__ */ new Set(), this.directMap.set(e, s));
302
+ const i = {
303
+ callback: (o) => t(o)
304
+ // runtime "type-lie" cast
305
+ };
306
+ return s.add(i), () => {
307
+ this._log.debug(`Removing exact subscription for: ${e}`), s.delete(i), s.size === 0 && this.directMap.delete(e);
308
+ };
309
+ }
310
+ /**
311
+ * Subscribes with either:
312
+ * - a wildcard string (contains `*`) that gets converted to a RegExp
313
+ * - a RegExp directly
314
+ *
315
+ * @template P - The payload shape you want to expect in the callback.
316
+ * @param {string | RegExp} patternOrRegExp - The pattern to match (`"input:*"` or `/^input:/`).
317
+ * @param {GameEventCallback<P>} callback - A callback expecting a `GameEvent<P>`.
318
+ * @returns {Unsubscribe} function that unsubscribes this pattern-based subscription.
319
+ */
320
+ subscribePattern(e, t) {
321
+ let s;
322
+ if (typeof e == "string") {
323
+ const o = e.replace(/[-/\\^$+?.()|[\]{}]/g, "\\$&").replace(/\*/g, ".*");
324
+ s = new RegExp(`^${o}$`), this._log.debug(`Adding pattern subscription for string: ${e}, regex: ${s.toString()}`);
325
+ } else
326
+ s = e, this._log.debug(`Adding pattern subscription for regex: ${s.toString()}`);
327
+ const i = {
328
+ pattern: s,
329
+ callback: (o) => t(o)
330
+ };
331
+ return this.patternSubs.add(i), () => {
332
+ this._log.debug(`Removing pattern subscription: ${s.toString()}`), this.patternSubs.delete(i);
333
+ };
334
+ }
335
+ /**
336
+ * Publishes an event, invoking all matching callbacks.
337
+ *
338
+ * Callbacks are called asynchronously (microtask) via `Promise.resolve()`.
339
+ *
340
+ * @param {GameEvent<any>} event - The event object to publish.
341
+ */
342
+ publish(e) {
343
+ this._log.trace(`Publishing event: ${e.type}`, e);
344
+ let t = 0;
345
+ const s = this.directMap.get(e.type);
346
+ if (s) {
347
+ t += s.size;
348
+ for (const i of s)
349
+ Promise.resolve().then(() => i.callback(e));
350
+ }
351
+ for (const i of this.patternSubs)
352
+ i.pattern && i.pattern.test(e.type) && (t++, Promise.resolve().then(() => i.callback(e)));
353
+ t === 0 ? this._log.debug(`No subscribers found for event: ${e.type}`) : this._log.trace(`Event ${e.type} dispatched to ${t} subscribers`);
354
+ }
355
+ }
356
+ class N {
357
+ constructor(e, t, s) {
358
+ n(this, "_engine");
359
+ n(this, "_physics");
360
+ n(this, "_log");
361
+ this._engine = e, this._physics = t, this._log = (s == null ? void 0 : s.getLogger("SceneEngine")) || new l("SceneEngine");
362
+ }
363
+ async _buildDemoScene(e, t) {
364
+ this._log.debug("Building demo scene..."), this._log.trace("Creating camera...");
365
+ const s = new V("camera1", new b(0, 5, -10), e);
366
+ s.setTarget(b.Zero()), s.attachControl(t, !0), this._log.trace("Creating light...");
367
+ const i = new G("light", new b(0, 1, 0), e);
368
+ i.intensity = 0.7, this._log.trace("Creating sphere...");
369
+ const o = D.CreateSphere("sphere", { diameter: 2, segments: 32 }, e);
370
+ o.position.y = 4, this._log.trace("Creating ground...");
371
+ const r = D.CreateGround("ground", { width: 10, height: 10 }, e);
372
+ this._log.trace("Adding physics to sphere..."), new E(o, C.SPHERE, { mass: 1, restitution: 0.75 }, e), this._log.trace("Adding physics to ground..."), new E(r, C.BOX, { mass: 0 }, e), this._log.debug("Demo scene built successfully");
373
+ }
374
+ // TODO: This needs more logic to handle different scenes
375
+ async loadScene(e) {
376
+ this._log.info("Loading scene..."), this._log.time("sceneLoad"), this._log.debug("Creating new scene...");
377
+ const t = new L(this._engine);
378
+ return this._log.debug("Enabling physics with gravity (0, -9.8, 0)..."), t.enablePhysics(new b(0, -9.8, 0), this._physics), await this._buildDemoScene(t, e), this._log.timeEnd("sceneLoad"), this._log.info("Scene loaded successfully"), t;
379
+ }
380
+ }
381
+ const J = [
382
+ "trigger",
383
+ "toggle",
384
+ "value"
385
+ ];
386
+ class Y {
387
+ //------------------------------------------------------------------------------------------------------------------
388
+ /**
389
+ * Create a new trigger binding
390
+ *
391
+ * @param action - Action to trigger
392
+ * @param deviceID - Device ID this binding is for
393
+ * @param reader - Input reader to read values from
394
+ * @param options - Binding configuration options
395
+ */
396
+ constructor(e, t, s, i = {}) {
397
+ n(this, "type", "trigger");
398
+ n(this, "action");
399
+ n(this, "context");
400
+ n(this, "deviceID");
401
+ n(this, "reader");
402
+ // Trigger-specific options
403
+ n(this, "_edgeMode");
404
+ n(this, "_threshold");
405
+ // State tracking
406
+ n(this, "_lastDigitalState", !1);
407
+ this.action = e, this.deviceID = t, this.reader = s, this.context = i.context, this._edgeMode = i.edgeMode ?? "rising", this._threshold = i.threshold ?? 0.5;
408
+ }
409
+ //------------------------------------------------------------------------------------------------------------------
410
+ // Public Getters
411
+ //------------------------------------------------------------------------------------------------------------------
412
+ /**
413
+ * Get the current options for this trigger binding.
414
+ *
415
+ * @returns The current options for this trigger binding
416
+ */
417
+ get options() {
418
+ return {
419
+ edgeMode: this._edgeMode,
420
+ threshold: this._threshold
421
+ };
422
+ }
423
+ //------------------------------------------------------------------------------------------------------------------
424
+ // Public Methods
425
+ //------------------------------------------------------------------------------------------------------------------
426
+ /**
427
+ * Process input state and emit an action if triggered
428
+ *
429
+ * @param state - Current input state
430
+ * @param eventBus - Event bus to emit action events to
431
+ * @returns True if an action was triggered, false otherwise
432
+ */
433
+ process(e, t) {
434
+ const s = this.reader.getValue(e) ?? !1, i = typeof s == "boolean" ? s : s >= this._threshold;
435
+ let o = !1;
436
+ switch (this._edgeMode) {
437
+ case "rising":
438
+ o = i && !this._lastDigitalState;
439
+ break;
440
+ case "falling":
441
+ o = !i && this._lastDigitalState;
442
+ break;
443
+ case "both":
444
+ o = i !== this._lastDigitalState;
445
+ break;
446
+ }
447
+ if (this._lastDigitalState = i, o) {
448
+ let r;
449
+ if (this.action.type === "analog") {
450
+ let h = typeof s == "number" ? s : s ? 1 : 0;
451
+ if (this.action.minValue !== void 0 || this.action.maxValue !== void 0) {
452
+ const u = this.action.minValue ?? 0, g = this.action.maxValue ?? 1;
453
+ h = u + h * (g - u);
454
+ }
455
+ r = h;
456
+ } else
457
+ r = !0;
458
+ const c = {
459
+ type: `action:${this.action.name}`,
460
+ payload: {
461
+ value: r,
462
+ deviceId: this.deviceID,
463
+ context: this.context
464
+ }
465
+ };
466
+ t.publish(c);
467
+ }
468
+ }
469
+ /**
470
+ * Returns a JSON-serializable representation of this trigger binding
471
+ *
472
+ * @returns A simple object representation that can be converted to JSON
473
+ */
474
+ toJSON() {
475
+ return {
476
+ type: this.type,
477
+ action: this.action.name,
478
+ input: {
479
+ deviceID: this.deviceID,
480
+ ...this.reader.toJSON()
481
+ },
482
+ context: this.context,
483
+ options: this.options
484
+ };
485
+ }
486
+ }
487
+ class H {
488
+ //------------------------------------------------------------------------------------------------------------------
489
+ /**
490
+ * Create a new toggle binding
491
+ *
492
+ * @param action - Action to toggle
493
+ * @param deviceID - Device ID this binding is for
494
+ * @param reader - Input reader to read values from
495
+ * @param options - Binding configuration options
496
+ */
497
+ constructor(e, t, s, i = {}) {
498
+ n(this, "type", "toggle");
499
+ n(this, "action");
500
+ n(this, "context");
501
+ n(this, "deviceID");
502
+ n(this, "reader");
503
+ // Toggle-specific options
504
+ n(this, "_invert");
505
+ n(this, "_threshold");
506
+ n(this, "_initialState");
507
+ n(this, "_onValue");
508
+ n(this, "_offValue");
509
+ // State tracking
510
+ n(this, "_lastDigitalState", !1);
511
+ n(this, "_toggleState");
512
+ this.action = e, this.deviceID = t, this.reader = s, this.context = i.context, this._invert = i.invert ?? !1, this._threshold = i.threshold ?? 0.5, this._initialState = i.initialState ?? !1, this._toggleState = this._initialState, this._onValue = i.onValue ?? !0, this._offValue = i.offValue ?? !1;
513
+ }
514
+ //------------------------------------------------------------------------------------------------------------------
515
+ // Public Getters
516
+ //------------------------------------------------------------------------------------------------------------------
517
+ /**
518
+ * Get the current toggle state
519
+ *
520
+ * @returns Current toggle state (true = on, false = off)
521
+ */
522
+ get state() {
523
+ return this._toggleState;
524
+ }
525
+ /**
526
+ * Set the toggle state directly (useful for programmatic control)
527
+ *
528
+ * @param value - New toggle state
529
+ */
530
+ set state(e) {
531
+ this._toggleState = e;
532
+ }
533
+ /**
534
+ * Get the value emitted when toggle is in the "on" state
535
+ *
536
+ * @returns The value for the "on" state
537
+ */
538
+ get onValue() {
539
+ return this._onValue;
540
+ }
541
+ /**
542
+ * Get the value emitted when toggle is in the "off" state
543
+ *
544
+ * @returns The value for the "off" state
545
+ */
546
+ get offValue() {
547
+ return this._offValue;
548
+ }
549
+ /**
550
+ * Get the current value based on toggle state
551
+ *
552
+ * @returns The current value (boolean or number)
553
+ */
554
+ get value() {
555
+ return this.action.type === "analog" ? this._toggleState ? this.action.maxValue ?? 1 : this.action.minValue ?? 0 : this._toggleState ? this._onValue : this._offValue;
556
+ }
557
+ /**
558
+ * Get the current options for this toggle binding.
559
+ *
560
+ * @returns The current options for this toggle binding
561
+ */
562
+ get options() {
563
+ return {
564
+ invert: this._invert,
565
+ threshold: this._threshold,
566
+ initialState: this._initialState,
567
+ onValue: this._onValue,
568
+ offValue: this._offValue
569
+ };
570
+ }
571
+ //------------------------------------------------------------------------------------------------------------------
572
+ // Public Methods
573
+ //------------------------------------------------------------------------------------------------------------------
574
+ /**
575
+ * Process input state and emit a digital action if toggled
576
+ *
577
+ * @param state - Current input state
578
+ * @param eventBus - Event bus to emit action events to
579
+ */
580
+ process(e, t) {
581
+ const s = this.reader.getValue(e) ?? !1, i = typeof s == "boolean" ? s : s >= this._threshold, o = this._invert ? !i && this._lastDigitalState : i && !this._lastDigitalState;
582
+ this._lastDigitalState = i, o && (this._toggleState = !this._toggleState, t.publish({
583
+ type: `action:${this.action.name}`,
584
+ payload: {
585
+ value: this.value,
586
+ deviceId: this.deviceID,
587
+ context: this.context
588
+ }
589
+ }));
590
+ }
591
+ /**
592
+ * Reset toggle to its initial state
593
+ */
594
+ reset() {
595
+ this._toggleState = this._initialState;
596
+ }
597
+ /**
598
+ * Returns a JSON-serializable representation of this toggle binding
599
+ *
600
+ * @returns A simple object representation that can be converted to JSON
601
+ */
602
+ toJSON() {
603
+ return {
604
+ type: this.type,
605
+ action: this.action.name,
606
+ input: {
607
+ deviceID: this.deviceID,
608
+ ...this.reader.toJSON()
609
+ },
610
+ state: this._toggleState,
611
+ context: this.context,
612
+ options: this.options
613
+ };
614
+ }
615
+ }
616
+ class X {
617
+ //------------------------------------------------------------------------------------------------------------------
618
+ /**
619
+ * Create a new value binding
620
+ *
621
+ * @param action - Action to emit values for
622
+ * @param deviceID - Device ID this binding is for
623
+ * @param reader - Input reader to read values from
624
+ * @param options - Binding configuration options
625
+ */
626
+ constructor(e, t, s, i = {}) {
627
+ n(this, "type", "value");
628
+ n(this, "action");
629
+ n(this, "context");
630
+ n(this, "deviceID");
631
+ n(this, "reader");
632
+ // Value-specific options
633
+ n(this, "_scale");
634
+ n(this, "_offset");
635
+ n(this, "_invert");
636
+ n(this, "_emitOnChange");
637
+ n(this, "_deadzone");
638
+ n(this, "_onValue");
639
+ n(this, "_offValue");
640
+ n(this, "_min");
641
+ n(this, "_max");
642
+ // State tracking
643
+ n(this, "_lastValue");
644
+ this.action = e, this.deviceID = t, this.reader = s, this.context = i.context, this._scale = i.scale ?? 1, this._offset = i.offset ?? 0, this._invert = i.invert ?? !1, this._emitOnChange = i.emitOnChange ?? !0, this._deadzone = i.deadzone ?? 0, this._onValue = i.onValue ?? !0, this._offValue = i.offValue ?? !1;
645
+ const o = this.action.type === "analog" ? this.action.minValue ?? Number.NEGATIVE_INFINITY : Number.NEGATIVE_INFINITY;
646
+ this._min = i.min ?? o;
647
+ const r = this.action.type === "analog" ? this.action.maxValue ?? Number.POSITIVE_INFINITY : Number.POSITIVE_INFINITY;
648
+ this._max = i.max ?? r;
649
+ }
650
+ //------------------------------------------------------------------------------------------------------------------
651
+ /**
652
+ * Get the current options for this value binding.
653
+ *
654
+ * @returns The current options for this value binding
655
+ */
656
+ get options() {
657
+ return {
658
+ scale: this._scale,
659
+ offset: this._offset,
660
+ invert: this._invert,
661
+ emitOnChange: this._emitOnChange,
662
+ deadzone: this._deadzone,
663
+ onValue: this._onValue,
664
+ offValue: this._offValue,
665
+ min: this._min,
666
+ max: this._max
667
+ };
668
+ }
669
+ //------------------------------------------------------------------------------------------------------------------
670
+ // Public Methods
671
+ //------------------------------------------------------------------------------------------------------------------
672
+ /**
673
+ * Process input state and emit an action value
674
+ *
675
+ * @param state - Current input state
676
+ * @param eventBus - Event bus to emit action events to
677
+ * @returns True if a value was emitted, false otherwise
678
+ */
679
+ process(e, t) {
680
+ const s = this.reader.getValue(e);
681
+ if (s === void 0)
682
+ return;
683
+ const i = typeof s == "boolean" ? s ? 1 : 0 : s;
684
+ if (i === void 0)
685
+ return;
686
+ let o = this._deadzone > 0 && Math.abs(i) < this._deadzone ? 0 : i;
687
+ o = (this._invert ? -o : o) * this._scale + this._offset, o = Math.max(this._min, Math.min(this._max, o)), !(this._emitOnChange && this._lastValue === o) && (this._lastValue = o, t.publish({
688
+ type: `action:${this.action.name}`,
689
+ payload: {
690
+ value: o,
691
+ deviceId: this.deviceID,
692
+ context: this.context
693
+ }
694
+ }));
695
+ }
696
+ /**
697
+ * Returns a JSON-serializable representation of this value binding
698
+ *
699
+ * @returns A simple object representation that can be converted to JSON
700
+ */
701
+ toJSON() {
702
+ return {
703
+ type: this.type,
704
+ action: this.action.name,
705
+ input: {
706
+ deviceID: this.deviceID,
707
+ ...this.reader.toJSON()
708
+ },
709
+ context: this.context,
710
+ options: this.options
711
+ };
712
+ }
713
+ }
714
+ class w {
715
+ /**
716
+ * Creates a new KeyboardValueReader
717
+ *
718
+ * @param keyCode - The key code to monitor (e.g., "KeyA", "Space")
719
+ * @param options - Configuration options
720
+ */
721
+ constructor(e, t = {}) {
722
+ /**
723
+ * The type of keyboard input (always 'key')
724
+ */
725
+ n(this, "sourceType", "key");
726
+ /**
727
+ * The key code to monitor (e.g., "KeyA", "Space")
728
+ */
729
+ n(this, "sourceKey");
730
+ /**
731
+ * Whether to use delta state instead of current state
732
+ */
733
+ n(this, "useDelta");
734
+ this.sourceKey = e, this.useDelta = t.useDelta || !1;
735
+ }
736
+ /**
737
+ * Gets the value of the key state
738
+ *
739
+ * @param state - The input state
740
+ * @returns The key state value or undefined if the state type is not keyboard
741
+ */
742
+ getValue(e) {
743
+ if (e.type !== "keyboard")
744
+ return;
745
+ const t = e;
746
+ return this.useDelta ? t.delta[this.sourceKey] : t.keys[this.sourceKey];
747
+ }
748
+ /**
749
+ * Creates a KeyboardValueReader from a string representation
750
+ *
751
+ * @param sourceKey - The key code (e.g., "KeyA", "Space")
752
+ * @param options - Optional configuration
753
+ * @returns A new KeyboardValueReader instance
754
+ */
755
+ static fromString(e, t = {}) {
756
+ return new w(e, t);
757
+ }
758
+ /**
759
+ * Returns a JSON-serializable representation of this keyboard value reader
760
+ *
761
+ * @returns A simple object representation that can be converted to JSON
762
+ */
763
+ toJSON() {
764
+ return {
765
+ type: "keyboard",
766
+ sourceType: this.sourceType,
767
+ sourceKey: this.sourceKey,
768
+ options: {
769
+ useDelta: this.useDelta
770
+ }
771
+ };
772
+ }
773
+ }
774
+ class x {
775
+ /**
776
+ * Creates a new MouseValueReader
777
+ *
778
+ * @param sourceType - Type of mouse input to monitor (button, position, wheel)
779
+ * @param sourceKey - The specific key for this input type
780
+ */
781
+ constructor(e, t) {
782
+ /**
783
+ * The type of mouse input
784
+ */
785
+ n(this, "sourceType");
786
+ /**
787
+ * The specific key for this input
788
+ */
789
+ n(this, "sourceKey");
790
+ this.sourceType = e, this.sourceKey = t;
791
+ }
792
+ /**
793
+ * Gets the value of the mouse input source
794
+ *
795
+ * @param state - The current input state
796
+ * @returns The value of the input source
797
+ */
798
+ getValue(e) {
799
+ if (e.type === "mouse")
800
+ switch (this.sourceType) {
801
+ case "button": {
802
+ const t = e.buttons[this.sourceKey];
803
+ return t ? t.pressed : void 0;
804
+ }
805
+ case "position": {
806
+ const [t, s] = this.sourceKey.split(":");
807
+ return !t || !s || t !== "absolute" && t !== "relative" || s !== "x" && s !== "y" ? void 0 : e.position[t][s];
808
+ }
809
+ case "wheel": {
810
+ const t = this.sourceKey === "deltaX" || this.sourceKey === "deltaY" || this.sourceKey === "deltaZ";
811
+ return e.wheel && t ? e.wheel[this.sourceKey] : void 0;
812
+ }
813
+ default:
814
+ return;
815
+ }
816
+ }
817
+ /**
818
+ * Creates a MouseValueReader from a string representation
819
+ *
820
+ * @param sourceTypeString - String in format "sourceType:sourceKey" (e.g., "button:0", "position:absolute:x")
821
+ * @returns A new MouseValueReader instance
822
+ */
823
+ static fromString(e) {
824
+ const [t, ...s] = e.split(":"), i = s.join(":");
825
+ if (!t || !i)
826
+ throw new Error(`Invalid mouse source format: ${e}`);
827
+ return new x(t, i);
828
+ }
829
+ /**
830
+ * Returns a JSON-serializable representation of this mouse value reader
831
+ *
832
+ * @returns A simple object representation that can be converted to JSON
833
+ */
834
+ toJSON() {
835
+ return {
836
+ type: "mouse",
837
+ sourceType: this.sourceType,
838
+ sourceKey: this.sourceKey
839
+ };
840
+ }
841
+ }
842
+ class S {
843
+ /**
844
+ * Creates a new GamepadValueReader
845
+ *
846
+ * @param sourceType - Type of gamepad input to monitor (button or axis)
847
+ * @param sourceKey - The specific key for this input type
848
+ * @param options - Configuration options
849
+ */
850
+ constructor(e, t, s = {}) {
851
+ /**
852
+ * The type of gamepad input
853
+ */
854
+ n(this, "sourceType");
855
+ /**
856
+ * The specific key for this input
857
+ */
858
+ n(this, "sourceKey");
859
+ /**
860
+ * Whether to use analog value for buttons
861
+ */
862
+ n(this, "useAnalogValue");
863
+ /**
864
+ * Deadzone value for axes
865
+ */
866
+ n(this, "deadzone");
867
+ /**
868
+ * Whether to invert axis values
869
+ */
870
+ n(this, "invert");
871
+ this.sourceType = e, this.sourceKey = t, this.useAnalogValue = s.useAnalogValue || !1, this.deadzone = s.deadzone || 0.1, this.invert = s.invert || !1;
872
+ }
873
+ /**
874
+ * Gets the value of the input source based on the current state
875
+ *
876
+ * @param state - The current input state
877
+ * @returns The value of the input source
878
+ */
879
+ getValue(e) {
880
+ if (e.type === "gamepad")
881
+ switch (this.sourceType) {
882
+ case "button": {
883
+ const t = e.buttons[this.sourceKey];
884
+ return t ? this.useAnalogValue ? t.value : t.pressed : void 0;
885
+ }
886
+ case "axis": {
887
+ let t = e.axes[this.sourceKey];
888
+ return t === void 0 ? void 0 : (Math.abs(t) < this.deadzone && (t = 0), this.invert ? -t : t);
889
+ }
890
+ default:
891
+ return;
892
+ }
893
+ }
894
+ /**
895
+ * Creates a GamepadValueReader from a string representation
896
+ *
897
+ * @param sourceTypeString - String in format "sourceType:sourceKey" (e.g., "button:0", "axis:1")
898
+ * @param options - Optional configuration options
899
+ * @returns A new GamepadValueReader instance
900
+ */
901
+ static fromString(e, t = {}) {
902
+ const [s, i] = e.split(":");
903
+ if (!s || !i)
904
+ throw new Error(`Invalid gamepad source format: ${e}`);
905
+ return new S(s, i, t);
906
+ }
907
+ /**
908
+ * Returns a JSON-serializable representation of this gamepad value reader
909
+ *
910
+ * @returns A simple object representation that can be converted to JSON
911
+ */
912
+ toJSON() {
913
+ return {
914
+ type: "gamepad",
915
+ sourceType: this.sourceType,
916
+ sourceKey: this.sourceKey,
917
+ options: {
918
+ useAnalogValue: this.useAnalogValue,
919
+ deadzone: this.deadzone,
920
+ invert: this.invert
921
+ }
922
+ };
923
+ }
924
+ }
925
+ class j {
926
+ /**
927
+ * Creates an instance of BindingManager.
928
+ *
929
+ * @param eventBus - The game event bus to publish events to
930
+ * @param logger - The logging utility to use
931
+ */
932
+ constructor(e, t) {
933
+ /** Map of device IDs to their bindings */
934
+ n(this, "_bindings", /* @__PURE__ */ new Map());
935
+ /** Map of action names to their action definitions */
936
+ n(this, "_actions", /* @__PURE__ */ new Map());
937
+ /** Map of all registered contexts by name for O(1) lookup */
938
+ n(this, "_contexts", /* @__PURE__ */ new Map());
939
+ /**
940
+ * Set of all active contexts (both exclusive and non-exclusive)
941
+ */
942
+ n(this, "_activeContexts", /* @__PURE__ */ new Set());
943
+ /** Event bus for handling game events */
944
+ n(this, "_eventBus");
945
+ /** Logger instance */
946
+ n(this, "_log");
947
+ this._eventBus = e, this._eventBus.subscribe("input:changed", (s) => {
948
+ s.payload && this.$handleInput(s.payload.device, s.payload.state);
949
+ }), this._log = (t == null ? void 0 : t.getLogger("BindingManager")) || new l("BindingManager"), this._log.debug("BindingManager initialized");
950
+ }
951
+ //------------------------------------------------------------------------------------------------------------------
952
+ // Private Methods
953
+ //------------------------------------------------------------------------------------------------------------------
954
+ /**
955
+ * Checks if a binding's context is currently active
956
+ *
957
+ * @param binding - The binding to check
958
+ * @returns True if binding's context is active or has no context
959
+ */
960
+ _isBindingContextActive(e) {
961
+ return e.context ? this._activeContexts.has(e.context) : !0;
962
+ }
963
+ /**
964
+ * Get a context by name, creating it if it doesn't exist
965
+ *
966
+ * @param contextName - The name of the context to get or create
967
+ * @param exclusive - Whether the context is exclusive (only used if creating)
968
+ * @returns The context object
969
+ */
970
+ _getOrCreateContext(e, t = !0) {
971
+ let s = this._contexts.get(e);
972
+ return s || (s = {
973
+ name: e,
974
+ exclusive: t
975
+ }, this._contexts.set(e, s), this._log.debug(`Auto-created context "${e}" (exclusive: ${t})`)), s;
976
+ }
977
+ /**
978
+ * Deactivate all exclusive contexts except the specified one
979
+ *
980
+ * @param exceptContextName - Name of context to not deactivate
981
+ * @returns Array of deactivated context names
982
+ */
983
+ _deactivateExclusiveContexts(e) {
984
+ const t = [];
985
+ for (const s of this._activeContexts) {
986
+ if (s === e)
987
+ continue;
988
+ const i = this._contexts.get(s);
989
+ i != null && i.exclusive && (this._activeContexts.delete(s), t.push(s));
990
+ }
991
+ return t;
992
+ }
993
+ /**
994
+ * Create a binding from a binding definition
995
+ *
996
+ * @param definition - The binding definition to create a binding from
997
+ * @returns A new binding instance or null if the type is not supported
998
+ */
999
+ _createBindingFromDefinition(e) {
1000
+ const t = this._actions.get(e.action);
1001
+ if (!t)
1002
+ return this._log.warn(`Cannot create binding: Action "${e.action}" not found.`), null;
1003
+ const { deviceID: s, ...i } = e.input;
1004
+ switch (e.type) {
1005
+ case "trigger":
1006
+ return new Y(t, s, this._createInputSourceFromDefinition(i), {
1007
+ ...e.options || {},
1008
+ context: e.context
1009
+ });
1010
+ case "toggle":
1011
+ return new H(t, s, this._createInputSourceFromDefinition(i), {
1012
+ ...e.options || {},
1013
+ context: e.context
1014
+ });
1015
+ case "value":
1016
+ return new X(t, s, this._createInputSourceFromDefinition(i), {
1017
+ ...e.options || {},
1018
+ context: e.context
1019
+ });
1020
+ default:
1021
+ return this._log.error(`Binding type not implemented: ${e.type}`), null;
1022
+ }
1023
+ }
1024
+ /**
1025
+ * Create a device value reader from a definition object
1026
+ *
1027
+ * @param definition - The device value reader definition
1028
+ * @returns A new device value reader instance
1029
+ * @throws Error if the reader type is not supported
1030
+ */
1031
+ _createInputSourceFromDefinition(e) {
1032
+ switch (e.type) {
1033
+ case "keyboard":
1034
+ return new w(e.sourceKey, e.options);
1035
+ case "mouse": {
1036
+ const t = e.sourceType;
1037
+ if (!(t === "button" || t === "position" || t === "wheel"))
1038
+ throw new Error(`Invalid mouse source type: ${t}`);
1039
+ return new x(t, e.sourceKey);
1040
+ }
1041
+ case "gamepad": {
1042
+ const t = e.sourceType;
1043
+ if (!(t === "button" || t === "axis"))
1044
+ throw new Error(`Invalid gamepad source type: ${t}`);
1045
+ return new S(t, e.sourceKey, e.options);
1046
+ }
1047
+ default:
1048
+ throw new Error(`Unsupported input source type: ${e.type}`);
1049
+ }
1050
+ }
1051
+ //------------------------------------------------------------------------------------------------------------------
1052
+ // Internal API
1053
+ //------------------------------------------------------------------------------------------------------------------
1054
+ /**
1055
+ * Handle input from a device and process all relevant bindings
1056
+ *
1057
+ * @param device - The input device
1058
+ * @param state - Current input state
1059
+ */
1060
+ $handleInput(e, t) {
1061
+ const s = this._bindings.get(e.id);
1062
+ if (!(!s || s.length === 0) && !(this._activeContexts.size === 0 && s.some((i) => i.context)))
1063
+ for (const i of s)
1064
+ this._isBindingContextActive(i) && i.process(t, this._eventBus);
1065
+ }
1066
+ //------------------------------------------------------------------------------------------------------------------
1067
+ // Action Management API
1068
+ //------------------------------------------------------------------------------------------------------------------
1069
+ /**
1070
+ * Registers an action
1071
+ *
1072
+ * @param action - The action to register
1073
+ * @throws Error if action is already registered
1074
+ */
1075
+ registerAction(e) {
1076
+ if (this._log.debug(`Registering action "${e.name}"`), this._actions.has(e.name)) {
1077
+ const t = `Action "${e.name}" already registered.`;
1078
+ throw this._log.error(t), new Error(t);
1079
+ }
1080
+ this._actions.set(e.name, e), this._log.debug(`Action "${e.name}" registered successfully`);
1081
+ }
1082
+ /**
1083
+ * Gets an action by name
1084
+ *
1085
+ * @param actionName - The name of the action to get
1086
+ * @returns The action or null if not found
1087
+ */
1088
+ getAction(e) {
1089
+ this._log.trace(`Getting action "${e}"`);
1090
+ const t = this._actions.get(e) ?? null;
1091
+ return t || this._log.debug(`Action "${e}" not found`), t;
1092
+ }
1093
+ //------------------------------------------------------------------------------------------------------------------
1094
+ // Context Management API
1095
+ //------------------------------------------------------------------------------------------------------------------
1096
+ /**
1097
+ * Registers a context with specific options
1098
+ *
1099
+ * @param contextName - The name of the context to register
1100
+ * @param exclusive - Whether the context is exclusive (default: true)
1101
+ * @returns The registered context
1102
+ */
1103
+ registerContext(e, t = !0) {
1104
+ const s = this._getOrCreateContext(e, t);
1105
+ return s.exclusive !== t && (s.exclusive = t, this._log.info(`Updated context "${e}" exclusivity: ${t}`)), s;
1106
+ }
1107
+ /**
1108
+ * Activates a context, enabling all bindings associated with it.
1109
+ * If the context is exclusive, all other exclusive contexts will be deactivated.
1110
+ *
1111
+ * @param contextName - The name of the context to activate
1112
+ */
1113
+ activateContext(e) {
1114
+ const s = this._getOrCreateContext(e).exclusive;
1115
+ this._log.debug(`Activating context "${e}" (exclusive: ${s})`);
1116
+ const i = this._activeContexts.has(e);
1117
+ if (s) {
1118
+ const o = this._deactivateExclusiveContexts(e);
1119
+ o.length > 0 && this._log.info(`Deactivated exclusive contexts: ${o.join(", ")}`);
1120
+ }
1121
+ i || (this._activeContexts.add(e), this._log.info(`Context "${e}" activated${s ? " as exclusive" : ""}`));
1122
+ }
1123
+ /**
1124
+ * Deactivates a context, disabling all bindings associated with it
1125
+ *
1126
+ * @param contextName - The name of the context to deactivate
1127
+ */
1128
+ deactivateContext(e) {
1129
+ this._log.debug(`Deactivating context "${e}"`), this._activeContexts.has(e) ? (this._activeContexts.delete(e), this._log.info(`Context "${e}" deactivated`)) : this._log.debug(`Context "${e}" was not active`);
1130
+ }
1131
+ /**
1132
+ * Returns a list of all active contexts
1133
+ *
1134
+ * @returns Array of active context names
1135
+ */
1136
+ getActiveContexts() {
1137
+ return [...this._activeContexts];
1138
+ }
1139
+ /**
1140
+ * Returns whether a context is active
1141
+ *
1142
+ * @param contextName - The context to check
1143
+ * @returns True if the context is active
1144
+ */
1145
+ isContextActive(e) {
1146
+ return this._activeContexts.has(e);
1147
+ }
1148
+ /**
1149
+ * Gets a context by name
1150
+ *
1151
+ * @param contextName - The context name to get
1152
+ * @returns The context or null if not found
1153
+ */
1154
+ getContext(e) {
1155
+ return this._contexts.get(e) || null;
1156
+ }
1157
+ //------------------------------------------------------------------------------------------------------------------
1158
+ // Binding Management API
1159
+ //------------------------------------------------------------------------------------------------------------------
1160
+ /**
1161
+ * Register a binding for an input device
1162
+ *
1163
+ * @param binding - The binding to register
1164
+ * @throws Error if binding type is invalid
1165
+ */
1166
+ $registerBinding(e) {
1167
+ var t;
1168
+ if (!J.includes(e.type))
1169
+ throw new Error(`Invalid binding type: ${e.type}`);
1170
+ e.context && !this._contexts.has(e.context) && this.registerContext(e.context), this._bindings.has(e.deviceID) || this._bindings.set(e.deviceID, []), (t = this._bindings.get(e.deviceID)) == null || t.push(e), this._log.debug(`Registered ${e.type} binding for "${e.action.name}" in context "${e.context || null}"`);
1171
+ }
1172
+ /**
1173
+ * Register a binding using a binding definition object
1174
+ *
1175
+ * @param definition - The binding definition to register
1176
+ */
1177
+ registerBinding(e) {
1178
+ const t = this._createBindingFromDefinition(e);
1179
+ t ? this.$registerBinding(t) : this._log.error(`Failed to create binding for action "${e.action}" with type "${e.type}"`);
1180
+ }
1181
+ /**
1182
+ * Unregister all bindings for an action within a context
1183
+ *
1184
+ * @param actionName - The name of the action
1185
+ * @param context - The context to unregister from (defaults to null)
1186
+ */
1187
+ unregisterBindings(e, t = null) {
1188
+ this._log.debug(`Unregistering all bindings for action "${e}" in context "${t}"`);
1189
+ let s = 0;
1190
+ for (const [i, o] of this._bindings.entries()) {
1191
+ const r = o.filter((c) => {
1192
+ const h = c.context || null, u = c.action.name !== e || h !== t;
1193
+ return u || s++, u;
1194
+ });
1195
+ r.length !== o.length && this._bindings.set(i, r);
1196
+ }
1197
+ this._log.info(`Removed ${s} bindings for action "${e}" in context "${t}"`);
1198
+ }
1199
+ /**
1200
+ * Gets all bindings for a specific action
1201
+ *
1202
+ * @param actionName - The name of the action
1203
+ * @param context - The context to get bindings from (optional)
1204
+ * @returns Array of bindings that match the criteria
1205
+ */
1206
+ getBindingsForAction(e, t) {
1207
+ const s = [];
1208
+ for (const i of this._bindings.values())
1209
+ for (const o of i) {
1210
+ const r = o.context || null;
1211
+ o.action.name === e && (!t || r === t) && s.push(o);
1212
+ }
1213
+ return s;
1214
+ }
1215
+ //------------------------------------------------------------------------------------------------------------------
1216
+ // Configuration Management API
1217
+ //------------------------------------------------------------------------------------------------------------------
1218
+ /**
1219
+ * Exports the current configuration to a serializable object
1220
+ *
1221
+ * @returns A BindingConfiguration object representing the current state
1222
+ */
1223
+ exportConfiguration() {
1224
+ this._log.debug("Exporting binding configuration");
1225
+ const e = [...this._actions.values()].map((i) => i.type === "analog" ? {
1226
+ name: i.name,
1227
+ type: i.type,
1228
+ minValue: i.minValue ?? 0,
1229
+ maxValue: i.maxValue ?? 1
1230
+ } : i), t = [];
1231
+ for (const i of this._bindings.values())
1232
+ for (const o of i)
1233
+ t.push(o.toJSON());
1234
+ const s = [...this._contexts.values()].map((i) => ({
1235
+ name: i.name,
1236
+ exclusive: i.exclusive,
1237
+ active: this._activeContexts.has(i.name)
1238
+ }));
1239
+ return this._log.info(`Configuration exported: ${e.length} actions, ${t.length} bindings, ${s.length} contexts`), {
1240
+ actions: e,
1241
+ bindings: t,
1242
+ contexts: s
1243
+ };
1244
+ }
1245
+ /**
1246
+ * Imports a configuration from an object
1247
+ *
1248
+ * @param config - The configuration to import
1249
+ */
1250
+ importConfiguration(e) {
1251
+ this._log.debug("Importing binding configuration"), this._bindings.clear(), this._actions.clear(), this._contexts.clear(), this._activeContexts.clear();
1252
+ for (const t of e.actions)
1253
+ this.registerAction(t);
1254
+ for (const t of e.contexts)
1255
+ this.registerContext(t.name, t.exclusive), t.active && this.activateContext(t.name);
1256
+ for (const t of e.bindings)
1257
+ try {
1258
+ this.registerBinding(t);
1259
+ } catch (s) {
1260
+ s instanceof Error && this._log.error(`Failed to import binding for action "${t.action}": ${s.message}`);
1261
+ }
1262
+ this._log.info(`Configuration imported: ${e.actions.length} actions, ${e.bindings.length} bindings, ${e.contexts.length} contexts`);
1263
+ }
1264
+ }
1265
+ function A() {
1266
+ return typeof window < "u" && typeof window.document < "u";
1267
+ }
1268
+ function $() {
1269
+ return A() && !!window.navigator.gpu;
1270
+ }
1271
+ class Z {
1272
+ //------------------------------------------------------------------------------------------------------------------
1273
+ constructor(e, t, s, i, o) {
1274
+ n(this, "_engine");
1275
+ n(this, "_entityManager");
1276
+ n(this, "_inputManager");
1277
+ n(this, "_sceneEngine");
1278
+ n(this, "_currentScene", null);
1279
+ n(this, "_log");
1280
+ n(this, "started", !1);
1281
+ this._engine = e, this._sceneEngine = t, this._entityManager = s, this._inputManager = i, this._log = (o == null ? void 0 : o.getLogger("GameManager")) || new l("GameManager"), A() && window.addEventListener("resize", this._resizeHandler.bind(this));
1282
+ }
1283
+ _renderLoop() {
1284
+ const e = this._engine.getDeltaTime();
1285
+ this._entityManager.$frameUpdate(e), this._inputManager && this._inputManager.pollGamepads(), this._currentScene && this._currentScene.render();
1286
+ }
1287
+ _resizeHandler() {
1288
+ this.started && this._engine.resize();
1289
+ }
1290
+ //------------------------------------------------------------------------------------------------------------------
1291
+ // Public API
1292
+ //------------------------------------------------------------------------------------------------------------------
1293
+ async start(e) {
1294
+ this._currentScene = await this._sceneEngine.loadScene(e), this._engine.runRenderLoop(this._renderLoop.bind(this)), this.started = !0, this._log.info("SkewedAspect Game Engine started successfully");
1295
+ }
1296
+ async stop() {
1297
+ this.started = !1, this._engine.stopRenderLoop(), this._log.info("SkewedAspect Game Engine stopped successfully");
1298
+ }
1299
+ }
1300
+ class ue {
1301
+ constructor() {
1302
+ n(this, "entity", null);
1303
+ }
1304
+ //------------------------------------------------------------------------------------------------------------------
1305
+ // Internal API
1306
+ //------------------------------------------------------------------------------------------------------------------
1307
+ $emit(e) {
1308
+ if (!this.entity)
1309
+ throw new Error("Entity is not set for this behavior.");
1310
+ e.senderID = this.entity.id, this.entity.eventBus.publish(e);
1311
+ }
1312
+ /**
1313
+ * Sets the entity for this behavior.
1314
+ * @param entity - The entity to set.
1315
+ */
1316
+ $setEntity(e) {
1317
+ this.entity = e;
1318
+ }
1319
+ }
1320
+ class q {
1321
+ /**
1322
+ * Creates an instance of GameEntityBase.
1323
+ * @param type - The type of the entity.
1324
+ * @param initialState - The initial state of the entity.
1325
+ * @param behaviors - An array of behaviors to attach to the entity.
1326
+ * @param eventBus
1327
+ */
1328
+ constructor(e, t, s, i) {
1329
+ /** The unique identifier of the entity. */
1330
+ n(this, "id");
1331
+ /** The type of the entity. */
1332
+ n(this, "type");
1333
+ /** The state of the entity. */
1334
+ n(this, "state");
1335
+ /** A map of behaviors attached to the entity. */
1336
+ n(this, "behaviors", /* @__PURE__ */ new Map());
1337
+ /** The event bus for the entity. */
1338
+ n(this, "eventBus");
1339
+ /** The event subscriptions for the entity. */
1340
+ n(this, "subscriptions", /* @__PURE__ */ new Map());
1341
+ this.id = crypto.randomUUID(), this.type = e, this.state = s, this.eventBus = t;
1342
+ for (const o of i)
1343
+ this.attachBehavior(new o());
1344
+ }
1345
+ //------------------------------------------------------------------------------------------------------------------
1346
+ // Internal Methods
1347
+ //------------------------------------------------------------------------------------------------------------------
1348
+ /**
1349
+ * Processes a game event by passing it to the entity's behaviors.
1350
+ * @param event - The game event to process.
1351
+ * @returns A promise that resolves when the event has been processed.
1352
+ */
1353
+ async $processEvent(e) {
1354
+ for (const t of this.behaviors.values())
1355
+ if (await t.processEvent(e, this.state))
1356
+ break;
1357
+ }
1358
+ /**
1359
+ * Updates the entity by calling the update method of its behaviors.
1360
+ * @param dt - The delta time since the last update.
1361
+ */
1362
+ $update(e) {
1363
+ var t;
1364
+ for (const s of this.behaviors.values())
1365
+ (t = s.update) == null || t.call(s, e, this.state);
1366
+ }
1367
+ /**
1368
+ * Destroys the entity by calling the destroy method of its behaviors.
1369
+ * @returns A promise that resolves when the entity has been destroyed.
1370
+ */
1371
+ async $destroy() {
1372
+ var e;
1373
+ for (const t of this.behaviors.values())
1374
+ (e = t.destroy) == null || e.call(t);
1375
+ for (const t of this.subscriptions.values())
1376
+ t.unsubscribe();
1377
+ this.behaviors.clear(), this.subscriptions.clear();
1378
+ }
1379
+ //------------------------------------------------------------------------------------------------------------------
1380
+ // Public Methods
1381
+ //------------------------------------------------------------------------------------------------------------------
1382
+ /**
1383
+ * Attaches a behavior to the entity.
1384
+ * @param behavior - The behavior to attach.
1385
+ * @throws Will throw an error if the behavior is already attached.
1386
+ */
1387
+ attachBehavior(e) {
1388
+ if (this.behaviors.has(e.name))
1389
+ throw new Error(`Behavior ${e.name} is already attached to this entity.`);
1390
+ for (const t of e.eventSubscriptions) {
1391
+ const s = this.subscriptions.get(t);
1392
+ if (s)
1393
+ s.count++;
1394
+ else {
1395
+ const i = this.eventBus.subscribe(t, this.$processEvent.bind(this));
1396
+ this.subscriptions.set(t, { count: 1, unsubscribe: i });
1397
+ }
1398
+ }
1399
+ this.behaviors.set(e.name, e), e.$setEntity(this);
1400
+ }
1401
+ /**
1402
+ * Detaches a behavior from the entity.
1403
+ * @param behaviorName - The behavior to detach.
1404
+ */
1405
+ detachBehavior(e) {
1406
+ const t = this.behaviors.get(e);
1407
+ if (t) {
1408
+ for (const s of t.eventSubscriptions) {
1409
+ const i = this.subscriptions.get(s);
1410
+ i && (i.count--, i.count <= 0 && (i.unsubscribe(), this.subscriptions.delete(s)));
1411
+ }
1412
+ this.behaviors.delete(t.name), t.$setEntity(null);
1413
+ }
1414
+ }
1415
+ }
1416
+ class Q {
1417
+ /**
1418
+ * Creates an instance of EntityManager.
1419
+ * @param eventBus - The event bus for the entity manager.
1420
+ * @param logger - The logging utility to use
1421
+ * @param bindingManager - The binding manager for registering actions
1422
+ */
1423
+ constructor(e, t, s) {
1424
+ /** The event bus for the entity manager. */
1425
+ n(this, "eventBus");
1426
+ /** A map of entities managed by the entity manager. */
1427
+ n(this, "entities", /* @__PURE__ */ new Map());
1428
+ /** A map of entity definitions registered with the entity manager. */
1429
+ n(this, "entityDefinitions", /* @__PURE__ */ new Map());
1430
+ /** Reference to the binding manager for registering actions */
1431
+ n(this, "bindingManager");
1432
+ /** Logger instance */
1433
+ n(this, "_log");
1434
+ this.eventBus = e, this.bindingManager = s, this._log = (t == null ? void 0 : t.getLogger("EntityManager")) || new l("EntityManager"), this._log.info("EntityManager initialized");
1435
+ }
1436
+ //------------------------------------------------------------------------------------------------------------------
1437
+ // Private Methods
1438
+ //------------------------------------------------------------------------------------------------------------------
1439
+ /**
1440
+ * Checks if two actions are compatible
1441
+ * @param existingAction - The action already registered
1442
+ * @param newAction - The action being registered
1443
+ * @returns true if the actions are compatible, false if they have conflicting options
1444
+ */
1445
+ areActionsCompatible(e, t) {
1446
+ return !(e.type !== t.type || e.type === "analog" && t.type === "analog" && (t.minValue !== void 0 && e.minValue !== t.minValue || t.maxValue !== void 0 && e.maxValue !== t.maxValue));
1447
+ }
1448
+ /**
1449
+ * Registers actions defined in the entity definition with the binding manager
1450
+ * @param entityDef - The entity definition containing actions to register
1451
+ */
1452
+ registerEntityActions(e) {
1453
+ if (e.actions)
1454
+ for (const t of e.actions)
1455
+ try {
1456
+ const s = this.bindingManager.getAction(t.name);
1457
+ s ? this.areActionsCompatible(s, t) ? this._log.trace(`Action "${t.name}" already registered with compatible options, skipping registration`) : this._log.warn(`Action "${t.name}" already registered with different options. Entity "${e.type}" requires: ${JSON.stringify(t)}, but found: ${JSON.stringify(s)}`) : (this._log.debug(`Registering action "${t.name}" from entity type "${e.type}"`), this.bindingManager.registerAction(t));
1458
+ } catch (s) {
1459
+ this._log.debug(`Failed to register action "${t.name}": ${s instanceof Error ? s.message : String(s)}`);
1460
+ }
1461
+ }
1462
+ $frameUpdate(e) {
1463
+ this._log.trace(`Updating ${this.entities.size} entities with dt=${e}`);
1464
+ for (const t of this.entities.values())
1465
+ t.$update(e);
1466
+ }
1467
+ //------------------------------------------------------------------------------------------------------------------
1468
+ // Public API
1469
+ //------------------------------------------------------------------------------------------------------------------
1470
+ /**
1471
+ * Registers a new entity definition.
1472
+ * @param entityDef - The definition of the entity.
1473
+ */
1474
+ registerEntityDefinition(e) {
1475
+ this._log.debug(`Registering entity definition: ${e.type}`), this.registerEntityActions(e), this.entityDefinitions.set(e.type, e);
1476
+ }
1477
+ /**
1478
+ * Creates a new entity of the given type.
1479
+ * @param type - The type of the entity to create.
1480
+ * @param initialState - The initial state of the entity.
1481
+ * @returns The created entity.
1482
+ */
1483
+ createEntity(e, t = {}) {
1484
+ var r;
1485
+ this._log.debug(`Creating entity of type: ${e}`);
1486
+ const s = this.entityDefinitions.get(e);
1487
+ if (!s) {
1488
+ const c = `Entity type ${e} is not registered.`;
1489
+ throw this._log.error(c), new Error(c);
1490
+ }
1491
+ this._log.trace(`Using entity definition with ${((r = s.behaviors) == null ? void 0 : r.length) || 0} behaviors`);
1492
+ const i = { ...s.defaultState, ...t }, o = new q(s.type, this.eventBus, i, s.behaviors);
1493
+ return this.entities.set(o.id, o), this._log.debug(`Entity created with ID: ${o.id}`), o;
1494
+ }
1495
+ /**
1496
+ * Destroys the entity with the given ID.
1497
+ * @param entityID - The ID of the entity to destroy.
1498
+ */
1499
+ destroyEntity(e) {
1500
+ this._log.debug(`Destroying entity: ${e}`);
1501
+ const t = this.entities.get(e);
1502
+ t ? (t.$destroy(), this.entities.delete(e), this._log.debug(`Entity ${e} destroyed`)) : this._log.warn(`Attempted to destroy non-existent entity: ${e}`);
1503
+ }
1504
+ /**
1505
+ * Gets the entity with the given ID.
1506
+ * @param entityID - The ID of the entity to get.
1507
+ * @returns The entity with the given ID, or null if it does not exist.
1508
+ */
1509
+ getEntity(e) {
1510
+ return this._log.trace(`Getting entity: ${e}`), this.entities.get(e) ?? null;
1511
+ }
1512
+ /**
1513
+ * Adds an existing entity to the entity manager.
1514
+ * @param entity - The entity to add.
1515
+ */
1516
+ addEntity(e) {
1517
+ this._log.debug(`Adding existing entity: ${e.id} (type: ${e.type})`), this.entities.set(e.id, e);
1518
+ }
1519
+ /**
1520
+ * Removes the entity with the given ID, without destroying it.
1521
+ * @param entityID - The ID of the entity to remove.
1522
+ */
1523
+ removeEntity(e) {
1524
+ this._log.debug(`Removing entity: ${e}`), this.entities.get(e) ? (this.entities.delete(e), this._log.debug(`Entity ${e} removed`)) : this._log.warn(`Attempted to remove non-existent entity: ${e}`);
1525
+ }
1526
+ }
1527
+ class ee {
1528
+ //------------------------------------------------------------------------------------------------------------------
1529
+ /**
1530
+ * Create a new KeyboardResourceAccess
1531
+ */
1532
+ constructor() {
1533
+ n(this, "_keyboardDevice");
1534
+ n(this, "_keysState", {});
1535
+ // Callbacks
1536
+ n(this, "_onDeviceConnected");
1537
+ n(this, "_onInputChanged");
1538
+ this._keyboardDevice = {
1539
+ id: "keyboard-0",
1540
+ name: "Keyboard",
1541
+ type: "keyboard",
1542
+ connected: !0
1543
+ }, this._setupKeyboardEvents(), setTimeout(() => this._notifyDeviceConnected(), 0);
1544
+ }
1545
+ //------------------------------------------------------------------------------------------------------------------
1546
+ // Public API
1547
+ //------------------------------------------------------------------------------------------------------------------
1548
+ /**
1549
+ * Register a callback for device connected events
1550
+ *
1551
+ * @param callback - The callback to register
1552
+ */
1553
+ onDeviceConnected(e) {
1554
+ this._onDeviceConnected = e;
1555
+ }
1556
+ /**
1557
+ * Register a callback for input changed events
1558
+ *
1559
+ * @param callback - The callback to register
1560
+ */
1561
+ onInputChanged(e) {
1562
+ this._onInputChanged = e;
1563
+ }
1564
+ /**
1565
+ * Get the current keyboard state
1566
+ */
1567
+ getState() {
1568
+ return {
1569
+ type: "keyboard",
1570
+ keys: { ...this._keysState },
1571
+ delta: {}
1572
+ };
1573
+ }
1574
+ /**
1575
+ * Get the keyboard device
1576
+ */
1577
+ getDevice() {
1578
+ return { ...this._keyboardDevice };
1579
+ }
1580
+ /**
1581
+ * Destroy the keyboard resource access and clean up event listeners
1582
+ */
1583
+ destroy() {
1584
+ window.removeEventListener("keydown", this._handleKeyDown), window.removeEventListener("keyup", this._handleKeyUp);
1585
+ }
1586
+ //------------------------------------------------------------------------------------------------------------------
1587
+ // Private Methods
1588
+ //------------------------------------------------------------------------------------------------------------------
1589
+ /**
1590
+ * Set up keyboard event listeners
1591
+ */
1592
+ _setupKeyboardEvents() {
1593
+ this._handleKeyDown = this._handleKeyDown.bind(this), this._handleKeyUp = this._handleKeyUp.bind(this), window.addEventListener("keydown", this._handleKeyDown), window.addEventListener("keyup", this._handleKeyUp);
1594
+ }
1595
+ /**
1596
+ * Handle keyboard key down events
1597
+ */
1598
+ _handleKeyDown(e) {
1599
+ this._keysState[e.code] = !0;
1600
+ const t = {};
1601
+ t[e.code] = !0;
1602
+ const s = {
1603
+ type: "keyboard",
1604
+ keys: { ...this._keysState },
1605
+ delta: t,
1606
+ event: e
1607
+ };
1608
+ this._notifyInputChanged(s);
1609
+ }
1610
+ /**
1611
+ * Handle keyboard key up events
1612
+ */
1613
+ _handleKeyUp(e) {
1614
+ this._keysState[e.code] = !1;
1615
+ const t = {};
1616
+ t[e.code] = !1;
1617
+ const s = {
1618
+ type: "keyboard",
1619
+ keys: { ...this._keysState },
1620
+ delta: t,
1621
+ event: e
1622
+ };
1623
+ this._notifyInputChanged(s);
1624
+ }
1625
+ /**
1626
+ * Notify subscribers of device connected event
1627
+ */
1628
+ _notifyDeviceConnected() {
1629
+ this._onDeviceConnected && this._onDeviceConnected(this._keyboardDevice);
1630
+ }
1631
+ /**
1632
+ * Notify subscribers of input changed event
1633
+ */
1634
+ _notifyInputChanged(e) {
1635
+ this._onInputChanged && this._onInputChanged(this._keyboardDevice, e);
1636
+ }
1637
+ }
1638
+ class te {
1639
+ //------------------------------------------------------------------------------------------------------------------
1640
+ /**
1641
+ * Create a new MouseResourceAccess
1642
+ *
1643
+ * @param targetElement - The DOM element to attach mouse listeners to (defaults to document.body)
1644
+ */
1645
+ constructor(e = document.body) {
1646
+ n(this, "_targetElement");
1647
+ n(this, "_mouseDevice");
1648
+ n(this, "_buttonState", {});
1649
+ n(this, "_axesState", {});
1650
+ n(this, "_position", {
1651
+ absolute: { x: 0, y: 0 },
1652
+ relative: { x: 0, y: 0 }
1653
+ });
1654
+ n(this, "_wheelState", {
1655
+ deltaX: 0,
1656
+ deltaY: 0,
1657
+ deltaZ: 0,
1658
+ deltaMode: 0
1659
+ });
1660
+ // Callbacks
1661
+ n(this, "_onDeviceConnected");
1662
+ n(this, "_onInputChanged");
1663
+ this._targetElement = e, this._mouseDevice = {
1664
+ id: "mouse-0",
1665
+ name: "Mouse",
1666
+ type: "mouse",
1667
+ connected: !0
1668
+ }, this._setupMouseEvents(), this._axesState["axis-x"] = 0, this._axesState["axis-y"] = 0, setTimeout(() => this._notifyDeviceConnected(), 0);
1669
+ }
1670
+ //------------------------------------------------------------------------------------------------------------------
1671
+ // Public API
1672
+ //------------------------------------------------------------------------------------------------------------------
1673
+ /**
1674
+ * Register a callback for device connected events
1675
+ *
1676
+ * @param callback - The callback to register
1677
+ */
1678
+ onDeviceConnected(e) {
1679
+ this._onDeviceConnected = e;
1680
+ }
1681
+ /**
1682
+ * Register a callback for input changed events
1683
+ *
1684
+ * @param callback - The callback to register
1685
+ */
1686
+ onInputChanged(e) {
1687
+ this._onInputChanged = e;
1688
+ }
1689
+ /**
1690
+ * Get the current mouse state
1691
+ */
1692
+ getState() {
1693
+ return {
1694
+ type: "mouse",
1695
+ buttons: { ...this._buttonState },
1696
+ axes: { ...this._axesState },
1697
+ position: {
1698
+ absolute: { ...this._position.absolute },
1699
+ relative: { ...this._position.relative }
1700
+ },
1701
+ wheel: { ...this._wheelState }
1702
+ };
1703
+ }
1704
+ /**
1705
+ * Get the mouse device
1706
+ */
1707
+ getDevice() {
1708
+ return { ...this._mouseDevice };
1709
+ }
1710
+ /**
1711
+ * Destroy the mouse resource access and clean up event listeners
1712
+ */
1713
+ destroy() {
1714
+ this._targetElement.removeEventListener("mousedown", this._handleMouseDown), this._targetElement.removeEventListener("mouseup", this._handleMouseUp), this._targetElement.removeEventListener("mousemove", this._handleMouseMove), this._targetElement.removeEventListener("wheel", this._handleMouseWheel);
1715
+ }
1716
+ //------------------------------------------------------------------------------------------------------------------
1717
+ // Private Methods
1718
+ //------------------------------------------------------------------------------------------------------------------
1719
+ /**
1720
+ * Set up mouse event listeners
1721
+ */
1722
+ _setupMouseEvents() {
1723
+ this._handleMouseDown = this._handleMouseDown.bind(this), this._handleMouseUp = this._handleMouseUp.bind(this), this._handleMouseMove = this._handleMouseMove.bind(this), this._handleMouseWheel = this._handleMouseWheel.bind(this), this._targetElement.addEventListener("mousedown", this._handleMouseDown), this._targetElement.addEventListener("mouseup", this._handleMouseUp), this._targetElement.addEventListener("mousemove", this._handleMouseMove), this._targetElement.addEventListener("wheel", this._handleMouseWheel);
1724
+ }
1725
+ /**
1726
+ * Handle mouse button down events
1727
+ */
1728
+ _handleMouseDown(e) {
1729
+ const t = `button-${e.button}`, s = {
1730
+ pressed: !0
1731
+ };
1732
+ this._buttonState[t] = s, this._notifyInputChanged({
1733
+ type: "mouse",
1734
+ event: e,
1735
+ buttons: { ...this._buttonState },
1736
+ axes: { ...this._axesState },
1737
+ position: { ...this._position }
1738
+ });
1739
+ }
1740
+ /**
1741
+ * Handle mouse button up events
1742
+ */
1743
+ _handleMouseUp(e) {
1744
+ const t = `button-${e.button}`, s = {
1745
+ pressed: !1
1746
+ };
1747
+ this._buttonState[t] = s, this._notifyInputChanged({
1748
+ type: "mouse",
1749
+ event: e,
1750
+ buttons: { ...this._buttonState },
1751
+ axes: { ...this._axesState },
1752
+ position: { ...this._position }
1753
+ });
1754
+ }
1755
+ /**
1756
+ * Handle mouse move events
1757
+ */
1758
+ _handleMouseMove(e) {
1759
+ this._position = {
1760
+ absolute: {
1761
+ x: e.clientX,
1762
+ y: e.clientY
1763
+ },
1764
+ relative: {
1765
+ x: e.movementX,
1766
+ y: e.movementY
1767
+ }
1768
+ }, this._axesState["axis-x"] = e.clientX, this._axesState["axis-y"] = e.clientY, this._notifyInputChanged({
1769
+ type: "mouse",
1770
+ event: e,
1771
+ buttons: { ...this._buttonState },
1772
+ axes: { ...this._axesState },
1773
+ position: { ...this._position }
1774
+ });
1775
+ }
1776
+ /**
1777
+ * Handle mouse wheel events
1778
+ */
1779
+ _handleMouseWheel(e) {
1780
+ this._wheelState = {
1781
+ deltaX: e.deltaX,
1782
+ deltaY: e.deltaY,
1783
+ deltaZ: e.deltaZ,
1784
+ deltaMode: e.deltaMode
1785
+ }, this._notifyInputChanged({
1786
+ type: "mouse",
1787
+ event: e,
1788
+ buttons: { ...this._buttonState },
1789
+ axes: { ...this._axesState },
1790
+ position: { ...this._position },
1791
+ wheel: { ...this._wheelState }
1792
+ });
1793
+ }
1794
+ /**
1795
+ * Notify subscribers of device connected event
1796
+ */
1797
+ _notifyDeviceConnected() {
1798
+ this._onDeviceConnected && this._onDeviceConnected(this._mouseDevice);
1799
+ }
1800
+ /**
1801
+ * Notify subscribers of input changed event
1802
+ */
1803
+ _notifyInputChanged(e) {
1804
+ this._onInputChanged && this._onInputChanged(this._mouseDevice, e);
1805
+ }
1806
+ }
1807
+ class se {
1808
+ //------------------------------------------------------------------------------------------------------------------
1809
+ /**
1810
+ * Create a new GamepadResourceAccess
1811
+ */
1812
+ constructor() {
1813
+ n(this, "_gamepadDevices", {});
1814
+ n(this, "_buttonStates", {});
1815
+ n(this, "_axesStates", {});
1816
+ // Callbacks
1817
+ n(this, "_onDeviceConnected");
1818
+ n(this, "_onDeviceDisconnected");
1819
+ n(this, "_onInputChanged");
1820
+ this._setupGamepadEvents();
1821
+ }
1822
+ //------------------------------------------------------------------------------------------------------------------
1823
+ // Public API
1824
+ //------------------------------------------------------------------------------------------------------------------
1825
+ /**
1826
+ * Register a callback for device connected events
1827
+ *
1828
+ * @param callback - The callback to register
1829
+ */
1830
+ onDeviceConnected(e) {
1831
+ this._onDeviceConnected = e;
1832
+ }
1833
+ /**
1834
+ * Register a callback for device disconnected events
1835
+ *
1836
+ * @param callback - The callback to register
1837
+ */
1838
+ onDeviceDisconnected(e) {
1839
+ this._onDeviceDisconnected = e;
1840
+ }
1841
+ /**
1842
+ * Register a callback for input changed events
1843
+ *
1844
+ * @param callback - The callback to register
1845
+ */
1846
+ onInputChanged(e) {
1847
+ this._onInputChanged = e;
1848
+ }
1849
+ /**
1850
+ * Get all connected gamepad devices
1851
+ */
1852
+ getDevices() {
1853
+ return Object.values(this._gamepadDevices).map((e) => ({ ...e }));
1854
+ }
1855
+ /**
1856
+ * Get all connected gamepad states
1857
+ */
1858
+ getStates() {
1859
+ const e = {};
1860
+ return Object.keys(this._buttonStates).forEach((t) => {
1861
+ const s = Number(t), i = this._buttonStates[s] || {}, o = this._axesStates[s] || {};
1862
+ e[s] = {
1863
+ type: "gamepad",
1864
+ buttons: { ...i },
1865
+ axes: { ...o }
1866
+ };
1867
+ }), e;
1868
+ }
1869
+ /**
1870
+ * Get a specific gamepad device by index
1871
+ *
1872
+ * @param index - The index of the gamepad to get
1873
+ */
1874
+ getDevice(e) {
1875
+ const t = this._gamepadDevices[e];
1876
+ return t ? { ...t } : null;
1877
+ }
1878
+ /**
1879
+ * Get a specific gamepad state by index
1880
+ *
1881
+ * @param index - The index of the gamepad state to get
1882
+ */
1883
+ getState(e) {
1884
+ const t = this._buttonStates[e], s = this._axesStates[e];
1885
+ return !t && !s ? null : {
1886
+ type: "gamepad",
1887
+ buttons: t ? { ...t } : {},
1888
+ axes: s ? { ...s } : {}
1889
+ };
1890
+ }
1891
+ /**
1892
+ * Poll for gamepad state updates - call this in your game loop
1893
+ */
1894
+ pollGamepads() {
1895
+ if (!navigator.getGamepads)
1896
+ return;
1897
+ const e = navigator.getGamepads();
1898
+ for (const t of e) {
1899
+ if (!t)
1900
+ continue;
1901
+ const s = t.index;
1902
+ if (!this._gamepadDevices[s]) {
1903
+ this._handleGamepadConnected(t);
1904
+ continue;
1905
+ }
1906
+ const i = this._gamepadDevices[s];
1907
+ if (!i)
1908
+ continue;
1909
+ const o = this._buttonStates[s] || {}, r = this._axesStates[s] || {}, c = {};
1910
+ let h = !1;
1911
+ t.buttons.forEach((d, _) => {
1912
+ const p = `button-${_}`, f = {
1913
+ pressed: d.pressed,
1914
+ touched: d.touched,
1915
+ value: d.value
1916
+ };
1917
+ c[p] = f;
1918
+ const y = o[p];
1919
+ (!y || y.pressed !== f.pressed || y.touched !== f.touched || y.value !== f.value) && (h = !0);
1920
+ });
1921
+ const u = {};
1922
+ let g = !1;
1923
+ if (t.axes.forEach((d, _) => {
1924
+ const p = `axis-${_}`;
1925
+ u[p] = d, r[p] !== d && (g = !0);
1926
+ }), this._buttonStates[s] = c, this._axesStates[s] = u, h || g) {
1927
+ const d = {
1928
+ type: "gamepad",
1929
+ buttons: { ...c },
1930
+ axes: { ...u }
1931
+ };
1932
+ this._notifyInputChanged(i, d);
1933
+ }
1934
+ }
1935
+ }
1936
+ /**
1937
+ * Destroy the gamepad resource access and clean up event listeners
1938
+ */
1939
+ destroy() {
1940
+ window.removeEventListener("gamepadconnected", this._handleGamepadConnected), window.removeEventListener("gamepaddisconnected", this._handleGamepadDisconnected);
1941
+ }
1942
+ //------------------------------------------------------------------------------------------------------------------
1943
+ // Private Methods
1944
+ //------------------------------------------------------------------------------------------------------------------
1945
+ /**
1946
+ * Set up gamepad event listeners
1947
+ */
1948
+ _setupGamepadEvents() {
1949
+ if (this._handleGamepadConnected = this._handleGamepadConnected.bind(this), this._handleGamepadDisconnected = this._handleGamepadDisconnected.bind(this), window.addEventListener("gamepadconnected", this._handleGamepadConnected), window.addEventListener("gamepaddisconnected", this._handleGamepadDisconnected), navigator.getGamepads) {
1950
+ const e = navigator.getGamepads();
1951
+ for (const t of e)
1952
+ t && this._handleGamepadConnected(t);
1953
+ }
1954
+ }
1955
+ /**
1956
+ * Handle gamepad connected event
1957
+ */
1958
+ _handleGamepadConnected(e) {
1959
+ const t = e instanceof GamepadEvent ? e.gamepad : e, s = t.index, i = {
1960
+ id: `gamepad-${s}`,
1961
+ name: t.id,
1962
+ type: "gamepad",
1963
+ connected: !0,
1964
+ index: s,
1965
+ mapping: t.mapping,
1966
+ axes: Array.from(t.axes),
1967
+ buttons: Array.from(t.buttons)
1968
+ };
1969
+ this._gamepadDevices[s] = i;
1970
+ const o = {};
1971
+ Array.from(t.buttons).forEach((c, h) => {
1972
+ o[`button-${h}`] = {
1973
+ pressed: c.pressed,
1974
+ touched: c.touched,
1975
+ value: c.value
1976
+ };
1977
+ }), this._buttonStates[s] = o;
1978
+ const r = {};
1979
+ Array.from(t.axes).forEach((c, h) => {
1980
+ r[`axis-${h}`] = c;
1981
+ }), this._axesStates[s] = r, this._notifyDeviceConnected(i), this._notifyInputChanged(i, {
1982
+ type: "gamepad",
1983
+ buttons: { ...o },
1984
+ axes: { ...r },
1985
+ event: e instanceof GamepadEvent ? e : void 0
1986
+ });
1987
+ }
1988
+ /**
1989
+ * Handle gamepad disconnected event
1990
+ */
1991
+ _handleGamepadDisconnected(e) {
1992
+ const s = e.gamepad.index, i = this._gamepadDevices[s];
1993
+ i && (i.connected = !1, this._notifyDeviceDisconnected(i), this._gamepadDevices[s] = void 0, this._buttonStates[s] = void 0, this._axesStates[s] = void 0);
1994
+ }
1995
+ /**
1996
+ * Notify subscribers of device connected event
1997
+ */
1998
+ _notifyDeviceConnected(e) {
1999
+ this._onDeviceConnected && this._onDeviceConnected(e);
2000
+ }
2001
+ /**
2002
+ * Notify subscribers of device disconnected event
2003
+ */
2004
+ _notifyDeviceDisconnected(e) {
2005
+ this._onDeviceDisconnected && this._onDeviceDisconnected(e);
2006
+ }
2007
+ /**
2008
+ * Notify subscribers of input changed event
2009
+ */
2010
+ _notifyInputChanged(e, t) {
2011
+ this._onInputChanged && this._onInputChanged(e, t);
2012
+ }
2013
+ }
2014
+ class ie {
2015
+ //------------------------------------------------------------------------------------------------------------------
2016
+ /**
2017
+ * Create a new UserInputManager
2018
+ *
2019
+ * @param eventBus - The game event bus to publish events to
2020
+ * @param canvas - The DOM element to attach input listeners to
2021
+ * @param logger - The logging utility to use
2022
+ */
2023
+ constructor(e, t, s) {
2024
+ n(this, "_eventBus");
2025
+ n(this, "_keyboardRA");
2026
+ n(this, "_mouseRA");
2027
+ n(this, "_gamepadRA");
2028
+ /** Logger instance */
2029
+ n(this, "_log");
2030
+ this._eventBus = e, this._log = (s == null ? void 0 : s.getLogger("UserInputManager")) || new l("UserInputManager"), this._log.info("Initializing UserInputManager"), this._log.debug("Initializing input resource access classes"), this._keyboardRA = new ee(), this._mouseRA = new te(t), this._gamepadRA = new se(), this._log.debug("Registering input event callbacks"), this._keyboardRA.onDeviceConnected(this._publishDeviceConnected.bind(this)), this._keyboardRA.onInputChanged(this._publishInputChanged.bind(this)), this._mouseRA.onDeviceConnected(this._publishDeviceConnected.bind(this)), this._mouseRA.onInputChanged(this._publishInputChanged.bind(this)), this._gamepadRA.onDeviceConnected(this._publishDeviceConnected.bind(this)), this._gamepadRA.onDeviceDisconnected(this._publishDeviceDisconnected.bind(this)), this._gamepadRA.onInputChanged(this._publishInputChanged.bind(this)), this._log.info("UserInputManager initialized successfully");
2031
+ }
2032
+ //------------------------------------------------------------------------------------------------------------------
2033
+ // Private methods
2034
+ //------------------------------------------------------------------------------------------------------------------
2035
+ /**
2036
+ * Publish device connected event to the event bus
2037
+ */
2038
+ _publishDeviceConnected(e) {
2039
+ this._log.debug(`Device connected: ${e.id} (${e.name})`);
2040
+ const t = {
2041
+ type: "input:device:connected",
2042
+ payload: { device: e }
2043
+ };
2044
+ this._eventBus.publish(t);
2045
+ }
2046
+ /**
2047
+ * Publish device disconnected event to the event bus
2048
+ */
2049
+ _publishDeviceDisconnected(e) {
2050
+ this._log.debug(`Device disconnected: ${e.id} (${e.name})`);
2051
+ const t = {
2052
+ type: "input:device:disconnected",
2053
+ payload: { device: e }
2054
+ };
2055
+ this._eventBus.publish(t);
2056
+ }
2057
+ /**
2058
+ * Publish input changed event to the event bus, used by all device types
2059
+ */
2060
+ _publishInputChanged(e, t) {
2061
+ this._log.trace(`Input changed: ${e.id}`, t);
2062
+ const s = {
2063
+ type: "input:changed",
2064
+ payload: {
2065
+ deviceId: e.id,
2066
+ device: e,
2067
+ state: t
2068
+ }
2069
+ };
2070
+ this._eventBus.publish(s);
2071
+ }
2072
+ //------------------------------------------------------------------------------------------------------------------
2073
+ // Internal methods
2074
+ //------------------------------------------------------------------------------------------------------------------
2075
+ /**
2076
+ * Destroy the input manager and clean up event listeners
2077
+ */
2078
+ $destroy() {
2079
+ this._log.info("Destroying UserInputManager"), this._log.debug("Cleaning up input resource access instances"), this._keyboardRA.destroy(), this._mouseRA.destroy(), this._gamepadRA.destroy(), this._log.info("UserInputManager destroyed");
2080
+ }
2081
+ //------------------------------------------------------------------------------------------------------------------
2082
+ // Public methods
2083
+ //------------------------------------------------------------------------------------------------------------------
2084
+ /**
2085
+ * Get all input devices
2086
+ *
2087
+ * @return An array of input devices
2088
+ */
2089
+ listDevices() {
2090
+ this._log.debug("Getting all connected input devices");
2091
+ const e = [];
2092
+ return e.push(this._keyboardRA.getDevice()), e.push(this._mouseRA.getDevice()), e.push(...this._gamepadRA.getDevices()), this._log.debug(`Found ${e.length} connected devices`), e;
2093
+ }
2094
+ /**
2095
+ * Get a specific input device by ID
2096
+ *
2097
+ * @param deviceId - The ID of the device to get
2098
+ *
2099
+ * @return The input device, or null if not found
2100
+ */
2101
+ getDevice(e) {
2102
+ if (this._log.debug(`Getting device: ${e}`), e.startsWith("keyboard-"))
2103
+ return this._keyboardRA.getDevice();
2104
+ if (e.startsWith("mouse-"))
2105
+ return this._mouseRA.getDevice();
2106
+ if (e.startsWith("gamepad-")) {
2107
+ const t = parseInt(e.split("-")[1], 10);
2108
+ return this._gamepadRA.getDevice(t);
2109
+ } else {
2110
+ const t = this.listDevices();
2111
+ for (const s of t)
2112
+ if (s.id === e)
2113
+ return s;
2114
+ }
2115
+ return this._log.warn(`Device not found: ${e}`), null;
2116
+ }
2117
+ /**
2118
+ * Poll for gamepad state updates - call this in your game loop
2119
+ */
2120
+ pollGamepads() {
2121
+ this._gamepadRA.pollGamepads();
2122
+ }
2123
+ }
2124
+ async function M(a, e) {
2125
+ const t = new K(a, e);
2126
+ return await t.initAsync(), t;
2127
+ }
2128
+ function v(a, e) {
2129
+ return new U(a, e.antialias, e.options, e.adaptToDeviceRatio);
2130
+ }
2131
+ function ne(a) {
2132
+ return new B(a);
2133
+ }
2134
+ async function oe(a, e) {
2135
+ if (a === null)
2136
+ return console.debug("Using Null Engine"), ne(e);
2137
+ const t = e.forceEngine || "auto";
2138
+ if (t === "webgpu")
2139
+ if ($())
2140
+ try {
2141
+ return console.debug("Using forced WebGPU engine"), await M(a, e);
2142
+ } catch (s) {
2143
+ throw console.error("Forced WebGPU initialization failed:", s), new Error("Forced WebGPU failed to initialize. If WebGPU is required, check browser compatibility.");
2144
+ }
2145
+ else
2146
+ throw new Error("WebGPU was forced but is not available in this browser.");
2147
+ if (t === "webgl")
2148
+ return console.debug("Using forced WebGL engine"), v(a, e);
2149
+ if ($())
2150
+ try {
2151
+ return console.debug("Using WebGPU"), M(a, e).catch((s) => (console.warn("WebGPU initialization failed, falling back to WebGL:", s), v(a, e)));
2152
+ } catch (s) {
2153
+ console.warn("WebGPU initialization failed, falling back to WebGL:", s);
2154
+ }
2155
+ else
2156
+ console.warn("WebGPU not supported, falling back to WebGL.");
2157
+ return console.debug("Using WebGL"), v(a, e);
2158
+ }
2159
+ async function ae() {
2160
+ const a = await z();
2161
+ return new R(!0, a);
2162
+ }
2163
+ async function de(a, e = {}) {
2164
+ const t = new F(e.logLevel || "debug"), s = t.getLogger("SAGE");
2165
+ s.info(`Initializing SAGE Game Engine v${m}...`), s.debug("Creating rendering engine...");
2166
+ const i = await oe(a, e.renderOptions || {});
2167
+ s.debug("Creating physics engine...");
2168
+ const o = await ae();
2169
+ s.debug("Creating event bus...");
2170
+ const r = new O(t);
2171
+ s.debug("Creating scene engine...");
2172
+ const c = new N(i, o, t);
2173
+ s.debug("Creating managers...");
2174
+ const h = new ie(r, a, t), u = new j(r, t), g = new Q(r, t, u), d = new Z(i, c, g, h, t);
2175
+ if (s.info(`SAGE Game Engine v${m} initialized successfully.`), s.debug("Registering default input bindings..."), e.bindings)
2176
+ for (const _ of e.bindings)
2177
+ u.registerBinding(_);
2178
+ return new T(
2179
+ a,
2180
+ i,
2181
+ o,
2182
+ r,
2183
+ t,
2184
+ // Engines
2185
+ {
2186
+ sceneEngine: c
2187
+ },
2188
+ // Managers
2189
+ {
2190
+ bindingManager: u,
2191
+ entityManager: g,
2192
+ gameManager: d,
2193
+ inputManager: h
2194
+ }
2195
+ );
2196
+ }
2197
+ export {
2198
+ W as ConsoleBackend,
2199
+ q as GameEntity,
2200
+ ue as GameEntityBehavior,
2201
+ O as GameEventBus,
2202
+ F as LoggingUtility,
2203
+ P as NullBackend,
2204
+ T as SkewedAspectGameEngine,
2205
+ m as VERSION,
2206
+ de as createGameEngine
2207
+ };
2208
+ //# sourceMappingURL=sage.es.js.map