many worldgen mods and gonna try technic now

This commit is contained in:
2026-09-13 03:30:48 -06:00
parent bac6ac94a3
commit 548749cb1a
4811 changed files with 163271 additions and 1 deletions
+17
View File
@@ -0,0 +1,17 @@
# 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
+47
View File
@@ -0,0 +1,47 @@
# 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
+105
View File
@@ -0,0 +1,105 @@
# 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.
+67
View File
@@ -0,0 +1,67 @@
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
@@ -0,0 +1,28 @@
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
+3
View File
@@ -0,0 +1,3 @@
default
intllib?
mapgen_helper
@@ -0,0 +1 @@
A mod that creates vast underground caverns and allows biomes to be defined for them
+284
View File
@@ -0,0 +1,284 @@
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
+619
View File
@@ -0,0 +1,619 @@
-- original cave code modified from paramat's subterrain
-- Modified by HeroOfTheWinds for caverealms
-- Modified by FaceDeer for subterrane
-- Depends default
-- License: code MIT
subterrane = {} --create a container for functions and constants
local modpath = minetest.get_modpath(minetest.get_current_modname())
dofile(modpath.."/dependencies.lua")
local c_stone = minetest.get_content_id(subterrane.dependencies.stone)
local c_clay = minetest.get_content_id(subterrane.dependencies.clay)
local c_desert_stone = minetest.get_content_id(subterrane.dependencies.desert_stone)
local c_sandstone = minetest.get_content_id(subterrane.dependencies.sandstone)
local c_air = minetest.get_content_id("air")
local c_water = minetest.get_content_id(subterrane.dependencies.water)
local c_obsidian = minetest.get_content_id(subterrane.dependencies.obsidian)
local c_cavern_air = c_air
local c_warren_air = c_air
local subterrane_enable_singlenode_mapping_mode = minetest.settings:get_bool("subterrane_enable_singlenode_mapping_mode", false)
if subterrane_enable_singlenode_mapping_mode then
c_cavern_air = c_stone
c_warren_air = c_clay
end
local c_lava_set -- will be populated with a set of nodes that count as lava
-- Performance instrumentation
local t_start = os.clock()
subterrane.registered_layers = {}
dofile(modpath.."/defaults.lua")
dofile(modpath.."/features.lua") -- some generic cave features useful for a variety of mapgens
dofile(modpath.."/player_spawn.lua") -- Function for spawning a player in a giant cavern
dofile(modpath.."/test_pos.lua") -- Function other mapgens can use to test if a position is inside a cavern
dofile(modpath.."/legacy.lua") -- contains old node definitions and functions, will be removed at some point in the future.
local disable_mapgen_caverns = function()
local mg_name = minetest.get_mapgen_setting("mg_name")
local flags_name
local default_flags
if mg_name == "v7" then
flags_name = "mgv7_spflags"
default_flags = "mountains,ridges,nofloatlands"
elseif mg_name == "v5" then
flags_name = "mgv5_spflags"
default_flags = ""
else
return
end
local function split(source, delimiters)
local elements = {}
local pattern = '([^'..delimiters..']+)'
string.gsub(source, pattern, function(value) elements[#elements + 1] = value; end);
return elements
end
local flags_setting = minetest.get_mapgen_setting(flags_name) or default_flags
local new_flags = {}
local flags = split(flags_setting, ", ")
local nocaverns_present = false
for _, flag in pairs(flags) do
if flag ~= "caverns" then
table.insert(new_flags, flag)
end
if flag == "nocaverns" then
nocaverns_present = true
end
end
if not nocaverns_present then
table.insert(new_flags, "nocaverns")
end
minetest.set_mapgen_setting(flags_name, table.concat(new_flags, ","), true)
end
disable_mapgen_caverns()
-- Column stuff
----------------------------------------------------------------------------------
local grid_size = mapgen_helper.block_size * 4
subterrane.get_column_points = function(minp, maxp, column_def)
local grids = mapgen_helper.get_nearest_regions(minp, grid_size)
local points = {}
for _, grid in ipairs(grids) do
--The y value of the returned point will be the radius of the column
local minp = {x=grid.x, y = column_def.minimum_radius*100, z=grid.z}
local maxp = {x=grid.x+grid_size-1, y=column_def.maximum_radius*100, z=grid.z+grid_size-1}
for _, point in ipairs(mapgen_helper.get_random_points(minp, maxp, column_def.minimum_count, column_def.maximum_count)) do
point.y = point.y / 100
if point.x > minp.x - point.y
and point.x < maxp.x + point.y
and point.z > minp.z - point.y
and point.z < maxp.z + point.y then
table.insert(points, point)
end
end
end
return points
end
subterrane.get_column_value = function(pos, points)
local heat = 0
for _, point in ipairs(points) do
local axis_point = {x=point.x, y=pos.y, z=point.z}
local radius = point.y
if (pos.x >= axis_point.x-radius and pos.x <= axis_point.x+radius
and pos.z >= axis_point.z-radius and pos.z <= axis_point.z+radius) then
local dist = vector.distance(pos, axis_point)
if dist < radius then
heat = math.max(heat, 1 - dist/radius)
end
end
end
return heat
end
-- Decoration node lists
----------------------------------------------------------------------------------
-- States any given node can be in. Used to detect boundaries
local outside_region = 1
local inside_ground = 2
local inside_tunnel = 3
local inside_cavern = 4
local inside_warren = 5
local inside_column = 6
-- These arrays will contain the indices of various nodes relevant to decoration
-- Note that table.getn and # will not correctly report the number of items in these since they're reused
-- between calls and are not cleared for efficiency. You can iterate through them using ipairs,
-- and you can get their content count from the similarly-named variable associated with them.
local cavern_data
local cavern_floor_nodes
local cavern_ceiling_nodes
local warren_floor_nodes
local warren_ceiling_nodes
local tunnel_floor_nodes
local tunnel_ceiling_nodes
local column_nodes
local initialize_node_arrays = function()
cavern_data = {}
cavern_floor_nodes = {}
cavern_data.cavern_floor_nodes = cavern_floor_nodes
cavern_data.cavern_floor_count = 0
cavern_ceiling_nodes = {}
cavern_data.cavern_ceiling_nodes = cavern_ceiling_nodes
cavern_data.cavern_ceiling_count = 0
warren_floor_nodes = {}
cavern_data.warren_floor_nodes = warren_floor_nodes
cavern_data.warren_floor_count = 0
warren_ceiling_nodes = {}
cavern_data.warren_ceiling_nodes = warren_ceiling_nodes
cavern_data.warren_ceiling_count = 0
tunnel_floor_nodes = {}
cavern_data.tunnel_floor_nodes = tunnel_floor_nodes
cavern_data.tunnel_floor_count = 0
tunnel_ceiling_nodes = {}
cavern_data.tunnel_ceiling_nodes = tunnel_ceiling_nodes
cavern_data.tunnel_ceiling_count = 0
column_nodes = {}
cavern_data.column_nodes = column_nodes
cavern_data.column_count = 0
end
initialize_node_arrays()
-- inserts nil after the last node so that ipairs will function correctly
local close_node_arrays = function()
cavern_ceiling_nodes[cavern_data.cavern_ceiling_count + 1] = nil
cavern_floor_nodes[cavern_data.cavern_floor_count + 1] = nil
warren_ceiling_nodes[cavern_data.warren_ceiling_count + 1] = nil
warren_floor_nodes[cavern_data.warren_floor_count + 1] = nil
tunnel_ceiling_nodes[cavern_data.tunnel_ceiling_count + 1] = nil
tunnel_floor_nodes[cavern_data.tunnel_floor_count + 1] = nil
column_nodes[cavern_data.column_count + 1] = nil
end
-- clear the tables without deleting them - easer on memory management this way
local clear_node_arrays = function()
cavern_data.cavern_ceiling_count = 0
cavern_data.cavern_floor_count = 0
cavern_data.warren_ceiling_count = 0
cavern_data.warren_floor_count = 0
cavern_data.tunnel_ceiling_count = 0
cavern_data.tunnel_floor_count = 0
cavern_data.column_count = 0
-- for k,v in pairs(cavern_ceiling_nodes) do cavern_ceiling_nodes[k] = nil end
-- for k,v in pairs(cavern_floor_nodes) do cavern_floor_nodes[k] = nil end
-- for k,v in pairs(warren_ceiling_nodes) do warren_ceiling_nodes[k] = nil end
-- for k,v in pairs(warren_floor_nodes) do warren_floor_nodes[k] = nil end
-- for k,v in pairs(tunnel_ceiling_nodes) do tunnel_ceiling_nodes[k] = nil end
-- for k,v in pairs(tunnel_floor_nodes) do tunnel_floor_nodes[k] = nil end
-- for k,v in pairs(column_nodes) do column_nodes[k] = nil end
close_node_arrays()
end
-- cave_layer_def
--{
-- 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.
-- y_min = -- required, the lowest elevation this cave layer will be generated in.
-- 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.
-- 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.
--}
-- column_def
--{
-- 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.
--}
-- cavern_data -- This is passed into the decorate method.
--{
-- 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 surface.
-- 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
-- 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.
--}
subterrane.register_layer = function(cave_layer_def)
local error_out = false
if cave_layer_def.y_min == nil then
minetest.log("error", "[subterrane] cave layer def " .. tostring(cave_layer_def.name) .. " did not have a y_min defined. Not registered.")
error_out = true
end
if cave_layer_def.y_max == nil then
minetest.log("error", "[subterrane] cave layer def " .. tostring(cave_layer_def.name) .. " did not have a y_max defined. Not registered.")
error_out = true
end
if error_out then return end
subterrane.set_defaults(cave_layer_def)
local YMIN = cave_layer_def.y_min
local YMAX = cave_layer_def.y_max
if cave_layer_def.name == nil then
cave_layer_def.name = tostring(YMIN) .. " to " .. tostring(YMAX)
end
local cave_name = cave_layer_def.name
if subterrane.registered_layers[cave_name] ~= nil then
minetest.log("warning", "[subterrane] cave layer def " .. tostring(cave_name) .. " has already been registered. Overriding with new definition.")
end
subterrane.registered_layers[cave_name] = cave_layer_def
local block_size = mapgen_helper.block_size
if (YMAX+32+1)%block_size ~= 0 then
local boundary = YMAX -(YMAX+32+1)%block_size
minetest.log("warning", "[subterrane] The y_max setting "..tostring(YMAX)..
" for cavern layer " .. cave_name .. " is not aligned with map chunk boundaries. Consider "..
tostring(boundary) .. " or " .. tostring(boundary+block_size) .. " for maximum mapgen efficiency.")
end
if (YMIN+32)%block_size ~= 0 then
local boundary = YMIN - (YMIN+32)%block_size
minetest.log("warning", "[subterrane] The y_min setting "..tostring(YMIN)..
" for cavern layer " .. cave_name .. " is not aligned with map chunk boundaries. Consider "..
tostring(boundary) .. " or " .. tostring(boundary+block_size) .. " for maximum mapgen efficiency.")
end
local BLEND = math.min(cave_layer_def.boundary_blend_range, (YMAX-YMIN)/2)
local TCAVE = cave_layer_def.cave_threshold
local warren_area_threshold = cave_layer_def.warren_region_threshold -- determines how much volume warrens are found in around caverns
local warren_area_variability_threshold = cave_layer_def.warren_region_variability_threshold -- determines how much of the warren_area_threshold volume actually has warrens in it
local warren_threshold = cave_layer_def.warren_threshold -- determines narrowness of warrens themselves
local solidify_lava = cave_layer_def.solidify_lava
local np_cave = cave_layer_def.perlin_cave
local np_wave = cave_layer_def.perlin_wave
local np_warren_area = cave_layer_def.perlin_warren_area
local np_warrens = cave_layer_def.perlin_warrens
local y_blend_min = YMIN + BLEND * 1.5
local y_blend_max = YMAX - BLEND * 1.5
local column_def = cave_layer_def.columns
local c_column
local c_warren_column
if column_def then
c_column = minetest.get_content_id(column_def.node)
if column_def.warren_node then
c_warren_column = minetest.get_content_id(column_def.warren_node)
end
end
local double_frequency = cave_layer_def.double_frequency
local decorate = cave_layer_def.decorate
if subterrane_enable_singlenode_mapping_mode then
decorate = nil
c_column = c_air
c_warren_column = nil
end
local is_ground_content = cave_layer_def.is_ground_content
if is_ground_content == nil then
is_ground_content = mapgen_helper.is_ground_content
end
-- On generated
----------------------------------------------------------------------------------
minetest.register_on_generated(function(minp, maxp, seed)
--if out of range of cave definition limits, abort
if minp.y > YMAX or maxp.y < YMIN then
return
end
t_start = os.clock()
if c_lava_set == nil then
c_lava_set = {}
for name, def in pairs(minetest.registered_nodes) do
if def.groups ~= nil and def.groups.lava ~= nil then
c_lava_set[minetest.get_content_id(name)] = true
end
end
end
local vm, data, data_param2, area = mapgen_helper.mapgen_vm_data_param2()
local emin = area.MinEdge
local emax = area.MaxEdge
local nvals_cave, cave_area = mapgen_helper.perlin3d("subterrane:cave", emin, emax, np_cave) --cave noise for structure
local nvals_wave = mapgen_helper.perlin3d("subterrane:wave", emin, emax, np_wave) --wavy structure of cavern ceilings and floors
-- pre-average everything so that the final values can be passed
-- along to the decorate function if it wants them
for vi, value in ipairs(nvals_cave) do
nvals_cave[vi] = (value + nvals_wave[vi])/2
end
local warren_area_uninitialized = true
local nvals_warren_area
local warrens_uninitialized = true
local nvals_warrens
local previous_y = emin.y
local previous_node_state = outside_region
local this_node_state = outside_region
local column_points = nil
local column_weight = nil
-- This information might be of use to the decorate function.
cavern_data.contains_cavern = false
cavern_data.contains_warren = false
cavern_data.nvals_cave = nvals_cave
cavern_data.cave_area = cave_area
cavern_data.cavern_def = cave_layer_def
-- The interp_yxz iterator iterates upwards in columns along the y axis.
-- starts at miny, goes to maxy, then switches to a new x,z and repeats.
for vi, x, y, z in area:iterp_yxz(emin, emax) do
-- We're "over-generating" when carving out the empty space of the cave volume so that decorations
-- can slop over the boundaries of the mapblock without being cut off.
-- We only want to add vi to the various decoration node lists if we're actually within the mapblock.
local is_within_current_mapblock = mapgen_helper.is_pos_within_box({x=x, y=y, z=z}, minp, maxp)
if y < previous_y then
-- we've switched to a new column
previous_node_state = outside_region
else
previous_node_state = this_node_state
end
previous_y = y
this_node_state = inside_ground
local cave_local_threshold
if y < y_blend_min then
cave_local_threshold = TCAVE + ((y_blend_min - y) / BLEND) ^ 2
elseif y > y_blend_max then
cave_local_threshold = TCAVE + ((y - y_blend_max) / BLEND) ^ 2
else
cave_local_threshold = TCAVE
end
local cave_value = nvals_cave[vi]
if double_frequency then
if cave_value < 0 then
cave_value = -cave_value
if subterrane_enable_singlenode_mapping_mode then
c_cavern_air = c_desert_stone
c_warren_air = c_sandstone
end
else
if subterrane_enable_singlenode_mapping_mode then
c_cavern_air = c_stone
c_warren_air = c_clay
end
end
end
-- inside a giant cavern
if cave_value > cave_local_threshold then
local column_value = 0
if column_def then
if column_points == nil then
column_points = subterrane.get_column_points(emin, maxp, column_def)
column_weight = column_def.weight
end
column_value = subterrane.get_column_value({x=x, y=y, z=z}, column_points)
end
if column_value > 0 and cave_value - column_value * column_weight < cave_local_threshold then
-- only add column nodes if we're within the current mapblock because
-- otherwise we're adding column nodes that the decoration loop won't know about
if is_ground_content(data[vi]) and is_within_current_mapblock then
data[vi] = c_column -- add a column node
end
this_node_state = inside_column
else
if is_ground_content(data[vi]) then
data[vi] = c_cavern_air --hollow it out to make the cave
end
this_node_state = inside_cavern
if is_within_current_mapblock then
cavern_data.contains_cavern = true
end
end
end
-- If there's lava near the edges of the cavern, solidify it.
if solidify_lava and cave_value > cave_local_threshold - 0.05 and c_lava_set[data[vi]] then
data[vi] = c_obsidian
end
--borderlands of a giant cavern, possible warren area
if cave_value <= cave_local_threshold and cave_value > warren_area_threshold then
if warren_area_uninitialized then
nvals_warren_area = mapgen_helper.perlin3d("subterrane:warren_area", emin, emax, np_warren_area) -- determine which areas are spongey with warrens
warren_area_uninitialized = false
end
local warren_area_value = nvals_warren_area[vi]
if warren_area_value > warren_area_variability_threshold then
-- we're in a warren-containing area
if solidify_lava and c_lava_set[data[vi]] then
data[vi] = c_obsidian
end
if warrens_uninitialized then
nvals_warrens = mapgen_helper.perlin3d("subterrane:warrens", emin, emax, np_warrens) --spongey warrens
warrens_uninitialized = false
end
-- we don't want warrens "cutting off" abruptly at the large-scale boundary noise thresholds, so turn these into gradients
-- that can be applied to choke off the warren gradually.
local cave_value_edge = math.min(1, (cave_value - warren_area_threshold) * 20) -- make 0.3 = 0 and 0.25 = 1 to produce a border gradient
local warren_area_value_edge = math.min(1, warren_area_value * 50) -- make 0 = 0 and 0.02 = 1 to produce a border gradient
local warren_value = nvals_warrens[vi]
local warren_local_threshold = warren_threshold + (2 - warren_area_value_edge - cave_value_edge)
if warren_value > warren_local_threshold then
local column_value = 0
if column_def then
if column_points == nil then
column_points = subterrane.get_column_points(emin, emax, column_def)
column_weight = column_def.weight
end
column_value = subterrane.get_column_value({x=x, y=y, z=z}, column_points)
end
if column_value > 0 and column_value + (warren_local_threshold - warren_value) * column_weight > 0 then
if c_warren_column then
if is_ground_content(data[vi]) then
data[vi] = c_warren_column -- add a column node
end
this_node_state = inside_column
end
else
if is_ground_content(data[vi]) then
data[vi] = c_warren_air --hollow it out to make the cave
end
if is_within_current_mapblock then
cavern_data.contains_warren = true
end
this_node_state = inside_warren
end
end
end
end
-- If decorate is defined, we want to track all this stuff
if decorate ~= nil then
local c_current_node = data[vi]
local current_node_is_open = c_current_node == c_air -- mapgen_helper.buildable_to(c_current_node)
if current_node_is_open and this_node_state == inside_ground then
-- we're in a preexisting open space (tunnel).
this_node_state = inside_tunnel
end
if is_within_current_mapblock then
if this_node_state == inside_column then
cavern_data.column_count = cavern_data.column_count + 1
column_nodes[cavern_data.column_count] = vi
elseif previous_node_state ~= this_node_state and previous_node_state ~= inside_column then
if previous_node_state == inside_ground then
if this_node_state == inside_tunnel then
-- we just entered a tunnel from below.
cavern_data.tunnel_floor_count = cavern_data.tunnel_floor_count + 1
tunnel_floor_nodes[cavern_data.tunnel_floor_count] = vi-area.ystride
elseif this_node_state == inside_cavern then
-- we just entered the cavern from below
cavern_data.cavern_floor_count = cavern_data.cavern_floor_count + 1
cavern_floor_nodes[cavern_data.cavern_floor_count] = vi - area.ystride
elseif this_node_state == inside_warren then
-- we just entered the warren from below
cavern_data.warren_floor_count = cavern_data.warren_floor_count + 1
warren_floor_nodes[cavern_data.warren_floor_count] = vi - area.ystride
end
elseif this_node_state == inside_ground then
if previous_node_state == inside_tunnel then
-- we just left a tunnel from below
cavern_data.tunnel_ceiling_count = cavern_data.tunnel_ceiling_count + 1
tunnel_ceiling_nodes[cavern_data.tunnel_ceiling_count] = vi
elseif previous_node_state == inside_cavern then
--we just left the cavern from below
cavern_data.cavern_ceiling_count = cavern_data.cavern_ceiling_count + 1
cavern_ceiling_nodes[cavern_data.cavern_ceiling_count] = vi
elseif previous_node_state == inside_warren then
--we just left the cavern from below
cavern_data.warren_ceiling_count = cavern_data.warren_ceiling_count + 1
warren_ceiling_nodes[cavern_data.warren_ceiling_count] = vi
end
end
end
end
end
end
if decorate then
close_node_arrays() -- inserts nil after the last node so that ipairs will function correctly
decorate(minp, maxp, seed, vm, cavern_data, area, data)
clear_node_arrays() -- if decorate is not defined these arrays will never have anything added to them, so it's safe to not call this in that case
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()
local time_taken = os.clock() - t_start -- how long this chunk took, in seconds
mapgen_helper.record_time(cave_name, time_taken)
end)
end
local last_check = os.clock()
minetest.register_globalstep(function(dtime)
local current_time = os.clock()
local threshold = t_start + 60
if last_check < threshold and current_time > threshold then
-- It's been 60 seconds since we last generated a cavern, release the memory the cavern arrays are being stored in.
initialize_node_arrays()
end
last_check = current_time
end)
minetest.log("info", "[Subterrane] loaded!")
+137
View File
@@ -0,0 +1,137 @@
--These nodes used to be defined by subterrane but were pulled due to not wanting to force all mods that use it to create these nodes.
--For backwards compatibility they can still be defined here, however.
local enable_legacy = minetest.settings:get_bool("subterrane_enable_legacy_dripstone", false)
if enable_legacy then
local modname = minetest.get_current_modname()
local S = minetest.get_translator(modname)
subterrane.register_stalagmite_nodes("subterrane:dry_stal", {
description = S("Dry Dripstone"),
tiles = {
"default_stone.png^[brighten",
},
groups = {cracky = 3, stone = 2},
sounds = default.node_sound_stone_defaults(),
})
local flowstone_drops
if minetest.get_modpath("default") then
flowstone_drops = 'default:cobble'
end
minetest.register_node("subterrane:dry_flowstone", {
description = S("Dry Flowstone"),
tiles = {"default_stone.png^[brighten"},
groups = {cracky = 3, stone = 1},
drop = flowstone_drops,
sounds = default.node_sound_stone_defaults(),
})
-----------------------------------------------
subterrane.register_stalagmite_nodes("subterrane:wet_stal", {
description = S("Wet Dripstone"),
tiles = {
"default_stone.png^[brighten^subterrane_dripstone_streaks.png",
},
groups = {cracky = 3, stone = 2, subterrane_wet_dripstone = 1},
sounds = default.node_sound_stone_defaults(),
}, "subterrane:dry_stal")
minetest.register_node("subterrane:wet_flowstone", {
description = S("Wet Flowstone"),
tiles = {"default_stone.png^[brighten^subterrane_dripstone_streaks.png"},
groups = {cracky = 3, stone = 1, subterrane_wet_dripstone = 1},
drop = flowstone_drops,
sounds = default.node_sound_stone_defaults(),
})
end
function subterrane:small_stalagmite(vi, area, data, param2_data, param2, height, stalagmite_id)
subterrane.stalagmite(vi, area, data, param2_data, param2, height, stalagmite_id)
end
function subterrane:giant_stalagmite(vi, area, data, min_height, max_height, base_material, root_material, shaft_material)
subterrane.big_stalagmite(vi, area, data, min_height, max_height, base_material, root_material, shaft_material)
end
function subterrane:giant_stalactite(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)
end
function subterrane:giant_shroom(vi, area, data, stem_material, cap_material, gill_material, stem_height, cap_radius, ignore_bounds)
subterrane.giant_mushroom(vi, area, data, stem_material, cap_material, gill_material, stem_height, cap_radius, ignore_bounds)
end
--FUNCTIONS--
local grid_size = mapgen_helper.block_size * 4
function subterrane:vertically_consistent_randomp(pos)
local next_seed = math.floor(math.random() * 2^31)
math.randomseed(minetest.hash_node_position({x=pos.x, y=0, z=pos.z}))
local output = math.random()
math.randomseed(next_seed)
return output
end
function subterrane:vertically_consistent_random(vi, area)
local pos = area:position(vi)
return subterrane:vertically_consistent_randomp(pos)
end
subterrane.get_point_heat = function(pos, points)
local heat = 0
for _, point in ipairs(points) do
local axis_point = {x=point.x, y=pos.y, z=point.z}
local radius = point.y
if (pos.x >= axis_point.x-radius and pos.x <= axis_point.x+radius
and pos.z >= axis_point.z-radius and pos.z <= axis_point.z+radius) then
local dist = vector.distance(pos, axis_point)
if dist < radius then
heat = math.max(heat, 1 - dist/radius)
end
end
end
return heat
end
-- Unfortunately there's no easy way to override a single biome, so do it by wiping everything and re-registering
-- Not only that, but the decorations also need to be wiped and re-registered - it appears they keep
-- track of the biome they belong to via an internal ID that gets changed when the biomes
-- are re-registered, resulting in them being left assigned to the wrong biomes.
function subterrane:override_biome(biome_def)
--Minetest 0.5 adds this "unregister biome" method
if minetest.unregister_biome and biome_def.name then
minetest.unregister_biome(biome_def.name)
minetest.register_biome(biome_def)
return
end
local registered_biomes_copy = {}
for old_biome_key, old_biome_def in pairs(minetest.registered_biomes) do
registered_biomes_copy[old_biome_key] = old_biome_def
end
local registered_decorations_copy = {}
for old_decoration_key, old_decoration_def in pairs(minetest.registered_decorations) do
registered_decorations_copy[old_decoration_key] = old_decoration_def
end
registered_biomes_copy[biome_def.name] = biome_def
minetest.clear_registered_decorations()
minetest.clear_registered_biomes()
for biome_key, new_biome_def in pairs(registered_biomes_copy) do
minetest.register_biome(new_biome_def)
end
for decoration_key, new_decoration_def in pairs(registered_decorations_copy) do
minetest.register_decoration(new_decoration_def)
end
end
+19
View File
@@ -0,0 +1,19 @@
Copyright 2017 by FaceDeer, HeroOfTheWinds, Paramat.
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.
@@ -0,0 +1,9 @@
# textdomain: subterrane
### legacy.lua ###
Dry Dripstone=Trockener Tropfstein
Dry Flowstone=Trockener Fließstein
Wet Dripstone=Nasser Tropfstein
Wet Flowstone=Nasser Fließstein
@@ -0,0 +1,13 @@
# textdomain: subterrane
### legacy.lua ###
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Dry Dripstone=Pietra gocciolante secca
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Dry Flowstone=Flowstone secco
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Wet Dripstone=Dripstone bagnato
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Wet Flowstone=Flowstone bagnato
@@ -0,0 +1,9 @@
# textdomain: subterrane
### legacy.lua ###
Dry Dripstone=
Dry Flowstone=
Wet Dripstone=
Wet Flowstone=
+4
View File
@@ -0,0 +1,4 @@
name = subterrane
description = A mapgen helper mod that facilitates the creation of vast underground caverns
optional_depends = default, mcl_core
depends = mapgen_helper
+141
View File
@@ -0,0 +1,141 @@
local sidelen = mapgen_helper.block_size
local snap_to_minp = function(ydepth)
return ydepth - (ydepth+32) % sidelen -- put ydepth at the minp.y of mapblocks
end
function subterrane:register_cave_spawn(cave_layer_def, start_depth)
minetest.register_on_newplayer(function(player)
local ydepth = snap_to_minp(start_depth or cave_layer_def.y_max)
local spawned = false
while spawned ~= true do
spawned = spawnplayer(cave_layer_def, player, ydepth)
ydepth = ydepth - sidelen
if ydepth < cave_layer_def.y_min then
ydepth = snap_to_minp(cave_layer_def.y_max)
end
end
end)
minetest.register_on_respawnplayer(function(player)
local ydepth = snap_to_minp(start_depth or cave_layer_def.y_max)
local spawned = false
while spawned ~= true do
spawned = spawnplayer(cave_layer_def, player, ydepth)
ydepth = ydepth - sidelen
if ydepth < cave_layer_def.y_min then
ydepth = snap_to_minp(cave_layer_def.y_max)
end
end
return true
end)
end
local outside_region = 0
local inside_ground = 1
local inside_cavern = 2
-- Spawn player underground in a giant cavern
function spawnplayer(cave_layer_def, player, ydepth)
subterrane.set_defaults(cave_layer_def)
local YMIN = cave_layer_def.y_min
local YMAX = cave_layer_def.y_max
local BLEND = math.min(cave_layer_def.boundary_blend_range, (YMAX-YMIN)/2)
local TCAVE = cave_layer_def.cave_threshold
local np_cave = cave_layer_def.perlin_cave
local np_wave = cave_layer_def.perlin_wave
local y_blend_min = YMIN + BLEND * 1.5
local y_blend_max = YMAX - BLEND * 1.5
local column_def = cave_layer_def.columns
local double_frequency = cave_layer_def.double_frequency
local options = {}
for chunk = 1, 64 do
minetest.log("info", "[subterrane] searching for spawn "..chunk)
local minp = {x = sidelen * math.random(-32, 32) - 32, z = sidelen * math.random(-32, 32) - 32, y = ydepth}
local maxp = {x = minp.x + sidelen - 1, z = minp.z + sidelen - 1, y = ydepth + sidelen - 1}
local nvals_cave, cave_area = mapgen_helper.perlin3d("subterrane:cave", minp, maxp, np_cave) --cave noise for structure
local nvals_wave = mapgen_helper.perlin3d("subterrane:wave", minp, maxp, np_wave) --wavy structure of cavern ceilings and floors
-- pre-average everything
for vi, value in ipairs(nvals_cave) do
nvals_cave[vi] = (value + nvals_wave[vi])/2
end
local column_points = nil
local column_weight = nil
local previous_y = minp.y
local previous_node_state = outside_region
for vi, x, y, z in cave_area:iterp_yxz(vector.add(minp, 2), vector.subtract(maxp, 2)) do
if y < previous_y then
previous_node_state = outside_region
end
previous_y = y
local cave_local_threshold
if y < y_blend_min then
cave_local_threshold = TCAVE + ((y_blend_min - y) / BLEND) ^ 2
elseif y > y_blend_max then
cave_local_threshold = TCAVE + ((y - y_blend_max) / BLEND) ^ 2
else
cave_local_threshold = TCAVE
end
local cave_value = nvals_cave[vi]
if double_frequency then
cave_value = math.abs(cave_value)
end
-- inside a giant cavern
if cave_value > cave_local_threshold then
local column_value = 0
local inside_column = false
if column_def then
if column_points == nil then
column_points = subterrane.get_column_points(minp, maxp, column_def)
column_weight = column_def.weight
end
column_value = subterrane.get_column_value({x=x, y=y, z=z}, column_points)
end
inside_column = column_value > 0 and cave_value - column_value * column_weight < cave_local_threshold
if previous_node_state == inside_ground and not inside_column then
-- we just entered the cavern from below. Do a quick check for head space.
local val_above = nvals_cave[vi+cave_area.ystride]
if double_frequency then
val_above = math.abs(val_above)
end
if val_above > cave_local_threshold then
table.insert(options, {x=x, y=y+1, z=z})
end
end
if not inside_column then
previous_node_state = inside_cavern
else
previous_node_state = inside_ground
end
else
previous_node_state = inside_ground
end
end
if table.getn(options) > 20 then -- minimum 20 to ensure the player is put in a place with a modicum of size
local choice = math.random(1, table.getn(options))
local spawnpoint = options[ choice ]
minetest.log("action", "[subterrane] spawning player " .. minetest.pos_to_string(spawnpoint))
player:setpos(spawnpoint)
return true
end
end
return false
end
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

@@ -0,0 +1,3 @@
subterrane_enable_legacy_dripstone (Adds old dripstone node definitions) bool false
#Use this mode with singlenode mapgen to produce a large-scale map of a particular world's caverns
subterrane_enable_singlenode_mapping_mode (Fills cavern volume with stone and warrens with desertstone) bool false
+65
View File
@@ -0,0 +1,65 @@
-- If pos is located inside a cavern volume, returns the list of cavern definitions that
-- are responsible for that cavern volume. If not inside a cavern volume returns an empty list.
-- This is a somewhat expensive function, take care not to use it more than necessary
subterrane.is_in_cavern = function(pos)
local results = {}
for _, cave_layer_def in pairs(subterrane.registered_layers) do
local YMIN = cave_layer_def.y_min
local YMAX = cave_layer_def.y_max
local y = pos.y
if y <= YMAX and y >= YMIN then
local block_size = mapgen_helper.block_size
local BLEND = math.min(cave_layer_def.boundary_blend_range, (YMAX-YMIN)/2)
local TCAVE = cave_layer_def.cave_threshold
local np_cave = cave_layer_def.perlin_cave
local np_wave = cave_layer_def.perlin_wave
local y_blend_min = YMIN + BLEND * 1.5
local y_blend_max = YMAX - BLEND * 1.5
local nval_cave = minetest.get_perlin(np_cave):get_3d(pos) --cave noise for structure
local nval_wave = minetest.get_perlin(np_wave):get_3d(pos) --wavy structure of cavern ceilings and floors
nval_cave = (nval_cave + nval_wave)/2
local cave_local_threshold
if y < y_blend_min then
cave_local_threshold = TCAVE + ((y_blend_min - y) / BLEND) ^ 2
elseif y > y_blend_max then
cave_local_threshold = TCAVE + ((y - y_blend_max) / BLEND) ^ 2
else
cave_local_threshold = TCAVE
end
if cave_layer_def.double_frequency and nval_cave < 0 then
nval_cave = -nval_cave
end
if nval_cave > cave_local_threshold then
table.insert(results, cave_layer_def)
end
end
end
return results
end
-- returns the value of the cavern field at a particular location for a particular cavern definition.
subterrane.get_cavern_value = function(name, pos)
local cave_layer_def = subterrane.registered_layers[name]
local YMIN = cave_layer_def.y_min
local YMAX = cave_layer_def.y_max
local y = pos.y
if y > YMAX or y < YMIN then
return nil
end
local block_size = mapgen_helper.block_size
local BLEND = math.min(cave_layer_def.boundary_blend_range, (YMAX-YMIN)/2)
local TCAVE = cave_layer_def.cave_threshold
local np_cave = cave_layer_def.perlin_cave
local np_wave = cave_layer_def.perlin_wave
local y_blend_min = YMIN + BLEND * 1.5
local y_blend_max = YMAX - BLEND * 1.5
local nval_cave = minetest.get_perlin(np_cave):get_3d(pos) --cave noise for structure
local nval_wave = minetest.get_perlin(np_wave):get_3d(pos) --wavy structure of cavern ceilings and floors
nval_cave = (nval_cave + nval_wave)/2
return nval_cave
end
Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

+22
View File
@@ -0,0 +1,22 @@
#! /bin/bash
# To create a new translation:
# msginit --locale=ll_CC -o locale/ll_CC.po -i locale/template.pot
cd "$(dirname "${BASH_SOURCE[0]}")/..";
# Extract translatable strings.
xgettext --from-code=UTF-8 \
--keyword=S \
--keyword=NS:1,2 \
--keyword=N_ \
--add-comments='Translators:' \
--add-location=file \
-o locale/template.pot \
$(find . -name '*.lua')
# Update translations.
find locale -name '*.po' | while read -r file; do
echo $file
msgmerge --update $file locale/template.pot;
done