120 lines
3.3 KiB
JavaScript
120 lines
3.3 KiB
JavaScript
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();
|
|
}
|
|
}
|