const BONUS_LIFETIME_MS = 15000; const BLINK_START_MS = 10000; // blink when < 5s left (at 10s elapsed of 15s) const COOLDOWN_PICKUP = 10000; const COOLDOWN_EXPIRE = 10000; const FIRST_SPAWN_MS = 20000; const PICKUP_RADIUS = 32; const BONUS_TYPES = ['medpack', 'shotgun', 'rocket', 'fourway', 'stimulant']; const FRAME_INDEX = { medpack: 0, shotgun: 1, rocket: 2, fourway: 3, stimulant: 4 }; const GLOW_COLOR = { medpack: 0x44ff44, shotgun: 0xff9900, rocket: 0xff3300, fourway: 0x00ffff, stimulant: 0xaa44ff }; export class BonusManager { constructor(scene, player) { this._scene = scene; this._player = player; this._countdown = FIRST_SPAWN_MS; this._active = null; // { sprite, glow, glowTween, blinkTween, type, elapsed } } update(delta) { if (!this._active) { if (this._player.weaponMode === 'default') { this._countdown -= delta; if (this._countdown <= 0) this._spawn(); } return; } const a = this._active; a.elapsed += delta; // Pickup check const dist = Phaser.Math.Distance.Between( a.sprite.x, a.sprite.y, this._player.x, this._player.y ); if (dist < PICKUP_RADIUS) { this._player.applyBonus(a.type); this._clear(); this._countdown = COOLDOWN_PICKUP; return; } // Start blinking for last 5 seconds if (a.elapsed >= BLINK_START_MS && !a.blinkTween) { a.blinkTween = this._scene.tweens.add({ targets: [a.sprite, a.glow], alpha: 0, duration: 200, yoyo: true, repeat: -1, }); } // Expiry if (a.elapsed >= BONUS_LIFETIME_MS) { this._clear(); this._countdown = COOLDOWN_EXPIRE; } } _spawn() { const type = BONUS_TYPES[Phaser.Math.Between(0, BONUS_TYPES.length - 1)]; const cell = this._findSpawnCell(); if (!cell) { // No valid cell found — retry in 2 seconds this._countdown = 2000; return; } const { cx, cy } = cell; const glow = this._scene.add.circle(cx, cy, 24, GLOW_COLOR[type], 0.4).setDepth(4); const glowTween = this._scene.tweens.add({ targets: glow, scaleX: 1.4, scaleY: 1.4, alpha: 0.1, duration: 700, yoyo: true, repeat: -1, }); const sprite = this._scene.add.image(cx, cy, 'bonus', FRAME_INDEX[type]).setDepth(5); this._active = { sprite, glow, glowTween, blinkTween: null, type, elapsed: 0 }; } _findSpawnCell() { const W = this._scene.scale.width; const H = this._scene.scale.height; const COLS = Math.floor(W / 80); const ROWS = Math.floor(H / 80); const candidates = []; for (let col = 1; col < COLS - 1; col++) { for (let row = 1; row < ROWS - 1; row++) { const cx = col * 80 + 40; const cy = row * 80 + 40; if (this._scene.barrierManager?.isPointBlocked(cx, cy)) continue; if (Phaser.Math.Distance.Between(cx, cy, this._player.x, this._player.y) < 300) continue; candidates.push({ cx, cy }); } } return candidates.length ? Phaser.Utils.Array.GetRandom(candidates) : null; } _clear() { if (!this._active) return; const a = this._active; a.blinkTween?.stop(); a.glowTween?.stop(); a.sprite?.destroy(); a.glow?.destroy(); this._active = null; } destroy() { this._clear(); } }