Move dungeon room scanning to mapgen thread

This commit is contained in:
sfan5 2024-09-17 22:13:28 +02:00
parent 2ce8ff3306
commit 8b8ad1106c
2 changed files with 117 additions and 78 deletions

View file

@ -14,62 +14,6 @@ local function random_sample(rand, list, count)
return ret return ret
end 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) local function populate_chest(pos, rand, dungeontype)
--minetest.chat_send_all("chest placed at " .. minetest.pos_to_string(pos) .. " [" .. 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}) --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
end end
local function place_chests(candidates, blockseed)
minetest.register_on_generated(function(minp, maxp, blockseed) if #candidates == 0 then
local gennotify = minetest.get_mapgen_object("gennotify") return
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 end
if #poslist == 0 then return end
local noise = minetest.get_perlin(10115, 4, 0.5, 1) local noise = minetest.get_perlin(10115, 4, 0.5, 1)
local rand = PcgRandom(noise3d_integer(noise, poslist[1])) local rand = PcgRandom(blockseed)
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 num_chests = rand:next(dungeon_loot.CHESTS_MIN, dungeon_loot.CHESTS_MAX) local num_chests = rand:next(dungeon_loot.CHESTS_MIN, dungeon_loot.CHESTS_MAX)
num_chests = math.min(#candidates, num_chests) 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) populate_chest(chestpos, PcgRandom(noise3d_integer(noise, chestpos)), room.type)
end end
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

View file

@ -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