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/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/init.lua b/pit_caves/init.lua new file mode 100644 index 0000000..36ebeef --- /dev/null +++ b/pit_caves/init.lua @@ -0,0 +1,196 @@ +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) + +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 +if minetest.get_modpath("default") then + c_gravel = minetest.get_content_id("default:gravel") +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) + + 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 + + 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 + + -- taper the top end + if y > top - 40 then + 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 + + --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("findpit", { + 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..5785838 --- /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 \ No newline at end of file diff --git a/pit_caves/settingtypes.txt b/pit_caves/settingtypes.txt new file mode 100644 index 0000000..7805edc --- /dev/null +++ b/pit_caves/settingtypes.txt @@ -0,0 +1,5 @@ +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