Files
2026-09-24 17:57:54 +02:00

137 lines
5.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Einfache Informationsseiten im Canvas: Ehrentafel, Steuerung, Credits.
* Alle kehren mit Esc, Enter oder Klick auf „Zurück“ ins Hauptmenü zurück.
* @module scenes/InfoScenes
*/
import { ORBITAL } from '../config.js';
import { CONTROLS, CREDITS, HIGHSCORES } from '../content/texts.js';
import { formatScore, formatSequence } from '../logic/format.js';
import { oncePerEvent } from '../systems/keyboard.js';
import { textStyle, UI, uiStyle } from '../ui/theme.js';
/**
* Gemeinsamer Rahmen: Hintergrund, Titel und Zurück-Schaltfläche.
* @param {Phaser.Scene} scene
* @param {string} title
* @param {string} backLabel
*/
function frame(scene, title, backLabel) {
const { width, height } = scene.scale;
scene.input.setDefaultCursor('default');
const bg = scene.add.graphics();
bg.fillGradientStyle(0x0d1321, 0x0d1321, 0x1b2438, 0x1b2438, 1);
bg.fillRect(0, 0, width, height);
bg.lineStyle(3, 0xf2c14e, 1);
bg.strokeRect(40, 30, width - 80, height - 60);
scene.add.text(width / 2, 70, title, textStyle({ fontSize: '38px', color: UI.colors.gold })).setOrigin(0.5);
const back = scene.add
.text(width / 2, height - 70, backLabel, textStyle({ fontSize: '22px' }))
.setOrigin(0.5)
.setInteractive({ useHandCursor: true });
back.on('pointerover', () => back.setColor(UI.colors.gold));
back.on('pointerout', () => back.setColor(UI.colors.cream));
const leave = () => scene.scene.start('Menu');
back.on('pointerup', leave);
const leaveByKey = oncePerEvent((event) => {
if (!event.repeat) leave();
});
scene.input.keyboard.on('keydown-ESC', leaveByKey);
scene.input.keyboard.on('keydown-ENTER', leaveByKey);
}
export class HighscoresScene extends Phaser.Scene {
constructor() {
super('Highscores');
}
create() {
frame(this, HIGHSCORES.title, HIGHSCORES.back);
const { width } = this.scale;
this.statusText = this.add.text(width / 2, 300, HIGHSCORES.loading, uiStyle({ fontSize: '22px', color: UI.colors.muted })).setOrigin(0.5);
this.registry
.get('services')
.api.fetchHighscores()
.then((result) => {
if (!this.sys.isActive()) return;
if (!result.ok) this.statusText.setText(HIGHSCORES.error);
else if (result.highscores.length === 0) this.statusText.setText(HIGHSCORES.empty);
else {
this.statusText.destroy();
this.renderTable(result.highscores);
}
});
}
/** @param {import('../systems/ApiClient.js').HighscoreEntry[]} entries */
renderTable(entries) {
const columns = { rank: 280, name: 360, score: 900, wave: 1000 };
const top = 130;
const rowHeight = 40;
const header = uiStyle({ fontSize: '17px', color: UI.colors.muted });
this.add.text(columns.rank, top, HIGHSCORES.columns.rank, header);
this.add.text(columns.name, top, HIGHSCORES.columns.name, header);
this.add.text(columns.score, top, HIGHSCORES.columns.score, header).setOrigin(1, 0);
this.add.text(columns.wave, top, HIGHSCORES.columns.wave, header).setOrigin(1, 0);
entries.forEach((entry, i) => {
const y = top + 40 + i * rowHeight;
const color = i === 0 ? UI.colors.gold : UI.colors.cream;
const style = uiStyle({ fontSize: '24px', color });
this.add.text(columns.rank, y, `${entry.rank}.`, style);
// Canvas-Text interpretiert kein HTML – Namen werden hier sicher als Text gezeichnet.
this.add.text(columns.name, y, entry.name, style);
this.add.text(columns.score, y, formatScore(entry.score), style).setOrigin(1, 0);
this.add.text(columns.wave, y, String(entry.wave), style).setOrigin(1, 0);
});
}
}
export class ControlsScene extends Phaser.Scene {
constructor() {
super('Controls');
}
create() {
frame(this, CONTROLS.title, CONTROLS.back);
const keyStyle = uiStyle({ fontSize: '20px', color: UI.colors.gold });
const valueStyle = uiStyle({ fontSize: '20px' });
CONTROLS.rows.forEach(([key, action], i) => {
const y = 130 + i * 38;
this.add.text(560, y, key, keyStyle).setOrigin(1, 0);
this.add.text(590, y, action, valueStyle);
});
const top = 130 + CONTROLS.rows.length * 38 + 30;
this.add.text(this.scale.width / 2, top, CONTROLS.orbitalTitle, textStyle({ fontSize: '22px', color: UI.colors.gold })).setOrigin(0.5);
ORBITAL.commands.forEach((command, i) => {
const y = top + 40 + i * 36;
this.add.text(560, y, command.name, valueStyle).setOrigin(1, 0);
this.add.text(590, y, formatSequence(command.sequence, ' '), { ...keyStyle, fontFamily: UI.symbolFont });
});
}
}
export class CreditsScene extends Phaser.Scene {
constructor() {
super('Credits');
}
create() {
frame(this, CREDITS.title, CREDITS.back);
const { width } = this.scale;
let y = 118;
for (const section of CREDITS.sections) {
const heading = this.add.text(width / 2, y, section.heading, textStyle({ fontSize: '21px', color: UI.colors.gold })).setOrigin(0.5, 0);
y += heading.height + 4;
for (const line of section.lines) {
const text = this.add
.text(width / 2, y, line, { ...uiStyle({ fontSize: '17px', align: 'center' }), wordWrap: { width: width - 200 } })
.setOrigin(0.5, 0);
// Tatsächliche Höhe verwenden: umgebrochene Zeilen sind höher als eine Zeile.
y += text.height + 2;
}
y += 12;
}
}
}