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:
270
cmd/client/connection_wasm.go
Normal file
270
cmd/client/connection_wasm.go
Normal file
@@ -0,0 +1,270 @@
|
||||
//go:build wasm
|
||||
// +build wasm
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"syscall/js"
|
||||
"time"
|
||||
|
||||
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game"
|
||||
)
|
||||
|
||||
// WebSocketMessage ist das Format für WebSocket-Nachrichten
|
||||
type WebSocketMessage struct {
|
||||
Type string `json:"type"`
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
|
||||
// wsConn verwaltet die WebSocket-Verbindung im Browser
|
||||
type wsConn struct {
|
||||
ws js.Value
|
||||
messagesChan chan []byte
|
||||
connected bool
|
||||
}
|
||||
|
||||
// connectToServer verbindet sich über WebSocket mit dem Gateway
|
||||
func (g *Game) connectToServer() {
|
||||
serverURL := "ws://localhost:8080/ws"
|
||||
log.Printf("🔌 Verbinde zu WebSocket-Gateway: %s", serverURL)
|
||||
|
||||
ws := js.Global().Get("WebSocket").New(serverURL)
|
||||
|
||||
conn := &wsConn{
|
||||
ws: ws,
|
||||
messagesChan: make(chan []byte, 100),
|
||||
connected: false,
|
||||
}
|
||||
|
||||
// OnOpen Handler
|
||||
ws.Call("addEventListener", "open", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
log.Println("✅ WebSocket verbunden!")
|
||||
conn.connected = true
|
||||
g.connected = true
|
||||
return nil
|
||||
}))
|
||||
|
||||
// OnMessage Handler
|
||||
ws.Call("addEventListener", "message", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
data := args[0].Get("data").String()
|
||||
|
||||
var msg WebSocketMessage
|
||||
if err := json.Unmarshal([]byte(data), &msg); err != nil {
|
||||
log.Printf("❌ Fehler beim Parsen der Nachricht: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
// An JavaScript senden
|
||||
g.sendLeaderboardToJS()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}))
|
||||
|
||||
// OnError Handler
|
||||
ws.Call("addEventListener", "error", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
log.Println("❌ WebSocket Fehler!")
|
||||
conn.connected = false
|
||||
g.connected = false
|
||||
return nil
|
||||
}))
|
||||
|
||||
// OnClose Handler
|
||||
ws.Call("addEventListener", "close", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
log.Println("🔌 WebSocket geschlossen")
|
||||
conn.connected = false
|
||||
g.connected = false
|
||||
return nil
|
||||
}))
|
||||
|
||||
// Warte bis Verbindung hergestellt ist
|
||||
for i := 0; i < 50; i++ {
|
||||
if conn.connected {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
if !conn.connected {
|
||||
log.Println("❌ WebSocket-Verbindung Timeout")
|
||||
return
|
||||
}
|
||||
|
||||
// WebSocket-Wrapper speichern
|
||||
g.wsConn = conn
|
||||
|
||||
// JOIN senden
|
||||
joinMsg := WebSocketMessage{
|
||||
Type: "join",
|
||||
Payload: game.JoinRequest{
|
||||
Name: g.playerName,
|
||||
RoomID: g.roomID,
|
||||
GameMode: g.gameMode,
|
||||
IsHost: g.isHost,
|
||||
TeamName: g.teamName,
|
||||
},
|
||||
}
|
||||
g.sendWebSocketMessage(joinMsg)
|
||||
|
||||
log.Printf("➡️ JOIN gesendet über WebSocket: Name=%s, RoomID=%s", g.playerName, g.roomID)
|
||||
}
|
||||
|
||||
// sendWebSocketMessage sendet eine Nachricht über WebSocket
|
||||
func (g *Game) sendWebSocketMessage(msg WebSocketMessage) {
|
||||
if g.wsConn == nil || !g.wsConn.connected {
|
||||
log.Println("⚠️ WebSocket nicht verbunden")
|
||||
return
|
||||
}
|
||||
|
||||
data, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
log.Printf("❌ Fehler beim Marshallen: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
g.wsConn.ws.Call("send", string(data))
|
||||
}
|
||||
|
||||
// sendInput sendet einen Input über WebSocket
|
||||
func (g *Game) sendInput(input game.ClientInput) {
|
||||
msg := WebSocketMessage{
|
||||
Type: "input",
|
||||
Payload: input,
|
||||
}
|
||||
g.sendWebSocketMessage(msg)
|
||||
}
|
||||
|
||||
// startGame sendet den Start-Befehl über WebSocket
|
||||
func (g *Game) startGame() {
|
||||
msg := WebSocketMessage{
|
||||
Type: "start",
|
||||
Payload: game.StartRequest{
|
||||
RoomID: g.roomID,
|
||||
},
|
||||
}
|
||||
g.sendWebSocketMessage(msg)
|
||||
log.Printf("▶️ START gesendet über WebSocket: RoomID=%s", g.roomID)
|
||||
}
|
||||
|
||||
// connectForLeaderboard verbindet für Leaderboard-Abfrage
|
||||
func (g *Game) connectForLeaderboard() {
|
||||
if g.wsConn != nil && g.wsConn.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 das Leaderboard an
|
||||
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 den Score ans Leaderboard
|
||||
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)
|
||||
}
|
||||
|
||||
// Dummy-Funktionen für Kompatibilität mit anderen Teilen des Codes
|
||||
func (g *Game) sendJoinRequest() {
|
||||
// Wird in connectToServer aufgerufen
|
||||
}
|
||||
|
||||
func (g *Game) sendStartRequest() {
|
||||
g.startGame()
|
||||
}
|
||||
|
||||
func (g *Game) publishInput(input game.ClientInput) {
|
||||
g.sendInput(input)
|
||||
}
|
||||
Reference in New Issue
Block a user