PlaceRuinedPortalPiece runs the template through the decoded processor stack — positional Legacy streams per cell drive the gold-to-air, lava-to-magma, netherrack-to-magma and block-age mossiness rolls, the features-cannot-replace guard skips protected cells, and template lava stays when it lands in existing lava. Drip columns below the portal draw from a documented approximation of the shared decoration stream; spreadNetherrack is not needed for the underground setups. The placement chain is now proven end to end: chunk (1,0) reproduces vanilla's saved start (portal_6, CLOCKWISE_90, NONE, air pocket at (16,12,0)) and the fixture parity ticks up to 98.033% with biomes and heightmaps still exact. generatorVersion bumps to 23.
191 lines
5.3 KiB
Go
191 lines
5.3 KiB
Go
package worldgen
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"regionio/internal/nbt"
|
|
)
|
|
|
|
// structure_template.go loads vanilla structure-template NBTs into palette-
|
|
// resolved block lists and transforms positions the way StructureTemplate does.
|
|
//
|
|
// The state resolver is injected because only the caller knows how names plus
|
|
// properties map onto the runtime's state IDs (surface rules know a subset,
|
|
// the full table knows everything).
|
|
|
|
// TemplateBlockInfo is one resolved template cell.
|
|
type TemplateBlockInfo struct {
|
|
Pos [3]int
|
|
State uint16
|
|
HasNBT bool
|
|
}
|
|
|
|
// LoadResolvedTemplate reads a template file from disk.
|
|
func LoadResolvedTemplate(path string, resolve func(name string, props map[string]string) (uint16, bool)) ([]TemplateBlockInfo, [3]int, error) {
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, [3]int{}, err
|
|
}
|
|
raw, err = readMaybeGzipBytes(raw)
|
|
if err != nil {
|
|
return nil, [3]int{}, err
|
|
}
|
|
return LoadResolvedTemplateBytes(raw, resolve)
|
|
}
|
|
|
|
// EmbeddedStructureTemplate reads a template from the embedded datapack data
|
|
// by its resource-path name, e.g. "ruined_portal/portal_1".
|
|
func EmbeddedStructureTemplate(name string) ([]byte, error) {
|
|
return dataFS.ReadFile("data/structure_template/" + name + ".nbt")
|
|
}
|
|
|
|
// LoadResolvedTemplateBytes parses template NBT already in memory.
|
|
func LoadResolvedTemplateBytes(raw []byte, resolve func(name string, props map[string]string) (uint16, bool)) ([]TemplateBlockInfo, [3]int, error) {
|
|
raw, err := readMaybeGzipBytes(raw)
|
|
if err != nil {
|
|
return nil, [3]int{}, err
|
|
}
|
|
_, root, err := nbt.UnmarshalNamed(raw)
|
|
if err != nil {
|
|
return nil, [3]int{}, err
|
|
}
|
|
comp, ok := root.(*nbt.Compound)
|
|
if !ok {
|
|
return nil, [3]int{}, fmt.Errorf("template root is not a compound")
|
|
}
|
|
|
|
var size [3]int
|
|
if rawTag, ok := comp.Get("size"); ok {
|
|
if tag, isList := rawTag.(nbt.List); isList && len(tag.Elems) == 3 {
|
|
size[0] = int(tag.Elems[0].(nbt.Int))
|
|
size[1] = int(tag.Elems[1].(nbt.Int))
|
|
size[2] = int(tag.Elems[2].(nbt.Int))
|
|
}
|
|
}
|
|
|
|
var palette []struct {
|
|
id uint16
|
|
valid bool
|
|
}
|
|
if rawTag, ok := comp.Get("palette"); ok {
|
|
if tag, isList := rawTag.(nbt.List); isList {
|
|
for _, entry := range tag.Elems {
|
|
stateComp, ok := entry.(*nbt.Compound)
|
|
if !ok {
|
|
palette = append(palette, struct {
|
|
id uint16
|
|
valid bool
|
|
}{})
|
|
continue
|
|
}
|
|
name := ""
|
|
if nameTag, ok := stateComp.Get("Name"); ok {
|
|
name = string(nameTag.(nbt.String))
|
|
}
|
|
props := map[string]string{}
|
|
if propTag, ok := stateComp.Get("Properties"); ok {
|
|
if propComp, isCompound := propTag.(*nbt.Compound); isCompound {
|
|
for _, key := range propComp.Keys() {
|
|
if v, ok := propComp.Get(key); ok {
|
|
props[key] = string(v.(nbt.String))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
id, valid := resolve(name, props)
|
|
palette = append(palette, struct {
|
|
id uint16
|
|
valid bool
|
|
}{id, valid})
|
|
}
|
|
}
|
|
}
|
|
|
|
var blocks []TemplateBlockInfo
|
|
if rawTag, ok := comp.Get("blocks"); ok {
|
|
if tag, isList := rawTag.(nbt.List); isList {
|
|
for _, entry := range tag.Elems {
|
|
blockComp, ok := entry.(*nbt.Compound)
|
|
if !ok {
|
|
continue
|
|
}
|
|
stateIdx := nbt.Int(0)
|
|
if stateTag, ok := blockComp.Get("state"); ok {
|
|
stateIdx = stateTag.(nbt.Int)
|
|
}
|
|
var pos [3]int
|
|
if posTag, ok := blockComp.Get("pos"); ok {
|
|
if posList, isList := posTag.(nbt.List); isList && 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 int(stateIdx) < 0 || int(stateIdx) >= len(palette) || !palette[stateIdx].valid {
|
|
continue
|
|
}
|
|
info := TemplateBlockInfo{Pos: pos, State: palette[stateIdx].id}
|
|
if _, hasNBT := blockComp.Get("nbt"); hasNBT {
|
|
info.HasNBT = true
|
|
}
|
|
blocks = append(blocks, info)
|
|
}
|
|
}
|
|
}
|
|
return blocks, size, nil
|
|
}
|
|
|
|
func readMaybeGzipBytes(b []byte) ([]byte, error) {
|
|
if len(b) >= 2 && b[0] == 0x1f && b[1] == 0x8b {
|
|
gr, err := gzip.NewReader(bytes.NewReader(b))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer gr.Close()
|
|
return io.ReadAll(gr)
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
// TransformBlockPos mirrors StructureTemplate.transform(BlockPos, mirror,
|
|
// rotation, pivot): the mirror flips around the template origin first, then the
|
|
// rotation turns the point around the pivot's X/Z pair. Rotation ordinals are
|
|
// none/clockwise_90/clockwise_180/counterclockwise_90.
|
|
func TransformBlockPos(pos [3]int, mirror string, rotation int, pivot [3]int) [3]int {
|
|
x, y, z := pos[0], pos[1], pos[2]
|
|
switch mirror {
|
|
case "left_right":
|
|
z = -z
|
|
case "front_back":
|
|
x = -x
|
|
}
|
|
px, pz := pivot[0], pivot[2]
|
|
switch rotation {
|
|
case 1: // clockwise_90 ($SwitchMap ordinal 1 -> bytecode target 172)
|
|
return [3]int{px + pz - z, y, pz - px + x}
|
|
case 2: // clockwise_180 (target 120)
|
|
return [3]int{px + px - x, y, pz + pz - z}
|
|
case 3: // counterclockwise_90 (target 146)
|
|
return [3]int{px - pz + z, y, px + pz - x}
|
|
}
|
|
return [3]int{x, y, z}
|
|
}
|
|
|
|
// MthGetSeed is Mth.getSeed(x,y,z), the positional seed every template
|
|
// processor derives its per-block random source from:
|
|
//
|
|
// l = (long)(x*3129871) ^ (long)z*116129781 ^ y
|
|
// l = l*l*42317861 + l*11
|
|
// return l >> 16
|
|
func MthGetSeed(x, y, z int) int64 {
|
|
l := int64(int32(x)*3129871) ^ int64(z)*116129781 ^ int64(int32(y))
|
|
l = l*l*42317861 + l*11
|
|
return l >> 16
|
|
}
|
|
|
|
|
|
|