bot can receive chunks now
This commit is contained in:
@ -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
|
||||||
|
@ -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
66
bot/world/chunks.go
Normal 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
8
bot/world/events.go
Normal 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
|
||||||
|
}
|
@ -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])
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -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}}
|
||||||
}
|
}
|
125
level/chunk.go
125
level/chunk.go
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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) {}
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
148
server/playerinfo.go
Normal 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
14
server/playermove.go
Normal 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{}
|
||||||
|
}
|
Reference in New Issue
Block a user