package main import ( "encoding/json" "flag" "fmt" "log" "math" "net" "server/internal/db" "server/internal/net/packets" "sync" "time" ) type WorldServer struct { listener net.Listener database *db.Database serverID string clients map[int]*Client clientLock sync.RWMutex world *packets.WorldConfig heightmap [][]float32 ticker *time.Ticker timeOfDay float32 } type Client struct { ID int PlayerID int64 Username string Conn net.Conn Encoder *json.Encoder Decoder *json.Decoder Position packets.Vec3 Rotation packets.Vec2 Velocity packets.Vec3 LastUpdate time.Time } type AuthRequest struct { Type string `json:"type"` Token string `json:"token"` } type AuthResponse struct { Type string `json:"type"` Success bool `json:"success"` Message string `json:"message,omitempty"` } func main() { var ( port = flag.String("port", "8082", "World server port") dbDSN = flag.String("db", "user:password@tcp(localhost:3306)/game", "Database DSN") ) flag.Parse() database, err := db.New(*dbDSN) if err != nil { log.Fatalf("Failed to connect to database: %v", err) } defer database.Close() heightmap := generateDefaultHeightmap(257) worldConfig := packets.NewWorldConfig(heightmap, 1.0) server := &WorldServer{ database: database, serverID: generateServerID(), clients: make(map[int]*Client), world: worldConfig, heightmap: heightmap, timeOfDay: 12.0, } listener, err := net.Listen("tcp", ":"+*port) if err != nil { log.Fatalf("Failed to start world server: %v", err) } server.listener = listener log.Printf("World server started on port %s (ID: %s)", *port, server.serverID) log.Printf("World bounds: Min(%v), Max(%v)", server.world.MinBounds, server.world.MaxBounds) go server.gameLoop() go server.updateTimeOfDay() for { conn, err := listener.Accept() if err != nil { log.Printf("Accept error: %v", err) continue } go server.handleConnection(conn) } } func (s *WorldServer) handleConnection(conn net.Conn) { defer conn.Close() decoder := json.NewDecoder(conn) encoder := json.NewEncoder(conn) var authReq AuthRequest if err := decoder.Decode(&authReq); err != nil || authReq.Type != "auth" { encoder.Encode(AuthResponse{ Type: "authResponse", Success: false, Message: "Authentication required", }) return } session, err := s.database.ValidateSession(authReq.Token) if err != nil { encoder.Encode(AuthResponse{ Type: "authResponse", Success: false, Message: "Invalid or expired session", }) return } player, err := s.database.GetPlayerByID(session.PlayerID) if err != nil { encoder.Encode(AuthResponse{ Type: "authResponse", Success: false, Message: "Player not found", }) return } encoder.Encode(AuthResponse{ Type: "authResponse", Success: true, }) position, _ := s.database.GetPlayerPosition(player.ID) client := &Client{ ID: s.getNextClientID(), PlayerID: player.ID, Username: player.Username, Conn: conn, Encoder: encoder, Decoder: decoder, Position: packets.Vec3{X: position.X, Y: position.Y, Z: position.Z}, Rotation: packets.Vec2{X: position.Yaw, Y: position.Pitch}, LastUpdate: time.Now(), } s.addClient(client) defer s.removeClient(client) s.sendInitialState(client) s.broadcastPlayerJoined(client) for { var msg json.RawMessage if err := decoder.Decode(&msg); err != nil { break } var baseMsg struct { Type string `json:"type"` } if err := json.Unmarshal(msg, &baseMsg); err != nil { continue } switch baseMsg.Type { case "movement": var moveMsg packets.MovementMessage if err := json.Unmarshal(msg, &moveMsg); err != nil { continue } s.handleMovement(client, moveMsg) case "chat": var chatMsg packets.ChatMessage if err := json.Unmarshal(msg, &chatMsg); err != nil { continue } s.handleChat(client, chatMsg) } } s.database.SavePlayerPosition(client.PlayerID, &db.Position{ X: client.Position.X, Y: client.Position.Y, Z: client.Position.Z, Yaw: client.Rotation.X, Pitch: client.Rotation.Y, World: "main", }) } func (s *WorldServer) handleMovement(client *Client, msg packets.MovementMessage) { newPos := packets.Vec3{X: msg.X, Y: msg.Y, Z: msg.Z} if !s.world.IsInBounds(newPos) { newPos = s.world.ClampPosition(newPos) client.Encoder.Encode(packets.PositionCorrectionMessage{ Type: "positionCorrection", X: newPos.X, Y: newPos.Y, Z: newPos.Z, }) } terrainHeight := s.world.GetHeightAt(s.heightmap, newPos.X, newPos.Z) if newPos.Y < terrainHeight { newPos.Y = terrainHeight client.Encoder.Encode(packets.PositionCorrectionMessage{ Type: "positionCorrection", X: newPos.X, Y: newPos.Y, Z: newPos.Z, }) } client.Position = newPos client.Rotation = packets.Vec2{X: msg.Yaw, Y: msg.Pitch} client.Velocity = packets.Vec3{X: msg.VelX, Y: msg.VelY, Z: msg.VelZ} client.LastUpdate = time.Now() s.broadcastMovement(client) } func (s *WorldServer) handleChat(client *Client, msg packets.ChatMessage) { fullMsg := packets.ChatMessage{ Type: "chat", Username: client.Username, Message: msg.Message, } s.broadcast(fullMsg) } func (s *WorldServer) sendInitialState(client *Client) { client.Encoder.Encode(packets.InitMessage{ Type: "init", PlayerID: client.ID, X: client.Position.X, Y: client.Position.Y, Z: client.Position.Z, Yaw: client.Rotation.X, Pitch: client.Rotation.Y, TimeOfDay: s.timeOfDay, }) s.clientLock.RLock() for _, other := range s.clients { if other.ID != client.ID { client.Encoder.Encode(packets.PlayerJoinedMessage{ Type: "playerJoined", PlayerID: other.ID, Username: other.Username, X: other.Position.X, Y: other.Position.Y, Z: other.Position.Z, }) } } s.clientLock.RUnlock() } func (s *WorldServer) broadcastPlayerJoined(client *Client) { msg := packets.PlayerJoinedMessage{ Type: "playerJoined", PlayerID: client.ID, Username: client.Username, X: client.Position.X, Y: client.Position.Y, Z: client.Position.Z, } s.broadcastExcept(msg, client.ID) } func (s *WorldServer) broadcastMovement(client *Client) { msg := packets.PlayerMovementMessage{ Type: "playerMovement", PlayerID: client.ID, X: client.Position.X, Y: client.Position.Y, Z: client.Position.Z, Yaw: client.Rotation.X, Pitch: client.Rotation.Y, VelX: client.Velocity.X, VelY: client.Velocity.Y, VelZ: client.Velocity.Z, } s.broadcastExcept(msg, client.ID) } func (s *WorldServer) broadcast(msg interface{}) { s.clientLock.RLock() defer s.clientLock.RUnlock() for _, client := range s.clients { client.Encoder.Encode(msg) } } func (s *WorldServer) broadcastExcept(msg interface{}, excludeID int) { s.clientLock.RLock() defer s.clientLock.RUnlock() for _, client := range s.clients { if client.ID != excludeID { client.Encoder.Encode(msg) } } } func (s *WorldServer) addClient(client *Client) { s.clientLock.Lock() s.clients[client.ID] = client s.clientLock.Unlock() log.Printf("Player %s (ID: %d) joined the world", client.Username, client.ID) } func (s *WorldServer) removeClient(client *Client) { s.clientLock.Lock() delete(s.clients, client.ID) s.clientLock.Unlock() s.broadcast(packets.PlayerLeftMessage{ Type: "playerLeft", PlayerID: client.ID, }) log.Printf("Player %s (ID: %d) left the world", client.Username, client.ID) } func (s *WorldServer) gameLoop() { ticker := time.NewTicker(50 * time.Millisecond) defer ticker.Stop() for range ticker.C { s.clientLock.RLock() for _, client := range s.clients { if time.Since(client.LastUpdate) > 30*time.Second { go func(c *Client) { c.Conn.Close() }(client) } } s.clientLock.RUnlock() } } func (s *WorldServer) updateTimeOfDay() { ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() for range ticker.C { s.timeOfDay += 0.1 if s.timeOfDay >= 24.0 { s.timeOfDay -= 24.0 } s.broadcast(packets.TimeUpdateMessage{ Type: "timeUpdate", TimeOfDay: s.timeOfDay, }) } } func (s *WorldServer) getNextClientID() int { s.clientLock.Lock() defer s.clientLock.Unlock() id := 1 for { if _, exists := s.clients[id]; !exists { return id } id++ } } func generateDefaultHeightmap(size int) [][]float32 { heightmap := make([][]float32, size) for i := range heightmap { heightmap[i] = make([]float32, size) for j := range heightmap[i] { x := float64(j-size/2) / float64(size) * 10 z := float64(i-size/2) / float64(size) * 10 heightmap[i][j] = float32(10 * (math.Sin(x) + math.Cos(z))) } } return heightmap } func generateServerID() string { return fmt.Sprintf("world-%d", time.Now().Unix()) }