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

View file

@ -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_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
-- Normal mode
--[[ Realm stacking (h is for height)
@ -91,7 +127,7 @@ else
mcl_vars.mg_bedrock_is_rough = false
end
mcl_vars.mg_overworld_max = 31000
mcl_vars.mg_overworld_max = mcl_vars.mapgen_edge_max
-- The Nether (around Y = -29000)
mcl_vars.mg_nether_min = -29067 -- Carefully chosen to be at a mapchunk border

View file

@ -405,53 +405,3 @@ function mcl_util.get_object_center(obj)
pos.y = pos.y + (ymax - ymin) / 2.0
return pos
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

View file

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

View file

@ -3967,7 +3967,7 @@ if mg_name ~= "singlenode" then
-- Overworld decorations for v6 are handled in mcl_mapgen_core
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 poslist = {}
for _, pos in ipairs(gennotify["decoration#"..deco_id_chorus_plant] or {}) do

View file

@ -1,17 +1,38 @@
-- FIXME: Chests may appear at openings
local mg_name = minetest.get_mapgen_setting("mg_name")
local pr = PseudoRandom(os.time())
-- Are dungeons disabled?
if mcl_vars.mg_dungeons == false then
if mcl_vars.mg_dungeons == false or mg_name == "singlenode" then
return
end
if mg_name ~= "singlenode" then
-- Get loot for dungeon chests
local get_loot = function()
local loottable = {
local min_y = math.max(mcl_vars.mg_overworld_min, mcl_vars.mg_bedrock_overworld_max) + 1
local max_y = mcl_vars.mg_overworld_max - 1
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_max = 3,
@ -54,347 +75,278 @@ local get_loot = function()
{ 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.
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
local items = mcl_loot.get_multi_loot(loottable, pr)
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
return items
end
if openings[pos.x] == nil then openings[pos.x] = {} 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
-- Buffer for LuaVoxelManip
local lvm_buffer = {}
-- Below the bedrock, generate air/void
minetest.register_on_generated(function(minp, maxp)
if maxp.y < mcl_vars.mg_overworld_min or minp.y > mcl_vars.mg_overworld_max then
return
-- 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
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
local data = vm:get_data(lvm_buffer)
local area = VoxelArea:new({MinEdge=emin, MaxEdge=emax})
local lvm_used = false
-- 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
local c_air = minetest.get_content_id("air")
local c_cobble = minetest.get_content_id("mcl_core:cobble")
local c_mossycobble = minetest.get_content_id("mcl_core:mossycobble")
-- Check conditions. If okay, start generating
if openings_counter < 1 or openings_counter > 5 then return end
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
local chest_posses = {}
local chests = {}
local spawner_posses = {}
-- Calculate the number of dungeon spawn attempts
local sizevector = vector.subtract(maxp, minp)
sizevector = vector.add(sizevector, 1)
local chunksize = sizevector.x * sizevector.y * sizevector.z
-- First prepare random chest positions.
-- Chests spawn at wall
-- 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(chunksize / 65536 * 8)
for a=1, attempts do
local x, y, z
local b = 7 -- buffer
x = math.random(minp.x+b, maxp.x-b)
local ymin = math.min(mcl_vars.mg_overworld_max, math.max(minp.y, mcl_vars.mg_bedrock_overworld_max) + 7)
local ymax = math.min(mcl_vars.mg_overworld_max, math.max(maxp.y, mcl_vars.mg_bedrock_overworld_max) - 4)
y = math.random(ymin, ymax)
z = math.random(minp.z+b, maxp.z-b)
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
-- 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 = pr:next(1, totalChestSlots)
if r == lastRandom and secondChance then
-- Oops! Same slot selected. Try again.
r = pr:next(1, totalChestSlots)
secondChance = false
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
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 = {}
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
-- Calculate the mob spawner position, to be re-used for later
local sp = {x = x + math.ceil(dim.x/2), y = y+1, z = z + math.ceil(dim.z/2)}
local rn = minetest.registered_nodes[mcl_mapgen_core.get_node(sp).name]
if rn and rn.is_ground_content then
table.insert(spawner_posses, sp)
end
if lvm_used then
local chest_param2 = {}
-- Determine correct chest rotation (must pointi outwards)
for c=1, #chest_posses do
local cpos = chest_posses[c]
-- 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 = {x = tx, y=ty, z=tz}
-- Check surroundings of chest to determine correct rotation
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 surroundings = {}
for s=1, #surround_vectors do
-- 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)
-- 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 = mcl_mapgen_core.get_node(p).name
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 pr:next(1,4) == 1 then
minetest.swap_node(p, {name = "mcl_core:cobble"})
else
minetest.swap_node(p, {name = "mcl_core:mossycobble"})
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 = math.random(0, 0)
-- 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
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
-- 1 or multiple possible open directions: Choose random facedir
local face_to = surroundings[math.random(1, #surroundings)]
facedir = minetest.dir_to_facedir(vector.subtract(cpos, face_to))
end
chest_param2[c] = facedir
end
local forChest = ty==y+1 and (tx==x+1 or tx==maxx-1 or tz==z+1 or tz==maxz-1)
-- Finally generate the dungeons all at once (except the chests and the spawners)
vm:set_data(data)
vm:calc_lighting()
vm:update_liquids()
vm:write_to_map()
-- Chests are placed seperately
for c=1, #chest_posses do
local cpos = chest_posses[c]
minetest.set_node(cpos, {name="mcl_chests:chest", param2=chest_param2[c]})
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)
-- Place next chest at the wall (if it was its chosen wall slot)
if forChest and (currentChest < totalChests + 1) and (chestSlots[currentChest] == chestSlotCounter) then
currentChest = currentChest + 1
table.insert(chests, {x=tx, y=ty, z=tz})
else
minetest.swap_node(p, {name = "air"})
end
if forChest then
chestSlotCounter = chestSlotCounter + 1
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)
-- 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
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)

View file

@ -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
--
@ -1190,12 +1233,14 @@ local perlin_structures
local perlin_vines, perlin_vines_fine, perlin_vines_upwards, perlin_vines_length, perlin_vines_density
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.
if maxp.y < -5 or minp.y > 0 then
return lvm_used
end
local pr = PseudoRandom(blockseed)
perlin_clay = perlin_clay or minetest.get_perlin({
offset = 0.5,
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 divz=0+1,divs-2 do
-- 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 cz = minp.z + math.floor((divz+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) + pr:next(-1,1)
local water_pos = voxelmanip_area:index(cx, y+1, cz)
local waternode = voxelmanip_data[water_pos]
local surface_pos = voxelmanip_area:index(cx, y, cz)
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
(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 z1 = -(diamondsize - math.abs(x1)), diamondsize - math.abs(x1) do
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
-- 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_temple = 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
-- Generate structures
local pr = PcgRandom(blockseed)
perlin_structures = perlin_structures or minetest.get_perlin(329, 3, 0.6, 100)
-- Assume X and Z lengths are equal
local divlen = 5
local divs = (maxp.x-minp.x)/divlen+1;
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)
for x0 = minp.x, maxp.x, divlen do for z0 = minp.z, maxp.z, divlen do
-- Determine amount from perlin noise
local amount = math.floor(perlin_structures:get_2d({x=x0, y=z0}) * 9)
-- Find random positions based on this random
local pr = PseudoRandom(seed+1)
local p, ground_y
for i=0, amount do
local x = pr:next(x0, x1)
local z = pr:next(z0, z1)
p = {x = pr:next(x0, x0+divlen-1), y = 0, z = pr:next(z0, z0+divlen-1)}
-- Find ground level
local ground_y = nil
ground_y = nil
local nn
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
local def = minetest.registered_nodes[checknode.name]
nn = checknode.name
local def = minetest.registered_nodes[nn]
if def and def.walkable then
ground_y = y
break
@ -1281,21 +1323,17 @@ local function generate_structures(minp, maxp, seed, biomemap)
end
if ground_y then
local p = {x=x,y=ground_y+1,z=z}
local nn = minetest.get_node(p).name
p.y = ground_y+1
local nn0 = minetest.get_node(p).name
-- Check if the node can be replaced
if minetest.registered_nodes[nn] and
minetest.registered_nodes[nn].buildable_to then
nn = minetest.get_node({x=x,y=ground_y,z=z}).name
local struct = false
if minetest.registered_nodes[nn0] and minetest.registered_nodes[nn0].buildable_to then
-- Desert temples and desert wells
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
-- Spawn desert temple
-- TODO: Check surface
if math.random(1,12000) == 1 then
mcl_structures.call_struct(p, "desert_temple")
if pr:next(1,12000) == 1 then
mcl_structures.call_struct(p, "desert_temple", nil, pr)
chunk_has_desert_temple = true
end
end
@ -1303,11 +1341,11 @@ local function generate_structures(minp, maxp, seed, biomemap)
local desert_well_prob = minecraft_chunk_probability(1000, minp, maxp)
-- Spawn desert well
if math.random(1, desert_well_prob) == 1 then
if pr:next(1, desert_well_prob) == 1 then
-- 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")
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
end
end
@ -1315,13 +1353,13 @@ local function generate_structures(minp, maxp, seed, biomemap)
-- 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
if math.random(1, 4400) == 1 then
if pr:next(1, 4400) == 1 then
-- Check surface
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 surface2 = minetest.find_nodes_in_area({x=p.x,y=p.y-1,z=p.z}, floor, "mcl_core:dirt_with_grass_snow")
if #surface + #surface2 >= 63 then
mcl_structures.call_struct(p, "igloo")
mcl_structures.call_struct(p, "igloo", nil, pr)
chunk_has_igloo = true
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
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
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).
-- Fossils may still appear partially exposed in caves, but this is O.K.
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"})
if #nodes >= 100 then -- >= 80%
mcl_structures.call_struct(p1, "fossil")
mcl_structures.call_struct(p1, "fossil", nil, pr)
end
end
end
@ -1348,7 +1386,7 @@ local function generate_structures(minp, maxp, seed, biomemap)
-- Witch hut
if ground_y <= 0 and nn == "mcl_core:dirt" then
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_shore = minetest.get_biome_id("Swampland_shore")
@ -1370,7 +1408,7 @@ local function generate_structures(minp, maxp, seed, biomemap)
end
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 size
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
-- function does sometimes NOT spawn the witch hut. One can only see the
-- 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.
@ -1451,7 +1489,7 @@ local function generate_structures(minp, maxp, seed, biomemap)
-- Ice spikes in v6
-- In other mapgens, ice spikes are generated as decorations.
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
-- Check surface
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"})
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
elseif spike < 100 then
-- 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"})
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
@ -1479,36 +1517,31 @@ local function generate_structures(minp, maxp, seed, biomemap)
end
end
end
end
end end
-- End exit portal
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.z <= END_EXIT_PORTAL_POS.z and maxp.z >= END_EXIT_PORTAL_POS.z then
local built = false
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.z <= END_EXIT_PORTAL_POS.z and maxp.z >= END_EXIT_PORTAL_POS.z then
for y=maxp.y, minp.y, -1 do
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
mcl_structures.call_struct(p, "end_exit_portal")
built = true
break
return
end
end
if not built then
mcl_structures.call_struct(END_EXIT_PORTAL_POS, "end_exit_portal")
end
mcl_structures.call_struct(END_EXIT_PORTAL_POS, "end_exit_portal")
end
end
-- Buffers for LuaVoxelManip
local lvm_buffer = {}
local lvm_buffer_param2 = {}
-- local lvm_buffer = {}
-- local lvm_buffer_param2 = {}
-- Generate tree decorations in the bounding box. This adds:
-- * Cocoa at jungle trees
-- * Jungle tree vines
-- * 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
return lvm_used
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
dir = math.random(1, cocoachance)
dir = pr:next(1, cocoachance)
if dir == 1 then
pos.z = pos.z + 1
@ -1594,7 +1627,7 @@ local function generate_tree_decorations(minp, maxp, seed, data, param2_data, ar
if dir < 5
and data[p_pos] == c_air
and l ~= nil and l > 12 then
local c = math.random(1, 3)
local c = pr:next(1, 3)
if c == 1 then
data[p_pos] = c_cocoa_1
elseif c == 2 then
@ -1810,125 +1843,206 @@ local generate_nether_decorations = function(minp, maxp, seed)
end
-- Below the bedrock, generate air/void
minetest.register_on_generated(function(minp, maxp, seed)
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
local data = vm:get_data(lvm_buffer)
local param2_data = vm:get_param2_data(lvm_buffer_param2)
local area = VoxelArea:new({MinEdge=emin, MaxEdge=emax})
local aream = VoxelArea:new({MinEdge={x=minp.x, y=0, z=minp.z}, MaxEdge={x=maxp.x, y=0, z=maxp.z}})
local lvm_used = false
local biomemap
minetest.register_on_generated(function(minp, maxp, blockseed)
add_chunk(minp)
local p1, p2 = {x=minp.x, y=minp.y, z=minp.z}, {x=maxp.x, y=maxp.y, z=maxp.z}
if lvm > 0 then
local lvm_used, shadow = false, false
local lb = {} -- buffer
local lb2 = {} -- param2
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
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.
-- Also perform some basic node replacements.
if lvm_used then
-- 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.
-- 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(content_id, check, min, max, minp, maxp, lvm_used)
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]) then
data[p_pos] = content_id
lvm_used = true
elseif check == data[p_pos] then
data[p_pos] = content_id
lvm_used = true
end
else
if nodes > 0 then
for _, rec in pairs(mcl_mapgen_core.registered_generators) do
if rec.nf then
rec.nf(p1, p2, blockseed)
end
end
end
-- add_chunk(minp)
end)
minetest.register_on_generated=function(node_function)
mcl_mapgen_core.register_generator("mod_"..tostring(#mcl_mapgen_core.registered_generators+1), nil, node_function)
end
function mcl_mapgen_core.register_generator(id, lvm_function, node_function, priority, needs_param2)
if not id then return end
local priority = priority or 5000
if lvm_function then lvm = lvm + 1 end
if lvm_function then nodes = nodes + 1 end
if needs_param2 then param2 = param2 + 1 end
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
lvm_used = true
end
else
data[p_pos] = content_id
lvm_used = true
end
end
end
end
return lvm_used
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
lvm_used = set_layers(c_void, nil, -31000, mcl_vars.mg_nether_min-1, minp, maxp, lvm_used)
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(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(c_void, nil, mcl_vars.mg_realm_barrier_overworld_end_max+1, mcl_vars.mg_overworld_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(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(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)
-- 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
-- Bedrock
local bedrock_check
if mcl_vars.mg_bedrock_is_rough then
bedrock_check = function(pos)
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)
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)
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)
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)
-- Flat Nether
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
-- Big lava seas by replacing air below a certain height
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(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_lava, c_air, mcl_vars.mg_overworld_min, mcl_vars.mg_lava_overworld_max, emin, emax, lvm_used, pr)
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
-- 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")
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 -----
----- 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.
-- 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"})
-- 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
local p_pos = area:index(nodes[n].x, nodes[n].y, nodes[n].z)
local p_pos_above = area:index(nodes[n].x, nodes[n].y+1, nodes[n].z)
local p_pos_below = area:index(nodes[n].x, nodes[n].y-1, nodes[n].z)
local b_pos = aream:index(nodes[n].x, 0, nodes[n].z)
local n = nodes[n]
local p_pos = area:index(n.x, n.y, n.z)
local p_pos_above = area:index(n.x, n.y+1, 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])
if bn then
local biome = minetest.registered_biomes[bn]
if biome then
if biome._mcl_biome_type then
param2_data[p_pos] = biome._mcl_palette_index
lvm_used = true
end
if biome and biome._mcl_biome_type then
data2[p_pos] = biome._mcl_palette_index
lvm_used = true
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
@ -1994,6 +2110,7 @@ minetest.register_on_generated(function(minp, maxp, seed)
lvm_used = true
end
end
end
-- 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.
-- * Generate spawn platform (End portal destination)
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
nodes = minetest.find_nodes_in_area(emin, emax, {"mcl_core:water_source", "mcl_core:stone", "mcl_core:sand", "mcl_core:dirt"})
else
nodes = minetest.find_nodes_in_area(emin, emax, {"mcl_core:water_source"})
end
for n=1, #nodes do
local y = nodes[n].y
local p_pos = area:index(nodes[n].x, y, nodes[n].z)
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[p_pos] = c_air
lvm_used = true
if #nodes > 0 then
lvm_used = true
for n=1, #nodes do
node = nodes[n]
data[area:index(node.x, node.y, node.z)] = c_air
end
end
-- Obsidian spawn platform
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.z <= mcl_vars.mg_end_platform_pos.z and maxp.z >= mcl_vars.mg_end_platform_pos.z then
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
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 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
@ -2065,7 +2183,7 @@ minetest.register_on_generated(function(minp, maxp, seed)
-- Final hackery: Set sun light level in the End.
-- -26912 is at a mapchunk border.
local shadow
local shadow = true
if minp.y >= -26912 and maxp.y <= mcl_vars.mg_end_max then
vm:set_lighting({day=15, night=15})
lvm_used = true
@ -2075,21 +2193,60 @@ minetest.register_on_generated(function(minp, maxp, seed)
lvm_used = true
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
-- Generate special decorations
generate_underground_mushrooms(minp, maxp, seed)
generate_nether_decorations(minp, maxp, seed)
generate_structures(minp, maxp, seed, biomemap)
generate_underground_mushrooms(minp, maxp, blockseed)
generate_nether_decorations(minp, maxp, blockseed)
generate_structures(minp, maxp, blockseed, biomemap)
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

View file

@ -67,7 +67,8 @@ local init_strongholds = function()
end
-- 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
if not strongholds[s].generated then
local pos = strongholds[s].pos
@ -80,6 +81,12 @@ local generate_strongholds = function(minp, maxp)
if pos.x + 6 > maxp.x then
pos.x = maxp.x - 7
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
pos.z = minp.z + 7
end
@ -87,7 +94,7 @@ local generate_strongholds = function(minp, maxp)
pos.z = maxp.z - 7
end
mcl_structures.call_struct(pos, "end_portal_shrine")
mcl_structures.call_struct(pos, "end_portal_shrine", nil, pr)
strongholds[s].generated = true
end
end
@ -96,9 +103,4 @@ end
init_strongholds()
--[[ Note this mod depends on mcl_mapgen_core to make sure the core mapgen runs FIRST.
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)
mcl_mapgen_core.register_generator("strongholds", nil, generate_strongholds, 999999)

View file

@ -1,24 +1,58 @@
local S = minetest.get_translator("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)
local localfile = minetest.get_modpath("mcl_structures").."/schematics/"..file
local file, errorload = io.open(localfile, "rb")
if errorload ~= nil then
minetest.log("error", '[mcl_structures] Could not open this struct: ' .. localfile)
return nil
minetest.log("error", '[mcl_structures] Could not open this struct: ' .. localfile)
return nil
end
local allnode = file:read("*a")
file:close()
local allnode = file:read("*a")
file:close()
return allnode
return allnode
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.
-- Useful to init chests from formspec.
local init_node_construct = function(pos)
@ -32,16 +66,17 @@ local init_node_construct = function(pos)
end
-- 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
rotation = "random"
end
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
return mcl_structures.generate_desert_well(pos, rotation)
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
return mcl_structures.generate_witch_hut(pos, rotation)
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
return mcl_structures.generate_ice_spike_large(pos, rotation)
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
return mcl_structures.generate_fossil(pos, rotation)
return mcl_structures.generate_fossil(pos, rotation, pr)
elseif struct_style == "end_exit_portal" then
return mcl_structures.generate_end_exit_portal(pos, rotation)
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
@ -65,12 +100,12 @@ mcl_structures.generate_desert_well = function(pos)
return minetest.place_schematic(newpos, path, "0", nil, true)
end
mcl_structures.generate_igloo = function(pos)
mcl_structures.generate_igloo = function(pos, rotation, pr)
-- 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
local r = math.random(1,2)
if success and r == 1 then
local r = pr:next(1,2)
if r == 1 then
-- Select basement depth
local dim = mcl_worlds.pos_to_dimension(pos)
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
return success
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}
-- trapdoor position
local tpos
@ -111,8 +146,8 @@ mcl_structures.generate_igloo = function(pos)
return success
end
local set_brick = function(pos)
local c = math.random(1, 3) -- cracked chance
local m = math.random(1, 10) -- chance for monster egg
local c = pr:next(1, 3) -- cracked chance
local m = pr:next(1, 10) -- chance for monster egg
local brick
if m == 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})
end
-- Place basement
mcl_structures.generate_igloo_basement(bpos, rotation)
mcl_structures.generate_igloo_basement(bpos, rotation, pr)
end
return success
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!
-- Furnace does ot work atm because apparently meta is not set. :-(
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 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
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 monster eggs
-- TODO: Spawn villager and zombie villager
local path = minetest.get_modpath("mcl_structures").."/schematics/mcl_structures_igloo_basement.mts"
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
mcl_structures.place_schematic(pos, path, orientation, nil, true, nil, igloo_placement_callback, pr)
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)
local r = math.random(1, 10)
local r = pr:next(1, 10)
local path
if r <= 3 then
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)
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)
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
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)
end
mcl_structures.generate_fossil = function(pos)
mcl_structures.generate_fossil = function(pos, rotation, pr)
-- Generates one out of 8 possible fossil pieces
local newpos = {x=pos.x,y=pos.y-1,z=pos.z}
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_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]
return minetest.place_schematic(newpos, path, "random", nil, true)
end
@ -273,24 +318,16 @@ mcl_structures.generate_end_exit_portal = function(pos)
return minetest.place_schematic(pos, path, "0", nil, true)
end
local generate_end_portal_shrine_no_delay = function(newpos)
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)
local function shrine_placement_callback(p1, p2, size, rotation, pr)
-- 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
local meta = minetest.get_meta(spawners[s])
mcl_mobspawners.setup_spawner(spawners[s], "mobs_mc:silverfish")
end
-- 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
local r_bricktype = 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
-- 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
local stair = minetest.get_node(stairs[s])
local r_type = pr:next(1, 100)
@ -344,7 +381,7 @@ local generate_end_portal_shrine_no_delay = function(newpos)
end
-- 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
for f=1, #frames do
local r_eye = pr:next(1, 10)
@ -357,40 +394,34 @@ local generate_end_portal_shrine_no_delay = function(newpos)
end
end
end
return ret
end
local function ecb_generate_end_portal_shrine(blockpos, action, calls_remaining, param)
if calls_remaining <= 0 then
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}
mcl_structures.generate_end_portal_shrine = function(pos, rotation, pr)
local offset = {x=6, y=4, z=6}
local size = {x=13, y=8, z=13}
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
mcl_structures.generate_desert_temple = function(pos)
-- 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
local ret = minetest.place_schematic(newpos, path, "random", nil, true)
if ret == nil then
return ret
local function temple_placement_callback(p1, p2, size, rotation, pr)
-- Delete cacti leftovers:
local cactus_nodes = minetest.find_nodes_in_area_under_air(p1, p2, "mcl_core:cactus")
if cactus_nodes and #cactus_nodes > 0 then
for _, pos in pairs(cactus_nodes) do
local node_below = minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z})
if node_below and node_below.name == "mcl_core:sandstone" then
minetest.swap_node(pos, {name="air"})
end
end
end
-- Find 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.
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
for c=1, #chests do
@ -436,7 +467,7 @@ mcl_structures.generate_desert_temple = function(pos)
end
-- 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
for p=1, #pplates do
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])
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
local registered_structures = {}

