Private
Public Access
1
0

add teacher and milestone quotes: implement random quotes, speech bubbles, and milestone achievements display
Some checks failed
Dynamic Branch Deploy / build-and-deploy (push) Failing after 1m35s

This commit is contained in:
Sebastian Unterschütz
2026-04-22 19:53:15 +02:00
parent 8454557f16
commit 568ce516e7
3 changed files with 115 additions and 3 deletions

View File

@@ -260,6 +260,9 @@ func (g *Game) UpdateGame() {
if len(g.trail) > 8 { if len(g.trail) > 8 {
g.trail = g.trail[1:] g.trail = g.trail[1:]
} }
// --- Zitate & Meilensteine ---
g.updateQuotes()
} }
// --- 6. KAMERA LOGIK (mit Smoothing) --- // --- 6. KAMERA LOGIK (mit Smoothing) ---
@@ -604,6 +607,13 @@ func (g *Game) drawStatusUI(screen *ebiten.Image, snap renderSnapshot) {
if snap.isDead { if snap.isDead {
g.drawSpectatorOverlay(screen, snap) g.drawSpectatorOverlay(screen, snap)
} }
// --- MEILENSTEIN-QUOTE ---
if time.Now().Before(g.milestoneQuoteTime) {
msg := fmt.Sprintf("🎉 %d PUNKTE! \"%s\"", g.lastMilestone, g.milestoneQuote.Text)
tw := float32(len(msg) * 7)
text.Draw(screen, msg, basicfont.Face7x13, int(float32(snap.canvasW)/2-tw/2), 60, color.RGBA{255, 255, 0, 255})
}
} }
} }
@@ -781,6 +791,64 @@ func (g *Game) TriggerShake(frames int, intensity float64) {
} }
} }
// updateQuotes verarbeitet die Logik für zufällige Lehrer-Sprüche und Meilensteine.
func (g *Game) updateQuotes() {
g.stateMutex.Lock()
status := g.gameState.Status
myScore := 0
for _, p := range g.gameState.Players {
if p.Name == g.playerName {
myScore = p.Score
break
}
}
g.stateMutex.Unlock()
if status != "RUNNING" {
return
}
now := time.Now()
// 1. Zufällige Lehrer-Sprüche (alle 10-25 Sekunden)
if now.After(g.teacherQuoteTime) {
g.teacherQuote = game.GetRandomQuote()
// Nächster Spruch in 15-30 Sekunden
g.teacherQuoteTime = now.Add(time.Duration(15+rand.Intn(15)) * time.Second)
}
// 2. Meilensteine (alle 1000 Punkte)
milestone := (myScore / 1000) * 1000
if milestone > 0 && milestone > g.lastMilestone {
g.lastMilestone = milestone
g.milestoneQuote = game.GetRandomQuote()
g.milestoneQuoteTime = now.Add(4 * time.Second) // 4 Sekunden anzeigen
log.Printf("🎉 Meilenstein erreicht: %d Punkte!", milestone)
}
}
// drawSpeechBubble zeichnet eine einfache Sprechblase mit Text.
func (g *Game) drawSpeechBubble(screen *ebiten.Image, x, y float32, msg string) {
// Text-Breite grob schätzen (Face7x13: ca 7px pro Zeichen)
tw := float32(len(msg) * 7)
th := float32(15)
padding := float32(8)
bx := x + 10
by := y - th - padding*2
bw := tw + padding*2
bh := th + padding*2
// Hintergrund
vector.DrawFilledRect(screen, bx, by, bw, bh, color.RGBA{255, 255, 255, 220}, false)
vector.StrokeRect(screen, bx, by, bw, bh, 2, color.Black, false)
// Kleiner Pfeil
vector.DrawFilledCircle(screen, bx, by+bh/2, 5, color.RGBA{255, 255, 255, 220}, false)
text.Draw(screen, msg, basicfont.Face7x13, int(bx+padding), int(by+padding+10), color.Black)
}
// drawTeacher zeichnet den Lehrer-Charakter am linken Bildschirmrand. // drawTeacher zeichnet den Lehrer-Charakter am linken Bildschirmrand.
func (g *Game) drawTeacher(screen *ebiten.Image, snap renderSnapshot) { func (g *Game) drawTeacher(screen *ebiten.Image, snap renderSnapshot) {
if snap.status != "RUNNING" && snap.status != "COUNTDOWN" { if snap.status != "RUNNING" && snap.status != "COUNTDOWN" {
@@ -825,8 +893,13 @@ func (g *Game) drawTeacher(screen *ebiten.Image, snap renderSnapshot) {
// Legs // Legs
vector.DrawFilledRect(screen, bodyX+1, groundY, bodyW/2-2, float32(snap.canvasH), color.RGBA{60, 10, 10, alpha}, false) vector.DrawFilledRect(screen, bodyX+1, groundY, bodyW/2-2, float32(snap.canvasH), color.RGBA{60, 10, 10, alpha}, false)
vector.DrawFilledRect(screen, bodyX+bodyW/2+1, groundY, bodyW/2-2, float32(snap.canvasH), color.RGBA{60, 10, 10, alpha}, false) vector.DrawFilledRect(screen, bodyX+bodyW/2+1, groundY, bodyW/2-2, float32(snap.canvasH), color.RGBA{60, 10, 10, alpha}, false)
}
// --- SPRECHBLASE ---
if time.Now().Before(g.teacherQuoteTime.Add(-10 * time.Second)) && g.teacherQuote.Text != "" {
// Wir zeigen den Spruch für 5-10 Sekunden an
g.drawSpeechBubble(screen, teacherCX+15, bodyY-20, g.teacherQuote.Text)
}
}
// Warning text — blinks when close // Warning text — blinks when close
if danger > 0.55 { if danger > 0.55 {
if (time.Now().UnixMilli()/300)%2 == 0 { if (time.Now().UnixMilli()/300)%2 == 0 {

View File

@@ -45,6 +45,14 @@ func (g *Game) drawGameOverScreen(screen *ebiten.Image, myScore int) {
// Großes GAME OVER // Großes GAME OVER
text.Draw(screen, "GAME OVER", basicfont.Face7x13, ScreenWidth/2-50, 60, color.RGBA{255, 0, 0, 255}) text.Draw(screen, "GAME OVER", basicfont.Face7x13, ScreenWidth/2-50, 60, color.RGBA{255, 0, 0, 255})
// Lehrer-Spruch zum Abschied
if g.deathQuote.Text != "" {
quoteMsg := fmt.Sprintf("\"%s\"", g.deathQuote.Text)
quoteW := len(quoteMsg) * 7
// Zentrieren und ggf. umbrechen wenn zu lang (hier erstmal einfach zentriert)
text.Draw(screen, quoteMsg, basicfont.Face7x13, ScreenWidth/2-quoteW/2, 80, color.RGBA{200, 200, 200, 255})
}
// Highscore prüfen und aktualisieren // Highscore prüfen und aktualisieren
if myScore > g.localHighscore { if myScore > g.localHighscore {
g.localHighscore = myScore g.localHighscore = myScore
@@ -52,9 +60,9 @@ func (g *Game) drawGameOverScreen(screen *ebiten.Image, myScore int) {
} }
// Persönlicher Highscore anzeigen // Persönlicher Highscore anzeigen
if myScore == g.localHighscore && myScore > 0 { if myScore == g.localHighscore && myScore > 0 {
text.Draw(screen, fmt.Sprintf("★ NEUER REKORD: %d ★", g.localHighscore), basicfont.Face7x13, ScreenWidth/2-80, 85, color.RGBA{255, 215, 0, 255}) text.Draw(screen, fmt.Sprintf("★ NEUER REKORD: %d ★", g.localHighscore), basicfont.Face7x13, ScreenWidth/2-80, 105, color.RGBA{255, 215, 0, 255})
} else { } else {
text.Draw(screen, fmt.Sprintf("Persönlicher Highscore: %d", g.localHighscore), basicfont.Face7x13, ScreenWidth/2-80, 85, color.Gray{Y: 180}) text.Draw(screen, fmt.Sprintf("Persönlicher Highscore: %d", g.localHighscore), basicfont.Face7x13, ScreenWidth/2-100, 105, color.Gray{Y: 180})
} }
// Linke Seite: Raum-Ergebnisse - Daten KOPIEREN mit Lock, dann außerhalb zeichnen // Linke Seite: Raum-Ergebnisse - Daten KOPIEREN mit Lock, dann außerhalb zeichnen

View File

@@ -29,6 +29,7 @@ const (
StateLobby = 1 StateLobby = 1
StateGame = 2 StateGame = 2
StateLeaderboard = 3 StateLeaderboard = 3
StatePresentation = 4
RefFloorY = 540 // Server-Welt Boden-Position (unveränderlich) RefFloorY = 540 // Server-Welt Boden-Position (unveränderlich)
) )
@@ -170,6 +171,20 @@ type Game struct {
// Audio System // Audio System
audio *AudioSystem audio *AudioSystem
// Zitate / Sprüche
teacherQuote game.Quote
teacherQuoteTime time.Time
milestoneQuote game.Quote
milestoneQuoteTime time.Time
deathQuote game.Quote
lastMilestone int
// Presentation Mode
presQuote game.Quote
presQuoteTime time.Time
presAssets []presAssetInstance
lastPresUpdate time.Time
// Kamera // Kamera
camX float64 camX float64
@@ -262,6 +277,17 @@ func (g *Game) Update() error {
g.showDebug = !g.showDebug g.showDebug = !g.showDebug
} }
// Presentation Toggle (F1)
if inpututil.IsKeyJustPressed(ebiten.KeyF1) {
if g.appState == StatePresentation {
g.appState = StateMenu
} else {
g.appState = StatePresentation
g.presAssets = nil // Reset assets
g.presQuoteTime = time.Now() // Force immediate first quote
}
}
// Pending Inputs zählen für Debug // Pending Inputs zählen für Debug
g.predictionMutex.Lock() g.predictionMutex.Lock()
g.pendingInputCount = len(g.pendingInputs) g.pendingInputCount = len(g.pendingInputs)
@@ -302,6 +328,7 @@ func (g *Game) Update() error {
} }
if currentStatus == "GAMEOVER" && g.lastStatus == "RUNNING" { if currentStatus == "GAMEOVER" && g.lastStatus == "RUNNING" {
g.audio.StopMusic() g.audio.StopMusic()
g.deathQuote = game.GetRandomQuote()
if g.gameMode == "solo" { if g.gameMode == "solo" {
g.verifyRoundResult() g.verifyRoundResult()
} }
@@ -317,6 +344,8 @@ func (g *Game) Update() error {
g.UpdateGame() g.UpdateGame()
case StateLeaderboard: case StateLeaderboard:
g.updateLeaderboard() g.updateLeaderboard()
case StatePresentation:
g.updatePresentation()
} }
return nil return nil
} }
@@ -478,6 +507,8 @@ func (g *Game) draw(screen *ebiten.Image) {
g.DrawGame(screen) g.DrawGame(screen)
case StateLeaderboard: case StateLeaderboard:
g.drawLeaderboard(screen) g.drawLeaderboard(screen)
case StatePresentation:
g.drawPresentation(screen)
} }
} }