Refactoring package go-mc/bot

This commit is contained in:
Tnze
2021-02-27 01:06:07 +08:00
parent e864580903
commit 3da9321f59
44 changed files with 564 additions and 3715 deletions

View File

@ -1,38 +0,0 @@
package world
// bitArray implements a bitfield array where values are packed into uint64
// values. If the next value does not fit into remaining space, the remaining
// space of a uint64 is unused.
type bitArray struct {
width uint // bit width of each value
valuesPerElement uint // number of values which fit into a single uint64.
data []uint64
}
// Size returns the number of elements that can fit into the bit array.
func (b *bitArray) Size() int {
return int(b.valuesPerElement) * len(b.data)
}
func (b *bitArray) Set(idx, val uint) {
var (
arrayIdx = idx / b.valuesPerElement
startBit = (idx % b.valuesPerElement) * b.width
mask = ^uint64((1<<b.width - 1) << startBit) // set for all bits except target
)
b.data[arrayIdx] = (b.data[arrayIdx] & mask) | uint64(val<<startBit)
}
func (b *bitArray) Get(idx uint) uint {
var (
arrayIdx = idx / b.valuesPerElement
offset = (idx % b.valuesPerElement) * b.width
mask = uint64((1<<b.width - 1) << offset) // set for just the target
)
return uint(b.data[arrayIdx]&mask) >> offset
}
func valuesPerBitArrayElement(bitsPerValue uint) uint {
return uint(64 / bitsPerValue)
}

View File

@ -1,50 +0,0 @@
package world
import (
"encoding/binary"
"encoding/hex"
"testing"
)
func TestBitArrayBasic(t *testing.T) {
a := bitArray{
width: 5,
valuesPerElement: valuesPerBitArrayElement(5),
data: make([]uint64, 5),
}
if got, want := a.Size(), 12*5; got != want {
t.Errorf("size = %d, want %d", got, want)
}
a.Set(0, 4)
if v := a.Get(0); v != 4 {
t.Errorf("v[0] = %d, want 4", v)
}
a.Set(12, 8)
if v := a.Get(12); v != 8 {
t.Errorf("v[12] = %d, want 8", v)
}
}
func TestBitArrayHardcoded(t *testing.T) {
d1, _ := hex.DecodeString("0020863148418841")
d2, _ := hex.DecodeString("01018A7260F68C87")
a := bitArray{
width: 5,
valuesPerElement: valuesPerBitArrayElement(5),
data: []uint64{binary.BigEndian.Uint64(d1), binary.BigEndian.Uint64(d2)},
}
if got, want := a.Size(), 12*2; got != want {
t.Errorf("size = %d, want %d", got, want)
}
want := []uint{1, 2, 2, 3, 4, 4, 5, 6, 6, 4, 8, 0, 7, 4, 3, 13, 15, 16, 9, 14, 10, 12, 0, 2}
for idx, want := range want {
if got := a.Get(uint(idx)); got != want {
t.Errorf("v[%d] = %d, want %d", idx, got, want)
}
}
}

View File

