diff --git a/client/main.cpp b/client/main.cpp index b70fd58..1eede90 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -228,8 +228,13 @@ private: return; } - // Update sky - sky->update(GetFrameTime()); + // Update sky with server time if connected, otherwise use local time + if (network.isConnected()) { + float serverTime = network.getServerTimeOfDay(); + sky->updateFromServerTime(serverTime); + } else { + sky->update(GetFrameTime()); + } // Time of day controls (for testing) if (IsKeyPressed(KEY_T)) { diff --git a/client/net/NetworkManager.cpp b/client/net/NetworkManager.cpp index fd1d27d..7ad136a 100644 --- a/client/net/NetworkManager.cpp +++ b/client/net/NetworkManager.cpp @@ -56,11 +56,24 @@ void NetworkManager::processMessage(const uint8_t* data, std::size_t size) { case MessageType::LoginResponse: handleLoginResponse(data, size); break; + case MessageType::TimeSync: + handleTimeSync(data, size); + break; default: break; } } +void NetworkManager::handleTimeSync(const uint8_t* data, std::size_t size) { + // Message format: [type(1)][timeOfDay(4)] + if (size < 5) return; + + float timeOfDay; + std::memcpy(&timeOfDay, &data[1], sizeof(timeOfDay)); + + serverTimeOfDay.store(timeOfDay); +} + void NetworkManager::handleSpawn(const uint8_t* data, std::size_t size) { // Message format: [type(1)][id(4)][x(4)][y(4)][z(4)][colorLen(1)][color(colorLen)] if (size < 18) return; diff --git a/client/net/NetworkManager.hpp b/client/net/NetworkManager.hpp index d91a60b..c85fe5a 100644 --- a/client/net/NetworkManager.hpp +++ b/client/net/NetworkManager.hpp @@ -25,7 +25,8 @@ enum class MessageType : uint8_t { ColorChanged = 0x0A, LoginResponse = 0x0B, Logout = 0x0C, - Heartbeat = 0x0D + Heartbeat = 0x0D, + TimeSync = 0x0E }; struct RemotePlayer { @@ -55,6 +56,7 @@ public: std::string getPlayerColor() const { return playerColor; } std::unordered_map getRemotePlayers(); + float getServerTimeOfDay() const { return serverTimeOfDay.load(); } // Available colors for cycling static const std::vector AVAILABLE_COLORS; @@ -76,6 +78,7 @@ private: std::mutex remotePlayersMutex; std::unordered_map remotePlayers; + std::atomic serverTimeOfDay{0.0f}; void startReceive(); void processMessage(const uint8_t* data, std::size_t size); @@ -86,4 +89,5 @@ private: void handlePlayerList(const uint8_t* data, std::size_t size); void handleColorChanged(const uint8_t* data, std::size_t size); void handleLoginResponse(const uint8_t* data, std::size_t size); + void handleTimeSync(const uint8_t* data, std::size_t size); }; diff --git a/client/sky/Sky.cpp b/client/sky/Sky.cpp index 07673f8..b10c7fc 100644 --- a/client/sky/Sky.cpp +++ b/client/sky/Sky.cpp @@ -75,6 +75,13 @@ void Sky::update(float) { moon.updatePosition(timeOfDay); } +void Sky::updateFromServerTime(float serverTimeOfDay) { + timeOfDay = serverTimeOfDay; + updateColors(); + sun.updatePosition(timeOfDay); + moon.updatePosition(timeOfDay); +} + void Sky::Sun::updatePosition(float timeOfDay) { // Convert time of day to angle (0.25 = sunrise at 6am, 0.75 = sunset at 6pm) // Adjust so that 0.25 (6am) = 0 degrees, 0.75 (6pm) = 180 degrees diff --git a/client/sky/Sky.hpp b/client/sky/Sky.hpp index b83219e..ed1f33b 100644 --- a/client/sky/Sky.hpp +++ b/client/sky/Sky.hpp @@ -57,6 +57,7 @@ public: ~Sky(); void update(float deltaTime); + void updateFromServerTime(float serverTimeOfDay); void render(const Camera3D& camera); void renderSkybox(const Camera3D& camera); // Render skybox that follows camera diff --git a/server/net/packets.go b/server/net/packets.go index feb2811..2fd0ab0 100644 --- a/server/net/packets.go +++ b/server/net/packets.go @@ -20,6 +20,7 @@ const ( MSG_LOGIN_RESPONSE = 0x0B MSG_LOGOUT = 0x0C MSG_HEARTBEAT = 0x0D + MSG_TIME_SYNC = 0x0E ) // Vec3 represents a 3D vector @@ -194,4 +195,12 @@ func DecodeHeartbeatPacket(data []byte) (playerID uint32, ok bool) { playerID = binary.LittleEndian.Uint32(data[1:5]) return playerID, true +} + +// EncodeTimeSyncPacket creates a time sync packet +func EncodeTimeSyncPacket(timeOfDay float32) []byte { + msg := make([]byte, 5) + msg[0] = MSG_TIME_SYNC + binary.LittleEndian.PutUint32(msg[1:5], math.Float32bits(timeOfDay)) + return msg } \ No newline at end of file diff --git a/server/net/server.go b/server/net/server.go index 69f3320..8713934 100644 --- a/server/net/server.go +++ b/server/net/server.go @@ -40,6 +40,8 @@ type Server struct { heightmap [][]float32 mutex sync.RWMutex nextID uint32 + timeOfDay float32 + startTime time.Time } // NewServer creates a new game server @@ -61,6 +63,8 @@ func NewServer(port string, heightmap [][]float32) (*Server, error) { userData: make(map[string]*UserData), heightmap: heightmap, nextID: 0, + timeOfDay: 0.0, + startTime: time.Now(), } server.loadUserData() @@ -90,6 +94,15 @@ func (s *Server) Run() error { } }() + // Start time of day updater and broadcaster + go func() { + ticker := time.NewTicker(100 * time.Millisecond) // Update time 10 times per second + defer ticker.Stop() + for range ticker.C { + s.updateAndBroadcastTime() + } + }() + buffer := make([]byte, 1024) log.Println("Server running...") @@ -121,6 +134,29 @@ func (s *Server) Run() error { } } +// updateAndBroadcastTime updates the server time and broadcasts it to all clients +func (s *Server) updateAndBroadcastTime() { + // Calculate elapsed time since server start + elapsed := time.Since(s.startTime).Seconds() + + // Day cycle duration in seconds (e.g., 10 minutes = 600 seconds) + dayDuration := 600.0 + + // Calculate time of day (0.0 to 1.0, where 0.5 is noon) + s.timeOfDay = float32(math.Mod(elapsed/dayDuration, 1.0)) + + // Broadcast to all connected players + msg := EncodeTimeSyncPacket(s.timeOfDay) + + s.mutex.RLock() + for _, p := range s.players { + if p.Address != nil { + s.conn.WriteToUDP(msg, p.Address) + } + } + s.mutex.RUnlock() +} + func (s *Server) handleLogin(data []byte, addr *net.UDPAddr) { username, ok := DecodeLoginPacket(data) if !ok || username == "" { @@ -202,6 +238,10 @@ func (s *Server) handleLogin(data []byte, addr *net.UDPAddr) { listMsg := EncodePlayerListPacket(existingPlayers) s.conn.WriteToUDP(listMsg, addr) } + + // Send current time of day to new player + timeMsg := EncodeTimeSyncPacket(s.timeOfDay) + s.conn.WriteToUDP(timeMsg, addr) // Notify other players about new player s.broadcastPlayerJoined(player)