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
Some checks failed
Dynamic Branch Deploy / build-and-deploy (push) Failing after 1m35s
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user