From b30272d2dd6b3b319b19a381bf463f965126ba9b Mon Sep 17 00:00:00 2001 From: Treer Date: Sat, 6 Feb 2021 19:49:44 +1100 Subject: [PATCH] Move dungeon excavation functions to separate file --- mapgen.lua | 218 ++-------------------------------------- mapgen_dungeons.lua | 238 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 209 deletions(-) create mode 100644 mapgen_dungeons.lua diff --git a/mapgen.lua b/mapgen.lua index 364cc79..85d5b81 100644 --- a/mapgen.lua +++ b/mapgen.lua @@ -68,7 +68,8 @@ if minetest.read_schematic == nil then error("This " .. nether.modname .. " mapgen requires Minetest v5.1 or greater, use mapgen_nobiomes.lua instead.", 0) end --- Load helper functions for generating the mantle / center region +-- Load mapgen helper functions for specialized areas of the mapgen +dofile(nether.path .. "/mapgen_dungeons.lua") dofile(nether.path .. "/mapgen_mantle.lua") @@ -281,206 +282,11 @@ local dbuf = {} local c_air = minetest.get_content_id("air") local c_netherrack = minetest.get_content_id("nether:rack") local c_netherrack_deep = minetest.get_content_id("nether:rack_deep") -local c_dungeonbrick = minetest.get_content_id("nether:brick") -local c_dungeonbrick_alt = minetest.get_content_id("nether:brick_cracked") -local c_netherbrick_slab = minetest.get_content_id("stairs:slab_nether_brick") -local c_netherfence = minetest.get_content_id("nether:fence_nether_brick") -local c_glowstone = minetest.get_content_id("nether:glowstone") -local c_lava_source = minetest.get_content_id("default:lava_source") local c_lavasea_source = minetest.get_content_id("nether:lava_source") -- same as lava but with staggered animation to look better as an ocean local c_lava_crust = minetest.get_content_id("nether:lava_crust") local c_native_mapgen = minetest.get_content_id("nether:native_mapgen") --- Dungeon excavation functions - -function is_dungeon_brick(node_id) - return node_id == c_dungeonbrick or node_id == c_dungeonbrick_alt -end - -function build_dungeon_room_list(data, area) - - local result = {} - - -- Unfortunately gennotify only returns dungeon rooms, not corridors. - -- We don't need to check for temples because only dungeons are generated in biomes - -- that define their own dungeon nodes. - local gennotify = minetest.get_mapgen_object("gennotify") - local roomLocations = gennotify["dungeon"] or {} - - -- Excavation should still know to stop if a cave or corridor has removed the dungeon wall. - -- See MapgenBasic::generateDungeons in mapgen.cpp for max room sizes. - local maxRoomSize = 18 - local maxRoomRadius = math.ceil(maxRoomSize / 2) - - local xStride, yStride, zStride = 1, area.ystride, area.zstride - local minEdge, maxEdge = area.MinEdge, area.MaxEdge - - for _, roomPos in ipairs(roomLocations) do - - if area:containsp(roomPos) then -- this safety check does not appear to be necessary, but lets make it explicit - - local room_vi = area:indexp(roomPos) - --data[room_vi] = minetest.get_content_id("default:torch") -- debug - - local startPos = vector.new(roomPos) - if roomPos.y + 1 <= maxEdge.y and data[room_vi + yStride] == c_air then - -- The roomPos coords given by gennotify are at floor level, but whenever possible we - -- want to be performing searches a node higher than floor level to avoids dungeon chests. - startPos.y = startPos.y + 1 - room_vi = area:indexp(startPos) - end - - local bound_min_x = math_max(minEdge.x, roomPos.x - maxRoomRadius) - local bound_min_y = math_max(minEdge.y, roomPos.y - 1) -- room coords given by gennotify are on the floor - local bound_min_z = math_max(minEdge.z, roomPos.z - maxRoomRadius) - - local bound_max_x = math_min(maxEdge.x, roomPos.x + maxRoomRadius) - local bound_max_y = math_min(maxEdge.y, roomPos.y + maxRoomSize) -- room coords given by gennotify are on the floor - local bound_max_z = math_min(maxEdge.z, roomPos.z + maxRoomRadius) - - local room_min = vector.new(startPos) - local room_max = vector.new(startPos) - - local vi = room_vi - while room_max.y < bound_max_y and data[vi + yStride] == c_air do - room_max.y = room_max.y + 1 - vi = vi + yStride - end - - vi = room_vi - while room_min.y > bound_min_y and data[vi - yStride] == c_air do - room_min.y = room_min.y - 1 - vi = vi - yStride - end - - vi = room_vi - while room_max.z < bound_max_z and data[vi + zStride] == c_air do - room_max.z = room_max.z + 1 - vi = vi + zStride - end - - vi = room_vi - while room_min.z > bound_min_z and data[vi - zStride] == c_air do - room_min.z = room_min.z - 1 - vi = vi - zStride - end - - vi = room_vi - while room_max.x < bound_max_x and data[vi + xStride] == c_air do - room_max.x = room_max.x + 1 - vi = vi + xStride - end - - vi = room_vi - while room_min.x > bound_min_x and data[vi - xStride] == c_air do - room_min.x = room_min.x - 1 - vi = vi - xStride - end - - local roomInfo = vector.new(roomPos) - roomInfo.minp = room_min - roomInfo.maxp = room_max - result[#result + 1] = roomInfo - end - end - - return result; -end - --- Only partially excavates dungeons, the rest is left as an exercise for the player ;) --- (Corridors and the parts of rooms which extend beyond the emerge boundary will remain filled) -function excavate_dungeons(data, area, rooms) - - local vi, node_id - - -- any air from the native mapgen has been replaced by netherrack, but - -- we don't want this inside dungeons, so fill dungeon rooms with air - for _, roomInfo in ipairs(rooms) do - - local room_min = roomInfo.minp - local room_max = roomInfo.maxp - - for z = room_min.z, room_max.z do - for y = room_min.y, room_max.y do - vi = area:index(room_min.x, y, z) - for x = room_min.x, room_max.x do - node_id = data[vi] - if node_id == c_netherrack or node_id == c_netherrack_deep then data[vi] = c_air end - vi = vi + 1 - end - end - end - end -end - --- Since we already know where all the rooms and their walls are, and have all the nodes stored --- in a voxelmanip already, we may as well add a little Nether flair to the dungeons found here. -function decorate_dungeons(data, area, rooms) - - local xStride, yStride, zStride = 1, area.ystride, area.zstride - local minEdge, maxEdge = area.MinEdge, area.MaxEdge - - for _, roomInfo in ipairs(rooms) do - - local room_min, room_max = roomInfo.minp, roomInfo.maxp - local room_size = vector.distance(room_min, room_max) - - if room_size > 10 then - local room_seed = roomInfo.x + 3 * roomInfo.z + 13 * roomInfo.y - local window_y = roomInfo.y + math_min(2, room_max.y - roomInfo.y - 1) - - if room_seed % 3 == 0 and room_max.y < maxEdge.y then - -- Glowstone chandelier (feel free to replace with a fancy schematic) - local vi = area:index(roomInfo.x, room_max.y + 1, roomInfo.z) - if is_dungeon_brick(data[vi]) then data[vi] = c_glowstone end - - elseif room_seed % 4 == 0 and room_min.y > minEdge.y - and room_min.x > minEdge.x and room_max.x < maxEdge.x - and room_min.z > minEdge.z and room_max.z < maxEdge.z then - -- lava well (feel free to replace with a fancy schematic) - local vi = area:index(roomInfo.x, room_min.y, roomInfo.z) - if is_dungeon_brick(data[vi - yStride]) then - data[vi - yStride] = c_lava_source - if data[vi - zStride] == c_air then data[vi - zStride] = c_netherbrick_slab end - if data[vi + zStride] == c_air then data[vi + zStride] = c_netherbrick_slab end - if data[vi - xStride] == c_air then data[vi - xStride] = c_netherbrick_slab end - if data[vi + xStride] == c_air then data[vi + xStride] = c_netherbrick_slab end - end - end - - -- Barred windows - if room_seed % 7 < 5 and room_max.x - room_min.x >= 4 and room_max.z - room_min.z >= 4 - and window_y >= minEdge.y and window_y + 1 <= maxEdge.y - and room_min.x > minEdge.x and room_max.x < maxEdge.x - and room_min.z > minEdge.z and room_max.z < maxEdge.z then - --data[area:indexp(roomInfo)] = minetest.get_content_id("default:mese_post_light") -- debug - - -- Until whisper glass is added, every window will be made of netherbrick fence (rather - -- than material depending on room_seed) - local window_node = c_netherfence - - local vi_min = area:index(room_min.x - 1, window_y, roomInfo.z) - local vi_max = area:index(room_max.x + 1, window_y, roomInfo.z) - local locations = {-zStride, zStride, -zStride + yStride, zStride + yStride} - for _, offset in ipairs(locations) do - if is_dungeon_brick(data[vi_min + offset]) then data[vi_min + offset] = window_node end - if is_dungeon_brick(data[vi_max + offset]) then data[vi_max + offset] = window_node end - end - vi_min = area:index(roomInfo.x, window_y, room_min.z - 1) - vi_max = area:index(roomInfo.x, window_y, room_max.z + 1) - locations = {-xStride, xStride, -xStride + yStride, xStride + yStride} - for _, offset in ipairs(locations) do - if is_dungeon_brick(data[vi_min + offset]) then data[vi_min + offset] = window_node end - if is_dungeon_brick(data[vi_max + offset]) then data[vi_max + offset] = window_node end - end - end - - -- Weeds on the floor once Nether weeds are added - end - end -end - local yblmin = NETHER_FLOOR + BLEND * 2 local yblmax = NETHER_CEILING - BLEND * 2 @@ -539,18 +345,17 @@ local function on_generated(minp, maxp, seed) nobj_cave = nobj_cave or minetest.get_perlin_map(mapgen.np_cave, chulens) local nvals_cave = nobj_cave:get_3d_map_flat(minp, nbuf_cave) - local dungeonRooms = build_dungeon_room_list(data, area) + local dungeonRooms = mapgen.build_dungeon_room_list(data, area) -- function from mapgen_dungeons.lua local abs_cave_noise, abs_cave_noise_adjusted local contains_nether = false - local contains_shell = false local contains_mantle = false - local contains_ocean = false + local contains_ocean = false for y = y0, y1 do -- Y loop first to minimise tcave & lava-sea calculations - local sea_level, cavern_limit_distance = mapgen.find_nearest_lava_sealevel(y) -- function provided by mapgen_mantle.lua + local sea_level, cavern_limit_distance = mapgen.find_nearest_lava_sealevel(y) -- function from mapgen_mantle.lua local above_lavasea = y > sea_level local below_lavasea = y < sea_level @@ -609,7 +414,6 @@ local function on_generated(minp, maxp, seed) else -- the shell seperating the mantle from the rest of the nether... data[vi] = c_netherrack -- excavate_dungeons() will mostly reverse this inside dungeons - contains_shell = true end end @@ -633,12 +437,12 @@ local function on_generated(minp, maxp, seed) end if contains_mantle or contains_ocean then - mapgen.add_basalt_columns(data, area, minp, maxp) -- function provided by mapgen_mantle.lua + mapgen.add_basalt_columns(data, area, minp, maxp) -- function from mapgen_mantle.lua end if contains_nether and contains_mantle then tunnelCandidate_count = tunnelCandidate_count + 1 - local success = mapgen.excavate_tunnel_to_center_of_the_nether(data, area, nvals_cave, minp, maxp) -- function provided by mapgen_mantle.lua + local success = mapgen.excavate_tunnel_to_center_of_the_nether(data, area, nvals_cave, minp, maxp) -- function from mapgen_mantle.lua if success then tunnel_count = tunnel_count + 1 end end total_chunk_count = total_chunk_count + 1 @@ -655,8 +459,8 @@ local function on_generated(minp, maxp, seed) -- any air from the native mapgen has been replaced by netherrack, but we -- don't want netherrack inside dungeons, so fill known dungeon rooms with air. - excavate_dungeons(data, area, dungeonRooms) - decorate_dungeons(data, area, dungeonRooms) + mapgen.excavate_dungeons(data, area, dungeonRooms) -- function from mapgen_dungeons.lua + mapgen.decorate_dungeons(data, area, dungeonRooms) -- function from mapgen_dungeons.lua vm:set_data(data) @@ -705,8 +509,4 @@ function nether.find_nether_ground_y(target_x, target_z, start_y, player_name) return math_max(start_y, NETHER_FLOOR + BLEND) -- Fallback end --- We don't need to be gen-notified of temples because only dungeons will be generated --- if a biome defines the dungeon nodes -minetest.set_gen_notify({dungeon = true}) - minetest.register_on_generated(on_generated) \ No newline at end of file diff --git a/mapgen_dungeons.lua b/mapgen_dungeons.lua new file mode 100644 index 0000000..ca99ae2 --- /dev/null +++ b/mapgen_dungeons.lua @@ -0,0 +1,238 @@ +--[[ + + Nether mod for minetest + + Helper functions for excavating and decorating dungeons, in a + separate file to keep the size of mapgen.lua manageable. + + + Copyright (C) 2021 Treer + + Permission to use, copy, modify, and/or distribute this software for + any purpose with or without fee is hereby granted, provided that the + above copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR + BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES + OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + SOFTWARE. + +]]-- + + +-- We don't need to be gen-notified of temples because only dungeons will be generated +-- if a biome defines the dungeon nodes +minetest.set_gen_notify({dungeon = true}) + + +-- Content ids + +local c_air = minetest.get_content_id("air") +local c_netherrack = minetest.get_content_id("nether:rack") +local c_netherrack_deep = minetest.get_content_id("nether:rack_deep") +local c_dungeonbrick = minetest.get_content_id("nether:brick") +local c_dungeonbrick_alt = minetest.get_content_id("nether:brick_cracked") +local c_netherbrick_slab = minetest.get_content_id("stairs:slab_nether_brick") +local c_netherfence = minetest.get_content_id("nether:fence_nether_brick") +local c_glowstone = minetest.get_content_id("nether:glowstone") +local c_lava_source = minetest.get_content_id("default:lava_source") + +-- Misc math functions + +-- avoid needing table lookups each time a common math function is invoked +local math_max, math_min = math.max, math.min + + +-- Dungeon excavation functions + +function is_dungeon_brick(node_id) + return node_id == c_dungeonbrick or node_id == c_dungeonbrick_alt +end + + +nether.mapgen.build_dungeon_room_list = function(data, area) + + local result = {} + + -- Unfortunately gennotify only returns dungeon rooms, not corridors. + -- We don't need to check for temples because only dungeons are generated in biomes + -- that define their own dungeon nodes. + local gennotify = minetest.get_mapgen_object("gennotify") + local roomLocations = gennotify["dungeon"] or {} + + -- Excavation should still know to stop if a cave or corridor has removed the dungeon wall. + -- See MapgenBasic::generateDungeons in mapgen.cpp for max room sizes. + local maxRoomSize = 18 + local maxRoomRadius = math.ceil(maxRoomSize / 2) + + local xStride, yStride, zStride = 1, area.ystride, area.zstride + local minEdge, maxEdge = area.MinEdge, area.MaxEdge + + for _, roomPos in ipairs(roomLocations) do + + if area:containsp(roomPos) then -- this safety check does not appear to be necessary, but lets make it explicit + + local room_vi = area:indexp(roomPos) + --data[room_vi] = minetest.get_content_id("default:torch") -- debug + + local startPos = vector.new(roomPos) + if roomPos.y + 1 <= maxEdge.y and data[room_vi + yStride] == c_air then + -- The roomPos coords given by gennotify are at floor level, but whenever possible we + -- want to be performing searches a node higher than floor level to avoids dungeon chests. + startPos.y = startPos.y + 1 + room_vi = area:indexp(startPos) + end + + local bound_min_x = math_max(minEdge.x, roomPos.x - maxRoomRadius) + local bound_min_y = math_max(minEdge.y, roomPos.y - 1) -- room coords given by gennotify are on the floor + local bound_min_z = math_max(minEdge.z, roomPos.z - maxRoomRadius) + + local bound_max_x = math_min(maxEdge.x, roomPos.x + maxRoomRadius) + local bound_max_y = math_min(maxEdge.y, roomPos.y + maxRoomSize) -- room coords given by gennotify are on the floor + local bound_max_z = math_min(maxEdge.z, roomPos.z + maxRoomRadius) + + local room_min = vector.new(startPos) + local room_max = vector.new(startPos) + + local vi = room_vi + while room_max.y < bound_max_y and data[vi + yStride] == c_air do + room_max.y = room_max.y + 1 + vi = vi + yStride + end + + vi = room_vi + while room_min.y > bound_min_y and data[vi - yStride] == c_air do + room_min.y = room_min.y - 1 + vi = vi - yStride + end + + vi = room_vi + while room_max.z < bound_max_z and data[vi + zStride] == c_air do + room_max.z = room_max.z + 1 + vi = vi + zStride + end + + vi = room_vi + while room_min.z > bound_min_z and data[vi - zStride] == c_air do + room_min.z = room_min.z - 1 + vi = vi - zStride + end + + vi = room_vi + while room_max.x < bound_max_x and data[vi + xStride] == c_air do + room_max.x = room_max.x + 1 + vi = vi + xStride + end + + vi = room_vi + while room_min.x > bound_min_x and data[vi - xStride] == c_air do + room_min.x = room_min.x - 1 + vi = vi - xStride + end + + local roomInfo = vector.new(roomPos) + roomInfo.minp = room_min + roomInfo.maxp = room_max + result[#result + 1] = roomInfo + end + end + + return result; +end + +-- Only partially excavates dungeons, the rest is left as an exercise for the player ;) +-- (Corridors and the parts of rooms which extend beyond the emerge boundary will remain filled) +nether.mapgen.excavate_dungeons = function(data, area, rooms) + + local vi, node_id + + -- any air from the native mapgen has been replaced by netherrack, but + -- we don't want this inside dungeons, so fill dungeon rooms with air + for _, roomInfo in ipairs(rooms) do + + local room_min = roomInfo.minp + local room_max = roomInfo.maxp + + for z = room_min.z, room_max.z do + for y = room_min.y, room_max.y do + vi = area:index(room_min.x, y, z) + for x = room_min.x, room_max.x do + node_id = data[vi] + if node_id == c_netherrack or node_id == c_netherrack_deep then data[vi] = c_air end + vi = vi + 1 + end + end + end + end +end + +-- Since we already know where all the rooms and their walls are, and have all the nodes stored +-- in a voxelmanip already, we may as well add a little Nether flair to the dungeons found here. +nether.mapgen.decorate_dungeons = function(data, area, rooms) + + local xStride, yStride, zStride = 1, area.ystride, area.zstride + local minEdge, maxEdge = area.MinEdge, area.MaxEdge + + for _, roomInfo in ipairs(rooms) do + + local room_min, room_max = roomInfo.minp, roomInfo.maxp + local room_size = vector.distance(room_min, room_max) + + if room_size > 10 then + local room_seed = roomInfo.x + 3 * roomInfo.z + 13 * roomInfo.y + local window_y = roomInfo.y + math_min(2, room_max.y - roomInfo.y - 1) + + if room_seed % 3 == 0 and room_max.y < maxEdge.y then + -- Glowstone chandelier (feel free to replace with a fancy schematic) + local vi = area:index(roomInfo.x, room_max.y + 1, roomInfo.z) + if is_dungeon_brick(data[vi]) then data[vi] = c_glowstone end + + elseif room_seed % 4 == 0 and room_min.y > minEdge.y + and room_min.x > minEdge.x and room_max.x < maxEdge.x + and room_min.z > minEdge.z and room_max.z < maxEdge.z then + -- lava well (feel free to replace with a fancy schematic) + local vi = area:index(roomInfo.x, room_min.y, roomInfo.z) + if is_dungeon_brick(data[vi - yStride]) then + data[vi - yStride] = c_lava_source + if data[vi - zStride] == c_air then data[vi - zStride] = c_netherbrick_slab end + if data[vi + zStride] == c_air then data[vi + zStride] = c_netherbrick_slab end + if data[vi - xStride] == c_air then data[vi - xStride] = c_netherbrick_slab end + if data[vi + xStride] == c_air then data[vi + xStride] = c_netherbrick_slab end + end + end + + -- Barred windows + if room_seed % 7 < 5 and room_max.x - room_min.x >= 4 and room_max.z - room_min.z >= 4 + and window_y >= minEdge.y and window_y + 1 <= maxEdge.y + and room_min.x > minEdge.x and room_max.x < maxEdge.x + and room_min.z > minEdge.z and room_max.z < maxEdge.z then + --data[area:indexp(roomInfo)] = minetest.get_content_id("default:mese_post_light") -- debug + + -- Until whisper glass is added, every window will be made of netherbrick fence (rather + -- than material depending on room_seed) + local window_node = c_netherfence + + local vi_min = area:index(room_min.x - 1, window_y, roomInfo.z) + local vi_max = area:index(room_max.x + 1, window_y, roomInfo.z) + local locations = {-zStride, zStride, -zStride + yStride, zStride + yStride} + for _, offset in ipairs(locations) do + if is_dungeon_brick(data[vi_min + offset]) then data[vi_min + offset] = window_node end + if is_dungeon_brick(data[vi_max + offset]) then data[vi_max + offset] = window_node end + end + vi_min = area:index(roomInfo.x, window_y, room_min.z - 1) + vi_max = area:index(roomInfo.x, window_y, room_max.z + 1) + locations = {-xStride, xStride, -xStride + yStride, xStride + yStride} + for _, offset in ipairs(locations) do + if is_dungeon_brick(data[vi_min + offset]) then data[vi_min + offset] = window_node end + if is_dungeon_brick(data[vi_max + offset]) then data[vi_max + offset] = window_node end + end + end + + -- Weeds on the floor once Nether weeds are added + end + end +end