Vanilla-faithful overworld generator (final_density + multi-noise biomes), full connection lifecycle (status/login/configuration/play), chunk streaming, creative block editing, and the protocol/nbt/registry infrastructure.
119 lines
4.1 KiB
Go
119 lines
4.1 KiB
Go
package world
|
|
|
|
import (
|
|
_ "embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"regionio/internal/registry"
|
|
"regionio/internal/worldgen"
|
|
)
|
|
|
|
//go:embed biome_parameters.json
|
|
var biomeParametersJSON []byte
|
|
|
|
// rawParameter mirrors one entry of biome_parameters.json: a biome name plus its
|
|
// climate ranges. Each axis value is a [min, max] array; depth is normally a
|
|
// scalar (0.0 surface / 1.0 underground) but a few cave entries carry a [min,
|
|
// max] array, so it is decoded loosely (see depthScalar).
|
|
type rawParameter struct {
|
|
Biome string `json:"biome"`
|
|
Param struct {
|
|
Temperature [2]float64 `json:"temperature"`
|
|
Humidity [2]float64 `json:"humidity"`
|
|
Continentalness [2]float64 `json:"continentalness"`
|
|
Erosion [2]float64 `json:"erosion"`
|
|
Weirdness [2]float64 `json:"weirdness"`
|
|
Depth any `json:"depth"`
|
|
Offset float64 `json:"offset"`
|
|
} `json:"parameters"`
|
|
}
|
|
|
|
// depthScalar extracts a scalar depth from a raw entry, accepting either a JSON
|
|
// number or a single-element [v] array. Arrays with a range are cave entries
|
|
// (non-surface) and return ok=false so the caller skips them.
|
|
func depthScalar(v any) (float64, bool) {
|
|
switch d := v.(type) {
|
|
case float64:
|
|
return d, true
|
|
case []any:
|
|
if len(d) == 1 {
|
|
if f, ok := d[0].(float64); ok {
|
|
return f, true
|
|
}
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
// surfaceTable is the biome parameter table filtered to depth=0 (surface layer),
|
|
// built once at init. Cave/underground entries (depth=1, or non-zero offset for
|
|
// lush/dripstone/deep_dark) are excluded until the per-cell milestone.
|
|
var (
|
|
surfaceTable *worldgen.ParameterTable
|
|
surfaceTableOnce sync.Once
|
|
)
|
|
|
|
// loadSurfaceTable parses the embedded biome parameters once and returns the
|
|
// surface-only ParameterTable. Panics on a parse error (a corrupt embedded
|
|
// table is a build-time bug, not a runtime condition).
|
|
func loadSurfaceTable() *worldgen.ParameterTable {
|
|
surfaceTableOnce.Do(func() {
|
|
var raw struct {
|
|
Biomes []rawParameter `json:"biomes"`
|
|
}
|
|
if err := json.Unmarshal(biomeParametersJSON, &raw); err != nil {
|
|
panic(fmt.Sprintf("world: parsing embedded biome_parameters.json: %v", err))
|
|
}
|
|
params := make([]worldgen.BiomeParameter, 0, len(raw.Biomes)/2)
|
|
for _, e := range raw.Biomes {
|
|
// Surface layer only: depth resolves to the scalar 0.0, and no cave
|
|
// offset. Range/array depths and non-zero offsets belong to cave
|
|
// biomes (lush/dripstone/deep_dark), deferred to the per-cell stage.
|
|
dp, ok := depthScalar(e.Param.Depth)
|
|
if !ok || dp != 0.0 || e.Param.Offset != 0.0 {
|
|
continue
|
|
}
|
|
params = append(params, makeBiomeParameter(e, dp))
|
|
}
|
|
surfaceTable = worldgen.NewParameterTable(params)
|
|
})
|
|
return surfaceTable
|
|
}
|
|
|
|
// makeBiomeParameter converts a raw JSON entry into a BiomeParameter, mapping
|
|
// the [min,max] ranges to quantized ClimateRanges. depth is a scalar in the
|
|
// source but a [depth, depth] band in the table (a single value).
|
|
func makeBiomeParameter(e rawParameter, depth float64) worldgen.BiomeParameter {
|
|
qr := func(a [2]float64) worldgen.ClimateRange {
|
|
return worldgen.ClimateRange{Min: worldgen.Quantize(a[0]), Max: worldgen.Quantize(a[1])}
|
|
}
|
|
dpQ := worldgen.Quantize(depth)
|
|
return worldgen.BiomeParameter{
|
|
Name: e.Biome,
|
|
Ranges: [worldgen.AxisCount]worldgen.ClimateRange{
|
|
qr(e.Param.Temperature),
|
|
qr(e.Param.Humidity),
|
|
qr(e.Param.Continentalness),
|
|
qr(e.Param.Erosion),
|
|
qr(e.Param.Weirdness),
|
|
{Min: dpQ, Max: dpQ + 1}, // half-open band covering exactly depth
|
|
},
|
|
Offset: worldgen.Quantize(e.Param.Offset),
|
|
}
|
|
}
|
|
|
|
// BiomeAt returns the network biome ID for the surface biome at block (wx, wz)
|
|
// given the loaded overworld density. It samples the climate axes at sea level,
|
|
// finds the matching biome in the parameter table, and resolves its name to a
|
|
// numeric ID via the synchronized biome registry. Unknown biomes fall back to
|
|
// plains so chunk encoding always gets a valid ID.
|
|
func BiomeAt(od *worldgen.OverworldDensity, wx, wz int) uint16 {
|
|
point := worldgen.SampleColumn(od, SeaLevel, wx, wz)
|
|
name := loadSurfaceTable().FindBiome(point)
|
|
if id := registry.Index("minecraft:worldgen/biome", name); id >= 0 {
|
|
return uint16(id)
|
|
}
|
|
return BiomePlains
|
|
}
|