code:
-- original cave code modified from paramat's subterrain
-- Modified by HeroOfTheWinds for caverealms
-- Modified by FaceDeer for subterrane
-- Depends default
-- License: code MIT

subterrane = {} --create a container for functions and constants

local modpath = minetest.get_modpath(minetest.get_current_modname())

dofile(modpath.."/dependencies.lua")

local c_stone = minetest.get_content_id(subterrane.dependencies.stone)
local c_clay = minetest.get_content_id(subterrane.dependencies.clay)
local c_desert_stone = minetest.get_content_id(subterrane.dependencies.desert_stone)
local c_sandstone = minetest.get_content_id(subterrane.dependencies.sandstone)

local c_air = minetest.get_content_id("air")
local c_water = minetest.get_content_id(subterrane.dependencies.water)

local c_obsidian = minetest.get_content_id(subterrane.dependencies.obsidian)

local c_cavern_air = c_air
local c_warren_air = c_air

local subterrane_enable_singlenode_mapping_mode = minetest.settings:get_bool("subterrane_enable_singlenode_mapping_mode", false)
if subterrane_enable_singlenode_mapping_mode then
	c_cavern_air = c_stone
	c_warren_air = c_clay
end

local c_lava_set -- will be populated with a set of nodes that count as lava

-- Performance instrumentation
local t_start = os.clock()

subterrane.registered_layers = {}

dofile(modpath.."/defaults.lua")
dofile(modpath.."/features.lua") -- some generic cave features useful for a variety of mapgens
dofile(modpath.."/player_spawn.lua") -- Function for spawning a player in a giant cavern
dofile(modpath.."/test_pos.lua") -- Function other mapgens can use to test if a position is inside a cavern
dofile(modpath.."/legacy.lua") -- contains old node definitions and functions, will be removed at some point in the future.

