import { BaseEnemy } from './BaseEnemy.js'; const EXPLODE_RADIUS = 180; // px — triggers proximity explosion const PROXIMITY_SHARD_PX = 250; // travel distance for proximity explosion const DEATH_SHARD_PX = 200; // travel distance for on-death explosion const SHARD_COUNT = 8; const SHARD_SPEED = 360; // px/s baseline (overridden per explosion type) const SHARD_DAMAGE = 15; const SHARD_DURATION = 700; // ms until shards disappear const SHARD_HIT_RADIUS = 18; // px — how close a shard must be to damage player const TICK_MS = 16; export class BomberEnemy extends BaseEnemy { constructor(scene, x, y, player) { super(scene, x, y, player, { frameOffset: 12, radius: 10, hp: 20, speed: 130, xp: 20, contactDamage: 5, // low — explosion is the main attack }); this._offsetAngle = Math.random() * Math.PI * 2; this._wobble = 0.3 + Math.random() * 0.4; this._exploded = false; this._deathSound = 'sfx-death-bomber'; } update(delta) { super.update(delta); if (this._dying) return; // Swarm-like wobble movement const t = this.scene.time.now * 0.001; const baseAngle = Phaser.Math.Angle.Between(this.x, this.y, this.player.x, this.player.y); const angle = baseAngle + Math.sin(t * 4 + this._offsetAngle) * this._wobble; this.sprite.body.setVelocity(Math.cos(angle) * this.speed, Math.sin(angle) * this.speed); // Proximity check — explode when close enough const dist = Phaser.Math.Distance.Between(this.x, this.y, this.player.x, this.player.y); if (dist < EXPLODE_RADIUS) { this._triggerExplosion(); } } _triggerExplosion() { if (this._dying) return; this._exploded = true; this._spawnShards(PROXIMITY_SHARD_PX); this._die(); } // Override _die so a bullet-killed Bomber still explodes (smaller radius). _die() { if (!this._exploded) { this._exploded = true; this._spawnShards(DEATH_SHARD_PX); } super._die(); } _spawnShards(travelPx) { const shardSpeed = travelPx / (SHARD_DURATION / 1000); const scene = this.scene; const ox = this.x; const oy = this.y; const player = this.player; const barriers = scene.barrierManager; // Burst flash at explosion center const flash = scene.add.circle(ox, oy, 36, 0xff6600, 0.9).setDepth(25); scene.tweens.add({ targets: flash, alpha: 0, scaleX: 2.5, scaleY: 2.5, duration: 320, onComplete: () => flash.destroy(), }); const ring = scene.add.circle(ox, oy, 42, 0xffaa00, 0).setDepth(25) .setStrokeStyle(3, 0xffaa00, 0.9); scene.tweens.add({ targets: ring, scaleX: 2.2, scaleY: 2.2, alpha: 0, duration: 400, onComplete: () => ring.destroy(), }); scene.cameras.main.shake(180, 0.01); // Build shards: each is a Phaser Triangle game object const shards = []; for (let i = 0; i < SHARD_COUNT; i++) { const angle = (i / SHARD_COUNT) * Math.PI * 2; // Triangle pointing "up" in local space: tip at top, base at bottom const tri = scene.add.triangle( ox, oy, 0, -10, // tip 7, 7, // base-right -7, 7, // base-left 0xff4400, ).setDepth(22).setRotation(angle); // rotate to face travel direction // Add a neon outline tri.setStrokeStyle(1, 0xffaa00); shards.push({ obj: tri, vx: Math.cos(angle) * shardSpeed, vy: Math.sin(angle) * shardSpeed, rotSpeed: (Math.random() > 0.5 ? 1 : -1) * (6 + Math.random() * 6), // rad/s alive: true, }); } // Manual update loop — avoids tween onComplete reliability issues let elapsed = 0; const playerDamaged = new Set(); const ticker = scene.time.addEvent({ delay: TICK_MS, repeat: Math.ceil(SHARD_DURATION / TICK_MS), callback: () => { elapsed += TICK_MS; const dt = TICK_MS / 1000; const fade = Math.max(0, 1 - elapsed / SHARD_DURATION); for (const shard of shards) { if (!shard.alive) continue; shard.obj.x += shard.vx * dt; shard.obj.y += shard.vy * dt; shard.obj.rotation += shard.rotSpeed * dt; shard.obj.alpha = 0.4 + fade * 0.6; // Player hit if (!playerDamaged.has(shard)) { const d = Phaser.Math.Distance.Between(shard.obj.x, shard.obj.y, player.x, player.y); if (d < SHARD_HIT_RADIUS + 16) { player.takeDamage(SHARD_DAMAGE); playerDamaged.add(shard); shard.alive = false; shard.obj.destroy(); continue; } } // Interior barrier hit if (barriers?.destroyInteriorBarrierAt(shard.obj.x, shard.obj.y)) { shard.alive = false; shard.obj.destroy(); continue; } } if (elapsed >= SHARD_DURATION) { shards.forEach(s => { if (s.alive) s.obj.destroy(); }); ticker.remove(); } }, }); } }