slop game oder so
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* Orbital-Befehle: Eingabe (Halte-Taste + Richtungsfolge), Abklingzeiten,
|
||||
* Bodenmarkierung und verzögerter Einschlag.
|
||||
*
|
||||
* Die Eingabelogik steckt in `logic/comboInput.js`, die Abklingzeiten in
|
||||
* `logic/cooldowns.js`; hier wird beides mit Phaser-Tastatur und Grafik verbunden.
|
||||
* @module systems/OrbitalSystem
|
||||
*/
|
||||
|
||||
import { COLORS, GAME, ORBITAL } from '../config.js';
|
||||
import { codeToDirection, ComboInput } from '../logic/comboInput.js';
|
||||
import { CooldownTracker } from '../logic/cooldowns.js';
|
||||
import { clamp, distance } from '../logic/math.js';
|
||||
import { GroundMarker } from './GroundMarker.js';
|
||||
import { oncePerEvent } from './keyboard.js';
|
||||
|
||||
/** Farbe der Markierung je Befehl. */
|
||||
const MARKER_COLORS = Object.freeze({
|
||||
strike: COLORS.strike,
|
||||
supply: COLORS.supply,
|
||||
turret: COLORS.turret,
|
||||
});
|
||||
|
||||
/**
|
||||
* @typedef {typeof ORBITAL.commands[number]} OrbitalCommand
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} OrbitalCallbacks
|
||||
* @property {() => {x: number, y: number}} getPlayer
|
||||
* @property {() => {x: number, y: number}} getAimPoint Mausposition in Weltkoordinaten.
|
||||
* @property {(command: OrbitalCommand, x: number, y: number) => void} onImpact
|
||||
* @property {(kind: 'step'|'fail'|'deploy'|'notReady', command?: OrbitalCommand, remainingMs?: number) => void} onFeedback
|
||||
*/
|
||||
|
||||
export class OrbitalSystem {
|
||||
/**
|
||||
* @param {Phaser.Scene} scene
|
||||
* @param {OrbitalCallbacks} callbacks
|
||||
*/
|
||||
constructor(scene, callbacks) {
|
||||
this.scene = scene;
|
||||
this.callbacks = callbacks;
|
||||
this.commands = ORBITAL.commands;
|
||||
this.combo = new ComboInput(this.commands);
|
||||
this.cooldowns = new CooldownTracker();
|
||||
/** @type {{command: OrbitalCommand, marker: GroundMarker}[]} */
|
||||
this.pending = [];
|
||||
this.now = 0;
|
||||
|
||||
this.holdKey = scene.input.keyboard.addKey(ORBITAL.holdKey);
|
||||
// Pfeiltasten abfangen, sonst scrollt der Browser die Seite.
|
||||
scene.input.keyboard.addCapture('UP,DOWN,LEFT,RIGHT');
|
||||
this.onKeyDown = oncePerEvent((event) => this.handleKeyDown(event));
|
||||
scene.input.keyboard.on('keydown', this.onKeyDown);
|
||||
}
|
||||
|
||||
/** @returns {boolean} true, solange ein Befehl eingegeben wird (Bewegung pausiert). */
|
||||
get isInputActive() {
|
||||
return this.combo.active;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} now Spielzeit in ms.
|
||||
*/
|
||||
update(now) {
|
||||
this.now = now;
|
||||
if (this.holdKey.isDown && !this.combo.active) this.combo.begin();
|
||||
else if (!this.holdKey.isDown && this.combo.active) this.combo.end();
|
||||
|
||||
for (let i = this.pending.length - 1; i >= 0; i -= 1) {
|
||||
const entry = this.pending[i];
|
||||
if (entry.marker.isDue(now)) {
|
||||
entry.marker.destroy();
|
||||
this.pending.splice(i, 1);
|
||||
this.callbacks.onImpact(entry.command, entry.marker.x, entry.marker.y);
|
||||
} else {
|
||||
entry.marker.update(now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {KeyboardEvent} event
|
||||
* @private
|
||||
*/
|
||||
handleKeyDown(event) {
|
||||
// Gedrückt gehaltene Tasten erzeugen Wiederholungen – die zählen nicht als Eingabe.
|
||||
if (event.repeat) return;
|
||||
// Q und erste Richtung können im selben Frame ankommen, bevor update() die
|
||||
// Eingabe gestartet hat – dann hier starten, sonst ginge die erste Richtung verloren.
|
||||
if (!this.combo.active && this.holdKey.isDown) this.combo.begin();
|
||||
if (!this.combo.active) return;
|
||||
const direction = codeToDirection(event.code);
|
||||
if (!direction) return;
|
||||
const result = this.combo.push(direction);
|
||||
if (result.status === 'progress') this.callbacks.onFeedback('step');
|
||||
else if (result.status === 'fail') this.callbacks.onFeedback('fail');
|
||||
else if (result.status === 'match') this.request(/** @type {string} */ (result.commandId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fordert einen Befehl an (sofern bereit) und setzt die Markierung ans Ziel.
|
||||
* @param {string} commandId
|
||||
* @returns {boolean}
|
||||
*/
|
||||
request(commandId) {
|
||||
const command = this.commands.find((c) => c.id === commandId);
|
||||
if (!command) return false;
|
||||
const now = this.now;
|
||||
if (!this.cooldowns.isReady(command.id, now)) {
|
||||
this.callbacks.onFeedback('notReady', command, this.cooldowns.remaining(command.id, now));
|
||||
return false;
|
||||
}
|
||||
const target = this.clampTarget(this.callbacks.getAimPoint());
|
||||
this.cooldowns.trigger(command.id, command.cooldownMs, now);
|
||||
const marker = new GroundMarker(
|
||||
this.scene,
|
||||
target.x,
|
||||
target.y,
|
||||
command.radius,
|
||||
MARKER_COLORS[command.id] ?? COLORS.gold,
|
||||
now,
|
||||
now + command.delayMs,
|
||||
{ label: command.shortName, beam: true },
|
||||
);
|
||||
marker.update(now);
|
||||
this.pending.push({ command, marker });
|
||||
this.callbacks.onFeedback('deploy', command);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begrenzt das Ziel auf die maximale Reichweite und die Arena.
|
||||
* @param {{x: number, y: number}} aim
|
||||
* @returns {{x: number, y: number}}
|
||||
*/
|
||||
clampTarget(aim) {
|
||||
const player = this.callbacks.getPlayer();
|
||||
const d = distance(player, aim);
|
||||
let { x, y } = aim;
|
||||
if (d > ORBITAL.maxTargetRange) {
|
||||
const f = ORBITAL.maxTargetRange / d;
|
||||
x = player.x + (aim.x - player.x) * f;
|
||||
y = player.y + (aim.y - player.y) * f;
|
||||
}
|
||||
const margin = GAME.spawnMargin;
|
||||
return {
|
||||
x: clamp(x, margin, GAME.world.width - margin),
|
||||
y: clamp(y, margin, GAME.world.height - margin),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Daten für die HUD-Anzeige.
|
||||
* @param {number} now
|
||||
*/
|
||||
hudState(now) {
|
||||
return {
|
||||
active: this.combo.active,
|
||||
commands: this.commands.map((c) => ({
|
||||
id: c.id,
|
||||
name: c.shortName,
|
||||
sequence: c.sequence,
|
||||
progress: this.combo.progressFor(c.id),
|
||||
ready: this.cooldowns.isReady(c.id, now),
|
||||
remainingMs: this.cooldowns.remaining(c.id, now),
|
||||
cooldownProgress: this.cooldowns.progress(c.id, now),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/** Aufräumen beim Beenden der Szene. */
|
||||
destroy() {
|
||||
this.scene.input.keyboard?.off('keydown', this.onKeyDown);
|
||||
for (const entry of this.pending) entry.marker.destroy();
|
||||
this.pending = [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user