local disable_mapgen_caverns = function()
	local mg_name = minetest.get_mapgen_setting("mg_name")
	local flags_name
	local default_flags

	if mg_name == "v7" then
		flags_name = "mgv7_spflags"
		default_flags = "mountains,ridges,nofloatlands"
	elseif mg_name == "v5" then
		flags_name = "mgv5_spflags"
		default_flags = ""
	else
		return
	end

	local function split(source, delimiters)
		local elements = {}
		local pattern = '([^'..delimiters..']+)'
		string.gsub(source, pattern, function(value) elements[#elements + 1] = value; end);
		return elements
	end

	local flags_setting = minetest.get_mapgen_setting(flags_name) or default_flags
	local new_flags = {}
	local flags = split(flags_setting, ", ")
	local nocaverns_present = false
	for _, flag in pairs(flags) do
		if flag ~= "caverns" then
			table.insert(new_flags, flag)
		end
		if flag == "nocaverns" then
			nocaverns_present = true
		end
	end
	if not nocaverns_present then
		table.insert(new_flags, "nocaverns")
	end
	minetest.set_mapgen_setting(flags_name, table.concat(new_flags, ","), true)
end
disable_mapgen_caverns()

-- Column stuff
----------------------------------------------------------------------------------

local grid_size = mapgen_helper.block_size * 4

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.minimum_radius*100, z=grid.z}
		local maxp = {x=grid.x+grid_size-1, y=column_def.maximum_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_column_value = 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

-- Decoration node lists
----------------------------------------------------------------------------------

-- States any given node can be in. Used to detect boundaries
local outside_region = 1
local inside_ground = 2
local inside_tunnel = 3
local inside_cavern = 4
local inside_warren = 5
local inside_column = 6

-- These arrays will contain the indices of various nodes relevant to decoration
-- Note that table.getn and # will not correctly report the number of items in these since they're reused
-- between calls and are not cleared for efficiency. You can iterate through them using ipairs,
-- and you can get their content count from the similarly-named variable associated with them.
local cavern_data
local cavern_floor_nodes
local cavern_ceiling_nodes
local warren_floor_nodes
local warren_ceiling_nodes
local tunnel_floor_nodes
local tunnel_ceiling_nodes
local column_nodes

local initialize_node_arrays = function()
	cavern_data = {}
	cavern_floor_nodes = {}
	cavern_data.cavern_floor_nodes = cavern_floor_nodes
	cavern_data.cavern_floor_count = 0
	cavern_ceiling_nodes = {}
	cavern_data.cavern_ceiling_nodes = cavern_ceiling_nodes
	cavern_data.cavern_ceiling_count = 0
	warren_floor_nodes = {}
	cavern_data.warren_floor_nodes = warren_floor_nodes
	cavern_data.warren_floor_count = 0
	warren_ceiling_nodes = {}
	cavern_data.warren_ceiling_nodes = warren_ceiling_nodes
	cavern_data.warren_ceiling_count = 0
	tunnel_floor_nodes = {}
	cavern_data.tunnel_floor_nodes = tunnel_floor_nodes
	cavern_data.tunnel_floor_count = 0
	tunnel_ceiling_nodes = {}
	cavern_data.tunnel_ceiling_nodes = tunnel_ceiling_nodes
	cavern_data.tunnel_ceiling_count = 0
	column_nodes = {}
	cavern_data.column_nodes = column_nodes
	cavern_data.column_count = 0
end
initialize_node_arrays()

-- inserts nil after the last node so that ipairs will function correctly
local close_node_arrays = function()
	cavern_ceiling_nodes[cavern_data.cavern_ceiling_count + 1] = nil
	cavern_floor_nodes[cavern_data.cavern_floor_count + 1] = nil
	warren_ceiling_nodes[cavern_data.warren_ceiling_count + 1] = nil
	warren_floor_nodes[cavern_data.warren_floor_count + 1] = nil
	tunnel_ceiling_nodes[cavern_data.tunnel_ceiling_count + 1] = nil
	tunnel_floor_nodes[cavern_data.tunnel_floor_count + 1] = nil
	column_nodes[cavern_data.column_count + 1] = nil
end

-- clear the tables without deleting them - easer on memory management this way
local clear_node_arrays = function()
	cavern_data.cavern_ceiling_count = 0
	cavern_data.cavern_floor_count = 0
	cavern_data.warren_ceiling_count = 0
	cavern_data.warren_floor_count = 0
	cavern_data.tunnel_ceiling_count = 0
	cavern_data.tunnel_floor_count = 0
	cavern_data.column_count = 0

--	for k,v in pairs(cavern_ceiling_nodes) do cavern_ceiling_nodes[k] = nil end
--	for k,v in pairs(cavern_floor_nodes) do cavern_floor_nodes[k] = nil end
--	for k,v in pairs(warren_ceiling_nodes) do warren_ceiling_nodes[k] = nil end
--	for k,v in pairs(warren_floor_nodes) do warren_floor_nodes[k] = nil end
--	for k,v in pairs(tunnel_ceiling_nodes) do tunnel_ceiling_nodes[k] = nil end
--	for k,v in pairs(tunnel_floor_nodes) do tunnel_floor_nodes[k] = nil end
--	for k,v in pairs(column_nodes) do column_nodes[k] = nil end

	close_node_arrays()
end

-- cave_layer_def
--{
--	name = -- optional, defaults to the string "y_min to y_max" (with actual values inserted in place of y_min and y_max). Used for logging.
--	y_max = -- required, the highest elevation this cave layer will be generated in.
--	y_min = -- 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 = 1/2 ground volume
--	warren_region_threshold = -- optional, defaults to 0.25. Used to determine how much volume warrens take up around caverns. Set it to be equal to or greater than the cave threshold to disable warrens entirely.
--	warren_region_variability_threshold = -- optional, defaults to 0.25. Used to determine how much of the region contained within the warren_region_threshold actually has warrens in it.
--	warren_threshold = -- Optional, defaults to 0.25. Determines how "spongey" warrens are, lower numbers make tighter, less-connected warren passages.
--	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)
--	perlin_warren_area = -- optional, a 3D perlin noise definition table for defining what places warrens form in
--	perlin_warrens = -- optional, a 3D perlin noise definition table for defining the warrens
--	solidify_lava = -- when set to true, lava near the edges of caverns is converted into obsidian to prevent it from spilling in.
--	columns = -- optional, a column_def table for producing truly enormous dripstone formations. See below for definition. Set to nil to disable columns.
--	double_frequency = -- when set to true, uses the absolute value of the cavern field to determine where to place caverns instead. This effectively doubles the number of large non-connected caverns.
--	decorate = -- optional, a function that is given a table of indices and a variety of other mapgen information so that it can place custom decorations on floors and ceilings. It is given the parameters (minp, maxp, seed, vm, cavern_data, area, data). See below for the cavern_data table's member definitions.
--	is_ground_content = -- optional, a function that takes a content_id and returns true if caverns should be carved through that node type. If not provided it defaults to a "is_ground_content" test.
--}

-- column_def
--{
--	maximum_radius = -- Maximum radius for individual columns, defaults to 10
--	minimum_radius = -- Minimum radius for individual columns, defaults to 4 (going lower that this can increase the likelihood of "intermittent" columns with floating sections)
--	node = -- node name to build columns out of. Defaults to default:stone
--	warren_node = -- node name to build columns out of in warren areas. If not set, the nodes that would be columns in warrens will be left as original ground contents
--	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.25
--	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 50
--	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 0.
--}

-- cavern_data -- This is passed into the decorate method.
--{
--	cavern_floor_nodes = {} -- List of data indexes for nodes that are part of cavern floors. Note: Use ipairs() when iterating this, not pairs()
--	cavern_floor_count = 0 -- the count of nodes in the preceeding list.
--	cavern_ceiling_nodes = {} -- List of data indexes for nodes that are part of cavern ceilings. Note: Use ipairs() when iterating this, not pairs()
--	cavern_ceiling_count = 0 -- the count of nodes in the preceeding list.
--	warren_floor_nodes = {} -- List of data indexes for nodes that are part of warren floors. Note: Use ipairs() when iterating this, not pairs()
--	warren_floor_count = 0 -- the count of nodes in the preceeding list.
--	warren_ceiling_nodes = {} -- List of data indexes for nodes that are part of warren floors. Note: Use ipairs() when iterating this, not pairs()
--	warren_ceiling_count = 0 -- the count of nodes in the preceeding list.
--	tunnel_floor_nodes = {} -- List of data indexes for nodes that are part of floors in pre-existing tunnels (anything generated before this mapgen runs). Note: Use ipairs() when iterating this, not pairs()
--	tunnel_floor_count = 0 -- the count of nodes in the preceeding list.
--	tunnel_ceiling_nodes = {}  -- List of data indexes for nodes that are part of ceiling in pre-existing tunnels (anything generated before this mapgen runs). Note: Use ipairs() when iterating this, not pairs()
--	tunnel_ceiling_count = 0 -- the count of nodes in the preceeding list.
--	column_nodes = {} -- Nodes that belong to columns. Note that if the warren_node was not set in the column definition these might not have been replaced by anything yet. This list contains *all* column nodes, not just ones on the surface.
--	column_count = 0 -- the count of nodes in the preceeding list.

--	contains_cavern = false -- Use this if you want a quick check if the generated map chunk has any cavern volume in it. Don't rely on the node counts above, if the map chunk doesn't intersect the floor or ceiling those could be 0 even if a cavern is present.
--	contains_warren = false -- Ditto for contains_cavern
--	nvals_cave = nvals_cave -- The noise array used to generate the cavern.
--	cave_area = cave_area -- a VoxelArea for indexing nvals_cave
--	cavern_def = cave_layer_def -- a reference to the cave layer def.
--}

subterrane.register_layer = function(cave_layer_def)
	local error_out = false
	if cave_layer_def.y_min == nil then
		minetest.log("error", "[subterrane] cave layer def " .. tostring(cave_layer_def.name) .. " did not have a y_min defined. Not registered.")
		error_out = true
	end
	if cave_layer_def.y_max == nil then
		minetest.log("error", "[subterrane] cave layer def " .. tostring(cave_layer_def.name) .. " did not have a y_max defined. Not registered.")
		error_out = true
	end
	if error_out then return end

	subterrane.set_defaults(cave_layer_def)

	local YMIN = cave_layer_def.y_min
	local YMAX = cave_layer_def.y_max

	if cave_layer_def.name == nil then
		cave_layer_def.name = tostring(YMIN) .. " to " .. tostring(YMAX)
	end

	local cave_name = cave_layer_def.name

	if subterrane.registered_layers[cave_name] ~= nil then
		minetest.log("warning", "[subterrane] cave layer def " .. tostring(cave_name) .. " has already been registered. Overriding with new definition.")
	end
	subterrane.registered_layers[cave_name] = cave_layer_def

	local block_size = mapgen_helper.block_size

	if (YMAX+32+1)%block_size ~= 0 then
		local boundary = YMAX -(YMAX+32+1)%block_size
		minetest.log("warning", "[subterrane] The y_max setting "..tostring(YMAX)..
			" for cavern layer " .. cave_name .. " is not aligned with map chunk boundaries. Consider "..
			tostring(boundary) .. " or " .. tostring(boundary+block_size) .. " for maximum mapgen efficiency.")
	end
	if (YMIN+32)%block_size ~= 0 then
		local boundary = YMIN - (YMIN+32)%block_size
		minetest.log("warning", "[subterrane] The y_min setting "..tostring(YMIN)..
			" for cavern layer " .. cave_name .. " is not aligned with map chunk boundaries. Consider "..
			tostring(boundary) .. " or " .. tostring(boundary+block_size) .. " for maximum mapgen efficiency.")
	end

	local BLEND = math.min(cave_layer_def.boundary_blend_range, (YMAX-YMIN)/2)

	local TCAVE = cave_layer_def.cave_threshold
	local warren_area_threshold = cave_layer_def.warren_region_threshold -- determines how much volume warrens are found in around caverns
	local warren_area_variability_threshold = cave_layer_def.warren_region_variability_threshold -- determines how much of the warren_area_threshold volume actually has warrens in it
	local warren_threshold = cave_layer_def.warren_threshold -- determines narrowness of warrens themselves

	local solidify_lava = cave_layer_def.solidify_lava

	local np_cave = cave_layer_def.perlin_cave
	local np_wave = cave_layer_def.perlin_wave
	local np_warren_area = cave_layer_def.perlin_warren_area
	local np_warrens = cave_layer_def.perlin_warrens

	local y_blend_min = YMIN + BLEND * 1.5
	local y_blend_max = YMAX - BLEND * 1.5

	local column_def = cave_layer_def.columns
	local c_column
	local c_warren_column

	if column_def then
		c_column = minetest.get_content_id(column_def.node)
		if column_def.warren_node then
			c_warren_column = minetest.get_content_id(column_def.warren_node)
		end
	end

	local double_frequency = cave_layer_def.double_frequency

	local decorate = cave_layer_def.decorate

	if subterrane_enable_singlenode_mapping_mode then
		decorate = nil
		c_column = c_air
		c_warren_column = nil
	end

	local is_ground_content = cave_layer_def.is_ground_content
	if is_ground_content == nil then
		is_ground_content = mapgen_helper.is_ground_content
	end

-- On generated
----------------------------------------------------------------------------------

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
	t_start = os.clock()

	if c_lava_set == nil then
		c_lava_set = {}
		for name, def in pairs(minetest.registered_nodes) do
			if def.groups ~= nil and def.groups.lava ~= nil then
				c_lava_set[minetest.get_content_id(name)] = true
			end
		end
	end

	local vm, data, data_param2, area = mapgen_helper.mapgen_vm_data_param2()
	local emin = area.MinEdge
	local emax = area.MaxEdge
	local nvals_cave, cave_area = mapgen_helper.perlin3d("subterrane:cave", emin, emax, np_cave) --cave noise for structure
	local nvals_wave = mapgen_helper.perlin3d("subterrane:wave", emin, emax, np_wave) --wavy structure of cavern ceilings and floors

	-- pre-average everything so that the final values can be passed
	-- along to the decorate function if it wants them
	for vi, value in ipairs(nvals_cave) do
		nvals_cave[vi] = (value + nvals_wave[vi])/2
	end

	local warren_area_uninitialized = true
	local nvals_warren_area
	local warrens_uninitialized = true
	local nvals_warrens

	local previous_y = emin.y
	local previous_node_state = outside_region
	local this_node_state = outside_region

	local column_points = nil
	local column_weight = nil

	-- This information might be of use to the decorate function.
	cavern_data.contains_cavern = false
	cavern_data.contains_warren = false
	cavern_data.nvals_cave = nvals_cave
	cavern_data.cave_area = cave_area
	cavern_data.cavern_def = cave_layer_def

	-- The interp_yxz iterator iterates upwards in columns along the y axis.
	-- starts at miny, goes to maxy, then switches to a new x,z and repeats.
	for vi, x, y, z in area:iterp_yxz(emin, emax) do
		-- We're "over-generating" when carving out the empty space of the cave volume so that decorations
		-- can slop over the boundaries of the mapblock without being cut off.
		-- We only want to add vi to the various decoration node lists if we're actually within the mapblock.
		local is_within_current_mapblock = mapgen_helper.is_pos_within_box({x=x, y=y, z=z}, minp, maxp)

		if y < previous_y then
			-- we've switched to a new column
			previous_node_state = outside_region
		else
			previous_node_state = this_node_state
		end
		previous_y = y
		this_node_state = inside_ground

		local cave_local_threshold
		if y < y_blend_min then
			cave_local_threshold = TCAVE + ((y_blend_min - y) / BLEND) ^ 2
		elseif y > y_blend_max then
			cave_local_threshold = TCAVE + ((y - y_blend_max) / BLEND) ^ 2
		else
			cave_local_threshold = TCAVE
		end

		local cave_value = nvals_cave[vi]
		if double_frequency then
			if cave_value < 0 then
				cave_value = -cave_value
				if subterrane_enable_singlenode_mapping_mode then
						c_cavern_air = c_desert_stone
						c_warren_air = c_sandstone
				end
			else
				if subterrane_enable_singlenode_mapping_mode then
						c_cavern_air = c_stone
						c_warren_air = c_clay
				end

			end
		end

		-- inside a giant cavern
		if cave_value > cave_local_threshold then
			local column_value = 0
			if column_def then
				if column_points == nil then
					column_points = subterrane.get_column_points(emin, maxp, column_def)
					column_weight = column_def.weight
				end
				column_value = subterrane.get_column_value({x=x, y=y, z=z}, column_points)
			end

			if column_value > 0 and cave_value - column_value * column_weight < cave_local_threshold then
				-- only add column nodes if we're within the current mapblock because
				-- otherwise we're adding column nodes that the decoration loop won't know about
				if is_ground_content(data[vi]) and is_within_current_mapblock then
					data[vi] = c_column -- add a column node
				end
				this_node_state = inside_column
			else
				if is_ground_content(data[vi]) then
					data[vi] = c_cavern_air --hollow it out to make the cave
				end
				this_node_state = inside_cavern
				if is_within_current_mapblock then
					cavern_data.contains_cavern = true
				end
			end
		end

		-- If there's lava near the edges of the cavern, solidify it.
		if solidify_lava and cave_value > cave_local_threshold - 0.05 and c_lava_set[data[vi]] then
			data[vi] = c_obsidian
		end

		--borderlands of a giant cavern, possible warren area
		if cave_value <= cave_local_threshold and cave_value > warren_area_threshold then

			if warren_area_uninitialized then
				nvals_warren_area = mapgen_helper.perlin3d("subterrane:warren_area", emin, emax, np_warren_area) -- determine which areas are spongey with warrens
				warren_area_uninitialized = false
			end

			local warren_area_value = nvals_warren_area[vi]
			if warren_area_value > warren_area_variability_threshold then
				-- we're in a warren-containing area
				if solidify_lava and c_lava_set[data[vi]] then
					data[vi] = c_obsidian
				end

				if warrens_uninitialized then
					nvals_warrens = mapgen_helper.perlin3d("subterrane:warrens", emin, emax, np_warrens) --spongey warrens
					warrens_uninitialized = false
				end

				-- we don't want warrens "cutting off" abruptly at the large-scale boundary noise thresholds, so turn these into gradients
				-- that can be applied to choke off the warren gradually.
				local cave_value_edge = math.min(1, (cave_value - warren_area_threshold) * 20) -- make 0.3 = 0 and 0.25 = 1 to produce a border gradient
				local warren_area_value_edge = math.min(1, warren_area_value * 50) -- make 0 = 0 and 0.02 = 1 to produce a border gradient

				local warren_value = nvals_warrens[vi]
				local warren_local_threshold = warren_threshold + (2 - warren_area_value_edge - cave_value_edge)
				if warren_value > warren_local_threshold then

					local column_value = 0
					if column_def then
						if column_points == nil then
							column_points = subterrane.get_column_points(emin, emax, column_def)
							column_weight = column_def.weight
						end
						column_value = subterrane.get_column_value({x=x, y=y, z=z}, column_points)
					end

					if column_value > 0 and column_value + (warren_local_threshold - warren_value) * column_weight > 0 then
						if c_warren_column then
							if is_ground_content(data[vi]) then
								data[vi] = c_warren_column -- add a column node
							end
							this_node_state = inside_column
						end
					else
						if is_ground_content(data[vi]) then
							data[vi] = c_warren_air --hollow it out to make the cave
						end
						if is_within_current_mapblock then
							cavern_data.contains_warren = true
						end
						this_node_state = inside_warren
					end
				end
			end
		end

		-- If decorate is defined, we want to track all this stuff
		if decorate ~= nil then
			local c_current_node = data[vi]
			local current_node_is_open = c_current_node == c_air -- mapgen_helper.buildable_to(c_current_node)
			if current_node_is_open and this_node_state == inside_ground then
				-- we're in a preexisting open space (tunnel).
				this_node_state = inside_tunnel
			end

			if is_within_current_mapblock then
				if this_node_state == inside_column then
					cavern_data.column_count = cavern_data.column_count + 1
					column_nodes[cavern_data.column_count] = vi
				elseif previous_node_state ~= this_node_state and previous_node_state ~= inside_column then
					if previous_node_state == inside_ground then
						if this_node_state == inside_tunnel then
							-- we just entered a tunnel from below.
							cavern_data.tunnel_floor_count = cavern_data.tunnel_floor_count + 1
							tunnel_floor_nodes[cavern_data.tunnel_floor_count] = vi-area.ystride
						elseif this_node_state == inside_cavern then
							-- we just entered the cavern from below
							cavern_data.cavern_floor_count = cavern_data.cavern_floor_count + 1
							cavern_floor_nodes[cavern_data.cavern_floor_count] = vi - area.ystride
						elseif this_node_state == inside_warren then
							-- we just entered the warren from below
							cavern_data.warren_floor_count = cavern_data.warren_floor_count + 1
							warren_floor_nodes[cavern_data.warren_floor_count] = vi - area.ystride
						end
					elseif this_node_state == inside_ground then
						if previous_node_state == inside_tunnel then
							-- we just left a tunnel from below
							cavern_data.tunnel_ceiling_count = cavern_data.tunnel_ceiling_count + 1
							tunnel_ceiling_nodes[cavern_data.tunnel_ceiling_count] = vi
						elseif previous_node_state == inside_cavern then
							--we just left the cavern from below
							cavern_data.cavern_ceiling_count = cavern_data.cavern_ceiling_count + 1
							cavern_ceiling_nodes[cavern_data.cavern_ceiling_count] = vi
						elseif previous_node_state == inside_warren then
							--we just left the cavern from below
							cavern_data.warren_ceiling_count = cavern_data.warren_ceiling_count + 1
							warren_ceiling_nodes[cavern_data.warren_ceiling_count] = vi
						end
					end
				end
			end
		end
	end

	if decorate then
		close_node_arrays() -- inserts nil after the last node so that ipairs will function correctly
		decorate(minp, maxp, seed, vm, cavern_data, area, data)
		clear_node_arrays() -- if decorate is not defined these arrays will never have anything added to them, so it's safe to not call this in that case
	end

	--send data back to voxelmanip
	vm:set_data(data)
	--calc lighting
	vm:set_lighting({day = 0, night = 0})
	vm:calc_lighting()
	vm:update_liquids()
	--write it to world
	vm:write_to_map()

	local time_taken = os.clock() - t_start -- how long this chunk took, in seconds
	mapgen_helper.record_time(cave_name, time_taken)
end)

