code:
-- These functions are a modification of the schematic placement code from src/mapgen/mg_schematic.cpp.
-- As such, this file is separately licened under the LGPL as follows:

-- License of Minetest source code
-------------------------------

--Minetest
--Copyright (C) 2010-2018 celeron55, Perttu Ahola <celeron55@gmail.com>

--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.

--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--GNU Lesser General Public License for more details.

--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

local c_air = minetest.get_content_id("air")
local c_ignore = minetest.get_content_id("ignore")

-- Table value = rotated facedir
-- Columns: 90, 180, 270 degrees rotation around vertical axis
-- Rotation is anticlockwise as seen from above (+Y)
local rotate_facedir_y =
{
	[0] = {1, 2, 3}     ,
	[1] = {2, 3, 0}     ,
	[2] = {3, 0, 1}     ,
	[3] = {0, 1, 2}     ,
	[4] = {13, 10, 19}  ,
	[5] = {14, 11, 16}  ,
	[6] = {15, 8, 17}   ,
	[7] = {12, 9, 18}   ,
	[8] = {17, 6, 15}   ,
	[9] = {18, 7, 12}   ,
	[10] = {19, 4, 13}  ,
	[11] = {16, 5, 14}  ,
	[12] = {9, 18, 7}   ,
	[13] = {10, 19, 4}  ,
	[14] = {11, 16, 5}  ,
	[15] = {8, 17, 6}   ,
	[16] = {5, 14, 11}  ,
	[17] = {6, 15, 8}   ,
	[18] = {7, 12, 9}   ,
	[19] = {4, 13, 10}  ,
	[20] = {23, 22, 21} ,
	[21] = {20, 23, 22} ,
	[22] = {21, 20, 23} ,
	[23] = {22, 21, 20} ,
}

local random_rotations = {0, 90, 180, 270}

local rotate_param2 = function(param2, paramtype2, rotation)
	param2 = param2 or 0
	if paramtype2 == "facedir" then
		if rotation == 90 then
			param2 = rotate_facedir_y[param2][1]
		elseif rotation == 180 then
			param2 = rotate_facedir_y[param2][2]
		elseif rotation == 270 then
			param2 = rotate_facedir_y[param2][3]
		end
	elseif paramtype2 == "wallmounted" then
		--TODO
	elseif paramtype2 == "colorfacedir" then
		--TODO
	elseif paramtype2 == "colorwallmounted" then
		--TODO
	end

	return param2
end

local swap = function(size_x, size_z)
	return size_z, size_x
end

-- Returns the minpos and maxpos of the bounding box that this schematic will be placed in given
-- the rotation and flag parameters. Useful for testing whether a schematic will fit in a place before actually
-- writing it to the data, so that you can abort and try something else instead.
mapgen_helper.get_schematic_bounding_box = function(pos, schematic, rotation, flags)
	flags = flags or {}
	local size = schematic.size
	local size_x = size.x
	local size_y = size.y
	local size_z = size.z
	local center_pos = schematic.center_pos

	if center_pos and rotation ~= nil and rotation ~= 0 then
		center_pos = vector.new(center_pos) -- make a copy so we can mess with it without damaging the schematic
		if rotation == 90 then
			center_pos.x, center_pos.z = swap(size_x - center_pos.x - 1, center_pos.z)
		elseif rotation == 180 then
			center_pos.z = size_z - center_pos.z - 1
			center_pos.x = size_x - center_pos.x - 1
		elseif rotation == 270 then
			center_pos.x, center_pos.z = swap(center_pos.x, size_z - center_pos.z - 1)
		end
	end

	if rotation == 90 or rotation == 270 then
		size_x, size_z = swap(size_x, size_z)
	end

	local minpos = vector.new(pos)

	if center_pos then
		if not flags.place_center_x then
			minpos.x = minpos.x - center_pos.x
		end
		if not flags.place_center_y then
			minpos.y = minpos.y - center_pos.y
		end
		if not flags.place_center_z then
			minpos.z = minpos.z - center_pos.z
		end
	end
	if flags.place_center_x then
		minpos.x = math.floor(minpos.x - (size_x - 1) / 2)
	end
	if flags.place_center_y then
		minpos.y = math.floor(minpos.y - (size_y - 1) / 2)
	end
	if flags.place_center_z then
		minpos.z = math.floor(minpos.z - (size_z - 1) / 2)
	end

	local maxpos = vector.add(minpos, {x=size_x-1, y=size_y-1, z=size_z-1})

	return minpos, maxpos
end

-- Takes a lua-format schematic and applies it to the data and param2_data arrays produced by vmanip instead of being applied to the vmanip directly. Useful in a mapgen loop that's doing other things with the data before and after applying schematics. A VoxelArea for the data also needs to be provided.

-- Schematic enhancements beyond the basic Minetest API that work with this:
-- * node defs can have a "place_on_condition" property defined, which is a function that takes a node content ID parameter and returns true to indicate the schematic should replace it or false to indicate it should not. Useful for, for example, a schematic that should replace water but not stone (placing decorations on the bottom of the ocean), or a schematic that replaces all buildable_to nodes (to prevent tufts of grass from knocking holes in foundations). "data, area, vi" parameters are also provided if you want to get fancy and base the condition on surrounding nodes, but bear in mind that the schematic is in the process of being written already so some neighbors will already have been replaced with schematic nodes and some neighbors will be replaced in the future.
-- * schematic can have a "center_pos" position defined relative to the placement pos that will be treated as the rotation and placement origin, unless overridden by the flags parameter

-- returns true if the schematic was entirely contained within the voxelarea, false otherwise.

local empty_table = {}

mapgen_helper.place_schematic_on_data = function(data, data_param2, area, pos, schematic, rotation, replacements, force_placement, flags)
	pos = vector.new(pos)
	replacements = replacements or empty_table
	flags = flags or empty_table -- TODO: support all flags formats
	if flags.force_placement ~= nil then
		force_placement = flags.force_placement -- TODO: unclear which force_placement parameter should have prededence here
	end
	local center_pos = schematic.center_pos
	if rotation == "random" then rotation = random_rotations[math.random(1,4)] end

	local schemdata = schematic.data
	local slice_probs = schematic.yslice_prob or {}

	local size = schematic.size
	local size_x = size.x
	local size_y = size.y
	local size_z = size.z

	local xstride = 1
	local ystride = size_x
	local zstride = size_x * size_y

	if center_pos and rotation ~= nil and rotation ~= 0 then
		center_pos = vector.new(center_pos) -- make a copy so we can mess with it without damaging the schematic
		if rotation == 90 then
			center_pos.x, center_pos.z = swap(size_x - center_pos.x - 1, center_pos.z)
		elseif rotation == 180 then
			center_pos.z = size_z - center_pos.z - 1
			center_pos.x = size_x - center_pos.x - 1
		elseif rotation == 270 then
			center_pos.x, center_pos.z = swap(center_pos.x, size_z - center_pos.z - 1)
		end
	end

	local i_start, i_step_x, i_step_z
	if rotation == 90 then
		i_start  = size_x
		i_step_x = zstride
		i_step_z = -xstride
		size_x, size_z = swap(size_x, size_z)
	elseif rotation == 180 then
		i_start  = zstride * (size_z - 1) + size_x
		i_step_x = -xstride
		i_step_z = -zstride
	elseif rotation == 270 then
		i_start  = zstride * (size_z - 1) + 1
		i_step_x = -zstride
		i_step_z = xstride
		size_x, size_z = swap(size_x, size_z)
	else
		i_start = 1
		i_step_x = xstride
		i_step_z = zstride
	end

	--	Adjust placement position if necessary
	if center_pos then
		if not flags.place_center_x then
			pos.x = pos.x - center_pos.x
		end
		if not flags.place_center_y then
			pos.y = pos.y - center_pos.y
		end
		if not flags.place_center_z then
			pos.z = pos.z - center_pos.z
		end
	end
	if flags.place_center_x then
		pos.x = math.floor(pos.x - (size_x - 1) / 2)
	end
	if flags.place_center_y then
		pos.y = math.floor(pos.y - (size_y - 1) / 2)
	end
	if flags.place_center_z then
		pos.z = math.floor(pos.z - (size_z - 1) / 2)
	end

	local maxpos = vector.add(pos, {x=size_x-1, y=size_y-1, z=size_z-1})
	local minEdge = area.MinEdge
	local maxEdge = area.MaxEdge
	if not (pos.x <= maxEdge.x and maxpos.x >= minEdge.x and
			pos.z <= maxEdge.z and maxpos.z >= minEdge.z and
			pos.y <= maxEdge.y and maxpos.y >= minEdge.y) then
		return false -- the bounding boxes of the area and the schematic don't overlap at all
	end

	local contained_in_area = true
	local on_place_callbacks = {}

	local y_map = pos.y
	for y = 0, size_y-1 do
		if slice_probs[y] == nil or slice_probs[y] == 255 or slice_probs[y] <= math.random(1, 255) then
			for z = 0, size_z-1 do
				local i = z * i_step_z + y * ystride + i_start
				for x = 0, size_x-1 do
					local vi = area:index(pos.x + x, y_map, pos.z + z)
					if area:containsi(vi) then
						local node_def = schemdata[i]
						local node_name = replacements[node_def.name] or node_def.name
						if node_name ~= "ignore" then
							local placement_prob = node_def.prob or 255
							if placement_prob ~= 0 then

								local force_place_node = node_def.force_place
								local place_on_condition = node_def.place_on_condition
								local on_place = node_def.on_place
								local old_node_id = data[vi]

								if (force_placement or force_place_node
									or (place_on_condition and place_on_condition(old_node_id, data, area, vi))
									or (not place_on_condition and (old_node_id == c_air or old_node_id == c_ignore)))
									and (placement_prob == 255 or math.random(1,255) <= placement_prob)
								then
									local registered_def = minetest.registered_nodes[node_name]
									if registered_def ~= nil then
										local paramtype2 = registered_def.paramtype2
										data[vi] = minetest.get_content_id(node_name)
										data_param2[vi] = rotate_param2(node_def.param2, paramtype2, rotation)

										if on_place then
											table.insert(on_place_callbacks, {on_place, vi})
										end
									else
										minetest.log("error", "mapgen_helper.place_schematic was given a schematic with unregistered node " .. tostring(node_name) .. " in it.")
									end
								end
							end
						end
					else
						contained_in_area = false -- schematic spilled over the edge of the area
					end
					i = i + i_step_x
				end
			end
		end
		y_map = y_map + 1
	end

	for k, callback in pairs(on_place_callbacks) do
		callback[1](callback[2], data, data_param2, area, pos, schematic, rotation, replacements, force_placement, flags)
	end

	return contained_in_area
end

-- aborts schematic placement if it won't fit into the provided data
mapgen_helper.place_schematic_on_data_if_it_fits = function(data, data_param2, area, pos, schematic, rotation, replacements, force_placement, flags)
	local minbound, maxbound = mapgen_helper.get_schematic_bounding_box(pos, schematic, rotation, flags)
	if mapgen_helper.is_box_within_box(minbound, maxbound, area.MinEdge, area.MaxEdge) then
		return mapgen_helper.place_schematic_on_data(data, data_param2, area, pos, schematic, rotation, replacements, force_placement, flags)
	end
	return false
end

