worldgen: parse configured feature definitions
This commit is contained in:
parent
fd32c65da9
commit
f1d1d109f4
2 changed files with 498 additions and 3 deletions
|
|
@ -6,6 +6,7 @@ import (
|
|||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
|
|
@ -67,6 +68,77 @@ type OreTarget struct {
|
|||
} `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"`
|
||||
|
|
@ -219,8 +291,8 @@ func (p PlacementPlan) SampleY(r RandomSource, minY, height int) int {
|
|||
return lo + int(r.NextIntN(int32(span)))
|
||||
}
|
||||
|
||||
func DecorationRandom(seed int64, chunkX, chunkZ int) (*Legacy, int64) {
|
||||
random := NewLegacy(0)
|
||||
func DecorationRandom(seed int64, chunkX, chunkZ int) (*WorldgenRandom, int64) {
|
||||
random := NewWorldgenRandom(0)
|
||||
decorationSeed := random.SetDecorationSeed(seed, chunkX<<4, chunkZ<<4)
|
||||
return random, decorationSeed
|
||||
}
|
||||
|
|
@ -388,6 +460,290 @@ func (s *FeatureSet) Ore(name string) (OreFeatureConfig, error) {
|
|||
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" {
|
||||
|
|
@ -526,7 +882,7 @@ func (s *FeatureSet) Placement(name string) (PlacementPlan, error) {
|
|||
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:random_offset":
|
||||
"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:
|
||||
|
|
@ -564,6 +920,22 @@ func (s *FeatureSet) ForEachPlacementPosition(name string, r RandomSource, origi
|
|||
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"`
|
||||
|
|
@ -634,6 +1006,31 @@ func (s *FeatureSet) ForEachPlacementPosition(name string, r RandomSource, origi
|
|||
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"`
|
||||
|
|
@ -672,6 +1069,56 @@ func (s *FeatureSet) ForEachPlacementPosition(name string, r RandomSource, origi
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -70,6 +70,54 @@ func TestFeatureDatapackLoadsAndLinks(t *testing.T) {
|
|||
if err != nil || lavaPlan.HeightDistribution != "minecraft:very_biased_to_bottom" {
|
||||
t.Fatalf("spring lava placement = %+v, err=%v", lavaPlan, err)
|
||||
}
|
||||
disk, err := set.Disk("minecraft:ore_clay")
|
||||
if err == nil {
|
||||
t.Fatalf("ore feature parsed as disk: %+v", disk)
|
||||
}
|
||||
disk, err = set.Disk("minecraft:disk_clay")
|
||||
if err != nil || disk.RadiusMin != 2 || disk.RadiusMax != 3 || disk.HalfHeight != 1 || disk.State.Name != "minecraft:clay" {
|
||||
t.Fatalf("clay disk = %+v, err=%v", disk, err)
|
||||
}
|
||||
sand, err := set.Disk("minecraft:disk_sand")
|
||||
if err != nil || sand.State.Name != "minecraft:sand" || sand.Fallback.Name != "minecraft:sand" ||
|
||||
len(sand.Rules) != 1 || sand.Rules[0].Then.Name != "minecraft:sandstone" {
|
||||
t.Fatalf("sand disk = %+v, err=%v", sand, err)
|
||||
}
|
||||
grassDisk, err := set.Disk("minecraft:disk_grass")
|
||||
if err != nil || grassDisk.State.Name != "minecraft:dirt" || grassDisk.Fallback.Name != "minecraft:dirt" ||
|
||||
len(grassDisk.Rules) != 1 || grassDisk.Rules[0].Then.Name != "minecraft:grass_block" {
|
||||
t.Fatalf("grass disk = %+v, err=%v", grassDisk, err)
|
||||
}
|
||||
geode, err := set.Geode("minecraft:amethyst_geode")
|
||||
if err != nil || geode.Outer.Name != "minecraft:smooth_basalt" || geode.Middle.Name != "minecraft:calcite" ||
|
||||
geode.Inner.Name != "minecraft:amethyst_block" || geode.Filling.Name != "minecraft:air" ||
|
||||
geode.AlternateInner.Name != "minecraft:budding_amethyst" || len(geode.InnerPlacements) != 4 ||
|
||||
geode.DistributionMin != 3 || geode.DistributionMax != 4 || geode.OuterWallMin != 4 || geode.OuterWallMax != 6 ||
|
||||
geode.PointOffsetMin != 1 || geode.PointOffsetMax != 2 || geode.MinGenOffset != -16 || geode.MaxGenOffset != 16 ||
|
||||
geode.FillingLayer != 1.7 || geode.InnerLayer != 2.2 || geode.MiddleLayer != 3.2 || geode.OuterLayer != 4.2 ||
|
||||
geode.NoiseMultiplier != 0.05 || geode.InvalidBlocksThreshold != 1 ||
|
||||
geode.CannotReplaceTag != "#minecraft:features_cannot_replace" || geode.InvalidBlocksTag != "#minecraft:geode_invalid_blocks" ||
|
||||
geode.CrackChance != 0.95 || geode.BaseCrackSize != 2 || geode.CrackPointOffset != 2 {
|
||||
t.Fatalf("amethyst geode = %+v, err=%v", geode, err)
|
||||
}
|
||||
patch, err := set.VegetationPatch("minecraft:moss_patch")
|
||||
if err != nil || patch.Surface != "floor" || patch.DepthMin != 1 || patch.DepthMax != 1 ||
|
||||
patch.XZRadiusMin != 4 || patch.XZRadiusMax != 7 || patch.Ground.Name != "minecraft:moss_block" ||
|
||||
patch.ReplaceableTag != "#minecraft:moss_replaceable" || patch.Vegetation.Name != "minecraft:moss_vegetation" {
|
||||
t.Fatalf("moss patch = %+v, err=%v", patch, err)
|
||||
}
|
||||
ceiling, err := set.VegetationPatch("minecraft:moss_patch_ceiling")
|
||||
if err != nil || ceiling.Surface != "ceiling" || ceiling.VegetationChance != 0.08 {
|
||||
t.Fatalf("ceiling moss patch = %+v, err=%v", ceiling, err)
|
||||
}
|
||||
magma, err := set.UnderwaterMagma("minecraft:underwater_magma")
|
||||
if err != nil || magma.FloorSearchRange != 5 || magma.PlacementRadiusAroundFloor != 1 || magma.PlacementProbability != 0.5 {
|
||||
t.Fatalf("underwater magma = %+v, err=%v", magma, err)
|
||||
}
|
||||
probability, err := set.Probability("minecraft:seagrass_tall")
|
||||
if err != nil || probability.Probability != 0.8 {
|
||||
t.Fatalf("seagrass probability = %+v, err=%v", probability, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFeatureStepsAgainstVanillaRuntimeVectors(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue