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