462 lines
12 KiB
Go
462 lines
12 KiB
Go
package level
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math/bits"
|
|
"strconv"
|
|
|
|
"github.com/Tnze/go-mc/level/block"
|
|
"github.com/Tnze/go-mc/nbt"
|
|
pk "github.com/Tnze/go-mc/net/packet"
|
|
"github.com/Tnze/go-mc/save"
|
|
)
|
|
|
|
type ChunkPos [2]int32
|
|
|
|
func (c ChunkPos) WriteTo(w io.Writer) (n int64, err error) {
|
|
n, err = pk.Int(c[0]).WriteTo(w)
|
|
if err != nil {
|
|
return
|
|
}
|
|
n1, err := pk.Int(c[1]).WriteTo(w)
|
|
return n + n1, err
|
|
}
|
|
|
|
func (c *ChunkPos) ReadFrom(r io.Reader) (n int64, err error) {
|
|
var x, z pk.Int
|
|
if n, err = x.ReadFrom(r); err != nil {
|
|
return n, err
|
|
}
|
|
var n1 int64
|
|
if n1, err = z.ReadFrom(r); err != nil {
|
|
return n + n1, err
|
|
}
|
|
*c = ChunkPos{int32(x), int32(z)}
|
|
return n + n1, nil
|
|
}
|
|
|
|
type Chunk struct {
|
|
Sections []Section
|
|
HeightMaps HeightMaps
|
|
BlockEntity []BlockEntity
|
|
Status ChunkStatus
|
|
}
|
|
|
|
func EmptyChunk(secs int) *Chunk {
|
|
sections := make([]Section, secs)
|
|
for i := range sections {
|
|
sections[i] = Section{
|
|
BlockCount: 0,
|
|
States: NewStatesPaletteContainer(16*16*16, 0),
|
|
Biomes: NewBiomesPaletteContainer(4*4*4, 0),
|
|
}
|
|
}
|
|
return &Chunk{
|
|
Sections: sections,
|
|
HeightMaps: HeightMaps{
|
|
WorldSurfaceWG: NewBitStorage(bits.Len(uint(secs)*16+1), 16*16, nil),
|
|
WorldSurface: NewBitStorage(bits.Len(uint(secs)*16+1), 16*16, nil),
|
|
OceanFloorWG: NewBitStorage(bits.Len(uint(secs)*16+1), 16*16, nil),
|
|
OceanFloor: NewBitStorage(bits.Len(uint(secs)*16+1), 16*16, nil),
|
|
MotionBlocking: NewBitStorage(bits.Len(uint(secs)*16+1), 16*16, nil),
|
|
MotionBlockingNoLeaves: NewBitStorage(bits.Len(uint(secs)*16+1), 16*16, nil),
|
|
},
|
|
Status: StatusEmpty,
|
|
}
|
|
}
|
|
|
|
// ChunkFromSave convert save.Chunk to level.Chunk.
|
|
func ChunkFromSave(c *save.Chunk) (*Chunk, error) {
|
|
secs := len(c.Sections)
|
|
sections := make([]Section, secs)
|
|
for _, v := range c.Sections {
|
|
i := int32(v.Y) - c.YPos
|
|
if i < 0 || i >= int32(secs) {
|
|
return nil, fmt.Errorf("section Y value %d out of bounds", v.Y)
|
|
}
|
|
var err error
|
|
sections[i].States, err = readStatesPalette(v.BlockStates.Palette, v.BlockStates.Data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sections[i].BlockCount = countNoneAirBlocks(§ions[i])
|
|
sections[i].Biomes, err = readBiomesPalette(v.Biomes.Palette, v.Biomes.Data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sections[i].SkyLight = v.SkyLight
|
|
sections[i].BlockLight = v.BlockLight
|
|
}
|
|
|
|
blockEntities := make([]BlockEntity, len(c.BlockEntities))
|
|
for i, v := range c.BlockEntities {
|
|
var tmp struct {
|
|
ID string `nbt:"id"`
|
|
X int32 `nbt:"x"`
|
|
Y int32 `nbt:"y"`
|
|
Z int32 `nbt:"z"`
|
|
}
|
|
if err := v.Unmarshal(&tmp); err != nil {
|
|
return nil, err
|
|
}
|
|
blockEntities[i].Data = v
|
|
if x, z := int(tmp.X-c.XPos<<4), int(tmp.Z-c.ZPos<<4); !blockEntities[i].PackXZ(x, z) {
|
|
return nil, errors.New("Packing a XZ(" + strconv.Itoa(x) + ", " + strconv.Itoa(z) + ") out of bound")
|
|
}
|
|
blockEntities[i].Y = int16(tmp.Y)
|
|
blockEntities[i].Type = block.EntityTypes[tmp.ID]
|
|
}
|
|
|
|
bitsForHeight := bits.Len( /* chunk height in blocks */ uint(secs)*16 + 1)
|
|
return &Chunk{
|
|
Sections: sections,
|
|
HeightMaps: HeightMaps{
|
|
WorldSurface: NewBitStorage(bitsForHeight, 16*16, c.Heightmaps["WORLD_SURFACE_WG"]),
|
|
WorldSurfaceWG: NewBitStorage(bitsForHeight, 16*16, c.Heightmaps["WORLD_SURFACE"]),
|
|
OceanFloorWG: NewBitStorage(bitsForHeight, 16*16, c.Heightmaps["OCEAN_FLOOR_WG"]),
|
|
OceanFloor: NewBitStorage(bitsForHeight, 16*16, c.Heightmaps["OCEAN_FLOOR"]),
|
|
MotionBlocking: NewBitStorage(bitsForHeight, 16*16, c.Heightmaps["MOTION_BLOCKING"]),
|
|
MotionBlockingNoLeaves: NewBitStorage(bitsForHeight, 16*16, c.Heightmaps["MOTION_BLOCKING_NO_LEAVES"]),
|
|
},
|
|
BlockEntity: blockEntities,
|
|
Status: ChunkStatus(c.Status),
|
|
}, nil
|
|
}
|
|
|
|
func readStatesPalette(palette []save.BlockState, data []uint64) (paletteData *PaletteContainer[BlocksState], err error) {
|
|
statePalette := make([]BlocksState, len(palette))
|
|
for i, v := range palette {
|
|
b, ok := block.FromID[v.Name]
|
|
if !ok {
|
|
return nil, fmt.Errorf("unknown block id: %v", v.Name)
|
|
}
|
|
if v.Properties.Data != nil {
|
|
if err := v.Properties.Unmarshal(&b); err != nil {
|
|
return nil, fmt.Errorf("unmarshal block properties fail: %v", err)
|
|
}
|
|
}
|
|
s, ok := block.ToStateID[b]
|
|
if !ok {
|
|
return nil, fmt.Errorf("unknown block: %v", b)
|
|
}
|
|
statePalette[i] = s
|
|
}
|
|
paletteData = NewStatesPaletteContainerWithData(16*16*16, data, statePalette)
|
|
return
|
|
}
|
|
|
|
func readBiomesPalette(palette []save.BiomeState, data []uint64) (*PaletteContainer[BiomesState], error) {
|
|
biomesRawPalette := make([]BiomesState, len(palette))
|
|
for i, v := range palette {
|
|
err := biomesRawPalette[i].UnmarshalText([]byte(v))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return NewBiomesPaletteContainerWithData(4*4*4, data, biomesRawPalette), nil
|
|
}
|
|
|
|
func countNoneAirBlocks(sec *Section) (blockCount int16) {
|
|
for i := 0; i < 16*16*16; i++ {
|
|
b := sec.GetBlock(i)
|
|
if !block.IsAir(b) {
|
|
blockCount++
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// ChunkToSave convert level.Chunk to save.Chunk
|
|
func ChunkToSave(c *Chunk, dst *save.Chunk) (err error) {
|
|
secs := len(c.Sections)
|
|
sections := make([]save.Section, secs)
|
|
for i, v := range c.Sections {
|
|
s := §ions[i]
|
|
states := &s.BlockStates
|
|
biomes := &s.Biomes
|
|
s.Y = int8(int32(i) + dst.YPos)
|
|
states.Palette, states.Data, err = writeStatesPalette(v.States)
|
|
if err != nil {
|
|
return
|
|
}
|
|
biomes.Palette, biomes.Data, err = writeBiomesPalette(v.Biomes)
|
|
if err != nil {
|
|
return
|
|
}
|
|
s.SkyLight = v.SkyLight
|
|
s.BlockLight = v.BlockLight
|
|
}
|
|
dst.Sections = sections
|
|
dst.Heightmaps["WORLD_SURFACE_WG"] = c.HeightMaps.WorldSurfaceWG.Raw()
|
|
dst.Heightmaps["WORLD_SURFACE"] = c.HeightMaps.WorldSurface.Raw()
|
|
dst.Heightmaps["OCEAN_FLOOR_WG"] = c.HeightMaps.OceanFloorWG.Raw()
|
|
dst.Heightmaps["OCEAN_FLOOR"] = c.HeightMaps.OceanFloor.Raw()
|
|
dst.Heightmaps["MOTION_BLOCKING"] = c.HeightMaps.MotionBlocking.Raw()
|
|
dst.Heightmaps["MOTION_BLOCKING_NO_LEAVES"] = c.HeightMaps.MotionBlockingNoLeaves.Raw()
|
|
dst.Status = string(c.Status)
|
|
return
|
|
}
|
|
|
|
func writeStatesPalette(paletteData *PaletteContainer[BlocksState]) (palette []save.BlockState, data []uint64, err error) {
|
|
rawPalette := paletteData.palette.export()
|
|
palette = make([]save.BlockState, len(rawPalette))
|
|
|
|
var buffer bytes.Buffer
|
|
for i, v := range rawPalette {
|
|
b := block.StateList[v]
|
|
palette[i].Name = b.ID()
|
|
|
|
buffer.Reset()
|
|
err = nbt.NewEncoder(&buffer).Encode(b, "")
|
|
if err != nil {
|
|
return
|
|
}
|
|
_, err = nbt.NewDecoder(&buffer).Decode(&palette[i].Properties)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
data = make([]uint64, len(paletteData.data.Raw()))
|
|
copy(data, paletteData.data.Raw())
|
|
return
|
|
}
|
|
|
|
func writeBiomesPalette(paletteData *PaletteContainer[BiomesState]) (palette []save.BiomeState, data []uint64, err error) {
|
|
rawPalette := paletteData.palette.export()
|
|
palette = make([]save.BiomeState, len(rawPalette))
|
|
|
|
var biomeID []byte
|
|
for i, v := range rawPalette {
|
|
biomeID, err = v.MarshalText()
|
|
if err != nil {
|
|
return
|
|
}
|
|
palette[i] = save.BiomeState(biomeID)
|
|
}
|
|
|
|
data = make([]uint64, len(paletteData.data.Raw()))
|
|
copy(data, paletteData.data.Raw())
|
|
return
|
|
}
|
|
|
|
func (c *Chunk) WriteTo(w io.Writer) (int64, error) {
|
|
data, err := c.Data()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
light := lightData{
|
|
SkyLightMask: make(pk.BitSet, (16*16*16-1)>>6+1),
|
|
BlockLightMask: make(pk.BitSet, (16*16*16-1)>>6+1),
|
|
SkyLight: []pk.ByteArray{},
|
|
BlockLight: []pk.ByteArray{},
|
|
}
|
|
for i, v := range c.Sections {
|
|
if v.SkyLight != nil {
|
|
light.SkyLightMask.Set(i, true)
|
|
light.SkyLight = append(light.SkyLight, v.SkyLight)
|
|
}
|
|
if v.BlockLight != nil {
|
|
light.BlockLightMask.Set(i, true)
|
|
light.BlockLight = append(light.BlockLight, v.BlockLight)
|
|
}
|
|
}
|
|
return pk.Tuple{
|
|
// Heightmaps
|
|
pk.NBT(struct {
|
|
MotionBlocking []uint64 `nbt:"MOTION_BLOCKING"`
|
|
WorldSurface []uint64 `nbt:"WORLD_SURFACE"`
|
|
}{
|
|
MotionBlocking: c.HeightMaps.MotionBlocking.Raw(),
|
|
WorldSurface: c.HeightMaps.WorldSurface.Raw(),
|
|
}),
|
|
pk.ByteArray(data),
|
|
pk.Array(c.BlockEntity),
|
|
&light,
|
|
}.WriteTo(w)
|
|
}
|
|
|
|
func (c *Chunk) ReadFrom(r io.Reader) (int64, error) {
|
|
var (
|
|
heightmaps struct {
|
|
MotionBlocking []uint64 `nbt:"MOTION_BLOCKING"`
|
|
WorldSurface []uint64 `nbt:"WORLD_SURFACE"`
|
|
}
|
|
data pk.ByteArray
|
|
)
|
|
|
|
n, err := pk.Tuple{
|
|
pk.NBT(&heightmaps),
|
|
&data,
|
|
pk.Array(&c.BlockEntity),
|
|
&lightData{
|
|
SkyLightMask: make(pk.BitSet, (16*16*16-1)>>6+1),
|
|
BlockLightMask: make(pk.BitSet, (16*16*16-1)>>6+1),
|
|
SkyLight: []pk.ByteArray{},
|
|
BlockLight: []pk.ByteArray{},
|
|
},
|
|
}.ReadFrom(r)
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
|
|
bitsForHeight := bits.Len( /* chunk height in blocks */ uint(len(c.Sections))*16 + 1)
|
|
c.HeightMaps.MotionBlocking = NewBitStorage(bitsForHeight, 16*16, heightmaps.MotionBlocking)
|
|
c.HeightMaps.WorldSurface = NewBitStorage(bitsForHeight, 16*16, heightmaps.WorldSurface)
|
|
|
|
err = c.PutData(data)
|
|
return n, err
|
|
}
|
|
|
|
func (c *Chunk) Data() ([]byte, error) {
|
|
var buff bytes.Buffer
|
|
for i := range c.Sections {
|
|
_, err := c.Sections[i].WriteTo(&buff)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return buff.Bytes(), nil
|
|
}
|
|
|
|
func (c *Chunk) PutData(data []byte) error {
|
|
r := bytes.NewReader(data)
|
|
for i := range c.Sections {
|
|
_, err := c.Sections[i].ReadFrom(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type HeightMaps struct {
|
|
WorldSurfaceWG *BitStorage // test = NOT_AIR
|
|
WorldSurface *BitStorage // test = NOT_AIR
|
|
OceanFloorWG *BitStorage // test = MATERIAL_MOTION_BLOCKING
|
|
OceanFloor *BitStorage // test = MATERIAL_MOTION_BLOCKING
|
|
MotionBlocking *BitStorage // test = BlocksMotion or isFluid
|
|
MotionBlockingNoLeaves *BitStorage // test = BlocksMotion or isFluid
|
|
}
|
|
|
|
type BlockEntity struct {
|
|
XZ int8
|
|
Y int16
|
|
Type block.EntityType
|
|
Data nbt.RawMessage
|
|
}
|
|
|
|
func (b BlockEntity) UnpackXZ() (X, Z int) {
|
|
return int((uint8(b.XZ) >> 4) & 0xF), int(uint8(b.XZ) & 0xF)
|
|
}
|
|
|
|
func (b *BlockEntity) PackXZ(X, Z int) bool {
|
|
if X > 0xF || Z > 0xF || X < 0 || Z < 0 {
|
|
return false
|
|
}
|
|
b.XZ = int8(X<<4 | Z)
|
|
return true
|
|
}
|
|
|
|
func (b BlockEntity) WriteTo(w io.Writer) (n int64, err error) {
|
|
return pk.Tuple{
|
|
pk.Byte(b.XZ),
|
|
pk.Short(b.Y),
|
|
pk.VarInt(b.Type),
|
|
pk.NBT(b.Data),
|
|
}.WriteTo(w)
|
|
}
|
|
|
|
func (b *BlockEntity) ReadFrom(r io.Reader) (n int64, err error) {
|
|
return pk.Tuple{
|
|
(*pk.Byte)(&b.XZ),
|
|
(*pk.Short)(&b.Y),
|
|
(*pk.VarInt)(&b.Type),
|
|
pk.NBT(&b.Data),
|
|
}.ReadFrom(r)
|
|
}
|
|
|
|
type Section struct {
|
|
BlockCount int16
|
|
States *PaletteContainer[BlocksState]
|
|
Biomes *PaletteContainer[BiomesState]
|
|
// Half a byte per light value.
|
|
// Could be nil if not exist
|
|
SkyLight []byte // len() == 2048
|
|
BlockLight []byte // len() == 2048
|
|
}
|
|
|
|
func (s *Section) GetBlock(i int) BlocksState {
|
|
return s.States.Get(i)
|
|
}
|
|
|
|
func (s *Section) SetBlock(i int, v BlocksState) {
|
|
if !block.IsAir(s.States.Get(i)) {
|
|
s.BlockCount--
|
|
}
|
|
if !block.IsAir(v) {
|
|
s.BlockCount++
|
|
}
|
|
s.States.Set(i, v)
|
|
}
|
|
|
|
func (s *Section) WriteTo(w io.Writer) (int64, error) {
|
|
return pk.Tuple{
|
|
pk.Short(s.BlockCount),
|
|
s.States,
|
|
s.Biomes,
|
|
}.WriteTo(w)
|
|
}
|
|
|
|
func (s *Section) ReadFrom(r io.Reader) (int64, error) {
|
|
return pk.Tuple{
|
|
(*pk.Short)(&s.BlockCount),
|
|
s.States,
|
|
s.Biomes,
|
|
}.ReadFrom(r)
|
|
}
|
|
|
|
type lightData struct {
|
|
SkyLightMask pk.BitSet
|
|
BlockLightMask pk.BitSet
|
|
SkyLight []pk.ByteArray
|
|
BlockLight []pk.ByteArray
|
|
}
|
|
|
|
func bitSetRev(set pk.BitSet) pk.BitSet {
|
|
rev := make(pk.BitSet, len(set))
|
|
for i := range rev {
|
|
rev[i] = ^set[i]
|
|
}
|
|
return rev
|
|
}
|
|
|
|
func (l *lightData) WriteTo(w io.Writer) (int64, error) {
|
|
return pk.Tuple{
|
|
pk.Boolean(true), // Trust Edges
|
|
l.SkyLightMask,
|
|
l.BlockLightMask,
|
|
bitSetRev(l.SkyLightMask),
|
|
bitSetRev(l.BlockLightMask),
|
|
pk.Array(l.SkyLight),
|
|
pk.Array(l.BlockLight),
|
|
}.WriteTo(w)
|
|
}
|
|
|
|
func (l *lightData) ReadFrom(r io.Reader) (int64, error) {
|
|
var TrustEdges pk.Boolean
|
|
var RevSkyLightMask, RevBlockLightMask pk.BitSet
|
|
return pk.Tuple{
|
|
&TrustEdges, // Trust Edges
|
|
&l.SkyLightMask,
|
|
&l.BlockLightMask,
|
|
&RevSkyLightMask,
|
|
&RevBlockLightMask,
|
|
pk.Array(&l.SkyLight),
|
|
pk.Array(&l.BlockLight),
|
|
}.ReadFrom(r)
|
|
}
|