@ -1,173 +0,0 @@
package world
import (
"bytes"
"fmt"
"io"
"math"
"github.com/Tnze/go-mc/data/block"
pk "github.com/Tnze/go-mc/net/packet"
)
const maxPaletteBits = 8
// DecodeChunkColumn decode the chunk data structure.
// If decoding went error, successful decoded data will be returned.
func DecodeChunkColumn(mask int32, data []byte) (*Chunk, error) {
var c Chunk
r := bytes.NewReader(data)
for sectionY := 0; sectionY < 16; sectionY++ {
// If the section's bit set in the mask
if (mask & (1 << uint(sectionY))) != 0 {
// read section
sec, err := readSection(r)
if err != nil {
return &c, fmt.Errorf("read section[%d] error: %w", sectionY, err)
}
c.Sections[sectionY] = sec
}
}
return &c, nil
}
func perBits(bpb byte) uint {
switch {
case bpb <= 4:
return 4
case bpb <= maxPaletteBits:
return uint(bpb)
default:
return uint(block.BitsPerBlock)
}
}
func readSection(data io.Reader) (s Section, err error) {
var nonAirBlockCount pk.Short
if _, err := nonAirBlockCount.ReadFrom(data); err != nil {
return nil, fmt.Errorf("block count: %w", err)
}
var bpb pk.UnsignedByte
if _, err := bpb.ReadFrom(data); err != nil {
return nil, fmt.Errorf("bits per block: %w", err)
}
// If bpb values greater than or equal to 9, use directSection.
// Otherwise use paletteSection.
var palettes []BlockStatus
var palettesIndex map[BlockStatus]int
if bpb <= maxPaletteBits {
// read palettes
var length pk.VarInt
if _, err := length.ReadFrom(data); err != nil {
return nil, fmt.Errorf("palette length: %w", err)
}
palettes = make([]BlockStatus, length)
palettesIndex = make(map[BlockStatus]int, length)
for i := 0; i < int(length); i++ {
var v pk.VarInt
if _, err := v.ReadFrom(data); err != nil {
return nil, fmt.Errorf("read palettes[%d] error: %w", i, err)
}
palettes[i] = BlockStatus(v)
palettesIndex[BlockStatus(v)] = i
}
}
// read data array
var dataLen pk.VarInt
if _, err := dataLen.ReadFrom(data); err != nil {
return nil, fmt.Errorf("read data array length error: %w", err)
}
if int(dataLen) < 16*16*16*int(bpb)/64 {
return nil, fmt.Errorf("data length (%d) is not enough of given bpb (%d)", dataLen, bpb)
}
dataArray := make([]uint64, dataLen)
for i := 0; i < int(dataLen); i++ {
var v pk.Long
if _, err := v.ReadFrom(data); err != nil {
return nil, fmt.Errorf("read dataArray[%d] error: %w", i, err)
}
dataArray[i] = uint64(v)
}
width := perBits(byte(bpb))
sec := directSection{
bitArray{
width: width,
valuesPerElement: valuesPerBitArrayElement(width),
data: dataArray,
},
}
if bpb <= maxPaletteBits {
return &paletteSection{
palette: palettes,
palettesIndex: palettesIndex,
directSection: sec,
}, nil
} else {
return &sec, nil
}
}
type directSection struct {
bitArray
}
func (d *directSection) GetBlock(offset uint) BlockStatus {
return BlockStatus(d.Get(offset))
}
func (d *directSection) SetBlock(offset uint, s BlockStatus) {
d.Set(offset, uint(s))
}
func (d *directSection) CanContain(s BlockStatus) bool {
return s <= (1<<d.width - 1)
}
func (d *directSection) clone(bpb uint) *directSection {
out := newSectionWithSize(bpb)
for offset := uint(0); offset < 16*16*16; offset++ {
out.SetBlock(offset, d.GetBlock(offset))
}
return out
}
func newSectionWithSize(bpb uint) *directSection {
valuesPerElement := valuesPerBitArrayElement(bpb)
return &directSection{
bitArray{
width: bpb,
valuesPerElement: valuesPerElement,
data: make([]uint64, int(math.Ceil(16*16*16/float64(valuesPerElement)))),
},
}
}
type paletteSection struct {
palette []BlockStatus
palettesIndex map[BlockStatus]int
directSection
}
func (p *paletteSection) GetBlock(offset uint) BlockStatus {
v := p.directSection.GetBlock(offset)
return p.palette[v]
}
func (p *paletteSection) SetBlock(offset uint, s BlockStatus) {
if i, ok := p.palettesIndex[s]; ok {
p.directSection.SetBlock(offset, BlockStatus(i))
return
}
i := len(p.palette)
p.palette = append(p.palette, s)
p.palettesIndex[s] = i
if !p.directSection.CanContain(BlockStatus(i)) {
// Increase the underlying directSection
// Suppose that old bpb fit len(p.palette) before it appended.
// So bpb+1 must enough for new len(p.palette).
p.directSection = *p.directSection.clone(p.width + 1)
}
p.directSection.SetBlock(offset, BlockStatus(i))
}

