From e8aeb34b93658110292247763196aec99a979f84 Mon Sep 17 00:00:00 2001 From: kno10 Date: Sat, 24 Aug 2024 19:24:31 +0200 Subject: [PATCH] code restructuring and cleanups --- mods/ITEMS/mcl_core/nodes_cactuscane.lua | 16 +- mods/ITEMS/mcl_crimson/init.lua | 4 +- mods/ITEMS/mcl_portals/portal_gateway.lua | 9 +- mods/MAPGEN/mcl_structures/igloo.lua | 34 +- mods/MAPGEN/mcl_structures/init.lua | 26 +- mods/MAPGEN/mcl_villages/init.lua | 10 +- mods/MAPGEN/vl_structures/api.lua | 362 +++------------------- mods/MAPGEN/vl_structures/commands.lua | 44 +++ mods/MAPGEN/vl_structures/emerge.lua | 187 +++++++++++ mods/MAPGEN/vl_structures/init.lua | 48 +-- mods/MAPGEN/vl_structures/spawning.lua | 50 +++ mods/MAPGEN/vl_structures/util.lua | 2 +- mods/MAPGEN/vl_terraforming/level.lua | 23 +- settingtypes.txt | 6 +- 14 files changed, 406 insertions(+), 415 deletions(-) create mode 100644 mods/MAPGEN/vl_structures/commands.lua create mode 100644 mods/MAPGEN/vl_structures/emerge.lua create mode 100644 mods/MAPGEN/vl_structures/spawning.lua diff --git a/mods/ITEMS/mcl_core/nodes_cactuscane.lua b/mods/ITEMS/mcl_core/nodes_cactuscane.lua index 805385124..9b69cdcec 100644 --- a/mods/ITEMS/mcl_core/nodes_cactuscane.lua +++ b/mods/ITEMS/mcl_core/nodes_cactuscane.lua @@ -1,5 +1,4 @@ -- Cactus and Sugar Cane - local S = minetest.get_translator(minetest.get_current_modname()) minetest.register_node("mcl_core:cactus", { @@ -42,9 +41,8 @@ minetest.register_node("mcl_core:cactus", { }, -- Only allow to place cactus on sand or cactus on_place = mcl_util.generate_on_place_plant_function(function(pos, node) - local node_below = minetest.get_node_or_nil({x=pos.x,y=pos.y-1,z=pos.z}) - if not node_below then return false end - return (node_below.name == "mcl_core:cactus" or minetest.get_item_group(node_below.name, "sand") == 1) + local node_below = minetest.get_node_or_nil(vector.offset(pos, 0, -1, 0)) + return node_below and (node_below.name == "mcl_core:cactus" or minetest.get_item_group(node_below.name, "sand") == 1) end), _mcl_blast_resistance = 0.4, _mcl_hardness = 0.4, @@ -90,7 +88,7 @@ minetest.register_node("mcl_core:reeds", { node_placement_prediction = "", drop = "mcl_core:reeds", -- to prevent color inheritation on_place = mcl_util.generate_on_place_plant_function(function(place_pos, place_node) - local soil_pos = {x=place_pos.x, y=place_pos.y-1, z=place_pos.z} + local soil_pos = vector.new(place_pos.x, place_pos.y-1, place_pos.z) local soil_node = minetest.get_node_or_nil(soil_pos) if not soil_node then return false end local snn = soil_node.name -- soil node name @@ -113,16 +111,12 @@ minetest.register_node("mcl_core:reeds", { -- Legal water position rules are the same as for decoration spawn_by rules. -- This differs from MC, which does not allow diagonal neighbors -- and neighbors 1 layer above. - local np1 = {x=soil_pos.x-1, y=soil_pos.y, z=soil_pos.z-1} - local np2 = {x=soil_pos.x+1, y=soil_pos.y+1, z=soil_pos.z+1} - if #minetest.find_nodes_in_area(np1, np2, {"group:water", "group:frosted_ice"}) > 0 then + if #minetest.find_nodes_in_area(vector.offset(soil_pos, -1, 0, -1), vector.offset(soil_pos, 1, 1, 1), {"group:water", "group:frosted_ice"}) > 0 then -- Water found! Sugar canes are happy! :-) return true end - -- No water found! Sugar canes are not amuzed and refuses to be placed. :-( return false - end), on_construct = function(pos) local node = minetest.get_node(pos) @@ -135,4 +129,4 @@ minetest.register_node("mcl_core:reeds", { end, _mcl_blast_resistance = 0, _mcl_hardness = 0, -}) \ No newline at end of file +}) diff --git a/mods/ITEMS/mcl_crimson/init.lua b/mods/ITEMS/mcl_crimson/init.lua index 9ec326a3c..26807a490 100644 --- a/mods/ITEMS/mcl_crimson/init.lua +++ b/mods/ITEMS/mcl_crimson/init.lua @@ -9,11 +9,11 @@ local wood_slab_groups = {handy = 1, axey = 1, material_wood = 1, wood_slab = 1} local wood_stair_groups = {handy = 1, axey = 1, material_wood = 1, wood_stairs = 1} local function generate_warped_tree(pos) - minetest.place_schematic(pos,modpath.."/schematics/warped_fungus_1.mts","random",nil,false,"place_center_x,place_center_z") + minetest.place_schematic(pos,modpath.."/schematics/warped_fungus_"..math.random(1,3)..".mts","random",nil,false,"place_center_x,place_center_z") end function generate_crimson_tree(pos) - minetest.place_schematic(pos,modpath.."/schematics/crimson_fungus_1.mts","random",nil,false,"place_center_x,place_center_z") + minetest.place_schematic(pos,modpath.."/schematics/crimson_fungus_"..math.random(1,3)..".mts","random",nil,false,"place_center_x,place_center_z") end function grow_vines(pos, moreontop ,vine, dir) diff --git a/mods/ITEMS/mcl_portals/portal_gateway.lua b/mods/ITEMS/mcl_portals/portal_gateway.lua index 124487971..e09e598ae 100644 --- a/mods/ITEMS/mcl_portals/portal_gateway.lua +++ b/mods/ITEMS/mcl_portals/portal_gateway.lua @@ -29,10 +29,13 @@ local gateway_positions = { local path_gateway_portal = minetest.get_modpath("mcl_structures").."/schematics/mcl_structures_end_gateway_portal.mts" local function spawn_gateway_portal(pos, dest_str) - return vl_structures.place_schematic(vector.offset(pos, -1, -2, -1), 0, nil, nil, path_gateway_portal, "0", nil, true, nil, nil, nil, - dest_str and function() + return vl_structures.place_schematic(vector.offset(pos, -1, -2, -1), 0, path_gateway_portal, "0", { + force_placement = true, + prepare = false, + after_place = dest_str and function() minetest.get_meta(pos):set_string("mcl_portals:gateway_destination", dest_str) - end) + end + }, nil) end function mcl_portals.spawn_gateway_portal() diff --git a/mods/MAPGEN/mcl_structures/igloo.lua b/mods/MAPGEN/mcl_structures/igloo.lua index 06c18414c..2ca3e0434 100644 --- a/mods/MAPGEN/mcl_structures/igloo.lua +++ b/mods/MAPGEN/mcl_structures/igloo.lua @@ -75,22 +75,26 @@ local function igloo_callback(cpos,def,pr,p1,p2,size,rotation) return false end local path = modpath.."/schematics/mcl_structures_igloo_basement.mts" - local prepare = { tolerance = -1, foundation = false, clear = false } - vl_structures.place_schematic(bpos, -1, nil, nil, path, rotation, nil, true, nil, prepare, pr, function(p1, p2) - -- Generate ladder to basement - local ladder = {name="mcl_core:ladder", param2=minetest.dir_to_wallmounted(tdir)} - minetest.set_node(tpos, {name="mcl_doors:trapdoor", param2=20+minetest.dir_to_facedir(dir)}) -- TODO: more reliable param2 - for y = tpos.y-1, bpos.y+4, -1 do - set_brick(vector.new(tpos.x-1, y, tpos.z )) - set_brick(vector.new(tpos.x+1, y, tpos.z )) - set_brick(vector.new(tpos.x , y, tpos.z-1)) - set_brick(vector.new(tpos.x , y, tpos.z+1)) - minetest.set_node(vector.new(tpos.x, y, tpos.z), ladder) + vl_structures.place_schematic(bpos, -1, path, rotation, { + force_place = true, + prepare = { tolerance = -1, foundation = false, clear = false }, + after_place = function(p1, p2) + -- Generate ladder to basement + local ladder = {name="mcl_core:ladder", param2=minetest.dir_to_wallmounted(tdir)} + -- TODO: use voxelmanip? + minetest.set_node(tpos, {name="mcl_doors:trapdoor", param2=20+minetest.dir_to_facedir(dir)}) -- TODO: more reliable param2 + for y = tpos.y-1, bpos.y+4, -1 do + set_brick(vector.new(tpos.x-1, y, tpos.z )) + set_brick(vector.new(tpos.x+1, y, tpos.z )) + set_brick(vector.new(tpos.x , y, tpos.z-1)) + set_brick(vector.new(tpos.x , y, tpos.z+1)) + minetest.set_node(vector.new(tpos.x, y, tpos.z), ladder) + end + vl_structures.fill_chests(p1,p2,def.loot,pr) + vl_structures.construct_nodes(p1,p2,{"mcl_brewing:stand_000","mcl_books:bookshelf"}) + spawn_mobs(p1,p2) end - vl_structures.fill_chests(p1,p2,def.loot,pr) - vl_structures.construct_nodes(p1,p2,{"mcl_brewing:stand_000","mcl_books:bookshelf"}) - spawn_mobs(p1,p2) - end) + }, pr) end vl_structures.register_structure("igloo",{ diff --git a/mods/MAPGEN/mcl_structures/init.lua b/mods/MAPGEN/mcl_structures/init.lua index c7e305277..9c323fac2 100644 --- a/mods/MAPGEN/mcl_structures/init.lua +++ b/mods/MAPGEN/mcl_structures/init.lua @@ -11,6 +11,21 @@ mcl_structures.fill_chests = vl_structures.fill_chests mcl_structures.spawn_mobs = vl_structures.spawn_mobs -- TODO: provide more legacy adapters that translate parameters? +mcl_structures.place_schematic = function(pos, schematic, rotation, replacements, force_placement, flags, after_placement_callback, pr, callback_param) + vl_structures.place_schematic(pos, yoffset, schematic, rotation, { + replacements = replacements, + force_placement = force_placement, + flags = flags, + after_place = after_placement_callback, + callback_param = callback_param + }, pr) +end +mcl_structures.place_structure = vl_structures.place_structure -- still compatible +mcl_structures.register_structure = function(name, def, nospawn) + -- nospawn: ignored, just pass no place_on! + if not def.solid_ground then def.prepare = def.prepare or {} end + vl_structures.register_structure(name, def) +end dofile(modpath.."/desert_temple.lua") dofile(modpath.."/desert_well.lua") @@ -29,20 +44,23 @@ dofile(modpath.."/witch_hut.lua") dofile(modpath.."/woodland_mansion.lua") vl_structures.register_structure("boulder",{ + -- as they have no place_on, they will not be spawned by this mechanism. this is just for /spawnstruct filenames = { + -- small boulder 3x as likely modpath.."/schematics/mcl_structures_boulder_small.mts", modpath.."/schematics/mcl_structures_boulder_small.mts", modpath.."/schematics/mcl_structures_boulder_small.mts", modpath.."/schematics/mcl_structures_boulder.mts", - -- small boulder 3x as likely }, -},true) --is spawned as a normal decoration. this is just for /spawnstruct +}) vl_structures.register_structure("ice_spike_small",{ + -- as they have no place_on, they will not be spawned by this mechanism. this is just for /spawnstruct filenames = { modpath.."/schematics/mcl_structures_ice_spike_small.mts" }, -},true) --is spawned as a normal decoration. this is just for /spawnstruct +}) vl_structures.register_structure("ice_spike_large",{ + -- as they have no place_on, they will not be spawned by this mechanism. this is just for /spawnstruct filenames = { modpath.."/schematics/mcl_structures_ice_spike_large.mts" }, -},true) --is spawned as a normal decoration. this is just for /spawnstruct +}) diff --git a/mods/MAPGEN/mcl_villages/init.lua b/mods/MAPGEN/mcl_villages/init.lua index 383a55d89..d3a8e0bd5 100644 --- a/mods/MAPGEN/mcl_villages/init.lua +++ b/mods/MAPGEN/mcl_villages/init.lua @@ -1,7 +1,7 @@ mcl_villages = {} mcl_villages.modpath = minetest.get_modpath(minetest.get_current_modname()) -local village_chance = tonumber(minetest.settings:get("mcl_villages_village_probability")) or 1 +local village_boost = tonumber(minetest.settings:get("vl_villages_boost")) or 1 dofile(mcl_villages.modpath.."/const.lua") dofile(mcl_villages.modpath.."/utils.lua") @@ -47,9 +47,9 @@ local mg_name = minetest.get_mapgen_setting("mg_name") if mg_name ~= "singlenode" then mcl_mapgen_core.register_generator("villages", nil, function(minp, maxp, blockseed) if maxp.y < 0 then return end - if village_chance == 0 then return end + if village_boost == 0 then return end local pr = PcgRandom(blockseed) - if pr:next(0, 100) > village_chance then return end + if pr:next(0,1e9) * 100e-9 >= village_boost then return end local big_minp = vector.copy(minp) --vector.offset(minp, -16, -16, -16) local big_maxp = vector.copy(maxp) --vector.offset(maxp, 16, 16, 16) minetest.emerge_area(big_minp, big_maxp, ecb_village, @@ -63,7 +63,7 @@ if mg_name ~= "singlenode" then lvm:set_data(data) -- FIXME: ugly hack, better directly manipulate the data array lvm:set_param2_data(data2) local pr = PcgRandom(blockseed) - if pr:next(0, 100) > village_chance then return end + if pr:next(0,1e9) * 100e-9 > village_boost then return end local settlement = mcl_villages.create_site_plan(lvm, minp, maxp, pr) if not settlement then return false, false end @@ -85,7 +85,7 @@ if mg_name ~= "singlenode" then mcl_mapgen_core.register_generator("villages", nil, function(minp, maxp, blockseed) if maxp.y < 0 or mcl_villages.village_exists(blockseed) then return end local pr = PcgRandom(blockseed) - if pr:next(0, 100) > village_chance then return end + if pr:next(0,1e9) * 10ee-9 > village_boost then return end --local lvm, emin, emax = minetest.get_mapgen_object("voxelmanip") -- did not get the lighting fixed? local lvm = VoxelManip() lvm:read_from_map(minp, maxp) diff --git a/mods/MAPGEN/vl_structures/api.lua b/mods/MAPGEN/vl_structures/api.lua index 8e076b7d1..919c2250e 100644 --- a/mods/MAPGEN/vl_structures/api.lua +++ b/mods/MAPGEN/vl_structures/api.lua @@ -1,11 +1,9 @@ vl_structures.registered_structures = {} -local mob_cap_player = tonumber(minetest.settings:get("mcl_mob_cap_player")) or 75 -local mob_cap_animal = tonumber(minetest.settings:get("mcl_mob_cap_animal")) or 10 local structure_boost = tonumber(minetest.settings:get("vl_structures_boost")) or 1 local worldseed = minetest.get_mapgen_setting("seed") local RANDOM_SEED_OFFSET = 959 -- random constant that should be unique across each library -local floor = math.floor + local vector_offset = vector.offset -- FIXME: switch to vl_structures_logging? @@ -18,27 +16,6 @@ function mcl_structures.is_disabled(structname) return table.indexof(disabled_structures,structname) ~= -1 end -local mg_name = minetest.get_mapgen_setting("mg_name") - --- see vl_terraforming for documentation -local DEFAULT_PREPARE = { tolerance = 10, foundation = -3, clear = false, clear_bottom = 0, clear_top = 4, padding = 1, corners = 1 } -local DEFAULT_FLAGS = "place_center_x,place_center_z" - -local function parse_prepare(prepare) - if prepare == nil or prepare == true then return DEFAULT_PREPARE end - if prepare == false then return {} end - if prepare.foundation == true then - prepare = table.copy(prepare) - prepare.foundation = 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 - --- Trim a full path name to its last two parts as short name for logging local function basename(filename) local fn = string.split(filename, "/") @@ -66,292 +43,32 @@ function vl_structures.load_schematic(filename, name) return s end --- Expected contents of param: --- pos vector: position (center.x, base.y, center.z) -- flags NOT supported --- size vector: structure size after rotation (!) --- yoffset number: relative to base.y, typically <= 0 --- y_min number: minimum y range permitted --- y_max number: maximum y range permitted --- schematic string or schematic: as in minetest.place_schematic --- rotation string: as in minetest.place_schematic --- replacement table: as in minetest.place_schematic --- force_placement boolean: as in minetest.place_schematic --- prepare table: instructions for preparation (usually from definition) --- tolerance number: tolerable ground unevenness, -1 to disable, default 10 --- foundation boolean or number: level ground underneath structure (true is a minimum depth of -3) --- clear boolean: clear overhead area --- clear_min number or string: height from base to start clearing, "top" to start at top --- clear_max number: height from top to stop primary clearing --- padding number: additional padding to increase the area, default 1 --- corners number: corner smoothing of foundation and clear, default 1 --- pr PcgRandom: random generator --- name string: for logging -local function emerge_schematic_vm(vm, param) - local pos, size, yoffset, pr = param.pos, param.size, param.yoffset or 0, param.pr - local prepare, surface_mat = parse_prepare(param.prepare), param.surface_mat - -- Step 1: adjust ground to a more level position - 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(param.schematic.name).." at "..minetest.pos_to_string(param.pos).." because ground is too uneven.") - return - end - end - 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 - if prepare and (prepare.clear or prepare.foundation) then - local prepare_start = os.clock() - -- Get materials from biome: - 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 { name = "mcl_core:dirt_with_grass" } - local node_filler = { name = b and b.node_filler or "mcl_core:dirt" } - local node_stone = { name = b and b.node_stone or "mcl_core:stone" } - local node_dust = b and b.node_dust and { name = b.node_dust } or nil - if node_top.name == "mcl_core:dirt_with_grass" and b then node_top.param2 = b._mcl_grass_palette_index end - - local corners, padding, depth = prepare.corners or 1, prepare.padding or 1, (type(prepare.foundation) == "number" and prepare.foundation) or -4 - 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 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) - end - 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 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) - end - end - -- note: pos is always the center position - minetest.place_schematic_on_vmanip(vm, vector_offset(pos, 0, (param.yoffset or 0), 0), param.schematic, param.rotation, param.replacements, param.force_placement, "place_center_x,place_center_z") - return pos -end - --- Additional parameters: --- emin vector: emerge area minimum --- emax vector: emerge area maximum --- after_placement_callback function: callback after placement, (pmin, pmax, size, rotation, pr, param) --- callback_param table: additional parameters to callback function -local function emerge_schematic(blockpos, action, calls_remaining, param) - if calls_remaining >= 1 then return end - local vm = VoxelManip() - vm:read_from_map(param.emin, param.emax) - local pos = emerge_schematic_vm(vm, param) - if not pos then return end - vm:write_to_map(true) - -- repair walls (TODO: port to vmanip? but no "vm.find_nodes_in_area" yet) - local pmin = vector_offset(pos, -floor((param.size.x-1)*0.5), 0, -floor((param.size.z-1)*0.5)) - local pmax = vector_offset(pmin, param.size.x-1, param.size.y-1, param.size.z-1) - if pmin and pmax and mcl_walls then - for _, n in pairs(minetest.find_nodes_in_area(pmin, pmax, { "group:wall" })) do - mcl_walls.update_wall(n) - end - end - if pmin and pmax and param.after_placement_callback then - param.after_placement_callback(pmin, pmax, param.size, param.rotation, param.pr, param.callback_param) - end -end - -function vl_structures.place_schematic(pos, yoffset, y_min, y_max, schematic, rotation, replacements, force_placement, flags, prepare, pr, after_placement_callback, callback_param) - if schematic and not schematic.size then -- e.g., igloo still passes filenames - schematic = vl_structures.load_schematic(schematic) - end - rotation = vl_structures.parse_rotation(rotation, pr) - prepare = parse_prepare(prepare) - local ppos, pmin, pmax, size = vl_structures.get_extends(pos, schematic.size, yoffset, rotation, flags or DEFAULT_FLAGS) - -- area to emerge. Add some margin to allow for finding better suitable ground etc. - local tolerance = prepare.tolerance or DEFAULT_PREPARE.tolerance -- may be negative to disable foundations - if not type(tolerance) == "number" then tolerance = 8 end -- for emerge only - 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 - local padding = (prepare.padding or 0) + 3 - local depth = prepare.foundation and ((prepare.depth or -4) - 15) or 0 -- minimum depth - local height = prepare.clear and (size.y * 2 + 6) or 0 -- headroom - emin = vector_offset(emin, -padding, depth, -padding) - emax = vector_offset(emax, padding, height, padding) - end - minetest.emerge_area(emin, emax, emerge_schematic, { - emin=emin, emax=emax, name=schematic.name, - pos=ppos, size=size, yoffset=yoffset, y_min=y_min, y_max=y_max, - schematic=schematic, rotation=rotation, replacements=replacements, force_placement=force_placement, - prepare=prepare, pr=pr, - after_placement_callback=after_placement_callback, callback_param=callback_param - }) -end - -local function emerge_complex_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 - - -- pick random daughter schematics + rotations - local daughters = {} - if def.daughters then - for i,d in pairs(def.daughters) 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 - end - - -- Step 1: adjust ground to a more level position - 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 - --if logging and not def.terrain_feature then minetest.log("action", "[vl_structures] "..def.name.." after find_level at "..minetest.pos_to_string(pos).." in "..string.format("%.2fms (main: %.2fms)", (os.clock()-start)*1000, (os.clock()-startmain)*1000)) end - 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) - -- todo: also support checking ground of daughter schematics, but not used by current schematics - -- Step 2: prepare ground foundations and clear - -- todo: allow daugthers to use prepare when parent does not - if prepare and (prepare.clear or prepare.foundation) then - local prepare_start = os.clock() - -- Get materials from biome: - 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 { name = "mcl_core:dirt_with_grass" } - local node_filler = { name = b and b.node_filler or "mcl_core:dirt" } - local node_stone = { name = b and b.node_stone or "mcl_core:stone" } - local node_dust = b and b.node_dust and { name = b.node_dust } or nil - if node_top.name == "mcl_core:dirt_with_grass" and b then node_top.param2 = b._mcl_grass_palette_index end - - local corners, padding, depth = prepare.corners or 1, prepare.padding or 1, (type(prepare.foundation) == "number" and prepare.foundation) or -4 - 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 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 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 - -- if logging and not def.terrain_feature then minetest.log("action", "[vl_structures] "..def.name.." after clear at "..minetest.pos_to_string(pos).." in "..string.format("%.2fms (main: %.2fms)", (os.clock()-start)*1000, (os.clock()-prepare_start)*1000)) end - 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 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 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 - -- if logging and not def.terrain_feature then minetest.log("action", "[vl_structures] "..def.name.." prepared at "..minetest.pos_to_string(pos).." in "..string.format("%.2fms (main: %.2fms)", (os.clock()-start)*1000, (os.clock()-prepare_start)*1000)) end - end - - -- note: pos is always the center position - minetest.place_schematic_on_vmanip(vm, vector_offset(pos, 0, (param.yoffset or 0), 0), param.schematic, param.rotation, param.replacements, param.force_placement, "place_center_x,place_center_z") - - for _,tmp in ipairs(daughters) do - local d, ds, rot = tmp[1], tmp[2], tmp[3] - --local dsize = vl_structures.size_rotated(ds.size, rot) - --local p = vector_offset(pos, d.pos.x - floor((ds.size.x-1)*0.5), d.pos.y + (yoffset or 0), - -- d.pos.z - floor((ds.size.z-1)*0.5)) - 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") - 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. - 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.." spawned at "..minetest.pos_to_string(pos).." in "..string.format("%.2fms (main: %.2fms)", (os.clock()-start)*1000, (endmain-startmain)*1000)) - end -end - ---- Place a schematic with daughters (nether bulwark, nether outpost with bridges) -local function place_complex_schematics(pos, yoffset, schematic, rotation, def, pr) - if schematic and not schematic.size then -- e.g., igloo still passes filenames - schematic = vl_structures.load_schematic(schematic) - end - 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 DEFAULT_FLAGS) - -- area to emerge. Add some margin to allow for finding better suitable ground etc. - local tolerance = prepare.tolerance or DEFAULT_PREPARE.tolerance -- may be negative to disable foundations - if type(tolerance) ~= "number" then tolerance = 10 end -- 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 mcl_foundations! - local padding = (prepare.padding or 0) + 3 - local depth = prepare.foundation and ((type(prepare.foundation) == "number" and prepare.foundation or DEFAULT_PREPARE.foundation) - 3) or 0 -- minimum depth - local height = prepare.clear and ((prepare.clear_top or 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? - 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_complex_schematics, { name = def.name, - emin=emin, emax=emax, def=def, schematic=schematic, - pos=ppos, yoffset=yoffset, size=size, rotation=rotation, - pr=pr - }) -end - --- TODO: remove blockseed? +-- @param pos vector: Position +-- @param def table: containing +-- pos vector: position (center.x, base.y, center.z) -- flags NOT supported, resolve before! +-- size vector: structure size after rotation (!) +-- yoffset number: relative to base.y, typically <= 0 +-- y_min number: minimum y range permitted +-- y_max number: maximum y range permitted +-- schematic string or schematic: as in minetest.place_schematic +-- rotation string: as in minetest.place_schematic +-- replacement table: as in minetest.place_schematic +-- force_placement boolean: as in minetest.place_schematic +-- prepare table: instructions for preparation (usually from definition) +-- tolerance number: tolerable ground unevenness, -1 to disable, default 10 +-- foundation boolean or number: level ground underneath structure (true is a minimum depth of -3) +-- clear boolean: clear overhead area +-- clear_min number or string: height from base to start clearing, "top" to start at top +-- clear_max number: height from top to stop primary clearing +-- padding number: additional padding to increase the area, default 1 +-- corners number: corner smoothing of foundation and clear, default 1 +-- name string: for logging +-- place_func function: to call when placing the structure +-- @param pr PcgRandom: random generator +-- @param blockseed number: passed to place_func only +-- @param rot string: rotation function vl_structures.place_structure(pos, def, pr, blockseed, rot) - if not def then return end + if not pos or not def then return end local log_enabled = logging and not def.terrain_feature -- load schematics the first time if def.filenames and not def.schematics then @@ -376,7 +93,7 @@ function vl_structures.place_structure(pos, def, pr, blockseed, rot) if def.schematics and #def.schematics > 0 then local schematic = def.schematics[pr:next(1,#def.schematics)] rot = vl_structures.parse_rotation(rot or "random", pr) - place_complex_schematics(pos, yoffset, schematic, rot, def, pr) + vl_structures.place_schematic(pos, yoffset, schematic, rot, def, pr) if log_enabled then minetest.log("verbose", "[vl_structures] "..def.name.." to be placed at "..minetest.pos_to_string(pos)) end @@ -411,8 +128,13 @@ function vl_structures.place_structure(pos, def, pr, blockseed, rot) end end ---nospawn means it will be placed by another (non-nospawn) structure that contains it's structblock i.e. it will not be placed by mapgen directly -function vl_structures.register_structure(name,def,nospawn) +local EMPTY_SCHEMATIC = { size = {x = 1, y = 1, z = 1}, data = { { name = "ignore" } } } +-- local EMPTY_SCHEMATIC = { size = {x = 0, y = 0, z = 0}, data = { } } + +--- Register a structure +-- @param name string: Structure name +-- @param def table: Structure definition +function vl_structures.register_structure(name,def) if vl_structures.is_disabled(name) then return end def.name = name vl_structures.registered_structures[name] = def @@ -420,7 +142,6 @@ function vl_structures.register_structure(name,def,nospawn) if not def.noise_params and def.chunk_probability and not def.fill_ratio then def.fill_ratio = 1.1/80/80 -- 1 per chunk, controlled by chunk probability only end - if nospawn or def.nospawn then return end -- ice column, boulder if def.filenames then for _, filename in ipairs(def.filenames) do if not mcl_util.file_exists(filename) then @@ -435,18 +156,18 @@ function vl_structures.register_structure(name,def,nospawn) name = "vl_structures:deco_"..name, priority = def.priority or (def.terrain_feature and 900) or 100, -- run before regular decorations deco_type = "schematic", - schematic = { size = {x = 1, y = 1, z = 1}, data = { { name = "ignore" } } }, + schematic = EMPTY_SCHEMATIC, -- use gennotify only place_on = def.place_on, spawn_by = def.spawn_by, num_spawn_by = def.num_spawn_by, sidelen = 80, -- no def.sidelen subdivisions for now, this field was used differently before fill_ratio = def.fill_ratio, noise_params = def.noise_params, - flags = def.flags or "place_center_x, place_center_z", + flags = def.flags, biomes = def.biomes, y_max = def.y_max, y_min = def.y_min - }, function() + }, function() -- callback when mcl_mapgen_core has reordered the decoration calls def.deco_id = minetest.get_decoration_id("vl_structures:deco_"..name) minetest.set_gen_notify({decoration=true}, { def.deco_id }) end) @@ -462,14 +183,13 @@ mcl_mapgen_core.register_generator("structures", nil, function(minp, maxp, block if struct.deco_id then for _, pos in pairs(gennotify["decoration#"..struct.deco_id] or {}) do local pr = PcgRandom(minetest.hash_node_position(pos) + worldseed + RANDOM_SEED_OFFSET) - local realpos = vector_offset(pos, 0, 1, 0) - if struct.chunk_probability == nil or pr:next(0, 1e9)/1e9 * struct.chunk_probability <= structure_boost then - vl_structures.place_structure(realpos, struct, pr, blockseed) - if struct.chunk_probability then break end -- one (attempt) per chunk only + if struct.chunk_probability == nil or pr:next(0, 1e9) * 1e-9 * struct.chunk_probability <= structure_boost then + vl_structures.place_structure(vector_offset(pos, 0, 1, 0), struct, pr, blockseed) + if struct.chunk_probability ~= nil then break end -- allow only one per gennotify, e.g., on multiple surfaces end end elseif struct.static_pos then - local pr + local pr -- initialize only when needed below for _, pos in pairs(struct.static_pos) do if vector.in_area(pos, minp, maxp) then pr = pr or PcgRandom(worldseed + RANDOM_SEED_OFFSET) diff --git a/mods/MAPGEN/vl_structures/commands.lua b/mods/MAPGEN/vl_structures/commands.lua new file mode 100644 index 000000000..f83529c06 --- /dev/null +++ b/mods/MAPGEN/vl_structures/commands.lua @@ -0,0 +1,44 @@ +local modname = minetest.get_current_modname() +local S = minetest.get_translator(modname) +local modpath = minetest.get_modpath(modname) + +--- /spawnstruct chat command +minetest.register_chatcommand("spawnstruct", { + params = mcl_dungeons and "dungeon" or "", + description = S("Generate a pre-defined structure near your position."), + privs = {debug = true}, + func = function(name, param) + local player = minetest.get_player_by_name(name) + if not player then return end + local pos = player:get_pos() + if not pos then return end + pos = vector.round(pos) + local dir = minetest.yaw_to_dir(player:get_look_horizontal()) + local rot = math.abs(dir.x) > math.abs(dir.z) and (dir.x < 0 and "270" or "90") or (dir.z < 0 and "180" or "0") + local seed = minetest.hash_node_position(pos) + local pr = PcgRandom(seed) + local errord = false + if param == "dungeon" and mcl_dungeons and mcl_dungeons.spawn_dungeon then + mcl_dungeons.spawn_dungeon(pos, rot, pr) + return true, "Spawning "..param + elseif param == "" then + minetest.chat_send_player(name, S("Error: No structure type given. Please use “/spawnstruct "..minetest.registered_chatcommands["spawnstruct"].params.."”.")) + else + for n,d in pairs(vl_structures.registered_structures) do + if n == param then + vl_structures.place_structure(pos, d, pr, seed, rot) + return true, "Spawning "..param + end + end + minetest.chat_send_player(name, S("Error: Unknown structure type. Please use “/spawnstruct "..minetest.registered_chatcommands["spawnstruct"].params.."”.")) + end + end +}) +minetest.register_on_mods_loaded(function() + local p = minetest.registered_chatcommands["spawnstruct"].params + for n,_ in pairs(vl_structures.registered_structures) do + p = (p ~= "" and (p.." | ") or "")..n + end + minetest.registered_chatcommands["spawnstruct"].params = p +end) + diff --git a/mods/MAPGEN/vl_structures/emerge.lua b/mods/MAPGEN/vl_structures/emerge.lua new file mode 100644 index 000000000..d81431661 --- /dev/null +++ b/mods/MAPGEN/vl_structures/emerge.lua @@ -0,0 +1,187 @@ +local DEFAULT_FLAGS = vl_structures.DEFAULT_FLAGS +local DEFAULT_PREPARE = vl_structures.DEFAULT_PREPARE + +local vector_offset = vector.offset +local floor = math.floor + +-- FIXME: switch to vl_structures_logging? +local logging = true or minetest.settings:get_bool("mcl_logging_structures", true) + +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 DEFAULT_PREPARE end + if prepare == false then return {} end + if prepare.foundation == true then + prepare = table.copy(prepare) + prepare.foundation = 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 { name = "mcl_core:dirt_with_grass" } + local node_filler = { name = b and b.node_filler or "mcl_core:dirt" } + local node_stone = { name = b and b.node_stone or "mcl_core:stone" } + local node_dust = b and b.node_dust and { name = b.node_dust } or nil + 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, depth = prepare.corners or 1, prepare.padding or 1, (type(prepare.foundation) == "number" and prepare.foundation) or -4 + 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 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 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 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 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 DEFAULT_FLAGS) + -- area to emerge. Add some margin to allow for finding better suitable ground etc. + local tolerance = prepare.tolerance or 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 mcl_foundations! + local padding = (prepare.padding or 0) + 3 + local depth = prepare.foundation and ((type(prepare.foundation) == "number" and prepare.foundation or DEFAULT_PREPARE.foundation) - 3) or 0 -- minimum depth + local height = prepare.clear and ((prepare.clear_top or 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 + diff --git a/mods/MAPGEN/vl_structures/init.lua b/mods/MAPGEN/vl_structures/init.lua index 878cb7994..ec85d3ccb 100644 --- a/mods/MAPGEN/vl_structures/init.lua +++ b/mods/MAPGEN/vl_structures/init.lua @@ -4,46 +4,12 @@ local modpath = minetest.get_modpath(modname) vl_structures = {} +-- see vl_terraforming for documentation +vl_structures.DEFAULT_PREPARE = { tolerance = 10, foundation = -3, clear = false, clear_bottom = 0, clear_top = 4, padding = 1, corners = 1 } +vl_structures.DEFAULT_FLAGS = "place_center_x,place_center_z" + dofile(modpath.."/util.lua") +dofile(modpath.."/emerge.lua") dofile(modpath.."/api.lua") - ---- /spawnstruct chat command -minetest.register_chatcommand("spawnstruct", { - params = mcl_dungeons and "dungeon" or "", - description = S("Generate a pre-defined structure near your position."), - privs = {debug = true}, - func = function(name, param) - local player = minetest.get_player_by_name(name) - if not player then return end - local pos = player:get_pos() - if not pos then return end - pos = vector.round(pos) - local dir = minetest.yaw_to_dir(player:get_look_horizontal()) - local rot = math.abs(dir.x) > math.abs(dir.z) and (dir.x < 0 and "270" or "90") or (dir.z < 0 and "180" or "0") - local seed = minetest.hash_node_position(pos) - local pr = PcgRandom(seed) - local errord = false - if param == "dungeon" and mcl_dungeons and mcl_dungeons.spawn_dungeon then - mcl_dungeons.spawn_dungeon(pos, rot, pr) - return true, "Spawning "..param - elseif param == "" then - minetest.chat_send_player(name, S("Error: No structure type given. Please use “/spawnstruct ”.")) - else - for n,d in pairs(vl_structures.registered_structures) do - if n == param then - vl_structures.place_structure(pos, d, pr, seed, rot) - return true, "Spawning "..param - end - end - minetest.chat_send_player(name, S("Error: Unknown structure type. Please use “/spawnstruct ”.")) - end - end -}) -minetest.register_on_mods_loaded(function() - local p = "" - for n,_ in pairs(vl_structures.registered_structures) do - p = p .. " | ".. n - end - minetest.registered_chatcommands["spawnstruct"].params = minetest.registered_chatcommands["spawnstruct"].params .. p -end) - +dofile(modpath.."/spawning.lua") +dofile(modpath.."/commands.lua") diff --git a/mods/MAPGEN/vl_structures/spawning.lua b/mods/MAPGEN/vl_structures/spawning.lua new file mode 100644 index 000000000..5f15d0909 --- /dev/null +++ b/mods/MAPGEN/vl_structures/spawning.lua @@ -0,0 +1,50 @@ +-- todo: move this mostly to the mcl_mobs module? +local mob_cap_player = tonumber(minetest.settings:get("mcl_mob_cap_player")) or 75 +local mob_cap_animal = tonumber(minetest.settings:get("mcl_mob_cap_animal")) or 10 +local mg_name = minetest.get_mapgen_setting("mg_name") +local vector_offset = vector.offset + +local structure_spawns = {} +--- Structure spawns via ABM +-- @param def table: containing +-- @param name string: Name +-- @param y_min number: minimum height +-- @param y_max number: maximum height +-- @param spawnon table: Node types to spawn on, can also use group:names +-- @param biomes table: Biomes to spawn in +-- @param chance number: Spawn chance, default 5, will trigger 1/chance per-node per-interval +-- @param interval number: Spawn check interval in seconds, default 60.0 +-- @param limit number: Local mob cap, default 7 +function vl_structures.register_structure_spawn(def) + minetest.register_abm({ + label = "Spawn "..def.name, + nodenames = def.spawnon, + min_y = def.y_min or -31000, + max_y = def.y_max or 31000, + interval = def.interval or 60, + chance = def.chance or 5, + action = function(pos, node, active_object_count, active_object_count_wider) + -- FIXME: review this logic, legacy code + local limit = def.limit or 7 + if active_object_count_wider > limit + mob_cap_animal then return end + if active_object_count_wider > mob_cap_player then return end + local p = vector_offset(pos, 0, 1, 0) + local pname = minetest.get_node(p).name + if def.type_of_spawning == "water" then + if pname ~= "mcl_core:water_source" and pname ~= "mclx_core:river_water_source" then return end + else + if pname ~= "air" then return end -- FIXME: allow everything non-walkable, non-water, non-lava? + end + if minetest.get_meta(pos):get_string("spawnblock") == "" then return end + if mg_name ~= "v6" and mg_name ~= "singlenode" and def.biomes then + if table.indexof(def.biomes, minetest.get_biome_name(minetest.get_biome_data(p).biome)) == -1 then + return + end + end + local mobdef = minetest.registered_entities[def.name] + if mobdef.can_spawn and not mobdef.can_spawn(p) then return end + minetest.add_entity(p, def.name) + end, + }) +end + diff --git a/mods/MAPGEN/vl_structures/util.lua b/mods/MAPGEN/vl_structures/util.lua index 3d7ee74aa..2bf93d9f4 100644 --- a/mods/MAPGEN/vl_structures/util.lua +++ b/mods/MAPGEN/vl_structures/util.lua @@ -53,7 +53,7 @@ end -- @return center on base level, area minimum, area maximum, rotated size (=pmax-pmin+1) function vl_structures.get_extends(pos, size, yoffset, rotation, flags) local size = vl_structures.size_rotated(size, rotation) - local pmin = vl_structures.top_left_from_flags(pos, size, flags or DEFAULT_FLAGS) + local pmin = vl_structures.top_left_from_flags(pos, size, flags or vl_structures.DEFAULT_FLAGS) local cent = vector_offset(pmin, floor((size.x-1)*0.5), 0, floor((size.z-1)*0.5)) -- center pmin.y = pmin.y + (yoffset or 0) -- to pmin and pmax only local pmax = vector_offset(pmin, size.x - 1, size.y - 1, size.z - 1) diff --git a/mods/MAPGEN/vl_terraforming/level.lua b/mods/MAPGEN/vl_terraforming/level.lua index 388f1b713..aa9d66cb9 100644 --- a/mods/MAPGEN/vl_terraforming/level.lua +++ b/mods/MAPGEN/vl_terraforming/level.lua @@ -165,21 +165,26 @@ function vl_terraforming.find_level_vm(vm, cpos, size, tolerance, mode) local find_ground = find_ground_vm if mode == "liquid_surface" or mode == "liquid" then find_ground = find_liquid_surface_vm end if mode == "under_air" then find_ground = find_under_air_vm end - local pos, surface_material = find_ground(vm, cpos) -- center + -- begin at center, then top-left and clockwise + local pos, surface_material = find_ground(vm, cpos) if not pos then return nil, nil end local ys = { pos.y } - pos.y = pos.y + 1 -- above ground + pos.y = pos.y + 1 -- position above surface if size.x == 1 and size.z == 1 then return pos end - pos.x, pos.z = pos.x - floor((size.x-1)/2), pos.z - floor((size.z-1)/2) -- top left + -- move to top left corner + pos.x, pos.z = pos.x - floor((size.x-1)/2), pos.z - floor((size.z-1)/2) local pos_c = find_ground(vm, pos) if pos_c then table.insert(ys, pos_c.y) end - pos.x = pos.x + size.x - 1 -- top right + -- move to top right corner + pos.x = pos.x + size.x - 1 local pos_c = find_ground(vm, pos) if pos_c then table.insert(ys, pos_c.y) end - pos.z = pos.z + size.z - 1 -- bottom right + -- move to bottom right corner + pos.z = pos.z + size.z - 1 local pos_c = find_ground(vm, pos) if pos_c then table.insert(ys, pos_c.y) end - pos.x = pos.x - (size.x - 1) -- bottom left + -- move to bottom left corner + pos.x = pos.x - (size.x - 1) local pos_c = find_ground(vm, pos) if pos_c then table.insert(ys, pos_c.y) end table.sort(ys) @@ -195,11 +200,11 @@ function vl_terraforming.find_level_vm(vm, cpos, size, tolerance, mode) end -- well supported base, not too uneven? if #ys < 4 or min(ys[#ys-1]-ys[1], ys[#ys]-ys[2]) > tolerance then - minetest.log("action", "[vl_terraforming] ground too uneven: "..#ys.." positions: "..({dump(ys):gsub("[\n\t ]+", " ")})[1] - .." tolerance "..tostring(#ys > 2 and min(ys[#ys-1]-ys[1], ys[#ys]-ys[2])).." > "..tolerance) + --minetest.log("action", "[vl_terraforming] ground too uneven: "..#ys.." positions: "..({dump(ys):gsub("[\n\t ]+", " ")})[1] + -- .." tolerance "..tostring(#ys > 2 and min(ys[#ys-1]-ys[1], ys[#ys]-ys[2])).." > "..tolerance) return nil, nil end - cpos.y = floor(0.5 * (ys[floor(1 + (#ys - 1) * 0.5)] + ys[ceil(1 + (#ys - 1) * 0.5)]) + 0.55) -- median except for largest, rounded, over surface + cpos.y = floor(0.5 * (ys[floor(1 + (#ys - 1) * 0.5)] + ys[ceil(1 + (#ys - 1) * 0.5)]) + 1) -- median except for largest, rounded, over surface return cpos, surface_material end diff --git a/settingtypes.txt b/settingtypes.txt index c1dd4234c..c7df6bff2 100644 --- a/settingtypes.txt +++ b/settingtypes.txt @@ -46,10 +46,10 @@ mcl_disabled_structures (Disabled structures) string mcl_disabled_events (Disabled events) string # Structure frequency multiplier, keep this less than 3 usually -vl_structures_boost (Structure frequency) float 1.0 0.0 10.0 +vl_structures_boost (Structure frequency multiplier) float 1.0 0.0 10.0 -# Amount of village to generate -mcl_villages_village_probability (Probability of villages) int 5 0 100 +# Village frequency multiplier, keep this less than 3 usually +vl_villages_boost (Village frequency multiplier) float 1.0 0.0 10.0 [Players] # If enabled, players respawn at the bed they last lay on instead of normal