feat(sasch40): v4.0 full-screen mobile conversion, Hinter den Bergen room with Liars Dice, beat-only concert audio, and updated art
This commit is contained in:
@@ -7,7 +7,7 @@ import { sound } from '../audio/synthAudio'
|
||||
|
||||
const store = useGameStore()
|
||||
|
||||
type TavernRoom = 'schankraum' | 'nebenraum' | 'marktplatz' | 'hinterland' | 'hinterzimmer'
|
||||
type TavernRoom = 'schankraum' | 'nebenraum' | 'marktplatz' | 'hinterland' | 'hinterzimmer' | 'hinter_den_bergen'
|
||||
const currentRoom = ref<TavernRoom>('schankraum')
|
||||
|
||||
interface ActiveDialog {
|
||||
@@ -309,9 +309,242 @@ function openCharacterDialog(charId: string) {
|
||||
activeDialog.value = null
|
||||
}
|
||||
}
|
||||
} else if (charId === 'markus') {
|
||||
activeDialog.value = {
|
||||
characterId: charId,
|
||||
name: char.name,
|
||||
title: char.title,
|
||||
portrait: char.portrait,
|
||||
text: 'Servus mein Sohn! Schau di an, 40 Jahre und herrschst über ganz Haumdaucher! Meine taiwanischen Freunde und ich trinken 58% Kinmen-Gaoliang und spielen Liar\'s Dice (Chui Niu / 吹牛)! Traust du dich gegen deinen alten Herrn anzutreten? Wer verliert, kippt einen Kurzen!',
|
||||
actionLabel: '🎲 Auf geht\'s Papa, Liar\'s Dice spielen!',
|
||||
actionFn: () => {
|
||||
activeDialog.value = null
|
||||
openLiarsDiceIntro()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Münchanien Hint Modal
|
||||
const munchanienHintActive = ref(false)
|
||||
function openMunchanienHint() {
|
||||
sound.playDialogueBlip()
|
||||
munchanienHintActive.value = true
|
||||
}
|
||||
|
||||
// Liar's Dice (Chui Niu / 吹牛) State & Logic
|
||||
const liarsDiceIntroOpen = ref(false)
|
||||
const liarsDiceActive = ref(false)
|
||||
const liarsDiceGameOver = ref(false)
|
||||
const liarsDicePlayerWon = ref(false)
|
||||
const liarsDiceRound = ref(1)
|
||||
const liarsDicePlayerWins = ref(0)
|
||||
const liarsDiceMarkusWins = ref(0)
|
||||
const playerDice = ref<number[]>([1, 2, 3, 4, 5])
|
||||
const markusDice = ref<number[]>([1, 2, 3, 4, 5])
|
||||
const markusDiceRevealed = ref(false)
|
||||
const currentBid = ref<{ count: number; value: number } | null>(null)
|
||||
const lastBidder = ref<'player' | 'markus' | null>(null)
|
||||
const liarsDiceMsg = ref('')
|
||||
const selectedBidCount = ref(2)
|
||||
const selectedBidValue = ref(2)
|
||||
const isShaking = ref(false)
|
||||
const shotAnimation = ref<'sascha' | 'markus' | null>(null)
|
||||
const isRoundEvaluating = ref(false)
|
||||
|
||||
function dieIcon(val: number): string {
|
||||
const icons = ['', '⚀', '⚁', '⚂', '⚃', '⚄', '⚅']
|
||||
return icons[val] || '🎲'
|
||||
}
|
||||
|
||||
function openLiarsDiceIntro() {
|
||||
sound.playDialogueBlip()
|
||||
liarsDiceIntroOpen.value = true
|
||||
}
|
||||
|
||||
function startLiarsDiceMatch() {
|
||||
sound.playDialogueBlip()
|
||||
liarsDiceIntroOpen.value = false
|
||||
liarsDiceActive.value = true
|
||||
liarsDiceGameOver.value = false
|
||||
liarsDicePlayerWon.value = false
|
||||
liarsDicePlayerWins.value = 0
|
||||
liarsDiceMarkusWins.value = 0
|
||||
liarsDiceRound.value = 1
|
||||
startNewDiceRound()
|
||||
}
|
||||
|
||||
function startNewDiceRound() {
|
||||
sound.playDiceShake()
|
||||
isShaking.value = true
|
||||
markusDiceRevealed.value = false
|
||||
currentBid.value = null
|
||||
lastBidder.value = null
|
||||
shotAnimation.value = null
|
||||
isRoundEvaluating.value = false
|
||||
selectedBidCount.value = 2
|
||||
selectedBidValue.value = 2
|
||||
|
||||
// Generate 5 random dice each (1 to 6)
|
||||
playerDice.value = Array.from({ length: 5 }, () => Math.floor(Math.random() * 6) + 1).sort((a, b) => a - b)
|
||||
markusDice.value = Array.from({ length: 5 }, () => Math.floor(Math.random() * 6) + 1).sort((a, b) => a - b)
|
||||
|
||||
setTimeout(() => {
|
||||
isShaking.value = false
|
||||
liarsDiceMsg.value = `Runde ${liarsDiceRound.value}: Schüttle die Becher! Gib dein Gebot ab (z.B. mindestens zwei Dreier).`
|
||||
}, 400)
|
||||
}
|
||||
|
||||
function canMakeBid(count: number, value: number): boolean {
|
||||
if (!currentBid.value) return count >= 1 && count <= 10
|
||||
if (count > currentBid.value.count) return true
|
||||
if (count === currentBid.value.count && value > currentBid.value.value) return true
|
||||
return false
|
||||
}
|
||||
|
||||
function makePlayerBid() {
|
||||
if (isRoundEvaluating.value || isShaking.value) return
|
||||
if (!canMakeBid(selectedBidCount.value, selectedBidValue.value)) {
|
||||
alert('Dein Gebot muss höher sein als das vorherige (höhere Anzahl oder bei gleicher Anzahl höhere Augenzahl)!')
|
||||
return
|
||||
}
|
||||
|
||||
sound.playDialogueBlip()
|
||||
currentBid.value = { count: selectedBidCount.value, value: selectedBidValue.value }
|
||||
lastBidder.value = 'player'
|
||||
liarsDiceMsg.value = `Du sagst an: Mindestens ${currentBid.value.count}x die ${dieIcon(currentBid.value.value)}!`
|
||||
|
||||
// Next minimum suggestion for controls
|
||||
if (selectedBidValue.value < 6) {
|
||||
selectedBidValue.value++
|
||||
} else {
|
||||
selectedBidCount.value++
|
||||
selectedBidValue.value = 2
|
||||
}
|
||||
|
||||
// Trigger Markus AI after a pause
|
||||
setTimeout(() => {
|
||||
markusTurn()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
function markusTurn() {
|
||||
if (isRoundEvaluating.value || !currentBid.value) return
|
||||
|
||||
const bid = currentBid.value
|
||||
// Count how many matching dice Markus has (1s are wild unless bid is on 1)
|
||||
const markusMatches = markusDice.value.filter(d => d === bid.value || (bid.value !== 1 && d === 1)).length
|
||||
const expectedPlayerMatches = bid.value === 1 ? 0.83 : 1.67
|
||||
const totalEstimated = markusMatches + expectedPlayerMatches
|
||||
|
||||
// Call bluff if the bid is too high or improbable
|
||||
const bluffThreshold = bid.count >= 6 ? 0.8 : 1.3
|
||||
if (bid.count > totalEstimated + bluffThreshold) {
|
||||
callBluff('markus')
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, Markus raises!
|
||||
const counts: Record<number, number> = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 }
|
||||
markusDice.value.forEach(d => counts[d]++)
|
||||
let bestVal = 2
|
||||
let maxC = 0
|
||||
for (let v = 2; v <= 6; v++) {
|
||||
const effective = counts[v] + counts[1]
|
||||
if (effective > maxC) {
|
||||
maxC = effective
|
||||
bestVal = v
|
||||
}
|
||||
}
|
||||
|
||||
let nextCount = bid.count
|
||||
let nextVal = bid.value + 1
|
||||
if (nextVal > 6) {
|
||||
nextCount = bid.count + 1
|
||||
nextVal = Math.min(6, Math.max(2, bestVal))
|
||||
}
|
||||
|
||||
if (nextCount === bid.count && bestVal > bid.value) {
|
||||
nextVal = bestVal
|
||||
}
|
||||
|
||||
sound.playDialogueBlip()
|
||||
currentBid.value = { count: nextCount, value: nextVal }
|
||||
lastBidder.value = 'markus'
|
||||
liarsDiceMsg.value = `Papa Markus erhöht: Mindestens ${nextCount}x die ${dieIcon(nextVal)}! Was sagst du, mein Sohn?`
|
||||
|
||||
// Update player selectors to be just above Markus' bid
|
||||
if (nextVal < 6) {
|
||||
selectedBidCount.value = nextCount
|
||||
selectedBidValue.value = nextVal + 1
|
||||
} else {
|
||||
selectedBidCount.value = nextCount + 1
|
||||
selectedBidValue.value = 2
|
||||
}
|
||||
}
|
||||
|
||||
function callBluff(caller: 'player' | 'markus') {
|
||||
if (isRoundEvaluating.value || !currentBid.value) return
|
||||
isRoundEvaluating.value = true
|
||||
|
||||
sound.playCupSlam()
|
||||
markusDiceRevealed.value = true
|
||||
|
||||
const bid = currentBid.value
|
||||
const allDice = [...playerDice.value, ...markusDice.value]
|
||||
const totalMatches = allDice.filter(d => d === bid.value || (bid.value !== 1 && d === 1)).length
|
||||
|
||||
const bidWasTrue = totalMatches >= bid.count
|
||||
|
||||
setTimeout(() => {
|
||||
sound.playGaoliangShot()
|
||||
if (caller === 'player') {
|
||||
if (bidWasTrue) {
|
||||
// Player called bluff wrongly -> Player loses round!
|
||||
liarsDiceMarkusWins.value++
|
||||
shotAnimation.value = 'sascha'
|
||||
liarsDiceMsg.value = `Aufgedeckt: Es liegen tatsächlich ${totalMatches}x ${dieIcon(bid.value)}! Das Gebot stimmte. Sascha kippt einen Kurzen Gaoliang! 🍶`
|
||||
} else {
|
||||
// Player caught Markus bluffing -> Markus loses round!
|
||||
liarsDicePlayerWins.value++
|
||||
shotAnimation.value = 'markus'
|
||||
liarsDiceMsg.value = `Aufgedeckt: Nur ${totalMatches}x ${dieIcon(bid.value)}! Markus hat geblufft und muss einen Gaoliang kippen! 乾杯!`
|
||||
}
|
||||
} else {
|
||||
// Markus called bluff
|
||||
if (bidWasTrue) {
|
||||
// Markus called bluff wrongly -> Markus loses round!
|
||||
liarsDicePlayerWins.value++
|
||||
shotAnimation.value = 'markus'
|
||||
liarsDiceMsg.value = `Aufgedeckt: Es liegen ${totalMatches}x ${dieIcon(bid.value)}! Saschas Gebot stimmte! Markus kippt einen Gaoliang!`
|
||||
} else {
|
||||
// Markus caught player bluffing -> Player loses round!
|
||||
liarsDiceMarkusWins.value++
|
||||
shotAnimation.value = 'sascha'
|
||||
liarsDiceMsg.value = `Aufgedeckt: Nur ${totalMatches}x ${dieIcon(bid.value)}! Du hast geblufft, Sascha! Trink einen Gaoliang!`
|
||||
}
|
||||
}
|
||||
|
||||
// Check Match End (Best of 3)
|
||||
if (liarsDicePlayerWins.value >= 2) {
|
||||
liarsDiceGameOver.value = true
|
||||
liarsDicePlayerWon.value = true
|
||||
sound.playFanfare()
|
||||
store.addKronkorken(10)
|
||||
store.addItem('58% Kinmen Gaoliang')
|
||||
store.unlockEasterEgg('taiwan_dice_champion')
|
||||
} else if (liarsDiceMarkusWins.value >= 2) {
|
||||
liarsDiceGameOver.value = true
|
||||
liarsDicePlayerWon.value = false
|
||||
}
|
||||
}, 600)
|
||||
}
|
||||
|
||||
function proceedNextDiceRound() {
|
||||
liarsDiceRound.value++
|
||||
startNewDiceRound()
|
||||
}
|
||||
|
||||
function inspectMirror() {
|
||||
sound.playFanfare()
|
||||
zulumanMirrorActive.value = true
|
||||
@@ -356,7 +589,7 @@ function proceedToExpress() {
|
||||
|
||||
<!-- ================= 1. GASTSTUBE (SCHANKRAUM) ================= -->
|
||||
<template v-if="currentRoom === 'schankraum'">
|
||||
<img src="/assets/scenes/schankraum_v2.jpg?v=3" alt="Gaststube Zum wummernden Ritter" class="tavern-bg" />
|
||||
<img src="/assets/scenes/schankraum.jpg" alt="Gaststube Zum wummernden Ritter" class="tavern-bg" />
|
||||
|
||||
<!-- Verena on the wooden gallery -->
|
||||
<button class="hotspot hotspot-verena" @click="openCharacterDialog('verena')">
|
||||
@@ -463,7 +696,7 @@ function proceedToExpress() {
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<!-- ================= 3. HINTERLAND ================= -->
|
||||
<!-- ================= 4. HINTERLAND ================= -->
|
||||
<template v-else-if="currentRoom === 'hinterland'">
|
||||
<img src="/assets/scenes/hinterland.jpg" alt="Hinterland Haumdaucher Alm" class="tavern-bg" />
|
||||
|
||||
@@ -474,10 +707,22 @@ function proceedToExpress() {
|
||||
<span class="hotspot-badge">Mane (Wanderin) 🥾</span>
|
||||
</button>
|
||||
|
||||
<!-- Hansi enjoying the view on the bench -->
|
||||
<!-- Hansi walking towards Münchanien -->
|
||||
<button class="hotspot hotspot-hansi" @click="openCharacterDialog('hansi')">
|
||||
<img :src="CHARACTERS.hansi.portrait" class="hotspot-thumb" alt="Hansi" />
|
||||
<span class="hotspot-badge">Hansi 🧔</span>
|
||||
<span class="hotspot-badge">Hansi (Richtung Münchanien) 🧔</span>
|
||||
</button>
|
||||
|
||||
<!-- Signpost to Münchanien -->
|
||||
<button class="hotspot hotspot-sign-munchanien" @click="openMunchanienHint">
|
||||
<span class="sign-icon">🪧</span>
|
||||
<span class="hotspot-badge">Wegweiser: Münchanien 🏰</span>
|
||||
</button>
|
||||
|
||||
<!-- Trail to Hinter den Bergen (Taiwan & Papa Markus) -->
|
||||
<button class="hotspot door-hotspot hotspot-door-hinter-den-bergen" @click="switchRoom('hinter_den_bergen')">
|
||||
<span class="door-icon">🏔️</span>
|
||||
<span class="hotspot-badge glow-door">Hinter den Bergen ▶</span>
|
||||
</button>
|
||||
|
||||
<!-- Trail back to Marktplatz -->
|
||||
@@ -487,7 +732,24 @@ function proceedToExpress() {
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<!-- ================= 4. HINTERZIMMER (SECRET VIP RAVE) ================= -->
|
||||
<!-- ================= 5. HINTER DEN BERGEN (TAIWAN & PAPA MARKUS) ================= -->
|
||||
<template v-else-if="currentRoom === 'hinter_den_bergen'">
|
||||
<img src="/assets/scenes/hinter_den_bergen.jpg" alt="Hinter den Bergen mit Papa Markus" class="tavern-bg" />
|
||||
|
||||
<!-- Markus and Taiwanese companions table -->
|
||||
<button class="hotspot hotspot-markus" @click="openCharacterDialog('markus')">
|
||||
<img :src="CHARACTERS.markus.portrait" class="hotspot-thumb" alt="Papa Markus" />
|
||||
<span class="hotspot-badge">Papa Markus 🎲🍶</span>
|
||||
</button>
|
||||
|
||||
<!-- Trail back to Hinterland -->
|
||||
<button class="hotspot door-hotspot hotspot-door-back-hinterland" @click="switchRoom('hinterland')">
|
||||
<span class="door-icon">🌲</span>
|
||||
<span class="hotspot-badge glow-door">◀ Zum Hinterland</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<!-- ================= 6. HINTERZIMMER (SECRET VIP RAVE) ================= -->
|
||||
<template v-else-if="currentRoom === 'hinterzimmer'">
|
||||
<img src="/assets/scenes/hinterzimmer.jpg" alt="Geheimes VIP Hinterzimmer" class="tavern-bg" />
|
||||
<div class="strobe-overlay"></div>
|
||||
@@ -527,6 +789,7 @@ function proceedToExpress() {
|
||||
currentRoom === 'nebenraum' ? '🎨 AKT III: KÖNIGLICHER NEBENRAUM & ATELIER 🎨' :
|
||||
currentRoom === 'marktplatz' ? '🎪 AKT III: HAUMDAUCHER MARKTPLATZ 🎪' :
|
||||
currentRoom === 'hinterland' ? '🌲 AKT III: ALPINES HINTERLAND 🌲' :
|
||||
currentRoom === 'hinter_den_bergen' ? '🏮 AKT III: HINTER DEN BERGEN (TAIWAN) 🏮' :
|
||||
'🪩 AKT III: DER GEHEIME PARTY BUNKER 🪩'
|
||||
}}
|
||||
</div>
|
||||
@@ -561,6 +824,13 @@ function proceedToExpress() {
|
||||
>
|
||||
🌲 Hinterland
|
||||
</button>
|
||||
<button
|
||||
class="room-tab"
|
||||
:class="{ active: currentRoom === 'hinter_den_bergen' }"
|
||||
@click="switchRoom('hinter_den_bergen')"
|
||||
>
|
||||
🏮 Hinter den Bergen
|
||||
</button>
|
||||
<button
|
||||
v-if="store.unlockedEasterEggs.includes('hinterzimmer') || currentRoom === 'hinterzimmer'"
|
||||
class="room-tab vip-tab"
|
||||
@@ -775,6 +1045,178 @@ function proceedToExpress() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Münchanien Hint Modal -->
|
||||
<div v-if="munchanienHintActive" class="dialog-overlay" @click="munchanienHintActive = false">
|
||||
<div class="dialog-box intro-modal-box" @click.stop>
|
||||
<div class="intro-modal-header">
|
||||
<span class="intro-tag">WEGWEISER NACH MÜNCHANIE</span>
|
||||
<button class="duel-close-btn" @click="munchanienHintActive = false" aria-label="Schließen">✕</button>
|
||||
</div>
|
||||
<h2 class="intro-modal-title">🏰 WEGWEISER: NACH MÜNCHANIE</h2>
|
||||
<p class="intro-modal-story">
|
||||
Auf dem geschnitzten Wegweiser steht mit Kreide geschrieben:<br><br>
|
||||
<em>„Hansi ist schon vorausgegangen, um die mächtige Sound-Anlage für das Abschiedskonzert in der Residenzstadt Münchanie aufzubauen! Bereite dich gut vor, sammle Kronkorken und Helles in allen Räumen und besuche alle Freunde, bevor du nach Akt IV aufbrichst!“</em>
|
||||
</p>
|
||||
<button class="action-btn" @click="munchanienHintActive = false">
|
||||
Verstanden! Weiter erkunden
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Liar's Dice Intro Modal -->
|
||||
<div v-if="liarsDiceIntroOpen" class="dialog-overlay" @click="liarsDiceIntroOpen = false">
|
||||
<div class="dialog-box intro-modal-box" @click.stop>
|
||||
<div class="intro-modal-header">
|
||||
<span class="intro-tag">MINISPIEL INTRO</span>
|
||||
<button class="duel-close-btn" @click="liarsDiceIntroOpen = false" aria-label="Schließen">✕</button>
|
||||
</div>
|
||||
<h2 class="intro-modal-title">🎲 LIAR'S DICE (CHUI NIU / 吹牛)</h2>
|
||||
<p class="intro-modal-story">
|
||||
Papa Markus fordert dich zu einer Partie Liar's Dice heraus, wie man es in den Gassen von Taipeh und Kinmen spielt! Wer blufft besser und wer muss den 58% Gaoliang-Schnaps kippen?
|
||||
</p>
|
||||
<div class="intro-modal-rules">
|
||||
<div class="rule-line">🎲 <strong>5 Würfel pro Spieler:</strong> Jeder sieht nur seine eigenen Würfel!</div>
|
||||
<div class="rule-line">✨ <strong>1er sind Joker:</strong> Zählen für alle Augenzahlen (außer wenn auf 1er geboten wird).</div>
|
||||
<div class="rule-line">🗣️ <strong>Gebote steigern:</strong> Schätze die Gesamtzahl aller Würfel (deine + Markus). Nächstes Gebot muss höhere Anzahl oder bei gleicher Anzahl höhere Augenzahl sein.</div>
|
||||
<div class="rule-line">🍶 <strong>Bluff anzweifeln:</strong> Glaubst du Markus lügt? Sag „Bluff!“ – wer unrecht hat, kippt einen Schnaps!</div>
|
||||
<div class="rule-line">🏆 <strong>Modus:</strong> Best of 3! Belohnung: +10 Kronkorken & 58% Kinmen Gaoliang!</div>
|
||||
</div>
|
||||
<button class="action-btn start-duel-action-btn" @click="startLiarsDiceMatch">
|
||||
🎲 Würfelbecher schütteln!
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Liar's Dice Game Modal -->
|
||||
<div v-if="liarsDiceActive" class="dialog-overlay">
|
||||
<div class="dialog-box liars-dice-box">
|
||||
<div class="liars-dice-header">
|
||||
<div class="dice-title-group">
|
||||
<h3>🎲 LIAR'S DICE GEGEN PAPA MARKUS</h3>
|
||||
<span class="dice-score">Stand: Sascha {{ liarsDicePlayerWins }} : {{ liarsDiceMarkusWins }} Markus (Best of 3)</span>
|
||||
</div>
|
||||
<button class="duel-close-btn" @click="liarsDiceActive = false" aria-label="Spiel verlassen">✕</button>
|
||||
</div>
|
||||
|
||||
<!-- Status Message -->
|
||||
<div class="dice-status-msg">
|
||||
{{ liarsDiceMsg }}
|
||||
</div>
|
||||
|
||||
<!-- Cups and Dice Arena -->
|
||||
<div class="dice-arena">
|
||||
<!-- Markus Side -->
|
||||
<div class="dice-player-row markus-row">
|
||||
<div class="dice-player-label">
|
||||
<img :src="CHARACTERS.markus.portrait" class="dice-avatar" alt="Papa Markus" />
|
||||
<span>Papa Markus</span>
|
||||
</div>
|
||||
<div class="dice-cup-area">
|
||||
<div v-if="!markusDiceRevealed" class="cup-covered">
|
||||
<span class="cup-icon">🥤</span>
|
||||
<span class="cup-text">[ 5 verdeckte Würfel ]</span>
|
||||
</div>
|
||||
<div v-else class="dice-revealed-row">
|
||||
<span v-for="(die, idx) in markusDice" :key="idx" class="dice-die markus-die">
|
||||
{{ dieIcon(die) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Bid Banner -->
|
||||
<div class="current-bid-banner" v-if="currentBid">
|
||||
<span class="bid-label">Aktuelles Gebot:</span>
|
||||
<span class="bid-value">Mindestens {{ currentBid.count }}x {{ dieIcon(currentBid.value) }}</span>
|
||||
<span class="bidder-tag">({{ lastBidder === 'player' ? 'von Sascha' : 'von Papa Markus' }})</span>
|
||||
</div>
|
||||
|
||||
<!-- Player Side -->
|
||||
<div class="dice-player-row player-row">
|
||||
<div class="dice-player-label">
|
||||
<span class="player-icon">👑</span>
|
||||
<span>König Sascha (Du)</span>
|
||||
</div>
|
||||
<div class="dice-revealed-row">
|
||||
<span v-for="(die, idx) in playerDice" :key="idx" class="dice-die player-die">
|
||||
{{ dieIcon(die) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Shot Drink Penalty Animation Banner -->
|
||||
<div v-if="shotAnimation" class="shot-animation-banner">
|
||||
🍶 {{ shotAnimation === 'sascha' ? 'Sascha trinkt einen 58% Gaoliang Shot! 🥴' : 'Markus kippt den Gaoliang Shot! 乾杯! 🎉' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Controls / Actions -->
|
||||
<div v-if="!liarsDiceGameOver && !isRoundEvaluating" class="dice-controls">
|
||||
<div class="bid-selectors">
|
||||
<div class="selector-col">
|
||||
<label>Anzahl:</label>
|
||||
<div class="counter-btns">
|
||||
<button class="mini-btn" @click="selectedBidCount = Math.max(1, selectedBidCount - 1)">-</button>
|
||||
<span class="counter-val">{{ selectedBidCount }}</span>
|
||||
<button class="mini-btn" @click="selectedBidCount = Math.min(10, selectedBidCount + 1)">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="selector-col">
|
||||
<label>Augenzahl:</label>
|
||||
<div class="die-val-btns">
|
||||
<button
|
||||
v-for="val in [2, 3, 4, 5, 6, 1]"
|
||||
:key="val"
|
||||
class="die-select-btn"
|
||||
:class="{ active: selectedBidValue === val }"
|
||||
@click="selectedBidValue = val"
|
||||
>
|
||||
{{ dieIcon(val) }} ({{ val }})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dice-action-buttons">
|
||||
<button class="action-btn bid-action-btn" @click="makePlayerBid">
|
||||
🗣️ Gebot abgeben ({{ selectedBidCount }}x {{ dieIcon(selectedBidValue) }})
|
||||
</button>
|
||||
<button
|
||||
v-if="currentBid && lastBidder === 'markus'"
|
||||
class="action-btn call-bluff-btn"
|
||||
@click="callBluff('player')"
|
||||
>
|
||||
🤥 Lüge! (Bluff aufdecken)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Next Round or Game Over -->
|
||||
<div v-else-if="isRoundEvaluating && !liarsDiceGameOver" class="dice-round-eval">
|
||||
<button class="action-btn next-round-btn" @click="proceedNextDiceRound">
|
||||
🎲 Nächste Runde starten ▶
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="liarsDiceGameOver" class="dice-game-over">
|
||||
<template v-if="liarsDicePlayerWon">
|
||||
<h2 class="victory-title">👑 SASCHA GEWINNT DAS LIAR'S DICE MATCH!</h2>
|
||||
<p class="dice-over-desc">Papa Markus klopft dir auf die Schulter: „Respekt, Sascha! Du hast das Zeug zum König von Taiwan!“</p>
|
||||
<p class="reward-text">+10 Kronkorken, 58% Kinmen Gaoliang Flasche & Titel freigeschaltet!</p>
|
||||
<button class="action-btn" @click="liarsDiceActive = false">乾杯 Papa! Zurücklehnen</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<h2 class="defeat-title">🍶 PAPA MARKUS TRIUMPHIERT!</h2>
|
||||
<p class="dice-over-desc">Papa Markus lacht schallend: „Gegen jahrzehntelange Gaoliang-Erfahrung kommst du noch nicht an, Sascha!“</p>
|
||||
<div class="boar-retry-actions">
|
||||
<button class="action-btn retry-btn" @click="startLiarsDiceMatch">Revanche spielen!</button>
|
||||
<button class="close-btn" @click="liarsDiceActive = false">Vielleicht später</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -901,11 +1343,11 @@ function proceedToExpress() {
|
||||
}
|
||||
|
||||
/* 1. Schankraum Hotspots */
|
||||
.hotspot-verena { top: 17%; left: 28%; }
|
||||
.hotspot-door-nebenraum { top: 42%; left: 34%; }
|
||||
.hotspot-armend { top: 62%; left: 18%; }
|
||||
.hotspot-lutz { top: 54%; left: 56%; }
|
||||
.hotspot-door-marktplatz { top: 50%; left: 84%; }
|
||||
.hotspot-verena { top: 21%; left: 28%; }
|
||||
.hotspot-door-nebenraum { top: 45%; left: 34%; }
|
||||
.hotspot-armend { top: 58%; left: 74%; }
|
||||
.hotspot-lutz { top: 56%; left: 52%; }
|
||||
.hotspot-door-marktplatz { top: 48%; left: 86%; }
|
||||
|
||||
/* 2. Nebenraum Hotspots */
|
||||
.hotspot-mirror-nebenraum { top: 36%; left: 24%; }
|
||||
@@ -974,8 +1416,8 @@ function proceedToExpress() {
|
||||
.hotspot-door-back-gaststube { top: 74%; left: 20%; }
|
||||
.hotspot-door-hinterland { top: 34%; left: 76%; }
|
||||
|
||||
/* 3. Hinterland Hotspots */
|
||||
.hotspot-mane { top: 54%; left: 36%; }
|
||||
/* 4. Hinterland Hotspots */
|
||||
.hotspot-mane { top: 52%; left: 82%; }
|
||||
.hiking-anim {
|
||||
animation: hikeBob 1.2s infinite ease-in-out;
|
||||
}
|
||||
@@ -990,10 +1432,16 @@ function proceedToExpress() {
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
|
||||
.hotspot-hansi { top: 56%; left: 74%; }
|
||||
.hotspot-door-back-marktplatz { top: 72%; left: 22%; }
|
||||
.hotspot-hansi { top: 44%; left: 63%; }
|
||||
.hotspot-sign-munchanien { top: 53%; left: 24%; }
|
||||
.hotspot-door-hinter-den-bergen { top: 42%; left: 24%; }
|
||||
.hotspot-door-back-marktplatz { top: 74%; left: 16%; }
|
||||
|
||||
/* 4. Hinterzimmer Hotspots & Strobe */
|
||||
/* 5. Hinter den Bergen Hotspots */
|
||||
.hotspot-markus { top: 56%; left: 50%; }
|
||||
.hotspot-door-back-hinterland { top: 74%; left: 20%; }
|
||||
|
||||
/* 6. Hinterzimmer Hotspots & Strobe */
|
||||
.strobe-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -1596,4 +2044,291 @@ function proceedToExpress() {
|
||||
.start-duel-action-btn:hover {
|
||||
background: #16a34a !important;
|
||||
}
|
||||
|
||||
/* Liar's Dice (Chui Niu) Modal & UI */
|
||||
.liars-dice-box {
|
||||
max-width: 360px;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.liars-dice-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
border-bottom: 1px solid #78350f;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.dice-title-group {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.dice-title-group h3 {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 7.5px;
|
||||
color: #ffd700;
|
||||
margin: 0 0 4px 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.dice-score {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 6.5px;
|
||||
color: #fef08a;
|
||||
}
|
||||
|
||||
.dice-status-msg {
|
||||
font-family: 'MedievalSharp', serif;
|
||||
font-size: 13.5px;
|
||||
color: #fef3c7;
|
||||
min-height: 38px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.dice-arena {
|
||||
background: #1a0f0a;
|
||||
border: 1.5px solid #d97706;
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dice-player-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
padding: 6px 8px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.dice-player-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 6.5px;
|
||||
color: #fef08a;
|
||||
}
|
||||
|
||||
.dice-avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #ffd700;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.player-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dice-cup-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cup-covered {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: #94a3b8;
|
||||
font-family: 'MedievalSharp', serif;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cup-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.dice-revealed-row {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.dice-die {
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.markus-die {
|
||||
color: #f87171;
|
||||
}
|
||||
|
||||
.player-die {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.current-bid-banner {
|
||||
background: #451a03;
|
||||
border: 1px dashed #ffd700;
|
||||
border-radius: 4px;
|
||||
padding: 5px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
font-size: 11px;
|
||||
color: #fef08a;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bid-label {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 6px;
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.bid-value {
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
.bidder-tag {
|
||||
font-size: 9.5px;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
.shot-animation-banner {
|
||||
background: #7f1d1d;
|
||||
border: 1.5px solid #ef4444;
|
||||
border-radius: 4px;
|
||||
padding: 6px;
|
||||
text-align: center;
|
||||
font-family: 'MedievalSharp', serif;
|
||||
font-size: 13px;
|
||||
color: #fef08a;
|
||||
animation: pulseAnim 0.8s infinite alternate;
|
||||
}
|
||||
|
||||
.dice-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.bid-selectors {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.selector-col {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.selector-col label {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 6px;
|
||||
color: #fed7aa;
|
||||
}
|
||||
|
||||
.counter-btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #1a0f0a;
|
||||
border: 1px solid #78350f;
|
||||
border-radius: 4px;
|
||||
justify-content: space-between;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.mini-btn {
|
||||
background: #b45309;
|
||||
border: 1px solid #f59e0b;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.counter-val {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 10px;
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
.die-val-btns {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.die-select-btn {
|
||||
background: #231209;
|
||||
border: 1px solid #78350f;
|
||||
border-radius: 3px;
|
||||
color: #fef3c7;
|
||||
font-size: 10px;
|
||||
padding: 4px 3px;
|
||||
cursor: pointer;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.die-select-btn.active {
|
||||
background: #d97706;
|
||||
border-color: #ffd700;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dice-action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.bid-action-btn {
|
||||
flex: 1.5;
|
||||
background: #15803d !important;
|
||||
border-color: #86efac !important;
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 6.5px;
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
.call-bluff-btn {
|
||||
flex: 1;
|
||||
background: #dc2626 !important;
|
||||
border-color: #fca5a5 !important;
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 6.5px;
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
.dice-round-eval {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.next-round-btn {
|
||||
width: 100%;
|
||||
background: #2563eb !important;
|
||||
border-color: #93c5fd !important;
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 7.5px;
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
.dice-game-over {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dice-over-desc {
|
||||
font-family: 'MedievalSharp', serif;
|
||||
font-size: 13.5px;
|
||||
margin: 6px 0;
|
||||
color: #fef3c7;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user