218 lines
6.0 KiB
Go
218 lines
6.0 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"image"
|
|
_ "image/png"
|
|
"image/color"
|
|
"math"
|
|
"math/rand"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
"github.com/hajimehoshi/ebiten/v2/text"
|
|
"github.com/hajimehoshi/ebiten/v2/vector"
|
|
"github.com/skip2/go-qrcode"
|
|
"golang.org/x/image/font/basicfont"
|
|
|
|
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game"
|
|
)
|
|
|
|
type presAssetInstance struct {
|
|
AssetID string
|
|
X, Y float64
|
|
VX float64
|
|
Scale float64
|
|
}
|
|
|
|
// generateQRCode erstellt ein ebiten.Image aus einem QR-Code
|
|
func generateQRCode(url string) *ebiten.Image {
|
|
pngData, err := qrcode.Encode(url, qrcode.Medium, 256)
|
|
if err != nil {
|
|
fmt.Println("Error generating QR code:", err)
|
|
return nil
|
|
}
|
|
|
|
img, _, err := image.Decode(bytes.NewReader(pngData))
|
|
if err != nil {
|
|
fmt.Println("Error decoding QR code:", err)
|
|
return nil
|
|
}
|
|
return ebiten.NewImageFromImage(img)
|
|
}
|
|
|
|
// updatePresentation verarbeitet die Logik für den Präsentationsmodus.
|
|
func (g *Game) updatePresentation() {
|
|
now := time.Now()
|
|
|
|
// Auto-Start Presentation Room when connected
|
|
g.stateMutex.Lock()
|
|
status := g.gameState.Status
|
|
g.stateMutex.Unlock()
|
|
|
|
if g.connected && status == "LOBBY" && g.isHost {
|
|
g.SendCommand("START_PRESENTATION")
|
|
}
|
|
|
|
// 1. Zitat-Wechsel alle 6 Sekunden
|
|
if now.After(g.presQuoteTime) {
|
|
g.presQuote = game.GetRandomQuote()
|
|
g.presQuoteTime = now.Add(6 * time.Second)
|
|
}
|
|
|
|
// 2. Assets spawnen (wenn zu wenige da sind)
|
|
if len(g.presAssets) < 8 && rand.Float64() < 0.05 {
|
|
// Wähle zufälliges Asset (Schüler, Lehrer, Items)
|
|
assetList := []string{"player", "coin", "eraser", "pc-trash", "godmode", "jumpboost", "magnet"}
|
|
id := assetList[rand.Intn(len(assetList))]
|
|
|
|
g.presAssets = append(g.presAssets, presAssetInstance{
|
|
AssetID: id,
|
|
X: float64(ScreenWidth + 100),
|
|
Y: float64(ScreenHeight - 150 - rand.Intn(100)),
|
|
VX: -(2.0 + rand.Float64()*4.0),
|
|
Scale: 1.0 + rand.Float64()*0.5,
|
|
})
|
|
}
|
|
|
|
// 3. Assets bewegen
|
|
newAssets := g.presAssets[:0]
|
|
for _, a := range g.presAssets {
|
|
a.X += a.VX
|
|
if a.X > -200 { // Noch im Bildbereich (mit Puffer)
|
|
newAssets = append(newAssets, a)
|
|
}
|
|
}
|
|
g.presAssets = newAssets
|
|
}
|
|
|
|
// drawPresentation zeichnet den Präsentationsmodus.
|
|
func (g *Game) drawPresentation(screen *ebiten.Image) {
|
|
// Hintergrund: Retro Dunkelblau
|
|
screen.Fill(color.RGBA{10, 15, 30, 255})
|
|
|
|
// Animierte Scanlines / Raster-Effekt (Retro Style)
|
|
for i := 0; i < ScreenHeight; i += 4 {
|
|
vector.DrawFilledRect(screen, 0, float32(i), float32(ScreenWidth), 1, color.RGBA{0, 0, 0, 40}, false)
|
|
}
|
|
|
|
// Überschrift
|
|
text.Draw(screen, "PRESENTATION MODE", basicfont.Face7x13, ScreenWidth/2-80, 50, color.RGBA{255, 255, 0, 255})
|
|
vector.StrokeLine(screen, ScreenWidth/2-90, 60, ScreenWidth/2+90, 60, 2, color.RGBA{255, 255, 0, 255}, false)
|
|
|
|
// Zitat groß in der Mitte
|
|
if g.presQuote.Text != "" {
|
|
quoteMsg := fmt.Sprintf("\"%s\"", g.presQuote.Text)
|
|
authorMsg := fmt.Sprintf("- %s", g.presQuote.Author)
|
|
|
|
// Einfaches Word-Wrap (sehr rudimentär)
|
|
drawWrappedText(screen, quoteMsg, ScreenWidth/2, ScreenHeight/2-20, 600, color.White)
|
|
text.Draw(screen, authorMsg, basicfont.Face7x13, ScreenWidth/2+100, ScreenHeight/2+50, color.RGBA{200, 200, 200, 255})
|
|
}
|
|
|
|
// Assets laufen unten durch
|
|
for _, a := range g.presAssets {
|
|
def, ok := g.world.Manifest.Assets[a.AssetID]
|
|
if !ok { continue }
|
|
|
|
img, ok := g.assetsImages[a.AssetID]
|
|
if !ok { continue }
|
|
|
|
op := &ebiten.DrawImageOptions{}
|
|
op.GeoM.Scale(a.Scale, a.Scale)
|
|
op.GeoM.Translate(a.X, a.Y)
|
|
|
|
// Leichtes Pulsieren/Animation
|
|
bob := math.Sin(float64(time.Now().UnixMilli())/200.0) * 5.0
|
|
op.GeoM.Translate(0, bob)
|
|
|
|
screen.DrawImage(img, op)
|
|
|
|
// Name des Assets drunter schreiben
|
|
text.Draw(screen, def.ID, basicfont.Face7x13, int(a.X), int(a.Y+80), color.RGBA{100, 200, 255, 150})
|
|
}
|
|
|
|
// Draw connected players (no names)
|
|
g.stateMutex.Lock()
|
|
for _, p := range g.gameState.Players {
|
|
if !p.IsAlive || p.Name == "PRESENTATION" {
|
|
continue // Skip Host and dead players
|
|
}
|
|
|
|
// Map player X/Y to screen
|
|
playerX := p.X
|
|
playerY := p.Y
|
|
|
|
// Keep players somewhat in bounds if they walk too far
|
|
if playerX > ScreenWidth {
|
|
playerX = math.Mod(playerX, ScreenWidth)
|
|
} else if playerX < 0 {
|
|
playerX = ScreenWidth - math.Mod(-playerX, ScreenWidth)
|
|
}
|
|
|
|
// Draw simple player sprite
|
|
img, ok := g.assetsImages["player"]
|
|
if ok {
|
|
op := &ebiten.DrawImageOptions{}
|
|
op.GeoM.Translate(playerX, playerY)
|
|
screen.DrawImage(img, op)
|
|
} else {
|
|
// Fallback rect
|
|
vector.DrawFilledRect(screen, float32(playerX), float32(playerY), 40, 60, color.RGBA{0, 255, 0, 255}, false)
|
|
}
|
|
|
|
// Draw Emote if active
|
|
if p.State != "" && strings.HasPrefix(p.State, "EMOTE_") {
|
|
emoteStr := p.State[6:] // e.g. EMOTE_1 -> "1"
|
|
emoteMap := map[string]string{
|
|
"1": "❤️",
|
|
"2": "😂",
|
|
"3": "😡",
|
|
"4": "👍",
|
|
}
|
|
if emoji, ok := emoteMap[emoteStr]; ok {
|
|
text.Draw(screen, emoji, basicfont.Face7x13, int(playerX+10), int(playerY-10), color.White)
|
|
}
|
|
}
|
|
}
|
|
g.stateMutex.Unlock()
|
|
|
|
// Draw QR Code
|
|
if g.presQRCode != nil {
|
|
qrSize := 150.0
|
|
qrW, _ := g.presQRCode.Size()
|
|
scale := float64(qrSize) / float64(qrW)
|
|
op := &ebiten.DrawImageOptions{}
|
|
op.GeoM.Scale(scale, scale)
|
|
op.GeoM.Translate(20, 20)
|
|
screen.DrawImage(g.presQRCode, op)
|
|
|
|
// Instruction
|
|
text.Draw(screen, "SCANNEN ZUM MITMACHEN!", basicfont.Face7x13, 20, 190, color.RGBA{255, 255, 0, 255})
|
|
}
|
|
|
|
// Hotkey Info
|
|
text.Draw(screen, "DRÜCKE [F1] ZUM BEENDEN", basicfont.Face7x13, ScreenWidth-250, ScreenHeight-30, color.RGBA{255, 255, 255, 100})
|
|
}
|
|
|
|
// drawWrappedText zeichnet Text mit automatischem Zeilenumbruch.
|
|
func drawWrappedText(screen *ebiten.Image, str string, x, y, maxWidth int, col color.Color) {
|
|
words := strings.Split(str, " ")
|
|
line := ""
|
|
currY := y
|
|
|
|
for _, w := range words {
|
|
testLine := line + w + " "
|
|
if len(testLine)*7 > maxWidth { // Grobe Schätzung Breite
|
|
text.Draw(screen, line, basicfont.Face7x13, x-len(line)*7/2, currY, col)
|
|
line = w + " "
|
|
currY += 20
|
|
} else {
|
|
line = testLine
|
|
}
|
|
}
|
|
text.Draw(screen, line, basicfont.Face7x13, x-len(line)*7/2, currY, col)
|
|
}
|