-- wraps the above for convenience, so you can use this style of schematic in non-mapgen contexts as well
mapgen_helper.place_schematic = function(pos, schematic, rotation, replacements, force_placement, flags)
	local minpos, maxpos = mapgen_helper.get_schematic_bounding_box(pos, schematic, rotation, flags)
	local vmanip = minetest.get_voxel_manip(minpos, maxpos)
	local data = vmanip:get_data()
	local data_param2 = vmanip:get_param2_data()
	local emin, emax = vmanip:get_emerged_area()
	local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax}
	local ret = mapgen_helper.place_schematic_on_data(data, data_param2, area, pos, schematic, rotation, replacements, force_placement, flags)
	vmanip:set_data(data)
	vmanip:set_param2_data(data_param2)
	vmanip:write_to_map()
	return ret -- should always be true since we created the voxelarea to fit the schematic
end


code:
-- Returns a consistent list of random points within a volume.
-- Each call to this method will give the same set of points if the same parameters are provided
mapgen_helper.get_random_points = function(minp, maxp, min_output_size, max_output_size)
	local next_seed = math.random(1, 2^21) -- should be 2^31, but I've had a report that this causes a crash in the Lua interpreter on some systems.
	math.randomseed(minetest.hash_node_position(minp) + mapgen_helper.mapgen_seed)

	local count = math.random(min_output_size, max_output_size)
	local result = {}
	while count > 0 do
		local point = {}
		point.x = math.random(minp.x, maxp.x)
		point.y = math.random(minp.y, maxp.y)
		point.z = math.random(minp.z, maxp.z)
		table.insert(result, point)
		count = count - 1
	end

	math.randomseed(next_seed)
	return result
end

-- Returns a random value based on the x and z coordinates of pos, always the same for the same x and z
mapgen_helper.xz_consistent_randomp = function(pos)
	local next_seed = math.random(1, 2^21) -- should be 2^31, but I've had a report that this causes a crash in the Lua interpreter on some systems.
	math.randomseed(pos.x + pos.z * 2 ^ 8)
	local output = math.random()
	math.randomseed(next_seed)
	return output
end

mapgen_helper.xz_consistent_randomi = function(area, vi)
	local pos = area:position(vi)
	return mapgen_helper.xz_consistent_randomp(pos)
end

repo_name: korkut/MeseCraft

license: mit


code:
local mapgen_chunksize = tonumber(minetest.get_mapgen_setting("chunksize"))

-- For getting large regions that don't cross mapgen block boundaries
-- For some reason, map chunks generate with -32, -32, -32 as the "origin" minp. To make the large-scale grid align with map chunks it needs to be offset like this.
mapgen_helper.get_region_minp_xz = function(pos_xz, mapblocks)
	local region_size = mapblocks * mapgen_chunksize * 16
	return {x = math.floor((pos.x+32) / region_size) * region_size - 32, y = 0, z = math.floor((pos.z+32) / region_size) * region_size - 32}
end

-- Given a position and a region scale, returns the minp corners of the four regions that the player is closest to.
-- For example, if we have a region scale of 6 and the parameter position is at "*", the four points marked "R" would be returned.

-- |-----|-----|-----|      |-----|-----|-----|      |-----|-----|-----|
-- |     |     |     |      |     |     |     |      |     |     |     |
-- |     |     |     |      |     |     |     |      |     |     |     |
-- |     |     |     |      |     |     |     |      |     |     |     |
-- |     |     |     |      |     |     |     |      |     |     |     |
-- |     |     |     |      |     |     |     |      |     |     |     |
-- |-----|-----|-----|      |-----|-----|-----|      |-----R-----R-----|
-- |     |     |     |      |     |     |     |      |     |     |     |
-- |     |     |     |      |     |     |     |      |     |    *|     |
-- |     |     |     |      |     |     |     |      |     |     |     |
-- |     | *   |     |      |     |    *|     |      |     |     |     |
-- |     |     |     |      |     |     |     |      |     |     |     |
-- R-----R-----|-----|      |-----R-----R-----|      |-----R-----R-----|
-- |     |     |     |      |     |     |     |      |     |     |     |
-- |     |     |     |      |     |     |     |      |     |     |     |
-- |     |     |     |      |     |     |     |      |     |     |     |
-- |     |     |     |      |     |     |     |      |     |     |     |
-- |     |     |     |      |     |     |     |      |     |     |     |
-- R-----R-----|-----|      |-----R-----R-----|      |-----|-----|-----|

-- In this way you can be sure you aren't near an area you haven't tested.

mapgen_helper.get_nearest_regions = function(pos_xz, gridscale)
	local half_scale = gridscale / 2
	local grid_cell = {x = math.floor(pos_xz.x / gridscale) * gridscale, z = math.floor(pos_xz.z / gridscale) * gridscale}
	local pos_internal = {x = pos_xz.x % gridscale, z = pos_xz.z % gridscale}
	local result = {grid_cell}
	local shift_x = gridscale
	local shift_z = gridscale
	if (pos_internal.x < half_scale) then
		shift_x = -gridscale
	end
	if (pos_internal.z < half_scale) then
		shift_z = -gridscale
	end

	table.insert(result, {x = grid_cell.x + shift_x, z = grid_cell.z + shift_z})
	table.insert(result, {x = grid_cell.x + shift_x, z = grid_cell.z})
	table.insert(result, {x = grid_cell.x, z = grid_cell.z + shift_z})

	return result
end

-- |---|---|---|
-- |   |   |   |
-- |   |   |   |
-- R---R---R---|
-- |   |   |   |
-- |   |  *|   |
-- R---R---R---|
-- |   |   |   |
-- |   |   |   |
-- R---R---R---|

mapgen_helper.get_adjacent_regions = function(pos_xz, gridscale)
	local grid_cell = {x = math.floor(pos_xz.x / gridscale) * gridscale, z = math.floor(pos_xz.z / gridscale) * gridscale}
	local result = {grid_cell}
	table.insert(result, {x = grid_cell.x + gridscale, z = grid_cell.y + gridscale})
	table.insert(result, {x = grid_cell.x + gridscale, z = grid_cell.y})
	table.insert(result, {x = grid_cell.x, z = grid_cell.y + gridscale})
	table.insert(result, {x = grid_cell.x - gridscale, z = grid_cell.y - gridscale})
	table.insert(result, {x = grid_cell.x - gridscale, z = grid_cell.y})
	table.insert(result, {x = grid_cell.x, z = grid_cell.y - gridscale})
	return result
end


code:
mapgen_helper_record_time (Enable time-taken logging) bool false
mapgen_helper_persist_recorded_time (Persist time-taken logging between server sessions) bool false
mapgen_helper_record_time_resolution (Resolution of time-taken histogram) float 0.1
mapgen_helper_log_locations (Enable location logging) bool false


code:
-- These functions are a minor modification of the voxel area iterator defined in the built in voxelarea.lua lua code.
-- As such, this file is separately licened under the LGPL as follows:

-- License of Minetest source code
-------------------------------

--Minetest
--Copyright (C) 2010-2018 celeron55, Perttu Ahola <celeron55@gmail.com>

--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.

--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--GNU Lesser General Public License for more details.

--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

function VoxelArea:iter_xyz(minx, miny, minz, maxx, maxy, maxz)
	local i = self:index(minx, miny, minz) - 1

	local x = minx - 1 -- subtracting one because x gets incremented before it gets returned the first time.
	local xrange = maxx - minx + 1
	local nextaction = i + 1 + xrange

	local y = 0
	local yrange = maxy - miny + 1
	local yreqstride = self.ystride - xrange

	local z = 0
	local zrange = maxz - minz + 1
	local multistride = self.zstride - ((yrange - 1) * self.ystride + xrange)

	return function()
		-- continue i until it needs to jump
		i = i + 1
		x = x + 1
		if i ~= nextaction then
			return i, x, y+miny, z+minz
		end

		-- continue y until maxy is exceeded
		y = y + 1
		x = minx -- new line
		if y ~= yrange then
			-- set i to index(minx, miny + y, minz + z) - 1
			i = i + yreqstride
			nextaction = i + xrange
			return i, x, y+miny, z+minz
		end

		-- continue z until maxz is exceeded
		z = z + 1
		if z == zrange then
			-- cuboid finished, return nil
			return
		end

		-- set i to index(minx, miny, minz + z) - 1
		i = i + multistride

		y = 0
		nextaction = i + xrange
		return i, x, y+miny, z+minz
	end
end

function VoxelArea:iterp_xyz(minp, maxp)
	return self:iter_xyz(minp.x, minp.y, minp.z, maxp.x, maxp.y, maxp.z)
end


code:
-- similar to iter_xyz, but iterates first along the y axis. Useful in mapgens that want to detect a vertical transition (eg, finding ground level)
function VoxelArea:iter_yxz(minx, miny, minz, maxx, maxy, maxz)
	local i = self:index(minx, miny, minz) - self.ystride

	local x = minx
	local y = miny - 1
	local z = minz

	return function()
		y = y + 1

		if y <= maxy then
			i = i + self.ystride
			return i, x, y, z
		end

		y = miny
		x = x + 1

		if x <= maxx then
			i = self:index(x, y, z)
			return i, x, y, z
		end

		x = minx
		z = z + 1

		if z <= maxz then
			i = self:index(x, y, z)
			return i, x, y, z
		end
	end
end

function VoxelArea:iterp_yxz(minp, maxp)
	return self:iter_yxz(minp.x, minp.y, minp.z, maxp.x, maxp.y, maxp.z)
end

function VoxelArea:get_y(i)
	return math.floor(((i - 1) % self.zstride) / self.ystride) + self.MinEdge.y
end

-- Used to make transform more efficient by skipping a table creation
function VoxelArea:position_xyz(vi)
	local MinEdge = self.MinEdge
	local Zstride = self.zstride
	local Ystride = self.ystride
	vi = vi - 1
	local z = math.floor(vi / Zstride) + MinEdge.z
	vi = vi % Zstride
	local y = math.floor(vi / Ystride) + MinEdge.y
	vi = vi % Ystride
	local x = vi + MinEdge.x
	return x, y, z
end

-- Takes another voxelarea and an index in it and transforms it into an index into its own
-- voxelarea, or nil if it's not in the voxelarea. This is useful when you've got, for example,
-- a mapgen's voxelmanipulator and a perlin noise array covering the map block but not the entire
-- emerged volume.
function VoxelArea:transform(area, vi)
	local x,y,z = area:position_xyz(vi)
	if self:contains(x,y,z) then
		return self:index(x,y,z)
	end
	return nil
end


code:
local modname = minetest.get_current_modname()
local S = minetest.get_translator(modname)
local modpath = minetest.get_modpath(modname)

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

local gas_desc
local gas_usage

local seep_desc
local seep_usage

if minetest.get_modpath("doc") then
	gas_desc = S("Gaseous hydrocarbons formed from the detritus of long dead plants and animals processed by heat and pressure deep within the earth.")
	gas_usage = S("Gas is highly hazardous. Heavier than air, it pools in deep caverns and asphyxiates the unwary.")
	if minetest.get_modpath("tnt") then
		gas_usage = gas_usage .. " " .. S("When exposed to air and an ignition source it can produce a deadly explosion.")
	end

	seep_desc = S("Some coal deposits have cracks that seep a steady flow of mine gas.")
	seep_usage = S("Mining out such a deposit seals the crack.")
end

minetest.register_alias("oil:gas", "mine_gas:gas")
minetest.register_alias("oil:gas_seep", "mine_gas:gas_seep")

