Private
Public Access
1
0

Add platform-specific implementations for assets, audio, WebSocket, and rendering on Desktop and WebAssembly platforms. Introduce embedded assets for WebAssembly and native file handling for Desktop. Add platform-specific chunk loading and game state synchronization.

This commit is contained in:
Sebastian Unterschütz
2026-01-04 01:25:04 +01:00
parent 85d697df19
commit 3232ee7c2f
86 changed files with 4931 additions and 486 deletions

View File

@@ -0,0 +1,268 @@
//go:build !wasm
// +build !wasm
package main
import (
"encoding/json"
"log"
"net/url"
"time"
"github.com/gorilla/websocket"
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game"
)
// wsConn verwaltet die WebSocket-Verbindung (Native Desktop)
type wsConn struct {
conn *websocket.Conn
send chan []byte
stopChan chan struct{}
}
// WebSocketMessage ist das Format für WebSocket-Nachrichten
type WebSocketMessage struct {
Type string `json:"type"`
Payload interface{} `json:"payload"`
}
// connectToServer verbindet sich über WebSocket mit dem Gateway (Native Desktop)
func (g *Game) connectToServer() {
serverURL := "ws://localhost:8080/ws"
log.Printf("🔌 Verbinde zu WebSocket-Gateway: %s", serverURL)
u, err := url.Parse(serverURL)
if err != nil {
log.Printf("❌ URL Parse Fehler: %v", err)
return
}
conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Printf("❌ WebSocket Connect Fehler: %v", err)
return
}
wsConn := &wsConn{
conn: conn,
send: make(chan []byte, 100),
stopChan: make(chan struct{}),
}
g.wsConn = wsConn
log.Println("✅ WebSocket verbunden!")
g.connected = true
// Goroutines für Lesen und Schreiben starten
go g.wsReadPump()
go g.wsWritePump()
// JOIN senden
g.sendJoinRequest()
}
// wsReadPump liest Nachrichten vom WebSocket
func (g *Game) wsReadPump() {
defer func() {
g.wsConn.conn.Close()
g.connected = false
}()
for {
var msg WebSocketMessage
err := g.wsConn.conn.ReadJSON(&msg)
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("⚠️ WebSocket Fehler: %v", err)
}
break
}
switch msg.Type {
case "game_update":
// GameState Update
payloadBytes, _ := json.Marshal(msg.Payload)
var state game.GameState
if err := json.Unmarshal(payloadBytes, &state); err == nil {
// Server Reconciliation für lokalen Spieler (VOR dem Lock)
for _, p := range state.Players {
if p.Name == g.playerName {
g.ReconcileWithServer(p)
break
}
}
g.stateMutex.Lock()
g.gameState = state
g.stateMutex.Unlock()
}
case "leaderboard_response":
// Leaderboard Response
payloadBytes, _ := json.Marshal(msg.Payload)
var resp game.LeaderboardResponse
if err := json.Unmarshal(payloadBytes, &resp); err == nil {
g.leaderboardMutex.Lock()
g.leaderboard = resp.Entries
g.leaderboardMutex.Unlock()
log.Printf("📊 Leaderboard empfangen: %d Einträge", len(resp.Entries))
}
}
}
}
// wsWritePump sendet Nachrichten zum WebSocket
func (g *Game) wsWritePump() {
defer g.wsConn.conn.Close()
for {
select {
case message, ok := <-g.wsConn.send:
if !ok {
g.wsConn.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
err := g.wsConn.conn.WriteMessage(websocket.TextMessage, message)
if err != nil {
log.Printf("⚠️ Fehler beim Senden: %v", err)
return
}
case <-g.wsConn.stopChan:
return
}
}
}
// sendWebSocketMessage sendet eine Nachricht über WebSocket
func (g *Game) sendWebSocketMessage(msg WebSocketMessage) {
if g.wsConn == nil || !g.connected {
log.Println("⚠️ WebSocket nicht verbunden")
return
}
data, err := json.Marshal(msg)
if err != nil {
log.Printf("❌ Fehler beim Marshallen: %v", err)
return
}
select {
case g.wsConn.send <- data:
default:
log.Println("⚠️ Send channel voll, Nachricht verworfen")
}
}
// sendJoinRequest sendet Join-Request über WebSocket
func (g *Game) sendJoinRequest() {
msg := WebSocketMessage{
Type: "join",
Payload: game.JoinRequest{
Name: g.playerName,
RoomID: g.roomID,
GameMode: g.gameMode,
IsHost: g.isHost,
TeamName: g.teamName,
},
}
g.sendWebSocketMessage(msg)
log.Printf("➡️ JOIN gesendet über WebSocket: Name=%s, RoomID=%s", g.playerName, g.roomID)
}
// sendStartRequest sendet Start-Request über WebSocket
func (g *Game) sendStartRequest() {
msg := WebSocketMessage{
Type: "start",
Payload: game.StartRequest{
RoomID: g.roomID,
},
}
g.sendWebSocketMessage(msg)
log.Printf("▶️ START gesendet über WebSocket: RoomID=%s", g.roomID)
}
// publishInput sendet Input über WebSocket
func (g *Game) publishInput(input game.ClientInput) {
msg := WebSocketMessage{
Type: "input",
Payload: input,
}
g.sendWebSocketMessage(msg)
}
// connectForLeaderboard verbindet für Leaderboard (Native)
func (g *Game) connectForLeaderboard() {
if g.wsConn != nil && g.connected {
// Bereits verbunden
g.requestLeaderboard()
return
}
// Neue Verbindung aufbauen
g.connectToServer()
// Kurz warten und dann Leaderboard anfragen
time.Sleep(500 * time.Millisecond)
g.requestLeaderboard()
}
// requestLeaderboard fordert Leaderboard an (Native)
func (g *Game) requestLeaderboard() {
mode := "solo"
if g.gameMode == "coop" {
mode = "coop"
}
msg := WebSocketMessage{
Type: "leaderboard_request",
Payload: game.LeaderboardRequest{
Mode: mode,
},
}
g.sendWebSocketMessage(msg)
log.Printf("🏆 Leaderboard-Request gesendet: Mode=%s", mode)
}
// submitScore sendet Score ans Leaderboard (Native)
func (g *Game) submitScore() {
if g.scoreSubmitted {
return
}
g.stateMutex.Lock()
score := 0
for _, p := range g.gameState.Players {
if p.Name == g.playerName {
score = p.Score
break
}
}
g.stateMutex.Unlock()
if score == 0 {
log.Println("⚠️ Score ist 0, überspringe Submission")
return
}
name := g.playerName
if g.gameMode == "coop" && g.teamName != "" {
name = g.teamName
}
msg := WebSocketMessage{
Type: "score_submit",
Payload: game.ScoreSubmission{
PlayerCode: g.playerCode,
Name: name,
Score: score,
Mode: g.gameMode,
},
}
g.sendWebSocketMessage(msg)
g.scoreSubmitted = true
log.Printf("📊 Score submitted: %s = %d", name, score)
}