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