end

local last_check = os.clock()
minetest.register_globalstep(function(dtime)
	local current_time = os.clock()
	local threshold = t_start + 60
	if last_check < threshold and current_time > threshold then
		-- It's been 60 seconds since we last generated a cavern, release the memory the cavern arrays are being stored in.
		initialize_node_arrays()
	end
	last_check = current_time
end)

minetest.log("info", "[Subterrane] loaded!")


code:
--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.settings:get_bool("subterrane_enable_legacy_dripstone", false)

if enable_legacy then

local modname = minetest.get_current_modname()
local S = minetest.get_translator(modname)

subterrane.register_stalagmite_nodes("subterrane:dry_stal", {
	description = S("Dry Dripstone"),
	tiles = {
		"default_stone.png^[brighten",
	},
	groups = {cracky = 3, stone = 2},
	sounds = default.node_sound_stone_defaults(),
})

local flowstone_drops
if minetest.get_modpath("default") then
	flowstone_drops = 'default:cobble'
end

minetest.register_node("subterrane:dry_flowstone", {
	description = S("Dry Flowstone"),
	tiles = {"default_stone.png^[brighten"},
	groups = {cracky = 3, stone = 1},
	drop = flowstone_drops,
	sounds = default.node_sound_stone_defaults(),
})

-----------------------------------------------

