dopaminejs 2.2.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/dopamine.cjs +37 -0
- package/dist/dopamine.js +584 -1403
- package/dist/engine.cjs +1 -0
- package/dist/engine.js +1158 -0
- package/dist/style.css +1 -1
- package/package.json +27 -13
- package/src/core/Collider.js +43 -7
- package/src/core/PluginRegistry.js +84 -7
- package/src/core/Scene.js +50 -5
- package/src/core/SystemRegistry.js +32 -11
- package/src/dopamine/audio/SoundManager.js +91 -20
- package/src/dopamine/components/ScreenShake.js +7 -6
- package/src/dopamine/components/index.js +0 -2
- package/src/dopamine/core/DataService.js +31 -3
- package/src/dopamine/core/RewardSystem.js +178 -22
- package/src/dopamine/effects/ParticleSystem.js +72 -15
- package/src/dopamine/index.js +56 -24
- package/src/dopamine/ui/GameUI.js +48 -5
- package/src/dopamine/ui/dopamine.css +18 -0
- package/src/engine.js +38 -0
- package/src/systems/Director.js +7 -0
- package/src/systems/Ticker.js +14 -3
- package/types/index.d.ts +59 -14
- package/dist/dopamine.umd.cjs +0 -37
|
@@ -56,6 +56,22 @@ export class GameUI {
|
|
|
56
56
|
</div>
|
|
57
57
|
`;
|
|
58
58
|
|
|
59
|
+
// Popups are decorative and short-lived, so a screen reader would
|
|
60
|
+
// miss them. This region repeats them as plain text.
|
|
61
|
+
this.liveRegion = document.createElement('div');
|
|
62
|
+
this.liveRegion.className = 'dopamine-live-region';
|
|
63
|
+
this.liveRegion.setAttribute('aria-live', 'polite');
|
|
64
|
+
this.liveRegion.setAttribute('role', 'status');
|
|
65
|
+
Object.assign(this.liveRegion.style, {
|
|
66
|
+
position: 'absolute',
|
|
67
|
+
width: '1px',
|
|
68
|
+
height: '1px',
|
|
69
|
+
overflow: 'hidden',
|
|
70
|
+
clipPath: 'inset(50%)',
|
|
71
|
+
whiteSpace: 'nowrap'
|
|
72
|
+
});
|
|
73
|
+
this.container.appendChild(this.liveRegion);
|
|
74
|
+
|
|
59
75
|
document.body.appendChild(this.container);
|
|
60
76
|
|
|
61
77
|
// Scoped to this instance. Document-wide getElementById would make a
|
|
@@ -91,6 +107,16 @@ export class GameUI {
|
|
|
91
107
|
return el;
|
|
92
108
|
}
|
|
93
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Repeat a popup as text for assistive technology.
|
|
112
|
+
* @private
|
|
113
|
+
*/
|
|
114
|
+
_announce(message) {
|
|
115
|
+
if (this.liveRegion) {
|
|
116
|
+
this.liveRegion.textContent = message;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
94
120
|
/**
|
|
95
121
|
* Replay a CSS animation by clearing it for one tick.
|
|
96
122
|
* @private
|
|
@@ -106,10 +132,15 @@ export class GameUI {
|
|
|
106
132
|
* @param {number} current - XP held right now
|
|
107
133
|
* @param {number} needed - XP still required for the next level
|
|
108
134
|
* @param {number} total - XP total that marks the next level
|
|
135
|
+
* @param {number} [progress] - Position within the current level, 0 to 1.
|
|
136
|
+
* Without it the bar falls back to current / total, which is lifetime
|
|
137
|
+
* XP and starts every level after the first partly full.
|
|
109
138
|
*/
|
|
110
|
-
updateXP(current, needed, total) {
|
|
111
|
-
const
|
|
112
|
-
|
|
139
|
+
updateXP(current, needed, total, progress) {
|
|
140
|
+
const fraction = Number.isFinite(progress)
|
|
141
|
+
? progress
|
|
142
|
+
: (total > 0 ? current / total : 0);
|
|
143
|
+
this.xpBar.style.width = `${Math.max(0, Math.min(100, fraction * 100))}%`;
|
|
113
144
|
this.xpText.textContent = `${current} / ${total} XP`;
|
|
114
145
|
|
|
115
146
|
this._replayAnimation(this.xpBar, 'xp-pulse 0.3s ease-out');
|
|
@@ -134,6 +165,8 @@ export class GameUI {
|
|
|
134
165
|
this.streakBadge.style.background = 'linear-gradient(135deg, #ff6b00 0%, #ff4400 100%)';
|
|
135
166
|
} else if (days >= 3) {
|
|
136
167
|
this.streakBadge.style.background = 'linear-gradient(135deg, #ff8800 0%, #ff6b00 100%)';
|
|
168
|
+
} else {
|
|
169
|
+
this.streakBadge.style.background = '';
|
|
137
170
|
}
|
|
138
171
|
}
|
|
139
172
|
|
|
@@ -167,6 +200,7 @@ export class GameUI {
|
|
|
167
200
|
popup.querySelector('.achievement-xp').textContent = `+${achievement.xp ?? 0} XP`;
|
|
168
201
|
|
|
169
202
|
this._mount(popup);
|
|
203
|
+
this._announce(`Achievement unlocked: ${achievement.name ?? ''}`);
|
|
170
204
|
|
|
171
205
|
// Trigger confetti at popup location
|
|
172
206
|
this._defer(() => {
|
|
@@ -197,6 +231,7 @@ export class GameUI {
|
|
|
197
231
|
overlay.querySelector('.level-up-number').textContent = newLevel;
|
|
198
232
|
|
|
199
233
|
this._mount(overlay);
|
|
234
|
+
this._announce(`Level up: ${newLevel}`);
|
|
200
235
|
|
|
201
236
|
// Fireworks effect
|
|
202
237
|
this._defer(() => {
|
|
@@ -265,13 +300,20 @@ export class GameUI {
|
|
|
265
300
|
const x = window.innerWidth / 2;
|
|
266
301
|
const y = window.innerHeight / 3;
|
|
267
302
|
|
|
303
|
+
// The message is shown as written. showLuckyMoment and showCombo
|
|
304
|
+
// expect a multiplier and would wrap it in "LUCKY ...×!".
|
|
268
305
|
if (type === 'legendary') {
|
|
269
|
-
this.
|
|
306
|
+
this.showFloatingText(message, x, y, '#ffd700', '36px');
|
|
307
|
+
this.particleSystem.starBurst(x, y, 12);
|
|
308
|
+
this.particleSystem.confetti(x, y, 25);
|
|
270
309
|
} else if (type === 'rare') {
|
|
271
|
-
this.
|
|
310
|
+
this.showFloatingText(message, x, y, '#ff6b6b', '32px');
|
|
311
|
+
this.particleSystem.fire(x, y, 20);
|
|
272
312
|
} else {
|
|
273
313
|
this.showFloatingText(message, x, y, '#fff', '24px');
|
|
274
314
|
}
|
|
315
|
+
|
|
316
|
+
this._announce(message);
|
|
275
317
|
}
|
|
276
318
|
|
|
277
319
|
/**
|
|
@@ -367,6 +409,7 @@ export class GameUI {
|
|
|
367
409
|
|
|
368
410
|
this.container?.remove();
|
|
369
411
|
this.container = null;
|
|
412
|
+
this.liveRegion = null;
|
|
370
413
|
this.xpBar = null;
|
|
371
414
|
this.xpText = null;
|
|
372
415
|
this.levelBadge = null;
|
|
@@ -375,3 +375,21 @@
|
|
|
375
375
|
font-size: 80px;
|
|
376
376
|
}
|
|
377
377
|
}
|
|
378
|
+
|
|
379
|
+
/* Users who asked the OS for less motion get the same information without
|
|
380
|
+
the movement. Floating text keeps its place until the script removes it:
|
|
381
|
+
its animation ends at opacity 0, so cutting the duration would hide it. */
|
|
382
|
+
@media (prefers-reduced-motion: reduce) {
|
|
383
|
+
.game-ui-overlay *,
|
|
384
|
+
.achievement-popup,
|
|
385
|
+
.achievement-popup *,
|
|
386
|
+
.level-up-overlay,
|
|
387
|
+
.level-up-overlay * {
|
|
388
|
+
animation: none !important;
|
|
389
|
+
transition: none !important;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.floating-text {
|
|
393
|
+
animation: none !important;
|
|
394
|
+
}
|
|
395
|
+
}
|
package/src/engine.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine subpath export — `import { Game, Scene } from 'dopaminejs/engine'`
|
|
3
|
+
*
|
|
4
|
+
* The game engine that DopamineJS is built on. Use this subpath if your project
|
|
5
|
+
* only needs the entity/component system, renderer, or physics, and not the
|
|
6
|
+
* progression mechanics (RewardSystem, GameUI, DataService).
|
|
7
|
+
*
|
|
8
|
+
* The root `dopaminejs` export continues to re-export everything, so nothing breaks.
|
|
9
|
+
* This subpath makes the README's distinction between the engine and the progression
|
|
10
|
+
* layer real in code and gives bundlers a clear surface to tree-shake against.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Entity / component system
|
|
14
|
+
export { Game } from './core/Game.js';
|
|
15
|
+
export { Scene } from './core/Scene.js';
|
|
16
|
+
export { GameObject } from './core/GameObject.js';
|
|
17
|
+
export { Component } from './core/Component.js';
|
|
18
|
+
export { Vector2 } from './core/Vector2.js';
|
|
19
|
+
export { Collider } from './core/Collider.js';
|
|
20
|
+
export { Sprite } from './core/Sprite.js';
|
|
21
|
+
export { Animator } from './core/Animator.js';
|
|
22
|
+
|
|
23
|
+
// Kernel and plugin infrastructure
|
|
24
|
+
export { DopamineKernel } from './core/DopamineKernel.js';
|
|
25
|
+
export { EventBus } from './core/EventBus.js';
|
|
26
|
+
export { SystemRegistry } from './core/SystemRegistry.js';
|
|
27
|
+
export { PluginRegistry } from './core/PluginRegistry.js';
|
|
28
|
+
|
|
29
|
+
// Built-in systems
|
|
30
|
+
export { Renderer } from './renderer/Renderer.js';
|
|
31
|
+
export { Ticker } from './systems/Ticker.js';
|
|
32
|
+
export { Loader, GlobalLoader } from './systems/Loader.js';
|
|
33
|
+
export { Input, GlobalInput } from './systems/Input.js';
|
|
34
|
+
export { Director } from './systems/Director.js';
|
|
35
|
+
export { Physics, GlobalPhysics } from './systems/Physics.js';
|
|
36
|
+
|
|
37
|
+
// Engine-level components
|
|
38
|
+
export { ParticleEmitter, ScreenShake } from './dopamine/components/index.js';
|
package/src/systems/Director.js
CHANGED
|
@@ -14,11 +14,18 @@ export class Director {
|
|
|
14
14
|
run(scene) {
|
|
15
15
|
if (this.currentScene) {
|
|
16
16
|
this.currentScene.onExit();
|
|
17
|
+
|
|
18
|
+
// Its colliders would keep colliding with the next scene.
|
|
19
|
+
this.currentScene.kernel = null;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
this.currentScene = scene;
|
|
20
23
|
this.currentScene.game = this.game;
|
|
21
24
|
|
|
25
|
+
if (this.game?.kernel) {
|
|
26
|
+
scene.kernel = this.game.kernel;
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
// Let the game know (if game engine needs to reference it directly)
|
|
23
30
|
this.game.scene = scene;
|
|
24
31
|
|
package/src/systems/Ticker.js
CHANGED
|
@@ -9,6 +9,7 @@ export class Ticker {
|
|
|
9
9
|
this.callbacks = new Set();
|
|
10
10
|
this._frameHandle = null;
|
|
11
11
|
this._tick = this._tick.bind(this);
|
|
12
|
+
this._failed = new WeakSet();
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -64,12 +65,22 @@ export class Ticker {
|
|
|
64
65
|
const dt = (time - this.lastTime) / 1000; // Delta time in seconds
|
|
65
66
|
this.lastTime = time;
|
|
66
67
|
|
|
67
|
-
// Cap dt to prevent huge jumps if tab was inactive
|
|
68
|
-
|
|
68
|
+
// Cap dt to prevent huge jumps if tab was inactive. Floored at 0: the
|
|
69
|
+
// first frame's timestamp can predate the performance.now() in start().
|
|
70
|
+
const safeDt = Math.max(0, Math.min(dt, 0.1));
|
|
69
71
|
|
|
70
72
|
// Snapshot: a callback may add or remove callbacks mid-frame.
|
|
71
73
|
for (const callback of [...this.callbacks]) {
|
|
72
|
-
|
|
74
|
+
try {
|
|
75
|
+
callback(safeDt);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
// A throw here used to skip the re-queue below with `running`
|
|
78
|
+
// still true, so the loop died and start() refused to revive it.
|
|
79
|
+
if (!this._failed.has(callback)) {
|
|
80
|
+
this._failed.add(callback);
|
|
81
|
+
console.error('[Ticker] Callback threw:', error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
// A callback may have called stop(); don't queue another frame if so.
|
package/types/index.d.ts
CHANGED
|
@@ -77,7 +77,11 @@ export type RewardEvent =
|
|
|
77
77
|
| 'xp_gained'
|
|
78
78
|
| 'level_up'
|
|
79
79
|
| 'achievement_unlocked'
|
|
80
|
-
| 'new_high_score'
|
|
80
|
+
| 'new_high_score'
|
|
81
|
+
/** Payload `{ current, longest }`. Fires when the day rolls over. */
|
|
82
|
+
| 'streak_updated'
|
|
83
|
+
/** Payload `{ key }`. The storage write failed; state lives in memory only. */
|
|
84
|
+
| 'save_failed';
|
|
81
85
|
|
|
82
86
|
export class EventEmitter {
|
|
83
87
|
on(event: string, callback: (data?: unknown) => void): () => void;
|
|
@@ -101,8 +105,12 @@ export class RewardSystem extends EventEmitter {
|
|
|
101
105
|
addXP(amount: number, reason?: string): Promise<AddXPResult>;
|
|
102
106
|
getXPForNextLevel(): XPProgress;
|
|
103
107
|
|
|
104
|
-
/**
|
|
105
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Persists once for the whole call, not once per internal mutation.
|
|
110
|
+
* @throws TypeError for an empty or reserved game name, or a `score` that
|
|
111
|
+
* is not a finite number.
|
|
112
|
+
*/
|
|
113
|
+
recordGame(gameName: string, result?: GameResult): Promise<void>;
|
|
106
114
|
|
|
107
115
|
checkAchievements(gameName: string, result: GameResult): Promise<AchievementDefinition[]>;
|
|
108
116
|
unlockAchievement(achievementId: string): Promise<boolean>;
|
|
@@ -122,16 +130,27 @@ export interface StorageLike {
|
|
|
122
130
|
removeItem(key: string): void;
|
|
123
131
|
}
|
|
124
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Promise-returning store such as AsyncStorage or an IndexedDB wrapper.
|
|
135
|
+
* DataService accepts either kind. SoundManager reads its mute flag in the
|
|
136
|
+
* constructor and needs the synchronous one.
|
|
137
|
+
*/
|
|
138
|
+
export interface AsyncStorageLike {
|
|
139
|
+
getItem(key: string): Promise<string | null>;
|
|
140
|
+
setItem(key: string, value: string): Promise<void>;
|
|
141
|
+
removeItem(key: string): Promise<void>;
|
|
142
|
+
}
|
|
143
|
+
|
|
125
144
|
export interface DataServiceConfig {
|
|
126
145
|
/** Defaults to localStorage, falling back to an in-memory store. */
|
|
127
|
-
storage?: StorageLike;
|
|
146
|
+
storage?: StorageLike | AsyncStorageLike;
|
|
128
147
|
/** Key prefix, default 'dopamine_'. */
|
|
129
148
|
prefix?: string;
|
|
130
149
|
}
|
|
131
150
|
|
|
132
151
|
export class DataService {
|
|
133
152
|
constructor(config?: DataServiceConfig);
|
|
134
|
-
storage: StorageLike;
|
|
153
|
+
storage: StorageLike | AsyncStorageLike;
|
|
135
154
|
prefix: string;
|
|
136
155
|
save(key: string, data: unknown): Promise<boolean>;
|
|
137
156
|
load<T = unknown>(key: string, defaultValue?: T | null): Promise<T | null>;
|
|
@@ -162,7 +181,8 @@ export class GameUI {
|
|
|
162
181
|
|
|
163
182
|
container: HTMLElement | null;
|
|
164
183
|
|
|
165
|
-
|
|
184
|
+
/** `progress` is the position within the current level, 0 to 1. */
|
|
185
|
+
updateXP(current: number, needed: number, total: number, progress?: number): void;
|
|
166
186
|
updateLevel(level: number): void;
|
|
167
187
|
updateStreak(days: number): void;
|
|
168
188
|
|
|
@@ -188,18 +208,25 @@ export interface ParticleSystemConfig {
|
|
|
188
208
|
container?: HTMLElement | string;
|
|
189
209
|
canvasId?: string;
|
|
190
210
|
zIndex?: string;
|
|
211
|
+
/** Cap on live particles, default 5000. Emits past it are dropped. */
|
|
212
|
+
maxParticles?: number;
|
|
213
|
+
/** Skip effects under prefers-reduced-motion. Default true. */
|
|
214
|
+
respectReducedMotion?: boolean;
|
|
191
215
|
}
|
|
192
216
|
|
|
193
217
|
export interface ParticleConfig {
|
|
194
218
|
x: number;
|
|
195
219
|
y: number;
|
|
196
220
|
count?: number;
|
|
197
|
-
|
|
221
|
+
/** One colour, or a list to pick from at random per particle. */
|
|
222
|
+
color?: string | string[];
|
|
198
223
|
size?: number;
|
|
199
224
|
life?: number;
|
|
200
225
|
decay?: number;
|
|
201
226
|
gravity?: number;
|
|
202
227
|
spread?: number;
|
|
228
|
+
/** Direction in radians. */
|
|
229
|
+
angle?: number;
|
|
203
230
|
speed?: number;
|
|
204
231
|
type?: string;
|
|
205
232
|
sprite?: string;
|
|
@@ -239,7 +266,7 @@ export interface SoundManagerConfig {
|
|
|
239
266
|
storage?: StorageLike;
|
|
240
267
|
/** Master volume 0 to 1, default 1. */
|
|
241
268
|
volume?: number;
|
|
242
|
-
/** Key to URL map,
|
|
269
|
+
/** Key to URL map, loaded when the audio context is first created. */
|
|
243
270
|
customSounds?: Record<string, string>;
|
|
244
271
|
}
|
|
245
272
|
|
|
@@ -268,6 +295,9 @@ export class SoundManager {
|
|
|
268
295
|
playClick(force?: boolean): void;
|
|
269
296
|
playSuccess(force?: boolean): void;
|
|
270
297
|
playError(force?: boolean): void;
|
|
298
|
+
|
|
299
|
+
/** Cancels queued tones, closes the audio context, drops decoded buffers. */
|
|
300
|
+
destroy(): void;
|
|
271
301
|
}
|
|
272
302
|
|
|
273
303
|
// ---------------------------------------------------------------------------
|
|
@@ -406,19 +436,29 @@ export class Component {
|
|
|
406
436
|
render(ctx: CanvasRenderingContext2D): void;
|
|
407
437
|
}
|
|
408
438
|
|
|
409
|
-
export interface
|
|
439
|
+
export interface BoxBounds {
|
|
440
|
+
left: number;
|
|
441
|
+
right: number;
|
|
442
|
+
top: number;
|
|
443
|
+
bottom: number;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export interface CircleBounds {
|
|
410
447
|
x: number;
|
|
411
448
|
y: number;
|
|
412
|
-
|
|
413
|
-
height: number;
|
|
449
|
+
radius: number;
|
|
414
450
|
}
|
|
415
451
|
|
|
452
|
+
/** A box collider returns edges, a circle collider returns centre and radius. */
|
|
453
|
+
export type Bounds = BoxBounds | CircleBounds;
|
|
454
|
+
|
|
416
455
|
export class Collider extends Component {
|
|
417
456
|
constructor(type?: 'box' | 'circle', width?: number, height?: number, radius?: number);
|
|
418
457
|
type: 'box' | 'circle';
|
|
419
458
|
width: number;
|
|
420
459
|
height: number;
|
|
421
460
|
radius: number;
|
|
461
|
+
tag: string | null;
|
|
422
462
|
getBounds(): Bounds;
|
|
423
463
|
}
|
|
424
464
|
|
|
@@ -435,11 +475,13 @@ export class Animator extends Component {
|
|
|
435
475
|
|
|
436
476
|
export class GameObject {
|
|
437
477
|
constructor(x?: number, y?: number);
|
|
438
|
-
|
|
478
|
+
x: number;
|
|
479
|
+
y: number;
|
|
439
480
|
rotation: number;
|
|
440
|
-
scale:
|
|
481
|
+
scale: { x: number; y: number };
|
|
441
482
|
components: Component[];
|
|
442
483
|
children: GameObject[];
|
|
484
|
+
parent: GameObject | null;
|
|
443
485
|
kernel: DopamineKernel | null;
|
|
444
486
|
tag?: string;
|
|
445
487
|
addChild(child: GameObject): GameObject;
|
|
@@ -500,7 +542,8 @@ export class Physics {
|
|
|
500
542
|
add(collider: Collider): void;
|
|
501
543
|
remove(collider: Collider): void;
|
|
502
544
|
step(): void;
|
|
503
|
-
|
|
545
|
+
/** Every collider that overlaps `source`. With a tag, only colliders carrying it. */
|
|
546
|
+
checkOverlap(source: Collider, targetTag?: string): Collider[];
|
|
504
547
|
addBody(body: Collider): void;
|
|
505
548
|
removeBody(body: Collider): void;
|
|
506
549
|
checkCollision(a: Collider, b: Collider): boolean;
|
|
@@ -585,4 +628,6 @@ export default class Dopamine {
|
|
|
585
628
|
particleSystem: ParticleSystem;
|
|
586
629
|
gameUI: GameUI;
|
|
587
630
|
init(): Promise<DopamineSubsystems>;
|
|
631
|
+
/** Removes the overlay and canvas, closes audio, detaches from reward events. */
|
|
632
|
+
destroy(): void;
|
|
588
633
|
}
|
package/dist/dopamine.umd.cjs
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
(function(h,f){typeof exports=="object"&&typeof module<"u"?f(exports):typeof define=="function"&&define.amd?define(["exports"],f):(h=typeof globalThis<"u"?globalThis:h||self,f(h.Dopamine={}))})(this,function(h){"use strict";class f{constructor(){this.listeners=new Map}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>this.off(e,t)}off(e,t){this.listeners.has(e)&&this.listeners.get(e).delete(t)}emit(e,t){this.listeners.has(e)&&this.listeners.get(e).forEach(s=>{try{s(t)}catch(i){console.error(`Error in listener for event '${e}':`,i)}})}clear(){this.listeners.clear()}}class P extends f{constructor(e,t={}){var s;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}_publish(e,t){this.emit(e,t),this.events&&this.events.emit(e,t)}async _batch(e){this._batchDepth++;try{return await e()}finally{this._batchDepth--,this._batchDepth===0&&this._pendingSave&&(this._pendingSave=!1,await this._write())}}async init(){const e=await this.dataService.load("player",null);return this.player=this._migrate(e),this._updateDailyStreak(),await this.save(),this.player}_getDefaultPlayer(){return{name:"Player",xp:0,level:1,totalGamesPlayed:0,createdAt:Date.now(),lastPlayedAt:Date.now(),streak:{current:1,longest:1,lastPlayDate:this._getTodayDateString()},achievements:{},stats:{}}}_migrate(e){const t=this._getDefaultPlayer();return!e||typeof e!="object"?t:{...t,...e,streak:{...t.streak,...e.streak||{}},achievements:e.achievements||{},stats:e.stats||{}}}async save(){if(this.player){if(this._batchDepth>0){this._pendingSave=!0;return}await this._write()}}async _write(){this.player.lastPlayedAt=Date.now(),await this.dataService.save("player",this.player)}async addXP(e,t=""){if(!Number.isFinite(e))throw new TypeError(`[DopamineJS] addXP expects a finite number, received ${e}`);const s=this.player.level;this.player.xp+=e;const i=this._calculateLevel(this.player.xp),r=i>s;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}}_calculateLevel(e){const t=Math.floor((1+Math.sqrt(1+4*e/50))/2);return Math.max(1,t)}getXPForNextLevel(){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;return{total:t,needed:Math.max(0,t-this.player.xp),progress:r}}async recordGame(e,t){return this._batch(()=>this._recordGame(e,t))}async _recordGame(e,t){this.player.stats[e]||(this.player.stats[e]={totalPlays:0,highScore:0});const s=this.player.stats[e];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=>{r!=="score"&&(s[r]=(s[r]||0)+(typeof t[r]=="number"?t[r]:0))});let i=10;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()}async checkAchievements(e,t){const s=[];for(const[i,r]of Object.entries(this.achievements)){if(this.player.achievements[i])continue;let n=!1;try{n=r.check(this.player,e,t)}catch(o){console.error(`[DopamineJS] Achievement "${i}" check threw:`,o);continue}n&&(await this.unlockAchievement(i),s.push(r))}return s}async unlockAchievement(e){const t=this.achievements[e];return t?(this.player.achievements[e]={unlockedAt:Date.now(),seen:!1},await this.addXP(Number.isFinite(t.xp)?t.xp:0,`Achievement: ${t.name}`),this._publish("achievement_unlocked",t),await this.save(),!0):!1}getUnlockedAchievements(){return Object.entries(this.player.achievements).map(([e,t])=>({...this.achievements[e],id:e,...t}))}getUnseenAchievements(){return this.getUnlockedAchievements().filter(e=>!e.seen)}async markAchievementsSeen(e){for(const t of e)this.player.achievements[t]&&(this.player.achievements[t].seen=!0);await this.save()}_updateDailyStreak(){const e=this._getTodayDateString(),t=this.player.streak.lastPlayDate;if(t===e)return;const s=this._getYesterdayDateString();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}getStreakMultiplier(){const e=this.player.streak.current;return e>=7?2:e>=5?1.5:e>=3?1.25:1}_toLocalDateString(e){const t=e.getFullYear(),s=String(e.getMonth()+1).padStart(2,"0"),i=String(e.getDate()).padStart(2,"0");return`${t}-${s}-${i}`}_getTodayDateString(){return this._toLocalDateString(new Date)}_getYesterdayDateString(){const e=new Date;return e.setDate(e.getDate()-1),this._toLocalDateString(e)}_initAchievements(){return{first_game:{name:"First Steps",description:"Play your first game",icon:"🎮",xp:50,check:e=>e.totalGamesPlayed>=1},level_5:{name:"Rising Star",description:"Reach level 5",icon:"⭐",xp:100,check:e=>e.level>=5},level_10:{name:"Expert Player",description:"Reach level 10",icon:"🏆",xp:250,check:e=>e.level>=10},streak_3:{name:"Dedicated",description:"Play 3 days in a row",icon:"🔥",xp:100,check:e=>e.streak.current>=3},streak_7:{name:"Unstoppable",description:"Play 7 days in a row",icon:"💥",xp:300,check:e=>e.streak.current>=7}}}}function w(){const a=new Map;return{getItem:e=>a.has(e)?a.get(e):null,setItem:(e,t)=>{a.set(e,String(t))},removeItem:e=>{a.delete(e)},clear:()=>{a.clear()}}}function x(){try{if(typeof localStorage>"u")return w();const a="__dopamine_probe__";return localStorage.setItem(a,"1"),localStorage.removeItem(a),localStorage}catch{return w()}}class C{constructor(e={}){this.storage=e.storage||x(),this.prefix=e.prefix||"dopamine_"}async save(e,t){try{const s=JSON.stringify(t);return this.storage.setItem(this.prefix+e,s),!0}catch(s){return console.error("Error saving data:",s),!1}}async load(e,t=null){try{const s=this.storage.getItem(this.prefix+e);return s?JSON.parse(s):t}catch(s){return console.error("Error loading data:",s),t}}async clear(e){this.storage.removeItem(this.prefix+e)}}const D="game-ui-overlay";class I{constructor(e){this.particleSystem=e,this.container=null,this.xpBar=null,this.levelDisplay=null,this.streakDisplay=null,this._timers=new Set,this._detached=[],this.init()}init(){this.container=document.createElement("div"),this.container.className="game-ui-overlay",document.getElementById(D)||(this.container.id=D),this.container.innerHTML=`
|
|
2
|
-
<div class="game-ui-top-bar">
|
|
3
|
-
<div class="level-badge">
|
|
4
|
-
<span class="level-label">LVL</span>
|
|
5
|
-
<span class="level-number">1</span>
|
|
6
|
-
</div>
|
|
7
|
-
|
|
8
|
-
<div class="xp-container">
|
|
9
|
-
<div class="xp-bar-bg">
|
|
10
|
-
<div class="xp-bar-fill" style="width: 0%"></div>
|
|
11
|
-
</div>
|
|
12
|
-
<div class="xp-text">0 / 100 XP</div>
|
|
13
|
-
</div>
|
|
14
|
-
|
|
15
|
-
<div class="streak-badge">
|
|
16
|
-
<span class="streak-icon">🔥</span>
|
|
17
|
-
<span class="streak-number">1</span>
|
|
18
|
-
</div>
|
|
19
|
-
</div>
|
|
20
|
-
`,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")}_defer(e,t){const s=setTimeout(()=>{this._timers.delete(s),e()},t);return this._timers.add(s),s}_mount(e){return document.body.appendChild(e),this._detached.push(e),e}_replayAnimation(e,t){e&&(e.style.animation="none",this._defer(()=>{e.style.animation=t},10))}updateXP(e,t,s){const i=s>0?e/s*100:0;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")}updateLevel(e){this.levelDisplay.textContent=e,this._replayAnimation(this.levelBadge,"level-pulse 0.5s ease-out")}updateStreak(e){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%)")}showAchievement(e){const t=document.createElement("div");t.className="achievement-popup",t.innerHTML=`
|
|
21
|
-
<div class="achievement-shine"></div>
|
|
22
|
-
<div class="achievement-content">
|
|
23
|
-
<div class="achievement-icon"></div>
|
|
24
|
-
<div class="achievement-info">
|
|
25
|
-
<div class="achievement-title">Achievement Unlocked!</div>
|
|
26
|
-
<div class="achievement-name"></div>
|
|
27
|
-
<div class="achievement-desc"></div>
|
|
28
|
-
<div class="achievement-xp"></div>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
`,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(()=>{const s=t.getBoundingClientRect();this.particleSystem.confetti(s.left+s.width/2,s.top+s.height/2,40)},300),this._defer(()=>{t.style.animation="slideOut 0.3s ease-in forwards",this._defer(()=>this._remove(t),300)},4e3)}showLevelUp(e,t){const s=document.createElement("div");s.className="level-up-overlay",s.innerHTML=`
|
|
32
|
-
<div class="level-up-content">
|
|
33
|
-
<div class="level-up-title">LEVEL UP!</div>
|
|
34
|
-
<div class="level-up-number"></div>
|
|
35
|
-
<div class="level-up-subtitle">Amazing progress!</div>
|
|
36
|
-
</div>
|
|
37
|
-
`,s.querySelector(".level-up-number").textContent=t,this._mount(s),this._defer(()=>{for(let i=0;i<5;i++)this._defer(()=>{const r=Math.random()*window.innerWidth,n=Math.random()*window.innerHeight*.6;this.particleSystem.confetti(r,n,30)},i*200)},300),this._defer(()=>{s.style.opacity="0",this._defer(()=>this._remove(s),500)},2500)}showFloatingText(e,t,s,i="#ffd700",r="24px"){const n=document.createElement("div");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)}showCombo(e,t,s){this.showFloatingText(`${e}× COMBO!`,t,s,"#ff6b6b","32px"),this.particleSystem.fire(t,s,20)}showNearMiss(e,t){this.showFloatingText("CLOSE CALL!",e,t,"#45b7d1","20px"),this.particleSystem.sparkle(e,t,15,"#45b7d1")}showLuckyMoment(e,t,s){this.showFloatingText(`LUCKY ${e}×!`,t,s,"#ffd700","36px"),this.particleSystem.starBurst(t,s,12),this.particleSystem.confetti(t,s,25)}showNotification(e,t="normal"){const s=window.innerWidth/2,i=window.innerHeight/3;t==="legendary"?this.showLuckyMoment(e,s,i):t==="rare"?this.showCombo(e,s,i):this.showFloatingText(e,s,i,"#fff","24px")}showSummary(e){const t=document.createElement("div");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";const s=document.createElement("h1");s.textContent="Game Over";const i=document.createElement("div");i.className="summary-score",i.style.cssText="font-size: 3rem; font-weight: bold; color: #e06020; margin: 10px 0;",i.textContent=e.score;const r=document.createElement("div");r.className="summary-metrics",r.style.cssText="width: 100%; margin-bottom: 20px;";for(const[o,l]of Object.entries(e.metrics||{})){const c=document.createElement("div");c.style.cssText="display:flex;justify-content:space-between;width:100%";const d=document.createElement("span");d.textContent=`${o}:`;const m=document.createElement("strong");m.textContent=l,c.append(d,m),r.appendChild(c)}t.append(s,i,r);const n=document.createElement("button");if(n.className="btn",n.textContent="Play Again",n.addEventListener("click",e.onReplay||(()=>window.location.reload())),t.appendChild(n),e.onExit){const o=document.createElement("button");o.className="btn",o.textContent="Exit",o.style.cssText="margin-top: 10px; background: #543847;",o.addEventListener("click",e.onExit),t.appendChild(o)}return this._mount(t),t}_remove(e){e.remove();const t=this._detached.indexOf(e);t>-1&&this._detached.splice(t,1)}destroy(){var e;for(const t of this._timers)clearTimeout(t);this._timers.clear();for(const t of this._detached)t.remove();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}}class O{constructor(e={}){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=new Map,this.customEffects=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)}_resize(){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;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)}registerSprite(e,t){const s=new Image;s.src=t,this.sprites.set(e,s)}registerEffect(e,t){this.customEffects.set(e,t)}play(e,t,s,...i){const r=this.customEffects.get(e);r?r(t,s,...i):console.warn(`[DopamineJS] Effect '${e}' not found.`)}emit(e){const{x:t,y:s,count:i=10,speed:r=5,gravity:n=.1,decay:o=.02,life:l=1,color:c="#ffd700",sprite:d=null,spread:m=Math.PI*2,angle:y=0,size:v=5}=e;for(let B=0;B<i;B++){const u=this._getParticle(),F=y+(Math.random()-.5)*m,N=Math.random()*r;u.x=t,u.y=s,u.vx=Math.cos(F)*N,u.vy=Math.sin(F)*N,u.life=l,u.decay=o*(.8+Math.random()*.4),u.gravity=n,u.color=Array.isArray(c)?c[Math.floor(Math.random()*c.length)]:c,u.size=v*(.8+Math.random()*.4),u.sprite=d,u.rotation=Math.random()*Math.PI*2,u.rotationSpeed=(Math.random()-.5)*.2,this.particles.push(u)}this._startAnimation()}_getParticle(){return this.pool.length>0?this.pool.pop():{}}_recycleParticle(e){this.pool.push(e)}confetti(e,t,s=50){const i=["#ff6b6b","#4ecdc4","#45b7d1","#f7dc6f","#c06c84","#6c5ce7"];this.emit({x:e,y:t,count:s,color:i,speed:10,gravity:.3,decay:.015,size:8,spread:Math.PI*2})}coinShower(e,t,s=10){this.emit({x:e,y:t,count:s,color:"#ffd700",speed:4,gravity:.4,decay:.012,size:12,angle:-Math.PI/2,spread:Math.PI/2})}sparkle(e,t,s=20,i="#ffd700"){this.emit({x:e,y:t,count:s,color:i,speed:3,gravity:-.05,decay:.03,size:3})}fire(e,t,s=15){const i=["#ff6b00","#ff8800","#ffaa00","#ff4400"];this.emit({x:e,y:t,count:s,color:i,speed:3,gravity:-.2,decay:.04,size:5,angle:-Math.PI/2,spread:Math.PI/4})}starBurst(e,t,s=8){for(let i=0;i<s;i++){const r=this._getParticle(),n=Math.PI*2/s*i,o=4;r.x=e,r.y=t,r.vx=Math.cos(n)*o,r.vy=Math.sin(n)*o,r.life=1,r.decay=.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)}this._startAnimation()}_startAnimation(){this.isAnimating||(this.isAnimating=!0,this._animate())}_animate(){this._frameHandle=null,this.ctx.clearRect(0,0,this.width,this.height);for(let e=this.particles.length-1;e>=0;e--){const t=this.particles[e];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){this._recycleParticle(t),this.particles.splice(e,1);continue}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)){const s=this.sprites.get(t.sprite);s.complete&&this.ctx.drawImage(s,-t.size/2,-t.size/2,t.size,t.size)}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());this.ctx.restore()}this.particles.length>0?this._frameHandle=requestAnimationFrame(()=>this._animate()):this.isAnimating=!1}_drawStarPath(e,t,s,i,r){let n=Math.PI/2*3,o=e,l=t;const c=Math.PI/s;this.ctx.beginPath(),this.ctx.moveTo(e,t-i);for(let d=0;d<s;d++)o=e+Math.cos(n)*i,l=t+Math.sin(n)*i,this.ctx.lineTo(o,l),n+=c,o=e+Math.cos(n)*r,l=t+Math.sin(n)*r,this.ctx.lineTo(o,l),n+=c;this.ctx.lineTo(e,t-i),this.ctx.closePath()}clear(){this.particles.forEach(e=>this._recycleParticle(e)),this.particles=[],this.ctx.clearRect(0,0,this.width,this.height)}destroy(){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=[]}}class T{constructor(e={}){this.audioContext=null,this.storageKey=e.storageKey||"dopamineSoundsMuted",this.storage=e.storage||x(),this.muted=this.storage.getItem(this.storageKey)==="true",this.assets=new Map,this.customSounds=e.customSounds||{},this.masterGain=null,this._volume=e.volume??1,Object.keys(this.customSounds).length>0&&this.preloadSounds(this.customSounds)}initAudio(){if(!this.audioContext){const e=typeof window<"u"?window.AudioContext||window.webkitAudioContext:void 0;if(!e)return!1;this.audioContext=new e,this.masterGain=this.audioContext.createGain(),this.masterGain.gain.value=this._volume,this.masterGain.connect(this.audioContext.destination)}return this.audioContext.state==="suspended"&&this.audioContext.resume(),!0}setVolume(e){return this._volume=Math.max(0,Math.min(1,e)),this.masterGain&&(this.masterGain.gain.value=this._volume),this._volume}getVolume(){return this._volume}_output(){return this.masterGain||this.audioContext.destination}toggleMute(){return this.muted=!this.muted,this.storage.setItem(this.storageKey,this.muted),this.muted}registerSound(e,t){this.customSounds[e]=t,this.audioContext&&this.loadSound(e,t)}async preloadSounds(e){const t=Object.entries(e).map(([s,i])=>this.loadSound(s,i));await Promise.allSettled(t)}async loadSound(e,t){try{if(!this.audioContext)return;const i=await(await fetch(t)).arrayBuffer(),r=await this.audioContext.decodeAudioData(i);this.assets.set(e,r)}catch(s){console.warn(`[DopamineJS] Failed to load sound '${e}':`,s)}}async play(e){if(this.muted||!this.initAudio())return;if(this.assets.has(e)){this._playBuffer(this.assets.get(e));return}if(this.customSounds[e]&&!this.assets.has(e)&&(await this.loadSound(e,this.customSounds[e]),this.assets.has(e))){this._playBuffer(this.assets.get(e));return}const t={jump:()=>this.playJump(!0),score:()=>this.playScore(!0),success:()=>this.playSuccess(!0),click:()=>this.playClick(!0),error:()=>this.playError(!0),gameover:()=>this.playGameOver(!0)};t[e]&&t[e]()}_playBuffer(e){const t=this.audioContext.createBufferSource();t.buffer=e,t.connect(this._output()),t.start(0)}playTone(e,t,s="sine",i=.3){if(this.muted||!this.initAudio())return;const r=this.audioContext.createOscillator(),n=this.audioContext.createGain();r.connect(n),n.connect(this._output()),r.frequency.value=e,r.type=s;const o=Math.max(1e-4,i);n.gain.setValueAtTime(o,this.audioContext.currentTime),n.gain.exponentialRampToValueAtTime(.01,this.audioContext.currentTime+t),r.start(this.audioContext.currentTime),r.stop(this.audioContext.currentTime+t)}playJump(e=!1){if(!e&&this.assets.has("jump"))return this.play("jump");this.playTone(400,.1,"square",.2)}playScore(e=!1){if(!e&&this.assets.has("score"))return this.play("score");this.playTone(800,.15,"sine",.25),setTimeout(()=>this.playTone(1e3,.15,"sine",.25),50)}playGameOver(e=!1){if(!e&&this.assets.has("gameover"))return this.play("gameover");this.playTone(300,.2,"sawtooth",.3),setTimeout(()=>this.playTone(200,.2,"sawtooth",.3),100),setTimeout(()=>this.playTone(150,.3,"sawtooth",.3),200)}playClick(e=!1){if(!e&&this.assets.has("click"))return this.play("click");this.playTone(600,.05,"sine",.1)}playSuccess(e=!1){if(!e&&this.assets.has("success"))return this.play("success");this.playTone(600,.1,"sine",.2),setTimeout(()=>this.playTone(800,.2,"sine",.2),100)}playError(e=!1){if(!e&&this.assets.has("error"))return this.play("error");this.playTone(200,.2,"sawtooth",.3)}}class H{constructor(e={}){this.config=e,this.dataService=new C(e.data||{}),this.rewardSystem=new P(this.dataService,e.rewards||{}),this.soundManager=new T(e.sound||{}),this.particleSystem=new O(e.particles||{}),this.gameUI=new I(this.particleSystem),this._bindEvents()}async init(){return await this.rewardSystem.init(),{rewardSystem:this.rewardSystem,gameUI:this.gameUI,particleSystem:this.particleSystem,soundManager:this.soundManager}}_bindEvents(){this.rewardSystem.on("xp_gained",e=>{const{total:t,needed:s}=this.rewardSystem.getXPForNextLevel();this.gameUI.updateXP(this.rewardSystem.player.xp,s,t),e.xpGained>0}),this.rewardSystem.on("level_up",e=>{this.gameUI.updateLevel(e.newLevel),this.gameUI.showLevelUp(e.oldLevel,e.newLevel)}),this.rewardSystem.on("achievement_unlocked",e=>{this.gameUI.showAchievement(e)}),this.rewardSystem.on("new_high_score",e=>{this.gameUI.showNotification(`New High Score: ${e.score}!`,"legendary"),this.soundManager.playSuccess()})}}class p{constructor(){this._listeners=new Map,this._onceListeners=new Map,this._sortedCache=new Map}on(e,t,s=0){return this._listeners.has(e)||this._listeners.set(e,new Set),this._listeners.get(e).add({callback:t,priority:s}),this._sortedCache.delete(e),this}once(e,t){return this._onceListeners.has(e)||this._onceListeners.set(e,new Set),this._onceListeners.get(e).add(t),this}off(e,t){const s=this._listeners.get(e);if(s)for(const r of[...s])r.callback===t&&(s.delete(r),this._sortedCache.delete(e));const i=this._onceListeners.get(e);return i&&i.delete(t),this}emit(e,t){const s=this._getSortedListeners(e);for(let r=0;r<s.length;r++)this._invoke(s[r].callback,t,e);const i=this._onceListeners.get(e);if(i&&i.size>0){const r=[...i];i.clear();for(const n of r)this._invoke(n,t,e)}}_invoke(e,t,s){try{e(t)}catch(i){console.error(`[DopamineJS] Listener for "${s}" threw:`,i)}}_getSortedListeners(e){if(this._sortedCache.has(e))return this._sortedCache.get(e);const t=this._listeners.get(e);if(!t||t.size===0)return[];const s=Array.from(t).sort((i,r)=>r.priority-i.priority);return this._sortedCache.set(e,s),s}clear(e){e?(this._listeners.delete(e),this._onceListeners.delete(e),this._sortedCache.delete(e)):(this._listeners.clear(),this._onceListeners.clear(),this._sortedCache.clear())}hasListeners(e){const t=this._listeners.get(e),s=this._onceListeners.get(e);return!!(t!=null&&t.size)||!!(s!=null&&s.size)}}p.Events={TICK:"tick",FIXED_UPDATE:"fixed_update",RENDER:"render",COLLISION_ENTER:"collision_enter",COLLISION_EXIT:"collision_exit",XP_GAINED:"xp_gained",LEVEL_UP:"level_up",ACHIEVEMENT_UNLOCKED:"achievement_unlocked",NEW_HIGH_SCORE:"new_high_score",STREAK_UPDATED:"streak_updated",SYSTEM_REGISTERED:"system_registered",SYSTEM_UNREGISTERED:"system_unregistered",PLUGIN_LOADED:"plugin_loaded",PLUGIN_UNLOADED:"plugin_unloaded"};class A{constructor(e){this.kernel=e,this._systems=new Map,this._updateOrder=[],this._fixedUpdateOrder=[],this._needsRecompute=!1}register(e,t,{priority:s=0,dependencies:i=[]}={}){this._systems.has(e)&&(console.warn(`[SystemRegistry] System "${e}" already registered. Replacing.`),this.unregister(e));for(const r of i)this._systems.has(r)||console.warn(`[SystemRegistry] Dependency "${r}" not found for system "${e}"`);return this._systems.set(e,{system:t,priority:s,dependencies:i}),t.init&&t.init(this.kernel),this._needsRecompute=!0,this.kernel.events.emit("system_registered",{name:e,system:t}),this}get(e){const t=this._systems.get(e);return t?t.system:void 0}has(e){return this._systems.has(e)}unregister(e){const t=this._systems.get(e);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}update(e){this._needsRecompute&&this._recomputeUpdateOrder();for(let t=0;t<this._updateOrder.length;t++){const s=this._updateOrder[t];s.update&&s.update(e)}}fixedUpdate(e){this._needsRecompute&&this._recomputeUpdateOrder();for(let t=0;t<this._fixedUpdateOrder.length;t++){const s=this._fixedUpdateOrder[t];s.fixedUpdate&&s.fixedUpdate(e)}}_recomputeUpdateOrder(){const e=Array.from(this._systems.entries()),t=new Map,s=new Map;for(const[n,o]of e)t.set(n,o.dependencies),s.set(n,0);for(const[n,o]of t)for(const l of o)s.has(l)&&s.set(n,s.get(n)+1);const i=[],r=[];for(const[n,o]of s)o===0&&i.push(n);for(;i.length>0;){i.sort((o,l)=>{const c=this._systems.get(o).priority;return this._systems.get(l).priority-c});const n=i.shift();r.push(n);for(const[o,l]of t)if(l.includes(n)){const c=s.get(o)-1;s.set(o,c),c===0&&i.push(o)}}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}getSystemNames(){return Array.from(this._systems.keys())}clear(){for(const e of this._systems.keys())this.unregister(e)}}class L{constructor(e){this.kernel=e,this._plugins=new Map,this._loadOrder=[]}use(e){if(!e.name)throw new Error("[PluginRegistry] Plugin must have a name");if(this._plugins.has(e.name))return console.warn(`[PluginRegistry] Plugin "${e.name}" already registered. Skipping.`),this;if(!e.init||typeof e.init!="function")throw new Error(`[PluginRegistry] Plugin "${e.name}" must have an init() method`);this._plugins.set(e.name,e),this._loadOrder.push(e.name);try{e.init(this.kernel),this.kernel.events.emit("plugin_loaded",{name:e.name,version:e.version})}catch(t){throw console.error(`[PluginRegistry] Failed to initialize plugin "${e.name}":`,t),this._plugins.delete(e.name),this._loadOrder.pop(),t}return this}async useAsync(e){if(!e.name)throw new Error("[PluginRegistry] Plugin must have a name");if(this._plugins.has(e.name))return console.warn(`[PluginRegistry] Plugin "${e.name}" already registered. Skipping.`),this;if(!e.init||typeof e.init!="function")throw new Error(`[PluginRegistry] Plugin "${e.name}" must have an init() method`);this._plugins.set(e.name,e),this._loadOrder.push(e.name);try{await e.init(this.kernel),this.kernel.events.emit("plugin_loaded",{name:e.name,version:e.version})}catch(t){throw console.error(`[PluginRegistry] Failed to initialize plugin "${e.name}":`,t),this._plugins.delete(e.name),this._loadOrder.pop(),t}return this}remove(e){const t=this._plugins.get(e);if(!t)return!1;if(t.destroy)try{t.destroy()}catch(i){console.error(`[PluginRegistry] Error destroying plugin "${e}":`,i)}this._plugins.delete(e);const s=this._loadOrder.indexOf(e);return s>-1&&this._loadOrder.splice(s,1),this.kernel.events.emit("plugin_unloaded",{name:e}),!0}get(e){return this._plugins.get(e)}has(e){return this._plugins.has(e)}getPluginNames(){return Array.from(this._plugins.keys())}getLoadOrder(){return[...this._loadOrder]}clear(){for(let e=this._loadOrder.length-1;e>=0;e--)this.remove(this._loadOrder[e])}}class U{constructor(e={}){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")}clear(){this.ctx.fillStyle=this.backgroundColor,this.ctx.fillRect(0,0,this.width,this.height)}resize(e,t){this.width=e,this.height=t,this.canvas.width=e,this.canvas.height=t}}class R{constructor(){this.running=!1,this.lastTime=0,this.callbacks=new Set,this._frameHandle=null,this._tick=this._tick.bind(this)}add(e){this.callbacks.add(e)}remove(e){this.callbacks.delete(e)}start(){this.running||(this.running=!0,this.lastTime=performance.now(),this._frameHandle!==null&&cancelAnimationFrame(this._frameHandle),this._frameHandle=requestAnimationFrame(this._tick))}stop(){this.running=!1,this._frameHandle!==null&&(cancelAnimationFrame(this._frameHandle),this._frameHandle=null)}_tick(e){if(this._frameHandle=null,!this.running)return;const t=(e-this.lastTime)/1e3;this.lastTime=e;const s=Math.min(t,.1);for(const i of[...this.callbacks])i(s);this.running&&(this._frameHandle=requestAnimationFrame(this._tick))}}function S(a,e){let t=!1;return new Proxy(a,{get(s,i,r){t||(t=!0,console.warn(e));const n=Reflect.get(s,i,r);return typeof n=="function"?n.bind(s):n},set(s,i,r){return t||(t=!0,console.warn(e)),Reflect.set(s,i,r)}})}class g{constructor(){this.gameObject=null,this.kernel=null}get physics(){var e;return(e=this.kernel)==null?void 0:e.systems.get("physics")}get input(){var e;return(e=this.kernel)==null?void 0:e.systems.get("input")}get loader(){var e;return(e=this.kernel)==null?void 0:e.systems.get("loader")}get events(){var e;return(e=this.kernel)==null?void 0:e.events}onAttach(){}onDetach(){}update(e){}render(e){}}class $ extends g{constructor(e="box",t=50,s=50,i=25){super(),this.type=e,this.width=t,this.height=s,this.radius=i,this.tag=null}onAttach(){super.onAttach(),this.physics&&this.physics.add(this)}onDetach(){super.onDetach(),this.physics&&this.physics.remove(this)}getBounds(){const e=this.gameObject;if(this.type==="box")return{left:e.x-this.width/2,right:e.x+this.width/2,top:e.y-this.height/2,bottom:e.y+this.height/2};if(this.type==="circle")return{x:e.x,y:e.y,radius:this.radius}}}class k{constructor(){this.colliders=[],this.kernel=null}init(e){this.kernel=e}fixedUpdate(e){this.step()}destroy(){this.colliders=[]}add(e){this.colliders.push(e)}remove(e){const t=this.colliders.indexOf(e);t>-1&&this.colliders.splice(t,1)}step(){for(let e=0;e<this.colliders.length;e++)for(let t=e+1;t<this.colliders.length;t++){const s=this.colliders[e],i=this.colliders[t];this._intersects(s,s.getBounds(),i,i.getBounds())&&(this._notifyCollision(s.gameObject,i.gameObject),this._notifyCollision(i.gameObject,s.gameObject))}}_notifyCollision(e,t){!e||!e.components||e.components.forEach(s=>{s.onCollisionEnter&&s.onCollisionEnter(t)})}checkOverlap(e,t){const s=[],i=e.getBounds();for(const r of this.colliders){if(r===e||t&&r.tag!==t)continue;const n=r.getBounds();this._intersects(e,i,r,n)&&s.push(r)}return s}_intersects(e,t,s,i){if(e.type==="box"&&s.type==="box")return t.left<i.right&&t.right>i.left&&t.top<i.bottom&&t.bottom>i.top;if(e.type==="circle"&&s.type==="circle"){const r=t.x-i.x,n=t.y-i.y;return Math.sqrt(r*r+n*n)<t.radius+i.radius}return!1}addBody(e){this.add(e)}removeBody(e){this.remove(e)}checkCollision(e,t){return this._intersects(e,e.getBounds(),t,t.getBounds())}raycast(e,t,s){return null}setGravity(e,t){}}const q=S(new k,"[DopamineJS] GlobalPhysics is deprecated. Use kernel.physics instead.");class b{constructor(){this.keys=new Map,this.mouse={x:0,y:0,buttons: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)}init(e){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)}destroy(){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)}_onKeyDown(e){this.keys.set(e.key,!0)}_onKeyUp(e){this.keys.set(e.key,!1)}_onMouseMove(e){this.mouse.x=e.clientX,this.mouse.y=e.clientY}_onMouseDown(e){this.mouse.buttons.set(e.button,!0)}_onMouseUp(e){this.mouse.buttons.set(e.button,!1)}isKeyDown(e){return this.keys.get(e)||!1}isMouseButtonDown(e=0){return this.mouse.buttons.get(e)||!1}}const K=S(new b,"[DopamineJS] GlobalInput is deprecated. Use kernel.input instead.");class M{constructor(){this.assets=new Map,this.kernel=null}init(e){this.kernel=e}destroy(){this.assets.clear()}async loadImage(e,t){return new Promise((s,i)=>{const r=new Image;r.onload=()=>{this.assets.set(e,r),s(r)},r.onerror=i,r.src=t})}get(e){return this.assets.get(e)}}const X=S(new M,"[DopamineJS] GlobalLoader is deprecated. Use kernel.loader instead.");class G{constructor(e={}){this.config=e,this._boundUpdate=this._update.bind(this),this.events=new p,this.systems=new A(this),this.plugins=new L(this),this._registerCoreSystems(e),this.fixedTimestep=e.fixedTimestep||1/60,this._accumulator=0,this._maxAccumulator=this.fixedTimestep*5}_registerCoreSystems(e){this.systems.register("ticker",new R,{priority:100});const t={width:e.width,height:e.height,backgroundColor:e.backgroundColor,canvas:e.canvas,...e.renderer};this.systems.register("renderer",new U(t),{priority:90}),this.systems.register("input",new b,{priority:80}),this.systems.register("loader",new M,{priority:70}),this.systems.register("physics",new k,{priority:60})}start(){const e=this.systems.get("ticker");e&&(e.add(this._boundUpdate),e.start()),this.events.emit("kernel_started")}stop(){const e=this.systems.get("ticker");e&&e.stop(),this.events.emit("kernel_stopped")}_update(e){for(this.events.emit(p.Events.TICK,{dt:e}),this._accumulator+=e,this._accumulator>this._maxAccumulator&&(this._accumulator=this._maxAccumulator);this._accumulator>=this.fixedTimestep;)this.events.emit(p.Events.FIXED_UPDATE,{dt:this.fixedTimestep}),this.systems.fixedUpdate(this.fixedTimestep),this._accumulator-=this.fixedTimestep;this.systems.update(e),this.events.emit(p.Events.RENDER,{dt:e})}get physics(){return this.systems.get("physics")}get input(){return this.systems.get("input")}get loader(){return this.systems.get("loader")}get renderer(){return this.systems.get("renderer")}get ticker(){return this.systems.get("ticker")}destroy(){this.stop();const e=this.systems.get("ticker");e&&e.remove(this._boundUpdate),this.plugins.clear(),this.systems.clear(),this.events.clear()}}const _={init(a){},update(a){},fixedUpdate(a){},destroy(){}};class J{constructor(){this.kernel=null}init(e){this.kernel=e}update(e){}fixedUpdate(e){}destroy(){}}const W={..._,addBody(a){},removeBody(a){},checkCollision(a,e){},raycast(a,e,t){},setGravity(a,e){},step(a){}},V={..._,registerSound(a,e){},preloadSounds(a){},play(a,e){},playTone(a,e,t,s){},stop(a){},toggleMute(){},setVolume(a){}},Y={..._,registerSprite(a,e){},registerEffect(a,e){},play(a,e,t,...s){},emit(a){},clear(){},confetti(a,e,t){},coinShower(a,e,t){},sparkle(a,e,t,s){},fire(a,e,t){},starBurst(a,e,t){}};class Q{constructor(){this.gameObjects=[],this.kernel=null}onEnter(){}onExit(){}add(e){return this.kernel&&(e.kernel=this.kernel),this.gameObjects.push(e),e}remove(e){const t=this.gameObjects.indexOf(e);t>-1&&this.gameObjects.splice(t,1)}update(e){for(const t of this.gameObjects)t.update(e)}render(e){for(const t of this.gameObjects)t.render(e)}}class z{constructor(e){this.game=e,this.currentScene=null}run(e){this.currentScene&&this.currentScene.onExit(),this.currentScene=e,this.currentScene.game=this.game,this.game.scene=e,e.onEnter()}}class Z{constructor(e={}){this.config=e,this.kernel=new G(e),this.director=new z(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)}get renderer(){return this.kernel.renderer}get ticker(){return this.kernel.ticker}get physics(){return this.kernel.physics}get input(){return this.kernel.input}start(){this.kernel.start()}stop(){this.kernel.stop()}setScene(e){e.kernel=this.kernel,this.director.run(e)}_update({dt:e}){this.director.currentScene&&this.director.currentScene.update(e)}_render({dt:e}){this.renderer.clear(),this.director.currentScene&&this.director.currentScene.render(this.renderer.ctx)}destroy(){this.kernel.destroy()}}class ee{constructor(e=0,t=0){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}get kernel(){return this._kernel}set kernel(e){this._kernel=e;for(const t of this.components)t.kernel=e;for(const t of this.children)t.kernel=e}addChild(e){return e.parent=this,e.kernel=this.kernel,this.children.push(e),e}addComponent(e){return e.gameObject=this,e.kernel=this.kernel,this.components.push(e),e.onAttach(),e}getComponent(e){return this.components.find(t=>t instanceof e)}update(e){for(const t of this.components)t.update&&t.update(e);for(const t of this.children)t.update(e)}render(e){e.save(),e.translate(this.x,this.y),e.rotate(this.rotation),e.scale(this.scale.x,this.scale.y);for(const t of this.components)t.render&&t.render(e);for(const t of this.children)t.render(e);e.restore()}}class E{constructor(e=0,t=0){this.x=e,this.y=t}add(e){return this.x+=e.x,this.y+=e.y,this}sub(e){return this.x-=e.x,this.y-=e.y,this}scale(e){return this.x*=e,this.y*=e,this}distance(e){const t=this.x-e.x,s=this.y-e.y;return Math.sqrt(t*t+s*s)}normalize(){const e=Math.sqrt(this.x*this.x+this.y*this.y);return e>0&&(this.x/=e,this.y/=e),this}clone(){return new E(this.x,this.y)}static distance(e,t){return Math.sqrt(Math.pow(t.x-e.x,2)+Math.pow(t.y-e.y,2))}}class j extends g{constructor(e){super(),this.image=e,this.anchor={x:.5,y:.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}}setFrame(e,t,s,i){this.frame={x:e,y:t,w:s,h:i},this.width=s,this.height=i}setTexture(e){this.image=e,e&&this.setFrame(0,0,e.width,e.height)}render(e){if(!this.image)return;const t=this.frame.w,s=this.frame.h,i=-t*this.anchor.x,r=-s*this.anchor.y;e.drawImage(this.image,this.frame.x,this.frame.y,t,s,i,r,t,s)}}class te extends g{constructor(){super(),this.animations={},this.currentAnim=null,this.timer=0,this.currentFrameIndex=0,this.sprite=null}onAttach(){this.sprite=this.gameObject.getComponent(j),this.sprite||console.warn("Animator attached to GameObject without a Sprite component.")}addAnimation(e,t,s=10,i=!0){this.animations[e]={frames:t,fps:s,loop:i}}play(e){this.currentAnim&&this.currentAnim.name===e||this.animations[e]&&(this.currentAnim={name:e,...this.animations[e]},this.currentFrameIndex=0,this.timer=0,this._updateFrame())}update(e){if(!this.currentAnim||!this.sprite)return;this.timer+=e;const t=1/this.currentAnim.fps;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())}_updateFrame(){const e=this.currentAnim.frames[this.currentFrameIndex];this.sprite.setFrame(e.x,e.y,e.w,e.h)}}class se{static createGround(e,t,s="#333"){const i=document.createElement("canvas");i.width=e,i.height=t;const r=i.getContext("2d");r.fillStyle=s,r.fillRect(0,0,e,t);for(let n=0;n<1e3;n++){r.fillStyle=Math.random()>.5?"#444":"#222";const o=Math.random()*4+2;r.fillRect(Math.random()*e,Math.random()*t,o,o)}r.strokeStyle="rgba(255,255,255,0.05)",r.lineWidth=2,r.beginPath();for(let n=0;n<=e;n+=64)r.moveTo(n,0),r.lineTo(n,t);for(let n=0;n<=t;n+=64)r.moveTo(0,n),r.lineTo(e,n);return r.stroke(),i}static createCharacterSheet(e,t){const r=document.createElement("canvas");r.width=64*4,r.height=64;const n=r.getContext("2d");for(let o=0;o<4;o++){const l=o*64,c=0,d=l+64/2,m=c+64/2,y=o%2===0?0:-2,v=o===1?5:o===3?-5:0;n.fillStyle=e,n.beginPath(),n.arc(d,m+y,20,0,Math.PI*2),n.fill(),n.fillStyle=t,n.beginPath(),n.arc(d-15,m+y+v,8,0,Math.PI*2),n.arc(d+15,m+y-v,8,0,Math.PI*2),n.fill(),n.fillStyle=ie(e),n.beginPath(),n.arc(d,m+y,15,0,Math.PI*2),n.fill(),t==="#333"&&(n.fillStyle="#000",n.fillRect(d+10,m+y-5,25,6))}return r}}function ie(a,e){return a}class re extends g{constructor(e){super(),this.system=e}play(e,t={}){const s=this.gameObject.x,i=this.gameObject.y;switch(e){case"confetti":this.system.confetti(s,i,t.count);break;case"fire":this.system.fire(s,i,t.count);break;case"sparkle":this.system.sparkle(s,i,t.count,t.color);break;case"blood":this.system.emit({x:s,y:i,count:10,color:["#8a0303","#ff0000"],speed:4,gravity:.1,decay:.05});break;default:this.system.emit({x:s,y:i,...t})}}}class ne extends g{constructor(){super(),this.intensity=0,this.duration=0,this.timer=0,this.originalPos={x:0,y:0},this.isShaking=!1}shake(e=5,t=.2){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)}update(e){if(!this.isShaking)return;if(this.timer+=e,this.timer>=this.duration){this.isShaking=!1,this.gameObject.x=this.originalPos.x,this.gameObject.y=this.originalPos.y;return}const t=(Math.random()-.5)*2*this.intensity,s=(Math.random()-.5)*2*this.intensity;this.gameObject.x=this.originalPos.x+t,this.gameObject.y=this.originalPos.y+s}}h.Animator=te,h.Collider=$,h.Component=g,h.DataService=C,h.Director=z,h.DopamineKernel=G,h.EventBus=p,h.EventEmitter=f,h.Game=Z,h.GameObject=ee,h.GameUI=I,h.GlobalInput=K,h.GlobalLoader=X,h.GlobalPhysics=q,h.IAudioSystem=V,h.IParticleSystem=Y,h.IPhysicsSystem=W,h.ISystem=_,h.Input=b,h.Loader=M,h.ParticleEmitter=re,h.ParticleSystem=O,h.Physics=k,h.PluginRegistry=L,h.Renderer=U,h.RewardSystem=P,h.Scene=Q,h.ScreenShake=ne,h.SoundManager=T,h.Sprite=j,h.System=J,h.SystemRegistry=A,h.TextureGenerator=se,h.Ticker=R,h.Vector2=E,h.createMemoryStorage=w,h.default=H,h.resolveStorage=x,Object.defineProperties(h,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
|