1
0
Fork 0

Huge update of new terraforming, structures, and village code

This commit is contained in:
kno10 2024-08-23 10:55:30 +02:00
parent b3efd6f681
commit 9411e60b8c
63 changed files with 2159 additions and 1690 deletions

View File

@ -128,7 +128,7 @@ mcl_mobs.register_mob("mobs_mc:enderdragon", {
on_die = function(self, pos, cmi_cause)
if self._portal_pos then
mcl_portals.spawn_gateway_portal()
mcl_structures.place_structure(self._portal_pos,mcl_structures.registered_structures["end_exit_portal_open"],PseudoRandom(minetest.get_mapgen_setting("seed")))
vl_structures.place_structure(self._portal_pos,vl_structures.registered_structures["end_exit_portal_open"],PseudoRandom(minetest.get_mapgen_setting("seed")))
if self._initial then
mcl_experience.throw_xp(pos, 11500) -- 500 + 11500 = 12000
minetest.set_node(vector.add(self._portal_pos, vector.new(0, 5, 0)), {name = "mcl_end:dragon_egg"})

View File

@ -80,7 +80,7 @@ local function spawn_crystal(pos)
crystal_explode(crystal)
end
local portal_pos = vector.add(portal_center, vector.new(0, -1, 0))
mcl_structures.place_structure(portal_pos,mcl_structures.registered_structures["end_exit_portal"],PseudoRandom(minetest.get_mapgen_setting("seed")),-1)
vl_structures.place_structure(portal_pos,vl_structures.registered_structures["end_exit_portal"],PseudoRandom(minetest.get_mapgen_setting("seed")),-1)
end
minetest.register_entity("mcl_end:crystal", {

View File

@ -89,7 +89,7 @@ minetest.register_craftitem("mcl_end:ender_eye", {
local player_name = user:get_player_name()
local origin = user:get_pos()
origin.y = origin.y + 1.5
local strongholds = mcl_structures.registered_structures["end_shrine"].static_pos
local strongholds = vl_structures.registered_structures["end_shrine"].static_pos
local dim = mcl_worlds.pos_to_dimension(origin)
local is_creative = minetest.is_creative_enabled(player_name)

View File

@ -29,7 +29,7 @@ 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 mcl_structures.place_schematic(vector.add(pos, vector.new(-1, -2, -1)), 0, nil, nil, path_gateway_portal, "0", nil, true, nil, nil, nil,
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()
minetest.get_meta(pos):set_string("mcl_portals:gateway_destination", dest_str)
end)

View File

@ -183,12 +183,12 @@ local dimension_to_teleport = { nether = "overworld", overworld = "nether" }
local limits = {
nether = {
pmin = {x=LIM_MIN, y = N_Y_MIN, z = LIM_MIN},
pmax = {x=LIM_MAX, y = N_Y_MAX, z = LIM_MAX},
pmin = vector.new(LIM_MIN, N_Y_MIN, LIM_MIN),
pmax = vector.new(LIM_MAX, N_Y_MAX, LIM_MAX),
},
overworld = {
pmin = {x=LIM_MIN, y = O_Y_MIN, z = LIM_MIN},
pmax = {x=LIM_MAX, y = O_Y_MAX, z = LIM_MAX},
pmin = vector.new(LIM_MIN, O_Y_MIN, LIM_MIN),
pmax = vector.new(LIM_MAX, O_Y_MAX, LIM_MAX),
},
}
@ -206,12 +206,10 @@ end
-- for old portals, so that players don't get surprises. New portals, or portals that lost
-- node storage due to destruction should use the lookup table.
local function get_portal_pos(pos)
local p1 = vector.offset(pos,-5,-1,-5)
local p2 = vector.offset(pos,5,5,5)
local nn = find_nodes_in_area(p1,p2,{"mcl_portals:portal"})
local nn = find_nodes_in_area(vector.offset(pos,-5,-1,-5), vector.offset(pos,5,5,5), {"mcl_portals:portal"})
for _,p in pairs(nn) do
local m = minetest.get_meta(p):get_string("target_portal")
if m and m ~= "" and mcl_vars.get_node(p).name == "mcl_portals:portal" then
if m and m ~= "" and minetest.get_node(p).name == "mcl_portals:portal" then
return minetest.get_position_from_hash(m)
end
end
@ -229,15 +227,13 @@ end
local function add_exit(p)
local retval = {key=false, new=false}
if not p or not p.y or not p.z or not p.x then
return retval
end
if not p or not p.y or not p.z or not p.x then return retval end
local x, y, z = floor(p.x), floor(p.y), floor(p.z)
local p = {x = x, y = y, z = z}
local p = vector.new(x, y, z)
if get_node({x=x,y=y-1,z=z}).name ~= OBSIDIAN
if get_node(vector.new(x, y-1, z)).name ~= OBSIDIAN
or get_node(p).name ~= PORTAL
or get_node({x=x,y=y+1,z=z}).name ~= PORTAL
or get_node(vector.new(x, y+1, z)).name ~= PORTAL
then
return retval
end
@ -301,7 +297,7 @@ local function remove_exit(p)
end
local x, y, z = floor(p.x), floor(p.y), floor(p.z)
local p = {x = x, y = y, z = z}
local p = vector.new(x, y, z)
local k = get_exit_key(p)
if not exits[k] then
@ -529,14 +525,14 @@ local function destroy_nether_portal(pos, node)
while i <= #nodes do
pos = nodes[i]
if orientation == 0 then
check_remove({x = pos.x - 1, y = pos.y, z = pos.z})
check_remove({x = pos.x + 1, y = pos.y, z = pos.z})
check_remove(vector.offset(pos, -1, 0, 0))
check_remove(vector.offset(pos, 1, 0, 0))
else
check_remove({x = pos.x, y = pos.y, z = pos.z - 1})
check_remove({x = pos.x, y = pos.y, z = pos.z + 1})
check_remove(vector.offset(pos, 0, 0, -1))
check_remove(vector.offset(pos, 0, 0, 1))
end
check_remove({x = pos.x, y = pos.y - 1, z = pos.z})
check_remove({x = pos.x, y = pos.y + 1, z = pos.z})
check_remove(vector.offset(pos, 0, -1, 0))
check_remove(vector.offset(pos, 0, 1, 0))
remove_exits({pos})
i = i + 1
end
@ -624,7 +620,7 @@ local function build_and_light_frame(x1, y1, z1, x2, y2, z2, name)
else
set_node(pos, {name = PORTAL, param2 = orientation})
add_exits({
{x=pos.x, y=pos.y-1, z=pos.z}
vector.new(pos.x, pos.y-1, pos.z)
})
end
end
@ -701,8 +697,8 @@ function build_nether_portal(cube_pos1, width, height, orientation, name, clear_
-- Build obsidian platform:
for x = pos.x - orientation, pos.x + orientation + (width_inner - 1) * (1 - orientation), 1 + orientation do
for z = pos.z - 1 + orientation, pos.z + 1 - orientation + (width_inner - 1) * orientation, 2 - orientation do
local pp = {x = x, y = pos.y - 1, z = z}
local pp_1 = {x = x, y = pos.y - 2, z = z}
local pp = vector.new(x, pos.y - 1, z)
local pp_1 = vector.new(x, pos.y - 2, z)
local nn = get_node(pp).name
local nn_1 = get_node(pp_1).name
if ((nn=="air" and nn_1 == "air") or not registered_nodes[nn].is_ground_content) and not is_protected(pp, name) then
@ -823,7 +819,7 @@ local function finalize_teleport(obj, exit)
local _, dim = mcl_worlds.y_to_layer(exit.y)
-- If player stands, player is at ca. something+0.5 which might cause precision problems, so we used ceil for objpos.y
objpos = {x = floor(objpos.x+0.5), y = ceil(objpos.y), z = floor(objpos.z+0.5)}
objpos = vector.new(floor(objpos.x+0.5), ceil(objpos.y), floor(objpos.z+0.5))
if get_node(objpos).name ~= PORTAL then
log("action", "Entity no longer standing in portal")
return
@ -1085,19 +1081,19 @@ local function search_for_build_location(blockpos, action, calls_remaining, para
if nc2 >= (W_MIN*(H_MIN-1)*W_MIN) - ACCEPTABLE_PORTAL_REPLACES then
-- We have sorted the candidates by distance, this is the best location.
distance = distance0
pos0 = {x=node.x, y=node.y, z=node.z}
pos0 = vector.new(node.x, node.y, node.z)
log("verbose", "Found acceptable location at "..pos_to_string(pos0)..", distance "..distance0..", air nodes "..nc2)
break
elseif not most_airy_pos or nc2>most_airy_count then
-- Remember the cube with the most amount of air as a fallback.
most_airy_count = nc2
most_airy_distance = distance0
most_airy_pos = {x=node.x, y=node.y, z=node.z}
most_airy_pos = vector.new(node.x, node.y, node.z)
log("verbose", "Found fallback location at "..pos_to_string(most_airy_pos)..", distance "..distance0..", air nodes "..nc2)
elseif most_airy_pos and nc2==most_airy_count and distance0<most_airy_distance then
-- Use distance as a tiebreaker.
most_airy_distance = distance0
most_airy_pos = {x=node.x, y=node.y, z=node.z}
most_airy_pos = vector.new(node.x, node.y, node.z)
log("verbose", "Found fallback location at "..pos_to_string(most_airy_pos)..", distance "..distance0..", air nodes "..nc2)
end
@ -1128,7 +1124,7 @@ local function search_for_build_location(blockpos, action, calls_remaining, para
log("verbose", "No space found, emerging one chunk below")
end
local new_target = {x=target.x, y=target.y + direction * mcl_vars.chunk_size_in_nodes, z=target.z}
local new_target = vector.new(target.x, target.y + direction * mcl_vars.chunk_size_in_nodes, target.z)
pos1, pos2 = find_build_limits(new_target, param.target_dim)
local diff = add(pos2, mul(pos1, -1))
@ -1202,7 +1198,7 @@ local function create_portal(origin, target, target_dim, name, obj)
origin = origin,
target = target,
target_dim = target_dim,
ideal_target = vector.new(target.x, target.y, target.z), -- copy
ideal_target = vector.copy(target),
pos1 = pos1,
pos2 = pos2,
name=name,
@ -1223,13 +1219,12 @@ local function available_for_nether_portal(p)
end
local function check_and_light_shape(pos, orientation)
local stack = {{x = pos.x, y = pos.y, z = pos.z}}
local stack = {vector.copy(pos)}
local node_list = {}
local index_list = {}
local node_counter = 0
-- Search most low node from the left (pos1) and most right node from the top (pos2)
local pos1 = {x = pos.x, y = pos.y, z = pos.z}
local pos2 = {x = pos.x, y = pos.y, z = pos.z}
local pos1, pos2 = vector.copy(pos), vector.copy(pos)
local kx, ky, kz = pos.x - 1999, pos.y - 1999, pos.z - 1999
while #stack > 0 do
@ -1247,22 +1242,22 @@ local function check_and_light_shape(pos, orientation)
return false
end
node_counter = node_counter + 1
node_list[node_counter] = {x = x, y = y, z = z}
node_list[node_counter] = vector.new(x, y, z)
index_list[k] = true
stack[i].y = y - 1
stack[i + 1] = {x = x, y = y + 1, z = z}
stack[i + 1] = vector.new(x, y + 1, z)
if orientation == 0 then
stack[i + 2] = {x = x - 1, y = y, z = z}
stack[i + 3] = {x = x + 1, y = y, z = z}
stack[i + 2] = vector.new(x - 1, y, z)
stack[i + 3] = vector.new(x + 1, y, z)
else
stack[i + 2] = {x = x, y = y, z = z - 1}
stack[i + 3] = {x = x, y = y, z = z + 1}
stack[i + 2] = vector.new(x, y, z - 1)
stack[i + 3] = vector.new(x, y, z + 1)
end
if (y < pos1.y) or (y == pos1.y and (x < pos1.x or z < pos1.z)) then
pos1 = {x = x, y = y, z = z}
pos1 = vector.new(x, y, z)
end
if (x > pos2.x or z > pos2.z) or (x == pos2.x and z == pos2.z and y > pos2.y) then
pos2 = {x = x, y = y, z = z}
pos2 = vector.new(x, y, z)
end
end
end
@ -1344,7 +1339,7 @@ local function check_portal_then_teleport(obj, origin, exit)
remove_exits({exit})
-- Also remove from structure storage, otherwise ABM will try the same bad exit again.
local objpos = obj:get_pos()
delete_portal_pos({x = floor(objpos.x+0.5), y = ceil(objpos.y), z = floor(objpos.z+0.5)})
delete_portal_pos(vector.new(floor(objpos.x+0.5), ceil(objpos.y), floor(objpos.z+0.5)))
origin_flush(origin, nil)
return
@ -1367,7 +1362,7 @@ local function teleport_no_delay(obj, portal_pos)
local target_dim = dimension_to_teleport[current_dim]
-- If player stands, player is at ca. something+0.5 which might cause precision problems, so we used ceil for objpos.y
origin = {x = floor(objpos.x+0.5), y = ceil(objpos.y), z = floor(objpos.z+0.5)}
origin = vector.new(floor(objpos.x+0.5), ceil(objpos.y), floor(objpos.z+0.5))
if get_node(origin).name ~= PORTAL then return end
local target = get_target(origin)
@ -1450,8 +1445,8 @@ local function animation(player, playername)
end
minetest.add_particlespawner({
amount = 1,
minpos = {x = pos.x - 0.1, y = pos.y + 1.4, z = pos.z - 0.1},
maxpos = {x = pos.x + 0.1, y = pos.y + 1.6, z = pos.z + 0.1},
minpos = vector.offset(pos, -0.1, 1.4, -0.1),
maxpos = vector.offset(pos, 0.1, 1.6, 0.1),
minvel = 0,
maxvel = 0,
minacc = 0,
@ -1496,11 +1491,11 @@ minetest.register_abm({
local time = random() * 1.9 + 0.5
local velocity, acceleration
if o == 1 then
velocity = {x = random() * 0.7 + 0.3, y = random() - 0.5, z = random() - 0.5}
acceleration = {x = random() * 1.1 + 0.3, y = random() - 0.5, z = random() - 0.5}
velocity = vector.new(random() * 0.7 + 0.3, random() - 0.5, random() - 0.5)
acceleration = vector.new(random() * 1.1 + 0.3, random() - 0.5, random() - 0.5)
else
velocity = {x = random() - 0.5, y = random() - 0.5, z = random() * 0.7 + 0.3}
acceleration = {x = random() - 0.5, y = random() - 0.5, z = random() * 1.1 + 0.3}
velocity = vector.new(random() - 0.5, random() - 0.5, random() * 0.7 + 0.3)
acceleration = vector.new(random() - 0.5, random() - 0.5, random() * 1.1 + 0.3)
end
local distance = add(mul(velocity, time), mul(acceleration, time * time / 2))
if d == 1 then
@ -1566,12 +1561,12 @@ minetest.override_item(OBSIDIAN, {
end
-- check each of 6 sides of it and destroy every portal
check_remove({x = pos.x - 1, y = pos.y, z = pos.z})
check_remove({x = pos.x + 1, y = pos.y, z = pos.z})
check_remove({x = pos.x, y = pos.y, z = pos.z - 1})
check_remove({x = pos.x, y = pos.y, z = pos.z + 1})
check_remove({x = pos.x, y = pos.y - 1, z = pos.z})
check_remove({x = pos.x, y = pos.y + 1, z = pos.z})
check_remove(vector.offset(pos, -1, 0, 0))
check_remove(vector.offset(pos, 1, 0, 0))
check_remove(vector.offset(pos, 0, 0, -1))
check_remove(vector.offset(pos, 0, 0, 1))
check_remove(vector.offset(pos, 0, -1, 0))
check_remove(vector.offset(pos, 0, 1, 0))
end,
_on_ignite = function(user, pointed_thing)
@ -1579,16 +1574,16 @@ minetest.override_item(OBSIDIAN, {
-- Check empty spaces around obsidian and light all frames found.
-- Permit igniting of portals that are partly protected to maintain integrity.
local portals_placed =
mcl_portals.light_nether_portal({x = x - 1, y = y, z = z}) or mcl_portals.light_nether_portal({x = x + 1, y = y, z = z}) or
mcl_portals.light_nether_portal({x = x, y = y - 1, z = z}) or mcl_portals.light_nether_portal({x = x, y = y + 1, z = z}) or
mcl_portals.light_nether_portal({x = x, y = y, z = z - 1}) or mcl_portals.light_nether_portal({x = x, y = y, z = z + 1})
mcl_portals.light_nether_portal(vector.new(x - 1, y, z)) or mcl_portals.light_nether_portal(vector.new(x + 1, y, z)) or
mcl_portals.light_nether_portal(vector.new(x, y - 1, z)) or mcl_portals.light_nether_portal(vector.new(x, y + 1, z)) or
mcl_portals.light_nether_portal(vector.new(x, y, z - 1)) or mcl_portals.light_nether_portal(vector.new(x, y, z + 1))
if portals_placed then
log("verbose", "Nether portal activated at "..pos_to_string({x=x,y=y,z=z})..".")
log("verbose", "Nether portal activated at "..pos_to_string(vector.new(x, y, z))..".")
if minetest.get_modpath("doc") then
doc.mark_entry_as_revealed(user:get_player_name(), "nodes", PORTAL)
-- Achievement for finishing a Nether portal TO the Nether
local dim = mcl_worlds.pos_to_dimension({x=x, y=y, z=z})
local dim = mcl_worlds.pos_to_dimension(vector.new(x, y, z))
if minetest.get_modpath("awards") and dim ~= "nether" and user:is_player() then
awards.unlock(user:get_player_name(), "mcl:buildNetherPortal")
end
@ -1600,13 +1595,13 @@ minetest.override_item(OBSIDIAN, {
end,
})
mcl_structures.register_structure("nether_portal",{
vl_structures.register_structure("nether_portal",{
nospawn = true,
filenames = {
modpath.."/schematics/mcl_portals_nether_portal.mts"
}
})
mcl_structures.register_structure("nether_portal_open",{
vl_structures.register_structure("nether_portal_open",{
nospawn = true,
filenames = {
modpath.."/schematics/mcl_portals_nether_portal_open.mts"

View File

@ -26,6 +26,9 @@ local mod_mcl_crimson = minetest.get_modpath("mcl_crimson")
local mod_mcl_blackstone = minetest.get_modpath("mcl_blackstone")
local mod_mcl_mangrove = minetest.get_modpath("mcl_mangrove")
-- these are registered asynchronously
local deco_ids_fungus = {}
local deco_ids_trees = {}
local deco_id_chorus_plant
--
@ -3988,7 +3991,11 @@ local function register_decorations()
schematic = mod_mcl_mangrove .. "/schematics/mcl_mangrove_tree_1.mts",
flags = "place_center_x, place_center_z, force_placement",
rotation = "random",
})
}, function()
local f = minetest.get_decoration_id("mcl_biomes:mangrove_tree_1")
table.insert(deco_ids_trees, f)
minetest.set_gen_notify({decoration = true}, {f})
end)
mcl_mapgen_core.register_decoration({
name = "mcl_biomes:mangrove_tree_2",
deco_type = "schematic",
@ -4001,7 +4008,11 @@ local function register_decorations()
schematic = mod_mcl_mangrove .. "/schematics/mcl_mangrove_tree_2.mts",
flags = "place_center_x, place_center_z, force_placement",
rotation = "random",
})
}, function()
local f = minetest.get_decoration_id("mcl_biomes:mangrove_tree_2")
table.insert(deco_ids_trees, f)
minetest.set_gen_notify({decoration = true}, {f})
end)
mcl_mapgen_core.register_decoration({
name = "mcl_biomes:mangrove_tree_3",
deco_type = "schematic",
@ -4014,7 +4025,11 @@ local function register_decorations()
schematic = mod_mcl_mangrove .. "/schematics/mcl_mangrove_tree_3.mts",
flags = "place_center_x, place_center_z, force_placement",
rotation = "random",
})
}, function()
local f = minetest.get_decoration_id("mcl_biomes:mangrove_tree_3")
table.insert(deco_ids_trees, f)
minetest.set_gen_notify({decoration = true}, {f})
end)
mcl_mapgen_core.register_decoration({
name = "mcl_biomes:mangrove_tree_4",
deco_type = "schematic",
@ -4027,7 +4042,11 @@ local function register_decorations()
schematic = mod_mcl_mangrove .. "/schematics/mcl_mangrove_tree_4.mts",
flags = "place_center_x, place_center_z, force_placement",
rotation = "random",
})
}, function()
local f = minetest.get_decoration_id("mcl_biomes:mangrove_tree_4")
table.insert(deco_ids_trees, f)
minetest.set_gen_notify({decoration = true}, {f})
end)
mcl_mapgen_core.register_decoration({
name = "mcl_biomes:mangrove_tree_5",
deco_type = "schematic",
@ -4040,7 +4059,11 @@ local function register_decorations()
schematic = mod_mcl_mangrove .. "/schematics/mcl_mangrove_tree_5.mts",
flags = "place_center_x, place_center_z, force_placement",
rotation = "random",
})
}, function()
local f = minetest.get_decoration_id("mcl_biomes:mangrove_tree_5")
table.insert(deco_ids_trees, f)
minetest.set_gen_notify({decoration = true}, {f})
end)
mcl_mapgen_core.register_decoration({
name = "mcl_biomes:mangrove_bee_nest",
deco_type = "schematic",
@ -4063,7 +4086,11 @@ local function register_decorations()
rotation = "random",
spawn_by = "group:flower",
priority = 1550,
})
}, function()
local f = minetest.get_decoration_id("mcl_biomes:mangrove_bee_nest")
table.insert(deco_ids_trees, f)
minetest.set_gen_notify({decoration = true}, {f})
end)
mcl_mapgen_core.register_decoration({
deco_type = "simple",
place_on = {"mcl_mud:mud"},
@ -5577,7 +5604,11 @@ local function register_dimension_decorations()
schematic = mod_mcl_crimson .. "/schematics/warped_fungus_1.mts",
size = vector.new(5, 11, 5),
rotation = "random",
})
}, function()
local f = minetest.get_decoration_id("mcl_biomes:warped_tree1")
table.insert(deco_ids_fungus, f)
minetest.set_gen_notify({decoration = true}, {f})
end)
mcl_mapgen_core.register_decoration({
deco_type = "schematic",
name = "mcl_biomes:warped_tree2",
@ -5591,7 +5622,11 @@ local function register_dimension_decorations()
schematic = mod_mcl_crimson .. "/schematics/warped_fungus_2.mts",
size = vector.new(5, 6, 5),
rotation = "random",
})
}, function()
local f = minetest.get_decoration_id("mcl_biomes:warped_tree2")
table.insert(deco_ids_fungus, f)
minetest.set_gen_notify({decoration = true}, {f})
end)
mcl_mapgen_core.register_decoration({
deco_type = "schematic",
name = "mcl_biomes:warped_tree3",
@ -5605,7 +5640,11 @@ local function register_dimension_decorations()
schematic = mod_mcl_crimson .. "/schematics/warped_fungus_3.mts",
size = vector.new(5, 12, 5),
rotation = "random",
})
}, function()
local f = minetest.get_decoration_id("mcl_biomes:warped_tree3")
table.insert(deco_ids_fungus, f)
minetest.set_gen_notify({decoration = true}, {f})
end)
mcl_mapgen_core.register_decoration({
deco_type = "simple",
place_on = {"mcl_crimson:warped_nylium", "mcl_crimson:twisting_vines"},
@ -5653,7 +5692,7 @@ local function register_dimension_decorations()
})
mcl_mapgen_core.register_decoration({
deco_type = "schematic",
name = "mcl_biomes:crimson_tree",
name = "mcl_biomes:crimson_tree1",
place_on = {"mcl_crimson:crimson_nylium"},
sidelen = 16,
fill_ratio = 0.008,
@ -5664,7 +5703,12 @@ local function register_dimension_decorations()
schematic = mod_mcl_crimson .. "/schematics/crimson_fungus_1.mts",
size = vector.new(5, 8, 5),
rotation = "random",
})
}, function()
local f = minetest.get_decoration_id("mcl_biomes:crimson_tree1")
table.insert(deco_ids_fungus, f)
minetest.set_gen_notify({decoration = true}, {f})
end)
minetest.register_alias("mcl_biomes:crimson_tree", "mcl_biomes:crimson_tree1") -- legacy inconsistency, fixed 08/2024
mcl_mapgen_core.register_decoration({
deco_type = "schematic",
name = "mcl_biomes:crimson_tree2",
@ -5678,7 +5722,11 @@ local function register_dimension_decorations()
schematic = mod_mcl_crimson .. "/schematics/crimson_fungus_2.mts",
size = vector.new(5, 12, 5),
rotation = "random",
})
}, function()
local f = minetest.get_decoration_id("mcl_biomes:crimson_tree2")
table.insert(deco_ids_fungus, f)
minetest.set_gen_notify({decoration = true}, {f})
end)
mcl_mapgen_core.register_decoration({
deco_type = "schematic",
name = "mcl_biomes:crimson_tree3",
@ -5692,7 +5740,11 @@ local function register_dimension_decorations()
schematic = mod_mcl_crimson .. "/schematics/crimson_fungus_3.mts",
size = vector.new(7, 13, 7),
rotation = "random",
})
}, function()
local f = minetest.get_decoration_id("mcl_biomes:crimson_tree3")
table.insert(deco_ids_fungus, f)
minetest.set_gen_notify({decoration = true}, {f})
end)
mcl_mapgen_core.register_decoration({
deco_type = "simple",
place_on = {"mcl_crimson:warped_nylium", "mcl_crimson:weeping_vines", "mcl_nether:netherrack"},
@ -5903,13 +5955,11 @@ local function register_dimension_decorations()
decoration = "mcl_end:chorus_flower",
height = 1,
biomes = {"End", "EndMidlands", "EndHighlands", "EndBarrens", "EndSmallIslands"},
})
},function()
deco_id_chorus_plant = minetest.get_decoration_id("mcl_biomes:chorus_plant")
minetest.set_gen_notify({decoration = true}, {deco_id_chorus_plant})
end)
-- TODO: End cities
end
@ -5943,30 +5993,6 @@ if mg_name ~= "singlenode" then
register_dimension_decorations()
-- Overworld decorations for v6 are handled in mcl_mapgen_core
local deco_ids_fungus = {
minetest.get_decoration_id("mcl_biomes:crimson_tree1"),
minetest.get_decoration_id("mcl_biomes:crimson_tree2"),
minetest.get_decoration_id("mcl_biomes:crimson_tree3"),
minetest.get_decoration_id("mcl_biomes:warped_tree1"),
minetest.get_decoration_id("mcl_biomes:warped_tree2"),
minetest.get_decoration_id("mcl_biomes:warped_tree3")
}
local deco_ids_trees = {
minetest.get_decoration_id("mcl_biomes:mangrove_tree_1"),
minetest.get_decoration_id("mcl_biomes:mangrove_tree_2"),
minetest.get_decoration_id("mcl_biomes:mangrove_tree_3"),
minetest.get_decoration_id("mcl_biomes:mangrove_tree_4"),
minetest.get_decoration_id("mcl_biomes:mangrove_tree_5"),
minetest.get_decoration_id("mcl_biomes:mangrove_bee_nest"),
}
for _, f in pairs(deco_ids_fungus) do
minetest.set_gen_notify({decoration = true}, {f})
end
for _, f in pairs(deco_ids_trees) do
minetest.set_gen_notify({decoration = true}, {f})
end
local function mangrove_roots_gen(gennotify, pr)
for _, f in pairs(deco_ids_trees) do
for _, pos in ipairs(gennotify["decoration#" .. f] or {}) do
@ -6020,9 +6046,7 @@ if mg_name ~= "singlenode" then
local biomemap = minetest.get_mapgen_object("biomemap")
local swamp_biome_id = minetest.get_biome_id("MangroveSwamp")
local swamp_shore_id = minetest.get_biome_id("MangroveSwamp_shore")
local is_swamp = table.indexof(biomemap, swamp_biome_id) ~= -1
local is_swamp_shore = table.indexof(biomemap, swamp_shore_id) ~= -1
if is_swamp or is_swamp_shore then
if biomemap and (table.indexof(biomemap, swamp_biome_id) ~= -1 or table.indexof(biomemap, swamp_shore_id) ~= -1) then
mangrove_roots_gen(gennotify, pr)
end
end

