Drive basic trees from biome feature stages
This commit is contained in:
parent
2bfa3155f3
commit
064f0f1cca
5 changed files with 339 additions and 41 deletions
|
|
@ -33,7 +33,7 @@ const dataVersion26 = 4790
|
||||||
// first time it ran: chunkAt prefers the store over the generator, so the
|
// first time it ran: chunkAt prefers the store over the generator, so the
|
||||||
// already-explored area around spawn keeps its old terrain and every later fix
|
// already-explored area around spawn keeps its old terrain and every later fix
|
||||||
// looks like it did nothing in exactly the place you are standing.
|
// looks like it did nothing in exactly the place you are standing.
|
||||||
const generatorVersion = 14
|
const generatorVersion = 15
|
||||||
|
|
||||||
// generatorVersionTag is the NBT key holding generatorVersion. It is namespaced
|
// generatorVersionTag is the NBT key holding generatorVersion. It is namespaced
|
||||||
// because it is ours, not part of the vanilla chunk format.
|
// because it is ours, not part of the vanilla chunk format.
|
||||||
|
|
|
||||||
127
internal/world/trees.go
Normal file
127
internal/world/trees.go
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
package world
|
||||||
|
|
||||||
|
import "regionio/internal/worldgen"
|
||||||
|
|
||||||
|
const vegetationStage = 9
|
||||||
|
|
||||||
|
func placeVanillaTrees(c *Chunk, seed int64, cx, cz int32, biomes *[16][16]string, surfTop *[16][16]int) {
|
||||||
|
set, err := worldgen.LoadFeatureSet()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
random, decorationSeed := worldgen.DecorationRandom(seed, int(cx), int(cz))
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for bx := 0; bx < 16; bx += 4 {
|
||||||
|
for bz := 0; bz < 16; bz += 4 {
|
||||||
|
stages := set.Biomes[biomes[bx][bz]].Features
|
||||||
|
if len(stages) <= vegetationStage {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for featureIndex, name := range stages[vegetationStage] {
|
||||||
|
if seen[name] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[name] = true
|
||||||
|
placed, ok := set.Placed[name]
|
||||||
|
if !ok || !treeFeatureType(set.Configured[placed.Feature].Type) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
plan, err := set.Placement(name)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
random.SetFeatureSeed(decorationSeed, featureIndex, vegetationStage)
|
||||||
|
count := plan.Count.Sample(random)
|
||||||
|
for attempt := 0; attempt < count; attempt++ {
|
||||||
|
x, z := int(random.NextIntN(16)), int(random.NextIntN(16))
|
||||||
|
y := MinY + surfTop[x][z] + 1
|
||||||
|
placeTreeReference(c, set, random, placed.Feature, x, y, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func treeFeatureType(kind string) bool {
|
||||||
|
return kind == "minecraft:tree" || kind == "minecraft:random_selector"
|
||||||
|
}
|
||||||
|
|
||||||
|
func placeTreeReference(c *Chunk, set *worldgen.FeatureSet, random worldgen.RandomSource, name string, x, y, z int) bool {
|
||||||
|
configured, ok := set.Configured[name]
|
||||||
|
if !ok {
|
||||||
|
if placed, exists := set.Placed[name]; exists {
|
||||||
|
return placeTreeReference(c, set, random, placed.Feature, x, y, z)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch configured.Type {
|
||||||
|
case "minecraft:random_selector":
|
||||||
|
selector, err := set.RandomSelector(name)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, entry := range selector.Features {
|
||||||
|
if random.NextFloat() < entry.Chance {
|
||||||
|
return placeTreeReference(c, set, random, entry.Feature.Name, x, y, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return placeTreeReference(c, set, random, selector.Default.Name, x, y, z)
|
||||||
|
case "minecraft:tree":
|
||||||
|
config, err := set.Tree(name)
|
||||||
|
if err != nil || config.TrunkPlacer.Type != "minecraft:straight_trunk_placer" || config.FoliagePlacer.Type != "minecraft:blob_foliage_placer" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return placeStraightBlobTree(c, random, x, y, z, config)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func placeStraightBlobTree(c *Chunk, random worldgen.RandomSource, x, y, z int, config worldgen.TreeFeatureConfig) bool {
|
||||||
|
if x < 2 || x >= 14 || z < 2 || z >= 14 || y <= MinY || y+12 >= MinY+WorldHeight {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
floor := c.GetBlock(x, y-1, z)
|
||||||
|
if floor != StateGrass && floor != StateDirt && floor != StateCoarseDirt && floor != StatePodzol {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
height := config.TrunkPlacer.BaseHeight
|
||||||
|
if config.TrunkPlacer.HeightRandA > 0 {
|
||||||
|
height += int(random.NextIntN(int32(config.TrunkPlacer.HeightRandA + 1)))
|
||||||
|
}
|
||||||
|
if config.TrunkPlacer.HeightRandB > 0 {
|
||||||
|
height += int(random.NextIntN(int32(config.TrunkPlacer.HeightRandB + 1)))
|
||||||
|
}
|
||||||
|
for dy := 0; dy <= height+config.FoliagePlacer.Height; dy++ {
|
||||||
|
if c.GetBlock(x, y+dy, z) != StateAir {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logState, okLog := nameToStateID(config.TrunkProvider.State.Name, config.TrunkProvider.State.Properties)
|
||||||
|
leafState, okLeaf := nameToStateID(config.FoliageProvider.State.Name, config.FoliageProvider.State.Properties)
|
||||||
|
if !okLog || !okLeaf {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c.SetBlock(x, y-1, z, StateDirt)
|
||||||
|
for dy := 0; dy < height; dy++ {
|
||||||
|
c.SetBlock(x, y+dy, z, logState)
|
||||||
|
}
|
||||||
|
centerY := y + height - 1 + config.FoliagePlacer.Offset
|
||||||
|
for layer := 0; layer < config.FoliagePlacer.Height; layer++ {
|
||||||
|
radius := config.FoliagePlacer.Radius
|
||||||
|
if layer == config.FoliagePlacer.Height-1 {
|
||||||
|
radius--
|
||||||
|
}
|
||||||
|
ly := centerY - layer
|
||||||
|
for dx := -radius; dx <= radius; dx++ {
|
||||||
|
for dz := -radius; dz <= radius; dz++ {
|
||||||
|
if abs(dx) == radius && abs(dz) == radius && random.NextIntN(2) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c.GetBlock(x+dx, ly, z+dz) == StateAir {
|
||||||
|
c.SetBlock(x+dx, ly, z+dz, leafState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
61
internal/world/trees_test.go
Normal file
61
internal/world/trees_test.go
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package world
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"regionio/internal/worldgen"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStraightBlobTreeFromDatapack(t *testing.T) {
|
||||||
|
set, err := worldgen.LoadFeatureSet()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
config, err := set.Tree("minecraft:oak")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
chunk := NewChunk(0, 0, BiomePlains)
|
||||||
|
chunk.setBlockRaw(8, 63, 8, StateGrass)
|
||||||
|
if !placeStraightBlobTree(chunk, worldgen.NewLegacy(42), 8, 64, 8, config) {
|
||||||
|
t.Fatal("datapack oak placement failed")
|
||||||
|
}
|
||||||
|
logs, leaves := 0, 0
|
||||||
|
for y := 64; y < 80; y++ {
|
||||||
|
for x := 0; x < 16; x++ {
|
||||||
|
for z := 0; z < 16; z++ {
|
||||||
|
switch chunk.GetBlock(x, y, z) {
|
||||||
|
case StateOakLog:
|
||||||
|
logs++
|
||||||
|
case StateOakLeaf:
|
||||||
|
leaves++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if logs < 4 || leaves < 10 {
|
||||||
|
t.Fatalf("tree has %d logs and %d leaves", logs, leaves)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBiomeTreeStageProducesTrees(t *testing.T) {
|
||||||
|
gen := NewVanillaGenerator(12345)
|
||||||
|
trees := 0
|
||||||
|
for cx := int32(-12); cx <= 12; cx += 3 {
|
||||||
|
for cz := int32(-12); cz <= 12; cz += 3 {
|
||||||
|
chunk := gen(cx, cz)
|
||||||
|
for y := SeaLevel; y < 160; y++ {
|
||||||
|
for x := 0; x < 16; x++ {
|
||||||
|
for z := 0; z < 16; z++ {
|
||||||
|
if chunk.GetBlock(x, y, z) == StateOakLog {
|
||||||
|
trees++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if trees == 0 {
|
||||||
|
t.Fatal("biome vegetation stages produced no oak logs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -480,50 +480,15 @@ func decorate(c *Chunk, od *worldgen.OverworldDensity, cx, cz int32, seed int64,
|
||||||
|
|
||||||
placeVanillaOres(c, seed, cx, cz, biomeName)
|
placeVanillaOres(c, seed, cx, cz, biomeName)
|
||||||
placeVanillaSprings(c, seed, cx, cz, biomeName)
|
placeVanillaSprings(c, seed, cx, cz, biomeName)
|
||||||
|
placeVanillaTrees(c, seed, cx, cz, biomeName, surfTop)
|
||||||
placeFlora(c, &r, surfTop, grass, biomeName)
|
placeFlora(c, &r, surfTop, grass, biomeName)
|
||||||
placeDesertFeatures(c, &r, surfTop, biomeName)
|
placeDesertFeatures(c, &r, surfTop, biomeName)
|
||||||
placeRocks(c, &r, surfTop, grass, biomeName)
|
placeRocks(c, &r, surfTop, grass, biomeName)
|
||||||
|
|
||||||
const attempts = 8
|
|
||||||
for a := 0; a < attempts; a++ {
|
|
||||||
lx := 2 + int(r.next()%12)
|
|
||||||
lz := 2 + int(r.next()%12)
|
|
||||||
if !grass[lx][lz] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
baseY := MinY + surfTop[lx][lz] + 1
|
|
||||||
placeOak(c, lx, baseY, lz, &r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place large structures like villages and strongholds
|
// Place large structures like villages and strongholds
|
||||||
worldgen.PlaceStructures(c, od, cx, cz, seed, surfTop, biomeName)
|
worldgen.PlaceStructures(c, od, cx, cz, seed, surfTop, biomeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func placeOak(c *Chunk, lx, baseY, lz int, r *chunkRand) {
|
|
||||||
h := 4 + int(r.next()%3) // trunk height 4..6
|
|
||||||
for i := 0; i < h; i++ {
|
|
||||||
c.SetBlock(lx, baseY+i, lz, StateOakLog)
|
|
||||||
}
|
|
||||||
topY := baseY + h - 1
|
|
||||||
// Canopy: two wide layers around the top, then two narrow layers above.
|
|
||||||
layers := []struct {
|
|
||||||
dy, radius int
|
|
||||||
}{{-1, 2}, {0, 2}, {1, 1}, {2, 1}}
|
|
||||||
for _, ly := range layers {
|
|
||||||
y := topY + ly.dy
|
|
||||||
for dx := -ly.radius; dx <= ly.radius; dx++ {
|
|
||||||
for dz := -ly.radius; dz <= ly.radius; dz++ {
|
|
||||||
if ly.radius == 2 && abs(dx) == 2 && abs(dz) == 2 {
|
|
||||||
continue // trim the far corners for a rounder shape
|
|
||||||
}
|
|
||||||
if c.GetBlock(lx+dx, y, lz+dz) == StateAir {
|
|
||||||
c.SetBlock(lx+dx, y, lz+dz, StateOakLeaf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func abs(v int) int {
|
func abs(v int) int {
|
||||||
if v < 0 {
|
if v < 0 {
|
||||||
return -v
|
return -v
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,44 @@ type SpringFeatureConfig struct {
|
||||||
ValidBlocks []string `json:"valid_blocks"`
|
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 {
|
type BlockState struct {
|
||||||
Name string `json:"Name"`
|
Name string `json:"Name"`
|
||||||
Properties map[string]string `json:"Properties"`
|
Properties map[string]string `json:"Properties"`
|
||||||
|
|
@ -76,6 +114,11 @@ type PlacementPlan struct {
|
||||||
|
|
||||||
type CountProvider struct {
|
type CountProvider struct {
|
||||||
Min, Max int
|
Min, Max int
|
||||||
|
Weighted []WeightedInt
|
||||||
|
}
|
||||||
|
|
||||||
|
type WeightedInt struct {
|
||||||
|
Value, Weight int
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeightProvider struct {
|
type HeightProvider struct {
|
||||||
|
|
@ -85,6 +128,22 @@ type HeightProvider struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p CountProvider) Sample(r RandomSource) 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 {
|
if p.Max <= p.Min {
|
||||||
return p.Min
|
return p.Min
|
||||||
}
|
}
|
||||||
|
|
@ -181,6 +240,74 @@ func (s *FeatureSet) Spring(name string) (SpringFeatureConfig, error) {
|
||||||
return config, nil
|
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) {
|
func (s *FeatureSet) Placement(name string) (PlacementPlan, error) {
|
||||||
placed, ok := s.Placed[name]
|
placed, ok := s.Placed[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -233,7 +360,9 @@ func (s *FeatureSet) Placement(name string) (PlacementPlan, error) {
|
||||||
return PlacementPlan{}, fmt.Errorf("worldgen: %s unsupported height distribution %q", name, value.Height.Type)
|
return PlacementPlan{}, fmt.Errorf("worldgen: %s unsupported height distribution %q", name, value.Height.Type)
|
||||||
}
|
}
|
||||||
plan.HeightDistribution, plan.MinY, plan.MaxY = value.Height.Type, min, max
|
plan.HeightDistribution, plan.MinY, plan.MaxY = value.Height.Type, min, max
|
||||||
case "minecraft:in_square", "minecraft:biome":
|
case "minecraft:in_square", "minecraft:biome", "minecraft:surface_water_depth_filter",
|
||||||
|
"minecraft:heightmap", "minecraft:block_predicate_filter", "minecraft:noise_threshold_count",
|
||||||
|
"minecraft:random_offset":
|
||||||
// Coordinate spreading and biome validation are applied by the world
|
// Coordinate spreading and biome validation are applied by the world
|
||||||
// executor. Keeping them in the parsed plan preserves their order.
|
// executor. Keeping them in the parsed plan preserves their order.
|
||||||
default:
|
default:
|
||||||
|
|
@ -253,10 +382,26 @@ func parseIntProvider(raw json.RawMessage) (CountProvider, error) {
|
||||||
Min int `json:"min_inclusive"`
|
Min int `json:"min_inclusive"`
|
||||||
Max int `json:"max_inclusive"`
|
Max int `json:"max_inclusive"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(raw, &uniform); err != nil || uniform.Type != "minecraft:uniform" {
|
if err := json.Unmarshal(raw, &uniform); err == nil && uniform.Type == "minecraft:uniform" {
|
||||||
return CountProvider{}, fmt.Errorf("unsupported count provider %s", raw)
|
return CountProvider{Min: uniform.Min, Max: uniform.Max}, nil
|
||||||
}
|
}
|
||||||
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) {
|
func parseFeatureHeight(raw json.RawMessage) (HeightProvider, error) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue