world: place template air, flood connected pockets, age stairs and slabs
The portal's air pocket is placed for real now: vanilla's fluid ticks then flood every pocket cell connected to surrounding water while sealed cells stay air, so the piece ends with a bounded BFS over its own air cells from water neighbours. BlockAgeProcessor gains the stairs/slab branches with their property-preserving mossy variants, and horizontal facings rotate with the piece (mirror first, then quarter turns). Portal box match against the fixture jumps from 52.7% to 84.1%; region parity rises to 98.047% with biomes and heightmaps exact. Remaining box diffs trace to drip columns reading the shared decoration stream, which is approximated per-cell.
This commit is contained in:
parent
39092b2cc1
commit
e0623be504
1 changed files with 191 additions and 6 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
package world
|
package world
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"regionio/internal/worldgen"
|
"regionio/internal/worldgen"
|
||||||
|
|
@ -86,29 +87,85 @@ func PlaceRuinedPortalPiece(region *decorationRegion, stub *RuinedPortalStub) er
|
||||||
return region.setBlock(x, y, z, state)
|
return region.setBlock(x, y, z, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
orientState := func(state uint16) uint16 {
|
||||||
|
if mirror == "none" && stub.Rotation == 0 {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
s, ok := stateByID(state)
|
||||||
|
if !ok {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
facing, hasFacing := s.Properties["facing"]
|
||||||
|
if !hasFacing {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
switch facing {
|
||||||
|
case "north", "east", "south", "west":
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
steps := stub.Rotation
|
||||||
|
if mirror == "front_back" {
|
||||||
|
switch facing {
|
||||||
|
case "east":
|
||||||
|
facing = "west"
|
||||||
|
case "west":
|
||||||
|
facing = "east"
|
||||||
|
}
|
||||||
|
} else if mirror == "left_right" {
|
||||||
|
switch facing {
|
||||||
|
case "north":
|
||||||
|
facing = "south"
|
||||||
|
case "south":
|
||||||
|
facing = "north"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < steps; i++ {
|
||||||
|
switch facing {
|
||||||
|
case "north":
|
||||||
|
facing = "east"
|
||||||
|
case "east":
|
||||||
|
facing = "south"
|
||||||
|
case "south":
|
||||||
|
facing = "west"
|
||||||
|
case "west":
|
||||||
|
facing = "north"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
props := make(map[string]string, len(s.Properties))
|
||||||
|
for k, v := range s.Properties {
|
||||||
|
props[k] = v
|
||||||
|
}
|
||||||
|
props["facing"] = facing
|
||||||
|
if id, resolved := nameToStateID(s.Name, props); resolved {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
processState := func(x, y, z int, localPos [3]int, state uint16) uint16 {
|
processState := func(x, y, z int, localPos [3]int, state uint16) uint16 {
|
||||||
seed := worldgen.MthGetSeed(x, y, z)
|
seed := worldgen.MthGetSeed(x, y, z)
|
||||||
roll := func(p float32) bool {
|
roll := func(p float32) bool {
|
||||||
r := worldgen.NewLegacy(seed)
|
r := worldgen.NewLegacy(seed)
|
||||||
return r.NextFloat() < p
|
return r.NextFloat() < p
|
||||||
}
|
}
|
||||||
switch state {
|
switch {
|
||||||
case ruinedGoldID:
|
case state == ruinedGoldID:
|
||||||
if roll(0.3) {
|
if roll(0.3) {
|
||||||
return ruinedAirID
|
return ruinedAirID
|
||||||
}
|
}
|
||||||
case ruinedLavaID:
|
case state == ruinedLavaID:
|
||||||
switch {
|
switch {
|
||||||
case stub.Cold:
|
case stub.Cold:
|
||||||
return ruinedNetherrack
|
return ruinedNetherrack
|
||||||
case roll(0.2):
|
case roll(0.2):
|
||||||
return ruinedMagmaID
|
return ruinedMagmaID
|
||||||
}
|
}
|
||||||
case ruinedNetherrack:
|
case state == ruinedNetherrack:
|
||||||
if roll(0.07) {
|
if roll(0.07) {
|
||||||
return ruinedMagmaID
|
return ruinedMagmaID
|
||||||
}
|
}
|
||||||
case stoneBricksID, stoneID, chiseledStoneBricks:
|
case isStoneFamilyForAge(state):
|
||||||
r := worldgen.NewLegacy(seed)
|
r := worldgen.NewLegacy(seed)
|
||||||
if r.NextFloat() >= 0.5 {
|
if r.NextFloat() >= 0.5 {
|
||||||
break
|
break
|
||||||
|
|
@ -117,7 +174,28 @@ func PlaceRuinedPortalPiece(region *decorationRegion, stub *RuinedPortalStub) er
|
||||||
return mossyStoneBricksID
|
return mossyStoneBricksID
|
||||||
}
|
}
|
||||||
return crackedStoneBricksID
|
return crackedStoneBricksID
|
||||||
case ruinedObsidianID:
|
case isStairState(state):
|
||||||
|
r := worldgen.NewLegacy(seed)
|
||||||
|
if r.NextFloat() >= 0.5 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
mossyStairs := withProps("minecraft:mossy_stone_brick_stairs", state)
|
||||||
|
mossySlab := mustState("minecraft:mossy_stone_brick_slab", nil)
|
||||||
|
nonMossy := []uint16{mustState("minecraft:stone_slab", nil), mustState("minecraft:stone_brick_slab", nil)}
|
||||||
|
if r.NextFloat() < stub.Mossiness {
|
||||||
|
pick := int(r.NextIntN(2))
|
||||||
|
if pick == 0 {
|
||||||
|
return mossyStairs
|
||||||
|
}
|
||||||
|
return mossySlab
|
||||||
|
}
|
||||||
|
return nonMossy[pick2(r)]
|
||||||
|
case isSlabState(state):
|
||||||
|
r := worldgen.NewLegacy(seed)
|
||||||
|
if r.NextFloat() < stub.Mossiness {
|
||||||
|
return withProps("minecraft:mossy_stone_brick_slab", state)
|
||||||
|
}
|
||||||
|
case state == ruinedObsidianID:
|
||||||
if roll(0.15) {
|
if roll(0.15) {
|
||||||
return cryingObsidianID
|
return cryingObsidianID
|
||||||
}
|
}
|
||||||
|
|
@ -126,6 +204,7 @@ func PlaceRuinedPortalPiece(region *decorationRegion, stub *RuinedPortalStub) er
|
||||||
}
|
}
|
||||||
|
|
||||||
// Two passes like buildInfoList: solids land before any template air.
|
// Two passes like buildInfoList: solids land before any template air.
|
||||||
|
var airLocals [][3]int
|
||||||
for _, passAir := range []bool{false, true} {
|
for _, passAir := range []bool{false, true} {
|
||||||
for _, b := range blocks {
|
for _, b := range blocks {
|
||||||
isAirLocal := b.State == ruinedAirID || b.State == ruinedCaveAirID
|
isAirLocal := b.State == ruinedAirID || b.State == ruinedCaveAirID
|
||||||
|
|
@ -134,7 +213,15 @@ func PlaceRuinedPortalPiece(region *decorationRegion, stub *RuinedPortalStub) er
|
||||||
}
|
}
|
||||||
p := worldgen.TransformBlockPos(b.Pos, mirror, stub.Rotation, pivot)
|
p := worldgen.TransformBlockPos(b.Pos, mirror, stub.Rotation, pivot)
|
||||||
x, y, z := stub.X+p[0], stub.Y+p[1], stub.Z+p[2]
|
x, y, z := stub.X+p[0], stub.Y+p[1], stub.Z+p[2]
|
||||||
|
if passAir {
|
||||||
|
airLocals = append(airLocals, b.Pos)
|
||||||
|
if !isLavaState(region.getBlock(x, y, z)) {
|
||||||
|
placeCell(b.Pos, ruinedAirID)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
final := processState(x, y, z, b.Pos, b.State)
|
final := processState(x, y, z, b.Pos, b.State)
|
||||||
|
final = orientState(final)
|
||||||
// LavaSubmerged: a template block landing in existing lava keeps
|
// LavaSubmerged: a template block landing in existing lava keeps
|
||||||
// the lava unless the template itself brings lava or magma.
|
// the lava unless the template itself brings lava or magma.
|
||||||
if final != ruinedLavaID && final != ruinedMagmaID && isLavaState(region.getBlock(x, y, z)) {
|
if final != ruinedLavaID && final != ruinedMagmaID && isLavaState(region.getBlock(x, y, z)) {
|
||||||
|
|
@ -144,10 +231,64 @@ func PlaceRuinedPortalPiece(region *decorationRegion, stub *RuinedPortalStub) er
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
floodWaterIntoAir(region, airLocals, mirror, stub.Rotation, pivot, stub)
|
||||||
|
|
||||||
addNetherrackDripColumnsBelowPortal(region, stub, blocks, size, mirror, pivot)
|
addNetherrackDripColumnsBelowPortal(region, stub, blocks, size, mirror, pivot)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// floodWaterIntoAir replicates vanilla's scheduled fluid ticks over a freshly
|
||||||
|
// placed air pocket: every pocket cell connected to surrounding water floods,
|
||||||
|
// while cells sealed off by solids stay air.
|
||||||
|
func floodWaterIntoAir(region *decorationRegion, airLocals [][3]int, mirror string, rotation int, pivot [3]int, stub *RuinedPortalStub) {
|
||||||
|
type cell struct{ x, y, z int }
|
||||||
|
templateAir := map[[3]int]bool{}
|
||||||
|
var airCells []cell
|
||||||
|
for _, local := range airLocals {
|
||||||
|
p := worldgen.TransformBlockPos(local, mirror, rotation, pivot)
|
||||||
|
w := cell{stub.X + p[0], stub.Y + p[1], stub.Z + p[2]}
|
||||||
|
if !monsterIsAir(region.getBlock(w.x, w.y, w.z)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
templateAir[[3]int{w.x, w.y, w.z}] = true
|
||||||
|
airCells = append(airCells, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
flooded := map[[3]int]bool{}
|
||||||
|
queue := []cell{}
|
||||||
|
for _, c := range airCells {
|
||||||
|
key := [3]int{c.x, c.y, c.z}
|
||||||
|
if flooded[key] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
touchesWater := false
|
||||||
|
for _, o := range [][3]int{{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}} {
|
||||||
|
if isWaterState(region.getBlock(c.x+o[0], c.y+o[1], c.z+o[2])) {
|
||||||
|
touchesWater = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if touchesWater {
|
||||||
|
queue = append(queue, c)
|
||||||
|
flooded[key] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for len(queue) > 0 {
|
||||||
|
c := queue[0]
|
||||||
|
queue = queue[1:]
|
||||||
|
region.setBlock(c.x, c.y, c.z, StateWater)
|
||||||
|
for _, o := range [][3]int{{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}} {
|
||||||
|
n := cell{c.x + o[0], c.y + o[1], c.z + o[2]}
|
||||||
|
key := [3]int{n.x, n.y, n.z}
|
||||||
|
if flooded[key] || !templateAir[key] || !monsterIsAir(region.getBlock(n.x, n.y, n.z)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
flooded[key] = true
|
||||||
|
queue = append(queue, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func addNetherrackDripColumnsBelowPortal(region *decorationRegion, stub *RuinedPortalStub, blocks []worldgen.TemplateBlockInfo, size [3]int, mirror string, pivot [3]int) {
|
func addNetherrackDripColumnsBelowPortal(region *decorationRegion, stub *RuinedPortalStub, blocks []worldgen.TemplateBlockInfo, size [3]int, mirror string, pivot [3]int) {
|
||||||
minX, minY, minZ, maxX, _, maxZ := boundingBoxOf(size, mirror, stub.Rotation, pivot, stub.X, stub.Z)
|
minX, minY, minZ, maxX, _, maxZ := boundingBoxOf(size, mirror, stub.Rotation, pivot, stub.X, stub.Z)
|
||||||
for x := minX + 1; x < maxX; x++ {
|
for x := minX + 1; x < maxX; x++ {
|
||||||
|
|
@ -190,3 +331,47 @@ func stubColdAt(region *decorationRegion, x, z int) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pick2(r *worldgen.Legacy) int { return int(r.NextIntN(2)) }
|
||||||
|
|
||||||
|
func mustState(name string, props map[string]string) uint16 {
|
||||||
|
id, ok := nameToStateID(name, props)
|
||||||
|
if !ok {
|
||||||
|
panic("world: missing state: " + name)
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func withProps(name string, from uint16) uint16 {
|
||||||
|
if s, ok := stateByID(from); ok {
|
||||||
|
if id, resolved := nameToStateID(name, s.Properties); resolved {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func isStairState(state uint16) bool {
|
||||||
|
if s, ok := stateByID(state); ok {
|
||||||
|
return strings.HasSuffix(s.Name, "_stairs")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSlabState(state uint16) bool {
|
||||||
|
if s, ok := stateByID(state); ok {
|
||||||
|
return strings.HasSuffix(s.Name, "_slab")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isStoneFamilyForAge covers BlockAgeProcessor's full-block branch:
|
||||||
|
// stone_bricks, stone, and chiseled_stone_bricks.
|
||||||
|
func isStoneFamilyForAge(state uint16) bool {
|
||||||
|
switch state {
|
||||||
|
case stoneBricksID, stoneID, chiseledStoneBricks:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue