ns_cavegen/init.lua

1349 lines
40 KiB
Lua
Raw Normal View History

2024-04-29 15:31:03 +00:00
-- DEVELOPMENT ONLY: Timer module for debugging performance
local modpath = minetest.get_modpath(minetest.get_current_modname())
dofile(modpath.."/lua/timer.lua")
-- DEVELOPMENT ONLY: End of timer module
-- Local variables used for programming
local internal =
2024-04-29 15:33:46 +00:00
{ a = nil
2024-04-17 21:08:44 +00:00
-- Average size of a cave biome
, biome_size = { x = 50, y = 50, z = 50 }
-- Average blob size of connectivity noise params
, cnct_blob = { x = 250, y = 100, z = 250 }
-- The margin that a voxelmanip gives around the chunk being generated
, mapgen_buffer = 16
2024-04-29 15:33:46 +00:00
-- Enum type used during cave generation for classifying different types
-- of cave nodes. Do not touch unless you're changing the code
, node_types =
{ stick_edge = 0
, air = 1
, stone = 2
, floor = 3
, wall = 4
, ceiling = 5
, floor_deco = 6
}
-- Point that can be used for generating Voronoi graphs
-- The number can be anything, as long as it reasonably far away from
-- the range 0 - 100
, outlandish_point = -1e3
-- Random seeds used to influence noise
, seeds =
{ connectivity = 297948
, heat = 320523
, humidity = 9923473
, verticality = 35644
}
-- Size of blocks being combined to simplify cave shape generation
-- Higher values means better performance, but lower detail and variations
, shape_cluster = { x = 32, y = 32, z = 32 }
-- Average size of a shape biome
, shape_size = { x = 50, y = 50, z = 50 }
-- Average blob size of verticality noise params
, vrtcl_blob = { x = 100, y = 250, z = 100 }
-- If another mod doesn't override this value, we will assume that this is
-- the world's depth.
, world_depth = -60
-- Lower bound for the entire world
, world_minp = { x = -1e6, y = -1e6, z = -1e6 }
-- Upper bound for cave generation
, world_maxp = { x = 1e6, y = 1e6, z = 1e6 }
}
-------------------------------------------------------------------------------
-- Flat3dArray
-------------------------------------------------------------------------------
-- The Flat3dArray helps translate, transform and read data from different
-- 1-dimensional flat tables that all represent some 3-dimensional set.
local Flat3dArray = {}
Flat3dArray.__index = Flat3dArray
2024-04-17 21:08:44 +00:00
-- Global variable that can be used by other mods
-- Please see API.md for reference
noordstar_caves =
{ a = nil
2024-04-29 15:33:46 +00:00
-- The `cave_vastness` is a variable that determines the size of caves.
-- The function takes in an x, y and z variable, and returns a number
-- between 0 and 1.
-- Low numbers mean very rare and tiny caves, while high numbers mean
-- massive caves, with massive open spaces.
-- If you wish to overwrite this function, it is good to keep in mind to:
-- - Make sure that the output changes VERY SLOWLY over time
-- - Keep the function performant as it will be executed many, many times
, cave_vastness = function(pos)
return pos.y / internal.world_depth
end
-- A public list of all registered biomes
, registered_biomes = {}
2024-04-30 10:13:40 +00:00
-- A public list of all registered decorations
, registered_decorations = {}
-- A public list of all registered shapes
, registered_shapes = {}
}
-- Remove all registered biomes and start with a clean slate
function noordstar_caves.clear_registered_biomes()
noordstar_caves.registered_biomes = {}
end
2024-04-30 10:13:40 +00:00
-- Remove all registered decorations and start with a clean slate
function noordstar_caves.clear_registered_decoration()
noordstar_caves.registered_decorations = {}
end
-- Remove all registered shapes and start with a clean slate
function noordstar_caves.clear_registered_shapes()
noordstar_caves.registered_shapes = {}
end
-- Register a new cave biome
function noordstar_caves.register_biome(def)
local d = internal.clean_biome_def(def)
if d then
noordstar_caves.registered_biomes[d.name] = d
end
end
2024-04-30 10:13:40 +00:00
-- Register a new cave decoration
function noordstar_caves.register_decoration(def)
local d = internal.clean_deco_def(def)
if d then
table.insert(noordstar_caves.registered_decorations, d)
end
end
-- Register a new cave shape
function noordstar_caves.register_shape(def)
local d = internal.clean_shape_def(def)
if d then
noordstar_caves.registered_shapes[d.name] = d
end
end
-- Override the world's depth
function noordstar_caves.set_world_depth(h)
internal.world_depth = h
end
noordstar_caves.set_world_depth(internal.world_depth)
-- Remove a specific registered cave biome
function noordstar_caves.unregister_biome(name)
noordstar_caves.registered_biomes[name] = nil
end
-- Remove a specific registered cave shape
function noordstar_caves.unregister_shape(name)
noordstar_caves.registered_shapes[name] = nil
end
2024-04-29 15:33:46 +00:00
-- Calculate the distance of a given biome to a given point
function internal.biome_def_distance(def, heat, humidity)
local dx = math.abs(humidity - def.humidity_point)
local dy = math.abs(heat - def.heat_point)
return dx^2 + dy^2
end
-- While a public `noordstar_caves.cave_vastness` function is exposed,
-- this mod uses an improved internal function to keep the edges pleasant
-- and to avoid potential errors.
function internal.cave_vastness(pos)
-- Calculate value and put it in bounds
local v = noordstar_caves.cave_vastness(pos)
v = math.min(v, 1)
v = math.max(v, 0)
local d = internal.world_depth
if pos.y > d + 20 then
return v
elseif pos.y > d + 5 then
return v * (pos.y - (d + 5)) / 15
else
return 0
end
end
-- Clean the user input on a biome definition before inserting it.
function internal.clean_biome_def(def)
if type(def.name) ~= "string" then
return nil
end
if type(def.heat_point) ~= "number" then
return nil
end
if type(def.humidity_point) ~= "number" then
return nil
end
local d = {
name = def.name,
heat_point = def.heat_point,
humidity_point = def.humidity_point,
}
-- Position
if type(def.min_pos) == "table" then
d.minp = {
x = def.min_pos.x or internal.world_minp.x,
y = def.min_pos.y or internal.world_minp.y,
z = def.min_pos.z or internal.world_minp.z,
}
elseif type(def.y_min) == "number" then
d.minp = {
x = internal.world_minp.x,
y = def.y_min,
z = internal.world_minp.z,
}
else
d.minp = {
x = internal.world_minp.x,
y = internal.world_minp.y,
z = internal.world_minp.z,
}
end
if type(def.max_pos) == "table" then
d.maxp = {
x = def.max_pos.x or internal.world_maxp.x,
y = def.max_pos.y or internal.world_maxp.y,
z = def.max_pos.z or internal.world_maxp.z,
}
elseif type(def.y_max) == "number" then
d.maxp = {
x = internal.world_maxp.x,
y = def.y_max,
z = internal.world_maxp.z,
}
else
d.maxp = {
x = internal.world_maxp.x,
y = internal.world_maxp.y,
z = internal.world_maxp.z,
}
end
-- Optional nodes
if type(def.node_dust) == "string" then
d.node_dust = def.node_dust
end
if type(def.node_floor) == "string" then
d.node_floor = def.node_floor
end
if type(def.node_wall) == "string" then
d.node_wall = def.node_wall
end
if type(def.node_roof) == "string" then
d.node_roof = def.node_roof
end
if type(def.node_air) == "string" then
d.node_air = def.node_air
end
if type(def.node_shell) == "string" then
d.node_shell = def.node_shell
end
if type(def.depth_shell) == "number" then
d.depth_shell = def.depth_shell
else
d.depth_shell = 0
end
2024-04-17 21:08:44 +00:00
return d
end
2024-04-30 10:13:40 +00:00
-- Clean the user input on a decoration definition before inserting it.
function internal.clean_deco_def(def)
if def.deco_type ~= "simple" and def.deco_type ~= "schematic" then
return nil
end
if def.deco_type == "simple" and type(def.decoration) ~= "string" then
return nil
end
if def.deco_type == "schematic" then
if type(def.schematic) == "string" then
elseif type(def.schematic) == "table" then
else
return nil
end
end
if type(def.fill_ratio) ~= "number" then
return nil
end
local d = {
deco_type = def.deco_type,
decoration = def.decoration,
schematic = def.schematic,
fill_ratio = def.fill_ratio,
}
if def.place_on == "floor" or def.place_on == "ceiling" then
d.place_on = def.place_on
else
d.place_on = "floor"
end
local function place(key, t, default)
if type(def[key]) == t then
d[key] = def[key]
else
d[key] = default
end
end
place("y_min", "number", internal.world_minp.y)
place("y_max", "number", internal.world_maxp.y)
place("height_max", "number", 0)
place("height", "number", 1)
place("place_offset_y", "number", 0)
place("replacements", "table", {})
place("flags", "string", "")
place("rotation", "string", "0")
d.height = math.max(d.height , 0)
d.height = math.min(d.height , 16)
d.height_max = math.max(d.height_max, 0)
d.height_max = math.min(d.height_max, 16)
return d
end
-- Clean the user input on a shape definition before inserting it.
function internal.clean_shape_def(def)
if type(def.name) ~= "string" then
return nil
end
if type(def.connectivity_point) ~= "number" then
return nil
end
if type(def.verticality_point) ~= "number" then
return nil
end
local d = {
name = def.name,
connectivity_point = def.connectivity_point,
verticality_point = def.verticality_point,
y_min = def.y_min or internal.world_minp.y,
y_max = def.y_max or internal.world_maxp.y,
}
if type(def.noise_params) == "table" then
d.noise_params = def.noise_params
end
local func = function(pos, v) return v end
if type(def.func) == "function" then
func = def.func
end
d.func = internal.enhanced_shape_func(def.name, func)
return d
end
2024-04-29 16:19:23 +00:00
-- Get the most nearby cave biome
2024-04-30 10:15:58 +00:00
function internal.closest_cave_biome(heat, humidity, pos)
local biome = internal.default_biome()
local d = internal.biome_def_distance(biome, heat, humidity)
2024-04-29 16:19:23 +00:00
for _, def in pairs(noordstar_caves.registered_biomes) do
2024-04-30 10:15:58 +00:00
if internal.is_valid_pos(pos, def.minp, def.maxp) then
local new_d = internal.biome_def_distance(def, heat, humidity)
if new_d <= d then
biome = def
d = new_d
end
end
end
2024-04-18 08:49:45 +00:00
2024-04-29 16:19:23 +00:00
return biome
end
-- Get the most nearby cave shape
2024-04-30 10:15:58 +00:00
function internal.closest_cave_shape(cnct, vrtcl, y)
local shape = internal.default_shape()
local d = internal.shape_def_distance(shape, cnct, vrtcl)
for key, def in pairs(noordstar_caves.registered_shapes) do
2024-04-30 10:15:58 +00:00
if def.y_min <= y and y <= def.y_max then
local new_d = internal.shape_def_distance(def, cnct, vrtcl)
if new_d <= d then
shape = def
d = new_d
end
end
end
return shape
end
-- Get connectivity noise params
function internal.connectivity_noise_params()
local factor = math.max(
math.abs(#noordstar_caves.registered_shapes) ^ 0.5
, 1
)
return {
offset = 50,
scale = 50,
spread =
internal.reduced_shape_pos(
{ x = factor * internal.cnct_blob.x
, y = factor * internal.cnct_blob.y
, z = factor * internal.cnct_blob.z
}
),
seed = internal.seeds.connectivity,
octaves = 2,
persistence = 0.2,
lacunarity = 2.0,
flags = "eased"
}
end
-- Get a default cave biome in case no biomes are registered
function internal.default_biome()
return internal.clean_biome_def(
{ name = "noordstar_caves:default_biome"
, heat_point = internal.outlandish_point
, humidity_point = internal.outlandish_point
}
)
end
-- Get a default cave shape in case no shapes are registered
function internal.default_shape()
return internal.clean_shape_def(
{ name = "noordstar_caves:none"
, connectivity_point = internal.outlandish_point
, verticality_point = internal.outlandish_point
, func = function (pos, v) return 0 end
}
)
end
-- This library decides not to trust that the user is giving a proper input
-- on its function field. To help the developer insert a proper function, a few
-- error messages are inserted to give the developer more insight in how their
-- function behaves.
function internal.enhanced_shape_func(name, func)
if type(func) ~= "function" then
error("Expected function, found type `" .. type(func) .. "`")
end
return function (pos, n)
local out = func(pos, n)
if type(out) == "number" then
return out
elseif n == nil then
error(
table.concat(
{ "Shape"
, name
, "function must return a number."
, "Your function's input `n` was nil."
, "Perhaps your `noise_params` field is invalid?"
}
, " "
)
)
else
error(
table.concat(
{ "Shape"
, name
, "function must return a number."
}
, " "
)
)
end
end
end
-- Get a Flat3dArray showing whether nodes are cave bools
function internal.flat_from_cave_bools(minp, maxp)
-- Calculate connectivity and verticality for each point
local connectivity = internal.flat_from_noise_params(
internal.connectivity_noise_params(),
internal.reduced_shape_pos(minp),
internal.reduced_shape_pos(maxp)
)
local verticality = internal.flat_from_noise_params(
internal.verticality_noise_params(),
internal.reduced_shape_pos(minp),
internal.reduced_shape_pos(maxp)
)
-- Using the calculated noise, determine the cave shape per shape chunk
local reduced = Flat3dArray:from_func(
internal.reduced_shape_pos(minp),
internal.reduced_shape_pos(maxp),
function (i, pos)
-- DEVELOPMENT ONLY: Unittest
if connectivity:pos_to_index(pos) ~= i then
error("Connectivity index doesn't match local index")
end
if verticality:pos_to_index(pos) ~= i then
error("Verticality index doesn't match local index")
end
-- DEVELOPMENT ONLY: End unittest
local cnct = connectivity:get_index(i)
local vrtcl = verticality:get_index(i)
2024-04-30 10:15:58 +00:00
local def = internal.closest_cave_shape(cnct, vrtcl, pos.y)
return def.name
end)
-- Calculate noise for each shape
local noise = {}
for key, shape in pairs(noordstar_caves.registered_shapes) do
noise[key] = internal.flat_from_shape_def(shape, minp, maxp)
end
2024-04-29 16:19:23 +00:00
local default = internal.default_shape()
noise[default.name] = internal.flat_from_shape_def(default, minp, maxp)
-- Create a flat array of bools
local bools = Flat3dArray:from_func(minp, maxp, function (i, pos)
local key = reduced:get_pos(internal.reduced_shape_pos(pos))
2024-04-29 16:19:23 +00:00
if noise[key] == nil then
error("Key " .. key .. " gave no value on noise")
end
local n = noise[key]:get_index(i)
local v = internal.cave_vastness(pos)
return internal.is_cave_node(n, v)
end)
return bools
end
2024-04-29 15:33:46 +00:00
function internal.flat_from_node_types(bools, minp, maxp)
local nt = internal.node_types
-- local bools = internal.flat_from_cave_bools(minp, maxp)
local node_types = Flat3dArray:from_func(minp, maxp, function (i, pos)
-- Simplify calculation by ignoring edges
if pos.x == minp.x or pos.x == maxp.x then
return nt.stick_edge
elseif pos.y == minp.y or pos.y == maxp.y then
return nt.stick_edge
elseif pos.z == maxp.z or pos.z == minp.z then
return nt.stick_edge
end
if bools:get_index(i) then
-- PART OF CAVE
if bools:get_index(i + bools.down) == false then
-- Block is right on the floor
return nt.floor_deco
end
return nt.air
else
-- NOT PART OF CAVE
-- Floor
if bools:get_index(i + bools.up) then
return nt.floor
end
-- Ceiling
if bools:get_index(i + bools.down) then
return nt.ceiling
end
-- Walls
if bools:get_index(i + bools.north) then
return nt.wall
end
if bools:get_index(i + bools.east) then
return nt.wall
end
if bools:get_index(i + bools.south) then
return nt.wall
end
if bools:get_index(i + bools.west) then
return nt.wall
end
return nt.stone
end
end)
return node_types
end
-- Get a Flat3dArray using noise_params
function internal.flat_from_noise_params(noise_params, minp, maxp)
local nx = maxp.x - minp.x + 1
local ny = maxp.y - minp.y + 1
local nz = maxp.z - minp.z + 1
local buffer = {}
local p = PerlinNoiseMap(noise_params, { x = nx, y = ny, z = nz })
if nz == 1 then
p:get_2d_map_flat(minp, buffer)
else
p:get_3d_map_flat(minp, buffer)
end
return Flat3dArray:new(minp, maxp, buffer)
end
-- Get a Flat3dArray about shape noise
-- This function is defined differently from other definitions because the
-- noise_params are optional in the shape definition
function internal.flat_from_shape_def(def, minp, maxp)
local noise_flat_map = {}
2024-04-18 08:49:45 +00:00
local nx = maxp.x - minp.x + 1
local ny = maxp.y - minp.y + 1
local nz = maxp.z - minp.z + 1
2024-04-18 08:49:45 +00:00
if def.noise_params then
local p = PerlinNoiseMap(def.noise_params, { x = nx, y = ny, z = nz })
if nz == 1 then
p:get_2d_map_flat(minp, noise_flat_map)
else
p:get_3d_map_flat(minp, noise_flat_map)
end
end
internal.iter_3d_area(minp, maxp, function (i, pos)
noise_flat_map[i] = def.func(pos, noise_flat_map[i])
end)
2024-04-29 16:19:23 +00:00
return Flat3dArray:new(minp, maxp, noise_flat_map)
end
-- Convert 3d relative coordinates to an index on a flat array
function internal.from_3d_to_flat(dx, dy, dz, nx, ny)
return (nx * ny * dz) + (nx * dy) + dx + 1
end
2024-04-29 15:33:46 +00:00
-- Generate caves given a voxelmanip array
function internal.generate_caves(data, minp, maxp)
-- Increased voxelmanip size
local vminp =
{ x = minp.x - internal.mapgen_buffer
, y = minp.y - internal.mapgen_buffer
, z = minp.z - internal.mapgen_buffer
}
local vmaxp =
{ x = maxp.x + internal.mapgen_buffer
, y = maxp.y + internal.mapgen_buffer
, z = maxp.z + internal.mapgen_buffer
}
-- Increased node type size to account for unknown edges
local bminp =
{ x = minp.x - 1
, y = minp.y - 1
, z = minp.z - 1
}
local bmaxp =
{ x = maxp.x + 1
, y = maxp.y + 1
, z = maxp.z + 1
}
-- Load data
local vmanip = Flat3dArray:new(vminp, vmaxp, data)
-- Get cave bools
local bools = internal.flat_from_cave_bools(bminp, bmaxp)
-- Get node types
local nts = internal.flat_from_node_types(bools, bminp, bmaxp)
-- Calculate biome heat & humidity
local heat_points = internal.flat_from_noise_params(
internal.heat_noise_params(), bminp, bmaxp
)
local humidity_points = internal.flat_from_noise_params(
internal.humidity_noise_params(), bminp, bmaxp
)
2024-04-29 16:26:59 +00:00
local air = minetest.get_content_id("air")
local schems = {}
2024-04-29 16:26:59 +00:00
2024-04-29 15:33:46 +00:00
-- Place blocks where necessary
internal.iter_3d_area(bminp, bmaxp, function (i, pos)
2024-04-29 16:26:59 +00:00
local vi = vmanip:pos_to_index(pos)
2024-04-29 15:33:46 +00:00
local function place(name)
2024-04-29 16:26:59 +00:00
if vmanip:get_index(vi) == air then
elseif type(name) == "string" then
vmanip:set_index(vi, minetest.get_content_id(name))
2024-04-29 15:33:46 +00:00
elseif type(name) == "nil" then
else
error("Inserted invalid type " .. type(name) .. " into voxelmanip array")
end
end
local nt = nts:get_index(i)
if nt == internal.node_types.stone then
elseif nt == internal.node_types.stick_edge then
elseif nt == internal.node_types.air then
place("air")
else
-- Find appropriate biome
local heat = heat_points:get_index(i)
local humidity = humidity_points:get_index(i)
2024-04-30 10:15:58 +00:00
local def = internal.closest_cave_biome(heat, humidity, pos)
2024-04-29 15:33:46 +00:00
if nt == internal.node_types.floor then
place(def.node_floor)
elseif nt == internal.node_types.wall then
place(def.node_wall)
elseif nt == internal.node_types.ceiling then
place(def.node_roof)
elseif nt == internal.node_types.floor_deco then
local schem_placed = false
if not schem_placed then
-- Prevent decorations from spawning in the air
if vmanip:get_index(vi) == air then
schem_placed = true
end
end
if not schem_placed then
for _, deco in ipairs(noordstar_caves.registered_decorations) do
if deco.place_on ~= "floor" then
elseif math.random() > deco.fill_ratio then
elseif not internal.is_deco_biome(deco, def.name) then
else
table.insert(schems, { pos = pos, deco = deco })
schem_placed = true
break
end
end
end
if not schem_placed then
place(def.node_dust or "air")
end
2024-04-29 15:33:46 +00:00
else
error(
table.concat(
{ "Found unknown node type "
, nt
, " (type "
, type(nt)
, ")"
}
, ""
)
)
end
end
end)
return vmanip.arr, schems
2024-04-29 15:33:46 +00:00
end
-- Get the noise params for the cave biome temperature.
function internal.heat_noise_params()
2024-04-30 10:13:40 +00:00
return {
offset = 50,
scale = 50,
spread = internal.biome_size,
seed = internal.seeds.heat,
octaves = 2,
persistence = 0.1,
lacunarity = 2.0,
flags = ""
}
end
-- Get the noise params for the cave biome humidity.
function internal.humidity_noise_params()
return {
offset = 50,
scale = 50,
spread = internal.biome_size,
seed = internal.seeds.humidity,
octaves = 2,
persistence = 0.1,
lacunarity = 2.0,
flags = ""
}
end
-- Return whether a node is part of a cave
function internal.is_cave_node(noise, vastness)
return noise > 1 - vastness
end
-- Return whether a given decoration definition can spawn in a given biome name
function internal.is_deco_biome(def, biome_name)
if type(def.biomes) == "nil" then
return true
elseif type(def.biomes) == "string" then
return def.biomes == biome_name
elseif type(def.biomes) == "table" then
for _, name in pairs(def.biomes) do
if name == biome_name then
return true
end
end
end
return false
end
-- Return a bool whether a position is within a given volume
function internal.is_valid_pos(pos, minp, maxp)
if minp.x <= pos.x and pos.x <= maxp.x then
if minp.y <= pos.y and pos.y <= maxp.y then
if minp.z <= pos.z and pos.z <= maxp.z then
return true
end
end
end
return false
end
-- Iterate over a 3d area, both indexing by the index and the ansolute position
function internal.iter_3d_area(minp, maxp, callback)
local nx = maxp.x - minp.x + 1
local ny = maxp.y - minp.y + 1
-- local nz = maxp.z - minp.z + 1
local i = 0
for z = minp.z, maxp.z, 1 do
for y = minp.y, maxp.y, 1 do
for x = minp.x, maxp.x, 1 do
local pos = { x = x, y = y, z = z }
i = i + 1
-- DEVELOPMENT ONLY: This function only serves as a unittest
-- DEVELOPMENT ONLY: to verify that the iteration across a 3D
-- DEVELOPMENT ONLY: table works appropriately.
-- DEVELOPMENT ONLY: This calculation is highly inefficient and
-- DEVELOPMENT ONLY: unnecessary so it is highly recommended
-- DEVELOPMENT ONLY: to remove this code in production.
local dx = x - minp.x
local dy = y - minp.y
local dz = z - minp.z
local si = internal.from_3d_to_flat(dx, dy, dz, nx, ny)
if i ~= si then
error(
table.concat(
{ "Expected position"
, internal.pos_to_str(pos)
, "to correspond to index"
, si
, "but our iteration arrived at index"
, i
}
, " "
)
)
end
-- DEVELOPMENT ONLY:
-- DEVELOPMENT ONLY: This is the bottom of the unittest.
-- DEVELOPMENT ONLY: Please remove this code in a future commit
-- DEVELOPMENT ONLY: before the mod is assumed to be
-- DEVELOPMENT ONLY: production-ready.
-- DEVELOPMENT ONLY:
callback(i, pos)
end
end
end
end
2024-04-29 15:33:46 +00:00
-- -- Log text to the Minetest chat
-- function internal.log(text)
-- minetest.chat_send_all(os.time() .. " - " .. text)
-- end
-- Place a table full of schematics into the world
function internal.place_schematics(schems)
for _, schem in ipairs(schems) do
local pos = schem.pos
local deco = schem.deco
if deco.deco_type == "simple" then
local h_min = deco.height
local h_max = math.max(deco.height_max, deco.height)
local dy = deco.place_offset_y - 1
if deco.place_on == "floor" then
for y = 1, math.random(h_min, h_max), 1 do
minetest.set_node(
{ x = pos.x, y = pos.y + y + dy, z = pos.z },
{ name = deco.decoration }
)
end
elseif deco.place_on == "ceiling" then
for y = 1, math.random(h_min, h_max), 1 do
minetest.set_node(
{ x = pos.x, y = pos.y - y - dy, z = pos.z },
{ name = deco.decoration }
)
end
else
error("Unknown place_on parameter `" .. deco.place_on .. "` for simple decoration!")
end
elseif deco.deco_type == "schematic" then
local name = deco.schematic
local force_placement = true
if string.find("force_placement", deco.flags) then
force_placement = true
end
if deco.place_on == "floor" then
local n = minetest.place_schematic(
pos, name, deco.rotation, deco.replacements,
force_placement, deco.flags
)
if type(n) == "nil" then
minetest.chat_send_all("Squeak could not load!")
else
minetest.chat_send_all("Placed a Squeak!")
if not tpd_yet then
minetest.get_player_by_name("singleplayer"):move_to(pos)
tpd_yet = true
minetest.chat_send_all(internal.pos_to_str(pos))
end
end
elseif deco.place_on == "ceiling" then
-- Aim to place schematic against the ceiling, not through it
local h = minetest.read_schematic(name).size.y
local c_pos = { x = pos.x, y = pos.y - h, z = pos.z }
if string.find("place_center_y", deco.flags) then
c_pos = pos
end
minetest.place_schematic(
pos, name, deco.rotation, deco.replacements,
force_placement, deco.flags
)
else
error("Unknown place_on parameter `" .. deco.place_on .. "` for simple decoration!")
end
else
error("Unknown decoration type `" .. deco.deco_type .. "`")
end
end
end
-- Helper function to convert a set of coordinates to a readable string
function internal.pos_to_str(pos)
return "(" .. pos.x .. ", " .. pos.y .. ", " .. pos.z .. " )"
end
-- Get a reduced position based on the chunks
function internal.reduced_shape_pos(pos)
return
{ x = math.floor(pos.x / internal.shape_cluster.x)
, y = math.floor(pos.y / internal.shape_cluster.y)
, z = math.floor(pos.z / internal.shape_cluster.z)
}
end
-- Calculate the relative index change to move downwards one position
function internal.relative_get_down(nx, ny)
return internal.relative_index_diff(0, -1, 0, nx, ny)
end
-- Calculate the relative index change to move eastwards one position
function internal.relative_get_east(nx, ny)
return internal.relative_index_diff(1, 0, 0, nx, ny)
end
-- Calculate the relative index change to move northwards one position
function internal.relative_get_north(nx, ny)
return internal.relative_index_diff(0, 0, 1, nx, ny)
end
-- Calculate the relative index change to move southwards one position
function internal.relative_get_south(nx, ny)
return internal.relative_index_diff(0, 0, -1, nx, ny)
end
-- Calculate the relative index change to move upwards one position
function internal.relative_get_up(nx, ny)
return internal.relative_index_diff(0, 1, 0, nx, ny)
end
-- Calculate the relative index change to move westwards one position
function internal.relative_get_west(nx, ny)
return internal.relative_index_diff(-1, 0, 0, nx, ny)
end
-- Calculate the difference in index to relative coordinates.
-- Used to speed up index lookup on flat 3d arrays.
function internal.relative_index_diff(dx, dy, dz, nx, ny)
return
( internal.from_3d_to_flat(dx, dy, dz, nx, ny)
- internal.from_3d_to_flat( 0, 0, 0, nx, ny)
)
end
-- Calculate the distance of a given shape to a given point
function internal.shape_def_distance(def, cnct, vrtcl)
local dx = math.abs(cnct - def.connectivity_point)
local dy = math.abs(vrtcl - def.verticality_point)
return dx^2 + dy^2
end
-- Get verticality noise params
function internal.verticality_noise_params()
local factor = math.max(
math.abs(#noordstar_caves.registered_shapes) ^ 0.5
, 1
)
return {
offset = 50,
scale = 50,
spread =
internal.reduced_shape_pos(
{ x = factor * internal.vrtcl_blob.x
, y = factor * internal.vrtcl_blob.y
, z = factor * internal.vrtcl_blob.z
}
),
seed = internal.seeds.verticality,
octaves = 2,
persistence = 0.2,
lacunarity = 2.0,
flags = "eased"
}
end
-- Determine whether a position is within given bounds
function internal.within_bounds(pos, minp, maxp)
if minp.x <= pos.x and pos.x <= maxp.x then
if minp.y <= pos.y and pos.y <= maxp.y then
if minp.z <= pos.z and pos.z <= maxp.z then
return true
end
end
end
return false
end
2024-04-29 15:33:46 +00:00
-- Create a new flat 3d array based on a function callback that gets the index
-- and position as inputs
function Flat3dArray:from_func(minp, maxp, callback)
local arr = {}
internal.iter_3d_area(minp, maxp, function (i, pos)
arr[i] = callback(i, pos)
end)
return self:new(minp, maxp, arr)
end
-- Get the Flat3dArray's value on the i'th index
function Flat3dArray:get_index(i)
local out = self.arr[i]
if out == nil then
error(
"Index " .. i .. " not found in array of length " .. #self.arr
)
end
return out
end
-- Get a value at a given position
function Flat3dArray:get_pos(pos)
-- pos_to_index already validates so there's no need to validate here
return self:get_index(self:pos_to_index(pos))
end
-- Create a new Flat3dArray instance
function Flat3dArray:new(minp, maxp, arr)
local instance = {}
setmetatable(instance, Flat3dArray)
local nx = maxp.x - minp.x + 1
local ny = maxp.y - minp.y + 1
local nz = maxp.z - minp.z + 1
if #arr ~= nx * ny * nz then
error(
"Input array doesn't match dimension lengths: " .. nx .. " x " ..
ny .. " x " .. nz .. " = " .. (nx*ny*nz) .. ", but found " .. #arr
)
end
-- Variables
instance.nx = nx
instance.ny = ny
instance.nz = nz
instance.minp = minp
instance.maxp = maxp
instance.arr = arr
-- Constants
instance.up = internal.relative_get_up(nx, ny)
instance.down = internal.relative_get_down(nx, ny)
instance.north = internal.relative_get_north(nx, ny)
instance.east = internal.relative_get_east(nx, ny)
instance.south = internal.relative_get_south(nx, ny)
instance.west = internal.relative_get_west(nx, ny)
return instance
end
-- Convert a position to an index
function Flat3dArray:pos_to_index(pos)
self:validate_pos(pos)
local dx = pos.x - self.minp.x
local dy = pos.y - self.minp.y
local dz = pos.z - self.minp.z
return internal.from_3d_to_flat(dx, dy, dz, self.nx, self.ny)
end
-- Override a value at a given index
function Flat3dArray:set_index(i, val)
self:validate_index(i)
self.arr[i] = val
end
-- Validate an index to be in the Flat3dArray
function Flat3dArray:validate_index(i)
if i < 1 or i > #self.arr then
error(
table.concat(
{ "Index"
, i
, "is not in range 1 -"
, #self.arr
}
, " "
)
)
end
end
-- Validate a position to be in the Flat3dArray
function Flat3dArray:validate_pos(pos)
if not internal.is_valid_pos(pos, self.minp, self.maxp) then
error(
table.concat(
{ "Position"
, internal.pos_to_str(pos)
, "is not within minp ="
, internal.pos_to_str(self.minp)
, "and maxp ="
, internal.pos_to_str(self.maxp)
}
, " "
)
)
end
end
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
2024-04-29 15:33:46 +00:00
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
minetest.register_on_generated(function(minp, maxp, blockseed)
math.randomseed(blockseed)
2024-04-29 15:33:46 +00:00
local vm = minetest.get_mapgen_object("voxelmanip")
local data = vm:get_data()
data, schems = internal.generate_caves(data, minp, maxp)
2024-04-29 15:33:46 +00:00
vm:set_data(data)
vm:write_to_map()
internal.place_schematics(schems)
2024-04-29 15:33:46 +00:00
end)
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
2024-04-18 08:49:45 +00:00
-- For show, the following code COULD in theory be run elsewhere
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
noordstar_caves.register_shape({
name = "noordstar_caves:bubbles",
noise_params = {
offset = 0.5,
scale = 0.5,
spread = { x = 20, y = 20, z = 20 },
seed = 248039,
octaves = 2,
persistence = 0.6,
lacunarity = 2.0,
flags = "eased"
},
func = function(pos, n)
return n
end,
-- TODO: Implement y limits
-- y_min = -31000,
-- y_max = 31000,
connectivity_point = 10,
verticality_point = 40,
})
-- noordstar_caves.register_shape({
-- name = "noordstar_caves:cliffs",
-- noise_params = {
-- offset = 0.4,
-- scale = 0.5,
-- spread = { x = 20, y = 100, z = 20 },
-- seed = 97354,
-- octaves = 4,
-- persistence = 0.6,
-- lacunarity = 2.0,
-- flags = ""
-- },
-- func = function(pos, n)
-- return n
-- end,
-- connectivity_point = 30,
-- verticality_point = 80,
-- })
-- noordstar_caves.register_shape({
-- name = "noordstar_caves:donuts",
-- noise_params = {
-- offset = 0.0,
-- scale = 1.0,
-- spread = { x = 30, y = 30, z = 30 },
-- seed = 3934,
-- octaves = 1,
-- persistence = 0.6,
-- lacunarity = 2.0,
-- flags = "eased"
-- },
-- func = function(pos, n)
-- return 1 - 2 * math.abs(n)^0.5
-- end,
-- connectivity_point = 50,
-- verticality_point = 40,
-- })
-- noordstar_caves.register_shape({
-- name = "noordstar_caves:wall",
-- func = function(pos, n)
-- return -0.5
-- end,
-- connectivity_point = 0,
-- verticality_point = 0,
-- })
-- noordstar_caves.set_world_depth(-60)
-- noordstar_caves.cave_vastness = function(pos) return math.abs(pos.y - 60) / 120 end
noordstar_caves.register_biome({
name = "test",
node_floor = "mcl_core:crying_obsidian",
node_wall = "mcl_core:sand",
node_roof = "mcl_ocean:sea_lantern",
node_dust = "mcl_core:snow",
heat_point = 0,
humidity_point = 0,
})
noordstar_caves.register_biome({
name = "test2",
node_floor = "mcl_amethyst:amethyst_block",
node_wall = "mcl_crimson:shroomlight",
node_roof = "mcl_colorblocks:glazed_terracotta_silver",
heat_point = 100,
humidity_point = 100,
})
noordstar_caves.register_decoration({
deco_type = "simple",
place_on = "floor",
fill_ratio = 0.01,
decoration = "mcl_core:cactus",
height = 3,
height_max = 8,
biomes = { "test" },
})
noordstar_caves.register_decoration({
deco_type = "schematic",
place_on = "floor",
fill_ratio = 0.005,
schematic = "test.mts",
rotation = "random",
place_offset_y = 5,
})