From f7ab56e475d7fe4bc28598ab8d8f56369516f788 Mon Sep 17 00:00:00 2001 From: Tronax Date: Mon, 17 Aug 2026 15:51:23 +0200 Subject: [PATCH] fix(ui+game): responsive profile bar, auto-wave toggle, and stats on early exit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Profile bar on phones: - With the new Statistik/Admin chips the bar overflowed the viewport on small screens. It now wraps (flex-wrap + max-width) and compacts its paddings, fonts and chip sizes below 560px Auto-wave (🔁 button in the HUD controls): - New persistent toggle (localStorage) that starts the next wave on its own ~0.4s after intermission begins, collecting the full early-call bonus without pressing the wave button every round - Toggling it on during an active intermission starts the wave immediately; works in solo and multiplayer (uses the same action path as the manual button); active state shows green Stats are now saved when leaving via the menu: - Previously only victory/defeat recorded a result — exiting a running game via "🚪 Zurück zum Hauptmenü" or the pause menu discarded the whole run, which is why endless-mode progress beyond wave 20 never showed up in the stats - engine.abandon() records best score, stats and crystals (counted as a defeat) whenever a solo game is left mid-run; no-ops when no game is running or the result was already reported, so no double counting Verified in the browser (390x844, admin account): profile bar wraps compactly; 🔁 auto-started wave 1 during intermission without manual click; exiting mid-wave recorded the run (💎 2, 1 Runde, beste Welle 1). Build clean, 54/54 tests green. --- src/components/Hud.vue | 47 ++++++++++++++++++++++++++++++- src/components/PauseOverlay.vue | 1 + src/components/UserProfileBar.vue | 21 ++++++++++++++ src/game/engine.ts | 18 ++++++++++++ src/game/store.ts | 3 ++ 5 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/components/Hud.vue b/src/components/Hud.vue index 9f5cd54..b7bf83b 100644 --- a/src/components/Hud.vue +++ b/src/components/Hud.vue @@ -1,5 +1,5 @@ diff --git a/src/components/UserProfileBar.vue b/src/components/UserProfileBar.vue index b1d0aa1..0fd9b25 100644 --- a/src/components/UserProfileBar.vue +++ b/src/components/UserProfileBar.vue @@ -49,7 +49,9 @@ async function doLogout(): Promise { .profile-bar { display: flex; align-items: center; + flex-wrap: wrap; gap: 8px; + max-width: 100%; background: var(--panel); border: 1px solid var(--panel-border); border-radius: 12px; @@ -79,6 +81,25 @@ async function doLogout(): Promise { padding: 5px 10px; cursor: pointer; } + +/* Phones: chips wrap into compact rows instead of overflowing */ +@media (max-width: 560px) { + .profile-bar { + gap: 5px; + padding: 5px 8px; + } + .crystals { + font-size: 13px; + } + .name { + font-size: 11.5px; + max-width: 92px; + } + .chip { + font-size: 11px; + padding: 4px 8px; + } +} .chip:hover { border-color: var(--accent); } diff --git a/src/game/engine.ts b/src/game/engine.ts index 005976a..45dbe3c 100644 --- a/src/game/engine.ts +++ b/src/game/engine.ts @@ -575,6 +575,24 @@ export class GameEngine { this.onGameEnd?.({ win, score: this.score, wave: this.waveNo, kills: this.kills, crystalsEarned }) } + /** + * Solo: leaving a running game via "zum Hauptmenü"/Pause-Menü. Records the + * run (best score, stats, crystals — counted as a defeat) so progress from + * endless runs or early exits is not lost. No-ops when no game is running + * or the result was already reported by finish(). + */ + abandon(): void { + if (this.mpGameActive) return + if (this.phase !== 'wave' && this.phase !== 'intermission') return + const bestBefore = loadBest(this.difficulty, this.mapId) + const best = Math.max(bestBefore, this.score) + if (typeof localStorage !== 'undefined') { + localStorage.setItem(bestScoreKey(this.difficulty, this.mapId), String(best)) + } + const crystalsEarned = calcCrystalsEarned(this.waveNo, this.score, false) + this.onGameEnd?.({ win: false, score: this.score, wave: this.waveNo, kills: this.kills, crystalsEarned }) + } + private spawnEnemy(kind: EnemyKind): void { const def = ENEMIES[kind] const diff = DIFFICULTIES[this.difficulty] diff --git a/src/game/store.ts b/src/game/store.ts index 45208a2..66b4253 100644 --- a/src/game/store.ts +++ b/src/game/store.ts @@ -41,6 +41,9 @@ export const store = reactive({ buildType: null as TowerKind | null, hoverValid: false, + + /** automatically start the next wave as soon as intermission begins */ + autoWave: typeof localStorage !== 'undefined' && localStorage.getItem('trxtd-autowave') === '1', selected: null as SelectedTowerInfo | null, obstacle: null as { tx: number; ty: number; type: 'tree' | 'rock'; cost: number } | null,