1
0
game/server/cmd/world/main.go

398 lines
8.8 KiB
Go

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())
}