dopaminejs 1.2.0 → 2.0.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 (45) hide show
  1. package/dist/dopamine.js +1947 -0
  2. package/dist/dopamine.umd.cjs +45 -0
  3. package/package.json +24 -32
  4. package/src/core/Animator.js +62 -0
  5. package/src/core/Collider.js +50 -0
  6. package/src/core/Component.js +38 -0
  7. package/src/core/DopamineKernel.js +153 -0
  8. package/src/core/EventBus.js +183 -0
  9. package/src/core/Game.js +89 -0
  10. package/src/core/GameObject.js +83 -0
  11. package/src/core/PluginRegistry.js +197 -0
  12. package/src/core/Scene.js +65 -0
  13. package/src/core/Sprite.js +51 -0
  14. package/src/core/SystemRegistry.js +242 -0
  15. package/src/core/Vector2.js +50 -0
  16. package/src/{audio → dopamine/audio}/SoundManager.js +1 -3
  17. package/src/dopamine/components/ParticleEmitter.js +50 -0
  18. package/src/dopamine/components/ScreenShake.js +49 -0
  19. package/src/dopamine/components/index.js +4 -0
  20. package/src/dopamine/effects/WebGLParticleSystem.js +348 -0
  21. package/src/dopamine/index.js +62 -0
  22. package/src/dopamine/utils/TextureGenerator.js +90 -0
  23. package/src/index.js +34 -62
  24. package/src/interfaces/IAudioSystem.js +58 -0
  25. package/src/interfaces/IParticleSystem.js +51 -0
  26. package/src/interfaces/IPhysicsSystem.js +52 -0
  27. package/src/interfaces/ISystem.js +48 -0
  28. package/src/interfaces/index.js +7 -0
  29. package/src/renderer/Renderer.js +37 -0
  30. package/src/systems/Director.js +27 -0
  31. package/src/systems/Input.js +77 -0
  32. package/src/systems/Loader.js +44 -0
  33. package/src/systems/Physics.js +117 -0
  34. package/src/systems/Ticker.js +61 -0
  35. package/ARCHITECTURE.md +0 -71
  36. package/CHANGELOG.md +0 -36
  37. package/README.md +0 -264
  38. package/dist/dopamine.mjs +0 -737
  39. package/dist/dopamine.umd.js +0 -45
  40. /package/src/{core → dopamine/core}/DataService.js +0 -0
  41. /package/src/{core → dopamine/core}/EventEmitter.js +0 -0
  42. /package/src/{core → dopamine/core}/RewardSystem.js +0 -0
  43. /package/src/{effects → dopamine/effects}/ParticleSystem.js +0 -0
  44. /package/src/{ui → dopamine/ui}/GameUI.js +0 -0
  45. /package/src/{ui → dopamine/ui}/dopamine.css +0 -0
