package worldgen import ( "archive/zip" "bytes" _ "embed" "encoding/json" "fmt" "math" "path" "sort" "strings" "sync" ) //go:embed feature_data.zip var featureData []byte // FeatureSet is the validated vanilla datapack graph used by decoration. type FeatureSet struct { Configured map[string]ConfiguredFeature Placed map[string]PlacedFeature Biomes map[string]BiomeGeneration BlockTags map[string][]string } type IndexedFeature struct { Name string Index int } // ScheduledFeature is one feature selected for a decoration stage. Index is // the stable FeatureSorter index within that stage and must be used when // deriving the feature random seed; it is not the index in any one biome. type ScheduledFeature struct { Name string Index int } type ConfiguredFeature struct { Type string Config json.RawMessage } type PlacedFeature struct { Feature string Placement []PlacementModifier } type PlacementModifier struct { Type string Raw json.RawMessage } type OreFeatureConfig struct { Size int DiscardAirExposure float64 `json:"discard_chance_on_air_exposure"` Targets []OreTarget } type OreTarget struct { State struct { Name string `json:"Name"` } `json:"state"` Target struct { PredicateType string `json:"predicate_type"` Tag string `json:"tag"` } `json:"target"` } // DiskFeatureConfig is the subset of DiskFeature used by overworld disks. // Radius is an inclusive uniform integer provider in the vanilla datapack. type DiskFeatureConfig struct { HalfHeight int RadiusMin, RadiusMax int State BlockState Fallback BlockState Rules []DiskStateRule Targets []string } type DiskStateRule struct { IfTrue json.RawMessage Then BlockState } type GeodeFeatureConfig struct { Outer, Middle, Inner, Filling BlockState AlternateInner BlockState InnerPlacements []BlockState DistributionMin, DistributionMax int OuterWallMin, OuterWallMax int PointOffsetMin, PointOffsetMax int MaxGenOffset, MinGenOffset int FillingLayer, InnerLayer float64 MiddleLayer, OuterLayer float64 NoiseMultiplier float64 InvalidBlocksThreshold int CannotReplaceTag, InvalidBlocksTag string CrackChance, BaseCrackSize float64 CrackPointOffset int UseAlternateLayerChance float64 PlacementsRequireAlternate bool UsePotentialPlacementsChance float64 } type VegetationPatchFeatureConfig struct { Surface string DepthMin, DepthMax int XZRadiusMin, XZRadiusMax int VerticalRange int ExtraBottomBlockChance float32 ExtraEdgeColumnChance float32 VegetationChance float32 Ground BlockState ReplaceableTag string Vegetation FeatureRef } // SimpleBlockFeatureConfig is the datapack form used by simple_block. The // provider entries retain their vanilla weights and named block properties so // the feature can choose a state with the feature RNG at placement time. type SimpleBlockFeatureConfig struct { States []WeightedBlockState } type WeightedBlockState struct { State BlockState Weight int } type UnderwaterMagmaFeatureConfig struct { FloorSearchRange int `json:"floor_search_range"` PlacementProbability float32 `json:"placement_probability_per_valid_position"` PlacementRadiusAroundFloor int `json:"placement_radius_around_floor"` } type ProbabilityFeatureConfig struct { Probability float32 } type SpringFeatureConfig struct { HoleCount int `json:"hole_count"` RequiresBlockBelow bool `json:"requires_block_below"` RockCount int `json:"rock_count"` State BlockState `json:"state"` ValidBlocks []string `json:"valid_blocks"` } type TreeFeatureConfig struct { TrunkProvider struct { Type string `json:"type"` State BlockState `json:"state"` } `json:"trunk_provider"` FoliageProvider struct { Type string `json:"type"` State BlockState `json:"state"` } `json:"foliage_provider"` TrunkPlacer struct { Type string `json:"type"` BaseHeight int `json:"base_height"` HeightRandA int `json:"height_rand_a"` HeightRandB int `json:"height_rand_b"` } `json:"trunk_placer"` FoliagePlacer struct { Type string `json:"type"` Height int `json:"height"` Offset int `json:"offset"` Radius int `json:"radius"` } `json:"foliage_placer"` } type FeatureRef struct { Name string Placement []PlacementModifier } type RandomSelectorConfig struct { Default FeatureRef Features []RandomSelectorEntry } type RandomSelectorEntry struct { Chance float32 Feature FeatureRef } type BlockState struct { Name string `json:"Name"` Properties map[string]string `json:"Properties"` } type PlacementPlan struct { Count CountProvider RarityChance int HeightDistribution string HeightPlateau int MinY HeightProvider MaxY HeightProvider } type FeaturePosition struct { X, Y, Z int } type PlacementContext struct { MinY, Height int BiomeAllows func(FeaturePosition) bool HeightAt func(string, int, int) int BlockPredicate func(json.RawMessage, FeaturePosition) (bool, error) } type CountProvider struct { Min, Max int Weighted []WeightedInt } type WeightedInt struct { Value, Weight int } type HeightProvider struct { Absolute *int AboveBottom *int BelowTop *int } func (p CountProvider) Sample(r RandomSource) int { if len(p.Weighted) > 0 { total := 0 for _, entry := range p.Weighted { total += entry.Weight } if total <= 0 { return 0 } roll := int(r.NextIntN(int32(total))) for _, entry := range p.Weighted { if roll < entry.Weight { return entry.Value } roll -= entry.Weight } } if p.Max <= p.Min { return p.Min } return p.Min + int(r.NextIntN(int32(p.Max-p.Min+1))) } func (p HeightProvider) Resolve(minY, height int) int { if p.Absolute != nil { return *p.Absolute } if p.AboveBottom != nil { return minY + *p.AboveBottom } if p.BelowTop != nil { return minY + height - 1 - *p.BelowTop } return minY } func (p PlacementPlan) SampleY(r RandomSource, minY, height int) int { lo, hi := p.MinY.Resolve(minY, height), p.MaxY.Resolve(minY, height) if hi < lo { return lo } span := hi - lo + 1 if p.HeightDistribution == "minecraft:trapezoid" { rangeSize := span - 1 if rangeSize <= 0 { return lo } plateau := p.HeightPlateau if plateau < 0 || plateau > rangeSize { return lo } left := (rangeSize - plateau) / 2 right := rangeSize - left return lo + int(r.NextIntN(int32(right+1))) + int(r.NextIntN(int32(left+1))) } if p.HeightDistribution == "minecraft:very_biased_to_bottom" { const inner = 8 if span-inner <= 0 { return lo } first := int(r.NextIntN(int32(span - inner))) return lo + int(r.NextIntN(int32(first+inner))) } return lo + int(r.NextIntN(int32(span))) } func DecorationRandom(seed int64, chunkX, chunkZ int) (*WorldgenRandom, int64) { random := NewWorldgenRandom(0) decorationSeed := random.SetDecorationSeed(seed, chunkX<<4, chunkZ<<4) return random, decorationSeed } type BiomeGeneration struct { Carvers json.RawMessage `json:"carvers"` Features [][]string `json:"features"` } var ( featureSetOnce sync.Once featureSet *FeatureSet featureSetErr error ) // LoadFeatureSet parses and validates the committed vanilla 26.1.2 feature // datapack archive. The result is immutable and shared by all worlds. func LoadFeatureSet() (*FeatureSet, error) { featureSetOnce.Do(func() { featureSet, featureSetErr = loadFeatureSet(featureData) }) return featureSet, featureSetErr } // FeatureSteps mirrors FeatureSorter.buildFeaturesPerStep. biomeOrder must be // BiomeSource.possibleBiomes encounter order because it assigns the stable // identity used to break otherwise unconstrained graph ties. func (s *FeatureSet) FeatureSteps(biomeOrder []string) ([][]string, error) { type node struct { stage, identity int name string } identities := make(map[string]int) nodes := make(map[[2]int]node) edges := make(map[[2]int]map[[2]int]bool) maxStages := 0 for _, biomeName := range biomeOrder { biome, ok := s.Biomes[biomeName] if !ok { continue } if len(biome.Features) > maxStages { maxStages = len(biome.Features) } var previous [2]int hasPrevious := false for stage, names := range biome.Features { for _, name := range names { identity, ok := identities[name] if !ok { identity = len(identities) identities[name] = identity } key := [2]int{stage, identity} nodes[key] = node{stage: stage, identity: identity, name: name} if hasPrevious { if edges[previous] == nil { edges[previous] = make(map[[2]int]bool) } edges[previous][key] = true } previous, hasPrevious = key, true } } } less := func(a, b [2]int) bool { return a[0] < b[0] || a[0] == b[0] && a[1] < b[1] } keys := make([][2]int, 0, len(nodes)) for key := range nodes { keys = append(keys, key) } sort.Slice(keys, func(i, j int) bool { return less(keys[i], keys[j]) }) visited := make(map[[2]int]bool) visiting := make(map[[2]int]bool) ordered := make([][2]int, 0, len(keys)) var visit func([2]int) error visit = func(key [2]int) error { if visited[key] { return nil } if visiting[key] { return fmt.Errorf("worldgen: feature order cycle") } visiting[key] = true children := make([][2]int, 0, len(edges[key])) for child := range edges[key] { children = append(children, child) } sort.Slice(children, func(i, j int) bool { return less(children[i], children[j]) }) for _, child := range children { if err := visit(child); err != nil { return err } } delete(visiting, key) visited[key] = true ordered = append(ordered, key) return nil } for _, key := range keys { if err := visit(key); err != nil { return nil, err } } steps := make([][]string, maxStages) for i := len(ordered) - 1; i >= 0; i-- { n := nodes[ordered[i]] steps[n.stage] = append(steps[n.stage], n.name) } return steps, nil } func IndexedFeatures(step []string, wanted map[string]bool) []IndexedFeature { result := make([]IndexedFeature, 0, len(wanted)) for index, name := range step { if wanted[name] { result = append(result, IndexedFeature{Name: name, Index: index}) } } return result } // FeatureSchedule returns the features present in the union of sourceBiomes // for one decoration stage. allBiomeOrder is the complete BiomeSource encounter // order used to build FeatureSorter, not the order of the local source region. // The result is stable and contains each placed feature at most once. func (s *FeatureSet) FeatureSchedule(allBiomeOrder, sourceBiomes []string, stage int) ([]ScheduledFeature, error) { steps, err := s.FeatureSteps(allBiomeOrder) if err != nil { return nil, err } if stage < 0 || stage >= len(steps) { return nil, fmt.Errorf("worldgen: feature stage %d out of range", stage) } wanted := make(map[string]bool) for _, biomeName := range sourceBiomes { biome, ok := s.Biomes[biomeName] if !ok || stage >= len(biome.Features) { continue } for _, name := range biome.Features[stage] { wanted[name] = true } } result := make([]ScheduledFeature, 0, len(wanted)) for index, name := range steps[stage] { if wanted[name] { result = append(result, ScheduledFeature{Name: name, Index: index}) } } return result, nil } func (s *FeatureSet) Ore(name string) (OreFeatureConfig, error) { configured, ok := s.Configured[name] if !ok || configured.Type != "minecraft:ore" { return OreFeatureConfig{}, fmt.Errorf("worldgen: %s is not an ore feature", name) } var config OreFeatureConfig if err := json.Unmarshal(configured.Config, &config); err != nil { return OreFeatureConfig{}, fmt.Errorf("worldgen: decode %s: %w", name, err) } if config.Size < 1 || len(config.Targets) == 0 { return OreFeatureConfig{}, fmt.Errorf("worldgen: invalid ore config %s", name) } return config, nil } func (s *FeatureSet) Disk(name string) (DiskFeatureConfig, error) { configured, ok := s.Configured[name] if !ok || configured.Type != "minecraft:disk" { return DiskFeatureConfig{}, fmt.Errorf("worldgen: %s is not a disk feature", name) } var raw struct { HalfHeight int `json:"half_height"` Radius struct { Min int `json:"min_inclusive"` Max int `json:"max_inclusive"` } `json:"radius"` StateProvider json.RawMessage `json:"state_provider"` Target struct { Blocks json.RawMessage `json:"blocks"` } `json:"target"` } if err := json.Unmarshal(configured.Config, &raw); err != nil { return DiskFeatureConfig{}, fmt.Errorf("worldgen: decode %s: %w", name, err) } var provider struct { State BlockState `json:"state"` Fallback struct { State BlockState `json:"state"` } `json:"fallback"` Rules []struct { Then struct { State BlockState `json:"state"` } `json:"then"` IfTrue json.RawMessage `json:"if_true"` } `json:"rules"` } if err := json.Unmarshal(raw.StateProvider, &provider); err != nil { return DiskFeatureConfig{}, fmt.Errorf("worldgen: decode %s state provider: %w", name, err) } config := DiskFeatureConfig{ HalfHeight: raw.HalfHeight, RadiusMin: raw.Radius.Min, RadiusMax: raw.Radius.Max, State: provider.State, Fallback: provider.Fallback.State, } for _, rule := range provider.Rules { if len(rule.IfTrue) != 0 && rule.Then.State.Name != "" { config.Rules = append(config.Rules, DiskStateRule{IfTrue: rule.IfTrue, Then: rule.Then.State}) } } if config.State.Name == "" { config.State = config.Fallback } if config.Fallback.Name == "" { config.Fallback = config.State } if len(raw.Target.Blocks) == 0 { return DiskFeatureConfig{}, fmt.Errorf("worldgen: invalid disk target %s", name) } if raw.Target.Blocks[0] == '"' { var block string if err := json.Unmarshal(raw.Target.Blocks, &block); err != nil { return DiskFeatureConfig{}, err } config.Targets = []string{block} } else if err := json.Unmarshal(raw.Target.Blocks, &config.Targets); err != nil { return DiskFeatureConfig{}, err } if config.RadiusMin < 0 || config.RadiusMax < config.RadiusMin || config.State.Name == "" || len(config.Targets) == 0 { return DiskFeatureConfig{}, fmt.Errorf("worldgen: invalid disk config %s", name) } return config, nil } func (s *FeatureSet) Geode(name string) (GeodeFeatureConfig, error) { configured, ok := s.Configured[name] if !ok || configured.Type != "minecraft:geode" { return GeodeFeatureConfig{}, fmt.Errorf("worldgen: %s is not a geode feature", name) } var raw struct { Distribution struct { Min int `json:"min_inclusive"` Max int `json:"max_inclusive"` } `json:"distribution_points"` OuterWall struct { Min int `json:"min_inclusive"` Max int `json:"max_inclusive"` } `json:"outer_wall_distance"` PointOffset struct { Min int `json:"min_inclusive"` Max int `json:"max_inclusive"` } `json:"point_offset"` MinGenOffset int `json:"min_gen_offset"` MaxGenOffset int `json:"max_gen_offset"` Crack struct { BaseSize float64 `json:"base_crack_size"` PointOffset int `json:"crack_point_offset"` Chance float64 `json:"generate_crack_chance"` } `json:"crack"` Layers struct { Filling float64 `json:"filling"` Inner float64 `json:"inner_layer"` Middle float64 `json:"middle_layer"` Outer float64 `json:"outer_layer"` } `json:"layers"` NoiseMultiplier float64 `json:"noise_multiplier"` InvalidBlocksThreshold int `json:"invalid_blocks_threshold"` CannotReplace string `json:"cannot_replace"` InvalidBlocks string `json:"invalid_blocks"` UseAlternateLayerChance float64 `json:"use_alternate_layer0_chance"` PlacementsRequireAlternate bool `json:"placements_require_layer0_alternate"` UsePotentialPlacementsChance float64 `json:"use_potential_placements_chance"` } if err := json.Unmarshal(configured.Config, &raw); err != nil { return GeodeFeatureConfig{}, fmt.Errorf("worldgen: decode %s: %w", name, err) } var wrapper struct { Blocks map[string]json.RawMessage `json:"blocks"` } if err := json.Unmarshal(configured.Config, &wrapper); err != nil { return GeodeFeatureConfig{}, err } blocks := wrapper.Blocks stateProvider := func(key string) BlockState { var provider struct { State BlockState `json:"state"` } _ = json.Unmarshal(blocks[key], &provider) return provider.State } stringValue := func(key string) string { var value string _ = json.Unmarshal(blocks[key], &value) return value } var innerPlacements []BlockState _ = json.Unmarshal(blocks["inner_placements"], &innerPlacements) config := GeodeFeatureConfig{ Outer: stateProvider("outer_layer_provider"), Middle: stateProvider("middle_layer_provider"), Inner: stateProvider("inner_layer_provider"), Filling: stateProvider("filling_provider"), AlternateInner: stateProvider("alternate_inner_layer_provider"), InnerPlacements: innerPlacements, DistributionMin: raw.Distribution.Min, DistributionMax: raw.Distribution.Max, OuterWallMin: raw.OuterWall.Min, OuterWallMax: raw.OuterWall.Max, PointOffsetMin: raw.PointOffset.Min, PointOffsetMax: raw.PointOffset.Max, MinGenOffset: raw.MinGenOffset, MaxGenOffset: raw.MaxGenOffset, FillingLayer: raw.Layers.Filling, InnerLayer: raw.Layers.Inner, MiddleLayer: raw.Layers.Middle, OuterLayer: raw.Layers.Outer, NoiseMultiplier: raw.NoiseMultiplier, InvalidBlocksThreshold: raw.InvalidBlocksThreshold, CannotReplaceTag: stringValue("cannot_replace"), InvalidBlocksTag: stringValue("invalid_blocks"), CrackChance: raw.Crack.Chance, BaseCrackSize: raw.Crack.BaseSize, CrackPointOffset: raw.Crack.PointOffset, UseAlternateLayerChance: raw.UseAlternateLayerChance, PlacementsRequireAlternate: raw.PlacementsRequireAlternate, UsePotentialPlacementsChance: raw.UsePotentialPlacementsChance, } if config.Outer.Name == "" || config.Middle.Name == "" || config.Inner.Name == "" || config.Filling.Name == "" || config.DistributionMin < 1 || config.DistributionMax < config.DistributionMin || config.OuterWallMin < 1 || config.OuterWallMax < config.OuterWallMin || config.PointOffsetMin < 0 || config.PointOffsetMax < config.PointOffsetMin || config.MaxGenOffset < config.MinGenOffset || config.FillingLayer <= 0 || config.InnerLayer <= 0 || config.MiddleLayer <= 0 || config.OuterLayer <= 0 || config.NoiseMultiplier < 0 || config.InvalidBlocksThreshold < 0 || config.CannotReplaceTag == "" || config.InvalidBlocksTag == "" { return GeodeFeatureConfig{}, fmt.Errorf("worldgen: invalid geode config %s", name) } return config, nil } func (s *FeatureSet) VegetationPatch(name string) (VegetationPatchFeatureConfig, error) { configured, ok := s.Configured[name] if !ok || configured.Type != "minecraft:vegetation_patch" { return VegetationPatchFeatureConfig{}, fmt.Errorf("worldgen: %s is not a vegetation patch", name) } var raw struct { Surface string `json:"surface"` Depth json.RawMessage `json:"depth"` XZRadius json.RawMessage `json:"xz_radius"` VerticalRange int `json:"vertical_range"` ExtraBottomBlockChance float32 `json:"extra_bottom_block_chance"` ExtraEdgeColumnChance float32 `json:"extra_edge_column_chance"` VegetationChance float32 `json:"vegetation_chance"` Replaceable string `json:"replaceable"` GroundState struct { State BlockState `json:"state"` } `json:"ground_state"` VegetationFeature json.RawMessage `json:"vegetation_feature"` } if err := json.Unmarshal(configured.Config, &raw); err != nil { return VegetationPatchFeatureConfig{}, fmt.Errorf("worldgen: decode %s: %w", name, err) } depth, err := parseIntProvider(raw.Depth) if err != nil || len(depth.Weighted) != 0 { return VegetationPatchFeatureConfig{}, fmt.Errorf("worldgen: %s depth: %w", name, err) } radius, err := parseIntProvider(raw.XZRadius) if err != nil || len(radius.Weighted) != 0 { return VegetationPatchFeatureConfig{}, fmt.Errorf("worldgen: %s radius: %w", name, err) } vegetation, err := parseFeatureRef(raw.VegetationFeature) if err != nil { return VegetationPatchFeatureConfig{}, fmt.Errorf("worldgen: %s vegetation: %w", name, err) } config := VegetationPatchFeatureConfig{ Surface: raw.Surface, DepthMin: depth.Min, DepthMax: depth.Max, XZRadiusMin: radius.Min, XZRadiusMax: radius.Max, VerticalRange: raw.VerticalRange, ExtraBottomBlockChance: raw.ExtraBottomBlockChance, ExtraEdgeColumnChance: raw.ExtraEdgeColumnChance, VegetationChance: raw.VegetationChance, Ground: raw.GroundState.State, ReplaceableTag: raw.Replaceable, Vegetation: vegetation, } if config.Surface != "floor" && config.Surface != "ceiling" || config.DepthMin < 1 || config.DepthMax < config.DepthMin || config.XZRadiusMin < 1 || config.XZRadiusMax < config.XZRadiusMin || config.VerticalRange < 1 || config.Ground.Name == "" || config.ReplaceableTag == "" { return VegetationPatchFeatureConfig{}, fmt.Errorf("worldgen: invalid vegetation patch %s", name) } return config, nil } func (s *FeatureSet) SimpleBlock(name string) (SimpleBlockFeatureConfig, error) { configured, ok := s.Configured[name] if !ok || configured.Type != "minecraft:simple_block" { return SimpleBlockFeatureConfig{}, fmt.Errorf("worldgen: %s is not a simple block feature", name) } var raw struct { ToPlace json.RawMessage `json:"to_place"` } if err := json.Unmarshal(configured.Config, &raw); err != nil { return SimpleBlockFeatureConfig{}, fmt.Errorf("worldgen: decode %s: %w", name, err) } var provider struct { Type string `json:"type"` State BlockState `json:"state"` Entries []struct { Data BlockState `json:"data"` Weight int `json:"weight"` } `json:"entries"` } if err := json.Unmarshal(raw.ToPlace, &provider); err != nil { return SimpleBlockFeatureConfig{}, fmt.Errorf("worldgen: decode %s provider: %w", name, err) } config := SimpleBlockFeatureConfig{} switch provider.Type { case "minecraft:simple_state_provider": config.States = []WeightedBlockState{{State: provider.State, Weight: 1}} case "minecraft:weighted_state_provider": for _, entry := range provider.Entries { if entry.Weight > 0 && entry.Data.Name != "" { config.States = append(config.States, WeightedBlockState{State: entry.Data, Weight: entry.Weight}) } } default: return SimpleBlockFeatureConfig{}, fmt.Errorf("worldgen: unsupported simple block provider %q", provider.Type) } if len(config.States) == 0 { return SimpleBlockFeatureConfig{}, fmt.Errorf("worldgen: invalid simple block feature %s", name) } return config, nil } func (s *FeatureSet) UnderwaterMagma(name string) (UnderwaterMagmaFeatureConfig, error) { configured, ok := s.Configured[name] if !ok || configured.Type != "minecraft:underwater_magma" { return UnderwaterMagmaFeatureConfig{}, fmt.Errorf("worldgen: %s is not underwater magma", name) } var config UnderwaterMagmaFeatureConfig if err := json.Unmarshal(configured.Config, &config); err != nil { return UnderwaterMagmaFeatureConfig{}, fmt.Errorf("worldgen: decode %s: %w", name, err) } if config.FloorSearchRange < 1 || config.PlacementRadiusAroundFloor < 0 || config.PlacementProbability <= 0 || config.PlacementProbability > 1 { return UnderwaterMagmaFeatureConfig{}, fmt.Errorf("worldgen: invalid underwater magma config %s", name) } return config, nil } func (s *FeatureSet) Probability(name string) (ProbabilityFeatureConfig, error) { configured, ok := s.Configured[name] if !ok || configured.Type != "minecraft:seagrass" { return ProbabilityFeatureConfig{}, fmt.Errorf("worldgen: %s is not seagrass", name) } var config ProbabilityFeatureConfig if err := json.Unmarshal(configured.Config, &config); err != nil { return ProbabilityFeatureConfig{}, fmt.Errorf("worldgen: decode %s: %w", name, err) } if config.Probability < 0 || config.Probability > 1 { return ProbabilityFeatureConfig{}, fmt.Errorf("worldgen: invalid probability %s", name) } return config, nil } func (s *FeatureSet) Spring(name string) (SpringFeatureConfig, error) { configured, ok := s.Configured[name] if !ok || configured.Type != "minecraft:spring_feature" { return SpringFeatureConfig{}, fmt.Errorf("worldgen: %s is not a spring feature", name) } var config SpringFeatureConfig if err := json.Unmarshal(configured.Config, &config); err != nil { return SpringFeatureConfig{}, fmt.Errorf("worldgen: decode %s: %w", name, err) } if config.State.Name == "" || len(config.ValidBlocks) == 0 || config.HoleCount < 0 || config.RockCount < 0 { return SpringFeatureConfig{}, fmt.Errorf("worldgen: invalid spring config %s", name) } return config, nil } func (s *FeatureSet) Tree(name string) (TreeFeatureConfig, error) { configured, ok := s.Configured[name] if !ok || configured.Type != "minecraft:tree" { return TreeFeatureConfig{}, fmt.Errorf("worldgen: %s is not a tree feature", name) } var config TreeFeatureConfig if err := json.Unmarshal(configured.Config, &config); err != nil { return TreeFeatureConfig{}, fmt.Errorf("worldgen: decode %s: %w", name, err) } if config.TrunkPlacer.Type == "" || config.FoliagePlacer.Type == "" || config.TrunkProvider.State.Name == "" || config.FoliageProvider.State.Name == "" { return TreeFeatureConfig{}, fmt.Errorf("worldgen: invalid tree config %s", name) } return config, nil } func (s *FeatureSet) RandomSelector(name string) (RandomSelectorConfig, error) { configured, ok := s.Configured[name] if !ok || configured.Type != "minecraft:random_selector" { return RandomSelectorConfig{}, fmt.Errorf("worldgen: %s is not a random selector", name) } var raw struct { Default json.RawMessage `json:"default"` Features []struct { Chance float32 `json:"chance"` Feature json.RawMessage `json:"feature"` } `json:"features"` } if err := json.Unmarshal(configured.Config, &raw); err != nil { return RandomSelectorConfig{}, err } selector := RandomSelectorConfig{Features: make([]RandomSelectorEntry, len(raw.Features))} var err error selector.Default, err = parseFeatureRef(raw.Default) if err != nil { return RandomSelectorConfig{}, err } for i, entry := range raw.Features { selector.Features[i].Chance = entry.Chance selector.Features[i].Feature, err = parseFeatureRef(entry.Feature) if err != nil { return RandomSelectorConfig{}, err } } return selector, nil } func parseFeatureRef(raw json.RawMessage) (FeatureRef, error) { var name string if err := json.Unmarshal(raw, &name); err == nil { return FeatureRef{Name: name}, nil } var inline struct { Feature string `json:"feature"` Placement []json.RawMessage `json:"placement"` } if err := json.Unmarshal(raw, &inline); err != nil || inline.Feature == "" { return FeatureRef{}, fmt.Errorf("worldgen: invalid feature reference %s", raw) } ref := FeatureRef{Name: inline.Feature, Placement: make([]PlacementModifier, len(inline.Placement))} for i, modifier := range inline.Placement { if err := json.Unmarshal(modifier, &ref.Placement[i]); err != nil { return FeatureRef{}, err } ref.Placement[i].Raw = modifier } return ref, nil } func (s *FeatureSet) Placement(name string) (PlacementPlan, error) { placed, ok := s.Placed[name] if !ok { return PlacementPlan{}, fmt.Errorf("worldgen: placed feature %s missing", name) } plan := PlacementPlan{Count: CountProvider{Min: 1, Max: 1}, MinY: HeightProvider{AboveBottom: intPtr(0)}, MaxY: HeightProvider{Absolute: intPtr(320)}} for _, modifier := range placed.Placement { switch modifier.Type { case "minecraft:rarity_filter": var value struct { Chance int `json:"chance"` } if err := json.Unmarshal(modifier.Raw, &value); err != nil || value.Chance < 1 { return PlacementPlan{}, fmt.Errorf("worldgen: %s invalid rarity filter", name) } plan.RarityChance = value.Chance case "minecraft:count": var value struct { Count json.RawMessage `json:"count"` } if err := json.Unmarshal(modifier.Raw, &value); err != nil { return PlacementPlan{}, err } count, err := parseIntProvider(value.Count) if err != nil { return PlacementPlan{}, fmt.Errorf("worldgen: %s count: %w", name, err) } plan.Count = count case "minecraft:height_range": var value struct { Height struct { Type string `json:"type"` Plateau int `json:"plateau"` Min json.RawMessage `json:"min_inclusive"` Max json.RawMessage `json:"max_inclusive"` } `json:"height"` } if err := json.Unmarshal(modifier.Raw, &value); err != nil { return PlacementPlan{}, err } min, err := parseFeatureHeight(value.Height.Min) if err != nil { return PlacementPlan{}, err } max, err := parseFeatureHeight(value.Height.Max) if err != nil { return PlacementPlan{}, err } if value.Height.Type != "minecraft:uniform" && value.Height.Type != "minecraft:trapezoid" && value.Height.Type != "minecraft:very_biased_to_bottom" { return PlacementPlan{}, fmt.Errorf("worldgen: %s unsupported height distribution %q", name, value.Height.Type) } plan.HeightDistribution, plan.HeightPlateau, plan.MinY, plan.MaxY = value.Height.Type, value.Height.Plateau, min, max case "minecraft:in_square", "minecraft:biome", "minecraft:surface_water_depth_filter", "minecraft:heightmap", "minecraft:block_predicate_filter", "minecraft:noise_threshold_count", "minecraft:noise_based_count", "minecraft:random_offset", "minecraft:environment_scan", "minecraft:surface_relative_threshold_filter": // Coordinate spreading and biome validation are applied by the world // executor. Keeping them in the parsed plan preserves their order. default: return PlacementPlan{}, fmt.Errorf("worldgen: %s unsupported placement modifier %q", name, modifier.Type) } } return plan, nil } // PlacementPositions executes a placed feature's modifiers in declared order. // The recursive walk mirrors Stream.flatMap: every repeated position completes // the remaining chain before the next repeated position consumes random draws. func (s *FeatureSet) PlacementPositions(name string, r RandomSource, origin FeaturePosition, context PlacementContext) ([]FeaturePosition, error) { var result []FeaturePosition err := s.ForEachPlacementPosition(name, r, origin, context, func(position FeaturePosition) error { result = append(result, position) return nil }) return result, err } // ForEachPlacementPosition preserves vanilla's lazy placement stream: the // configured feature consumes random draws for one position before modifiers // produce the next repeated position. func (s *FeatureSet) ForEachPlacementPosition(name string, r RandomSource, origin FeaturePosition, context PlacementContext, visit func(FeaturePosition) error) error { placed, ok := s.Placed[name] if !ok { return fmt.Errorf("worldgen: placed feature %s missing", name) } var apply func(int, FeaturePosition) error apply = func(index int, position FeaturePosition) error { if index == len(placed.Placement) { return visit(position) } modifier := placed.Placement[index] next := func(value FeaturePosition) error { return apply(index+1, value) } switch modifier.Type { case "minecraft:noise_based_count": var value struct { Ratio int `json:"noise_to_count_ratio"` Factor float64 `json:"noise_factor"` Offset float64 `json:"noise_offset"` } if err := json.Unmarshal(modifier.Raw, &value); err != nil || value.Ratio < 0 || value.Factor == 0 { return fmt.Errorf("worldgen: %s invalid noise based count", name) } noise := BiomeInfoNoise(float64(position.X)/value.Factor, float64(position.Z)/value.Factor) for count := int(math.Ceil((noise + value.Offset) * float64(value.Ratio))); count > 0; count-- { if err := next(position); err != nil { return err } } return nil case "minecraft:count": var value struct { Count json.RawMessage `json:"count"` } if err := json.Unmarshal(modifier.Raw, &value); err != nil { return err } count, err := parseIntProvider(value.Count) if err != nil { return err } for n := count.Sample(r); n > 0; n-- { if err := next(position); err != nil { return err } } return nil case "minecraft:rarity_filter": var value struct { Chance int `json:"chance"` } if err := json.Unmarshal(modifier.Raw, &value); err != nil || value.Chance < 1 { return fmt.Errorf("worldgen: %s invalid rarity filter", name) } if r.NextFloat() < 1/float32(value.Chance) { return next(position) } return nil case "minecraft:in_square": position.X += int(r.NextIntN(16)) position.Z += int(r.NextIntN(16)) return next(position) case "minecraft:height_range": plan, err := placementHeightPlan(modifier.Raw) if err != nil { return err } position.Y = plan.SampleY(r, context.MinY, context.Height) return next(position) case "minecraft:heightmap": var value struct { Heightmap string `json:"heightmap"` } if err := json.Unmarshal(modifier.Raw, &value); err != nil || value.Heightmap == "" { return fmt.Errorf("worldgen: %s invalid heightmap placement", name) } if context.HeightAt == nil { return fmt.Errorf("worldgen: %s heightmap placement requires HeightAt", name) } position.Y = context.HeightAt(value.Heightmap, position.X, position.Z) if position.Y > context.MinY { return next(position) } return nil case "minecraft:surface_water_depth_filter": var value struct { MaxWaterDepth int `json:"max_water_depth"` } if err := json.Unmarshal(modifier.Raw, &value); err != nil || value.MaxWaterDepth < 0 { return fmt.Errorf("worldgen: %s invalid surface water depth filter", name) } if context.HeightAt == nil { return fmt.Errorf("worldgen: %s surface water depth filter requires HeightAt", name) } oceanFloor := context.HeightAt("OCEAN_FLOOR", position.X, position.Z) worldSurface := context.HeightAt("WORLD_SURFACE", position.X, position.Z) if worldSurface-oceanFloor <= value.MaxWaterDepth { return next(position) } return nil case "minecraft:surface_relative_threshold_filter": var value struct { Heightmap string `json:"heightmap"` Min *int `json:"min_inclusive"` Max *int `json:"max_inclusive"` } if err := json.Unmarshal(modifier.Raw, &value); err != nil || value.Heightmap == "" { return fmt.Errorf("worldgen: %s invalid surface relative threshold", name) } if context.HeightAt == nil { return fmt.Errorf("worldgen: %s surface relative threshold requires HeightAt", name) } minOffset, maxOffset := -int(^uint(0)>>1)-1, int(^uint(0)>>1) if value.Min != nil { minOffset = *value.Min } if value.Max != nil { maxOffset = *value.Max } surface := context.HeightAt(value.Heightmap, position.X, position.Z) positionY, surfaceY := int64(position.Y), int64(surface) if positionY >= surfaceY+int64(minOffset) && positionY <= surfaceY+int64(maxOffset) { return next(position) } return nil case "minecraft:random_offset": var value struct { XZSpread json.RawMessage `json:"xz_spread"` YSpread json.RawMessage `json:"y_spread"` } if err := json.Unmarshal(modifier.Raw, &value); err != nil { return err } xz, err := parsePlacementIntProvider(value.XZSpread) if err != nil { return fmt.Errorf("worldgen: %s xz spread: %w", name, err) } y, err := parsePlacementIntProvider(value.YSpread) if err != nil { return fmt.Errorf("worldgen: %s y spread: %w", name, err) } position.X += xz.Sample(r) position.Y += y.Sample(r) position.Z += xz.Sample(r) return next(position) case "minecraft:block_predicate_filter": var value struct { Predicate json.RawMessage `json:"predicate"` } if err := json.Unmarshal(modifier.Raw, &value); err != nil || len(value.Predicate) == 0 { return fmt.Errorf("worldgen: %s invalid block predicate filter", name) } if context.BlockPredicate == nil { return fmt.Errorf("worldgen: %s block predicate filter requires BlockPredicate", name) } ok, err := context.BlockPredicate(value.Predicate, position) if err != nil { return err } if ok { return next(position) } return nil case "minecraft:environment_scan": var value struct { Direction string `json:"direction_of_search"` Target json.RawMessage `json:"target_condition"` Allowed json.RawMessage `json:"allowed_search_condition"` MaxSteps int `json:"max_steps"` } if err := json.Unmarshal(modifier.Raw, &value); err != nil || value.MaxSteps < 1 || value.Direction != "up" && value.Direction != "down" { return fmt.Errorf("worldgen: %s invalid environment scan", name) } if context.BlockPredicate == nil { return fmt.Errorf("worldgen: %s environment scan requires BlockPredicate", name) } allowed, err := context.BlockPredicate(value.Allowed, position) if err != nil || !allowed { return err } dy := 1 if value.Direction == "down" { dy = -1 } for step := 0; step < value.MaxSteps; step++ { matched, err := context.BlockPredicate(value.Target, position) if err != nil { return err } if matched { return next(position) } position.Y += dy if position.Y < context.MinY || position.Y >= context.MinY+context.Height { return nil } allowed, err = context.BlockPredicate(value.Allowed, position) if err != nil { return err } if !allowed { break } } matched, err := context.BlockPredicate(value.Target, position) if err != nil { return err } if matched { return next(position) } return nil case "minecraft:biome": if context.BiomeAllows == nil || context.BiomeAllows(position) { return next(position) } return nil default: return fmt.Errorf("worldgen: %s unsupported executable placement modifier %q", name, modifier.Type) } } return apply(0, origin) } type placementIntProvider struct { typeName string min, max, plateau int mean, deviation float32 } func (p placementIntProvider) Sample(r RandomSource) int { switch p.typeName { case "minecraft:uniform": return p.min + int(r.NextIntN(int32(p.max-p.min+1))) case "minecraft:trapezoid": if p.plateau == 0 && p.max == -p.min { return int(r.NextIntN(int32(p.max+1))) - int(r.NextIntN(int32(p.max+1))) } rangeSize := p.max - p.min if p.plateau == rangeSize { return p.min + int(r.NextIntN(int32(rangeSize+1))) } left := (rangeSize - p.plateau) / 2 right := rangeSize - left return p.min + int(r.NextIntN(int32(right+1))) + int(r.NextIntN(int32(left+1))) case "minecraft:clamped_normal": value := float32(r.NextGaussian())*p.deviation + p.mean if value < float32(p.min) { value = float32(p.min) } else if value > float32(p.max) { value = float32(p.max) } return int(value) default: return p.min } } func parsePlacementIntProvider(raw json.RawMessage) (placementIntProvider, error) { var fixed int if err := json.Unmarshal(raw, &fixed); err == nil { return placementIntProvider{min: fixed, max: fixed}, nil } var value struct { Type string `json:"type"` Min int `json:"min"` Max int `json:"max"` MinInclusive int `json:"min_inclusive"` MaxInclusive int `json:"max_inclusive"` Plateau int `json:"plateau"` Mean, Deviation float32 } if err := json.Unmarshal(raw, &value); err != nil { return placementIntProvider{}, err } provider := placementIntProvider{typeName: value.Type, plateau: value.Plateau, mean: value.Mean, deviation: value.Deviation} switch value.Type { case "minecraft:trapezoid": provider.min, provider.max = value.Min, value.Max if provider.max < provider.min || provider.plateau < 0 || provider.plateau > provider.max-provider.min { return placementIntProvider{}, fmt.Errorf("invalid trapezoid provider %s", raw) } case "minecraft:uniform", "minecraft:clamped_normal": provider.min, provider.max = value.MinInclusive, value.MaxInclusive if provider.max < provider.min || value.Type == "minecraft:clamped_normal" && provider.deviation <= 0 { return placementIntProvider{}, fmt.Errorf("invalid %s provider %s", value.Type, raw) } default: return placementIntProvider{}, fmt.Errorf("unsupported int provider %s", raw) } return provider, nil } func placementHeightPlan(raw json.RawMessage) (PlacementPlan, error) { var value struct { Height struct { Type string `json:"type"` Plateau int `json:"plateau"` Min json.RawMessage `json:"min_inclusive"` Max json.RawMessage `json:"max_inclusive"` } `json:"height"` } if err := json.Unmarshal(raw, &value); err != nil { return PlacementPlan{}, err } min, err := parseFeatureHeight(value.Height.Min) if err != nil { return PlacementPlan{}, err } max, err := parseFeatureHeight(value.Height.Max) if err != nil { return PlacementPlan{}, err } if value.Height.Type != "minecraft:uniform" && value.Height.Type != "minecraft:trapezoid" && value.Height.Type != "minecraft:very_biased_to_bottom" { return PlacementPlan{}, fmt.Errorf("worldgen: unsupported height distribution %q", value.Height.Type) } return PlacementPlan{HeightDistribution: value.Height.Type, HeightPlateau: value.Height.Plateau, MinY: min, MaxY: max}, nil } func parseIntProvider(raw json.RawMessage) (CountProvider, error) { var fixed int if err := json.Unmarshal(raw, &fixed); err == nil { return CountProvider{Min: fixed, Max: fixed}, nil } var uniform struct { Type string `json:"type"` Min int `json:"min_inclusive"` Max int `json:"max_inclusive"` } if err := json.Unmarshal(raw, &uniform); err == nil && uniform.Type == "minecraft:uniform" { return CountProvider{Min: uniform.Min, Max: uniform.Max}, nil } var weighted struct { Type string `json:"type"` Distribution []struct { Data int `json:"data"` Weight int `json:"weight"` } `json:"distribution"` } if err := json.Unmarshal(raw, &weighted); err == nil && weighted.Type == "minecraft:weighted_list" { provider := CountProvider{Weighted: make([]WeightedInt, 0, len(weighted.Distribution))} for _, entry := range weighted.Distribution { if entry.Weight > 0 { provider.Weighted = append(provider.Weighted, WeightedInt{Value: entry.Data, Weight: entry.Weight}) } } return provider, nil } return CountProvider{}, fmt.Errorf("unsupported count provider %s", raw) } func parseFeatureHeight(raw json.RawMessage) (HeightProvider, error) { var value struct { Absolute *int `json:"absolute"` AboveBottom *int `json:"above_bottom"` BelowTop *int `json:"below_top"` } if err := json.Unmarshal(raw, &value); err != nil { return HeightProvider{}, err } if value.Absolute == nil && value.AboveBottom == nil && value.BelowTop == nil { return HeightProvider{}, fmt.Errorf("unsupported height provider %s", raw) } return HeightProvider{Absolute: value.Absolute, AboveBottom: value.AboveBottom, BelowTop: value.BelowTop}, nil } func intPtr(value int) *int { return &value } func loadFeatureSet(data []byte) (*FeatureSet, error) { zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) if err != nil { return nil, fmt.Errorf("worldgen: feature archive: %w", err) } set := &FeatureSet{ Configured: make(map[string]ConfiguredFeature), Placed: make(map[string]PlacedFeature), Biomes: make(map[string]BiomeGeneration), BlockTags: make(map[string][]string), } for _, file := range zr.File { r, err := file.Open() if err != nil { return nil, err } var raw json.RawMessage if err := json.NewDecoder(r).Decode(&raw); err != nil { r.Close() return nil, fmt.Errorf("worldgen: decode %s: %w", file.Name, err) } r.Close() name := resourceName(file.Name) switch { case strings.Contains(file.Name, "/configured_feature/"): var value ConfiguredFeature if err := json.Unmarshal(raw, &value); err != nil { return nil, fmt.Errorf("worldgen: configured feature %s: %w", name, err) } set.Configured[name] = value case strings.Contains(file.Name, "/placed_feature/"): var value struct { Feature string `json:"feature"` Placement []json.RawMessage `json:"placement"` } if err := json.Unmarshal(raw, &value); err != nil { return nil, fmt.Errorf("worldgen: placed feature %s: %w", name, err) } placed := PlacedFeature{Feature: value.Feature, Placement: make([]PlacementModifier, len(value.Placement))} for i, modifierRaw := range value.Placement { if err := json.Unmarshal(modifierRaw, &placed.Placement[i]); err != nil { return nil, fmt.Errorf("worldgen: placed feature %s modifier %d: %w", name, i, err) } placed.Placement[i].Raw = modifierRaw } set.Placed[name] = placed case strings.Contains(file.Name, "/biome/"): var biome BiomeGeneration if err := json.Unmarshal(raw, &biome); err != nil { return nil, fmt.Errorf("worldgen: biome %s: %w", name, err) } set.Biomes[name] = biome case strings.Contains(file.Name, "/tags/block/"): var tag struct { Values []string `json:"values"` } if err := json.Unmarshal(raw, &tag); err != nil { return nil, fmt.Errorf("worldgen: block tag %s: %w", name, err) } set.BlockTags[name] = tag.Values } } if len(set.Configured) < 200 || len(set.Placed) < 250 || len(set.Biomes) < 60 { return nil, fmt.Errorf("worldgen: incomplete feature archive: %d configured, %d placed, %d biomes", len(set.Configured), len(set.Placed), len(set.Biomes)) } for name, placed := range set.Placed { if strings.HasPrefix(placed.Feature, "minecraft:") { if _, ok := set.Configured[placed.Feature]; !ok { return nil, fmt.Errorf("worldgen: placed feature %s references missing %s", name, placed.Feature) } } } for biomeName, biome := range set.Biomes { for stage, names := range biome.Features { for _, name := range names { if _, ok := set.Placed[name]; !ok { return nil, fmt.Errorf("worldgen: biome %s stage %d references missing %s", biomeName, stage, name) } } } } return set, nil } func resourceName(file string) string { base := strings.TrimSuffix(path.Base(file), ".json") return "minecraft:" + base }