1 -- FIXME: Chests may appear at openings
3 local mg_name
= minetest
.get_mapgen_setting("mg_name")
4 local pr
= PseudoRandom(os
.time())
6 -- Get loot for dungeon chests
7 local get_loot
= function()
13 { itemstring
= "mobs:nametag", weight
= 20 },
14 { itemstring
= "mcl_mobitems:saddle", weight
= 20 },
15 { itemstring
= "mcl_jukebox:record_1", weight
= 15 },
16 { itemstring
= "mcl_jukebox:record_4", weight
= 15 },
17 { itemstring
= "mobs_mc:iron_horse_armor", weight
= 15 },
18 { itemstring
= "mcl_core:apple_gold", weight
= 15 },
19 -- TODO: Enchanted Book
20 { itemstring
= "mcl_books:book", weight
= 10 },
21 { itemstring
= "mobs_mc:gold_horse_armor", weight
= 10 },
22 { itemstring
= "mobs_mc:diamond_horse_armor", weight
= 5 },
23 -- TODO: Enchanted Golden Apple
24 { itemstring
= "mcl_core:apple_gold", weight
= 2 },
31 { itemstring
= "mcl_farming:wheat_item", weight
= 20, amount_min
= 1, amount_max
= 4 },
32 { itemstring
= "mcl_farming:bread", weight
= 20 },
33 { itemstring
= "mcl_core:coal_lump", weight
= 15, amount_min
= 1, amount_max
= 4 },
34 { itemstring
= "mesecons:redstone", weight
= 15, amount_min
= 1, amount_max
= 4 },
35 { itemstring
= "mcl_farming:beetroot_seeds", weight
= 10, amount_min
= 2, amount_max
= 4 },
36 { itemstring
= "mcl_farming:melon_seeds", weight
= 10, amount_min
= 2, amount_max
= 4 },
37 { itemstring
= "mcl_farming:pumpkin_seeds", weight
= 10, amount_min
= 2, amount_max
= 4 },
38 { itemstring
= "mcl_core:iron_ingot", weight
= 10, amount_min
= 1, amount_max
= 4 },
39 { itemstring
= "mcl_buckets:bucket_empty", weight
= 10 },
40 { itemstring
= "mcl_core:gold_ingot", weight
= 5, amount_min
= 1, amount_max
= 4 },
47 { itemstring
= "mcl_mobitems:bone", weight
= 10, amount_min
= 1, amount_max
= 8 },
48 { itemstring
= "mcl_mobitems:gunpowder", weight
= 10, amount_min
= 1, amount_max
= 8 },
49 { itemstring
= "mcl_mobitems:rotten_flesh", weight
= 10, amount_min
= 1, amount_max
= 8 },
50 { itemstring
= "mcl_mobitems:string", weight
= 10, amount_min
= 1, amount_max
= 8 },
55 -- Bonus loot for v6 mapgen: Otherwise unobtainable saplings.
56 if mg_name
== "v6" then
57 table.insert(loottable
, {
61 { itemstring
= "mcl_core:birchsapling", weight
= 1, amount_min
= 1, amount_max
= 2 },
62 { itemstring
= "mcl_core:acaciasapling", weight
= 1, amount_min
= 1, amount_max
= 2 },
63 { itemstring
= "", weight
= 11 },
67 local items
= mcl_loot
.get_multi_loot(loottable
, pr
)
73 -- Buffer for LuaVoxelManip
76 -- Below the bedrock, generate air/void
77 minetest
.register_on_generated(function(minp
, maxp
)
78 if maxp
.y
< mcl_vars
.mg_overworld_min
or minp
.y
> mcl_vars
.mg_overworld_max
then
82 local vm
, emin
, emax
= minetest
.get_mapgen_object("voxelmanip")
83 local data
= vm
:get_data(lvm_buffer
)
84 local area
= VoxelArea
:new({MinEdge
=emin
, MaxEdge
=emax
})
85 local lvm_used
= false
87 local c_air
= minetest
.get_content_id("air")
88 local c_cobble
= minetest
.get_content_id("mcl_core:cobble")
89 local c_mossycobble
= minetest
.get_content_id("mcl_core:mossycobble")
91 -- Remember spawner chest positions to set metadata later
92 local chest_posses
= {}
93 local spawner_posses
= {}
95 -- Calculate the number of dungeon spawn attempts
96 local sizevector
= vector
.subtract(maxp
, minp
)
97 sizevector
= vector
.add(sizevector
, 1)
98 local chunksize
= sizevector
.x
* sizevector
.y
* sizevector
.z
100 -- In Minecraft, there 8 dungeon spawn attempts Minecraft chunk (16*256*16 = 65536 blocks).
101 -- Minetest chunks don't have this size, so scale the number accordingly.
102 local attempts
= math
.ceil(chunksize
/ 65536 * 8)
106 local b
= 7 -- buffer
107 x
= math
.random(minp
.x
+b
, maxp
.x
-b
)
109 local ymin
= math
.min(mcl_vars
.mg_overworld_max
, math
.max(minp
.y
, mcl_vars
.mg_bedrock_overworld_max
) + 7)
110 local ymax
= math
.min(mcl_vars
.mg_overworld_max
, math
.max(maxp
.y
, mcl_vars
.mg_bedrock_overworld_max
) - 4)
112 y
= math
.random(ymin
, ymax
)
113 z
= math
.random(minp
.z
+b
, maxp
.z
-b
)
115 local dungeonsizes
= {
121 local dim
= dungeonsizes
[math
.random(1, #dungeonsizes
)]
123 -- Check floor and ceiling: Must be *completely* solid
124 local ceilingfloor_ok
= true
125 for tx
= x
, x
+dim
.x
do
126 for tz
= z
, z
+dim
.z
do
127 local floor = minetest
.get_name_from_content_id(data
[area
:index(tx
, y
, tz
)])
128 local ceiling
= minetest
.get_name_from_content_id(data
[area
:index(tx
, y
+dim
.y
+1, tz
)])
129 if (not minetest
.registered_nodes
[floor].walkable
) or (not minetest
.registered_nodes
[ceiling
].walkable
) then
130 ceilingfloor_ok
= false
134 if not ceilingfloor_ok
then break end
137 -- Check for air openings (2 stacked air at ground level) in wall positions
138 local openings_counter
= 0
139 -- Store positions of openings; walls will not be generated here
141 -- Corners are stored because a corner-only opening needs to be increased,
142 -- so entities can get through.
144 if ceilingfloor_ok
then
147 -- walls along x axis (contain corners)
148 { x
, x
+dim
.x
+1, "x", "z", z
},
149 { x
, x
+dim
.x
+1, "x", "z", z
+dim
.z
+1 },
150 -- walls along z axis (exclude corners)
151 { z
+1, z
+dim
.z
, "z", "x", x
},
152 { z
+1, z
+dim
.z
, "z", "x", x
+dim
.x
+1 },
156 local wall
= walls
[w
]
157 for iter
= wall
[1], wall
[2] do
160 pos
[wall
[4]]
= wall
[5]
163 if openings
[pos
.x
] == nil then openings
[pos
.x
] = {} end
165 local door1
= area
:index(pos
.x
, pos
.y
, pos
.z
)
167 local door2
= area
:index(pos
.x
, pos
.y
, pos
.z
)
168 local doorname1
= minetest
.get_name_from_content_id(data
[door1
])
169 local doorname2
= minetest
.get_name_from_content_id(data
[door2
])
170 if doorname1
== "air" and doorname2
== "air" then
171 openings_counter
= openings_counter
+ 1
172 openings
[pos
.x
][pos
.z
] = true
175 if wall
[3] == "x" and (iter
== wall
[1] or iter
== wall
[2]) then
176 table.insert(corners
, {x
=pos
.x
, z
=pos
.z
})
184 -- If all openings are only at corners, the dungeon can't be accessed yet.
185 -- This code extends the openings of corners so they can be entered.
186 if openings_counter
>= 1 and openings_counter
== #corners
then
188 -- Prevent creating too many openings because this would lead to dungeon rejection
189 if openings_counter
>= 5 then
192 -- A corner is widened by adding openings to both neighbors
193 local cx
, cz
= corners
[c
].x
, corners
[c
].z
194 local cxn
, czn
= cx
, cz
205 openings
[cx
][czn
] = true
206 openings_counter
= openings_counter
+ 1
207 if openings_counter
< 5 then
208 openings
[cxn
][cz
] = true
209 openings_counter
= openings_counter
+ 1
214 -- Check conditions. If okay, start generating
215 if ceilingfloor_ok
and openings_counter
>= 1 and openings_counter
<= 5 then
216 -- Okay! Spawning starts!
218 -- First prepare random chest positions.
219 -- Chests spawn at wall
221 -- We assign each position at the wall a number and each chest gets one of these numbers randomly
222 local totalChests
= 2 -- this code strongly relies on this number being 2
223 local totalChestSlots
= (dim
.x
-1) * (dim
.z
-1)
224 local chestSlots
= {}
225 -- There is a small chance that both chests have the same slot.
226 -- In that case, we give a 2nd chance for the 2nd chest to get spawned.
227 -- If it failed again, tough luck! We stick with only 1 chest spawned.
229 local secondChance
= true -- second chance is still available
230 for i
=1, totalChests
do
231 local r
= math
.random(1, totalChestSlots
)
232 if r
== lastRandom
and secondChance
then
233 -- Oops! Same slot selected. Try again.
234 r
= math
.random(1, totalChestSlots
)
238 table.insert(chestSlots
, r
)
240 table.sort(chestSlots
)
241 local currentChest
= 1
243 -- Calculate the mob spawner position, to be re-used for later
244 local spawner_pos
= {x
= x
+ math
.ceil(dim
.x
/2), y
= y
+1, z
= z
+ math
.ceil(dim
.z
/2)}
245 table.insert(spawner_posses
, spawner_pos
)
247 -- Generate walls and floor
248 local maxx
, maxy
, maxz
= x
+dim
.x
+1, y
+dim
.y
, z
+dim
.z
+1
249 local chestSlotCounter
= 1
253 local p_pos
= area
:index(tx
, ty
, tz
)
255 -- Do not overwrite nodes with is_ground_content == false (e.g. bedrock)
256 -- Exceptions: cobblestone and moss stone so neighborings dungeons nicely connect to each other
257 local name
= minetest
.get_name_from_content_id(data
[p_pos
])
258 if name
== "mcl_core:cobble" or name
== "mcl_core:mossycobble" or minetest
.registered_nodes
[name
].is_ground_content
then
261 if math
.random(1,4) == 1 then
262 data
[p_pos
] = c_cobble
264 data
[p_pos
] = c_mossycobble
268 --[[ Note: No additional cobblestone ceiling is generated. This is intentional.
269 The solid blocks above the dungeon are considered as the “ceiling”.
270 It is possible (but rare) for a dungeon to generate below sand or gravel. ]]
272 elseif ty
> y
and (tx
== x
or tx
== maxx
or (tz
== z
or tz
== maxz
)) then
273 -- Check if it's an opening first
274 if (not openings
[tx
][tz
]) or ty
== maxy
then
275 -- Place wall or ceiling
276 data
[p_pos
] = c_cobble
277 elseif ty
< maxy
- 1 then
278 -- Normally the openings are already clear, but not if it is a corner
279 -- widening. Make sure to clear at least the bottom 2 nodes of an opening.
281 elseif ty
== maxy
- 1 and data
[p_pos
] ~= c_air
then
282 -- This allows for variation between 2-node and 3-node high openings.
283 data
[p_pos
] = c_cobble
285 -- If it was an opening, the lower 3 blocks are not touched at all
289 local forChest
= ty
==y
+1 and (tx
==x
+1 or tx
==maxx
-1 or tz
==z
+1 or tz
==maxz
-1)
291 -- Place next chest at the wall (if it was its chosen wall slot)
292 if forChest
and (currentChest
< totalChests
+ 1) and (chestSlots
[currentChest
] == chestSlotCounter
) then
293 table.insert(chest_posses
, {x
=tx
, y
=ty
, z
=tz
})
294 currentChest
= currentChest
+ 1
299 chestSlotCounter
= chestSlotCounter
+ 1
313 local chest_param2
= {}
314 -- Determine correct chest rotation (must pointi outwards)
315 for c
=1, #chest_posses
do
316 local cpos
= chest_posses
[c
]
318 -- Check surroundings of chest to determine correct rotation
319 local surround_vectors
= {
325 local surroundings
= {}
327 for s
=1, #surround_vectors
do
328 -- Detect the 4 horizontal neighbors
329 local spos
= vector
.add(cpos
, surround_vectors
[s
])
330 local wpos
= vector
.subtract(cpos
, surround_vectors
[s
])
331 local p_pos
= area
:index(spos
.x
, spos
.y
, spos
.z
)
332 local p_pos2
= area
:index(wpos
.x
, wpos
.y
, wpos
.z
)
334 local nodename
= minetest
.get_name_from_content_id(data
[p_pos
])
335 local nodename2
= minetest
.get_name_from_content_id(data
[p_pos2
])
336 local nodedef
= minetest
.registered_nodes
[nodename
]
337 local nodedef2
= minetest
.registered_nodes
[nodename2
]
338 -- The chest needs an open space in front of it and a walkable node (except chest) behind it
339 if nodedef
and nodedef
.walkable
== false and nodedef2
and nodedef2
.walkable
== true and nodename2
~= "mcl_chests:chest" then
340 table.insert(surroundings
, spos
)
343 -- Set param2 (=facedir) of this chest
345 if #surroundings
<= 0 then
346 -- Fallback if chest ended up in the middle of a room for some reason
347 facedir
= math
.random(0, 0)
349 -- 1 or multiple possible open directions: Choose random facedir
350 local face_to
= surroundings
[math
.random(1, #surroundings
)]
351 facedir
= minetest
.dir_to_facedir(vector
.subtract(cpos
, face_to
))
353 chest_param2
[c
] = facedir
356 -- Finally generate the dungeons all at once (except the chests and the spawners)
362 -- Chests are placed seperately
363 for c
=1, #chest_posses
do
364 local cpos
= chest_posses
[c
]
365 minetest
.set_node(cpos
, {name
="mcl_chests:chest", param2
=chest_param2
[c
]})
366 local meta
= minetest
.get_meta(cpos
)
367 local inv
= meta
:get_inventory()
368 local items
= get_loot()
369 for i
=1, math
.min(#items
, inv
:get_size("main")) do
370 inv
:set_stack("main", i
, ItemStack(items
[i
]))
374 -- Mob spawners are placed seperately, too
375 -- We don't want to destroy non-ground nodes
376 for s
=1, #spawner_posses
do
377 local sp
= spawner_posses
[s
]
378 local n
= minetest
.get_name_from_content_id(data
[area
:index(sp
.x
,sp
.y
,sp
.z
)])
379 if minetest
.registered_nodes
[n
].is_ground_content
then
381 -- ... and place it and select a random mob
382 minetest
.set_node(sp
, {name
= "mcl_mobspawners:spawner"})
389 local spawner_mob
= mobs
[math
.random(1, #mobs
)]
391 mcl_mobspawners
.setup_spawner(sp
, spawner_mob
)