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