riverdev/init.lua

698 lines
20 KiB
Lua

-- Parameters
local PSCA = 16 -- Player scatter. Maximum distance in chunks (80 nodes)
-- of player spawn from (0, 0, 0)
local YWATER = 1 -- Water surface y
local YSAND = 4 -- Top of beach y
local YTER = -64 -- Deepest seabed y
local YPINE = 47 -- Pines above this y
local TERSCA = 512 -- Terrain vertical scale in nodes
local BASAMP = 0.3 -- Base amplitude relative to 3D noise amplitude. Ridge network structure
local MIDAMP = 0.05 -- Mid amplitude relative to 3D noise amplitude. River valley structure
local TSTONE = 0.02 -- Maximum depth of stone under surface
local TRIVER = -0.018 -- River depth
local TRSAND = -0.022 -- Depth of river sand
local TPFLO = 0.02 -- Width of flora clearing around paths
local TTUN = 0.06 -- Tunnel width
local TFIS = 0.004 -- Fissure width
local TCAV = 1.1 -- Cavern threshold
local ORETHI = 0.003 -- Ore seam minimum thickness (diamond, mese, gold)
-- 1 / n ^ 3 where n = average distance between ores
local ORECHA = 1 / 5 ^ 3 -- Ore chance per stone node
-- 1 / n ^ 2 where n = average distance between features
local BOLCHA = 1 / 128 ^ 2 -- Boulder chance per surfacenode
local LOTET = -0.4 -- Low temperature threshold
local HITET = 0.4 -- High ^
local LOHUT = 0.2 -- Low humidity threshold (abs noise)
local HIHUT = 0.6 -- High ^
-- 2D noise for mid terrain / river
local np_mid = {
offset = 0,
scale = 1,
spread = {x = 1536, y = 1536, z = 1536},
seed = 85546,
octaves = 6,
persist = 0.4
}
-- 2D noise for base terrain / humidity
local np_base = {
offset = 0,
scale = 1,
spread = {x = 3072, y = 3072, z = 3072},
seed = -990054,
octaves = 3,
persist = 0.4
}
-- 2D noises for patha / top terrain
local np_patha = {
offset = 0,
scale = 1,
spread = {x = 768, y = 768, z = 768},
seed = 7000023,
octaves = 4,
persist = 0.4
}
-- 2D noises for pathb / top terrain
local np_pathb = {
offset = 0,
scale = 1,
spread = {x = 768, y = 768, z = 768},
seed = 23,
octaves = 4,
persist = 0.4
}
-- 2D noise for temperature
local np_temp = {
offset = 0,
scale = 1,
spread = {x = 3072, y = 3072, z = 3072},
seed = 18882,
octaves = 3,
persist = 0.4
}
-- 3D noise for terrain
local np_terrain = {
offset = 0,
scale = 1,
spread = {x = 384, y = 192, z = 384},
seed = 5900033,
octaves = 5,
persist = 0.67
}
-- 3D noise for alt terrain
local np_terrainalt = {
offset = 0,
scale = 1,
spread = {x = 311, y = 155, z = 311},
seed = -5933,
octaves = 5,
persist = 0.67
}
-- 3D noises for tunnels
local np_weba = {
offset = 0,
scale = 1,
spread = {x = 61, y = 61, z = 61},
seed = 5900033,
octaves = 3,
persist = 0.5
}
local np_webb = {
offset = 0,
scale = 1,
spread = {x = 67, y = 67, z = 67},
seed = 33,
octaves = 3,
persist = 0.5
}
-- 3D noise for strata layering
local np_strata = {
offset = 0,
scale = 1,
spread = {x = 3072, y = 48, z = 3072},
seed = 92219,
octaves = 4,
persist = 1
}
-- Do files
dofile(minetest.get_modpath("riverdev") .. "/functions.lua")
dofile(minetest.get_modpath("riverdev") .. "/nodes.lua")
-- Set mapgen parameters
minetest.set_mapgen_params({mgname = "singlenode", flags = "nolight"})
-- Mapgen functions
local function riverdev_pathbrush(x, y, z, area, data,
y0, wood, emerlen, stable, under, si)
local c_stone = minetest.get_content_id("riverdev:stone")
local c_path = minetest.get_content_id("riverdev:path")
local c_wood = minetest.get_content_id("default:junglewood")
local c_snow = minetest.get_content_id("default:snow")
if wood and math.random() < 0.2 then
local vi = area:index(x, y - 2, z)
for j = y - 2, y0 - 16, -1 do -- use mapblock shell
if data[vi] == c_stone then
break
else
data[vi] = c_wood
end
vi = vi - emerlen
end
end
for k = -1, 1 do
local vi = area:index(x - 1, y - 1, z + k)
local via = vi + emerlen
for i = -1, 1 do
if wood then
data[vi] = c_wood
else
data[vi] = c_path
end
if data[via] ~= c_path and data[via] ~= c_wood
and under[si] == 2 then
data[via] = c_snow
end
vi = vi + 1
via = via + 1
end
end
stable[si] = 0
under[si] = 0
end
local function riverdev_surface(x, y, z, area, data, y1, vi, viu,
n_abspatha, n_abspathb, n_temp, n_humid, under, si)
local c_grass = minetest.get_content_id("riverdev:grass")
local c_snowblock = minetest.get_content_id("default:snowblock")
local c_drygrass = minetest.get_content_id("riverdev:drygrass")
local c_icydirt = minetest.get_content_id("riverdev:icydirt")
if under[si] == 1 then -- tundra
data[viu] = c_icydirt
elseif under[si] == 2 then -- taiga
data[viu] = c_grass
data[vi] = c_snowblock
elseif under[si] == 3 then -- dry grassland
data[viu] = c_drygrass
elseif under[si] == 4 then -- forest/grassland
if math.random() < BOLCHA
and n_abspatha > TPFLO and n_abspathb > TPFLO then
riverdev_boulder(x, y, z, area, data)
else
data[viu] = c_grass
end
elseif under[si] == 6 then -- savanna
data[viu] = c_drygrass
elseif under[si] == 7 then -- rainforest
data[viu] = c_grass
elseif under[si] == 8 then -- sand
if math.random() < BOLCHA
and n_abspatha > TPFLO and n_abspathb > TPFLO then
riverdev_boulder(x, y, z, area, data)
end
elseif under[si] == 9 and n_temp < LOTET then -- stone
data[vi] = c_snowblock
end
end
-- initialize noise objects to nil
local nobj_terrain = nil
local nobj_terrainalt = nil
local nobj_weba = nil
local nobj_webb = nil
local nobj_strata = nil
local nobj_mid = nil
local nobj_base = nil
local nobj_patha = nil
local nobj_pathb = nil
local nobj_temp = nil
-- Localise noise buffers
local nbuf_terrain = {}
local nbuf_terrainalt = {}
local nbuf_weba = {}
local nbuf_webb = {}
local nbuf_strata = {}
local nbuf_mid = {}
local nbuf_base = {}
local nbuf_humid = {}
local nbuf_patha = {}
local nbuf_pathb = {}
local nbuf_temp = {}
-- Localise data buffer
local dbuf = {}
-- On generated function
minetest.register_on_generated(function(minp, maxp, seed)
local t0 = os.clock()
local x1 = maxp.x
local y1 = maxp.y
local z1 = maxp.z
local x0 = minp.x
local y0 = minp.y
local z0 = minp.z
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
local area = VoxelArea:new{MinEdge = emin, MaxEdge = emax}
local data = vm:get_data(dbuf)
local c_air = minetest.get_content_id("air")
local c_ignore = minetest.get_content_id("ignore")
local c_water = minetest.get_content_id("default:water_source")
local c_sand = minetest.get_content_id("default:sand")
local c_sandstone = minetest.get_content_id("default:sandstone")
local c_wood = minetest.get_content_id("default:junglewood")
local c_snowblock = minetest.get_content_id("default:snowblock")
local c_ice = minetest.get_content_id("default:ice")
local c_desand = minetest.get_content_id("default:desert_sand")
local c_stodiam = minetest.get_content_id("default:stone_with_diamond")
local c_stomese = minetest.get_content_id("default:stone_with_mese")
local c_stogold = minetest.get_content_id("default:stone_with_gold")
local c_stocopp = minetest.get_content_id("default:stone_with_copper")
local c_stoiron = minetest.get_content_id("default:stone_with_iron")
local c_stocoal = minetest.get_content_id("default:stone_with_coal")
local c_dirt = minetest.get_content_id("riverdev:dirt")
local c_grass = minetest.get_content_id("riverdev:grass")
local c_stone = minetest.get_content_id("riverdev:stone")
local c_redstone = minetest.get_content_id("riverdev:redstone")
local c_path = minetest.get_content_id("riverdev:path")
local c_freshwater = minetest.get_content_id("riverdev:freshwater")
local c_mixwater = minetest.get_content_id("riverdev:mixwater")
local c_freshwaterflow = minetest.get_content_id("riverdev:freshwaterflow")
local c_mixwaterflow = minetest.get_content_id("riverdev:mixwaterflow")
local c_permafrost = minetest.get_content_id("riverdev:permafrost")
local sidelen = x1 - x0 + 1 -- mapgen chunk side length
local overlen = sidelen + 1 -- perlinmap overgeneration horizontal side length
local emerlen = sidelen + 32 -- voxelmanip emerged volume edge length
--local emerarea = emerlen ^ 2 -- voxelmanip emerged volume face area
local chulensxyz = {x = overlen, y = sidelen + 2, z = overlen}
local minposxyz = {x = x0 - 1, y = y0 - 1, z = z0 - 1}
local chulensxz = {x = overlen, y = overlen, z = 1} -- different because here x=x, y=z
local minposxz = {x = x0 - 1, y = z0 - 1}
-- 3D and 2D noise objects created once on first mapchunk generation only
nobj_terrain = nobj_terrain or minetest.get_perlin_map(np_terrain, chulensxyz)
nobj_terrainalt = nobj_terrainalt or minetest.get_perlin_map(np_terrainalt, chulensxyz)
nobj_weba = nobj_weba or minetest.get_perlin_map(np_weba, chulensxyz)
nobj_webb = nobj_webb or minetest.get_perlin_map(np_webb, chulensxyz)
nobj_strata = nobj_strata or minetest.get_perlin_map(np_strata, chulensxyz)
nobj_mid = nobj_mid or minetest.get_perlin_map(np_mid, chulensxz)
nobj_base = nobj_base or minetest.get_perlin_map(np_base, chulensxz)
nobj_patha = nobj_patha or minetest.get_perlin_map(np_patha, chulensxz)
nobj_pathb = nobj_pathb or minetest.get_perlin_map(np_pathb, chulensxz)
nobj_temp = nobj_temp or minetest.get_perlin_map(np_temp, chulensxyz)
-- 3D and 2D perlinmaps created per mapchunk
local nvals_terrain = nobj_terrain:get3dMap_flat(minposxyz, nbuf_terrain)
local nvals_terrainalt = nobj_terrainalt:get3dMap_flat(minposxyz, nbuf_terrainalt)
local nvals_weba = nobj_weba:get3dMap_flat(minposxyz, nbuf_weba)
local nvals_webb = nobj_webb:get3dMap_flat(minposxyz, nbuf_webb)
local nvals_strata = nobj_strata:get3dMap_flat(minposxyz, nbuf_strata)
local nvals_mid = nobj_mid:get2dMap_flat(minposxz, nbuf_mid)
local nvals_base = nobj_base:get2dMap_flat(minposxz, nbuf_base)
local nvals_humid = nobj_base:get2dMap_flat({x = x0 - 1, y = z0 + 383}, nbuf_humid)
local nvals_patha = nobj_patha:get2dMap_flat(minposxz, nbuf_patha)
local nvals_pathb = nobj_pathb:get2dMap_flat(minposxz, nbuf_pathb)
local nvals_temp = nobj_temp:get2dMap_flat(minposxz, nbuf_temp)
-- ungenerated chunk below?
local viu = area:index(x0, y0 - 1, z0)
local ungen = data[viu] == c_ignore
local nixyz = 1
local nixz = 1
local stable = {}
local under = {}
for z = z0 - 1, z1 do
for y = y0 - 1, y1 + 1 do
local si = 1
local vi = area:index(x0 - 1, y, z)
local viu = vi - emerlen
local n_xprepatha = false
local n_xprepathb = false
for x = x0 - 1, x1 do
local nodid = data[vi]
local nodidu = data[viu]
local chunkxz = x >= x0 and z >= z0
local n_patha = nvals_patha[nixz]
local n_abspatha = math.abs(n_patha)
local n_zprepatha = nvals_patha[(nixz - overlen)]
local n_pathb = nvals_pathb[nixz]
local n_abspathb = math.abs(n_pathb)
local n_zprepathb = nvals_pathb[(nixz - overlen)]
local n_absweba = math.abs(nvals_weba[nixyz])
local n_abswebb = math.abs(nvals_webb[nixyz])
local novoid = not (n_absweba < TTUN and n_abswebb < TTUN)
local n_terrain = (nvals_terrain[nixyz]
+ nvals_terrainalt[nixyz] + 2) / 2
local n_absmid = (math.abs(nvals_mid[nixz])) ^ 0.8
local n_absbase = (math.abs(nvals_base[nixz])) ^ 0.8
local n_invbase = math.max(1 - n_absbase, 0)
local grad = (YTER - y) / TERSCA -- noise gradient
local densitybase = n_invbase * BASAMP + grad -- ridge surface
local densitymid = n_absmid * MIDAMP + densitybase -- river valley surface
local density = n_terrain * n_invbase * n_absmid -- actual surface
* n_abspatha ^ 1.5 * n_abspathb ^ 1.5 + densitymid
local n_strata = math.abs(nvals_strata[nixyz])
local n_temp = nvals_temp[nixz]
local n_humid = math.abs(nvals_humid[nixz]) - n_absmid * 0.5 + 0.5
local tstone = math.max(TSTONE * (1 + grad * 2), 0)
local triver = TRIVER * n_absbase
local trsand = TRSAND * n_absbase
local wood = densitybase > trsand * 2 and density < 0
if chunkxz and y == y0 - 1 then -- overgeneration, initialise tables
under[si] = 0
if ungen then -- guess by calculating density
if density >= 0 and novoid then
stable[si] = 2
else
stable[si] = 0
end
else -- scan top layer of chunk below
if nodid == c_air
or nodid == c_water
or nodid == c_freshwater
or nodid == c_freshwaterflow
or nodid == c_mixwater
or nodid == c_mixwaterflow then
stable[si] = 0
else
stable[si] = 2
end
end
elseif chunkxz and y >= y0 and y <= y1 then -- chunk generation
local biome = 0
if n_temp < LOTET then
if n_humid < LOHUT then -- tundra
biome = 1
else -- taiga
biome = 2
end
elseif n_temp > HITET then
if n_humid < LOHUT then -- desert
biome = 5
elseif n_humid > HIHUT then -- rainforest
biome = 7
else -- savanna
biome = 6
end
else
if n_humid < LOHUT then -- dry grassland
biome = 3
else -- temperate forest / grassland
biome = 4
end
end
if density >= tstone and (novoid
or (density < tstone * 1.5
and (y <= YWATER or densitybase >= triver))) then
if n_strata < 0.1 then -- sandstone
data[vi] = c_sandstone
elseif n_strata > 1.4
and n_strata < 1.4 + ORETHI then
data[vi] = c_stodiam
elseif n_strata > 1.2
and n_strata < 1.2 + ORETHI then
data[vi] = c_stomese
elseif n_strata > 1
and n_strata < 1 + ORETHI then
data[vi] = c_stogold
elseif n_strata > 0.8
and n_strata < 0.8 + ORETHI * 2 then
data[vi] = c_stocopp
elseif n_strata > 0.6
and n_strata < 0.6 + ORETHI * 3 then
data[vi] = c_stoiron
elseif n_strata > 0.4
and n_strata < 0.4 + ORETHI * 4 then
data[vi] = c_stocoal
elseif biome == 5 then
data[vi] = c_redstone -- redstone layer
else
data[vi] = c_stone -- stone
end
stable[si] = stable[si] + 1
under[si] = 9
elseif y > YSAND -- paths
and ((not wood and density < 0 and under[si] ~= 0)
or (wood and densitybase > trsand * 2
and densitybase < trsand * 2 + 0.002))
and (((n_patha >= 0 and n_xprepatha < 0)
or (n_patha < 0 and n_xprepatha >= 0))
or ((n_patha >= 0 and n_zprepatha < 0)
or (n_patha < 0 and n_zprepatha >= 0))
or ((n_pathb >= 0 and n_xprepathb < 0)
or (n_pathb < 0 and n_xprepathb >= 0))
or ((n_pathb >= 0 and n_zprepathb < 0)
or (n_pathb < 0 and n_zprepathb >= 0))) then
riverdev_pathbrush(x, y, z, area, data,
y0, wood, emerlen, stable, under, si)
elseif density >= 0 and density < tstone -- fine materials
and stable[si] >= 2 and nodid ~= c_stone then -- do not replace boulder
if y <= YSAND or densitybase >= trsand then
data[vi] = c_sand
under[si] = 8
elseif biome == 1 then -- tundra
data[vi] = c_permafrost
under[si] = 1
elseif biome == 2 then -- taiga
data[vi] = c_dirt
under[si] = 2
elseif biome == 3 then -- dry grassland
data[vi] = c_dirt
under[si] = 3
elseif biome == 4 then -- forest / grassland
data[vi] = c_dirt
under[si] = 4
elseif biome == 5 then -- desert
data[vi] = c_desand
under[si] = 5
elseif biome == 6 then -- savanna
data[vi] = c_dirt
under[si] = 6
elseif biome == 7 then -- rainforest
data[vi] = c_dirt
under[si] = 7
end
elseif y <= YWATER and density < tstone
and nodid ~= c_stone then -- sea water
data[vi] = c_water
stable[si] = 0
under[si] = 0
elseif densitybase >= triver and density < tstone
and nodid ~= c_stone then -- river water
if y == YWATER + 1 then
data[vi] = c_mixwater
else
data[vi] = c_freshwater
end
stable[si] = 0
under[si] = 0
elseif density < 0 and y > YWATER
and under[si] ~= 0 -- detect surface, place surface nodes
and nodid ~= c_stone
and nodid ~= c_snowblock and nodidu ~= c_snowblock
and nodid ~= c_path and nodidu ~= c_path
and nodid ~= c_wood and nodidu ~= c_wood then
riverdev_surface(x, y, z, area, data, y1, vi, viu,
n_abspatha, n_abspathb, n_temp, n_humid, under, si)
stable[si] = 0
under[si] = 0
else -- air or tunnel
stable[si] = 0
under[si] = 0
end
elseif chunkxz and y == y1 + 1 then -- overgeneration
if y > YSAND
and ((not wood and density < 0 and under[si] ~= 0)
or (wood and densitybase > trsand * 2
and densitybase < trsand * 2 + 0.002))
and (((n_patha >= 0 and n_xprepatha < 0)
or (n_patha < 0 and n_xprepatha >= 0)) -- patha
or ((n_patha >= 0 and n_zprepatha < 0)
or (n_patha < 0 and n_zprepatha >= 0))
or ((n_pathb >= 0 and n_xprepathb < 0)
or (n_pathb < 0 and n_xprepathb >= 0)) -- pathb
or ((n_pathb >= 0 and n_zprepathb < 0)
or (n_pathb < 0 and n_zprepathb >= 0))) then
riverdev_pathbrush(x, y, z, area, data,
y0, wood, emerlen, stable, under, si)
elseif density < 0 and y > YWATER
and under[si] ~= 0 -- detect surface, place surface nodes
and nodid ~= c_stone
and nodid ~= c_snowblock and nodidu ~= c_snowblock
and nodid ~= c_path and nodidu ~= c_path
and nodid ~= c_wood and nodidu ~= c_wood then
riverdev_surface(x, y, z, area, data, y1, vi, viu,
n_abspatha, n_abspathb, n_temp, n_humid, under, si)
end
end
n_xprepatha = n_patha
n_xprepathb = n_pathb
nixyz = nixyz + 1
nixz = nixz + 1
vi = vi + 1
viu = viu + 1
si = si + 1
end
nixz = nixz - overlen
end
nixz = nixz + overlen
end
vm:set_data(data)
vm:calc_lighting()
vm:write_to_map(data)
vm:update_liquids()
local chugent = math.ceil((os.clock() - t0) * 1000)
print ("[riverdev] " .. chugent .. " ms")
end)
-- Spawn player function
-- Only works with chunksize = 5 mapblocks
local function riverdev_spawnplayer(player)
local xsp
local ysp
local zsp
local nobj_terrain = nil
local nobj_mid = nil
local nobj_base = nil
local nobj_patha = nil
local nobj_pathb = nil
for chunk = 1, 128 do
print ("[riverdev] searching for spawn " .. chunk)
local x0 = 80 * math.random(-PSCA, PSCA) - 32
local z0 = 80 * math.random(-PSCA, PSCA) - 32
local y0 = 80 * math.floor((YWATER + 32) / 80) - 32
local x1 = x0 + 79
local z1 = z0 + 79
local y1 = y0 + 79
local sidelen = 80
local chulensxyz = {x = sidelen, y = sidelen, z = sidelen}
local minposxyz = {x = x0, y = y0, z = z0}
local chulensxz = {x = sidelen, y = sidelen, z = 1}
local minposxz = {x = x0, y = z0}
nobj_terrain = nobj_terrain or minetest.get_perlin_map(np_terrain, chulensxyz)
nobj_mid = nobj_mid or minetest.get_perlin_map(np_mid, chulensxz)
nobj_base = nobj_base or minetest.get_perlin_map(np_base, chulensxz)
nobj_patha = nobj_patha or minetest.get_perlin_map(np_patha, chulensxz)
nobj_pathb = nobj_pathb or minetest.get_perlin_map(np_pathb, chulensxz)
local nvals_terrain = nobj_terrain:get3dMap_flat(minposxyz)
local nvals_mid = nobj_mid:get2dMap_flat(minposxz)
local nvals_base = nobj_base:get2dMap_flat(minposxz)
local nvals_patha = nobj_patha:get2dMap_flat(minposxz)
local nvals_pathb = nobj_pathb:get2dMap_flat(minposxz)
local nixyz = 1
local nixz = 1
for z = z0, z1 do
for y = y0, y1 do
for x = x0, x1 do
local n_patha = nvals_patha[nixz]
local n_abspatha = math.abs(n_patha)
local n_pathb = nvals_pathb[nixz]
local n_abspathb = math.abs(n_pathb)
local n_terrain = (nvals_terrain[nixyz] + 2) / 2
local n_absmid = (math.abs(nvals_mid[nixz])) ^ 0.8
local n_absbase = (math.abs(nvals_base[nixz])) ^ 0.8
local n_invbase = math.max(1 - n_absbase, 0)
local grad = (YTER - y) / TERSCA
local densitybase = n_invbase * BASAMP + grad
local densitymid = n_absmid * MIDAMP + densitybase
local density = n_terrain * n_invbase * n_absmid *
n_abspatha ^ 1.5 * n_abspathb ^ 1.5 + densitymid
if y >= YWATER and density > -0.01 and density < 0 then
ysp = y + 1
xsp = x
zsp = z
break
end
nixz = nixz + 1
nixyz = nixyz + 1
end
if ysp then
break
end
nixz = nixz - 80
end
if ysp then
break
end
nixz = nixz + 80
end
if ysp then
break
end
end
if ysp then
print ("[riverdev] spawn player (" .. xsp .. " " .. ysp .. " " .. zsp .. ")")
player:setpos({x = xsp, y = ysp, z = zsp})
else
print ("[riverdev] no suitable spawn found")
player:setpos({x = 0, y = 2, z = 0})
end
end
minetest.register_on_newplayer(function(player)
riverdev_spawnplayer(player)
end)
minetest.register_on_respawnplayer(function(player)
riverdev_spawnplayer(player)
return true
end)