Private
Public Access
1
0
Files
EscapeFromTeacher/cmd/server/websocket_gateway.go
Sebastian Unterschütz 4be6cc791e
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 8m32s
- Refactor dirt and stone generation to optimize visible depth and adjust randomization.
- Remove unused `StartWebSocketGateway` function from `websocket_gateway.go`.
- Add security checks to track player-room mapping, enforce valid input, and prevent ID spoofing in `gateway.go`.
- Refactor touch control logic to dynamically position joystick and buttons above gameplay floor.
- Introduce dynamic floor Y-coordinate calculation (`GetFloorYFromHeight`) for better scaling across different screen sizes.
- Adjust rendering logic to align assets, particles, and debug visuals with dynamic screen height transformations.
- Update canvas CSS to support fullscreen scaling without center alignment.
2026-01-09 21:34:24 +01:00

251 lines
6.5 KiB
Go

package main
import (
"encoding/json"
"log"
"net/http"
"sync"
"github.com/gorilla/websocket"
"github.com/nats-io/nats.go"
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // Erlaube alle Origins für Development
},
}
// WebSocketMessage ist das allgemeine Format für WebSocket-Nachrichten
type WebSocketMessage struct {
Type string `json:"type"` // "join", "input", "start", "leaderboard_request"
Payload json.RawMessage `json:"payload"` // Beliebige JSON-Daten
}
// WebSocketClient repräsentiert einen verbundenen WebSocket-Client
type WebSocketClient struct {
conn *websocket.Conn
natsConn *nats.EncodedConn
playerID string
playerCode string
roomID string
send chan []byte
mutex sync.Mutex
subUpdates *nats.Subscription
subScoreResp *nats.Subscription
}
// handleWebSocket verwaltet eine WebSocket-Verbindung
func handleWebSocket(w http.ResponseWriter, r *http.Request, ec *nats.EncodedConn) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("❌ WebSocket Upgrade Fehler: %v", err)
return
}
client := &WebSocketClient{
conn: conn,
natsConn: ec,
send: make(chan []byte, 256),
}
log.Printf("🔌 Neuer WebSocket-Client verbunden: %s", conn.RemoteAddr())
// Goroutinen für Lesen und Schreiben starten
go client.writePump()
go client.readPump()
}
// readPump liest Nachrichten vom WebSocket-Client
func (c *WebSocketClient) readPump() {
defer func() {
if c.subUpdates != nil {
c.subUpdates.Unsubscribe()
}
if c.subScoreResp != nil {
c.subScoreResp.Unsubscribe()
}
c.conn.Close()
log.Printf("🔌 WebSocket-Client getrennt: %s", c.conn.RemoteAddr())
}()
for {
var msg WebSocketMessage
err := c.conn.ReadJSON(&msg)
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("⚠️ WebSocket Fehler: %v", err)
}
break
}
// Nachricht basierend auf Typ verarbeiten
c.handleMessage(msg)
}
}
// writePump sendet Nachrichten zum WebSocket-Client
func (c *WebSocketClient) writePump() {
defer c.conn.Close()
for {
message, ok := <-c.send
if !ok {
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
err := c.conn.WriteMessage(websocket.TextMessage, message)
if err != nil {
log.Printf("⚠️ Fehler beim Senden: %v", err)
return
}
}
}
// handleMessage verarbeitet eingehende Nachrichten vom Client
func (c *WebSocketClient) handleMessage(msg WebSocketMessage) {
switch msg.Type {
case "join":
var req game.JoinRequest
if err := json.Unmarshal(msg.Payload, &req); err != nil {
log.Printf("❌ Join-Payload ungültig: %v", err)
return
}
c.playerID = req.Name
c.roomID = req.RoomID
if c.roomID == "" {
c.roomID = "lobby"
}
log.Printf("📥 WebSocket JOIN: Name=%s, RoomID=%s", req.Name, req.RoomID)
// An NATS weiterleiten
c.natsConn.Publish("game.join", &req)
// Auf Game-Updates für diesen Raum subscriben
roomChannel := "game.update." + c.roomID
sub, err := c.natsConn.Subscribe(roomChannel, func(state *game.GameState) {
// GameState an WebSocket-Client senden
data, _ := json.Marshal(map[string]interface{}{
"type": "game_update",
"payload": state,
})
select {
case c.send <- data:
default:
log.Printf("⚠️ Send channel voll, Nachricht verworfen")
}
})
if err != nil {
log.Printf("❌ Fehler beim Subscribe auf %s: %v", roomChannel, err)
} else {
c.subUpdates = sub
log.Printf("👂 WebSocket-Client lauscht auf %s", roomChannel)
}
case "input":
var input game.ClientInput
if err := json.Unmarshal(msg.Payload, &input); err != nil {
log.Printf("❌ Input-Payload ungültig: %v", err)
return
}
// PlayerID setzen falls nicht vorhanden
if input.PlayerID == "" {
input.PlayerID = c.playerID
}
// An NATS weiterleiten
c.natsConn.Publish("game.input", &input)
case "start":
var req game.StartRequest
if err := json.Unmarshal(msg.Payload, &req); err != nil {
log.Printf("❌ Start-Payload ungültig: %v", err)
return
}
log.Printf("▶️ WebSocket START: RoomID=%s", req.RoomID)
c.natsConn.Publish("game.start", &req)
case "leaderboard_request":
var req game.LeaderboardRequest
if err := json.Unmarshal(msg.Payload, &req); err != nil {
log.Printf("❌ Leaderboard-Request ungültig: %v", err)
return
}
log.Printf("🏆 WebSocket Leaderboard-Request: Mode=%s", req.Mode)
// Auf Leaderboard-Response subscriben (einmalig)
responseChannel := "leaderboard.response." + c.playerID
sub, _ := c.natsConn.Subscribe(responseChannel, func(resp *game.LeaderboardResponse) {
data, _ := json.Marshal(map[string]interface{}{
"type": "leaderboard_response",
"payload": resp,
})
select {
case c.send <- data:
default:
}
})
// Nach 5 Sekunden unsubscriben
go func() {
<-make(chan struct{})
sub.Unsubscribe()
}()
// Request mit ResponseChannel an NATS senden
req.ResponseChannel = responseChannel
c.natsConn.Publish("leaderboard.request", &req)
case "score_submit":
var submit game.ScoreSubmission
if err := json.Unmarshal(msg.Payload, &submit); err != nil {
log.Printf("❌ Score-Submit ungültig: %v", err)
return
}
log.Printf("📊 WebSocket Score-Submit: Player=%s, Score=%d", submit.PlayerCode, submit.Score)
// Speichere PlayerCode und subscribe auf Response-Channel
if c.playerCode == "" && submit.PlayerCode != "" {
c.playerCode = submit.PlayerCode
// Subscribe auf Score-Response für diesen Spieler
responseChannel := "score.response." + submit.PlayerCode
sub, err := c.natsConn.Subscribe(responseChannel, func(resp *game.ScoreSubmissionResponse) {
// ScoreSubmissionResponse an WebSocket-Client senden
data, _ := json.Marshal(map[string]interface{}{
"type": "score_response",
"payload": resp,
})
select {
case c.send <- data:
log.Printf("📤 Proof-Code an Client gesendet: %s", resp.ProofCode)
default:
log.Printf("⚠️ Send channel voll, Proof-Code verworfen")
}
})
if err != nil {
log.Printf("❌ Fehler beim Subscribe auf %s: %v", responseChannel, err)
} else {
c.subScoreResp = sub
log.Printf("👂 WebSocket-Client lauscht auf Score-Responses: %s", responseChannel)
}
}
c.natsConn.Publish("score.submit", &submit)
default:
log.Printf("⚠️ Unbekannter Nachrichtentyp: %s", msg.Type)
}
}