From 99e9753772aa8ef1b79a868df8c62aa9d99d2386 Mon Sep 17 00:00:00 2001 From: Bram van den Heuvel Date: Tue, 10 Sep 2024 13:02:44 +0200 Subject: [PATCH] Add biome optimizations + decorations --- init.lua | 413 ++++++++++++++++++++++++++++++++++++++--------- lua/register.lua | 38 +++++ mod.conf | 2 +- 3 files changed, 379 insertions(+), 74 deletions(-) diff --git a/init.lua b/init.lua index d4206a2..894f5f0 100644 --- a/init.lua +++ b/init.lua @@ -63,63 +63,57 @@ function internal.cave_vastness(pos) end -- 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 = {} + local ceiling_decos = {} + local ceilings = {} + local contents = {} + local floor_decos = {} + local floors = {} + local walls = {} 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 + if used_shapes[va:index(pos.x, pos.y, pos.z)] == true then -- Part of cave if not used_shapes[va:index(pos.x, pos.y + 1, pos.z)] then - items[i] = ENUM_CEILING_DECORATION + table.insert(ceiling_decos, i) elseif not used_shapes[va:index(pos.x, pos.y - 1, pos.z)] then - items[i] = ENUM_FLOOR_DECORATION + table.insert(floor_decos, i) else - items[i] = ENUM_AIR + table.insert(contents, i) end else -- Not part of cave if used_shapes[va:index(pos.x, pos.y + 1, pos.z)] then - items[i] = ENUM_FLOOR + table.insert(floors, i) elseif used_shapes[va:index(pos.x, pos.y - 1, pos.z)] then - items[i] = ENUM_CEILING + table.insert(ceilings, i) elseif used_shapes[va:index(pos.x - 1, pos.y, pos.z)] then - items[i] = ENUM_WALL + table.insert(walls, i) elseif used_shapes[va:index(pos.x + 1, pos.y, pos.z)] then - items[i] = ENUM_WALL + table.insert(walls, i) elseif used_shapes[va:index(pos.x, pos.y, pos.z - 1)] then - items[i] = ENUM_WALL + table.insert(walls, i) elseif used_shapes[va:index(pos.x, pos.y, pos.z + 1)] then - items[i] = ENUM_WALL - else - items[i] = ENUM_STONE + table.insert(walls, i) 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 + return { + ceiling_decos = ceiling_decos, + ceilings = ceilings, + contents = contents, + floor_decos = floor_decos, + floors = floors, + walls = walls, + } end function internal.clean_biome_def(def) @@ -156,6 +150,41 @@ function internal.clean_biome_def(def) return def end +function internal.clean_deco_def(def) + def.deco_type = def.deco_type or "simple" + def.place_on = def.place_on or "floor" + def.y_min = def.y_min or WORLD_MINP.y + def.y_max = def.y_max or WORLD_MAXP.y + + assert(def.deco_type == "simple" or def.deco_type == "schematic") + assert(def.place_on == "floor" or def.place_on == "ceiling") + assert(type(def.fill_ratio) == "number") + assert(def.biomes == nil or type(def.biomes) == "table") + + if def.deco_type == "simple" then + def.height = def.height or 1 + def.height_max = def.height_max or def.height + def.place_offset_y = def.place_offset_y or 0 + + assert(type(def.deco_type) == "string") + assert(type(def.height) == "number") + assert(type(def.height_max) == "number") + assert(type(def.place_offset_y) == "number") + + elseif def.deco_type == "schematic" then + def.replacements = def.replacements or {} + def.place_offset_y = def.place_offset_y or 0 + + assert(type(def.schematic) == "string" or type(def.schematic) == "table") + assert(type(def.replacements) == "table") + + assert(def.rotation == "0" or def.rotation == "90" or def.rotation == "180" or def.rotation == "270" or def.rotation == "random") + assert(type(def.place_offset_y) == "number") + end + + return def +end + function internal.clean_shape_def(def) assert( type(def) == "table", @@ -455,13 +484,52 @@ function internal.mapgen(minp, maxp, blockseed, vm, va) -- Manipulate `data` table by adding classified nodes based on which biome -- they're in. + local sva = VoxelArea(minp, maxp) local data = vm:get_data() - internal.write_classified_biome_nodes( - data, va, classified_nodes, used_biomes, minp, maxp + internal.write_classified_node( + data, va, used_biomes, classified_nodes.ceilings, sva, "node_roof" + ) + internal.write_classified_node( + data, va, used_biomes, classified_nodes.contents, sva, "node_air" + ) + internal.write_classified_node( + data, va, used_biomes, classified_nodes.floors, sva, "node_floor" + ) + internal.write_classified_node( + data, va, used_biomes, classified_nodes.walls, sva, "node_wall" + ) + + -- Place floor decorations + -- In case the dust has not been defined, place air nodes first + internal.write_classified_node( + data, va, used_biomes, classified_nodes.floor_decos, sva, "node_air" + ) + internal.write_classified_node( + data, va, used_biomes, classified_nodes.floor_decos, sva, "node_dust" + ) + local claimed_floor = internal.write_simple_floor_decorations( + data, va, used_biomes, classified_nodes.floor_decos, sva + ) + + -- Place ceiling decorations + internal.write_classified_node( + data, va, used_biomes, classified_nodes.ceiling_decos, sva, "node_air" + ) + local claimed_ceiling = internal.write_simple_ceiling_decorations( + data, va, used_biomes, classified_nodes.ceiling_decos, sva ) vm:set_data(data) + + -- Set schematic decorations + internal.write_schematic_floor_decoration( + vm, used_biomes, classified_nodes.floor_decos, sva, claimed_floor + ) + internal.write_schematic_ceiling_decoration( + vm, used_biomes, classified_nodes.ceiling_decos, sva, claimed_ceiling + ) + vm:write_to_map() end @@ -486,6 +554,14 @@ function internal.register_biome(biome) ns_caves.registered_biomes[biome.name] = biome end +-- Register a new decoration +function internal.register_decoration(deco) + table.insert( + ns_caves.registered_decorations, + internal.clean_deco_def(deco) + ) +end + -- Register a new shape function internal.register_shape(shape) shape = internal.clean_shape_def(shape) @@ -530,57 +606,242 @@ function internal.verticality_noise_params() } end -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()) - +function internal.write_classified_node(vm_data, va, used_biomes, classified_nodes, small_va, biome_key) 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 biome_to_id = {} + biome_to_id[default_biome.name] = default_biome[biome_key] or "" - local node_type = classified_nodes[si] - local biome = used_biomes[si] + for _, i in ipairs(classified_nodes) do + local pos = small_va:position(i) + local biome = used_biomes[i] or default_biome.name - if biome == nil then - biome = default_biome - else - biome = ns_caves.registered_biomes[biome] or default_biome + if biome_to_id[biome] == nil then + local biome_def + + if biome == default_biome.name then + biome_def = default_biome + else + biome_def = ns_caves.registered_biomes[biome] or default_biome + end + + if biome_def[biome_key] == nil then + biome_to_id[biome] = "" + else + biome_to_id[biome] = minetest.get_content_id(biome_def[biome_key]) or "" + end 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 .. ")" - ) + local content_id = biome_to_id[biome] + + if content_id ~= "" then + vm_data[va:index(pos.x, pos.y, pos.z)] = content_id + end + end +end + +function internal.write_schematic_ceiling_decoration(vmanip, used_biomes, classified_nodes, sva, claimed_spots) + for _, def in pairs(ns_caves.registered_decorations) do + if def.deco_type == "schematic" and def.place_on == "ceiling" then + -- Place the decoration, if they're in the appropriate biome + for _, i in ipairs(classified_nodes) do + local good_biome = def.biomes == nil + local unclaimed_spot = true + + for _, ci in ipairs(claimed_spots) do + if ci == i then + unclaimed_spot = false + break + end + end + + if unclaimed_spot and not good_biome then + local current_biome = used_biomes[i] + for _, name in ipairs(def.biomes) do + if name == current_biome then + good_biome = true + break + end + end + end + + if unclaimed_spot and good_biome and math.random() < def.fill_ratio then + -- Automatically place the top at the top of the cave + local pos = sva:position(i) + local h = def.schematic + + if type(def.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 + def.place_offset_y, z = pos.z }, + def.schematic, def.rotation, def.replacement, true, + def.flags + ) + + table.insert(claimed_spots, i) + end end - error( - "Encountered unknown node type enum value " .. node_type - ) end end - return schems + return claimed_spots +end + +function internal.write_schematic_floor_decoration(vmanip, used_biomes, classified_nodes, sva, claimed_spots) + for _, def in pairs(ns_caves.registered_decorations) do + if def.deco_type == "schematic" and def.place_on == "floor" then + -- Place the decoration, if they're in the appropriate biome + for _, i in ipairs(classified_nodes) do + local good_biome = def.biomes == nil + local unclaimed_spot = true + + for _, ci in ipairs(claimed_spots) do + if ci == i then + unclaimed_spot = false + break + end + end + + if unclaimed_spot and not good_biome then + local current_biome = used_biomes[i] + for _, name in ipairs(def.biomes) do + if name == current_biome then + good_biome = true + break + end + end + end + + if unclaimed_spot and good_biome and math.random() < def.fill_ratio then + minetest.place_schematic_on_vmanip( + vmanip, sva:position(i), def.schematic, def.rotation, + def.replacement, true, def.flags + ) + + table.insert(claimed_spots, i) + end + end + end + end + + return claimed_spots +end + +function internal.write_simple_ceiling_decorations(vm_data, va, used_biomes, classified_nodes, sva) + local claimed_spots = {} + + for _, def in pairs(ns_caves.registered_decorations) do + if def.deco_type == "simple" and def.place_on == "ceiling" then + -- Place the decoration, if they're in the appropriate biome. + for _, i in ipairs(classified_nodes) do + local pos = sva:position(i) + local good_biome = def.biomes == nil + local unclaimed_spot = true + + for _, ci in ipairs(claimed_spots) do + if ci == i then + unclaimed_spot = false + break + end + end + + if not good_biome and unclaimed_spot then + local current_biome = used_biomes[i] + for _, name in ipairs(def.biomes) do + if name == current_biome then + good_biome = true + break + end + end + end + + if unclaimed_spot and good_biome and math.random() < def.fill_ratio then + -- Determine the height + local height = def.height + if def.height_max > height then + height = math.min( + 16, math.random(def.height, def.height_max) + ) + end + + -- Place the structure! + for h = 1, height, 1 do + local y = pos.y - h + def.place_offset_y + 1 + + if sva:contains(pos.x, y, pos.z) then + vm_data[va:index(pos.x, y, pos.z)] = minetest.get_content_id(def.decoration) + end + end + + table.insert(claimed_spots, i) + end + end + end + end + + return claimed_spots +end +function internal.write_simple_floor_decorations(vm_data, va, used_biomes, classified_nodes, sva) + local claimed_spots = {} + + for _, def in pairs(ns_caves.registered_decorations) do + if def.deco_type == "simple" and def.place_on == "floor" then + -- Place the decoration, if they're in the appropriate biome. + for _, i in ipairs(classified_nodes) do + local pos = sva:position(i) + local good_biome = def.biomes == nil + local unclaimed_spot = true + + for _, ci in ipairs(claimed_spots) do + if ci == i then + unclaimed_spot = false + break + end + end + + if unclaimed_spot and not good_biome then + local current_biome = used_biomes[i] + for _, name in ipairs(def.biomes) do + if name == current_biome then + good_biome = true + break + end + end + end + + if unclaimed_spot and good_biome and math.random() < def.fill_ratio then + -- Determine the height + local height = def.height + if def.height_max > height then + height = math.min( + 16, math.random(def.height, def.height_max) + ) + end + + -- Place the structure! + for h = 1, height, 1 do + local y = pos.y + h + def.place_offset_y - 1 + + if sva:contains(pos.x, y, pos.z) then + vm_data[va:index(pos.x, y, pos.z)] = minetest.get_content_id(def.decoration) + end + end + + table.insert(claimed_spots, i) + end + end + end + end + + return claimed_spots end ------------------------------------------------------------------------------- @@ -597,6 +858,8 @@ minetest.register_on_generated(function(minp, maxp, blockseed) local vm = minetest.get_mapgen_object("voxelmanip") local va = VoxelArea(vm:get_emerged_area()) + math.randomseed(blockseed) + internal.mapgen(minp, maxp, blockseed, vm, va) end) @@ -613,10 +876,14 @@ ns_caves = { register_biome = internal.register_biome, + register_decoration = internal.register_decoration, + register_shape = internal.register_shape, registered_biomes = {}, + registered_decorations = {}, + registered_shapes = {}, } diff --git a/lua/register.lua b/lua/register.lua index 8826c1b..d989cca 100644 --- a/lua/register.lua +++ b/lua/register.lua @@ -44,3 +44,41 @@ ns_caves.register_biome({ heat_point = 0, humidity_point = 50, }) + +ns_caves.register_biome({ + name = "ns_caves:drip", + node_floor = "dripstone:dry_dripstone_block", + node_wall = "dripstone:dry_dripstone_block", + node_roof = "dripstone:dry_dripstone_block", + heat_point = 50, + humidity_point = 0, +}) + +ns_caves.register_decoration({ + deco_type = "simple", + place_on = "ceiling", + fill_ratio = 0.25, + biomes = { "ns_caves:snow" }, + decoration = "mcl_core:water_source", + height = 1, + place_offset_y = 2, +}) + +ns_caves.register_decoration({ + deco_type = "simple", + place_on = "ceiling", + fill_ratio = 0.025, + decoration = "mcl_bamboo:bamboo_block", + height = 3, + height_max = 16, +}) + +ns_caves.register_decoration({ + deco_type = "simple", + place_on = "floor", + fill_ratio = 0.025, + decoration = "mcl_bamboo:bamboo_block", + height = 3, + height_max = 16, +}) + diff --git a/mod.conf b/mod.conf index cd58ad7..f094283 100644 --- a/mod.conf +++ b/mod.conf @@ -2,5 +2,5 @@ name=ns_caves description=A mod that adds more depths and caves to VoxeLibre or Mineclonia author=Noordstar title=Noordstar Caves -depends=mcl_init +depends=dripstone,mcl_init optional_depends=mcl_init,mcl_worlds \ No newline at end of file