Merge pull request #57 from Tnze/patch-chunkdata
Rewrite chunk data decoder
This commit is contained in:
@ -3,94 +3,167 @@ package world
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
// "io"
|
|
||||||
"github.com/Tnze/go-mc/data"
|
"github.com/Tnze/go-mc/data"
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
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) {
|
func DecodeChunkColumn(mask int32, data []byte) (*Chunk, error) {
|
||||||
var c Chunk
|
var c Chunk
|
||||||
r := bytes.NewReader(data)
|
r := bytes.NewReader(data)
|
||||||
for sectionY := 0; sectionY < 16; sectionY++ {
|
for sectionY := 0; sectionY < 16; sectionY++ {
|
||||||
if (mask & (1 << uint(sectionY))) == 0 { // Is the given bit set in the mask?
|
// If the section's bit set in the mask
|
||||||
continue
|
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)
|
||||||
}
|
}
|
||||||
var (
|
c.Sections[sectionY] = sec
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//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
|
return &c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func perBits(BitsPerBlock byte) uint {
|
func perBits(BitsPerBlock byte) int {
|
||||||
switch {
|
switch {
|
||||||
case BitsPerBlock <= 4:
|
case BitsPerBlock <= 4:
|
||||||
return 4
|
return 4
|
||||||
case BitsPerBlock < 9:
|
case BitsPerBlock < 9:
|
||||||
return uint(BitsPerBlock)
|
return int(BitsPerBlock)
|
||||||
default:
|
default:
|
||||||
return uint(data.BitsPerBlock) // DefaultBitsPerBlock
|
return data.BitsPerBlock // DefaultBitsPerBlock
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fillSection(s *Section, bpb uint, DataArray []int64, palette []uint) {
|
func readSection(data pk.DecodeReader) (s Section, err error) {
|
||||||
mask := uint(1<<bpb - 1)
|
var BlockCount pk.Short
|
||||||
for n := 0; n < 16*16*16; n++ {
|
if err := BlockCount.Decode(data); err != nil {
|
||||||
offset := uint(n * int(bpb))
|
return nil, fmt.Errorf("read block count error: %w", err)
|
||||||
data := uint(DataArray[offset/64])
|
|
||||||
data >>= offset % 64
|
|
||||||
if offset%64 > 64-bpb {
|
|
||||||
l := 64 - offset % 64
|
|
||||||
data |= uint(DataArray[offset/64+1] << l)
|
|
||||||
}
|
}
|
||||||
data &= mask
|
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 {
|
if bpb < 9 {
|
||||||
s.blocks[n%16][n/(16*16)][n%(16*16)/16].id = palette[data]
|
// read palettes
|
||||||
} else {
|
var length pk.VarInt
|
||||||
s.blocks[n%16][n/(16*16)][n%(16*16)/16].id = data
|
if err := length.Decode(data); err != nil {
|
||||||
|
return nil, fmt.Errorf("read palettes length error: %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.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<<d.bpb - 1)) // mask
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *directSection) SetBlock(offset int, s BlockStatus) {
|
||||||
|
offset *= d.bpb
|
||||||
|
padding := offset % 64
|
||||||
|
mask := uint64(math.MaxUint64<<(padding+d.bpb) | (1<<padding - 1))
|
||||||
|
d.data[offset/64] = d.data[offset/64]&mask | uint64(s)<<padding
|
||||||
|
if padding > 64-d.bpb {
|
||||||
|
l := padding - (64 - d.bpb)
|
||||||
|
d.data[offset/64+1] = d.data[offset/64+1]&(math.MaxUint64<<l) | uint64(s)>>(64-padding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *directSection) CanContain(s BlockStatus) bool {
|
||||||
|
return s <= (1<<d.bpb - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *directSection) clone(bpb int) *directSection {
|
||||||
|
newSection := &directSection{
|
||||||
|
bpb: bpb,
|
||||||
|
data: make([]uint64, 16*16*16*bpb/64),
|
||||||
|
}
|
||||||
|
for offset := 0; offset < 16*16*16; offset++ {
|
||||||
|
newSection.SetBlock(offset, d.GetBlock(offset))
|
||||||
|
}
|
||||||
|
return newSection
|
||||||
|
}
|
||||||
|
|
||||||
|
type paletteSection struct {
|
||||||
|
palette []BlockStatus
|
||||||
|
palettesIndex map[BlockStatus]int
|
||||||
|
directSection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *paletteSection) GetBlock(offset int) BlockStatus {
|
||||||
|
v := p.directSection.GetBlock(offset)
|
||||||
|
return p.palette[v]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *paletteSection) SetBlock(offset int, 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.bpb + 1)
|
||||||
|
}
|
||||||
|
p.directSection.SetBlock(offset, BlockStatus(i))
|
||||||
|
}
|
||||||
|
78
bot/world/chunk_test.go
Normal file
78
bot/world/chunk_test.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package world
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Tnze/go-mc/data"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newDirectSection(bpb int) Section {
|
||||||
|
return &directSection{
|
||||||
|
bpb: bpb,
|
||||||
|
data: make([]uint64, 16*16*16*bpb/64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDirectSection(t *testing.T) {
|
||||||
|
for bpb := 4; bpb <= data.BitsPerBlock; bpb++ {
|
||||||
|
testSection(newDirectSection(bpb), bpb)(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDirectSection_clone(t *testing.T) {
|
||||||
|
s := newDirectSection(9)
|
||||||
|
dataset := randData(9)
|
||||||
|
for i := 0; i < 16*16*16; i++ {
|
||||||
|
s.SetBlock(i, dataset[i])
|
||||||
|
}
|
||||||
|
s = s.(*directSection).clone(data.BitsPerBlock)
|
||||||
|
for i := 0; i < 16*16*16; i++ {
|
||||||
|
if s := s.GetBlock(i); dataset[i] != s {
|
||||||
|
t.Fatalf("direct section error: want: %v, get %v", dataset[i], s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPaletteSection(t *testing.T) {
|
||||||
|
t.Run("Correctness", testSection(&paletteSection{
|
||||||
|
palettesIndex: make(map[BlockStatus]int),
|
||||||
|
directSection: *(newDirectSection(7).(*directSection)),
|
||||||
|
}, 7))
|
||||||
|
t.Run("AutomaticExpansion", testSection(&paletteSection{
|
||||||
|
palettesIndex: make(map[BlockStatus]int),
|
||||||
|
directSection: *(newDirectSection(4).(*directSection)),
|
||||||
|
}, 9))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSection(s Section, bpb int) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
for _, dataset := range [][16 * 16 * 16]BlockStatus{secData(bpb), randData(bpb)} {
|
||||||
|
for i := 0; i < 16*16*16; i++ {
|
||||||
|
s.SetBlock(i, dataset[i])
|
||||||
|
}
|
||||||
|
for i := 0; i < 16*16*16; i++ {
|
||||||
|
if v := s.GetBlock(i); dataset[i] != v {
|
||||||
|
t.Fatalf("direct section error: want: %v, get %v", dataset[i], v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func secData(bpb int) (data [16 * 16 * 16]BlockStatus) {
|
||||||
|
mask := 1<<bpb - 1
|
||||||
|
var v int
|
||||||
|
for i := range data {
|
||||||
|
data[i] = BlockStatus(v)
|
||||||
|
v = (v + 1) & mask
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func randData(bpb int) (data [16 * 16 * 16]BlockStatus) {
|
||||||
|
data = secData(bpb)
|
||||||
|
rand.Shuffle(len(data), func(i, j int) {
|
||||||
|
data[i], data[j] = data[j], data[i]
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
@ -1,78 +0,0 @@
|
|||||||
package world
|
|
||||||
|
|
||||||
// import (
|
|
||||||
// "math"
|
|
||||||
// "time"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// // SetPosition method move your character around.
|
|
||||||
// // Server will ignore this if changes too much.
|
|
||||||
// func (g *Client) SetPosition(x, y, z float64, onGround bool) {
|
|
||||||
// g.motion <- func() {
|
|
||||||
// g.player.X, g.player.Y, g.player.Z = x, y, z
|
|
||||||
// g.player.OnGround = onGround
|
|
||||||
// sendPlayerPositionPacket(g) //向服务器更新位置
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // LookAt method turn player's hand and make it look at a point.
|
|
||||||
// func (g *Client) LookAt(x, y, z float64) {
|
|
||||||
// x0, y0, z0 := g.player.X, g.player.Y, g.player.Z
|
|
||||||
// x, y, z = x-x0, y-y0, z-z0
|
|
||||||
|
|
||||||
// r := math.Sqrt(x*x + y*y + z*z)
|
|
||||||
// yaw := -math.Atan2(x, z) / math.Pi * 180
|
|
||||||
// for yaw < 0 {
|
|
||||||
// yaw = 360 + yaw
|
|
||||||
// }
|
|
||||||
// pitch := -math.Asin(y/r) / math.Pi * 180
|
|
||||||
|
|
||||||
// g.LookYawPitch(float32(yaw), float32(pitch))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // LookYawPitch set player's hand to the direct by yaw and pitch.
|
|
||||||
// // yaw can be [0, 360) and pitch can be (-180, 180).
|
|
||||||
// // if |pitch|>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)
|
|
||||||
// }
|
|
||||||
// }
|
|
@ -4,27 +4,34 @@ import (
|
|||||||
"github.com/Tnze/go-mc/bot/world/entity"
|
"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 {
|
type World struct {
|
||||||
Entities map[int32]entity.Entity
|
Entities map[int32]entity.Entity
|
||||||
Chunks map[ChunkLoc]*Chunk
|
Chunks map[ChunkLoc]*Chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
//Chunk store a 256*16*16 clolumn blocks
|
// Chunk store a 256*16*16 column blocks
|
||||||
type Chunk struct {
|
type Chunk struct {
|
||||||
sections [16]Section
|
Sections [16]Section
|
||||||
}
|
}
|
||||||
|
|
||||||
//Section store a 16*16*16 cube blocks
|
// Section store a 16*16*16 cube blocks
|
||||||
type Section struct {
|
type Section interface {
|
||||||
blocks [16][16][16]Block
|
// 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
|
func SectionOffset(x, y, z int) (offset int) {
|
||||||
type Block struct {
|
// According to wiki.vg: Data Array is given for each block with increasing x coordinates,
|
||||||
id uint
|
// 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 {
|
type ChunkLoc struct {
|
||||||
X, Z int
|
X, Z int
|
||||||
}
|
}
|
||||||
@ -47,25 +54,18 @@ type ChunkLoc struct {
|
|||||||
// East
|
// East
|
||||||
// )
|
// )
|
||||||
|
|
||||||
// // getBlock return the block in the position (x, y, z)
|
// getBlock return the block in the position (x, y, z)
|
||||||
// func (w *world) getBlock(x, y, z int) Block {
|
func (w *World) GetBlockStatus(x, y, z int) BlockStatus {
|
||||||
// c := w.chunks[chunkLoc{x >> 4, z >> 4}]
|
// Use n>>4 rather then n/16. It acts wrong if n<0.
|
||||||
// if c != nil {
|
c := w.Chunks[ChunkLoc{x >> 4, z >> 4}]
|
||||||
// cx, cy, cz := x&15, y&15, z&15
|
if c != nil {
|
||||||
// /*
|
// (n&(16-1)) == (n<0 ? n%16+16 : n%16)
|
||||||
// n = n&(16-1)
|
if sec := c.Sections[y>>4]; sec != nil {
|
||||||
|
return sec.GetBlock(SectionOffset(x&15, y&15, z&15))
|
||||||
// is equal to
|
}
|
||||||
|
}
|
||||||
// n %= 16
|
return 0
|
||||||
// if n < 0 { n += 16 }
|
}
|
||||||
// */
|
|
||||||
|
|
||||||
// return c.sections[y/16].blocks[cx][cy][cz]
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return Block{id: 0}
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (b Block) String() string {
|
// func (b Block) String() string {
|
||||||
// return blockNameByID[b.id]
|
// return blockNameByID[b.id]
|
||||||
|
@ -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})
|
|
||||||
// }
|
|
||||||
// }
|
|
Reference in New Issue
Block a user