minetest.register_node("mine_gas:gas", {
	description = S("Mine Gas"),
	_doc_items_longdesc = gas_desc,
	_doc_items_usagehelp = gas_usage,
	walkable = false,
	pointable = false,
	diggable = false,
	is_ground_content = false,
	buildable_to = true,
	drawtype = "glasslike",
	drowning = 1,
	post_effect_color = {a = 20, r = 20, g = 20, b = 250},
	tiles = {"mine_gas.png^[opacity:28"},
	use_texture_alpha = "blend",
	groups = {not_in_creative_inventory=1, ropes_can_extend_into=1, not_solid=1, not_opaque=1},
	paramtype = "light",
	drop = {},
	sunlight_propagates = true,
	--on_blast = function() end, -- unaffected by explosions
})

minetest.register_node("mine_gas:gas_seep", {
	description = S("Gas Seep"),
	_doc_items_longdesc = seep_desc,
	_doc_items_usagehelp = seep_usage,
	tiles = {df_dependencies.texture_stone.."^"..df_dependencies.texture_mineral_coal.."^[combine:16x80:0,-16=crack_anylength.png"},
	groups = {cracky = 3, pickaxey=1, building_block=1, material_stone=1},
	drop = df_dependencies.node_name_coal_lump,
	sounds = df_dependencies.sound_stone(),
	is_ground_content = true,
	_mcl_blast_resistance = 5,
	_mcl_hardness = 3,
})

minetest.register_on_dignode(function(pos, oldnode, digger)
	if minetest.get_item_group(oldnode.name, "digtron") > 0 then
		-- skip digtron moved nodes
		return;
	end

	local np = minetest.find_node_near(pos, 1,{"mine_gas:gas"})
	if np ~= nil then
		minetest.set_node(pos, {name = "mine_gas:gas"})
		return
	end
end)

local directions = {
	{x=1, y=0, z=0},
	{x=-1, y=0, z=0},
	{x=0, y=0, z=1},
	{x=0, y=0, z=-1},
}

local gas_node = {name="mine_gas:gas"}
minetest.register_abm({
    label = "mine_gas:gas movement",
    nodenames = {"mine_gas:gas"},
    neighbors = {"group:liquid", "air"},
    interval = 1.0,
    chance = 1,
    catch_up = true,
    action = function(pos, node)
		local next_pos = {x=pos.x, y=pos.y+1, z=pos.z}
		local next_node = minetest.get_node(next_pos)
		if minetest.get_item_group(next_node.name, "liquid") > 0 then
			minetest.swap_node(next_pos, gas_node)
			minetest.swap_node(pos, next_node)
		else
			next_pos.y = pos.y-1
			next_node = minetest.get_node(next_pos)
			if next_node.name == "air" then
				minetest.swap_node(next_pos, gas_node)
				minetest.swap_node(pos, next_node)
			else
				local dir = directions[math.random(1,4)]
				local next_pos = vector.add(pos, dir)
				local next_node = minetest.get_node(next_pos)
				if next_node.name == "air" or  minetest.get_item_group(next_node.name, "liquid") > 0 then
					if next_node.name == "air" or math.random() < 0.5 then -- gas never "climbs" above air.
						minetest.swap_node(next_pos, gas_node)
						minetest.swap_node(pos, next_node)
					else
						-- this can get gas to rise up out of the surface of liquid, preventing it from forming a permanent hole.
						next_pos.y = next_pos.y + 1
						next_node = minetest.get_node(next_pos)
						if next_node.name == "air" then
							minetest.swap_node(next_pos, gas_node)
							minetest.swap_node(pos, next_node)
						end
					end
				end
			end
		end
	end,
})

local soundfile_cool_lava = df_dependencies.soundfile_cool_lava

minetest.register_abm({
	label = "mine_gas:gas snuffing torches",
	nodenames = {"group:torch"},
	neighbors = {"mine_gas:gas"},
	interval = 1.0,
	chance = 1,
	catch_up = true,
	action = function(pos, node)
		if not minetest.find_node_near(pos, 1, "air") then
			local torch_node = minetest.get_node(pos)
			local drops = minetest.get_node_drops(torch_node.name, "")
			for _, dropped_item in pairs(drops) do
				minetest.add_item(pos, dropped_item)
	        end
			minetest.set_node(pos, {name="mine_gas:gas"})
			minetest.sound_play(
				soundfile_cool_lava,
				{pos = pos, max_hear_distance = 16, gain = 0.1}
			)
		end
	end,
})

local tnt_boom = df_dependencies.tnt_boom

if tnt_boom then
	minetest.register_abm({
		label = "mine_gas:gas ignition",
		nodenames = {"group:torch", "group:igniter", "group:fire"}, -- checking for ignition sources because there will be fewer than there are gas nodes
		neighbors = {"mine_gas:gas"},
		interval = 1.0,
		chance = 1,
		catch_up = true,
		action = function(pos, node)
			if minetest.find_node_near(pos, 1, "air") then
				tnt_boom(pos, {radius=1, damage_radius=6})
				-- One in a hundred explosions will spawn a gas wisp
				if math.random() < 0.01 then
					minetest.set_node(pos, {name="mine_gas:gas_wisp"})
				end
			end
		end,
	})
end

local orthogonal = {
	{x=0,y=0,z=1},
	{x=0,y=1,z=0},
	{x=1,y=0,z=0},
	{x=0,y=0,z=-1},
	{x=0,y=-1,z=0},
	{x=-1,y=0,z=0},
}

local stone_with_coal = df_dependencies.node_name_stone_with_coal
minetest.register_lbm({
    label = "shut down gas seeps near lava",
    name = "mine_gas:shut_down_lava_adjacent",
    nodenames = {"mine_gas:gas_seep"},
    run_at_every_load = true,
    action = function(pos, node)
		minetest.after(math.random()*60, function()
			if minetest.find_node_near(pos, 30, "group:lava") then
				minetest.set_node(pos, {name=stone_with_coal})
			end
		end)
	end,
})

minetest.register_abm({
	label = "mine_gas:gas seep",
	nodenames = {"mine_gas:gas_seep"},
	neighbors = {"air"},
	interval = 1.0,
	chance = 1,
	catch_up = true,
	action = function(pos, node)
		local target_pos = vector.add(pos,orthogonal[math.random(1,6)])
		if minetest.get_node(target_pos).name == "air" then
			minetest.swap_node(target_pos, {name="mine_gas:gas"})
			if math.random() < 0.5 then
				minetest.sound_play(
					"mine_gas_seep_hiss",
					{pos = pos, max_hear_distance = 8, gain = 0.05}
				)
			end
		end
	end,
})


code:
# textdomain: mine_gas

### init.lua ###

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Gas Seep=Gassickern

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Gas is highly hazardous. Heavier than air, it pools in deep caverns and asphyxiates the unwary.=Gas ist sehr gefährlich. Schwerer als Luft, sammelt es sich in tiefen Höhlen und erstickt die Unachtsamen.

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Gaseous hydrocarbons formed from the detritus of long dead plants and animals processed by heat and pressure deep within the earth.=Gasförmige Kohlenwasserstoffe, die aus dem Abfall langer toter Pflanzen und Tiere entstehen, die durch Hitze und Druck tief in der Erde verarbeitet werden.

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Mine Gas=Minengas
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Mining out such a deposit seals the crack.=Durch den Abbau einer solchen Ablagerung wird der Riss versiegelt.

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Some coal deposits have cracks that seep a steady flow of mine gas.=Einige Kohlevorkommen weisen Risse auf, die einen stetigen Fluss von Minengas versickern lassen.

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
When exposed to air and an ignition source it can produce a deadly explosion.=Wenn es Luft und einer Zündquelle ausgesetzt wird, kann es zu einer tödlichen Explosion kommen.

### wisp.lua ###

Gas Wisp=

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Gas Wisps have no known use, and fortunately they are not hostile or very dangerous.=Gas-Wisps haben keine bekannte Verwendung und sind glücklicherweise nicht feindlich oder sehr gefährlich.

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Mysterious blue flames may occasionally be found flickering on the edges of oil lakes, burning without oxygen and seeming to maintain their size as they drift about through the unbreathable atmosphere.=Gelegentlich flackern mysteriöse blaue Flammen an den Rändern von Ölseen, brennen ohne Sauerstoff und scheinen ihre Größe beizubehalten, wenn sie durch die unatmbare Atmosphäre treiben.


code:
# textdomain: mine_gas

### init.lua ###

Gas Seep=

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Gas is highly hazardous. Heavier than air, it pools in deep caverns and asphyxiates the unwary.=Il gas è altamente pericoloso. Più pesante dell'aria, si accumula in profonde caverne e asfissia gli incauti.

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Gaseous hydrocarbons formed from the detritus of long dead plants and animals processed by heat and pressure deep within the earth.=Idrocarburi gassosi formati dai detriti di piante e animali morti da tempo lavorati dal calore e dalla pressione nelle profondità della terra.

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Mine Gas=Il mio gas
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Mining out such a deposit seals the crack.=L'estrazione di un tale deposito sigilla la fessura.

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Some coal deposits have cracks that seep a steady flow of mine gas.=Alcuni depositi di carbone hanno crepe che filtrano un flusso costante di gas di miniera.

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
When exposed to air and an ignition source it can produce a deadly explosion.=Se esposto all'aria e ad una fonte di ignizione può produrre un'esplosione mortale.

### wisp.lua ###

Gas Wisp=

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Gas Wisps have no known use, and fortunately they are not hostile or very dangerous.=I Gas Wisps non hanno un uso noto e fortunatamente non sono ostili o molto pericolosi.

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Mysterious blue flames may occasionally be found flickering on the edges of oil lakes, burning without oxygen and seeming to maintain their size as they drift about through the unbreathable atmosphere.=Occasionalmente si possono trovare misteriose fiamme blu tremolanti sui bordi dei laghi petroliferi, che bruciano senza ossigeno e sembrano mantenere le loro dimensioni mentre vagano nell'atmosfera irrespirabile.


code:
# textdomain: mine_gas

### init.lua ###

Gas Seep=

Gas is highly hazardous. Heavier than air, it pools in deep caverns and asphyxiates the unwary.=

Gaseous hydrocarbons formed from the detritus of long dead plants and animals processed by heat and pressure deep within the earth.=

Mine Gas=
Mining out such a deposit seals the crack.=

Some coal deposits have cracks that seep a steady flow of mine gas.=

When exposed to air and an ignition source it can produce a deadly explosion.=

### wisp.lua ###

Gas Wisp=

Gas Wisps have no known use, and fortunately they are not hostile or very dangerous.=

Mysterious blue flames may occasionally be found flickering on the edges of oil lakes, burning without oxygen and seeming to maintain their size as they drift about through the unbreathable atmosphere.=


code:
name = mine_gas
description = An explosive, asphyxiating heavier-than-air gas to add hazards to caves
depends = df_dependencies
optional_depends = tnt, doc


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

local wisp_desc
local wisp_usage

if minetest.get_modpath("doc") then
	wisp_desc = S("Mysterious blue flames may occasionally be found flickering on the edges of oil lakes, burning without oxygen and seeming to maintain their size as they drift about through the unbreathable atmosphere.")
	wisp_usage = S("Gas Wisps have no known use, and fortunately they are not hostile or very dangerous.")
end

