163 lines
3.9 KiB
Go
163 lines
3.9 KiB
Go
package worldgen
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"regionio/internal/nbt"
|
|
)
|
|
|
|
type ChunkWriter interface {
|
|
SetBlock(lx, y, lz int, state uint16)
|
|
GetBlock(lx, y, lz int) uint16
|
|
}
|
|
|
|
// Template represents a loaded NBT structure.
|
|
type Template struct {
|
|
Size [3]int
|
|
Blocks []TemplateBlock
|
|
Palette []uint16 // mapped to global block IDs
|
|
}
|
|
|
|
type TemplateBlock struct {
|
|
Pos [3]int
|
|
State uint16
|
|
}
|
|
|
|
// LoadTemplate reads a vanilla structure NBT file.
|
|
func LoadTemplate(path string) (*Template, error) {
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// NBT files are usually gzipped.
|
|
var r io.Reader = bytes.NewReader(b)
|
|
if len(b) >= 2 && b[0] == 0x1f && b[1] == 0x8b {
|
|
gr, err := gzip.NewReader(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer gr.Close()
|
|
r = gr
|
|
}
|
|
|
|
out, err := io.ReadAll(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, root, err := nbt.UnmarshalNamed(out)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
comp, ok := root.(*nbt.Compound)
|
|
if !ok {
|
|
return nil, fmt.Errorf("root is not a compound")
|
|
}
|
|
|
|
tmpl := &Template{}
|
|
|
|
if tag, ok := comp.Get("size"); ok {
|
|
if sizeList, ok := tag.(nbt.List); ok && len(sizeList.Elems) == 3 {
|
|
tmpl.Size[0] = int(sizeList.Elems[0].(nbt.Int))
|
|
tmpl.Size[1] = int(sizeList.Elems[1].(nbt.Int))
|
|
tmpl.Size[2] = int(sizeList.Elems[2].(nbt.Int))
|
|
}
|
|
}
|
|
|
|
// Parse palette
|
|
if tag, ok := comp.Get("palette"); ok {
|
|
if paletteList, ok := tag.(nbt.List); ok {
|
|
for _, v := range paletteList.Elems {
|
|
stateComp, ok := v.(*nbt.Compound)
|
|
if !ok {
|
|
continue
|
|
}
|
|
nameTag, _ := stateComp.Get("Name")
|
|
name := string(nameTag.(nbt.String))
|
|
props := make(map[string]string)
|
|
|
|
if propTag, ok := stateComp.Get("Properties"); ok {
|
|
if propComp, ok := propTag.(*nbt.Compound); ok {
|
|
for _, k := range propComp.Keys() {
|
|
pval, _ := propComp.Get(k)
|
|
if str, ok := pval.(nbt.String); ok {
|
|
props[k] = string(str)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
id := surfaceBlockID(name, props)
|
|
if id == 0 && name != "minecraft:air" {
|
|
// Fallback to default block ID for the name
|
|
id = defaultBlockIDs[name]
|
|
}
|
|
tmpl.Palette = append(tmpl.Palette, id)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse blocks
|
|
if tag, ok := comp.Get("blocks"); ok {
|
|
if blocksList, ok := tag.(nbt.List); ok {
|
|
for _, v := range blocksList.Elems {
|
|
blockComp, ok := v.(*nbt.Compound)
|
|
if !ok {
|
|
continue
|
|
}
|
|
stateTag, _ := blockComp.Get("state")
|
|
stateIdx := int(stateTag.(nbt.Int))
|
|
|
|
var pos [3]int
|
|
if posTag, ok := blockComp.Get("pos"); ok {
|
|
if posList, ok := posTag.(nbt.List); ok && len(posList.Elems) == 3 {
|
|
pos[0] = int(posList.Elems[0].(nbt.Int))
|
|
pos[1] = int(posList.Elems[1].(nbt.Int))
|
|
pos[2] = int(posList.Elems[2].(nbt.Int))
|
|
}
|
|
}
|
|
|
|
if stateIdx >= 0 && stateIdx < len(tmpl.Palette) {
|
|
state := tmpl.Palette[stateIdx]
|
|
tmpl.Blocks = append(tmpl.Blocks, TemplateBlock{
|
|
Pos: pos,
|
|
State: state,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return tmpl, nil
|
|
}
|
|
|
|
// Place applies the template blocks to the ChunkWriter if they fall within the given chunk boundaries.
|
|
// cx, cz are the chunk coordinates we are currently generating.
|
|
// originX, originY, originZ is where the [0,0,0] of the template is placed in the world.
|
|
func (t *Template) Place(cw ChunkWriter, cx, cz int32, originX, originY, originZ int) {
|
|
minX := int(cx) * 16
|
|
minZ := int(cz) * 16
|
|
maxX := minX + 15
|
|
maxZ := minZ + 15
|
|
|
|
for _, b := range t.Blocks {
|
|
wx := originX + b.Pos[0]
|
|
wy := originY + b.Pos[1]
|
|
wz := originZ + b.Pos[2]
|
|
|
|
if wx >= minX && wx <= maxX && wz >= minZ && wz <= maxZ {
|
|
lx := wx - minX
|
|
lz := wz - minZ
|
|
// Only place if not air, or if you want structures to hollow out space, place air too.
|
|
// Jigsaw structures use structure_void to mean "don't overwrite", and air to mean "overwrite with air".
|
|
// Since we fallback to 0 for unknown blocks, we need to be careful.
|
|
// For now, place everything.
|
|
cw.SetBlock(lx, wy, lz, b.State)
|
|
}
|
|
}
|
|
}
|