diff --git a/bot/world/chunk.go b/bot/world/chunk.go index 2d2a97a..36fc3a1 100644 --- a/bot/world/chunk.go +++ b/bot/world/chunk.go @@ -3,94 +3,167 @@ package world import ( "bytes" "fmt" - // "io" "github.com/Tnze/go-mc/data" pk "github.com/Tnze/go-mc/net/packet" + "math" ) -//DecodeChunkColumn decode the chunk data structure +// 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 (mask & (1 << uint(sectionY))) == 0 { // Is the given bit set in the mask? - continue - } - var ( - BlockCount pk.Short - BitsPerBlock pk.Byte - ) - if err := BlockCount.Decode(r); err != nil { - return nil, err - } - if err := BitsPerBlock.Decode(r); err != nil { - return nil, err - } - //读调色板 - var palette []uint - if BitsPerBlock < 9 { - var length pk.VarInt - if err := length.Decode(r); err != nil { - return nil, fmt.Errorf("read palette (id len) fail: %v", err) - } - palette = make([]uint, length) - - for id := uint(0); id < uint(length); id++ { - var stateID pk.VarInt - if err := stateID.Decode(r); err != nil { - return nil, fmt.Errorf("read palette (id) fail: %v", err) - } - - palette[id] = uint(stateID) + // 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 } - - //Section数据 - var DataArrayLength pk.VarInt - if err := DataArrayLength.Decode(r); err != nil { - return nil, fmt.Errorf("read DataArrayLength fail: %v", err) - } - - DataArray := make([]int64, DataArrayLength) - for i := 0; i < int(DataArrayLength); i++ { - if err := (*pk.Long)(&DataArray[i]).Decode(r); err != nil { - return nil, fmt.Errorf("read DataArray fail: %v", err) - } - } - //用数据填充区块 - fillSection(&c.sections[sectionY], perBits(byte(BitsPerBlock)), DataArray, palette) } - return &c, nil } -func perBits(BitsPerBlock byte) uint { +func perBits(BitsPerBlock byte) int { switch { case BitsPerBlock <= 4: return 4 case BitsPerBlock < 9: - return uint(BitsPerBlock) + return int(BitsPerBlock) default: - return uint(data.BitsPerBlock) // DefaultBitsPerBlock + return data.BitsPerBlock // DefaultBitsPerBlock } } -func fillSection(s *Section, bpb uint, DataArray []int64, palette []uint) { - mask := uint(1<>= offset % 64 - if offset%64 > 64-bpb { - l := 64 - offset % 64 - data |= uint(DataArray[offset/64+1] << l) +func readSection(data pk.DecodeReader) (s Section, err error) { + var BlockCount pk.Short + if err := BlockCount.Decode(data); err != nil { + return nil, fmt.Errorf("read block count error: %w", err) + } + var bpb pk.UnsignedByte + if err := bpb.Decode(data); err != nil { + return nil, fmt.Errorf("read bits per block error: %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 < 9 { + // read palettes + var length pk.VarInt + if err := length.Decode(data); err != nil { + return nil, fmt.Errorf("read palettes length error: %w", err) } - data &= mask - - if bpb < 9 { - s.blocks[n%16][n/(16*16)][n%(16*16)/16].id = palette[data] - } else { - s.blocks[n%16][n/(16*16)][n%(16*16)/16].id = data + palettes = make([]BlockStatus, length) + palettesIndex = make(map[BlockStatus]int, length) + for i := 0; i < int(length); i++ { + var v pk.VarInt + if err := v.Decode(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.Decode(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.Decode(data); err != nil { + return nil, fmt.Errorf("read dataArray[%d] error: %w", i, err) + } + dataArray[i] = uint64(v) + } + + sec := directSection{bpb: perBits(byte(bpb)), data: dataArray} + if bpb < 9 { + return &paletteSection{ + palette: palettes, + palettesIndex: palettesIndex, + directSection: sec, + }, nil + } else { + return &sec, nil + } +} + +type directSection struct { + bpb int + data []uint64 +} + +func (d *directSection) GetBlock(offset int) BlockStatus { + offset *= d.bpb + padding := offset % 64 + block := uint32(d.data[offset/64] >> padding) + if padding > 64-d.bpb { + l := 64 - padding + block |= uint32(d.data[offset/64+1] << l) + } + return BlockStatus(block & (1< 64-d.bpb { + l := padding - (64 - d.bpb) + d.data[offset/64+1] = d.data[offset/64+1]&(math.MaxUint64<>(64-padding) + } +} + +func (d *directSection) CanContain(s BlockStatus) bool { + return s <= (1<90 the player's hand will be very strange. -// func (g *Client) LookYawPitch(yaw, pitch float32) { -// g.motion <- func() { -// g.player.Yaw, g.player.Pitch = yaw, pitch -// sendPlayerLookPacket(g) //向服务器更新朝向 -// } -// } - -// // SwingHand sent when the player's arm swings. -// // if hand is true, swing the main hand -// func (g *Client) SwingHand(hand bool) { -// if hand { -// sendAnimationPacket(g, 0) -// } else { -// sendAnimationPacket(g, 1) -// } -// } - -// // Dig a block in the position and wait for it's breaked -// func (g *Client) Dig(x, y, z int) error { -// b := g.GetBlock(x, y, z).id -// sendPlayerDiggingPacket(g, 0, x, y, z, Top) //start -// sendPlayerDiggingPacket(g, 2, x, y, z, Top) //end - -// for { -// time.Sleep(time.Millisecond * 50) -// if g.GetBlock(x, y, z).id != b { -// break -// } -// g.SwingHand(true) -// } - -// return nil -// } - -// // UseItem use the item in hand. -// // if hand is true, swing the main hand -// func (g *Client) UseItem(hand bool) { -// if hand { -// sendUseItemPacket(g, 0) -// } else { -// sendUseItemPacket(g, 1) -// } -// } diff --git a/bot/world/world.go b/bot/world/world.go index 975bec6..58a7e89 100644 --- a/bot/world/world.go +++ b/bot/world/world.go @@ -4,27 +4,34 @@ import ( "github.com/Tnze/go-mc/bot/world/entity" ) -//World record all of the things in the world where player at +// 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 clolumn blocks +// Chunk store a 256*16*16 column blocks type Chunk struct { - sections [16]Section + Sections [16]Section } -//Section store a 16*16*16 cube blocks -type Section struct { - blocks [16][16][16]Block +// Section store a 16*16*16 cube blocks +type Section interface { + // GetBlock return block status, offset can be calculate by SectionOffset. + GetBlock(offset int) BlockStatus + // SetBlock is the reverse operation of GetBlock. + SetBlock(offset int, s BlockStatus) } -//Block is the base of world -type Block struct { - id uint +func SectionOffset(x, y, z int) (offset int) { + // 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 x + z*16 + y*16*16 } +type BlockStatus uint32 + type ChunkLoc struct { X, Z int } @@ -47,25 +54,18 @@ type ChunkLoc struct { // East // ) -// // getBlock return the block in the position (x, y, z) -// func (w *world) getBlock(x, y, z int) Block { -// c := w.chunks[chunkLoc{x >> 4, z >> 4}] -// if c != nil { -// cx, cy, cz := x&15, y&15, z&15 -// /* -// n = n&(16-1) - -// is equal to - -// n %= 16 -// if n < 0 { n += 16 } -// */ - -// return c.sections[y/16].blocks[cx][cy][cz] -// } - -// return Block{id: 0} -// } +// 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 { + // (n&(16-1)) == (n<0 ? n%16+16 : n%16) + if sec := c.Sections[y>>4]; sec != nil { + return sec.GetBlock(SectionOffset(x&15, y&15, z&15)) + } + } + return 0 +} // func (b Block) String() string { // return blockNameByID[b.id] diff --git a/bot/world/world_test.go b/bot/world/world_test.go deleted file mode 100644 index 641e28b..0000000 --- a/bot/world/world_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package world - -// import "testing" - -// func TestBlockString(t *testing.T) { -// for i := uint(0); i < 8598+1; i++ { -// t.Log(Block{id: i}) -// } -// }