package world import ( "encoding/json" "fmt" "regionio/internal/worldgen" ) // decorationRegion is the mutable terrain view used while replaying source // chunk feature passes. A target needs base terrain through radius two: its nine // possible source chunks each inspect biomes in their own radius-one region. type decorationRegion struct { chunks map[[2]int32]*Chunk sourceX int32 sourceZ int32 } func newDecorationRegion(chunks []*Chunk) (*decorationRegion, error) { region := &decorationRegion{chunks: make(map[[2]int32]*Chunk, len(chunks))} for _, chunk := range chunks { if chunk == nil { return nil, fmt.Errorf("world: nil chunk in decoration region") } key := [2]int32{chunk.X, chunk.Z} if _, exists := region.chunks[key]; exists { return nil, fmt.Errorf("world: duplicate decoration chunk (%d,%d)", chunk.X, chunk.Z) } region.chunks[key] = chunk } return region, nil } func (r *decorationRegion) setSource(cx, cz int32) error { if _, ok := r.chunks[[2]int32{cx, cz}]; !ok { return fmt.Errorf("world: decoration source (%d,%d) unavailable", cx, cz) } r.sourceX, r.sourceZ = cx, cz return nil } func (r *decorationRegion) chunkAtBlock(x, z int) *Chunk { return r.chunks[[2]int32{int32(x >> 4), int32(z >> 4)}] } func (r *decorationRegion) getBlock(x, y, z int) uint16 { if y < MinY || y >= MinY+WorldHeight { return StateAir } chunk := r.chunkAtBlock(x, z) if chunk == nil { return StateAir } return chunk.GetBlock(x&15, y, z&15) } func (r *decorationRegion) setBlock(x, y, z int, state uint16) bool { if y < MinY || y >= MinY+WorldHeight { return false } cx, cz := int32(x>>4), int32(z>>4) if abs32(cx-r.sourceX) > 1 || abs32(cz-r.sourceZ) > 1 { return false } chunk := r.chunks[[2]int32{cx, cz}] if chunk == nil { return false } chunk.SetBlock(x&15, y, z&15, state) return true } // heightAt mirrors WorldGenRegion.getHeight: one above the highest matching // block, or MinY when the column has no match. func (r *decorationRegion) heightAt(kind string, x, z int) int { for y := MinY + WorldHeight - 1; y >= MinY; y-- { state := r.getBlock(x, y, z) match := false switch kind { case "WORLD_SURFACE", "WORLD_SURFACE_WG": match = state != StateAir case "OCEAN_FLOOR", "OCEAN_FLOOR_WG": match = stateFlags(state)&flagBlocksMotion != 0 case "MOTION_BLOCKING": match = blocksMotionOrFluid(state) case "MOTION_BLOCKING_NO_LEAVES": match = blocksMotionNoLeaves(state) } if match { return y + 1 } } return MinY } func (r *decorationRegion) getBiome(x, y, z int) (uint16, bool) { chunk := r.chunkAtBlock(x, z) if chunk == nil { return 0, false } return chunk.GetBiome(x&15, y, z&15), true } func (r *decorationRegion) biomeAllowsFeature(set *worldgen.FeatureSet, feature string, stage int, position worldgen.FeaturePosition) bool { id, ok := r.getBiome(position.X, position.Y, position.Z) if !ok { return false } biome, ok := set.Biomes[biomeNameByID(id)] if !ok || stage < 0 || stage >= len(biome.Features) { return false } for _, name := range biome.Features[stage] { if name == feature { return true } } return false } func (r *decorationRegion) sourceBiomes() []string { seen := make(map[uint16]bool) var names []string for cx := r.sourceX - 1; cx <= r.sourceX+1; cx++ { for cz := r.sourceZ - 1; cz <= r.sourceZ+1; cz++ { chunk := r.chunks[[2]int32{cx, cz}] if chunk == nil { continue } for si := 0; si < SectionCount; si++ { for bx := 0; bx < biomeCellsXZ; bx++ { for by := 0; by < biomeCellsXZ; by++ { for bz := 0; bz < biomeCellsXZ; bz++ { id := chunk.GetBiome(bx*biomeCellSize, MinY+si*16+by*biomeCellSize, bz*biomeCellSize) if !seen[id] { seen[id] = true names = append(names, biomeNameByID(id)) } } } } } } } return names } func (r *decorationRegion) scheduledFeatures(stage int) ([]worldgen.ScheduledFeature, error) { set, err := worldgen.LoadFeatureSet() if err != nil { return nil, err } if err := r.ensureSourceNeighborhood(); err != nil { return nil, err } return set.FeatureSchedule(possibleBiomeOrder(), r.sourceBiomes(), stage) } func (r *decorationRegion) ensureSourceNeighborhood() error { for cx := r.sourceX - 1; cx <= r.sourceX+1; cx++ { for cz := r.sourceZ - 1; cz <= r.sourceZ+1; cz++ { if _, ok := r.chunks[[2]int32{cx, cz}]; !ok { return fmt.Errorf("world: source biome neighborhood missing (%d,%d)", cx, cz) } } } return nil } func (r *decorationRegion) placementContext(biomeAllows func(worldgen.FeaturePosition) bool) worldgen.PlacementContext { set, err := worldgen.LoadFeatureSet() if err != nil { panic("world: loading feature datapack: " + err.Error()) } return worldgen.PlacementContext{ MinY: MinY, Height: WorldHeight, BiomeAllows: biomeAllows, HeightAt: r.heightAt, BlockPredicate: func(predicate json.RawMessage, position worldgen.FeaturePosition) (bool, error) { return r.testBlockPredicate(set, predicate, position) }, } } func abs32(value int32) int32 { if value < 0 { return -value } return value }