107 lines
3.2 KiB
JavaScript
107 lines
3.2 KiB
JavaScript
/**
|
|
* Abwurfobjekte der Orbital-Befehle: Verteidigungsgeschütz und Nachschubkapsel.
|
|
* @module entities/Deployables
|
|
*/
|
|
|
|
import { GAME } from '../config.js';
|
|
import { angleTo, distance } from '../logic/math.js';
|
|
import { TEXTURE_KEYS } from '../systems/Placeholders.js';
|
|
|
|
/** Letzte Sekunden, in denen ein auslaufendes Objekt blinkt. */
|
|
const EXPIRY_WARNING_MS = 3000;
|
|
|
|
/**
|
|
* Geschütz »Wachsamer Nachbar«: zielt automatisch auf den nächsten Gegner in Reichweite.
|
|
*/
|
|
export class Turret extends Phaser.GameObjects.Sprite {
|
|
/**
|
|
* @param {Phaser.Scene} scene
|
|
* @param {number} x
|
|
* @param {number} y
|
|
* @param {{durationMs: number, fireIntervalMs: number, range: number, bulletDamage: number, bulletSpeed: number}} cfg
|
|
* @param {number} now
|
|
*/
|
|
constructor(scene, x, y, cfg, now) {
|
|
super(scene, x, y, TEXTURE_KEYS.turret);
|
|
scene.add.existing(this);
|
|
this.setDepth(GAME.depth.pickups);
|
|
this.cfg = cfg;
|
|
this.expiresAt = now + cfg.durationMs;
|
|
this.nextShotAt = now;
|
|
}
|
|
|
|
/**
|
|
* @param {number} now
|
|
* @param {Iterable<import('./Enemy.js').Enemy>} enemies
|
|
* @param {(x: number, y: number, angle: number) => void} fire
|
|
* @returns {boolean} false, wenn die Einsatzzeit abgelaufen ist.
|
|
*/
|
|
step(now, enemies, fire) {
|
|
if (now >= this.expiresAt) return false;
|
|
let target = null;
|
|
let best = this.cfg.range;
|
|
for (const enemy of enemies) {
|
|
if (!enemy.alive) continue;
|
|
const d = distance(this, enemy);
|
|
if (d < best) {
|
|
best = d;
|
|
target = enemy;
|
|
}
|
|
}
|
|
if (target) {
|
|
const angle = angleTo(this, target);
|
|
this.setRotation(angle);
|
|
if (now >= this.nextShotAt) {
|
|
this.nextShotAt = now + this.cfg.fireIntervalMs;
|
|
fire(this.x + Math.cos(angle) * 24, this.y + Math.sin(angle) * 24, angle);
|
|
}
|
|
}
|
|
// Kurz vor Ablauf blinken, damit der Spieler nicht überrascht wird.
|
|
const left = this.expiresAt - now;
|
|
this.setAlpha(left < EXPIRY_WARNING_MS && Math.floor(now / 150) % 2 === 0 ? 0.4 : 1);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Nachschubkapsel »Fürsorgepaket«: Beim Berühren gibt es Leben und Munition.
|
|
*/
|
|
export class SupplyPod extends Phaser.Physics.Arcade.Sprite {
|
|
/**
|
|
* @param {Phaser.Scene} scene
|
|
* @param {number} x
|
|
* @param {number} y
|
|
* @param {{lifetimeMs: number, pickupRadius: number, heal: number}} cfg
|
|
* @param {number} now
|
|
*/
|
|
constructor(scene, x, y, cfg, now) {
|
|
super(scene, x, y, TEXTURE_KEYS.supplyPod);
|
|
scene.add.existing(this);
|
|
scene.physics.add.existing(this);
|
|
this.setDepth(GAME.depth.pickups);
|
|
this.cfg = cfg;
|
|
this.expiresAt = now + cfg.lifetimeMs;
|
|
this.collected = false;
|
|
}
|
|
|
|
/** Nach dem Einfügen in die Gruppe: runden Aufnahmebereich setzen. */
|
|
initBody() {
|
|
const r = this.cfg.pickupRadius;
|
|
this.body.setCircle(r, this.width / 2 - r, this.height / 2 - r);
|
|
// Liegt fest am Boden: kein Wegschieben durch Physik.
|
|
this.body.setImmovable(true);
|
|
this.body.moves = false;
|
|
}
|
|
|
|
/**
|
|
* @param {number} now
|
|
* @returns {boolean} false, wenn die Kapsel abgelaufen ist.
|
|
*/
|
|
tick(now) {
|
|
const left = this.expiresAt - now;
|
|
if (left <= 0) return false;
|
|
this.setAlpha(left < EXPIRY_WARNING_MS && Math.floor(now / 150) % 2 === 0 ? 0.4 : 1);
|
|
return true;
|
|
}
|
|
}
|