Basic entity tracking

This commit is contained in:
Tom
2020-09-19 15:59:03 -07:00
parent 7d1825b7e8
commit 7d122e2f8b
11 changed files with 357 additions and 158 deletions

View File

@ -2,13 +2,27 @@ package entity
import (
"github.com/Tnze/go-mc/data"
"github.com/Tnze/go-mc/data/entity"
"github.com/Tnze/go-mc/nbt"
pk "github.com/Tnze/go-mc/net/packet"
"github.com/google/uuid"
)
//Entity is the entity of minecraft
//Entity represents an instance of an entity.
type Entity struct {
EntityID int //实体ID
ID int32
Data int32
Base *entity.Entity
UUID uuid.UUID
X, Y, Z float64
Pitch, Yaw int8
VelX, VelY, VelZ int16
OnGround bool
IsLiving bool
HeadPitch int8
}
// The Slot data structure is how Minecraft represents an item and its associated data in the Minecraft Protocol

View File

@ -1,120 +1,15 @@
package world
import (
"sync"
"github.com/Tnze/go-mc/bot/world/entity"
"github.com/Tnze/go-mc/data/block"
pk "github.com/Tnze/go-mc/net/packet"
)
// World record all of the things in the world where player at
type World struct {
Entities map[int32]entity.Entity
Chunks map[ChunkLoc]*Chunk
}
// Chunk store a 256*16*16 column blocks
type Chunk struct {
Sections [16]Section
}
// Section store a 16*16*16 cube blocks
type Section interface {
// GetBlock return block status, offset can be calculate by SectionOffset.
GetBlock(offset uint) BlockStatus
// SetBlock is the reverse operation of GetBlock.
SetBlock(offset uint, s BlockStatus)
}
func SectionIdx(x, y, z int) uint {
// According to wiki.vg: Data Array is given for each block with increasing x coordinates,
// within rows of increasing z coordinates, within layers of increasing y coordinates.
// So offset equals to ( x*16^0 + z*16^1 + y*16^2 )*(bits per block).
return uint(((y & 15) << 8) | (z << 4) | x)
}
type BlockStatus uint32
type ChunkLoc struct {
X, Z int
}
// //Entity 表示一个实体
// type Entity interface {
// EntityID() int32
// }
// //Face is a face of a block
// type Face byte
// // All six faces in a block
// const (
// Bottom Face = iota
// Top
// North
// South
// West
// East
// )
// getBlock return the block in the position (x, y, z)
func (w *World) GetBlockStatus(x, y, z int) BlockStatus {
// Use n>>4 rather then n/16. It acts wrong if n<0.
c := w.Chunks[ChunkLoc{x >> 4, z >> 4}]
if c != nil && y >= 0 {
if sec := c.Sections[y>>4]; sec != nil {
return sec.GetBlock(SectionIdx(x&15, y&15, z&15))
}
}
return 0
}
func (w *World) UnloadChunk(loc ChunkLoc) {
delete(w.Chunks, loc)
}
func (w *World) UnaryBlockUpdate(pos pk.Position, bStateID BlockStatus) bool {
c := w.Chunks[ChunkLoc{X: pos.X >> 4, Z: pos.Z >> 4}]
if c == nil {
return false
}
sIdx, bIdx := pos.Y>>4, SectionIdx(pos.X&15, pos.Y&15, pos.Z&15)
if sec := c.Sections[sIdx]; sec == nil {
sec = newSectionWithSize(uint(block.BitsPerBlock))
sec.SetBlock(bIdx, bStateID)
c.Sections[sIdx] = sec
} else {
sec.SetBlock(bIdx, bStateID)
}
return true
}
func (w *World) MultiBlockUpdate(loc ChunkLoc, sectionY int, blocks []pk.VarLong) bool {
c := w.Chunks[loc]
if c == nil {
return false // not loaded
}
sec := c.Sections[sectionY]
if sec == nil {
sec = newSectionWithSize(uint(block.BitsPerBlock))
c.Sections[sectionY] = sec
}
for _, b := range blocks {
bStateID := b >> 12
x, z, y := (b>>8)&0xf, (b>>4)&0xf, b&0xf
sec.SetBlock(SectionIdx(int(x&15), int(y&15), int(z&15)), BlockStatus(bStateID))
}
return true
}
// func (b Block) String() string {
// return blockNameByID[b.id]
// }
//LoadChunk load chunk at (x, z)
func (w *World) LoadChunk(x, z int, c *Chunk) {
w.Chunks[ChunkLoc{X: x, Z: z}] = c
entityLock sync.RWMutex
Entities map[int32]*entity.Entity
chunkLock sync.RWMutex
Chunks map[ChunkLoc]*Chunk
}

