dopaminejs 2.1.0 → 2.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.
package/dist/engine.js ADDED
@@ -0,0 +1,1158 @@
1
+ class a {
2
+ constructor() {
3
+ this._listeners = /* @__PURE__ */ new Map(), this._onceListeners = /* @__PURE__ */ new Map(), this._sortedCache = /* @__PURE__ */ new Map();
4
+ }
5
+ /**
6
+ * Register an event listener
7
+ * @param {string} event - Event name
8
+ * @param {Function} callback - Callback function
9
+ * @param {number} priority - Higher priority = called first (default: 0)
10
+ * @returns {EventBus} - For chaining
11
+ */
12
+ on(e, t, s = 0) {
13
+ return this._listeners.has(e) || this._listeners.set(e, /* @__PURE__ */ new Set()), this._listeners.get(e).add({ callback: t, priority: s }), this._sortedCache.delete(e), this;
14
+ }
15
+ /**
16
+ * Register a one-time event listener
17
+ * @param {string} event - Event name
18
+ * @param {Function} callback - Callback function
19
+ * @returns {EventBus} - For chaining
20
+ */
21
+ once(e, t) {
22
+ return this._onceListeners.has(e) || this._onceListeners.set(e, /* @__PURE__ */ new Set()), this._onceListeners.get(e).add(t), this;
23
+ }
24
+ /**
25
+ * Remove an event listener
26
+ * @param {string} event - Event name
27
+ * @param {Function} callback - Callback function to remove
28
+ * @returns {EventBus} - For chaining
29
+ */
30
+ off(e, t) {
31
+ const s = this._listeners.get(e);
32
+ if (s)
33
+ for (const r of [...s])
34
+ r.callback === t && (s.delete(r), this._sortedCache.delete(e));
35
+ const i = this._onceListeners.get(e);
36
+ return i && i.delete(t), this;
37
+ }
38
+ /**
39
+ * Emit an event to all registered listeners
40
+ * @param {string} event - Event name
41
+ * @param {*} data - Event data
42
+ */
43
+ emit(e, t) {
44
+ const s = this._getSortedListeners(e);
45
+ for (let r = 0; r < s.length; r++)
46
+ this._invoke(s[r].callback, t, e);
47
+ const i = this._onceListeners.get(e);
48
+ if (i && i.size > 0) {
49
+ const r = [...i];
50
+ i.clear();
51
+ for (const n of r)
52
+ this._invoke(n, t, e);
53
+ }
54
+ }
55
+ /**
56
+ * Call one listener without letting it abort the dispatch.
57
+ *
58
+ * A throwing listener used to take out every lower-priority listener for
59
+ * that event, which on 'tick' means the game silently stops updating.
60
+ *
61
+ * @private
62
+ */
63
+ _invoke(e, t, s) {
64
+ try {
65
+ e(t);
66
+ } catch (i) {
67
+ console.error(`[DopamineJS] Listener for "${s}" threw:`, i);
68
+ }
69
+ }
70
+ /**
71
+ * Get sorted listeners for an event (with caching)
72
+ * @private
73
+ */
74
+ _getSortedListeners(e) {
75
+ if (this._sortedCache.has(e))
76
+ return this._sortedCache.get(e);
77
+ const t = this._listeners.get(e);
78
+ if (!t || t.size === 0)
79
+ return [];
80
+ const s = Array.from(t).sort((i, r) => r.priority - i.priority);
81
+ return this._sortedCache.set(e, s), s;
82
+ }
83
+ /**
84
+ * Remove all listeners for an event, or all events if no event specified
85
+ * @param {string} [event] - Event name (optional)
86
+ */
87
+ clear(e) {
88
+ e ? (this._listeners.delete(e), this._onceListeners.delete(e), this._sortedCache.delete(e)) : (this._listeners.clear(), this._onceListeners.clear(), this._sortedCache.clear());
89
+ }
90
+ /**
91
+ * Check if an event has listeners
92
+ * @param {string} event - Event name
93
+ * @returns {boolean}
94
+ */
95
+ hasListeners(e) {
96
+ const t = this._listeners.get(e), s = this._onceListeners.get(e);
97
+ return !!t?.size || !!s?.size;
98
+ }
99
+ }
100
+ a.Events = {
101
+ // Core game loop events
102
+ TICK: "tick",
103
+ FIXED_UPDATE: "fixed_update",
104
+ RENDER: "render",
105
+ // Physics events
106
+ COLLISION_ENTER: "collision_enter",
107
+ COLLISION_EXIT: "collision_exit",
108
+ // Dopamine events (from RewardSystem)
109
+ XP_GAINED: "xp_gained",
110
+ LEVEL_UP: "level_up",
111
+ ACHIEVEMENT_UNLOCKED: "achievement_unlocked",
112
+ NEW_HIGH_SCORE: "new_high_score",
113
+ STREAK_UPDATED: "streak_updated",
114
+ // System lifecycle events
115
+ SYSTEM_REGISTERED: "system_registered",
116
+ SYSTEM_UNREGISTERED: "system_unregistered",
117
+ PLUGIN_LOADED: "plugin_loaded",
118
+ PLUGIN_UNLOADED: "plugin_unloaded"
119
+ };
120
+ class g {
121
+ constructor(e) {
122
+ this.kernel = e, this._systems = /* @__PURE__ */ new Map(), this._updateOrder = [], this._fixedUpdateOrder = [], this._needsRecompute = !1, this._failed = /* @__PURE__ */ new WeakSet();
123
+ }
124
+ /**
125
+ * Run one system's update without letting a throw stop the others.
126
+ * @private
127
+ */
128
+ _run(e, t, s) {
129
+ try {
130
+ e[t](s);
131
+ } catch (i) {
132
+ this._failed.has(e) || (this._failed.add(e), console.error(`[SystemRegistry] System ${t}() threw:`, i));
133
+ }
134
+ }
135
+ /**
136
+ * Register a system
137
+ * @param {string} name - Unique system name
138
+ * @param {Object} system - System instance (must implement ISystem interface)
139
+ * @param {Object} options - Configuration options
140
+ * @param {number} options.priority - Update priority (higher = earlier, default: 0)
141
+ * @param {string[]} options.dependencies - System names this depends on
142
+ * @returns {SystemRegistry} - For chaining
143
+ */
144
+ register(e, t, { priority: s = 0, dependencies: i = [] } = {}) {
145
+ this._systems.has(e) && (console.warn(`[SystemRegistry] System "${e}" already registered. Replacing.`), this.unregister(e));
146
+ for (const r of i)
147
+ this._systems.has(r) || console.warn(`[SystemRegistry] Dependency "${r}" not found for system "${e}"`);
148
+ return this._systems.set(e, {
149
+ system: t,
150
+ priority: s,
151
+ dependencies: i
152
+ }), t.init && t.init(this.kernel), this._needsRecompute = !0, this.kernel.events.emit("system_registered", { name: e, system: t }), this;
153
+ }
154
+ /**
155
+ * Get a system by name
156
+ * @param {string} name - System name
157
+ * @returns {Object|undefined} - System instance
158
+ */
159
+ get(e) {
160
+ const t = this._systems.get(e);
161
+ return t ? t.system : void 0;
162
+ }
163
+ /**
164
+ * Check if a system is registered
165
+ * @param {string} name - System name
166
+ * @returns {boolean}
167
+ */
168
+ has(e) {
169
+ return this._systems.has(e);
170
+ }
171
+ /**
172
+ * Unregister a system
173
+ * @param {string} name - System name
174
+ * @returns {boolean} - True if system was found and removed
175
+ */
176
+ unregister(e) {
177
+ const t = this._systems.get(e);
178
+ if (!t)
179
+ return !1;
180
+ try {
181
+ t.system.destroy && t.system.destroy();
182
+ } finally {
183
+ this._systems.delete(e), this._needsRecompute = !0, this.kernel.events.emit("system_unregistered", { name: e });
184
+ }
185
+ return !0;
186
+ }
187
+ /**
188
+ * Update all systems (called each frame)
189
+ * @param {number} dt - Delta time in seconds
190
+ */
191
+ update(e) {
192
+ this._needsRecompute && this._recomputeUpdateOrder();
193
+ for (let t = 0; t < this._updateOrder.length; t++) {
194
+ const s = this._updateOrder[t];
195
+ s.update && this._run(s, "update", e);
196
+ }
197
+ }
198
+ /**
199
+ * Fixed update for physics (called at fixed timestep)
200
+ * @param {number} dt - Fixed delta time
201
+ */
202
+ fixedUpdate(e) {
203
+ this._needsRecompute && this._recomputeUpdateOrder();
204
+ for (let t = 0; t < this._fixedUpdateOrder.length; t++) {
205
+ const s = this._fixedUpdateOrder[t];
206
+ s.fixedUpdate && this._run(s, "fixedUpdate", e);
207
+ }
208
+ }
209
+ /**
210
+ * Recompute the update order based on dependencies and priorities
211
+ * Uses topological sort for dependencies
212
+ * @private
213
+ */
214
+ _recomputeUpdateOrder() {
215
+ const e = Array.from(this._systems.entries()), t = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map();
216
+ for (const [n, o] of e)
217
+ t.set(n, o.dependencies), s.set(n, 0);
218
+ for (const [n, o] of t)
219
+ for (const c of o)
220
+ s.has(c) && s.set(n, s.get(n) + 1);
221
+ const i = [], r = [];
222
+ for (const [n, o] of s)
223
+ o === 0 && i.push(n);
224
+ for (; i.length > 0; ) {
225
+ i.sort((o, c) => {
226
+ const d = this._systems.get(o).priority;
227
+ return this._systems.get(c).priority - d;
228
+ });
229
+ const n = i.shift();
230
+ r.push(n);
231
+ for (const [o, c] of t)
232
+ if (c.includes(n)) {
233
+ const d = s.get(o) - 1;
234
+ s.set(o, d), d === 0 && i.push(o);
235
+ }
236
+ }
237
+ r.length !== e.length && (console.error("[SystemRegistry] Circular dependency detected in systems!"), r.length = 0, r.push(...e.map(([n]) => n).sort((n, o) => this._systems.get(o).priority - this._systems.get(n).priority))), this._updateOrder = r.map((n) => this._systems.get(n).system).filter((n) => n.update), this._fixedUpdateOrder = r.map((n) => this._systems.get(n).system).filter((n) => n.fixedUpdate), this._needsRecompute = !1;
238
+ }
239
+ /**
240
+ * Get all registered system names
241
+ * @returns {string[]}
242
+ */
243
+ getSystemNames() {
244
+ return Array.from(this._systems.keys());
245
+ }
246
+ /**
247
+ * Clear all systems
248
+ */
249
+ clear() {
250
+ for (const e of this._systems.keys())
251
+ this.unregister(e);
252
+ }
253
+ }
254
+ class p {
255
+ constructor(e) {
256
+ this.kernel = e, this._plugins = /* @__PURE__ */ new Map(), this._loadOrder = [], this._inFlight = /* @__PURE__ */ new Set();
257
+ }
258
+ /**
259
+ * @private
260
+ */
261
+ _systemsSince(e) {
262
+ return this.kernel.systems.getSystemNames().filter((t) => !e.has(t));
263
+ }
264
+ /**
265
+ * Mark systems as belonging to a plugin that loaded.
266
+ * @private
267
+ */
268
+ _claim(e) {
269
+ for (const t of this._inFlight)
270
+ for (const s of e)
271
+ t.foreign.add(s);
272
+ }
273
+ /**
274
+ * Remove the systems a failed plugin registered. They would keep updating
275
+ * with nothing left to remove them.
276
+ * @private
277
+ */
278
+ _rollback(e) {
279
+ for (const t of e)
280
+ try {
281
+ this.kernel.systems.unregister(t);
282
+ } catch (s) {
283
+ console.error(`[PluginRegistry] Error removing system "${t}":`, s);
284
+ }
285
+ }
286
+ /**
287
+ * Forget a plugin whose init failed.
288
+ *
289
+ * Removed by name: with two async inits in flight, the failed plugin is
290
+ * not necessarily the last entry.
291
+ * @private
292
+ */
293
+ _forget(e) {
294
+ this._plugins.delete(e);
295
+ const t = this._loadOrder.indexOf(e);
296
+ t > -1 && this._loadOrder.splice(t, 1);
297
+ }
298
+ /**
299
+ * Register and initialize a plugin
300
+ *
301
+ * Plugin interface:
302
+ * {
303
+ * name: string,
304
+ * version?: string,
305
+ * init(kernel): void,
306
+ * destroy?(): void
307
+ * }
308
+ *
309
+ * @param {Object} plugin - Plugin instance
310
+ * @returns {PluginRegistry} - For chaining
311
+ */
312
+ use(e) {
313
+ if (!e.name)
314
+ throw new Error("[PluginRegistry] Plugin must have a name");
315
+ if (this._plugins.has(e.name))
316
+ return console.warn(`[PluginRegistry] Plugin "${e.name}" already registered. Skipping.`), this;
317
+ if (!e.init || typeof e.init != "function")
318
+ throw new Error(`[PluginRegistry] Plugin "${e.name}" must have an init() method`);
319
+ this._plugins.set(e.name, e), this._loadOrder.push(e.name);
320
+ const t = new Set(this.kernel.systems.getSystemNames());
321
+ try {
322
+ e.init(this.kernel), this.kernel.events.emit("plugin_loaded", {
323
+ name: e.name,
324
+ version: e.version
325
+ }), this._claim(this._systemsSince(t));
326
+ } catch (s) {
327
+ throw console.error(`[PluginRegistry] Failed to initialize plugin "${e.name}":`, s), this._forget(e.name), this._rollback(this._systemsSince(t)), s;
328
+ }
329
+ return this;
330
+ }
331
+ /**
332
+ * Register and initialize a plugin asynchronously
333
+ * Useful for plugins that need to load external resources
334
+ *
335
+ * @param {Object} plugin - Plugin instance with async init
336
+ * @returns {Promise<PluginRegistry>} - For chaining
337
+ */
338
+ async useAsync(e) {
339
+ if (!e.name)
340
+ throw new Error("[PluginRegistry] Plugin must have a name");
341
+ if (this._plugins.has(e.name))
342
+ return console.warn(`[PluginRegistry] Plugin "${e.name}" already registered. Skipping.`), this;
343
+ if (!e.init || typeof e.init != "function")
344
+ throw new Error(`[PluginRegistry] Plugin "${e.name}" must have an init() method`);
345
+ this._plugins.set(e.name, e), this._loadOrder.push(e.name);
346
+ const t = {
347
+ before: new Set(this.kernel.systems.getSystemNames()),
348
+ foreign: /* @__PURE__ */ new Set()
349
+ }, s = () => this._systemsSince(t.before).filter((i) => !t.foreign.has(i));
350
+ this._inFlight.add(t);
351
+ try {
352
+ await e.init(this.kernel), this._inFlight.delete(t), this._claim(s()), this.kernel.events.emit("plugin_loaded", {
353
+ name: e.name,
354
+ version: e.version
355
+ });
356
+ } catch (i) {
357
+ console.error(`[PluginRegistry] Failed to initialize plugin "${e.name}":`, i), this._inFlight.delete(t), this._forget(e.name);
358
+ const r = s();
359
+ throw this._inFlight.size === 0 ? this._rollback(r) : r.length > 0 && console.warn(`[PluginRegistry] Left systems in place after "${e.name}" failed: ${r.join(", ")}`), i;
360
+ }
361
+ return this;
362
+ }
363
+ /**
364
+ * Remove a plugin
365
+ * @param {string} name - Plugin name
366
+ * @returns {boolean} - True if plugin was found and removed
367
+ */
368
+ remove(e) {
369
+ const t = this._plugins.get(e);
370
+ if (!t)
371
+ return !1;
372
+ if (t.destroy)
373
+ try {
374
+ t.destroy();
375
+ } catch (i) {
376
+ console.error(`[PluginRegistry] Error destroying plugin "${e}":`, i);
377
+ }
378
+ this._plugins.delete(e);
379
+ const s = this._loadOrder.indexOf(e);
380
+ return s > -1 && this._loadOrder.splice(s, 1), this.kernel.events.emit("plugin_unloaded", { name: e }), !0;
381
+ }
382
+ /**
383
+ * Get a plugin by name
384
+ * @param {string} name - Plugin name
385
+ * @returns {Object|undefined} - Plugin instance
386
+ */
387
+ get(e) {
388
+ return this._plugins.get(e);
389
+ }
390
+ /**
391
+ * Check if a plugin is registered
392
+ * @param {string} name - Plugin name
393
+ * @returns {boolean}
394
+ */
395
+ has(e) {
396
+ return this._plugins.has(e);
397
+ }
398
+ /**
399
+ * Get all registered plugin names
400
+ * @returns {string[]}
401
+ */
402
+ getPluginNames() {
403
+ return Array.from(this._plugins.keys());
404
+ }
405
+ /**
406
+ * Get plugin load order
407
+ * @returns {string[]}
408
+ */
409
+ getLoadOrder() {
410
+ return [...this._loadOrder];
411
+ }
412
+ /**
413
+ * Clear all plugins (in reverse load order)
414
+ */
415
+ clear() {
416
+ for (let e = this._loadOrder.length - 1; e >= 0; e--)
417
+ this.remove(this._loadOrder[e]);
418
+ }
419
+ }
420
+ class k {
421
+ constructor(e = {}) {
422
+ this.width = e.width || 800, this.height = e.height || 600, this.backgroundColor = e.backgroundColor || "#000000", e.canvas ? (this.canvas = e.canvas, this.canvas.width = this.width, this.canvas.height = this.height) : (this.canvas = document.createElement("canvas"), this.canvas.width = this.width, this.canvas.height = this.height, (e.parent ? typeof e.parent == "string" ? document.querySelector(e.parent) : e.parent : document.body).appendChild(this.canvas)), this.ctx = this.canvas.getContext("2d");
423
+ }
424
+ /**
425
+ * Clear the canvas.
426
+ */
427
+ clear() {
428
+ this.ctx.fillStyle = this.backgroundColor, this.ctx.fillRect(0, 0, this.width, this.height);
429
+ }
430
+ resize(e, t) {
431
+ this.width = e, this.height = t, this.canvas.width = e, this.canvas.height = t;
432
+ }
433
+ }
434
+ class w {
435
+ constructor() {
436
+ this.running = !1, this.lastTime = 0, this.callbacks = /* @__PURE__ */ new Set(), this._frameHandle = null, this._tick = this._tick.bind(this), this._failed = /* @__PURE__ */ new WeakSet();
437
+ }
438
+ /**
439
+ * Add a callback to the loop.
440
+ * @param {Function} callback - Function to call each frame (receives dt).
441
+ */
442
+ add(e) {
443
+ this.callbacks.add(e);
444
+ }
445
+ /**
446
+ * Remove a callback from the loop.
447
+ * @param {Function} callback
448
+ */
449
+ remove(e) {
450
+ this.callbacks.delete(e);
451
+ }
452
+ /**
453
+ * Start the loop.
454
+ */
455
+ start() {
456
+ this.running || (this.running = !0, this.lastTime = performance.now(), this._frameHandle !== null && cancelAnimationFrame(this._frameHandle), this._frameHandle = requestAnimationFrame(this._tick));
457
+ }
458
+ /**
459
+ * Stop the loop.
460
+ */
461
+ stop() {
462
+ this.running = !1, this._frameHandle !== null && (cancelAnimationFrame(this._frameHandle), this._frameHandle = null);
463
+ }
464
+ _tick(e) {
465
+ if (this._frameHandle = null, !this.running) return;
466
+ const t = (e - this.lastTime) / 1e3;
467
+ this.lastTime = e;
468
+ const s = Math.max(0, Math.min(t, 0.1));
469
+ for (const i of [...this.callbacks])
470
+ try {
471
+ i(s);
472
+ } catch (r) {
473
+ this._failed.has(i) || (this._failed.add(i), console.error("[Ticker] Callback threw:", r));
474
+ }
475
+ this.running && (this._frameHandle = requestAnimationFrame(this._tick));
476
+ }
477
+ }
478
+ function u(h, e) {
479
+ let t = !1;
480
+ return new Proxy(h, {
481
+ get(s, i, r) {
482
+ t || (t = !0, console.warn(e));
483
+ const n = Reflect.get(s, i, r);
484
+ return typeof n == "function" ? n.bind(s) : n;
485
+ },
486
+ set(s, i, r) {
487
+ return t || (t = !0, console.warn(e)), Reflect.set(s, i, r);
488
+ }
489
+ });
490
+ }
491
+ class l {
492
+ constructor() {
493
+ this.gameObject = null, this.kernel = null;
494
+ }
495
+ /**
496
+ * Access systems through kernel (no more globals!)
497
+ */
498
+ get physics() {
499
+ return this.kernel?.systems.get("physics");
500
+ }
501
+ get input() {
502
+ return this.kernel?.systems.get("input");
503
+ }
504
+ get loader() {
505
+ return this.kernel?.systems.get("loader");
506
+ }
507
+ get events() {
508
+ return this.kernel?.events;
509
+ }
510
+ onAttach() {
511
+ }
512
+ onDetach() {
513
+ }
514
+ update(e) {
515
+ }
516
+ render(e) {
517
+ }
518
+ }
519
+ class b extends l {
520
+ constructor(e = "box", t = 50, s = 50, i = 25) {
521
+ super(), this.type = e, this.width = t, this.height = s, this.radius = i, this.tag = null;
522
+ }
523
+ get kernel() {
524
+ return this._kernel ?? null;
525
+ }
526
+ /**
527
+ * The kernel can arrive after onAttach(), when the collider was added to
528
+ * an object before that object joined a scene. Registering only in
529
+ * onAttach() left such colliders out of physics for good.
530
+ */
531
+ set kernel(e) {
532
+ e !== this._kernel && (this._unregister(), this._kernel = e, this._register());
533
+ }
534
+ onAttach() {
535
+ super.onAttach(), this._attached = !0, this._register();
536
+ }
537
+ onDetach() {
538
+ super.onDetach(), this._attached = !1, this._unregister();
539
+ }
540
+ /**
541
+ * @private
542
+ */
543
+ _register() {
544
+ if (this._physics || !this._attached) return;
545
+ const e = this.physics;
546
+ e && (e.add(this), this._physics = e);
547
+ }
548
+ /**
549
+ * @private
550
+ */
551
+ _unregister() {
552
+ this._physics && (this._physics.remove(this), this._physics = null);
553
+ }
554
+ getBounds() {
555
+ const e = this.gameObject;
556
+ if (this.type === "box")
557
+ return {
558
+ left: e.x - this.width / 2,
559
+ right: e.x + this.width / 2,
560
+ top: e.y - this.height / 2,
561
+ bottom: e.y + this.height / 2
562
+ };
563
+ if (this.type === "circle")
564
+ return {
565
+ x: e.x,
566
+ y: e.y,
567
+ radius: this.radius
568
+ };
569
+ }
570
+ }
571
+ class m {
572
+ constructor() {
573
+ this.colliders = [], this.kernel = null;
574
+ }
575
+ /**
576
+ * ISystem interface - called when registered
577
+ */
578
+ init(e) {
579
+ this.kernel = e;
580
+ }
581
+ /**
582
+ * ISystem interface - called at fixed timestep
583
+ */
584
+ fixedUpdate(e) {
585
+ this.step();
586
+ }
587
+ /**
588
+ * ISystem interface - cleanup
589
+ */
590
+ destroy() {
591
+ this.colliders = [];
592
+ }
593
+ add(e) {
594
+ this.colliders.push(e);
595
+ }
596
+ remove(e) {
597
+ const t = this.colliders.indexOf(e);
598
+ t > -1 && this.colliders.splice(t, 1);
599
+ }
600
+ /**
601
+ * Main Physics Step.
602
+ * Checks all pairs and triggers callbacks.
603
+ */
604
+ step() {
605
+ for (let e = 0; e < this.colliders.length; e++)
606
+ for (let t = e + 1; t < this.colliders.length; t++) {
607
+ const s = this.colliders[e], i = this.colliders[t];
608
+ this._intersects(s, s.getBounds(), i, i.getBounds()) && (this._notifyCollision(s.gameObject, i.gameObject), this._notifyCollision(i.gameObject, s.gameObject));
609
+ }
610
+ }
611
+ _notifyCollision(e, t) {
612
+ !e || !e.components || e.components.forEach((s) => {
613
+ s.onCollisionEnter && s.onCollisionEnter(t);
614
+ });
615
+ }
616
+ /**
617
+ * Check if a specific collider overlaps with anything in the target group (tag).
618
+ * @param {Collider} source
619
+ * @param {string} targetTag
620
+ */
621
+ checkOverlap(e, t) {
622
+ const s = [], i = e.getBounds();
623
+ for (const r of this.colliders) {
624
+ if (r === e || t && r.tag !== t) continue;
625
+ const n = r.getBounds();
626
+ this._intersects(e, i, r, n) && s.push(r);
627
+ }
628
+ return s;
629
+ }
630
+ _intersects(e, t, s, i) {
631
+ if (e.type === "box" && s.type === "box")
632
+ return t.left < i.right && t.right > i.left && t.top < i.bottom && t.bottom > i.top;
633
+ if (e.type === "circle" && s.type === "circle") {
634
+ const r = t.x - i.x, n = t.y - i.y;
635
+ return Math.sqrt(r * r + n * n) < t.radius + i.radius;
636
+ }
637
+ return !1;
638
+ }
639
+ // IPhysicsSystem interface methods (for plugin compatibility)
640
+ addBody(e) {
641
+ this.add(e);
642
+ }
643
+ removeBody(e) {
644
+ this.remove(e);
645
+ }
646
+ checkCollision(e, t) {
647
+ return this._intersects(e, e.getBounds(), t, t.getBounds());
648
+ }
649
+ raycast(e, t, s) {
650
+ return null;
651
+ }
652
+ setGravity(e, t) {
653
+ }
654
+ }
655
+ const O = u(
656
+ new m(),
657
+ "[DopamineJS] GlobalPhysics is deprecated. Use kernel.physics instead."
658
+ );
659
+ class f {
660
+ constructor() {
661
+ this.keys = /* @__PURE__ */ new Map(), this.mouse = { x: 0, y: 0, buttons: /* @__PURE__ */ new Map() }, this.kernel = null, this._onKeyDown = this._onKeyDown.bind(this), this._onKeyUp = this._onKeyUp.bind(this), this._onMouseMove = this._onMouseMove.bind(this), this._onMouseDown = this._onMouseDown.bind(this), this._onMouseUp = this._onMouseUp.bind(this);
662
+ }
663
+ /**
664
+ * ISystem interface - called when registered
665
+ */
666
+ init(e) {
667
+ this.kernel = e, window.addEventListener("keydown", this._onKeyDown), window.addEventListener("keyup", this._onKeyUp), window.addEventListener("mousemove", this._onMouseMove), window.addEventListener("mousedown", this._onMouseDown), window.addEventListener("mouseup", this._onMouseUp);
668
+ }
669
+ /**
670
+ * ISystem interface - cleanup
671
+ */
672
+ destroy() {
673
+ window.removeEventListener("keydown", this._onKeyDown), window.removeEventListener("keyup", this._onKeyUp), window.removeEventListener("mousemove", this._onMouseMove), window.removeEventListener("mousedown", this._onMouseDown), window.removeEventListener("mouseup", this._onMouseUp);
674
+ }
675
+ _onKeyDown(e) {
676
+ this.keys.set(e.key, !0);
677
+ }
678
+ _onKeyUp(e) {
679
+ this.keys.set(e.key, !1);
680
+ }
681
+ _onMouseMove(e) {
682
+ this.mouse.x = e.clientX, this.mouse.y = e.clientY;
683
+ }
684
+ _onMouseDown(e) {
685
+ this.mouse.buttons.set(e.button, !0);
686
+ }
687
+ _onMouseUp(e) {
688
+ this.mouse.buttons.set(e.button, !1);
689
+ }
690
+ isKeyDown(e) {
691
+ return this.keys.get(e) || !1;
692
+ }
693
+ isMouseButtonDown(e = 0) {
694
+ return this.mouse.buttons.get(e) || !1;
695
+ }
696
+ }
697
+ const M = u(
698
+ new f(),
699
+ "[DopamineJS] GlobalInput is deprecated. Use kernel.input instead."
700
+ );
701
+ class y {
702
+ constructor() {
703
+ this.assets = /* @__PURE__ */ new Map(), this.kernel = null;
704
+ }
705
+ /**
706
+ * ISystem interface - called when registered
707
+ */
708
+ init(e) {
709
+ this.kernel = e;
710
+ }
711
+ /**
712
+ * ISystem interface - cleanup
713
+ */
714
+ destroy() {
715
+ this.assets.clear();
716
+ }
717
+ async loadImage(e, t) {
718
+ return new Promise((s, i) => {
719
+ const r = new Image();
720
+ r.onload = () => {
721
+ this.assets.set(e, r), s(r);
722
+ }, r.onerror = i, r.src = t;
723
+ });
724
+ }
725
+ get(e) {
726
+ return this.assets.get(e);
727
+ }
728
+ }
729
+ const C = u(
730
+ new y(),
731
+ "[DopamineJS] GlobalLoader is deprecated. Use kernel.loader instead."
732
+ );
733
+ class x {
734
+ constructor(e = {}) {
735
+ this.config = e, this._boundUpdate = this._update.bind(this), this.events = new a(), this.systems = new g(this), this.plugins = new p(this), this._registerCoreSystems(e), this.fixedTimestep = e.fixedTimestep || 1 / 60, this._accumulator = 0, this._maxAccumulator = this.fixedTimestep * 5;
736
+ }
737
+ /**
738
+ * Register core engine systems
739
+ * These can be replaced by plugins
740
+ * @private
741
+ */
742
+ _registerCoreSystems(e) {
743
+ this.systems.register("ticker", new w(), {
744
+ priority: 100
745
+ });
746
+ const t = {
747
+ width: e.width,
748
+ height: e.height,
749
+ backgroundColor: e.backgroundColor,
750
+ canvas: e.canvas,
751
+ ...e.renderer
752
+ };
753
+ this.systems.register("renderer", new k(t), {
754
+ priority: 90
755
+ }), this.systems.register("input", new f(), {
756
+ priority: 80
757
+ }), this.systems.register("loader", new y(), {
758
+ priority: 70
759
+ }), this.systems.register("physics", new m(), {
760
+ priority: 60
761
+ });
762
+ }
763
+ /**
764
+ * Start the kernel (begins game loop)
765
+ */
766
+ start() {
767
+ const e = this.systems.get("ticker");
768
+ e && (e.add(this._boundUpdate), e.start()), this.events.emit("kernel_started");
769
+ }
770
+ /**
771
+ * Stop the kernel
772
+ */
773
+ stop() {
774
+ const e = this.systems.get("ticker");
775
+ e && e.stop(), this.events.emit("kernel_stopped");
776
+ }
777
+ /**
778
+ * Main update loop
779
+ * @private
780
+ */
781
+ _update(e) {
782
+ for (this.events.emit(a.Events.TICK, { dt: e }), this._accumulator += e, this._accumulator > this._maxAccumulator && (this._accumulator = this._maxAccumulator); this._accumulator >= this.fixedTimestep; )
783
+ this.events.emit(a.Events.FIXED_UPDATE, { dt: this.fixedTimestep }), this.systems.fixedUpdate(this.fixedTimestep), this._accumulator -= this.fixedTimestep;
784
+ this.systems.update(e), this.events.emit(a.Events.RENDER, { dt: e });
785
+ }
786
+ /**
787
+ * Shorthand accessors for common systems (backward compatibility)
788
+ */
789
+ get physics() {
790
+ return this.systems.get("physics");
791
+ }
792
+ get input() {
793
+ return this.systems.get("input");
794
+ }
795
+ get loader() {
796
+ return this.systems.get("loader");
797
+ }
798
+ get renderer() {
799
+ return this.systems.get("renderer");
800
+ }
801
+ get ticker() {
802
+ return this.systems.get("ticker");
803
+ }
804
+ /**
805
+ * Destroy the kernel and cleanup all systems
806
+ */
807
+ destroy() {
808
+ this.stop();
809
+ const e = this.systems.get("ticker");
810
+ e && e.remove(this._boundUpdate), this.plugins.clear(), this.systems.clear(), this.events.clear();
811
+ }
812
+ }
813
+ class D {
814
+ constructor() {
815
+ this.gameObjects = [], this._kernel = null;
816
+ }
817
+ get kernel() {
818
+ return this._kernel;
819
+ }
820
+ /**
821
+ * Objects added before the scene had a kernel receive it here. Scenes are
822
+ * often built in a constructor, before Game.setScene() injects the kernel.
823
+ * The Director sets it to null on exit, which takes colliders out of
824
+ * physics until the scene runs again.
825
+ */
826
+ set kernel(e) {
827
+ this._kernel = e;
828
+ for (const t of this.gameObjects)
829
+ t.kernel = e;
830
+ }
831
+ /**
832
+ * Called when the scene is added to the Game.
833
+ */
834
+ onEnter() {
835
+ }
836
+ /**
837
+ * Called when the scene is removed from the Game.
838
+ */
839
+ onExit() {
840
+ }
841
+ /**
842
+ * Add a GameObject to the scene.
843
+ * @param {GameObject} gameObject
844
+ */
845
+ add(e) {
846
+ return this.kernel && (e.kernel = this.kernel), e._detached && (e._detached = !1, this._eachComponent(e, (t) => t.onAttach())), this.gameObjects.push(e), e;
847
+ }
848
+ /**
849
+ * Remove a GameObject from the scene.
850
+ * @param {GameObject} gameObject
851
+ */
852
+ remove(e) {
853
+ const t = this.gameObjects.indexOf(e);
854
+ t > -1 && (this.gameObjects.splice(t, 1), e._detached = !0, this._eachComponent(e, (s) => s.onDetach()));
855
+ }
856
+ /**
857
+ * Visit the components of an object and of all its children.
858
+ * @private
859
+ */
860
+ _eachComponent(e, t) {
861
+ for (const s of [...e.components])
862
+ t(s);
863
+ for (const s of e.children)
864
+ this._eachComponent(s, t);
865
+ }
866
+ /**
867
+ * Update loop for the scene.
868
+ * @param {number} dt
869
+ */
870
+ update(e) {
871
+ for (const t of [...this.gameObjects])
872
+ t._detached || t.update(e);
873
+ }
874
+ /**
875
+ * Render loop for the scene.
876
+ * @param {CanvasRenderingContext2D} ctx
877
+ */
878
+ render(e) {
879
+ for (const t of [...this.gameObjects])
880
+ t._detached || t.render(e);
881
+ }
882
+ }
883
+ class v {
884
+ constructor(e) {
885
+ this.game = e, this.currentScene = null;
886
+ }
887
+ /**
888
+ * Switch to a new scene.
889
+ * @param {Scene} scene
890
+ */
891
+ run(e) {
892
+ this.currentScene && (this.currentScene.onExit(), this.currentScene.kernel = null), this.currentScene = e, this.currentScene.game = this.game, this.game?.kernel && (e.kernel = this.game.kernel), this.game.scene = e, e.onEnter();
893
+ }
894
+ }
895
+ class L {
896
+ constructor(e = {}) {
897
+ this.config = e, this.kernel = new x(e), this.director = new v(this), this.kernel.systems.register("director", this.director), this.kernel.events.on(a.Events.TICK, this._update.bind(this), 10), this.kernel.events.on(a.Events.RENDER, this._render.bind(this), 10);
898
+ }
899
+ /**
900
+ * Backward compatibility accessors
901
+ */
902
+ get renderer() {
903
+ return this.kernel.renderer;
904
+ }
905
+ get ticker() {
906
+ return this.kernel.ticker;
907
+ }
908
+ get physics() {
909
+ return this.kernel.physics;
910
+ }
911
+ get input() {
912
+ return this.kernel.input;
913
+ }
914
+ /**
915
+ * Start the game loop.
916
+ */
917
+ start() {
918
+ this.kernel.start();
919
+ }
920
+ /**
921
+ * Stop the game loop.
922
+ */
923
+ stop() {
924
+ this.kernel.stop();
925
+ }
926
+ /**
927
+ * Switch to a new scene.
928
+ * @param {Scene} newScene
929
+ */
930
+ setScene(e) {
931
+ e.kernel = this.kernel, this.director.run(e);
932
+ }
933
+ _update({ dt: e }) {
934
+ this.director.currentScene && this.director.currentScene.update(e);
935
+ }
936
+ _render({ dt: e }) {
937
+ this.renderer.clear(), this.director.currentScene && this.director.currentScene.render(this.renderer.ctx);
938
+ }
939
+ /**
940
+ * Destroy the game and cleanup
941
+ */
942
+ destroy() {
943
+ this.kernel.destroy();
944
+ }
945
+ }
946
+ class R {
947
+ constructor(e = 0, t = 0) {
948
+ this.x = e, this.y = t, this.rotation = 0, this.scale = { x: 1, y: 1 }, this.components = [], this.children = [], this.parent = null, this._kernel = null;
949
+ }
950
+ get kernel() {
951
+ return this._kernel;
952
+ }
953
+ set kernel(e) {
954
+ this._kernel = e;
955
+ for (const t of this.components)
956
+ t.kernel = e;
957
+ for (const t of this.children)
958
+ t.kernel = e;
959
+ }
960
+ /**
961
+ * Add a child object.
962
+ * @param {GameObject} child
963
+ */
964
+ addChild(e) {
965
+ return e.parent = this, e.kernel = this.kernel, this.children.push(e), e;
966
+ }
967
+ /**
968
+ * Add a component.
969
+ * @param {Component} component
970
+ */
971
+ addComponent(e) {
972
+ return e.gameObject = this, e.kernel = this.kernel, this.components.push(e), e.onAttach(), e;
973
+ }
974
+ /**
975
+ * Get a component by class type.
976
+ * @param {class} type
977
+ */
978
+ getComponent(e) {
979
+ return this.components.find((t) => t instanceof e);
980
+ }
981
+ update(e) {
982
+ for (const t of this.components)
983
+ t.update && t.update(e);
984
+ for (const t of this.children)
985
+ t.update(e);
986
+ }
987
+ render(e) {
988
+ e.save(), e.translate(this.x, this.y), e.rotate(this.rotation), e.scale(this.scale.x, this.scale.y);
989
+ for (const t of this.components)
990
+ t.render && t.render(e);
991
+ for (const t of this.children)
992
+ t.render(e);
993
+ e.restore();
994
+ }
995
+ }
996
+ class _ {
997
+ constructor(e = 0, t = 0) {
998
+ this.x = e, this.y = t;
999
+ }
1000
+ add(e) {
1001
+ return this.x += e.x, this.y += e.y, this;
1002
+ }
1003
+ sub(e) {
1004
+ return this.x -= e.x, this.y -= e.y, this;
1005
+ }
1006
+ scale(e) {
1007
+ return this.x *= e, this.y *= e, this;
1008
+ }
1009
+ distance(e) {
1010
+ const t = this.x - e.x, s = this.y - e.y;
1011
+ return Math.sqrt(t * t + s * s);
1012
+ }
1013
+ normalize() {
1014
+ const e = Math.sqrt(this.x * this.x + this.y * this.y);
1015
+ return e > 0 && (this.x /= e, this.y /= e), this;
1016
+ }
1017
+ clone() {
1018
+ return new _(this.x, this.y);
1019
+ }
1020
+ static distance(e, t) {
1021
+ return Math.sqrt(Math.pow(t.x - e.x, 2) + Math.pow(t.y - e.y, 2));
1022
+ }
1023
+ }
1024
+ class E extends l {
1025
+ constructor(e) {
1026
+ super(), this.image = e, this.anchor = { x: 0.5, y: 0.5 }, this.width = e ? e.width : 0, this.height = e ? e.height : 0, this.frame = { x: 0, y: 0, w: this.width, h: this.height };
1027
+ }
1028
+ setFrame(e, t, s, i) {
1029
+ this.frame = { x: e, y: t, w: s, h: i }, this.width = s, this.height = i;
1030
+ }
1031
+ setTexture(e) {
1032
+ this.image = e, e && this.setFrame(0, 0, e.width, e.height);
1033
+ }
1034
+ render(e) {
1035
+ if (!this.image) return;
1036
+ const t = this.frame.w, s = this.frame.h, i = -t * this.anchor.x, r = -s * this.anchor.y;
1037
+ e.drawImage(
1038
+ this.image,
1039
+ this.frame.x,
1040
+ this.frame.y,
1041
+ t,
1042
+ s,
1043
+ // Source
1044
+ i,
1045
+ r,
1046
+ t,
1047
+ s
1048
+ // Destination
1049
+ );
1050
+ }
1051
+ }
1052
+ class P extends l {
1053
+ constructor() {
1054
+ super(), this.animations = {}, this.currentAnim = null, this.timer = 0, this.currentFrameIndex = 0, this.sprite = null;
1055
+ }
1056
+ onAttach() {
1057
+ this.sprite = this.gameObject.getComponent(E), this.sprite || console.warn("Animator attached to GameObject without a Sprite component.");
1058
+ }
1059
+ addAnimation(e, t, s = 10, i = !0) {
1060
+ this.animations[e] = { frames: t, fps: s, loop: i };
1061
+ }
1062
+ play(e) {
1063
+ this.currentAnim && this.currentAnim.name === e || this.animations[e] && (this.currentAnim = { name: e, ...this.animations[e] }, this.currentFrameIndex = 0, this.timer = 0, this._updateFrame());
1064
+ }
1065
+ update(e) {
1066
+ if (!this.currentAnim || !this.sprite) return;
1067
+ this.timer += e;
1068
+ const t = 1 / this.currentAnim.fps;
1069
+ this.timer >= t && (this.timer -= t, this.currentFrameIndex++, this.currentFrameIndex >= this.currentAnim.frames.length && (this.currentAnim.loop ? this.currentFrameIndex = 0 : this.currentFrameIndex = this.currentAnim.frames.length - 1), this._updateFrame());
1070
+ }
1071
+ _updateFrame() {
1072
+ const e = this.currentAnim.frames[this.currentFrameIndex];
1073
+ this.sprite.setFrame(e.x, e.y, e.w, e.h);
1074
+ }
1075
+ }
1076
+ class U extends l {
1077
+ constructor(e) {
1078
+ super(), this.system = e;
1079
+ }
1080
+ /**
1081
+ * Trigger an effect at the GameObject's current position.
1082
+ * @param {string} effectName - 'confetti', 'fire', 'sparkle', etc.
1083
+ * @param {object} options - override options
1084
+ */
1085
+ play(e, t = {}) {
1086
+ const s = this.gameObject.x, i = this.gameObject.y;
1087
+ switch (e) {
1088
+ case "confetti":
1089
+ this.system.confetti(s, i, t.count);
1090
+ break;
1091
+ case "fire":
1092
+ this.system.fire(s, i, t.count);
1093
+ break;
1094
+ case "sparkle":
1095
+ this.system.sparkle(s, i, t.count, t.color);
1096
+ break;
1097
+ case "blood":
1098
+ this.system.emit({
1099
+ x: s,
1100
+ y: i,
1101
+ count: 10,
1102
+ color: ["#8a0303", "#ff0000"],
1103
+ speed: 4,
1104
+ gravity: 0.1,
1105
+ decay: 0.05
1106
+ });
1107
+ break;
1108
+ default:
1109
+ this.system.emit({
1110
+ x: s,
1111
+ y: i,
1112
+ ...t
1113
+ });
1114
+ }
1115
+ }
1116
+ }
1117
+ class A extends l {
1118
+ constructor() {
1119
+ super(), this.intensity = 0, this.duration = 0, this.timer = 0, this.originalPos = { x: 0, y: 0 }, this.isShaking = !1;
1120
+ }
1121
+ shake(e = 5, t = 0.2) {
1122
+ this.isShaking || (this.originalPos.x = this.gameObject.x, this.originalPos.y = this.gameObject.y), this.intensity = e, this.duration = t, this.timer = 0, this.isShaking = !0;
1123
+ }
1124
+ update(e) {
1125
+ if (!this.isShaking) return;
1126
+ if (this.timer += e, this.timer >= this.duration) {
1127
+ this.isShaking = !1, this.gameObject.x = this.originalPos.x, this.gameObject.y = this.originalPos.y;
1128
+ return;
1129
+ }
1130
+ const t = (Math.random() - 0.5) * 2 * this.intensity, s = (Math.random() - 0.5) * 2 * this.intensity;
1131
+ this.gameObject.x = this.originalPos.x + t, this.gameObject.y = this.originalPos.y + s;
1132
+ }
1133
+ }
1134
+ export {
1135
+ P as Animator,
1136
+ b as Collider,
1137
+ l as Component,
1138
+ v as Director,
1139
+ x as DopamineKernel,
1140
+ a as EventBus,
1141
+ L as Game,
1142
+ R as GameObject,
1143
+ M as GlobalInput,
1144
+ C as GlobalLoader,
1145
+ O as GlobalPhysics,
1146
+ f as Input,
1147
+ y as Loader,
1148
+ U as ParticleEmitter,
1149
+ m as Physics,
1150
+ p as PluginRegistry,
1151
+ k as Renderer,
1152
+ D as Scene,
1153
+ A as ScreenShake,
1154
+ E as Sprite,
1155
+ g as SystemRegistry,
1156
+ w as Ticker,
1157
+ _ as Vector2
1158
+ };