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 {
g.trail = g.trail[1:]
}
// --- Zitate & Meilensteine ---
g.updateQuotes()
}
// --- 6. KAMERA LOGIK (mit Smoothing) ---
@@ -604,6 +607,13 @@ func (g *Game) drawStatusUI(screen *ebiten.Image, snap renderSnapshot) {
if snap.isDead {
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.
func (g *Game) drawTeacher(screen *ebiten.Image, snap renderSnapshot) {
if snap.status != "RUNNING" && snap.status != "COUNTDOWN" {
@@ -825,8 +893,13 @@ func (g *Game) drawTeacher(screen *ebiten.Image, snap renderSnapshot) {
// Legs
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)
}
// --- 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
if danger > 0.55 {
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
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
if myScore > g.localHighscore {
g.localHighscore = myScore
@@ -52,9 +60,9 @@ func (g *Game) drawGameOverScreen(screen *ebiten.Image, myScore int) {
}
// Persönlicher Highscore anzeigen
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 {
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

View File

@@ -29,6 +29,7 @@ const (
StateLobby = 1
StateGame = 2
StateLeaderboard = 3
StatePresentation = 4
RefFloorY = 540 // Server-Welt Boden-Position (unveränderlich)
)
@@ -170,6 +171,20 @@ type Game struct {
// Audio System
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
camX float64
@@ -262,6 +277,17 @@ func (g *Game) Update() error {
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
g.predictionMutex.Lock()
g.pendingInputCount = len(g.pendingInputs)
@@ -302,6 +328,7 @@ func (g *Game) Update() error {
}
if currentStatus == "GAMEOVER" && g.lastStatus == "RUNNING" {
g.audio.StopMusic()
g.deathQuote = game.GetRandomQuote()
if g.gameMode == "solo" {
g.verifyRoundResult()
}
@@ -317,6 +344,8 @@ func (g *Game) Update() error {
g.UpdateGame()
case StateLeaderboard:
g.updateLeaderboard()
case StatePresentation:
g.updatePresentation()
}
return nil
}
@@ -478,6 +507,8 @@ func (g *Game) draw(screen *ebiten.Image) {
g.DrawGame(screen)
case StateLeaderboard:
g.drawLeaderboard(screen)
case StatePresentation:
g.drawPresentation(screen)
}
}