bd5b1d22c8
Previously, only nodes strictly within the map block were being carved out as open space. This meant that multi-node decorations (such as giant mushrooms) would collide with walls and ceilings that would be carved out by adjacent block generation later, leaving them sliced off. Now Subterrane carves out border regions too. Make note of the is_ground_content function, put all your decoration nodes into this to prevent them from being removed. Can't rely on just is_ground_function node properties for most situations, it's too simplistic.
519 lines
19 KiB
Lua
519 lines
19 KiB
Lua
--These nodes used to be defined by subterrane but were pulled due to not wanting to force all mods that use it to create these nodes.
|
|
--For backwards compatibility they can still be defined here, however.
|
|
|
|
local enable_legacy = minetest.setting_getbool("subterrane_enable_legacy_dripstone")
|
|
|
|
if enable_legacy then
|
|
|
|
subterrane.register_stalagmite_nodes("subterrane:dry_stal", {
|
|
description = "Dry Dripstone",
|
|
tiles = {
|
|
"default_stone.png^[brighten",
|
|
},
|
|
groups = {cracky = 3, stone = 2},
|
|
sounds = default.node_sound_stone_defaults(),
|
|
})
|
|
|
|
minetest.register_node("subterrane:dry_flowstone", {
|
|
description = "Dry Flowstone",
|
|
tiles = {"default_stone.png^[brighten"},
|
|
groups = {cracky = 3, stone = 1},
|
|
drop = 'default:cobble',
|
|
sounds = default.node_sound_stone_defaults(),
|
|
})
|
|
|
|
-----------------------------------------------
|
|
|
|
subterrane.register_stalagmite_nodes("subterrane:wet_stal", {
|
|
description = "Wet Dripstone",
|
|
tiles = {
|
|
"default_stone.png^[brighten^subterrane_dripstone_streaks.png",
|
|
},
|
|
groups = {cracky = 3, stone = 2, subterrane_wet_dripstone = 1},
|
|
sounds = default.node_sound_stone_defaults(),
|
|
}, "subterrane:dry_stal")
|
|
|
|
minetest.register_node("subterrane:wet_flowstone", {
|
|
description = "Wet Flowstone",
|
|
tiles = {"default_stone.png^[brighten^subterrane_dripstone_streaks.png"},
|
|
groups = {cracky = 3, stone = 1, subterrane_wet_dripstone = 1},
|
|
drop = 'default:cobble',
|
|
sounds = default.node_sound_stone_defaults(),
|
|
})
|
|
|
|
end
|
|
|
|
function subterrane:small_stalagmite(vi, area, data, param2_data, param2, height, stalagmite_id)
|
|
subterrane.stalagmite(vi, area, data, param2_data, param2, height, stalagmite_id)
|
|
end
|
|
|
|
function subterrane:giant_stalagmite(vi, area, data, min_height, max_height, base_material, root_material, shaft_material)
|
|
subterrane.big_stalagmite(vi, area, data, min_height, max_height, base_material, root_material, shaft_material)
|
|
end
|
|
|
|
function subterrane:giant_stalactite(vi, area, data, min_height, max_height, base_material, root_material, shaft_material)
|
|
subterrane.big_stalactite(vi, area, data, min_height, max_height, base_material, root_material, shaft_material)
|
|
end
|
|
|
|
function subterrane:giant_shroom(vi, area, data, stem_material, cap_material, gill_material, stem_height, cap_radius, ignore_bounds)
|
|
subterrane.giant_mushroom(vi, area, data, stem_material, cap_material, gill_material, stem_height, cap_radius, ignore_bounds)
|
|
end
|
|
-------------------------------------------------------------------------------------
|
|
-- Original cave registration code.
|
|
|
|
|
|
local c_lava = minetest.get_content_id("default:lava_source")
|
|
local c_obsidian = minetest.get_content_id("default:obsidian")
|
|
local c_stone = minetest.get_content_id("default:stone")
|
|
local c_air = minetest.get_content_id("air")
|
|
|
|
subterrane.default_perlin_cave = {
|
|
offset = 0,
|
|
scale = 1,
|
|
spread = {x=256, y=256, z=256},
|
|
seed = -400000000089,
|
|
octaves = 3,
|
|
persist = 0.67
|
|
}
|
|
|
|
subterrane.default_perlin_wave = {
|
|
offset = 0,
|
|
scale = 1,
|
|
spread = {x=512, y=256, z=512}, -- squashed 2:1
|
|
seed = 59033,
|
|
octaves = 6,
|
|
persist = 0.63
|
|
}
|
|
|
|
-- cave_layer_def
|
|
--{
|
|
-- minimum_depth = -- required, the highest elevation this cave layer will be generated in.
|
|
-- maximum_depth = -- required, the lowest elevation this cave layer will be generated in.
|
|
-- cave_threshold = -- optional, Cave threshold. Defaults to 0.5. 1 = small rare caves, 0.5 = 1/3rd ground volume, 0 = 1/2 ground volume
|
|
-- boundary_blend_range = -- optional, range near ymin and ymax over which caves diminish to nothing. Defaults to 128.
|
|
-- perlin_cave = -- optional, a 3D perlin noise definition table to define the shape of the caves
|
|
-- perlin_wave = -- optional, a 3D perlin noise definition table that's averaged with the cave noise to add more horizontal surfaces (squash its spread on the y axis relative to perlin_cave to accomplish this)
|
|
-- columns = -- optional, a column_def table for producing truly enormous dripstone formations
|
|
--}
|
|
|
|
-- column_def
|
|
--{
|
|
-- max_column_radius = -- Maximum radius for individual columns, defaults to 10
|
|
-- min_column_radius = -- Minimum radius for individual columns, defaults to 2 (going lower can increase the likelihood of "intermittent" columns with floating sections)
|
|
-- node = -- node name to build columns out of. Defaults to default:stone
|
|
-- weight = -- a floating point value (usually in the range of 0.5-1) to modify how strongly the column is affected by the surrounding cave. Lower values create a more variable, tapered stalactite/stalagmite combination whereas a value of 1 produces a roughly cylindrical column. Defaults to 0.5
|
|
-- maximum_count = -- The maximum number of columns placed in any given column region (each region being a square 4 times the length and width of a map chunk). Defaults to 100
|
|
-- minimum_count = -- The minimum number of columns placed in a column region. The actual number placed will be randomly selected between this range. Defaults to 25.
|
|
--}
|
|
|
|
--extra biome properties used by subterrane
|
|
--{
|
|
-- _subterrane_ceiling_decor = -- function for putting stuff on the ceiling of the big caverns
|
|
-- _subterrane_floor_decor = -- function for putting stuff on the floor of the big caverns
|
|
-- _subterrane_fill_node = -- node to fill the cavern with (defaults to air)
|
|
-- _subterrane_column_node = -- override the node the giant columns in this biome are made from
|
|
-- _subterrane_cave_floor_decor = -- function for putting stuff on the floors of other preexisting open space
|
|
-- _subterrane_cave_ceiling_decor = -- function for putting stuff on the ceiling of other preexisting open space
|
|
-- _subterrane_mitigate_lava = -- try to patch the walls of big caverns with obsidian plugs when lava intersects. Not perfect, but helpful.
|
|
-- _subterrane_override_sea_level = -- Y coordinate where an underground sea level begins. Biomes' y coordinate cutoffs are unreliable underground, this forces subterrane to take this sea level cutoff into account.
|
|
-- _subterrane_override_under_sea_biome = -- When below the override_sea_level, the biome with this name will be looked up and substituted.
|
|
-- _subterrane_column_node = -- overrides the node type of a cavern layer's column_def, if there are columns here.
|
|
--}
|
|
|
|
local default_column = {
|
|
max_column_radius = 10,
|
|
min_column_radius = 2,
|
|
node = c_stone,
|
|
weight = 0.25,
|
|
maximum_count = 100,
|
|
minimum_count = 25,
|
|
}
|
|
|
|
function subterrane:register_cave_layer(cave_layer_def)
|
|
|
|
table.insert(subterrane.registered_layers, cave_layer_def)
|
|
|
|
local YMIN = cave_layer_def.maximum_depth
|
|
local YMAX = cave_layer_def.minimum_depth
|
|
local BLEND = math.min(cave_layer_def.boundary_blend_range or 128, (YMAX-YMIN)/2)
|
|
local TCAVE = cave_layer_def.cave_threshold or 0.5
|
|
|
|
local np_cave = cave_layer_def.perlin_cave or subterrane.default_perlin_cave
|
|
local np_wave = cave_layer_def.perlin_wave or subterrane.default_perlin_wave
|
|
|
|
local yblmin = YMIN + BLEND * 1.5
|
|
local yblmax = YMAX - BLEND * 1.5
|
|
|
|
local column_def = cave_layer_def.columns
|
|
local c_column
|
|
|
|
if column_def then
|
|
column_def.max_column_radius = column_def.max_column_radius or default_column.max_column_radius
|
|
column_def.min_column_radius = column_def.min_column_radius or default_column.min_column_radius
|
|
c_column = column_def.node or default_column.node
|
|
column_def.weight = column_def.weight or default_column.weight
|
|
column_def.maximum_count = column_def.maximum_count or default_column.maximum_count
|
|
column_def.minimum_count = column_def.minimum_count or default_column.minimum_count
|
|
end
|
|
|
|
-- On generated function
|
|
minetest.register_on_generated(function(minp, maxp, seed)
|
|
--if out of range of cave definition limits, abort
|
|
if minp.y > YMAX or maxp.y < YMIN then
|
|
return
|
|
end
|
|
|
|
local t_start = os.clock()
|
|
local y_max = maxp.y
|
|
local y_min = minp.y
|
|
|
|
minetest.log("info", "[subterrane] chunk minp " .. minetest.pos_to_string(minp)) --tell people you are generating a chunk
|
|
|
|
local vm, data, data_param2, area = mapgen_helper.mapgen_vm_data_param2()
|
|
|
|
local layer_range_name = tostring(YMIN).." to "..tostring(YMAX)
|
|
local nvals_cave, cave_area = mapgen_helper.perlin3d("cave "..layer_range_name, minp, maxp, np_cave) --cave noise for structure
|
|
local nvals_wave = mapgen_helper.perlin3d("wave "..layer_range_name, minp, maxp, np_wave) --wavy structure of cavern ceilings and floors
|
|
local cave_iterator = cave_area:iterp(minp, maxp)
|
|
|
|
local biomemap = minetest.get_mapgen_object("biomemap")
|
|
|
|
local column_points = nil
|
|
local column_weight = nil
|
|
if column_def then
|
|
column_points = subterrane.get_column_points(minp, maxp, column_def)
|
|
column_weight = column_def.weight
|
|
end
|
|
|
|
for vi, x, y, z in area:iterp_xyz(minp, maxp) do
|
|
local index_3d = cave_iterator()
|
|
local index_2d = mapgen_helper.index2d(minp, maxp, x, z)
|
|
|
|
local tcave --declare variable
|
|
--determine the overall cave threshold
|
|
if y < yblmin then
|
|
tcave = TCAVE + ((yblmin - y) / BLEND) ^ 2
|
|
elseif y > yblmax then
|
|
tcave = TCAVE + ((y - yblmax) / BLEND) ^ 2
|
|
else
|
|
tcave = TCAVE
|
|
end
|
|
|
|
local biome
|
|
if biomemap then
|
|
biome = mapgen_helper.get_biome_def(biomemap[index_2d])
|
|
end
|
|
|
|
if biome and biome._subterrane_override_sea_level and y <= biome._subterrane_override_sea_level then
|
|
local override_name = biome._subterrane_override_under_sea_biome
|
|
if override_name then
|
|
biome = minetest.registered_biomes[override_name]
|
|
else
|
|
biome = nil
|
|
end
|
|
end
|
|
|
|
local fill_node = c_air
|
|
local column_node = c_column
|
|
if biome then
|
|
if biome._subterrane_fill_node then
|
|
fill_node = biome._subterrane_fill_node
|
|
end
|
|
if biome._subterrane_column_node then
|
|
column_node = biome._subterrane_column_node
|
|
end
|
|
end
|
|
|
|
local cave_value = (nvals_cave[index_3d] + nvals_wave[index_3d])/2
|
|
if cave_value > tcave then --if node falls within cave threshold
|
|
local column_value = 0
|
|
if column_def then
|
|
column_value = subterrane.get_point_heat({x=x, y=y, z=z}, column_points)
|
|
end
|
|
if column_value > 0 and cave_value - column_value * column_weight < tcave then
|
|
data[vi] = column_node -- add a column
|
|
else
|
|
data[vi] = fill_node --hollow it out to make the cave
|
|
end
|
|
elseif biome and biome._subterrane_cave_fill_node and data[vi] == c_air then
|
|
data[vi] = biome._subterrane_cave_fill_node
|
|
end
|
|
|
|
if biome and biome._subterrane_mitigate_lava and cave_value > tcave - 0.1 then -- Eliminate nearby lava to keep it from spilling in
|
|
if data[vi] == c_lava then
|
|
data[vi] = c_obsidian
|
|
end
|
|
end
|
|
end
|
|
|
|
cave_iterator = cave_area:iterp(minp, maxp) -- reset this iterator
|
|
for vi, x, y, z in area:iterp_xyz(minp, maxp) do
|
|
local index_3d = cave_iterator()
|
|
local index_2d = mapgen_helper.index2d(minp, maxp, x, z)
|
|
|
|
local ai = vi + area.ystride
|
|
local bi = vi - area.ystride
|
|
|
|
local tcave --same as above
|
|
if y < yblmin then
|
|
tcave = TCAVE + ((yblmin - y) / BLEND) ^ 2
|
|
elseif y > yblmax then
|
|
tcave = TCAVE + ((y - yblmax) / BLEND) ^ 2
|
|
else
|
|
tcave = TCAVE
|
|
end
|
|
|
|
local biome
|
|
if biomemap then
|
|
biome = mapgen_helper.get_biome_def(biomemap[index_2d])
|
|
end
|
|
local fill_node = c_air
|
|
local cave_fill_node = c_air
|
|
|
|
if biome and biome._subterrane_override_sea_level and y <= biome._subterrane_override_sea_level then
|
|
local override_name = biome._subterrane_override_under_sea_biome
|
|
if override_name then
|
|
biome = minetest.registered_biomes[override_name]
|
|
else
|
|
biome = nil
|
|
end
|
|
end
|
|
|
|
if biome then
|
|
local cave_value = (nvals_cave[index_3d] + nvals_wave[index_3d])/2
|
|
-- only check nodes near the edges of caverns
|
|
if cave_value > tcave - 0.05 and cave_value < tcave + 0.05 then
|
|
if biome._subterrane_fill_node then
|
|
fill_node = biome._subterrane_fill_node
|
|
end
|
|
--ceiling
|
|
if biome._subterrane_ceiling_decor
|
|
and data[ai] ~= fill_node
|
|
and data[vi] == fill_node
|
|
and y < y_max
|
|
then --ceiling
|
|
biome._subterrane_ceiling_decor(area, data, ai, vi, bi, data_param2)
|
|
end
|
|
--floor
|
|
if biome._subterrane_floor_decor
|
|
and data[bi] ~= fill_node
|
|
and data[vi] == fill_node
|
|
and y > y_min
|
|
then --floor
|
|
biome._subterrane_floor_decor(area, data, ai, vi, bi, data_param2)
|
|
end
|
|
|
|
elseif cave_value <= tcave then --if node falls outside cave threshold
|
|
-- decorate other "native" caves and tunnels
|
|
if biome._subterrane_cave_fill_node then
|
|
cave_fill_node = biome._subterrane_cave_fill_node
|
|
if data[vi] == c_air then
|
|
data[vi] = cave_fill_node
|
|
end
|
|
end
|
|
|
|
if biome._subterrane_cave_ceiling_decor
|
|
and data[ai] ~= cave_fill_node
|
|
and data[vi] == cave_fill_node
|
|
and y < y_max
|
|
then --ceiling
|
|
biome._subterrane_cave_ceiling_decor(area, data, ai, vi, bi, data_param2)
|
|
end
|
|
if biome._subterrane_cave_floor_decor
|
|
and data[bi] ~= cave_fill_node
|
|
and data[vi] == cave_fill_node
|
|
and y > y_min
|
|
then --ground
|
|
biome._subterrane_cave_floor_decor(area, data, ai, vi, bi, data_param2)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--send data back to voxelmanip
|
|
vm:set_data(data)
|
|
vm:set_param2_data(data_param2)
|
|
--calc lighting
|
|
vm:set_lighting({day = 0, night = 0})
|
|
vm:calc_lighting()
|
|
|
|
--write it to world
|
|
vm:write_to_map()
|
|
|
|
local chunk_generation_time = math.ceil((os.clock() - t_start) * 1000) --grab how long it took
|
|
if chunk_generation_time < 1000 then
|
|
minetest.log("info", "[subterrane] "..chunk_generation_time.." ms") --tell people how long
|
|
else
|
|
minetest.log("warning", "[subterrane] took "..chunk_generation_time.." ms to generate map block "
|
|
.. minetest.pos_to_string(minp) .. minetest.pos_to_string(maxp))
|
|
end
|
|
end)
|
|
end
|
|
|
|
|
|
function subterrane:register_cave_decor(minimum_depth, maximum_depth)
|
|
|
|
-- On generated function
|
|
minetest.register_on_generated(function(minp, maxp, seed)
|
|
--if out of range of cave definition limits, abort
|
|
if minp.y > minimum_depth or maxp.y < maximum_depth then
|
|
return
|
|
end
|
|
|
|
--easy reference to commonly used values
|
|
local t_start = os.clock()
|
|
local y_max = maxp.y
|
|
local y_min = minp.y
|
|
|
|
minetest.log("info", "[subterrane] chunk minp " .. minetest.pos_to_string(minp)) --tell people you are generating a chunk
|
|
|
|
local vm, data, data_param2, area = mapgen_helper.mapgen_vm_data_param2()
|
|
local biomemap = minetest.get_mapgen_object("biomemap")
|
|
|
|
for vi, x, y, z in area:iterp_xyz(minp, maxp) do
|
|
--decoration loop, places nodes on floor and ceiling
|
|
local index_2d = mapgen_helper.index2d(minp, maxp, x, z)
|
|
local ai = vi + area.ystride
|
|
local bi = vi - area.ystride
|
|
|
|
local biome
|
|
if biomemap then
|
|
biome = mapgen_helper.get_biome_def(biomemap[index_2d])
|
|
end
|
|
local cave_fill_node = c_air
|
|
|
|
if biome then
|
|
-- decorate "native" caves and tunnels
|
|
if biome._subterrane_cave_fill_node then
|
|
cave_fill_node = biome._subterrane_cave_fill_node
|
|
if data[vi] == c_air then
|
|
data[vi] = cave_fill_node
|
|
end
|
|
end
|
|
|
|
if biome._subterrane_cave_ceiling_decor
|
|
and data[ai] ~= cave_fill_node
|
|
and data[vi] == cave_fill_node
|
|
and y < y_max
|
|
then --ceiling
|
|
biome._subterrane_cave_ceiling_decor(area, data, ai, vi, bi, data_param2)
|
|
end
|
|
--ground
|
|
if biome._subterrane_cave_floor_decor
|
|
and data[bi] ~= cave_fill_node
|
|
and data[vi] == cave_fill_node
|
|
and y > y_min
|
|
then --ground
|
|
biome._subterrane_cave_floor_decor(area, data, ai, vi, bi, data_param2)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
--send data back to voxelmanip
|
|
vm:set_data(data)
|
|
vm:set_param2_data(data_param2)
|
|
--calc lighting
|
|
vm:set_lighting({day = 0, night = 0})
|
|
vm:calc_lighting()
|
|
--write it to world
|
|
vm:write_to_map()
|
|
|
|
local chunk_generation_time = math.ceil((os.clock() - t_start) * 1000) --grab how long it took
|
|
if chunk_generation_time < 1000 then
|
|
minetest.log("info", "[subterrane] "..chunk_generation_time.." ms") --tell people how long
|
|
else
|
|
minetest.log("warning", "[subterrane] took "..chunk_generation_time.." ms to generate a map block")
|
|
end
|
|
end)
|
|
end
|
|
|
|
--FUNCTIONS--
|
|
|
|
local grid_size = mapgen_helper.block_size * 4
|
|
|
|
function subterrane:vertically_consistent_randomp(pos)
|
|
local next_seed = math.floor(math.random() * 2^31)
|
|
math.randomseed(minetest.hash_node_position({x=pos.x, y=0, z=pos.z}))
|
|
local output = math.random()
|
|
math.randomseed(next_seed)
|
|
return output
|
|
end
|
|
|
|
function subterrane:vertically_consistent_random(vi, area)
|
|
local pos = area:position(vi)
|
|
return subterrane:vertically_consistent_randomp(pos)
|
|
end
|
|
|
|
subterrane.get_column_points = function(minp, maxp, column_def)
|
|
local grids = mapgen_helper.get_nearest_regions(minp, grid_size)
|
|
local points = {}
|
|
for _, grid in ipairs(grids) do
|
|
--The y value of the returned point will be the radius of the column
|
|
local minp = {x=grid.x, y = column_def.min_column_radius*100, z=grid.z}
|
|
local maxp = {x=grid.x+grid_size-1, y=column_def.max_column_radius*100, z=grid.z+grid_size-1}
|
|
for _, point in ipairs(mapgen_helper.get_random_points(minp, maxp, column_def.minimum_count, column_def.maximum_count)) do
|
|
point.y = point.y / 100
|
|
if point.x > minp.x - point.y
|
|
and point.x < maxp.x + point.y
|
|
and point.z > minp.z - point.y
|
|
and point.z < maxp.z + point.y then
|
|
table.insert(points, point)
|
|
end
|
|
end
|
|
end
|
|
return points
|
|
end
|
|
|
|
subterrane.get_point_heat = function(pos, points)
|
|
local heat = 0
|
|
for _, point in ipairs(points) do
|
|
local axis_point = {x=point.x, y=pos.y, z=point.z}
|
|
local radius = point.y
|
|
if (pos.x >= axis_point.x-radius and pos.x <= axis_point.x+radius
|
|
and pos.z >= axis_point.z-radius and pos.z <= axis_point.z+radius) then
|
|
|
|
local dist = vector.distance(pos, axis_point)
|
|
if dist < radius then
|
|
heat = math.max(heat, 1 - dist/radius)
|
|
end
|
|
|
|
end
|
|
end
|
|
return heat
|
|
end
|
|
|
|
-- Unfortunately there's no easy way to override a single biome, so do it by wiping everything and re-registering
|
|
-- Not only that, but the decorations also need to be wiped and re-registered - it appears they keep
|
|
-- track of the biome they belong to via an internal ID that gets changed when the biomes
|
|
-- are re-registered, resulting in them being left assigned to the wrong biomes.
|
|
function subterrane:override_biome(biome_def)
|
|
|
|
--Minetest 0.5 adds this "unregister biome" method
|
|
if minetest.unregister_biome and biome_def.name then
|
|
minetest.unregister_biome(biome_def.name)
|
|
minetest.register_biome(biome_def)
|
|
return
|
|
end
|
|
|
|
local registered_biomes_copy = {}
|
|
for old_biome_key, old_biome_def in pairs(minetest.registered_biomes) do
|
|
registered_biomes_copy[old_biome_key] = old_biome_def
|
|
end
|
|
local registered_decorations_copy = {}
|
|
for old_decoration_key, old_decoration_def in pairs(minetest.registered_decorations) do
|
|
registered_decorations_copy[old_decoration_key] = old_decoration_def
|
|
end
|
|
|
|
registered_biomes_copy[biome_def.name] = biome_def
|
|
|
|
minetest.clear_registered_decorations()
|
|
minetest.clear_registered_biomes()
|
|
for biome_key, new_biome_def in pairs(registered_biomes_copy) do
|
|
minetest.register_biome(new_biome_def)
|
|
end
|
|
for decoration_key, new_decoration_def in pairs(registered_decorations_copy) do
|
|
minetest.register_decoration(new_decoration_def)
|
|
end
|
|
end
|