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