world: replay datapack features across regions
This commit is contained in:
parent
f1d1d109f4
commit
d8b5d0f79b
13 changed files with 1259 additions and 8 deletions
242
internal/world/geodes.go
Normal file
242
internal/world/geodes.go
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
package world
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"regionio/internal/worldgen"
|
||||
)
|
||||
|
||||
const geodesStage = 2
|
||||
|
||||
type geodePoint struct {
|
||||
x, y, z int
|
||||
offset int
|
||||
}
|
||||
|
||||
// placeScheduledGeodes replays the stage-2 amethyst geode feature. The layer
|
||||
// calculation follows GeodeFeature.place: sampled distance points are combined
|
||||
// with vanilla normal-noise perturbation, then the nearest layer threshold
|
||||
// selects filling, inner, middle, or outer material.
|
||||
func (r *decorationRegion) placeScheduledGeodes(seed int64) error {
|
||||
set, err := worldgen.LoadFeatureSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.ensureSourceNeighborhood(); err != nil {
|
||||
return err
|
||||
}
|
||||
schedule, err := set.FeatureSchedule(possibleBiomeOrder(), r.sourceBiomes(), geodesStage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
random, decorationSeed := worldgen.DecorationRandom(seed, int(r.sourceX), int(r.sourceZ))
|
||||
origin := worldgen.FeaturePosition{X: int(r.sourceX) << 4, Y: MinY, Z: int(r.sourceZ) << 4}
|
||||
for _, scheduled := range schedule {
|
||||
placed, ok := set.Placed[scheduled.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
configured, ok := set.Configured[placed.Feature]
|
||||
if !ok || configured.Type != "minecraft:geode" {
|
||||
continue
|
||||
}
|
||||
config, err := set.Geode(placed.Feature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
random.SetFeatureSeed(decorationSeed, scheduled.Index, geodesStage)
|
||||
context := r.placementContext(func(position worldgen.FeaturePosition) bool {
|
||||
return r.biomeAllowsFeature(set, scheduled.Name, geodesStage, position)
|
||||
})
|
||||
if err := set.ForEachPlacementPosition(scheduled.Name, random, origin, context, func(position worldgen.FeaturePosition) error {
|
||||
r.placeGeode(random, seed, position, config, set)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *decorationRegion) placeGeode(random worldgen.RandomSource, seed int64, origin worldgen.FeaturePosition, config worldgen.GeodeFeatureConfig, set *worldgen.FeatureSet) bool {
|
||||
distributionPoints := sampleGeodeInt(random, config.DistributionMin, config.DistributionMax)
|
||||
pointScale := float64(distributionPoints) / float64(config.OuterWallMax)
|
||||
fillingThreshold := 1 / math.Sqrt(config.FillingLayer)
|
||||
innerThreshold := 1 / math.Sqrt(config.InnerLayer+pointScale)
|
||||
middleThreshold := 1 / math.Sqrt(config.MiddleLayer+pointScale)
|
||||
outerThreshold := 1 / math.Sqrt(config.OuterLayer+pointScale)
|
||||
crackSize := config.BaseCrackSize + random.NextDouble()/2
|
||||
if distributionPoints > 3 {
|
||||
crackSize += pointScale
|
||||
}
|
||||
crackThreshold := 1 / math.Sqrt(crackSize)
|
||||
generateCrack := random.NextFloat() < float32(config.CrackChance)
|
||||
|
||||
points := make([]geodePoint, 0, distributionPoints)
|
||||
invalid := 0
|
||||
invalidBlocks := geodeTagIDs(set, config.InvalidBlocksTag)
|
||||
for i := 0; i < distributionPoints; i++ {
|
||||
point := geodePoint{
|
||||
x: origin.X + sampleGeodeInt(random, config.OuterWallMin, config.OuterWallMax),
|
||||
y: origin.Y + sampleGeodeInt(random, config.OuterWallMin, config.OuterWallMax),
|
||||
z: origin.Z + sampleGeodeInt(random, config.OuterWallMin, config.OuterWallMax),
|
||||
offset: sampleGeodeInt(random, config.PointOffsetMin, config.PointOffsetMax),
|
||||
}
|
||||
state := r.getBlock(point.x, point.y, point.z)
|
||||
if state == StateAir || invalidBlocks[state] {
|
||||
invalid++
|
||||
if invalid > config.InvalidBlocksThreshold {
|
||||
return false
|
||||
}
|
||||
}
|
||||
points = append(points, point)
|
||||
}
|
||||
|
||||
crackPoints := geodeCrackPoints(origin, random, distributionPoints, generateCrack)
|
||||
noise := worldgen.NewNormalNoise(worldgen.NewLegacy(seed), -4, []float64{1})
|
||||
cannotReplace := geodeTagIDs(set, config.CannotReplaceTag)
|
||||
placed := false
|
||||
var potentialPlacements [][3]int
|
||||
for z := origin.Z + config.MinGenOffset; z <= origin.Z+config.MaxGenOffset; z++ {
|
||||
for y := origin.Y + config.MinGenOffset; y <= origin.Y+config.MaxGenOffset; y++ {
|
||||
for x := origin.X + config.MinGenOffset; x <= origin.X+config.MaxGenOffset; x++ {
|
||||
perturbation := noise.GetValue(float64(x), float64(y), float64(z)) * config.NoiseMultiplier
|
||||
innerDistance := geodeDistance(x, y, z, points, perturbation)
|
||||
if innerDistance < outerThreshold {
|
||||
continue
|
||||
}
|
||||
current := r.getBlock(x, y, z)
|
||||
if cannotReplace[current] {
|
||||
continue
|
||||
}
|
||||
crackDistance := 0.0
|
||||
if generateCrack {
|
||||
crackDistance = geodeDistanceWithOffset(x, y, z, crackPoints, config.CrackPointOffset, perturbation)
|
||||
}
|
||||
var state uint16
|
||||
switch {
|
||||
case generateCrack && crackDistance >= crackThreshold && innerDistance < fillingThreshold:
|
||||
state = StateAir
|
||||
case innerDistance >= fillingThreshold:
|
||||
state, _ = nameToStateID(config.Filling.Name, config.Filling.Properties)
|
||||
case innerDistance >= innerThreshold:
|
||||
alternate := random.NextFloat() < float32(config.UseAlternateLayerChance)
|
||||
provider := config.Inner
|
||||
if alternate && config.AlternateInner.Name != "" {
|
||||
provider = config.AlternateInner
|
||||
}
|
||||
state, _ = nameToStateID(provider.Name, provider.Properties)
|
||||
if (!config.PlacementsRequireAlternate || alternate) &&
|
||||
random.NextFloat() < float32(config.UsePotentialPlacementsChance) {
|
||||
potentialPlacements = append(potentialPlacements, [3]int{x, y, z})
|
||||
}
|
||||
case innerDistance >= middleThreshold:
|
||||
state, _ = nameToStateID(config.Middle.Name, config.Middle.Properties)
|
||||
case innerDistance >= outerThreshold:
|
||||
state, _ = nameToStateID(config.Outer.Name, config.Outer.Properties)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if r.setBlock(x, y, z, state) {
|
||||
placed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
r.placeGeodeInnerPlacements(random, potentialPlacements, config, cannotReplace)
|
||||
return placed
|
||||
}
|
||||
|
||||
func (r *decorationRegion) placeGeodeInnerPlacements(random worldgen.RandomSource, positions [][3]int, config worldgen.GeodeFeatureConfig, cannotReplace map[uint16]bool) {
|
||||
if len(config.InnerPlacements) == 0 {
|
||||
return
|
||||
}
|
||||
directions := [...]struct {
|
||||
dx, dy, dz int
|
||||
name string
|
||||
}{
|
||||
{0, -1, 0, "down"}, {0, 1, 0, "up"}, {0, 0, -1, "north"},
|
||||
{0, 0, 1, "south"}, {-1, 0, 0, "west"}, {1, 0, 0, "east"},
|
||||
}
|
||||
for _, position := range positions {
|
||||
placement := config.InnerPlacements[int(random.NextIntN(int32(len(config.InnerPlacements))))]
|
||||
for _, direction := range directions {
|
||||
x, y, z := position[0]+direction.dx, position[1]+direction.dy, position[2]+direction.dz
|
||||
current := r.getBlock(x, y, z)
|
||||
if current != StateAir && !isWaterState(current) {
|
||||
continue
|
||||
}
|
||||
props := make(map[string]string, len(placement.Properties))
|
||||
for key, value := range placement.Properties {
|
||||
props[key] = value
|
||||
}
|
||||
props["facing"] = direction.name
|
||||
props["waterlogged"] = "false"
|
||||
if isWaterState(current) {
|
||||
props["waterlogged"] = "true"
|
||||
}
|
||||
state, ok := nameToStateID(placement.Name, props)
|
||||
if ok && !cannotReplace[current] {
|
||||
if r.setBlock(x, y, z, state) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sampleGeodeInt(random worldgen.RandomSource, min, max int) int {
|
||||
if max <= min {
|
||||
return min
|
||||
}
|
||||
return min + int(random.NextIntN(int32(max-min+1)))
|
||||
}
|
||||
|
||||
func geodeTagIDs(set *worldgen.FeatureSet, tag string) map[uint16]bool {
|
||||
if strings.HasPrefix(tag, "#") {
|
||||
tag = tag[1:]
|
||||
}
|
||||
ids := make(map[uint16]bool)
|
||||
for _, name := range flattenBlockTag(set, tag, nil) {
|
||||
if id, ok := nameToStateID(name, nil); ok {
|
||||
ids[id] = true
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func geodeDistance(x, y, z int, points []geodePoint, perturbation float64) float64 {
|
||||
distance := 0.0
|
||||
for _, point := range points {
|
||||
dx, dy, dz := x-point.x, y-point.y, z-point.z
|
||||
distance += 1/math.Sqrt(float64(dx*dx+dy*dy+dz*dz+point.offset)) + perturbation
|
||||
}
|
||||
return distance
|
||||
}
|
||||
|
||||
func geodeDistanceWithOffset(x, y, z int, points []geodePoint, offset int, perturbation float64) float64 {
|
||||
distance := 0.0
|
||||
for _, point := range points {
|
||||
dx, dy, dz := x-point.x, y-point.y, z-point.z
|
||||
distance += 1/math.Sqrt(float64(dx*dx+dy*dy+dz*dz+offset)) + perturbation
|
||||
}
|
||||
return distance
|
||||
}
|
||||
|
||||
func geodeCrackPoints(origin worldgen.FeaturePosition, random worldgen.RandomSource, distributionPoints int, enabled bool) []geodePoint {
|
||||
if !enabled {
|
||||
return nil
|
||||
}
|
||||
offset := distributionPoints*2 + 1
|
||||
switch random.NextIntN(4) {
|
||||
case 0:
|
||||
return []geodePoint{{x: origin.X + offset, y: origin.Y + 7, z: origin.Z}, {x: origin.X + offset, y: origin.Y + 5, z: origin.Z}, {x: origin.X + offset, y: origin.Y + 1, z: origin.Z}}
|
||||
case 1:
|
||||
return []geodePoint{{x: origin.X, y: origin.Y + 7, z: origin.Z + offset}, {x: origin.X, y: origin.Y + 5, z: origin.Z + offset}, {x: origin.X, y: origin.Y + 1, z: origin.Z + offset}}
|
||||
case 2:
|
||||
return []geodePoint{{x: origin.X + offset, y: origin.Y + 7, z: origin.Z + offset}, {x: origin.X + offset, y: origin.Y + 5, z: origin.Z + offset}, {x: origin.X + offset, y: origin.Y + 1, z: origin.Z + offset}}
|
||||
default:
|
||||
return []geodePoint{{x: origin.X, y: origin.Y + 7, z: origin.Z}, {x: origin.X, y: origin.Y + 5, z: origin.Z}, {x: origin.X, y: origin.Y + 1, z: origin.Z}}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue