VoxeLibre/mods/MAPGEN/vl_structures/emerge.lua
2024-09-20 17:19:19 +02:00

179 lines
10 KiB
Lua

local vector_offset = vector.offset
local floor = math.floor
local logging = minetest.settings:get_bool("vl_structures_logging", false)
local mg_name = minetest.get_mapgen_setting("mg_name")
-- parse the prepare parameter
local function parse_prepare(prepare)
if prepare == nil or prepare == true then return vl_structures.DEFAULT_PREPARE end
if prepare == false then return {} end
if prepare.foundation == true then prepare.foundation = vl_structures.DEFAULT_PREPARE.foundation end
return prepare
end
-- check "enabled" tolerances
local function tolerance_enabled(tolerance, mode)
return mode ~= "off" and tolerance and (tolerance == "max" or tolerance == "min" or tolerance >= 0) and true
end
--- Main palcement step, when the area has been emerged
local function emerge_schematics(blockpos, action, calls_remaining, param)
if calls_remaining >= 1 then return end
local start = os.clock()
local vm = VoxelManip()
vm:read_from_map(param.emin, param.emax)
local startmain = os.clock()
local pos, size, yoffset, def, pr = param.pos, param.size, param.yoffset or 0, param.def, param.pr
local prepare, surface_mat = parse_prepare(param.prepare or def.prepare), param.surface_mat
-- Step 0: pick random daughter schematics + rotations
local daughters = {}
for i,d in pairs(def.daughters or {}) do
if not d.schematics or #d.schematics == 0 then
error("Daughter schematics not loaded for structure "..def.name)
end
local ds = d.schematics[#d.schematics > 1 and pr:next(1,#d.schematics) or 1]
local rotation = vl_structures.parse_rotation(d.rotation, pr)
table.insert(daughters, {d, ds, rotation})
end
-- Step 1: adjust ground to a more level position
-- todo: also support checking ground of daughter schematics, but not used by current schematics
if pos and size and prepare and tolerance_enabled(prepare.tolerance, prepare.mode) then
pos, surface_mat = vl_terraforming.find_level_vm(vm, pos, size, prepare.tolerance, prepare.mode)
if not pos then
minetest.log("warning", "[vl_structures] Not spawning "..tostring(def.name or param.schematic.name).." at "..minetest.pos_to_string(param.pos).." because ground is too uneven.")
return
end
-- obey height restrictions, to not violate nether roof
if def.y_max and pos.y - yoffset > def.y_max then pos.y = def.y_max - yoffset end
if def.y_min and pos.y - yoffset < def.y_min then pos.y = def.y_min - yoffset end
end
-- Placement area from center position:
local pmin = vector_offset(pos, -floor((size.x-1)*0.5), yoffset, -floor((size.z-1)*0.5))
local pmax = vector_offset(pmin, size.x-1, size.y-1, size.z-1)
-- Step 2: prepare ground foundations and clear
-- todo: allow daugthers to use prepare when parent does not, currently not used
if prepare and (prepare.clear or prepare.foundation) then
local prepare_start = os.clock()
-- Get materials from biome (TODO: make this a function + table?):
local b = mg_name ~= "v6" and minetest.registered_biomes[minetest.get_biome_name(minetest.get_biome_data(pos).biome)]
local node_top = b and b.node_top and { name = b.node_top } or surface_mat or vl_structures.DEFAULT_SURFACE
local node_filler = b and b.node_filler and { name = b.node_filler } or vl_structures.DEFAULT_FILLER
local node_stone = b and b.node_stone and { name = b.node_stone } or vl_structures.DEFAULT_STONE
local node_dust = b and b.node_dust and { name = b.node_dust } or vl_structures.DEFAULT_DUST
if node_top.name == "mcl_core:dirt_with_grass" and b then node_top.param2 = b._mcl_grass_palette_index end
-- Step 2a: clear overhead area
local corners, padding = prepare.corners or 1, prepare.padding or 1
local gp = vector_offset(pmin, -padding, -yoffset, -padding) -- base level
if prepare.clear then
local yoff, ymax = prepare.clear_bottom or 0, size.y + yoffset + (prepare.clear_top or vl_structures.DEFAULT_PREPARE.clear_top)
if prepare.clear_bottom == "top" or prepare.clear_bottom == "above" then yoff = size.y + yoffset end
--minetest.log("action", "[vl_structures] clearing air "..minetest.pos_to_string(gp)..": ".. (size.x + padding * 2)..","..ymax..","..(size.z + padding * 2))
vl_terraforming.clearance_vm(vm, gp.x, gp.y + yoff, gp.z,
size.x + padding * 2, ymax - yoff, size.z + padding * 2,
corners, node_top, node_dust, pr)
-- clear for daughters
for _,tmp in ipairs(daughters) do
local dd, ds, dr = tmp[1], tmp[2], tmp[3]
local ddp = parse_prepare(dd.prepare)
if ddp and ddp.clear then
local dsize = vl_structures.size_rotated(ds.size, dr) -- FIXME: rotation of parent
local corners, padding, yoffset = ddp.corners or 1, ddp.padding or 1, ddp.yoffset or 0
local yoff, ymax = ddp.clear_bottom or 0, dsize.y + yoffset + (ddp.clear_top or vl_structures.DEFAULT_PREPARE.clear_top)
if ddp.clear_bottom == "top" or ddp.clear_bottom == "above" then yoff = dsize.y + yoffset end
local gp = vector_offset(pos, dd.pos.x - floor((dsize.x-1)*0.5) - padding,
dd.pos.y,
dd.pos.z - floor((dsize.z-1)*0.5) - padding)
local sy = ymax - yoff
--minetest.log("action", "[vl_structures] clearing air "..minetest.pos_to_string(gp)..": ".. (dsize.x + padding * 2)..","..sy..","..(dsize.z + padding * 2))
if sy > 0 then
vl_terraforming.clearance_vm(vm, gp.x, gp.y + yoff, gp.z,
dsize.x + padding * 2, ymax - yoff, dsize.z + padding * 2,
corners, node_top, node_dust, pr)
end
end
end
end
-- Step 2b: baseplate underneath
if prepare.foundation then
-- minetest.log("action", "[vl_structures] fill foundation "..minetest.pos_to_string(gp).." with "..tostring(node_top.name).." "..tostring(node_filler.name))
local depth = (type(prepare.foundation) == "number" and prepare.foundation) or vl_structures.DEFAULT_PREPARE.foundation
vl_terraforming.foundation_vm(vm, gp.x, gp.y - 1, gp.z,
size.x + padding * 2, depth, size.z + padding * 2,
corners, node_top, node_filler, node_stone, node_dust, pr)
-- foundation for daughters
for _, tmp in ipairs(daughters) do
local dd, ds, dr = tmp[1], tmp[2], tmp[3]
local ddp = parse_prepare(dd.prepare)
if ddp and ddp.foundation then
local dsize = vl_structures.size_rotated(ds.size, dr) -- FIXME: rotation of parent
local corners, padding, yoffset = ddp.corners or 1, ddp.padding or 1, ddp.yoffset or 0
local depth = (type(ddp.foundation) == "number" and ddp.foundation) or vl_structures.DEFAULT_PREPARE.foundation
local gp = vector_offset(pos, dd.pos.x - floor((dsize.x-1)*0.5) - padding,
dd.pos.y + (yoffset or 0),
dd.pos.z - floor((dsize.z-1)*0.5) - padding)
vl_terraforming.foundation_vm(vm, gp.x, gp.y - 1, gp.z,
dsize.x + padding * 2, depth, dsize.z + padding * 2,
corners, node_top, node_filler, node_stone, node_dust, pr)
end
end
end
end
-- Step 3: place schematic on center position
minetest.place_schematic_on_vmanip(vm, pmin, param.schematic, param.rotation, param.replacements, param.force_placement, "")
-- Step 3: place daughter schematics
for _,tmp in ipairs(daughters) do
local d, ds, rot = tmp[1], tmp[2], tmp[3]
local p = vector_offset(pos, d.pos.x, d.pos.y + (yoffset or 0), d.pos.z)
minetest.place_schematic_on_vmanip(vm, p, ds, rot, d.replacements, d.force_placement, "place_center_x,place_center_z")
-- todo: allow after_place callbacks for daughter schematics?
end
local endmain = os.clock()
vm:write_to_map(true)
-- Note: deliberately pos, p1 and p2 from the parent, as these are calls to the parent script
if def.loot then vl_structures.fill_chests(pmin,pmax,def.loot,pr) end
if def.construct_nodes then vl_structures.construct_nodes(pmin,pmax,def.construct_nodes) end
if def.after_place then def.after_place(pos,def,pr,pmin,pmax,size,param.rotation) end
if logging and not def.terrain_feature then
minetest.log("action", "[vl_structures] "..(def.name or "unnamed").." spawned at "..minetest.pos_to_string(pos).." in "..string.format("%.2fms (main: %.2fms)", (os.clock()-start)*1000, (endmain-startmain)*1000))
end
end
--- Wrapper to emerge an appropriate area for a schematic (with daughters, such as nether bulwark, nether outpost with bridges)
vl_structures.place_schematic = function(pos, yoffset, schematic, rotation, def, pr)
if schematic and not schematic.size then schematic = vl_structures.load_schematic(schematic) end -- legacy
local rotation = vl_structures.parse_rotation(rotation, pr)
local prepare = parse_prepare(def.prepare)
local ppos, pmin, pmax, size = vl_structures.get_extends(pos, schematic.size, yoffset, rotation, def.flags or vl_structures.DEFAULT_FLAGS)
-- area to emerge. Add some margin to allow for finding better suitable ground etc.
local tolerance = prepare.tolerance or vl_structures.DEFAULT_PREPARE.tolerance -- may be negative to disable foundations
if type(tolerance) ~= "number" then tolerance = 10 end -- extra height for emerge only, min/max/liquid_surface
local emin, emax = vector_offset(pmin, 0, -math.max(tolerance, 0), 0), vector.offset(pmax, 0, math.max(tolerance, 0), 0)
-- if we need to generate a foundation, we need to emerge a larger area:
if prepare.foundation or prepare.clear then -- these functions need some extra margins. Must match vl_terraforming!
local padding = (prepare.padding or 0) + 3
local depth = prepare.foundation and ((type(prepare.foundation) == "number" and prepare.foundation or vl_structures.DEFAULT_PREPARE.foundation) - 3) or 0 -- minimum depth
local height = prepare.clear and ((prepare.clear_top or vl_structures.DEFAULT_PREPARE.clear_top)*1.5+0.5*(size.y+yoffset)+2) or 0 -- headroom
emin = vector_offset(emin, -padding, depth, -padding)
emax = vector_offset(emax, padding, height, padding)
end
-- finally, add the configured emerge margin for daugther schematics
-- TODO: compute this instead? But we do not know rotations and sizes of daughters yet
if def.emerge_padding then
if #def.emerge_padding ~= 2 then error("Schematic "..def.name.." has an incorrect 'emerge_padding'. Must be two vectors.") end
emin, emax = emin + def.emerge_padding[1], emax + def.emerge_padding[2]
end
-- if logging and not def.terrain_feature then minetest.log("action", "[vl_structures] "..def.name.." needs emerge "..minetest.pos_to_string(emin).."-"..minetest.pos_to_string(emax)) end
minetest.emerge_area(emin, emax, emerge_schematics, { name = def.name,
emin=emin, emax=emax, def=def, schematic=schematic,
pos=ppos, yoffset=yoffset, size=size, rotation=rotation,
pr=pr
})
end