RegionIO/internal/world/encode.go
Master290 a7bb9496ae Initial commit: RegionIO Minecraft server core (26.1.2/protocol 775)
Vanilla-faithful overworld generator (final_density + multi-noise biomes),
full connection lifecycle (status/login/configuration/play), chunk streaming,
creative block editing, and the protocol/nbt/registry infrastructure.
2026-06-24 00:32:51 +03:00

269 lines
7 KiB
Go

package world
import (
"math/bits"
"regionio/internal/protocol"
)
// World vertical geometry for the overworld dimension type.
const (
MinY = -64
WorldHeight = 384
SectionCount = WorldHeight / 16 // 24 sections
sectionVol = 16 * 16 * 16 // 4096 blocks
)
// Common block-state network IDs (from the generated block report).
const (
StateAir uint16 = 0
StateStone uint16 = 1
StateGrass uint16 = 9
StateDirt uint16 = 10
StateBedrock uint16 = 85
StateWater uint16 = 86
StateSand uint16 = 118
StateGravel uint16 = 124
StateOakLog uint16 = 137
StateOakLeaf uint16 = 279
)
// BiomePlains is the network ID (registry index) of minecraft:plains.
const BiomePlains uint16 = 40
// totalBlockStates is one past the largest block-state ID; it sets the
// direct-palette bit width.
const totalBlockStates = 29873
// Chunk is a 16xWorldHeightx16 column of block states with a single biome.
// A nil section is entirely air.
type Chunk struct {
X, Z int32
sections [SectionCount]*[sectionVol]uint16
biome uint16
}
// NewChunk returns an empty (all-air) chunk at (x, z) with the given biome.
func NewChunk(x, z int32, biome uint16) *Chunk {
return &Chunk{X: x, Z: z, biome: biome}
}
// blockIndex maps local coordinates to the YZX-ordered section array index.
func blockIndex(lx, ly, lz int) int { return (ly&15)<<8 | (lz&15)<<4 | (lx & 15) }
// section returns section i, allocating it on first write.
func (c *Chunk) section(i int) *[sectionVol]uint16 {
if c.sections[i] == nil {
c.sections[i] = new([sectionVol]uint16)
}
return c.sections[i]
}
// GetBlock returns the block state at local (lx, lz) and world height y, or
// StateAir if the section is empty or y is out of range.
func (c *Chunk) GetBlock(lx, y, lz int) uint16 {
si := (y - MinY) >> 4
if si < 0 || si >= SectionCount {
return StateAir
}
s := c.sections[si]
if s == nil {
return StateAir
}
return s[blockIndex(lx, y, lz)]
}
// SetBlock sets the block at local (lx, lz) and absolute world height y.
func (c *Chunk) SetBlock(lx, y, lz int, state uint16) {
si := (y - MinY) >> 4
if si < 0 || si >= SectionCount {
return
}
c.section(si)[blockIndex(lx, y, lz)] = state
}
// Encode serializes the level_chunk_with_light body for this chunk.
func (c *Chunk) Encode() []byte {
w := protocol.NewWriter(8192)
w.Int32(c.X).Int32(c.Z)
c.writeHeightmaps(w)
// Section data is length-prefixed.
sec := protocol.NewWriter(4096)
for i := 0; i < SectionCount; i++ {
c.writeSection(sec, i)
}
w.VarInt(int32(sec.Len()))
w.Raw(sec.Bytes())
w.VarInt(0) // block entity count
c.writeLight(w)
return w.Bytes()
}
// Heightmap.Types ordinals sent to the client.
const (
hmWorldSurface = 1
hmMotionBlocking = 4
hmMotionBlockingNoLeaves = 5
)
// writeHeightmaps emits the three client-relevant heightmaps. For our blocky
// terrain (no leaves/transparency) they share the same column heights.
func (c *Chunk) writeHeightmaps(w *protocol.Writer) {
heights := c.columnHeights()
packed := packHeightmap(heights)
w.VarInt(3)
for _, t := range []int32{hmMotionBlockingNoLeaves, hmMotionBlocking, hmWorldSurface} {
w.VarInt(t)
w.VarInt(int32(len(packed)))
for _, v := range packed {
w.Int64(int64(v))
}
}
}
// columnHeights returns, per column, (highestNonAirY + 1) - MinY, clamped to 0.
func (c *Chunk) columnHeights() [256]uint16 {
var h [256]uint16
for lx := 0; lx < 16; lx++ {
for lz := 0; lz < 16; lz++ {
height := 0
for y := MinY + WorldHeight - 1; y >= MinY; y-- {
si := (y - MinY) >> 4
s := c.sections[si]
if s != nil && s[blockIndex(lx, y, lz)] != StateAir {
height = y + 1 - MinY
break
}
}
h[lz*16+lx] = uint16(height)
}
}
return h
}
// packHeightmap packs 256 column heights at 9 bits each, 7 values per long,
// without spanning longs (37 longs).
func packHeightmap(h [256]uint16) []uint64 {
const bpe = 9
const perLong = 64 / bpe // 7
out := make([]uint64, (256+perLong-1)/perLong)
for i, v := range h {
out[i/perLong] |= uint64(v&0x1FF) << uint((i%perLong)*bpe)
}
return out
}
// writeSection emits one chunk section: block count, block paletted container,
// then the (single-value) biome paletted container.
func (c *Chunk) writeSection(w *protocol.Writer, i int) {
s := c.sections[i]
if s == nil {
w.Uint16(0) // non-air block count
w.Uint16(0) // reserved 2-byte field (always 0 in vanilla)
writeSingleValued(w, uint32(StateAir))
} else {
w.Uint16(uint16(nonAirCount(s)))
w.Uint16(0) // reserved 2-byte field
writeBlockPalette(w, s)
}
// Biomes: a single value covers the whole section for now.
writeSingleValued(w, uint32(c.biome))
}
func nonAirCount(s *[sectionVol]uint16) int {
n := 0
for _, v := range s {
if v != StateAir {
n++
}
}
return n
}
// writeSingleValued writes a bits-per-entry-0 paletted container (no data).
func writeSingleValued(w *protocol.Writer, value uint32) {
w.Byte(0)
w.VarInt(int32(value))
}
// writeBlockPalette writes a block-state paletted container, choosing the
// single-valued, indirect, or direct encoding as appropriate.
func writeBlockPalette(w *protocol.Writer, s *[sectionVol]uint16) {
palette, indexOf := buildPalette(s)
if len(palette) == 1 {
writeSingleValued(w, uint32(palette[0]))
return
}
bpe := bitsFor(len(palette))
if bpe < 4 {
bpe = 4 // minimum for the indirect block format
}
if bpe > 8 {
writeDirect(w, s)
return
}
w.Byte(byte(bpe))
w.VarInt(int32(len(palette)))
for _, st := range palette {
w.VarInt(int32(st))
}
writePackedIndices(w, bpe, sectionVol, func(i int) uint32 {
return uint32(indexOf[s[i]])
})
}
// writeDirect writes a direct (palette-less) container of global state IDs.
func writeDirect(w *protocol.Writer, s *[sectionVol]uint16) {
bpe := bitsFor(totalBlockStates)
w.Byte(byte(bpe))
writePackedIndices(w, bpe, sectionVol, func(i int) uint32 {
return uint32(s[i])
})
}
// writePackedIndices emits the long-array data: count entries of bpe bits each,
// packed perLong=64/bpe values per long, never spanning a long boundary. The
// long count is NOT length-prefixed; the client derives it from bpe.
func writePackedIndices(w *protocol.Writer, bpe, count int, value func(i int) uint32) {
perLong := 64 / bpe
numLongs := (count + perLong - 1) / perLong
mask := uint64(1)<<uint(bpe) - 1
for l := 0; l < numLongs; l++ {
var packed uint64
for j := 0; j < perLong; j++ {
idx := l*perLong + j
if idx >= count {
break
}
packed |= (uint64(value(idx)) & mask) << uint(j*bpe)
}
w.Int64(int64(packed))
}
}
// buildPalette returns the distinct block states in s and a value->index map.
func buildPalette(s *[sectionVol]uint16) ([]uint16, map[uint16]int) {
indexOf := make(map[uint16]int)
var palette []uint16
for _, v := range s {
if _, ok := indexOf[v]; !ok {
indexOf[v] = len(palette)
palette = append(palette, v)
}
}
return palette, indexOf
}
// bitsFor returns the bits needed to index n distinct values (min 1).
func bitsFor(n int) int {
if n <= 1 {
return 0
}
return bits.Len(uint(n - 1))
}