2024-08-24 19:24:31 +02:00
local vector_offset = vector.offset
local floor = math.floor
2024-09-05 01:03:33 +02:00
local logging = minetest.settings : get_bool ( " vl_structures_logging " , false )
2024-08-24 19:24:31 +02:00
local mg_name = minetest.get_mapgen_setting ( " mg_name " )
-- parse the prepare parameter
local function parse_prepare ( prepare )
2024-09-05 01:03:33 +02:00
if prepare == nil or prepare == true then return vl_structures.DEFAULT_PREPARE end
2024-08-24 19:24:31 +02:00
if prepare == false then return { } end
2024-09-05 01:03:33 +02:00
if prepare.foundation == true then prepare.foundation = vl_structures.DEFAULT_PREPARE . foundation end
2024-08-24 19:24:31 +02:00
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
2024-10-20 21:00:12 +02:00
local dust_mat = nil
2024-08-24 19:24:31 +02:00
-- 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
2024-10-20 21:00:12 +02:00
-- hack to get dust nodes more often, in case the mapgen messed with biomes
local n = vm : get_node_at ( vector_offset ( param.opos , 0 , 1 , 0 ) )
if n.name == " mcl_core:snow " then dust_mat = n end
2024-08-24 19:24:31 +02:00
-- 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?):
2024-10-20 21:00:12 +02:00
-- use original position to not get a different biome than expected for the structure
local b = mg_name ~= " v6 " and minetest.registered_biomes [ minetest.get_biome_name ( minetest.get_biome_data ( vector_offset ( param.pos , 0 , 1 , 0 ) ) . biome ) ]
--minetest.log("action", tostring(def.name or param.schematic.name).." in biome: "..dump(b,""):gsub("\n",""))
2024-09-05 01:03:33 +02:00
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
2024-10-20 21:00:12 +02:00
local node_dust = b and b.node_dust and { name = b.node_dust } or dust_mat or vl_structures.DEFAULT_DUST
2024-08-24 19:24:31 +02:00
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
2024-09-05 01:03:33 +02:00
local corners , padding = prepare.corners or 1 , prepare.padding or 1
2024-08-24 19:24:31 +02:00
local gp = vector_offset ( pmin , - padding , - yoffset , - padding ) -- base level
if prepare.clear then
2024-09-05 01:03:33 +02:00
local yoff , ymax = prepare.clear_bottom or 0 , size.y + yoffset + ( prepare.clear_top or vl_structures.DEFAULT_PREPARE . clear_top )
2024-08-24 19:24:31 +02:00
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
2024-09-05 01:03:33 +02:00
local yoff , ymax = ddp.clear_bottom or 0 , dsize.y + yoffset + ( ddp.clear_top or vl_structures.DEFAULT_PREPARE . clear_top )
2024-08-24 19:24:31 +02:00
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
2024-10-20 21:00:12 +02:00
-- minetest.log("action", "[vl_structures] "..tostring(def.name or param.schematic.name).." fill foundation "..minetest.pos_to_string(gp).." with "..tostring(node_top.name).." "..tostring(node_filler.name).." "..tostring(node_dust and node_dust.name))
2024-09-05 01:03:33 +02:00
local depth = ( type ( prepare.foundation ) == " number " and prepare.foundation ) or vl_structures.DEFAULT_PREPARE . foundation
2024-08-24 19:24:31 +02:00
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
2024-09-05 01:03:33 +02:00
local depth = ( type ( ddp.foundation ) == " number " and ddp.foundation ) or vl_structures.DEFAULT_PREPARE . foundation
2024-08-24 19:24:31 +02:00
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 ( )
2024-10-20 21:00:12 +02:00
-- TODO: step 4: sprinkle extra dust on top.
2024-08-24 19:24:31 +02:00
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
2024-09-05 23:13:08 +02:00
if def.name and not ( def.terrain_feature or def.no_registry ) then vl_structures.register_structures_spawn ( def.name , pos ) end
2024-08-24 19:24:31 +02:00
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 )
2024-09-05 01:03:33 +02:00
local ppos , pmin , pmax , size = vl_structures.get_extends ( pos , schematic.size , yoffset , rotation , def.flags or vl_structures.DEFAULT_FLAGS )
2024-08-24 19:24:31 +02:00
-- area to emerge. Add some margin to allow for finding better suitable ground etc.
2024-09-05 01:03:33 +02:00
local tolerance = prepare.tolerance or vl_structures.DEFAULT_PREPARE . tolerance -- may be negative to disable foundations
2024-08-24 19:24:31 +02:00
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:
2024-09-05 01:03:33 +02:00
if prepare.foundation or prepare.clear then -- these functions need some extra margins. Must match vl_terraforming!
2024-08-24 19:24:31 +02:00
local padding = ( prepare.padding or 0 ) + 3
2024-09-05 01:03:33 +02:00
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
2024-08-24 19:24:31 +02:00
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 ,
2024-10-20 21:00:12 +02:00
pos = ppos , opos = pos , yoffset = yoffset , size = size , rotation = rotation ,
2024-08-24 19:24:31 +02:00
pr = pr
} )
end