View file

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

View file

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

View file

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

View file

@ -52,7 +52,7 @@ end
--
-- 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)
-- 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)
end
local function ecb_build_a_settlement(blockpos, action, calls_remaining, param)
if calls_remaining <= 0 then
build_a_settlement_no_delay(param.minp, param.maxp, param.blockseed)
end
local function ecb_village(blockpos, action, calls_remaining, param)
if calls_remaining >= 1 then return end
local minp, maxp, blockseed = param.minp, param.maxp, param.blockseed
build_a_settlement(minp, maxp, blockseed)
end
-- Disable natural generation in singlenode.
local mg_name = minetest.get_mapgen_setting("mg_name")
if mg_name ~= "singlenode" then
minetest.register_on_generated(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
mcl_mapgen_core.register_generator("villages", nil, function(minp, maxp, blockseed)
-- don't build settlement underground
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
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
-- 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
-- manually place villages
@ -108,7 +106,7 @@ if minetest.is_creative_enabled("") then
if not pointed_thing.under then return end
local minp = vector.subtract( 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

View file

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

View file

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

View file

@ -1089,7 +1089,7 @@ local function create_corridor_system(main_cave_coords)
end
-- 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 can't use the mapgen seed as this would make the algorithm depending on the order the mapchunk generate.
InitRandomizer(blockseed)
@ -1115,4 +1115,4 @@ minetest.register_on_generated(function(minp, maxp, blockseed)
end
end
end
end)
end, 10)