minetest.register_node("mine_gas:gas_wisp", {
	description = S("Gas Wisp"),
	_doc_items_longdesc = wisp_desc,
	_doc_items_usagehelp = wisp_usage,
	walkable = false,
	is_ground_content = false,
	diggable = false,
	buildable_to = true,
	drawtype = "plantlike",
	waving = 1,
	drowning = 1,
	damage_per_second = 4,
	post_effect_color = {a = 20, r = 20, g = 20, b = 250},
	tiles = {{name = "mine_gas_wisp.png", animation =
		{
			type = "vertical_frames",
			aspect_w = 16,
			aspect_h = 16,
			length = 1.5,
		},
	}},
	light_source = 10,
	use_texture_alpha = "clip",
	groups = {not_in_creative_inventory=0},
	paramtype = "light",
	paramtype2 = "meshoptions",
	place_param2 = 0,
	-- * bit 4 (0x10, decimal 16) - Makes the plant mesh 1.4x larger
	inventory_image = "mine_gas_wisp_inventory.png",
	drop = {},
	sunlight_propagates = true,
	on_blast = function() end, -- unaffected by explosions
	_mcl_blast_resistance = 3600000,
	_mcl_hardness = 0,
})

minetest.register_abm({
    label = "mine_gas:gas wisp movement",
    nodenames = {"mine_gas:gas_wisp"},
    neighbors = {"mine_gas:gas"},
    interval = 1.0,
    chance = 10,
    catch_up = true,
    action = function(pos, node)
		local next_pos = {x=pos.x+math.random(1,3)-2, y=pos.y+math.random(1,3)-2, z=pos.z+math.random(1,3)-2}
		local next_node = minetest.get_node(next_pos)
		if next_node.name == "mine_gas:gas" then
			minetest.swap_node(next_pos, node)
			minetest.swap_node(pos, next_node)
		end
		pos = next_pos

	end,
})

minetest.register_abm({
    label = "mine_gas:gas wisp extinguish",
    nodenames = {"mine_gas:gas_wisp"},
    neighbors = {"air"},
    interval = 10,
    chance = 1,
    catch_up = true,
    action = function(pos, node)
		for y = -1, 1 do
			for x = -1, 1 do
				for z = -1, 1 do
					local node_name = minetest.get_node({x=pos.x+x, y=pos.y+y, z=pos.z+z}).name
					if node_name == "mine_gas:gas" or minetest.get_item_group(node_name, "flammable") > 0 then
						-- there's gas or a flammable adjacent, don't extinguish
						return
					end
				end
			end
		end
		-- no gas adjacent, extinguish
		minetest.set_node(pos, {name="air"})
	end,
})


code:
df_farming:cave_straw 173 186 188
df_farming:cave_wheat_1 154 200 200
df_farming:cave_wheat_2 164 200 200
df_farming:cave_wheat_3 179 204 204
df_farming:cave_wheat_4 187 204 204
df_farming:cave_wheat_5 188 200 200
df_farming:cave_wheat_6 187 193 193
df_farming:cave_wheat_7 196 196 196
df_farming:cave_wheat_8 207 207 207
df_farming:cave_wheat_seed 112 205 205
df_farming:cavern_fungi 169 144 184
df_farming:dead_fungus 135 117 104
df_farming:dimple_cup_1 56 48 154
df_farming:dimple_cup_2 78 72 151
df_farming:dimple_cup_3 79 73 154
df_farming:dimple_cup_4 74 67 156
df_farming:dimple_cup_seed 45 36 152
df_farming:dwarven_syrup_flowing 175 124 79
df_farming:dwarven_syrup_source 175 124 79
df_farming:pig_tail_1 177 177 177
df_farming:pig_tail_2 180 180 180
df_farming:pig_tail_3 181 181 181
df_farming:pig_tail_4 183 183 183
df_farming:pig_tail_5 183 183 183
df_farming:pig_tail_6 184 184 184
df_farming:pig_tail_7 184 184 184
df_farming:pig_tail_8 185 185 185
df_farming:pig_tail_seed 157 157 157
df_farming:pig_tail_trampled 148 147 147
df_farming:plump_helmet_1 127 0 127
df_farming:plump_helmet_2 127 0 127
df_farming:plump_helmet_3 127 0 127
df_farming:plump_helmet_4 127 0 127
df_farming:plump_helmet_4_picked 127 0 127
df_farming:plump_helmet_spawn 127 0 127
df_farming:quarry_bush_1 68 68 68
df_farming:quarry_bush_2 91 91 91
df_farming:quarry_bush_3 77 77 77
df_farming:quarry_bush_4 90 90 90
df_farming:quarry_bush_5 82 82 82
df_farming:quarry_bush_seed 187 124 80
df_farming:sweet_pod_1 212 212 212
df_farming:sweet_pod_2 215 215 215
df_farming:sweet_pod_3 215 215 215
df_farming:sweet_pod_4 218 218 218
df_farming:sweet_pod_5 216 190 190
df_farming:sweet_pod_6 220 165 165
df_farming:sweet_pod_seed 255 167 167
df_farming:wheat_trampled 209 216 220
df_mapitems:big_crystal 202 56 52
df_mapitems:big_crystal_30 202 56 52
df_mapitems:big_crystal_30_45 202 56 52
df_mapitems:castle_coral 126 117 45
df_mapitems:castle_coral_skeleton 235 230 215
df_mapitems:cave_coral_1 101 73 70
df_mapitems:cave_coral_2 101 73 70
df_mapitems:cave_coral_3 101 73 70
df_mapitems:cave_pearls 233 221 212
df_mapitems:cobble_with_floor_fungus 161 159 146
df_mapitems:cobble_with_floor_fungus_fine 147 146 138
df_mapitems:dirt_with_cave_moss 88 177 171
df_mapitems:dirt_with_cave_moss_trampled 90 176 170
df_mapitems:dirt_with_pebble_fungus 162 157 152
df_mapitems:dirt_with_pebble_fungus_trampled 161 156 151
df_mapitems:dirt_with_stillworm 107 107 89
df_mapitems:dirt_with_stillworm_trampled 109 107 91
df_mapitems:dry_flowstone 129 129 129
df_mapitems:dry_stal_1 129 129 129
df_mapitems:dry_stal_2 129 129 129
df_mapitems:dry_stal_3 129 129 129
df_mapitems:dry_stal_4 129 129 129
df_mapitems:glow_mese 143 136 2
df_mapitems:glow_ruby_ore 164 74 82
df_mapitems:glow_worm 183 255 255
df_mapitems:ice_with_hoar_moss 111 121 174
df_mapitems:icicle_1 164 164 252
df_mapitems:icicle_2 164 164 252
df_mapitems:icicle_3 164 164 252
df_mapitems:icicle_4 164 164 252
df_mapitems:med_crystal 202 56 52
df_mapitems:med_crystal_30 202 56 52
df_mapitems:med_crystal_30_45 202 56 52
df_mapitems:mese_crystal 143 136 2
df_mapitems:rock_rot 92 82 74
df_mapitems:salt_crystal 248 201 201
df_mapitems:salty_cobble 169 158 158
df_mapitems:sand_scum 109 110 78
df_mapitems:sand_scum_trampled 110 111 81
df_mapitems:snareweed 82 68 35
df_mapitems:spongestone 70 47 28
df_mapitems:spongestone_trampled 74 53 37
df_mapitems:veinstone 120 99 95
df_mapitems:wet_flowstone 153 153 153
df_mapitems:wet_stal_1 153 153 153
df_mapitems:wet_stal_2 153 153 153
df_mapitems:wet_stal_3 153 153 153
df_mapitems:wet_stal_4 153 153 153
df_primordial_items:dirt_with_jungle_grass 15 32 8
df_primordial_items:dirt_with_jungle_grass_trampled 28 40 25
df_primordial_items:dirt_with_mycelium 132 127 157
df_primordial_items:dirt_with_mycelium_trampled 132 127 156
df_primordial_items:fern_1 6 52 42
df_primordial_items:fern_2 1 51 24
df_primordial_items:fern_sapling 8 54 22
df_primordial_items:fern_wood 115 89 68
df_primordial_items:fungal_grass_1 118 121 180
df_primordial_items:fungal_grass_2 116 118 179
df_primordial_items:giant_fern_leaves 16 52 29
df_primordial_items:giant_fern_tree 59 107 64
df_primordial_items:giant_fern_tree_slant_bottom 59 107 64
df_primordial_items:giant_fern_tree_slant_full 59 107 64
df_primordial_items:giant_fern_tree_slant_top 59 107 64
df_primordial_items:giant_hypha 221 206 230
df_primordial_items:giant_hypha_apical_mapgen 221 206 230
df_primordial_items:giant_hypha_apical_meristem 221 206 230
df_primordial_items:giant_hypha_root 221 206 230
df_primordial_items:glow_orb 176 172 223
df_primordial_items:glow_orb_hanging 174 173 210
df_primordial_items:glow_orb_stalks 135 136 196
df_primordial_items:glow_plant_1 168 101 171
df_primordial_items:glow_plant_2 50 91 101
df_primordial_items:glow_plant_3 54 108 134
df_primordial_items:glow_pods 209 128 186
df_primordial_items:glownode 174 175 199
df_primordial_items:glownode_stalk 146 147 198
df_primordial_items:jungle_grass_1 45 68 32
df_primordial_items:jungle_grass_2 42 65 30
df_primordial_items:jungle_grass_3 39 62 29
df_primordial_items:jungle_ivy 8 53 15
df_primordial_items:jungle_leaves 1 45 16
df_primordial_items:jungle_leaves_glowing 0 87 90
df_primordial_items:jungle_mushroom_1 92 76 67
df_primordial_items:jungle_mushroom_2 101 85 76
df_primordial_items:jungle_mushroom_cap_1 176 137 105
df_primordial_items:jungle_mushroom_cap_2 72 51 34
df_primordial_items:jungle_mushroom_sapling 101 85 76
df_primordial_items:jungle_mushroom_trunk 165 140 125
df_primordial_items:jungle_roots_1 33 25 16
df_primordial_items:jungle_roots_2 36 18 9
df_primordial_items:jungle_thorns 28 22 17
df_primordial_items:jungle_tree 144 107 73
df_primordial_items:jungle_tree_glowing 144 107 73
df_primordial_items:jungle_tree_mossy 144 107 73
df_primordial_items:jungletree_sapling 27 60 80
df_primordial_items:mush_sapling 128 123 151
df_primordial_items:mushroom_cap 47 35 66
df_primordial_items:mushroom_gills 163 167 192
df_primordial_items:mushroom_gills_glowing 197 180 249
df_primordial_items:mushroom_trunk 161 171 196
df_primordial_items:packed_roots 21 11 6
df_primordial_items:plant_matter 27 45 21
df_primordial_items:plant_matter_trampled 36 50 32
df_primordial_items:primordial_mush_cap_wood 118 117 140
df_primordial_items:primordial_mush_trunk_wood 169 171 193
df_trees:black_cap 58 58 58
df_trees:black_cap_gills 0 0 0
df_trees:black_cap_sapling 66 69 76
df_trees:black_cap_stem 58 58 58
df_trees:black_cap_wood 47 47 47
df_trees:blood_thorn 143 23 31
df_trees:blood_thorn_dead 143 23 31
df_trees:blood_thorn_spike 125 2 34
df_trees:blood_thorn_spike_dead 125 2 34
df_trees:blood_thorn_wood 74 12 25
df_trees:fungiwood 225 211 117
df_trees:fungiwood_sapling 228 191 86
df_trees:fungiwood_shelf 225 211 117
df_trees:fungiwood_wood 193 173 84
df_trees:glowing_bottle_cyan 214 215 223
df_trees:glowing_bottle_golden 214 215 223
df_trees:glowing_bottle_green 214 215 223
df_trees:glowing_bottle_red 214 215 223
df_trees:goblin_cap 197 68 17
df_trees:goblin_cap_gills 239 169 169
df_trees:goblin_cap_sapling 196 175 170
df_trees:goblin_cap_stem 216 207 149
df_trees:goblin_cap_stem_wood 215 205 165
df_trees:goblin_cap_wood 215 68 19
df_trees:nether_cap 10 29 101
df_trees:nether_cap_gills 3 120 144
df_trees:nether_cap_sapling 3 88 231
df_trees:nether_cap_stem 58 51 142
df_trees:nether_cap_wood 17 17 95
df_trees:spindlestem_cap_cyan 179 230 215
df_trees:spindlestem_cap_golden 235 230 164
df_trees:spindlestem_cap_green 179 230 164
df_trees:spindlestem_cap_red 235 176 164
df_trees:spindlestem_cap_white 235 230 215
df_trees:spindlestem_seedling 235 230 215
df_trees:spindlestem_stem 235 230 215
df_trees:spore_tree 218 198 168
df_trees:spore_tree_fruiting_body 172 164 158
df_trees:spore_tree_hyphae 172 164 158
df_trees:spore_tree_ladder 191 186 175
df_trees:spore_tree_sapling 163 152 146
df_trees:spore_tree_wood 210 199 170
df_trees:torchspine_1 11 11 11
df_trees:torchspine_1_lit 196 153 69
df_trees:torchspine_2 53 53 53
df_trees:torchspine_3 94 95 95
df_trees:torchspine_4 137 137 137
df_trees:torchspine_ember 11 11 11
df_trees:tower_cap 235 230 215
df_trees:tower_cap_gills 227 226 178
df_trees:tower_cap_sapling 188 188 178
df_trees:tower_cap_stem 235 230 215
df_trees:tower_cap_wood 236 231 221
df_trees:tunnel_tube 201 0 140
df_trees:tunnel_tube_fruiting_body 201 0 140
df_trees:tunnel_tube_fruiting_body_burning 201 0 140
df_trees:tunnel_tube_sapling 182 42 177
df_trees:tunnel_tube_slant_bottom 201 0 140
df_trees:tunnel_tube_slant_full 201 0 140
df_trees:tunnel_tube_slant_top 201 0 140
df_trees:tunnel_tube_wood 157 20 98
df_underworld_items:digging_seal 231 167 236
df_underworld_items:glow_amethyst 105 42 125
df_underworld_items:glowstone 245 212 156
df_underworld_items:hunter_statue 50 44 38
df_underworld_items:inscription_block 52 45 39
df_underworld_items:pit_plasma 230 164 235
df_underworld_items:pit_plasma_flowing 231 167 236
df_underworld_items:puzzle_seal 52 45 39
df_underworld_items:slade 50 44 38
df_underworld_items:slade_block 52 45 39
df_underworld_items:slade_brick 50 45 40
df_underworld_items:slade_capstone 52 45 39
df_underworld_items:slade_sand 43 39 36
df_underworld_items:slade_seal 47 42 36
df_underworld_items:slade_wall 50 45 40
dynamic_liquid:clay 183 183 183
dynamic_liquid:spring 68 65 54
ice_sprites:ice_sprite 184 255 255
ice_sprites:ice_sprite_bottle 188 194 206
magma_conduits:glow_obsidian 49 5 5
magma_conduits:hot_cobble 79 52 49
magma_conduits:lava_flowing 203 108 20
magma_conduits:lava_source 210 116 23
magma_conduits:stone 97 94 93
mine_gas:gas_seep 89 86 85
mine_gas:gas_wisp 0 138 255
oil:oil_flowing 40 40 40
oil:oil_source 40 40 40
ropes:copper1rope_block 55 45 29
ropes:copper2rope_block 58 48 30
ropes:copper3rope_block 59 49 31
ropes:copper4rope_block 60 49 31
ropes:copper5rope_block 60 49 32
ropes:ladder_steel 177 177 177
ropes:ladder_wood 142 111 64
ropes:rope 62 51 32
ropes:rope_bottom 62 51 32
ropes:ropeladder 88 69 41
ropes:ropeladder_bottom 88 70 40
ropes:ropeladder_falling 88 69 41
ropes:ropeladder_top 91 73 47
ropes:steel1rope_block 55 45 29
ropes:steel2rope_block 58 48 30
ropes:steel3rope_block 59 49 31
ropes:steel4rope_block 60 49 31
ropes:steel5rope_block 60 49 32
ropes:steel6rope_block 58 48 30
ropes:steel7rope_block 59 49 31
ropes:steel8rope_block 60 49 31
ropes:steel9rope_block 60 49 32
ropes:wood1rope_block 55 45 29
ropes:wood2rope_block 58 48 30
ropes:wood_bridge 142 111 64
stairs:slab_black_cap_wood 47 47 47
stairs:slab_blood_thorn_wood 74 12 25
stairs:slab_fungiwood_wood 193 173 84
stairs:slab_goblin_cap_stem_wood 215 205 165
stairs:slab_goblin_cap_wood 215 68 19
stairs:slab_nether_cap_wood 17 17 95
stairs:slab_slade_block 74 39 39
stairs:slab_slade_brick 68 40 40
stairs:slab_spore_tree_wood 210 199 170
stairs:slab_tower_cap_wood 236 231 221
stairs:slab_tunnel_tube_wood 157 20 98
stairs:stair_black_cap_wood 47 47 47
stairs:stair_blood_thorn_wood 74 12 25
stairs:stair_fungiwood_wood 193 173 84
stairs:stair_goblin_cap_stem_wood 215 205 165
stairs:stair_goblin_cap_wood 215 68 19
stairs:stair_inner_black_cap_wood 47 47 47
stairs:stair_inner_blood_thorn_wood 74 12 25
stairs:stair_inner_fungiwood_wood 193 173 84
stairs:stair_inner_goblin_cap_stem_wood 215 205 165
stairs:stair_inner_goblin_cap_wood 215 68 19
stairs:stair_inner_nether_cap_wood 17 17 95
stairs:stair_inner_slade_block 74 39 39
stairs:stair_inner_slade_brick 68 40 40
stairs:stair_inner_spore_tree_wood 210 199 170
stairs:stair_inner_tower_cap_wood 236 231 221
stairs:stair_inner_tunnel_tube_wood 157 20 98
stairs:stair_nether_cap_wood 17 17 95
stairs:stair_outer_black_cap_wood 47 47 47
stairs:stair_outer_blood_thorn_wood 74 12 25
stairs:stair_outer_fungiwood_wood 193 173 84
stairs:stair_outer_goblin_cap_stem_wood 215 205 165
stairs:stair_outer_goblin_cap_wood 215 68 19
stairs:stair_outer_nether_cap_wood 17 17 95
stairs:stair_outer_slade_block 74 39 39
stairs:stair_outer_slade_brick 68 40 40
stairs:stair_outer_spore_tree_wood 210 199 170
stairs:stair_outer_tower_cap_wood 236 231 221
stairs:stair_outer_tunnel_tube_wood 157 20 98
stairs:stair_slade_block 74 39 39
stairs:stair_slade_brick 68 40 40
stairs:stair_spore_tree_wood 210 199 170
stairs:stair_tower_cap_wood 236 231 221
stairs:stair_tunnel_tube_wood 157 20 98


