TRXTD/src/components/Hud.vue

222 lines
5.6 KiB
Vue
Raw 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.

<script setup lang="ts">
import { computed, ref } from 'vue'
import { store } from '@/game/store'
import { engine } from '@/game/engine'
import { mpgame, uiSetSpeed, uiStartWaveEarly, uiTogglePause } from '@/game/mpgame'
import { DIFFICULTIES, ENEMIES, earlyCallBonus } from '@/game/config'
const confirmExit = ref(false)
const waveLabel = computed(() => {
if (store.endless || store.mp.active) return `Welle ${store.waveNo}`
return `Welle ${store.waveNo}/${store.totalWaves}`
})
const nextBonus = computed(() => (store.phase === 'intermission' ? earlyCallBonus(store.nextWaveIn) : 0))
function previewTooltip(kind: string): string {
return ENEMIES[kind as keyof typeof ENEMIES].name
}
function exitToMenu(): void {
confirmExit.value = false
if (store.mp.active) {
mpgame.leave()
} else {
store.screen = 'menu'
store.result = null
engine.setPaused(false)
}
}
</script>
<template>
<div class="hud">
<div class="hud-stats">
<div class="stat gold" title="Gold">🪙 {{ store.money }}</div>
<div class="stat lives" title="Leben"> {{ store.lives }}</div>
<div class="stat wave" title="Welle">🌊 {{ waveLabel }}</div>
<div v-if="!store.mp.active" class="stat score" title="Punkte">🏆 {{ store.score }}</div>
<div class="stat diff">
{{ store.mp.active ? (store.mp.mode === 'coop' ? '🤝 Coop' : '⚔ 1v1') : DIFFICULTIES[store.difficulty].name }}
</div>
</div>
<div class="hud-wave">
<button
v-if="store.phase === 'intermission'"
class="btn wave-btn pulse"
title="Nächste Welle sofort starten (Leertaste)"
@click="uiStartWaveEarly()"
>
Nächste Welle <span v-if="nextBonus > 0" class="bonus">+{{ nextBonus }}🪙</span>
</button>
<div v-else-if="store.phase === 'wave'" class="wave-running">
<span class="dot" /> Welle läuft
</div>
<div v-if="store.phase === 'intermission'" class="countdown">
automatisch in {{ store.nextWaveIn }}s
</div>
<div v-if="store.nextPreview.length && store.phase === 'intermission'" class="preview">
<span class="preview-label">Danach:</span>
<span
v-for="p in store.nextPreview"
:key="p.kind"
class="preview-chip"
:style="{ background: ENEMIES[p.kind].color }"
:title="previewTooltip(p.kind)"
>
{{ ENEMIES[p.kind].name }} ×{{ p.count }}
</span>
</div>
</div>
<div class="hud-controls">
<button
v-if="!store.mp.active"
class="btn icon"
:title="store.paused ? 'Weiter (P)' : 'Pause (P)'"
@click="uiTogglePause()"
>
{{ store.paused ? '▶' : '⏸' }}
</button>
<button
v-for="s in [1, 2, 3]"
:key="s"
class="btn icon"
:class="{ active: store.speed === s }"
:title="`Geschwindigkeit ${s}x`"
@click="uiSetSpeed(s)"
>
{{ s }}×
</button>
<button class="btn icon" :title="store.muted ? 'Ton an (M)' : 'Ton aus (M)'" @click="engine.toggleMute()">
{{ store.muted ? '🔇' : '🔊' }}
</button>
<template v-if="!confirmExit">
<button class="btn icon danger" title="Zurück zum Hauptmenü" @click="confirmExit = true">🚪</button>
</template>
<template v-else>
<button class="btn icon danger confirm" title="Wirklich beenden?" @click="exitToMenu">Sicher?</button>
<button class="btn icon" @click="confirmExit = false"></button>
</template>
</div>
</div>
</template>
<style scoped>
.hud {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
background: var(--panel);
border: 1px solid var(--panel-border);
border-radius: 12px;
padding: 8px 12px;
}
.hud-stats {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.stat {
background: var(--panel-inset);
border-radius: 8px;
padding: 5px 10px;
font-weight: 700;
font-size: 14px;
white-space: nowrap;
}
.stat.gold { color: #ffd23e; }
.stat.lives { color: #ff7a6e; }
.stat.wave { color: #7fd8ff; }
.stat.score { color: #b8f5c0; }
.stat.diff { color: #c9d4e0; font-size: 12px; align-self: center; }
.hud-wave {
flex: 1;
min-width: 200px;
/* feste Höhe, damit das Spielfeld nicht springt, wenn der Wellen-Button kommt/geht */
min-height: 78px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 3px;
}
.wave-btn {
background: linear-gradient(180deg, #e0563f, #b93a28);
color: #fff;
font-size: 15px;
}
.wave-btn .bonus {
background: rgba(0, 0, 0, 0.25);
border-radius: 6px;
padding: 1px 6px;
margin-left: 4px;
font-size: 12px;
}
.wave-running {
color: var(--text-dim);
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.dot {
width: 9px;
height: 9px;
border-radius: 50%;
background: #e0563f;
animation: blink 1s infinite;
}
@keyframes blink {
50% { opacity: 0.3; }
}
.countdown {
font-size: 11px;
color: var(--text-dim);
}
.preview {
display: flex;
gap: 4px;
align-items: center;
flex-wrap: wrap;
justify-content: center;
}
.preview-label {
font-size: 11px;
color: var(--text-dim);
}
.preview-chip {
color: #10151c;
font-size: 10.5px;
font-weight: 800;
padding: 2px 6px;
border-radius: 20px;
white-space: nowrap;
}
.hud-controls {
display: flex;
gap: 6px;
}
.icon {
min-width: 40px;
padding: 6px 8px;
}
.icon.active {
background: var(--accent);
color: #10151c;
}
.icon.danger:hover {
background: #a33;
color: #fff;
}
.icon.confirm {
background: #a33;
color: #fff;
min-width: 60px;
}
</style>