2017-05-24 20:48:09 +00:00
-- FIXME: Chests may appear at openings
2017-05-24 10:46:54 +00:00
2021-03-06 23:49:34 +00:00
mcl_dungeons = { }
2017-09-07 02:38:50 +00:00
local mg_name = minetest.get_mapgen_setting ( " mg_name " )
2017-05-24 08:55:25 +00:00
2020-06-16 00:33:51 +00:00
-- Are dungeons disabled?
2021-02-21 23:15:32 +00:00
if mcl_vars.mg_dungeons == false or mg_name == " singlenode " then
2020-06-16 00:33:51 +00:00
return
end
2021-02-21 23:15:32 +00:00
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
2021-03-06 23:49:34 +00:00
-- 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
2021-02-21 23:15:32 +00:00
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 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
2021-03-06 23:49:34 +00:00
local check = not ( param.dontcheck or false )
2021-02-21 23:15:32 +00:00
-- Check floor and ceiling: Must be *completely* solid
local y_floor = y
local y_ceiling = y + dim.y + 1
2021-03-07 02:00:49 +00:00
if check then for tx = x + 1 , x + dim.x do for tz = z + 1 , z + dim.z do
2021-02-21 23:15:32 +00:00
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
2021-03-06 23:49:34 +00:00
end end end
2021-02-21 23:15:32 +00:00
-- 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 = { }
2021-03-07 02:00:49 +00:00
local x2 , z2 = x + dim.x + 1 , z + dim.z + 1
2021-02-21 23:15:32 +00:00
2021-03-07 02:00:49 +00:00
if mcl_mapgen_core.get_node ( { x = x , y = y + 1 , z = z } ) . name == " air " and mcl_mapgen_core.get_node ( { x = x , y = y + 2 , z = z } ) . name == " air " then
openings_counter = openings_counter + 1
if not openings [ x ] then openings [ x ] = { } end
openings [ x ] [ z ] = true
table.insert ( corners , { x = x , z = z } )
end
if mcl_mapgen_core.get_node ( { x = x2 , y = y + 1 , z = z } ) . name == " air " and mcl_mapgen_core.get_node ( { x = x2 , y = y + 2 , z = z } ) . name == " air " then
openings_counter = openings_counter + 1
if not openings [ x2 ] then openings [ x2 ] = { } end
openings [ x2 ] [ z ] = true
table.insert ( corners , { x = x2 , z = z } )
end
if mcl_mapgen_core.get_node ( { x = x , y = y + 1 , z = z2 } ) . name == " air " and mcl_mapgen_core.get_node ( { x = x , y = y + 2 , z = z2 } ) . name == " air " then
openings_counter = openings_counter + 1
if not openings [ x ] then openings [ x ] = { } end
openings [ x ] [ z2 ] = true
table.insert ( corners , { x = x , z = z2 } )
end
if mcl_mapgen_core.get_node ( { x = x2 , y = y + 1 , z = z2 } ) . name == " air " and mcl_mapgen_core.get_node ( { x = x2 , y = y + 2 , z = z2 } ) . name == " air " then
openings_counter = openings_counter + 1
if not openings [ x2 ] then openings [ x2 ] = { } end
openings [ x2 ] [ z2 ] = true
table.insert ( corners , { x = x2 , z = z2 } )
end
2017-05-24 07:46:40 +00:00
2021-03-07 02:00:49 +00:00
for wx = x + 1 , x + dim.x do
if mcl_mapgen_core.get_node ( { x = wx , y = y + 1 , z = z } ) . name == " air " and mcl_mapgen_core.get_node ( { x = wx , y = y + 2 , z = z } ) . name == " air " then
openings_counter = openings_counter + 1
if check and openings_counter > 5 then return end
if not openings [ wx ] then openings [ wx ] = { } end
openings [ wx ] [ z ] = true
end
if mcl_mapgen_core.get_node ( { x = wx , y = y + 1 , z = z2 } ) . name == " air " and mcl_mapgen_core.get_node ( { x = wx , y = y + 2 , z = z2 } ) . name == " air " then
openings_counter = openings_counter + 1
if check and openings_counter > 5 then return end
if not openings [ wx ] then openings [ wx ] = { } end
openings [ wx ] [ z2 ] = true
end
end
for wz = z + 1 , z + dim.z do
if mcl_mapgen_core.get_node ( { x = x , y = y + 1 , z = wz } ) . name == " air " and mcl_mapgen_core.get_node ( { x = x , y = y + 2 , z = wz } ) . name == " air " then
openings_counter = openings_counter + 1
if check and openings_counter > 5 then return end
if not openings [ x ] then openings [ x ] = { } end
openings [ x ] [ wz ] = true
end
if mcl_mapgen_core.get_node ( { x = x2 , y = y + 1 , z = wz } ) . name == " air " and mcl_mapgen_core.get_node ( { x = x2 , y = y + 2 , z = wz } ) . name == " air " then
openings_counter = openings_counter + 1
if check and openings_counter > 5 then return end
if not openings [ x2 ] then openings [ x2 ] = { } end
openings [ x2 ] [ wz ] = true
2021-02-21 23:15:32 +00:00
end
2017-05-24 07:46:40 +00:00
end
2021-02-21 23:15:32 +00:00
-- 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
2021-03-07 02:00:49 +00:00
if not openings [ cxn ] then openings [ cxn ] = { } end
2021-02-21 23:15:32 +00:00
openings [ cxn ] [ cz ] = true
openings_counter = openings_counter + 1
end
end
end
2017-05-24 07:46:40 +00:00
2021-02-21 23:15:32 +00:00
-- Check conditions. If okay, start generating
2021-03-06 23:49:34 +00:00
if check and ( openings_counter < 1 or openings_counter > 5 ) then return end
2021-02-21 23:15:32 +00:00
minetest.log ( " action " , " [mcl_dungeons] Placing new dungeon at " .. minetest.pos_to_string ( { x = x , y = y , z = z } ) )
-- Okay! Spawning starts!
2017-05-24 07:46:40 +00:00
2017-05-25 03:01:50 +00:00
-- Remember spawner chest positions to set metadata later
2021-02-21 23:15:32 +00:00
local chests = { }
2017-05-25 03:01:50 +00:00
local spawner_posses = { }
2017-05-24 08:55:25 +00:00
2021-02-21 23:15:32 +00:00
-- 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
2021-03-07 02:00:49 +00:00
local totalChestSlots = ( dim.x + dim.z - 2 ) * 2
2021-02-21 23:15:32 +00:00
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
-- 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
2017-05-24 07:46:40 +00:00
2021-02-21 23:15:32 +00:00
-- 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 }
-- 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
2021-03-07 02:00:49 +00:00
if minetest.registered_nodes [ name ] . is_ground_content or name == " mcl_core:cobble " or name == " mcl_core:mossycobble " then
2021-02-21 23:15:32 +00:00
-- 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 " } )
2017-05-24 07:46:40 +00:00
end
2021-02-21 23:15:32 +00:00
-- 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 . ] ]
2021-03-07 02:00:49 +00:00
elseif tx == x or tz == z or tx == maxx or tz == maxz then
2021-02-21 23:15:32 +00:00
-- Check if it's an opening first
2021-03-07 02:00:49 +00:00
if ( ty == maxy ) or ( not ( openings [ tx ] and openings [ tx ] [ tz ] ) ) then
2021-02-21 23:15:32 +00:00
-- 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.
2021-03-07 02:00:49 +00:00
if name ~= " air " then minetest.swap_node ( p , { name = " air " } ) end
elseif name ~= " air " then
2021-02-21 23:15:32 +00:00
-- This allows for variation between 2-node and 3-node high openings.
minetest.swap_node ( p , { name = " mcl_core:cobble " } )
2017-05-24 07:46:40 +00:00
end
2021-02-21 23:15:32 +00:00
-- If it was an opening, the lower 3 blocks are not touched at all
2017-05-24 07:46:40 +00:00
2021-02-21 23:15:32 +00:00
-- Room interiour
else
2021-03-07 02:00:49 +00:00
if ( ty == y + 1 ) and ( tx == x + 1 or tx == maxx - 1 or tz == z + 1 or tz == maxz - 1 ) 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
2021-02-21 23:15:32 +00:00
local forChest = ty == y + 1 and ( tx == x + 1 or tx == maxx - 1 or tz == z + 1 or tz == maxz - 1 )
2017-05-24 07:46:40 +00:00
2021-02-21 23:15:32 +00:00
-- 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 } )
2017-08-12 18:02:52 +00:00
else
2021-03-07 02:00:49 +00:00
--minetest.swap_node(p, {name = "air"})
2017-08-12 18:02:52 +00:00
end
2021-02-21 23:15:32 +00:00
if forChest then
chestSlotCounter = chestSlotCounter + 1
2017-08-12 18:02:52 +00:00
end
end
end
2021-02-21 23:15:32 +00:00
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 )
2017-05-24 07:46:40 +00:00
end
end
2021-02-21 23:15:32 +00:00
-- 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 ) )
2017-05-24 18:50:42 +00:00
end
2021-02-21 23:15:32 +00:00
minetest.set_node ( pos , { name = " mcl_chests:chest " , param2 = facedir } )
local meta = minetest.get_meta ( pos )
2021-03-08 00:14:03 +00:00
local loottable =
{
{
stacks_min = 1 ,
stacks_max = 3 ,
items = {
{ itemstring = " mcl_mobs:nametag " , weight = 20 } ,
{ itemstring = " mcl_mobitems:saddle " , weight = 20 } ,
{ itemstring = " mcl_jukebox:record_1 " , weight = 15 } ,
{ itemstring = " mcl_jukebox:record_4 " , weight = 15 } ,
{ itemstring = " mobs_mc:iron_horse_armor " , weight = 15 } ,
{ itemstring = " mcl_core:apple_gold " , weight = 15 } ,
{ itemstack = mcl_enchanting.get_uniform_randomly_enchanted_book ( { " soul_speed " } , pr ) , weight = 10 } ,
{ itemstring = " mobs_mc:gold_horse_armor " , weight = 10 } ,
{ itemstring = " mobs_mc:diamond_horse_armor " , weight = 5 } ,
{ itemstring = " mcl_core:apple_gold_enchanted " , weight = 2 } ,
}
} ,
{
stacks_min = 1 ,
stacks_max = 4 ,
items = {
{ itemstring = " mcl_farming:wheat_item " , weight = 20 , amount_min = 1 , amount_max = 4 } ,
{ itemstring = " mcl_farming:bread " , weight = 20 } ,
{ itemstring = " mcl_core:coal_lump " , weight = 15 , amount_min = 1 , amount_max = 4 } ,
{ itemstring = " mesecons:redstone " , weight = 15 , amount_min = 1 , amount_max = 4 } ,
{ itemstring = " mcl_farming:beetroot_seeds " , weight = 10 , amount_min = 2 , amount_max = 4 } ,
{ itemstring = " mcl_farming:melon_seeds " , weight = 10 , amount_min = 2 , amount_max = 4 } ,
{ itemstring = " mcl_farming:pumpkin_seeds " , weight = 10 , amount_min = 2 , amount_max = 4 } ,
{ itemstring = " mcl_core:iron_ingot " , weight = 10 , amount_min = 1 , amount_max = 4 } ,
{ itemstring = " mcl_buckets:bucket_empty " , weight = 10 } ,
{ itemstring = " mcl_core:gold_ingot " , weight = 5 , amount_min = 1 , amount_max = 4 } ,
} ,
} ,
{
stacks_min = 3 ,
stacks_max = 3 ,
items = {
{ itemstring = " mcl_mobitems:bone " , weight = 10 , amount_min = 1 , amount_max = 8 } ,
{ itemstring = " mcl_mobitems:gunpowder " , weight = 10 , amount_min = 1 , amount_max = 8 } ,
{ itemstring = " mcl_mobitems:rotten_flesh " , 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
2021-02-22 17:58:35 +00:00
mcl_loot.fill_inventory ( meta : get_inventory ( ) , " main " , mcl_loot.get_multi_loot ( loottable , pr ) , pr )
2021-02-21 23:15:32 +00:00
end
2017-05-25 03:01:50 +00:00
2021-02-21 23:15:32 +00:00
-- 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 ) ]
2017-05-25 03:01:50 +00:00
2021-02-21 23:15:32 +00:00
mcl_mobspawners.setup_spawner ( sp , spawner_mob , 0 , 7 )
2017-05-24 08:55:25 +00:00
end
2021-02-21 23:15:32 +00:00
end
2017-05-24 08:55:25 +00:00
2021-02-21 23:15:32 +00:00
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 ) ]
2021-03-07 02:00:49 +00:00
local x = pr : next ( minp.x , maxp.x - dim.x - 1 )
local y = pr : next ( ymin , ymax - dim.y - 1 )
local z = pr : next ( minp.z , maxp.z - dim.z - 1 )
2021-02-21 23:15:32 +00:00
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
2021-01-26 02:23:38 +00:00
end
2021-02-21 23:15:32 +00:00
2021-03-06 23:49:34 +00:00
function mcl_dungeons . spawn_dungeon ( p1 , _ , pr )
if not p1 or not pr or not p1.x or not p1.y or not p1.z then return end
local dim = dungeonsizes [ pr : next ( 1 , # dungeonsizes ) ]
local p2 = { x = p1.x + dim.x + 1 , y = p1.y + dim.y + 1 , z = p1.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 , dontcheck = true }
minetest.emerge_area ( p1 , p2 , ecb_spawn_dungeon , param )
end
2021-02-21 23:15:32 +00:00
mcl_mapgen_core.register_generator ( " dungeons " , nil , dungeons_nodes , 999999 )