All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 2m27s
296 lines
6.6 KiB
Go
296 lines
6.6 KiB
Go
//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,
|
|
PlayerCode: g.playerCode,
|
|
},
|
|
}
|
|
g.sendWebSocketMessage(msg)
|
|
log.Printf("➡️ JOIN gesendet über WebSocket: Name=%s, RoomID=%s, PlayerCode=%s", g.playerName, g.roomID, g.playerCode)
|
|
}
|
|
|
|
// sendStartRequest sendet Start-Request über WebSocket
|
|
func (g *Game) sendStartRequest() {
|
|
myID := g.getMyPlayerID()
|
|
msg := WebSocketMessage{
|
|
Type: "start",
|
|
Payload: game.StartRequest{
|
|
RoomID: g.roomID,
|
|
PlayerID: myID,
|
|
},
|
|
}
|
|
g.sendWebSocketMessage(msg)
|
|
log.Printf("▶️ START gesendet über WebSocket: RoomID=%s, PlayerID=%s", g.roomID, myID)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// Verwende Team-Name für Coop-Mode, sonst Player-Name
|
|
displayName := name
|
|
teamName := ""
|
|
playerCodeToUse := g.playerCode
|
|
|
|
if g.gameMode == "coop" {
|
|
g.stateMutex.Lock()
|
|
teamName = g.gameState.TeamName
|
|
hostPlayerCode := g.gameState.HostPlayerCode
|
|
g.stateMutex.Unlock()
|
|
|
|
if teamName != "" {
|
|
displayName = teamName
|
|
}
|
|
|
|
// In Coop: Verwende Host's PlayerCode für Score
|
|
if hostPlayerCode != "" {
|
|
playerCodeToUse = hostPlayerCode
|
|
log.Printf("🔑 Coop-Mode: Verwende Host PlayerCode für Score-Submission")
|
|
}
|
|
}
|
|
|
|
msg := WebSocketMessage{
|
|
Type: "score_submit",
|
|
Payload: game.ScoreSubmission{
|
|
PlayerName: displayName,
|
|
PlayerCode: playerCodeToUse,
|
|
Name: displayName, // Für Kompatibilität
|
|
Score: score,
|
|
Mode: g.gameMode,
|
|
TeamName: teamName, // Team-Name für Coop
|
|
},
|
|
}
|
|
g.sendWebSocketMessage(msg)
|
|
|
|
g.scoreSubmitted = true
|
|
log.Printf("📊 Score submitted: %s = %d (PlayerCode: %s, TeamName: %s)", displayName, score, playerCodeToUse, teamName)
|
|
}
|