Skip to content

Commit

Permalink
Pit caves (#20)
Browse files Browse the repository at this point in the history
* A simple pit cave mod, based off of code from the volcano pipes in my magma_conduits mod

* option to seal off ocean pits

* reuse chasm's ignore list

* documentation

* rename findpits to prevent confusion with underworld glowing pits
  • Loading branch information
FaceDeer authored May 8, 2021
1 parent 79fd356 commit 289c353
Show file tree
Hide file tree
Showing 11 changed files with 294 additions and 2 deletions.
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Sounds and textures are under various licenses, see the license.txt file in the
License for Code
----------------

Copyright (C) 2018 FaceDeer
Copyright (C) 2021 FaceDeer

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
4 changes: 4 additions & 0 deletions chasms/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ chasms.register_ignore = function(node_name)
ignore[minetest.get_content_id(node_name)] = true
end

chasms.ignore_content_id = function(content_id)
return ignore[content_id]
end

local maxy = tonumber(minetest.settings:get("chasms_maxy")) or -50
local miny = tonumber(minetest.settings:get("chasms_miny")) or -2500
local falloff = tonumber(minetest.settings:get("chasms_falloff")) or 100
Expand Down
Binary file added df_caverns/screenshots/pit_cave.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ Not all vast open spaces underground are the result of aeons of erosion by water

The great extent of chasms makes them hospitable to small flying creatures, and their narrowness makes the hospitable to creatures that feed on them - giant cave spider webs can be found strung across them here and there. A dubious salvation for anyone falling from above.

# Pit caves

![Looking up from the bottom of a pit](./df_caverns/screenshots/pit_cave.jpg)

A pit cave 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. Given the vast scope of the flowstone formations found throughout the upper layers of the world's caverns, correspondingly gigantic pits can also be found. They sometimes breach the surface of the world, and at their greatest extent can have a drop over two kilometers deep. Bring plenty of rope when exploring such abysses.

# Sunless Sea

![Sunless river](./df_caverns/screenshots/sunless_river.jpg)
Expand Down
22 changes: 22 additions & 0 deletions pit_caves/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
License for Code
----------------

Copyright (C) 2021 FaceDeer

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
18 changes: 18 additions & 0 deletions pit_caves/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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.
221 changes: 221 additions & 0 deletions pit_caves/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
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 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

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
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,
})
11 changes: 11 additions & 0 deletions pit_caves/locale/template.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# 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=

4 changes: 4 additions & 0 deletions pit_caves/mod.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name = pit_caves
description = Inserts very tall "pit" caves underground
depends =
optional_depends = mapgen_helper, default, chasms
6 changes: 6 additions & 0 deletions pit_caves/settingtypes.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
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
2 changes: 1 addition & 1 deletion subterrane
Submodule subterrane updated 3 files
+28 −0 dependencies.lua
+12 −12 init.lua
+2 −1 mod.conf

0 comments on commit 289c353

Please sign in to comment.