diff --git a/LICENSE.txt b/LICENSE.txt index 11ce895..f0b5f69 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -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 diff --git a/chasms/init.lua b/chasms/init.lua index e0d6ec6..ef59a71 100644 --- a/chasms/init.lua +++ b/chasms/init.lua @@ -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 diff --git a/df_caverns/screenshots/pit_cave.jpg b/df_caverns/screenshots/pit_cave.jpg new file mode 100644 index 0000000..428f6f7 Binary files /dev/null and b/df_caverns/screenshots/pit_cave.jpg differ diff --git a/guide.md b/guide.md index 4cb6df4..2ad4036 100644 --- a/guide.md +++ b/guide.md @@ -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) diff --git a/pit_caves/LICENSE.txt b/pit_caves/LICENSE.txt new file mode 100644 index 0000000..1b07b15 --- /dev/null +++ b/pit_caves/LICENSE.txt @@ -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. \ No newline at end of file diff --git a/pit_caves/README.md b/pit_caves/README.md new file mode 100644 index 0000000..d604410 --- /dev/null +++ b/pit_caves/README.md @@ -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. \ No newline at end of file diff --git a/pit_caves/init.lua b/pit_caves/init.lua new file mode 100644 index 0000000..3e308fb --- /dev/null +++ b/pit_caves/init.lua @@ -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, +}) diff --git a/pit_caves/locale/template.txt b/pit_caves/locale/template.txt new file mode 100644 index 0000000..654affe --- /dev/null +++ b/pit_caves/locale/template.txt @@ -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= + diff --git a/pit_caves/mod.conf b/pit_caves/mod.conf new file mode 100644 index 0000000..cfe309a --- /dev/null +++ b/pit_caves/mod.conf @@ -0,0 +1,4 @@ +name = pit_caves +description = Inserts very tall "pit" caves underground +depends = +optional_depends = mapgen_helper, default, chasms \ No newline at end of file diff --git a/pit_caves/settingtypes.txt b/pit_caves/settingtypes.txt new file mode 100644 index 0000000..63801c1 --- /dev/null +++ b/pit_caves/settingtypes.txt @@ -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 \ No newline at end of file diff --git a/subterrane b/subterrane index 1f385fc..331d08b 160000 --- a/subterrane +++ b/subterrane @@ -1 +1 @@ -Subproject commit 1f385fc84a72494dd4e09ef40717c5dcef3398ba +Subproject commit 331d08b1567cf023695832e4ab593d33720b9290