code:
name = dfcaverns
description = Adds vast underground caverns in the style of Dwarf Fortress, complete with underground flora in diverse biomes.


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

local oil_desc
local oil_usage

if minetest.get_modpath("doc") then
	oil_desc = S("Liquid hydrocarbons formed from the detritus of long dead plants and animals processed by heat and pressure deep within the earth.")
	oil_usage = S("Buckets of oil can be used as fuel.")
end

local oil_sounds = {footstep = {name = "oil_oil_footstep", gain = 0.2}}

local water_source = df_dependencies.node_name_water_source
local water_flowing = df_dependencies.node_name_water_flowing
local bucket_empty = df_dependencies.node_name_bucket_empty

minetest.register_node("oil:oil_source", {
	description = S("Oil"),
	_doc_items_longdesc = oil_desc,
	_doc_items_usagehelp = oil_usage,
	drawtype = "liquid",
	tiles = {
		{
			name = "oil_oil_source_animated.png",
			animation = {
				type = "vertical_frames",
				aspect_w = 16,
				aspect_h = 16,
				length = 2.0,
			},
		},
	},
	special_tiles = {
		-- New-style water source material (mostly unused)
		{
			name = "oil_oil_source_animated.png",
			animation = {
				type = "vertical_frames",
				aspect_w = 16,
				aspect_h = 16,
				length = 2.0,
			},
			backface_culling = false,
		},
	},
	alpha = 255,
	paramtype = "light",
	walkable = false,
	pointable = false,
	diggable = false,
	sunlight_propagates = false,
	buildable_to = true,
	is_ground_content = false,
	drop = "",
	drowning = 1,
	liquidtype = "source",
	liquid_range = 3,
	liquid_renewable = false,
	liquid_alternative_flowing = "oil:oil_flowing",
	liquid_alternative_source = "oil:oil_source",
	liquid_viscosity = 1,
	post_effect_color = {a = 250, r = 0, g = 0, b = 0},
	groups = {liquid = 1, dig_by_piston=1},
	sounds = oil_sounds,
	_mcl_blast_resistance      = 100,
	_mcl_hardness              = -1,
})

minetest.register_node("oil:oil_flowing", {
	description = S("Flowing Oil"),
	_doc_items_longdesc = oil_desc,
	_doc_items_usagehelp = oil_usage,
	drawtype = "flowingliquid",
	tiles = {"oil_oil.png"},
	special_tiles = {
		{
			name = "oil_oil_flowing_animated.png",
			backface_culling = false,
			animation = {
				type = "vertical_frames",
				aspect_w = 16,
				aspect_h = 16,
				length = 4.0,
			},
		},
		{
			name = "oil_oil_flowing_animated.png",
			backface_culling = true,
			animation = {
				type = "vertical_frames",
				aspect_w = 16,
				aspect_h = 16,
				length = 4.0,
			},
		},
	},
	alpha = 255,
	paramtype = "light",
	paramtype2 = "flowingliquid",
	walkable = false,
	pointable = false,
	diggable = false,
	buildable_to = true,
	is_ground_content = false,
	sunlight_propagates = false,
	drop = "",
	drowning = 1,
	liquidtype = "flowing",
	liquid_range = 3,
	liquid_renewable = false,
	liquid_alternative_flowing = "oil:oil_flowing",
	liquid_alternative_source = "oil:oil_source",
	liquid_viscosity = 1,
	post_effect_color = {a = 250, r = 0, g = 0, b = 0},
	groups = {liquid = 1, not_in_creative_inventory = 1, dig_by_piston=1},
	sounds = oil_sounds,
	_mcl_blast_resistance      = 100,
	_mcl_hardness              = -1,
})

minetest.register_craft({
	type = "fuel",
	recipe = "oil:oil_source",
	burntime = 370, -- same as coalblock
})