View File

@ -2,6 +2,7 @@
mcl_dungeons = {}
local logging = minetest.settings:get_bool("mcl_logging_dungeons", false)
local mg_name = minetest.get_mapgen_setting("mg_name")
-- Are dungeons disabled?
if mcl_vars.mg_dungeons == false or mg_name == "singlenode" then return end
@ -234,7 +235,9 @@ local function ecb_spawn_dungeon(blockpos, action, calls_remaining, param)
-- Check conditions. If okay, start generating
if check and (openings_counter < 1 or openings_counter > 5) then return end
if logging then
minetest.log("action","[mcl_dungeons] Placing new dungeon at "..minetest.pos_to_string(vector_new(x, y, z)))
end
-- Okay! Spawning starts!
-- Remember spawner chest positions to set metadata later
@ -369,7 +372,9 @@ local function ecb_spawn_dungeon(blockpos, action, calls_remaining, param)
set_node(pos, {name="mcl_chests:chest", param2=facedir})
local meta = get_meta(pos)
if logging then
minetest.log("action", "[mcl_dungeons] Filling chest " .. tostring(c) .. " at " .. minetest.pos_to_string(pos))
end
mcl_loot.fill_inventory(meta:get_inventory(), "main", mcl_loot.get_multi_loot(loottable, pr), pr)
end
@ -404,7 +409,9 @@ local function dungeons_nodes(minp, maxp, blockseed)
local z = pr:next(minp.z, maxp.z-dim.z-1)
local p1 = vector_new(x, y, z)
local p2 = vector_new(x+dim.x+1, y+dim.y+1, z+dim.z+1)
if logging then
minetest.log("verbose","[mcl_dungeons] size=" ..minetest.pos_to_string(dim) .. ", emerge from "..minetest.pos_to_string(p1) .. " to " .. minetest.pos_to_string(p2))
end
emerge_area(p1, p2, ecb_spawn_dungeon, {p1=p1, p2=p2, dim=dim, pr=pr})
end
end
@ -414,7 +421,9 @@ function mcl_dungeons.spawn_dungeon(p1, _, pr)
if not p1 or not pr or not p1.x or not p1.y or not p1.z then return end
local dim = dungeonsizes[pr:next(1, #dungeonsizes)]
local p2 = vector_new(p1.x+dim.x+1, p1.y+dim.y+1, p1.z+dim.z+1)
if logging then
minetest.log("verbose","[mcl_dungeons] size=" ..minetest.pos_to_string(dim) .. ", emerge from "..minetest.pos_to_string(p1) .. " to " .. minetest.pos_to_string(p2))
end
emerge_area(p1, p2, ecb_spawn_dungeon, {p1=p1, p2=p2, dim=dim, pr=pr, dontcheck=true})
end

View File

@ -1,11 +1,9 @@
local registered_generators = {}
local lvm, nodes, param2 = 0, 0, 0
local lvm_buffer, lvm_buffer2 = {}, {}
local logging = minetest.settings:get_bool("mcl_logging_mapgen", false)
local log_timing = minetest.settings:get_bool("mcl_logging_mapgen_timing", false) -- detailed, for performance debugging
local registered_generators = {}
local lvm, nodes, param2 = 0, 0, 0
local function run_generators(minp, maxp, blockseed)
if nodes == 0 then return end
for _, rec in ipairs(registered_generators) do
@ -25,8 +23,8 @@ minetest.register_on_generated(function(minp, maxp, blockseed)
if lvm > 0 then
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
local area = VoxelArea(emin, emax)
local data = vm:get_data(lvm_buffer)
local data2 = param2 > 0 and vm:get_param2_data(lvm_buffer2)
local data = vm:get_data()
local data2 = param2 > 0 and vm:get_param2_data()
if log_timing then
minetest.log("action", string.format("[mcl_mapgen_core] %-20s %s ... %s %8.2fms", "get_data", minetest.pos_to_string(minp), minetest.pos_to_string(maxp), (os.clock() - t1)*1000))
end
@ -148,12 +146,11 @@ end
-- adding for example a new structure can still change everything that comes
-- later, because currently decoration blockseeds are incremented sequentially
-- c.f., https://github.com/minetest/minetest/issues/14919
local minetest_register_decoration = minetest.register_decoration
local pending_decorations = {}
function mcl_mapgen_core.register_decoration(def, callback)
if pending_decorations == nil then
minetest.log("warning", "Decoration registered after mapgen: "..tostring(def.name))
minetest_register_decoration(def)
minetest.register_decoration(def)
if callback ~= nil then callback() end
return
end
@ -186,8 +183,7 @@ local function sort_decorations()
end
table.sort(keys)
for _, key in ipairs(keys) do
-- minetest.log("action", "Deco: "..key)
minetest_register_decoration(map[key])
minetest.register_decoration(map[key])
if map[key].callback then map[key].callback() end
end
pending_decorations = nil -- as we will not run again

View File

@ -509,7 +509,7 @@ local function generate_mgv6_structures()
local surface = minetest.find_nodes_in_area({x=p.x,y=p.y-1,z=p.z}, floor, "mcl_core:snowblock")
local surface2 = minetest.find_nodes_in_area({x=p.x,y=p.y-1,z=p.z}, floor, "mcl_core:dirt_with_grass_snow")
if #surface + #surface2 >= 63 then
mcl_structures.call_struct(p, "igloo", nil, pr)
vl_structures.call_struct(p, "igloo", nil, pr)
chunk_has_igloo = true
end
end
@ -528,7 +528,7 @@ local function generate_mgv6_structures()
local nodes = minetest.find_nodes_in_area(p1, p2, {"mcl_core:sandstone", "mcl_core:stone", "mcl_core:diorite", "mcl_core:andesite", "mcl_core:granite", "mcl_core:stone_with_coal", "mcl_core:dirt", "mcl_core:gravel"})
if #nodes >= 100 then -- >= 80%
mcl_structures.call_struct(p1, "fossil", nil, pr)
vl_structures.call_struct(p1, "fossil", nil, pr)
end
end
end
@ -565,10 +565,7 @@ local function generate_mgv6_structures()
if #free_nodes >= ((size.x+1)*(size.y+1)*(size.z+1)) then
local place = {x=p.x, y=WITCH_HUT_HEIGHT-1, z=p.z}
-- FIXME: For some mysterious reason (black magic?) this
-- function does sometimes NOT spawn the witch hut. One can only see the
-- oak wood nodes in the water, but no hut. :-/
mcl_structures.place_structure(place,mcl_structures.registered_structures["witch_hut"],pr)
vl_structures.place_structure(place,vl_structures.registered_structures["witch_hut"],pr)
local function place_tree_if_free(pos, prev_result)
local nn = minetest.get_node(pos).name
@ -637,7 +634,7 @@ local function generate_mgv6_structures()
local spruce_collisions = minetest.find_nodes_in_area({x=p.x+1,y=p.y+2,z=p.z+1}, {x=p.x+4, y=p.y+6, z=p.z+4}, {"mcl_core:sprucetree", "mcl_core:spruceleaves"})
if #surface >= 9 and #spruce_collisions == 0 then
mcl_structures.place_structure(p,mcl_structures.registered_structures["ice_spike_large"],pr)
vl_structures.place_structure(p,vl_structures.registered_structures["ice_spike_large"],pr)
end
elseif spike < 100 then
-- Check surface
@ -648,7 +645,7 @@ local function generate_mgv6_structures()
local spruce_collisions = minetest.find_nodes_in_area({x=p.x+1,y=p.y+1,z=p.z+1}, {x=p.x+6, y=p.y+6, z=p.z+6}, {"mcl_core:sprucetree", "mcl_core:spruceleaves"})
if #surface >= 25 and #spruce_collisions == 0 then
mcl_structures.place_structure(p,mcl_structures.registered_structures["ice_spike_small"],pr)
vl_structures.place_structure(p,vl_structures.registered_structures["ice_spike_small"],pr)
end
end
end

View File

@ -5,20 +5,18 @@ local peaceful = minetest.settings:get_bool("only_peaceful_mobs", false)
local BLAZE_SPAWNER_MAX_LIGHT = 11
mcl_structures.register_structure("nether_outpost",{
vl_structures.register_structure("nether_outpost",{
place_on = {"mcl_nether:netherrack","mcl_crimson:crimson_nylium","mcl_crimson:warped_nylium","mcl_blackstone:basalt","mcl_blackstone:soul_soil","mcl_blackstone:blackstone","mcl_nether:soul_sand"},
chunk_probability = 23,
flags = "all_floors",
flags = "place_center_x, place_center_y, all_floors",
biomes = {"Nether","SoulsandValley","WarpedForest","CrimsonForest","BasaltDelta"},
sidelen = 24,
solid_ground = true,
prepare = { tolerance=20, padding=2, corners=5, foundation=true, clearance=true },
prepare = { tolerance = 20, padding = 4, corners = 5, foundation = true, clear = true, clear_top = 4 },
y_min = mcl_vars.mg_lava_nether_max - 1,
y_max = mcl_vars.mg_nether_max - 30,
filenames = { modpath.."/schematics/mcl_nether_fortresses_nether_outpost.mts" },
y_offset = 0,
after_place = function(pos)
local sp = minetest.find_nodes_in_area(pos,vector.offset(pos,0,20,0),{"mcl_mobspawners:spawner"})
after_place = function(pos,def,pr,p1,p2)
local sp = minetest.find_nodes_in_area(p1,p2,{"mcl_mobspawners:spawner"})
if not sp[1] then return end
mcl_mobspawners.setup_spawner(sp[1], "mobs_mc:blaze", 0, BLAZE_SPAWNER_MAX_LIGHT, 10, 8, 0)
end
@ -29,85 +27,101 @@ local nbridges = {
modpath.."/schematics/mcl_nether_fortresses_nether_bridge_3.mts",
modpath.."/schematics/mcl_nether_fortresses_nether_bridge_4.mts",
}
mcl_structures.register_structure("nether_bridge",{
vl_structures.register_structure("nether_bridge",{
place_on = {"mcl_nether:nether_lava_source","mcl_nether:netherrack","mcl_crimson:crimson_nylium","mcl_crimson:warped_nylium","mcl_blackstone:basalt","mcl_blackstone:soul_soil","mcl_blackstone:blackstone","mcl_nether:soul_sand"},
chunk_probability = 5, -- because of the small height allowed, these are quite rare otherwise
flags = "all_floors, liquid_surface",
prepare = { tolerance=-1, clearance = 6 },
chunk_probability = 8, -- because of the y restriction these are quite rare
flags = "place_center_x, place_center_y, all_floors",
prepare = { tolerance = 50, padding = -1, corners = 0, clear_bottom = 8, clear_top = 6 }, -- asymmetric padding would be nice to have
force_placement = true,
sidelen = 38,
solid_ground = false,
y_min = mcl_vars.mg_lava_nether_max - 5,
y_max = mcl_vars.mg_lava_nether_max + 15,
y_min = mcl_vars.mg_lava_nether_max,
y_max = mcl_vars.mg_lava_nether_max + 25, -- otherwise, we may see some very long legs
filenames = nbridges,
y_offset = function(pr) return pr:next(-12, -5) end,
after_place = function(pos,def,pr)
local p1 = vector.offset(pos,-14,0,-14)
local p2 = vector.offset(pos,14,24,14)
mcl_structures.spawn_mobs("mobs_mc:witherskeleton",{"mcl_blackstone:blackstone_chiseled_polished"},p1,p2,pr,5)
y_offset = function(pr) return pr:next(-8, -5) end,
after_place = function(pos,def,pr,p1,p2)
vl_structures.spawn_mobs("mobs_mc:witherskeleton",{"mcl_blackstone:blackstone_chiseled_polished"},p1,p2,pr,5)
-- p1.y is not a typo, we want to lowest level only
local legs = minetest.find_nodes_in_area(vector.new(p1.x,p1.y,p1.z),vector.new(p2.x,p1.y,p2.z), "mcl_nether:nether_brick")
local bricks = {}
-- TODO: port leg generation to voxel manipulators?
for _,leg in pairs(legs) do
while true do
leg = vector.offset(leg,0,-1,0)
local nodename = minetest.get_node(leg).name
if nodename == "ignore" then break end
if nodename ~= "air" and nodename ~= "mcl_core:lava_source" and minetest.get_item_group(nodename, "solid") ~= 0 then break end
table.insert(bricks,leg)
end
end
minetest.bulk_set_node(bricks, {name = "mcl_nether:nether_brick", param2 = 2})
end
})
mcl_structures.register_structure("nether_outpost_with_bridges",{
vl_structures.register_structure("nether_outpost_with_bridges",{
place_on = {"mcl_nether:netherrack","mcl_crimson:crimson_nylium","mcl_crimson:warped_nylium","mcl_blackstone:basalt","mcl_blackstone:soul_soil","mcl_blackstone:blackstone","mcl_nether:soul_sand","mcl_nether:nether_lava_source"},
chunk_probability = 33,
flags = "all_floors",
chunk_probability = 10, -- because of the y restriction, it will still be rare
flags = "place_center_x, place_center_y, all_floors",
biomes = {"Nether","SoulsandValley","WarpedForest","CrimsonForest","BasaltDelta"},
sidelen = 24,
solid_ground = true,
prepare = { tolerance=30, padding=4, corners=5, foundation=true, clearance=true },
prepare = { tolerance = 20, padding = 4, corners = 5, foundation = true, clear_top = 3 },
y_min = mcl_vars.mg_lava_nether_max - 1,
y_max = mcl_vars.mg_nether_max - 30,
y_max = mcl_vars.mg_lava_nether_max + 40,
-- todo: spawn_by a lot of air?
filenames = { modpath.."/schematics/mcl_nether_fortresses_nether_outpost.mts" },
daughters = {{
files = { nbridges[1] },
pos = vector.new(0,-3,-25),
rot = 180,
prepare = { tolerance = -1, foundation = false, clearance = 14, padding = -2, corners=2 },
},
emerge_padding = { vector.new(-38,-8,-38), vector.new(38,0,38) },
daughters = {
{
files = { nbridges[1] },
filenames = { nbridges[1], nbridges[2] },
pos = vector.new(0,-3,24),
rot = 0,
rotation= 0,
no_level = true,
prepare = { tolerance = -1, foundation = false, clearance = 14, padding = -2, corners=2 },
prepare = { tolerance = -1, foundation = false, clear = true, clear_bottom = 16, clear_top = 2, padding = 1, corners = 4 },
},
{
files = { nbridges[1] },
pos = vector.new(-25,-3,0),
rot = 270,
no_level = true,
prepare = { tolerance = -1, foundation = false, clearance = 14, padding = -2, corners=2 },
},
{
files = { nbridges[1] },
filenames = { nbridges[1], nbridges[2] },
pos = vector.new(24,-3,0),
rot = 90,
rotation = 90,
no_level = true,
prepare = { tolerance = -1, foundation = false, clearance = 14, padding = -2, corners=2 },
prepare = { tolerance = -1, foundation = false, clear = true, clear_bottom = 16, clear_top = 2, padding = 1, corners = 4 },
},
{
filenames = { nbridges[1], nbridges[2] },
pos = vector.new(0,-3,-25),
rotation = 180,
no_level = true,
prepare = { tolerance = -1, foundation = false, clear = true, clear_bottom = 16, clear_top = 2, padding = 1, corners = 4 },
},
{
filenames = { nbridges[1], nbridges[2] },
pos = vector.new(-25,-3,0),
rotation = 270,
no_level = true,
prepare = { tolerance = -1, foundation = false, clear = true, clear_bottom = 16, clear_top = 2, padding = 1, corners = 4 },
},
},
after_place = function(pos,def,pr)
local sp = minetest.find_nodes_in_area(pos,vector.offset(pos,0,20,0),{"mcl_mobspawners:spawner"})
after_place = function(pos,def,pr,p1,p2)
local sp = minetest.find_nodes_in_area(p1,p2,{"mcl_mobspawners:spawner"})
if not sp[1] then return end
mcl_mobspawners.setup_spawner(sp[1], "mobs_mc:blaze", 0, BLAZE_SPAWNER_MAX_LIGHT, 10, 8, 0)
local legs = minetest.find_nodes_in_area(vector.offset(pos,-45,-2,-45),vector.offset(pos,45,0,45), "mcl_nether:nether_brick")
-- the -3 offset needs to be carefully aligned with the bridges above
local legs = minetest.find_nodes_in_area(vector.offset(pos,-45,-3,-45),vector.offset(pos,45,-3,45), "mcl_nether:nether_brick")
local bricks = {}
-- TODO: port leg generation to voxel manipulators?
for _,leg in pairs(legs) do
while minetest.get_item_group(mcl_vars.get_node(vector.offset(leg,0,-1,0), true, 333333).name, "solid") == 0 do
while true do
leg = vector.offset(leg,0,-1,0)
local nodename = minetest.get_node(leg).name
if nodename == "ignore" then break end
if nodename ~= "air" and nodename ~= "mcl_core:lava_source" and minetest.get_item_group(nodename, "solid") ~= 0 then break end
table.insert(bricks,leg)
end
end
minetest.bulk_set_node(bricks, {name = "mcl_nether:nether_brick", param2 = 2})
local p1, p2 = vector.offset(pos,-45,12,-45), vector.offset(pos,45,22,45)
mcl_structures.spawn_mobs("mobs_mc:witherskeleton",{"mcl_blackstone:blackstone_chiseled_polished"},p1,p2,pr,5)
vl_structures.spawn_mobs("mobs_mc:witherskeleton",{"mcl_blackstone:blackstone_chiseled_polished"},p1,p2,pr,5)
end
})
mcl_structures.register_structure_spawn({
vl_structures.register_structure_spawn({
name = "mobs_mc:witherskeleton",
y_min = mcl_vars.mg_lava_nether_max,
y_max = mcl_vars.mg_nether_max,
@ -117,14 +131,12 @@ mcl_structures.register_structure_spawn({
spawnon = { "mcl_blackstone:blackstone_chiseled_polished" },
})
mcl_structures.register_structure("nether_bulwark",{
vl_structures.register_structure("nether_bulwark",{
place_on = {"mcl_nether:netherrack","mcl_crimson:crimson_nylium","mcl_crimson:warped_nylium","mcl_blackstone:basalt","mcl_blackstone:soul_soil","mcl_blackstone:blackstone","mcl_nether:soul_sand"},
chunk_probability = 29,
flags = "all_floors",
flags = "place_center_x, place_center_y, all_floors",
biomes = {"Nether","SoulsandValley","WarpedForest","CrimsonForest"},
sidelen = 36,
solid_ground = true,
prepare = { tolerance=15, padding=4, corners=4, foundation=true, clearance=true },
prepare = { tolerance=10, padding=4, corners=5, foundation=-5, clear_top=0 },
y_min = mcl_vars.mg_lava_nether_max - 1,
y_max = mcl_vars.mg_nether_max - 30,
filenames = {
@ -134,24 +146,24 @@ mcl_structures.register_structure("nether_bulwark",{
modpath.."/schematics/mcl_nether_fortresses_nether_bulwark_4.mts",
},
daughters = {{
files = {
filenames = {
modpath.."/schematics/mcl_nether_fortresses_nether_bulwark_interior_1.mts",
modpath.."/schematics/mcl_nether_fortresses_nether_bulwark_interior_2.mts",
modpath.."/schematics/mcl_nether_fortresses_nether_bulwark_interior_3.mts",
modpath.."/schematics/mcl_nether_fortresses_nether_bulwark_interior_4.mts",
},
pos = vector.new(0,1,0),
force_place = true,
prepare = { tolerance = -1, foundation = false, clearance = false },
rotation = "random",
force_placement = true,
prepare = { tolerance = -1, foundation = false, clear = false },
},
},
y_offset = 0,
construct_nodes = {"group:wall"},
after_place = function(pos,def,pr)
local p1, p2 = vector.offset(pos,-14,0,-14), vector.offset(pos,14,24,14)
mcl_structures.spawn_mobs("mobs_mc:piglin",{"mcl_blackstone:blackstone_brick_polished","mcl_stairs:slab_blackstone_polished"},p1,p2,pr,5)
mcl_structures.spawn_mobs("mobs_mc:piglin_brute",{"mcl_blackstone:blackstone_brick_polished","mcl_stairs:slab_blackstone_polished"},p1,p2,pr)
mcl_structures.spawn_mobs("mobs_mc:hoglin",{"mcl_blackstone:nether_gold"},p1,p2,pr,4)
after_place = function(pos,def,pr,p1,p2)
vl_structures.spawn_mobs("mobs_mc:piglin",{"mcl_blackstone:blackstone_brick_polished","mcl_stairs:slab_blackstone_polished"},p1,p2,pr,5)
vl_structures.spawn_mobs("mobs_mc:piglin_brute",{"mcl_blackstone:blackstone_brick_polished","mcl_stairs:slab_blackstone_polished"},p1,p2,pr)
vl_structures.spawn_mobs("mobs_mc:hoglin",{"mcl_blackstone:nether_gold"},p1,p2,pr,4)
end,
loot = {
["mcl_chests:chest_small" ] ={
@ -198,22 +210,22 @@ mcl_structures.register_structure("nether_bulwark",{
},
})
mcl_structures.register_structure_spawn({
vl_structures.register_structure_spawn({
name = "mobs_mc:piglin",
y_min = mcl_vars.mg_nether_min,
y_max = mcl_vars.mg_nether_max,
chance = 10,
interval = 60,
limit = 9,
spawnon = {"mcl_blackstone:blackstone_brick_polished","mcl_stairs:slab_blackstone_polished"},
spawnon = {"mcl_blackstone:blackstone_brick_polished", "mcl_stairs:slab_blackstone_polished"},
})
mcl_structures.register_structure_spawn({
vl_structures.register_structure_spawn({
name = "mobs_mc:piglin_brute",
y_min = mcl_vars.mg_nether_min,
y_max = mcl_vars.mg_nether_max,
chance = 20,
interval = 60,
limit = 4,
spawnon = {"mcl_blackstone:blackstone_brick_polished","mcl_stairs:slab_blackstone_polished"},
spawnon = {"mcl_blackstone:blackstone_brick_polished", "mcl_stairs:slab_blackstone_polished"},
})

View File

@ -83,19 +83,20 @@ local function generate_strongholds(minp, maxp, blockseed)
pos.z = maxp.z - 7
end
--mcl_structures.call_struct(pos, "end_portal_shrine", nil, pr)
--vl_structures.call_struct(pos, "end_portal_shrine", nil, pr)
strongholds[s].generated = true
end
end
end
end
mcl_structures.register_structure("end_shrine",{
vl_structures.register_structure("end_shrine",{
static_pos = init_strongholds(),
prepare = { tolerance = -1, foundation = false, clear = false },
filenames = {
minetest.get_modpath("mcl_structures").."/schematics/mcl_structures_end_portal_room_simple.mts"
},
after_place = function(pos,def,pr,blockseed,p1,p2,size,rotation)
after_place = function(pos,def,pr,p1,p2,size,rotation)
local p1 = vector.subtract(pos,size)
local p2 = vector.add(pos,size)
local spawners = minetest.find_nodes_in_area(p1, p2, "mcl_mobspawners:spawner")

View File

@ -1,38 +1,7 @@
# mcl_structures
Structure placement API for MCL2.
VoxeLibre structures
## mcl_structures.register_structure(name,structure definition,nospawn)
If nospawn is truthy the structure will not be placed by mapgen and the decoration parameters can be omitted. This is intended for secondary structures the placement of which gets triggered by the placement of other structures. It can also be used to register testing structures so they can be used with /spawnstruct.
This module contains standard VoxeLibre structures such as nether portals.
### structure definition
{
fill_ratio = OR noise = {},
biomes = {},
y_min =,
y_max =,
place_on = {},
spawn_by = {},
num_spawn_by =,
flags = (default: "place_center_x, place_center_z, force_placement")
(same as decoration def)
y_offset =, --can be a number or a function returning a number
filenames = {} OR place_func = function(pos,def,pr)
-- filenames can be a list of any schematics accepted by mcl_structures.place_schematic / minetest.place_schematic
on_place = function(pos,def,pr) end,
-- called before placement. denies placement when returning falsy.
after_place = function(pos,def,pr)
-- executed after successful placement
sidelen = int, --length of one side of the structure. used for foundations.
solid_ground = bool, -- structure requires solid ground
make_foundation = bool, -- a foundation is automatically built for the structure. needs the sidelen param
loot = ,
--a table of loot tables for mcl_loot indexed by node names
-- e.g. { ["mcl_chests:chest_small"] = {loot},... }
}
## mcl_structures.registered_structures
Table of the registered structure defintions indexed by name.
The API has been redesigned and moved to the vl_structures module.
## mcl_structures.place_structure(pos, def, pr)
Places a structure using the mapgen placement function
## mcl_structures.place_schematic(pos, schematic, rotation, replacements, force_placement, flags, after_placement_callback, pr, callback_param)

View File

@ -1,464 +0,0 @@
mcl_structures.registered_structures = {}
local peaceful = minetest.settings:get_bool("only_peaceful_mobs", false)
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("mcl_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 logging = minetest.settings:get_bool("mcl_logging_structures", true)
local mg_name = minetest.get_mapgen_setting("mg_name")
local disabled_structures = minetest.settings:get("mcl_disabled_structures")
if disabled_structures then disabled_structures = disabled_structures:split(",")
else disabled_structures = {} end
function mcl_structures.is_disabled(structname)
return table.indexof(disabled_structures,structname) ~= -1
end
local ROTATIONS = { "0", "90", "180", "270" }
function mcl_structures.parse_rotation(rotation, pr)
if rotation == "random" and pr then return ROTATIONS[pr:next(1,#ROTATIONS)] end
return rotation
end
--- Get the size after rotation.
-- @param size vector: Size information
-- @param rotation string or number: only 0, 90, 180, 270 are allowed
-- @return vector: new vector, for safety
function mcl_structures.size_rotated(size, rotation)
if rotation == "90" or rotation == "270" or rotation == 90 or rotation == 270 then
return vector.new(size.z, size.y, size.x)
end
return vector.copy(size)
end
--- Get top left position after apply centering flags and padding.
-- @param pos vector: Placement position
-- @param[opt] size vector: Size information
-- @param[opt] flags string or table: as in minetest.place_schematic, place_center_x, place_center_y
-- @param[opt] padding number: optional margin (integer)
-- @return vector: new vector, for safety
function mcl_structures.top_left_from_flags(pos, size, flags, padding)
local dx, dy, dz = 0, 0, 0
-- must match src/mapgen/mg_schematic.cpp to be consistent
if type(flags) == "table" then
if flags["place_center_x"] ~= nil then dx = -math.floor((size.x-1)*0.5) end
if flags["place_center_y"] ~= nil then dy = -math.floor((size.y-1)*0.5) end
if flags["place_center_z"] ~= nil then dz = -math.floor((size.z-1)*0.5) end
elseif type(flags) == "string" then
if string.find(flags, "place_center_x") then dx = -math.floor((size.x-1)*0.5) end
if string.find(flags, "place_center_y") then dy = -math.floor((size.y-1)*0.5) end
if string.find(flags, "place_center_z") then dz = -math.floor((size.z-1)*0.5) end
end
if padding then
dx = dx - padding
dz = dz - padding
end
return vector.offset(pos, dx, dy, dz)
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)
-- clearance boolean or string or number: clear overhead area (offset, or "top" to begin over the structure only)
-- padding number: additional padding to increase the area, default 1
-- corners number: corner smoothing of foundation and clearance, default 1
-- pr PcgRandom: random generator
-- name string: for logging
local function emerge_schematic_vm(vm, param)
local pos, size, prepare, surface_mat = param.pos, param.size, param.prepare, nil
-- adjust ground to a move level position
if pos and size and prepare and (prepare.tolerance or 10) >= 0 then
pos, surface_mat = mcl_structures.find_level(vm, pos, size, prepare.tolerance)
if not pos then
minetest.log("warning", "[mcl_structures] Not spawning "..tostring(param.name or param.schematic.name).." at "..minetest.pos_to_string(param.pos).." because ground is too uneven.")
return nil
end
if param.y_max and pos.y > param.y_max then pos.y = param.y_max end
if param.y_min and pos.y < param.y_min then pos.y = param.y_min end
end
-- Prepare the environment
if prepare and (prepare.clearance or prepare.foundation) then
-- 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 or (surface_mat and surface_mat.name) or "mcl_core:dirt_with_grass"
local node_filler = b and b.node_filler or "mcl_core:dirt"
local node_stone = b and b.node_stone or "mcl_core:stone"
-- FIXME: not yet used: local node_dust = b and b.node_dust
local node_top_param2 = node_top == "mcl_core:dirt_with_grass" and b._mcl_grass_palette_index or 0 -- grass color, also other materials?
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(pos, -math.floor((size.x-1)*0.5) - padding, 0, -math.floor((size.z-1)*0.5)-padding)
local gs = vector.offset(size, padding*2, depth, padding*2)
if prepare.clearance then
-- minetest.log("action", "[mcl_structures] clearing air "..minetest.pos_to_string(gp).." +"..minetest.pos_to_string(gs).." corners "..corners)
-- TODO: add more parameters?
local yoff, height = 0, size.y + (param.yoffset or 0)
if prepare.clearance == "top" or prepare.clearance == "above" then
yoff, height = height, 0
elseif type(prepare.clearance) == "number" then
yoff, height = prepare.clearance, height - prepare.clearance
end
mcl_structures.clearance(vm, gp.x, gp.y + yoff, gp.z, gs.x, height, gs.z, corners, {name=node_top, param2=node_top_param2}, param.pr)
end
if prepare.foundation then
-- minetest.log("action", "[mcl_structures] fill foundation "..minetest.pos_to_string(gp).." +"..minetest.pos_to_string(gs).." corners "..corners)
local depth = (type(prepare.foundation) == "number" and prepare.foundation) or -3
mcl_structures.foundation(vm, gp.x, gp.y - 1, gp.z, gs.x, depth, gs.z, corners,
{name=node_top, param2=node_top_param2}, {name=node_filler}, {name=node_stone}, param.pr)
end
end
-- place the actual schematic
pos.y = pos.y + (param.yoffset or 0)
minetest.place_schematic_on_vmanip(vm, pos, 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)
vm:write_to_map(true)
if not pos then return end
-- repair walls (TODO: port to vmanip? but no "vm.find_nodes_in_area" yet)
local pmin = vector.offset(pos, -math.floor((param.size.x-1)*0.5), 0, -math.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
local DEFAULT_PREPARE = { tolerance = 8, foundation = -3, clearance = false, padding = 1, corners = 1 }
local DEFAULT_FLAGS = "place_center_x,place_center_z"
function mcl_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 = loadstring(minetest.serialize_schematic(schematic, "lua", {lua_use_comments = false, lua_num_indent_spaces = 0}) .. " return schematic")()
end
rotation = mcl_structures.parse_rotation(rotation, pr)
local size = mcl_structures.size_rotated(schematic.size, rotation)
-- area to emerge; note that alignment flags could be non-center, although we almost always use place_center_x,place_center_z
local pmin = mcl_structures.top_left_from_flags(pos, flags or DEFAULT_FLAGS)
local ppos = vector.offset(pmin, math.floor((size.x-1)*0.5), 0, math.floor((size.z-1)*0.5)) -- center
local pmax = vector.offset(pmin, size.x - 1, size.y - 1, size.z - 1)
if prepare == nil or prepare == true then prepare = DEFAULT_PREPARE end
if prepare == false then prepare = {} end
-- area to emerge. Add some margin to allow for finding better suitable ground etc.
local emin, emax = vector.offset(pmin, -1, -5, -1), vector.offset(pmax, 1, 5, 1)
if prepare then emin.y = emin.y - (prepare.tolerance or 10) end
-- if we need to generate a foundation, we need to emerge a larger area:
if prepare.foundation or prepare.clearance then
-- these functions need some extra margins
local padding, depth, height = (prepare.padding or 0) + 3, (prepare.depth or -4) - 15, size.y * 2 + 6
emin = vector.offset(pmin, -padding, depth + math.min(yoffset or 0, 0), -padding)
emax = vector.offset(pmax, padding, height + math.max(yoffset or 0, 0), padding)
end
minetest.emerge_area(emin, emax, emerge_schematic, {
emin=emin, emax=emax, name=schematic.name or (type(schematic)=="string" and schematic),
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
-- Call all on_construct handlers
-- also called from mcl_villages for job sites
function mcl_structures.init_node_construct(pos)
local node = minetest.get_node(pos)
local def = node and minetest.registered_nodes[node.name]
if def and def.on_construct then def.on_construct(pos) end
end
-- Find nodes to call on_construct handlers for
function mcl_structures.construct_nodes(p1,p2,nodes)
local nn = minetest.find_nodes_in_area(p1,p2,nodes)
for _,p in pairs(nn) do mcl_structures.init_node_construct(p) end
end
function mcl_structures.fill_chests(p1,p2,loot,pr)
for it,lt in pairs(loot) do
local nodes = minetest.find_nodes_in_area(p1, p2, it)
for _,p in pairs(nodes) do
local lootitems = mcl_loot.get_multi_loot(lt, pr)
mcl_structures.init_node_construct(p)
local meta = minetest.get_meta(p)
local inv = meta:get_inventory()
mcl_loot.fill_inventory(inv, "main", lootitems, pr)
end
end
end
function mcl_structures.spawn_mobs(mob,spawnon,p1,p2,pr,n,water)
n = n or 1
local sp = {}
if water then
local nn = minetest.find_nodes_in_area(p1,p2,spawnon)
for k,v in pairs(nn) do
if minetest.get_item_group(minetest.get_node(vector.offset(v,0,1,0)).name,"water") > 0 then
table.insert(sp,v)
end
end
else
sp = minetest.find_nodes_in_area_under_air(p1,p2,spawnon)
end
table.shuffle(sp)
local count = 0
local mob_def = minetest.registered_entities[mob]
local enabled = (not peaceful) or (mob_def and mob_def.spawn_class ~= "hostile")
for _,node in pairs(sp) do
if enabled and count < n and minetest.add_entity(vector.offset(node, 0, 1, 0), mob) then
count = count + 1
end
minetest.get_meta(node):set_string("spawnblock", "yes") -- note: also in peaceful mode!
end
end
function mcl_structures.place_structure(pos, def, pr, blockseed, rot)
if not def then return end
local log_enabled = logging and not def.terrain_feature
-- currently only used by fallen_tree, to check for sufficient empty space to fall
if def.on_place and not def.on_place(pos,def,pr,blockseed) then
if log_enabled then
minetest.log("warning","[mcl_structures] "..def.name.." at "..minetest.pos_to_string(pos).." not placed. on_place conditions not satisfied.")
end
return false
end
-- Apply vertical offset for schematic
local yoffset = (type(def.y_offset) == "function" and def.y_offset(pr)) or def.y_offset or 0
if def.schematics and #def.schematics > 0 then
local schematic = def.schematics[pr:next(1,#def.schematics)]
rot = mcl_structures.parse_rotation(rot or "random", pr)
if not def.daughters then
mcl_structures.place_schematic(pos, yoffset, def.y_min, def.y_max, schematic, rot, def.replacements, def.force_placement, "place_center_x,place_center_z", def.prepare, pr,
function(p1, p2, size, rotation)
if def.loot then mcl_structures.fill_chests(p1,p2,def.loot,pr) end
if def.construct_nodes then mcl_structures.construct_nodes(p1,p2,def.construct_nodes) end
if def.after_place then def.after_place(pos,def,pr,p1,p2,size,rotation) end
if log_enabled then
minetest.log("action", "[mcl_structures] "..def.name.." spawned at "..minetest.pos_to_string(pos))
end
end)
else -- currently only nether bulwarks + nether outpost with bridges?
-- FIXME: this really needs to be run in a single emerge!
mcl_structures.place_schematic(pos, yoffset, def.y_min, def.y_max, schematic, rot, def.replacements, def.force_placement, "place_center_x,place_center_z", def.prepare, pr,
function(p1, p2, size, rotation)
for i,d in pairs(def.daughters) do
local ds = d.files[pr:next(1,#d.files)]
-- Daughter schematics are not loaded yet.
if ds and not ds.size then
ds = loadstring(minetest.serialize_schematic(ds, "lua", {lua_use_comments = false, lua_num_indent_spaces = 0}) .. " return schematic")()
end
-- FIXME: apply centering, apply parent rotation.
local rot = d.rot or 0
local dsize = mcl_structures.size_rotated(ds.size, rot)
local p = vector.new(math.floor((p1.x+p2.x)*0.5) + d.pos.x - math.floor((dsize.x-1)*0.5), p1.y + (yoffset or 0) + d.pos.y, math.floor((p1.z+p2.z)*0.5) + d.pos.z - math.floor((dsize.z-1)*0.5))
local callback = nil
if i == #def.daughters then
callback = function()
-- Note: deliberately pos, p1 and p2 from the parent, as these are calls to the parent.
if def.loot then mcl_structures.fill_chests(p1,p2,def.loot,pr) end
if def.construct_nodes then mcl_structures.construct_nodes(p1,p2,def.construct_nodes) end
if def.after_place then def.after_place(pos,def,pr,p1,p2,size,rotation) end
if log_enabled then
minetest.log("action", "[mcl_structures] "..def.name.." spawned at "..minetest.pos_to_string(pos))
end
end
end
mcl_structures.place_schematic(p, yoffset, d.y_min or def.y_min, d.y_max or def.y_max, ds, rot, nil, true, "place_center_x,place_center_y", d.prepare, pr, callback)
end
end)
end
if log_enabled then
minetest.log("verbose", "[mcl_structures] "..def.name.." to be placed at "..minetest.pos_to_string(pos))
end
return true
end
if not def.place_func then
minetest.log("warning","[mcl_structures] no schematics and no place_func for schematic "..def.name)
return false
end
if def.solid_ground and def.sidelen and not def.prepare then
-- TODO: this assumes place_center, make padding configurable, use actual size?
local ground_p1 = vector.offset(pos,-math.floor(def.sidelen/2),-1,-math.floor(def.sidelen/2))
local ground_p2 = vector.offset(ground_p1,def.sidelen-1,0,def.sidelen-1)
local solid = minetest.find_nodes_in_area(ground_p1,ground_p2,{"group:solid"})
if #solid < def.sidelen * def.sidelen then
if log_enabled then
minetest.log("warning", "[mcl_structures] "..def.name.." at "..minetest.pos_to_string(pos).." not placed. No solid ground.")
end
return false
end
end
local pp = yoffset ~= 0 and vector.offset(pos, 0, yoffset, 0) or pos
if def.place_func and def.place_func(pp,def,pr,blockseed) then
if not def.after_place or (def.after_place and def.after_place(pp,def,pr,blockseed)) then
if def.prepare then
minetest.log("warning", "[mcl_structures] needed prepare for "..def.name.." placed at "..minetest.pos_to_string(pp).." but did not have size information")
end
if def.sidelen then
local p1, p2 = vector.offset(pos,-def.sidelen,-def.sidelen,-def.sidelen), vector.offset(pos,def.sidelen,def.sidelen,def.sidelen)
if def.loot then mcl_structures.fill_chests(p1,p2,def.loot,pr) end
if def.construct_nodes then mcl_structures.construct_nodes(p1,p2,def.construct_nodes) end
end
if log_enabled then
minetest.log("action","[mcl_structures] "..def.name.." placed at "..minetest.pos_to_string(pp))
end
return true
else
minetest.log("warning","[mcl_structures] after_place failed for schematic "..def.name)
return false
end
elseif log_enabled then
minetest.log("warning","[mcl_structures] place_func failed for schematic "..def.name)
end
end
local EMPTY_SCHEMATIC = { size = {x = 0, y = 0, z = 0}, data = { } }
function mcl_structures.register_structure(name,def,nospawn) --nospawn means it will not be placed by mapgen decoration mechanism
if mcl_structures.is_disabled(name) then return end
def.name = name
def.prepare = def.prepare or (type(def.make_foundation) == table and def.make_foundation)
def.flags = def.flags or "place_center_x, place_center_z, force_placement"
if def.filenames then
if #def.filenames == 0 then
minetest.log("warning","[mcl_structures] schematic "..name.." has an empty list of filenames.")
end
def.schematics = def.schematics or {}
for _, filename in ipairs(def.filenames) do
if not mcl_util.file_exists(filename) then
minetest.log("warning","[mcl_structures] schematic "..name.." is missing file "..tostring(filename))
else
-- load, and ensure we have size information
local s = nil --minetest.read_schematic(filename)
if not s or not s.size then
s = loadstring(minetest.serialize_schematic(filename, "lua", {lua_use_comments = false, lua_num_indent_spaces = 0}) .. " return schematic")()
end
if not s then
minetest.log("warning", "[mcl_structures] failed to load schematic "..tostring(filename))
elseif not s.size then
minetest.log("warning", "[mcl_structures] no size information for schematic "..tostring(filename))
else
if logging then
minetest.log("verbose", "[mcl_structures] loaded schematic "..tostring(filename).." size "..minetest.pos_to_string(s.size))
end
if not s.name then s.name = name or filename end
table.insert(def.schematics, s)
end
end
end
end
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
mcl_structures.registered_structures[name] = def
if nospawn then return end -- ice column, boulder
if def.place_on then
minetest.register_on_mods_loaded(function() --make sure all previous decorations and biomes have been registered
def.deco = mcl_mapgen_core.register_decoration({
name = "mcl_structures:deco_"..name,
priority = def.priority or (def.terrain_feature and 900) or 100, -- run before regular decorations
deco_type = "schematic",
schematic = EMPTY_SCHEMATIC,
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
fill_ratio = def.fill_ratio,
noise_params = def.noise_params,
flags = def.flags,
biomes = def.biomes,
y_max = def.y_max,
y_min = def.y_min
}, function()
def.deco_id = minetest.get_decoration_id("mcl_structures:deco_"..name)
minetest.set_gen_notify({decoration=true}, { def.deco_id })
end)
end)
end
end
local structure_spawns = {}
function mcl_structures.register_structure_spawn(def)
--name,y_min,y_max,spawnon,biomes,chance,interval,limit
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)
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
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
-- To avoid a cyclic dependency, run this when modules have finished loading
minetest.register_on_mods_loaded(function()
mcl_mapgen_core.register_generator("structures", nil, function(minp, maxp, blockseed)
local gennotify = minetest.get_mapgen_object("gennotify")
for _,struct in pairs(mcl_structures.registered_structures) do
if struct.deco_id then
for _, pos in pairs(gennotify["decoration#"..struct.deco_id] or {}) do
local pr = PcgRandom(minetest.hash_node_position(pos) + blockseed + RANDOM_SEED_OFFSET)
if struct.chunk_probability == nil or pr:next(1, struct.chunk_probability) == 1 then
mcl_structures.place_structure(vector.offset(pos,0,1,0),struct,pr,blockseed)
if struct.chunk_probability then break end -- one (attempt) per chunk only
end
end
elseif struct.static_pos then
local pr = PcgRandom(blockseed + RANDOM_SEED_OFFSET)
for _, pos in pairs(struct.static_pos) do
if vector.in_area(pos, minp, maxp) then
mcl_structures.place_structure(pos, struct, pr, blockseed)
end
end
end
end
return false, false, false
end, 100, true)
end)

View File

@ -2,15 +2,12 @@ local modname = minetest.get_current_modname()
local S = minetest.get_translator(modname)
local modpath = minetest.get_modpath(modname)
local function temple_placement_callback(pos,def, pr)
local hl = def.sidelen / 2
local p1 = vector.offset(pos,-hl,-hl,-hl)
local p2 = vector.offset(pos,hl,hl,hl)
local function temple_placement_callback(pos,def,pr,p1,p2)
-- Delete cacti leftovers:
local cactus_nodes = minetest.find_nodes_in_area_under_air(p1, p2, "mcl_core:cactus")
if cactus_nodes and #cactus_nodes > 0 then
for _, pos in pairs(cactus_nodes) do
local node_below = minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z})
local node_below = minetest.get_node(vector.offset(pos,0,-1,0))
if node_below and node_below.name == "mcl_core:sandstone" then
minetest.swap_node(pos, {name="air"})
end
@ -32,12 +29,11 @@ local function temple_placement_callback(pos,def, pr)
end
end
mcl_structures.register_structure("desert_temple",{
vl_structures.register_structure("desert_temple",{
place_on = {"group:sand"},
flags = "place_center_x, place_center_z",
solid_ground = true,
sidelen = 18,
y_offset = -12,
prepare = { padding = 3, corners = 3, foundation = true, clear = false },
chunk_probability = 8,
y_max = mcl_vars.mg_overworld_max,
y_min = 1,

View File

@ -0,0 +1,15 @@
local modname = minetest.get_current_modname()
--local S = minetest.get_translator(modname)
local modpath = minetest.get_modpath(modname)
vl_structures.register_structure("desert_well",{
place_on = {"group:sand"},
flags = "place_center_x, place_center_z",
chunk_probability = 15,
y_max = mcl_vars.mg_overworld_max,
y_min = 1,
y_offset = -2,
biomes = { "Desert" },
filenames = { modpath.."/schematics/mcl_structures_desert_well.mts" },
})

View File

@ -4,38 +4,34 @@ local modpath = minetest.get_modpath(modname)
local spawnon = {"mcl_end:purpur_block"}
local function spawn_shulkers(pos,def,pr)
local p1 = vector.offset(pos,-def.sidelen/2,-1,-def.sidelen/2)
local p2 = vector.offset(pos,def.sidelen/2,def.sidelen,def.sidelen/2)
mcl_structures.spawn_mobs("mobs_mc:shulker",spawnon,p1,p2,pr,1)
local guard = minetest.find_node_near(pos,def.sidelen,{"mcl_itemframes:item_frame"})
if guard then
minetest.add_entity(vector.offset(guard,0,-1.5,0),"mobs_mc:shulker")
local function spawn_shulkers(pos,def,pr,p1,p2)
vl_structures.spawn_mobs("mobs_mc:shulker",spawnon,p1,p2,pr,1)
local guard = minetest.find_nodes_in_area(p1,p2,{"mcl_itemframes:item_frame"})
if #guard > 0 then
minetest.add_entity(vector.offset(guard[1],0,-1.5,0),"mobs_mc:shulker")
end
end
mcl_structures.register_structure("end_shipwreck",{
vl_structures.register_structure("end_shipwreck",{
place_on = {"mcl_end:end_stone"},
flags = "place_center_x, place_center_z, all_floors",
y_offset = function(pr) return pr:next(-50,-20) end,
y_offset = function(pr) return pr:next(20,50) end,
force_placement = false,
prepare = { foundation = false, clear = false },
chunk_probability = 25,
--y_max = mcl_vars.mg_end_max,
--y_min = mcl_vars.mg_end_min -100,
biomes = { "End", "EndHighlands", "EndMidlands", "EndBarrens", "EndSmallIslands" },
sidelen = 32,
filenames = {
modpath.."/schematics/mcl_structures_end_shipwreck_1.mts",
},
construct_nodes = {"mcl_chests:ender_chest_small","mcl_chests:ender_chest","mcl_brewing:stand_000","mcl_chests:violet_shulker_box_small"},
after_place = function(pos,def,pr)
local fr = minetest.find_node_near(pos,def.sidelen,{"mcl_itemframes:item_frame"})
if fr then
if mcl_itemframes then
mcl_itemframes.update_item_entity(fr,minetest.get_node(fr))
after_place = function(pos,def,pr,p1,p2)
local fr = minetest.find_nodes_in_area(p1,p2,{"mcl_itemframes:item_frame"})
if #fr > 0 and mcl_itemframes then
mcl_itemframes.update_item_entity(fr[1],minetest.get_node(fr))
end
end
return spawn_shulkers(pos,def,pr)
return spawn_shulkers(pos,def,pr,p1,p2)
end,
loot = {
[ "mcl_itemframes:item_frame" ] ={{
@ -52,7 +48,7 @@ mcl_structures.register_structure("end_shipwreck",{
{ itemstring = "mcl_mobitems:bone", weight = 20, amount_min = 4, amount_max=6 },
{ itemstring = "mcl_farming:beetroot_seeds", weight = 16, amount_min = 1, amount_max=10 },
{ itemstring = "mcl_core:gold_ingot", weight = 15, amount_min = 2, amount_max = 7 },
--{ itemstring = "mcl_bamboo:bamboo", weight = 15, amount_min = 1, amount_max=3 }, --FIXME BAMBOO
{ itemstring = "mcl_bamboo:bamboo", weight = 15, amount_min = 1, amount_max=3 },
{ itemstring = "mcl_core:iron_ingot", weight = 15, amount_min = 4, amount_max = 8 },
{ itemstring = "mcl_core:diamond", weight = 3, amount_min = 2, amount_max = 7 },
{ itemstring = "mcl_mobitems:saddle", weight = 3, },
@ -85,16 +81,16 @@ mcl_structures.register_structure("end_shipwreck",{
}
})
mcl_structures.register_structure("end_boat",{
vl_structures.register_structure("end_boat",{
place_on = {"mcl_end:end_stone"},
fill_ratio = 0.01,
flags = "place_center_x, place_center_z, all_floors",
y_offset = function(pr) return pr:next(15,30) end,
chunk_probability = 900,
force_placement = false,
prepare = { foundation = false, clear = false },
chunk_probability = 10,
--y_max = mcl_vars.mg_end_max,
--y_min = mcl_vars.mg_end_min -100,
biomes = { "End", "EndHighlands", "EndMidlands", "EndBarrens", "EndSmallIslands" },
sidelen = 20,
filenames = {
modpath.."/schematics/mcl_structures_end_boat.mts",
},
@ -133,7 +129,7 @@ mcl_structures.register_structure("end_boat",{
}
})
mcl_structures.register_structure_spawn({
vl_structures.register_structure_spawn({
name = "mobs_mc:shulker",
y_min = mcl_vars.mg_end_min,
y_max = mcl_vars.mg_end_max,

View File

@ -2,8 +2,7 @@ local modname = minetest.get_current_modname()
local S = minetest.get_translator(modname)
local modpath = minetest.get_modpath(modname)
mcl_structures.register_structure("end_spawn_obsidian_platform",{
vl_structures.register_structure("end_spawn_obsidian_platform",{
static_pos ={mcl_vars.mg_end_platform_pos},
place_func = function(pos,def,pr)
local obby = minetest.find_nodes_in_area(vector.offset(pos,-2,0,-2),vector.offset(pos,2,0,2),{"air","mcl_end:end_stone"})
@ -14,17 +13,13 @@ mcl_structures.register_structure("end_spawn_obsidian_platform",{
end,
})
mcl_structures.register_structure("end_exit_portal",{
vl_structures.register_structure("end_exit_portal",{
static_pos = { mcl_vars.mg_end_exit_portal_pos },
filenames = {
modpath.."/schematics/mcl_structures_end_exit_portal.mts"
},
after_place = function(pos,def,pr,blockseed)
if minetest.settings:get_bool("only_peaceful_mobs", false) then
return
end
local p1 = vector.offset(pos,-16,-16,-16)
local p2 = vector.offset(pos,16,21,16)
after_place = function(pos,def,pr,p1,p2)
if minetest.settings:get_bool("only_peaceful_mobs", false) then return end
minetest.emerge_area(p1,p2,function(blockpos, action, calls_remaining, param)
if calls_remaining > 0 then return end
minetest.bulk_set_node(minetest.find_nodes_in_area(p1,p2,{"mcl_portals:portal_end"}),{name="air"})
@ -32,9 +27,9 @@ mcl_structures.register_structure("end_exit_portal",{
if obj then
local dragon_entity = obj:get_luaentity()
dragon_entity._portal_pos = pos
if blockseed ~= -1 then
-- FIXME: if blockseed ~= -1 then
dragon_entity._initial = true
end
-- end
else
minetest.log("error", "[mcl_mapgen_core] ERROR! Ender dragon doesn't want to spawn")
end
@ -42,17 +37,15 @@ mcl_structures.register_structure("end_exit_portal",{
end)
end
})
mcl_structures.register_structure("end_exit_portal_open",{
vl_structures.register_structure("end_exit_portal_open",{
filenames = {
modpath.."/schematics/mcl_structures_end_exit_portal.mts"
},
after_place = function(pos,def,pr)
local p1 = vector.offset(pos,-16,-16,-16)
local p2 = vector.offset(pos,16,16,16)
after_place = function(pos,def,pr,p1,p2)
minetest.fix_light(p1,p2)
end
})
mcl_structures.register_structure("end_gateway_portal",{
vl_structures.register_structure("end_gateway_portal",{
filenames = {
modpath.."/schematics/mcl_structures_end_gateway_portal.mts"
},
@ -79,7 +72,6 @@ end
function make_cage(pos,width)
local nodes = {}
local nodes2 = {}
local r = math.max(1,math.floor(width/2) - 2)
for x=-r,r do for y = 0,width do for z = -r,r do
if x == r or x == -r or z==r or z == -r then
@ -97,13 +89,13 @@ end
local function get_points_on_circle(pos,r,n)
local rt = {}
for i=1, n do
table.insert(rt,vector.offset(pos,r * math.cos(((i-1)/n) * (2*math.pi)),0, r* math.sin(((i-1)/n) * (2*math.pi)) ))
table.insert(rt,vector.offset(pos,r * math.cos(((i-1)/n) * (2*math.pi)),0, r * math.sin(((i-1)/n) * (2*math.pi)) ))
end
return rt
end
mcl_structures.register_structure("end_spike",{
static_pos =get_points_on_circle(vector.offset(mcl_vars.mg_end_exit_portal_pos,0,-20,0),43,10),
vl_structures.register_structure("end_spike",{
static_pos = get_points_on_circle(vector.offset(mcl_vars.mg_end_exit_portal_pos, 0, -20, 0), 43, 10),
place_func = function(pos,def,pr)
local d = pr:next(6,12)
local h = d * pr:next(4,6)

View File

@ -0,0 +1,25 @@
local modname = minetest.get_current_modname()
--local S = minetest.get_translator(modname)
local modpath = minetest.get_modpath(modname)
vl_structures.register_structure("fossil",{
place_on = {"group:material_stone","group:sand"},
flags = "place_center_x, place_center_z",
prepare = { },
chunk_probability = 15, -- was 25
y_offset = function(pr) return pr:next(-16,-32) end,
y_max = 15,
y_min = mcl_vars.mg_overworld_min + 35,
biomes = { "Desert" },
filenames = {
modpath.."/schematics/mcl_structures_fossil_skull_1.mts", -- 4×5×5
modpath.."/schematics/mcl_structures_fossil_skull_2.mts", -- 5×5×5
modpath.."/schematics/mcl_structures_fossil_skull_3.mts", -- 5×5×7
modpath.."/schematics/mcl_structures_fossil_skull_4.mts", -- 7×5×5
modpath.."/schematics/mcl_structures_fossil_spine_1.mts", -- 3×3×13
modpath.."/schematics/mcl_structures_fossil_spine_2.mts", -- 5×4×13
modpath.."/schematics/mcl_structures_fossil_spine_3.mts", -- 7×4×13
modpath.."/schematics/mcl_structures_fossil_spine_4.mts", -- 8×5×13
},
})

View File

@ -1,334 +0,0 @@
local AIR = {name = "air"}
local abs = math.abs
local max = math.max
-- fairly strict: air, ignore, or no_paths marker
local function is_air(node)
return not node or node.name == "air" or node.name == "ignore" or node.name == "mcl_villages:no_paths"
end
-- check if a node is walkable (solid), but not tree/leaves
local function is_solid_not_tree(node)
if not node or node.name == "air" or node.name == "ignore" or node.name == "mcl_villages:no_paths" then return false end
local meta = minetest.registered_items[node.name]
local groups = meta and meta.groups
return meta and meta.walkable and not (groups and (groups["deco_block"] or groups["tree"] or groups["leaves"] or groups["plant"]))
end
-- check if a node is walkable (solid), but not tree/leaves or buildungs
local function is_solid_not_tree_or_building(node)
if not node or node.name == "air" or node.name == "ignore" or node.name == "mcl_villages:no_paths" then return false end
local meta = minetest.registered_items[node.name]
local groups = meta and meta.groups
return meta and meta.walkable and not (groups and (groups["deco_block"] or groups["tree"] or groups["leaves"] or groups["plant"] or groups["building_block"]))
end
-- check if a node is tree
local function is_tree(node)
if not node or node.name == "air" or node.name == "ignore" or node.name == "mcl_villages:no_paths" then return false end
local meta = minetest.registered_items[node.name]
local groups = meta and meta.groups
return groups and (groups["tree"] or groups["leaves"])
end
-- replace a non-solid node, optionally also "additional"
local function make_solid(lvm, cp, with, additional)
local cur = lvm:get_node_at(cp)
if not is_solid_not_tree(cur) or (additional and cur.name == additional.name) then
lvm:set_node_at(cp, with)
end
end
local function excavate(lvm,xi,yi,zi,pr,keep_trees)
local pos, n, c = vector.new(xi,yi,zi), nil, 0
local node = lvm:get_node_at(pos)
if is_air(node) then return false end -- already empty, nothing to do
if keep_trees and is_tree(node) then return false end
pos.y = pos.y-1
if not is_air(lvm:get_node_at(pos)) then return false end -- below is solid, do not clear above anymore
-- count empty nodes below otherwise
for x = xi-1,xi+1 do
for z = zi-1,zi+1 do
pos.x, pos.z = x, z
if is_air(lvm:get_node_at(pos)) then c = c + 1 end
end
end
-- try to completely remove trees overhead
-- stop randomly depending on fill, to narrow down the caves
if not keep_trees and not is_tree(node) and (pr:next(0,1e9)/1e9)^2 > c/9.1 then return false end
lvm:set_node_at(vector.new(xi, yi, zi), AIR)
return true -- modified
end
function mcl_structures.clearance(lvm, px, py, pz, sx, sy, sz, corners, surface_mat, pr)
corners = corners or 0
local wx2, wz2 = max(sx - corners, 1)^-2*2, max(sz - corners, 1)^-2*2
local cx, cz = px + sx * 0.5 - 0.5, pz + sz * 0.5 - 0.5
-- excavate the needed volume and some headroom
for xi = px,px+sx-1 do
local dx2 = (cx-xi)^2*wx2
for zi = pz,pz+sz-1 do
local dz2 = (cz-zi)^2*wz2
if dx2+dz2 <= 1 then
lvm:set_node_at(vector.new(xi, py, zi), AIR)
local n = lvm:get_node_at(vector.new(xi, py-1, zi))
if n and n.name ~= surface_mat.name and is_solid_not_tree_or_building(n) then
lvm:set_node_at(vector.new(xi, py-1, zi), surface_mat)
end
-- py+1 to py+4 are filled wider below, this is the top of the building only
for yi = py+5,py+sy do
lvm:set_node_at(vector.new(xi, yi, zi), AIR)
end
end
end
end
-- slightly widen the cave above, to make easier to enter for mobs
for xi = px-1,px+sx do
local dx2 = max(abs(cx-xi)-1,0)^2*wx2
for zi = pz-1,pz+sz do
local dz2 = max(abs(cz-zi)-1,0)^2*wz2
if dx2+dz2 <= 1 then
for yi = py+1,py+4 do
lvm:set_node_at(vector.new(xi, yi, zi), AIR)
end
local n = lvm:get_node_at(vector.new(xi, py, zi))
for yi = py,py-1,-1 do
local n = lvm:get_node_at(vector.new(xi, yi, zi))
if is_tree(n) then
lvm:set_node_at(vector.new(xi, yi, zi), AIR)
else
if n and n.name ~= surface_mat.name and is_solid_not_tree_or_building(n) then
lvm:set_node_at(vector.new(xi, yi, zi), surface_mat)
end
break
end
end
end
end
end
-- some extra gaps for entry
for xi = px-2,px+sx+1 do
local dx2 = max(abs(cx-xi)-2,0)^2*wx2
for zi = pz-2,pz+sz+1 do
local dz2 = max(abs(cz-zi)-2,0)^2*wz2
if dx2+dz2 <= 1 and pr:next(1,4) == 1 then
for yi = py+2,py+4 do
lvm:set_node_at(vector.new(xi, yi, zi), AIR)
end
local n = lvm:get_node_at(vector.new(xi, py+1, zi))
for yi = py+1,py-1,-1 do
local n = lvm:get_node_at(vector.new(xi, yi, zi))
if is_tree(n) then
lvm:set_node_at(vector.new(xi, yi, zi), AIR)
else
if n and n.name ~= surface_mat.name and is_solid_not_tree_or_building(n) then
lvm:set_node_at(vector.new(xi, py+1, zi), surface_mat)
end
break
end
end
end
end
end
-- cave some additional area overhead, try to make it interesting though
local min_clear, max_clear = sy+5, sy*2+5 -- FIXME: make parameters
for yi = py+2,py+max_clear do
local dy2 = (py-yi)^2*0.025
local active = false
for xi = px-2,px+sx+1 do
local dx2 = max(abs(cx-xi)-2,0)^2*wx2
for zi = pz-2,pz+sz+1 do
local dz2 = max(abs(cz-zi)-2,0)^2*wz2
local keep_trees = (xi<px or xi>=px+sx) or (zi<pz or zi>=pz+sz) -- TODO make configurable?
if dx2+dz2+dy2 <= 1 and excavate(lvm,xi,yi,zi,pr,keep_trees) then active = true end
end
end
if not active and yi > py+min_clear then break end
end
end
-- TODO: allow controlling the random depth?
-- TODO: add support for dust_mat (snow)
local function grow_foundation(lvm,xi,yi,zi,pr,surface_mat,platform_mat,stone_mat)
local pos, n, c = vector.new(xi,yi,zi), nil, 0
if is_solid_not_tree(lvm:get_node_at(pos)) then return false end -- already solid, nothing to do
pos.y = pos.y+1
local cur = lvm:get_node_at(pos)
if not is_solid_not_tree(cur) then return false end -- above is empty, do not fill below
if cur and cur.name and cur.name ~= surface_mat.name then platform_mat = cur end
if pr:next(1,5) == 5 then -- randomly switch to stone sometimes
platform_mat = stone_mat
end
-- count solid nodes above otherwise
for x = xi-1,xi+1 do
for z = zi-1,zi+1 do
pos.x, pos.z = x, z
if is_solid_not_tree(lvm:get_node_at(pos)) then c = c + 1 end
end
end
-- stop randomly depending on fill, to narrow down the foundation
if (pr:next(0,1e9)/1e9)^2 > c/9.1 then return false end
lvm:set_node_at(vector.new(xi, yi, zi), platform_mat)
return true -- modified
end
-- generate a foundations around px,py,pz with size sx,sy,sz (sy < 0)
-- TODO: add support for dust_mat (snow)
-- Rounding: we model an ellipse. At zero rounding, we want the line go through the corner, at sx/2, sz/2.
-- For this, we need to make ellipse sized 2a=sqrt(2)*sx, 2b=sqrt(2)*sz,
-- Which yields a = sx/sqrt(2), b=sz/sqrt(2) and a^2=sx^2*0.5, b^2=sz^2*0.5
-- To get corners, we decrease a and b by approx. corners each
-- The ellipse condition dx^2/a^2+dz^2/b^2 <= 1 then yields dx^2/(sx^2*0.5) + dz^2/(sz^2*0.5) <= 1
-- We use wx2=(sx^2)^-2*2, wz2=(sz^2)^-2*2 and then dx^2*wx2+dz^2*wz2 <= 1
function mcl_structures.foundation(lvm, px, py, pz, sx, depth, sz, corners, surface_mat, platform_mat, stone_mat, pr)
corners = corners or 0
local wx2, wz2 = max(sx - corners, 1)^-2*2, max(sz - corners, 1)^-2*2
local cx, cz = px + sx * 0.5 - 0.5, pz + sz * 0.5 - 0.5
-- generate a baseplate
for xi = px,px+sx-1 do
local dx2 = (cx-xi)^2*wx2
for zi = pz,pz+sz-1 do
local dz2 = (cz-zi)^2*wz2
if dx2+dz2 <= 1 then
lvm:set_node_at(vector.new(xi, py, zi), surface_mat)
make_solid(lvm, vector.new(xi, py-1, zi), platform_mat)
end
end
end
-- slightly widen the baseplate below, to make easier to enter for mobs
if corners and corners > 0 then
for xi = px-1,px+sx do
local dx2 = max(abs(cx-xi)-1,0)^2*wx2
-- TODO: compute the z value ranges directly?
for zi = pz-1,pz+sz do
local dz2 = max(abs(cz-zi)-1,0)^2*wz2
if dx2+dz2 <= 1 then
make_solid(lvm, vector.new(xi, py-1, zi), surface_mat)
end
end
end
else
for xi = px+1,px+sx-1-1 do
make_solid(lvm, vector.new(xi, py-1, pz-1), surface_mat, platform_mat)
make_solid(lvm, vector.new(xi, py-1, pz), platform_mat)
make_solid(lvm, vector.new(xi, py-1, pz+sz-1), platform_mat)
make_solid(lvm, vector.new(xi, py-1, pz+sz), surface_mat, platform_mat)
end
for zi = pz+1,pz+sz-1-1 do
make_solid(lvm, vector.new(px-1, py-1, zi), surface_mat, platform_mat)
make_solid(lvm, vector.new(px, py-1, zi), platform_mat)
make_solid(lvm, vector.new(px+sx-1, py-1, zi), platform_mat)
make_solid(lvm, vector.new(px+sx, py-1, zi), surface_mat, platform_mat)
end
-- make some additional steps, along both x sides
for xi = px+1,px+sx-2 do
local cp = vector.new(xi, py-3, pz-1)
if is_solid_not_tree(lvm:get_node_at(cp)) then
cp = vector.new(xi, py-2, pz-1)
make_solid(lvm, cp, surface_mat, platform_mat)
cp.z = pz-2
make_solid(lvm, cp, surface_mat, platform_mat)
end
local cp = vector.new(xi, py-3, pz+sz)
if is_solid_not_tree(lvm:get_node_at(cp)) then
cp = vector.new(xi, py-2, pz+sz)
make_solid(lvm, cp, surface_mat, platform_mat)
cp.z = pz + sz + 1
make_solid(lvm, cp, surface_mat, platform_mat)
end
end
-- make some additional steps, along both z sides
for zi = pz+1,pz+sz-2 do
local cp = vector.new(px-1, py-3, zi)
if is_solid_not_tree(lvm:get_node_at(cp)) then
cp = vector.new(px-1, py-2, zi)
make_solid(lvm, cp, surface_mat, platform_mat)
cp.x = px-2
make_solid(lvm, cp, surface_mat, platform_mat)
end
local cp = vector.new(px+sx, py-3, zi)
if is_solid_not_tree(lvm:get_node_at(cp)) then
cp = vector.new(px+sx, py-2, zi)
make_solid(lvm, cp, surface_mat, platform_mat)
cp.x = px+sx+1
make_solid(lvm, cp, surface_mat, platform_mat)
end
end
end
-- construct additional baseplate below, also try to make it interesting
for yi = py-2,py-20,-1 do
local dy2 = (py-yi)^2*0.025
local active = false
for xi = px-1,px+sx do
local dx2 = max(abs(cx-xi)-1,0)^2*wx2
for zi = pz-1,pz+sz do
local dz2 = max(abs(cz-zi)-1,0)^2*wz2
if dx2+dy2+dz2 <= 1 then
if grow_foundation(lvm,xi,yi,zi,pr,surface_mat,platform_mat,stone_mat) then active = true end
end
end
end
if not active and yi < py + depth then break end
end
end
-- return position and material of surface
function mcl_structures.find_ground(lvm, pos)
if not pos then return nil, nil end
pos = vector.copy(pos)
local cur = lvm:get_node_at(pos)
if not cur or cur.name == "ignore" then
local e1, e2 = lvm:get_emerged_area()
minetest.log("warning","find_ground with invalid position (outside of emerged area?) at "..minetest.pos_to_string(pos)..": "..tostring(cur and cur.name).." area: "..minetest.pos_to_string(e1).." "..minetest.pos_to_string(e2))
return nil
end
if is_solid_not_tree(cur) then -- find up
local prev = cur
while true do
pos.y = pos.y + 1
local cur = lvm:get_node_at(pos)
if not cur or cur.name == "ignore" then
minetest.log("action", "No ground, "..tostring(cur and cur.name).." over "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
end
if not is_solid_not_tree(cur) then
pos.y = pos.y - 1
return pos, prev
end
prev = cur
end
else -- find down
while true do
pos.y = pos.y - 1
local prev = cur
local cur = lvm:get_node_at(pos)
if not cur or cur.name == "ignore" then
minetest.log("action", "No ground, "..tostring(cur and cur.name).." below "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
end
if is_solid_not_tree(cur) then
return pos, cur
end
end
end
end
-- find suitable height for a structure of this size
-- @param lvm VoxelManip: to read data
-- @param cpos vector: center
-- @param size vector: area size
-- @param tolerance number: maximum height difference allowed, default 8
-- @return position, surface material
function mcl_structures.find_level(lvm, cpos, size, tolerance)
local cpos, surface_material = mcl_structures.find_ground(lvm, cpos)
if not cpos then return nil, nil end
local ys = {cpos.y}
local pos = vector.offset(cpos, -math.floor((size.x-1)/2), 0, -math.floor((size.z-1)/2)) -- top left
local pos_c = mcl_structures.find_ground(lvm, pos)
if pos_c then table.insert(ys, pos_c.y) end
local pos_c = mcl_structures.find_ground(lvm, vector.offset(pos, size.x-1, 0, 0))
if pos_c then table.insert(ys, pos_c.y) end
local pos_c = mcl_structures.find_ground(lvm, vector.offset(pos, 0, 0, size.z-1))
if pos_c then table.insert(ys, pos_c.y) end
local pos_c = mcl_structures.find_ground(lvm, vector.offset(pos, size.x-1, 0, size.z-1))
if pos_c then table.insert(ys, pos_c.y) end
table.sort(ys)
-- well supported base, not too uneven?
if #ys <= 4 or math.max(ys[#ys-1]-ys[1], ys[#ys]-ys[2]) > (tolerance or 8) then
minetest.log("action", "[mcl_structures] ground too uneven: "..#ys.." positions, trimmed difference "..(#ys < 2 and "" or math.max(ys[#ys-1]-ys[1], ys[#ys]-ys[2])))
return nil, nil
end
cpos.y = math.round(0.5*(ys[math.floor(#ys * 0.5)] + ys[math.ceil(#ys * 0.5)])) + 1 -- median, rounded, over surface
return cpos, surface_material
end

View File

@ -75,7 +75,7 @@ local function makegeode(pos,def,pr)
return true
end
mcl_structures.register_structure("geode",{
vl_structures.register_structure("geode",{
place_on = {"group:material_stone"},
noise_params = {
offset = 0,

View File

@ -19,33 +19,15 @@ local function spawn_mobs(p1,p2,vi,zv)
end
end
local function generate_igloo_basement(pos, orientation, loot, pr)
end
local function generate_igloo(pos, def, pr)
local path = modpath.."/schematics/mcl_structures_igloo_top.mts"
local rotation = tostring(pr:next(0,3)*90)
-- TODO: ymin, ymax
mcl_structures.place_schematic(pos, -2, nil, nil, path, rotation, nil, true, nil, {padding=0, corners=2}, pr, function(p1, p2)
mcl_structures.construct_nodes(p1, p2, {"mcl_furnaces:furnace","mcl_books:bookshelf"})
local function igloo_callback(cpos,def,pr,p1,p2,size,rotation)
vl_structures.construct_nodes(p1, p2, {"mcl_furnaces:furnace","mcl_books:bookshelf"})
-- Place igloo basement with 50% chance
local r = 1--pr:next(1,2)
if r == 1 then
if pr:next(1,2) == 1 then return end
local pos = p1 -- we use top left as reference
-- Select basement depth
local dim = mcl_worlds.pos_to_dimension(pos)
local buffer
if dim == "nether" then
buffer = pos.y - (mcl_vars.mg_lava_nether_max + 10)
elseif dim == "end" then
buffer = pos.y - (mcl_vars.mg_end_min + 1)
elseif dim == "overworld" then
buffer = pos.y - (mcl_vars.mg_lava_overworld_max + 10)
else
return true
end
if buffer <= 9 then return true end
local depth = pr:next(9, buffer)
local bpos = vector.new(pos.x, pos.y-depth, pos.z)
local maxdepth = pos.y - (mcl_vars.mg_lava_overworld_max + 10)
if maxdepth <= 9 then return true end
local depth = pr:next(9, maxdepth)
-- trapdoor position and orientation
local tpos, dir, tdir
if rotation == "0" then
@ -79,52 +61,48 @@ local function generate_igloo(pos, def, pr)
end
minetest.set_node(pos, {name=brick})
end
local real_depth = 0
local real_depth = 2
-- Check how deep we can actually dig
for y=1, depth-5 do
for y=pos.y-real_depth, pos.y-depth, -1 do
real_depth = real_depth + 1
local node = minetest.get_node(vector.new(tpos.x, tpos.y-y, tpos.z))
local node = minetest.get_node(vector.new(tpos.x, y, tpos.z))
local def = node and minetest.registered_nodes[node.name]
if not (def and def.walkable and def.liquidtype == "none" and def.is_ground_content) then
bpos.y = tpos.y-y+1
break
end
if not (def and def.walkable and def.liquidtype == "none" and def.is_ground_content) then break end
end
local bpos = vector.new(cpos.x, pos.y-real_depth+1, cpos.z)
if real_depth <= 6 then
minetest.log("not deep enough")
minetest.log("action", "Ground not deep enough for igloo basement: "..real_depth)
return false
end
local path = modpath.."/schematics/mcl_structures_igloo_basement.mts"
mcl_structures.place_schematic(bpos, 0, nil, nil, path, rotation, nil, true, nil, nil, pr, function(p1, p2)
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=1, real_depth do
set_brick(vector.new(tpos.x-1, tpos.y-y, tpos.z ))
set_brick(vector.new(tpos.x+1, tpos.y-y, tpos.z ))
set_brick(vector.new(tpos.x , tpos.y-y, tpos.z-1))
set_brick(vector.new(tpos.x , tpos.y-y, tpos.z+1))
minetest.set_node(vector.new(tpos.x, tpos.y-y, tpos.z), ladder)
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
mcl_structures.fill_chests(p1,p2,def.loot,pr)
mcl_structures.construct_nodes(p1,p2,{"mcl_brewing:stand_000","mcl_books:bookshelf"})
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)
end
end)
return true
end
mcl_structures.register_structure("igloo",{
vl_structures.register_structure("igloo",{
filenames = { modpath.."/schematics/mcl_structures_igloo_top.mts" },
place_on = {"mcl_core:snowblock","mcl_core:snow","group:grass_block_snow"},
sidelen = 16,
chunk_probability = 7,
solid_ground = true,
prepare = { padding = 1, corners = 1, foundation = -6, clear_top=-1 },
y_max = mcl_vars.mg_overworld_max,
y_min = 0,
y_offset = -2,
biomes = { "ColdTaiga", "IcePlainsSpikes", "IcePlains" },
place_func = generate_igloo,
after_place = igloo_callback,
loot = {
["mcl_chests:chest_small"] = {{
stacks_min = 1,

View File

@ -1,62 +1,34 @@
local modname = minetest.get_current_modname()
local S = minetest.get_translator(modname)
local modpath = minetest.get_modpath(modname)
mcl_structures = {}
dofile(modpath.."/api.lua")
dofile(modpath.."/foundation.lua")
dofile(modpath.."/shipwrecks.lua")
-- some legacy API adapters
mcl_structures.is_disabled = vl_structures.is_disabled
mcl_structures.init_node_construct = vl_structures.init_node_construct
mcl_structures.construct_nodes = vl_structures.construct_nodes
mcl_structures.fill_chests = vl_structures.fill_chests
mcl_structures.spawn_mobs = vl_structures.spawn_mobs
-- TODO: provide more legacy adapters that translate parameters?
dofile(modpath.."/desert_temple.lua")
dofile(modpath.."/desert_well.lua")
dofile(modpath.."/end_city.lua")
dofile(modpath.."/end_spawn.lua")
dofile(modpath.."/fossil.lua")
dofile(modpath.."/geode.lua")
dofile(modpath.."/igloo.lua")
dofile(modpath.."/jungle_temple.lua")
dofile(modpath.."/ocean_ruins.lua")
dofile(modpath.."/witch_hut.lua")
dofile(modpath.."/igloo.lua")
dofile(modpath.."/woodland_mansion.lua")
dofile(modpath.."/ruined_portal.lua")
dofile(modpath.."/geode.lua")
dofile(modpath.."/ocean_temple.lua")
dofile(modpath.."/pillager_outpost.lua")
dofile(modpath.."/end_spawn.lua")
dofile(modpath.."/end_city.lua")
dofile(modpath.."/ruined_portal.lua")
dofile(modpath.."/shipwrecks.lua")
dofile(modpath.."/witch_hut.lua")
dofile(modpath.."/woodland_mansion.lua")
mcl_structures.register_structure("desert_well",{
place_on = {"group:sand"},
flags = "place_center_x, place_center_z",
not_near = { "desert_temple_new" },
solid_ground = true,
sidelen = 4,
chunk_probability = 15,
y_max = mcl_vars.mg_overworld_max,
y_min = 1,
y_offset = -2,
biomes = { "Desert" },
filenames = { modpath.."/schematics/mcl_structures_desert_well.mts" },
})
mcl_structures.register_structure("fossil",{
place_on = {"group:material_stone","group:sand"},
flags = "place_center_x, place_center_z",
solid_ground = true,
sidelen = 13,
chunk_probability = 25,
y_offset = function(pr) return ( pr:next(1,16) * -1 ) -16 end,
y_max = 15,
y_min = mcl_vars.mg_overworld_min + 35,
biomes = { "Desert" },
filenames = {
modpath.."/schematics/mcl_structures_fossil_skull_1.mts", -- 4×5×5
modpath.."/schematics/mcl_structures_fossil_skull_2.mts", -- 5×5×5
modpath.."/schematics/mcl_structures_fossil_skull_3.mts", -- 5×5×7
modpath.."/schematics/mcl_structures_fossil_skull_4.mts", -- 7×5×5
modpath.."/schematics/mcl_structures_fossil_spine_1.mts", -- 3×3×13
modpath.."/schematics/mcl_structures_fossil_spine_2.mts", -- 5×4×13
modpath.."/schematics/mcl_structures_fossil_spine_3.mts", -- 7×4×13
modpath.."/schematics/mcl_structures_fossil_spine_4.mts", -- 8×5×13
},
})
mcl_structures.register_structure("boulder",{
vl_structures.register_structure("boulder",{
filenames = {
modpath.."/schematics/mcl_structures_boulder_small.mts",
modpath.."/schematics/mcl_structures_boulder_small.mts",
@ -66,70 +38,11 @@ mcl_structures.register_structure("boulder",{
},
},true) --is spawned as a normal decoration. this is just for /spawnstruct
mcl_structures.register_structure("ice_spike_small",{
vl_structures.register_structure("ice_spike_small",{
filenames = { modpath.."/schematics/mcl_structures_ice_spike_small.mts" },
},true) --is spawned as a normal decoration. this is just for /spawnstruct
mcl_structures.register_structure("ice_spike_large",{
sidelen = 6,
vl_structures.register_structure("ice_spike_large",{
filenames = { modpath.."/schematics/mcl_structures_ice_spike_large.mts" },
},true) --is spawned as a normal decoration. this is just for /spawnstruct
-- Debug command
local function dir_to_rotation(dir)
local ax, az = math.abs(dir.x), math.abs(dir.z)
if ax > az then
if dir.x < 0 then
return "270"
end
return "90"
end
if dir.z < 0 then
return "180"
end
return "0"
end
minetest.register_chatcommand("spawnstruct", {
params = "dungeon",
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 = dir_to_rotation(dir)
local seed = minetest.hash_node_position(pos)
local pr = PcgRandom(seed)
local errord = false
local message = S("Structure placed.")
if param == "dungeon" and mcl_dungeons and mcl_dungeons.spawn_dungeon then
mcl_dungeons.spawn_dungeon(pos, rot, pr)
elseif param == "" then
message = S("Error: No structure type given. Please use “/spawnstruct <type>”.")
errord = true
else
for n,d in pairs(mcl_structures.registered_structures) do
if n == param then
mcl_structures.place_structure(pos,d,pr,seed,rot)
return true,message
end
end
message = S("Error: Unknown structure type. Please use “/spawnstruct <type>”.")
errord = true
end
minetest.chat_send_player(name, message)
if errord then
minetest.chat_send_player(name, S("Use /help spawnstruct to see a list of avaiable types."))
end
end
})
minetest.register_on_mods_loaded(function()
local p = ""
for n,_ in pairs(mcl_structures.registered_structures) do
p = p .. " | "..n
end
minetest.registered_chatcommands["spawnstruct"].params = minetest.registered_chatcommands["spawnstruct"].params .. p
end)

View File

@ -2,16 +2,14 @@ local modname = minetest.get_current_modname()
local S = minetest.get_translator(modname)
local modpath = minetest.get_modpath(modname)
mcl_structures.register_structure("jungle_temple",{
vl_structures.register_structure("jungle_temple",{
place_on = {"group:grass_block","group:dirt","mcl_core:dirt_with_grass"},
flags = "place_center_x, place_center_z",
solid_ground = true,
y_offset = function(pr) return pr:next(-3,0) -5 end,
chunk_probability = 5,
y_max = mcl_vars.mg_overworld_max,
y_min = 1,
biomes = { "Jungle" },
sidelen = 18,
filenames = {
modpath.."/schematics/mcl_structures_jungle_temple.mts",
modpath.."/schematics/mcl_structures_jungle_temple_nice.mts",

View File

@ -1,4 +1,4 @@
name = mcl_structures
author = Wuzzy, cora, kno10
description = Structure placement for MCL2
depends = mcl_init, mcl_util, mcl_loot
depends = mcl_init, mcl_util, mcl_loot, vl_terraforming, vl_structures

View File

@ -75,13 +75,11 @@ local cold = {
spawn_by = {"mcl_core:water_source"},
num_spawn_by = 2,
flags = "place_center_x, place_center_z, force_placement",
solid_ground = true,
y_offset = -1,
y_min = mcl_vars.mg_overworld_min,
y_max = -2,
biomes = cold_oceans,
chunk_probability = 10,
sidelen = 20,
filenames = {
modpath.."/schematics/mcl_structures_ocean_ruins_cold_1.mts",
modpath.."/schematics/mcl_structures_ocean_ruins_cold_2.mts",
@ -126,5 +124,5 @@ warm.filenames = {
modpath.."/schematics/mcl_structures_ocean_ruins_warm_4.mts",
}
mcl_structures.register_structure("cold_ocean_ruins",cold)
mcl_structures.register_structure("warm_ocean_ruins",warm)
vl_structures.register_structure("cold_ocean_ruins",cold)
vl_structures.register_structure("warm_ocean_ruins",warm)

View File

@ -0,0 +1,157 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
--local S = minetest.get_translator(modname)
local water_level = minetest.get_mapgen_setting("water_level")
local spawnon = { "mcl_stairs:slab_prismarine_dark" }
local ocean_biomes = {
"RoofedForest_ocean",
"JungleEdgeM_ocean",
"BirchForestM_ocean",
"BirchForest_ocean",
"IcePlains_deep_ocean",
"Jungle_deep_ocean",
"Savanna_ocean",
"MesaPlateauF_ocean",
"ExtremeHillsM_deep_ocean",
"Savanna_deep_ocean",
"SunflowerPlains_ocean",
"Swampland_deep_ocean",
"Swampland_ocean",
"MegaSpruceTaiga_deep_ocean",
"ExtremeHillsM_ocean",
"JungleEdgeM_deep_ocean",
"SunflowerPlains_deep_ocean",
"BirchForest_deep_ocean",
"IcePlainsSpikes_ocean",
"Mesa_ocean",
"StoneBeach_ocean",
"Plains_deep_ocean",
"JungleEdge_deep_ocean",
"SavannaM_deep_ocean",
"Desert_deep_ocean",
"Mesa_deep_ocean",
"ColdTaiga_deep_ocean",
"Plains_ocean",
"MesaPlateauFM_ocean",
"Forest_deep_ocean",
"JungleM_deep_ocean",
"FlowerForest_deep_ocean",
"MushroomIsland_ocean",
"MegaTaiga_ocean",
"StoneBeach_deep_ocean",
"IcePlainsSpikes_deep_ocean",
"ColdTaiga_ocean",
"SavannaM_ocean",
"MesaPlateauF_deep_ocean",
"MesaBryce_deep_ocean",
"ExtremeHills+_deep_ocean",
"ExtremeHills_ocean",
"MushroomIsland_deep_ocean",
"Forest_ocean",
"MegaTaiga_deep_ocean",
"JungleEdge_ocean",
"MesaBryce_ocean",
"MegaSpruceTaiga_ocean",
"ExtremeHills+_ocean",
"Jungle_ocean",
"RoofedForest_deep_ocean",
"IcePlains_ocean",
"FlowerForest_ocean",
"ExtremeHills_deep_ocean",
"MesaPlateauFM_deep_ocean",
"Desert_ocean",
"Taiga_ocean",
"BirchForestM_deep_ocean",
"Taiga_deep_ocean",
"JungleM_ocean"
}
vl_structures.register_structure("ocean_temple",{
place_on = {"group:sand","mcl_core:gravel"},
spawn_by = {"group:water"},
num_spawn_by = 4,
noise_params = {
offset = 0,
scale = 0.0000122,
spread = {x = 250, y = 250, z = 250},
seed = 32345,
octaves = 3,
persist = 0.001,
flags = "absvalue",
},
force_place = true,
biomes = ocean_biomes,
y_max = water_level-4,
y_min = mcl_vars.mg_overworld_min,
filenames = {
modpath .. "/schematics/mcl_structures_ocean_temple.mts",
modpath .. "/schematics/mcl_structures_ocean_temple_2.mts",
},
prepare = { tolerance = -1, clear = false, foundation = false },
y_offset = function(pr) return pr:next(-2,0) end,
after_place = function(p,def,pr,p1,p2)
vl_structures.spawn_mobs("mobs_mc:guardian",spawnon,p1,p2,pr,5,true)
vl_structures.spawn_mobs("mobs_mc:guardian_elder",spawnon,p1,p2,pr,1,true)
vl_structures.construct_nodes(p1,p2,{"group:wall"})
end,
loot = {
["mcl_chests:chest_small"] = {
{
stacks_min = 3,
stacks_max = 10,
items = {
{ itemstring = "mcl_sus_stew:stew", weight = 10, amount_min = 1, amount_max = 1 },
{ itemstring = "mcl_core:paper", weight = 8, amount_min = 1, amount_max = 12 },
{ itemstring = "mcl_fishing:fish_raw", weight = 5, amount_min = 8, amount_max = 21 },
{ itemstring = "mcl_fishing:salmon_raw", weight = 7, amount_min = 4, amount_max = 8 },
{ itemstring = "mcl_tnt:tnt", weight = 1, amount_min = 1, amount_max = 2 },
}
},
{
stacks_min = 2,
stacks_max = 6,
items = {
{ itemstring = "mcl_core:iron_ingot", weight = 10, amount_min = 1, amount_max = 5 },
{ itemstring = "mcl_core:goldblock", weight = 1, amount_min = 1, amount_max = 2 },
{ itemstring = "mcl_experience:bottle", weight = 5, amount_min = 1, amount_max = 1 },
{ itemstring = "mcl_core:diamond", weight = 5, amount_min = 1, amount_max = 1 },
{ itemstring = "mcl_fishing:fishing_rod", weight = 1, amount_min = 1, amount_max = 1 },
}
},
{
stacks_min = 4,
stacks_max = 4,
items = {
--{ itemstring = "FIXME TREASURE MAP", weight = 8, amount_min = 1, amount_max = 5 },
{ itemstring = "mcl_books:book", weight = 1, amount_min = 1, amount_max = 5 },
{ itemstring = "mcl_clock:clock", weight = 1, amount_min = 1, amount_max = 1 },
{ itemstring = "mcl_compass:compass", weight = 1, amount_min = 1, amount_max = 1 },
{ itemstring = "mcl_maps:empty_map", weight = 1, amount_min = 1, amount_max = 1 },
}
},
}
}
})
vl_structures.register_structure_spawn({
name = "mobs_mc:guardian",
y_min = mcl_vars.mg_overworld_min,
y_max = mcl_vars.mg_overworld_max,
chance = 10,
interval = 60,
limit = 9,
spawnon = spawnon,
})
vl_structures.register_structure_spawn({
name = "mobs_mc:guardian_elder",
y_min = mcl_vars.mg_overworld_min,
y_max = mcl_vars.mg_overworld_max,
chance = 100,
interval = 60,
limit = 4,
spawnon = spawnon,
})

View File

@ -3,12 +3,10 @@ local modpath = minetest.get_modpath(modname)
local spawnon = {"mcl_core:stripped_oak","mcl_stairs:slab_birchwood_top"}
mcl_structures.register_structure("pillager_outpost",{
vl_structures.register_structure("pillager_outpost",{
place_on = {"group:grass_block","group:dirt","mcl_core:dirt_with_grass","group:sand"},
flags = "place_center_x, place_center_z",
solid_ground = true,
prepare = { padding = 2, corners = 4, foundation = 6, clearance = true },
sidelen = 20,
prepare = { padding = 3, corners = 4, foundation = -6, clear = true },
y_offset = 0,
chunk_probability = 15,
y_max = mcl_vars.mg_overworld_max,
@ -61,13 +59,13 @@ mcl_structures.register_structure("pillager_outpost",{
},
after_place = function(p,def,pr)
local p1, p2 = vector.offset(p,-9,0,-9), vector.offset(p,9,32,9)
mcl_structures.spawn_mobs("mobs_mc:pillager",spawnon,p1,p2,pr,5)
mcl_structures.spawn_mobs("mobs_mc:parrot",{"mesecons_pressureplates:pressure_plate_stone_off"},p1,p2,pr,3)
mcl_structures.spawn_mobs("mobs_mc:iron_golem",{"mesecons_button:button_stone_off"},p1,p2,pr,1)
vl_structures.spawn_mobs("mobs_mc:pillager",spawnon,p1,p2,pr,5)
vl_structures.spawn_mobs("mobs_mc:parrot",{"mesecons_pressureplates:pressure_plate_stone_off"},p1,p2,pr,3)
vl_structures.spawn_mobs("mobs_mc:iron_golem",{"mesecons_button:button_stone_off"},p1,p2,pr,1)
end
})
mcl_structures.register_structure_spawn({
vl_structures.register_structure_spawn({
name = "mobs_mc:pillager",
y_min = mcl_vars.mg_overworld_min,
y_max = mcl_vars.mg_overworld_max,

View File

@ -13,12 +13,10 @@ end
local def = {
place_on = {"group:grass_block","group:dirt","mcl_core:dirt_with_grass","group:grass_block","group:sand","group:grass_block_snow","mcl_core:snow"},
flags = "place_center_x, place_center_z, all_floors",
solid_ground = true,
prepare = { padding = 0, corners = 3, tolerance = 10, foundation = true, clearance = true },
prepare = { padding = 0, corners = 3, tolerance = 15, foundation = true, clear = true, clear_top = 0 },
chunk_probability = 20,
y_max = mcl_vars.mg_overworld_max,
y_min = 1,
sidelen = 12,
y_offset = -5,
filenames = {
modpath.."/schematics/mcl_structures_ruined_portal_1.mts",
@ -26,12 +24,10 @@ local def = {
modpath.."/schematics/mcl_structures_ruined_portal_3.mts",
modpath.."/schematics/mcl_structures_ruined_portal_4.mts",
modpath.."/schematics/mcl_structures_ruined_portal_5.mts",
modpath.."/schematics/mcl_structures_ruined_portal_6.mts",
modpath.."/schematics/mcl_structures_ruined_portal_99.mts",
},
after_place = function(pos,def,pr)
local hl = def.sidelen / 2
local p1 = vector.offset(pos,-hl,-hl,-hl)
local p2 = vector.offset(pos,hl,hl,hl)
after_place = function(pos,def,pr,p1,p2)
local gold = minetest.find_nodes_in_area(p1,p2,{"mcl_core:goldblock"})
local lava = minetest.find_nodes_in_area(p1,p2,{"mcl_core:lava_source"})
local rack = minetest.find_nodes_in_area(p1,p2,{"mcl_nether:netherrack"})
@ -101,9 +97,10 @@ local def = {
}}
}
}
mcl_structures.register_structure("ruined_portal_overworld",def)
vl_structures.register_structure("ruined_portal_overworld",def)
local ndef = table.copy(def)
ndef.y_min=mcl_vars.mg_lava_nether_max +10
ndef.y_max=mcl_vars.mg_nether_max - 15
ndef.place_on = {"mcl_nether:netherrack","group:soul_block","mcl_blackstone:basalt,mcl_blackstone:blackstone","mcl_crimson:crimson_nylium","mcl_crimson:warped_nylium"},
mcl_structures.register_structure("ruined_portal_nether",ndef)
vl_structures.register_structure("ruined_portal_nether",ndef)

View File

@ -1,10 +1,7 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
--local S = minetest.get_translator(modname)
local seed = minetest.get_mapgen_setting("seed")
local water_level = minetest.get_mapgen_setting("water_level")
local pr = PseudoRandom(seed)
--schematics by chmodsayshello
local schems = {
@ -94,7 +91,7 @@ local beach_biomes = {
"Jungle_shore"
}
mcl_structures.register_structure("shipwreck",{
vl_structures.register_structure("shipwreck",{
place_on = {"group:sand","mcl_core:gravel"},
spawn_by = {"group:water"},
num_spawn_by = 4,
@ -107,11 +104,11 @@ mcl_structures.register_structure("shipwreck",{
persist = 0.001,
flags = "absvalue",
},
sidelen = 16,
flags = "force_placement",
biomes = ocean_biomes,
y_max = water_level-4,
y_min = mcl_vars.mg_overworld_min,
prepare = { tolerance = -1, clear = false, foundation = false },
filenames = schems,
y_offset = function(pr) return pr:next(-4,-2) end,
loot = {
@ -174,93 +171,3 @@ mcl_structures.register_structure("shipwreck",{
}
})
local spawnon = { "mcl_stairs:slab_prismarine_dark"}
mcl_structures.register_structure("ocean_temple",{
place_on = {"group:sand","mcl_core:gravel"},
spawn_by = {"group:water"},
num_spawn_by = 4,
noise_params = {
offset = 0,
scale = 0.0000122,
spread = {x = 250, y = 250, z = 250},
seed = 32345,
octaves = 3,
persist = 0.001,
flags = "absvalue",
},
sidelen = 32,
flags = "force_placement",
biomes = ocean_biomes,
y_max = water_level-4,
y_min = mcl_vars.mg_overworld_min,
filenames = {
modpath .. "/schematics/mcl_structures_ocean_temple.mts",
modpath .. "/schematics/mcl_structures_ocean_temple_2.mts",
},
y_offset = function(pr) return pr:next(-2,0) end,
after_place = function(p,def,pr)
local p1 = vector.offset(p,-9,0,-9)
local p2 = vector.offset(p,9,32,9)
mcl_structures.spawn_mobs("mobs_mc:guardian",spawnon,p1,p2,pr,5,true)
mcl_structures.spawn_mobs("mobs_mc:guardian_elder",spawnon,p1,p2,pr,1,true)
mcl_structures.construct_nodes(p1,p2,{"group:wall"})
end,
loot = {
["mcl_chests:chest_small"] = {
{
stacks_min = 3,
stacks_max = 10,
items = {
{ itemstring = "mcl_sus_stew:stew", weight = 10, amount_min = 1, amount_max = 1 },
{ itemstring = "mcl_core:paper", weight = 8, amount_min = 1, amount_max = 12 },
{ itemstring = "mcl_fishing:fish_raw", weight = 5, amount_min = 8, amount_max = 21 },
{ itemstring = "mcl_fishing:salmon_raw", weight = 7, amount_min = 4, amount_max = 8 },
{ itemstring = "mcl_tnt:tnt", weight = 1, amount_min = 1, amount_max = 2 },
}
},
{
stacks_min = 2,
stacks_max = 6,
items = {
{ itemstring = "mcl_core:iron_ingot", weight = 10, amount_min = 1, amount_max = 5 },
{ itemstring = "mcl_core:goldblock", weight = 1, amount_min = 1, amount_max = 2 },
{ itemstring = "mcl_experience:bottle", weight = 5, amount_min = 1, amount_max = 1 },
{ itemstring = "mcl_core:diamond", weight = 5, amount_min = 1, amount_max = 1 },
{ itemstring = "mcl_fishing:fishing_rod", weight = 1, amount_min = 1, amount_max = 1 },
}
},
{
stacks_min = 4,
stacks_max = 4,
items = {
--{ itemstring = "FIXME TREASURE MAP", weight = 8, amount_min = 1, amount_max = 5 },
{ itemstring = "mcl_books:book", weight = 1, amount_min = 1, amount_max = 5 },
{ itemstring = "mcl_clock:clock", weight = 1, amount_min = 1, amount_max = 1 },
{ itemstring = "mcl_compass:compass", weight = 1, amount_min = 1, amount_max = 1 },
{ itemstring = "mcl_maps:empty_map", weight = 1, amount_min = 1, amount_max = 1 },
}
},
}
}
})
mcl_structures.register_structure_spawn({
name = "mobs_mc:guardian",
y_min = mcl_vars.mg_overworld_min,
y_max = mcl_vars.mg_overworld_max,
chance = 10,
interval = 60,
limit = 9,
spawnon = spawnon,
})
mcl_structures.register_structure_spawn({
name = "mobs_mc:guardian_elder",
y_min = mcl_vars.mg_overworld_min,
y_max = mcl_vars.mg_overworld_max,
chance = 100,
interval = 60,
limit = 4,
spawnon = spawnon,
})

View File

@ -23,14 +23,16 @@ local function spawn_witch(p1,p2)
end
end
local function hut_placement_callback(pos,def,pr)
local hl = def.sidelen / 2
local p1 = vector.offset(pos,-hl,-hl,-hl)
local p2 = vector.offset(pos,hl,hl,hl)
local legs = minetest.find_nodes_in_area(vector.offset(pos,-hl,0,-hl),vector.offset(pos,hl,0,hl), "mcl_core:tree")
local function hut_placement_callback(pos,def,pr,p1,p2)
-- p1.y is the bottom slice only, not a typo, we look for the hut legs
local legs = minetest.find_nodes_in_area(p1,vector.new(p2.x,p1.y,p2.z), "mcl_core:tree")
local tree = {}
-- TODO: port leg generation to VoxelManip?
for _,leg in pairs(legs) do
while minetest.get_item_group(mcl_vars.get_node(vector.offset(leg,0,-1,0), true, 333333).name, "water") ~= 0 do
while true do
local name = minetest.get_node(vector.offset(leg,0,-1,0)).name
if name == "ignore" then break end
if name ~= "air" and minetest.get_item_group(name, "water") == 0 then break end
leg = vector.offset(leg,0,-1,0)
table.insert(tree,leg)
end
@ -39,14 +41,16 @@ local function hut_placement_callback(pos,def,pr)
spawn_witch(p1,p2)
end
mcl_structures.register_structure("witch_hut",{
place_on = {"mcl_core:water_source","mclx_core:river_water_source"},
flags = "place_center_x, place_center_z, liquid_surface, force_placement",
sidelen = 8,
vl_structures.register_structure("witch_hut",{
place_on = {"mcl_core:water_source","group:sand","group:grass_block","group:dirt","mclx_core:river_water_source"},
spawn_by = {"mcl_core:water_source","mclx_core:river_water_source"},
check_offset = -1,
num_spawn_by = 3,
flags = "place_center_x, place_center_z, all_surfaces",
chunk_probability = 8,
prepare = { tolerance=-1, clearance="top", foundation=false },
prepare = { mode="under_air", tolerance=4, clear_bottom=3, padding=0, corners=1, foundation=false },
y_max = mcl_vars.mg_overworld_max,
y_min = -4,
y_min = -5,
y_offset = 0,
biomes = { "Swampland", "Swampland_ocean", "Swampland_shore" },
filenames = { modpath.."/schematics/mcl_structures_witch_hut.mts" },

View File

@ -5,28 +5,24 @@ local peaceful = minetest.settings:get_bool("only_peaceful_mobs", false)
local spawnon = {"mcl_deepslate:deepslate","mcl_core:birchwood","mcl_wool:red_carpet","mcl_wool:brown_carpet"}
mcl_structures.register_structure("woodland_cabin",{
vl_structures.register_structure("woodland_cabin",{
place_on = {"group:grass_block","group:dirt","mcl_core:dirt_with_grass"},
flags = "place_center_x, place_center_z",
solid_ground = true,
prepare = { padding = 2, corners = 5, foundation = true, clearance = true },
prepare = { padding = 2, corners = 5, foundation = true, clear = true, clear_top = 2 },
force_placement = false,
chunk_probability = 20,
y_max = mcl_vars.mg_overworld_max,
y_min = 1,
biomes = { "RoofedForest" },
sidelen = 32,
filenames = {
modpath.."/schematics/mcl_structures_woodland_cabin.mts",
modpath.."/schematics/mcl_structures_woodland_outpost.mts",
},
construct_nodes = {"mcl_barrels:barrel_closed","mcl_books:bookshelf"},
after_place = function(p,def,pr)
local p1=vector.offset(p,-def.sidelen,-1,-def.sidelen)
local p2=vector.offset(p,def.sidelen,def.sidelen,def.sidelen)
mcl_structures.spawn_mobs("mobs_mc:vindicator",spawnon,p1,p2,pr,5)
mcl_structures.spawn_mobs("mobs_mc:evoker",spawnon,p1,p2,pr,1)
mcl_structures.spawn_mobs("mobs_mc:parrot",{"mcl_heads:wither_skeleton"},p1,p2,pr,1)
after_place = function(p,def,pr,p1,p2)
vl_structures.spawn_mobs("mobs_mc:vindicator",spawnon,p1,p2,pr,5)
vl_structures.spawn_mobs("mobs_mc:evoker",spawnon,p1,p2,pr,1)
vl_structures.spawn_mobs("mobs_mc:parrot",{"mcl_heads:wither_skeleton"},p1,p2,pr,1)
end,
loot = {
["mcl_chests:chest_small" ] ={{
@ -69,7 +65,7 @@ mcl_structures.register_structure("woodland_cabin",{
}
})
mcl_structures.register_structure_spawn({
vl_structures.register_structure_spawn({
name = "mobs_mc:vindicator",
y_min = mcl_vars.mg_overworld_min,
y_max = mcl_vars.mg_overworld_max,
@ -79,7 +75,7 @@ mcl_structures.register_structure_spawn({
spawnon = spawnon,
})
mcl_structures.register_structure_spawn({
vl_structures.register_structure_spawn({
name = "mobs_mc:evoker",
y_min = mcl_vars.mg_overworld_min,
y_max = mcl_vars.mg_overworld_max,

View File

@ -83,57 +83,76 @@ end
local mushrooms = {"mcl_mushrooms:mushroom_brown","mcl_mushrooms:mushroom_red"}
local function get_fallen_tree_schematic(pos,pr)
local function place_tree(pos,def,pr)
local tree = minetest.find_node_near(pos,15,{"group:tree"})
if not tree then return end
tree = minetest.get_node(tree).name
local maxlen = 8
local minlen = 2
local vprob = 120
local mprob = 160
local minlen, maxlen = 3, 9
local vrate, mrate = 120, 160
local len = pr:next(minlen,maxlen)
local schem = {
size = {x = len + 2, y = 2, z = 3},
data = {
{name = "air", prob=0},
{name = "air", prob=0},
}
}
for i = 1,len do
table.insert(schem.data,{name = "mcl_core:vine",param2=4, prob=vprob})
local dir = pr:next(0,3)
local dx, dy, dz, param2, w1, w2
if dir == 0 then
dx, dy, dz, param2, w1, w2 = 1, 0, 0, 12, 5, 4
elseif dir == 1 then
dx, dy, dz, param2, w1, w2 = -1, 0, 0, 12, 4, 5
elseif dir == 2 then
dx, dy, dz, param2, w1, w2 = 0, 0, 1, 6, 3, 2
else -- if dir == 3 then
dx, dy, dz, param2, w1, w2 = 0, 0, -1, 6, 2, 3
end
table.insert(schem.data,{name = "air", prob=0})
table.insert(schem.data,{name = "air", prob=0})
-- TODO: port this to voxel manipulators
-- ensure we have room for the tree
local minsupport, maxsupport = 99, 1
for i = 1,len do
table.insert(schem.data,{name = "air", prob=0})
-- check below
local n = minetest.get_node(vector.offset(pos, dx * i, -1, dz * i)).name
local nd = minetest.registered_nodes[n]
if n ~= "air" and nd.groups and nd.groups.solid and i > 2 then
if i < minsupport then minsupport = i end
maxsupport = i
end
table.insert(schem.data,{name = tree, param2 = 0})
table.insert(schem.data,{name = "air", prob=0})
for i = 1,len do
table.insert(schem.data,{name = tree, param2 = 12})
-- check space
local n = minetest.get_node(vector.offset(pos, dx * i, 0, dz * i)).name
local nd = minetest.registered_nodes[n]
if n ~= "air" and nd.groups and not nd.groups.plant then
if i < minlen or pr:next(1,maxsupport) == 1 then return end
len = i
break
end
table.insert(schem.data,{name = "air", prob=0})
table.insert(schem.data,{name = "air", prob=0})
for i = 1,len do
table.insert(schem.data,{name = mushrooms[pr:next(1,#mushrooms)], param2 = 12, prob=mprob})
end
table.insert(schem.data,{name = "air", prob=0})
table.insert(schem.data,{name = "air", prob=0})
for i = 1,len do
table.insert(schem.data,{name = "mcl_core:vine",param2=5, prob=vprob})
if maxsupport - minsupport < minlen then return end
len = math.min(len, maxsupport - 1)
if len < minlen then return end
-- place the tree
minetest.swap_node(pos, {name = tree, param2 = 0})
for i = 2,len do
minetest.swap_node(vector.offset(pos, dx * i, 0, dz * i), {name = tree, param2 = param2})
if pr:next(0,255) < vrate then
local side = vector.offset(pos, dx * i + dz, 0, dz * i + dx)
local n = minetest.get_node(side).name
if n == "air" then
minetest.swap_node(side, {name="mcl_core:vine", param2=w1})
end
end
if pr:next(0,255) < vrate then
local side = vector.offset(pos, dx * i - dz, 0, dz * i - dx)
local n = minetest.get_node(side).name
if n == "air" then
minetest.swap_node(side, {name="mcl_core:vine", param2=w2})
end
end
if pr:next(0,255) < mrate then
local top = vector.offset(pos, dx * i, 1, dz * i)
local n = minetest.get_node(top).name
if n == "air" then
minetest.swap_node(top, {name = mushrooms[pr:next(1,#mushrooms)], param2 = 12})
end
end
table.insert(schem.data,{name = "air", prob=0})
table.insert(schem.data,{name = "air", prob=0})
for i = 1,len do
table.insert(schem.data,{name = "air", prob=0})
end
return schem
end
mcl_structures.register_structure("fallen_tree",{
vl_structures.register_structure("fallen_tree",{
priority = 1100, -- after regular trees
place_on = {"group:grass_block"},
terrain_feature = true,
@ -146,24 +165,14 @@ mcl_structures.register_structure("fallen_tree",{
persist = 0.66
},
flags = "place_center_x, place_center_z",
sidelen = 18,
sidelen = 10,
solid_ground = true,
y_max = mcl_vars.mg_overworld_max,
y_min = minetest.get_mapgen_setting("water_level"),
on_place = function(pos,def,pr)
local air_p1 = vector.offset(pos,-def.sidelen/2,1,-def.sidelen/2)
local air_p2 = vector.offset(air_p1,def.sidelen-1,0,def.sidelen-1)
local air = minetest.find_nodes_in_area(air_p1,air_p2,{"air"})
return #air >= (def.sidelen * def.sidelen) / 2
end,
place_func = function(pos,def,pr)
local schem=get_fallen_tree_schematic(pos,pr)
if not schem then return end
return minetest.place_schematic(pos,schem,"random")
end
place_func = place_tree
})
mcl_structures.register_structure("lavapool",{
vl_structures.register_structure("lavapool",{
place_on = {"group:sand", "group:dirt", "group:stone"},
terrain_feature = true,
noise_params = {
@ -183,7 +192,7 @@ mcl_structures.register_structure("lavapool",{
end
})
mcl_structures.register_structure("water_lake",{
vl_structures.register_structure("water_lake",{
place_on = {"group:dirt","group:stone"},
terrain_feature = true,
noise_params = {
@ -203,7 +212,7 @@ mcl_structures.register_structure("water_lake",{
end
})
mcl_structures.register_structure("water_lake_mangrove_swamp",{
vl_structures.register_structure("water_lake_mangrove_swamp",{
place_on = {"mcl_mud:mud"},
biomes = { "MangroveSwamp" },
terrain_feature = true,
@ -224,7 +233,7 @@ mcl_structures.register_structure("water_lake_mangrove_swamp",{
end
})
mcl_structures.register_structure("basalt_column",{
vl_structures.register_structure("basalt_column",{
place_on = {"mcl_blackstone:blackstone","mcl_blackstone:basalt"},
terrain_feature = true,
spawn_by = {"air"},
@ -268,7 +277,7 @@ mcl_structures.register_structure("basalt_column",{
return true
end
})
mcl_structures.register_structure("basalt_pillar",{
vl_structures.register_structure("basalt_pillar",{
place_on = {"mcl_blackstone:blackstone","mcl_blackstone:basalt"},
terrain_feature = true,
noise_params = {
@ -310,7 +319,7 @@ mcl_structures.register_structure("basalt_pillar",{
end
})
mcl_structures.register_structure("lavadelta",{
vl_structures.register_structure("lavadelta",{
place_on = {"mcl_blackstone:blackstone","mcl_blackstone:basalt"},
spawn_by = {"mcl_blackstone:basalt","mcl_blackstone:blackstone"},
num_spawn_by = 2,

View File

@ -15,45 +15,22 @@ function mcl_villages.initialize_settlement_info(pr)
num_jobs = 0,
num_beds = 0,
}
for k, v in pairs(mcl_villages.schematic_houses) do
count_buildings[v["name"]] = 0
end
for k, v in pairs(mcl_villages.schematic_jobs) do
count_buildings[v["name"]] = 0
end
return count_buildings
end
-------------------------------------------------------------------------------
-- evaluate settlement_info and place schematics
-------------------------------------------------------------------------------
-- Initialize node
local function construct_node(p1, p2, name)
local r = minetest.registered_nodes[name]
if not r or not r.on_construct then
minetest.log("warning", "[mcl_villages] No on_construct defined for node name " .. name)
end
local nodes = minetest.find_nodes_in_area(p1, p2, name)
for p=1, #nodes do
r.on_construct(nodes[p])
end
return nodes
end
local function spawn_cats(pos)
local sp=minetest.find_nodes_in_area_under_air(vector.offset(pos,-20,-20,-20),vector.offset(pos,20,20,20),{"group:opaque"})
for i=1,math.random(3) do
local v = minetest.add_entity(vector.offset(sp[math.random(#sp)],0,1,0),"mobs_mc:cat"):get_luaentity()
if v then
v._home = pos
end
for _ = 1, math.random(3) do
local v = minetest.add_entity(vector.offset(sp[math.random(#sp)],0,1,0),"mobs_mc:cat")
if v and v:get_luaentity() then v:get_luaentity()._home = pos end
end
end
local function init_nodes(p1, p2, pr)
mcl_structures.construct_nodes(p1, p2, {
vl_structures.construct_nodes(p1, p2, {
"mcl_itemframes:item_frame",
"mcl_itemframes:glow_item_frame",
"mcl_furnaces:furnace",
@ -69,55 +46,26 @@ local function init_nodes(p1, p2, pr)
-- Support mods with custom job sites
local job_sites = minetest.find_nodes_in_area(p1, p2, mobs_mc.jobsites)
for _, v in pairs(job_sites) do
mcl_structures.init_node_construct(v)
vl_structures.init_node_construct(v)
end
-- Do new chest nodes first
local nodes = construct_node(p1, p2, "mcl_chests:chest_small")
if nodes and #nodes > 0 then
local nodes = vl_structures.construct_nodes(p1, p2, {"mcl_chests:chest_small", "mcl_chests:chest" }) or {}
for p=1, #nodes do
mcl_villages.fill_chest(nodes[p], pr)
end
end
-- Do old chest nodes after
local nodes = construct_node(p1, p2, "mcl_chests:chest")
if nodes and #nodes > 0 then
for p=1, #nodes do
mcl_villages.fill_chest(nodes[p], pr)
end
end
end
-- check ground for a single building, adjust position
local function check_ground(lvm, cpos, size)
local cpos, surface_material = mcl_villages.find_surface(lvm, cpos)
if not cpos then return nil, nil end
local pos = vector.offset(cpos, -math.floor((size.x-1)/2), 0, -math.floor((size.z-1)/2))
local ys = {pos.y}
local pos_c = mcl_villages.find_surface_down(lvm, vector.offset(pos, 0, size.y, 0))
if pos_c then table.insert(ys, pos_c.y) end
local pos_c = mcl_villages.find_surface_down(lvm, vector.offset(pos, size.x-1, size.y, 0))
if pos_c then table.insert(ys, pos_c.y) end
local pos_c = mcl_villages.find_surface_down(lvm, vector.offset(pos, 0, size.y, size.z-1))
if pos_c then table.insert(ys, pos_c.y) end
local pos_c = mcl_villages.find_surface_down(lvm, vector.offset(pos, size.x-1, size.y, size.z-1))
if pos_c then table.insert(ys, pos_c.y) end
table.sort(ys)
-- well supported base, not too uneven?
if #ys < 5 or ys[#ys]-ys[1] > 8 then return nil, nil end
cpos.y = math.floor(0.5 * (ys[math.floor(#ys/2)] + ys[math.ceil(#ys/2)]) + 0.5) -- median, rounded
return cpos, surface_material
end
local function add_building(settlement, building, count_buildings)
table.insert(settlement, building)
count_buildings[building["name"]] = count_buildings[building["name"]] + 1
count_buildings.num_jobs = count_buildings.num_jobs + (building["num_jobs"] or 0)
count_buildings.num_beds = count_buildings.num_beds + (building["num_beds"] or 0)
count_buildings[building.name] = (count_buildings[building.name] or 0) + 1
count_buildings.num_jobs = count_buildings.num_jobs + (building.num_jobs or 0)
count_buildings.num_beds = count_buildings.num_beds + (building.num_beds or 0)
if building.group then
count_buildings[building.group] = (count_buildings[building.group] or 0) + 1
end
end
local function layout_town(lvm, minp, maxp, pr, input_settlement)
local function layout_town(vm, minp, maxp, pr, input_settlement)
local center = vector.new(pr:next(minp.x + 24, maxp.x - 24), maxp.y, pr:next(minp.z + 24, maxp.z - 24))
minetest.log("action", "[mcl_villages] sudo make me a village at: " .. minetest.pos_to_string(minp).." - "..minetest.pos_to_string(maxp))
local possible_rotations = {"0", "90", "180", "270"}
@ -126,8 +74,8 @@ local function layout_town(lvm, minp, maxp, pr, input_settlement)
local settlement = {}
-- now some buildings around in a circle, radius = size of town center
local x, y, z, r, lastr = center.x, center.y, center.z, 0, 99
local mindist = 4
if #input_settlement >= 12 then mindist = 3 end
local mindist = 3
if #input_settlement >= 12 then mindist = 2 end
-- draw j circles around center and increase radius by math.random(2,4)
for j = 1,20 do
local steps = math.min(math.floor(math.pi * 2 * r / 2), 30) -- try up to 30 angles
@ -146,9 +94,9 @@ local function layout_town(lvm, minp, maxp, pr, input_settlement)
-- ensure we have 3 space for terraforming, and avoid problems with VoxelManip
if tlpos.x - 3 >= minp.x and tlpos.x + size.x + 3 <= maxp.x
and tlpos.z + 3 >= minp.z and tlpos.z + size.y + 3 <= maxp.z then
local pos, surface_material = check_ground(lvm, cpos, size)
local pos, surface_material = vl_terraforming.find_level_vm(vm, cpos, size)
-- check distance to other buildings. Note that we still want to add baseplates etc.
if pos and mcl_villages.check_distance(settlement, cpos, size.x, size.z, mindist) then
if pos and mcl_villages.surface_mat[surface_material.name] and mcl_villages.check_distance(settlement, cpos, size.x, size.z, mindist) then
-- use town bell as new reference point for placement height
if #settlement == 0 then
center_surface, y = cpos, math.min(maxp.y, pos.y + mcl_villages.max_height_difference * 0.5 + 1)
@ -186,7 +134,7 @@ local function layout_town(lvm, minp, maxp, pr, input_settlement)
return settlement
end
function mcl_villages.create_site_plan(lvm, minp, maxp, pr)
function mcl_villages.create_site_plan(vm, minp, maxp, pr)
local settlement = {}
-- initialize all settlement_info table
@ -208,12 +156,12 @@ function mcl_villages.create_site_plan(lvm, minp, maxp, pr)
local building_info = mcl_villages.schematic_jobs[rindex]
if
(building_info["min_jobs"] == nil or count_buildings.number_of_jobs >= building_info["min_jobs"])
and (building_info["max_jobs"] == nil or count_buildings.number_of_jobs <= building_info["max_jobs"])
(building_info.min_jobs == nil or count_buildings.number_of_jobs >= building_info.min_jobs)
and (building_info.max_jobs == nil or count_buildings.number_of_jobs <= building_info.max_jobs)
and (
building_info["num_others"] == nil
or count_buildings[building_info["name"]] == 0
or building_info["num_others"] * count_buildings[building_info["name"]] < count_buildings.num_jobs
building_info.num_others == nil
or (count_buildings[building_info.group or building_info.name] or 0) == 0
or building_info.num_others * (count_buildings[building_info.group or building_info.name] or 0) < count_buildings.num_jobs
)
then
add_building(settlement, building_info, count_buildings)
@ -232,12 +180,12 @@ function mcl_villages.create_site_plan(lvm, minp, maxp, pr)
local building_info = mcl_villages.schematic_houses[rindex]
if
(building_info["min_jobs"] == nil or count_buildings.number_of_jobs >= building_info["min_jobs"])
and (building_info["max_jobs"] == nil or count_buildings.number_of_jobs <= building_info["max_jobs"])
(building_info.min_jobs == nil or count_buildings.number_of_jobs >= building_info.min_jobs)
and (building_info.max_jobs == nil or count_buildings.number_of_jobs <= building_info.max_jobs)
and (
building_info["num_others"] == nil
or count_buildings[building_info["name"]] == 0
or building_info["num_others"] * count_buildings[building_info["name"]] < count_buildings.num_jobs
building_info.num_others == nil
or (count_buildings[building_info.group or building_info.name] or 0) == 0
or building_info.num_others * (count_buildings[building_info.group or building_info.name] or 0) < count_buildings.num_jobs
)
then
add_building(settlement, building_info, count_buildings)
@ -246,7 +194,7 @@ function mcl_villages.create_site_plan(lvm, minp, maxp, pr)
-- Based on number of villagers
local num_wells = pr:next(1, math.ceil(count_buildings.num_beds / 10))
for i = 1, num_wells do
for _ = 1, num_wells do
local windex = pr:next(1, #mcl_villages.schematic_wells)
local cur_schem = table.copy(mcl_villages.schematic_wells[windex])
table.insert(settlement, pr:next(1, #settlement), cur_schem)
@ -261,12 +209,12 @@ function mcl_villages.create_site_plan(lvm, minp, maxp, pr)
end
table.insert(settlement, 1, bell_info)
return layout_town(lvm, minp, maxp, pr, settlement)
return layout_town(vm, minp, maxp, pr, settlement)
end
function mcl_villages.place_schematics(lvm, settlement, blockseed, pr)
-- local lvm = VoxelManip()
function mcl_villages.place_schematics(vm, settlement, blockseed, pr)
-- local vm = VoxelManip()
local bell_pos = vector.offset(settlement[1].minp, math.floor(settlement[1].size.x/2), 0, math.floor(settlement[1].size.z/2))
local bell_center_pos
local bell_center_node_type
@ -284,12 +232,12 @@ function mcl_villages.place_schematics(lvm, settlement, blockseed, pr)
local schematic = loadstring(schem_lua)()
-- the foundation and air space for the building was already built before
-- lvm:read_from_map(vector.new(minp.x, minp.y, minp.z), vector.new(maxp.x, maxp.y, maxp.z))
-- lvm:get_data()
-- vm:read_from_map(vector.new(minp.x, minp.y, minp.z), vector.new(maxp.x, maxp.y, maxp.z))
-- vm:get_data()
-- now added in placement code already, pos has the primary height if (building.yadjust or 0) ~= 0 then minp = vector.offset(minp, 0, building.yadjust, 0) end
-- minetest.log("debug", "placing schematics for "..building.name.." at "..minetest.pos_to_string(minp).." on "..surface_material)
minetest.place_schematic_on_vmanip(
lvm,
vm,
minp,
schematic,
rotation,
@ -305,27 +253,27 @@ function mcl_villages.place_schematics(lvm, settlement, blockseed, pr)
p.x = x
for y = minp.y,maxp.y-1 do
p.y = y
local n = lvm:get_node_at(p)
local n = vm:get_node_at(p)
if n and n.name == "mcl_villages:no_paths" then
p.y = y+1
n = lvm:get_node_at(p)
n = vm:get_node_at(p)
if n and n.name == "air" then
lvm:set_node_at(p, {name="mcl_villages:no_paths"})
vm:set_node_at(p, {name="mcl_villages:no_paths"})
end
end
end
end
end
mcl_villages.store_path_ends(lvm, minp, maxp, cpos, blockseed, bell_pos)
mcl_villages.store_path_ends(vm, minp, maxp, cpos, blockseed, bell_pos)
if building.name == "belltower" then -- TODO: allow multiple types?
bell_center_pos = cpos
local center_node = lvm:get_node_at(cpos)
local center_node = vm:get_node_at(cpos)
bell_center_node_type = center_node.name
end
end
lvm:write_to_map(true) -- for path finder and light
vm:write_to_map(true) -- for path finder and light
local biome_data = minetest.get_biome_data(bell_pos)
local biome_name = minetest.get_biome_name(biome_data.biome)
@ -344,8 +292,8 @@ function mcl_villages.place_schematics(lvm, settlement, blockseed, pr)
end
-- read back any changes
local emin, emax = lvm:get_emerged_area()
lvm:read_from_map(emin, emax)
local emin, emax = vm:get_emerged_area()
vm:read_from_map(emin, emax)
end
function mcl_villages.post_process_village(blockseed)
@ -371,6 +319,7 @@ function mcl_villages.post_process_village(blockseed)
else
minetest.log("info", "Could not create a golem!")
end
spawn_cats(bell)
for _, building in pairs(settlement_info) do
@ -392,7 +341,7 @@ function mcl_villages.post_process_village(blockseed)
for _, bed_pos in pairs(bld_beds) do
local bed_node = minetest.get_node(bed_pos)
local bed_group = core.get_item_group(bed_node.name, "bed")
local bed_group = minetest.get_item_group(bed_node.name, "bed")
-- We only spawn at bed bottoms
-- 1 is bottom, 2 is top
@ -413,7 +362,7 @@ function mcl_villages.post_process_village(blockseed)
local m = minetest.get_meta(bed_pos)
m:set_string("bell_pos", minetest.pos_to_string(bell_pos))
if m:get_string("villager") == "" then
local v = minetest.add_entity(bed_pos, "mobs_mc:villager")
local v = minetest.add_entity(vector.offset(bed_pos, 0, 0.06, 0), "mobs_mc:villager")
if v then
local l = v:get_luaentity()
l._bed = bed_pos
@ -440,14 +389,14 @@ function mcl_villages.post_process_village(blockseed)
end
-- Terraform for an entire village
function mcl_villages.terraform(lvm, settlement, pr)
-- TODO: further optimize by using raw data arrays instead of set_node_at. But OK for a first draft.
-- we make the foundations 1 node wider than requested, to have one node for path laying
function mcl_villages.terraform(vm, settlement, pr)
-- TODO: sort top-down, then bottom-up, or opposite?
-- we make the foundations 2 node wider than necessary, to have one node for path laying
for i, building in ipairs(settlement) do
if not building.no_clearance then
local pos, size = building.pos, building.size
pos = vector.offset(pos, -math.floor(size.x/2), 0, -math.floor(size.z/2))
mcl_structures.clearance(lvm, pos.x-1, pos.y, pos.z-1, size.x+2, size.y, size.z+2, 2, building.surface_mat, pr)
vl_terraforming.clearance_vm(vm, pos.x-1, pos.y, pos.z-1, size.x+2, size.y, size.z+2, 2, building.surface_mat, building.dust_mat, pr)
end
end
for i, building in ipairs(settlement) do
@ -456,10 +405,11 @@ function mcl_villages.terraform(lvm, settlement, pr)
local surface_mat = building.surface_mat
local platform_mat = building.platform_mat or { name = foundation_materials[surface_mat.name] or "mcl_core:dirt" }
local stone_mat = building.stone_mat or { name = "mcl_core:stone" }
local dust_mat = building.dust_mat
building.platform_mat = platform_mat -- remember for use in schematic placement
building.stone_mat = stone_mat
pos = vector.offset(pos, -math.floor((size.x-1)/2), 0, -math.floor((size.z-1)/2))
mcl_structures.foundation(lvm, pos.x-2, pos.y, pos.z-2, size.x+4, -4, size.z+4, 2, surface_mat, platform_mat, stone_mat, pr)
vl_terraforming.foundation_vm(vm, pos.x-2, pos.y, pos.z-2, size.x+4, -5, size.z+4, 2, surface_mat, platform_mat, stone_mat, dust_mat, pr)
end
end
end

View File

@ -1,5 +1,5 @@
name = mcl_villages
author = Rochambeau, kno10
description = This mod adds settlements on world generation.
depends = mcl_core, mcl_util, mcl_mapgen_core, mcl_structures, mcl_loot, mobs_mc
depends = mcl_core, mcl_util, mcl_mapgen_core, mcl_structures, mcl_loot, mobs_mc, vl_terraforming
optional_depends = mcl_farming

View File

@ -1,79 +1,3 @@
local function is_above_surface(name)
if name == "air" or name == "mcl_bamboo:bamboo" or name == "mcl_core:vine" or name == "mcl_core:snow" then
return true
end
local meta = core.registered_items[name]
local groups = meta and meta.groups
return groups and (groups["deco_block"] or groups["tree"] or groups["leaves"] or groups["plant"])
end
function mcl_villages.find_surface_down(lvm, pos, surface_node)
local p6 = vector.new(pos)
surface_node = surface_node or lvm:get_node_at(p6)
if not surface_node then return end
local has_air = is_above_surface(surface_node.name)
for y = p6.y - 1, math.max(0, p6.y - 80), -1 do
p6.y = y
local top_node = surface_node
surface_node = lvm:get_node_at(p6)
if not surface_node or surface_node.name == "ignore" then return nil end
if is_above_surface(surface_node.name) then
has_air = true
else
if has_air then
if mcl_villages.surface_mat[surface_node.name] then
--minetest.log("Found "..surface_node.name.." below "..top_node.name)
return p6, surface_node
else
local ndef = minetest.registered_nodes[surface_node.name]
if ndef and ndef.walkable then
--minetest.log("Found non-suitable "..surface_node.name.." below "..top_node.name)
return nil
end
end
end
has_air = false
end
end
end
function mcl_villages.find_surface_up(lvm, pos, surface_node)
local p6 = vector.new(pos)
surface_node = surface_node or lvm:get_node_at(p6) --, true, 1000000)
if not surface_node then return end
for y = p6.y + 1, p6.y + 80 do
p6.y = y
local top_node = lvm:get_node_at(p6)
if not top_node or top_node.name == "ignore" then return nil end
if is_above_surface(top_node.name) then
if mcl_villages.surface_mat[surface_node.name] then
-- minetest.log("Found "..surface_node.name.." below "..top_node.name)
p6.y = p6.y - 1
return p6, surface_node
else
local ndef = minetest.registered_nodes[surface_node.name]
if ndef and ndef.walkable then
-- minetest.log("Found non-suitable "..surface_node.name.." below "..top_node.name)
return nil
end
end
end
surface_node = top_node
end
end
-------------------------------------------------------------------------------
-- function to find surface block y coordinate
-- returns surface postion
-------------------------------------------------------------------------------
function mcl_villages.find_surface(lvm, pos)
local p6 = vector.new(pos)
if p6.y < 0 then p6.y = 0 end -- start at water level
local surface_node = lvm:get_node_at(p6)
-- downward, if starting position is empty
if is_above_surface(surface_node.name) then
return mcl_villages.find_surface_down(lvm, p6, surface_node)
else
return mcl_villages.find_surface_up(lvm, p6, surface_node)
end
end
-- check the minimum distance of two squares, on axes
function mcl_villages.check_distance(settlement, cpos, sizex, sizez, limit)
for i, building in ipairs(settlement) do

View File

@ -1,2 +1,2 @@
name = MAPGEN
description = Meta-modpack containing map generating mods for MineClone 2
description = Meta-modpack containing map generating mods for VoxeLibre

View File

@ -1096,11 +1096,10 @@ local function create_corridor_system(main_cave_coords)
return true
end
mcl_structures.register_structure("mineshaft",{
vl_structures.register_structure("mineshaft",{
place_on = {"group:sand","group:grass_block","mcl_core:water_source","group:dirt","mcl_core:dirt_with_grass","mcl_core:gravel","group:material_stone","mcl_core:snow"},
chunk_probability = 4,
flags = "place_center_x, place_center_z, force_placement, all_floors",
sidelen = 32,
y_max = 40,
y_min = mcl_vars.mg_overworld_min,
place_func = function(pos,_,pr,blockseed)

View File

@ -0,0 +1,37 @@
# vl_structures
Updated API for structure spawning for VoxeLibre and Mineclonia
## vl_structures.register_structure(name,structure definition,nospawn)
If nospawn is truthy the structure will not be placed by mapgen and the decoration parameters can be omitted. This is intended for secondary structures the placement of which gets triggered by the placement of other structures. It can also be used to register testing structures so they can be used with /spawnstruct.
### structure definition
{
fill_ratio = OR noise = {},
biomes = {},
y_min =,
y_max =,
place_on = {},
spawn_by = {},
num_spawn_by =,
flags = (default: "place_center_x, place_center_z, force_placement")
(same as decoration def)
y_offset =, --can be a number or a function returning a number
filenames = {} OR place_func = function(pos,def,pr)
-- filenames can be a list of any schematics accepted by mcl_structures.place_schematic / minetest.place_schematic
on_place = function(pos,def,pr) end,
-- called before placement. denies placement when returning falsy.
after_place = function(pos,def,pr)
-- executed after successful placement
prepare = table, -- a foundation is automatically built for the structure
loot = ,
--a table of loot tables for mcl_loot indexed by node names
-- e.g. { ["mcl_chests:chest_small"] = {loot},... }
}
## vl_structures.registered_structures
Table of the registered structure defintions indexed by name.
## vl_structures.place_structure(pos, def, pr)
Places a structure using the mapgen placement function
## vl_structures.place_schematic(pos, schematic, rotation, replacements, force_placement, flags, after_placement_callback, pr, callback_param)

View File

@ -0,0 +1,518 @@
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?
local logging = true or minetest.settings:get_bool("mcl_logging_structures", true)
-- FIXME: switch to vl_structures_disabled?
local disabled_structures = minetest.settings:get("mcl_disabled_structures")
disabled_structures = disabled_structures and disabled_structures:split(",") or {}
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, "/")
return #fn > 1 and (fn[#fn-1].."/"..fn[#fn]) or fn[#fn]
end
--- Load a schematic file
-- @param filename string: file name
-- @param name string: for logging, optional
-- @return loaded schematic
function vl_structures.load_schematic(filename, name)
-- load, and ensure we have size information
if filename == nil then error("Filename is nil for schematic "..tostring(name)) end
if type(filename) == "string" then minetest.log("action", "Loading "..filename) end
local s = loadstring(minetest.serialize_schematic(filename, "lua", {lua_use_comments = false, lua_num_indent_spaces = 0}) .. " return schematic")()
if not s then
minetest.log("warning", "[vl_structures] failed to load schematic "..basename(filename))
return nil
elseif not s.size then
minetest.log("warning", "[vl_structures] no size information for schematic "..basename(filename))
return nil
end
if logging then minetest.log("warning", "[vl_structures] loaded schematic "..basename(filename).." size "..minetest.pos_to_string(s.size)) end
if not s.name then s.name = name or basename(filename) end
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?
function vl_structures.place_structure(pos, def, pr, blockseed, rot)
if 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
if #def.filenames == 0 then minetest.log("warning","[vl_structures] schematic "..def.name.." has an empty list of filenames.") end
def.schematics = {}
for _, filename in ipairs(def.filenames) do
local s = vl_structures.load_schematic(filename, def.name)
if s then table.insert(def.schematics, s) end
end
if def.daughters then
for _,d in pairs(def.daughters) do
d.schematics = {}
for _, filename in ipairs(d.filenames) do
local s = vl_structures.load_schematic(filename, d.name)
if s then table.insert(d.schematics, s) end
end
end
end
end
-- Apply vertical offset for schematic
local yoffset = (type(def.y_offset) == "function" and def.y_offset(pr)) or def.y_offset or 0
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)
if log_enabled then
minetest.log("verbose", "[vl_structures] "..def.name.." to be placed at "..minetest.pos_to_string(pos))
end
return true
end
-- structure has a custom place function
if not def.place_func then
minetest.log("warning","[vl_structures] no schematics and no place_func for schematic "..def.name)
return false
end
local pp = yoffset ~= 0 and vector_offset(pos, 0, yoffset, 0) or pos
if def.place_func and def.prepare then
minetest.log("warning", "[vl_structures] needed prepare for "..def.name.." placed at "..minetest.pos_to_string(pp).." but do not have size information")
end
if def.place_func and def.place_func(pp,def,pr,blockseed) then
if not def.after_place or (def.after_place and def.after_place(pos,def,pr,pmin,pmax,size,param.rotation)) then
if def.sidelen then
local p1, p2 = vector_offset(pos,-def.sidelen,-def.sidelen,-def.sidelen), vector.offset(pos,def.sidelen,def.sidelen,def.sidelen)
if def.loot then vl_structures.fill_chests(p1,p2,def.loot,pr) end
if def.construct_nodes then vl_structures.construct_nodes(p1,p2,def.construct_nodes) end
end
if log_enabled then
minetest.log("action","[vl_structures] "..def.name.." placed at "..minetest.pos_to_string(pp))
end
return true
else
minetest.log("warning","[vl_structures] after_place failed for schematic "..def.name)
return false
end
elseif log_enabled then
minetest.log("warning","[vl_structures] place_func failed for schematic "..def.name)
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)
if vl_structures.is_disabled(name) then return end
def.name = name
vl_structures.registered_structures[name] = def
if def.prepare and def.prepare.clear == nil and (def.prepare.clear_bottom or def.prepare.clear_top) then def.prepare.clear = true end
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
minetest.log("warning","[vl_structures] schematic "..(name or "unknown").." is missing file "..basename(filename))
return nil
end
end
end
if def.place_on then
minetest.register_on_mods_loaded(function()
def.deco = mcl_mapgen_core.register_decoration({
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" } } },
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",
biomes = def.biomes,
y_max = def.y_max,
y_min = def.y_min
}, function()
def.deco_id = minetest.get_decoration_id("vl_structures:deco_"..name)
minetest.set_gen_notify({decoration=true}, { def.deco_id })
end)
end)
end
end
-- To avoid a cyclic dependency, run this when modules have finished loading
minetest.register_on_mods_loaded(function()
mcl_mapgen_core.register_generator("structures", nil, function(minp, maxp, blockseed)
local gennotify = minetest.get_mapgen_object("gennotify")
for _,struct in pairs(vl_structures.registered_structures) do
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
end
end
elseif struct.static_pos then
local pr
for _, pos in pairs(struct.static_pos) do
if vector.in_area(pos, minp, maxp) then
pr = pr or PcgRandom(worldseed + RANDOM_SEED_OFFSET)
vl_structures.place_structure(pos, struct, pr, blockseed)
end
end
end
end
return false, false, false
end, 100, true)
end)
local structure_spawns = {}
function vl_structures.register_structure_spawn(def)
--name,y_min,y_max,spawnon,biomes,chance,interval,limit
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)
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
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

View File

@ -0,0 +1,49 @@
local modname = minetest.get_current_modname()
local S = minetest.get_translator(modname)
local modpath = minetest.get_modpath(modname)
vl_structures = {}
dofile(modpath.."/util.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 <type>”."))
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 <type>”."))
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)

View File

@ -0,0 +1,4 @@
name = vl_structures
author = kno10
description = Structures API for VoxeLibre and Mineclonia
depends = mcl_init, mcl_util, mcl_loot, vl_terraforming

View File

@ -0,0 +1,132 @@
local peaceful = minetest.settings:get_bool("only_peaceful_mobs", false)
local floor = math.floor
local vector_offset = vector.offset
local ROTATIONS = { "0", "90", "180", "270" }
--- Parse a rotation value
-- @param rotation string: when "random", a rotation is chosen at random
-- @param[opt] pr PseudoRandom: random generator
-- @return Rotation
function vl_structures.parse_rotation(rotation, pr)
if rotation == "random" and pr then return ROTATIONS[pr:next(1,#ROTATIONS)] end
return rotation
end
--- Get the size after rotation.
-- @param size vector: Size information
-- @param rotation string or number: only 0, 90, 180, 270 are allowed
-- @return vector: new vector, for safety
function vl_structures.size_rotated(size, rotation)
if rotation == "90" or rotation == "270" or rotation == 90 or rotation == 270 then return vector.new(size.z, size.y, size.x) end
return vector.copy(size)
end
--- Get top left position after apply centering flags and padding.
-- @param pos vector: Placement position
-- @param[opt] size vector: Size information
-- @param[opt] flags string or table: as in minetest.place_schematic, place_center_x, place_center_y; default none
-- @return vector: new vector, for safety
function vl_structures.top_left_from_flags(pos, size, flags)
local dx, dy, dz = 0, 0, 0
-- must match src/mapgen/mg_schematic.cpp to be consistent
if type(flags) == "table" then
if flags["place_center_x"] ~= nil then dx = -floor((size.x-1)*0.5) end
if flags["place_center_y"] ~= nil then dy = -floor((size.y-1)*0.5) end
if flags["place_center_z"] ~= nil then dz = -floor((size.z-1)*0.5) end
return vector_offset(pos, dx, dy, dz)
elseif type(flags) == "string" then
if string.find(flags, "place_center_x") then dx = -floor((size.x-1)*0.5) end
if string.find(flags, "place_center_y") then dy = -floor((size.y-1)*0.5) end
if string.find(flags, "place_center_z") then dz = -floor((size.z-1)*0.5) end
return vector_offset(pos, dx, dy, dz)
end
return pos
end
--- Get the extends of a schematic after rotation and flags
-- @param pos vector: position of base
-- @param size vector: size of structure
-- @param[opt] yoffset number: vertical offset
-- @param[opt] rotation string: rotation value
-- @param[opt] flags string or table: as in minetest.place_schematic, place_center_x, place_center_y; default none
-- @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 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)
return cent, pmin, pmax, size
end
--- Call all on_construct handlers. Also called from mcl_villages for job sites
-- @param pos Node position
function vl_structures.init_node_construct(pos)
local node = minetest.get_node(pos)
local def = node and minetest.registered_nodes[node.name]
if def and def.on_construct then return def.on_construct(pos) end
end
--- Call on_construct handlers for all nodes of given types
-- @param p1 vector: Lowest coordinates of range
-- @param p2 vector: Highest coordinates of range
-- @param nodes string or table: node name or list of node names
-- @return nodes found
function vl_structures.construct_nodes(p1,p2,nodes)
local nn = minetest.find_nodes_in_area(p1,p2,nodes)
for _,p in pairs(nn) do vl_structures.init_node_construct(p) end
return nn or {}
end
--- Fill loot chests
-- @param p1 vector: Lowest coordinates of range
-- @param p2 vector: Highest coordinates of range
-- @param loot table: Loot table
-- @param pr PseudoRandom: random generator
function vl_structures.fill_chests(p1,p2,loot,pr)
for it,lt in pairs(loot) do
local nodes = minetest.find_nodes_in_area(p1, p2, it)
for _,p in pairs(nodes) do
local lootitems = mcl_loot.get_multi_loot(lt, pr)
vl_structures.init_node_construct(p)
local meta = minetest.get_meta(p)
local inv = meta:get_inventory()
mcl_loot.fill_inventory(inv, "main", lootitems, pr)
end
end
end
--- Spawn mobs for a structure
-- @param mob string: mob to spawn
-- @param spawnon string or table: nodes to spawn on
-- @param p1 vector: Lowest coordinates of range
-- @param p2 vector: Highest coordinates of range
-- @param pr PseudoRandom: random generator
-- @param n number: Number of mobs to spawn
-- @param water boolean: Spawn water mobs
function vl_structures.spawn_mobs(mob,spawnon,p1,p2,pr,n,water)
n = n or 1
local sp = {}
if water then
local nn = minetest.find_nodes_in_area(p1,p2,spawnon)
for k,v in pairs(nn) do
if minetest.get_item_group(minetest.get_node(vector_offset(v,0,1,0)).name,"water") > 0 then
table.insert(sp,v)
end
end
else
sp = minetest.find_nodes_in_area_under_air(p1,p2,spawnon)
end
table.shuffle(sp)
local count = 0
local mob_def = minetest.registered_entities[mob]
local enabled = (not peaceful) or (mob_def and mob_spawn_class ~= "hostile")
for _, node in pairs(sp) do
if enabled and count < n and minetest.add_entity(vector_offset(node, 0, 1, 0), mob) then
count = count + 1
end
minetest.get_meta(node):set_string("spawnblock", "yes") -- note: also in peaceful mode!
end
end

View File

@ -0,0 +1,99 @@
# vl_terraforming
Terraforming module for VoxeLibre and MineClonia
This module provides the following key functionalities:
- given a position, find the ground surface
- given a position and size, find a balanced height (trimmed median height)
- build a baseplate for a building
- clear the area above a building
## Rounded corners support
To get nicer looking baseplates, the code supports rounded corners.
These are obtained by intersecting the square with an ellipse.
At zero rounding, we want the line go through the corner, at sx/2, sz/2.
For this, we need to make ellipse sized $2a=\sqrt{2} sx$, $2b=\sqrt{2} sz$,
Which yields $a = sx/\sqrt{2}$, $b=sz/\sqrt{2}$ and $a^2=0.5 sx^2$, $b^2=0.5 sz^2$
To get corners, we decrease $a$ and $b$ by the corners parameter each
The ellipse condition $dx^2/a^2+dz^2/b^2 \leq 1$ then yields $dx^2/(0.5 sx^2) + dz^2/(0.5 sz^2) \leq 1$
We use $wx2=2 sx^-2$, $wz2=2 sz^-2$ and then $dx^2 wx2 + dz^2 wz2 \leq 1$.
## vl_terraforming.find_ground_vm(vm, pos)
Find ground starting at the given position. When in a solid area, moves up; otherwise searches downwards.
This will ignore trees, mushrooms, and similar surface decorations.
## vl_terraforming.find_under_air_vm(vm, pos)
Find ground or liquid surface, starting at the given position. When in a solid or liquid area, moves up; otherwise searches downwards.
This will ignore trees, mushrooms, and similar surface decorations.
## vl_terraforming.find_liquid_surface_vm(vm, pos)
Find a liquid surface starting at the given position. When in a solid or liquid area, moves up; otherwise searches downwards.
This will ignore trees, mushrooms, and similar surface decorations.
## vl_terraforming.find_level_vm(vm, cpos, size, tolerance, mode)
Find "level" ground for a building, centered at the given position, and of the given size.
For this, five samples are taken: center, top left, top right, bottom left, and bottom right.
One of these values may be "extreme", and tolerance specifies the maximum height difference of the remaining four values.
The (rounded) median of these values is used, unless tolerance is set to "min" or "max".
The "mode" can be set to "solid" (default), "liquid" (liquid surfaces only), "under_air" (both liquid and solid surfaces).
## vl_terraforming.foundation_vm(vm, px, py, pz, sx, sy, sz, corners, surface_mat, platform_mat, stone_mat, dust_mat, pr)
The position (px, py, pz) and the size (sx, sy, sz) give the volume of the main base plate,
where sy < 0, so that you can later place the structure at (px, py, pz).
The baseplate will be grown by 1 in the level below, to allow mobs to enter, then randomly fade away below.
-sy can be used to control a minimum depth.
Corners specifies how much to cut the corners, use 0 for a square baseplate.
The materials specified (as lua nodes, to have param2 support) are used a follows:
- surface_mat for surface nodes
- platform_mat below surface nodes
- stone_mat randomly used below platform_mat
- dust_mat on top of surface nodes (snow cover, optional)
pr is a PcgRandom random generator
## vl_terraforming.clearance_vm(vm, px, py, pz, sx, sy, sz, corners, surface_mat, dust_mat, pr)
The position (px, py, pz) and the size (sx, sy, sz) give the volume overhead to clear.
The area will be grown by 1 above, to allow mobs to enter, then randomly fade away as height increases beyond sy.
Corners specifies how much to cut the corners, use 0 for a square area.
The surface_mat will be used to turn nodes into surface nodes when widening the area.
pr is a PcgRandom random generator
## TODO
- [ ] add an API that works on VM buffers
- [ ] add an API version working on the non-VM API
- [ ] benchmark if VM is actually faster than not using VM (5.9 has some optimizations not in VM)
- [ ] improve tree removal

View File

@ -0,0 +1,174 @@
local AIR = {name = "air"}
local abs = math.abs
local max = math.max
local floor = math.floor
local vector_new = vector.new
local is_solid_not_tree = vl_terraforming._is_solid_not_tree
local is_tree_not_leaves = vl_terraforming._is_tree_not_leaves
--- Clear an area for a structure
--
-- Rounding: we model an ellipse. At zero rounding, we want the line go through the corner, at sx/2, sz/2.
-- For this, we need to make ellipse sized 2a=sqrt(2)*sx, 2b=sqrt(2)*sz,
-- Which yields a = sx/sqrt(2), b=sz/sqrt(2) and a^2=sx^2*0.5, b^2=sz^2*0.5
-- To get corners, we decrease a and b by approx. corners each
-- The ellipse condition dx^2/a^2+dz^2/b^2 <= 1 then yields dx^2/(sx^2*0.5) + dz^2/(sz^2*0.5) <= 1
-- We use wx2=sx^-2*2, wz2=sz^-2*2 and then dx^2*wx2+dz^2*wz2 <= 1
--
-- @param vm VoxelManip: Lua voxel manipulator
-- @param px number: lowest x
-- @param py number: lowest y
-- @param pz number: lowest z
-- @param sx number: x width
-- @param sy number: y height
-- @param sz number: z depth
-- @param corners number: corner rounding
-- @param surface_mat Node: surface node material
-- @param dust_mat Node: surface dust material
-- @param pr PcgRandom: random generator
function vl_terraforming.clearance_vm(vm, px, py, pz, sx, sy, sz, corners, surface_mat, dust_mat, pr)
if sx <= 0 or sy <= 0 or sz <= 0 then return end
local get_node_at = vm.get_node_at
local set_node_at = vm.set_node_at
corners = corners or 0
local wx2, wz2 = max(sx - corners, 1)^-2 * 2, max(sz - corners, 1)^-2 * 2
local cx, cz = px + sx * 0.5 - 0.5, pz + sz * 0.5 - 0.5
local min_clear, max_clear = py+sy, py+floor(sy*1.5+2) -- todo: make more parameterizable, but adds another parameter
-- excavate the needed volume and some headroom
local vec = vector_new(0, 0, 0) -- single vector, to avoid allocations -- performance!
for xi = px-1,px+sx do
local dx = abs(cx-xi)
local dx2 = max(dx+0.51,0)^2*wx2
local dx21 = max(dx-0.49,0)^2*wx2
vec.x = xi
for zi = pz-1,pz+sz do
local dz = abs(cz-zi)
local dz2 = max(dz+0.51,0)^2*wz2
local dz21 = max(dz-0.49,0)^2*wz2
vec.z = zi
if xi >= px and xi < px+sx and zi >= pz and zi < pz+sz and dx2+dz2 <= 1 then
vec.y = py
if vm:get_node_at(vec).name ~= "mcl_core:bedrock" then set_node_at(vm, vec, AIR) end
vec.y = py - 1
local n = get_node_at(vm, vec)
if n and n.name ~= surface_mat.name and is_solid_not_tree(n) then
set_node_at(vm, vec, surface_mat)
end
for yi = py+1,min_clear do -- full height for inner area
vec.y = yi
if vm:get_node_at(vec).name ~= "mcl_core:bedrock" then set_node_at(vm, vec, AIR) end
end
elseif dx21+dz21 <= 1 then
-- widen the cave above by 1, to make easier to enter for mobs
-- todo: make configurable?
vec.y = py + 1
local name = vm:get_node_at(vec).name
if name ~= "mcl_core:bedrock" then
local mat = AIR
if dust_mat then
vec.y = py
if vm:get_node_at(vec).name == surface_mat.name then mat = dust_mat end
vec.y = py + 1
end
set_node_at(vm, vec, mat)
end
for yi = py+2,min_clear-1 do
vec.y = yi
if vm:get_node_at(vec).name ~= "mcl_core:bedrock" then set_node_at(vm, vec, AIR) end
if yi > py+4 then
local p = (yi-py) / (max_clear-py)
--minetest.log(tostring(p).."^2 "..tostring(p*p).." rand: "..pr:next(0,1e9)/1e9)
if (pr:next(0,1e9)/1e9) < p then break end
end
end
-- remove some tree parts and fix surfaces down
for yi = py,py-1,-1 do
vec.y = yi
local n = get_node_at(vm, vec)
if is_tree_not_leaves(n) then
set_node_at(vm, vec, surface_mat)
if dust_mat and yi == py then
vec.y = yi + 1
if vm:get_node_at(vec).name == "air" then set_node_at(vm, vec, dust_mat) end
end
else
if n and n.name ~= surface_mat.name and is_solid_not_tree(n) then
set_node_at(vm, vec, surface_mat)
if dust_mat then
vec.y = yi + 1
if vm:get_node_at(vec).name == "air" then set_node_at(vm, vec, dust_mat) end
end
end
break
end
end
end
end
end
-- some extra gaps for entry
-- todo: make optional instead of hard-coded 25%
-- todo: only really useful if there is space at px-3,py+3 to px-3,py+5
--[[
for xi = px-2,px+sx+1 do
local dx21 = max(abs(cx-xi)-0.49,0)^2*wx2
local dx22 = max(abs(cx-xi)-1.49,0)^2*wx2
for zi = pz-2,pz+sz+1 do
local dz21 = max(abs(cz-zi)-0.49,0)^2*wz2
local dz22 = max(abs(cz-zi)-1.49,0)^2*wz2
if dx21+dz21 > 1 and dx22+dz22 <= 1 and pr:next(1,4) == 1 then
if py+4 < sy then
for yi = py+2,py+4 do
vec = vector_new(xi, yi, zi)
if vm:get_node_at(vec).name ~= "mcl_core:bedrock" then set_node_at(vm, vec, v) end
end
end
for yi = py+1,py-1,-1 do
local n = get_node_at(vm, vector_new(xi, yi, zi))
if is_tree_bot_leaves(n) and n.name ~= "mcl_core:bedrock" then
set_node_at(vm, vector_new(xi, yi, zi), AIR)
else
if n and n.name ~= surface_mat.name and is_solid_not_tree(n) then
set_node_at(vm, vector_new(xi, yi, zi), surface_mat)
end
break
end
end
end
end
end
]]--
-- cave some additional area overhead, try to make it interesting though
for yi = min_clear+1,max_clear do
local dy2 = max(yi-min_clear-1,0)^2*0.05
local active = false
for xi = px-2,px+sx+1 do
local dx22 = max(abs(cx-xi)-1.49,0)^2*wx2
for zi = pz-2,pz+sz+1 do
local dz22 = max(abs(cz-zi)-1.49,0)^2*wz2
local keep_trees = (xi<px or xi>=px+sx) or (zi<pz or zi>=pz+sz) -- TODO make parameter?
if dx22+dy2+dz22 <= 1 then
vec.x, vec.y, vec.z = xi, yi, zi
local name = get_node_at(vm, vec).name
-- don't break bedrock or air
if name == "air" or name == "ignore" or name == "mcl_core:bedrock" or name == "mcl_villages:no_paths" then goto continue end
local meta = minetest.registered_items[name]
local groups = meta and meta.groups
local is_tree = groups.leaves or groups.tree or (groups.compostability or 0 > 50)
if keep_trees and is_tree then goto continue end
vec.y = yi-1
-- do not clear above solid
local name_below = get_node_at(vm, vec).name
if name_below ~= "air" and name_below ~= "ignore" and name_below ~= "mcl_core:bedrock" then goto continue end
-- try to completely remove trees overhead
-- stop randomly depending on fill, to narrow down the caves
if not keep_trees and not is_tree and (pr:next(0,1e9)/1e9)^0.5 > 1-(dx22+dy2+dz22-0.1) then goto continue end
vec.x, vec.y, vec.z = xi, yi, zi
set_node_at(vm, vec, AIR)
active = true
::continue::
end
end
end
if not active then break end
end
end

View File

@ -0,0 +1,117 @@
local abs = math.abs
local max = math.max
local vector_new = vector.new
local is_solid_not_tree = vl_terraforming._is_solid_not_tree
local make_solid_vm = vl_terraforming._make_solid_vm
--- Grow the foundation downwards
-- @param vm VoxelManip: Lua Voxel Manipulator
-- @param xi number: x coordinate
-- @param yi number: y coordinate
-- @param zi number: z coordinate
-- @param pr PcgRandom: random generator
-- @param surface_mat Node: surface material node
-- @param platform_mat Node: platform material node
-- @param stone_mat Node: stone material node
local function grow_foundation_vm(vm,xi,yi,zi,pr,surface_mat,platform_mat,stone_mat)
local get_node_at = vm.get_node_at
local pos, n, c = vector_new(xi,yi,zi), nil, 0
if is_solid_not_tree(get_node_at(vm, pos)) then return false end -- already solid, nothing to do
pos.y = pos.y + 1
local cur = get_node_at(vm, pos)
if not is_solid_not_tree(cur) then return false end -- above is empty, do not fill below
if cur and cur.name and cur.name ~= surface_mat.name then platform_mat = cur end
if pr:next(1,4) == 1 then platform_mat = stone_mat end -- randomly switch to stone sometimes
-- count solid nodes above otherwise
for x = xi-1,xi+1 do
for z = zi-1,zi+1 do
pos.x, pos.z = x, z
if is_solid_not_tree(get_node_at(vm, pos)) then c = c + 1 end
end
end
-- stop randomly depending on fill, to narrow down the foundation
-- TODO: allow controlling the random depth with an additional parameter?
if (pr:next(0,1e9)/1e9)^2 > c/9.1 then return false end
pos.x, pos.y, pos.z = xi, yi, zi
if get_node_at(vm, pos).name == "mcl_core:bedrock" then return false end
vm:set_node_at(pos, platform_mat)
return true
end
--- Generate a foundation from px,py,pz with size sx,sy,sz (sy < 0) plus some margin
-- TODO: add support for dust_mat (snow)
--
-- Rounding: we model an ellipse. At zero rounding, we want the line go through the corner, at sx/2, sz/2.
-- For this, we need to make ellipse sized 2a=sqrt(2)*sx, 2b=sqrt(2)*sz,
-- Which yields a = sx/sqrt(2), b=sz/sqrt(2) and a^2=sx^2*0.5, b^2=sz^2*0.5
-- To get corners, we decrease a and b by approx. corners each
-- The ellipse condition dx^2/a^2+dz^2/b^2 <= 1 then yields dx^2/(sx^2*0.5) + dz^2/(sz^2*0.5) <= 1
-- We use wx2=sx^-2*2, wz2=sz^-2*2 and then dx^2*wx2+dz^2*wz2 <= 1
--
-- @param vm VoxelManip: Lua Voxel Manipulator
-- @param px number: lowest x
-- @param py number: lowest y
-- @param pz number: lowest z
-- @param sx number: x width
-- @param sy number: y height
-- @param sz number: z depth
-- @param corners number: Corner rounding
-- @param surface_mat Node: surface material node
-- @param platform_mat Node: platform material node
-- @param stone_mat Node: stone material node
-- @param dust_mat Node: dust material, optional
-- @param pr PcgRandom: random generator
function vl_terraforming.foundation_vm(vm, px, py, pz, sx, sy, sz, corners, surface_mat, platform_mat, stone_mat, dust_mat, pr)
if sx <= 0 or sy >= 0 or sz <= 0 then return end
local get_node_at = vm.get_node_at
local set_node_at = vm.set_node_at
corners = corners or 0
local wx2, wz2 = max(sx - corners, 1)^-2 * 2, max(sz - corners, 1)^-2 * 2
local cx, cz = px + sx * 0.5 - 0.5, pz + sz * 0.5 - 0.5
-- generate a baseplate (2 layers, lower is wider
local pos = vector_new(px, py, pz)
for xi = px-1,px+sx do
local dx2 = max(abs(cx-xi)+0.51,0)^2*wx2
local dx21 = max(abs(cx-xi)-0.49,0)^2*wx2
pos.x = xi
for zi = pz-1,pz+sz do
local dz2 = max(abs(cz-zi)+0.51,0)^2*wz2
local dz21 = max(abs(cz-zi)-0.49,0)^2*wz2
pos.z = zi
if xi >= px and xi < px+sx and zi >= pz and zi < pz+sz and dx2+dz2 <= 1 then
pos.y = py
if get_node_at(vm, pos).name ~= "mcl_core:bedrock" then
set_node_at(vm, pos, surface_mat)
if dust_mat then
pos.y = py + 1
if get_node_at(vm, pos).name == "air" then set_node_at(vm, pos, dust_mat) end
end
pos.y = py - 1
make_solid_vm(vm, pos, platform_mat)
end
elseif dx21+dz21 <= 1 then -- and pr:next(1,4) < 4 then -- TODO: make randomness configurable.
-- slightly widen the baseplate below, to make easier to enter for mobs
pos.y = py - 1
make_solid_vm(vm, pos, surface_mat)
if dust_mat then
pos.y = py
if get_node_at(vm, pos).name == "air" then set_node_at(vm, pos, dust_mat) end
end
end
end
end
-- construct additional baseplate below, also try to make it interesting
for yi = py-2,py-20,-1 do
local dy2 = max(0,py-2-yi)^2*0.05
local active = false
for xi = px-1,px+sx do
local dx22 = max(abs(cx-xi)-1.49,0)^2*wx2
for zi = pz-1,pz+sz do
local dz22 = max(abs(cz-zi)-1.49,0)^2*wz2
if dx22+dy2+dz22 <= 1 and grow_foundation_vm(vm,xi,yi,zi,pr,surface_mat,platform_mat,stone_mat) then active = true end
end
end
if not active and yi < py + sy then break end
end
-- TODO: add back additional steps for easier entering, optional, and less regular?
end

View File

@ -0,0 +1,8 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
vl_terraforming = {}
dofile(modpath.."/util.lua")
dofile(modpath.."/clearance.lua")
dofile(modpath.."/foundation.lua")
dofile(modpath.."/level.lua")

View File

@ -0,0 +1,205 @@
local min = math.min
local floor = math.floor
local ceil = math.ceil
local vector_copy = vector.copy
local is_liquid = vl_terraforming._is_liquid
local is_solid_not_tree = vl_terraforming._is_solid_not_tree
--- Find ground below a given position
-- @param vm VoxelManip: buffer
-- @param pos vector: Start position
-- @return position and material of surface
function vl_terraforming.find_ground_vm(vm, pos)
if not pos then return nil, nil end
pos = vector_copy(pos)
local cur = vm:get_node_at(pos)
if cur.name == "ignore" then
local e1, e2 = vm:get_emerged_area()
minetest.log("warning","find_ground with invalid position (outside of emerged area?) at "..minetest.pos_to_string(pos)
..": "..tostring(cur and cur.name).." area: "..minetest.pos_to_string(e1).." "..minetest.pos_to_string(e2))
return nil
end
if is_solid_not_tree(cur) then -- find up
local prev = cur
while true do
pos.y = pos.y + 1
local cur = vm:get_node_at(pos)
if not cur or cur.name == "ignore" then
-- minetest.log("action", "No ground, "..tostring(cur and cur.name).." over "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
end
if not is_solid_not_tree(cur) then
pos.y = pos.y - 1
return pos, prev
end
prev = cur
end
else -- find down
while true do
pos.y = pos.y - 1
local prev = cur
local cur = vm:get_node_at(pos)
if not cur or cur.name == "ignore" then
-- minetest.log("action", "No ground, "..tostring(cur and cur.name).." below "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
end
if is_liquid(cur) then
return nil
end
if is_solid_not_tree(cur) then
return pos, cur
end
end
end
end
local find_ground_vm = vl_terraforming.find_ground_vm
--- Find ground or liquid surface for a given position
-- @param vm VoxelManip: buffer
-- @param pos vector: Start position
-- @return position and material of surface
function vl_terraforming.find_under_air_vm(vm, pos)
if not pos then return nil, nil end
pos = vector_copy(pos)
local cur = vm:get_node_at(pos)
if cur.name == "ignore" then
local e1, e2 = vm:get_emerged_area()
minetest.log("warning","find_under_air with invalid position (outside of emerged area?) at "..minetest.pos_to_string(pos)
..": "..tostring(cur and cur.name).." area: "..minetest.pos_to_string(e1).." "..minetest.pos_to_string(e2))
return nil
end
if is_solid_not_tree(cur) or is_liquid(cur) then -- find up
local prev = cur
while true do
pos.y = pos.y + 1
local cur = vm:get_node_at(pos)
if not cur or cur.name == "ignore" then
-- minetest.log("action", "No ground, "..tostring(cur and cur.name).." over "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
end
if not is_solid_not_tree(cur) and not is_liquid(cur) then
pos.y = pos.y - 1
-- minetest.log("action", "Found surface: "..minetest.pos_to_string(pos).." "..tostring(prev and prev.name).." under "..tostring(cur and cur.name))
return pos, prev
end
prev = cur
end
else -- find down
while true do
pos.y = pos.y - 1
local prev = cur
local cur = vm:get_node_at(pos)
if not cur or cur.name == "ignore" then
-- minetest.log("action", "No ground, "..tostring(cur and cur.name).." below "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
end
if is_solid_not_tree(cur) or is_liquid(cur) then
-- minetest.log("action", "Found surface: "..minetest.pos_to_string(pos).." "..(cur and cur.name).." over "..(prev and prev.name))
return pos, cur
end
end
end
end
local find_under_air_vm = vl_terraforming.find_under_air_vm
--- Find liquid surface for a given position
-- @param vm VoxelManip: buffer
-- @param pos vector: Start position
-- @return position and material of surface
function vl_terraforming.find_liquid_surface_vm(vm, pos)
if not pos then return nil, nil end
pos = vector_copy(pos)
local cur = vm:get_node_at(pos)
if cur.name == "ignore" then
local e1, e2 = vm:get_emerged_area()
minetest.log("warning","find_liquid_surface with invalid position (outside of emerged area?) at "..minetest.pos_to_string(pos)
..": "..tostring(cur and cur.name).." area: "..minetest.pos_to_string(e1).." "..minetest.pos_to_string(e2))
return nil
end
if is_liquid(cur) then -- find up
local prev = cur
while true do
pos.y = pos.y + 1
local cur = vm:get_node_at(pos)
if not cur or cur.name == "ignore" then
-- minetest.log("action", "No ground, "..tostring(cur and cur.name).." over "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
end
if not is_liquid(cur) then
pos.y = pos.y - 1
-- minetest.log("action", "Found surface: "..minetest.pos_to_string(pos).." "..tostring(prev and prev.name).." under "..tostring(cur and cur.name))
return pos, prev
end
prev = cur
end
else -- find down
while true do
pos.y = pos.y - 1
local prev = cur
local cur = vm:get_node_at(pos)
if not cur or cur.name == "ignore" then
-- minetest.log("action", "No ground, "..tostring(cur and cur.name).." below "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
end
if is_solid_not_tree(cur) then
-- minetest.log("action", "No ground, "..tostring(cur and cur.name).." below "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
end
if is_liquid(cur) then
-- minetest.log("action", "Found surface: "..minetest.pos_to_string(pos).." "..(cur and cur.name).." over "..(prev and prev.name))
return pos, cur
end
end
end
end
local find_liquid_surface_vm = vl_terraforming.find_liquid_surface_vm
--- find suitable height for a structure of this size
-- @param vm VoxelManip: to read data
-- @param cpos vector: center
-- @param size vector: area size
-- @param tolerance number or string: maximum height difference allowed, default 8.
-- @param mode string: "solid" (default), "liquid_surface", "under_air"
-- @return position over surface, surface material (or nil, nil)
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
if not pos then return nil, nil end
local ys = { pos.y }
pos.y = pos.y + 1 -- above ground
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
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
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
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
local pos_c = find_ground(vm, pos)
if pos_c then table.insert(ys, pos_c.y) end
table.sort(ys)
tolerance = tolerance or 8
if tolerance == "min" then
cpos.y = ys[1] + 1
return cpos, surface_material
end
if tolerance == "max" then
cpos.y = ys[#ys] + 1
return cpos, surface_material
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)
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
return cpos, surface_material
end

View File

@ -0,0 +1,3 @@
name = vl_terraforming
author = kno10
description = Terraforming API for VoxeLibre and Mineclonia

View File

@ -0,0 +1,63 @@
--- fairly strict: air, ignore, or no_paths marker
-- @param node string or Node: node or node name
-- @return true for air and ignore nodes
function vl_terraforming._is_air(node)
local name = node.name or node
return name == "air" or name == "ignore" or name == "mcl_villages:no_paths"
end
--- check if a node is walkable (solid), but not tree/leaves/fungi/bamboo/vines/etc.
-- @param node LUA node or node name
-- @return truthy when solid but not tree/decoration/fungi
function vl_terraforming._is_solid_not_tree(node)
local name = node.name or node
if name == "air" or name == "ignore" or name == "mcl_villages:no_paths" or name == "mcl_core:bedrock" then return false end
if name == "mcl_nether:soul_sand" then return true end -- not "solid". Other exceptions we need?
if name == "mcl_nether:nether_wart_block" then return false end -- crimson forest, treat as tree
-- is deco_block if name == "mcl_crimson:warped_wart_block" then return false end -- warped forest, treat as tree
-- is deco_block if name == "mcl_crimson:shroomlight" then return false end -- crimson forest, treat as tree
-- is deco_block if name == "mcl_core:snow" then return false end
-- is walkable if name == "mcl_core:snowblock" then return true end
local meta = minetest.registered_items[name]
local groups = meta and meta.groups
return meta and meta.walkable and not (groups and (groups.deco_block or groups.tree or groups.leaves or groups.plant))
end
local is_solid_not_tree = vl_terraforming._is_solid_not_tree
--- check if a node is tree
-- @param node string or Node: node or node name
-- @return true for tree, leaves
function vl_terraforming._is_tree_not_leaves(node)
local name = node.name or node
if name == "air" or name == "ignore" or name == "mcl_villages:no_paths" then return false end
-- if name == "mcl_nether:nether_wart_block" then return true end -- crimson forest, treat as tree
-- if name == "mcl_crimson:warped_wart_block" then return true end -- warped forest, treat as tree
-- if name == "mcl_crimson:shroomlight" then return true end -- crimson forest, treat as tree
local meta = minetest.registered_items[name]
return meta and meta.groups and meta.groups.tree
end
--- check if a node is liquid
-- @param node string or Node: node or node name
-- @return true for water, lava
function vl_terraforming._is_liquid(node)
local name = node.name or node
if name == "air" or name == "ignore" or name == "mcl_villages:no_paths" then return false end
local meta = minetest.registered_items[name]
local groups = meta and meta.groups
return groups and groups.liquid
end
--- replace a non-solid node, optionally also "additional"
-- @param vm voxelmanip
-- @param pos position
-- @param with replacement Lua node (not just name)
-- @param always additional node to awlays replace even when solid
function vl_terraforming._make_solid_vm(vm, pos, with, always)
local cur = vm:get_node_at(pos)
if cur.name == "ignore" or cur.name == "mcl_core:bedrock" then return end
if cur.name == always or not is_solid_not_tree(cur) then
vm:set_node_at(pos, with)
return true
end
end

View File

@ -45,6 +45,9 @@ mcl_disabled_structures (Disabled structures) string
# Comma separated list of disabled event names
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
# Amount of village to generate
mcl_villages_village_probability (Probability of villages) int 5 0 100