From 5acd7f73c5fd07df72a2692505e12400178438ed Mon Sep 17 00:00:00 2001 From: Tnze Date: Mon, 20 Dec 2021 15:32:44 +0800 Subject: [PATCH] simple conversion between save.Chunk and level.Chunk --- data/block/block.go | 4 +- data/block/gen_block.go | 4 +- examples/frameworkServer/main.go | 33 +++- level/bitstorage.go | 3 +- level/chunk.go | 264 +++++++++++++++++++++++++++++++ level/palette.go | 93 +++++++++-- server/dimension.go | 164 +------------------ 7 files changed, 376 insertions(+), 189 deletions(-) create mode 100644 level/chunk.go diff --git a/data/block/block.go b/data/block/block.go index 8e108fe..75c3701 100644 --- a/data/block/block.go +++ b/data/block/block.go @@ -4,12 +4,12 @@ package block import ( - "math" + "math/bits" ) // BitsPerBlock indicates how many bits are needed to represent all possible // block states. This value is used to determine the size of the global palette. -var BitsPerBlock = int(math.Ceil(math.Log2(float64(len(StateID))))) +var BitsPerBlock = bits.Len(uint(len(StateID))) // ID describes the numeric ID of a block. type ID uint32 diff --git a/data/block/gen_block.go b/data/block/gen_block.go index 22005e8..94ced0f 100644 --- a/data/block/gen_block.go +++ b/data/block/gen_block.go @@ -24,12 +24,12 @@ const ( package block import ( - "math" + "math/bits" ) // BitsPerBlock indicates how many bits are needed to represent all possible // block states. This value is used to determine the size of the global palette. -var BitsPerBlock = int(math.Ceil(math.Log2(float64(len(StateID))))) +var BitsPerBlock = bits.Len(uint(len(StateID))) // ID describes the numeric ID of a block. type ID uint32 diff --git a/examples/frameworkServer/main.go b/examples/frameworkServer/main.go index 72a0131..b9be1b8 100644 --- a/examples/frameworkServer/main.go +++ b/examples/frameworkServer/main.go @@ -3,6 +3,9 @@ package main import ( _ "embed" "github.com/Tnze/go-mc/chat" + "github.com/Tnze/go-mc/level" + "github.com/Tnze/go-mc/save" + "github.com/Tnze/go-mc/save/region" "github.com/Tnze/go-mc/server" "image" _ "image/png" @@ -21,14 +24,9 @@ func main() { if err != nil { log.Fatalf("Set server info error: %v", err) } - defaultDimension := server.NewSimpleDim(16) - chunk00 := server.EmptyChunk(16) - for s := 0; s < 16; s++ { - for i := 0; i < 16*16; i++ { - chunk00.Sections[s].SetBlock(i, 1) - } - } - defaultDimension.LoadChunk(server.ChunkPos{X: 0, Z: 0}, chunk00) + defaultDimension := server.NewSimpleDim(256) + chunk00 := level.ChunkFromSave(readChunk00(), 256) + defaultDimension.LoadChunk(level.ChunkPos{X: 0, Z: 0}, chunk00) s := server.Server{ ListPingHandler: serverInfo, LoginHandler: &server.MojangLoginHandler{ @@ -61,3 +59,22 @@ func readIcon() image.Image { } return icon } + +func readChunk00() *save.Chunk { + r, err := region.Open("./save/testdata/region/r.0.0.mca") + if err != nil { + panic(err) + } + defer r.Close() + + var c save.Chunk + data, err := r.ReadSector(0, 0) + if err != nil { + panic(err) + } + err = c.Load(data) + if err != nil { + panic(err) + } + return &c +} diff --git a/level/bitstorage.go b/level/bitstorage.go index 194572f..dd6c29e 100644 --- a/level/bitstorage.go +++ b/level/bitstorage.go @@ -2,9 +2,10 @@ package level import ( "fmt" - pk "github.com/Tnze/go-mc/net/packet" "io" "math" + + pk "github.com/Tnze/go-mc/net/packet" ) const indexOutOfBounds = "index out of bounds" diff --git a/level/chunk.go b/level/chunk.go new file mode 100644 index 0000000..b1205ec --- /dev/null +++ b/level/chunk.go @@ -0,0 +1,264 @@ +package level + +import ( + "bytes" + "io" + "math/bits" + "strings" + "sync" + "unsafe" + + "github.com/Tnze/go-mc/data/block" + pk "github.com/Tnze/go-mc/net/packet" + "github.com/Tnze/go-mc/save" +) + +type ChunkPos struct{ X, Z int } +type Chunk struct { + sync.Mutex + Sections []Section + HeightMaps HeightMaps +} +type HeightMaps struct { + MotionBlocking *BitStorage + WorldSurface *BitStorage +} + +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{ + MotionBlocking: NewBitStorage(bits.Len(uint(secs)*16), 16*16, nil), + }, + } +} + +var biomesIDs = map[string]int{"ocean": 0, + "deep_ocean": 24, + "frozen_ocean": 10, + "deep_frozen_ocean": 50, + "cold_ocean": 46, + "deep_cold_ocean": 49, + "lukewarm_ocean": 45, + "deep_lukewarm_ocean": 48, + "warm_ocean": 44, + "river": 7, + "frozen_river": 11, + "beach": 16, + "stony_shore": 25, + "snowy_beach": 26, + "forest": 4, + "flower_forest": 132, + "birch_forest": 27, + "old_growth_birch_forest": 155, + "dark_forest": 29, + "jungle": 21, + "sparse_jungle": 23, + "bamboo_jungle": 168, + "taiga": 5, + "snowy_taiga": 30, + "old_growth_pine_taiga": 32, + "old_growth_spruce_taiga": 160, + "mushroom_fields": 14, + "swamp": 6, + "savanna": 35, + "savanna_plateau": 36, + "windswept_savanna": 163, + "plains": 1, + "sunflower_plains": 129, + "desert": 2, + "snowy_plains": 12, + "ice_spikes": 140, + "windswept_hills": 3, + "windswept_forest": 34, + "windswept_gravelly_hills": 131, + "badlands": 37, + "wooded_badlands": 38, + "eroded_badlands": 165, + "dripstone_caves": 174, + "lush_caves": 175, + "nether_wastes": 8, + "crimson_forest": 171, + "warped_forest": 172, + "soul_sand_valley": 170, + "basalt_deltas": 173, + "the_end": 9, + "small_end_islands": 40, + "end_midlands": 41, + "end_highlands": 42, + "end_barrens": 43, + "the_void": 127, + "meadow": 177, + "grove": 178, + "snowy_slopes": 179, + "frozen_peaks": 180, + "jagged_peaks": 181, + "stony_peaks": 182, +} + +func ChunkFromSave(c *save.Chunk, secs int) *Chunk { + sections := make([]Section, secs) + for _, v := range c.Sections { + var blockCount int16 + stateData := *(*[]uint64)((unsafe.Pointer)(&v.BlockStates.Data)) + statePalette := v.BlockStates.Palette + stateRawPalette := make([]int, len(statePalette)) + for i, v := range statePalette { + // TODO: Consider the properties of block, not only index the block name + stateRawPalette[i] = int(stateIDs[strings.TrimPrefix(v.Name, "minecraft:")]) + if v.Name != "minecraft:air" { + blockCount++ + } + } + + biomesData := *(*[]uint64)((unsafe.Pointer)(&v.BlockStates.Data)) + biomesPalette := v.Biomes.Palette + biomesRawPalette := make([]int, len(biomesPalette)) + for i, v := range biomesPalette { + biomesRawPalette[i] = biomesIDs[strings.TrimPrefix(v, "minecraft:")] + } + + i := int32(int8(v.Y)) - c.YPos + sections[i].blockCount = blockCount + sections[i].States = NewStatesPaletteContainerWithData(16*16*16, stateData, stateRawPalette) + sections[i].Biomes = NewBiomesPaletteContainerWithData(16*16*16*2, biomesData, biomesRawPalette) + } + for i := range sections { + if sections[i].States == nil { + sections[i] = Section{ + blockCount: 0, + States: NewStatesPaletteContainer(16*16*16, 0), + Biomes: NewBiomesPaletteContainer(4*4*4, 0), + } + } + } + + motionBlocking := *(*[]uint64)(unsafe.Pointer(&c.Heightmaps.MotionBlocking)) + worldSurface := *(*[]uint64)(unsafe.Pointer(&c.Heightmaps.WorldSurface)) + return &Chunk{ + Sections: sections, + HeightMaps: HeightMaps{ + MotionBlocking: NewBitStorage(bits.Len(uint(secs)), 16*16, motionBlocking), + WorldSurface: NewBitStorage(bits.Len(uint(secs)), 16*16, worldSurface), + }, + } +} + +// TODO: This map should be moved to data/block. +var stateIDs = make(map[string]uint32) + +func init() { + for i, v := range block.StateID { + name := block.ByID[v].Name + if _, ok := stateIDs[name]; !ok { + stateIDs[name] = i + } + } +} + +func (c *Chunk) WriteTo(w io.Writer) (int64, error) { + data, err := c.Data() + if err != nil { + return 0, err + } + 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.MotionBlocking.Raw(), + }), + pk.ByteArray(data), + pk.VarInt(0), // TODO: Block Entity + &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{}, + }, + }.WriteTo(w) +} + +func (c *Chunk) Data() ([]byte, error) { + var buff bytes.Buffer + for _, section := range c.Sections { + _, err := section.WriteTo(&buff) + if err != nil { + return nil, err + } + } + return buff.Bytes(), nil +} + +type Section struct { + blockCount int16 + States *PaletteContainer + Biomes *PaletteContainer +} + +func (s *Section) GetBlock(i int) int { + return s.States.Get(i) +} +func (s *Section) SetBlock(i int, v int) { + // TODO: Handle cave air and void air + if s.States.Get(i) != 0 { + s.blockCount-- + } + if v != 0 { + 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) +} diff --git a/level/palette.go b/level/palette.go index 15b458b..482d960 100644 --- a/level/palette.go +++ b/level/palette.go @@ -12,7 +12,7 @@ type state = int type PaletteContainer struct { bits int - config func(bits int) palette + config paletteCfg palette palette data *BitStorage } @@ -20,34 +20,63 @@ type PaletteContainer struct { func NewStatesPaletteContainer(length int, defaultValue state) *PaletteContainer { return &PaletteContainer{ bits: 0, - config: createStatesPalette, + config: statesCfg{}, palette: &singleValuePalette{v: defaultValue}, data: NewBitStorage(0, length, nil), } } -func NewStatesPaletteContainerWithData(length int, data []uint64, palette []int) *PaletteContainer { - n := bits.Len(uint(len(palette))) - return &PaletteContainer{ - bits: n, - config: createStatesPalette, - palette: &linearPalette{ - values: palette, +func NewStatesPaletteContainerWithData(length int, data []uint64, pat []int) *PaletteContainer { + var p palette + var n int + if len(pat) == 1 { + p = &singleValuePalette{pat[0]} + n = 0 + } else { + n = statesCfg{}.bits(bits.Len(uint(len(pat)))) + p = &linearPalette{ + values: pat, bits: n, - }, - data: NewBitStorage(n, length, data), + } + } + return &PaletteContainer{ + bits: n, + config: statesCfg{}, + palette: p, + data: NewBitStorage(n, length, data), } } func NewBiomesPaletteContainer(length int, defaultValue state) *PaletteContainer { return &PaletteContainer{ bits: 0, - config: createBiomesPalette, + config: biomesCfg{}, palette: &singleValuePalette{v: defaultValue}, data: NewBitStorage(0, length, nil), } } +func NewBiomesPaletteContainerWithData(length int, data []uint64, pat []int) *PaletteContainer { + var p palette + var n int + if len(pat) == 1 { + p = &singleValuePalette{pat[0]} + n = 0 + } else { + n = biomesCfg{}.bits(bits.Len(uint(len(pat)))) + p = &linearPalette{ + values: pat, + bits: n, + } + } + return &PaletteContainer{ + bits: n, + config: biomesCfg{}, + palette: p, + data: NewBitStorage(n, length, data), + } +} + func (p *PaletteContainer) Get(i int) state { return p.palette.value(p.data.Get(i)) } @@ -61,7 +90,7 @@ func (p *PaletteContainer) Set(i int, v state) { newPalette := PaletteContainer{ bits: vv, config: p.config, - palette: p.config(vv), + palette: p.config.create(vv), data: NewBitStorage(vv, oldLen+1, nil), } // copy @@ -89,7 +118,7 @@ func (p *PaletteContainer) ReadFrom(r io.Reader) (n int64, err error) { if err != nil { return } - p.palette = p.config(int(bits)) + p.palette = p.config.create(int(bits)) nn, err := p.palette.ReadFrom(r) n += nn @@ -105,7 +134,27 @@ func (p *PaletteContainer) ReadFrom(r io.Reader) (n int64, err error) { return n, nil } -func createStatesPalette(bits int) palette { +type paletteCfg interface { + bits(int) int + create(bits int) palette +} + +type statesCfg struct{} + +func (s statesCfg) bits(bits int) int { + switch bits { + case 0: + return 0 + case 1, 2, 3, 4: + return 4 + case 5, 6, 7, 8: + return bits + default: + return bits + } +} + +func (s statesCfg) create(bits int) palette { switch bits { case 0: return &singleValuePalette{v: -1} @@ -119,7 +168,19 @@ func createStatesPalette(bits int) palette { } } -func createBiomesPalette(bits int) palette { +type biomesCfg struct{} + +func (b biomesCfg) bits(bits int) int { + switch bits { + case 0: + return 0 + case 1, 2, 3: + return bits + default: + return bits + } +} +func (b biomesCfg) create(bits int) palette { switch bits { case 0: return &singleValuePalette{v: -1} diff --git a/server/dimension.go b/server/dimension.go index ffcbb91..b148b43 100644 --- a/server/dimension.go +++ b/server/dimension.go @@ -1,18 +1,9 @@ package server import ( - "bytes" - "io" - "math/bits" - "strings" - "sync" - "unsafe" - - "github.com/Tnze/go-mc/data/block" "github.com/Tnze/go-mc/data/packetid" "github.com/Tnze/go-mc/level" pk "github.com/Tnze/go-mc/net/packet" - "github.com/Tnze/go-mc/save" ) type Level interface { @@ -26,160 +17,19 @@ type LevelInfo struct { HashedSeed uint64 } -type ChunkPos struct{ X, Z int } -type Chunk struct { - sync.Mutex - Sections []Section - HeightMaps *level.BitStorage -} - -func EmptyChunk(secs int) *Chunk { - sections := make([]Section, secs) - for i := range sections { - sections[i] = Section{ - blockCount: 0, - States: level.NewStatesPaletteContainer(16*16*16, 0), - Biomes: level.NewBiomesPaletteContainer(4*4*4, 0), - } - } - return &Chunk{ - Sections: sections, - HeightMaps: level.NewBitStorage(bits.Len(uint(secs)*16), 16*16, nil), - } -} - -func ChunkFromSave(c *save.Chunk) *Chunk { - sections := make([]Section, len(c.Sections)) - for i := range sections { - data := *(*[]uint64)((unsafe.Pointer)(&c.Sections[i].BlockStates.Data)) - palette := c.Sections[i].BlockStates.Palette - rawPalette := make([]int, len(palette)) - for i, v := range palette { - // TODO: Consider the properties of block, not only index the block name - rawPalette[i] = int(stateIDs[strings.TrimPrefix(v.Name, "minecraft:")]) - } - sections[i].States = level.NewStatesPaletteContainerWithData(16*16*16, data, rawPalette) - } - return &Chunk{ - Sections: sections, - HeightMaps: nil, - } -} - -// TODO: This map should be moved to data/block. -var stateIDs map[string]uint32 - -func init() { - for i, v := range block.StateID { - name := block.ByID[v].Name - if _, ok := stateIDs[name]; !ok { - stateIDs[name] = i - } - } -} - -func (c *Chunk) WriteTo(w io.Writer) (int64, error) { - data, err := c.Data() - if err != nil { - return 0, err - } - return pk.Tuple{ - // Heightmaps - pk.NBT(struct { - MotionBlocking []uint64 `nbt:"MOTION_BLOCKING"` - }{c.HeightMaps.Raw()}), - pk.ByteArray(data), - pk.VarInt(0), // TODO: Block Entity - }.WriteTo(w) -} - -func (c *Chunk) Data() ([]byte, error) { - var buff bytes.Buffer - for _, section := range c.Sections { - _, err := section.WriteTo(&buff) - if err != nil { - return nil, err - } - } - return buff.Bytes(), nil -} - -type Section struct { - blockCount int16 - States *level.PaletteContainer - Biomes *level.PaletteContainer -} - -func (s *Section) GetBlock(i int) int { - return s.States.Get(i) -} -func (s *Section) SetBlock(i int, v int) { - // TODO: Handle cave air and void air - if s.States.Get(i) != 0 { - s.blockCount-- - } - if v != 0 { - 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) -} - type SimpleDim struct { numOfSection int - Columns map[ChunkPos]*Chunk + Columns map[level.ChunkPos]*level.Chunk } func NewSimpleDim(secs int) *SimpleDim { return &SimpleDim{ numOfSection: secs, - Columns: make(map[ChunkPos]*Chunk), + Columns: make(map[level.ChunkPos]*level.Chunk), } } -func (s *SimpleDim) LoadChunk(pos ChunkPos, c *Chunk) { +func (s *SimpleDim) LoadChunk(pos level.ChunkPos, c *level.Chunk) { s.Columns[pos] = c } @@ -197,12 +47,6 @@ func (s *SimpleDim) PlayerJoin(p *Player) { packetid.ClientboundLevelChunkWithLight, pk.Int(pos.X), pk.Int(pos.Z), column, - &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{}, - }, ) column.Unlock() @@ -214,7 +58,7 @@ func (s *SimpleDim) PlayerJoin(p *Player) { err := p.WritePacket(Packet757(pk.Marshal( packetid.ClientboundPlayerPosition, - pk.Double(0), pk.Double(0), pk.Double(0), + pk.Double(0), pk.Double(143), pk.Double(0), pk.Float(0), pk.Float(0), pk.Byte(0), pk.VarInt(0),