Implement multiplayer persistence and vanilla lighting
This commit is contained in:
parent
8f7cacf9d9
commit
cae06eb97e
47 changed files with 3784 additions and 465 deletions
207
internal/world/light_test.go
Normal file
207
internal/world/light_test.go
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
package world
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"testing"
|
||||
|
||||
"regionio/internal/protocol"
|
||||
)
|
||||
|
||||
// Captured from vanilla 26.1.2 after placing minecraft:glowstone at
|
||||
// (15,100,8). Layout is YZX over [0..30]x[85..115]x[-7..23].
|
||||
//
|
||||
//go:embed testdata/vanilla_glowstone_block_light.bin
|
||||
var vanillaGlowstoneBlockLight []byte
|
||||
|
||||
func TestIncrementalBlockLightAgainstVanillaFixture(t *testing.T) {
|
||||
const minX, minY, minZ, size = 0, 85, -7, 31
|
||||
if len(vanillaGlowstoneBlockLight) != size*size*size {
|
||||
t.Fatalf("vanilla fixture length = %d, want %d", len(vanillaGlowstoneBlockLight), size*size*size)
|
||||
}
|
||||
cache := NewCache(-1, func(cx, cz int32) *Chunk {
|
||||
return NewChunk(cx, cz, BiomePlains)
|
||||
})
|
||||
for cz := int32(-1); cz <= 1; cz++ {
|
||||
for cx := int32(-1); cx <= 1; cx++ {
|
||||
cache.chunkAt(cx, cz)
|
||||
}
|
||||
}
|
||||
glowstone := nameToStateID("minecraft:glowstone", nil)
|
||||
if valid, _ := cache.SetBlockWithLight(15, 100, 8, glowstone); !valid {
|
||||
t.Fatal("glowstone edit rejected")
|
||||
}
|
||||
|
||||
fixtureIndex := 0
|
||||
mismatches := 0
|
||||
for y := minY; y < minY+size; y++ {
|
||||
for z := minZ; z < minZ+size; z++ {
|
||||
for x := minX; x < minX+size; x++ {
|
||||
chunk := cache.chunkAt(int32(x>>4), int32(z>>4))
|
||||
_, got, ready := chunk.LightAt(x, y, z)
|
||||
want := vanillaGlowstoneBlockLight[fixtureIndex]
|
||||
fixtureIndex++
|
||||
if !ready || got != want {
|
||||
if mismatches < 10 {
|
||||
t.Errorf("block light (%d,%d,%d) = %d, ready=%v; vanilla=%d", x, y, z, got, ready, want)
|
||||
}
|
||||
mismatches++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if mismatches > 10 {
|
||||
t.Errorf("... and %d additional light mismatches", mismatches-10)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncrementalBlockLightCrossesChunkBoundaryAndClears(t *testing.T) {
|
||||
cache := NewCache(-1, func(cx, cz int32) *Chunk {
|
||||
return NewChunk(cx, cz, BiomePlains)
|
||||
})
|
||||
left := cache.chunkAt(0, 0)
|
||||
right := cache.chunkAt(1, 0)
|
||||
if _, err := cache.FrameErr(0, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := cache.FrameErr(1, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
glowstone := nameToStateID("minecraft:glowstone", nil)
|
||||
if emission := lightEmission(glowstone); emission != 15 {
|
||||
t.Fatalf("glowstone emission = %d, want 15", emission)
|
||||
}
|
||||
valid, changed := cache.SetBlockWithLight(15, 0, 8, glowstone)
|
||||
if !valid || !containsChunkPos(changed, 0, 0) || !containsChunkPos(changed, 1, 0) {
|
||||
t.Fatalf("place changed = %v, valid=%v; want chunks (0,0) and (1,0)", changed, valid)
|
||||
}
|
||||
assertLight(t, left, 15, 0, 8, 15)
|
||||
assertLight(t, right, 0, 0, 8, 14)
|
||||
assertLight(t, right, 1, 0, 8, 13)
|
||||
|
||||
valid, changed = cache.SetBlockWithLight(15, 0, 8, StateAir)
|
||||
if !valid || !containsChunkPos(changed, 0, 0) || !containsChunkPos(changed, 1, 0) {
|
||||
t.Fatalf("remove changed = %v, valid=%v; want chunks (0,0) and (1,0)", changed, valid)
|
||||
}
|
||||
assertLight(t, left, 15, 0, 8, 0)
|
||||
assertLight(t, right, 0, 0, 8, 0)
|
||||
}
|
||||
|
||||
func TestIncrementalSkyLightSpreadsUnderRoofAcrossChunkBoundary(t *testing.T) {
|
||||
cache := NewCache(-1, func(cx, cz int32) *Chunk {
|
||||
chunk := NewChunk(cx, cz, BiomePlains)
|
||||
for z := 0; z < 16; z++ {
|
||||
for x := 0; x < 16; x++ {
|
||||
chunk.setBlockRaw(x, 1, z, StateStone)
|
||||
}
|
||||
}
|
||||
return chunk
|
||||
})
|
||||
left := cache.chunkAt(0, 0)
|
||||
right := cache.chunkAt(1, 0)
|
||||
if _, err := cache.FrameErr(0, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := cache.FrameErr(1, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertSky(t, right, 0, 0, 8, 0)
|
||||
valid, changed := cache.SetBlockWithLight(15, 1, 8, StateAir)
|
||||
if !valid || !containsChunkPos(changed, 0, 0) || !containsChunkPos(changed, 1, 0) {
|
||||
t.Fatalf("open changed = %v, valid=%v; want chunks (0,0) and (1,0)", changed, valid)
|
||||
}
|
||||
assertSky(t, left, 15, 0, 8, 15)
|
||||
assertSky(t, right, 0, 0, 8, 14)
|
||||
assertSky(t, right, 1, 0, 8, 13)
|
||||
|
||||
valid, changed = cache.SetBlockWithLight(15, 1, 8, StateStone)
|
||||
if !valid || !containsChunkPos(changed, 0, 0) || !containsChunkPos(changed, 1, 0) {
|
||||
t.Fatalf("close changed = %v, valid=%v; want chunks (0,0) and (1,0)", changed, valid)
|
||||
}
|
||||
assertSky(t, left, 15, 0, 8, 0)
|
||||
assertSky(t, right, 0, 0, 8, 0)
|
||||
}
|
||||
|
||||
func assertLight(t *testing.T, chunk *Chunk, x, y, z int, want byte) {
|
||||
t.Helper()
|
||||
_, got, ready := chunk.LightAt(x, y, z)
|
||||
if !ready || got != want {
|
||||
t.Fatalf("block light (%d,%d,%d) = %d, ready=%v; want %d", x, y, z, got, ready, want)
|
||||
}
|
||||
}
|
||||
|
||||
func assertSky(t *testing.T, chunk *Chunk, x, y, z int, want byte) {
|
||||
t.Helper()
|
||||
got, _, ready := chunk.LightAt(x, y, z)
|
||||
if !ready || got != want {
|
||||
t.Fatalf("sky light (%d,%d,%d) = %d, ready=%v; want %d", x, y, z, got, ready, want)
|
||||
}
|
||||
}
|
||||
|
||||
func containsChunkPos(chunks []ChunkPos, x, z int32) bool {
|
||||
for _, chunk := range chunks {
|
||||
if chunk.X == x && chunk.Z == z {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestEncodeLightUpdateLayout(t *testing.T) {
|
||||
chunk := NewChunk(-2, 3, BiomePlains)
|
||||
r := protocol.NewReader(chunk.EncodeLightUpdate())
|
||||
|
||||
if x, err := r.VarInt(); err != nil || x != -2 {
|
||||
t.Fatalf("chunk x = %d, %v; want -2", x, err)
|
||||
}
|
||||
if z, err := r.VarInt(); err != nil || z != 3 {
|
||||
t.Fatalf("chunk z = %d, %v; want 3", z, err)
|
||||
}
|
||||
|
||||
masks := make([]uint64, 4)
|
||||
for i := range masks {
|
||||
length, err := r.VarInt()
|
||||
if err != nil || length < 0 || length > 1 {
|
||||
t.Fatalf("mask[%d] length = %d, %v; want 0 or 1", i, length, err)
|
||||
}
|
||||
if length == 1 {
|
||||
value, err := r.Int64()
|
||||
if err != nil {
|
||||
t.Fatalf("mask[%d]: %v", i, err)
|
||||
}
|
||||
masks[i] = uint64(value)
|
||||
}
|
||||
}
|
||||
if masks[0] == 0 || masks[2] == 0 {
|
||||
t.Fatalf("sky masks were not populated: data=%#x empty=%#x", masks[0], masks[2])
|
||||
}
|
||||
if masks[1] != 0 || masks[3] != (uint64(1)<<lightSections)-1 {
|
||||
t.Fatalf("block masks = data %#x, empty %#x", masks[1], masks[3])
|
||||
}
|
||||
|
||||
consumeLightArrays(t, r)
|
||||
consumeLightArrays(t, r)
|
||||
if r.Remaining() != 0 {
|
||||
t.Fatalf("light update trailing bytes = %d", r.Remaining())
|
||||
}
|
||||
}
|
||||
|
||||
func consumeLightArrays(t *testing.T, r *protocol.Reader) {
|
||||
t.Helper()
|
||||
count, err := r.VarInt()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i := int32(0); i < count; i++ {
|
||||
length, err := r.VarInt()
|
||||
if err != nil || length != 2048 {
|
||||
t.Fatalf("light array[%d] length = %d, %v; want 2048", i, length, err)
|
||||
}
|
||||
for j := int32(0); j < length; j++ {
|
||||
if _, err := r.ReadByte(); err != nil {
|
||||
t.Fatalf("light array[%d] byte %d: %v", i, j, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue