Files
go-mc/bot/world/chunk.go
2020-05-24 12:40:01 +08:00

170 lines
4.4 KiB
Go

package world
import (
"bytes"
"fmt"
"github.com/Tnze/go-mc/data"
pk "github.com/Tnze/go-mc/net/packet"
)
// 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(BitsPerBlock byte) int {
switch {
case BitsPerBlock <= 4:
return 4
case BitsPerBlock < 9:
return int(BitsPerBlock)
default:
return data.BitsPerBlock // DefaultBitsPerBlock
}
}
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)
}
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((1<<d.bpb - 1) << padding)
d.data[offset/64] = d.data[offset/64]&mask | uint64(s)<<padding
if padding > 64-d.bpb {
l := padding - (64 - d.bpb)
const maxUint64 = 1<<64 - 1
d.data[offset/64+1] = d.data[offset/64+1]&(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))
}