1
0

make server authoritative on time of day

This commit is contained in:
Sky Johnson 2025-09-09 18:57:41 -05:00
parent aec43c0bcf
commit c39715b84c
7 changed files with 82 additions and 3 deletions

View File

@ -228,8 +228,13 @@ private:
return; return;
} }
// Update sky // Update sky with server time if connected, otherwise use local time
sky->update(GetFrameTime()); if (network.isConnected()) {
float serverTime = network.getServerTimeOfDay();
sky->updateFromServerTime(serverTime);
} else {
sky->update(GetFrameTime());
}
// Time of day controls (for testing) // Time of day controls (for testing)
if (IsKeyPressed(KEY_T)) { if (IsKeyPressed(KEY_T)) {

View File

@ -56,11 +56,24 @@ void NetworkManager::processMessage(const uint8_t* data, std::size_t size) {
case MessageType::LoginResponse: case MessageType::LoginResponse:
handleLoginResponse(data, size); handleLoginResponse(data, size);
break; break;
case MessageType::TimeSync:
handleTimeSync(data, size);
break;
default: default:
break; 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) { 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)] // Message format: [type(1)][id(4)][x(4)][y(4)][z(4)][colorLen(1)][color(colorLen)]
if (size < 18) return; if (size < 18) return;

View File

@ -25,7 +25,8 @@ enum class MessageType : uint8_t {
ColorChanged = 0x0A, ColorChanged = 0x0A,
LoginResponse = 0x0B, LoginResponse = 0x0B,
Logout = 0x0C, Logout = 0x0C,
Heartbeat = 0x0D Heartbeat = 0x0D,
TimeSync = 0x0E
}; };
struct RemotePlayer { struct RemotePlayer {
@ -55,6 +56,7 @@ public:
std::string getPlayerColor() const { return playerColor; } std::string getPlayerColor() const { return playerColor; }
std::unordered_map<uint32_t, RemotePlayer> getRemotePlayers(); std::unordered_map<uint32_t, RemotePlayer> getRemotePlayers();
float getServerTimeOfDay() const { return serverTimeOfDay.load(); }
// Available colors for cycling // Available colors for cycling
static const std::vector<std::string> AVAILABLE_COLORS; static const std::vector<std::string> AVAILABLE_COLORS;
@ -76,6 +78,7 @@ private:
std::mutex remotePlayersMutex; std::mutex remotePlayersMutex;
std::unordered_map<uint32_t, RemotePlayer> remotePlayers; std::unordered_map<uint32_t, RemotePlayer> remotePlayers;
std::atomic<float> serverTimeOfDay{0.0f};
void startReceive(); void startReceive();
void processMessage(const uint8_t* data, std::size_t size); 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 handlePlayerList(const uint8_t* data, std::size_t size);
void handleColorChanged(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 handleLoginResponse(const uint8_t* data, std::size_t size);
void handleTimeSync(const uint8_t* data, std::size_t size);
}; };

View File

@ -75,6 +75,13 @@ void Sky::update(float) {
moon.updatePosition(timeOfDay); moon.updatePosition(timeOfDay);
} }
void Sky::updateFromServerTime(float serverTimeOfDay) {
timeOfDay = serverTimeOfDay;
updateColors();
sun.updatePosition(timeOfDay);
moon.updatePosition(timeOfDay);
}
void Sky::Sun::updatePosition(float timeOfDay) { void Sky::Sun::updatePosition(float timeOfDay) {
// Convert time of day to angle (0.25 = sunrise at 6am, 0.75 = sunset at 6pm) // 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 // Adjust so that 0.25 (6am) = 0 degrees, 0.75 (6pm) = 180 degrees

View File

@ -57,6 +57,7 @@ public:
~Sky(); ~Sky();
void update(float deltaTime); void update(float deltaTime);
void updateFromServerTime(float serverTimeOfDay);
void render(const Camera3D& camera); void render(const Camera3D& camera);
void renderSkybox(const Camera3D& camera); // Render skybox that follows camera void renderSkybox(const Camera3D& camera); // Render skybox that follows camera

View File

@ -20,6 +20,7 @@ const (
MSG_LOGIN_RESPONSE = 0x0B MSG_LOGIN_RESPONSE = 0x0B
MSG_LOGOUT = 0x0C MSG_LOGOUT = 0x0C
MSG_HEARTBEAT = 0x0D MSG_HEARTBEAT = 0x0D
MSG_TIME_SYNC = 0x0E
) )
// Vec3 represents a 3D vector // Vec3 represents a 3D vector
@ -195,3 +196,11 @@ func DecodeHeartbeatPacket(data []byte) (playerID uint32, ok bool) {
playerID = binary.LittleEndian.Uint32(data[1:5]) playerID = binary.LittleEndian.Uint32(data[1:5])
return playerID, true 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
}

View File

@ -40,6 +40,8 @@ type Server struct {
heightmap [][]float32 heightmap [][]float32
mutex sync.RWMutex mutex sync.RWMutex
nextID uint32 nextID uint32
timeOfDay float32
startTime time.Time
} }
// NewServer creates a new game server // NewServer creates a new game server
@ -61,6 +63,8 @@ func NewServer(port string, heightmap [][]float32) (*Server, error) {
userData: make(map[string]*UserData), userData: make(map[string]*UserData),
heightmap: heightmap, heightmap: heightmap,
nextID: 0, nextID: 0,
timeOfDay: 0.0,
startTime: time.Now(),
} }
server.loadUserData() 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) buffer := make([]byte, 1024)
log.Println("Server running...") 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) { func (s *Server) handleLogin(data []byte, addr *net.UDPAddr) {
username, ok := DecodeLoginPacket(data) username, ok := DecodeLoginPacket(data)
if !ok || username == "" { if !ok || username == "" {
@ -203,6 +239,10 @@ func (s *Server) handleLogin(data []byte, addr *net.UDPAddr) {
s.conn.WriteToUDP(listMsg, addr) 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 // Notify other players about new player
s.broadcastPlayerJoined(player) s.broadcastPlayerJoined(player)