set_time was declared nowhere and sent never, so the client's sky was frozen wherever it started: the sun did not move, night did not fall, and nothing in the world had a time. 26.1.2 replaced the old (gameTime, dayTime, doDaylightCycle) triple with a registry of named clocks. The packet now carries a fixed-width game time plus a map from world clock to (totalTicks, partialTick, rate); the client advances each clock locally at its rate and drives the minecraft:day timeline -- a keyframe track over a 24000-tick period -- from the overworld clock. The clock registry is already among the 28 we sync, so the id is looked up rather than hardcoded and the server refuses to start if it is missing. The counter rides the entity tick loop because that is the only loop already running at 20 TPS. It belongs on the single authoritative tick the engine still needs; putting a sixth ticker beside the five that exist would make that worse. Broadcast every second, which is what vanilla does -- the client interpolates in between, so the resend only corrects drift. The clock also persists now. The world metadata file was written once and never touched again; it is atomically rewritable, carries gameTime and dayTime, and a file written before those fields existed still opens and resumes at dawn as it did. Saved every 30 seconds alongside the chunk autosave, and once more on shutdown after the final flush.
90 lines
3.2 KiB
Go
90 lines
3.2 KiB
Go
package server
|
|
|
|
import (
|
|
"sync/atomic"
|
|
|
|
"regionio/internal/protocol"
|
|
"regionio/internal/registry"
|
|
)
|
|
|
|
// clock.go drives the world clock. Without it the client's sky is frozen: it
|
|
// never receives a time, so the sun sits wherever it started and night never
|
|
// falls.
|
|
//
|
|
// 26.1.2 replaced the old (gameTime, dayTime, doDaylightCycle) triple with a
|
|
// registry of named clocks. set_time carries the world's game time plus a map
|
|
// of clock to (totalTicks, partialTick, rate); the client advances each clock
|
|
// locally at its rate and drives the minecraft:day timeline — a keyframe track
|
|
// over a 24000-tick period — from the overworld clock.
|
|
|
|
// worldClockOverworld is the network ID the client assigns
|
|
// minecraft:overworld in the synced minecraft:world_clock registry. Looked up
|
|
// rather than hardcoded so it cannot drift from the registry we actually send.
|
|
var worldClockOverworld = int32(registry.Index("minecraft:world_clock", "minecraft:overworld"))
|
|
|
|
func init() {
|
|
if worldClockOverworld < 0 {
|
|
panic("server: minecraft:overworld missing from the synced minecraft:world_clock registry")
|
|
}
|
|
}
|
|
|
|
const (
|
|
// TicksPerDay is period_ticks from data/minecraft/timeline/day.json.
|
|
TicksPerDay = 24000
|
|
|
|
// timeSyncTicks is how often the clock is rebroadcast. The client
|
|
// interpolates in between, so this only has to correct drift; vanilla
|
|
// resends on the same one-second cadence.
|
|
timeSyncTicks = 20
|
|
|
|
// clockRateNormal is the client-side advance rate, in ticks per tick.
|
|
clockRateNormal = 1.0
|
|
|
|
// timePersistTicks is how often the clock is written back to the world
|
|
// metadata: every 30 seconds, matching the chunk autosave interval.
|
|
timePersistTicks = 600
|
|
)
|
|
|
|
// worldClock is the tick counter behind the sky. gameTime counts every tick the
|
|
// world has ever run; dayTime is what the sky is drawn from, and vanilla lets
|
|
// the two diverge (a time command moves one and not the other).
|
|
type worldClock struct {
|
|
gameTime atomic.Int64
|
|
dayTime atomic.Int64
|
|
}
|
|
|
|
// WorldTime returns the current game time and time of day.
|
|
func (s *Server) WorldTime() (gameTime, dayTime int64) {
|
|
return s.clock.gameTime.Load(), s.clock.dayTime.Load()
|
|
}
|
|
|
|
// SetWorldTime replaces both counters, for restoring a saved world.
|
|
func (s *Server) SetWorldTime(gameTime, dayTime int64) {
|
|
s.clock.gameTime.Store(gameTime)
|
|
s.clock.dayTime.Store(dayTime)
|
|
}
|
|
|
|
// advanceWorldTime moves the clock on by one tick and returns the new values.
|
|
func (s *Server) advanceWorldTime() (gameTime, dayTime int64) {
|
|
return s.clock.gameTime.Add(1), s.clock.dayTime.Add(1)
|
|
}
|
|
|
|
// SetTimePacket encodes a set_time body for the current clock.
|
|
func (s *Server) SetTimePacket() []byte {
|
|
gameTime, dayTime := s.WorldTime()
|
|
return encodeSetTime(gameTime, dayTime)
|
|
}
|
|
|
|
// encodeSetTime writes ClientboundSetTimePacket: a fixed-width game time, then
|
|
// a map from clock to clock state. Only the overworld clock is sent — it is the
|
|
// only dimension the server has.
|
|
func encodeSetTime(gameTime, dayTime int64) []byte {
|
|
w := protocol.NewWriter(24)
|
|
w.Int64(gameTime)
|
|
w.VarInt(1) // one entry in the clock map
|
|
w.VarInt(worldClockOverworld)
|
|
w.VarLong(dayTime)
|
|
w.Float32(0) // partialTick: we are exactly on a tick boundary
|
|
w.Float32(clockRateNormal) // rate: the daylight cycle always runs
|
|
return w.Bytes()
|
|
}
|