From 8b8ad1106c3cb73ce0e972875429d81dbedfe532 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 17 Sep 2024 22:13:28 +0200 Subject: [PATCH] Move dungeon room scanning to mapgen thread --- mods/dungeon_loot/mapgen.lua | 103 +++++++++-------------------------- mods/dungeon_loot/scan.lua | 92 +++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 78 deletions(-) create mode 100644 mods/dungeon_loot/scan.lua diff --git a/mods/dungeon_loot/mapgen.lua b/mods/dungeon_loot/mapgen.lua index 3b597f2efb..2bca1f3229 100644 --- a/mods/dungeon_loot/mapgen.lua +++ b/mods/dungeon_loot/mapgen.lua @@ -14,62 +14,6 @@ local function random_sample(rand, list, count) return ret end -local function find_walls(cpos, is_temple) - local is_wall = function(node) - return node.name ~= "air" and node.name ~= "ignore" - end - - local dirs = {{x=1, z=0}, {x=-1, z=0}, {x=0, z=1}, {x=0, z=-1}} - local get_node = minetest.get_node - - local ret = {} - local mindist = {x=0, z=0} - local min = function(a, b) return a ~= 0 and math.min(a, b) or b end - for _, dir in ipairs(dirs) do - for i = 1, 9 do -- 9 = max room size / 2 - local pos = vector.add(cpos, {x=dir.x*i, y=0, z=dir.z*i}) - - -- continue in that direction until we find a wall-like node - local node = get_node(pos) - if is_wall(node) then - local front_below = vector.subtract(pos, {x=dir.x, y=1, z=dir.z}) - local above = vector.add(pos, {x=0, y=1, z=0}) - - -- check that it: - --- is at least 2 nodes high (not a staircase) - --- has a floor - if is_wall(get_node(front_below)) and is_wall(get_node(above)) then - table.insert(ret, {pos = pos, facing = {x=-dir.x, y=0, z=-dir.z}}) - if dir.z == 0 then - mindist.x = min(mindist.x, i-1) - else - mindist.z = min(mindist.z, i-1) - end - end - -- abort even if it wasn't a wall cause something is in the way - break - end - end - end - - local biome = minetest.get_biome_data(cpos) - biome = biome and minetest.get_biome_name(biome.biome) or "" - local type = "normal" - if is_temple or biome:find("desert") == 1 then - type = "desert" - elseif biome:find("sandstone_desert") == 1 then - type = "sandstone" - elseif biome:find("icesheet") == 1 then - type = "ice" - end - - return { - walls = ret, - size = {x=mindist.x*2, z=mindist.z*2}, - type = type, - } -end - local function populate_chest(pos, rand, dungeontype) --minetest.chat_send_all("chest placed at " .. minetest.pos_to_string(pos) .. " [" .. dungeontype .. "]") --minetest.add_node(vector.add(pos, {x=0, y=1, z=0}), {name="default:torch", param2=1}) @@ -121,30 +65,13 @@ local function populate_chest(pos, rand, dungeontype) end end - -minetest.register_on_generated(function(minp, maxp, blockseed) - local gennotify = minetest.get_mapgen_object("gennotify") - local poslist = gennotify["dungeon"] or {} - local n_dungeons = #poslist - -- Add MGv6 desert temples to the list too - for _, entry in ipairs(gennotify["temple"] or {}) do - table.insert(poslist, entry) +local function place_chests(candidates, blockseed) + if #candidates == 0 then + return end - if #poslist == 0 then return end local noise = minetest.get_perlin(10115, 4, 0.5, 1) - local rand = PcgRandom(noise3d_integer(noise, poslist[1])) - - local candidates = {} - -- process at most 8 rooms to keep runtime of this predictable - local num_process = math.min(#poslist, 8) - for i = 1, num_process do - local room = find_walls(poslist[i], i > n_dungeons) - -- skip small rooms and everything that doesn't at least have 3 walls - if math.min(room.size.x, room.size.z) >= 4 and #room.walls >= 3 then - table.insert(candidates, room) - end - end + local rand = PcgRandom(blockseed) local num_chests = rand:next(dungeon_loot.CHESTS_MIN, dungeon_loot.CHESTS_MAX) num_chests = math.min(#candidates, num_chests) @@ -170,4 +97,24 @@ minetest.register_on_generated(function(minp, maxp, blockseed) populate_chest(chestpos, PcgRandom(noise3d_integer(noise, chestpos)), room.type) end end -end) +end + +if minetest.register_mapgen_script then + -- dungeon rooms are scanned in mapgen thread + minetest.set_gen_notify({custom=true}, nil, {"dungeon_loot:candidates"}) + minetest.register_mapgen_script(minetest.get_modpath("dungeon_loot") .. "/scan.lua") + minetest.register_on_generated(function(minp, maxp, blockseed) + local gennotify = minetest.get_mapgen_object("gennotify") + local candidates = gennotify.custom["dungeon_loot:candidates"] + assert(type(candidates) == "table", "candidates must exist") + place_chests(candidates, blockseed) + end) +else + -- dungeon rooms are scanned in callback here + dofile(minetest.get_modpath("dungeon_loot") .. "/scan.lua") + minetest.register_on_generated(function(minp, maxp, blockseed) + -- process at most 8 rooms to keep runtime of this predictable + local candidates = dungeon_loot._internal_find_rooms(8) + place_chests(candidates, blockseed) + end) +end diff --git a/mods/dungeon_loot/scan.lua b/mods/dungeon_loot/scan.lua new file mode 100644 index 0000000000..f01e7ba565 --- /dev/null +++ b/mods/dungeon_loot/scan.lua @@ -0,0 +1,92 @@ +if minetest.save_gen_notify then + dungeon_loot = {} +end + +local function find_walls(cpos, is_temple) + local is_wall = function(node) + return node.name ~= "air" and node.name ~= "ignore" + end + + local dirs = {{x=1, z=0}, {x=-1, z=0}, {x=0, z=1}, {x=0, z=-1}} + local get_node = minetest.get_node + + local ret = {} + local mindist = {x=0, z=0} + local min = function(a, b) return a ~= 0 and math.min(a, b) or b end + for _, dir in ipairs(dirs) do + for i = 1, 9 do -- 9 = max room size / 2 + local pos = vector.add(cpos, {x=dir.x*i, y=0, z=dir.z*i}) + + -- continue in that direction until we find a wall-like node + local node = get_node(pos) + if is_wall(node) then + local front_below = vector.subtract(pos, {x=dir.x, y=1, z=dir.z}) + local above = vector.add(pos, {x=0, y=1, z=0}) + + -- check that it: + --- is at least 2 nodes high (not a staircase) + --- has a floor + if is_wall(get_node(front_below)) and is_wall(get_node(above)) then + table.insert(ret, {pos = pos, facing = {x=-dir.x, y=0, z=-dir.z}}) + if dir.z == 0 then + mindist.x = min(mindist.x, i-1) + else + mindist.z = min(mindist.z, i-1) + end + end + -- abort even if it wasn't a wall cause something is in the way + break + end + end + end + + local biome = minetest.get_biome_data(cpos) + biome = biome and minetest.get_biome_name(biome.biome) or "" + local type = "normal" + if is_temple or biome:find("desert") == 1 then + type = "desert" + elseif biome:find("sandstone_desert") == 1 then + type = "sandstone" + elseif biome:find("icesheet") == 1 then + type = "ice" + end + + return { + walls = ret, + size = {x=mindist.x*2, z=mindist.z*2}, + type = type, + } +end + +function dungeon_loot._internal_find_rooms(max_rooms) + local gennotify = minetest.get_mapgen_object("gennotify") + local poslist = gennotify["dungeon"] or {} + local n_dungeons = #poslist + -- Add MGv6 desert temples to the list too + for _, entry in ipairs(gennotify["temple"] or {}) do + table.insert(poslist, entry) + end + if #poslist == 0 then + return {} + end + + local candidates = {} + local num_process = math.min(#poslist, max_rooms) + for i = 1, num_process do + local room = find_walls(poslist[i], i > n_dungeons) + -- skip small rooms and everything that doesn't at least have 3 walls + if math.min(room.size.x, room.size.z) >= 4 and #room.walls >= 3 then + table.insert(candidates, room) + end + end + + return candidates +end + +-- if loaded inside mapgen env, do our job there and export the result +if minetest.save_gen_notify then + minetest.register_on_generated(function(vmanip, minp, maxp, blockseed) + local candidates = dungeon_loot._internal_find_rooms(9999) + minetest.save_gen_notify("dungeon_loot:candidates", candidates) + end) +end