kay27 2021-02-22 03:15:32 +04:00
parent d8b5620115
commit 89e55e9065
15 changed files with 944 additions and 801 deletions

@ -46,6 +46,42 @@ local numcmax = math.max(math.floor((mapgen_limit_max - ccfmax) / chunk_size_in_
mcl_vars.mapgen_edge_min = central_chunk_min_pos - numcmin * chunk_size_in_nodes mcl_vars.mapgen_edge_min = central_chunk_min_pos - numcmin * chunk_size_in_nodes
mcl_vars.mapgen_edge_max = central_chunk_max_pos + numcmax * chunk_size_in_nodes mcl_vars.mapgen_edge_max = central_chunk_max_pos + numcmax * chunk_size_in_nodes
local function coordinate_to_block(x)
return math.floor(x / mcl_vars.MAP_BLOCKSIZE)
end
local function coordinate_to_chunk(x)
return math.floor((coordinate_to_block(x) + central_chunk_offset) / mcl_vars.chunksize)
end
function mcl_vars.pos_to_block(pos)
return {
x = coordinate_to_block(pos.x),
y = coordinate_to_block(pos.y),
z = coordinate_to_block(pos.z)
}
end
function mcl_vars.pos_to_chunk(pos)
return {
x = coordinate_to_chunk(pos.x),
y = coordinate_to_chunk(pos.y),
z = coordinate_to_chunk(pos.z)
}
end
local k_positive = math.ceil(mcl_vars.MAX_MAP_GENERATION_LIMIT / chunk_size_in_nodes)
local k_positive_z = k_positive * 2
local k_positive_y = k_positive_z * k_positive_z
function mcl_vars.get_chunk_number(pos) -- unsigned int
local c = mcl_vars.pos_to_chunk(pos)
return
(c.y + k_positive) * k_positive_y +
(c.z + k_positive) * k_positive_z +
c.x + k_positive
end
if not superflat and not singlenode then if not superflat and not singlenode then
-- Normal mode -- Normal mode
--[[ Realm stacking (h is for height) --[[ Realm stacking (h is for height)
@ -91,7 +127,7 @@ else
mcl_vars.mg_bedrock_is_rough = false mcl_vars.mg_bedrock_is_rough = false
end end
mcl_vars.mg_overworld_max = 31000 mcl_vars.mg_overworld_max = mcl_vars.mapgen_edge_max
-- The Nether (around Y = -29000) -- The Nether (around Y = -29000)
mcl_vars.mg_nether_min = -29067 -- Carefully chosen to be at a mapchunk border mcl_vars.mg_nether_min = -29067 -- Carefully chosen to be at a mapchunk border

@ -405,53 +405,3 @@ function mcl_util.get_object_center(obj)
pos.y = pos.y + (ymax - ymin) / 2.0 pos.y = pos.y + (ymax - ymin) / 2.0
return pos return pos
end end
local get_node_emerge_queue = {}
local function ecb_get_far_node(blockpos, action, calls_remaining, param)
if calls_remaining <= 0 and param then
minetest.log("verbose","[mcl_util] ecb done for param = "..param.." node.name="..minetest.get_node(minetest.string_to_pos(param)).name)
get_node_emerge_queue[param] = nil
end
end
function mcl_util.get_far_node(pos, force)
local node = minetest.get_node(pos)
if node.name ~= "ignore" then
return node
end
minetest.get_voxel_manip():read_from_map(pos, pos)
node = minetest.get_node(pos)
if node.name ~= "ignore" or not force then
return node
end
local blockpos = vector.multiply(vector.floor(vector.divide(pos, mcl_vars.MAP_BLOCKSIZE)), mcl_vars.MAP_BLOCKSIZE)
local key = minetest.pos_to_string(blockpos)
for i=1,2 do -- give engine 2 chances to emerge the data
if not get_node_emerge_queue[key] then
get_node_emerge_queue[key] = 1
minetest.log("verbose","[mcl_util] emerge during get_far_node("..minetest.pos_to_string(pos).."), key="..key..", blockpos="..minetest.pos_to_string(blockpos))
minetest.emerge_area(blockpos, vector.add(blockpos, mcl_vars.MAP_BLOCKSIZE-1), ecb_get_far_node, key)
end
while not get_node_emerge_queue[key] do end
minetest.log("verbose","[mcl_util] emerge finished for node "..minetest.pos_to_string(pos)..", key="..key..", blockpos="..minetest.pos_to_string(blockpos)..", node.name="..mcl_util.get_far_node(pos).name)
node = minetest.get_node(pos)
if node.name ~= "ignore" then
return node
end
minetest.get_voxel_manip():read_from_map(pos, pos)
node = minetest.get_node(pos)
if node.name ~= "ignore" or not force then
return node
end
end
node.name = "air" -- engine continuously returns "ignore" - probably it is a bug
minetest.swap_node(pos, node) -- engine continuously returns "ignore" - probably it is a bug
return node -- engine continuously returns "ignore" - probably it is a bug
end

@ -1,4 +1,5 @@
mcl_init mcl_init
mcl_mapgen_core
mcl_core mcl_core
mcl_worlds mcl_worlds
mcl_farming mcl_farming

@ -3967,7 +3967,7 @@ if mg_name ~= "singlenode" then
-- Overworld decorations for v6 are handled in mcl_mapgen_core -- Overworld decorations for v6 are handled in mcl_mapgen_core
if deco_id_chorus_plant then if deco_id_chorus_plant then
minetest.register_on_generated(function(minp, maxp, blockseed) mcl_mapgen_core.register_generator("chorus_grow", nil, function(minp, maxp, blockseed)
local gennotify = minetest.get_mapgen_object("gennotify") local gennotify = minetest.get_mapgen_object("gennotify")
local poslist = {} local poslist = {}
for _, pos in ipairs(gennotify["decoration#"..deco_id_chorus_plant] or {}) do for _, pos in ipairs(gennotify["decoration#"..deco_id_chorus_plant] or {}) do

@ -1,17 +1,38 @@
-- FIXME: Chests may appear at openings -- FIXME: Chests may appear at openings
local mg_name = minetest.get_mapgen_setting("mg_name") local mg_name = minetest.get_mapgen_setting("mg_name")
local pr = PseudoRandom(os.time())
-- Are dungeons disabled? -- Are dungeons disabled?
if mcl_vars.mg_dungeons == false then if mcl_vars.mg_dungeons == false or mg_name == "singlenode" then
return return
end end
if mg_name ~= "singlenode" then local min_y = math.max(mcl_vars.mg_overworld_min, mcl_vars.mg_bedrock_overworld_max) + 1
-- Get loot for dungeon chests local max_y = mcl_vars.mg_overworld_max - 1
local get_loot = function()
local loottable = { local dungeonsizes = {
{ x=5, y=4, z=5},
{ x=5, y=4, z=7},
{ x=7, y=4, z=5},
{ x=7, y=4, z=7},
}
local dirs = {
{ x= 1, y=0, z= 0 },
{ x= 0, y=0, z= 1 },
{ x=-1, y=0, z= 0 },
{ x= 0, y=0, z=-1 },
}
local surround_vectors = {
{ x=-1, y=0, z=0 },
{ x=1, y=0, z=0 },
{ x=0, y=0, z=-1 },
{ x=0, y=0, z=1 },
}
local loottable =
{
{ {
stacks_min = 1, stacks_min = 1,
stacks_max = 3, stacks_max = 3,
@ -54,347 +75,278 @@ local get_loot = function()
{ itemstring = "mcl_mobitems:string", weight = 10, amount_min = 1, amount_max = 8 }, { itemstring = "mcl_mobitems:string", weight = 10, amount_min = 1, amount_max = 8 },
}, },
} }
}
-- Bonus loot for v6 mapgen: Otherwise unobtainable saplings.
if mg_name == "v6" then
table.insert(loottable, {
stacks_min = 1,
stacks_max = 3,
items = {
{ itemstring = "mcl_core:birchsapling", weight = 1, amount_min = 1, amount_max = 2 },
{ itemstring = "mcl_core:acaciasapling", weight = 1, amount_min = 1, amount_max = 2 },
{ itemstring = "", weight = 6 },
},
})
end
-- Calculate the number of dungeon spawn attempts
-- In Minecraft, there 8 dungeon spawn attempts Minecraft chunk (16*256*16 = 65536 blocks).
-- Minetest chunks don't have this size, so scale the number accordingly.
local attempts = math.ceil(((mcl_vars.chunksize * mcl_vars.MAP_BLOCKSIZE) ^ 3) / 8192) -- 63 = 80*80*80/8192
local function ecb_spawn_dungeon(blockpos, action, calls_remaining, param)
if calls_remaining >= 1 then return end
local p1, p2, dim, pr = param.p1, param.p2, param.dim, param.pr
local x, y, z = p1.x, p1.y, p1.z
-- Check floor and ceiling: Must be *completely* solid
local y_floor = y
local y_ceiling = y + dim.y + 1
for tx = x, x + dim.x do for tz = z, z + dim.z do
if not minetest.registered_nodes[mcl_mapgen_core.get_node({x = tx, y = y_floor , z = tz}).name].walkable
or not minetest.registered_nodes[mcl_mapgen_core.get_node({x = tx, y = y_ceiling, z = tz}).name].walkable then return false end
end end
-- Check for air openings (2 stacked air at ground level) in wall positions
local openings_counter = 0
-- Store positions of openings; walls will not be generated here
local openings = {}
-- Corners are stored because a corner-only opening needs to be increased,
-- so entities can get through.
local corners = {}
local walls = {
-- walls along x axis (contain corners)
{ x, x+dim.x+1, "x", "z", z },
{ x, x+dim.x+1, "x", "z", z+dim.z+1 },
-- walls along z axis (exclude corners)
{ z+1, z+dim.z, "z", "x", x },
{ z+1, z+dim.z, "z", "x", x+dim.x+1 },
} }
-- Bonus loot for v6 mapgen: Otherwise unobtainable saplings. for w=1, #walls do
if mg_name == "v6" then local wall = walls[w]
table.insert(loottable, { for iter = wall[1], wall[2] do
stacks_min = 1, local pos = {}
stacks_max = 3, pos[wall[3]] = iter
items = { pos[wall[4]] = wall[5]
{ itemstring = "mcl_core:birchsapling", weight = 1, amount_min = 1, amount_max = 2 }, pos.y = y+1
{ itemstring = "mcl_core:acaciasapling", weight = 1, amount_min = 1, amount_max = 2 },
{ itemstring = "", weight = 6 },
},
})
end
local items = mcl_loot.get_multi_loot(loottable, pr)
return items if openings[pos.x] == nil then openings[pos.x] = {} end
end local doorname1 = mcl_mapgen_core.get_node(pos).name
pos.y = y+2
local doorname2 = mcl_mapgen_core.get_node(pos).name
if doorname1 == "air" and doorname2 == "air" then
openings_counter = openings_counter + 1
openings[pos.x][pos.z] = true
-- Record corners
-- Buffer for LuaVoxelManip if wall[3] == "x" and (iter == wall[1] or iter == wall[2]) then
local lvm_buffer = {} table.insert(corners, {x=pos.x, z=pos.z})
end
-- Below the bedrock, generate air/void end
minetest.register_on_generated(function(minp, maxp) end
if maxp.y < mcl_vars.mg_overworld_min or minp.y > mcl_vars.mg_overworld_max then
return
end end
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip") -- If all openings are only at corners, the dungeon can't be accessed yet.
local data = vm:get_data(lvm_buffer) -- This code extends the openings of corners so they can be entered.
local area = VoxelArea:new({MinEdge=emin, MaxEdge=emax}) if openings_counter >= 1 and openings_counter == #corners then
local lvm_used = false for c=1, #corners do
-- Prevent creating too many openings because this would lead to dungeon rejection
if openings_counter >= 5 then
break
end
-- A corner is widened by adding openings to both neighbors
local cx, cz = corners[c].x, corners[c].z
local cxn, czn = cx, cz
if x == cx then
cxn = cxn + 1
else
cxn = cxn - 1
end
if z == cz then
czn = czn + 1
else
czn = czn - 1
end
openings[cx][czn] = true
openings_counter = openings_counter + 1
if openings_counter < 5 then
openings[cxn][cz] = true
openings_counter = openings_counter + 1
end
end
end
local c_air = minetest.get_content_id("air") -- Check conditions. If okay, start generating
local c_cobble = minetest.get_content_id("mcl_core:cobble") if openings_counter < 1 or openings_counter > 5 then return end
local c_mossycobble = minetest.get_content_id("mcl_core:mossycobble")
minetest.log("action","[mcl_dungeons] Placing new dungeon at "..minetest.pos_to_string({x=x,y=y,z=z}))
-- Okay! Spawning starts!
-- Remember spawner chest positions to set metadata later -- Remember spawner chest positions to set metadata later
local chest_posses = {} local chests = {}
local spawner_posses = {} local spawner_posses = {}
-- Calculate the number of dungeon spawn attempts -- First prepare random chest positions.
local sizevector = vector.subtract(maxp, minp) -- Chests spawn at wall
sizevector = vector.add(sizevector, 1)
local chunksize = sizevector.x * sizevector.y * sizevector.z
-- In Minecraft, there 8 dungeon spawn attempts Minecraft chunk (16*256*16 = 65536 blocks). -- We assign each position at the wall a number and each chest gets one of these numbers randomly
-- Minetest chunks don't have this size, so scale the number accordingly. local totalChests = 2 -- this code strongly relies on this number being 2
local attempts = math.ceil(chunksize / 65536 * 8) local totalChestSlots = (dim.x-1) * (dim.z-1)
local chestSlots = {}
for a=1, attempts do -- There is a small chance that both chests have the same slot.
local x, y, z -- In that case, we give a 2nd chance for the 2nd chest to get spawned.
local b = 7 -- buffer -- If it failed again, tough luck! We stick with only 1 chest spawned.
x = math.random(minp.x+b, maxp.x-b) local lastRandom
local secondChance = true -- second chance is still available
local ymin = math.min(mcl_vars.mg_overworld_max, math.max(minp.y, mcl_vars.mg_bedrock_overworld_max) + 7) for i=1, totalChests do
local ymax = math.min(mcl_vars.mg_overworld_max, math.max(maxp.y, mcl_vars.mg_bedrock_overworld_max) - 4) local r = pr:next(1, totalChestSlots)
if r == lastRandom and secondChance then
y = math.random(ymin, ymax) -- Oops! Same slot selected. Try again.
z = math.random(minp.z+b, maxp.z-b) r = pr:next(1, totalChestSlots)
secondChance = false
local dungeonsizes = {
{ x=5, y=4, z=5},
{ x=5, y=4, z=7},
{ x=7, y=4, z=5},
{ x=7, y=4, z=7},
}
local dim = dungeonsizes[math.random(1, #dungeonsizes)]
-- Check floor and ceiling: Must be *completely* solid
local ceilingfloor_ok = true
for tx = x, x+dim.x do
for tz = z, z+dim.z do
local floor = minetest.get_name_from_content_id(data[area:index(tx, y, tz)])
local ceiling = minetest.get_name_from_content_id(data[area:index(tx, y+dim.y+1, tz)])
if (not minetest.registered_nodes[floor].walkable) or (not minetest.registered_nodes[ceiling].walkable) then
ceilingfloor_ok = false
break
end
end
if not ceilingfloor_ok then break end
end end
lastRandom = r
table.insert(chestSlots, r)
end
table.sort(chestSlots)
local currentChest = 1
-- Check for air openings (2 stacked air at ground level) in wall positions -- Calculate the mob spawner position, to be re-used for later
local openings_counter = 0 local sp = {x = x + math.ceil(dim.x/2), y = y+1, z = z + math.ceil(dim.z/2)}
-- Store positions of openings; walls will not be generated here local rn = minetest.registered_nodes[mcl_mapgen_core.get_node(sp).name]
local openings = {} if rn and rn.is_ground_content then
-- Corners are stored because a corner-only opening needs to be increased, table.insert(spawner_posses, sp)
-- so entities can get through.
local corners = {}
if ceilingfloor_ok then
local walls = {
-- walls along x axis (contain corners)
{ x, x+dim.x+1, "x", "z", z },
{ x, x+dim.x+1, "x", "z", z+dim.z+1 },
-- walls along z axis (exclude corners)
{ z+1, z+dim.z, "z", "x", x },
{ z+1, z+dim.z, "z", "x", x+dim.x+1 },
}
for w=1, #walls do
local wall = walls[w]
for iter = wall[1], wall[2] do
local pos = {}
pos[wall[3]] = iter
pos[wall[4]] = wall[5]
pos.y = y+1
if openings[pos.x] == nil then openings[pos.x] = {} end
local door1 = area:index(pos.x, pos.y, pos.z)
pos.y = y+2
local door2 = area:index(pos.x, pos.y, pos.z)
local doorname1 = minetest.get_name_from_content_id(data[door1])
local doorname2 = minetest.get_name_from_content_id(data[door2])
if doorname1 == "air" and doorname2 == "air" then
openings_counter = openings_counter + 1
openings[pos.x][pos.z] = true
-- Record corners
if wall[3] == "x" and (iter == wall[1] or iter == wall[2]) then
table.insert(corners, {x=pos.x, z=pos.z})
end
end
end
end
end
-- If all openings are only at corners, the dungeon can't be accessed yet.
-- This code extends the openings of corners so they can be entered.
if openings_counter >= 1 and openings_counter == #corners then
for c=1, #corners do
-- Prevent creating too many openings because this would lead to dungeon rejection
if openings_counter >= 5 then
break
end
-- A corner is widened by adding openings to both neighbors
local cx, cz = corners[c].x, corners[c].z
local cxn, czn = cx, cz
if x == cx then
cxn = cxn + 1
else
cxn = cxn - 1
end
if z == cz then
czn = czn + 1
else
czn = czn - 1
end
openings[cx][czn] = true
openings_counter = openings_counter + 1
if openings_counter < 5 then
openings[cxn][cz] = true
openings_counter = openings_counter + 1
end
end
end
-- Check conditions. If okay, start generating
if ceilingfloor_ok and openings_counter >= 1 and openings_counter <= 5 then
-- Okay! Spawning starts!
-- First prepare random chest positions.
-- Chests spawn at wall
-- We assign each position at the wall a number and each chest gets one of these numbers randomly
local totalChests = 2 -- this code strongly relies on this number being 2
local totalChestSlots = (dim.x-1) * (dim.z-1)
local chestSlots = {}
-- There is a small chance that both chests have the same slot.
-- In that case, we give a 2nd chance for the 2nd chest to get spawned.
-- If it failed again, tough luck! We stick with only 1 chest spawned.
local lastRandom
local secondChance = true -- second chance is still available
for i=1, totalChests do
local r = math.random(1, totalChestSlots)
if r == lastRandom and secondChance then
-- Oops! Same slot selected. Try again.
r = math.random(1, totalChestSlots)
secondChance = false
end
lastRandom = r
table.insert(chestSlots, r)
end
table.sort(chestSlots)
local currentChest = 1
-- Calculate the mob spawner position, to be re-used for later
local spawner_pos = {x = x + math.ceil(dim.x/2), y = y+1, z = z + math.ceil(dim.z/2)}
table.insert(spawner_posses, spawner_pos)
-- Generate walls and floor
local maxx, maxy, maxz = x+dim.x+1, y+dim.y, z+dim.z+1
local chestSlotCounter = 1
for tx = x, maxx do
for tz = z, maxz do
for ty = y, maxy do
local p_pos = area:index(tx, ty, tz)
-- Do not overwrite nodes with is_ground_content == false (e.g. bedrock)
-- Exceptions: cobblestone and mossy cobblestone so neighborings dungeons nicely connect to each other
local name = minetest.get_name_from_content_id(data[p_pos])
if name == "mcl_core:cobble" or name == "mcl_core:mossycobble" or minetest.registered_nodes[name].is_ground_content then
-- Floor
if ty == y then
if math.random(1,4) == 1 then
data[p_pos] = c_cobble
else
data[p_pos] = c_mossycobble
end
-- Generate walls
--[[ Note: No additional cobblestone ceiling is generated. This is intentional.
The solid blocks above the dungeon are considered as the ceiling.
It is possible (but rare) for a dungeon to generate below sand or gravel. ]]
elseif ty > y and (tx == x or tx == maxx or (tz == z or tz == maxz)) then
-- Check if it's an opening first
if (not openings[tx][tz]) or ty == maxy then
-- Place wall or ceiling
data[p_pos] = c_cobble
elseif ty < maxy - 1 then
-- Normally the openings are already clear, but not if it is a corner
-- widening. Make sure to clear at least the bottom 2 nodes of an opening.
data[p_pos] = c_air
elseif ty == maxy - 1 and data[p_pos] ~= c_air then
-- This allows for variation between 2-node and 3-node high openings.
data[p_pos] = c_cobble
end
-- If it was an opening, the lower 3 blocks are not touched at all
-- Room interiour
else
local forChest = ty==y+1 and (tx==x+1 or tx==maxx-1 or tz==z+1 or tz==maxz-1)
-- Place next chest at the wall (if it was its chosen wall slot)
if forChest and (currentChest < totalChests + 1) and (chestSlots[currentChest] == chestSlotCounter) then
table.insert(chest_posses, {x=tx, y=ty, z=tz})
currentChest = currentChest + 1
else
data[p_pos] = c_air
end
if forChest then
chestSlotCounter = chestSlotCounter + 1
end
end
end
end
end
end
end
lvm_used = true
end end
if lvm_used then -- Generate walls and floor
local chest_param2 = {} local maxx, maxy, maxz = x+dim.x+1, y+dim.y, z+dim.z+1
-- Determine correct chest rotation (must pointi outwards) local chestSlotCounter = 1
for c=1, #chest_posses do for tx = x, maxx do
local cpos = chest_posses[c] for tz = z, maxz do
for ty = y, maxy do
local p = {x = tx, y=ty, z=tz}
-- Check surroundings of chest to determine correct rotation -- Do not overwrite nodes with is_ground_content == false (e.g. bedrock)
local surround_vectors = { -- Exceptions: cobblestone and mossy cobblestone so neighborings dungeons nicely connect to each other
{ x=-1, y=0, z=0 }, local name = mcl_mapgen_core.get_node(p).name
{ x=1, y=0, z=0 }, if name == "mcl_core:cobble" or name == "mcl_core:mossycobble" or minetest.registered_nodes[name].is_ground_content then
{ x=0, y=0, z=-1 }, -- Floor
{ x=0, y=0, z=1 }, if ty == y then
} if pr:next(1,4) == 1 then
local surroundings = {} minetest.swap_node(p, {name = "mcl_core:cobble"})
else
for s=1, #surround_vectors do minetest.swap_node(p, {name = "mcl_core:mossycobble"})
-- Detect the 4 horizontal neighbors
local spos = vector.add(cpos, surround_vectors[s])
local wpos = vector.subtract(cpos, surround_vectors[s])
local p_pos = area:index(spos.x, spos.y, spos.z)
local p_pos2 = area:index(wpos.x, wpos.y, wpos.z)
local nodename = minetest.get_name_from_content_id(data[p_pos])
local nodename2 = minetest.get_name_from_content_id(data[p_pos2])
local nodedef = minetest.registered_nodes[nodename]
local nodedef2 = minetest.registered_nodes[nodename2]
-- The chest needs an open space in front of it and a walkable node (except chest) behind it
if nodedef and nodedef.walkable == false and nodedef2 and nodedef2.walkable == true and nodename2 ~= "mcl_chests:chest" then
table.insert(surroundings, spos)
end end
end
-- Set param2 (=facedir) of this chest -- Generate walls
local facedir --[[ Note: No additional cobblestone ceiling is generated. This is intentional.
if #surroundings <= 0 then The solid blocks above the dungeon are considered as the ceiling.
-- Fallback if chest ended up in the middle of a room for some reason It is possible (but rare) for a dungeon to generate below sand or gravel. ]]
facedir = math.random(0, 0)
elseif ty > y and (tx == x or tx == maxx or (tz == z or tz == maxz)) then
-- Check if it's an opening first
if (not openings[tx][tz]) or ty == maxy then
-- Place wall or ceiling
minetest.swap_node(p, {name = "mcl_core:cobble"})
elseif ty < maxy - 1 then
-- Normally the openings are already clear, but not if it is a corner
-- widening. Make sure to clear at least the bottom 2 nodes of an opening.
minetest.swap_node(p, {name = "air"})
elseif ty == maxy - 1 and mcl_mapgen_core.get_node(p).name ~= "air" then
-- This allows for variation between 2-node and 3-node high openings.
minetest.swap_node(p, {name = "mcl_core:cobble"})
end
-- If it was an opening, the lower 3 blocks are not touched at all
-- Room interiour
else else
-- 1 or multiple possible open directions: Choose random facedir local forChest = ty==y+1 and (tx==x+1 or tx==maxx-1 or tz==z+1 or tz==maxz-1)
local face_to = surroundings[math.random(1, #surroundings)]
facedir = minetest.dir_to_facedir(vector.subtract(cpos, face_to))
end
chest_param2[c] = facedir
end
-- Finally generate the dungeons all at once (except the chests and the spawners) -- Place next chest at the wall (if it was its chosen wall slot)
vm:set_data(data) if forChest and (currentChest < totalChests + 1) and (chestSlots[currentChest] == chestSlotCounter) then
vm:calc_lighting() currentChest = currentChest + 1
vm:update_liquids() table.insert(chests, {x=tx, y=ty, z=tz})
vm:write_to_map() else
minetest.swap_node(p, {name = "air"})
-- Chests are placed seperately end
for c=1, #chest_posses do if forChest then
local cpos = chest_posses[c] chestSlotCounter = chestSlotCounter + 1
minetest.set_node(cpos, {name="mcl_chests:chest", param2=chest_param2[c]}) end
local meta = minetest.get_meta(cpos)
local inv = meta:get_inventory()
local items = get_loot()
mcl_loot.fill_inventory(inv, "main", items)
end
-- Mob spawners are placed seperately, too
-- We don't want to destroy non-ground nodes
for s=1, #spawner_posses do
local sp = spawner_posses[s]
local n = minetest.get_name_from_content_id(data[area:index(sp.x,sp.y,sp.z)])
if minetest.registered_nodes[n].is_ground_content then
-- ... and place it and select a random mob
minetest.set_node(sp, {name = "mcl_mobspawners:spawner"})
local mobs = {
"mobs_mc:zombie",
"mobs_mc:zombie",
"mobs_mc:spider",
"mobs_mc:skeleton",
}
local spawner_mob = mobs[math.random(1, #mobs)]
mcl_mobspawners.setup_spawner(sp, spawner_mob, 0, 7)
end end
end end
end end end
for c=#chests, 1, -1 do
local pos = chests[c]
local surroundings = {}
for s=1, #surround_vectors do
-- Detect the 4 horizontal neighbors
local spos = vector.add(pos, surround_vectors[s])
local wpos = vector.subtract(pos, surround_vectors[s])
local nodename = minetest.get_node(spos).name
local nodename2 = minetest.get_node(wpos).name
local nodedef = minetest.registered_nodes[nodename]
local nodedef2 = minetest.registered_nodes[nodename2]
-- The chest needs an open space in front of it and a walkable node (except chest) behind it
if nodedef and nodedef.walkable == false and nodedef2 and nodedef2.walkable == true and nodename2 ~= "mcl_chests:chest" then
table.insert(surroundings, spos)
end
end
-- Set param2 (=facedir) of this chest
local facedir
if #surroundings <= 0 then
-- Fallback if chest ended up in the middle of a room for some reason
facedir = pr:next(0, 0)
else
-- 1 or multiple possible open directions: Choose random facedir
local face_to = surroundings[pr:next(1, #surroundings)]
facedir = minetest.dir_to_facedir(vector.subtract(pos, face_to))
end
minetest.set_node(pos, {name="mcl_chests:chest", param2=facedir})
local meta = minetest.get_meta(pos)
mcl_loot.fill_inventory(meta:get_inventory(), "main", mcl_loot.get_multi_loot(loottable, pr))
end end
end) -- Mob spawners are placed seperately, too
-- We don't want to destroy non-ground nodes
for s=#spawner_posses, 1, -1 do
local sp = spawner_posses[s]
-- ... and place it and select a random mob
minetest.set_node(sp, {name = "mcl_mobspawners:spawner"})
local mobs = {
"mobs_mc:zombie",
"mobs_mc:zombie",
"mobs_mc:spider",
"mobs_mc:skeleton",
}
local spawner_mob = mobs[pr:next(1, #mobs)]
mcl_mobspawners.setup_spawner(sp, spawner_mob, 0, 7)
end
end end
local function dungeons_nodes(minp, maxp, blockseed)
local ymin, ymax = math.max(min_y, minp.y), math.min(max_y, maxp.y)
if ymax < ymin then return false end
local pr = PseudoRandom(blockseed)
for a=1, attempts do
local dim = dungeonsizes[pr:next(1, #dungeonsizes)]
local x = pr:next(minp.x, maxp.x-dim.x-2)
local y = pr:next(ymin , ymax -dim.y-2)
local z = pr:next(minp.z, maxp.z-dim.z-2)
local p1 = {x=x,y=y,z=z}
local p2 = {x = x+dim.x+1, y = y+dim.y+1, z = z+dim.z+1}
minetest.log("verbose","[mcl_dungeons] size=" ..minetest.pos_to_string(dim) .. ", emerge from "..minetest.pos_to_string(p1) .. " to " .. minetest.pos_to_string(p2))
local param = {p1=p1, p2=p2, dim=dim, pr=pr}
minetest.emerge_area(p1, p2, ecb_spawn_dungeon, param)
end
end
mcl_mapgen_core.register_generator("dungeons", nil, dungeons_nodes, 999999)

@ -1,3 +1,46 @@
mcl_mapgen_core = {}
mcl_mapgen_core.registered_generators = {}
local lvm, nodes, param2 = 0, 0, 0
local generating = {} -- generating chunks
local chunks = {} -- intervals of chunks generated
local function add_chunk(pos)
local n = mcl_vars.get_chunk_number(pos) -- unsigned int
local prev
for i, d in pairs(chunks) do
if n <= d[2] then -- we've found it
if (n == d[2]) or (n >= d[1]) then return end -- already here
if n == d[1]-1 then -- right before:
if prev and (prev[2] == n-1) then
prev[2] = d[2]
table.remove(chunks, i)
return
end
d[1] = n
return
end
if prev and (prev[2] == n-1) then --join to previous
prev[2] = n
return
end
table.insert(chunks, i, {n, n}) -- insert new interval before i
return
end
prev = d
end
chunks[#chunks] = {n, n}
end
function mcl_mapgen_core.is_generated(pos)
local n = mcl_vars.get_chunk_number(pos) -- unsigned int
for i, d in pairs(chunks) do
if n <= d[2] then
return (n >= d[1])
end
end
return false
end
-- --
-- Aliases for map generator outputs -- Aliases for map generator outputs
-- --
@ -1190,12 +1233,14 @@ local perlin_structures
local perlin_vines, perlin_vines_fine, perlin_vines_upwards, perlin_vines_length, perlin_vines_density local perlin_vines, perlin_vines_fine, perlin_vines_upwards, perlin_vines_length, perlin_vines_density
local perlin_clay local perlin_clay
local function generate_clay(minp, maxp, seed, voxelmanip_data, voxelmanip_area, lvm_used) local function generate_clay(minp, maxp, blockseed, voxelmanip_data, voxelmanip_area, lvm_used)
-- TODO: Make clay generation reproducible for same seed. -- TODO: Make clay generation reproducible for same seed.
if maxp.y < -5 or minp.y > 0 then if maxp.y < -5 or minp.y > 0 then
return lvm_used return lvm_used
end end
local pr = PseudoRandom(blockseed)
perlin_clay = perlin_clay or minetest.get_perlin({ perlin_clay = perlin_clay or minetest.get_perlin({
offset = 0.5, offset = 0.5,
scale = 0.2, scale = 0.2,
@ -1212,18 +1257,18 @@ local function generate_clay(minp, maxp, seed, voxelmanip_data, voxelmanip_area,
for divx=0+1,divs-2 do for divx=0+1,divs-2 do
for divz=0+1,divs-2 do for divz=0+1,divs-2 do
-- Get position and shift it a bit randomly so the clay do not obviously appear in a grid -- Get position and shift it a bit randomly so the clay do not obviously appear in a grid
local cx = minp.x + math.floor((divx+0.5)*divlen) + math.random(-1,1) local cx = minp.x + math.floor((divx+0.5)*divlen) + pr:next(-1,1)
local cz = minp.z + math.floor((divz+0.5)*divlen) + math.random(-1,1) local cz = minp.z + math.floor((divz+0.5)*divlen) + pr:next(-1,1)
local water_pos = voxelmanip_area:index(cx, y+1, cz) local water_pos = voxelmanip_area:index(cx, y+1, cz)
local waternode = voxelmanip_data[water_pos] local waternode = voxelmanip_data[water_pos]
local surface_pos = voxelmanip_area:index(cx, y, cz) local surface_pos = voxelmanip_area:index(cx, y, cz)
local surfacenode = voxelmanip_data[surface_pos] local surfacenode = voxelmanip_data[surface_pos]
local genrnd = math.random(1, 20) local genrnd = pr:next(1, 20)
if genrnd == 1 and perlin_clay:get_3d({x=cx,y=y,z=cz}) > 0 and waternode == c_water and if genrnd == 1 and perlin_clay:get_3d({x=cx,y=y,z=cz}) > 0 and waternode == c_water and
(surfacenode == c_dirt or minetest.get_item_group(minetest.get_name_from_content_id(surfacenode), "sand") == 1) then (surfacenode == c_dirt or minetest.get_item_group(minetest.get_name_from_content_id(surfacenode), "sand") == 1) then
local diamondsize = math.random(1, 3) local diamondsize = pr:next(1, 3)
for x1 = -diamondsize, diamondsize do for x1 = -diamondsize, diamondsize do
for z1 = -(diamondsize - math.abs(x1)), diamondsize - math.abs(x1) do for z1 = -(diamondsize - math.abs(x1)), diamondsize - math.abs(x1) do
local ccpos = voxelmanip_area:index(cx+x1, y, cz+z1) local ccpos = voxelmanip_area:index(cx+x1, y, cz+z1)
@ -1242,37 +1287,34 @@ local function generate_clay(minp, maxp, seed, voxelmanip_data, voxelmanip_area,
end end
-- TODO: Try to use more efficient structure generating code -- TODO: Try to use more efficient structure generating code
local function generate_structures(minp, maxp, seed, biomemap) local function generate_structures(minp, maxp, blockseed, biomemap)
local chunk_has_desert_well = false local chunk_has_desert_well = false
local chunk_has_desert_temple = false local chunk_has_desert_temple = false
local chunk_has_igloo = false local chunk_has_igloo = false
local struct_min, struct_max = -3, 64 local struct_min, struct_max = -3, 111 --64
if maxp.y >= struct_min and minp.y <= struct_max then if maxp.y >= struct_min and minp.y <= struct_max then
-- Generate structures -- Generate structures
local pr = PcgRandom(blockseed)
perlin_structures = perlin_structures or minetest.get_perlin(329, 3, 0.6, 100) perlin_structures = perlin_structures or minetest.get_perlin(329, 3, 0.6, 100)
-- Assume X and Z lengths are equal -- Assume X and Z lengths are equal
local divlen = 5 local divlen = 5
local divs = (maxp.x-minp.x)/divlen+1; for x0 = minp.x, maxp.x, divlen do for z0 = minp.z, maxp.z, divlen do
for divx=0,divs-1 do
for divz=0,divs-1 do
local x0 = minp.x + math.floor((divx+0)*divlen)
local z0 = minp.z + math.floor((divz+0)*divlen)
local x1 = minp.x + math.floor((divx+1)*divlen)
local z1 = minp.z + math.floor((divz+1)*divlen)
-- Determine amount from perlin noise -- Determine amount from perlin noise
local amount = math.floor(perlin_structures:get_2d({x=x0, y=z0}) * 9) local amount = math.floor(perlin_structures:get_2d({x=x0, y=z0}) * 9)
-- Find random positions based on this random -- Find random positions based on this random
local pr = PseudoRandom(seed+1) local p, ground_y
for i=0, amount do for i=0, amount do
local x = pr:next(x0, x1) p = {x = pr:next(x0, x0+divlen-1), y = 0, z = pr:next(z0, z0+divlen-1)}
local z = pr:next(z0, z1)
-- Find ground level -- Find ground level
local ground_y = nil ground_y = nil
local nn
for y = struct_max, struct_min, -1 do for y = struct_max, struct_min, -1 do
local checknode = minetest.get_node_or_nil({x=x,y=y,z=z}) p.y = y
local checknode = minetest.get_node(p)
if checknode then if checknode then
local def = minetest.registered_nodes[checknode.name] nn = checknode.name
local def = minetest.registered_nodes[nn]
if def and def.walkable then if def and def.walkable then
ground_y = y ground_y = y
break break
@ -1281,21 +1323,17 @@ local function generate_structures(minp, maxp, seed, biomemap)
end end
if ground_y then if ground_y then
local p = {x=x,y=ground_y+1,z=z} p.y = ground_y+1
local nn = minetest.get_node(p).name local nn0 = minetest.get_node(p).name
-- Check if the node can be replaced -- Check if the node can be replaced
if minetest.registered_nodes[nn] and if minetest.registered_nodes[nn0] and minetest.registered_nodes[nn0].buildable_to then
minetest.registered_nodes[nn].buildable_to then
nn = minetest.get_node({x=x,y=ground_y,z=z}).name
local struct = false
-- Desert temples and desert wells -- Desert temples and desert wells
if nn == "mcl_core:sand" or (nn == "mcl_core:sandstone") then if nn == "mcl_core:sand" or (nn == "mcl_core:sandstone") then
if not chunk_has_desert_temple and not chunk_has_desert_well and ground_y > 3 then if not chunk_has_desert_temple and not chunk_has_desert_well and ground_y > 3 then
-- Spawn desert temple -- Spawn desert temple
-- TODO: Check surface -- TODO: Check surface
if math.random(1,12000) == 1 then if pr:next(1,12000) == 1 then
mcl_structures.call_struct(p, "desert_temple") mcl_structures.call_struct(p, "desert_temple", nil, pr)
chunk_has_desert_temple = true chunk_has_desert_temple = true
end end
end end
@ -1303,11 +1341,11 @@ local function generate_structures(minp, maxp, seed, biomemap)
local desert_well_prob = minecraft_chunk_probability(1000, minp, maxp) local desert_well_prob = minecraft_chunk_probability(1000, minp, maxp)
-- Spawn desert well -- Spawn desert well
if math.random(1, desert_well_prob) == 1 then if pr:next(1, desert_well_prob) == 1 then
-- Check surface -- Check surface
local surface = minetest.find_nodes_in_area({x=p.x,y=p.y-1,z=p.z}, {x=p.x+5, y=p.y-1, z=p.z+5}, "mcl_core:sand") local surface = minetest.find_nodes_in_area({x=p.x,y=p.y-1,z=p.z}, {x=p.x+5, y=p.y-1, z=p.z+5}, "mcl_core:sand")
if #surface >= 25 then if #surface >= 25 then
mcl_structures.call_struct(p, "desert_well") mcl_structures.call_struct(p, "desert_well", nil, pr)
chunk_has_desert_well = true chunk_has_desert_well = true
end end
end end
@ -1315,13 +1353,13 @@ local function generate_structures(minp, maxp, seed, biomemap)
-- Igloos -- Igloos
elseif not chunk_has_igloo and (nn == "mcl_core:snowblock" or nn == "mcl_core:snow" or (minetest.get_item_group(nn, "grass_block_snow") == 1)) then elseif not chunk_has_igloo and (nn == "mcl_core:snowblock" or nn == "mcl_core:snow" or (minetest.get_item_group(nn, "grass_block_snow") == 1)) then
if math.random(1, 4400) == 1 then if pr:next(1, 4400) == 1 then
-- Check surface -- Check surface
local floor = {x=p.x+9, y=p.y-1, z=p.z+9} local floor = {x=p.x+9, y=p.y-1, z=p.z+9}
local surface = minetest.find_nodes_in_area({x=p.x,y=p.y-1,z=p.z}, floor, "mcl_core:snowblock") 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") 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 if #surface + #surface2 >= 63 then
mcl_structures.call_struct(p, "igloo") mcl_structures.call_struct(p, "igloo", nil, pr)
chunk_has_igloo = true chunk_has_igloo = true
end end
end end
@ -1331,16 +1369,16 @@ local function generate_structures(minp, maxp, seed, biomemap)
if nn == "mcl_core:sandstone" or nn == "mcl_core:sand" and not chunk_has_desert_temple and ground_y > 3 then if nn == "mcl_core:sandstone" or nn == "mcl_core:sand" and not chunk_has_desert_temple and ground_y > 3 then
local fossil_prob = minecraft_chunk_probability(64, minp, maxp) local fossil_prob = minecraft_chunk_probability(64, minp, maxp)
if math.random(1, fossil_prob) == 1 then if pr:next(1, fossil_prob) == 1 then
-- Spawn fossil below desert surface between layers 40 and 49 -- Spawn fossil below desert surface between layers 40 and 49
local p1 = {x=p.x, y=math.random(mcl_worlds.layer_to_y(40), mcl_worlds.layer_to_y(49)), z=p.z} local p1 = {x=p.x, y=pr:next(mcl_worlds.layer_to_y(40), mcl_worlds.layer_to_y(49)), z=p.z}
-- Very rough check of the environment (we expect to have enough stonelike nodes). -- Very rough check of the environment (we expect to have enough stonelike nodes).
-- Fossils may still appear partially exposed in caves, but this is O.K. -- Fossils may still appear partially exposed in caves, but this is O.K.
local p2 = vector.add(p1, 4) local p2 = vector.add(p1, 4)
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"}) 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% if #nodes >= 100 then -- >= 80%
mcl_structures.call_struct(p1, "fossil") mcl_structures.call_struct(p1, "fossil", nil, pr)
end end
end end
end end
@ -1348,7 +1386,7 @@ local function generate_structures(minp, maxp, seed, biomemap)
-- Witch hut -- Witch hut
if ground_y <= 0 and nn == "mcl_core:dirt" then if ground_y <= 0 and nn == "mcl_core:dirt" then
local prob = minecraft_chunk_probability(48, minp, maxp) local prob = minecraft_chunk_probability(48, minp, maxp)
if math.random(1, prob) == 1 then if pr:next(1, prob) == 1 then
local swampland = minetest.get_biome_id("Swampland") local swampland = minetest.get_biome_id("Swampland")
local swampland_shore = minetest.get_biome_id("Swampland_shore") local swampland_shore = minetest.get_biome_id("Swampland_shore")
@ -1370,7 +1408,7 @@ local function generate_structures(minp, maxp, seed, biomemap)
end end
if here_be_witches then if here_be_witches then
local r = tostring(math.random(0, 3) * 90) -- "0", "90", "180" or 270" local r = tostring(pr:next(0, 3) * 90) -- "0", "90", "180" or 270"
local p1 = {x=p.x-1, y=WITCH_HUT_HEIGHT+2, z=p.z-1} local p1 = {x=p.x-1, y=WITCH_HUT_HEIGHT+2, z=p.z-1}
local size local size
if r == "0" or r == "180" then if r == "0" or r == "180" then
@ -1389,7 +1427,7 @@ local function generate_structures(minp, maxp, seed, biomemap)
-- FIXME: For some mysterious reason (black magic?) this -- FIXME: For some mysterious reason (black magic?) this
-- function does sometimes NOT spawn the witch hut. One can only see the -- function does sometimes NOT spawn the witch hut. One can only see the
-- oak wood nodes in the water, but no hut. :-/ -- oak wood nodes in the water, but no hut. :-/
mcl_structures.call_struct(place, "witch_hut", r) mcl_structures.call_struct(place, "witch_hut", r, pr)
-- TODO: Spawn witch in or around hut when the mob sucks less. -- TODO: Spawn witch in or around hut when the mob sucks less.
@ -1451,7 +1489,7 @@ local function generate_structures(minp, maxp, seed, biomemap)
-- Ice spikes in v6 -- Ice spikes in v6
-- In other mapgens, ice spikes are generated as decorations. -- In other mapgens, ice spikes are generated as decorations.
if mg_name == "v6" and not chunk_has_igloo and nn == "mcl_core:snowblock" then if mg_name == "v6" and not chunk_has_igloo and nn == "mcl_core:snowblock" then
local spike = math.random(1, 58000) local spike = pr:next(1,58000)
if spike < 3 then if spike < 3 then
-- Check surface -- Check surface
local floor = {x=p.x+4, y=p.y-1, z=p.z+4} local floor = {x=p.x+4, y=p.y-1, z=p.z+4}
@ -1460,7 +1498,7 @@ local function generate_structures(minp, maxp, seed, biomemap)
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"}) 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 if #surface >= 9 and #spruce_collisions == 0 then
mcl_structures.call_struct(p, "ice_spike_large") mcl_structures.call_struct(p, "ice_spike_large", nil, pr)
end end
elseif spike < 100 then elseif spike < 100 then
-- Check surface -- Check surface
@ -1471,7 +1509,7 @@ local function generate_structures(minp, maxp, seed, biomemap)
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"}) 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 if #surface >= 25 and #spruce_collisions == 0 then
mcl_structures.call_struct(p, "ice_spike_small") mcl_structures.call_struct(p, "ice_spike_small", nil, pr)
end end
end end
end end
@ -1479,36 +1517,31 @@ local function generate_structures(minp, maxp, seed, biomemap)
end end
end end
end end end
end
-- End exit portal -- End exit portal
elseif minp.y <= END_EXIT_PORTAL_POS.y and maxp.y >= END_EXIT_PORTAL_POS.y and elseif minp.y <= END_EXIT_PORTAL_POS.y and maxp.y >= END_EXIT_PORTAL_POS.y and
minp.x <= END_EXIT_PORTAL_POS.x and maxp.x >= END_EXIT_PORTAL_POS.x and minp.x <= END_EXIT_PORTAL_POS.x and maxp.x >= END_EXIT_PORTAL_POS.x and
minp.z <= END_EXIT_PORTAL_POS.z and maxp.z >= END_EXIT_PORTAL_POS.z then minp.z <= END_EXIT_PORTAL_POS.z and maxp.z >= END_EXIT_PORTAL_POS.z then
local built = false
for y=maxp.y, minp.y, -1 do for y=maxp.y, minp.y, -1 do
local p = {x=END_EXIT_PORTAL_POS.x, y=y, z=END_EXIT_PORTAL_POS.z} local p = {x=END_EXIT_PORTAL_POS.x, y=y, z=END_EXIT_PORTAL_POS.z}
if minetest.get_node(p).name == "mcl_end:end_stone" then if minetest.get_node(p).name == "mcl_end:end_stone" then
mcl_structures.call_struct(p, "end_exit_portal") mcl_structures.call_struct(p, "end_exit_portal")
built = true return
break
end end
end end
if not built then mcl_structures.call_struct(END_EXIT_PORTAL_POS, "end_exit_portal")
mcl_structures.call_struct(END_EXIT_PORTAL_POS, "end_exit_portal")
end
end end
end end
-- Buffers for LuaVoxelManip -- Buffers for LuaVoxelManip
local lvm_buffer = {} -- local lvm_buffer = {}
local lvm_buffer_param2 = {} -- local lvm_buffer_param2 = {}
-- Generate tree decorations in the bounding box. This adds: -- Generate tree decorations in the bounding box. This adds:
-- * Cocoa at jungle trees -- * Cocoa at jungle trees
-- * Jungle tree vines -- * Jungle tree vines
-- * Oak vines in swamplands -- * Oak vines in swamplands
local function generate_tree_decorations(minp, maxp, seed, data, param2_data, area, biomemap, lvm_used) local function generate_tree_decorations(minp, maxp, seed, data, param2_data, area, biomemap, lvm_used, pr)
if maxp.y < 0 then if maxp.y < 0 then
return lvm_used return lvm_used
end end
@ -1576,7 +1609,7 @@ local function generate_tree_decorations(minp, maxp, seed, data, param2_data, ar
if minetest.find_node_near(pos, 1, {"mcl_core:jungleleaves"}) then if minetest.find_node_near(pos, 1, {"mcl_core:jungleleaves"}) then
dir = math.random(1, cocoachance) dir = pr:next(1, cocoachance)
if dir == 1 then if dir == 1 then
pos.z = pos.z + 1 pos.z = pos.z + 1
@ -1594,7 +1627,7 @@ local function generate_tree_decorations(minp, maxp, seed, data, param2_data, ar
if dir < 5 if dir < 5
and data[p_pos] == c_air and data[p_pos] == c_air
and l ~= nil and l > 12 then and l ~= nil and l > 12 then
local c = math.random(1, 3) local c = pr:next(1, 3)
if c == 1 then if c == 1 then
data[p_pos] = c_cocoa_1 data[p_pos] = c_cocoa_1
elseif c == 2 then elseif c == 2 then
@ -1810,125 +1843,206 @@ local generate_nether_decorations = function(minp, maxp, seed)
end end
-- Below the bedrock, generate air/void minetest.register_on_generated(function(minp, maxp, blockseed)
minetest.register_on_generated(function(minp, maxp, seed) add_chunk(minp)
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip") local p1, p2 = {x=minp.x, y=minp.y, z=minp.z}, {x=maxp.x, y=maxp.y, z=maxp.z}
local data = vm:get_data(lvm_buffer) if lvm > 0 then
local param2_data = vm:get_param2_data(lvm_buffer_param2) local lvm_used, shadow = false, false
local area = VoxelArea:new({MinEdge=emin, MaxEdge=emax}) local lb = {} -- buffer
local aream = VoxelArea:new({MinEdge={x=minp.x, y=0, z=minp.z}, MaxEdge={x=maxp.x, y=0, z=maxp.z}}) local lb2 = {} -- param2
local lvm_used = false local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
local biomemap local e1, e2 = {x=emin.x, y=emin.y, z=emin.z}, {x=emax.x, y=emax.y, z=emax.z}
local data2
local data = vm:get_data(lb)
if param2 > 0 then
data2 = vm:get_param2_data(lb2)
end
local area = VoxelArea:new({MinEdge=e1, MaxEdge=e2})
local ymin, ymax for _, rec in pairs(mcl_mapgen_core.registered_generators) do
if rec.vf then
local lvm_used0, shadow0 = rec.vf(vm, data, data2, e1, e2, area, p1, p2, blockseed)
if lvm_used0 then
lvm_used = true
end
if shadow0 then
shadow = true
end
end
end
-- Generate basic layer-based nodes: void, bedrock, realm barrier, lava seas, etc. if lvm_used then
-- Also perform some basic node replacements. -- Write stuff
vm:set_data(data)
if param2 > 0 then
vm:set_param2_data(data2)
end
vm:calc_lighting(p1, p2, shadow)
vm:write_to_map()
vm:update_liquids()
end
end
-- Helper function to set all nodes in the layers between min and max. if nodes > 0 then
-- content_id: Node to set for _, rec in pairs(mcl_mapgen_core.registered_generators) do
-- check: optional. if rec.nf then
-- If content_id, node will be set only if it is equal to check. rec.nf(p1, p2, blockseed)
-- If function(pos_to_check, content_id_at_this_pos), will set node only if returns true. end
-- min, max: Minimum and maximum Y levels of the layers to set end
-- minp, maxp: minp, maxp of the on_generated end
-- lvm_used: Set to true if any node in this on_generated has been set before.
-- -- add_chunk(minp)
-- returns true if any node was set and lvm_used otherwise end)
local function set_layers(content_id, check, min, max, minp, maxp, lvm_used)
if (maxp.y >= min and minp.y <= max) then minetest.register_on_generated=function(node_function)
for y = math.max(min, minp.y), math.min(max, maxp.y) do mcl_mapgen_core.register_generator("mod_"..tostring(#mcl_mapgen_core.registered_generators+1), nil, node_function)
for x = minp.x, maxp.x do end
for z = minp.z, maxp.z do
local p_pos = area:index(x, y, z) function mcl_mapgen_core.register_generator(id, lvm_function, node_function, priority, needs_param2)
if check then if not id then return end
if type(check) == "function" and check({x=x,y=y,z=z}, data[p_pos]) then
data[p_pos] = content_id local priority = priority or 5000
lvm_used = true
elseif check == data[p_pos] then if lvm_function then lvm = lvm + 1 end
data[p_pos] = content_id if lvm_function then nodes = nodes + 1 end
lvm_used = true if needs_param2 then param2 = param2 + 1 end
end
else local new_record = {
i = priority,
vf = lvm_function,
nf = node_function,
needs_param2 = needs_param2,
}
mcl_mapgen_core.registered_generators[id] = new_record
table.sort(
mcl_mapgen_core.registered_generators,
function(a, b)
return (a.i < b.i) or ((a.i == b.i) and (a.vf ~= nil) and (b.vf == nil))
end)
end
function mcl_mapgen_core.unregister_generator(id)
if not mcl_mapgen_core.registered_generators[id] then return end
local rec = mcl_mapgen_core.registered_generators[id]
mcl_mapgen_core.registered_generators[id] = nil
if rec.vf then lvm = lvm - 1 end
if rev.nf then nodes = nodes - 1 end
if rec.needs_param2 then param2 = param2 - 1 end
if rec.needs_level0 then level0 = level0 - 1 end
end
-- Generate basic layer-based nodes: void, bedrock, realm barrier, lava seas, etc.
-- Also perform some basic node replacements.
local bedrock_check
if mcl_vars.mg_bedrock_is_rough then
bedrock_check = function(pos, _, pr)
local y = pos.y
-- Bedrock layers with increasing levels of roughness, until a perfecly flat bedrock later at the bottom layer
-- This code assumes a bedrock height of 5 layers.
local diff = mcl_vars.mg_bedrock_overworld_max - y -- Overworld bedrock
local ndiff1 = mcl_vars.mg_bedrock_nether_bottom_max - y -- Nether bedrock, bottom
local ndiff2 = mcl_vars.mg_bedrock_nether_top_max - y -- Nether bedrock, ceiling
local top
if diff == 0 or ndiff1 == 0 or ndiff2 == 4 then
-- 50% bedrock chance
top = 2
elseif diff == 1 or ndiff1 == 1 or ndiff2 == 3 then
-- 66.666...%
top = 3
elseif diff == 2 or ndiff1 == 2 or ndiff2 == 2 then
-- 75%
top = 4
elseif diff == 3 or ndiff1 == 3 or ndiff2 == 1 then
-- 90%
top = 10
elseif diff == 4 or ndiff1 == 4 or ndiff2 == 0 then
-- 100%
return true
else
-- Not in bedrock layer
return false
end
return pr:next(1, top) <= top-1
end
end
-- Helper function to set all nodes in the layers between min and max.
-- content_id: Node to set
-- check: optional.
-- If content_id, node will be set only if it is equal to check.
-- If function(pos_to_check, content_id_at_this_pos), will set node only if returns true.
-- min, max: Minimum and maximum Y levels of the layers to set
-- minp, maxp: minp, maxp of the on_generated
-- lvm_used: Set to true if any node in this on_generated has been set before.
--
-- returns true if any node was set and lvm_used otherwise
local function set_layers(data, area, content_id, check, min, max, minp, maxp, lvm_used, pr)
if (maxp.y >= min and minp.y <= max) then
for y = math.max(min, minp.y), math.min(max, maxp.y) do
for x = minp.x, maxp.x do
for z = minp.z, maxp.z do
local p_pos = area:index(x, y, z)
if check then
if type(check) == "function" and check({x=x,y=y,z=z}, data[p_pos], pr) then
data[p_pos] = content_id
lvm_used = true
elseif check == data[p_pos] then
data[p_pos] = content_id data[p_pos] = content_id
lvm_used = true lvm_used = true
end end
else
data[p_pos] = content_id
lvm_used = true
end end
end end
end end
end end
return lvm_used
end end
return lvm_used
end
-- Below the bedrock, generate air/void
local function basic(vm, data, data2, emin, emax, area, minp, maxp, blockseed)
local biomemap, ymin, ymax
local lvm_used = false
local pr = PseudoRandom(blockseed)
-- The Void -- The Void
lvm_used = set_layers(c_void, nil, -31000, mcl_vars.mg_nether_min-1, minp, maxp, lvm_used) lvm_used = set_layers(data, area, c_void , nil, mcl_vars.mapgen_edge_min , mcl_vars.mg_nether_min -1, minp, maxp, lvm_used, pr)
lvm_used = set_layers(c_void, nil, mcl_vars.mg_nether_max+1, mcl_vars.mg_end_min-1, minp, maxp, lvm_used) lvm_used = set_layers(data, area, c_void , nil, mcl_vars.mg_nether_max +1, mcl_vars.mg_end_min -1, minp, maxp, lvm_used, pr)
lvm_used = set_layers(c_void, nil, mcl_vars.mg_end_max+1, mcl_vars.mg_realm_barrier_overworld_end_min-1, minp, maxp, lvm_used) lvm_used = set_layers(data, area, c_void , nil, mcl_vars.mg_end_max +1, mcl_vars.mg_realm_barrier_overworld_end_min-1, minp, maxp, lvm_used, pr)
lvm_used = set_layers(c_void, nil, mcl_vars.mg_realm_barrier_overworld_end_max+1, mcl_vars.mg_overworld_min-1, minp, maxp, lvm_used)
-- Realm barrier between the Overworld void and the End -- Realm barrier between the Overworld void and the End
lvm_used = set_layers(c_realm_barrier, nil, mcl_vars.mg_realm_barrier_overworld_end_min, mcl_vars.mg_realm_barrier_overworld_end_max, minp, maxp, lvm_used) lvm_used = set_layers(data, area, c_realm_barrier, nil, mcl_vars.mg_realm_barrier_overworld_end_min , mcl_vars.mg_realm_barrier_overworld_end_max , minp, maxp, lvm_used, pr)
lvm_used = set_layers(data, area, c_void , nil, mcl_vars.mg_realm_barrier_overworld_end_max+1, mcl_vars.mg_overworld_min -1, minp, maxp, lvm_used, pr)
if mg_name ~= "singlenode" then if mg_name ~= "singlenode" then
-- Bedrock -- Bedrock
local bedrock_check lvm_used = set_layers(data, area, c_bedrock, bedrock_check, mcl_vars.mg_bedrock_overworld_min, mcl_vars.mg_bedrock_overworld_max, minp, maxp, lvm_used, pr)
if mcl_vars.mg_bedrock_is_rough then lvm_used = set_layers(data, area, c_bedrock, bedrock_check, mcl_vars.mg_bedrock_nether_bottom_min, mcl_vars.mg_bedrock_nether_bottom_max, minp, maxp, lvm_used, pr)
bedrock_check = function(pos) lvm_used = set_layers(data, area, c_bedrock, bedrock_check, mcl_vars.mg_bedrock_nether_top_min, mcl_vars.mg_bedrock_nether_top_max, minp, maxp, lvm_used, pr)
local y = pos.y
-- Bedrock layers with increasing levels of roughness, until a perfecly flat bedrock later at the bottom layer
-- This code assumes a bedrock height of 5 layers.
local diff = mcl_vars.mg_bedrock_overworld_max - y -- Overworld bedrock
local ndiff1 = mcl_vars.mg_bedrock_nether_bottom_max - y -- Nether bedrock, bottom
local ndiff2 = mcl_vars.mg_bedrock_nether_top_max - y -- Nether bedrock, ceiling
local top
if diff == 0 or ndiff1 == 0 or ndiff2 == 4 then
-- 50% bedrock chance
top = 2
elseif diff == 1 or ndiff1 == 1 or ndiff2 == 3 then
-- 66.666...%
top = 3
elseif diff == 2 or ndiff1 == 2 or ndiff2 == 2 then
-- 75%
top = 4
elseif diff == 3 or ndiff1 == 3 or ndiff2 == 1 then
-- 90%
top = 10
elseif diff == 4 or ndiff1 == 4 or ndiff2 == 0 then
-- 100%
return true
else
-- Not in bedrock layer
return false
end
return math.random(1, top) <= top-1
end
else
bedrock_check = nil
end
lvm_used = set_layers(c_bedrock, bedrock_check, mcl_vars.mg_bedrock_overworld_min, mcl_vars.mg_bedrock_overworld_max, minp, maxp, lvm_used)
lvm_used = set_layers(c_bedrock, bedrock_check, mcl_vars.mg_bedrock_nether_bottom_min, mcl_vars.mg_bedrock_nether_bottom_max, minp, maxp, lvm_used)
lvm_used = set_layers(c_bedrock, bedrock_check, mcl_vars.mg_bedrock_nether_top_min, mcl_vars.mg_bedrock_nether_top_max, minp, maxp, lvm_used)
-- Flat Nether -- Flat Nether
if mg_name == "flat" then if mg_name == "flat" then
lvm_used = set_layers(c_air, nil, mcl_vars.mg_flat_nether_floor, mcl_vars.mg_flat_nether_ceiling, minp, maxp, lvm_used) lvm_used = set_layers(data, area, c_air, nil, mcl_vars.mg_flat_nether_floor, mcl_vars.mg_flat_nether_ceiling, minp, maxp, lvm_used, pr)
end end
-- Big lava seas by replacing air below a certain height -- Big lava seas by replacing air below a certain height
if mcl_vars.mg_lava then if mcl_vars.mg_lava then
lvm_used = set_layers(c_lava, c_air, mcl_vars.mg_overworld_min, mcl_vars.mg_lava_overworld_max, emin, emax, lvm_used) lvm_used = set_layers(data, area, c_lava, c_air, mcl_vars.mg_overworld_min, mcl_vars.mg_lava_overworld_max, emin, emax, lvm_used, pr)
lvm_used = set_layers(c_nether_lava, c_air, mcl_vars.mg_nether_min, mcl_vars.mg_lava_nether_max, emin, emax, lvm_used) lvm_used = set_layers(data, area, c_nether_lava, c_air, mcl_vars.mg_nether_min, mcl_vars.mg_lava_nether_max, emin, emax, lvm_used, pr)
end end
-- Clay, vines, cocoas -- Clay, vines, cocoas
lvm_used = generate_clay(minp, maxp, seed, data, area, lvm_used) lvm_used = generate_clay(minp, maxp, blockseed, data, area, lvm_used)
biomemap = minetest.get_mapgen_object("biomemap") biomemap = minetest.get_mapgen_object("biomemap")
lvm_used = generate_tree_decorations(minp, maxp, seed, data, param2_data, area, biomemap, lvm_used) lvm_used = generate_tree_decorations(minp, maxp, blockseed, data, data2, area, biomemap, lvm_used, pr)
----- Interactive block fixing section ----- ----- Interactive block fixing section -----
----- The section to perform basic block overrides of the core mapgen generated world. ----- ----- The section to perform basic block overrides of the core mapgen generated world. -----
@ -1974,19 +2088,21 @@ minetest.register_on_generated(function(minp, maxp, seed)
-- Set param2 (=color) of grass blocks. -- Set param2 (=color) of grass blocks.
-- Clear snowy grass blocks without snow above to ensure consistency. -- Clear snowy grass blocks without snow above to ensure consistency.
local nodes = minetest.find_nodes_in_area(minp, maxp, {"mcl_core:dirt_with_grass", "mcl_core:dirt_with_grass_snow"}) local nodes = minetest.find_nodes_in_area(minp, maxp, {"mcl_core:dirt_with_grass", "mcl_core:dirt_with_grass_snow"})
-- Flat area at y=0 to read biome 3 times faster than 5.3.0.get_biome_data(pos).biome: 43us vs 125us per iteration:
local aream = VoxelArea:new({MinEdge={x=minp.x, y=0, z=minp.z}, MaxEdge={x=maxp.x, y=0, z=maxp.z}})
for n=1, #nodes do for n=1, #nodes do
local p_pos = area:index(nodes[n].x, nodes[n].y, nodes[n].z) local n = nodes[n]
local p_pos_above = area:index(nodes[n].x, nodes[n].y+1, nodes[n].z) local p_pos = area:index(n.x, n.y, n.z)
local p_pos_below = area:index(nodes[n].x, nodes[n].y-1, nodes[n].z) local p_pos_above = area:index(n.x, n.y+1, n.z)
local b_pos = aream:index(nodes[n].x, 0, nodes[n].z) local p_pos_below = area:index(n.x, n.y-1, n.z)
local b_pos = aream:index(n.x, 0, n.z)
local bn = minetest.get_biome_name(biomemap[b_pos]) local bn = minetest.get_biome_name(biomemap[b_pos])
if bn then if bn then
local biome = minetest.registered_biomes[bn] local biome = minetest.registered_biomes[bn]
if biome then if biome and biome._mcl_biome_type then
if biome._mcl_biome_type then data2[p_pos] = biome._mcl_palette_index
param2_data[p_pos] = biome._mcl_palette_index lvm_used = true
lvm_used = true
end
end end
end end
if data[p_pos] == c_dirt_with_grass_snow and p_pos_above and data[p_pos_above] ~= c_top_snow and data[p_pos_above] ~= c_snow_block then if data[p_pos] == c_dirt_with_grass_snow and p_pos_above and data[p_pos_above] ~= c_top_snow and data[p_pos_above] ~= c_snow_block then
@ -1994,6 +2110,7 @@ minetest.register_on_generated(function(minp, maxp, seed)
lvm_used = true lvm_used = true
end end
end end
end end
-- Nether block fixes: -- Nether block fixes:
@ -2025,27 +2142,28 @@ minetest.register_on_generated(function(minp, maxp, seed)
-- * Remove stone, sand, dirt in v6 so our End map generator works in v6. -- * Remove stone, sand, dirt in v6 so our End map generator works in v6.
-- * Generate spawn platform (End portal destination) -- * Generate spawn platform (End portal destination)
elseif emin.y <= mcl_vars.mg_end_max and emax.y >= mcl_vars.mg_end_min then elseif emin.y <= mcl_vars.mg_end_max and emax.y >= mcl_vars.mg_end_min then
local nodes local nodes, node
if mg_name == "v6" then if mg_name == "v6" then
nodes = minetest.find_nodes_in_area(emin, emax, {"mcl_core:water_source", "mcl_core:stone", "mcl_core:sand", "mcl_core:dirt"}) nodes = minetest.find_nodes_in_area(emin, emax, {"mcl_core:water_source", "mcl_core:stone", "mcl_core:sand", "mcl_core:dirt"})
else else
nodes = minetest.find_nodes_in_area(emin, emax, {"mcl_core:water_source"}) nodes = minetest.find_nodes_in_area(emin, emax, {"mcl_core:water_source"})
end end
for n=1, #nodes do if #nodes > 0 then
local y = nodes[n].y lvm_used = true
local p_pos = area:index(nodes[n].x, y, nodes[n].z) for n=1, #nodes do
node = nodes[n]
if data[p_pos] == c_water or data[p_pos] == c_stone or data[p_pos] == c_dirt or data[p_pos] == c_sand then data[area:index(node.x, node.y, node.z)] = c_air
data[p_pos] = c_air
lvm_used = true
end end
end end
-- Obsidian spawn platform -- Obsidian spawn platform
if minp.y <= mcl_vars.mg_end_platform_pos.y and maxp.y >= mcl_vars.mg_end_platform_pos.y and if minp.y <= mcl_vars.mg_end_platform_pos.y and maxp.y >= mcl_vars.mg_end_platform_pos.y and
minp.x <= mcl_vars.mg_end_platform_pos.x and maxp.x >= mcl_vars.mg_end_platform_pos.z and minp.x <= mcl_vars.mg_end_platform_pos.x and maxp.x >= mcl_vars.mg_end_platform_pos.z and
minp.z <= mcl_vars.mg_end_platform_pos.z and maxp.z >= mcl_vars.mg_end_platform_pos.z then minp.z <= mcl_vars.mg_end_platform_pos.z and maxp.z >= mcl_vars.mg_end_platform_pos.z then
local pos1 = {x = math.max(minp.x, mcl_vars.mg_end_platform_pos.x-2), y = math.max(minp.y, mcl_vars.mg_end_platform_pos.y), z = math.max(minp.z, mcl_vars.mg_end_platform_pos.z-2)}
local pos2 = {x = math.min(maxp.x, mcl_vars.mg_end_platform_pos.x+2), y = math.min(maxp.y, mcl_vars.mg_end_platform_pos.y+2), z = math.min(maxp.z, mcl_vars.mg_end_platform_pos.z+2)}
for x=math.max(minp.x, mcl_vars.mg_end_platform_pos.x-2), math.min(maxp.x, mcl_vars.mg_end_platform_pos.x+2) do for x=math.max(minp.x, mcl_vars.mg_end_platform_pos.x-2), math.min(maxp.x, mcl_vars.mg_end_platform_pos.x+2) do
for z=math.max(minp.z, mcl_vars.mg_end_platform_pos.z-2), math.min(maxp.z, mcl_vars.mg_end_platform_pos.z+2) do for z=math.max(minp.z, mcl_vars.mg_end_platform_pos.z-2), math.min(maxp.z, mcl_vars.mg_end_platform_pos.z+2) do
for y=math.max(minp.y, mcl_vars.mg_end_platform_pos.y), math.min(maxp.y, mcl_vars.mg_end_platform_pos.y+2) do for y=math.max(minp.y, mcl_vars.mg_end_platform_pos.y), math.min(maxp.y, mcl_vars.mg_end_platform_pos.y+2) do
@ -2065,7 +2183,7 @@ minetest.register_on_generated(function(minp, maxp, seed)
-- Final hackery: Set sun light level in the End. -- Final hackery: Set sun light level in the End.
-- -26912 is at a mapchunk border. -- -26912 is at a mapchunk border.
local shadow local shadow = true
if minp.y >= -26912 and maxp.y <= mcl_vars.mg_end_max then if minp.y >= -26912 and maxp.y <= mcl_vars.mg_end_max then
vm:set_lighting({day=15, night=15}) vm:set_lighting({day=15, night=15})
lvm_used = true lvm_used = true
@ -2075,21 +2193,60 @@ minetest.register_on_generated(function(minp, maxp, seed)
lvm_used = true lvm_used = true
end end
-- Write stuff
if lvm_used then
vm:set_data(data)
vm:set_param2_data(param2_data)
vm:calc_lighting(nil, nil, shadow)
vm:write_to_map()
vm:update_liquids()
end
if mg_name ~= "singlenode" then if mg_name ~= "singlenode" then
-- Generate special decorations -- Generate special decorations
generate_underground_mushrooms(minp, maxp, seed) generate_underground_mushrooms(minp, maxp, blockseed)
generate_nether_decorations(minp, maxp, seed) generate_nether_decorations(minp, maxp, blockseed)
generate_structures(minp, maxp, seed, biomemap) generate_structures(minp, maxp, blockseed, biomemap)
end end
end)
return lvm_used, shadow
end
mcl_mapgen_core.register_generator("main", basic, nil, 1, true)
-- "Trivial" (actually NOT) function to just read the node and some stuff to not just return "ignore", like 5.3.0 does.
-- p: Position, if it's wrong, {name="error"} node will return.
-- force: optional (default: false) - Do the maximum to still read the node within us_timeout.
-- us_timeout: optional (default: 244 = 0.000244 s = 1/80/80/80), set it at least to 3000000 to let mapgen to finish its job.
--
-- returns node definition, eg. {name="air"}. Unfortunately still can return {name="ignore"}.
function mcl_mapgen_core.get_node(p, force, us_timeout)
-- check initial circumstances
if not p or not p.x or not p.y or not p.z then return {name="error"} end
-- try common way
local node = minetest.get_node(p)
if node.name ~= "ignore" then
return node
end
-- copy table to get sure it won't changed by other threads
local pos = {x=p.x,y=p.y,z=p.z}
-- try LVM
minetest.get_voxel_manip():read_from_map(pos, pos)
node = minetest.get_node(pos)
if node.name ~= "ignore" or not force then
return node
end
-- all ways failed - need to emerge (or forceload if generated)
local us_timeout = us_timeout or 244
if mcl_mapgen_core.is_generated(pos) then
minetest.forceload_block(pos)
else
minetest.emerge_area(pos, pos)
end
local t = minetest.get_us_time()
node = minetest.get_node(pos)
while (not node or node.name == "ignore") and (minetest.get_us_time() - t < us_timeout) do
node = minetest.get_node(pos)
end
return node
-- it still can return "ignore", LOL, even if force = true, but only after time out
end

@ -67,7 +67,8 @@ local init_strongholds = function()
end end
-- Stronghold generation for register_on_generated. -- Stronghold generation for register_on_generated.
local generate_strongholds = function(minp, maxp) local generate_strongholds = function(minp, maxp, blockseed)
local pr = PseudoRandom(blockseed)
for s=1, #strongholds do for s=1, #strongholds do
if not strongholds[s].generated then if not strongholds[s].generated then
local pos = strongholds[s].pos local pos = strongholds[s].pos
@ -80,6 +81,12 @@ local generate_strongholds = function(minp, maxp)
if pos.x + 6 > maxp.x then if pos.x + 6 > maxp.x then
pos.x = maxp.x - 7 pos.x = maxp.x - 7
end end
if pos.y - 4 < minp.y then
pos.y = minp.y + 5
end
if pos.y + 4 > maxp.y then
pos.y = maxp.y - 5
end
if pos.z - 6 < minp.z then if pos.z - 6 < minp.z then
pos.z = minp.z + 7 pos.z = minp.z + 7
end end
@ -87,7 +94,7 @@ local generate_strongholds = function(minp, maxp)
pos.z = maxp.z - 7 pos.z = maxp.z - 7
end end
mcl_structures.call_struct(pos, "end_portal_shrine") mcl_structures.call_struct(pos, "end_portal_shrine", nil, pr)
strongholds[s].generated = true strongholds[s].generated = true
end end
end end
@ -96,9 +103,4 @@ end
init_strongholds() init_strongholds()
--[[ Note this mod depends on mcl_mapgen_core to make sure the core mapgen runs FIRST. mcl_mapgen_core.register_generator("strongholds", nil, generate_strongholds, 999999)
This is important because we need this to make sure the stronghold isn't instantly
overwritten by the core mapgen (since it uses LuaVoxelManip). ]]
minetest.register_on_generated(function(minp, maxp, blockseed)
generate_strongholds(minp, maxp)
end)

@ -1,24 +1,58 @@
local S = minetest.get_translator("mcl_structures") local S = minetest.get_translator("mcl_structures")
mcl_structures ={} mcl_structures ={}
local rotations = {
"0",
"90",
"180",
"270"
}
mcl_structures.minetest_place_schematic = minetest.place_schematic
local function ecb_place(blockpos, action, calls_remaining, param)
if calls_remaining >= 1 then return end
mcl_structures.minetest_place_schematic(param.pos, param.schematic, param.rotation, param.replacements, param.force_placement, param.flags)
if param.after_placement_callback and param.p1 and param.p2 then
param.after_placement_callback(param.p1, param.p2, param.size, param.rotation, param.pr)
end
end
minetest.place_schematic = function(pos, schematic, rotation, replacements, force_placement, flags, after_placement_callback, pr)
local s = loadstring(minetest.serialize_schematic(schematic, "lua", {lua_use_comments = false, lua_num_indent_spaces = 0}) .. " return(schematic)")()
if s and s.size then
local x, z = s.size.x, s.size.z
if rotation then
if rotation == "random" and pr then
rotation = rotations[pr:next(1,#rotations)]
end
if rotation == "random" then
x = math.max(x, z)
z = x
elseif rotation == "90" or rotation == "270" then
x, z = z, x
end
end
local p1 = {x=pos.x , y=pos.y , z=pos.z }
local p2 = {x=pos.x+x-1, y=pos.y+s.size.y-1, z=pos.z+z-1}
minetest.log("verbose","[mcl_structures] size=" ..minetest.pos_to_string(s.size) .. ", rotation=" .. tostring(rotation) .. ", emerge from "..minetest.pos_to_string(p1) .. " to " .. minetest.pos_to_string(p2))
local param = {pos=vector.new(pos), schematic=s, rotation=rotation, replacements=replacements, force_placement=force_placement, flags=flags, p1=p1, p2=p2, after_placement_callback = after_placement_callback, size=vector.new(s.size), pr=pr}
minetest.emerge_area(p1, p2, ecb_place, param)
end
end
mcl_structures.place_schematic = minetest.place_schematic -- for direct usage
mcl_structures.get_struct = function(file) mcl_structures.get_struct = function(file)
local localfile = minetest.get_modpath("mcl_structures").."/schematics/"..file local localfile = minetest.get_modpath("mcl_structures").."/schematics/"..file
local file, errorload = io.open(localfile, "rb") local file, errorload = io.open(localfile, "rb")
if errorload ~= nil then if errorload ~= nil then
minetest.log("error", '[mcl_structures] Could not open this struct: ' .. localfile) minetest.log("error", '[mcl_structures] Could not open this struct: ' .. localfile)
return nil return nil
end end
local allnode = file:read("*a") local allnode = file:read("*a")
file:close() file:close()
return allnode return allnode
end end
local mapseed = tonumber(minetest.get_mapgen_setting("seed"))
-- Random number generator for all generated structures
local pr = PseudoRandom(mapseed)
-- Call on_construct on pos. -- Call on_construct on pos.
-- Useful to init chests from formspec. -- Useful to init chests from formspec.
local init_node_construct = function(pos) local init_node_construct = function(pos)
@ -32,16 +66,17 @@ local init_node_construct = function(pos)
end end
-- The call of Struct -- The call of Struct
mcl_structures.call_struct = function(pos, struct_style, rotation) mcl_structures.call_struct = function(pos, struct_style, rotation, pr)
minetest.log("action","[mcl_structures] call_struct " .. struct_style.." at "..minetest.pos_to_string(pos))
if not rotation then if not rotation then
rotation = "random" rotation = "random"
end end
if struct_style == "desert_temple" then if struct_style == "desert_temple" then
return mcl_structures.generate_desert_temple(pos, rotation) return mcl_structures.generate_desert_temple(pos, rotation, pr)
elseif struct_style == "desert_well" then elseif struct_style == "desert_well" then
return mcl_structures.generate_desert_well(pos, rotation) return mcl_structures.generate_desert_well(pos, rotation)
elseif struct_style == "igloo" then elseif struct_style == "igloo" then
return mcl_structures.generate_igloo(pos, rotation) return mcl_structures.generate_igloo(pos, rotation, pr)
elseif struct_style == "witch_hut" then elseif struct_style == "witch_hut" then
return mcl_structures.generate_witch_hut(pos, rotation) return mcl_structures.generate_witch_hut(pos, rotation)
elseif struct_style == "ice_spike_small" then elseif struct_style == "ice_spike_small" then
@ -49,13 +84,13 @@ mcl_structures.call_struct = function(pos, struct_style, rotation)
elseif struct_style == "ice_spike_large" then elseif struct_style == "ice_spike_large" then
return mcl_structures.generate_ice_spike_large(pos, rotation) return mcl_structures.generate_ice_spike_large(pos, rotation)
elseif struct_style == "boulder" then elseif struct_style == "boulder" then
return mcl_structures.generate_boulder(pos, rotation) return mcl_structures.generate_boulder(pos, rotation, pr)
elseif struct_style == "fossil" then elseif struct_style == "fossil" then
return mcl_structures.generate_fossil(pos, rotation) return mcl_structures.generate_fossil(pos, rotation, pr)
elseif struct_style == "end_exit_portal" then elseif struct_style == "end_exit_portal" then
return mcl_structures.generate_end_exit_portal(pos, rotation) return mcl_structures.generate_end_exit_portal(pos, rotation)
elseif struct_style == "end_portal_shrine" then elseif struct_style == "end_portal_shrine" then
return mcl_structures.generate_end_portal_shrine(pos, rotation) return mcl_structures.generate_end_portal_shrine(pos, rotation, pr)
end end
end end
@ -65,12 +100,12 @@ mcl_structures.generate_desert_well = function(pos)
return minetest.place_schematic(newpos, path, "0", nil, true) return minetest.place_schematic(newpos, path, "0", nil, true)
end end
mcl_structures.generate_igloo = function(pos) mcl_structures.generate_igloo = function(pos, rotation, pr)
-- Place igloo -- Place igloo
local success, rotation = mcl_structures.generate_igloo_top(pos) local success, rotation = mcl_structures.generate_igloo_top(pos, pr)
-- Place igloo basement with 50% chance -- Place igloo basement with 50% chance
local r = math.random(1,2) local r = pr:next(1,2)
if success and r == 1 then if r == 1 then
-- Select basement depth -- Select basement depth
local dim = mcl_worlds.pos_to_dimension(pos) local dim = mcl_worlds.pos_to_dimension(pos)
local buffer = pos.y - (mcl_vars.mg_lava_overworld_max + 10) local buffer = pos.y - (mcl_vars.mg_lava_overworld_max + 10)
@ -86,7 +121,7 @@ mcl_structures.generate_igloo = function(pos)
if buffer <= 19 then if buffer <= 19 then
return success return success
end end
local depth = math.random(19, buffer) local depth = pr:next(19, buffer)
local bpos = {x=pos.x, y=pos.y-depth, z=pos.z} local bpos = {x=pos.x, y=pos.y-depth, z=pos.z}
-- trapdoor position -- trapdoor position
local tpos local tpos
@ -111,8 +146,8 @@ mcl_structures.generate_igloo = function(pos)
return success return success
end end
local set_brick = function(pos) local set_brick = function(pos)
local c = math.random(1, 3) -- cracked chance local c = pr:next(1, 3) -- cracked chance
local m = math.random(1, 10) -- chance for monster egg local m = pr:next(1, 10) -- chance for monster egg
local brick local brick
if m == 1 then if m == 1 then
if c == 1 then if c == 1 then
@ -155,75 +190,74 @@ mcl_structures.generate_igloo = function(pos)
minetest.set_node({x=tpos.x,y=tpos.y-y,z=tpos.z}, {name="mcl_core:ladder", param2=ladder_param2}) minetest.set_node({x=tpos.x,y=tpos.y-y,z=tpos.z}, {name="mcl_core:ladder", param2=ladder_param2})
end end
-- Place basement -- Place basement
mcl_structures.generate_igloo_basement(bpos, rotation) mcl_structures.generate_igloo_basement(bpos, rotation, pr)
end end
return success return success
end end
mcl_structures.generate_igloo_top = function(pos) mcl_structures.generate_igloo_top = function(pos, pr)
-- FIXME: This spawns bookshelf instead of furnace. Fix this! -- FIXME: This spawns bookshelf instead of furnace. Fix this!
-- Furnace does ot work atm because apparently meta is not set. :-( -- Furnace does ot work atm because apparently meta is not set. :-(
local newpos = {x=pos.x,y=pos.y-1,z=pos.z} local newpos = {x=pos.x,y=pos.y-1,z=pos.z}
local path = minetest.get_modpath("mcl_structures").."/schematics/mcl_structures_igloo_top.mts" local path = minetest.get_modpath("mcl_structures").."/schematics/mcl_structures_igloo_top.mts"
local rotation = tostring(math.random(0,3)*90) local rotation = tostring(pr:next(0,3)*90)
return minetest.place_schematic(newpos, path, rotation, nil, true), rotation return minetest.place_schematic(newpos, path, rotation, nil, true), rotation
end end
mcl_structures.generate_igloo_basement = function(pos, orientation) local function igloo_placement_callback(p1, p2, size, orientation, pr)
local chest_offset
if orientation == "0" then
chest_offset = {x=5, y=1, z=5}
elseif orientation == "90" then
chest_offset = {x=5, y=1, z=3}
elseif orientation == "180" then
chest_offset = {x=3, y=1, z=1}
elseif orientation == "270" then
chest_offset = {x=1, y=1, z=5}
else
return
end
local size = {x=9,y=5,z=7}
local lootitems = mcl_loot.get_multi_loot({
{
stacks_min = 1,
stacks_max = 1,
items = {
{ itemstring = "mcl_core:apple_gold", weight = 1 },
}
},
{
stacks_min = 2,
stacks_max = 8,
items = {
{ itemstring = "mcl_core:coal_lump", weight = 15, amount_min = 1, amount_max = 4 },
{ itemstring = "mcl_core:apple", weight = 15, amount_min = 1, amount_max = 3 },
{ itemstring = "mcl_farming:wheat_item", weight = 10, amount_min = 2, amount_max = 3 },
{ itemstring = "mcl_core:gold_nugget", weight = 10, amount_min = 1, amount_max = 3 },
{ itemstring = "mcl_mobitems:rotten_flesh", weight = 10 },
{ itemstring = "mcl_tools:axe_stone", weight = 2 },
{ itemstring = "mcl_core:emerald", weight = 1 },
}
}}, pr)
local chest_pos = vector.add(p1, chest_offset)
init_node_construct(chest_pos)
local meta = minetest.get_meta(chest_pos)
local inv = meta:get_inventory()
mcl_loot.fill_inventory(inv, "main", lootitems)
end
mcl_structures.generate_igloo_basement = function(pos, orientation, pr)
-- TODO: Add brewing stand -- TODO: Add brewing stand
-- TODO: Add monster eggs -- TODO: Add monster eggs
-- TODO: Spawn villager and zombie villager -- TODO: Spawn villager and zombie villager
local path = minetest.get_modpath("mcl_structures").."/schematics/mcl_structures_igloo_basement.mts" local path = minetest.get_modpath("mcl_structures").."/schematics/mcl_structures_igloo_basement.mts"
mcl_structures.place_schematic(pos, path, orientation, nil, true, nil, igloo_placement_callback, pr)
local success = minetest.place_schematic(pos, path, orientation, nil, true)
if success then
local chest_offset
if orientation == "0" then
chest_offset = {x=5, y=1, z=5}
elseif orientation == "90" then
chest_offset = {x=5, y=1, z=3}
elseif orientation == "180" then
chest_offset = {x=3, y=1, z=1}
elseif orientation == "270" then
chest_offset = {x=1, y=1, z=5}
else
return success
end
local size = {x=9,y=5,z=7}
local lootitems = mcl_loot.get_multi_loot({
{
stacks_min = 1,
stacks_max = 1,
items = {
{ itemstring = "mcl_core:apple_gold", weight = 1 },
}
},
{
stacks_min = 2,
stacks_max = 8,
items = {
{ itemstring = "mcl_core:coal_lump", weight = 15, amount_min = 1, amount_max = 4 },
{ itemstring = "mcl_core:apple", weight = 15, amount_min = 1, amount_max = 3 },
{ itemstring = "mcl_farming:wheat_item", weight = 10, amount_min = 2, amount_max = 3 },
{ itemstring = "mcl_core:gold_nugget", weight = 10, amount_min = 1, amount_max = 3 },
{ itemstring = "mcl_mobitems:rotten_flesh", weight = 10 },
{ itemstring = "mcl_tools:axe_stone", weight = 2 },
{ itemstring = "mcl_core:emerald", weight = 1 },
}
}}, pr)
local chest_pos = vector.add(pos, chest_offset)
init_node_construct(chest_pos)
local meta = minetest.get_meta(chest_pos)
local inv = meta:get_inventory()
mcl_loot.fill_inventory(inv, "main", lootitems)
end
return success
end end
mcl_structures.generate_boulder = function(pos) mcl_structures.generate_boulder = function(pos, rotation, pr)
-- Choose between 2 boulder sizes (2×2×2 or 3×3×3) -- Choose between 2 boulder sizes (2×2×2 or 3×3×3)
local r = math.random(1, 10) local r = pr:next(1, 10)
local path local path
if r <= 3 then if r <= 3 then
path = minetest.get_modpath("mcl_structures").."/schematics/mcl_structures_boulder_small.mts" path = minetest.get_modpath("mcl_structures").."/schematics/mcl_structures_boulder_small.mts"
@ -235,9 +269,20 @@ mcl_structures.generate_boulder = function(pos)
return minetest.place_schematic(newpos, path) return minetest.place_schematic(newpos, path)
end end
local function hut_placement_callback(p1, p2, size, orientation, pr)
if not p1 or not p2 then return end
local legs = minetest.find_nodes_in_area(p1, p2, "mcl_core:tree")
for i = 1, #legs do
while minetest.get_item_group(mcl_mapgen_core.get_node({x=legs[i].x, y=legs[i].y-1, z=legs[i].z}, true, 333333).name, "water") ~= 0 do
legs[i].y = legs[i].y - 1
minetest.swap_node(legs[i], {name = "mcl_core:tree", param2 = 2})
end
end
end
mcl_structures.generate_witch_hut = function(pos, rotation) mcl_structures.generate_witch_hut = function(pos, rotation)
local path = minetest.get_modpath("mcl_structures").."/schematics/mcl_structures_witch_hut.mts" local path = minetest.get_modpath("mcl_structures").."/schematics/mcl_structures_witch_hut.mts"
return minetest.place_schematic(pos, path, rotation, nil, true) mcl_structures.place_schematic(pos, path, rotation, nil, true, nil, hut_placement_callback, pr)
end end
mcl_structures.generate_ice_spike_small = function(pos) mcl_structures.generate_ice_spike_small = function(pos)
@ -250,7 +295,7 @@ mcl_structures.generate_ice_spike_large = function(pos)
return minetest.place_schematic(pos, path, "random", nil, false) return minetest.place_schematic(pos, path, "random", nil, false)
end end
mcl_structures.generate_fossil = function(pos) mcl_structures.generate_fossil = function(pos, rotation, pr)
-- Generates one out of 8 possible fossil pieces -- Generates one out of 8 possible fossil pieces
local newpos = {x=pos.x,y=pos.y-1,z=pos.z} local newpos = {x=pos.x,y=pos.y-1,z=pos.z}
local fossils = { local fossils = {
@ -263,7 +308,7 @@ mcl_structures.generate_fossil = function(pos)
"mcl_structures_fossil_spine_3.mts", -- 7×4×13 "mcl_structures_fossil_spine_3.mts", -- 7×4×13
"mcl_structures_fossil_spine_4.mts", -- 8×5×13 "mcl_structures_fossil_spine_4.mts", -- 8×5×13
} }
local r = math.random(1, #fossils) local r = pr:next(1, #fossils)
local path = minetest.get_modpath("mcl_structures").."/schematics/"..fossils[r] local path = minetest.get_modpath("mcl_structures").."/schematics/"..fossils[r]
return minetest.place_schematic(newpos, path, "random", nil, true) return minetest.place_schematic(newpos, path, "random", nil, true)
end end
@ -273,24 +318,16 @@ mcl_structures.generate_end_exit_portal = function(pos)
return minetest.place_schematic(pos, path, "0", nil, true) return minetest.place_schematic(pos, path, "0", nil, true)
end end
local generate_end_portal_shrine_no_delay = function(newpos) local function shrine_placement_callback(p1, p2, size, rotation, pr)
local path = minetest.get_modpath("mcl_structures").."/schematics/mcl_structures_end_portal_room_simple.mts"
local size = {x=13, y=8, z=13}
local ret = minetest.place_schematic(newpos, path, "0", nil, true)
if ret == nil then
return ret
end
local area_start, area_end = newpos, vector.add(newpos, size)
-- Find and setup spawner with silverfish -- Find and setup spawner with silverfish
local spawners = minetest.find_nodes_in_area(area_start, area_end, "mcl_mobspawners:spawner") local spawners = minetest.find_nodes_in_area(p1, p2, "mcl_mobspawners:spawner")
for s=1, #spawners do for s=1, #spawners do
local meta = minetest.get_meta(spawners[s]) local meta = minetest.get_meta(spawners[s])
mcl_mobspawners.setup_spawner(spawners[s], "mobs_mc:silverfish") mcl_mobspawners.setup_spawner(spawners[s], "mobs_mc:silverfish")
end end
-- Shuffle stone brick types -- Shuffle stone brick types
local bricks = minetest.find_nodes_in_area(area_start, area_end, "mcl_core:stonebrick") local bricks = minetest.find_nodes_in_area(p1, p2, "mcl_core:stonebrick")
for b=1, #bricks do for b=1, #bricks do
local r_bricktype = pr:next(1, 100) local r_bricktype = pr:next(1, 100)
local r_infested = pr:next(1, 100) local r_infested = pr:next(1, 100)
@ -317,7 +354,7 @@ local generate_end_portal_shrine_no_delay = function(newpos)
end end
-- Also replace stairs -- Also replace stairs
local stairs = minetest.find_nodes_in_area(area_start, area_end, {"mcl_stairs:stair_stonebrick", "mcl_stairs:stair_stonebrick_outer", "mcl_stairs:stair_stonebrick_inner"}) local stairs = minetest.find_nodes_in_area(p1, p2, {"mcl_stairs:stair_stonebrick", "mcl_stairs:stair_stonebrick_outer", "mcl_stairs:stair_stonebrick_inner"})
for s=1, #stairs do for s=1, #stairs do
local stair = minetest.get_node(stairs[s]) local stair = minetest.get_node(stairs[s])
local r_type = pr:next(1, 100) local r_type = pr:next(1, 100)
@ -344,7 +381,7 @@ local generate_end_portal_shrine_no_delay = function(newpos)
end end
-- Randomly add ender eyes into end portal frames, but never fill the entire frame -- Randomly add ender eyes into end portal frames, but never fill the entire frame
local frames = minetest.find_nodes_in_area(area_start, area_end, "mcl_portals:end_portal_frame") local frames = minetest.find_nodes_in_area(p1, p2, "mcl_portals:end_portal_frame")
local eyes = 0 local eyes = 0
for f=1, #frames do for f=1, #frames do
local r_eye = pr:next(1, 10) local r_eye = pr:next(1, 10)
@ -357,40 +394,34 @@ local generate_end_portal_shrine_no_delay = function(newpos)
end end
end end
end end
return ret
end end
local function ecb_generate_end_portal_shrine(blockpos, action, calls_remaining, param) mcl_structures.generate_end_portal_shrine = function(pos, rotation, pr)
if calls_remaining <= 0 then local offset = {x=6, y=4, z=6}
generate_end_portal_shrine_no_delay({x=param.x, y=param.y, z=param.z})
end
end
mcl_structures.generate_end_portal_shrine = function(pos)
local offset = {x=6, y=8, z=6}
local size = {x=13, y=8, z=13} local size = {x=13, y=8, z=13}
local newpos = { x = pos.x - offset.x, y = pos.y, z = pos.z - offset.z } local newpos = { x = pos.x - offset.x, y = pos.y, z = pos.z - offset.z }
minetest.emerge_area(vector.subtract(newpos,10), vector.add(vector.add(newpos, size),10), ecb_generate_end_portal_shrine, {x=newpos.x, y=newpos.y, z=newpos.z})
local path = minetest.get_modpath("mcl_structures").."/schematics/mcl_structures_end_portal_room_simple.mts"
mcl_structures.place_schematic(newpos, path, "0", nil, true, nil, shrine_placement_callback, pr)
end end
mcl_structures.generate_desert_temple = function(pos) local function temple_placement_callback(p1, p2, size, rotation, pr)
-- No Generating for the temple ... Why using it ? No Change
local path = minetest.get_modpath("mcl_structures").."/schematics/mcl_structures_desert_temple.mts" -- Delete cacti leftovers:
local newpos = {x=pos.x,y=pos.y-12,z=pos.z} local cactus_nodes = minetest.find_nodes_in_area_under_air(p1, p2, "mcl_core:cactus")
local size = {x=22, y=24, z=22} if cactus_nodes and #cactus_nodes > 0 then
if newpos == nil then for _, pos in pairs(cactus_nodes) do
return local node_below = minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z})
end if node_below and node_below.name == "mcl_core:sandstone" then
local ret = minetest.place_schematic(newpos, path, "random", nil, true) minetest.swap_node(pos, {name="air"})
if ret == nil then end
return ret end
end end
-- Find chests. -- Find chests.
-- FIXME: Searching this large area just for the chets is not efficient. Need a better way to find the chests; -- FIXME: Searching this large area just for the chets is not efficient. Need a better way to find the chests;
-- probably let's just infer it from newpos because the schematic always the same. -- probably let's just infer it from newpos because the schematic always the same.
local chests = minetest.find_nodes_in_area({x=newpos.x-size.x, y=newpos.y, z=newpos.z-size.z}, vector.add(newpos, size), "mcl_chests:chest") local chests = minetest.find_nodes_in_area(p1, p2, "mcl_chests:chest")
-- Add desert temple loot into chests -- Add desert temple loot into chests
for c=1, #chests do for c=1, #chests do
@ -436,7 +467,7 @@ mcl_structures.generate_desert_temple = function(pos)
end end
-- Initialize pressure plates and randomly remove up to 5 plates -- Initialize pressure plates and randomly remove up to 5 plates
local pplates = minetest.find_nodes_in_area({x=newpos.x-size.x, y=newpos.y, z=newpos.z-size.z}, vector.add(newpos, size), "mesecons_pressureplates:pressure_plate_stone_off") local pplates = minetest.find_nodes_in_area(p1, p2, "mesecons_pressureplates:pressure_plate_stone_off")
local pplates_remove = 5 local pplates_remove = 5
for p=1, #pplates do for p=1, #pplates do
if pplates_remove > 0 and pr:next(1, 100) >= 50 then if pplates_remove > 0 and pr:next(1, 100) >= 50 then
@ -448,8 +479,17 @@ mcl_structures.generate_desert_temple = function(pos)
minetest.registered_nodes["mesecons_pressureplates:pressure_plate_stone_off"].on_construct(pplates[p]) minetest.registered_nodes["mesecons_pressureplates:pressure_plate_stone_off"].on_construct(pplates[p])
end end
end end
end
return ret mcl_structures.generate_desert_temple = function(pos, rotation, pr)
-- No Generating for the temple ... Why using it ? No Change
local path = minetest.get_modpath("mcl_structures").."/schematics/mcl_structures_desert_temple.mts"
local newpos = {x=pos.x,y=pos.y-12,z=pos.z}
local size = {x=22, y=24, z=22}
if newpos == nil then
return
end
minetest.place_schematic(newpos, path, "random", nil, true, nil, temple_placement_callback, pr)
end end
local registered_structures = {} local registered_structures = {}

@ -4,8 +4,8 @@
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
function settlements.build_schematic(vm, data, va, pos, building, replace_wall, name) function settlements.build_schematic(vm, data, va, pos, building, replace_wall, name)
-- get building node material for better integration to surrounding -- get building node material for better integration to surrounding
local platform_material = mcl_util.get_far_node(pos, true) local platform_material = mcl_mapgen_core.get_node(pos)
if not platform_material then if not platform_material or (platform_material.name == "air" or platform_material.name == "ignore") then
return return
end end
platform_material = platform_material.name platform_material = platform_material.name
@ -81,12 +81,15 @@ function settlements.create_site_plan(maxp, minp, pr)
local possible_rotations = {"0", "90", "180", "270"} local possible_rotations = {"0", "90", "180", "270"}
-- find center of chunk -- find center of chunk
local center = { local center = {
x=maxp.x-half_map_chunk_size, x=math.floor((minp.x+maxp.x)/2),
y=maxp.y, y=maxp.y,
z=maxp.z-half_map_chunk_size z=math.floor((minp.z+maxp.z)/2)
} }
-- find center_surface of chunk -- find center_surface of chunk
local center_surface , surface_material = settlements.find_surface(center) local center_surface , surface_material = settlements.find_surface(center, true)
local chunks = {}
chunks[mcl_vars.get_chunk_number(center)] = true
-- go build settlement around center -- go build settlement around center
if not center_surface then return false end if not center_surface then return false end
@ -114,49 +117,57 @@ function settlements.create_site_plan(maxp, minp, pr)
local x, z, r = center_surface.x, center_surface.z, building_all_info["hsize"] local x, z, r = center_surface.x, center_surface.z, building_all_info["hsize"]
-- draw j circles around center and increase radius by math.random(2,5) -- draw j circles around center and increase radius by math.random(2,5)
for j = 1,20 do for j = 1,20 do
if number_built < number_of_buildings then -- set position on imaginary circle
-- set position on imaginary circle for j = 0, 360, 15 do
for j = 0, 360, 15 do local angle = j * math.pi / 180
local angle = j * math.pi / 180 local ptx, ptz = x + r * math.cos( angle ), z + r * math.sin( angle )
local ptx, ptz = x + r * math.cos( angle ), z + r * math.sin( angle ) ptx = settlements.round(ptx, 0)
ptx = settlements.round(ptx, 0) ptz = settlements.round(ptz, 0)
ptz = settlements.round(ptz, 0) local pos1 = { x=ptx, y=center_surface.y+50, z=ptz}
local pos1 = { x=ptx, y=center_surface.y+50, z=ptz} local chunk_number = mcl_vars.get_chunk_number(pos1)
local pos_surface, surface_material = settlements.find_surface(pos1) local pos_surface, surface_material
if not pos_surface then break end if chunks[chunk_number] then
pos_surface, surface_material = settlements.find_surface(pos1)
else
chunks[chunk_number] = true
pos_surface, surface_material = settlements.find_surface(pos1, true)
end
if not pos_surface then break end
local randomized_schematic_table = shuffle(settlements.schematic_table, pr) local randomized_schematic_table = shuffle(settlements.schematic_table, pr)
-- pick schematic -- pick schematic
local size = #randomized_schematic_table local size = #randomized_schematic_table
for i = size, 1, -1 do for i = size, 1, -1 do
-- already enough buildings of that type? -- already enough buildings of that type?
if count_buildings[randomized_schematic_table[i]["name"]] < randomized_schematic_table[i]["max_num"]*number_of_buildings then if count_buildings[randomized_schematic_table[i]["name"]] < randomized_schematic_table[i]["max_num"]*number_of_buildings then
building_all_info = randomized_schematic_table[i] building_all_info = randomized_schematic_table[i]
-- check distance to other buildings -- check distance to other buildings
local distance_to_other_buildings_ok = settlements.check_distance(settlement_info, pos_surface, building_all_info["hsize"]) local distance_to_other_buildings_ok = settlements.check_distance(settlement_info, pos_surface, building_all_info["hsize"])
if distance_to_other_buildings_ok then if distance_to_other_buildings_ok then
-- count built houses -- count built houses
count_buildings[building_all_info["name"]] = count_buildings[building_all_info["name"]] +1 count_buildings[building_all_info["name"]] = count_buildings[building_all_info["name"]] +1
rotation = possible_rotations[ pr:next(1, #possible_rotations ) ] rotation = possible_rotations[ pr:next(1, #possible_rotations ) ]
number_built = number_built + 1 number_built = number_built + 1
settlement_info[index] = { settlement_info[index] = {
pos = pos_surface, pos = pos_surface,
name = building_all_info["name"], name = building_all_info["name"],
hsize = building_all_info["hsize"], hsize = building_all_info["hsize"],
rotat = rotation, rotat = rotation,
surface_mat = surface_material surface_mat = surface_material
} }
index = index + 1 index = index + 1
break break
end
end end
end end
if number_of_buildings == number_built then
break
end
end end
r = r + pr:next(2,5) if number_of_buildings == number_built then
break
end
end end
if number_built >= number_of_buildings then
break
end
r = r + pr:next(2,5)
end end
settlements.debug("really ".. number_built) settlements.debug("really ".. number_built)
return settlement_info return settlement_info

@ -1,4 +1,5 @@
mcl_util mcl_util
mcl_mapgen_core
mcl_core mcl_core
mcl_loot mcl_loot
mcl_farming? mcl_farming?

@ -51,11 +51,12 @@ function settlements.terraform(settlement_info, pr)
settlements.ground(p, pr) settlements.ground(p, pr)
else else
-- write ground -- write ground
local p = {x=pos.x+xi, y=pos.y+yi, z=pos.z+zi} -- local p = {x=pos.x+xi, y=pos.y+yi, z=pos.z+zi}
local node = mcl_util.get_far_node(p, true) -- local node = mcl_mapgen_core.get_node(p)
if node and node.name ~= "air" then -- if node and node.name ~= "air" then
minetest.swap_node(p,{name="air"}) -- minetest.swap_node(p,{name="air"})
end -- end
minetest.swap_node({x=pos.x+xi, y=pos.y+yi, z=pos.z+zi},{name="air"})
end end
end end
end end

@ -52,7 +52,7 @@ end
-- --
-- on map generation, try to build a settlement -- on map generation, try to build a settlement
-- --
local function build_a_settlement_no_delay(minp, maxp, blockseed) local function build_a_settlement(minp, maxp, blockseed)
local pr = PseudoRandom(blockseed) local pr = PseudoRandom(blockseed)
-- fill settlement_info with buildings and their data -- fill settlement_info with buildings and their data
@ -72,30 +72,28 @@ local function build_a_settlement_no_delay(minp, maxp, blockseed)
settlements.initialize_nodes(settlement_info, pr) settlements.initialize_nodes(settlement_info, pr)
end end
local function ecb_build_a_settlement(blockpos, action, calls_remaining, param) local function ecb_village(blockpos, action, calls_remaining, param)
if calls_remaining <= 0 then if calls_remaining >= 1 then return end
build_a_settlement_no_delay(param.minp, param.maxp, param.blockseed) local minp, maxp, blockseed = param.minp, param.maxp, param.blockseed
end build_a_settlement(minp, maxp, blockseed)
end end
-- Disable natural generation in singlenode. -- Disable natural generation in singlenode.
local mg_name = minetest.get_mapgen_setting("mg_name") local mg_name = minetest.get_mapgen_setting("mg_name")
if mg_name ~= "singlenode" then if mg_name ~= "singlenode" then
minetest.register_on_generated(function(minp, maxp, blockseed) mcl_mapgen_core.register_generator("villages", nil, function(minp, maxp, blockseed)
-- needed for manual and automated settlement building
local heightmap = minetest.get_mapgen_object("heightmap")
-- randomly try to build settlements
if blockseed % 77 ~= 17 then return end
-- don't build settlement underground -- don't build settlement underground
if maxp.y < 0 then return end if maxp.y < 0 then return end
-- randomly try to build settlements
if blockseed % 77 ~= 17 then return end
-- needed for manual and automated settlement building
-- don't build settlements on (too) uneven terrain -- don't build settlements on (too) uneven terrain
local height_difference = settlements.evaluate_heightmap(minp, maxp) local heightmap = minetest.get_mapgen_object("heightmap")
local height_difference = settlements.evaluate_heightmap()
if height_difference > max_height_difference then return end if height_difference > max_height_difference then return end
-- we need 'minetest.after' here to exit from emerging thread we probably currently in:
minetest.after(0.1, build_a_settlement_no_delay, vector.new(minp), vector.new(maxp), blockseed) local param={minp=vector.new(minp), maxp=vector.new(maxp), blockseed=blockseed}
minetest.emerge_area(minp, maxp, ecb_village, param)
end) end)
end end
-- manually place villages -- manually place villages
@ -108,7 +106,7 @@ if minetest.is_creative_enabled("") then
if not pointed_thing.under then return end if not pointed_thing.under then return end
local minp = vector.subtract( pointed_thing.under, half_map_chunk_size) local minp = vector.subtract( pointed_thing.under, half_map_chunk_size)
local maxp = vector.add( pointed_thing.under, half_map_chunk_size) local maxp = vector.add( pointed_thing.under, half_map_chunk_size)
build_a_settlement_no_delay(minp, maxp, math.random(0,32767)) build_a_settlement(minp, maxp, math.random(0,32767))
end end
}) })
end end

@ -45,31 +45,27 @@ end
-- function to find surface block y coordinate -- function to find surface block y coordinate
-- returns surface postion -- returns surface postion
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
function settlements.find_surface(pos) function settlements.find_surface(pos, wait)
local p6 = vector.new(pos) local p6 = vector.new(pos)
local cnt = 0 local cnt = 0
local itter -- count up or down local itter = 1 -- count up or down
local cnt_max = 200 local cnt_max = 200
-- check, in which direction to look for surface -- check, in which direction to look for surface
local surface_node = mcl_util.get_far_node(p6, true) local surface_node
if surface_node and string.find(surface_node.name,"air") then if wait then
itter = -1 surface_node = mcl_mapgen_core.get_node(p6, true, 10000000)
else else
itter = 1 surface_node = mcl_mapgen_core.get_node(p6)
end
if surface_node.name=="air" or surface_node.name=="ignore" then
itter = -1
end end
-- go through nodes an find surface -- go through nodes an find surface
while cnt < cnt_max do while cnt < cnt_max do
cnt = cnt+1
surface_node = mcl_util.get_far_node(p6, true)
if surface_node.name == "ignore" then
settlements.debug("find_surface1: nil or ignore")
return nil
end
-- Check Surface_node and Node above -- Check Surface_node and Node above
-- --
if settlements.surface_mat[surface_node.name] then if settlements.surface_mat[surface_node.name] then
local surface_node_plus_1 = mcl_util.get_far_node({ x=p6.x, y=p6.y+1, z=p6.z}, true) local surface_node_plus_1 = mcl_mapgen_core.get_node({ x=p6.x, y=p6.y+1, z=p6.z})
if surface_node_plus_1 and surface_node and if surface_node_plus_1 and surface_node and
(string.find(surface_node_plus_1.name,"air") or (string.find(surface_node_plus_1.name,"air") or
string.find(surface_node_plus_1.name,"snow") or string.find(surface_node_plus_1.name,"snow") or
@ -93,6 +89,8 @@ function settlements.find_surface(pos)
settlements.debug("find_surface4: y<0") settlements.debug("find_surface4: y<0")
return nil return nil
end end
cnt = cnt+1
surface_node = mcl_mapgen_core.get_node(p6)
end end
settlements.debug("find_surface5: cnt_max overflow") settlements.debug("find_surface5: cnt_max overflow")
return nil return nil
@ -241,7 +239,7 @@ function settlements.initialize_nodes(settlement_info, pr)
for xi = 0,width do for xi = 0,width do
for zi = 0,depth do for zi = 0,depth do
local ptemp = {x=p.x+xi, y=p.y+yi, z=p.z+zi} local ptemp = {x=p.x+xi, y=p.y+yi, z=p.z+zi}
local node = mcl_util.get_far_node(ptemp, true) local node = mcl_mapgen_core.get_node(ptemp)
if node.name == "mcl_furnaces:furnace" or if node.name == "mcl_furnaces:furnace" or
node.name == "mcl_chests:chest" or node.name == "mcl_chests:chest" or
node.name == "mcl_anvils:anvil" then node.name == "mcl_anvils:anvil" then
@ -272,44 +270,39 @@ end
-- evaluate heightmap -- evaluate heightmap
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
function settlements.evaluate_heightmap() function settlements.evaluate_heightmap()
local heightmap = minetest.get_mapgen_object("heightmap") local heightmap = minetest.get_mapgen_object("heightmap")
-- max height and min height, initialize with impossible values for easier first time setting -- max height and min height, initialize with impossible values for easier first time setting
local max_y = -50000 local max_y = -50000
local min_y = 50000 local min_y = 50000
-- only evaluate the center square of heightmap 40 x 40 -- only evaluate the center square of heightmap 40 x 40
local square_start = 1621 local square_start = 1621
local square_end = 1661 local square_end = 1661
for j = 1 , 40, 1 do for j = 1 , 40, 1 do
for i = square_start, square_end, 1 do for i = square_start, square_end, 1 do
-- skip buggy heightmaps, return high value -- skip buggy heightmaps, return high value
if heightmap[i] == -31000 or if heightmap[i] == -31000 or heightmap[i] == 31000 then
heightmap[i] == 31000 return max_height_difference + 1
then end
return max_height_difference + 1 if heightmap[i] < min_y then
end min_y = heightmap[i]
if heightmap[i] < min_y end
then if heightmap[i] > max_y then
min_y = heightmap[i] max_y = heightmap[i]
end end
if heightmap[i] > max_y end
then -- set next line
max_y = heightmap[i] square_start = square_start + 80
end square_end = square_end + 80
end end
-- set next line -- return the difference between highest and lowest pos in chunk
square_start = square_start + 80 local height_diff = max_y - min_y
square_end = square_end + 80 -- filter buggy heightmaps
end if height_diff <= 1 then
-- return the difference between highest and lowest pos in chunk return max_height_difference + 1
local height_diff = max_y - min_y end
-- filter buggy heightmaps -- debug info
if height_diff <= 1 settlements.debug("heightdiff ".. height_diff)
then return height_diff
return max_height_difference + 1
end
-- debug info
settlements.debug("heightdiff ".. height_diff)
return height_diff
end end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- Set array to list -- Set array to list

@ -1,6 +1,7 @@
mcl_init mcl_init
mcl_worlds mcl_worlds
mcl_core mcl_core
mcl_mapgen_core
mcl_loot mcl_loot
mcl_tnt mcl_tnt
mcl_farming mcl_farming

@ -1089,7 +1089,7 @@ local function create_corridor_system(main_cave_coords)
end end
-- The rail corridor algorithm starts here -- The rail corridor algorithm starts here
minetest.register_on_generated(function(minp, maxp, blockseed) mcl_mapgen_core.register_generator("railcorridors", nil, function(minp, maxp, blockseed, _pr)
-- We re-init the randomizer for every mapchunk as we start generating in the middle of each mapchunk. -- We re-init the randomizer for every mapchunk as we start generating in the middle of each mapchunk.
-- We can't use the mapgen seed as this would make the algorithm depending on the order the mapchunk generate. -- We can't use the mapgen seed as this would make the algorithm depending on the order the mapchunk generate.
InitRandomizer(blockseed) InitRandomizer(blockseed)
@ -1115,4 +1115,4 @@ minetest.register_on_generated(function(minp, maxp, blockseed)
end end
end end
end end
end) end, 10)