mirror of
https://github.com/FaceDeer/dfcaverns.git
synced 2024-12-24 18:00:36 +01:00
Chasms (#19)
* initial chasms mod * tweak default chasm settings * prevent chasms from breaching oil and magma seas, make veinstone actually do something * overgenerate caverns to eliminate floating stalactites * make veinstone punchable instead of right-clickable * ensure dfcaverns get carved before chasms this has an unfortunate tradeoff. Chasms will no longer have floating giant columns in them, but will also no longer have smaller stalactites and stalagmites. Also will carve chasms through lake water. Not sure if this is ideal. * add rare big webs to the chasms, to give them a unique feature * reverse the dependencies for df_caverns and chasms, let chasms go first. * fix web generator * add webs to level 3 tunnels, fix sunless sea chasms * fix up tunnel webs * make webs snappy * make webs slightly more prevalent * add chasms to the guide * final touch-ups before merging * allow anchoring against unloaded blocks
This commit is contained in:
parent
0acb3ab09b
commit
9b7e71c675
@ -36,7 +36,7 @@ Some of the other cave decorations provide dim bioluminescent lighting in some c
|
||||
|
||||
The "[doc](https://forum.minetest.net/viewtopic.php?f=9&t=15912&p=240152)" mod is supported to provide in-game documentation for all of the new items and nodes this mod adds.
|
||||
|
||||
"[ropes](https://github.com/minetest-mods/ropes)" are very useful for navigating some of the large open spaces this mod provides.
|
||||
"[ropes](https://github.com/minetest-mods/ropes)" are very useful for navigating some of the large open spaces this mod provides. Some are large enough that a [glider](https://github.com/CBugDCoder/glider) may be well suited.
|
||||
|
||||
"[radiant damage](https://github.com/FaceDeer/radiant_damage)" greatly increases the danger of the Magma Sea if heat radiance is enabled, as well as several of the rare crystals in the deeper layers that emit Mese radiation if that damage type is enabled.
|
||||
|
||||
|
24
big_webs/LICENSE.txt
Normal file
24
big_webs/LICENSE.txt
Normal file
@ -0,0 +1,24 @@
|
||||
Sounds and textures are under various licenses, see the license.txt file in the /sounds and /textures directories for details.
|
||||
|
||||
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.
|
198
big_webs/init.lua
Normal file
198
big_webs/init.lua
Normal file
@ -0,0 +1,198 @@
|
||||
local modname = minetest.get_current_modname()
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
local default_path = minetest.get_modpath("default")
|
||||
|
||||
local get_node_box = function(connector_thickness)
|
||||
return {
|
||||
type = "connected",
|
||||
--fixed = {-hub_thickness,-hub_thickness,-hub_thickness,hub_thickness,hub_thickness,hub_thickness},
|
||||
connect_top = {-connector_thickness, 0, -connector_thickness, connector_thickness, 0.5, connector_thickness},
|
||||
connect_bottom = {-connector_thickness, -0.5, -connector_thickness, connector_thickness, 0, connector_thickness},
|
||||
connect_back = {-connector_thickness, -connector_thickness, 0, connector_thickness, connector_thickness, 0.5},
|
||||
connect_right = {0, -connector_thickness, -connector_thickness, 0.5, connector_thickness, connector_thickness},
|
||||
connect_front = {-connector_thickness, -connector_thickness, -0.5, connector_thickness, connector_thickness, 0},
|
||||
connect_left = {-0.5, -connector_thickness, -connector_thickness, 0, connector_thickness, connector_thickness},
|
||||
disconnected = {-connector_thickness, -connector_thickness, -connector_thickness, connector_thickness, connector_thickness, connector_thickness},
|
||||
}
|
||||
end
|
||||
|
||||
local in_anchor_group = function(name)
|
||||
return
|
||||
minetest.get_item_group(name, "soil") > 0 or
|
||||
minetest.get_item_group(name, "stone") > 0 or
|
||||
minetest.get_item_group(name, "tree") > 0 or
|
||||
minetest.get_item_group(name, "leaves") > 0 or
|
||||
minetest.get_item_group(name, "sand") > 0 or
|
||||
minetest.get_item_group(name, "wood") > 0 or
|
||||
name == "ignore"
|
||||
end
|
||||
|
||||
local cardinal_directions = {
|
||||
{x=1,y=0,z=0},
|
||||
{x=-1,y=0,z=0},
|
||||
{x=0,y=1,z=0},
|
||||
{x=0,y=-1,z=0},
|
||||
{x=0,y=0,z=1},
|
||||
{x=0,y=0,z=-1}
|
||||
}
|
||||
|
||||
local cardinal_planes = {
|
||||
{3,5},
|
||||
{3,5},
|
||||
{1,5},
|
||||
{1,5},
|
||||
{1,3},
|
||||
{1,3},
|
||||
}
|
||||
|
||||
local insert_if_not_in_hashtable = function(pos, insert_into, if_not_in)
|
||||
local hash = minetest.hash_node_position(pos)
|
||||
if if_not_in[hash] then
|
||||
return
|
||||
end
|
||||
table.insert(insert_into, pos)
|
||||
end
|
||||
|
||||
-- flood fill through the web to get all web and anchor locations
|
||||
local get_web_nodes = function(pos, webs, anchors)
|
||||
local to_check = {}
|
||||
table.insert(to_check, pos)
|
||||
while next(to_check) ~= nil do
|
||||
local check_pos = table.remove(to_check)
|
||||
local check_node = minetest.get_node(check_pos)
|
||||
if minetest.get_item_group(check_node.name, "webbing") > 0 then
|
||||
webs[minetest.hash_node_position(check_pos)] = true
|
||||
for _, dir in pairs(cardinal_directions) do
|
||||
insert_if_not_in_hashtable(vector.add(check_pos, dir), to_check, webs)
|
||||
end
|
||||
elseif in_anchor_group(check_node.name) then
|
||||
anchors[minetest.hash_node_position(check_pos)] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local sound
|
||||
if default_path then
|
||||
sound = default.node_sound_leaves_defaults()
|
||||
end
|
||||
|
||||
|
||||
local web_line = function(pos, dir, distance)
|
||||
local web_spine = {}
|
||||
for i = 0, distance do
|
||||
local web_pos = vector.add(pos, vector.multiply(dir,i))
|
||||
local node_name = minetest.get_node(web_pos).name
|
||||
if node_name == "air" or node_name == "big_webs:webbing" then
|
||||
table.insert(web_spine, web_pos)
|
||||
elseif in_anchor_group(node_name) then
|
||||
anchored=true
|
||||
break
|
||||
else
|
||||
anchored=false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if anchored then
|
||||
for _, web_pos in pairs(web_spine) do
|
||||
if math.random() < 0.9 then
|
||||
minetest.set_node(web_pos, {name="big_webs:webbing"})
|
||||
end
|
||||
end
|
||||
return web_spine
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local generate_web = function(pos)
|
||||
local dir_choice = math.random(1, 6)
|
||||
local dir = cardinal_directions[dir_choice]
|
||||
local web_spine = web_line(pos, dir, 30)
|
||||
if web_spine then
|
||||
local dir2 = cardinal_directions[cardinal_planes[dir_choice][math.random(1, 2)]]
|
||||
local dir2_opposite = vector.multiply(dir2, -1)
|
||||
for _, web_pos in pairs(web_spine) do
|
||||
web_line(web_pos, dir2, 15)
|
||||
web_line(web_pos, dir2_opposite, 15)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_node("big_webs:webbing", {
|
||||
description = S("Giant Cave Spider Webbing"),
|
||||
_doc_items_longdesc = S("Thick ropes of sticky, springy silk, strung between cavern walls in hopes of catching bats and even larger beasts."),
|
||||
_doc_items_usagehelp = S("Webbing can be collected and re-strung elsewhere to aid in climbing. It absorbs all falling damage when you land on it."),
|
||||
tiles = {
|
||||
{name="big_webs.png"},
|
||||
},
|
||||
use_texture_alpha = "blend",
|
||||
connects_to = {"group:soil", "group:stone", "group:tree", "group:leaves", "group:sand", "group:wood", "group:webbing"},
|
||||
connect_sides = { "top", "bottom", "front", "left", "back", "right" },
|
||||
drawtype = "nodebox",
|
||||
node_box = get_node_box(0.0625),
|
||||
collision_box = get_node_box(0.0625),
|
||||
inventory_image = "big_webs_item.png",
|
||||
wield_image = "big_webs_item.png",
|
||||
paramtype = "light",
|
||||
is_ground_content = false,
|
||||
climbable = true,
|
||||
floodable = true,
|
||||
groups = {snappy = 2, choppy = 2, webbing = 1, flammable=1, fall_damage_add_percent=-100, bouncy=20},
|
||||
sounds = sound,
|
||||
on_construct = function(pos)
|
||||
minetest.get_node_timer(pos):start(30)
|
||||
end,
|
||||
on_destruct = function(pos)
|
||||
for _, dir in pairs(cardinal_directions) do
|
||||
local neighbor_pos = vector.add(pos, dir)
|
||||
if minetest.get_item_group(minetest.get_node(neighbor_pos).name, "webbing") > 0 then
|
||||
minetest.get_node_timer(neighbor_pos):start(30)
|
||||
end
|
||||
end
|
||||
minetest.get_node_timer(pos):stop()
|
||||
end,
|
||||
on_timer = function(pos, elapsed)
|
||||
local webs = {}
|
||||
local anchors = {}
|
||||
get_web_nodes(pos, webs, anchors)
|
||||
local first_anchor = next(anchors)
|
||||
for hash, _ in pairs(webs) do
|
||||
local web_pos = minetest.get_position_from_hash(hash)
|
||||
if first_anchor == nil then
|
||||
-- unsupported web
|
||||
minetest.set_node(web_pos, {name="air"})
|
||||
minetest.item_drop(ItemStack("big_webs:webbing"), nil, web_pos)
|
||||
end
|
||||
minetest.get_node_timer(web_pos):stop() -- no need to recheck
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_node("big_webs:web_egg", {
|
||||
description = S("Giant Cave Spider Web Generator"),
|
||||
tiles = {
|
||||
{name="big_webs.png"},
|
||||
},
|
||||
use_texture_alpha = "blend",
|
||||
connects_to = {"group:soil", "group:stone", "group:tree", "group:leaves", "group:sand", "group:wood", "group:webbing"},
|
||||
connect_sides = { "top", "bottom", "front", "left", "back", "right" },
|
||||
drawtype = "nodebox",
|
||||
node_box = get_node_box(0.0625),
|
||||
collision_box = get_node_box(0.0625),
|
||||
inventory_image = "big_webs_item.png",
|
||||
wield_image = "big_webs_item.png",
|
||||
paramtype = "light",
|
||||
is_ground_content = false,
|
||||
climbable = true,
|
||||
floodable = true,
|
||||
groups = {snappy = 2, choppy = 2, webbing = 1, flammable=1, fall_damage_add_percent=-100, bouncy=20},
|
||||
sounds = sound,
|
||||
on_construct = function(pos)
|
||||
minetest.get_node_timer(pos):start(1)
|
||||
end,
|
||||
on_timer = function(pos, elapsed)
|
||||
minetest.set_node(pos, {name="air"})
|
||||
generate_web(pos)
|
||||
end,
|
||||
})
|
12
big_webs/locale/template.txt
Normal file
12
big_webs/locale/template.txt
Normal file
@ -0,0 +1,12 @@
|
||||
# textdomain: big_webs
|
||||
|
||||
|
||||
### init.lua ###
|
||||
|
||||
Giant Cave Spider Web Generator=
|
||||
Giant Cave Spider Webbing=
|
||||
|
||||
Thick ropes of sticky, springy silk, strung between cavern walls in hopes of catching bats and even larger beasts.=
|
||||
|
||||
Webbing can be collected and re-strung elsewhere to aid in climbing. It absorbs all falling damage when you land on it.=
|
||||
|
2
big_webs/mod.conf
Normal file
2
big_webs/mod.conf
Normal file
@ -0,0 +1,2 @@
|
||||
name=big_webs
|
||||
optional_depends=default
|
BIN
big_webs/textures/big_webs.png
Normal file
BIN
big_webs/textures/big_webs.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 309 B |
BIN
big_webs/textures/big_webs_item.png
Normal file
BIN
big_webs/textures/big_webs_item.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 380 B |
21
chasms/LICENSE.txt
Normal file
21
chasms/LICENSE.txt
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
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.
|
169
chasms/init.lua
Normal file
169
chasms/init.lua
Normal file
@ -0,0 +1,169 @@
|
||||
local data = {}
|
||||
|
||||
chasms = {}
|
||||
|
||||
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
|
||||
|
||||
local web_probability = 0.15 -- the chance that a given mapblock will have webbing criss-crossing the chasm
|
||||
|
||||
local chasms_threshold = tonumber(minetest.settings:get("chasms_threshold")) or 0.9
|
||||
local np_chasms_default = {
|
||||
offset = 0,
|
||||
scale = 1,
|
||||
spread = {x = 50, y = 1000, z = 3000},
|
||||
seed = 94586,
|
||||
octaves = 2,
|
||||
persist = 0.63,
|
||||
lacunarity = 2.0,
|
||||
}
|
||||
local np_chasms = minetest.settings:get_np_group("chasms_params") or np_chasms_default
|
||||
-- For some reason, these numbers are returned as strings by get_np_group.
|
||||
local tonumberize_params = function(params)
|
||||
params.scale = tonumber(params.scale)
|
||||
params.lacunarity = tonumber(params.lacunarity)
|
||||
params.spread.x = tonumber(params.spread.x)
|
||||
params.spread.y = tonumber(params.spread.y)
|
||||
params.spread.z = tonumber(params.spread.z)
|
||||
params.offset = tonumber(params.offset)
|
||||
params.persistence = tonumber(params.persistence)
|
||||
end
|
||||
tonumberize_params(np_chasms)
|
||||
local nobj_chasm
|
||||
local chasm_data = {}
|
||||
|
||||
local waver_strength = 8
|
||||
local waver_vector = {x=waver_strength, y=0, z=0}
|
||||
local np_waver = {
|
||||
offset = 0,
|
||||
scale = waver_strength,
|
||||
spread = {x = 50, y = 50, z = 50},
|
||||
seed = 49585,
|
||||
octaves = 2,
|
||||
persist = 0.63,
|
||||
lacunarity = 2.0,
|
||||
}
|
||||
local nobj_waver
|
||||
local waver_data = {}
|
||||
|
||||
local minfalloff = miny + falloff
|
||||
local maxfalloff = maxy - falloff
|
||||
local get_intensity = function(y)
|
||||
if y < miny or y > maxy then
|
||||
return 0
|
||||
end
|
||||
if y <= maxfalloff and y >= minfalloff then
|
||||
return 1
|
||||
end
|
||||
if y < minfalloff then
|
||||
return (y-miny)/falloff
|
||||
end
|
||||
-- if y > maxfalloff then
|
||||
return (maxy-y)/falloff
|
||||
-- end
|
||||
end
|
||||
|
||||
local c_air = minetest.get_content_id("air")
|
||||
local c_web
|
||||
|
||||
local big_webs_path = minetest.get_modpath("big_webs")
|
||||
if big_webs_path then
|
||||
c_web = minetest.get_content_id("big_webs:webbing")
|
||||
end
|
||||
|
||||
local z_displace = 10000
|
||||
|
||||
|
||||
local calculate_web_array = function(minp, maxp)
|
||||
local seed = math.random()*10000000
|
||||
math.randomseed(minp.y + z_displace*minp.z) -- use consistent seeds across the x axis
|
||||
local webs = {}
|
||||
for count = 1, math.random(5,20) do
|
||||
local width = math.random(5, 25)
|
||||
local direction_vertical = math.random() > 0.5
|
||||
local web_y = math.random(minp.y+8, maxp.y-8)
|
||||
local web_z = math.random(minp.z+8, maxp.z-8)
|
||||
for i = -math.floor(width/2), math.ceil(width/2) do
|
||||
if direction_vertical then
|
||||
webs[(web_y+i) + web_z*z_displace] = true
|
||||
else
|
||||
webs[web_y + (web_z+i)*z_displace] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
math.randomseed(seed)
|
||||
return webs
|
||||
end
|
||||
|
||||
minetest.register_on_generated(function(minp, maxp, seed)
|
||||
if minp.y >= maxy or maxp.y <= miny then
|
||||
return
|
||||
end
|
||||
|
||||
-- check if webs are present
|
||||
local webs
|
||||
local webs_present = false
|
||||
if big_webs_path then
|
||||
local seed = math.random()*10000000
|
||||
math.randomseed(minp.y + z_displace*minp.z) -- use consistent seeds across the x axis
|
||||
if math.random() < web_probability then
|
||||
webs_present = true
|
||||
end
|
||||
math.randomseed(seed)
|
||||
end
|
||||
|
||||
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
|
||||
vm:get_data(data)
|
||||
|
||||
nobj_chasm = nobj_chasm or minetest.get_perlin_map(np_chasms, {x = emax.x - emin.x + 1 + waver_strength*2, y = emax.y - emin.y + 1, z = emax.z - emin.z + 1})
|
||||
nobj_chasm:get_3d_map_flat(vector.subtract(emin, waver_vector), chasm_data)
|
||||
|
||||
nobj_waver = nobj_waver or minetest.get_perlin_map(np_waver, {x = emax.x - emin.x + 1, y = emax.y - emin.y + 1, z = emax.z - emin.z + 1})
|
||||
nobj_waver:get_3d_map_flat(emin, waver_data)
|
||||
|
||||
local chasm_area = VoxelArea:new{MinEdge = vector.subtract(emin, waver_vector), MaxEdge = vector.add(emax, waver_vector)}
|
||||
local data_area = VoxelArea:new{MinEdge = emin, MaxEdge = emax}
|
||||
|
||||
for i, x, y, z in data_area:iterp_xyz(emin, emax) do
|
||||
local waver = math.min(math.max(math.floor(waver_data[i]+0.5), -waver_strength), waver_strength)
|
||||
local intensity = get_intensity(y)
|
||||
if chasm_data[chasm_area:index(x+waver, y, z)]*intensity > chasms_threshold then
|
||||
if webs_present then
|
||||
webs = webs or calculate_web_array(minp, maxp) -- only calculate webs when we know we're in a chasm
|
||||
if webs[y + z*z_displace] and math.random() < 0.85 then -- random holes in the web
|
||||
data[i] = c_web
|
||||
minetest.get_node_timer({x=x,y=y,z=z}):start(1) -- this timer will check for unsupported webs
|
||||
else
|
||||
data[i] = c_air
|
||||
end
|
||||
else
|
||||
data[i] = c_air
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vm:set_data(data)
|
||||
vm:calc_lighting()
|
||||
vm:write_to_map()
|
||||
end)
|
||||
|
||||
local nobj_local_chasm = minetest.get_perlin(np_chasms)
|
||||
local nobj_local_waver = minetest.get_perlin(np_waver)
|
||||
|
||||
chasms.is_in_chasm = function(pos)
|
||||
nobj_local_chasm = nobj_local_chasm or minetest.get_perlin(np_chasms)
|
||||
nobj_local_waver = nobj_local_waver or minetest.get_perlin(np_waver)
|
||||
local waver = math.min(math.max(math.floor(nobj_local_waver:get_3d(pos)+0.5), -waver_strength), waver_strength)
|
||||
local chasm_value = nobj_local_chasm:get_3d({x=pos.x+waver, y=pos.y, z=pos.z})
|
||||
return chasm_value*get_intensity(pos.y) > chasms_threshold
|
||||
end
|
||||
|
||||
-- A little cheaper to run, for mapgens that know they don't have to worry about the tops and bottoms of chasms
|
||||
chasms.is_in_chasm_without_taper = function(pos)
|
||||
nobj_local_chasm = nobj_local_chasm or minetest.get_perlin(np_chasms)
|
||||
nobj_local_waver = nobj_local_waver or minetest.get_perlin(np_waver)
|
||||
local waver = math.min(math.max(math.floor(nobj_local_waver:get_3d(pos)+0.5), -waver_strength), waver_strength)
|
||||
local chasm_value = nobj_local_chasm:get_3d({x=pos.x+waver, y=pos.y, z=pos.z})
|
||||
return chasm_value > chasms_threshold
|
||||
end
|
3
chasms/mod.conf
Normal file
3
chasms/mod.conf
Normal file
@ -0,0 +1,3 @@
|
||||
name=chasms
|
||||
depends=mapgen_helper
|
||||
optional_depends=big_webs
|
5
chasms/settingtypes.txt
Normal file
5
chasms/settingtypes.txt
Normal file
@ -0,0 +1,5 @@
|
||||
chasms_params (Noise params for chasms) noise_params_3d 0, 1, (50, 1000, 3000), 94586, 2, 0.63, 2.0
|
||||
chasms_threshold (Noise threshold for chasms) float 0.9
|
||||
chasms_maxy (Maximum Y) int -50
|
||||
chasms_miny (Minimum Y) int -2500
|
||||
chasms_falloff (Taper range when approaching max or min) int 100
|
@ -10,6 +10,8 @@ local c_spindlestem_white = df_caverns.node_id.spindlestem_white
|
||||
local tower_cap_shrublist
|
||||
local fungiwood_shrublist
|
||||
|
||||
local chasms_path = minetest.get_modpath("chasms")
|
||||
|
||||
if minetest.get_modpath("df_farming") then
|
||||
tower_cap_shrublist = {
|
||||
df_farming.spawn_plump_helmet_vm,
|
||||
@ -273,6 +275,18 @@ local decorate_level_1 = function(minp, maxp, seed, vm, node_arrays, area, data)
|
||||
if dry and data[vi] == c_wet_flowstone then
|
||||
data[vi] = c_dry_flowstone
|
||||
end
|
||||
|
||||
if chasms_path then
|
||||
local pos = area:position(vi)
|
||||
if chasms.is_in_chasm_without_taper(pos) then
|
||||
local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.
|
||||
if flooded_caverns and pos.y < subsea_level then
|
||||
data[vi] = c_water
|
||||
else
|
||||
data[vi] = c_air
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vm:set_param2_data(data_param2)
|
||||
|
@ -14,6 +14,9 @@ local c_dry_flowstone = df_caverns.node_id.dry_flowstone
|
||||
local c_veinstone = df_caverns.node_id.veinstone
|
||||
local c_pearls = df_caverns.node_id.pearls
|
||||
|
||||
local chasms_path = minetest.get_modpath("chasms")
|
||||
|
||||
|
||||
local wall_vein_perlin_params = {
|
||||
offset = 0,
|
||||
scale = 1,
|
||||
@ -354,6 +357,19 @@ local decorate_level_2 = function(minp, maxp, seed, vm, node_arrays, area, data)
|
||||
if dry and data[vi] == c_wet_flowstone then
|
||||
data[vi] = c_dry_flowstone
|
||||
end
|
||||
|
||||
if chasms_path then
|
||||
local pos = area:position(vi)
|
||||
if chasms.is_in_chasm_without_taper(pos) then
|
||||
local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.
|
||||
if flooded_caverns and pos.y < subsea_level then
|
||||
data[vi] = c_water
|
||||
else
|
||||
data[vi] = c_air
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
vm:set_param2_data(data_param2)
|
||||
|
@ -17,6 +17,9 @@ local c_glow_ore = df_caverns.node_id.glow_ore
|
||||
local c_salty_cobble = df_caverns.node_id.salty_cobble
|
||||
local c_salt_crystal = df_caverns.node_id.salt_crystal
|
||||
local c_sprite = df_caverns.node_id.sprite
|
||||
local c_webs_egg = df_caverns.node_id.big_webs_egg
|
||||
|
||||
local chasms_path = minetest.get_modpath("chasms")
|
||||
|
||||
local subsea_level = math.floor(df_caverns.config.level3_min - (df_caverns.config.level3_min - df_caverns.config.level2_min) * 0.33)
|
||||
local flooding_threshold = math.min(df_caverns.config.tunnel_flooding_threshold, df_caverns.config.cavern_threshold)
|
||||
@ -355,6 +358,7 @@ local decorate_level_3 = function(minp, maxp, seed, vm, node_arrays, area, data)
|
||||
local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
|
||||
local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
|
||||
local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.
|
||||
local ystride = area.ystride
|
||||
|
||||
if not (flooded_caverns and minp.y < subsea_level and area:get_y(vi) < subsea_level) then
|
||||
if flooded_caverns or biome_name == "blackcap" then
|
||||
@ -363,9 +367,15 @@ local decorate_level_3 = function(minp, maxp, seed, vm, node_arrays, area, data)
|
||||
else
|
||||
df_caverns.tunnel_ceiling(minp, maxp, area, vi, nvals_cracks, data, data_param2, false)
|
||||
end
|
||||
if c_webs_egg and (biome_name == "barren" or biome_name == "blackcap") and nvals_cracks[index2d] > 0.5 and math.random() < 0.1 then
|
||||
local index = vi-ystride
|
||||
if data[index] == c_air then
|
||||
data[index] = c_webs_egg
|
||||
minetest.get_node_timer(area:position(index)):start(1)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- air pockets
|
||||
local ystride = area.ystride
|
||||
local cracks = nvals_cracks[index2d]
|
||||
if cracks > 0.5 and data[vi-ystride] == c_water then
|
||||
data[vi-ystride] = c_air
|
||||
@ -459,7 +469,7 @@ local decorate_level_3 = function(minp, maxp, seed, vm, node_arrays, area, data)
|
||||
end
|
||||
|
||||
----------------------------------------------
|
||||
-- Column material override for dry biome
|
||||
-- Column material override for dry and icy biomes
|
||||
for _, vi in ipairs(node_arrays.column_nodes) do
|
||||
local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
|
||||
local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
|
||||
@ -476,7 +486,7 @@ local decorate_level_3 = function(minp, maxp, seed, vm, node_arrays, area, data)
|
||||
-- with the full blown generated array rigamarole.
|
||||
hoar_moss_generator = hoar_moss_generator or minetest.get_perlin(hoar_moss_perlin_params)
|
||||
local pos = area:position(vi)
|
||||
if hoar_moss_generator.get_3d and hoar_moss_generator:get_3d({x=pos.z, y=pos.y, z=pos.x}) > 0.5 then -- TODO: version 0.4.16 gets no hoar moss
|
||||
if hoar_moss_generator:get_3d({x=pos.z, y=pos.y, z=pos.x}) > 0.5 then
|
||||
data[vi] = c_hoar_moss
|
||||
else
|
||||
data[vi] = c_ice
|
||||
@ -491,6 +501,19 @@ local decorate_level_3 = function(minp, maxp, seed, vm, node_arrays, area, data)
|
||||
elseif biome_name == "barren" and not flooded_caverns and data[vi] == c_wet_flowstone then
|
||||
data[vi] = c_dry_flowstone
|
||||
end
|
||||
|
||||
if chasms_path then
|
||||
local pos = area:position(vi)
|
||||
if chasms.is_in_chasm_without_taper(pos) then
|
||||
if flooded_caverns and pos.y < subsea_level then
|
||||
data[vi] = c_water -- this puts a crack in the ice of icy biomes, but why not? A crack in the ice is interesting.
|
||||
else
|
||||
data[vi] = c_air
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
vm:set_param2_data(data_param2)
|
||||
|
@ -1,4 +1,4 @@
|
||||
name = df_caverns
|
||||
description = Adds vast underground caverns in the style of Dwarf Fortress, complete with underground flora in diverse biomes. Also adds stalactite/stalagmite decorations in the smaller tunnels.
|
||||
depends = default, subterrane, df_trees, df_mapitems
|
||||
optional_depends = df_farming, ice_sprites, oil, df_underworld_items, magma_conduits, bones_loot, named_waypoints, name_generator, fireflies
|
||||
optional_depends = df_farming, ice_sprites, oil, df_underworld_items, magma_conduits, bones_loot, named_waypoints, name_generator, fireflies, chasms, big_webs
|
@ -14,6 +14,11 @@ if minetest.get_modpath("df_farming") then
|
||||
df_caverns.node_id.dead_fungus = minetest.get_content_id("df_farming:dead_fungus")
|
||||
end
|
||||
|
||||
if minetest.get_modpath("big_webs") then
|
||||
df_caverns.node_id.big_webs = minetest.get_content_id("big_webs:webbing")
|
||||
df_caverns.node_id.big_webs_egg = minetest.get_content_id("big_webs:web_egg")
|
||||
end
|
||||
|
||||
df_caverns.node_id.air = minetest.get_content_id("air")
|
||||
|
||||
df_caverns.node_id.cobble = minetest.get_content_id("default:cobble")
|
||||
|
BIN
df_caverns/screenshots/chasm.jpg
Normal file
BIN
df_caverns/screenshots/chasm.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
@ -18,12 +18,30 @@ local c_spongestone = df_caverns.node_id.spongestone
|
||||
local c_rock_rot = df_caverns.node_id.rock_rot
|
||||
local c_water = df_caverns.node_id.water
|
||||
local c_wet_flowstone = df_caverns.node_id.wet_flowstone
|
||||
local c_webs = df_caverns.node_id.big_webs
|
||||
local c_webs_egg = df_caverns.node_id.big_webs_egg
|
||||
|
||||
df_caverns.data_param2 = {}
|
||||
|
||||
-- prevent mapgen from using these nodes as a base for stalactites or stalagmites
|
||||
local dont_build_speleothems_on = {}
|
||||
for _, content_id in pairs(df_mapitems.wet_stalagmite_ids) do
|
||||
dont_build_speleothems_on[content_id] = true
|
||||
end
|
||||
for _, content_id in pairs(df_mapitems.dry_stalagmite_ids) do
|
||||
dont_build_speleothems_on[content_id] = true
|
||||
end
|
||||
if minetest.get_modpath("big_webs") then
|
||||
dont_build_speleothems_on[c_webs] = true
|
||||
dont_build_speleothems_on[c_webs_egg] = true
|
||||
end
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
df_caverns.stalagmites = function(abs_cracks, vert_rand, vi, area, data, data_param2, wet, reverse_sign)
|
||||
if dont_build_speleothems_on[data[vi]] then
|
||||
return
|
||||
end
|
||||
local flowstone
|
||||
local stalagmite_ids
|
||||
if wet then
|
||||
@ -118,13 +136,6 @@ df_caverns.glow_worm_cavern_ceiling = function(abs_cracks, vert_rand, vi, area,
|
||||
end
|
||||
end
|
||||
|
||||
local content_in_list=function(content, list)
|
||||
for i, v in ipairs(list) do
|
||||
if content == v then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
df_caverns.tunnel_floor = function(minp, maxp, area, vi, nvals_cracks, data, data_param2, wet, dirt_node)
|
||||
if maxp.y > -30 then
|
||||
wet = false
|
||||
@ -135,7 +146,7 @@ df_caverns.tunnel_floor = function(minp, maxp, area, vi, nvals_cracks, data, dat
|
||||
local abs_cracks = math.abs(cracks)
|
||||
|
||||
if wet then
|
||||
if abs_cracks < 0.05 and data[vi+ystride] == c_air and not content_in_list(data[vi], df_mapitems.wet_stalagmite_ids) then -- make sure data[vi] is not already flowstone. Stalagmites from lower levels are acting as base for further stalagmites
|
||||
if abs_cracks < 0.05 and data[vi+ystride] == c_air and not dont_build_speleothems_on[data[vi]] then -- make sure data[vi] is not already flowstone. Stalagmites from lower levels are acting as base for further stalagmites
|
||||
local param2 = abs_cracks*1000000 - math.floor(abs_cracks*1000000/4)*4
|
||||
local height = math.floor(abs_cracks * 100)
|
||||
subterrane.stalagmite(vi+ystride, area, data, data_param2, param2, height, df_mapitems.wet_stalagmite_ids)
|
||||
@ -144,7 +155,7 @@ df_caverns.tunnel_floor = function(minp, maxp, area, vi, nvals_cracks, data, dat
|
||||
data[vi] = dirt_node
|
||||
end
|
||||
else
|
||||
if abs_cracks < 0.025 and data[vi+ystride] == c_air and not content_in_list(data[vi], df_mapitems.dry_stalagmite_ids) then -- make sure data[vi] is not already flowstone. Stalagmites from lower levels are acting as base for further stalagmites
|
||||
if abs_cracks < 0.025 and data[vi+ystride] == c_air and not dont_build_speleothems_on[data[vi]] then -- make sure data[vi] is not already flowstone. Stalagmites from lower levels are acting as base for further stalagmites
|
||||
local param2 = abs_cracks*1000000 - math.floor(abs_cracks*1000000/4)*4
|
||||
local height = math.floor(abs_cracks * 100)
|
||||
subterrane.stalagmite(vi+ystride, area, data, data_param2, param2, height, df_mapitems.dry_stalagmite_ids)
|
||||
@ -165,14 +176,14 @@ df_caverns.tunnel_ceiling = function(minp, maxp, area, vi, nvals_cracks, data, d
|
||||
local abs_cracks = math.abs(cracks)
|
||||
|
||||
if wet then
|
||||
if abs_cracks < 0.05 and data[vi-ystride] == c_air and not content_in_list(data[vi], df_mapitems.wet_stalagmite_ids) then -- make sure data[vi] is not already flowstone. Stalagmites from lower levels are acting as base for further stalagmites
|
||||
if abs_cracks < 0.05 and data[vi-ystride] == c_air and not dont_build_speleothems_on[data[vi]] then -- make sure data[vi] is not already flowstone. Stalagmites from lower levels are acting as base for further stalagmites
|
||||
local param2 = abs_cracks*1000000 - math.floor(abs_cracks*1000000/4)*4
|
||||
local height = math.floor(abs_cracks * 100)
|
||||
subterrane.stalactite(vi-ystride, area, data, data_param2, param2, height, df_mapitems.wet_stalagmite_ids)
|
||||
data[vi] = c_wet_flowstone
|
||||
end
|
||||
else
|
||||
if abs_cracks < 0.025 and data[vi-ystride] == c_air and not content_in_list(data[vi], df_mapitems.dry_stalagmite_ids) then -- make sure data[vi] is not already flowstone. Stalagmites from lower levels are acting as base for further stalagmites
|
||||
if abs_cracks < 0.025 and data[vi-ystride] == c_air and not dont_build_speleothems_on[data[vi]] then -- make sure data[vi] is not already flowstone. Stalagmites from lower levels are acting as base for further stalagmites
|
||||
local param2 = abs_cracks*1000000 - math.floor(abs_cracks*1000000/4)*4
|
||||
local height = math.floor(abs_cracks * 100)
|
||||
subterrane.stalactite(vi-ystride, area, data, data_param2, param2, height, df_mapitems.dry_stalagmite_ids)
|
||||
|
@ -9,6 +9,8 @@ local c_dry_flowstone = df_caverns.node_id.dry_flowstone
|
||||
local c_lava = df_caverns.node_id.lava
|
||||
local c_obsidian = df_caverns.node_id.obsidian
|
||||
|
||||
local chasms_path = minetest.get_modpath("chasms")
|
||||
|
||||
local c_coral_table = {}
|
||||
for node_name, node_def in pairs(minetest.registered_nodes) do
|
||||
if minetest.get_item_group(node_name, "dfcaverns_cave_coral") > 0 then
|
||||
@ -383,6 +385,17 @@ local decorate_sunless_sea = function(minp, maxp, seed, vm, node_arrays, area, d
|
||||
data_param2[vi] = math.random(1,4)-1
|
||||
minetest.get_node_timer(area:position(vi)):start(math.random(10, 60))
|
||||
end
|
||||
|
||||
if chasms_path then
|
||||
local pos = area:position(vi)
|
||||
if chasms.is_in_chasm(pos) then
|
||||
if pos.y <= sea_level then
|
||||
data[vi] = c_water
|
||||
else
|
||||
data[vi] = c_air
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -9,6 +9,58 @@ minetest.register_node("df_mapitems:veinstone", {
|
||||
_magma_conduits_heats_to = df_mapitems.node_name.cobble,
|
||||
is_ground_content = false,
|
||||
light_source = 2,
|
||||
drop = df_mapitems.node_name.cobble,
|
||||
drop = "df_mapitems:veinstone",
|
||||
sounds = df_mapitems.sounds.stone,
|
||||
on_punch = function(pos, node, puncher, pointed_thing)
|
||||
minetest.node_punch(pos, node, puncher, pointed_thing)
|
||||
minetest.swap_node(pos, {name="df_mapitems:veinstone_pulse"})
|
||||
minetest.get_node_timer(pos):start(2)
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_node("df_mapitems:veinstone_pulse", {
|
||||
description = S("Veinstone"),
|
||||
_doc_items_longdesc = df_mapitems.doc.veinstone_desc,
|
||||
_doc_items_usagehelp = df_mapitems.doc.veinstone_usage,
|
||||
tiles = {df_mapitems.texture.stone .. "^dfcaverns_veins.png"},
|
||||
groups = {cracky = 3, stone = 1, lava_heatable = 1, not_in_creative_inventory = 1},
|
||||
_magma_conduits_heats_to = df_mapitems.node_name.cobble,
|
||||
is_ground_content = false,
|
||||
light_source = 8,
|
||||
drop = "df_mapitems:veinstone",
|
||||
sounds = df_mapitems.sounds.stone,
|
||||
on_timer = function(pos, elapsed)
|
||||
local positions, count = minetest.find_nodes_in_area(vector.add(pos,1), vector.subtract(pos,1), "df_mapitems:veinstone")
|
||||
if count["df_mapitems:veinstone"] == 0 then
|
||||
positions, count = minetest.find_nodes_in_area(vector.add(pos,2), vector.subtract(pos,2), "df_mapitems:veinstone")
|
||||
end
|
||||
if count["df_mapitems:veinstone"] == 0 then
|
||||
positions = {[1] = minetest.find_node_near(pos, 3, "df_mapitems:veinstone")}
|
||||
end
|
||||
if positions[1] == nil then
|
||||
positions = {[1] = minetest.find_node_near(pos, 4, "df_mapitems:veinstone")}
|
||||
end
|
||||
for _, neighbor_pos in pairs(positions) do
|
||||
minetest.swap_node(neighbor_pos, {name="df_mapitems:veinstone_pulse"})
|
||||
minetest.get_node_timer(neighbor_pos):start(2)
|
||||
end
|
||||
minetest.swap_node(pos, {name="df_mapitems:veinstone_refractory"})
|
||||
minetest.get_node_timer(pos):start(12)
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_node("df_mapitems:veinstone_refractory", {
|
||||
description = S("Veinstone"),
|
||||
_doc_items_longdesc = df_mapitems.doc.veinstone_desc,
|
||||
_doc_items_usagehelp = df_mapitems.doc.veinstone_usage,
|
||||
tiles = {df_mapitems.texture.stone .. "^dfcaverns_veins.png"},
|
||||
groups = {cracky = 3, stone = 1, lava_heatable = 1, not_in_creative_inventory = 1},
|
||||
_magma_conduits_heats_to = df_mapitems.node_name.cobble,
|
||||
is_ground_content = false,
|
||||
light_source = 1,
|
||||
drop = "df_mapitems:veinstone",
|
||||
sounds = df_mapitems.sounds.stone,
|
||||
on_timer = function(pos, elapsed)
|
||||
minetest.swap_node(pos, {name="df_mapitems:veinstone"})
|
||||
end,
|
||||
})
|
14
guide.md
14
guide.md
@ -160,6 +160,14 @@ Quarry bushes can be found here.
|
||||
|
||||
On the third cavern layer the dry barren caverns will sprout clusters of enormous hexagonal red crystals from their floors and ceilings, providing light. These crystals have no particular use but are rare and beautiful to look at.
|
||||
|
||||
# Chasms
|
||||
|
||||
![A chasm](./df_caverns/screenshots/chasm.jpg)
|
||||
|
||||
Not all vast open spaces underground are the result of aeons of erosion by water or magma. The foundations of the world shift from time to time, causing deep faults to split open in the rock. Yawning chasms can be found oriented along the north-south axis, some of them stretching for kilometers both in length and depth. They cross through multiple cavern layers and are an environment in their own right. Chasms can be a convenient way of traveling long distances and also a convenient way of falling to your death.
|
||||
|
||||
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.
|
||||
|
||||
# Sunless Sea
|
||||
|
||||
![Sunless river](./df_caverns/screenshots/sunless_river.jpg)
|
||||
@ -174,7 +182,7 @@ The distinguishing feature of the Sunless Sea is the vast expanse of water to be
|
||||
|
||||
Below the surface of the water Snareweed can be found, a dangerous kelp-like growth that uses bioluminescence and reflective patches to lure cave fish close enough to snag with their barbs. Swimming through Snareweed is a painful experience. Deeper down are craggy towers of softly glowing Cave Coral.
|
||||
|
||||
This is the last stop for the living world, however. Caverns continue to stretch below but nothing grows down there.
|
||||
This is the last stop for the living world. Caverns continue to stretch below but nothing grows below.
|
||||
|
||||
# Lakes of Oil
|
||||
|
||||
@ -200,9 +208,9 @@ The Magma Sea is not without its treasures, however. In the most infernal region
|
||||
|
||||
![There are older and fouler things than orcs in the deep places of the world](./df_caverns/screenshots/underworld.jpg)
|
||||
|
||||
The foundations of the world lie at -3100 meters, under a default configuration. The diggable rock of the world ends at a rippling layer of invulnerable Slade, a material of unparalleled density. There is a cavern layer at the seam between rock and Slade, however, as if the earth itself was reluctant to touch the strange matter below. The crevices of the ceiling have strange glowing rocks in them, producing a surprisingly bright ambiance. Don't attempt to disturb those rocks.
|
||||
The foundations of the world lie at -3100 meters. The diggable rock of the world ends at a rippling layer of invulnerable Slade, a material of unparalleled density. There is a cavern layer at the seam between rock and Slade, however, as if the earth itself was reluctant to touch the strange matter below. The crevices of the ceiling have strange glowing rocks in them, producing a surprisingly bright ambiance. Don't attempt to disturb those rocks.
|
||||
|
||||
There is no native life in the Underworld. It appears there once _was_, however. Occasional clusters of vacant buildings can be found, impossibly crafted from bricks of Slade and empty of any furnishings. Around those clusters of vacant buildings are fields filled with sealed pits lined with flawless Slade blocks. It is unclear whether these pits are as vacant as the buildings, though. The seals capping them are engraved with ancient words in lost tongues, but one phrase can be translated from the oldest known languages: "This Place is Not a Place of Honor."
|
||||
There is no native life in the Underworld. It appears there once _was_, however. Occasional clusters of vacant buildings can be found, impossibly crafted from bricks of Slade and empty of any furnishings. Around those clusters of vacant buildings are fields filled with sealed pits that are lined with flawless Slade blocks. It is unclear whether these pits are as vacant as the buildings, though. The seals capping them are engraved with ancient words in lost tongues, but one phrase can be translated from the oldest known languages: "This Place is Not a Place of Honor."
|
||||
|
||||
Just as ominously, the ancient bones of long-dead warriors are scattered amidst the buildings. They contain loot for those willing to risk disturbing them. There are no traces of who - or what - they died fighting, but their ancient sentinels still lurk nearby to guard them.
|
||||
|
||||
|
@ -114,7 +114,7 @@ hunter_statue.register_hunter_statue = function(node_name, statue_def)
|
||||
if fleshy_armour then
|
||||
armour_multiplier = fleshy_armour/100
|
||||
end
|
||||
nearest_player:add_player_velocity(vector.multiply(vector.direction(pos, nearest_pos), knockback))
|
||||
nearest_player:add_velocity(vector.multiply(vector.direction(pos, nearest_pos), knockback))
|
||||
nearest_player:set_hp(math.max(nearest_player:get_hp() - damage*armour_multiplier, 0))
|
||||
minetest.sound_play({name="hunter_statue_thud"}, {pos = nearest_pos})
|
||||
return
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 6439ca59bfe8e3e4c5ed0826f41d2c49b862fb75
|
||||
Subproject commit 6cc2fe8faea4026101dc9f462a7594edfc26ca7f
|
Loading…
Reference in New Issue
Block a user