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