106
bot/world/world_chunk.go Normal file
View File

@ -0,0 +1,106 @@
package world
import (
"github.com/Tnze/go-mc/data/block"
pk "github.com/Tnze/go-mc/net/packet"
)
// Chunk store a 256*16*16 area of blocks, sharded on the Y axis into 16
// sections.
type Chunk struct {
Sections [16]Section
}
// Section implements storage of blocks within a fixed 16*16*16 area.
type Section interface {
// GetBlock return block status, offset can be calculate by SectionOffset.
GetBlock(offset uint) BlockStatus
// SetBlock is the reverse operation of GetBlock.
SetBlock(offset uint, s BlockStatus)
}
func sectionIdx(x, y, z int) uint {
return uint(((y & 15) << 8) | (z << 4) | x)
}
type BlockStatus uint32
type ChunkLoc struct {
X, Z int
}
// GetBlockStatus return the state ID of the block at the given position.
func (w *World) GetBlockStatus(x, y, z int) BlockStatus {
w.chunkLock.RLock()
defer w.chunkLock.RUnlock()
c := w.Chunks[ChunkLoc{x >> 4, z >> 4}]
if c != nil && y >= 0 {
if sec := c.Sections[y>>4]; sec != nil {
return sec.GetBlock(sectionIdx(x&15, y&15, z&15))
}
}
return 0
}
// UnloadChunk unloads a chunk from the world.
func (w *World) UnloadChunk(loc ChunkLoc) {
w.chunkLock.Lock()
delete(w.Chunks, loc)
w.chunkLock.Unlock()
}
// UnaryBlockUpdate updates the block at the specified position with a new
// state ID.
func (w *World) UnaryBlockUpdate(pos pk.Position, bStateID BlockStatus) bool {
w.chunkLock.Lock()
defer w.chunkLock.Unlock()
c := w.Chunks[ChunkLoc{X: pos.X >> 4, Z: pos.Z >> 4}]
if c == nil {
return false
}
sIdx, bIdx := pos.Y>>4, sectionIdx(pos.X&15, pos.Y&15, pos.Z&15)
if sec := c.Sections[sIdx]; sec == nil {
sec = newSectionWithSize(uint(block.BitsPerBlock))
sec.SetBlock(bIdx, bStateID)
c.Sections[sIdx] = sec
} else {
sec.SetBlock(bIdx, bStateID)
}
return true
}
// MultiBlockUpdate updates a packed specification of blocks within a single
// section of a chunk.
func (w *World) MultiBlockUpdate(loc ChunkLoc, sectionY int, blocks []pk.VarLong) bool {
w.chunkLock.Lock()
defer w.chunkLock.Unlock()
c := w.Chunks[loc]
if c == nil {
return false // not loaded
}
sec := c.Sections[sectionY]
if sec == nil {
sec = newSectionWithSize(uint(block.BitsPerBlock))
c.Sections[sectionY] = sec
}
for _, b := range blocks {
bStateID := b >> 12
x, z, y := (b>>8)&0xf, (b>>4)&0xf, b&0xf
sec.SetBlock(sectionIdx(int(x&15), int(y&15), int(z&15)), BlockStatus(bStateID))
}
return true
}
//LoadChunk adds the given chunk to the world.
func (w *World) LoadChunk(x, z int, c *Chunk) {
w.chunkLock.Lock()
w.Chunks[ChunkLoc{X: x, Z: z}] = c
w.chunkLock.Unlock()
}

167
bot/world/world_entity.go Normal file
View File

