From 6b38a7d7b27c27aae4d049e365636885c844e962 Mon Sep 17 00:00:00 2001 From: Bram van den Heuvel Date: Mon, 9 Sep 2024 15:10:39 +0200 Subject: [PATCH] Refactor to ns_caves --- init.lua | 1588 +++++++++++++--------------------------------- lua/register.lua | 46 ++ mod.conf | 2 +- 3 files changed, 504 insertions(+), 1132 deletions(-) create mode 100644 lua/register.lua diff --git a/init.lua b/init.lua index 9438aa0..d4206a2 100644 --- a/init.lua +++ b/init.lua @@ -1,491 +1,195 @@ --- -- DEVELOPMENT ONLY: Timer module for debugging performance --- local modpath = minetest.get_modpath(minetest.get_current_modname()) +-- Average size of each cave biome +local BIOME_SIZE = { x = 250, y = 250, z = 250 } --- dofile(modpath.."/lua/timer.lua") +-- Average step size of connectivity params +-- Given that connectivity is mostly a horizontal feature, it mostly changes +-- on a vertical scale, and barely on a horizontal scale. +local CONNECTIVITY_BLOB = { x = 250, y = 100, z = 250 } --- -- DEVELOPMENT ONLY: End of timer module +local ENUM_AIR = 1 +local ENUM_CEILING = 2 +local ENUM_CEILING_DECORATION = 3 +local ENUM_FLOOR = 4 +local ENUM_FLOOR_DECORATION = 5 +local ENUM_STONE = 6 +local ENUM_WALL = 7 --- Local variables used for programming -local internal = - { a = nil +-- Point that is so out of the normal connectivity/verticality scope (0 - 100) +-- that it is only selected when no other items are registered. +local OUTLANDISH_POINT = -1e3 - -- Average size of a cave biome - , biome_size = { x = 250, y = 250, z = 250 } +-- Average size of each cave shape +local SHAPE_SIZE = { x = 128, y = 128, z = 128 } - -- Average blob size of connectivity noise params - , cnct_blob = { x = 250, y = 100, z = 250 } +-- Several seeds for mapgen +local SEED_CONNECTIVITY = 297948 +local SEED_HEAT = 320523 +local SEED_HUMIDITY = 9923473 +local SEED_VERTICALITY = 35644 - -- The margin that a voxelmanip gives around the chunk being generated - , mapgen_buffer = 16 +-- Average step size of verticality params +-- Given that verticality is mostly a vertical feature, it mostly changes on a +-- horizontal scale, and barely on a vertical scale. +local VERTICALITY_BLOB = { x = 100, y = 250, z = 100 } - -- 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 - , ceiling_deco = 7 - } +-- Lower bound for cave generation +local WORLD_MINP = { x = -1e6, y = -1e6, z = -1e6 } - -- 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 +-- Upper bound for cave generation +local WORLD_MAXP = { x = 1e6, y = 1e6, z = 1e6 } - -- Random seeds used to influence noise - , seeds = - { connectivity = 297948 - , heat = 320523 - , humidity = 9923473 - , verticality = 35644 - } +local WORLD_DEPTH = -60 - -- 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 } - - -- Barrier showing the world where areas should be considered as caves - -- when determining the weather. - , weather_y = 0 - - -- 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 } - } +local internal = {} ------------------------------------------------------------------------------- --- 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 +-------------------------------- INTERNAL API --------------------------------- +------------------------------------------------------------------------------- +------------------------------------------------------------------------------- --- Global variable that can be used by other mods --- Please see API.md for reference -noordstar_caves = - { a = nil - - -- 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) - local y = math.abs(pos.y) - local d = math.abs(internal.world_depth / 2) - return 1 - (math.abs(y - d) / d) - end - - -- A public list of all registered biomes - , registered_biomes = {} - - -- 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 - --- 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 - --- 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) - if type(h) ~= "number" then - return - end - h = math.round(h) - - internal.world_depth = h - - if mcl_vars then - -- internal.weather_y = mcl_vars.mg_overworld_min - - mcl_vars.mg_overworld_min = h - mcl_vars.mg_bedrock_overworld_min = h - mcl_vars.mg_bedrock_overworld_max = h + 4 - mcl_vars.mg_lava_overworld_max = h + 10 - mcl_vars.mg_end_max = h - 2000 - mcl_vars.mg_realm_barrier_overworld_end_max = mcl_vars.mg_end_max - mcl_vars.mg_realm_barrier_overworld_end_min = mcl_vars.mg_end_max - 11 - - mcl_vars.mg_lava = false - end - - if mcl_mapgen then - -- internal.weather_y = mcl_mapgen.overworld.min - - mcl_mapgen.overworld.min = h - mcl_mapgen.overworld.bedrock_min = h - mcl_mapgen.overworld.bedrock_max = mcl_mapgen.overworld.bedrock_min + (mcl_mapgen.bedrock_is_rough and 4 or h) - mcl_mapgen.overworld.lava_max = h + 6 - mcl_mapgen.overworld.railcorridors_height_min = -50 - mcl_mapgen.overworld.railcorridors_height_max = -2 - - mcl_mapgen.end_.max = h - 2000 - mcl_mapgen.realm_barrier_overworld_end_max = mcl_mapgen.end_.max - mcl_mapgen.realm_barrier_overworld_end_min = mcl_mapgen.end_.max - 11 - - if mcl_mapgen.on_settings_changed then - mcl_mapgen.on_settings_changed() - else - minetest.log("error", "The installed version of the mcl_mapgen mod (part of Mineclone 5) " - .."does not have an mcl_mapgen.on_settings_changed method. This will likely result in " - .."altitudes below the original bedrock being inaccessible to players.") - end - end - -end -noordstar_caves.set_world_depth(internal.world_depth) - -if mcl_worlds then - local old_has_weather = mcl_worlds.has_weather - mcl_worlds.has_weather = function(pos) - -- No weather in the deep caverns - if pos.y >= internal.world_depth and pos.y <= internal.weather_y then - return false - end - return old_has_weather(pos) - end -end - --- 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 - - --- 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 v = ns_caves.cave_vastness(pos) - 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 + if not v then return 0 + elseif v < 0 then + return 0 + elseif v > 1 then + return 1 + else + return v end end --- Clean the user input on a biome definition before inserting it. +-- Classify all nodes in the chunk into what they are +-- TODO: Perhaps instead of iterating over ALL nodes, we can sort all node types +-- TODO: into separate tables, effectively allowing us to process the nodes +-- TODO: independently, saving computation time. +function internal.classify_nodes(used_shapes, minp, maxp) + local sminp = vector.offset(minp, 1, 1, 1) + local smaxp = vector.offset(maxp, -1, -1, -1) + local sva = VoxelArea(sminp, smaxp) + local va = VoxelArea(minp, maxp) + + local items = {} + + for i in sva:iterp(sminp, smaxp) do + + local pos = sva:position(i) + local bi = va:index(pos.x, pos.y, pos.z) + + local function is_part_of_cave(v) + return used_shapes[va:index(v.x, v.y, v.z)] + end + + if used_shapes[bi] == true then + -- Part of cave + if not used_shapes[va:index(pos.x, pos.y + 1, pos.z)] then + items[i] = ENUM_CEILING_DECORATION + elseif not used_shapes[va:index(pos.x, pos.y - 1, pos.z)] then + items[i] = ENUM_FLOOR_DECORATION + else + items[i] = ENUM_AIR + end + else + -- Not part of cave + if used_shapes[va:index(pos.x, pos.y + 1, pos.z)] then + items[i] = ENUM_FLOOR + elseif used_shapes[va:index(pos.x, pos.y - 1, pos.z)] then + items[i] = ENUM_CEILING + elseif used_shapes[va:index(pos.x - 1, pos.y, pos.z)] then + items[i] = ENUM_WALL + elseif used_shapes[va:index(pos.x + 1, pos.y, pos.z)] then + items[i] = ENUM_WALL + elseif used_shapes[va:index(pos.x, pos.y, pos.z - 1)] then + items[i] = ENUM_WALL + elseif used_shapes[va:index(pos.x, pos.y, pos.z + 1)] then + items[i] = ENUM_WALL + else + items[i] = ENUM_STONE + end + end + end + + local custom_y = -13 + if sminp.y < custom_y and custom_y < smaxp.y then + for i in sva:iterp({ x = sminp.x, y = custom_y, z = sminp.z }, { x = smaxp.x, y = custom_y, z = smaxp.z }) do + items[i] = ENUM_CEILING + end + end + + return items +end + 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 + assert(type(def) == "table") + assert(type(def.name) == "string") + assert(type(def.heat_point) == "number") + assert(type(def.humidity_point) == "number") - local d = { - name = def.name, - heat_point = def.heat_point, - humidity_point = def.humidity_point, - } + def.y_min = def.y_min or (def.min_pos and def.min_pos.y) or WORLD_MINP.y + def.y_max = def.y_max or (def.max_pos and def.max_pos.y) or WORLD_MAXP.y - -- 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 + def.min_pos = def.min_pos or { x = WORLD_MINP.x, y = def.y_min, z = WORLD_MINP.z } + def.max_pos = def.max_pos or { x = WORLD_MAXP.x, y = def.y_max, z = WORLD_MAXP.z } - 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 + assert(type(def.y_min) == "number") + assert(type(def.y_max) == "number") + assert(type(def.min_pos) == "table") + assert(type(def.max_pos) == "table") + assert(type(def.min_pos.x) == "number") + assert(type(def.max_pos.x) == "number") + assert(type(def.min_pos.y) == "number") + assert(type(def.max_pos.y) == "number") + assert(type(def.min_pos.z) == "number") + assert(type(def.max_pos.z) == "number") - -- 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 + def.node_air = def.node_air or "air" - return d + assert(def.node_dust == nil or type(def.node_dust) == "string") + assert(def.node_floor == nil or type(def.node_floor) == "string") + assert(def.node_wall == nil or type(def.node_wall) == "string") + assert(def.node_roof == nil or type(def.node_roof) == "string") + assert(type(def.node_air) == "string") + + return def end --- 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, - biomes = def.biomes, - } - - 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_center_x,place_center_z") - 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 + assert( + type(def) == "table", + "Shape def is meant to be a table type" + ) + assert(type(def.name) == "string", "Shape name is meant to be a string") + assert(type(def.connectivity_point) == "number") + assert(type(def.verticality_point) == "number") - local d = { - name = def.name, - connectivity_point = def.connectivity_point, - verticality_point = def.verticality_point, + def.y_min = def.y_min or WORLD_MINP.y + def.y_max = def.y_max or WORLD_MAXP.y + def.func = def.func or function(_, n) return n end - y_min = def.y_min or internal.world_minp.y, - y_max = def.y_max or internal.world_maxp.y, - } + assert(type(def.y_min) == "number") + assert(type(def.y_max) == "number") + assert(type(def.func) == "function") + assert(def.noise_params == nil or type(def.noise_params) == "table") - d.y_min = internal.reduced_shape_pos({ x = 0, y = d.y_min, z = 0 }).y + 1 - d.y_max = internal.reduced_shape_pos({ x = 0, y = d.y_max, z = 0 }).y - 1 - - 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 - --- Get the most nearby cave biome -function internal.closest_cave_biome(heat, humidity, pos) - local biome = internal.default_biome() - local d = internal.biome_def_distance(biome, heat, humidity) - - for _, def in pairs(noordstar_caves.registered_biomes) do - 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 - - return biome -end - --- Get the most nearby cave shape -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 - 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 + return def end -- Get connectivity noise params function internal.connectivity_noise_params() - local factor = math.max( - math.abs(#noordstar_caves.registered_shapes) ^ 0.5 - , 1 - ) + local factor = math.max(1, math.abs(#ns_caves.registered_shapes) ^ 0.5) 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, + spread = { + x = factor * CONNECTIVITY_BLOB.x, + y = factor * CONNECTIVITY_BLOB.y, + z = factor * CONNECTIVITY_BLOB.z, + }, + seed = SEED_CONNECTIVITY, octaves = 2, persistence = 0.2, lacunarity = 2.0, @@ -497,8 +201,8 @@ end function internal.default_biome() return internal.clean_biome_def( { name = "noordstar_caves:default_biome" - , heat_point = internal.outlandish_point - , humidity_point = internal.outlandish_point + , heat_point = OUTLANDISH_POINT + , humidity_point = OUTLANDISH_POINT } ) end @@ -507,356 +211,177 @@ end function internal.default_shape() return internal.clean_shape_def( { name = "noordstar_caves:none" - , connectivity_point = internal.outlandish_point - , verticality_point = internal.outlandish_point + , connectivity_point = OUTLANDISH_POINT + , verticality_point = 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) .. "`") +-- Get a (sorta) Euclidian distance. The exact distance doesn't matter, +-- it just needs to correctly determine whichever value is closer in a +-- Euclidian space. Consequently, the square root is ignored as optimization. +function internal.euclidian(x1, x2, y1, y2) + return (x1 - x2) ^ 2 + (y1 - y2) ^ 2 +end + +-- For each node, determine which cave biome they're in. +function internal.find_biome_allocations(heat, humidity) + assert(#heat == #humidity) + + local allocs = {} + local default_biome = internal.default_biome() + + for i = 1, #heat, 1 do + local e, u = heat[i], humidity[i] + local d = internal.euclidian( + e, default_biome.heat_point, + u, default_biome.humidity_point + ) + local biome_name + + -- Find the appropriate biome + for name, def in pairs(ns_caves.registered_biomes) do + local def_d = internal.euclidian( + e, def.heat_point, u, def.humidity_point + ) + + if def_d < d then + d = def_d + biome_name = name + end + end + + allocs[i] = biome_name end - return function (pos, n) - local out = func(pos, n) + return allocs +end - 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?" - } - , " " - ) +-- For each node, determine which cave shape they follow. +function internal.find_shape_allocations(connectivity, verticality) + assert(#connectivity == #verticality) + + local allocs = {} + local default_shape = internal.default_shape() + + for i = 1, #connectivity, 1 do + local c, v = connectivity[i], verticality[i] + local d = internal.euclidian( + c, default_shape.connectivity_point, + v, default_shape.verticality_point + ) + local shape_name + + -- Find the appropriate shape + for name, def in pairs(ns_caves.registered_shapes) do + local def_d = internal.euclidian( + c, def.connectivity_point, v, def.verticality_point ) + + if def_d < d then + d = def_d + shape_name = name + end + end + + -- Assign the chosen name + allocs[i] = shape_name + end + + return allocs +end + +-- Once it has been figured out which node belongs to which shape, +-- get noise values from the respective shapes. +-- This function does its operations in-place. +function internal.find_shape_values(used_shapes, minp, maxp, va) + -- Cache shapes so they don't need to be recalculated. + local captured_shapes = {} + local default_shape = internal.default_shape() + + for i in va:iterp(minp, maxp) do + -- Get shape values per item + local name = used_shapes[i] + local shape + + if name == nil then + shape = default_shape else + shape = ns_caves.registered_shapes[name] + end + + if captured_shapes[shape] == nil then + -- minetest.debug("Generating new shape! " .. shape.name) + captured_shapes[shape] = internal.generate_shape(shape, minp, maxp, va) + end + + used_shapes[i] = captured_shapes[shape][i] + if used_shapes[i] == nil then error( - table.concat( - { "Shape" - , name - , "function must return a number." - } - , " " - ) + "Noise value ended up nil unexpectedly: i = " .. i .. ", shape = " .. shape.name ) end end + + -- minetest.debug("Finished generating shapes.") 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) +function internal.generate_connectivity_noise(minp, maxp) + return internal.generate_perlin_noise( + internal.connectivity_noise_params(), minp, maxp ) - local verticality = internal.flat_from_noise_params( - internal.verticality_noise_params(), - internal.reduced_shape_pos(minp), - internal.reduced_shape_pos(maxp) +end + +function internal.generate_heat_noise(minp, maxp) + return internal.generate_perlin_noise( + internal.heat_noise_params(), minp, 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) - local cnct = connectivity:get_index(i) - local vrtcl = verticality:get_index(i) - - 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 - - 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)) - - 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 -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 - - if bools:get_index(i + bools.up) == false then - -- Block is near the ceiling - return nt.ceiling_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 +function internal.generate_humidity_noise(minp, maxp) + return internal.generate_perlin_noise( + internal.humidity_noise_params(), minp, maxp + ) 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 +-- Generate Perlin noise within given boundaries +function internal.generate_perlin_noise(noiseparams, minp, maxp) + local size = { + x = 1 + maxp.x - minp.x, + y = 1 + maxp.y - minp.y, + z = 1 + maxp.z - minp.z, + } - 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) + return PerlinNoiseMap(noiseparams, size):get_3d_map_flat(minp) 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 = {} - - local nx = maxp.x - minp.x + 1 - local ny = maxp.y - minp.y + 1 - local nz = maxp.z - minp.z + 1 +function internal.generate_shape(def, minp, maxp, va) + -- local noise_flat = {} + -- Get random noise if noise_params are given 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 + return internal.generate_perlin_noise(def.noise_params, minp, maxp) + else + return {} + -- for i in va:iterp(minp, maxp) do + -- noise_flat[i] = 0 + -- end end - internal.iter_3d_area(minp, maxp, function (i, pos) - noise_flat_map[i] = def.func(pos, noise_flat_map[i]) - end) + -- -- Update noise with custom defined function + -- for i in va:iterp(minp, maxp) do + -- noise_flat[i] = def.func(va:position(i), noise_flat[i]) + -- end - return Flat3dArray:new(minp, maxp, noise_flat_map) + -- return noise_flat 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 - --- 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) - - -- timer.checkpoint("Calculate cave shape") - - -- 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 +-- Generate verticality noise within given boundaries +function internal.generate_verticality_noise(minp, maxp) + return internal.generate_perlin_noise( + internal.verticality_noise_params(), minp, maxp ) - local humidity_points = internal.flat_from_noise_params( - internal.humidity_noise_params(), bminp, bmaxp - ) - - -- timer.checkpoint("Calculate block types & biomes") - - local air = minetest.get_content_id("air") - local schems = {} - - -- Place blocks where necessary - internal.iter_3d_area(bminp, bmaxp, function (i, pos) - - local vi = vmanip:pos_to_index(pos) - local function place(name) - - if vmanip:get_index(vi) == air then - elseif type(name) == "string" then - vmanip:set_index(vi, minetest.get_content_id(name)) - 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) - - local def = internal.closest_cave_biome(heat, humidity, pos) - - 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 or nt == internal.node_types.ceiling_deco then - local schem_placed = false - local appropriate_place = "floor" - if nt == internal.node_types.ceiling_deco then - appropriate_place = "ceiling" - end - - 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 ~= appropriate_place 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 - local n = "air" - if nt == internal.node_types.floor_deco then - n = def.node_dust or n - end - place(n) - schem_placed = true - end - else - error( - table.concat( - { "Found unknown node type " - , nt - , " (type " - , type(nt) - , ")" - } - , "" - ) - ) - end - end - end) - - return vmanip, schems end -- Get the noise params for the cave biome temperature. @@ -864,8 +389,8 @@ function internal.heat_noise_params() return { offset = 50, scale = 50, - spread = internal.biome_size, - seed = internal.seeds.heat, + spread = BIOME_SIZE, + seed = SEED_HEAT, octaves = 2, persistence = 0.1, lacunarity = 2.0, @@ -878,8 +403,8 @@ function internal.humidity_noise_params() return { offset = 50, scale = 50, - spread = internal.biome_size, - seed = internal.seeds.humidity, + spread = BIOME_SIZE, + seed = SEED_HUMIDITY, octaves = 2, persistence = 0.1, lacunarity = 2.0, @@ -887,243 +412,117 @@ function internal.humidity_noise_params() } end --- Return whether a node is part of a cave -function internal.is_cave_node(noise, vastness) - return noise > 1 - vastness +-- Take all necessary steps to execute the mapgen +function internal.mapgen(minp, maxp, blockseed, vm, va) + -- Create bordered VoxelArea. + -- The point of this is so walls and ceilings can be determined using + -- bordering nodes in different chunks. + local bminp = vector.offset(minp, -1, -1, -1) + local bmaxp = vector.offset(maxp, 1, 1, 1) + local bva = VoxelArea(bminp, bmaxp) + + -- Find cave shape params + local connectivity = internal.generate_connectivity_noise(bminp, bmaxp) + local verticality = internal.generate_verticality_noise(bminp, bmaxp) + + -- Draw cave shapes + local used_shapes = internal.find_shape_allocations(connectivity, verticality) + internal.find_shape_values(used_shapes, bminp, bmaxp, bva) + internal.shape_to_air(used_shapes, bminp, bmaxp, bva) + + -- Find cave biome params + local heat = internal.generate_heat_noise(minp, maxp) + local humidity = internal.generate_humidity_noise(minp, maxp) + + -- -- DEBUG: Write to air (or not) + -- local air = 0 + + -- for i in small_va:iterp(minp, maxp) do + -- if used_shapes[i] == true then + -- local pos = small_va:position(i) + -- local vmi = va:index(pos.x, pos.y, pos.z) + -- data[vmi] = minetest.CONTENT_AIR + + -- air = air + 1 + -- end + -- end + + -- Classify various nodes as walls, floors, ceilings + local classified_nodes = internal.classify_nodes(used_shapes, bminp, bmaxp) + + -- Draw cave biomes + local used_biomes = internal.find_biome_allocations(heat, humidity) + + -- Manipulate `data` table by adding classified nodes based on which biome + -- they're in. + local data = vm:get_data() + + internal.write_classified_biome_nodes( + data, va, classified_nodes, used_biomes, minp, maxp + ) + + vm:set_data(data) + vm:write_to_map() 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 +-- Place items +function internal.place_node_on_data(data, i, name) + if name == nil then + return end - return false -end + local node_id = minetest.get_content_id(name) --- 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 + if node_id == nil then + return end - return false + data[i] = node_id 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 - callback(i, pos) - end - end - end +-- Register a new biome +function internal.register_biome(biome) + biome = internal.clean_biome_def(biome) + ns_caves.registered_biomes[biome.name] = biome end --- -- Log text to the Minetest chat --- function internal.log(text) --- minetest.chat_send_all(os.time() .. " - " .. text) --- end +-- Register a new shape +function internal.register_shape(shape) + shape = internal.clean_shape_def(shape) + ns_caves.registered_shapes[shape.name] = shape +end --- Place all schematic decorations into the world -function internal.place_schematic_decorations(vmanip, schems) - for _, schem in ipairs(schems) do - local pos = { - x = schem.pos.x, - y = schem.pos.y + schem.deco.place_offset_y, - z = schem.pos.z, - } - local deco = schem.deco +-- Convert all shape noise into clarifications whether a node is wall or air. +-- This function does its operations in-place. +function internal.shape_to_air(noise_values, minp, maxp, va) + for i in va:iterp(minp, maxp) do + local vastness = internal.cave_vastness(va:position(i)) - if deco.deco_type ~= "schematic" then + if noise_values[i] == nil then + error( + "Noise value ended up nil unexpectedly" + ) + elseif noise_values[i] >= 1 - vastness then + noise_values[i] = true else - if deco.place_on == "floor" then - minetest.place_schematic_on_vmanip( - vmanip, pos, - deco.schematic, deco.rotation, deco.replacement, - true, deco.flags - ) - elseif deco.place_on == "ceiling" then - local h = deco.schematic - - if type(deco.schematic) == "string" then - h = minetest.read_schematic(h, {}) - - if type(h) == "nil" then - error("Could not find schematic! Perhaps it the filename is incorrect?") - end - end - - h = h.size.y - - minetest.place_schematic_on_vmanip( - vmanip, { x = pos.x, y = pos.y - h + 1, z = pos.z }, - deco.schematic, deco.rotation, deco.replacement, - true, deco.flags - ) - else - error("Unknown place_on value `" .. deco.place_on .. "`") - end - end - end -end - --- Place all simple decorations into the world -function internal.place_simple_decorations(flat, schems) - for _, schem in ipairs(schems) do - if schem.deco.deco_type == "simple" then - local pos = { - x = schem.pos.x, - y = schem.pos.y + schem.deco.place_offset_y, - z = schem.pos.z, - } - local deco = schem.deco - - local i = flat:pos_to_index(pos) - - local dir = nil - - if deco.place_on == "floor" then - dir = flat.up - elseif deco.place_on == "ceiling" then - dir = flat.down - else - error("Invalid place_on value `" .. deco.place_on .. "`") - end - - local h_min = deco.height - local h_max = math.max(deco.height, deco.height_max) - - for dy = 0, math.random(h_min, h_max) - 1, 1 do - local li = i + dir * dy - - flat:set_index(li, minetest.get_content_id(deco.decoration)) - end - - 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 - --- Prevent v7 from spawning large caves underground -function internal.stop_v7_caverns() - local s = minetest.get_mapgen_setting("mgv7_spflags") - - if s then - s = s:gsub(", nocaverns", "") - s = s:gsub(", caverns" , "") - s = s:gsub("nocaverns, ", "") - s = s:gsub("caverns, " , "") - s = s:gsub("nocaverns" , "") - s = s:gsub("caverns" , "") - - if s == "" then - minetest.set_mapgen_setting("mgv7_spflags", "nocaverns", true) - else - minetest.set_mapgen_setting("mgv7_spflags", s .. ", nocaverns", true) + noise_values[i] = false end end end -- Get verticality noise params function internal.verticality_noise_params() - local factor = math.max( - math.abs(#noordstar_caves.registered_shapes) ^ 0.5 - , 1 - ) + local factor = math.max(1, math.abs(#ns_caves.registered_shapes) ^ 0.5) 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, + spread = { + x = factor * VERTICALITY_BLOB.x, + y = factor * VERTICALITY_BLOB.y, + z = factor * VERTICALITY_BLOB.z, + }, + seed = SEED_VERTICALITY, octaves = 2, persistence = 0.2, lacunarity = 2.0, @@ -1131,167 +530,94 @@ function internal.verticality_noise_params() } 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 +function internal.write_classified_biome_nodes(vm_data, va, classified_nodes, used_biomes, minp, maxp) + local small_va = VoxelArea(minp, maxp) + + -- assert(#classified_nodes == #used_biomes) + -- assert(#classified_nodes == small_va:getVolume()) + + local default_biome = internal.default_biome() + local schems = {} + + for i in va:iterp(minp, maxp) do + local pos = va:position(i) + local si = small_va:index(pos.x, pos.y, pos.z) + + local node_type = classified_nodes[si] + local biome = used_biomes[si] + + if biome == nil then + biome = default_biome + else + biome = ns_caves.registered_biomes[biome] or default_biome + end + + if node_type == ENUM_AIR then + internal.place_node_on_data(vm_data, i, biome.node_air) + elseif node_type == ENUM_CEILING then + internal.place_node_on_data(vm_data, i, biome.node_roof) + elseif node_type == ENUM_CEILING_DECORATION then + -- TODO: Return schematics to be placed + internal.place_node_on_data(vm_data, i, biome.node_air) + elseif node_type == ENUM_FLOOR then + internal.place_node_on_data(vm_data, i, biome.node_floor) + elseif node_type == ENUM_FLOOR_DECORATION then + -- TODO: Return schematics to be placed + internal.place_node_on_data(vm_data, i, biome.node_air) + elseif node_type == ENUM_STONE then + -- Nothing needs to be placed + elseif node_type == ENUM_WALL then + internal.place_node_on_data(vm_data, i, biome.node_wall) + else + if node_type == nil then + error( + "Expected enum value, encountered nil at index " .. si .. " (originally " .. i .. ")" + ) end + error( + "Encountered unknown node type enum value " .. node_type + ) end end - return false -end - - --- 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 + return schems end ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- +--------------------------------- PUBLIC API ---------------------------------- ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- -------------------------------------------------------------------------------- + minetest.register_on_generated(function(minp, maxp, blockseed) - -- timer.start() - - math.randomseed(blockseed) + if maxp.y < WORLD_DEPTH then + return + end local vm = minetest.get_mapgen_object("voxelmanip") - local data = vm:get_data() + local va = VoxelArea(vm:get_emerged_area()) - local flat, schems = internal.generate_caves(data, minp, maxp) - -- timer.checkpoint("Place caves") - - internal.place_simple_decorations(flat, schems) - - vm:set_data(flat.arr) - vm:write_to_map() - - internal.place_schematic_decorations(vm, schems) - -- timer.checkpoint("Place decorations") - - -- timer.stop() + internal.mapgen(minp, maxp, blockseed, vm, va) end) --- Prevent v7 from spawning large caves underground -internal.stop_v7_caverns() +ns_caves = { + cave_vastness = function(pos) + if pos.y > 0 or pos.y < WORLD_DEPTH then + return 0 + end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- + local y = math.abs(pos.y) + local d = math.abs(WORLD_DEPTH / 2) + return 1 - (math.abs(y - d) / d) + end, + + register_biome = internal.register_biome, + + register_shape = internal.register_shape, + + registered_biomes = {}, + + registered_shapes = {}, +} + +dofile(minetest.get_modpath(minetest.get_current_modname()) .. "/lua/register.lua") \ No newline at end of file diff --git a/lua/register.lua b/lua/register.lua new file mode 100644 index 0000000..8826c1b --- /dev/null +++ b/lua/register.lua @@ -0,0 +1,46 @@ +ns_caves.register_shape({ + name = "ns_caves:bubbles", + noise_params = { + offset = -0.2, + scale = 0.5, + spread = { x = 100, y = 100, z = 100 }, + seed = 248039, + octaves = 2, + persistence = 0.6, + lacunarity = 2.0, + flags = "eased", + }, + y_min = -31000, + y_max = 0, + connectivity_point = 10, + verticality_point = 10, +}) + +ns_caves.register_shape({ + name = "ns_caves:cliffs", + noise_params = { + offset = -0.4, + scale = 0.9, + spread = { x = 30, y = 200, z = 30 }, + seed = 92742002, + octaves = 2, + persistence = 0.6, + lacunarity = 3.0, + flags = "eased", + }, + y_min = -31000, + y_max = 0, + connectivity_point = 20, + verticality_point = 80, +}) + +ns_caves.register_biome({ + name = "ns_caves:snow", + node_dust = "mcl_core:snow", + node_floor = "mcl_crimson:shroomlight", + node_wall = "mcl_core:cobble", + node_roof = "dripstone:dry_dripstone_block", + node_air = "air", + heat_point = 0, + humidity_point = 50, +}) diff --git a/mod.conf b/mod.conf index f45772e..cd58ad7 100644 --- a/mod.conf +++ b/mod.conf @@ -1,4 +1,4 @@ -name=noordstar_caves +name=ns_caves description=A mod that adds more depths and caves to VoxeLibre or Mineclonia author=Noordstar title=Noordstar Caves