From c05a27a621e675d6f1a1f409692c575ca5f20c41 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Mon, 8 Sep 2025 14:31:14 -0500 Subject: [PATCH] split networking on server --- assets/heightmap.bin | Bin 40004 -> 40004 bytes assets/heightmap.png | Bin 4639 -> 4624 bytes server/main.go | 511 +++++++++--------------------------------- server/net/packets.go | 144 ++++++++++++ server/net/server.go | 353 +++++++++++++++++++++++++++++ server/players.json | 2 +- 6 files changed, 599 insertions(+), 411 deletions(-) create mode 100644 server/net/packets.go create mode 100644 server/net/server.go diff --git a/assets/heightmap.bin b/assets/heightmap.bin index e77e467caa4d71be1eb54050b098e14628ffe715..08ce812e6b1c5687ed4303586f70ad97dbfae48e 100644 GIT binary patch literal 40004 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Ð*PA0ew1jrGNbbMV`%1TJ@Ss#ran0x$+EJA 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%0Dk`CpnLKw>Dre}0-6c&-`ywi_sf>}=P5`Q zco6Z%^ilL_r4POGIFI&jzD-w$^PY1Yqq(1M(3ILHTD<2THD6Xudn`qIJGq>;O=zGo zy3I7P@+J+PpH8Fs9Y~t}dRkyShPI!jV(aT8;=FWSQATCHp|$KCiH74|;wyiRjGpkE zEY}*03(njrV`GKhlbi77EcXH1Ibp@!W3U_=1n2N56sadedr3OFI`W`5rwqC4>+pEs z1H}G(3jeql&`s&Y#&7bH zc@CUk48=VqJnzPkmU0uVD;vc=fkFCaoTV;$c-J)**njkFh7wL*|o`Lk}cN&3uh4 z1LDN)+=bM?DwM`8%caI&^XSg@bgDTai|TzWpxe|+sprT%>gslxnvc$;g%=Cx-T}ol zMCTHH%Y8##%z`e?e<`|KW=Y3LM@URQ&y+}>DogZ(&r5n1EhaUCFB6|@ZREo`ISj8J z2SL#o;e_7_@*L)VoS%602~wxN z!bZ*;8K2b+*9^`a3E(^|lb>i@^b^0P|Aabzp@wtC;^+56ZU1Lj^RBw?y7!2^`vHpc zDKe`mZY;Tu{QMg1tZqh4#xoS(euGhuzaT5+7o0ikPy8h-lp@_E8GLesd*Z z&ksdm?OFaz+GGTi?>}%|`76SXb;G9oC7w67!HDlUH4kbrrRh56^%X-QIR_u|Q?T1T z2G;%I{C+qP=7GoIXUe-->Z{ROZh(`khNId35h=FICHftSq;TsQ67Id2ST3QGw*^TH zKPcCVYiImLH4FXevea{QRSX~cFFa4Ros;Mi?{qr8EuG$RPo-ry5^0n|0>3W~rvnVU z=)v@*)OUsg^?9Z#hJJLBME-g!$>m+e=#X4VQN0YQeBwcz$8%pmhR{3C7_ zizpAEu|%ueZ|HzGQzkY^1?qO6$P&?3c`AKd7=4F&XhT$Bn&TB z5p1ri2$uJigorW);b(`OP{aO1iTzhB7CD#HfHP(M?jt{^4!bv1VAWkIzrWAt?|d2P z7!Z%t@u4u@b^`AT4kOag9;>GC8Jg29wEjB;_8rejy+su{-yxDal1!p>)t8))nodqw z#z~%<8cP?4G>BKP>e2rC4K!9`5A}TSOGU>pYP>a>rmxMUuUaqDX2S$}WNi>t&vK^0 zXXnr_-{ffch9q(1?_;Fn6VfEchQE=NX}^cyF(d!gsKjh~k+p*?2?oHnUp+U71YN4b%7Z>S+T4JAaDbADnK zHWOtvHR5aKE$QhVER`xni}h9tbk;OOnzPJ_I?WECdzBMtPiY2K8<$0Q-HE4~pM9y0 zr!l>2AWIWhWs9+04q~B^ymV?ylq9wBPjYb5GP16J8qto>AfrxgARA-y$fz?HZ+XEqLP_|?N=3-EQx^J56@(*~Y}bW&omOP5S~=Rn*%&BW}dEx<<%C%M?S=iprqeCR|Vm5u&mGxS)r0MmuB-dQoxy@2O{K! Ng_~rB!UZzI{{VWGXy5<< literal 40004 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 diff --git a/assets/heightmap.png b/assets/heightmap.png index 8881242325a0f21678a073c09d08c81d0006ce1f..70537e6b2e9feaa28403dffc54fb01e3416b7650 100644 GIT binary patch literal 4624 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 V$= len(s.heightmap) || z0 < 0 || z1 >= len(s.heightmap) { - return 0 - } - - // Get fractional parts - tx := fx - float32(x0) - tz := fz - float32(z0) - - // Bilinear interpolation - h00 := s.heightmap[z0][x0] - h10 := s.heightmap[z0][x1] - h01 := s.heightmap[z1][x0] - h11 := s.heightmap[z1][x1] - - h0 := h00*(1-tx) + h10*tx - h1 := h01*(1-tx) + h11*tx - - return h0*(1-tz) + h1*tz -} - -func (s *GameServer) loadPlayerPositions() { - data, err := os.ReadFile("players.json") +// loadHeightmapBinary loads a heightmap from a binary file +func loadHeightmapBinary(filename string) ([][]float32, error) { + file, err := os.Open(filename) if err != nil { - return + return nil, fmt.Errorf("failed to open file %s: %w", filename, err) + } + defer file.Close() + + var size int32 + if err := binary.Read(file, binary.LittleEndian, &size); err != nil { + return nil, fmt.Errorf("failed to read size: %w", err) } - var savedPlayers map[uint32]Vec3 - json.Unmarshal(data, &savedPlayers) - - for id, pos := range savedPlayers { - if id > s.nextID { - s.nextID = id - } - s.players[id] = &Player{ - ID: id, - Position: pos, - LastSeen: time.Now(), - } - } -} - -func (s *GameServer) sendPlayerList(addr *net.UDPAddr, players []*Player) { - if len(players) == 0 { - return - } - - msg := make([]byte, 1024) - msg[0] = MSG_PLAYER_LIST - msg[1] = uint8(len(players)) - - offset := 2 - for _, p := range players { - binary.LittleEndian.PutUint32(msg[offset:], p.ID) - binary.LittleEndian.PutUint32(msg[offset+4:], math.Float32bits(p.Position.X)) - binary.LittleEndian.PutUint32(msg[offset+8:], math.Float32bits(p.Position.Y)) - binary.LittleEndian.PutUint32(msg[offset+12:], math.Float32bits(p.Position.Z)) - - colorBytes := []byte(p.Color) - msg[offset+16] = uint8(len(colorBytes)) - copy(msg[offset+17:], colorBytes) - - offset += 17 + len(colorBytes) - if offset > 1000 { - break // Prevent overflow - } - } - - s.conn.WriteToUDP(msg[:offset], addr) -} - -func (s *GameServer) broadcastPlayerJoined(newPlayer *Player) { - colorBytes := []byte(newPlayer.Color) - msg := make([]byte, 18+len(colorBytes)) - msg[0] = MSG_PLAYER_JOINED - binary.LittleEndian.PutUint32(msg[1:5], newPlayer.ID) - binary.LittleEndian.PutUint32(msg[5:9], math.Float32bits(newPlayer.Position.X)) - binary.LittleEndian.PutUint32(msg[9:13], math.Float32bits(newPlayer.Position.Y)) - binary.LittleEndian.PutUint32(msg[13:17], math.Float32bits(newPlayer.Position.Z)) - msg[17] = uint8(len(colorBytes)) - copy(msg[18:], colorBytes) - - s.mutex.RLock() - for _, p := range s.players { - if p.ID != newPlayer.ID && p.Address != nil { - s.conn.WriteToUDP(msg, p.Address) - } - } - s.mutex.RUnlock() -} - -func (s *GameServer) broadcastPlayerLeft(playerID uint32) { - msg := make([]byte, 5) - msg[0] = MSG_PLAYER_LEFT - binary.LittleEndian.PutUint32(msg[1:5], playerID) - - s.mutex.RLock() - for _, p := range s.players { - if p.ID != playerID && p.Address != nil { - s.conn.WriteToUDP(msg, p.Address) - } - } - s.mutex.RUnlock() -} - -func (s *GameServer) broadcastUpdate(player *Player) { - msg := make([]byte, 17) - msg[0] = MSG_UPDATE - binary.LittleEndian.PutUint32(msg[1:5], player.ID) - binary.LittleEndian.PutUint32(msg[5:9], math.Float32bits(player.Position.X)) - binary.LittleEndian.PutUint32(msg[9:13], math.Float32bits(player.Position.Y)) - binary.LittleEndian.PutUint32(msg[13:17], math.Float32bits(player.Position.Z)) - - s.mutex.RLock() - for _, p := range s.players { - if p.Address != nil { - s.conn.WriteToUDP(msg, p.Address) - } - } - s.mutex.RUnlock() -} - -func (s *GameServer) handleColorChange(data []byte, addr *net.UDPAddr) { - if len(data) < 6 { - return - } - - playerID := binary.LittleEndian.Uint32(data[1:5]) - colorLen := data[5] - - if len(data) < 6+int(colorLen) { - return - } - - newColor := string(data[6 : 6+colorLen]) - - // Validate color - validColors := []string{"red", "green", "orange", "purple", "white"} - isValid := slices.Contains(validColors, newColor) - - if !isValid { - return - } - - s.mutex.Lock() - player, exists := s.players[playerID] - if !exists { - s.mutex.Unlock() - return - } - - player.Color = newColor - s.mutex.Unlock() - - // Broadcast color change to all players - s.broadcastColorChanged(playerID, newColor) - - fmt.Printf("Player %d changed color to %s\n", playerID, newColor) -} - -func (s *GameServer) broadcastColorChanged(playerID uint32, color string) { - colorBytes := []byte(color) - msg := make([]byte, 6+len(colorBytes)) - msg[0] = MSG_COLOR_CHANGED - binary.LittleEndian.PutUint32(msg[1:5], playerID) - msg[5] = uint8(len(colorBytes)) - copy(msg[6:], colorBytes) - - s.mutex.RLock() - for _, p := range s.players { - if p.Address != nil { - s.conn.WriteToUDP(msg, p.Address) - } - } - s.mutex.RUnlock() -} - -func (s *GameServer) savePlayerPositions() { - s.mutex.RLock() - savedPlayers := make(map[uint32]Vec3) - for id, player := range s.players { - savedPlayers[id] = player.Position - } - s.mutex.RUnlock() - - data, _ := json.Marshal(savedPlayers) - os.WriteFile("players.json", data, 0644) -} - -func (s *GameServer) handleLogin(addr *net.UDPAddr) { - s.mutex.Lock() - s.nextID++ - playerID := s.nextID - - // Assign color based on player ID to ensure variety - colors := []string{"red", "green", "orange", "purple", "white"} - // Cycle through colors based on player ID - colorIndex := (playerID - 1) % uint32(len(colors)) - color := colors[colorIndex] - - // Spawn at random position on heightmap - x := rand.Float32() * WORLD_SIZE - WORLD_SIZE/2 - z := rand.Float32() * WORLD_SIZE - WORLD_SIZE/2 - y := s.getHeightAt(x, z) + PLAYER_HEIGHT - - player := &Player{ - ID: playerID, - Position: Vec3{x, y, z}, - Color: color, - Address: addr, - LastSeen: time.Now(), - } - - // Send existing players to new player - existingPlayers := make([]*Player, 0) - for _, p := range s.players { - if p.ID != playerID { - existingPlayers = append(existingPlayers, p) - } - } - - s.players[playerID] = player - s.mutex.Unlock() - - // Send spawn message with color - colorBytes := []byte(color) - msg := make([]byte, 18+len(colorBytes)) - msg[0] = MSG_SPAWN - binary.LittleEndian.PutUint32(msg[1:5], playerID) - binary.LittleEndian.PutUint32(msg[5:9], math.Float32bits(player.Position.X)) - binary.LittleEndian.PutUint32(msg[9:13], math.Float32bits(player.Position.Y)) - binary.LittleEndian.PutUint32(msg[13:17], math.Float32bits(player.Position.Z)) - msg[17] = uint8(len(colorBytes)) - copy(msg[18:], colorBytes) - - s.conn.WriteToUDP(msg, addr) - - // Send player list to new player - s.sendPlayerList(addr, existingPlayers) - - // Notify other players about new player - s.broadcastPlayerJoined(player) - - fmt.Printf("Player %d logged in at (%.2f, %.2f, %.2f) with color %s\n", playerID, x, y, z, color) - - s.savePlayerPositions() -} - -func (s *GameServer) handleMove(data []byte, addr *net.UDPAddr) { - if len(data) < 17 { - return - } - - playerID := binary.LittleEndian.Uint32(data[1:5]) - dx := math.Float32frombits(binary.LittleEndian.Uint32(data[5:9])) - // dy := math.Float32frombits(binary.LittleEndian.Uint32(data[9:13])) // Not used - Y position is determined by terrain height - dz := math.Float32frombits(binary.LittleEndian.Uint32(data[13:17])) - - s.mutex.Lock() - player, exists := s.players[playerID] - if !exists { - s.mutex.Unlock() - return - } - - // Server-authoritative movement - server decides the actual speed - // dx/dz from client are just normalized direction vectors - deltaTime := float32(0.016) // Assume 60fps for now - newX := player.Position.X + dx * MOVE_SPEED * deltaTime - newZ := player.Position.Z + dz * MOVE_SPEED * deltaTime - - // Clamp to world bounds - newX = float32(math.Max(float64(-WORLD_SIZE/2), math.Min(float64(WORLD_SIZE/2), float64(newX)))) - newZ = float32(math.Max(float64(-WORLD_SIZE/2), math.Min(float64(WORLD_SIZE/2), float64(newZ)))) - - // Set Y to terrain height with some smoothing - targetY := s.getHeightAt(newX, newZ) + PLAYER_HEIGHT - // Smooth the Y transition - smoothFactor := float32(0.15) // How quickly to adapt to new height - newY := player.Position.Y + (targetY - player.Position.Y) * smoothFactor - - player.Position.X = newX - player.Position.Y = newY - player.Position.Z = newZ - player.LastSeen = time.Now() - - s.mutex.Unlock() - - // Broadcast position update to all players - s.broadcastUpdate(player) -} - -func (s *GameServer) run() { - buffer := make([]byte, 1024) - - // Periodic save - go func() { - ticker := time.NewTicker(10 * time.Second) - for range ticker.C { - s.savePlayerPositions() - } - }() - - for { - n, addr, err := s.conn.ReadFromUDP(buffer) - if err != nil { - continue - } - - if n < 1 { - continue - } - - msgType := buffer[0] - - switch msgType { - case MSG_LOGIN: - s.handleLogin(addr) - case MSG_MOVE: - s.handleMove(buffer[:n], addr) - case MSG_CHANGE_COLOR: - s.handleColorChange(buffer[:n], addr) + heightmap := make([][]float32, size) + for y := range heightmap { + heightmap[y] = make([]float32, size) + for x := range heightmap[y] { + if err := binary.Read(file, binary.LittleEndian, &heightmap[y][x]); err != nil { + return nil, fmt.Errorf("failed to read heightmap data: %w", err) + } } } + return heightmap, nil } func main() { - // Generate and save heightmap - fmt.Println("Generating heightmap...") - heightmap := generateHeightmap(WORLD_SIZE) - saveHeightmapPNG(heightmap, "../assets/heightmap.png") - saveHeightmapBinary(heightmap, "../assets/heightmap.bin") + // Parse command-line flags + var ( + port = flag.String("port", "9999", "UDP port to listen on") + worldSize = flag.Int("size", WorldSize, "World size for heightmap generation") + skipGen = flag.Bool("skip-gen", false, "Skip heightmap generation") + assetsPath = flag.String("assets", "../assets", "Path to assets directory") + ) + flag.Parse() - // Start UDP server - addr, _ := net.ResolveUDPAddr("udp", ":9999") - conn, err := net.ListenUDP("udp", addr) + // Setup logging + log.SetPrefix("[GameServer] ") + log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds) + + var heightmap [][]float32 + + if !*skipGen { + // Generate and save heightmap + log.Printf("Generating %dx%d heightmap...", *worldSize, *worldSize) + heightmap = generateHeightmap(*worldSize) + + pngPath := fmt.Sprintf("%s/heightmap.png", *assetsPath) + if err := saveHeightmapPNG(heightmap, pngPath); err != nil { + log.Printf("Warning: Failed to save PNG heightmap: %v", err) + } else { + log.Printf("Saved heightmap PNG to %s", pngPath) + } + + binPath := fmt.Sprintf("%s/heightmap.bin", *assetsPath) + if err := saveHeightmapBinary(heightmap, binPath); err != nil { + log.Fatalf("Failed to save binary heightmap: %v", err) + } + log.Printf("Saved heightmap binary to %s", binPath) + } else { + // Load existing heightmap + binPath := fmt.Sprintf("%s/heightmap.bin", *assetsPath) + log.Printf("Loading existing heightmap from %s", binPath) + var err error + heightmap, err = loadHeightmapBinary(binPath) + if err != nil { + log.Fatalf("Failed to load heightmap: %v", err) + } + } + + server, err := net.NewServer(*port, heightmap) if err != nil { - panic(err) - } - defer conn.Close() - - server := &GameServer{ - conn: conn, - players: make(map[uint32]*Player), - heightmap: heightmap, - nextID: 0, + log.Fatalf("Failed to create server: %v", err) } - server.loadPlayerPositions() - - fmt.Println("Server running on :9999") - server.run() + log.Printf("Starting game server on port %s", *port) + if err := server.Run(); err != nil { + log.Fatalf("Server failed: %v", err) + } } diff --git a/server/net/packets.go b/server/net/packets.go new file mode 100644 index 0000000..4268f7e --- /dev/null +++ b/server/net/packets.go @@ -0,0 +1,144 @@ +package net + +import ( + "encoding/binary" + "math" +) + +// Message type constants +const ( + MSG_LOGIN = 0x01 + MSG_POSITION = 0x02 + MSG_SPAWN = 0x03 + MSG_MOVE = 0x04 + MSG_UPDATE = 0x05 + MSG_PLAYER_JOINED = 0x06 + MSG_PLAYER_LEFT = 0x07 + MSG_PLAYER_LIST = 0x08 + MSG_CHANGE_COLOR = 0x09 + MSG_COLOR_CHANGED = 0x0A +) + +// Vec3 represents a 3D vector +type Vec3 struct { + X, Y, Z float32 +} + +// EncodeSpawnPacket creates a spawn packet +func EncodeSpawnPacket(playerID uint32, position Vec3, color string) []byte { + colorBytes := []byte(color) + msg := make([]byte, 18+len(colorBytes)) + msg[0] = MSG_SPAWN + binary.LittleEndian.PutUint32(msg[1:5], playerID) + binary.LittleEndian.PutUint32(msg[5:9], math.Float32bits(position.X)) + binary.LittleEndian.PutUint32(msg[9:13], math.Float32bits(position.Y)) + binary.LittleEndian.PutUint32(msg[13:17], math.Float32bits(position.Z)) + msg[17] = uint8(len(colorBytes)) + copy(msg[18:], colorBytes) + return msg +} + +// EncodeUpdatePacket creates an update packet +func EncodeUpdatePacket(playerID uint32, position Vec3) []byte { + msg := make([]byte, 17) + msg[0] = MSG_UPDATE + binary.LittleEndian.PutUint32(msg[1:5], playerID) + binary.LittleEndian.PutUint32(msg[5:9], math.Float32bits(position.X)) + binary.LittleEndian.PutUint32(msg[9:13], math.Float32bits(position.Y)) + binary.LittleEndian.PutUint32(msg[13:17], math.Float32bits(position.Z)) + return msg +} + +// EncodePlayerJoinedPacket creates a player joined packet +func EncodePlayerJoinedPacket(playerID uint32, position Vec3, color string) []byte { + colorBytes := []byte(color) + msg := make([]byte, 18+len(colorBytes)) + msg[0] = MSG_PLAYER_JOINED + binary.LittleEndian.PutUint32(msg[1:5], playerID) + binary.LittleEndian.PutUint32(msg[5:9], math.Float32bits(position.X)) + binary.LittleEndian.PutUint32(msg[9:13], math.Float32bits(position.Y)) + binary.LittleEndian.PutUint32(msg[13:17], math.Float32bits(position.Z)) + msg[17] = uint8(len(colorBytes)) + copy(msg[18:], colorBytes) + return msg +} + +// EncodePlayerLeftPacket creates a player left packet +func EncodePlayerLeftPacket(playerID uint32) []byte { + msg := make([]byte, 5) + msg[0] = MSG_PLAYER_LEFT + binary.LittleEndian.PutUint32(msg[1:5], playerID) + return msg +} + +// EncodeColorChangedPacket creates a color changed packet +func EncodeColorChangedPacket(playerID uint32, color string) []byte { + colorBytes := []byte(color) + msg := make([]byte, 6+len(colorBytes)) + msg[0] = MSG_COLOR_CHANGED + binary.LittleEndian.PutUint32(msg[1:5], playerID) + msg[5] = uint8(len(colorBytes)) + copy(msg[6:], colorBytes) + return msg +} + +// EncodePlayerListPacket creates a player list packet +func EncodePlayerListPacket(players []*Player) []byte { + if len(players) == 0 { + return []byte{MSG_PLAYER_LIST, 0} + } + + msg := make([]byte, 1024) + msg[0] = MSG_PLAYER_LIST + msg[1] = uint8(len(players)) + + offset := 2 + for _, p := range players { + binary.LittleEndian.PutUint32(msg[offset:], p.ID) + binary.LittleEndian.PutUint32(msg[offset+4:], math.Float32bits(p.Position.X)) + binary.LittleEndian.PutUint32(msg[offset+8:], math.Float32bits(p.Position.Y)) + binary.LittleEndian.PutUint32(msg[offset+12:], math.Float32bits(p.Position.Z)) + + colorBytes := []byte(p.Color) + msg[offset+16] = uint8(len(colorBytes)) + copy(msg[offset+17:], colorBytes) + + offset += 17 + len(colorBytes) + if offset > 1000 { + break // Prevent overflow + } + } + + return msg[:offset] +} + +// DecodeMovePacket decodes a move packet +func DecodeMovePacket(data []byte) (playerID uint32, delta Vec3, ok bool) { + if len(data) < 17 { + return 0, Vec3{}, false + } + + playerID = binary.LittleEndian.Uint32(data[1:5]) + delta.X = math.Float32frombits(binary.LittleEndian.Uint32(data[5:9])) + delta.Y = math.Float32frombits(binary.LittleEndian.Uint32(data[9:13])) + delta.Z = math.Float32frombits(binary.LittleEndian.Uint32(data[13:17])) + + return playerID, delta, true +} + +// DecodeColorChangePacket decodes a color change packet +func DecodeColorChangePacket(data []byte) (playerID uint32, color string, ok bool) { + if len(data) < 6 { + return 0, "", false + } + + playerID = binary.LittleEndian.Uint32(data[1:5]) + colorLen := data[5] + + if len(data) < 6+int(colorLen) { + return 0, "", false + } + + color = string(data[6 : 6+colorLen]) + return playerID, color, true +} \ No newline at end of file diff --git a/server/net/server.go b/server/net/server.go new file mode 100644 index 0000000..493c08f --- /dev/null +++ b/server/net/server.go @@ -0,0 +1,353 @@ +package net + +import ( + "slices" + "encoding/json" + "fmt" + "log" + "math" + "math/rand" + "net" + "os" + "sync" + "time" +) + +// Player represents a connected player +type Player struct { + ID uint32 + Position Vec3 + Velocity Vec3 + Color string + Address *net.UDPAddr + LastSeen time.Time +} + +// Server manages the game state and networking +type Server struct { + conn *net.UDPConn + players map[uint32]*Player + heightmap [][]float32 + mutex sync.RWMutex + nextID uint32 +} + +// NewServer creates a new game server +func NewServer(port string, heightmap [][]float32) (*Server, error) { + addr, err := net.ResolveUDPAddr("udp", ":"+port) + if err != nil { + return nil, fmt.Errorf("failed to resolve UDP address: %w", err) + } + + conn, err := net.ListenUDP("udp", addr) + if err != nil { + return nil, fmt.Errorf("failed to listen on UDP: %w", err) + } + + server := &Server{ + conn: conn, + players: make(map[uint32]*Player), + heightmap: heightmap, + nextID: 0, + } + + server.loadPlayerPositions() + + return server, nil +} + +// Run starts the server main loop +func (s *Server) Run() error { + defer s.conn.Close() + + // Start periodic save + go func() { + ticker := time.NewTicker(10 * time.Second) + defer ticker.Stop() + for range ticker.C { + s.savePlayerPositions() + } + }() + + // Start player timeout checker + go func() { + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + for range ticker.C { + s.checkTimeouts() + } + }() + + buffer := make([]byte, 1024) + log.Println("Server running...") + + for { + n, addr, err := s.conn.ReadFromUDP(buffer) + if err != nil { + log.Printf("Error reading UDP: %v", err) + continue + } + + if n < 1 { + continue + } + + msgType := buffer[0] + + switch msgType { + case MSG_LOGIN: + s.handleLogin(addr) + case MSG_MOVE: + s.handleMove(buffer[:n], addr) + case MSG_CHANGE_COLOR: + s.handleColorChange(buffer[:n], addr) + } + } +} + +func (s *Server) handleLogin(addr *net.UDPAddr) { + s.mutex.Lock() + s.nextID++ + playerID := s.nextID + + // Assign color based on player ID + colors := []string{"red", "green", "orange", "purple", "white"} + colorIndex := (playerID - 1) % uint32(len(colors)) + color := colors[colorIndex] + + // Spawn at random position on heightmap + x := rand.Float32()*100 - 50 + z := rand.Float32()*100 - 50 + y := s.getHeightAt(x, z) + 1.0 + + player := &Player{ + ID: playerID, + Position: Vec3{x, y, z}, + Color: color, + Address: addr, + LastSeen: time.Now(), + } + + // Send existing players to new player before adding them + existingPlayers := make([]*Player, 0) + for _, p := range s.players { + if p.ID != playerID { + existingPlayers = append(existingPlayers, p) + } + } + + s.players[playerID] = player + s.mutex.Unlock() + + // Send spawn message with color + spawnMsg := EncodeSpawnPacket(playerID, player.Position, color) + s.conn.WriteToUDP(spawnMsg, addr) + + // Send player list to new player + if len(existingPlayers) > 0 { + listMsg := EncodePlayerListPacket(existingPlayers) + s.conn.WriteToUDP(listMsg, addr) + } + + // Notify other players about new player + s.broadcastPlayerJoined(player) + + log.Printf("Player %d logged in at (%.2f, %.2f, %.2f) with color %s", + playerID, x, y, z, color) + + s.savePlayerPositions() +} + +func (s *Server) handleMove(data []byte, _ *net.UDPAddr) { + playerID, delta, ok := DecodeMovePacket(data) + if !ok { + return + } + + s.mutex.Lock() + player, exists := s.players[playerID] + if !exists { + s.mutex.Unlock() + return + } + + // Server-authoritative movement + deltaTime := float32(0.016) // 60fps + newX := player.Position.X + delta.X*15.0*deltaTime + newZ := player.Position.Z + delta.Z*15.0*deltaTime + + // Clamp to world bounds + newX = float32(math.Max(-50, math.Min(50, float64(newX)))) + newZ = float32(math.Max(-50, math.Min(50, float64(newZ)))) + + // Set Y to terrain height with smoothing + targetY := s.getHeightAt(newX, newZ) + 1.0 + smoothFactor := float32(0.15) + newY := player.Position.Y + (targetY-player.Position.Y)*smoothFactor + + player.Position.X = newX + player.Position.Y = newY + player.Position.Z = newZ + player.LastSeen = time.Now() + + s.mutex.Unlock() + + // Broadcast position update to all players + s.broadcastUpdate(player) +} + +func (s *Server) handleColorChange(data []byte, _ *net.UDPAddr) { + playerID, newColor, ok := DecodeColorChangePacket(data) + if !ok { + return + } + + // Validate color + validColors := []string{"red", "green", "orange", "purple", "white"} + isValid := slices.Contains(validColors, newColor) + + if !isValid { + return + } + + s.mutex.Lock() + player, exists := s.players[playerID] + if !exists { + s.mutex.Unlock() + return + } + + player.Color = newColor + s.mutex.Unlock() + + // Broadcast color change to all players + s.broadcastColorChanged(playerID, newColor) +} + +func (s *Server) getHeightAt(x, z float32) float32 { + // Convert world coords to heightmap coords with bilinear interpolation + size := float32(len(s.heightmap)) + fx := (x/100 + 0.5) * (size - 1) + fz := (z/100 + 0.5) * (size - 1) + + // Get integer coordinates + x0 := int(math.Floor(float64(fx))) + z0 := int(math.Floor(float64(fz))) + x1 := x0 + 1 + z1 := z0 + 1 + + // Clamp to bounds + if x0 < 0 || x1 >= len(s.heightmap) || z0 < 0 || z1 >= len(s.heightmap) { + return 0 + } + + // Get fractional parts + tx := fx - float32(x0) + tz := fz - float32(z0) + + // Bilinear interpolation + h00 := s.heightmap[z0][x0] + h10 := s.heightmap[z0][x1] + h01 := s.heightmap[z1][x0] + h11 := s.heightmap[z1][x1] + + h0 := h00*(1-tx) + h10*tx + h1 := h01*(1-tx) + h11*tx + + return h0*(1-tz) + h1*tz +} + +func (s *Server) broadcastUpdate(player *Player) { + msg := EncodeUpdatePacket(player.ID, player.Position) + + s.mutex.RLock() + for _, p := range s.players { + if p.Address != nil { + s.conn.WriteToUDP(msg, p.Address) + } + } + s.mutex.RUnlock() +} + +func (s *Server) broadcastPlayerJoined(newPlayer *Player) { + msg := EncodePlayerJoinedPacket(newPlayer.ID, newPlayer.Position, newPlayer.Color) + + s.mutex.RLock() + for _, p := range s.players { + if p.ID != newPlayer.ID && p.Address != nil { + s.conn.WriteToUDP(msg, p.Address) + } + } + s.mutex.RUnlock() +} + +func (s *Server) broadcastPlayerLeft(playerID uint32) { + msg := EncodePlayerLeftPacket(playerID) + + s.mutex.RLock() + for _, p := range s.players { + if p.ID != playerID && p.Address != nil { + s.conn.WriteToUDP(msg, p.Address) + } + } + s.mutex.RUnlock() +} + +func (s *Server) broadcastColorChanged(playerID uint32, color string) { + msg := EncodeColorChangedPacket(playerID, color) + + s.mutex.RLock() + for _, p := range s.players { + if p.Address != nil { + s.conn.WriteToUDP(msg, p.Address) + } + } + s.mutex.RUnlock() +} + +func (s *Server) checkTimeouts() { + s.mutex.Lock() + defer s.mutex.Unlock() + + now := time.Now() + for id, player := range s.players { + if now.Sub(player.LastSeen) > 30*time.Second { + delete(s.players, id) + go s.broadcastPlayerLeft(id) + log.Printf("Player %d timed out", id) + } + } +} + +func (s *Server) loadPlayerPositions() { + data, err := os.ReadFile("players.json") + if err != nil { + return + } + + var savedPlayers map[uint32]Vec3 + json.Unmarshal(data, &savedPlayers) + + for id, pos := range savedPlayers { + if id > s.nextID { + s.nextID = id + } + s.players[id] = &Player{ + ID: id, + Position: pos, + LastSeen: time.Now(), + } + } +} + +func (s *Server) savePlayerPositions() { + s.mutex.RLock() + savedPlayers := make(map[uint32]Vec3) + for id, player := range s.players { + savedPlayers[id] = player.Position + } + s.mutex.RUnlock() + + data, _ := json.Marshal(savedPlayers) + os.WriteFile("players.json", data, 0644) +} diff --git a/server/players.json b/server/players.json index a7c4f67..9159d25 100644 --- a/server/players.json +++ b/server/players.json @@ -1 +1 @@ -{"1":{"X":-2.5774379,"Y":0.3485479,"Z":3.2305741},"2":{"X":-1.6390398,"Y":0.5682664,"Z":1.0276936}} \ No newline at end of file +{"1":{"X":-13.046684,"Y":-0.008753866,"Z":15.9760895}} \ No newline at end of file