if minetest.get_modpath("dynamic_liquid") then
	dynamic_liquid.liquid_abm("oil:oil_source", "oil:oil_flowing", 2)

	-- not using default liquid_abm to prevent oil from churning around once it's on the surface of water - we want slicks to remain cohesive and static
	minetest.register_abm({
		label = "oil:oil floats",
		nodenames = {"oil:oil_source"},
		neighbors = {water_source},
		interval = 1.0,
		chance = 1,
		catch_up = true,
		action = function(pos, node)
			local next_pos = {x=pos.x, y=pos.y+1, z=pos.z}
			local next_node = minetest.get_node(next_pos)
			local above_name = next_node.name
			if above_name == water_source then
				minetest.swap_node(next_pos, {name="oil:oil_source"})
				minetest.swap_node(pos, next_node)
			else
				next_pos.y = pos.y
				local displacement
				if math.random() > 0.5 then displacement = 1 else displacement = -1 end
				if math.random() > 0.5 then
					next_pos.x = next_pos.x + displacement
				else
					next_pos.z = next_pos.z + displacement
				end
				next_node = minetest.get_node(next_pos)
				if next_node.name == water_source then
					if above_name ~= "air" then
						-- we're not on the surface, so try any lateral movement
						minetest.swap_node(next_pos, {name="oil:oil_source"})
						minetest.swap_node(pos, next_node)
					elseif math.random() < 0.1 then -- expensive and not commonly needed, so don't try this often
						--Otherwise, count neighboring oil nodes and if we're increasing the number of neighboring oil nodes by moving then do so.
						local oil_neighbors_here, here_count = minetest.find_nodes_in_area({x=pos.x-1, y=pos.y, z=pos.z-1}, {x=pos.x+1, y=pos.y, z=pos.z+1}, {"oil:oil_source"})
						local oil_neighbors_there, there_count = minetest.find_nodes_in_area({x=next_pos.x-1, y=next_pos.y, z=next_pos.z-1}, {x=next_pos.x+1, y=next_pos.y, z=next_pos.z+1}, {"oil:oil_source"})
						if there_count["oil:oil_source"] >= here_count["oil:oil_source"] then
							minetest.swap_node(next_pos, {name="oil:oil_source"})
							minetest.swap_node(pos, next_node)
						end
					end
				end
			end
		end,
	})

	-- If oil is wandering around on top of a layer of water that isn't full, drop it down
	-- into the water layer. This helps drive the system toward a more static state with a flat-looking surface.
	minetest.register_abm({
		label = "oil:oil settles",
		nodenames = {"oil:oil_source"},
		neighbors = {water_flowing},
		interval = 1.0,
		chance = 1,
		catch_up = true,
		action = function(pos, node)
			local next_pos = {x=pos.x, y=pos.y-1, z=pos.z}
			local next_node = minetest.get_node(next_pos)
			if next_node.name == water_flowing then
				minetest.swap_node(next_pos, {name="oil:oil_source"})
				minetest.swap_node(pos, next_node)
			end
		end,
	})

end

if df_dependencies.bucket_register_liquid then
	df_dependencies.bucket_register_liquid(
		"oil:oil_source",
		"oil:oil_flowing",
		"oil:oil_bucket",
		"oil_bucket.png",
		S("Oil Bucket")
	)

	minetest.register_craft({
		type = "fuel",
		recipe = "oil:oil_bucket",
		burntime = 370, -- same as coalblock
		replacements = {{"oil:oil_bucket", bucket_empty}},
	})

	if minetest.get_modpath("basic_materials") then
		minetest.register_craft({
			type = "cooking",
			output = "basic_materials:paraffin",
			recipe = "oil:oil_bucket",
			cooktime = 5,
			replacements = {{"oil:oil_bucket", bucket_empty}},
		})
	end
end


code:
# textdomain: oil

### init.lua ###

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Buckets of oil can be used as fuel.=Eimer Öl können als Kraftstoff verwendet werden.
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Flowing Oil=Fließendes Öl

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Liquid hydrocarbons formed from the detritus of long dead plants and animals processed by heat and pressure deep within the earth.=Flüssige Kohlenwasserstoffe, die aus dem Abfall langer toter Pflanzen und Tiere entstehen, die durch Hitze und Druck tief in der Erde verarbeitet werden.

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Oil=Öl
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Oil Bucket=Öleimer


code:
# textdomain: oil

### init.lua ###

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Buckets of oil can be used as fuel.=I secchi d'olio possono essere usati come carburante.
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Flowing Oil=Olio che scorre

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Liquid hydrocarbons formed from the detritus of long dead plants and animals processed by heat and pressure deep within the earth.=Idrocarburi liquidi formati dai detriti di piante e animali morti da tempo lavorati dal calore e dalla pressione nelle profondità della terra.

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Oil=Olio
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Oil Bucket=Secchio d'olio


code:
# textdomain: oil

### init.lua ###

Buckets of oil can be used as fuel.=
Flowing Oil=

Liquid hydrocarbons formed from the detritus of long dead plants and animals processed by heat and pressure deep within the earth.=

Oil=
Oil Bucket=


code:
name = oil
description = Oil liquid type
depends = df_dependencies
optional_depends = dynamic_liquid, doc, basic_materials


code:
A pit cave, shaft cave or vertical cave—or often simply called a pit (in the US) or pot (in the UK); jama in South Slavic languages scientific and colloquial vocabulary (borrowed since early research in the Western Balkan Dinaric Alpine karst)—is a type of cave which contains one or more significant vertical shafts rather than being predominantly a conventional horizontal cave passage. Pit caves typically form in limestone as a result of long-term erosion by water.

In the real world, the deepest known vertical drop in a cave is 603m in Vrtoglavica Cave in Slovenia. This mod adds pits of varying depth, with some under the default settings being up to 2500m deep. They are widely scattered and not all of them breach the surface, so they are a rare find, but with the right tools a pit cave can give access to a huge swath of underground terrain.

## Settings and commands

The following settings are available for configuring pit cave generation:

    pit_caves_min_bottom (Lower limit of bottoms of pits) int -2500
    pit_caves_max_bottom (Upper limit of bottoms of pits) int -500
    pit_caves_min_top (Lower limit of tops of pits) int -100
    pit_caves_max_top (Upper limit of tops of pits) int 100
    pit_caves_mapblock_spacing (Average number of map blocks between pits) int 16
    pit_caves_seal_ocean (Seal off pits that are under ocean water) bool true

The pit_caves_seal_ocean setting isn't perfect, some map generation scenarios can result in a gap through which water can flow. But it's better than having drain holes everywhere.

Users with the "server" privilege can use the ``/find_pit_caves`` command, which will list the locations of nearby pit caves.


code:
pit_caves = {}

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

local min_depth = tonumber(minetest.settings:get("pit_caves_min_bottom") or -2500)
local max_depth = tonumber(minetest.settings:get("pit_caves_max_bottom") or -500)
local min_top = tonumber(minetest.settings:get("pit_caves_min_top") or -100)
local max_top = tonumber(minetest.settings:get("pit_caves_max_top") or 100)

local seal_ocean = minetest.settings:get_bool("pit_caves_seal_ocean", true)

assert(min_depth < max_depth, "pit_caves_min_bottom is above pit_caves_max_bottom")
assert(min_top < max_top, "pit_caves_min_top is above pit_caves_max_top")
assert(max_depth < min_top, "pit_caves_max_bottom is above pit_caves_min_top")

local pit_radius = 3 -- approximate minimum radius of pit - noise adds a lot to this

local region_mapblocks = tonumber(minetest.settings:get("pit_caves_mapblock_spacing") or 16)
local mapgen_chunksize = tonumber(minetest.get_mapgen_setting("chunksize"))
local pit_region_size = region_mapblocks * mapgen_chunksize * 16

local c_air = minetest.get_content_id("air")
local c_gravel = c_air
local water_node
if minetest.get_modpath("default") then
	c_gravel = minetest.get_content_id("default:gravel")
	if seal_ocean then
		water_node = "default:water_source"
	end
end

local log_location
if minetest.get_modpath("mapgen_helper") and mapgen_helper.log_location_enabled then
	log_location = mapgen_helper.log_first_location
end

local ignore
if minetest.get_modpath("chasms") then
	-- the chasms mod already sets up a method to allow chasms to avoid overwriting stalactites and whatnot,
	-- hijack that.
	ignore = chasms.ignore_content_id
end

local water_level = tonumber(minetest.get_mapgen_setting("water_level"))
local mapgen_seed = tonumber(minetest.get_mapgen_setting("seed")) % 2^16

local scatter_2d = function(min_xz, gridscale, border_width)
	local bordered_scale = gridscale - 2 * border_width
	local point = {}
	point.x = math.floor(math.random() * bordered_scale + min_xz.x + border_width)
	point.y = 0
	point.z = math.floor(math.random() * bordered_scale + min_xz.z + border_width)
	return point
end

-- For some reason, map chunks generate with -32, -32, -32 as the "origin" minp. To make the large-scale grid align with map chunks it needs to be offset like this.
local get_corner = function(pos)
	return {x = math.floor((pos.x+32) / pit_region_size) * pit_region_size - 32, z = math.floor((pos.z+32) / pit_region_size) * pit_region_size - 32}
end

local get_pit = function(pos)
	local corner_xz = get_corner(pos)
	local next_seed = math.random(1, 1000000000)
	math.randomseed(corner_xz.x + corner_xz.z * 2 ^ 8 + mapgen_seed + 1)

	local location = scatter_2d(corner_xz, pit_region_size, 0)
	local depth = math.floor(math.random() * (max_depth - min_depth) + min_depth)
	local top = math.floor(math.random() * (max_top - min_top) + min_top)

	math.randomseed(next_seed)
	return {location = location, depth = depth, top = top}
end

pit_caves.get_nearest_pit = get_pit

local perlin_params = {
	offset = 0,
	scale = 1,
	spread = {x=30, y=30, z=30},
	seed = 45011,
	octaves = 3,
	persist = 0.67
}
local data = {}

minetest.register_on_generated(function(minp, maxp, seed)
	if minp.y > max_top or maxp.y < min_depth then
		return
	end

	local pit = get_pit(minp)

	if pit == nil then
		return -- no pit in this map region
	end

	local location_x = pit.location.x
	local location_z = pit.location.z

	-- early out if the pit is too far away to matter
	-- The plus 20 is because the noise being added will generally be in the 0-20 range, see the "distance" calculation below
	if	location_x - 20 > maxp.x or
		location_x + 20 < minp.x or
		location_z - 20 > maxp.z or
		location_z + 20 < minp.z
	then
		return
	end

	local top = pit.top
	local depth = pit.depth

	local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
	local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax}
	vm:get_data(data)

	if water_node and minp.y <= water_level and maxp.y >= water_level-240 then
		local test_node = minetest.get_node(vector.new(location_x, water_level, location_z))
		if test_node.name == water_node then
			top = math.min(-32, top) -- we're coming up under the ocean, abort the pit.
			-- note that this does depend on the water-level map block having been generated already,
			-- which could lead to a sharp cutoff if that's not the case - if the player's coming
			-- up a pit from below into an unexplored ocean, for example. But it should still at least
			-- seal the hole before the ocean pours down into it, so that's acceptable. And I expect
			-- most of the time the surface world will be explored first before pits are discovered.
		end
	end

	local nvals_perlin = mapgen_helper.perlin3d("pit_caves:pit", emin, emax, perlin_params)

	for vi, x, y, z in area:iterp_xyz(emin, emax) do
		if not (ignore and ignore(data[vi])) then
			local distance_perturbation = (nvals_perlin[vi]+1)*10
			local distance = vector.distance({x=x, y=y, z=z}, {x=location_x, y=y, z=location_z}) - distance_perturbation
			local taper_min = top - 40

			if y < top and y > depth then
				if y > top - 40 then
					-- taper the top end
					distance = distance - ((taper_min - y)/2)
				end

				if distance < pit_radius then
					if y < depth + 20 and data[vi] ~= c_air then
						data[vi] = c_gravel
					else
						data[vi] = c_air
						if log_location then log_location("pit_cave", vector.new(x,y,z)) end
					end
				end
			end
		end
	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()
