diff --git a/bot/basic/info.go b/bot/basic/info.go index 25eb066..cf2b9c4 100644 --- a/bot/basic/info.go +++ b/bot/basic/info.go @@ -11,7 +11,7 @@ import ( // WorldInfo content player info in server. type WorldInfo struct { DimensionCodec nbt.StringifiedMessage - Dimension nbt.StringifiedMessage + Dimension Dimension WorldNames []string // Identifiers for all worlds on the server. WorldName string // Name of the world being spawned into. HashedSeed int64 // First 8 bytes of the SHA-256 hash of the world's seed. Used client side for biome noise @@ -24,6 +24,25 @@ type WorldInfo struct { IsFlat bool // True if the world is a superflat world; flat worlds have different void fog and a horizon at y=0 instead of y=63. } +type Dimension struct { + PiglinSafe int8 `nbt:"piglin_safe"` + Natural int8 `nbt:"natural"` + AmbientLight float32 `nbt:"ambient_light"` + FixedTime *int64 `nbt:"fixed_time"` + Infiniburn string `nbt:"infiniburn"` + RespawnAnchorWorks int8 `nbt:"respawn_anchor_works"` + HasSkylight int8 `nbt:"has_skylight"` + BedWorks int8 `nbt:"bed_works"` + Effects string `nbt:"effects"` + HasRaids int8 `nbt:"has_raids"` + LogicalHeight int32 `nbt:"logical_height"` + CoordinateScale float64 `nbt:"coordinate_scale"` + MinY int32 `nbt:"min_y"` + HasCeiling int8 `nbt:"has_ceiling"` + Ultrawarm int8 `nbt:"ultrawarm"` + Height int32 `nbt:"height"` +} + type PlayerInfo struct { EID int32 // The player's Entity ID (EID). Hardcore bool // Is hardcore diff --git a/bot/mcbot.go b/bot/mcbot.go index bd57aef..7f0a000 100644 --- a/bot/mcbot.go +++ b/bot/mcbot.go @@ -28,10 +28,7 @@ func (c *Client) JoinServer(addr string) (err error) { // JoinServerWithDialer is similar to JoinServer but using a Dialer. func (c *Client) JoinServerWithDialer(d *net.Dialer, addr string) (err error) { - var dialer *mcnet.Dialer - if d != nil { - dialer = &mcnet.Dialer{Dialer: *d} - } + dialer := (*mcnet.Dialer)(d) return c.join(context.Background(), dialer, addr) } diff --git a/bot/world/chunks.go b/bot/world/chunks.go new file mode 100644 index 0000000..870bf07 --- /dev/null +++ b/bot/world/chunks.go @@ -0,0 +1,66 @@ +package world + +import ( + "github.com/Tnze/go-mc/bot" + "github.com/Tnze/go-mc/bot/basic" + "github.com/Tnze/go-mc/data/packetid" + "github.com/Tnze/go-mc/level" + pk "github.com/Tnze/go-mc/net/packet" +) + +type World struct { + c *bot.Client + p *basic.Player + events EventsListener + + Columns map[level.ChunkPos]*level.Chunk +} + +func NewWorld(c *bot.Client, p *basic.Player, events EventsListener) (w *World) { + w = &World{ + c: c, p: p, + events: events, + Columns: make(map[level.ChunkPos]*level.Chunk), + } + c.Events.AddListener( + bot.PacketHandler{Priority: 64, ID: packetid.ClientboundLogin, F: w.onPlayerSpawn}, + bot.PacketHandler{Priority: 64, ID: packetid.ClientboundRespawn, F: w.onPlayerSpawn}, + bot.PacketHandler{Priority: 0, ID: packetid.ClientboundLevelChunkWithLight, F: w.handleLevelChunkWithLightPacket}, + bot.PacketHandler{Priority: 0, ID: packetid.ClientboundForgetLevelChunk, F: w.handleForgetLevelChunkPacket}, + ) + return +} + +func (w *World) onPlayerSpawn(pk.Packet) error { + // unload all chunks + w.Columns = make(map[level.ChunkPos]*level.Chunk) + return nil +} + +func (w *World) handleLevelChunkWithLightPacket(packet pk.Packet) error { + var pos level.ChunkPos + chunk := level.EmptyChunk(int(w.p.WorldInfo.Dimension.Height / 16)) + if err := packet.Scan(&pos, chunk); err != nil { + return err + } + w.Columns[pos] = chunk + if w.events.LoadChunk != nil { + if err := w.events.LoadChunk(pos); err != nil { + return err + } + } + return nil +} + +func (w *World) handleForgetLevelChunkPacket(packet pk.Packet) error { + var pos level.ChunkPos + if err := packet.Scan(&pos); err != nil { + return err + } + var err error + if w.events.UnloadChunk != nil { + err = w.events.UnloadChunk(pos) + } + delete(w.Columns, pos) + return err +} diff --git a/bot/world/events.go b/bot/world/events.go new file mode 100644 index 0000000..92bf375 --- /dev/null +++ b/bot/world/events.go @@ -0,0 +1,8 @@ +package world + +import "github.com/Tnze/go-mc/level" + +type EventsListener struct { + LoadChunk func(pos level.ChunkPos) error + UnloadChunk func(pos level.ChunkPos) error +} diff --git a/examples/daze/daze.go b/examples/daze/daze.go index 481fb95..c08b82d 100644 --- a/examples/daze/daze.go +++ b/examples/daze/daze.go @@ -5,6 +5,8 @@ package main import ( "errors" "flag" + "github.com/Tnze/go-mc/bot/world" + "github.com/Tnze/go-mc/level" "log" "time" @@ -22,6 +24,7 @@ import ( var address = flag.String("address", "127.0.0.1", "The server address") var client *bot.Client var player *basic.Player +var worldManager *world.World var screenManager *screen.Manager func main() { @@ -31,11 +34,16 @@ func main() { client.Auth.Name = "Daze" player = basic.NewPlayer(client, basic.DefaultSettings) basic.EventsListener{ - GameStart: onGameStart, - ChatMsg: onChatMsg, - Disconnect: onDisconnect, - Death: onDeath, + GameStart: onGameStart, + ChatMsg: onChatMsg, + Disconnect: onDisconnect, + HealthChange: nil, + Death: onDeath, }.Attach(client) + worldManager = world.NewWorld(client, player, world.EventsListener{ + LoadChunk: onChunkLoad, + UnloadChunk: onChunkUnload, + }) screenManager = screen.NewManager(client, screen.EventsListener{ Open: nil, SetSlot: onScreenSlotChange, @@ -92,6 +100,16 @@ func onChatMsg(c chat.Message, _ byte, _ uuid.UUID) error { return nil } +func onChunkLoad(pos level.ChunkPos) error { + log.Println("Load chunk:", pos) + return nil +} + +func onChunkUnload(pos level.ChunkPos) error { + log.Println("Unload chunk:", pos) + return nil +} + func onScreenSlotChange(id, index int) error { if id == -2 { log.Printf("Slot: inventory: %v", screenManager.Inventory.Slots[index]) diff --git a/examples/frameworkServer/main.go b/examples/frameworkServer/main.go index 63657f4..b9d44e6 100644 --- a/examples/frameworkServer/main.go +++ b/examples/frameworkServer/main.go @@ -10,6 +10,7 @@ import ( "log" "os" "path/filepath" + "time" "github.com/Tnze/go-mc/chat" "github.com/Tnze/go-mc/level" @@ -33,6 +34,8 @@ func main() { log.Fatalf("Set server info error: %v", err) } + keepAlive := server.NewKeepAlive() + playerInfo := server.NewPlayerInfo(time.Second, keepAlive) defaultDimension, err := loadAllRegions(*regionPath) if err != nil { log.Fatalf("Load chunks fail: %v", err) @@ -60,7 +63,8 @@ func main() { game := server.NewGame( defaultDimension, playerList, - server.NewKeepAlive(), + playerInfo, + keepAlive, server.NewGlobalChat(), commands, ) diff --git a/level/block/block.go b/level/block/block.go index 8b759e1..23b1119 100644 --- a/level/block/block.go +++ b/level/block/block.go @@ -18,8 +18,8 @@ type Block interface { //go:embed block_states.nbt var blockStates []byte -var toStateID map[Block]int -var fromStateID []Block +var ToStateID map[Block]int +var StateList []Block // BitsPerBlock indicates how many bits are needed to represent all possible // block states. This value is used to determine the size of the global palette. @@ -41,8 +41,8 @@ func init() { if _, err = nbt.NewDecoder(z).Decode(&states); err != nil { panic(err) } - toStateID = make(map[Block]int, len(states)) - fromStateID = make([]Block, 0, len(states)) + ToStateID = make(map[Block]int, len(states)) + StateList = make([]Block, 0, len(states)) for _, state := range states { block := fromID[state.Name] if state.Properties.Type != nbt.TagEnd { @@ -51,29 +51,16 @@ func init() { panic(err) } } - if _, ok := toStateID[block]; ok { + if _, ok := ToStateID[block]; ok { panic(fmt.Errorf("state %#v already exist", block)) } - toStateID[block] = len(fromStateID) - fromStateID = append(fromStateID, block) + ToStateID[block] = len(StateList) + StateList = append(StateList, block) } - BitsPerBlock = bits.Len(uint(len(fromStateID))) -} - -func FromStateID(stateID int) (b Block, ok bool) { - if stateID >= 0 && stateID < len(fromStateID) { - b = fromStateID[stateID] - ok = true - } - return + BitsPerBlock = bits.Len(uint(len(StateList))) } func DefaultBlock(id string) (b Block, ok bool) { b, ok = fromID[id] return } - -func ToStateID(b Block) (i int, ok bool) { - i, ok = toStateID[b] - return -} diff --git a/level/block/generator/blocks/blocks.go.tmpl b/level/block/generator/blocks/blocks.go.tmpl index 0c87869..4ba8339 100644 --- a/level/block/generator/blocks/blocks.go.tmpl +++ b/level/block/generator/blocks/blocks.go.tmpl @@ -12,6 +12,6 @@ type ( {{- range .}} func ({{.Name | ToGoTypeName}}) ID() string { return {{.Name | printf "%q"}} } {{- end}} -var fromID = map[string]Block { {{- range .}} +var FromID = map[string]Block { {{- range .}} {{.Name | printf "%#v"}}: {{.Name | ToGoTypeName}}{},{{end}} } \ No newline at end of file diff --git a/level/chunk.go b/level/chunk.go index 21d0519..5df181d 100644 --- a/level/chunk.go +++ b/level/chunk.go @@ -10,19 +10,40 @@ import ( "unsafe" "github.com/Tnze/go-mc/level/block" + "github.com/Tnze/go-mc/nbt" pk "github.com/Tnze/go-mc/net/packet" "github.com/Tnze/go-mc/save" ) type ChunkPos struct{ X, Z int } + +func (c ChunkPos) WriteTo(w io.Writer) (n int64, err error) { + n, err = pk.Int(c.X).WriteTo(w) + if err != nil { + return + } + n1, err := pk.Int(c.Z).WriteTo(w) + return n + n1, err +} + +func (c *ChunkPos) ReadFrom(r io.Reader) (n int64, err error) { + var x, z pk.Int + if n, err = x.ReadFrom(r); err != nil { + return n, err + } + var n1 int64 + if n1, err = z.ReadFrom(r); err != nil { + return n + n1, err + } + *c = ChunkPos{int(x), int(z)} + return n + n1, nil +} + type Chunk struct { sync.Mutex - Sections []Section - HeightMaps HeightMaps -} -type HeightMaps struct { - MotionBlocking *BitStorage - WorldSurface *BitStorage + Sections []Section + HeightMaps HeightMaps + BlockEntity []BlockEntity } func EmptyChunk(secs int) *Chunk { @@ -122,7 +143,7 @@ func ChunkFromSave(c *save.Chunk, secs int) *Chunk { blockCount++ } var ok bool - stateRawPalette[i], ok = block.ToStateID(b) + stateRawPalette[i], ok = block.ToStateID[b] if !ok { panic(fmt.Errorf("state id not found: %#v", b)) } @@ -176,7 +197,7 @@ func (c *Chunk) WriteTo(w io.Writer) (int64, error) { WorldSurface: c.HeightMaps.MotionBlocking.Raw(), }), pk.ByteArray(data), - pk.VarInt(0), // TODO: Block Entity + pk.Array(c.BlockEntity), &lightData{ SkyLightMask: make(pk.BitSet, (16*16*16-1)>>6+1), BlockLightMask: make(pk.BitSet, (16*16*16-1)>>6+1), @@ -186,10 +207,38 @@ func (c *Chunk) WriteTo(w io.Writer) (int64, error) { }.WriteTo(w) } +func (c *Chunk) ReadFrom(r io.Reader) (int64, error) { + var ( + heightmaps struct { + MotionBlocking []uint64 `nbt:"MOTION_BLOCKING"` + WorldSurface []uint64 `nbt:"WORLD_SURFACE"` + } + data pk.ByteArray + ) + + n, err := pk.Tuple{ + pk.NBT(&heightmaps), + &data, + pk.Array(&c.BlockEntity), + &lightData{ + SkyLightMask: make(pk.BitSet, (16*16*16-1)>>6+1), + BlockLightMask: make(pk.BitSet, (16*16*16-1)>>6+1), + SkyLight: []pk.ByteArray{}, + BlockLight: []pk.ByteArray{}, + }, + }.ReadFrom(r) + if err != nil { + return n, err + } + + err = c.PutData(data) + return n, err +} + func (c *Chunk) Data() ([]byte, error) { var buff bytes.Buffer - for _, section := range c.Sections { - _, err := section.WriteTo(&buff) + for i := range c.Sections { + _, err := c.Sections[i].WriteTo(&buff) if err != nil { return nil, err } @@ -197,6 +246,47 @@ func (c *Chunk) Data() ([]byte, error) { return buff.Bytes(), nil } +func (c *Chunk) PutData(data []byte) error { + r := bytes.NewReader(data) + for i := range c.Sections { + _, err := c.Sections[i].ReadFrom(r) + if err != nil { + return err + } + } + return nil +} + +type HeightMaps struct { + MotionBlocking *BitStorage + WorldSurface *BitStorage +} + +type BlockEntity struct { + XZ int8 + Y int16 + Type int32 + Data nbt.RawMessage +} + +func (b BlockEntity) WriteTo(w io.Writer) (n int64, err error) { + return pk.Tuple{ + pk.Byte(b.XZ), + pk.Short(b.Y), + pk.VarInt(b.Type), + pk.NBT(b.Data), + }.WriteTo(w) +} + +func (b *BlockEntity) ReadFrom(r io.Reader) (n int64, err error) { + return pk.Tuple{ + (*pk.Byte)(&b.XZ), + (*pk.Short)(&b.Y), + (*pk.VarInt)(&b.Type), + pk.NBT(&b.Data), + }.ReadFrom(r) +} + type Section struct { blockCount int16 States *PaletteContainer @@ -207,7 +297,10 @@ func (s *Section) GetBlock(i int) int { return s.States.Get(i) } func (s *Section) SetBlock(i int, v int) { - b, _ := block.FromStateID(s.States.Get(i)) + var b block.Block + if stateID := s.States.Get(i); stateID >= 0 && stateID < len(block.StateList) { + b = block.StateList[stateID] + } if isAir(b) { s.blockCount-- } @@ -260,6 +353,20 @@ func (l *lightData) WriteTo(w io.Writer) (int64, error) { }.WriteTo(w) } +func (l *lightData) ReadFrom(r io.Reader) (int64, error) { + var TrustEdges pk.Boolean + var RevSkyLightMask, RevBlockLightMask pk.BitSet + return pk.Tuple{ + &TrustEdges, // Trust Edges + &l.SkyLightMask, + &l.BlockLightMask, + &RevSkyLightMask, + &RevBlockLightMask, + pk.Array(&l.SkyLight), + pk.Array(&l.BlockLight), + }.ReadFrom(r) +} + func isAir(b block.Block) bool { switch b.(type) { case block.Air, block.CaveAir, block.VoidAir: diff --git a/level/palette.go b/level/palette.go index 83002b9..3a296c6 100644 --- a/level/palette.go +++ b/level/palette.go @@ -274,6 +274,11 @@ func (l *linearPalette) ReadFrom(r io.Reader) (n int64, err error) { if n, err = size.ReadFrom(r); err != nil { return } + if int(size) > cap(l.values) { + l.values = make([]state, size) + } else { + l.values = l.values[:size] + } for i := 0; i < int(size); i++ { if nn, err := value.ReadFrom(r); err != nil { return n + nn, err diff --git a/nbt/decode.go b/nbt/decode.go index 9788421..8bdbe94 100644 --- a/nbt/decode.go +++ b/nbt/decode.go @@ -256,20 +256,32 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte) error { vt = reflect.TypeOf([]int64{}) // pass } else if vt.Kind() != reflect.Slice { return errors.New("cannot parse TagLongArray to " + vt.String() + ", it must be a slice") - } else if val.Type().Elem().Kind() != reflect.Int64 { + } + switch val.Type().Elem().Kind() { + case reflect.Int64: + buf := reflect.MakeSlice(vt, int(aryLen), int(aryLen)) + for i := 0; i < int(aryLen); i++ { + value, err := d.readLong() + if err != nil { + return err + } + buf.Index(i).SetInt(value) + } + val.Set(buf) + case reflect.Uint64: + buf := reflect.MakeSlice(vt, int(aryLen), int(aryLen)) + for i := 0; i < int(aryLen); i++ { + value, err := d.readLong() + if err != nil { + return err + } + buf.Index(i).SetUint(uint64(value)) + } + val.Set(buf) + default: return errors.New("cannot parse TagLongArray to " + vt.String()) } - buf := reflect.MakeSlice(vt, int(aryLen), int(aryLen)) - for i := 0; i < int(aryLen); i++ { - value, err := d.readLong() - if err != nil { - return err - } - buf.Index(i).SetInt(value) - } - val.Set(buf) - case TagList: listType, err := d.r.ReadByte() if err != nil { diff --git a/net/conn.go b/net/conn.go index 3e99c40..f4dec81 100644 --- a/net/conn.go +++ b/net/conn.go @@ -62,9 +62,7 @@ func DialMCTimeout(addr string, timeout time.Duration) (*Conn, error) { return DefaultDialer.DialMCContext(ctx, addr) } -type Dialer struct { - net.Dialer -} +type Dialer net.Dialer func (d *Dialer) resolver() *net.Resolver { if d != nil && d.Resolver != nil { @@ -122,7 +120,7 @@ func (d *Dialer) DialMCContext(ctx context.Context, addr string) (*Conn, error) defer cancel() } } - conn, err := d.DialContext(dialCtx, "tcp", addr) + conn, err := (*net.Dialer)(d).DialContext(dialCtx, "tcp", addr) if err != nil { if firstErr == nil { firstErr = err diff --git a/server/dimension.go b/server/dimension.go index 0a7707b..a2d2515 100644 --- a/server/dimension.go +++ b/server/dimension.go @@ -7,6 +7,7 @@ import ( ) type Level interface { + Init(g *Game) Info() LevelInfo PlayerJoin(p *Player) PlayerQuit(p *Player) @@ -19,18 +20,20 @@ type LevelInfo struct { type SimpleDim struct { numOfSection int - Columns map[level.ChunkPos]*level.Chunk + columns map[level.ChunkPos]*level.Chunk } +func (s *SimpleDim) Init(*Game) {} + func NewSimpleDim(secs int) *SimpleDim { return &SimpleDim{ numOfSection: secs, - Columns: make(map[level.ChunkPos]*level.Chunk), + columns: make(map[level.ChunkPos]*level.Chunk), } } func (s *SimpleDim) LoadChunk(pos level.ChunkPos, c *level.Chunk) { - s.Columns[pos] = c + s.columns[pos] = c } func (s *SimpleDim) Info() LevelInfo { @@ -41,28 +44,16 @@ func (s *SimpleDim) Info() LevelInfo { } func (s *SimpleDim) PlayerJoin(p *Player) { - for pos, column := range s.Columns { + for pos, column := range s.columns { column.Lock() packet := pk.Marshal( packetid.ClientboundLevelChunkWithLight, - pk.Int(pos.X), pk.Int(pos.Z), - column, + pos, column, ) column.Unlock() p.WritePacket(Packet758(packet)) } - - p.WritePacket(Packet758(pk.Marshal( - packetid.ClientboundPlayerPosition, - pk.Double(0), pk.Double(143), pk.Double(0), - pk.Float(0), pk.Float(0), - pk.Byte(0), - pk.VarInt(0), - pk.Boolean(true), - ))) } -func (s *SimpleDim) PlayerQuit(p *Player) { - -} +func (s *SimpleDim) PlayerQuit(*Player) {} diff --git a/server/gameplay.go b/server/gameplay.go index c7a75f8..06b452d 100644 --- a/server/gameplay.go +++ b/server/gameplay.go @@ -56,6 +56,7 @@ func NewGame(dim Level, components ...Component) *Game { components: components, handlers: make(map[int32][]*PacketHandler), } + dim.Init(g) for _, v := range components { v.Init(g) } diff --git a/server/keepalive.go b/server/keepalive.go index f560b1f..b5378d3 100644 --- a/server/keepalive.go +++ b/server/keepalive.go @@ -48,12 +48,11 @@ func NewKeepAlive() (k *KeepAlive) { } } -func (k *KeepAlive) AddPlayerDelayUpdateHandler(f func(p *Player, delay time.Duration)) *KeepAlive { +func (k *KeepAlive) AddPlayerDelayUpdateHandler(f func(p *Player, delay time.Duration)) { if k.updatePlayerDelay != nil { panic("add player update handler twice") } k.updatePlayerDelay = f - return k } // Init implement Component for KeepAlive diff --git a/server/playerinfo.go b/server/playerinfo.go new file mode 100644 index 0000000..c6f59f3 --- /dev/null +++ b/server/playerinfo.go @@ -0,0 +1,148 @@ +package server + +import ( + "context" + "io" + "time" + + "github.com/google/uuid" + + "github.com/Tnze/go-mc/data/packetid" + pk "github.com/Tnze/go-mc/net/packet" +) + +type PlayerInfo struct { + updateDelay chan playerInfo + join chan *Player + quit chan *Player + ticker *time.Ticker +} + +type playerInfo struct { + player *Player + delay time.Duration +} + +func (p *playerInfo) WriteTo(w io.Writer) (n int64, err error) { + return pk.Tuple{ + pk.UUID(p.player.UUID), + pk.String(p.player.Name), + pk.Array([]pk.FieldEncoder{}), + pk.VarInt(p.player.Gamemode), + pk.VarInt(p.delay), + pk.Boolean(false), + }.WriteTo(w) +} + +type playerInfoList struct { + list map[uuid.UUID]playerInfo +} + +func (p *playerInfoList) WriteTo(w io.Writer) (n int64, err error) { + n, err = pk.VarInt(len(p.list)).WriteTo(w) + if err != nil { + return + } + var n1 int64 + for _, p := range p.list { + n1, err = p.WriteTo(w) + n += n1 + if err != nil { + return + } + } + return +} + +type playerDelayUpdate playerInfo + +func (p playerDelayUpdate) WriteTo(w io.Writer) (n int64, err error) { + return pk.Tuple{ + pk.UUID(p.player.UUID), + pk.VarInt(p.delay.Milliseconds()), + }.WriteTo(w) +} + +const ( + actionAddPlayer = iota + actionUpdateGamemode + actionUpdateLatency + actionUpdateDisplayName + actionRemovePlayer +) + +type PlayerDelaySource interface { + AddPlayerDelayUpdateHandler(f func(p *Player, delay time.Duration)) +} + +func NewPlayerInfo(updateFreq time.Duration, delaySource PlayerDelaySource) *PlayerInfo { + p := &PlayerInfo{ + updateDelay: make(chan playerInfo), + join: make(chan *Player), + quit: make(chan *Player), + ticker: time.NewTicker(updateFreq), + } + if delaySource != nil { + delaySource.AddPlayerDelayUpdateHandler(p.onPlayerDelayUpdate) + } + return p +} + +func (p *PlayerInfo) Init(*Game) {} + +func (p *PlayerInfo) Run(ctx context.Context) { + players := &playerInfoList{list: make(map[uuid.UUID]playerInfo)} + var delayBuffer []playerDelayUpdate + for { + select { + case player := <-p.join: + info := playerInfo{player: player, delay: 0} + pack := Packet758(pk.Marshal( + packetid.ClientboundPlayerInfo, + pk.VarInt(actionAddPlayer), + pk.VarInt(1), + &info, + )) + players.list[player.UUID] = info + for _, p := range players.list { + p.player.WritePacket(pack) + } + player.WritePacket(Packet758(pk.Marshal( + packetid.ClientboundPlayerInfo, + pk.VarInt(actionAddPlayer), + players, + ))) + case player := <-p.quit: + delete(players.list, player.UUID) + pack := Packet758(pk.Marshal( + packetid.ClientboundPlayerInfo, + pk.VarInt(actionRemovePlayer), + pk.VarInt(1), + pk.UUID(player.UUID), + )) + for _, p := range players.list { + p.player.WritePacket(pack) + } + case change := <-p.updateDelay: + delayBuffer = append(delayBuffer, playerDelayUpdate(change)) + case <-p.ticker.C: + pack := Packet758(pk.Marshal( + packetid.ClientboundPlayerInfo, + pk.VarInt(actionUpdateLatency), + pk.Array(&delayBuffer), + )) + for _, p := range players.list { + p.player.WritePacket(pack) + } + delayBuffer = delayBuffer[:0] + case <-ctx.Done(): + break + } + } +} + +func (p *PlayerInfo) AddPlayer(player *Player) { p.join <- player } +func (p *PlayerInfo) RemovePlayer(player *Player) { p.quit <- player } +func (p *PlayerInfo) onPlayerDelayUpdate(player *Player, delay time.Duration) { + p.updateDelay <- playerInfo{player: player, delay: delay} +} diff --git a/server/playermove.go b/server/playermove.go new file mode 100644 index 0000000..2ab3992 --- /dev/null +++ b/server/playermove.go @@ -0,0 +1,14 @@ +package server + +type EntitySet struct { +} + +type entityPosition struct { + player *Player + x, y, z float64 + yaw, pitch float32 +} + +func NewEntitySet() *EntitySet { + return &EntitySet{} +}