View File

@ -1,95 +0,0 @@
package entity
import (
"io"
"github.com/Tnze/go-mc/data/entity"
item "github.com/Tnze/go-mc/data/item"
pk "github.com/Tnze/go-mc/net/packet"
"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.
type Entity struct {
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
type Slot struct {
Present bool
ItemID item.ID
Count int8
NBT pk.NBT
}
type SlotNBT struct {
data interface{}
}
//Decode implement packet.FieldDecoder interface
func (s *Slot) ReadFrom(r io.Reader) (int64, error) {
var itemID pk.VarInt
n, err := pk.Tuple{
(*pk.Boolean)(&s.Present),
pk.Opt{
Has: (*pk.Boolean)(&s.Present),
Field: pk.Tuple{
&itemID,
(*pk.Byte)(&s.Count),
&s.NBT,
},
},
}.ReadFrom(r)
if err != nil {
return n, err
}
s.ItemID = item.ID(itemID)
return n, nil
}
func (s Slot) WriteTo(w io.Writer) (int64, error) {
return pk.Tuple{
pk.Boolean(s.Present),
pk.Opt{
Has: (*pk.Boolean)(&s.Present),
Field: pk.Tuple{
pk.VarInt(s.ItemID),
pk.Byte(s.Count),
s.NBT,
},
},
}.WriteTo(w)
}
func (s Slot) String() string {
return item.ByID[s.ItemID].DisplayName
}

View File

@ -1,35 +0,0 @@
package player
import "github.com/Tnze/go-mc/bot/world/entity"
type Pos struct {
X, Y, Z float64
Yaw, Pitch float32
OnGround bool
}
func (p Pos) PosEqual(other Pos) bool {
return p.X == other.X && p.Y == other.Y && p.Z == other.Z
}
func (p Pos) LookEqual(other Pos) bool {
return p.Yaw == other.Yaw && p.Pitch == other.Pitch
}
func (p Pos) Equal(other Pos) bool {
return p.PosEqual(other) && p.LookEqual(other) && p.OnGround == other.OnGround
}
// Player includes the player's status.
type Player struct {
entity.Entity
UUID [2]int64 //128bit UUID
Pos Pos
HeldItem int //拿着的物品栏位
Health float32 //血量
Food int32 //饱食度
FoodSaturation float32 //食物饱和度
Level int32
}

View File

@ -1,21 +0,0 @@
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))
}

View File

@ -1,15 +0,0 @@
package world
import (
"sync"
"github.com/Tnze/go-mc/bot/world/entity"
)
// World record all of the things in the world where player at
type World struct {
entityLock sync.RWMutex
Entities map[int32]*entity.Entity
chunkLock sync.RWMutex
Chunks map[ChunkLoc]*Chunk
}

View File

@ -1,152 +0,0 @@
package world
import (
"github.com/Tnze/go-mc/bot/world/entity"
"github.com/Tnze/go-mc/data/block"
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
// sections.
type Chunk struct {
Sections [16]Section
TileEntities map[TilePosition]entity.BlockEntity
}
// 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
}
// 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.
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 {
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)
}
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 {
var (
bStateID = BlockStatus(b >> 12)
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
}
//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()
}
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
}

View File

@ -1,165 +0,0 @@
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 received.
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 received.
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)
}
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 received.
func (w *World) OnSpawnPlayer(pkt ptypes.SpawnPlayer) error {
w.entityLock.Lock()
defer w.entityLock.Unlock()
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 received.
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 received.
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 received.
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 received.
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
}