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.
- package/dist/dopamine.js +1947 -0
- package/dist/dopamine.umd.cjs +45 -0
- package/package.json +24 -32
- package/src/core/Animator.js +62 -0
- package/src/core/Collider.js +50 -0
- package/src/core/Component.js +38 -0
- package/src/core/DopamineKernel.js +153 -0
- package/src/core/EventBus.js +183 -0
- package/src/core/Game.js +89 -0
- package/src/core/GameObject.js +83 -0
- package/src/core/PluginRegistry.js +197 -0
- package/src/core/Scene.js +65 -0
- package/src/core/Sprite.js +51 -0
- package/src/core/SystemRegistry.js +242 -0
- package/src/core/Vector2.js +50 -0
- package/src/{audio → dopamine/audio}/SoundManager.js +1 -3
- package/src/dopamine/components/ParticleEmitter.js +50 -0
- package/src/dopamine/components/ScreenShake.js +49 -0
- package/src/dopamine/components/index.js +4 -0
- package/src/dopamine/effects/WebGLParticleSystem.js +348 -0
- package/src/dopamine/index.js +62 -0
- package/src/dopamine/utils/TextureGenerator.js +90 -0
- package/src/index.js +34 -62
- package/src/interfaces/IAudioSystem.js +58 -0
- package/src/interfaces/IParticleSystem.js +51 -0
- package/src/interfaces/IPhysicsSystem.js +52 -0
- package/src/interfaces/ISystem.js +48 -0
- package/src/interfaces/index.js +7 -0
- package/src/renderer/Renderer.js +37 -0
- package/src/systems/Director.js +27 -0
- package/src/systems/Input.js +77 -0
- package/src/systems/Loader.js +44 -0
- package/src/systems/Physics.js +117 -0
- package/src/systems/Ticker.js +61 -0
- package/ARCHITECTURE.md +0 -71
- package/CHANGELOG.md +0 -36
- package/README.md +0 -264
- package/dist/dopamine.mjs +0 -737
- package/dist/dopamine.umd.js +0 -45
- /package/src/{core → dopamine/core}/DataService.js +0 -0
- /package/src/{core → dopamine/core}/EventEmitter.js +0 -0
- /package/src/{core → dopamine/core}/RewardSystem.js +0 -0
- /package/src/{effects → dopamine/effects}/ParticleSystem.js +0 -0
- /package/src/{ui → dopamine/ui}/GameUI.js +0 -0
- /package/src/{ui → dopamine/ui}/dopamine.css +0 -0
package/dist/dopamine.js
ADDED
|
@@ -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
|
+
};
|