Track and update tile entities
This commit is contained in:
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/bot/world"
|
"github.com/Tnze/go-mc/bot/world"
|
||||||
|
"github.com/Tnze/go-mc/bot/world/entity"
|
||||||
"github.com/Tnze/go-mc/bot/world/entity/player"
|
"github.com/Tnze/go-mc/bot/world/entity/player"
|
||||||
"github.com/Tnze/go-mc/chat"
|
"github.com/Tnze/go-mc/chat"
|
||||||
"github.com/Tnze/go-mc/data"
|
"github.com/Tnze/go-mc/data"
|
||||||
@ -186,6 +187,8 @@ func (c *Client) handlePacket(p pk.Packet) (disconnect bool, err error) {
|
|||||||
err = handleMultiBlockChangePacket(c, p)
|
err = handleMultiBlockChangePacket(c, p)
|
||||||
case data.UnloadChunk:
|
case data.UnloadChunk:
|
||||||
err = handleUnloadChunkPacket(c, p)
|
err = handleUnloadChunkPacket(c, p)
|
||||||
|
case data.TileEntityData:
|
||||||
|
err = handleTileEntityDataPacket(c, p)
|
||||||
|
|
||||||
case data.PositionClientbound:
|
case data.PositionClientbound:
|
||||||
err = handlePlayerPositionAndLookPacket(c, p)
|
err = handlePlayerPositionAndLookPacket(c, p)
|
||||||
@ -623,11 +626,23 @@ func handleChunkDataPacket(c *Client, p pk.Packet) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("decode chunk column: %w", err)
|
return fmt.Errorf("decode chunk column: %w", err)
|
||||||
}
|
}
|
||||||
|
chunk.TileEntities = make(map[world.TilePosition]entity.BlockEntity, 64)
|
||||||
|
for _, e := range pkt.BlockEntities {
|
||||||
|
chunk.TileEntities[world.ToTilePos(e.X, e.Y, e.Z)] = e
|
||||||
|
}
|
||||||
|
|
||||||
c.Wd.LoadChunk(int(pkt.X), int(pkt.Z), chunk)
|
c.Wd.LoadChunk(int(pkt.X), int(pkt.Z), chunk)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleTileEntityDataPacket(c *Client, p pk.Packet) error {
|
||||||
|
var pkt ptypes.TileEntityData
|
||||||
|
if err := pkt.Decode(p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.Wd.TileEntityUpdate(pkt)
|
||||||
|
}
|
||||||
|
|
||||||
func handlePlayerPositionAndLookPacket(c *Client, p pk.Packet) error {
|
func handlePlayerPositionAndLookPacket(c *Client, p pk.Packet) error {
|
||||||
var pkt ptypes.PositionAndLookClientbound
|
var pkt ptypes.PositionAndLookClientbound
|
||||||
if err := pkt.Decode(p); err != nil {
|
if err := pkt.Decode(p); err != nil {
|
||||||
|
@ -8,6 +8,23 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// BlockEntity describes the representation of a tile entity at a position.
|
||||||
|
type BlockEntity struct {
|
||||||
|
ID string `nbt:"id"`
|
||||||
|
|
||||||
|
// global co-ordinates
|
||||||
|
X int `nbt:"x"`
|
||||||
|
Y int `nbt:"y"`
|
||||||
|
Z int `nbt:"z"`
|
||||||
|
|
||||||
|
// sign-specific.
|
||||||
|
Color string `nbt:"color"`
|
||||||
|
Text1 string `nbt:"Text1"`
|
||||||
|
Text2 string `nbt:"Text2"`
|
||||||
|
Text3 string `nbt:"Text3"`
|
||||||
|
Text4 string `nbt:"Text4"`
|
||||||
|
}
|
||||||
|
|
||||||
//Entity represents an instance of an entity.
|
//Entity represents an instance of an entity.
|
||||||
type Entity struct {
|
type Entity struct {
|
||||||
ID int32
|
ID int32
|
||||||
|
21
bot/world/tile.go
Normal file
21
bot/world/tile.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package world
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TilePosition describes the location of a tile/block entity within a chunk.
|
||||||
|
type TilePosition uint32
|
||||||
|
|
||||||
|
func (p TilePosition) Pos() (x, y, z int) {
|
||||||
|
return int((p>>8) & 0xff), int((p>>16) & 0xff), int(p&0xff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p TilePosition) String() string {
|
||||||
|
x, y, z := p.Pos()
|
||||||
|
return fmt.Sprintf("(%d, %d, %d)", x, y, z)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToTilePos(x, y, z int) TilePosition {
|
||||||
|
return TilePosition((y&0xff) << 16 | (x&15) << 8 | (z&15))
|
||||||
|
}
|
@ -1,14 +1,17 @@
|
|||||||
package world
|
package world
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/Tnze/go-mc/bot/world/entity"
|
||||||
"github.com/Tnze/go-mc/data/block"
|
"github.com/Tnze/go-mc/data/block"
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
|
"github.com/Tnze/go-mc/net/ptypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Chunk store a 256*16*16 area of blocks, sharded on the Y axis into 16
|
// Chunk store a 256*16*16 area of blocks, sharded on the Y axis into 16
|
||||||
// sections.
|
// sections.
|
||||||
type Chunk struct {
|
type Chunk struct {
|
||||||
Sections [16]Section
|
Sections [16]Section
|
||||||
|
TileEntities map[TilePosition]entity.BlockEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
// Section implements storage of blocks within a fixed 16*16*16 area.
|
// Section implements storage of blocks within a fixed 16*16*16 area.
|
||||||
@ -29,6 +32,23 @@ type ChunkLoc struct {
|
|||||||
X, Z int
|
X, Z int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Signs returns a list of signs on the server within viewing range.
|
||||||
|
func (w *World) Signs() []entity.BlockEntity {
|
||||||
|
w.chunkLock.RLock()
|
||||||
|
defer w.chunkLock.RUnlock()
|
||||||
|
|
||||||
|
out := make([]entity.BlockEntity, 0, 4)
|
||||||
|
for _, c := range w.Chunks {
|
||||||
|
for _, e := range c.TileEntities {
|
||||||
|
if e.ID == "minecraft:sign" {
|
||||||
|
out = append(out, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// GetBlockStatus return the state ID of the block at the given position.
|
// GetBlockStatus return the state ID of the block at the given position.
|
||||||
func (w *World) GetBlockStatus(x, y, z int) BlockStatus {
|
func (w *World) GetBlockStatus(x, y, z int) BlockStatus {
|
||||||
w.chunkLock.RLock()
|
w.chunkLock.RLock()
|
||||||
@ -67,6 +87,10 @@ func (w *World) UnaryBlockUpdate(pos pk.Position, bStateID BlockStatus) bool {
|
|||||||
sec.SetBlock(bIdx, bStateID)
|
sec.SetBlock(bIdx, bStateID)
|
||||||
c.Sections[sIdx] = sec
|
c.Sections[sIdx] = sec
|
||||||
} else {
|
} else {
|
||||||
|
tp := ToTilePos(pos.X, pos.Y, pos.Z)
|
||||||
|
if _, ok := c.TileEntities[tp]; ok && sec.GetBlock(bIdx) != bStateID {
|
||||||
|
delete(c.TileEntities, tp)
|
||||||
|
}
|
||||||
sec.SetBlock(bIdx, bStateID)
|
sec.SetBlock(bIdx, bStateID)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -90,9 +114,16 @@ func (w *World) MultiBlockUpdate(loc ChunkLoc, sectionY int, blocks []pk.VarLong
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, b := range blocks {
|
for _, b := range blocks {
|
||||||
bStateID := b >> 12
|
var (
|
||||||
x, z, y := (b>>8)&0xf, (b>>4)&0xf, b&0xf
|
bStateID = BlockStatus(b >> 12)
|
||||||
sec.SetBlock(sectionIdx(int(x&15), int(y&15), int(z&15)), BlockStatus(bStateID))
|
x, z, y = (b >> 8) & 0xf, (b >> 4) & 0xf, b & 0xf
|
||||||
|
bIdx = sectionIdx(int(x&15), int(y&15), int(z&15))
|
||||||
|
tp = ToTilePos(int(x), int(y), int(z))
|
||||||
|
)
|
||||||
|
if _, ok := c.TileEntities[tp]; ok && sec.GetBlock(bIdx) != bStateID {
|
||||||
|
delete(c.TileEntities, tp)
|
||||||
|
}
|
||||||
|
sec.SetBlock(bIdx, bStateID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@ -104,3 +135,18 @@ func (w *World) LoadChunk(x, z int, c *Chunk) {
|
|||||||
w.Chunks[ChunkLoc{X: x, Z: z}] = c
|
w.Chunks[ChunkLoc{X: x, Z: z}] = c
|
||||||
w.chunkLock.Unlock()
|
w.chunkLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *World) TileEntityUpdate(pkt ptypes.TileEntityData) error {
|
||||||
|
w.chunkLock.Lock()
|
||||||
|
defer w.chunkLock.Unlock()
|
||||||
|
c := w.Chunks[ChunkLoc{X: pkt.Pos.X >> 4, Z: pkt.Pos.Z >> 4}]
|
||||||
|
if c == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch pkt.Action {
|
||||||
|
case 9: // Sign update
|
||||||
|
c.TileEntities[ToTilePos(pkt.Pos.X, pkt.Pos.Y, pkt.Pos.Z)] = pkt.Data
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/bot/world/entity"
|
||||||
"github.com/Tnze/go-mc/nbt"
|
"github.com/Tnze/go-mc/nbt"
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
)
|
)
|
||||||
@ -77,8 +78,7 @@ func (b *biomesData) Decode(r pk.DecodeReader) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type chunkData []byte
|
type chunkData []byte
|
||||||
type blockEntities []blockEntity
|
type blockEntities []entity.BlockEntity
|
||||||
type blockEntity struct{}
|
|
||||||
|
|
||||||
// Decode implement net.packet.FieldDecoder
|
// Decode implement net.packet.FieldDecoder
|
||||||
func (c *chunkData) Decode(r pk.DecodeReader) error {
|
func (c *chunkData) Decode(r pk.DecodeReader) error {
|
||||||
@ -108,3 +108,21 @@ func (b *blockEntities) Decode(r pk.DecodeReader) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TileEntityData describes a change to a tile entity.
|
||||||
|
type TileEntityData struct {
|
||||||
|
Pos pk.Position
|
||||||
|
Action pk.UnsignedByte
|
||||||
|
Data entity.BlockEntity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TileEntityData) Decode(pkt pk.Packet) error {
|
||||||
|
r := bytes.NewReader(pkt.Data)
|
||||||
|
if err := p.Pos.Decode(r); err != nil {
|
||||||
|
return fmt.Errorf("position: %v", err)
|
||||||
|
}
|
||||||
|
if err := p.Action.Decode(r); err != nil {
|
||||||
|
return fmt.Errorf("action: %v", err)
|
||||||
|
}
|
||||||
|
return nbt.NewDecoder(r).Decode(&p.Data)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user