@@ -0,0 +1,1947 @@
1
+ class M {
2
+ constructor() {
3
+ this.listeners = /* @__PURE__ */ new Map();
4
+ }
5
+ /**
6
+ * Subscribe to an event
7
+ * @param {string} event - Event name
8
+ * @param {Function} callback - Callback function
9
+ * @returns {Function} Unsubscribe function
10
+ */
11
+ on(t, e) {
12
+ return this.listeners.has(t) || this.listeners.set(t, /* @__PURE__ */ new Set()), this.listeners.get(t).add(e), () => this.off(t, e);
13
+ }
14
+ /**
15
+ * Unsubscribe from an event
16
+ * @param {string} event - Event name
17
+ * @param {Function} callback - Callback function
18
+ */
19
+ off(t, e) {
20
+ this.listeners.has(t) && this.listeners.get(t).delete(e);
21
+ }
22
+ /**
23
+ * Emit an event
24
+ * @param {string} event - Event name
25
+ * @param {*} data - Event data
26
+ */
27
+ emit(t, e) {
28
+ this.listeners.has(t) && this.listeners.get(t).forEach((s) => {
29
+ try {
30
+ s(e);
31
+ } catch (i) {
32
+ console.error(`Error in listener for event '${t}':`, i);
33
+ }
34
+ });
35
+ }
36
+ /**
37
+ * Clear all listeners
38
+ */
39
+ clear() {
40
+ this.listeners.clear();
41
+ }
42
+ }
43
+ class E extends M {
44
+ constructor(t, e = {}) {
45
+ super(), this.dataService = t, this.player = null, this.achievements = { ...this._initAchievements(), ...e.achievements || {} };
46
+ }
47
+ /**
48
+ * Initialize the reward system for current player
49
+ * @returns {Promise<Object>} Player data
50
+ */
51
+ async init() {
52
+ return this.player = await this.dataService.load("player", this._getDefaultPlayer()), this._updateDailyStreak(), await this.save(), this.player;
53
+ }
54
+ /**
55
+ * Get default player data structure
56
+ */
57
+ _getDefaultPlayer() {
58
+ return {
59
+ name: "Player",
60
+ xp: 0,
61
+ level: 1,
62
+ totalGamesPlayed: 0,
63
+ createdAt: Date.now(),
64
+ lastPlayedAt: Date.now(),
65
+ streak: {
66
+ current: 1,
67
+ longest: 1,
68
+ lastPlayDate: this._getTodayDateString()
69
+ },
70
+ achievements: {},
71
+ // achievement_id: { unlockedAt: timestamp, seen: boolean }
72
+ stats: {
73
+ // Dynamic stats object, games will add their own keys
74
+ }
75
+ };
76
+ }
77
+ /**
78
+ * Save player data
79
+ */
80
+ async save() {
81
+ this.player && (this.player.lastPlayedAt = Date.now(), await this.dataService.save("player", this.player));
82
+ }
83
+ /**
84
+ * Add XP and handle level ups
85
+ * @param {number} amount - XP to add
86
+ * @param {string} reason - Why XP was awarded (for notifications)
87
+ * @returns {Object} { leveledUp: boolean, newLevel: number, xpGained: number }
88
+ */
89
+ async addXP(t, e = "") {
90
+ const s = this.player.level;
91
+ this.player.xp += t;
92
+ const i = this._calculateLevel(this.player.xp), r = i > s;
93
+ return r && (this.player.level = i), await this.save(), this.emit("xp_gained", { amount: t, reason: e, leveledUp: r, newLevel: i }), r && this.emit("level_up", { oldLevel: s, newLevel: i }), { leveledUp: r, newLevel: i, xpGained: t };
94
+ }
95
+ /**
96
+ * Calculate level from XP (exponential curve)
97
+ */
98
+ _calculateLevel(t) {
99
+ const e = Math.floor((1 + Math.sqrt(1 + 8 * t / 50)) / 2);
100
+ return Math.max(1, e);
101
+ }
102
+ /**
103
+ * Get XP needed for next level
104
+ */
105
+ getXPForNextLevel() {
106
+ const t = this.player.level + 1, e = 50 * t * (t - 1), s = 50 * this.player.level * (this.player.level - 1);
107
+ return {
108
+ total: e,
109
+ needed: e - this.player.xp,
110
+ progress: (this.player.xp - s) / (e - s)
111
+ };
112
+ }
113
+ /**
114
+ * Record game result and award XP
115
+ * @param {string} gameName - Unique ID for the game
116
+ * @param {Object} result - Game-specific result data
117
+ */
118
+ async recordGame(t, e) {
119
+ this.player.stats[t] || (this.player.stats[t] = { totalPlays: 0, highScore: 0 });
120
+ const s = this.player.stats[t];
121
+ s.totalPlays++, this.player.totalGamesPlayed++, e.score !== void 0 && e.score > s.highScore && (s.highScore = e.score, this.emit("new_high_score", { gameName: t, score: e.score })), Object.keys(e).forEach((r) => {
122
+ r !== "score" && (s[r] = (s[r] || 0) + (typeof e[r] == "number" ? e[r] : 0));
123
+ });
124
+ let i = 10;
125
+ e.score && (i += Math.floor(e.score / 5)), i = Math.floor(i * this.getStreakMultiplier()), await this.addXP(i, `Played ${t}`), await this.checkAchievements(t, e), await this.save();
126
+ }
127
+ /**
128
+ * Check and unlock achievements
129
+ */
130
+ async checkAchievements(t, e) {
131
+ const s = [];
132
+ for (const [i, r] of Object.entries(this.achievements))
133
+ this.player.achievements[i] || r.check(this.player, t, e) && (await this.unlockAchievement(i), s.push(r));
134
+ return s;
135
+ }
136
+ /**
137
+ * Unlock an achievement
138
+ */
139
+ async unlockAchievement(t) {
140
+ const e = this.achievements[t];
141
+ return e ? (this.player.achievements[t] = {
142
+ unlockedAt: Date.now(),
143
+ seen: !1
144
+ }, await this.addXP(e.xp, `Achievement: ${e.name}`), this.emit("achievement_unlocked", e), await this.save(), !0) : !1;
145
+ }
146
+ /**
147
+ * Get all unlocked achievements
148
+ */
149
+ getUnlockedAchievements() {
150
+ return Object.entries(this.player.achievements).map(([t, e]) => ({
151
+ ...this.achievements[t],
152
+ id: t,
153
+ ...e
154
+ }));
155
+ }
156
+ /**
157
+ * Get achievements user hasn't seen the notification for yet
158
+ */
159
+ getUnseenAchievements() {
160
+ return this.getUnlockedAchievements().filter((t) => !t.seen);
161
+ }
162
+ /**
163
+ * Mark achievements as seen
164
+ */
165
+ async markAchievementsSeen(t) {
166
+ for (const e of t)
167
+ this.player.achievements[e] && (this.player.achievements[e].seen = !0);
168
+ await this.save();
169
+ }
170
+ /**
171
+ * Update daily streak
172
+ */
173
+ _updateDailyStreak() {
174
+ const t = this._getTodayDateString(), e = this.player.streak.lastPlayDate;
175
+ if (e === t)
176
+ return;
177
+ const s = this._getYesterdayDateString();
178
+ e === s ? (this.player.streak.current++, this.player.streak.current > this.player.streak.longest && (this.player.streak.longest = this.player.streak.current)) : this.player.streak.current = 1, this.player.streak.lastPlayDate = t;
179
+ }
180
+ /**
181
+ * Get streak multiplier for XP
182
+ */
183
+ getStreakMultiplier() {
184
+ const t = this.player.streak.current;
185
+ return t >= 7 ? 2 : t >= 5 ? 1.5 : t >= 3 ? 1.25 : 1;
186
+ }
187
+ /**
188
+ * Get today's date as YYYY-MM-DD string
189
+ */
190
+ _getTodayDateString() {
191
+ return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
192
+ }
193
+ /**
194
+ * Get yesterday's date as YYYY-MM-DD string
195
+ */
196
+ _getYesterdayDateString() {
197
+ const t = /* @__PURE__ */ new Date();
198
+ return t.setDate(t.getDate() - 1), t.toISOString().split("T")[0];
199
+ }
200
+ /**
201
+ * Initialize achievement definitions
202
+ * Can be extended by games
203
+ */
204
+ _initAchievements() {
205
+ return {
206
+ // Universal achievements
207
+ first_game: {
208
+ name: "First Steps",
209
+ description: "Play your first game",
210
+ icon: "🎮",
211
+ xp: 50,
212
+ check: (t) => t.totalGamesPlayed >= 1
213
+ },
214
+ level_5: {
215
+ name: "Rising Star",
216
+ description: "Reach level 5",
217
+ icon: "⭐",
218
+ xp: 100,
219
+ check: (t) => t.level >= 5
220
+ },
221
+ level_10: {
222
+ name: "Expert Player",
223
+ description: "Reach level 10",
224
+ icon: "🏆",
225
+ xp: 250,
226
+ check: (t) => t.level >= 10
227
+ },
228
+ streak_3: {
229
+ name: "Dedicated",
230
+ description: "Play 3 days in a row",
231
+ icon: "🔥",
232
+ xp: 100,
233
+ check: (t) => t.streak.current >= 3
234
+ },
235
+ streak_7: {
236
+ name: "Unstoppable",
237
+ description: "Play 7 days in a row",
238
+ icon: "💥",
239
+ xp: 300,
240
+ check: (t) => t.streak.current >= 7
241
+ }
242
+ };
243
+ }
244
+ }
245
+ class P {
246
+ constructor(t = {}) {
247
+ this.storage = t.storage || window.localStorage, this.prefix = t.prefix || "dopamine_";
248
+ }
249
+ /**
250
+ * Save data to local storage
251
+ * @param {string} key
252
+ * @param {any} data
253
+ */
254
+ async save(t, e) {
255
+ try {
256
+ const s = JSON.stringify(e);
257
+ return this.storage.setItem(this.prefix + t, s), !0;
258
+ } catch (s) {
259
+ return console.error("Error saving data:", s), !1;
260
+ }
261
+ }
262
+ /**
263
+ * Load data from local storage
264
+ * @param {string} key
265
+ * @param {any} defaultValue
266
+ */
267
+ async load(t, e = null) {
268
+ try {
269
+ const s = this.storage.getItem(this.prefix + t);
270
+ return s ? JSON.parse(s) : e;
271
+ } catch (s) {
272
+ return console.error("Error loading data:", s), e;
273
+ }
274
+ }
275
+ /**
276
+ * Clear specific data
277
+ * @param {string} key
278
+ */
279
+ async clear(t) {
280
+ this.storage.removeItem(this.prefix + t);
281
+ }
282
+ }
283
+ class T {
284
+ constructor(t) {
285
+ this.particleSystem = t, this.container = null, this.xpBar = null, this.levelDisplay = null, this.streakDisplay = null, this.init();
286
+ }
287
+ /**
288
+ * Initialize UI overlay
289
+ */
290
+ init() {
291
+ this.container = document.createElement("div"), this.container.id = "game-ui-overlay", this.container.innerHTML = `
292
+ <div class="game-ui-top-bar">
293
+ <div class="level-badge" id="level-badge">
294
+ <span class="level-label">LVL</span>
295
+ <span class="level-number" id="level-number">1</span>
296
+ </div>
297
+
298
+ <div class="xp-container">
299
+ <div class="xp-bar-bg">
300
+ <div class="xp-bar-fill" id="xp-bar-fill" style="width: 0%"></div>
301
+ </div>
302
+ <div class="xp-text" id="xp-text">0 /100 XP</div>
303
+ </div>
304
+
305
+ <div class="streak-badge" id="streak-badge">
306
+ <span class="streak-icon">🔥</span>
307
+ <span class="streak-number" id="streak-number">1</span>
308
+ </div>
309
+ </div>
310
+ `, document.body.appendChild(this.container), this.xpBar = document.getElementById("xp-bar-fill"), this.xpText = document.getElementById("xp-text"), this.levelDisplay = document.getElementById("level-number"), this.streakDisplay = document.getElementById("streak-number");
311
+ }
312
+ /**
313
+ * Update XP bar
314
+ */
315
+ updateXP(t, e, s) {
316
+ const i = t / s * 100;
317
+ this.xpBar.style.width = `${Math.min(100, i)}%`, this.xpText.textContent = `${t} / ${s} XP`, this.xpBar.style.animation = "none", setTimeout(() => {
318
+ this.xpBar.style.animation = "xp-pulse 0.3s ease-out";
319
+ }, 10);
320
+ }
321
+ /**
322
+ * Update level display
323
+ */
324
+ updateLevel(t) {
325
+ this.levelDisplay.textContent = t;
326
+ const e = document.getElementById("level-badge");
327
+ e.style.animation = "none", setTimeout(() => {
328
+ e.style.animation = "level-pulse 0.5s ease-out";
329
+ }, 10);
330
+ }
331
+ /**
332
+ * Update streak display
333
+ */
334
+ updateStreak(t) {
335
+ this.streakDisplay.textContent = t;
336
+ const e = document.getElementById("streak-badge");
337
+ t >= 7 ? e.style.background = "linear-gradient(135deg, #ff6b00 0%, #ff4400 100%)" : t >= 3 && (e.style.background = "linear-gradient(135deg, #ff8800 0%, #ff6b00 100%)");
338
+ }
339
+ /**
340
+ * Show achievement unlocked popup
341
+ */
342
+ showAchievement(t) {
343
+ const e = document.createElement("div");
344
+ e.className = "achievement-popup", e.innerHTML = `
345
+ <div class="achievement-shine"></div>
346
+ <div class="achievement-content">
347
+ <div class="achievement-icon">${t.icon}</div>
348
+ <div class="achievement-info">
349
+ <div class="achievement-title">Achievement Unlocked!</div>
350
+ <div class="achievement-name">${t.name}</div>
351
+ <div class="achievement-desc">${t.description}</div>
352
+ <div class="achievement-xp">+${t.xp} XP</div>
353
+ </div>
354
+ </div>
355
+ `, document.body.appendChild(e), setTimeout(() => {
356
+ const s = e.getBoundingClientRect();
357
+ this.particleSystem.confetti(s.left + s.width / 2, s.top + s.height / 2, 40);
358
+ }, 300), setTimeout(() => {
359
+ e.style.animation = "slideOut 0.3s ease-in forwards", setTimeout(() => e.remove(), 300);
360
+ }, 4e3);
361
+ }
362
+ /**
363
+ * Show level up screen
364
+ */
365
+ showLevelUp(t, e) {
366
+ const s = document.createElement("div");
367
+ s.className = "level-up-overlay", s.innerHTML = `
368
+ <div class="level-up-content">
369
+ <div class="level-up-title">LEVEL UP!</div>
370
+ <div class="level-up-number">${e}</div>
371
+ <div class="level-up-subtitle">Amazing progress!</div>
372
+ </div>
373
+ `, document.body.appendChild(s), setTimeout(() => {
374
+ for (let i = 0; i < 5; i++)
375
+ setTimeout(() => {
376
+ const r = Math.random() * window.innerWidth, n = Math.random() * window.innerHeight * 0.6;
377
+ this.particleSystem.confetti(r, n, 30);
378
+ }, i * 200);
379
+ }, 300), setTimeout(() => {
380
+ s.style.opacity = "0", setTimeout(() => s.remove(), 500);
381
+ }, 2500);
382
+ }
383
+ /**
384
+ * Show floating text (e.g., "+50 XP", "COMBO!")
385
+ */
386
+ showFloatingText(t, e, s, i = "#ffd700", r = "24px") {
387
+ const n = document.createElement("div");
388
+ n.className = "floating-text", n.textContent = t, n.style.left = e + "px", n.style.top = s + "px", n.style.color = i, n.style.fontSize = r, document.body.appendChild(n), setTimeout(() => n.remove(), 2e3);
389
+ }
390
+ /**
391
+ * Show combo multiplier
392
+ */
393
+ showCombo(t, e, s) {
394
+ this.showFloatingText(`${t}× COMBO!`, e, s, "#ff6b6b", "32px"), this.particleSystem.fire(e, s, 20);
395
+ }
396
+ /**
397
+ * Show "near miss"indicator
398
+ */
399
+ showNearMiss(t, e) {
400
+ this.showFloatingText("CLOSE CALL!", t, e, "#45b7d1", "20px"), this.particleSystem.sparkle(t, e, 15, "#45b7d1");
401
+ }
402
+ /**
403
+ * Show lucky moment (random multiplier)
404
+ */
405
+ showLuckyMoment(t, e, s) {
406
+ this.showFloatingText(`LUCKY ${t}×!`, e, s, "#ffd700", "36px"), this.particleSystem.starBurst(e, s, 12), this.particleSystem.confetti(e, s, 25);
407
+ }
408
+ /**
409
+ * Show generic notification
410
+ */
411
+ showNotification(t, e = "normal") {
412
+ const s = window.innerWidth / 2, i = window.innerHeight / 3;
413
+ e === "legendary" ? this.showLuckyMoment(t, s, i) : e === "rare" ? this.showCombo(t, s, i) : this.showFloatingText(t, s, i, "#fff", "24px");
414
+ }
415
+ /**
416
+ * Show Game Over Summary
417
+ */
418
+ showSummary(t) {
419
+ const e = document.createElement("div");
420
+ e.className = "message-box", e.style.display = "flex", e.style.position = "absolute", e.style.top = "50%", e.style.left = "50%", e.style.transform = "translate(-50%, -50%)", e.style.zIndex = "1000";
421
+ let s = "";
422
+ t.metrics && (s = Object.entries(t.metrics).map(([i, r]) => `<div style="display:flex;justify-content:space-between;width:100%"><span>${i}:</span> <strong>${r}</strong></div>`).join("")), e.innerHTML = `
423
+ <h1>Game Over</h1>
424
+ <div style="font-size: 3rem; font-weight: bold; color: #e06020; margin: 10px 0;">${t.score}</div>
425
+ <div style="width: 100%; margin-bottom: 20px;">
426
+ ${s}
427
+ </div>
428
+ <button class="btn" onclick="location.reload()">Play Again</button>
429
+ <button class="btn" onclick="window.location.href='index.html'" style="margin-top: 10px; background: #543847;">Exit</button>
430
+ `, document.body.appendChild(e);
431
+ }
432
+ }
433
+ class C {
434
+ constructor(t = {}) {
435
+ this.container = t.container ? typeof t.container == "string" ? document.querySelector(t.container) : t.container : document.body, this.canvasId = t.canvasId || "dopamine-particle-canvas", this.canvas = document.getElementById(this.canvasId), this.canvas || (this.canvas = document.createElement("canvas"), this.canvas.id = this.canvasId, this.canvas.style.pointerEvents = "none", this.canvas.style.zIndex = t.zIndex || "9999", this.container === document.body ? (this.canvas.style.position = "fixed", this.canvas.style.top = "0", this.canvas.style.left = "0") : (this.canvas.style.position = "absolute", this.canvas.style.top = "0", this.canvas.style.left = "0", window.getComputedStyle(this.container).position === "static" && (this.container.style.position = "relative")), this.container.appendChild(this.canvas)), this.ctx = this.canvas.getContext("2d"), this.particles = [], this.pool = [], this.sprites = /* @__PURE__ */ new Map(), this.customEffects = /* @__PURE__ */ new Map(), this.isAnimating = !1, this._resize(), window.ResizeObserver && this.container !== document.body ? new ResizeObserver(() => this._resize()).observe(this.container) : window.addEventListener("resize", () => this._resize());
436
+ }
437
+ _resize() {
438
+ this.container === document.body ? (this.canvas.width = window.innerWidth, this.canvas.height = window.innerHeight) : (this.canvas.width = this.container.clientWidth, this.canvas.height = this.container.clientHeight);
439
+ }
440
+ /**
441
+ * Register a custom sprite image
442
+ * @param {string} key
443
+ * @param {string} url
444
+ */
445
+ registerSprite(t, e) {
446
+ const s = new Image();
447
+ s.src = e, this.sprites.set(t, s);
448
+ }
449
+ /**
450
+ * Register a custom effect
451
+ * @param {string} name
452
+ * @param {Function} callback - (x, y, ...args) => void
453
+ */
454
+ registerEffect(t, e) {
455
+ this.customEffects.set(t, e);
456
+ }
457
+ /**
458
+ * Play a registered effect
459
+ */
460
+ play(t, e, s, ...i) {
461
+ const r = this.customEffects.get(t);
462
+ r ? r(e, s, ...i) : console.warn(`[DopamineJS] Effect '${t}' not found.`);
463
+ }
464
+ /**
465
+ * Emit particles with custom config
466
+ * @param {Object} config
467
+ */
468
+ emit(t) {
469
+ const {
470
+ x: e,
471
+ y: s,
472
+ count: i = 10,
473
+ speed: r = 5,
474
+ gravity: n = 0.1,
475
+ decay: o = 0.02,
476
+ life: h = 1,
477
+ color: c = "#ffd700",
478
+ sprite: d = null,
479
+ spread: u = Math.PI * 2,
480
+ angle: m = 0,
481
+ size: f = 5
482
+ } = t;
483
+ for (let v = 0; v < i; v++) {
484
+ const l = this._getParticle(), x = m + (Math.random() - 0.5) * u, w = Math.random() * r;
485
+ l.x = e, l.y = s, l.vx = Math.cos(x) * w, l.vy = Math.sin(x) * w, l.life = h, l.decay = o * (0.8 + Math.random() * 0.4), l.gravity = n, l.color = Array.isArray(c) ? c[Math.floor(Math.random() * c.length)] : c, l.size = f * (0.8 + Math.random() * 0.4), l.sprite = d, l.rotation = Math.random() * Math.PI * 2, l.rotationSpeed = (Math.random() - 0.5) * 0.2, this.particles.push(l);
486
+ }
487
+ this._startAnimation();
488
+ }
489
+ _getParticle() {
490
+ return this.pool.length > 0 ? this.pool.pop() : {};
491
+ }
492
+ _recycleParticle(t) {
493
+ this.pool.push(t);
494
+ }
495
+ // --- Built-in Effects ---
496
+ confetti(t, e, s = 50) {
497
+ const i = ["#ff6b6b", "#4ecdc4", "#45b7d1", "#f7dc6f", "#c06c84", "#6c5ce7"];
498
+ this.emit({
499
+ x: t,
500
+ y: e,
501
+ count: s,
502
+ color: i,
503
+ speed: 10,
504
+ gravity: 0.3,
505
+ decay: 0.015,
506
+ size: 8,
507
+ spread: Math.PI * 2
508
+ });
509
+ }
510
+ coinShower(t, e, s = 10) {
511
+ this.emit({
512
+ x: t,
513
+ y: e,
514
+ count: s,
515
+ color: "#ffd700",
516
+ speed: 4,
517
+ gravity: 0.4,
518
+ decay: 0.012,
519
+ size: 12,
520
+ angle: -Math.PI / 2,
521
+ // Upwards initially
522
+ spread: Math.PI / 2
523
+ });
524
+ }
525
+ sparkle(t, e, s = 20, i = "#ffd700") {
526
+ this.emit({
527
+ x: t,
528
+ y: e,
529
+ count: s,
530
+ color: i,
531
+ speed: 3,
532
+ gravity: -0.05,
533
+ // Float up
534
+ decay: 0.03,
535
+ size: 3
536
+ });
537
+ }
538
+ fire(t, e, s = 15) {
539
+ const i = ["#ff6b00", "#ff8800", "#ffaa00", "#ff4400"];
540
+ this.emit({
541
+ x: t,
542
+ y: e,
543
+ count: s,
544
+ color: i,
545
+ speed: 3,
546
+ gravity: -0.2,
547
+ decay: 0.04,
548
+ size: 5,
549
+ angle: -Math.PI / 2,
550
+ spread: Math.PI / 4
551
+ });
552
+ }
553
+ starBurst(t, e, s = 8) {
554
+ for (let i = 0; i < s; i++) {
555
+ const r = this._getParticle(), n = Math.PI * 2 / s * i, o = 4;
556
+ r.x = t, r.y = e, r.vx = Math.cos(n) * o, r.vy = Math.sin(n) * o, r.life = 1, r.decay = 0.03, r.gravity = 0, r.color = "#ffffff", r.size = 15, r.sprite = null, r.type = "star", r.rotation = n, r.rotationSpeed = 0, this.particles.push(r);
557
+ }
558
+ this._startAnimation();
559
+ }
560
+ _startAnimation() {
561
+ this.isAnimating || (this.isAnimating = !0, this._animate());
562
+ }
563
+ _animate() {
564
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
565
+ for (let t = this.particles.length - 1; t >= 0; t--) {
566
+ const e = this.particles[t];
567
+ if (e.vy += e.gravity, e.x += e.vx, e.y += e.vy, e.life -= e.decay, e.rotationSpeed && (e.rotation += e.rotationSpeed), e.life <= 0) {
568
+ this._recycleParticle(e), this.particles.splice(t, 1);
569
+ continue;
570
+ }
571
+ if (this.ctx.save(), this.ctx.globalAlpha = e.life, this.ctx.translate(e.x, e.y), this.ctx.rotate(e.rotation), e.sprite && this.sprites.has(e.sprite)) {
572
+ const s = this.sprites.get(e.sprite);
573
+ s.complete && this.ctx.drawImage(s, -e.size / 2, -e.size / 2, e.size, e.size);
574
+ } else e.type === "star" ? (this.ctx.fillStyle = e.color, this._drawStarPath(0, 0, 5, e.size, e.size / 2), this.ctx.fill()) : (this.ctx.fillStyle = e.color, this.ctx.beginPath(), this.ctx.arc(0, 0, e.size, 0, Math.PI * 2), this.ctx.fill());
575
+ this.ctx.restore();
576
+ }
577
+ this.particles.length > 0 ? requestAnimationFrame(() => this._animate()) : this.isAnimating = !1;
578
+ }
579
+ _drawStarPath(t, e, s, i, r) {
580
+ let n = Math.PI / 2 * 3, o = t, h = e;
581
+ const c = Math.PI / s;
582
+ this.ctx.beginPath(), this.ctx.moveTo(t, e - i);
583
+ for (let d = 0; d < s; d++)
584
+ o = t + Math.cos(n) * i, h = e + Math.sin(n) * i, this.ctx.lineTo(o, h), n += c, o = t + Math.cos(n) * r, h = e + Math.sin(n) * r, this.ctx.lineTo(o, h), n += c;
585
+ this.ctx.lineTo(t, e - i), this.ctx.closePath();
586
+ }
587
+ clear() {
588
+ this.particles.forEach((t) => this._recycleParticle(t)), this.particles = [], this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
589
+ }
590
+ }
591
+ class I {
592
+ constructor(t = {}) {
593
+ this.audioContext = null, this.storageKey = t.storageKey || "dopamineSoundsMuted", this.muted = localStorage.getItem(this.storageKey) === "true", this.assets = /* @__PURE__ */ new Map(), this.customSounds = t.customSounds || {}, Object.keys(this.customSounds).length > 0 && this.preloadSounds(this.customSounds);
594
+ }
595
+ /**
596
+ * Initialize Audio Context (must be called after user interaction)
597
+ */
598
+ initAudio() {
599
+ if (!this.audioContext) {
600
+ const t = window.AudioContext || window.webkitAudioContext;
601
+ this.audioContext = new t();
602
+ }
603
+ this.audioContext.state === "suspended" && this.audioContext.resume();
604
+ }
605
+ /**
606
+ * Toggle mute state
607
+ * @returns {boolean} New mute state
608
+ */
609
+ toggleMute() {
610
+ return this.muted = !this.muted, localStorage.setItem(this.storageKey, this.muted), this.muted;
611
+ }
612
+ /**
613
+ * Register a custom sound URL
614
+ * @param {string} key - Unique identifier (e.g., 'jump', 'win')
615
+ * @param {string} url - Path to audio file
616
+ */
617
+ registerSound(t, e) {
618
+ this.customSounds[t] = e, this.audioContext && this.loadSound(t, e);
619
+ }
620
+ /**
621
+ * Preload multiple sounds
622
+ * @param {Object} soundMap - { key: url }
623
+ */
624
+ async preloadSounds(t) {
625
+ const e = Object.entries(t).map(([s, i]) => this.loadSound(s, i));
626
+ await Promise.allSettled(e);
627
+ }
628
+ /**
629
+ * Load and decode a sound file
630
+ */
631
+ async loadSound(t, e) {
632
+ try {
633
+ if (!this.audioContext)
634
+ return;
635
+ const i = await (await fetch(e)).arrayBuffer(), r = await this.audioContext.decodeAudioData(i);
636
+ this.assets.set(t, r);
637
+ } catch (s) {
638
+ console.warn(`[DopamineJS] Failed to load sound '${t}':`, s);
639
+ }
640
+ }
641
+ /**
642
+ * Play a sound by key (custom) or fallback to synth
643
+ * @param {string} key - 'jump', 'score', 'success', etc.
644
+ */
645
+ async play(t) {
646
+ if (this.muted) return;
647
+ if (this.initAudio(), this.assets.has(t)) {
648
+ this._playBuffer(this.assets.get(t));
649
+ return;
650
+ }
651
+ if (this.customSounds[t] && !this.assets.has(t) && (await this.loadSound(t, this.customSounds[t]), this.assets.has(t))) {
652
+ this._playBuffer(this.assets.get(t));
653
+ return;
654
+ }
655
+ const e = {
656
+ jump: () => this.playJump(!0),
657
+ score: () => this.playScore(!0),
658
+ success: () => this.playSuccess(!0),
659
+ click: () => this.playClick(!0),
660
+ error: () => this.playError(!0),
661
+ gameover: () => this.playGameOver(!0)
662
+ };
663
+ e[t] && e[t]();
664
+ }
665
+ _playBuffer(t) {
666
+ const e = this.audioContext.createBufferSource();
667
+ e.buffer = t, e.connect(this.audioContext.destination), e.start(0);
668
+ }
669
+ /**
670
+ * Play a synthesized tone
671
+ */
672
+ playTone(t, e, s = "sine", i = 0.3) {
673
+ if (this.muted) return;
674
+ this.initAudio();
675
+ const r = this.audioContext.createOscillator(), n = this.audioContext.createGain();
676
+ r.connect(n), n.connect(this.audioContext.destination), r.frequency.value = t, r.type = s, n.gain.setValueAtTime(i, this.audioContext.currentTime), n.gain.exponentialRampToValueAtTime(0.01, this.audioContext.currentTime + e), r.start(this.audioContext.currentTime), r.stop(this.audioContext.currentTime + e);
677
+ }
678
+ // --- Presets (Synth) ---
679
+ playJump(t = !1) {
680
+ if (!t && this.assets.has("jump")) return this.play("jump");
681
+ this.playTone(400, 0.1, "square", 0.2);
682
+ }
683
+ playScore(t = !1) {
684
+ if (!t && this.assets.has("score")) return this.play("score");
685
+ this.playTone(800, 0.15, "sine", 0.25), setTimeout(() => this.playTone(1e3, 0.15, "sine", 0.25), 50);
686
+ }
687
+ playGameOver(t = !1) {
688
+ if (!t && this.assets.has("gameover")) return this.play("gameover");
689
+ this.playTone(300, 0.2, "sawtooth", 0.3), setTimeout(() => this.playTone(200, 0.2, "sawtooth", 0.3), 100), setTimeout(() => this.playTone(150, 0.3, "sawtooth", 0.3), 200);
690
+ }
691
+ playClick(t = !1) {
692
+ if (!t && this.assets.has("click")) return this.play("click");
693
+ this.playTone(600, 0.05, "sine", 0.1);
694
+ }
695
+ playSuccess(t = !1) {
696
+ if (!t && this.assets.has("success")) return this.play("success");
697
+ this.playTone(600, 0.1, "sine", 0.2), setTimeout(() => this.playTone(800, 0.2, "sine", 0.2), 100);
698
+ }
699
+ playError(t = !1) {
700
+ if (!t && this.assets.has("error")) return this.play("error");
701
+ this.playTone(200, 0.2, "sawtooth", 0.3);
702
+ }
703
+ }
704
+ class $ {
705
+ constructor(t = {}) {
706
+ this.config = t, this.dataService = new P(t.data || {}), this.rewardSystem = new E(this.dataService, t.rewards || {}), this.soundManager = new I(t.sound || {}), this.particleSystem = new C(t.particles || {}), this.gameUI = new T(this.particleSystem), this._bindEvents();
707
+ }
708
+ async init() {
709
+ return await this.rewardSystem.init(), console.log("DopamineJS Initialized 🚀"), {
710
+ rewardSystem: this.rewardSystem,
711
+ gameUI: this.gameUI,
712
+ particleSystem: this.particleSystem,
713
+ soundManager: this.soundManager
714
+ };
715
+ }
716
+ _bindEvents() {
717
+ this.rewardSystem.on("xp_gained", (t) => {
718
+ const { total: e, needed: s } = this.rewardSystem.getXPForNextLevel();
719
+ this.gameUI.updateXP(this.rewardSystem.player.xp, s, e), t.xpGained > 0;
720
+ }), this.rewardSystem.on("level_up", (t) => {
721
+ this.gameUI.updateLevel(t.newLevel), this.gameUI.showLevelUp(t.oldLevel, t.newLevel);
722
+ }), this.rewardSystem.on("achievement_unlocked", (t) => {
723
+ this.gameUI.showAchievement(t);
724
+ }), this.rewardSystem.on("new_high_score", (t) => {
725
+ this.gameUI.showNotification(`New High Score: ${t.score}!`, "legendary"), this.soundManager.playSuccess();
726
+ });
727
+ }
728
+ }
729
+ class p {
730
+ constructor() {
731
+ this._listeners = /* @__PURE__ */ new Map(), this._onceListeners = /* @__PURE__ */ new Map(), this._sortedCache = /* @__PURE__ */ new Map();
732
+ }
733
+ /**
734
+ * Register an event listener
735
+ * @param {string} event - Event name
736
+ * @param {Function} callback - Callback function
737
+ * @param {number} priority - Higher priority = called first (default: 0)
738
+ * @returns {EventBus} - For chaining
739
+ */
740
+ on(t, e, s = 0) {
741
+ return this._listeners.has(t) || this._listeners.set(t, /* @__PURE__ */ new Set()), this._listeners.get(t).add({ callback: e, priority: s }), this._sortedCache.delete(t), this;
742
+ }
743
+ /**
744
+ * Register a one-time event listener
745
+ * @param {string} event - Event name
746
+ * @param {Function} callback - Callback function
747
+ * @returns {EventBus} - For chaining
748
+ */
749
+ once(t, e) {
750
+ return this._onceListeners.has(t) || this._onceListeners.set(t, /* @__PURE__ */ new Set()), this._onceListeners.get(t).add(e), this;
751
+ }
752
+ /**
753
+ * Remove an event listener
754
+ * @param {string} event - Event name
755
+ * @param {Function} callback - Callback function to remove
756
+ * @returns {EventBus} - For chaining
757
+ */
758
+ off(t, e) {
759
+ const s = this._listeners.get(t);
760
+ if (s) {
761
+ for (const r of s)
762
+ if (r.callback === e) {
763
+ s.delete(r), this._sortedCache.delete(t);
764
+ break;
765
+ }
766
+ }
767
+ const i = this._onceListeners.get(t);
768
+ return i && i.delete(e), this;
769
+ }
770
+ /**
771
+ * Emit an event to all registered listeners
772
+ * @param {string} event - Event name
773
+ * @param {*} data - Event data
774
+ */
775
+ emit(t, e) {
776
+ const s = this._getSortedListeners(t);
777
+ for (let r = 0; r < s.length; r++)
778
+ s[r].callback(e);
779
+ const i = this._onceListeners.get(t);
780
+ if (i && i.size > 0) {
781
+ for (const r of i)
782
+ r(e);
783
+ i.clear();
784
+ }
785
+ }
786
+ /**
787
+ * Get sorted listeners for an event (with caching)
788
+ * @private
789
+ */
790
+ _getSortedListeners(t) {
791
+ if (this._sortedCache.has(t))
792
+ return this._sortedCache.get(t);
793
+ const e = this._listeners.get(t);
794
+ if (!e || e.size === 0)
795
+ return [];
796
+ const s = Array.from(e).sort((i, r) => r.priority - i.priority);
797
+ return this._sortedCache.set(t, s), s;
798
+ }
799
+ /**
800
+ * Remove all listeners for an event, or all events if no event specified
801
+ * @param {string} [event] - Event name (optional)
802
+ */
803
+ clear(t) {
804
+ t ? (this._listeners.delete(t), this._onceListeners.delete(t), this._sortedCache.delete(t)) : (this._listeners.clear(), this._onceListeners.clear(), this._sortedCache.clear());
805
+ }
806
+ /**
807
+ * Check if an event has listeners
808
+ * @param {string} event - Event name
809
+ * @returns {boolean}
810
+ */
811
+ hasListeners(t) {
812
+ const e = this._listeners.get(t), s = this._onceListeners.get(t);
813
+ return e && e.size > 0 || s && s.size > 0;
814
+ }
815
+ }
816
+ p.Events = {
817
+ // Core game loop events
818
+ TICK: "tick",
819
+ FIXED_UPDATE: "fixed_update",
820
+ RENDER: "render",
821
+ // Physics events
822
+ COLLISION_ENTER: "collision_enter",
823
+ COLLISION_EXIT: "collision_exit",
824
+ // Dopamine events (from RewardSystem)
825
+ XP_GAINED: "xp_gained",
826
+ LEVEL_UP: "level_up",
827
+ ACHIEVEMENT_UNLOCKED: "achievement_unlocked",
828
+ NEW_HIGH_SCORE: "new_high_score",
829
+ STREAK_UPDATED: "streak_updated",
830
+ // System lifecycle events
831
+ SYSTEM_REGISTERED: "system_registered",
832
+ SYSTEM_UNREGISTERED: "system_unregistered",
833
+ PLUGIN_LOADED: "plugin_loaded",
834
+ PLUGIN_UNLOADED: "plugin_unloaded"
835
+ };
836
+ class O {
837
+ constructor(t) {
838
+ this.kernel = t, this._systems = /* @__PURE__ */ new Map(), this._updateOrder = [], this._fixedUpdateOrder = [], this._needsRecompute = !1;
839
+ }
840
+ /**
841
+ * Register a system
842
+ * @param {string} name - Unique system name
843
+ * @param {Object} system - System instance (must implement ISystem interface)
844
+ * @param {Object} options - Configuration options
845
+ * @param {number} options.priority - Update priority (higher = earlier, default: 0)
846
+ * @param {string[]} options.dependencies - System names this depends on
847
+ * @returns {SystemRegistry} - For chaining
848
+ */
849
+ register(t, e, { priority: s = 0, dependencies: i = [] } = {}) {
850
+ this._systems.has(t) && (console.warn(`[SystemRegistry] System "${t}" already registered. Replacing.`), this.unregister(t));
851
+ for (const r of i)
852
+ this._systems.has(r) || console.warn(`[SystemRegistry] Dependency "${r}" not found for system "${t}"`);
853
+ return this._systems.set(t, {
854
+ system: e,
855
+ priority: s,
856
+ dependencies: i
857
+ }), e.init && e.init(this.kernel), this._needsRecompute = !0, this.kernel.events.emit("system_registered", { name: t, system: e }), this;
858
+ }
859
+ /**
860
+ * Get a system by name
861
+ * @param {string} name - System name
862
+ * @returns {Object|undefined} - System instance
863
+ */
864
+ get(t) {
865
+ const e = this._systems.get(t);
866
+ return e ? e.system : void 0;
867
+ }
868
+ /**
869
+ * Check if a system is registered
870
+ * @param {string} name - System name
871
+ * @returns {boolean}
872
+ */
873
+ has(t) {
874
+ return this._systems.has(t);
875
+ }
876
+ /**
877
+ * Unregister a system
878
+ * @param {string} name - System name
879
+ * @returns {boolean} - True if system was found and removed
880
+ */
881
+ unregister(t) {
882
+ const e = this._systems.get(t);
883
+ return e ? (e.system.destroy && e.system.destroy(), this._systems.delete(t), this._needsRecompute = !0, this.kernel.events.emit("system_unregistered", { name: t }), !0) : !1;
884
+ }
885
+ /**
886
+ * Update all systems (called each frame)
887
+ * @param {number} dt - Delta time in seconds
888
+ */
889
+ update(t) {
890
+ this._needsRecompute && this._recomputeUpdateOrder();
891
+ for (let e = 0; e < this._updateOrder.length; e++) {
892
+ const s = this._updateOrder[e];
893
+ s.update && s.update(t);
894
+ }
895
+ }
896
+ /**
897
+ * Fixed update for physics (called at fixed timestep)
898
+ * @param {number} dt - Fixed delta time
899
+ */
900
+ fixedUpdate(t) {
901
+ this._needsRecompute && this._recomputeUpdateOrder();
902
+ for (let e = 0; e < this._fixedUpdateOrder.length; e++) {
903
+ const s = this._fixedUpdateOrder[e];
904
+ s.fixedUpdate && s.fixedUpdate(t);
905
+ }
906
+ }
907
+ /**
908
+ * Recompute the update order based on dependencies and priorities
909
+ * Uses topological sort for dependencies
910
+ * @private
911
+ */
912
+ _recomputeUpdateOrder() {
913
+ const t = Array.from(this._systems.entries()), e = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map();
914
+ for (const [n, o] of t)
915
+ e.set(n, o.dependencies), s.set(n, 0);
916
+ for (const [n, o] of e)
917
+ for (const h of o)
918
+ s.has(h) && s.set(n, s.get(n) + 1);
919
+ const i = [], r = [];
920
+ for (const [n, o] of s)
921
+ o === 0 && i.push(n);
922
+ for (; i.length > 0; ) {
923
+ i.sort((o, h) => {
924
+ const c = this._systems.get(o).priority;
925
+ return this._systems.get(h).priority - c;
926
+ });
927
+ const n = i.shift();
928
+ r.push(n);
929
+ for (const [o, h] of e)
930
+ if (h.includes(n)) {
931
+ const c = s.get(o) - 1;
932
+ s.set(o, c), c === 0 && i.push(o);
933
+ }
934
+ }
935
+ r.length !== t.length && (console.error("[SystemRegistry] Circular dependency detected in systems!"), r.length = 0, r.push(...t.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;
936
+ }
937
+ /**
938
+ * Get all registered system names
939
+ * @returns {string[]}
940
+ */
941
+ getSystemNames() {
942
+ return Array.from(this._systems.keys());
943
+ }
944
+ /**
945
+ * Clear all systems
946
+ */
947
+ clear() {
948
+ for (const t of this._systems.keys())
949
+ this.unregister(t);
950
+ }
951
+ }
952
+ class A {
953
+ constructor(t) {
954
+ this.kernel = t, this._plugins = /* @__PURE__ */ new Map(), this._loadOrder = [];
955
+ }
956
+ /**
957
+ * Register and initialize a plugin
958
+ *
959
+ * Plugin interface:
960
+ * {
961
+ * name: string,
962
+ * version?: string,
963
+ * init(kernel): void,
964
+ * destroy?(): void
965
+ * }
966
+ *
967
+ * @param {Object} plugin - Plugin instance
968
+ * @returns {PluginRegistry} - For chaining
969
+ */
970
+ use(t) {
971
+ if (!t.name)
972
+ throw new Error("[PluginRegistry] Plugin must have a name");
973
+ if (this._plugins.has(t.name))
974
+ return console.warn(`[PluginRegistry] Plugin "${t.name}" already registered. Skipping.`), this;
975
+ if (!t.init || typeof t.init != "function")
976
+ throw new Error(`[PluginRegistry] Plugin "${t.name}" must have an init() method`);
977
+ this._plugins.set(t.name, t), this._loadOrder.push(t.name);
978
+ try {
979
+ t.init(this.kernel), this.kernel.events.emit("plugin_loaded", {
980
+ name: t.name,
981
+ version: t.version
982
+ }), console.log(`[PluginRegistry] Loaded plugin: ${t.name}${t.version ? ` v${t.version}` : ""}`);
983
+ } catch (e) {
984
+ throw console.error(`[PluginRegistry] Failed to initialize plugin "${t.name}":`, e), this._plugins.delete(t.name), this._loadOrder.pop(), e;
985
+ }
986
+ return this;
987
+ }
988
+ /**
989
+ * Register and initialize a plugin asynchronously
990
+ * Useful for plugins that need to load external resources
991
+ *
992
+ * @param {Object} plugin - Plugin instance with async init
993
+ * @returns {Promise<PluginRegistry>} - For chaining
994
+ */
995
+ async useAsync(t) {
996
+ if (!t.name)
997
+ throw new Error("[PluginRegistry] Plugin must have a name");
998
+ if (this._plugins.has(t.name))
999
+ return console.warn(`[PluginRegistry] Plugin "${t.name}" already registered. Skipping.`), this;
1000
+ if (!t.init || typeof t.init != "function")
1001
+ throw new Error(`[PluginRegistry] Plugin "${t.name}" must have an init() method`);
1002
+ this._plugins.set(t.name, t), this._loadOrder.push(t.name);
1003
+ try {
1004
+ await t.init(this.kernel), this.kernel.events.emit("plugin_loaded", {
1005
+ name: t.name,
1006
+ version: t.version
1007
+ }), console.log(`[PluginRegistry] Loaded plugin: ${t.name}${t.version ? ` v${t.version}` : ""}`);
1008
+ } catch (e) {
1009
+ throw console.error(`[PluginRegistry] Failed to initialize plugin "${t.name}":`, e), this._plugins.delete(t.name), this._loadOrder.pop(), e;
1010
+ }
1011
+ return this;
1012
+ }
1013
+ /**
1014
+ * Remove a plugin
1015
+ * @param {string} name - Plugin name
1016
+ * @returns {boolean} - True if plugin was found and removed
1017
+ */
1018
+ remove(t) {
1019
+ const e = this._plugins.get(t);
1020
+ if (!e)
1021
+ return !1;
1022
+ if (e.destroy)
1023
+ try {
1024
+ e.destroy();
1025
+ } catch (i) {
1026
+ console.error(`[PluginRegistry] Error destroying plugin "${t}":`, i);
1027
+ }
1028
+ this._plugins.delete(t);
1029
+ const s = this._loadOrder.indexOf(t);
1030
+ return s > -1 && this._loadOrder.splice(s, 1), this.kernel.events.emit("plugin_unloaded", { name: t }), !0;
1031
+ }
1032
+ /**
1033
+ * Get a plugin by name
1034
+ * @param {string} name - Plugin name
1035
+ * @returns {Object|undefined} - Plugin instance
1036
+ */
1037
+ get(t) {
1038
+ return this._plugins.get(t);
1039
+ }
1040
+ /**
1041
+ * Check if a plugin is registered
1042
+ * @param {string} name - Plugin name
1043
+ * @returns {boolean}
1044
+ */
1045
+ has(t) {
1046
+ return this._plugins.has(t);
1047
+ }
1048
+ /**
1049
+ * Get all registered plugin names
1050
+ * @returns {string[]}
1051
+ */
1052
+ getPluginNames() {
1053
+ return Array.from(this._plugins.keys());
1054
+ }
1055
+ /**
1056
+ * Get plugin load order
1057
+ * @returns {string[]}
1058
+ */
1059
+ getLoadOrder() {
1060
+ return [...this._loadOrder];
1061
+ }
1062
+ /**
1063
+ * Clear all plugins (in reverse load order)
1064
+ */
1065
+ clear() {
1066
+ for (let t = this._loadOrder.length - 1; t >= 0; t--)
1067
+ this.remove(this._loadOrder[t]);
1068
+ }
1069
+ }
1070
+ class D {
1071
+ constructor(t = {}) {
1072
+ this.width = t.width || 800, this.height = t.height || 600, this.backgroundColor = t.backgroundColor || "#000000", this.canvas = document.createElement("canvas"), this.canvas.width = this.width, this.canvas.height = this.height, this.ctx = this.canvas.getContext("2d"), (t.parent ? typeof t.parent == "string" ? document.querySelector(t.parent) : t.parent : document.body).appendChild(this.canvas);
1073
+ }
1074
+ /**
1075
+ * Clear the canvas.
1076
+ */
1077
+ clear() {
1078
+ this.ctx.fillStyle = this.backgroundColor, this.ctx.fillRect(0, 0, this.width, this.height);
1079
+ }
1080
+ resize(t, e) {
1081
+ this.width = t, this.height = e, this.canvas.width = t, this.canvas.height = e;
1082
+ }
1083
+ }
1084
+ class L {
1085
+ constructor() {
1086
+ this.running = !1, this.lastTime = 0, this.callbacks = /* @__PURE__ */ new Set(), this._tick = this._tick.bind(this);
1087
+ }
1088
+ /**
1089
+ * Add a callback to the loop.
1090
+ * @param {Function} callback - Function to call each frame (receives dt).
1091
+ */
1092
+ add(t) {
1093
+ this.callbacks.add(t);
1094
+ }
1095
+ /**
1096
+ * Remove a callback from the loop.
1097
+ * @param {Function} callback
1098
+ */
1099
+ remove(t) {
1100
+ this.callbacks.delete(t);
1101
+ }
1102
+ /**
1103
+ * Start the loop.
1104
+ */
1105
+ start() {
1106
+ this.running || (this.running = !0, this.lastTime = performance.now(), requestAnimationFrame(this._tick));
1107
+ }
1108
+ /**
1109
+ * Stop the loop.
1110
+ */
1111
+ stop() {
1112
+ this.running = !1;
1113
+ }
1114
+ _tick(t) {
1115
+ if (!this.running) return;
1116
+ const e = (t - this.lastTime) / 1e3;
1117
+ this.lastTime = t;
1118
+ const s = Math.min(e, 0.1);
1119
+ for (const i of this.callbacks)
1120
+ i(s);
1121
+ requestAnimationFrame(this._tick);
1122
+ }
1123
+ }
1124
+ class y {
1125
+ constructor() {
1126
+ this.gameObject = null, this.kernel = null;
1127
+ }
1128
+ /**
1129
+ * Access systems through kernel (no more globals!)
1130
+ */
1131
+ get physics() {
1132
+ var t;
1133
+ return (t = this.kernel) == null ? void 0 : t.systems.get("physics");
1134
+ }
1135
+ get input() {
1136
+ var t;
1137
+ return (t = this.kernel) == null ? void 0 : t.systems.get("input");
1138
+ }
1139
+ get loader() {
1140
+ var t;
1141
+ return (t = this.kernel) == null ? void 0 : t.systems.get("loader");
1142
+ }
1143
+ get events() {
1144
+ var t;
1145
+ return (t = this.kernel) == null ? void 0 : t.events;
1146
+ }
1147
+ onAttach() {
1148
+ }
1149
+ onDetach() {
1150
+ }
1151
+ update(t) {
1152
+ }
1153
+ render(t) {
1154
+ }
1155
+ }
1156
+ class B extends y {
1157
+ constructor(t = "box", e = 50, s = 50, i = 25) {
1158
+ super(), this.type = t, this.width = e, this.height = s, this.radius = i, this.tag = null;
1159
+ }
1160
+ onAttach() {
1161
+ super.onAttach(), this.physics && this.physics.add(this);
1162
+ }
1163
+ onDetach() {
1164
+ super.onDetach(), this.physics && this.physics.remove(this);
1165
+ }
1166
+ getBounds() {
1167
+ const t = this.gameObject;
1168
+ if (this.type === "box")
1169
+ return {
1170
+ left: t.x - this.width / 2,
1171
+ right: t.x + this.width / 2,
1172
+ top: t.y - this.height / 2,
1173
+ bottom: t.y + this.height / 2
1174
+ };
1175
+ if (this.type === "circle")
1176
+ return {
1177
+ x: t.x,
1178
+ y: t.y,
1179
+ radius: this.radius
1180
+ };
1181
+ }
1182
+ }
1183
+ class _ {
1184
+ constructor() {
1185
+ this.colliders = [], this.kernel = null;
1186
+ }
1187
+ /**
1188
+ * ISystem interface - called when registered
1189
+ */
1190
+ init(t) {
1191
+ this.kernel = t;
1192
+ }
1193
+ /**
1194
+ * ISystem interface - called at fixed timestep
1195
+ */
1196
+ fixedUpdate(t) {
1197
+ this.step();
1198
+ }
1199
+ /**
1200
+ * ISystem interface - cleanup
1201
+ */
1202
+ destroy() {
1203
+ this.colliders = [];
1204
+ }
1205
+ add(t) {
1206
+ this.colliders.push(t);
1207
+ }
1208
+ remove(t) {
1209
+ const e = this.colliders.indexOf(t);
1210
+ e > -1 && this.colliders.splice(e, 1);
1211
+ }
1212
+ /**
1213
+ * Main Physics Step.
1214
+ * Checks all pairs and triggers callbacks.
1215
+ */
1216
+ step() {
1217
+ for (let t = 0; t < this.colliders.length; t++)
1218
+ for (let e = t + 1; e < this.colliders.length; e++) {
1219
+ const s = this.colliders[t], i = this.colliders[e];
1220
+ this._intersects(s, s.getBounds(), i, i.getBounds()) && (this._notifyCollision(s.gameObject, i.gameObject), this._notifyCollision(i.gameObject, s.gameObject));
1221
+ }
1222
+ }
1223
+ _notifyCollision(t, e) {
1224
+ !t || !t.components || t.components.forEach((s) => {
1225
+ s.onCollisionEnter && s.onCollisionEnter(e);
1226
+ });
1227
+ }
1228
+ /**
1229
+ * Check if a specific collider overlaps with anything in the target group (tag).
1230
+ * @param {Collider} source
1231
+ * @param {string} targetTag
1232
+ */
1233
+ checkOverlap(t, e) {
1234
+ const s = [], i = t.getBounds();
1235
+ for (const r of this.colliders) {
1236
+ if (r === t || e && r.tag !== e) continue;
1237
+ const n = r.getBounds();
1238
+ this._intersects(t, i, r, n) && s.push(r);
1239
+ }
1240
+ return s;
1241
+ }
1242
+ _intersects(t, e, s, i) {
1243
+ if (t.type === "box" && s.type === "box")
1244
+ return e.left < i.right && e.right > i.left && e.top < i.bottom && e.bottom > i.top;
1245
+ if (t.type === "circle" && s.type === "circle") {
1246
+ const r = e.x - i.x, n = e.y - i.y;
1247
+ return Math.sqrt(r * r + n * n) < e.radius + i.radius;
1248
+ }
1249
+ return !1;
1250
+ }
1251
+ // IPhysicsSystem interface methods (for plugin compatibility)
1252
+ addBody(t) {
1253
+ this.add(t);
1254
+ }
1255
+ removeBody(t) {
1256
+ this.remove(t);
1257
+ }
1258
+ checkCollision(t, e) {
1259
+ return this._intersects(t, t.getBounds(), e, e.getBounds());
1260
+ }
1261
+ raycast(t, e, s) {
1262
+ return null;
1263
+ }
1264
+ setGravity(t, e) {
1265
+ }
1266
+ }
1267
+ const G = new _();
1268
+ console.warn("[DopamineJS] GlobalPhysics is deprecated. Use kernel.physics instead.");
1269
+ class k {
1270
+ constructor() {
1271
+ 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);
1272
+ }
1273
+ /**
1274
+ * ISystem interface - called when registered
1275
+ */
1276
+ init(t) {
1277
+ this.kernel = t, 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);
1278
+ }
1279
+ /**
1280
+ * ISystem interface - cleanup
1281
+ */
1282
+ destroy() {
1283
+ 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);
1284
+ }
1285
+ _onKeyDown(t) {
1286
+ this.keys.set(t.key, !0);
1287
+ }
1288
+ _onKeyUp(t) {
1289
+ this.keys.set(t.key, !1);
1290
+ }
1291
+ _onMouseMove(t) {
1292
+ this.mouse.x = t.clientX, this.mouse.y = t.clientY;
1293
+ }
1294
+ _onMouseDown(t) {
1295
+ this.mouse.buttons.set(t.button, !0);
1296
+ }
1297
+ _onMouseUp(t) {
1298
+ this.mouse.buttons.set(t.button, !1);
1299
+ }
1300
+ isKeyDown(t) {
1301
+ return this.keys.get(t) || !1;
1302
+ }
1303
+ isMouseButtonDown(t = 0) {
1304
+ return this.mouse.buttons.get(t) || !1;
1305
+ }
1306
+ }
1307
+ const F = new k();
1308
+ console.warn("[DopamineJS] GlobalInput is deprecated. Use kernel.input instead.");
1309
+ class S {
1310
+ constructor() {
1311
+ this.assets = /* @__PURE__ */ new Map(), this.kernel = null;
1312
+ }
1313
+ /**
1314
+ * ISystem interface - called when registered
1315
+ */
1316
+ init(t) {
1317
+ this.kernel = t;
1318
+ }
1319
+ /**
1320
+ * ISystem interface - cleanup
1321
+ */
1322
+ destroy() {
1323
+ this.assets.clear();
1324
+ }
1325
+ async loadImage(t, e) {
1326
+ return new Promise((s, i) => {
1327
+ const r = new Image();
1328
+ r.onload = () => {
1329
+ this.assets.set(t, r), s(r);
1330
+ }, r.onerror = i, r.src = e;
1331
+ });
1332
+ }
1333
+ get(t) {
1334
+ return this.assets.get(t);
1335
+ }
1336
+ }
1337
+ const N = new S();
1338
+ console.warn("[DopamineJS] GlobalLoader is deprecated. Use kernel.loader instead.");
1339
+ class U {
1340
+ constructor(t = {}) {
1341
+ this.config = t, this.events = new p(), this.systems = new O(this), this.plugins = new A(this), this._registerCoreSystems(t), this.fixedTimestep = t.fixedTimestep || 1 / 60, this._accumulator = 0, this._maxAccumulator = this.fixedTimestep * 5;
1342
+ }
1343
+ /**
1344
+ * Register core engine systems
1345
+ * These can be replaced by plugins
1346
+ * @private
1347
+ */
1348
+ _registerCoreSystems(t) {
1349
+ this.systems.register("ticker", new L(), {
1350
+ priority: 100
1351
+ }), this.systems.register("renderer", new D(t.renderer || {}), {
1352
+ priority: 90
1353
+ }), this.systems.register("input", new k(), {
1354
+ priority: 80
1355
+ }), this.systems.register("loader", new S(), {
1356
+ priority: 70
1357
+ }), this.systems.register("physics", new _(), {
1358
+ priority: 60
1359
+ });
1360
+ }
1361
+ /**
1362
+ * Start the kernel (begins game loop)
1363
+ */
1364
+ start() {
1365
+ const t = this.systems.get("ticker");
1366
+ t && (t.add(this._update.bind(this)), t.start()), this.events.emit("kernel_started");
1367
+ }
1368
+ /**
1369
+ * Stop the kernel
1370
+ */
1371
+ stop() {
1372
+ const t = this.systems.get("ticker");
1373
+ t && t.stop(), this.events.emit("kernel_stopped");
1374
+ }
1375
+ /**
1376
+ * Main update loop
1377
+ * @private
1378
+ */
1379
+ _update(t) {
1380
+ for (this.events.emit(p.Events.TICK, { dt: t }), this._accumulator += t, this._accumulator > this._maxAccumulator && (this._accumulator = this._maxAccumulator); this._accumulator >= this.fixedTimestep; )
1381
+ this.events.emit(p.Events.FIXED_UPDATE, { dt: this.fixedTimestep }), this.systems.fixedUpdate(this.fixedTimestep), this._accumulator -= this.fixedTimestep;
1382
+ this.systems.update(t), this.events.emit(p.Events.RENDER, { dt: t });
1383
+ }
1384
+ /**
1385
+ * Shorthand accessors for common systems (backward compatibility)
1386
+ */
1387
+ get physics() {
1388
+ return this.systems.get("physics");
1389
+ }
1390
+ get input() {
1391
+ return this.systems.get("input");
1392
+ }
1393
+ get loader() {
1394
+ return this.systems.get("loader");
1395
+ }
1396
+ get renderer() {
1397
+ return this.systems.get("renderer");
1398
+ }
1399
+ get ticker() {
1400
+ return this.systems.get("ticker");
1401
+ }
1402
+ /**
1403
+ * Destroy the kernel and cleanup all systems
1404
+ */
1405
+ destroy() {
1406
+ this.stop(), this.plugins.clear(), this.systems.clear(), this.events.clear();
1407
+ }
1408
+ }
1409
+ const g = {
1410
+ /**
1411
+ * Called when system is registered to the kernel
1412
+ * @param {DopamineKernel} kernel - The kernel instance
1413
+ */
1414
+ init(a) {
1415
+ },
1416
+ /**
1417
+ * Called each frame (variable timestep)
1418
+ * @param {number} dt - Delta time in seconds
1419
+ */
1420
+ update(a) {
1421
+ },
1422
+ /**
1423
+ * Called at fixed timestep (for physics)
1424
+ * @param {number} dt - Fixed delta time
1425
+ */
1426
+ fixedUpdate(a) {
1427
+ },
1428
+ /**
1429
+ * Called when system is unregistered
1430
+ */
1431
+ destroy() {
1432
+ }
1433
+ };
1434
+ class K {
1435
+ constructor() {
1436
+ this.kernel = null;
1437
+ }
1438
+ init(t) {
1439
+ this.kernel = t;
1440
+ }
1441
+ update(t) {
1442
+ }
1443
+ fixedUpdate(t) {
1444
+ }
1445
+ destroy() {
1446
+ }
1447
+ }
1448
+ const X = {
1449
+ ...g,
1450
+ /**
1451
+ * Add a physics body to the simulation
1452
+ * @param {Object} body - Physics body (implementation-specific)
1453
+ */
1454
+ addBody(a) {
1455
+ },
1456
+ /**
1457
+ * Remove a physics body from the simulation
1458
+ * @param {Object} body - Physics body to remove
1459
+ */
1460
+ removeBody(a) {
1461
+ },
1462
+ /**
1463
+ * Check collision between two bodies
1464
+ * @param {Object} a - First body
1465
+ * @param {Object} b - Second body
1466
+ * @returns {boolean} - True if colliding
1467
+ */
1468
+ checkCollision(a, t) {
1469
+ },
1470
+ /**
1471
+ * Perform a raycast
1472
+ * @param {Object} origin - Ray origin {x, y}
1473
+ * @param {Object} direction - Ray direction {x, y}
1474
+ * @param {number} distance - Maximum ray distance
1475
+ * @returns {Object|null} - Hit result or null
1476
+ */
1477
+ raycast(a, t, e) {
1478
+ },
1479
+ /**
1480
+ * Set gravity for the physics world
1481
+ * @param {number} x - Gravity X component
1482
+ * @param {number} y - Gravity Y component
1483
+ */
1484
+ setGravity(a, t) {
1485
+ },
1486
+ /**
1487
+ * Step the physics simulation
1488
+ * @param {number} dt - Delta time
1489
+ */
1490
+ step(a) {
1491
+ }
1492
+ }, H = {
1493
+ ...g,
1494
+ /**
1495
+ * Register a sound with a key
1496
+ * @param {string} key - Sound identifier
1497
+ * @param {string} url - Path to audio file
1498
+ */
1499
+ registerSound(a, t) {
1500
+ },
1501
+ /**
1502
+ * Preload multiple sounds
1503
+ * @param {Object} soundMap - Map of key -> url
1504
+ * @returns {Promise<void>}
1505
+ */
1506
+ preloadSounds(a) {
1507
+ },
1508
+ /**
1509
+ * Play a sound by key
1510
+ * @param {string} key - Sound identifier
1511
+ * @param {Object} options - Playback options (volume, loop, etc.)
1512
+ */
1513
+ play(a, t) {
1514
+ },
1515
+ /**
1516
+ * Play a synthesized tone
1517
+ * @param {number} frequency - Frequency in Hz
1518
+ * @param {number} duration - Duration in seconds
1519
+ * @param {string} type - Waveform type ('sine', 'square', etc.)
1520
+ * @param {number} volume - Volume (0-1)
1521
+ */
1522
+ playTone(a, t, e, s) {
1523
+ },
1524
+ /**
1525
+ * Stop a playing sound
1526
+ * @param {string} key - Sound identifier
1527
+ */
1528
+ stop(a) {
1529
+ },
1530
+ /**
1531
+ * Toggle mute state
1532
+ * @returns {boolean} - New mute state
1533
+ */
1534
+ toggleMute() {
1535
+ },
1536
+ /**
1537
+ * Set master volume
1538
+ * @param {number} volume - Volume (0-1)
1539
+ */
1540
+ setVolume(a) {
1541
+ }
1542
+ }, q = {
1543
+ ...g,
1544
+ /**
1545
+ * Register a sprite for particles
1546
+ * @param {string} key - Sprite identifier
1547
+ * @param {string} url - Path to image
1548
+ */
1549
+ registerSprite(a, t) {
1550
+ },
1551
+ /**
1552
+ * Register a custom effect
1553
+ * @param {string} name - Effect name
1554
+ * @param {Function} callback - Effect function (x, y, ...args) => void
1555
+ */
1556
+ registerEffect(a, t) {
1557
+ },
1558
+ /**
1559
+ * Play a registered effect
1560
+ * @param {string} name - Effect name
1561
+ * @param {number} x - X position
1562
+ * @param {number} y - Y position
1563
+ * @param {...any} args - Additional arguments
1564
+ */
1565
+ play(a, t, e, ...s) {
1566
+ },
1567
+ /**
1568
+ * Emit particles with custom configuration
1569
+ * @param {Object} config - Particle configuration
1570
+ */
1571
+ emit(a) {
1572
+ },
1573
+ /**
1574
+ * Clear all particles
1575
+ */
1576
+ clear() {
1577
+ },
1578
+ // Built-in effects (optional, but recommended)
1579
+ confetti(a, t, e) {
1580
+ },
1581
+ coinShower(a, t, e) {
1582
+ },
1583
+ sparkle(a, t, e, s) {
1584
+ },
1585
+ fire(a, t, e) {
1586
+ },
1587
+ starBurst(a, t, e) {
1588
+ }
1589
+ };
1590
+ class J {
1591
+ constructor() {
1592
+ this.children = [], this.game = null;
1593
+ }
1594
+ /**
1595
+ * Called when the scene is added to the Game.
1596
+ */
1597
+ onEnter() {
1598
+ }
1599
+ onExit() {
1600
+ }
1601
+ /**
1602
+ * Update loop for the scene.
1603
+ * @param {number} dt
1604
+ */
1605
+ update(t) {
1606
+ for (const e of this.children)
1607
+ e.update && e.update(t);
1608
+ }
1609
+ /**
1610
+ this.gameObjects = [];
1611
+ this.kernel = null; // Injected by Game
1612
+ }
1613
+
1614
+ /**
1615
+ * Add a GameObject to the scene.
1616
+ * @param {GameObject} gameObject
1617
+ */
1618
+ add(t) {
1619
+ return t.kernel = this.kernel, this.gameObjects.push(t), t;
1620
+ }
1621
+ /**
1622
+ * Remove a GameObject from the scene.
1623
+ * @param {GameObject} gameObject
1624
+ */
1625
+ remove(t) {
1626
+ const e = this.gameObjects.indexOf(t);
1627
+ e > -1 && this.gameObjects.splice(e, 1);
1628
+ }
1629
+ update(t) {
1630
+ for (const e of this.gameObjects)
1631
+ e.update(t);
1632
+ }
1633
+ render(t) {
1634
+ for (const e of this.gameObjects)
1635
+ e.render(t);
1636
+ }
1637
+ }
1638
+ class R {
1639
+ constructor(t) {
1640
+ this.game = t, this.currentScene = null;
1641
+ }
1642
+ /**
1643
+ * Switch to a new scene.
1644
+ * @param {Scene} scene
1645
+ */
1646
+ run(t) {
1647
+ this.currentScene && this.currentScene.onExit(), this.currentScene = t, this.currentScene.game = this.game, this.game.scene = t, t.onEnter();
1648
+ }
1649
+ }
1650
+ class W {
1651
+ constructor(t = {}) {
1652
+ this.config = t, this.kernel = new U(t), this.director = new R(this), this.kernel.events.on(p.Events.TICK, this._update.bind(this), 10), this.kernel.events.on(p.Events.RENDER, this._render.bind(this), 10);
1653
+ }
1654
+ /**
1655
+ * Backward compatibility accessors
1656
+ */
1657
+ get renderer() {
1658
+ return this.kernel.renderer;
1659
+ }
1660
+ get ticker() {
1661
+ return this.kernel.ticker;
1662
+ }
1663
+ get physics() {
1664
+ return this.kernel.physics;
1665
+ }
1666
+ get input() {
1667
+ return this.kernel.input;
1668
+ }
1669
+ /**
1670
+ * Start the game loop.
1671
+ */
1672
+ start() {
1673
+ this.kernel.start();
1674
+ }
1675
+ /**
1676
+ * Stop the game loop.
1677
+ */
1678
+ stop() {
1679
+ this.kernel.stop();
1680
+ }
1681
+ /**
1682
+ * Switch to a new scene.
1683
+ * @param {Scene} newScene
1684
+ */
1685
+ setScene(t) {
1686
+ t.kernel = this.kernel, this.director.run(t);
1687
+ }
1688
+ _update({ dt: t }) {
1689
+ this.director.currentScene && this.director.currentScene.update(t);
1690
+ }
1691
+ _render({ dt: t }) {
1692
+ this.renderer.clear(), this.director.currentScene && this.director.currentScene.render(this.renderer.ctx);
1693
+ }
1694
+ /**
1695
+ * Destroy the game and cleanup
1696
+ */
1697
+ destroy() {
1698
+ this.kernel.destroy();
1699
+ }
1700
+ }
1701
+ class Y {
1702
+ constructor(t = 0, e = 0) {
1703
+ this.x = t, this.y = e, this.rotation = 0, this.scale = { x: 1, y: 1 }, this.components = [], this.children = [], this.parent = null, this.kernel = null;
1704
+ }
1705
+ /**
1706
+ * Add a child object.
1707
+ * @param {GameObject} child
1708
+ */
1709
+ addChild(t) {
1710
+ return t.parent = this, t.kernel = this.kernel, this.children.push(t), t;
1711
+ }
1712
+ /**
1713
+ * Add a component.
1714
+ * @param {Component} component
1715
+ */
1716
+ addComponent(t) {
1717
+ return t.gameObject = this, t.kernel = this.kernel, this.components.push(t), t.onAttach(), t;
1718
+ }
1719
+ /**
1720
+ * Get a component by class type.
1721
+ * @param {class} type
1722
+ */
1723
+ getComponent(t) {
1724
+ return this.components.find((e) => e instanceof t);
1725
+ }
1726
+ update(t) {
1727
+ for (const e of this.components)
1728
+ e.update && e.update(t);
1729
+ for (const e of this.children)
1730
+ e.update(t);
1731
+ }
1732
+ render(t) {
1733
+ t.save(), t.translate(this.x, this.y), t.rotate(this.rotation), t.scale(this.scale.x, this.scale.y);
1734
+ for (const e of this.components)
1735
+ e.render && e.render(t);
1736
+ for (const e of this.children)
1737
+ e.render(t);
1738
+ t.restore();
1739
+ }
1740
+ }
1741
+ class b {
1742
+ constructor(t = 0, e = 0) {
1743
+ this.x = t, this.y = e;
1744
+ }
1745
+ add(t) {
1746
+ return this.x += t.x, this.y += t.y, this;
1747
+ }
1748
+ sub(t) {
1749
+ return this.x -= t.x, this.y -= t.y, this;
1750
+ }
1751
+ scale(t) {
1752
+ return this.x *= t, this.y *= t, this;
1753
+ }
1754
+ distance(t) {
1755
+ const e = this.x - t.x, s = this.y - t.y;
1756
+ return Math.sqrt(e * e + s * s);
1757
+ }
1758
+ normalize() {
1759
+ const t = Math.sqrt(this.x * this.x + this.y * this.y);
1760
+ return t > 0 && (this.x /= t, this.y /= t), this;
1761
+ }
1762
+ clone() {
1763
+ return new b(this.x, this.y);
1764
+ }
1765
+ static distance(t, e) {
1766
+ return Math.sqrt(Math.pow(e.x - t.x, 2) + Math.pow(e.y - t.y, 2));
1767
+ }
1768
+ }
1769
+ class z extends y {
1770
+ constructor(t) {
1771
+ super(), this.image = t, this.anchor = { x: 0.5, y: 0.5 }, this.width = t ? t.width : 0, this.height = t ? t.height : 0, this.frame = { x: 0, y: 0, w: this.width, h: this.height };
1772
+ }
1773
+ setFrame(t, e, s, i) {
1774
+ this.frame = { x: t, y: e, w: s, h: i }, this.width = s, this.height = i;
1775
+ }
1776
+ setTexture(t) {
1777
+ this.image = t, t && this.setFrame(0, 0, t.width, t.height);
1778
+ }
1779
+ render(t) {
1780
+ if (!this.image) return;
1781
+ const e = this.frame.w, s = this.frame.h, i = -e * this.anchor.x, r = -s * this.anchor.y;
1782
+ t.drawImage(
1783
+ this.image,
1784
+ this.frame.x,
1785
+ this.frame.y,
1786
+ e,
1787
+ s,
1788
+ // Source
1789
+ i,
1790
+ r,
1791
+ e,
1792
+ s
1793
+ // Destination
1794
+ );
1795
+ }
1796
+ }
1797
+ class V extends y {
1798
+ constructor() {
1799
+ super(), this.animations = {}, this.currentAnim = null, this.timer = 0, this.currentFrameIndex = 0, this.sprite = null;
1800
+ }
1801
+ onAttach() {
1802
+ this.sprite = this.gameObject.getComponent(z), this.sprite || console.warn("Animator attached to GameObject without a Sprite component.");
1803
+ }
1804
+ addAnimation(t, e, s = 10, i = !0) {
1805
+ this.animations[t] = { frames: e, fps: s, loop: i };
1806
+ }
1807
+ play(t) {
1808
+ this.currentAnim && this.currentAnim.name === t || this.animations[t] && (this.currentAnim = { name: t, ...this.animations[t] }, this.currentFrameIndex = 0, this.timer = 0, this._updateFrame());
1809
+ }
1810
+ update(t) {
1811
+ if (!this.currentAnim || !this.sprite) return;
1812
+ this.timer += t;
1813
+ const e = 1 / this.currentAnim.fps;
1814
+ this.timer >= e && (this.timer -= e, this.currentFrameIndex++, this.currentFrameIndex >= this.currentAnim.frames.length && (this.currentAnim.loop ? this.currentFrameIndex = 0 : this.currentFrameIndex = this.currentAnim.frames.length - 1), this._updateFrame());
1815
+ }
1816
+ _updateFrame() {
1817
+ const t = this.currentAnim.frames[this.currentFrameIndex];
1818
+ this.sprite.setFrame(t.x, t.y, t.w, t.h);
1819
+ }
1820
+ }
1821
+ class Q {
1822
+ static createGround(t, e, s = "#333") {
1823
+ const i = document.createElement("canvas");
1824
+ i.width = t, i.height = e;
1825
+ const r = i.getContext("2d");
1826
+ r.fillStyle = s, r.fillRect(0, 0, t, e);
1827
+ for (let n = 0; n < 1e3; n++) {
1828
+ r.fillStyle = Math.random() > 0.5 ? "#444" : "#222";
1829
+ const o = Math.random() * 4 + 2;
1830
+ r.fillRect(Math.random() * t, Math.random() * e, o, o);
1831
+ }
1832
+ r.strokeStyle = "rgba(255,255,255,0.05)", r.lineWidth = 2, r.beginPath();
1833
+ for (let n = 0; n <= t; n += 64)
1834
+ r.moveTo(n, 0), r.lineTo(n, e);
1835
+ for (let n = 0; n <= e; n += 64)
1836
+ r.moveTo(0, n), r.lineTo(t, n);
1837
+ return r.stroke(), i;
1838
+ }
1839
+ static createCharacterSheet(t, e) {
1840
+ const r = document.createElement("canvas");
1841
+ r.width = 64 * 4, r.height = 64;
1842
+ const n = r.getContext("2d");
1843
+ for (let o = 0; o < 4; o++) {
1844
+ const h = o * 64, c = 0, d = h + 64 / 2, u = c + 64 / 2, m = o % 2 === 0 ? 0 : -2, f = o === 1 ? 5 : o === 3 ? -5 : 0;
1845
+ n.fillStyle = t, n.beginPath(), n.arc(d, u + m, 20, 0, Math.PI * 2), n.fill(), n.fillStyle = e, n.beginPath(), n.arc(d - 15, u + m + f, 8, 0, Math.PI * 2), n.arc(d + 15, u + m - f, 8, 0, Math.PI * 2), n.fill(), n.fillStyle = j(t), n.beginPath(), n.arc(d, u + m, 15, 0, Math.PI * 2), n.fill(), e === "#333" && (n.fillStyle = "#000", n.fillRect(d + 10, u + m - 5, 25, 6));
1846
+ }
1847
+ return r;
1848
+ }
1849
+ }
1850
+ function j(a, t) {
1851
+ return a;
1852
+ }
1853
+ class Z extends y {
1854
+ constructor(t) {
1855
+ super(), this.system = t;
1856
+ }
1857
+ /**
1858
+ * Trigger an effect at the GameObject's current position.
1859
+ * @param {string} effectName - 'confetti', 'fire', 'sparkle', etc.
1860
+ * @param {object} options - override options
1861
+ */
1862
+ play(t, e = {}) {
1863
+ const s = this.gameObject.x, i = this.gameObject.y;
1864
+ switch (t) {
1865
+ case "confetti":
1866
+ this.system.confetti(s, i, e.count);
1867
+ break;
1868
+ case "fire":
1869
+ this.system.fire(s, i, e.count);
1870
+ break;
1871
+ case "sparkle":
1872
+ this.system.sparkle(s, i, e.count, e.color);
1873
+ break;
1874
+ case "blood":
1875
+ this.system.emit({
1876
+ x: s,
1877
+ y: i,
1878
+ count: 10,
1879
+ color: ["#8a0303", "#ff0000"],
1880
+ speed: 4,
1881
+ gravity: 0.1,
1882
+ decay: 0.05
1883
+ });
1884
+ break;
1885
+ default:
1886
+ this.system.emit({
1887
+ x: s,
1888
+ y: i,
1889
+ ...e
1890
+ });
1891
+ }
1892
+ }
1893
+ }
1894
+ class tt extends y {
1895
+ constructor() {
1896
+ super(), this.intensity = 0, this.duration = 0, this.timer = 0, this.originalPos = { x: 0, y: 0 }, this.isShaking = !1;
1897
+ }
1898
+ shake(t = 5, e = 0.2) {
1899
+ this.intensity = t, this.duration = e, this.timer = 0, this.isShaking = !0, this.timer === 0 && (this.originalPos.x = this.gameObject.x, this.originalPos.y = this.gameObject.y);
1900
+ }
1901
+ update(t) {
1902
+ if (!this.isShaking) return;
1903
+ if (this.timer += t, this.timer >= this.duration) {
1904
+ this.isShaking = !1, this.gameObject.x = this.originalPos.x, this.gameObject.y = this.originalPos.y;
1905
+ return;
1906
+ }
1907
+ const e = (Math.random() - 0.5) * 2 * this.intensity, s = (Math.random() - 0.5) * 2 * this.intensity;
1908
+ this.gameObject.x = this.originalPos.x + e, this.gameObject.y = this.originalPos.y + s;
1909
+ }
1910
+ }
1911
+ export {
1912
+ V as Animator,
1913
+ B as Collider,
1914
+ y as Component,
1915
+ P as DataService,
1916
+ R as Director,
1917
+ U as DopamineKernel,
1918
+ p as EventBus,
1919
+ W as Game,
1920
+ Y as GameObject,
1921
+ T as GameUI,
1922
+ F as GlobalInput,
1923
+ N as GlobalLoader,
1924
+ G as GlobalPhysics,
1925
+ H as IAudioSystem,
1926
+ q as IParticleSystem,
1927
+ X as IPhysicsSystem,
1928
+ g as ISystem,
1929
+ k as Input,
1930
+ S as Loader,
1931
+ Z as ParticleEmitter,
1932
+ C as ParticleSystem,
1933
+ _ as Physics,
1934
+ A as PluginRegistry,
1935
+ D as Renderer,
1936
+ E as RewardSystem,
1937
+ J as Scene,
1938
+ tt as ScreenShake,
1939
+ I as SoundManager,
1940
+ z as Sprite,
1941
+ K as System,
1942
+ O as SystemRegistry,
1943
+ Q as TextureGenerator,
1944
+ L as Ticker,
1945
+ b as Vector2,
1946
+ $ as default
1947
+ };