Files

428 lines
12 KiB
Go
Raw Permalink Normal View History

2026-07-11 00:33:35 +07:00
package generator
import (
"math"
"github.com/df-mc/dragonfly/server/block"
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/dragonfly/server/world/biome"
"github.com/df-mc/dragonfly/server/world/chunk"
)
2026-07-12 04:04:55 +07:00
type chunkRand struct {
state uint64
}
func newChunkRand(seed uint64) *chunkRand {
return &chunkRand{state: seed}
}
func (r *chunkRand) Uint32() uint32 {
r.state = r.state*6364136223846793005 + 1442695040888963407
return uint32(r.state >> 32)
}
func (r *chunkRand) Intn(n int) int {
if n <= 0 {
return 0
}
return int(r.Uint32() % uint32(n))
}
func chunkSeed(cx, cz int32) uint64 {
return uint64(cx)*341873128712 + uint64(cz)*132897987541
}
2026-07-11 00:33:35 +07:00
// VanillaLightweight is a lightweight vanilla-like terrain generator optimised
2026-07-12 04:04:55 +07:00
// for low-end ARM boards (e.g. Armbian H680P).
2026-07-11 00:33:35 +07:00
type VanillaLightweight struct {
2026-07-12 04:04:55 +07:00
biome uint32
grass uint32
dirt uint32
stone uint32
sand uint32
water uint32
bedrock uint32
deepslate uint32
// Ores (Stone variants)
coalOre uint32
ironOre uint32
goldOre uint32
diamondOre uint32
lapisOre uint32
redstoneOre uint32
copperOre uint32
emeraldOre uint32
// Ores (Deepslate variants)
coalOreDeepslate uint32
ironOreDeepslate uint32
goldOreDeepslate uint32
diamondOreDeepslate uint32
lapisOreDeepslate uint32
redstoneOreDeepslate uint32
copperOreDeepslate uint32
emeraldOreDeepslate uint32
// Vegetation & Trees
shortGrass uint32
fern uint32
dandelion uint32
log uint32
leaves uint32
// Village Houses
cobblestone uint32
planks uint32
glass uint32
2026-07-11 00:33:35 +07:00
}
// NewVanillaLightweight creates a new VanillaLightweight generator.
func NewVanillaLightweight(br world.BlockRegistry) VanillaLightweight {
return VanillaLightweight{
2026-07-12 04:04:55 +07:00
biome: uint32(biome.Plains{}.EncodeBiome()),
grass: br.BlockRuntimeID(block.Grass{}),
dirt: br.BlockRuntimeID(block.Dirt{}),
stone: br.BlockRuntimeID(block.Stone{}),
sand: br.BlockRuntimeID(block.Sand{}),
water: br.BlockRuntimeID(block.Water{Still: true, Depth: 8}),
bedrock: br.BlockRuntimeID(block.Bedrock{}),
deepslate: br.BlockRuntimeID(block.Deepslate{Type: block.NormalDeepslate()}),
coalOre: br.BlockRuntimeID(block.CoalOre{Type: block.StoneOre()}),
ironOre: br.BlockRuntimeID(block.IronOre{Type: block.StoneOre()}),
goldOre: br.BlockRuntimeID(block.GoldOre{Type: block.StoneOre()}),
diamondOre: br.BlockRuntimeID(block.DiamondOre{Type: block.StoneOre()}),
lapisOre: br.BlockRuntimeID(block.LapisOre{Type: block.StoneOre()}),
redstoneOre: br.BlockRuntimeID(block.RedstoneOre{Type: block.StoneOre()}),
copperOre: br.BlockRuntimeID(block.CopperOre{Type: block.StoneOre()}),
emeraldOre: br.BlockRuntimeID(block.EmeraldOre{Type: block.StoneOre()}),
coalOreDeepslate: br.BlockRuntimeID(block.CoalOre{Type: block.DeepslateOre()}),
ironOreDeepslate: br.BlockRuntimeID(block.IronOre{Type: block.DeepslateOre()}),
goldOreDeepslate: br.BlockRuntimeID(block.GoldOre{Type: block.DeepslateOre()}),
diamondOreDeepslate: br.BlockRuntimeID(block.DiamondOre{Type: block.DeepslateOre()}),
lapisOreDeepslate: br.BlockRuntimeID(block.LapisOre{Type: block.DeepslateOre()}),
redstoneOreDeepslate: br.BlockRuntimeID(block.RedstoneOre{Type: block.DeepslateOre()}),
copperOreDeepslate: br.BlockRuntimeID(block.CopperOre{Type: block.DeepslateOre()}),
emeraldOreDeepslate: br.BlockRuntimeID(block.EmeraldOre{Type: block.DeepslateOre()}),
shortGrass: br.BlockRuntimeID(block.ShortGrass{}),
fern: br.BlockRuntimeID(block.Fern{}),
dandelion: br.BlockRuntimeID(block.Flower{Type: block.Dandelion()}),
log: br.BlockRuntimeID(block.Log{Wood: block.OakWood()}),
leaves: br.BlockRuntimeID(block.Leaves{Type: block.OakLeaves()}),
cobblestone: br.BlockRuntimeID(block.Cobblestone{}),
planks: br.BlockRuntimeID(block.Planks{Wood: block.OakWood()}),
glass: br.BlockRuntimeID(block.Glass{}),
2026-07-11 00:33:35 +07:00
}
}
const seaLevel int16 = 62
// heightAt returns the terrain height at a given world coordinate using cheap
2026-07-12 04:04:55 +07:00
// trigonometric functions. Supports mountains.
2026-07-11 00:33:35 +07:00
func heightAt(wx, wz float64) int16 {
h := 64.0 +
8.0*math.Sin(wx*0.0157)*math.Cos(wz*0.0157) +
4.0*math.Sin(wx*0.0341+wz*0.0271) +
2.0*math.Cos(wx*0.083)*math.Sin(wz*0.083)
2026-07-12 04:04:55 +07:00
// Mountains (low frequency wave)
mountainNoise := math.Sin(wx*0.005) * math.Cos(wz*0.005)
if mountainNoise > 0.1 {
h += (mountainNoise - 0.1) * 60.0
}
2026-07-11 00:33:35 +07:00
return int16(h)
}
2026-07-12 04:04:55 +07:00
// isCave determines if there should be a cave at the given absolute coordinate.
func isCave(wx, wy, wz float64, height, min int16) bool {
if int16(wy) <= min+4 || int16(wy) >= height-4 {
return false
}
// Fast trigonometric cave formula
n := math.Sin(wx*0.09)*math.Cos(wz*0.09)*math.Sin(wy*0.15) +
math.Cos(wx*0.04)*math.Sin(wz*0.04)*math.Cos(wy*0.08)
return n > 0.72
}
// getOreRuntimeID determines if a block should be an ore.
func (v VanillaLightweight) getOreRuntimeID(wx float64, y int16, wz float64) (uint32, bool) {
h := uint32(wx)*374761393 + uint32(y)*668265263 + uint32(wz)*131071
h = (h ^ (h >> 13)) * 12741261
h = h & 0xffff
isDeep := y < 0
if y < 16 {
if h < 8 {
if isDeep {
return v.diamondOreDeepslate, true
}
return v.diamondOre, true
}
}
if y < 32 {
if h < 20 {
if isDeep {
return v.goldOreDeepslate, true
}
return v.goldOre, true
}
}
if y < 32 {
if h < 18 {
if isDeep {
return v.lapisOreDeepslate, true
}
return v.lapisOre, true
}
}
if y < 16 {
if h < 30 {
if isDeep {
return v.redstoneOreDeepslate, true
}
return v.redstoneOre, true
}
}
if y < 64 {
if h < 55 {
if isDeep {
return v.ironOreDeepslate, true
}
return v.ironOre, true
}
}
if y < 48 {
if h < 45 {
if isDeep {
return v.copperOreDeepslate, true
}
return v.copperOre, true
}
}
if y < 128 {
if h < 100 {
if isDeep {
return v.coalOreDeepslate, true
}
return v.coalOre, true
}
}
if y > 60 && y < 120 {
if h < 15 {
return v.emeraldOre, true
}
}
return 0, false
}
2026-07-11 00:33:35 +07:00
// GenerateChunk generates a single chunk.
func (v VanillaLightweight) GenerateChunk(pos world.ChunkPos, c *chunk.Chunk) {
min := int16(c.Range().Min())
cx, cz := int32(pos.X())<<4, int32(pos.Z())<<4
2026-07-12 04:04:55 +07:00
minX := int32(pos.X()) * 16
maxX := minX + 15
minZ := int32(pos.Z()) * 16
maxZ := minZ + 15
// 1. Generate base terrain and caves
2026-07-11 00:33:35 +07:00
for x := uint8(0); x < 16; x++ {
for z := uint8(0); z < 16; z++ {
wx := float64(cx + int32(x))
wz := float64(cz + int32(z))
height := heightAt(wx, wz)
top := height
if seaLevel > top {
top = seaLevel
}
for y := min; y <= top; y++ {
c.SetBiome(x, y, z, v.biome)
2026-07-12 04:04:55 +07:00
if y == min {
2026-07-11 00:33:35 +07:00
c.SetBlock(x, y, z, 0, v.bedrock)
2026-07-12 04:04:55 +07:00
continue
}
// Check for cave carving
if isCave(wx, float64(y), wz, height, min) {
c.SetBlock(x, y, z, 0, 0) // Air
continue
}
switch {
2026-07-11 00:33:35 +07:00
case y < height-3:
2026-07-12 04:04:55 +07:00
if oreID, ok := v.getOreRuntimeID(wx, y, wz); ok {
c.SetBlock(x, y, z, 0, oreID)
} else if y < 0 {
c.SetBlock(x, y, z, 0, v.deepslate)
} else {
c.SetBlock(x, y, z, 0, v.stone)
}
2026-07-11 00:33:35 +07:00
case y < height:
c.SetBlock(x, y, z, 0, v.dirt)
case y == height:
if height < seaLevel-1 {
// Underwater floor → sand.
c.SetBlock(x, y, z, 0, v.sand)
} else {
c.SetBlock(x, y, z, 0, v.grass)
2026-07-12 04:04:55 +07:00
// Spawn short grass / fern / dandelion with a coordinate-based hash
hGrass := uint32(wx)*131071 + uint32(wz)*374761393
hGrass = (hGrass ^ (hGrass >> 13)) * 12741261
hGrass = hGrass & 0xff
if hGrass < 40 && height+1 <= int16(c.Range().Max()) {
if hGrass < 30 {
c.SetBlock(x, height+1, z, 0, v.shortGrass)
} else if hGrass < 35 {
c.SetBlock(x, height+1, z, 0, v.fern)
} else {
c.SetBlock(x, height+1, z, 0, v.dandelion)
}
}
2026-07-11 00:33:35 +07:00
}
default:
// y > height && y <= seaLevel → water.
if y <= seaLevel {
c.SetBlock(x, y, z, 0, v.water)
}
}
}
2026-07-12 04:04:55 +07:00
// Set biome above terrain
2026-07-11 00:33:35 +07:00
for y := top + 1; y <= top+16 && y <= int16(c.Range().Max()); y++ {
c.SetBiome(x, y, z, v.biome)
}
}
}
2026-07-12 04:04:55 +07:00
// 2. Generate structures (trees, village houses) using neighbor lookup for seamless chunk boundaries
for ncx := pos.X() - 1; ncx <= pos.X() + 1; ncx++ {
for ncz := pos.Z() - 1; ncz <= pos.Z() + 1; ncz++ {
seed := chunkSeed(int32(ncx), int32(ncz))
rng := newChunkRand(seed)
// 2a. Village house (3% chance per chunk)
hasHouse := rng.Intn(100) < 3
if hasHouse {
hx := int32(ncx)*16 + int32(rng.Intn(10)) + 3
hz := int32(ncz)*16 + int32(rng.Intn(10)) + 3
hy := heightAt(float64(hx), float64(hz))
if hy >= seaLevel {
// Draw the house
for dx := int32(-2); dx <= 2; dx++ {
for dz := int32(-2); dz <= 2; dz++ {
ax := hx + dx
az := hz + dz
if ax >= minX && ax <= maxX && az >= minZ && az <= maxZ {
lx := uint8(ax - minX)
lz := uint8(az - minZ)
for dy := int16(0); dy <= 4; dy++ {
ay := hy + dy
var bid uint32
if dx == -2 || dx == 2 || dz == -2 || dz == 2 {
// Walls
if dy == 0 {
bid = v.cobblestone
} else if dy < 4 {
if (dx == -2 || dx == 2) && (dz == -2 || dz == 2) {
bid = v.cobblestone
} else if dy == 2 && (dx == 0 || dz == 0) {
bid = v.glass
} else if dy == 1 && dx == 0 && dz == -2 {
bid = 0 // Door bottom
} else if dy == 2 && dx == 0 && dz == -2 {
bid = 0 // Door top
} else {
bid = v.planks
}
} else {
bid = v.planks
}
} else {
bid = 0 // Inside air
}
if ay >= min && ay <= int16(c.Range().Max()) {
c.SetBlock(lx, ay, lz, 0, bid)
}
}
}
}
}
}
// Skip generating trees in chunks that have houses to avoid overlap
continue
}
// 2b. Trees (0-2 per chunk)
numTrees := rng.Intn(3)
for i := 0; i < numTrees; i++ {
tx := int32(ncx)*16 + int32(rng.Intn(16))
tz := int32(ncz)*16 + int32(rng.Intn(16))
ty := heightAt(float64(tx), float64(tz))
if ty >= seaLevel {
th := int16(4 + rng.Intn(3)) // height 4-6
// Draw Trunk
if tx >= minX && tx <= maxX && tz >= minZ && tz <= maxZ {
lx := uint8(tx - minX)
lz := uint8(tz - minZ)
for dy := int16(1); dy <= th; dy++ {
ay := ty + dy
if ay >= min && ay <= int16(c.Range().Max()) {
c.SetBlock(lx, ay, lz, 0, v.log)
}
}
}
// Draw Leaves
for ly := ty + th - 2; ly <= ty + th + 1; ly++ {
r := int32(2)
if ly > ty+th {
r = 1
}
for dx := -r; dx <= r; dx++ {
for dz := -r; dz <= r; dz++ {
if r == 2 && (dx == -2 || dx == 2) && (dz == -2 || dz == 2) {
continue // Round leaves corners
}
if dx == 0 && dz == 0 && ly <= ty+th {
continue // Trunk position
}
ax := tx + dx
az := tz + dz
if ax >= minX && ax <= maxX && az >= minZ && az <= maxZ {
lx := uint8(ax - minX)
lz := uint8(az - minZ)
if ly >= min && ly <= int16(c.Range().Max()) {
c.SetBlock(lx, ly, lz, 0, v.leaves)
}
}
}
}
}
}
}
}
}
2026-07-11 00:33:35 +07:00
}
// DefaultSpawn returns a fixed, safe spawn position on solid ground at 0, 0.
func (v VanillaLightweight) DefaultSpawn(dim world.Dimension) cube.Pos {
h := heightAt(0, 0)
return cube.Pos{0, int(h) + 1, 0}
}