end)

----------------------------------------------------------------------------------------------
-- Debugging and sightseeing commands

function round(val, decimal)
  if (decimal) then
    return math.floor( (val * 10^decimal) + 0.5) / (10^decimal)
  else
    return math.floor(val+0.5)
  end
end

local send_pit_state = function(pos, name)
	local pit = get_pit(pos)
	if pit == nil then
		return false
	end
	local location = {x=math.floor(pit.location.x), y=pit.top, z=math.floor(pit.location.z)}
	minetest.chat_send_player(name, S("Pit at @1, bottom @2", minetest.pos_to_string(location), pit.depth))
	return true
end

local send_nearby_states = function(pos, name)
	local retval = false
	retval = send_pit_state({x=pos.x-pit_region_size, y=0, z=pos.z+pit_region_size}, name) or retval
	retval = send_pit_state({x=pos.x, y=0, z=pos.z+pit_region_size}, name) or retval
	retval = send_pit_state({x=pos.x+pit_region_size, y=0, z=pos.z+pit_region_size}, name) or retval
	retval = send_pit_state({x=pos.x-pit_region_size, y=0, z=pos.z}, name) or retval
	retval = send_pit_state(pos, name) or retval
	retval = send_pit_state({x=pos.x+pit_region_size, y=0, z=pos.z}, name) or retval
	retval = send_pit_state({x=pos.x-pit_region_size, y=0, z=pos.z-pit_region_size}, name) or retval
	retval = send_pit_state({x=pos.x, y=0, z=pos.z-pit_region_size}, name) or retval
	retval = send_pit_state({x=pos.x+pit_region_size, y=0, z=pos.z-pit_region_size}, name) or retval
	return retval
end

minetest.register_chatcommand("find_pit_caves", {
    params = "pos", -- Short parameter description
    description = S("find the pits near the player's map region, or in the map region containing pos if provided"),
    func = function(name, param)
		if minetest.check_player_privs(name, {server = true}) then
			local pos = {}
			pos.x, pos.y, pos.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
			pos.x = tonumber(pos.x)
			pos.y = tonumber(pos.y)
			pos.z = tonumber(pos.z)
			if pos.x and pos.y and pos.z then
				if not send_nearby_states(pos, name) then
					minetest.chat_send_player(name, S("No pits near @1", minetest.pos_to_string(pos)))
				end
				return true
			else
				local playerobj = minetest.get_player_by_name(name)
				pos = playerobj:get_pos()
				if not send_nearby_states(pos, name) then
					pos.x = math.floor(pos.x)
					pos.y = math.floor(pos.y)
					pos.z = math.floor(pos.z)
					minetest.chat_send_player(name, S("No pits near @1", minetest.pos_to_string(pos)))
				end
				return true
			end
		else
			return false, S("You need the server privilege to use this command.")
		end
	end,
})


code:
# textdomain: pit_caves

### init.lua ###

No pits near @1=
Pit at @1, bottom @2=
You need the server privilege to use this command.=

find the pits near the player's map region, or in the map region containing pos if provided=


code:
name = pit_caves
description = Inserts very tall "pit" caves underground
depends =
optional_depends = mapgen_helper, default, chasms


code:
pit_caves_min_bottom (Lower limit of bottoms of pits) int -2500
pit_caves_max_bottom (Upper limit of bottoms of pits) int -500
pit_caves_min_top (Lower limit of tops of pits) int -100
pit_caves_max_top (Upper limit of tops of pits) int 100
pit_caves_mapblock_spacing (Average number of map blocks between pits) int 16
pit_caves_seal_ocean (Seal off pits that are under ocean water) bool true


code:
# Auto detect text files and perform LF normalization
* text=auto

# Custom for Visual Studio
*.cs     diff=csharp

# Standard to msysgit
*.doc	 diff=astextplain
*.DOC	 diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot  diff=astextplain
*.DOT  diff=astextplain
*.pdf  diff=astextplain
*.PDF	 diff=astextplain
*.rtf	 diff=astextplain
*.RTF	 diff=astextplain


code:
# Windows image file caches
Thumbs.db
ehthumbs.db

# Folder config file
Desktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msm
*.msp

# Windows shortcuts
*.lnk

# =========================
# Operating System Files
# =========================

# OSX
# =========================

.DS_Store
.AppleDouble
.LSOverride

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk


code:
# subterrane

This mod was based off of Caverealms by HeroOfTheWinds, which was in turn based off of Subterrain by Paramat.