subterrane.register_stalagmite_nodes("subterrane:wet_stal", {
	description = S("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 = S("Wet Flowstone"),
	tiles = {"default_stone.png^[brighten^subterrane_dripstone_streaks.png"},
	groups = {cracky = 3, stone = 1, subterrane_wet_dripstone = 1},
	drop = flowstone_drops,
	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

--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_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

repo_name: korkut/MeseCraft

license: mit


code:
# textdomain: subterrane

### legacy.lua ###

Dry Dripstone=Trockener Tropfstein
Dry Flowstone=Trockener Fließstein
Wet Dripstone=Nasser Tropfstein
Wet Flowstone=Nasser Fließstein


code:
# textdomain: subterrane

### legacy.lua ###

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Dry Dripstone=Pietra gocciolante secca
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Dry Flowstone=Flowstone secco
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Wet Dripstone=Dripstone bagnato
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Wet Flowstone=Flowstone bagnato


code:
# textdomain: subterrane

### legacy.lua ###

Dry Dripstone=
Dry Flowstone=
Wet Dripstone=
Wet Flowstone=


code:
name = subterrane
description = A mapgen helper mod that facilitates the creation of vast underground caverns
optional_depends = default, mcl_core
depends = mapgen_helper


code:
local sidelen = mapgen_helper.block_size

local snap_to_minp = function(ydepth)
	return ydepth - (ydepth+32) % sidelen -- put ydepth at the minp.y of mapblocks
end

function subterrane:register_cave_spawn(cave_layer_def, start_depth)
	minetest.register_on_newplayer(function(player)
		local ydepth = snap_to_minp(start_depth or cave_layer_def.y_max)
		local spawned = false
		while spawned ~= true do
			spawned = spawnplayer(cave_layer_def, player, ydepth)
			ydepth = ydepth - sidelen
			if ydepth < cave_layer_def.y_min then
				ydepth = snap_to_minp(cave_layer_def.y_max)
			end
		end
	end)

	minetest.register_on_respawnplayer(function(player)
		local ydepth = snap_to_minp(start_depth or cave_layer_def.y_max)
		local spawned = false
		while spawned ~= true do
			spawned = spawnplayer(cave_layer_def, player, ydepth)
			ydepth = ydepth - sidelen
			if ydepth < cave_layer_def.y_min then
				ydepth = snap_to_minp(cave_layer_def.y_max)
			end
		end
		return true
	end)
end

local outside_region = 0
local inside_ground = 1
local inside_cavern = 2

-- Spawn player underground in a giant cavern
function spawnplayer(cave_layer_def, player, ydepth)
	subterrane.set_defaults(cave_layer_def)

	local YMIN = cave_layer_def.y_min
	local YMAX = cave_layer_def.y_max
	local BLEND = math.min(cave_layer_def.boundary_blend_range, (YMAX-YMIN)/2)

	local TCAVE = cave_layer_def.cave_threshold

	local np_cave = cave_layer_def.perlin_cave
	local np_wave = cave_layer_def.perlin_wave

	local y_blend_min = YMIN + BLEND * 1.5
	local y_blend_max = YMAX - BLEND * 1.5

	local column_def = cave_layer_def.columns
	local double_frequency = cave_layer_def.double_frequency

	local options = {}

	for chunk = 1, 64 do
		minetest.log("info", "[subterrane] searching for spawn "..chunk)

		local minp = {x = sidelen * math.random(-32, 32) - 32, z = sidelen * math.random(-32, 32) - 32, y = ydepth}
		local maxp = {x = minp.x + sidelen - 1, z = minp.z + sidelen - 1, y = ydepth + sidelen - 1}

		local nvals_cave, cave_area = mapgen_helper.perlin3d("subterrane:cave", minp, maxp, np_cave) --cave noise for structure
		local nvals_wave = mapgen_helper.perlin3d("subterrane:wave", minp, maxp, np_wave) --wavy structure of cavern ceilings and floors

		-- pre-average everything
		for vi, value in ipairs(nvals_cave) do
			nvals_cave[vi] = (value + nvals_wave[vi])/2
		end
		local column_points = nil
		local column_weight = nil

		local previous_y = minp.y
		local previous_node_state = outside_region

		for vi, x, y, z in cave_area:iterp_yxz(vector.add(minp, 2), vector.subtract(maxp, 2)) do

			if y < previous_y then
				previous_node_state = outside_region
			end
			previous_y = y

			local cave_local_threshold
			if y < y_blend_min then
				cave_local_threshold = TCAVE + ((y_blend_min - y) / BLEND) ^ 2
			elseif y > y_blend_max then
				cave_local_threshold = TCAVE + ((y - y_blend_max) / BLEND) ^ 2
			else
				cave_local_threshold = TCAVE
			end

			local cave_value = nvals_cave[vi]
			if double_frequency then
				cave_value = math.abs(cave_value)
			end

			-- inside a giant cavern
			if cave_value > cave_local_threshold then
				local column_value = 0
				local inside_column = false
				if column_def then
					if column_points == nil then
						column_points = subterrane.get_column_points(minp, maxp, column_def)
						column_weight = column_def.weight
					end
					column_value = subterrane.get_column_value({x=x, y=y, z=z}, column_points)
				end
				inside_column =  column_value > 0 and cave_value - column_value * column_weight < cave_local_threshold
				if previous_node_state == inside_ground and not inside_column then
					-- we just entered the cavern from below. Do a quick check for head space.
					local val_above = nvals_cave[vi+cave_area.ystride]
					if double_frequency then
						val_above = math.abs(val_above)
					end
					if val_above > cave_local_threshold then
						table.insert(options, {x=x, y=y+1, z=z})
					end
				end
				if not inside_column then
					previous_node_state = inside_cavern
				else
					previous_node_state = inside_ground
				end

			else
				previous_node_state = inside_ground
			end
		end

		if table.getn(options) > 20 then -- minimum 20 to ensure the player is put in a place with a modicum of size
			local choice = math.random(1, table.getn(options))
			local spawnpoint = options[ choice ]
			minetest.log("action", "[subterrane] spawning player " .. minetest.pos_to_string(spawnpoint))
			player:setpos(spawnpoint)
			return true
		end
	end
	return false
end


code:
subterrane_enable_legacy_dripstone (Adds old dripstone node definitions) bool false
#Use this mode with singlenode mapgen to produce a large-scale map of a particular world's caverns
subterrane_enable_singlenode_mapping_mode (Fills cavern volume with stone and warrens with desertstone) bool false


code:
-- If pos is located inside a cavern volume, returns the list of cavern definitions that
-- are responsible for that cavern volume. If not inside a cavern volume returns an empty list.

-- This is a somewhat expensive function, take care not to use it more than necessary
subterrane.is_in_cavern = function(pos)
	local results = {}

	for _, cave_layer_def in pairs(subterrane.registered_layers) do
		local YMIN = cave_layer_def.y_min
		local YMAX = cave_layer_def.y_max
		local y = pos.y
		if y <= YMAX and y >= YMIN then
			local block_size = mapgen_helper.block_size
			local BLEND = math.min(cave_layer_def.boundary_blend_range, (YMAX-YMIN)/2)
			local TCAVE = cave_layer_def.cave_threshold
			local np_cave = cave_layer_def.perlin_cave
			local np_wave = cave_layer_def.perlin_wave
			local y_blend_min = YMIN + BLEND * 1.5
			local y_blend_max = YMAX - BLEND * 1.5
			local nval_cave = minetest.get_perlin(np_cave):get_3d(pos) --cave noise for structure
			local nval_wave = minetest.get_perlin(np_wave):get_3d(pos) --wavy structure of cavern ceilings and floors
			nval_cave = (nval_cave + nval_wave)/2
			local cave_local_threshold
			if y < y_blend_min then
				cave_local_threshold = TCAVE + ((y_blend_min - y) / BLEND) ^ 2
			elseif y > y_blend_max then
				cave_local_threshold = TCAVE + ((y - y_blend_max) / BLEND) ^ 2
			else
				cave_local_threshold = TCAVE
			end
			if cave_layer_def.double_frequency and nval_cave < 0 then
				nval_cave = -nval_cave
			end
			if nval_cave > cave_local_threshold then
				table.insert(results, cave_layer_def)
			end
		end
	end

	return results
end

-- returns the value of the cavern field at a particular location for a particular cavern definition.
subterrane.get_cavern_value = function(name, pos)
	local cave_layer_def = subterrane.registered_layers[name]

	local YMIN = cave_layer_def.y_min
	local YMAX = cave_layer_def.y_max
	local y = pos.y
	if y > YMAX or y < YMIN then
		return nil
	end

	local block_size = mapgen_helper.block_size
	local BLEND = math.min(cave_layer_def.boundary_blend_range, (YMAX-YMIN)/2)
	local TCAVE = cave_layer_def.cave_threshold
	local np_cave = cave_layer_def.perlin_cave
	local np_wave = cave_layer_def.perlin_wave
	local y_blend_min = YMIN + BLEND * 1.5
	local y_blend_max = YMAX - BLEND * 1.5
	local nval_cave = minetest.get_perlin(np_cave):get_3d(pos) --cave noise for structure
	local nval_wave = minetest.get_perlin(np_wave):get_3d(pos) --wavy structure of cavern ceilings and floors
	nval_cave = (nval_cave + nval_wave)/2
	return nval_cave
end


code:
#! /bin/bash

# To create a new translation:
#   msginit --locale=ll_CC -o locale/ll_CC.po -i locale/template.pot

cd "$(dirname "${BASH_SOURCE[0]}")/..";

# Extract translatable strings.
xgettext --from-code=UTF-8 \
		--keyword=S \
		--keyword=NS:1,2 \
		--keyword=N_ \
		--add-comments='Translators:' \
		--add-location=file \
		-o locale/template.pot \
		$(find . -name '*.lua')

# Update translations.
find locale -name '*.po' | while read -r file; do
	echo $file
	msgmerge --update $file locale/template.pot;
done


code:
# geomoria

This mod uses a simple table-based description of an underground region to create huge, geomorphic constructions. My goal is to make it look a bit like Tolkien's Moria.

![screenshot](https://raw.githubusercontent.com/duane-r/geomoria/master/screenshot1.jpg)

The source is available on github.

Code: LGPL2

Mod dependencies: default

Download: https://github.com/duane-r/geomoria/archive/master.zip


code:
-- Geomoria geomorph.lua
-- Copyright Duane Robertson (duane@duanerobertson.com), 2017
-- Distributed under the LGPLv2.1 (https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html)

--newnode = geomoria_mod.clone_node("air")
--newnode.light_source = 14
--minetest.register_node("geomoria:bright_air", newnode)

geomoria_mod.geomorph = function(minp, maxp, data, p2data, area, node, heightmap)
  if not (minp and maxp and data and p2data and area and node and type(data) == 'table' and type(p2data) == 'table') then
    return
  end

  if minp.y >= 200 then
    return
  end

  local csize = vector.add(vector.subtract(maxp, minp), 1)
  local write = false
  local wetness = 0

  local avg = (minp.y + maxp.y) / 2
  local out_of_range
  if avg > geomoria_mod.geomoria_depth * 80 - 32 then
    out_of_range = true
  end

  local index = 0
  local index3d = 0

  local plan_name = geomoria_mod.plans_keys[math.random(#geomoria_mod.plans_keys)]
  local plan = geomoria_mod.plans[plan_name]
  local rot = math.random(4) - 1

  local exit_stair = (minp.z % (csize.z * 10)) < csize.z and (minp.x % (csize.x * 10)) < csize.x
  if out_of_range then
    plan = geomoria_mod.stair_height
    rot = 0
  elseif exit_stair then
    plan = geomoria_mod.stair_base
    rot = 0
  end

  if not out_of_range then
    for _, item in pairs(plan) do
      if item.act == 'fill' or item.act == 'stair' or item.act == 'ladder' or item.act == 'cylinder' or item.act == 'sphere' then
        local coords = item.coords

        if not (coords and item.node and type(coords) == 'table' and type(item.node) == 'string' and #coords == 6) then
          print('Geomoria: Invalid plan')
          return
        end

        local min_x, max_x, min_z, max_z, dy

        if rot == 0 then
          min_x, max_x = coords[1], coords[1] + coords[2] - 1
          min_z, max_z = coords[5], coords[5] + coords[6] - 1
        elseif rot == 1 then
          min_x, max_x = coords[5], coords[5] + coords[6] - 1
          min_z, max_z = csize.x - (coords[1] + coords[2]), csize.x - coords[1] - 1
        elseif rot == 2 then
          min_x, max_x = csize.x - (coords[1] + coords[2]), csize.x - coords[1] - 1
          min_z, max_z = csize.z - (coords[5] + coords[6]), csize.z - coords[5] - 1
        elseif rot == 3 then
          min_x, max_x = csize.z - (coords[5] + coords[6]), csize.z - coords[5] - 1
          min_z, max_z = coords[1], coords[1] + coords[2] - 1
        end

        local min_y = coords[3]
        local max_y = coords[3] + coords[4] - 1
        if item.act == 'stair' then
          max_y = max_y + 4
        end

        for dz = min_z - 2, max_z + 2 do
          for dy = min_y - 2, max_y + 2 do
            for dx = min_x - 2, max_x + 2 do
              if dx >= 0 and dx <= 79 and dy >= 0 and dy <= 79 and dz >= 0 and dz <= 79 then
                local ivm = area:index(minp.x + dx, minp.y + dy, minp.z + dz)
                data[ivm] = node['default:stone']
              end
            end
          end
        end
      end
    end
  end

  for _, item in pairs(plan) do
    if item.param then
      if item.param == 'wet' then
        wetness = 1
      elseif item.param == 'dry' then
        wetness = -1
      end
      --elseif item.hook and type(item.hook) == 'function' then
      --  local n, pos = item.hook(item.hook_params)
      --  if type(n) == 'number' and type(pos) == 'table' and type(pos.x) == 'number' and type(pos.y) == 'number' and type(pos.z) == 'number' then
      --    local ivm = area:index(minp.x + pos.x, minp.y + pos.y, minp.z + pos.z)
      --    data[ivm] = node[item.line]
      --  else
      --    print("Geomoria: Could not interpret hook return values")
      --  end
    elseif item.act == 'fill' or item.act == 'stair' or item.act == 'ladder' or item.act == 'cylinder' or item.act == 'sphere' then
      local coords = item.coords
      local p2 = item.param2

      if p2 and item.act == 'ladder' then
        -- 2 X+   3 X-   4 Z+   5 Z-
        local tran = '4253'
        local fp2 = tran:find(tostring(p2)) - 1
        p2 = (fp2 + rot) % 4
        p2 = tonumber(tran:sub(p2 + 1, p2 + 1))
      elseif p2 and p2 < 4 then
        --p2 = math.floor(p2 / 4) * 4 + (p2 + rot) % 4
        p2 = (p2 + rot) % 4
      elseif p2 then
        --
      else
        p2 = nil
      end

      local min_x, max_x, min_z, max_z, dy

      if rot == 0 then
        min_x, max_x = coords[1], coords[1] + coords[2] - 1
        min_z, max_z = coords[5], coords[5] + coords[6] - 1
      elseif rot == 1 then
        min_x, max_x = coords[5], coords[5] + coords[6] - 1
        min_z, max_z = csize.x - (coords[1] + coords[2]), csize.x - coords[1] - 1
      elseif rot == 2 then
        min_x, max_x = csize.x - (coords[1] + coords[2]), csize.x - coords[1] - 1
        min_z, max_z = csize.z - (coords[5] + coords[6]), csize.z - coords[5] - 1
      elseif rot == 3 then
        min_x, max_x = csize.z - (coords[5] + coords[6]), csize.z - coords[5] - 1
        min_z, max_z = coords[1], coords[1] + coords[2] - 1
      end

      if item.act == 'fill' or item.act == 'ladder' then
        if item.line then
          for dz = min_z - 1, max_z + 1 do
            for dy = coords[3] - 1, coords[3] + coords[4] do
              for dx = min_x - 1, max_x + 1 do
                local ivm = area:index(minp.x + dx, minp.y + dy, minp.z + dz)
                data[ivm] = node[item.line]
              end
            end
          end
        end
        if item.floor then
          for dz = min_z - 1, max_z + 1 do
            for dx = min_x - 1, max_x + 1 do
              local ivm = area:index(minp.x + dx, minp.y + coords[3] - 1, minp.z + dz)
              data[ivm] = node[item.floor]
            end
          end
        end

        if geomoria_mod.cheap_lighting and item.node == 'air' then
          if coords[4] < 13 and coords[2] > 10 and coords[6] > 10 then
            for dz = min_z, max_z do
              for dx = min_x, max_x do
                if dx % 8 == 0 and dz % 8 == 0 then
                  local ivm = area:index(minp.x + dx, minp.y + coords[3] + coords[4], minp.z + dz)
                  if data[ivm] == node['default:stone'] or data[ivm] == node['default:stone_block'] then
                    data[ivm] = node['default:meselamp']
                  end
                end
              end
            end
          else
            local alt = minp.y + coords[3] + math.floor(coords[4] / 2)
            for dz = min_z - 1, max_z + 1, max_z - min_z + 2 do
              for dx = min_x, max_x do
                if dx % 10 == 0 then
                  local ivm = area:index(minp.x + dx, alt, minp.z + dz)
                  if data[ivm] == node['default:stone'] or data[ivm] == node['default:stone_block'] then
                    data[ivm] = node['default:meselamp']
                  end
                end
              end
            end
            for dz = min_z, max_z do
              for dx = min_x - 1, max_x + 1, max_x - min_x + 2 do
                if dz % 10 == 0 then
                  local ivm = area:index(minp.x + dx, alt, minp.z + dz)
                  if data[ivm] == node['default:stone'] or data[ivm] == node['default:stone_block'] then
                    data[ivm] = node['default:meselamp']
                  end
                end
              end
            end
          end
        end

        for dz = min_z, max_z do
          for dx = min_x, max_x do
            local height
            if heightmap then
              height = heightmap[dz * csize.x + dx + 1]
              if item.node == 'air' then
                height = height + 4
              end
            end
            for dy = coords[3], coords[3] + coords[4] - 1 do
              if not height or (minp.y + dy) < height then
                if not item.random or math.random(item.random) == 1 then
                  local ivm = area:index(minp.x + dx, minp.y + dy, minp.z + dz)
                  data[ivm] = node[item.node]
                  p2data[ivm] = p2
                end
              end
            end
          end
        end

        if item.treasure and math.random(item.treasure) == 1 then
          local x = minp.x + min_x + math.random(max_x - min_x + 1) - 1
          local y = minp.y + coords[3]
          local z = minp.z + min_z + math.random(max_z - min_z + 1) - 1
          local n = geomoria_mod.treasure_chest_hook({x=x, y=y, z=z}, min_x, max_x, min_z, max_z, data, area, node)
          if type(n) == 'number' then
            local ivm = area:index(x, y, z)
            data[ivm] = n
          end
        end

        write = true
      elseif item.act == 'stair' then
        for dz = min_z, max_z do
          for dx = min_x, max_x do
            if p2 == 0 then
              dy = coords[3] + dz - min_z
            elseif p2 == 1 then
              dy = coords[3] + dx - min_x
            elseif p2 == 2 then
              dy = coords[3] + max_z - dz
            elseif p2 == 3 then
              dy = coords[3] + max_x - dx
            end

            local height
            if heightmap then
              height = heightmap[dz * csize.x + dx + 1]
            end

            local y1 = item.depth and dy - item.depth or coords[3]
            y1 = math.max(y1, coords[3])
            for y = y1, dy - 1 do
              if not height or (minp.y + y) <= height then
                local ivm = area:index(minp.x + dx, minp.y + y, minp.z + dz)
                data[ivm] = node['default:stone']
              end
            end

            y1 = item.height and dy + item.height or coords[3] + coords[4] + 2
            y1 = math.min(y1, coords[3] + coords[4] + 2)
            for y = dy + 1, y1 do
              if not height or (minp.y + y) <= (height + 4) then
                local ivm = area:index(minp.x + dx, minp.y + y, minp.z + dz)
                data[ivm] = node['air']
              end
            end

            if not height or (minp.y + dy) <= height then
              local ivm = area:index(minp.x + dx, minp.y + dy, minp.z + dz)
              data[ivm] = node[item.node]
              p2data[ivm] = p2
            end
          end
        end
        write = true
      elseif item.act == 'cylinder' then
        local ax = item.axis
        if ax ~= 'y' and (rot == 1 or rot == 3) then
          if ax == 'z' then
            ax = 'x'
          else
            ax = 'z'
          end
        end

        local r2 = (coords[4] / 2) ^ 2
        if ax == 'y' then
          r2 = (coords[2] / 2) ^ 2
        end

        local rx = (min_x + max_x) / 2
        local ry = (coords[3] + coords[3] + coords[4] - 1) / 2
        local rz = (min_z + max_z) / 2

        min_x = math.max(min_x, 0)
        local min_y = math.max(coords[3], 0)
        min_z = math.max(min_z, 0)
        max_x = math.min(max_x, csize.x - 1)
        local max_y = math.min(coords[3] + coords[4] - 1, csize.y - 1)
        max_z = math.min(max_z, csize.z - 1)

        for dz = min_z, max_z do
          for dy = min_y, max_y do
            for dx = min_x, max_x do
              if (ax == 'x' and (rz - dz) ^ 2 + (ry - dy) ^ 2 <= r2) or
                (ax == 'y' and (rx - dx) ^ 2 + (rz - dz) ^ 2 <= r2) or
                (ax == 'z' and (rx - dx) ^ 2 + (ry - dy) ^ 2 <= r2) then
                if not item.random or math.random(item.random) == 1 then
                  local ivm = area:index(minp.x + dx, minp.y + dy, minp.z + dz)
                  data[ivm] = node[item.node]
                  p2data[ivm] = p2
                end
              end
            end
          end
        end

        write = true
      elseif item.act == 'sphere' then
        local r2 = (coords[2] / 2) ^ 2

        local rx = (min_x + max_x) / 2
        local ry = (coords[3] + coords[3] + coords[4] - 1) / 2
        local rz = (min_z + max_z) / 2

        min_x = math.max(min_x, 0)
        local min_y = math.max(coords[3], 0)
        min_z = math.max(min_z, 0)
        max_x = math.min(max_x, csize.x - 1)
        local max_y = math.min(coords[3] + coords[4] - 1, csize.y - 1)
        max_z = math.min(max_z, csize.z - 1)

        for dz = min_z, max_z do
          for dy = min_y, max_y do
            for dx = min_x, max_x do
              if (rz - dz) ^ 2 + (ry - dy) ^ 2 + (rx - dx) ^ 2 <= r2 then
                if not item.random or math.random(item.random) == 1 then
                  local ivm = area:index(minp.x + dx, minp.y + dy, minp.z + dz)
                  data[ivm] = node[item.node]
                  p2data[ivm] = p2
                end
              end
            end
          end
        end

        write = true
      end
    end
  end

  return write, wetness
end


code:
-- Geomoria init.lua
-- Copyright Duane Robertson (duane@duanerobertson.com), 2017
-- Distributed under the LGPLv2.1 (https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html)

geomoria_mod = {}
geomoria_mod.version = "1.0"
geomoria_mod.path = minetest.get_modpath(minetest.get_current_modname())
geomoria_mod.world = minetest.get_worldpath()
geomoria_mod.plans = {}
geomoria_mod.plans_keys = {}
geomoria_mod.geomoria_depth = -1

geomoria_mod.generate_ores = minetest.settings:get_bool('geomoria_generate_ores')
if geomoria_mod.generate_ores == nil then
  geomoria_mod.generate_ores = false
end

geomoria_mod.add_fissures = minetest.settings:get_bool('geomoria_add_fissures')
if geomoria_mod.add_fissures == nil then
  geomoria_mod.add_fissures = true
end

geomoria_mod.cheap_lighting = minetest.settings:get_bool('geomoria_cheap_lighting')
if geomoria_mod.cheap_lighting == nil then
  geomoria_mod.cheap_lighting = false
end

geomoria_mod.damage_level = minetest.settings:get_bool('geomoria_damage_level')
geomoria_mod.damage_level = (geomoria_mod.damage_level or 5)

local treasure_chest = 'default:chest'
if minetest.registered_items['booty:coffer'] then
  treasure_chest = 'booty:coffer'
end

-- This can be overridden to do complicated treasure placement.
--  'pos' is the coordinate where a chest would go.
--  'min/max' values are the coordinates of the room or hall.
--  'data' is the mapgen data for that chunk.
--  'area' is the VoxelArea structure for the chunk.
-- The function must return the get_content_id value for a node.
--
-- If you override this, it might make sense to save the original
--  function and call it, in case someone else overrides it.
--  However, there can be only one return value.
function geomoria_mod.treasure_chest_hook(pos, min_x, max_x, min_z, max_z, data, area, node)
  return node[treasure_chest]
end

geomoria_mod.default_exits = {
  {act = 'fill', node = 'air', coords = {0, 1, 21, 3, 19, 2}},
  {act = 'fill', node = 'air', coords = {0, 1, 21, 3, 39, 2}},
  {act = 'fill', node = 'air', coords = {0, 1, 21, 3, 59, 2}},
  {act = 'fill', node = 'air', coords = {79, 1, 21, 3, 19, 2}},
  {act = 'fill', node = 'air', coords = {79, 1, 21, 3, 39, 2}},
  {act = 'fill', node = 'air', coords = {79, 1, 21, 3, 59, 2}},
  {act = 'fill', node = 'air', coords = {19, 2, 21, 3, 0, 1}},
  {act = 'fill', node = 'air', coords = {39, 2, 21, 3, 0, 1}},
  {act = 'fill', node = 'air', coords = {59, 2, 21, 3, 0, 1}},
  {act = 'fill', node = 'air', coords = {19, 2, 21, 3, 79, 1}},
  {act = 'fill', node = 'air', coords = {39, 2, 21, 3, 79, 1}},
  {act = 'fill', node = 'air', coords = {59, 2, 21, 3, 79, 1}},
  {act = 'fill', node = 'air', coords = {0, 1, 51, 3, 39, 2}},
  {act = 'fill', node = 'air', coords = {79, 1, 51, 3, 39, 2}},
  {act = 'fill', node = 'air', coords = {39, 2, 51, 3, 0, 1}},
  {act = 'fill', node = 'air', coords = {39, 2, 51, 3, 79, 1}},
}

function geomoria_mod.clone_node(name)
  if not (name and type(name) == 'string') then
    return
  end

  local node = minetest.registered_nodes[name]
  local node2 = table.copy(node)
  return node2
end

dofile(geomoria_mod.path .. "/plans.lua")

for k, v in pairs(geomoria_mod.plans) do
  geomoria_mod.plans_keys[#geomoria_mod.plans_keys+1] = k
end

dofile(geomoria_mod.path .. "/mapgen.lua")


code:
-- Geomoria mapgen.lua
-- Copyright Duane Robertson (duane@duanerobertson.com), 2017
-- Distributed under the LGPLv2.1 (https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html)

local DEBUG
local max_depth = 31000
local geomoria_depth = geomoria_mod.geomoria_depth
local math_random = math.random

local ground_nodes = {}
local ground_nodes_names = {
  'default:desert_stone',
  'default:dirt',
  'default:dirt_with_dry_grass',
  'default:dirt_with_grass',
  'default:dirt_with_snow',
  'default:sand',
  'default:sandstone',
  'default:stone',
}
for _, i in pairs(ground_nodes_names) do
  ground_nodes[minetest.get_content_id(i)] = true
end

-- This table looks up nodes that aren't already stored.
local node = setmetatable({}, {
  __index = function(t, k)
    if not (t and k and type(t) == 'table') then
      return
    end

    t[k] = minetest.get_content_id(k)
    return t[k]
  end
})

local data = {}
local p2data = {}

local fissure_noise_map, damage_noise_map
local fissure_noise, damage_noise = {}, {}

local function generate(p_minp, p_maxp, seed)
  if not (p_minp and p_maxp and seed) then
    return
  end

  local minp, maxp = p_minp, p_maxp
  local avg = (minp.y + maxp.y) / 2
  local csize = vector.add(vector.subtract(maxp, minp), 1)

  local exit_stair = (minp.z % (csize.z * 10)) < csize.z and (minp.x % (csize.x * 10)) < csize.x
  if avg < (geomoria_depth - 1) * 80 - 32 or (not exit_stair and avg > geomoria_depth * 80 - 32) then
    return
  end

  local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
  if not (vm and emin and emax) then
    return
  end

  vm:get_data(data)
  p2data = vm:get_param2_data()
  local heightmap
  local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax})

  if exit_stair and minp.y < 200 and avg > geomoria_depth * 80 - 32 then
    heightmap = minetest.get_mapgen_object("heightmap")

    -- Correct heightmap.
    --if maxp.y < -300 or minp.y > 300 then
    --  for i = 1, #heightmap do
    --    heightmap[i] = (maxp.y < 0) and max_depth or - max_depth
    --  end
    --else
    --  local index = 0
    --  for z = minp.z, maxp.z do
    --    for x = minp.x, maxp.x do
    --      index = index + 1

    --      local height = heightmap[index]
    --      if height and height < maxp.y - 1 and height > minp.y then
    --        --nop
    --      else
    --        height = - max_depth
    --        local ivm2 = area:index(x, maxp.y + 8, z)
    --        for y = maxp.y + 8, minp.y - 8, -1 do
    --          if ground_nodes[data[ivm2]] then
    --            height = (y < maxp.y + 8) and y or max_depth
    --            break
    --          end
    --          ivm2 = ivm2 - area.ystride
    --        end
    --        heightmap[index] = height
    --      end
    --    end
    --  end
    --end
  end

  if geomoria_mod.add_fissures then
    if not (damage_noise_map and fissure_noise_map) then
      damage_noise_map = minetest.get_perlin_map({offset = 0, scale = 0.5, seed = -6827, spread = {x = 200, y = 200, z = 200}, octaves = 3, persist = 0.8, lacunarity = 2}, csize)
      fissure_noise_map = minetest.get_perlin_map({offset = 0, scale = 1, seed = -8402, spread = {x = 8, y = 64, z = 8}, octaves = 3, persist = 0.5, lacunarity = 2}, csize)

      if not (damage_noise_map and fissure_noise_map) then
        return
      end
    end

    damage_noise = damage_noise_map:get2dMap_flat({x=minp.x, y=minp.z}, damage_noise)
    fissure_noise = fissure_noise_map:get3dMap_flat(minp, fissure_noise)
  else
    heightmap = minetest.get_mapgen_object("heightmap")
    damage_noise = heightmap
    fissure_noise = heightmap
    -- damage_noise = heightmap:get2dMap_flat({x=minp.x, y=minp.z}, damage_noise)
    -- fissure_noise = heightmap:get3dMap_flat(minp, fissure_noise)

	end

  local write, wetness = geomoria_mod.geomorph(minp, maxp, data, p2data, area, node, heightmap)

  if not write then
    return
  end

  if not wetness then
    wetness = 0
  end

  if not DEBUG and fissure_noise then
    local index = 1
    local index2 = 1
    for z = minp.z, maxp.z do
      for y = minp.y, maxp.y do
        local ivm = area:index(minp.x, y, z)
        local taper = 0
        --local taper = math.abs(30 - (y - minp.y)) / 50 - 0.7
        local dy = y - minp.y
        if dy < 20 then
          taper = (20 - dy) / 40
        elseif dy > 59 then
          taper = (dy - 59) / 40
        end

        for x = minp.x, maxp.x do
		  local damage
		  if fissure_noise and damage_noise then
            damage = (fissure_noise[index] - damage_noise[index2 + x - minp.x] - taper)
		  else
		    damage = geomoria_mod.damage_level
		  end
          if damage > geomoria_mod.damage_level then
            if data[ivm] ~= node['default:water_source'] and data[ivm] ~= node['default:water_source'] then
			  --ShadMOrdre 20180821
              --data[ivm] = node['air']
            end
          elseif (data[ivm] == node['default:stone'] or data[ivm] == node['default:stone_block']) and damage > geomoria_mod.damage_level - 0.5 then
            if (wetness > 0 and math_random(2) == 1) or (wetness == 0 and math_random(10) == 1) then
              if minetest.registered_items['fun_caves:glowing_fungal_stone'] and math_random(4) == 1 then
			    --ShadMOrdre 20180821
                --data[ivm] = node['fun_caves:glowing_fungal_stone']
              else
                data[ivm] = node['default:mossycobble']
              end
            else
              data[ivm] = node['default:cobble']
            end
          end

          ivm = ivm + 1
          index = index + 1
        end
      end
      index2 = index2 + csize.x
    end
  end

  if write then

    vm:set_data(data)
    vm:set_param2_data(p2data)

    if DEBUG then
      vm:set_lighting({day = 14, night = 14})
    else
      vm:set_lighting({day = 0, night = 0}, minp, maxp)
      vm:calc_lighting()
    end
    vm:update_liquids()
    vm:write_to_map()
  end
end

if geomoria_mod.path then
  dofile(geomoria_mod.path .. "/geomorph.lua")
end

local function pgenerate(...)
  local status, err = pcall(generate, ...)
  --local status, err = true
  --generate(...)
  if not status then
    print('Geomoria: Could not generate terrain:')
    print(dump(err))
    collectgarbage("collect")
  end
end

minetest.register_on_generated(pgenerate)


code:
name = geomoria
description = Underground Dwarven City
author = duane
depends = default, stairs, moreblocks, doors