add presentation mode: implement presentation logic, QR code support, animated quotes, assets display, and emotes
Some checks failed
Dynamic Branch Deploy / build-and-deploy (push) Failing after 1m33s
Some checks failed
Dynamic Branch Deploy / build-and-deploy (push) Failing after 1m33s
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"log"
|
||||
"math"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
@@ -265,6 +266,20 @@ func (g *Game) UpdateGame() {
|
||||
g.updateQuotes()
|
||||
}
|
||||
|
||||
// --- EMOTES ---
|
||||
if inpututil.IsKeyJustPressed(ebiten.Key1) {
|
||||
g.SendCommand("EMOTE_1")
|
||||
}
|
||||
if inpututil.IsKeyJustPressed(ebiten.Key2) {
|
||||
g.SendCommand("EMOTE_2")
|
||||
}
|
||||
if inpututil.IsKeyJustPressed(ebiten.Key3) {
|
||||
g.SendCommand("EMOTE_3")
|
||||
}
|
||||
if inpututil.IsKeyJustPressed(ebiten.Key4) {
|
||||
g.SendCommand("EMOTE_4")
|
||||
}
|
||||
|
||||
// --- 6. KAMERA LOGIK (mit Smoothing) ---
|
||||
g.stateMutex.Lock()
|
||||
targetCam := g.gameState.ScrollX
|
||||
@@ -330,6 +345,15 @@ func (g *Game) handleTouchInput() {
|
||||
x, y := ebiten.TouchPosition(id)
|
||||
fx, fy := float64(x), float64(y)
|
||||
|
||||
// ── EMOTES ───────────────────────────────────────────────────────────
|
||||
if fx >= float64(g.lastCanvasWidth)-80.0 && fy >= 40.0 && fy <= 250.0 && isJustPressed(id) {
|
||||
emoteIdx := int((fy - 50.0) / 50.0)
|
||||
if emoteIdx >= 0 && emoteIdx <= 3 {
|
||||
g.SendCommand(fmt.Sprintf("EMOTE_%d", emoteIdx+1))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if fx >= halfW {
|
||||
// ── RECHTE SEITE: Jump und Down ──────────────────────────────────────
|
||||
g.btnJumpPressed = true
|
||||
@@ -563,7 +587,25 @@ func (g *Game) drawPlayers(screen *ebiten.Image, snap renderSnapshot) {
|
||||
if name == "" {
|
||||
name = id
|
||||
}
|
||||
text.Draw(screen, name, basicfont.Face7x13, int((posX-g.camX)*snap.viewScale), int(screenY-25), ColText)
|
||||
|
||||
// In Presentation Mode normal players don't show names, only Host/PRESENTATION does (which is hidden anyway)
|
||||
if snap.status != "PRESENTATION" || name == g.playerName {
|
||||
text.Draw(screen, name, basicfont.Face7x13, int((posX-g.camX)*snap.viewScale), int(screenY-25), ColText)
|
||||
}
|
||||
|
||||
// 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((posX-g.camX)*snap.viewScale+15), int(screenY-40), color.White)
|
||||
}
|
||||
}
|
||||
|
||||
if g.showDebug {
|
||||
g.drawPlayerHitbox(screen, posX, screenY, snap.viewScale)
|
||||
@@ -779,6 +821,20 @@ func (g *Game) drawTouchControls(screen *ebiten.Image) {
|
||||
vector.DrawFilledCircle(screen, float32(downX), float32(downY), float32(downR), color.RGBA{50, 120, 220, 55}, false)
|
||||
vector.StrokeCircle(screen, float32(downX), float32(downY), float32(downR), 2, color.RGBA{80, 160, 255, 120}, false)
|
||||
text.Draw(screen, "▼", basicfont.Face7x13, int(downX)-4, int(downY)+5, color.RGBA{200, 220, 255, 180})
|
||||
|
||||
// ── D) Emote Buttons (oben rechts) ─────────────────────────────────────────
|
||||
emoteY := 50.0
|
||||
emoteXBase := float64(tcW) - 60.0
|
||||
emoteSize := 40.0
|
||||
emotes := []string{"❤️", "😂", "😡", "👍"}
|
||||
|
||||
for i, em := range emotes {
|
||||
x := emoteXBase
|
||||
y := emoteY + float64(i)*50.0
|
||||
vector.DrawFilledRect(screen, float32(x), float32(y), float32(emoteSize), float32(emoteSize), color.RGBA{0, 0, 0, 100}, false)
|
||||
vector.StrokeRect(screen, float32(x), float32(y), float32(emoteSize), float32(emoteSize), 2, color.RGBA{255, 255, 255, 100}, false)
|
||||
text.Draw(screen, em, basicfont.Face7x13, int(x)+10, int(y)+25, color.White)
|
||||
}
|
||||
}
|
||||
|
||||
// TriggerShake aktiviert den Screen-Shake-Effekt.
|
||||
|
||||
@@ -15,6 +15,7 @@ require (
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
|
||||
@@ -16,6 +16,8 @@ github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA=
|
||||
|
||||
@@ -184,6 +184,7 @@ type Game struct {
|
||||
presQuoteTime time.Time
|
||||
presAssets []presAssetInstance
|
||||
lastPresUpdate time.Time
|
||||
presQRCode *ebiten.Image
|
||||
|
||||
// Kamera
|
||||
camX float64
|
||||
@@ -281,10 +282,24 @@ func (g *Game) Update() error {
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyF1) {
|
||||
if g.appState == StatePresentation {
|
||||
g.appState = StateMenu
|
||||
g.disconnectFromServer()
|
||||
} else {
|
||||
g.appState = StatePresentation
|
||||
g.presAssets = nil // Reset assets
|
||||
g.presQuoteTime = time.Now() // Force immediate first quote
|
||||
|
||||
// Setup Server Connection for Presentation Mode
|
||||
g.gameMode = "coop" // Use coop logic on server
|
||||
g.isHost = true
|
||||
g.roomID = "PRES" + generateRoomCode()
|
||||
g.playerName = "PRESENTATION"
|
||||
|
||||
// Start connection process in background
|
||||
go g.connectAndStart()
|
||||
|
||||
// Generate QR Code URL
|
||||
joinURL := "https://escape-from-school.de/?room=" + g.roomID
|
||||
g.presQRCode = generateQRCode(joinURL)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,8 +331,8 @@ func (g *Game) Update() error {
|
||||
}
|
||||
}
|
||||
|
||||
// COUNTDOWN/RUNNING-Übergang: AppState auf StateGame setzen + JS benachrichtigen
|
||||
if (currentStatus == "COUNTDOWN" || currentStatus == "RUNNING") && g.appState != StateGame {
|
||||
// COUNTDOWN/RUNNING/PRESENTATION-Übergang: AppState auf StateGame setzen + JS benachrichtigen
|
||||
if (currentStatus == "COUNTDOWN" || currentStatus == "RUNNING" || currentStatus == "PRESENTATION") && g.appState != StateGame && g.appState != StatePresentation {
|
||||
log.Printf("🎮 Spiel startet! Status: %s -> %s", g.lastStatus, currentStatus)
|
||||
g.appState = StateGame
|
||||
g.notifyGameStarted()
|
||||
|
||||
217
cmd/client/presentation.go
Normal file
217
cmd/client/presentation.go
Normal file
@@ -0,0 +1,217 @@
|
||||
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)
|
||||
}
|
||||
@@ -684,6 +684,21 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
document.getElementById('playerName').value = savedName;
|
||||
}
|
||||
|
||||
// Auto-Join if URL parameter ?room=XYZ is present
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const roomParam = urlParams.get('room');
|
||||
if (roomParam) {
|
||||
document.getElementById('joinRoomCode').value = roomParam;
|
||||
|
||||
// Wait for WASM to be ready, then auto-join
|
||||
const checkWASM = setInterval(() => {
|
||||
if (wasmReady) {
|
||||
clearInterval(checkWASM);
|
||||
joinRoom();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Load local highscore
|
||||
const highscore = localStorage.getItem('escape_local_highscore') || 0;
|
||||
const hsElement = document.getElementById('localHighscore');
|
||||
|
||||
49
pkg/game/quotes.go
Normal file
49
pkg/game/quotes.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package game
|
||||
|
||||
import "math/rand"
|
||||
|
||||
type Quote struct {
|
||||
Text string
|
||||
Author string
|
||||
Ctx string
|
||||
}
|
||||
|
||||
var Quotes = []Quote{
|
||||
{Text: "Mobbing ist besser als gar keine sozialen Kontakte.", Author: "Ein Lehrer"},
|
||||
{Text: "Was heißt Strafe auf Englisch? „Richard“?", Author: "Ein Lehrer"},
|
||||
{Text: "Heute ist alles richtig eingetragen.", Author: "Eine Lehrerin"},
|
||||
{Text: "Verstehen Sie überhaupt die Prüfungsfragen? Neh, ach …", Author: "Ein Lehrer"},
|
||||
{Text: "Ist das eine rechtsextreme Handlung?!", Author: "Schüler"},
|
||||
{Text: "Ich bin mit dem Staat verheiratet. … Ich hab das nur wegen der Pension gemacht.", Author: "Ein Lehrer"},
|
||||
{Text: "Ich hab 'nen Freund! – Das ist egal.", Author: "Lehrerin & Schüler"},
|
||||
{Text: "Neues Lieblingswort: „Hanebüchen“", Author: "Ein Lehrer"},
|
||||
{Text: "Ich mag Menschen quälen.", Author: "Ein Lehrer"},
|
||||
{Text: "Morgen sind Schüler unserer polnischen Partnerschule da. Die wollen von dem Besten lernen – also von mir.", Author: "Ein Lehrer"},
|
||||
{Text: "Ich bräuchte jetzt wirklich einen Kaffee oder ein Bier.", Author: "Eine Lehrerin"},
|
||||
{Text: "Scheiße, darauf kann ich nicht rumschreiben!", Author: "Ein Lehrer"},
|
||||
{Text: "Warum muss ich jetzt wieder Scheiße erklären, die ich net verzapft hab. Lasst mich doch in Ruhe.", Author: "Ein Lehrer"},
|
||||
{Text: "Dann können Rollstuhlfahrer gleich mit in den Krieg ziehen.", Author: "Ein Lehrer"},
|
||||
{Text: "Mobbing ist 3 Monate durchgängig. Ich kann sie also nicht gar nicht mobben, weil sie immer weg sind.", Author: "Ein Lehrer"},
|
||||
{Text: "Ich spiele kein Schach mehr, seit ich gegen ein Kind verloren hab.", Author: "Ein Lehrer"},
|
||||
{Text: "Spielen Sie „God of War“? So sehen sie auch aus.", Author: "Ein Lehrer"},
|
||||
{Text: "Die Antwort soll „ja“ sein. Mit genug Reden kann man auch das Gegenteil argumentieren.", Author: "Ein Lehrer"},
|
||||
{Text: "Es geht darum, Sie drei Jahre hinzuhalten – und dann sind Sie eh weg.", Author: "Ein Lehrer"},
|
||||
{Text: "Es gibt hier gar kein Problem. ... Es gibt verdammt nochmal keine Probleme!", Author: "Ein Lehrer"},
|
||||
{Text: "Ich denke immer, ich bin doof. Aber das ist so.", Author: "Ein Lehrer"},
|
||||
{Text: "Warum wollen Sie die Schule versichern? Die können Sie sowieso nicht verklagen.", Author: "Ein Lehrer"},
|
||||
{Text: "Haltet euch sklavisch an die Notation!", Author: "Ein Lehrer"},
|
||||
{Text: "Nur die Paranoiden werden überleben.", Author: "Ein Lehrer"},
|
||||
{Text: "Ich bin gerade im Größenwahn und es wird immer verrückter.", Author: "Ein Lehrer"},
|
||||
{Text: "Programmieren kann so ekelhaft sein, wenn man sich wirklich damit beschäftigt.", Author: "Ein Lehrer"},
|
||||
{Text: "Vorsicht, sauer. Hab ich meinen Kindern geklaut.", Author: "Ein Lehrer"},
|
||||
{Text: "Es gibt noch solche von der Resterampe wie mich.", Author: "Ein Lehrer"},
|
||||
{Text: "Jetzt muss ich mir schon die Musterlösung schönsaufen.", Author: "Ein Lehrer"},
|
||||
{Text: "Ich muss die Prüfung nicht schreiben!", Author: "Ein Lehrer"},
|
||||
{Text: "Werd ich echt alt?", Author: "Ein Lehrer"},
|
||||
{Text: "Die Geißel Gottes. Ich freue mich, Sie zu sehen…!", Author: "Ein Lehrer"},
|
||||
{Text: "Kind kriegen ist glaub ich schon geiler als auf'm Mount Everest zu steigen.", Author: "Ein Lehrer"},
|
||||
}
|
||||
|
||||
func GetRandomQuote() Quote {
|
||||
return Quotes[rand.Intn(len(Quotes))]
|
||||
}
|
||||
@@ -342,6 +342,12 @@ func (r *Room) HandleInput(input game.ClientInput) {
|
||||
if input.PlayerID == r.HostID && r.Status == "LOBBY" {
|
||||
r.StartCountdown()
|
||||
}
|
||||
case "START_PRESENTATION":
|
||||
if input.PlayerID == r.HostID && r.Status == "LOBBY" {
|
||||
r.Status = "PRESENTATION"
|
||||
r.GameStartTime = time.Now()
|
||||
r.CurrentSpeed = 0
|
||||
}
|
||||
case "SET_TEAM_NAME":
|
||||
// Nur Host darf Team-Name setzen und nur in der Lobby
|
||||
if input.PlayerID == r.HostID && r.Status == "LOBBY" {
|
||||
@@ -349,6 +355,19 @@ func (r *Room) HandleInput(input game.ClientInput) {
|
||||
log.Printf("🏷️ Team-Name gesetzt: '%s' (von Host %s)", r.TeamName, p.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Emote Handling (z.B. EMOTE_1, EMOTE_2)
|
||||
if len(input.Type) > 6 && input.Type[:6] == "EMOTE_" {
|
||||
p.State = input.Type
|
||||
|
||||
// Emote nach 2 Sekunden zurücksetzen
|
||||
go func(player *ServerPlayer, emote string) {
|
||||
time.Sleep(2 * time.Second)
|
||||
if player.State == emote {
|
||||
player.State = ""
|
||||
}
|
||||
}(p, input.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Room) StartCountdown() {
|
||||
@@ -388,6 +407,11 @@ func (r *Room) Update() {
|
||||
r.GlobalScrollX += r.CurrentSpeed
|
||||
// Bewegende Plattformen updaten
|
||||
r.UpdateMovingPlatforms()
|
||||
} else if r.Status == "PRESENTATION" {
|
||||
// Keine Kamera-Bewegung, keine Schwierigkeitssteigerung, aber Physik läuft weiter
|
||||
r.CurrentSpeed = 0
|
||||
// Bewegende Plattformen können sich auch hier bewegen, wenn gewünscht
|
||||
r.UpdateMovingPlatforms()
|
||||
}
|
||||
|
||||
maxX := r.GlobalScrollX
|
||||
|
||||
Reference in New Issue
Block a user