It is intended as a utility mod for other mods to use when creating a more interesting underground experience in Minetest, primarily through the creation of enormous underground "natural" caverns with hooks to add custom decorations. Installing this mod by itself will not do anything. This mod depends in turn on the [mapgen_helper](https://github.com/minetest-mods/mapgen_helper) mod.

The API has the following methods:

# Cavern registration

## subterrane:register_cave_layer(cave_layer_def)

cave_layer_def is a table of the form:

```
{
	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. Upper boundaries for cavern depths are most efficient when they fit the formula (x*80-32)-1 where x is an integer, that way you don't generate map blocks that straddle two cavern layers. The "-1" keeps them from intruding on the map block above.
	y_min = -- required, the lowest elevation this cave layer will be generated in. Lower boundaries are most efficient when they fit (x*80-32).
	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.
	on_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.
}
```

The column_def table is of the form:

```
{
	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.
}
```

This causes large caverns to be hollowed out during map generation. By default these caverns are just featureless cavities, but you can add extra subterrane-specific properties to biomes and the mapgen code will use them to add features of your choice using the "on_decorate" callback. The callback is passed a data table with the following information:

```
{
	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 outer surface of the column.
	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, but for warrens instead of caverns.
	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.
}
```

# Common cavern features

Subterrane has a number of functions bundled with it for generating some basic features commonly found in caverns, both realistic and fantastical.

* `subterrane.register_stalagmite_nodes(base_name, base_node_def, drop_base_name)`
	* This registers a set of four standardized stalactite/stalagmite nodes that can be used with the subterrane.stalagmite function below. "base name" is a string that forms the prefix of the names of the nodes defined, for example the coolcaves mod might use "coolcaves:crystal_stal" and the resulting nodes registered would be "coolcaves:crystal_stal_1" through "coolcaves:crystal_stal_4". "base_node_def" is a node definition table much like is used with the usual node registration function. register_stalagmite_nodes will amend or substitute properties in this definition as needed, so the simplest base_node_def might just define the textures used. "drop_base_name" is an optional string that will substitute the node drops with stalagmites created by another use of register_stalagmite_nodes, for example if you wrote
		* `subterrane.register_stalagmite_nodes("coolcaves:dry_stal", base_dry_node_def)`
		* `subterrane.register_stalagmite_nodes("coolcaves:wet_stal", base_wet_node_def, "coolcaves:dry_stal")`
	* then when the player mines a dry stalactite they'll get a dry stalactite node and if they mine a wet stalactite they'll get a corresponding dry stalactite node as the drop instead.
	* This method returns a table consisting of the content IDs for the four stalactite nodes, which can be used directly in the following methods:

* `subterrane.stalagmite(vi, area, data, param2_data, param2, height, stalagmite_id)`
* `subterrane.stalactite(vi, area, data, param2_data, param2, height, stalagmite_id)`
	* These methods can be used to create a small stalactite or stalagmite, generally no more than 5 nodes tall. Use a negative height to generate a stalactite. The parameter stalagmite_id is a table of four content IDs for the stalagmite nodes, in order from thinnest ("_1") to thickest ("_4"). The register_stalagmite_nodes method returns a table that can be used for this directly.

* `subterrane.big_stalagmite(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)`
	* Generates a very large multi-node stalagmite or stalactite three nodes in diameter (with a five-node-diameter "root").

* `subterrane.giant_mushroom(vi, area, data, stem_material, cap_material, gill_material, stem_height, cap_radius)`
	* Generates an enormous mushroom. Cap radius works well in the range of around 2-6, larger or smaller than that may look odd.

# Player spawn

## subterrane:register_cave_spawn(cave_layer_def, start_depth)

When the player spawns (or respawns due to death), this method will tell Minetest to attempt to locate a subterrane-generated cavern to place the player in. cave_layer_def is the same format as the cave definition above. Start_depth is the depth at which the game will start searching for a location to place the player. If the game doesn't find a location immediately it may wind up restarting the search for a spawn location at the top of the cave definition, so start_depth is not a guarantee that the player will start at least that deep; he could spawn anywhere within the cave layer's depth range.


code:
subterrane.defaults = {
	--y_max = Must be provided by the user
	--y_min = Must be provided by the user
	cave_threshold = 0.5,
	warren_region_threshold = 0.25,
	warren_region_variability_threshold = 0.25,
	warren_threshold = 0.25,
	boundary_blend_range = 128,
	perlin_cave = {
		offset = 0,
		scale = 1,
		spread = {x=256, y=256, z=256},
		seed = -400000000089,
		octaves = 3,
		persist = 0.67
	},
	perlin_wave = {
		offset = 0,
		scale = 1,
		spread = {x=512, y=256, z=512}, -- squashed 2:1
		seed = 59033,
		octaves = 6,
		persist = 0.63
	},
	perlin_warren_area = {
		offset = 0,
		scale = 1,
		spread = {x=1024, y=128, z=1024},
		seed = -12554445,
		octaves = 2,
		persist = 0.67
	},
	perlin_warrens = {
		offset = 0,
		scale = 1,
		spread = {x=32, y=12, z=32},
		seed = 600089,
		octaves = 3,
		persist = 0.67
	},
	--solidify_lava =
	columns = {
		maximum_radius = 10,
		minimum_radius = 4,
		node = "default:stone",
		weight = 0.25,
		maximum_count = 50,
		minimum_count = 0,
	},
	--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.
}

local recurse_defaults
recurse_defaults = function(target_table, default_table)
	for k, v in pairs(default_table) do
		if target_table[k] == nil then
			target_table[k] = v -- TODO: deep copy if v is a table.
		elseif type(target_table[k]) == "table" then
			recurse_defaults(target_table[k], v)
		end
	end
end

subterrane.set_defaults = function(cave_layer_def)
	recurse_defaults(cave_layer_def, subterrane.defaults)
end


code:
subterrane.dependencies = {}

local default_modpath = minetest.get_modpath("default")
local mcl_core_modpath = minetest.get_modpath("mcl_core")

assert(default_modpath or mcl_core_modpath, "[subterrane] This mod requires either minetest_game (default) or Mineclone2 (mcl_core) to be installed")

if default_modpath then

subterrane.dependencies.stone = "default:stone"
subterrane.dependencies.clay = "default:clay"
subterrane.dependencies.desert_stone = "default:desert_stone"
subterrane.dependencies.sandstone = "default:sandstone"
subterrane.dependencies.water = "default:water_source"
subterrane.dependencies.obsidian = "default:obsidian"

elseif mcl_core_modpath then

subterrane.dependencies.stone = "mcl_core:stone"
subterrane.dependencies.clay = "mcl_core:clay"
subterrane.dependencies.desert_stone = "mcl_core:redsandstone"
subterrane.dependencies.sandstone = "mcl_core:sandstone"
subterrane.dependencies.water = "mcl_core:water_source"
subterrane.dependencies.obsidian = "mcl_core:obsidian"

end

minetest.after(0, function() subterrane.dependencies = nil end) -- ensure these are only used during initialization, to avoid polluting the global namespace with irrelevancies


code:
local c_air = minetest.get_content_id("air")

---------------------------------------------------------------------------
-- For registering a set of stalactite/stalagmite nodes to use with the small stalactite placement function below

local x_disp = 0.125
local z_disp = 0.125

local stal_on_place = function(itemstack, placer, pointed_thing)
	local pt = pointed_thing
	-- check if pointing at a node
	if not pt then
		return itemstack
	end
	if pt.type ~= "node" then
		return itemstack
	end

	local under = minetest.get_node(pt.under)
	local above = minetest.get_node(pt.above)

	if minetest.is_protected(pt.above, placer:get_player_name()) then
		minetest.record_protection_violation(pt.above, placer:get_player_name())
		return
	end

	-- return if any of the nodes is not registered
	if not minetest.registered_nodes[under.name] or not minetest.registered_nodes[above.name] then
		return itemstack
	end
	-- check if you can replace the node above the pointed node
	if not minetest.registered_nodes[above.name].buildable_to then
		return itemstack
	end

	local new_param2
	-- check if pointing at an existing stalactite
	if minetest.get_item_group(under.name, "subterrane_stal_align") ~= 0 then
		new_param2 = under.param2
	else
		new_param2 = math.random(0,3)
	end

	-- add the node and remove 1 item from the itemstack
	minetest.add_node(pt.above, {name = itemstack:get_name(), param2 = new_param2})
	if not minetest.is_creative_enabled(placer:get_player_name()) then
		itemstack:take_item()
	end
	return itemstack
end

local stal_box_1 = {{-0.0625+x_disp, -0.5, -0.0625+z_disp, 0.0625+x_disp, 0.5, 0.0625+z_disp}}
local stal_box_2 = {{-0.125+x_disp, -0.5, -0.125+z_disp, 0.125+x_disp, 0.5, 0.125+z_disp}}
local stal_box_3 = {{-0.25+x_disp, -0.5, -0.25+z_disp, 0.25+x_disp, 0.5, 0.25+z_disp}}
local stal_box_4 = {{-0.375+x_disp, -0.5, -0.375+z_disp, 0.375+x_disp, 0.5, 0.375+z_disp}}

-- Note that a circular table reference will result in a crash, TODO: guard against that.
-- Unlikely to be needed, though - it'd take a lot of work for users to get into this bit of trouble.
local function deep_copy(table_in)
	local table_out = {}
	for index, value in pairs(table_in) do
		if type(value) == "table" then
			table_out[index] = deep_copy(value)
		else
			table_out[index] = value
		end
	end
	return table_out
end

subterrane.register_stalagmite_nodes = function(base_name, base_node_def, drop_base_name)
	base_node_def.groups = base_node_def.groups or {}
	base_node_def.groups.subterrane_stal_align = 1
	base_node_def.groups.flow_through = 1
	base_node_def.drawtype = "nodebox"
	base_node_def.paramtype = "light"
	base_node_def.paramtype2 = "facedir"
	base_node_def.node_box = {type = "fixed"}

	local def1 = deep_copy(base_node_def)
	def1.groups.fall_damage_add_percent = 100
	def1.node_box.fixed = stal_box_1
	def1.on_place = stal_on_place
	if drop_base_name then
		def1.drop = drop_base_name.."_1"
	end
	minetest.register_node(base_name.."_1", def1)

	local def2 = deep_copy(base_node_def)
	def2.groups.fall_damage_add_percent = 50
	def2.node_box.fixed = stal_box_2
	def2.on_place = stal_on_place
	if drop_base_name then
		def2.drop = drop_base_name.."_2"
	end
	minetest.register_node(base_name.."_2", def2)

	local def3 = deep_copy(base_node_def)
	def3.node_box.fixed = stal_box_3
	def3.on_place = stal_on_place
	if drop_base_name then
		def3.drop = drop_base_name.."_3"
	end
	minetest.register_node(base_name.."_3", def3)

	local def4 = deep_copy(base_node_def)
	def4.node_box.fixed = stal_box_4
	def4.on_place = stal_on_place
	if drop_base_name then
		def4.drop = drop_base_name.."_4"
	end
	minetest.register_node(base_name.."_4", def4)

	return {
		minetest.get_content_id(base_name.."_1"),
		minetest.get_content_id(base_name.."_2"),
		minetest.get_content_id(base_name.."_3"),
		minetest.get_content_id(base_name.."_4"),
	}
end

-------------------------------------------------------------------------------------------------
-- Use with stalactite nodes defined above

-- stalagmite_id is a table of the content ids of the four stalagmite sections, from _1 to _4.
function subterrane.stalagmite(vi, area, data, param2_data, param2, height, stalagmite_id)
	if height == nil then height = math.random(1,4) end
	if param2 == nil then param2 = math.random(0,3) end

	local sign, id_modifier
	if height > 0 then
		sign = 1
		id_modifier = 1 -- stalagmites are blunter than stalactites
	else
		sign = -1
		id_modifier = 0
	end

	for i = 1, math.abs(height) do
		local svi = vi + (height - i * sign) * area.ystride
		if data[svi] == c_air then -- test for air because we don't want these poking into water
			data[svi] = stalagmite_id[math.min(i+id_modifier,4)]
			param2_data[svi] = param2
		end
	end
end

function subterrane.stalactite(vi, area, data, param2_data, param2, height, stalagmite_id)
	subterrane.stalagmite(vi, area, data, param2_data, param2, -height, stalagmite_id)
end

-------------------------------------------------------------------------------------------------
-- Builds very large stalactites and stalagmites

--giant stalagmite spawner
function subterrane.big_stalagmite(vi, area, data, min_height, max_height, base_material, root_material, shaft_material)
	local pos = area:position(vi)
	local x = pos.x
	local y = pos.y
	local z = pos.z

	local top = math.random(min_height,max_height)
	for j = -2, top do --y
		for k = -3, 3 do
			for l = -3, 3 do
				if j <= 0 then
					if k*k + l*l <= 9 then
						local vi = area:index(x+k, y+j, z+l)
						if mapgen_helper.buildable_to(data[vi]) then data[vi] = base_material end
					end
				elseif j <= top/5 then
					if k*k + l*l <= 4 then
						local vi = area:index(x+k, y+j, z+l)
						data[vi] = root_material
					end
				elseif j <= top/5 * 3 then
					if k*k + l*l <= 1 then
						local vi = area:index(x+k, y+j, z+l)
						data[vi] = shaft_material
					end
				else
					local vi = area:index(x, y+j, z)
					data[vi] = shaft_material
				end
			end
		end
	end
end

--giant stalactite spawner
function subterrane.big_stalactite(vi, area, data, min_height, max_height, base_material, root_material, shaft_material)
	local pos = area:position(vi)
	local x = pos.x
	local y = pos.y
	local z = pos.z

	local bot = math.random(-max_height, -min_height) --grab a random height for the stalagmite
	for j = bot, 2 do --y
		for k = -3, 3 do
			for l = -3, 3 do
				if j >= -1 then
					if k*k + l*l <= 9 then
						local vi = area:index(x+k, y+j, z+l)
						if mapgen_helper.buildable_to(data[vi]) then data[vi] = base_material end
					end
				elseif j >= bot/5 then
					if k*k + l*l <= 4 then
						local vi = area:index(x+k, y+j, z+l)
						data[vi] = root_material
					end
				elseif j >= bot/5 * 3 then
					if k*k + l*l <= 1 then
						local vi = area:index(x+k, y+j, z+l)
						data[vi] = shaft_material
					end
				else
					local vi = area:index(x, y+j, z)
					data[vi] = shaft_material
				end
			end
		end
	end
end

----------------------------------------------------------------------------------------
-- Giant mushrooms

--function to create giant 'shrooms. Cap radius works well from about 2-6
--if ignore_bounds is true this function will place the mushroom even if it overlaps the edge of the voxel area.
function subterrane.giant_mushroom(vi, area, data, stem_material, cap_material, gill_material, stem_height, cap_radius, ignore_bounds)

	if not ignore_bounds and
		not (area:containsi(vi - cap_radius - area.zstride*cap_radius) and
		area:containsi(vi + cap_radius + stem_height*area.ystride + area.zstride*cap_radius)) then
			return false -- mushroom overlaps the bounds of the voxel area, abort.
	end

	local pos = area:position(vi)
	local x = pos.x
	local y = pos.y
	local z = pos.z

	--cap
	for k = -cap_radius, cap_radius do
	for l = -cap_radius, cap_radius do
		if k*k + l*l <= cap_radius*cap_radius then
			local vi = area:index(x+k, y+stem_height, z+l)
			if mapgen_helper.buildable_to(data[vi]) then data[vi] = cap_material end
		end
		if k*k + l*l <= (cap_radius-1)*(cap_radius-1) and (cap_radius-1) > 0 then
			local vi = area:index(x+k, y+stem_height+1, z+l)
			data[vi] = cap_material
			vi = area:index(x+k, y+stem_height, z+l)
			if data[vi] == cap_material then data[vi] = gill_material end
		end
		if k*k + l*l <= (cap_radius-2)*(cap_radius-2) and (cap_radius-2) > 0 then
			local vi = area:index(x+k, y+stem_height+2, z+l)
			if mapgen_helper.buildable_to(data[vi]) then data[vi] = cap_material end
		end
		if k*k + l*l <= (cap_radius-3)*(cap_radius-3) and (cap_radius-3) > 0 then
			local vi = area:index(x+k, y+stem_height+3, z+l)
			if mapgen_helper.buildable_to(data[vi]) then data[vi] = cap_material end
		end
	end
	end
	--stem
	for j = -2, stem_height do -- going down to -2 to ensure the stem is flush with the ground
		local vi = area:index(x, y+j, z)
		if j >= 0 or area:containsi(vi) then -- since -2 puts us below the bounds we've already tested, add a contains check here.
			if mapgen_helper.buildable_to(data[vi]) or data[vi] == gill_material then data[vi] = stem_material end
			if cap_radius > 3 then
				local ai = area:index(x, y+j, z+1)
				if mapgen_helper.buildable_to(data[ai]) or data[ai] == gill_material then data[ai] = stem_material end
				ai = area:index(x, y+j, z-1)
				if mapgen_helper.buildable_to(data[ai]) or data[ai] == gill_material then data[ai] = stem_material end
				ai = area:index(x+1, y+j, z)
				if mapgen_helper.buildable_to(data[ai]) or data[ai] == gill_material then data[ai] = stem_material end
				ai = area:index(x-1, y+j, z)
				if mapgen_helper.buildable_to(data[ai]) or data[ai] == gill_material then data[ai] = stem_material end
			end
		end
	end
	return true
end