@ -0,0 +1,167 @@
package world
import (
"fmt"
"github.com/Tnze/go-mc/bot/world/entity"
e "github.com/Tnze/go-mc/data/entity"
pk "github.com/Tnze/go-mc/net/packet"
"github.com/Tnze/go-mc/net/ptypes"
"github.com/google/uuid"
)
// PlayerEntities returns a list of players on the server within viewing range.
func (w *World) PlayerEntities() []entity.Entity {
w.entityLock.RLock()
defer w.entityLock.RUnlock()
out := make([]entity.Entity, 0, 12)
for _, ent := range w.Entities {
if ent.Base.ID == e.Player.ID {
out = append(out, *ent)
}
}
return out
}
// OnSpawnEntity should be called when a SpawnEntity packet
// is recieved.
func (w *World) OnSpawnEntity(pkt ptypes.SpawnEntity) error {
w.entityLock.Lock()
defer w.entityLock.Unlock()
base, ok := e.ByID[e.ID(pkt.Type)]
if !ok {
return fmt.Errorf("unknown entity ID %v", pkt.Type)
}
w.Entities[int32(pkt.ID)] = &entity.Entity{
ID: int32(pkt.ID),
Base: base,
Data: int32(pkt.Data),
UUID: uuid.UUID(pkt.UUID),
X: float64(pkt.X),
Y: float64(pkt.Y),
Z: float64(pkt.Z),
Pitch: int8(pkt.Pitch),
Yaw: int8(pkt.Yaw),
VelX: int16(pkt.VelX),
VelY: int16(pkt.VelY),
VelZ: int16(pkt.VelZ),
}
return nil
}
// OnSpawnLivingEntity should be called when a SpawnLivingEntity packet
// is recieved.
func (w *World) OnSpawnLivingEntity(pkt ptypes.SpawnLivingEntity) error {
w.entityLock.Lock()
defer w.entityLock.Unlock()
base, ok := e.ByID[e.ID(pkt.Type)]
if !ok {
return fmt.Errorf("unknown entity ID %v", pkt.Type)
}
// fmt.Printf("SpawnLivingEntity %q\n", base.Name)
w.Entities[int32(pkt.ID)] = &entity.Entity{
ID: int32(pkt.ID),
Base: base,
UUID: uuid.UUID(pkt.UUID),
X: float64(pkt.X),
Y: float64(pkt.Y),
Z: float64(pkt.Z),
Pitch: int8(pkt.Pitch),
Yaw: int8(pkt.Yaw),
VelX: int16(pkt.VelX),
VelY: int16(pkt.VelY),
VelZ: int16(pkt.VelZ),
HeadPitch: int8(pkt.HeadPitch),
}
return nil
}
// OnSpawnPlayer should be called when a SpawnPlayer packet
// is recieved.
func (w *World) OnSpawnPlayer(pkt ptypes.SpawnPlayer) error {
w.entityLock.Lock()
defer w.entityLock.Unlock()
// fmt.Printf("SpawnPlayer %v\n", pkt)
w.Entities[int32(pkt.ID)] = &entity.Entity{
ID: int32(pkt.ID),
Base: &e.Player,
UUID: uuid.UUID(pkt.UUID),
X: float64(pkt.X),
Y: float64(pkt.Y),
Z: float64(pkt.Z),
Pitch: int8(pkt.Pitch),
Yaw: int8(pkt.Yaw),
}
return nil
}
// OnEntityPosUpdate should be called when an EntityPosition packet
// is recieved.
func (w *World) OnEntityPosUpdate(pkt ptypes.EntityPosition) error {
w.entityLock.Lock()
defer w.entityLock.Unlock()
ent, ok := w.Entities[int32(pkt.ID)]
if !ok {
return fmt.Errorf("cannot handle position update for unknown entity %d", pkt.ID)
}
ent.X += float64(pkt.X) / (128 * 32)
ent.Y += float64(pkt.Y) / (128 * 32)
ent.Z += float64(pkt.Z) / (128 * 32)
ent.OnGround = bool(pkt.OnGround)
return nil
}
// OnEntityPosLookUpdate should be called when an EntityPositionLook packet
// is recieved.
func (w *World) OnEntityPosLookUpdate(pkt ptypes.EntityPositionLook) error {
w.entityLock.Lock()
defer w.entityLock.Unlock()
ent, ok := w.Entities[int32(pkt.ID)]
if !ok {
return fmt.Errorf("cannot handle position look update for unknown entity %d", pkt.ID)
}
ent.X += float64(pkt.X) / (128 * 32)
ent.Y += float64(pkt.Y) / (128 * 32)
ent.Z += float64(pkt.Z) / (128 * 32)
ent.OnGround = bool(pkt.OnGround)
ent.Pitch, ent.Yaw = int8(pkt.Pitch), int8(pkt.Yaw)
return nil
}
// OnEntityLookUpdate should be called when an EntityRotation packet
// is recieved.
func (w *World) OnEntityLookUpdate(pkt ptypes.EntityRotation) error {
w.entityLock.Lock()
defer w.entityLock.Unlock()
ent, ok := w.Entities[int32(pkt.ID)]
if !ok {
return fmt.Errorf("cannot handle look update for unknown entity %d", pkt.ID)
}
ent.Pitch, ent.Yaw = int8(pkt.Pitch), int8(pkt.Yaw)
ent.OnGround = bool(pkt.OnGround)
return nil
}
// OnEntityDestroy should be called when a DestroyEntities packet
// is recieved.
func (w *World) OnEntityDestroy(eIDs []pk.VarInt) error {
w.entityLock.Lock()
defer w.entityLock.Unlock()
for _, eID := range eIDs {
delete(w.Entities, int32(eID))
}
return nil
}