import { ChaseEnemy } from '../entities/enemies/ChaseEnemy.js'; import { ShooterEnemy } from '../entities/enemies/ShooterEnemy.js'; import { SwarmerEnemy } from '../entities/enemies/SwarmerEnemy.js'; const ENEMY_CLASSES = { ChaseEnemy, ShooterEnemy, SwarmerEnemy }; const SPAWN_MARGIN = 40; export class WaveManager { constructor(scene, player) { this.scene = scene; this.player = player; this.zones = []; this.zoneIndex = 0; this.waveIndex = 0; this.enemies = []; this.active = false; this.spriteGroup = scene.physics.add.group(); } load(data) { this.zones = data; } start() { this.active = true; this._spawnWave(); } get currentZone() { return this.zones[this.zoneIndex]; } get currentWave() { return this.currentZone?.waves[this.waveIndex]; } get zoneNum() { return this.zoneIndex + 1; } get waveNum() { return this.waveIndex + 1; } get totalWaves() { return this.currentZone?.waves.length ?? 0; } _spawnWave() { const wave = this.currentWave; if (!wave) return; this.scene.events.emit('wave-start', { zone: this.zoneNum, wave: this.waveNum, totalWaves: this.totalWaves }); wave.enemies.forEach(({ type, count }) => { const EnemyClass = ENEMY_CLASSES[type]; if (!EnemyClass) return; for (let i = 0; i < count; i++) { const [x, y] = this._edgePosition(); const enemy = new EnemyClass(this.scene, x, y, this.player); this.enemies.push(enemy); this.spriteGroup.add(enemy.sprite); } }); } _edgePosition() { const W = this.scene.scale.width; const H = this.scene.scale.height; const side = Phaser.Math.Between(0, 3); switch (side) { case 0: return [Phaser.Math.Between(0, W), -SPAWN_MARGIN]; case 1: return [W + SPAWN_MARGIN, Phaser.Math.Between(0, H)]; case 2: return [Phaser.Math.Between(0, W), H + SPAWN_MARGIN]; case 3: return [-SPAWN_MARGIN, Phaser.Math.Between(0, H)]; } } update(delta) { if (!this.active) return; // Prune dead enemies this.enemies = this.enemies.filter(e => e.active); // Update living enemies this.enemies.forEach(e => e.update(delta)); // Check wave clear if (this.enemies.length === 0) { this.active = false; this.scene.time.delayedCall(1500, () => this._advance()); } } _advance() { this.waveIndex++; if (this.waveIndex >= this.currentZone.waves.length) { // All waves cleared — let the player exit to trigger next zone this.scene.events.emit('zone-clear', { zone: this.zoneNum }); this.scene.events.emit('zone-waves-complete'); } else { this.active = true; this._spawnWave(); } } /** Called by GameScene after the player exits the screen. */ startNextZone() { this.waveIndex = 0; this.zoneIndex++; if (this.zoneIndex >= this.zones.length) { this.scene.events.emit('victory'); return; } this.active = true; this._spawnWave(); } reset() { this.enemies.forEach(e => { if (e.active) e.destroy(); }); this.enemies = []; this.spriteGroup.clear(false, false); this.zoneIndex = 0; this.waveIndex = 0; this.active = false; } }