bot can receive chunks now

This commit is contained in:
Tnze
2022-03-13 11:57:02 +08:00
parent d8695636b6
commit 2aace6b51a
17 changed files with 452 additions and 78 deletions

View File

@ -11,7 +11,7 @@ import (
// WorldInfo content player info in server. // WorldInfo content player info in server.
type WorldInfo struct { type WorldInfo struct {
DimensionCodec nbt.StringifiedMessage DimensionCodec nbt.StringifiedMessage
Dimension nbt.StringifiedMessage Dimension Dimension
WorldNames []string // Identifiers for all worlds on the server. WorldNames []string // Identifiers for all worlds on the server.
WorldName string // Name of the world being spawned into. 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 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. 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 { type PlayerInfo struct {
EID int32 // The player's Entity ID (EID). EID int32 // The player's Entity ID (EID).
Hardcore bool // Is hardcore Hardcore bool // Is hardcore

View File

@ -28,10 +28,7 @@ func (c *Client) JoinServer(addr string) (err error) {
// JoinServerWithDialer is similar to JoinServer but using a Dialer. // JoinServerWithDialer is similar to JoinServer but using a Dialer.
func (c *Client) JoinServerWithDialer(d *net.Dialer, addr string) (err error) { func (c *Client) JoinServerWithDialer(d *net.Dialer, addr string) (err error) {
var dialer *mcnet.Dialer dialer := (*mcnet.Dialer)(d)
if d != nil {
dialer = &mcnet.Dialer{Dialer: *d}
}
return c.join(context.Background(), dialer, addr) return c.join(context.Background(), dialer, addr)
} }

66
bot/world/chunks.go Normal file
View File

@ -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
}

8
bot/world/events.go Normal file
View File

@ -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
}

View File

@ -5,6 +5,8 @@ package main
import ( import (
"errors" "errors"
"flag" "flag"
"github.com/Tnze/go-mc/bot/world"
"github.com/Tnze/go-mc/level"
"log" "log"
"time" "time"
@ -22,6 +24,7 @@ import (
var address = flag.String("address", "127.0.0.1", "The server address") var address = flag.String("address", "127.0.0.1", "The server address")
var client *bot.Client var client *bot.Client
var player *basic.Player var player *basic.Player
var worldManager *world.World
var screenManager *screen.Manager var screenManager *screen.Manager
func main() { func main() {
@ -34,8 +37,13 @@ func main() {
GameStart: onGameStart, GameStart: onGameStart,
ChatMsg: onChatMsg, ChatMsg: onChatMsg,
Disconnect: onDisconnect, Disconnect: onDisconnect,
HealthChange: nil,
Death: onDeath, Death: onDeath,
}.Attach(client) }.Attach(client)
worldManager = world.NewWorld(client, player, world.EventsListener{
LoadChunk: onChunkLoad,
UnloadChunk: onChunkUnload,
})
screenManager = screen.NewManager(client, screen.EventsListener{ screenManager = screen.NewManager(client, screen.EventsListener{
Open: nil, Open: nil,
SetSlot: onScreenSlotChange, SetSlot: onScreenSlotChange,
@ -92,6 +100,16 @@ func onChatMsg(c chat.Message, _ byte, _ uuid.UUID) error {
return nil 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 { func onScreenSlotChange(id, index int) error {
if id == -2 { if id == -2 {
log.Printf("Slot: inventory: %v", screenManager.Inventory.Slots[index]) log.Printf("Slot: inventory: %v", screenManager.Inventory.Slots[index])

View File

@ -10,6 +10,7 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"time"
"github.com/Tnze/go-mc/chat" "github.com/Tnze/go-mc/chat"
"github.com/Tnze/go-mc/level" "github.com/Tnze/go-mc/level"
@ -33,6 +34,8 @@ func main() {
log.Fatalf("Set server info error: %v", err) log.Fatalf("Set server info error: %v", err)
} }
keepAlive := server.NewKeepAlive()
playerInfo := server.NewPlayerInfo(time.Second, keepAlive)
defaultDimension, err := loadAllRegions(*regionPath) defaultDimension, err := loadAllRegions(*regionPath)
if err != nil { if err != nil {
log.Fatalf("Load chunks fail: %v", err) log.Fatalf("Load chunks fail: %v", err)
@ -60,7 +63,8 @@ func main() {
game := server.NewGame( game := server.NewGame(
defaultDimension, defaultDimension,
playerList, playerList,
server.NewKeepAlive(), playerInfo,
keepAlive,
server.NewGlobalChat(), server.NewGlobalChat(),
commands, commands,
) )

View File

@ -18,8 +18,8 @@ type Block interface {
//go:embed block_states.nbt //go:embed block_states.nbt
var blockStates []byte var blockStates []byte
var toStateID map[Block]int var ToStateID map[Block]int
var fromStateID []Block var StateList []Block
// BitsPerBlock indicates how many bits are needed to represent all possible // 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. // 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 { if _, err = nbt.NewDecoder(z).Decode(&states); err != nil {
panic(err) panic(err)
} }
toStateID = make(map[Block]int, len(states)) ToStateID = make(map[Block]int, len(states))
fromStateID = make([]Block, 0, len(states)) StateList = make([]Block, 0, len(states))
for _, state := range states { for _, state := range states {
block := fromID[state.Name] block := fromID[state.Name]
if state.Properties.Type != nbt.TagEnd { if state.Properties.Type != nbt.TagEnd {
@ -51,29 +51,16 @@ func init() {
panic(err) panic(err)
} }
} }
if _, ok := toStateID[block]; ok { if _, ok := ToStateID[block]; ok {
panic(fmt.Errorf("state %#v already exist", block)) panic(fmt.Errorf("state %#v already exist", block))
} }
toStateID[block] = len(fromStateID) ToStateID[block] = len(StateList)
fromStateID = append(fromStateID, block) StateList = append(StateList, block)
} }
BitsPerBlock = bits.Len(uint(len(fromStateID))) BitsPerBlock = bits.Len(uint(len(StateList)))
}
func FromStateID(stateID int) (b Block, ok bool) {
if stateID >= 0 && stateID < len(fromStateID) {
b = fromStateID[stateID]
ok = true
}
return
} }
func DefaultBlock(id string) (b Block, ok bool) { func DefaultBlock(id string) (b Block, ok bool) {
b, ok = fromID[id] b, ok = fromID[id]
return return
} }
func ToStateID(b Block) (i int, ok bool) {
i, ok = toStateID[b]
return
}

View File

@ -12,6 +12,6 @@ type (
{{- range .}} {{- range .}}
func ({{.Name | ToGoTypeName}}) ID() string { return {{.Name | printf "%q"}} } func ({{.Name | ToGoTypeName}}) ID() string { return {{.Name | printf "%q"}} }
{{- end}} {{- end}}
var fromID = map[string]Block { {{- range .}} var FromID = map[string]Block { {{- range .}}
{{.Name | printf "%#v"}}: {{.Name | ToGoTypeName}}{},{{end}} {{.Name | printf "%#v"}}: {{.Name | ToGoTypeName}}{},{{end}}
} }

View File

@ -10,19 +10,40 @@ import (
"unsafe" "unsafe"
"github.com/Tnze/go-mc/level/block" "github.com/Tnze/go-mc/level/block"
"github.com/Tnze/go-mc/nbt"
pk "github.com/Tnze/go-mc/net/packet" pk "github.com/Tnze/go-mc/net/packet"
"github.com/Tnze/go-mc/save" "github.com/Tnze/go-mc/save"
) )
type ChunkPos struct{ X, Z int } 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 { type Chunk struct {
sync.Mutex sync.Mutex
Sections []Section Sections []Section
HeightMaps HeightMaps HeightMaps HeightMaps
} BlockEntity []BlockEntity
type HeightMaps struct {
MotionBlocking *BitStorage
WorldSurface *BitStorage
} }
func EmptyChunk(secs int) *Chunk { func EmptyChunk(secs int) *Chunk {
@ -122,7 +143,7 @@ func ChunkFromSave(c *save.Chunk, secs int) *Chunk {
blockCount++ blockCount++
} }
var ok bool var ok bool
stateRawPalette[i], ok = block.ToStateID(b) stateRawPalette[i], ok = block.ToStateID[b]
if !ok { if !ok {
panic(fmt.Errorf("state id not found: %#v", b)) 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(), WorldSurface: c.HeightMaps.MotionBlocking.Raw(),
}), }),
pk.ByteArray(data), pk.ByteArray(data),
pk.VarInt(0), // TODO: Block Entity pk.Array(c.BlockEntity),
&lightData{ &lightData{
SkyLightMask: make(pk.BitSet, (16*16*16-1)>>6+1), SkyLightMask: make(pk.BitSet, (16*16*16-1)>>6+1),
BlockLightMask: 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) }.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) { func (c *Chunk) Data() ([]byte, error) {
var buff bytes.Buffer var buff bytes.Buffer
for _, section := range c.Sections { for i := range c.Sections {
_, err := section.WriteTo(&buff) _, err := c.Sections[i].WriteTo(&buff)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -197,6 +246,47 @@ func (c *Chunk) Data() ([]byte, error) {
return buff.Bytes(), nil 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 { type Section struct {
blockCount int16 blockCount int16
States *PaletteContainer States *PaletteContainer
@ -207,7 +297,10 @@ func (s *Section) GetBlock(i int) int {
return s.States.Get(i) return s.States.Get(i)
} }
func (s *Section) SetBlock(i int, v int) { 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) { if isAir(b) {
s.blockCount-- s.blockCount--
} }
@ -260,6 +353,20 @@ func (l *lightData) WriteTo(w io.Writer) (int64, error) {
}.WriteTo(w) }.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 { func isAir(b block.Block) bool {
switch b.(type) { switch b.(type) {
case block.Air, block.CaveAir, block.VoidAir: case block.Air, block.CaveAir, block.VoidAir:

View File

@ -274,6 +274,11 @@ func (l *linearPalette) ReadFrom(r io.Reader) (n int64, err error) {
if n, err = size.ReadFrom(r); err != nil { if n, err = size.ReadFrom(r); err != nil {
return 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++ { for i := 0; i < int(size); i++ {
if nn, err := value.ReadFrom(r); err != nil { if nn, err := value.ReadFrom(r); err != nil {
return n + nn, err return n + nn, err

View File

@ -256,10 +256,9 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte) error {
vt = reflect.TypeOf([]int64{}) // pass vt = reflect.TypeOf([]int64{}) // pass
} else if vt.Kind() != reflect.Slice { } else if vt.Kind() != reflect.Slice {
return errors.New("cannot parse TagLongArray to " + vt.String() + ", it must be a slice") return errors.New("cannot parse TagLongArray to " + vt.String() + ", it must be a slice")
} else if val.Type().Elem().Kind() != reflect.Int64 {
return errors.New("cannot parse TagLongArray to " + vt.String())
} }
switch val.Type().Elem().Kind() {
case reflect.Int64:
buf := reflect.MakeSlice(vt, int(aryLen), int(aryLen)) buf := reflect.MakeSlice(vt, int(aryLen), int(aryLen))
for i := 0; i < int(aryLen); i++ { for i := 0; i < int(aryLen); i++ {
value, err := d.readLong() value, err := d.readLong()
@ -269,6 +268,19 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte) error {
buf.Index(i).SetInt(value) buf.Index(i).SetInt(value)
} }
val.Set(buf) 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())
}
case TagList: case TagList:
listType, err := d.r.ReadByte() listType, err := d.r.ReadByte()

View File

@ -62,9 +62,7 @@ func DialMCTimeout(addr string, timeout time.Duration) (*Conn, error) {
return DefaultDialer.DialMCContext(ctx, addr) return DefaultDialer.DialMCContext(ctx, addr)
} }
type Dialer struct { type Dialer net.Dialer
net.Dialer
}
func (d *Dialer) resolver() *net.Resolver { func (d *Dialer) resolver() *net.Resolver {
if d != nil && d.Resolver != nil { if d != nil && d.Resolver != nil {
@ -122,7 +120,7 @@ func (d *Dialer) DialMCContext(ctx context.Context, addr string) (*Conn, error)
defer cancel() defer cancel()
} }
} }
conn, err := d.DialContext(dialCtx, "tcp", addr) conn, err := (*net.Dialer)(d).DialContext(dialCtx, "tcp", addr)
if err != nil { if err != nil {
if firstErr == nil { if firstErr == nil {
firstErr = err firstErr = err

View File

@ -7,6 +7,7 @@ import (
) )
type Level interface { type Level interface {
Init(g *Game)
Info() LevelInfo Info() LevelInfo
PlayerJoin(p *Player) PlayerJoin(p *Player)
PlayerQuit(p *Player) PlayerQuit(p *Player)
@ -19,18 +20,20 @@ type LevelInfo struct {
type SimpleDim struct { type SimpleDim struct {
numOfSection int numOfSection int
Columns map[level.ChunkPos]*level.Chunk columns map[level.ChunkPos]*level.Chunk
} }
func (s *SimpleDim) Init(*Game) {}
func NewSimpleDim(secs int) *SimpleDim { func NewSimpleDim(secs int) *SimpleDim {
return &SimpleDim{ return &SimpleDim{
numOfSection: secs, 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) { func (s *SimpleDim) LoadChunk(pos level.ChunkPos, c *level.Chunk) {
s.Columns[pos] = c s.columns[pos] = c
} }
func (s *SimpleDim) Info() LevelInfo { func (s *SimpleDim) Info() LevelInfo {
@ -41,28 +44,16 @@ func (s *SimpleDim) Info() LevelInfo {
} }
func (s *SimpleDim) PlayerJoin(p *Player) { func (s *SimpleDim) PlayerJoin(p *Player) {
for pos, column := range s.Columns { for pos, column := range s.columns {
column.Lock() column.Lock()
packet := pk.Marshal( packet := pk.Marshal(
packetid.ClientboundLevelChunkWithLight, packetid.ClientboundLevelChunkWithLight,
pk.Int(pos.X), pk.Int(pos.Z), pos, column,
column,
) )
column.Unlock() column.Unlock()
p.WritePacket(Packet758(packet)) 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) {}
}

View File

@ -56,6 +56,7 @@ func NewGame(dim Level, components ...Component) *Game {
components: components, components: components,
handlers: make(map[int32][]*PacketHandler), handlers: make(map[int32][]*PacketHandler),
} }
dim.Init(g)
for _, v := range components { for _, v := range components {
v.Init(g) v.Init(g)
} }

View File

@ -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 { if k.updatePlayerDelay != nil {
panic("add player update handler twice") panic("add player update handler twice")
} }
k.updatePlayerDelay = f k.updatePlayerDelay = f
return k
} }
// Init implement Component for KeepAlive // Init implement Component for KeepAlive

148
server/playerinfo.go Normal file
View File

@ -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}
}

14
server/playermove.go Normal file
View File

@ -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{}
}