mapgen_dungeons.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. --[[
  2. Nether mod for minetest
  3. All the Dungeon related functions used by the biomes-based mapgen are here.
  4. Copyright (C) 2021 Treer
  5. Permission to use, copy, modify, and/or distribute this software for
  6. any purpose with or without fee is hereby granted, provided that the
  7. above copyright notice and this permission notice appear in all copies.
  8. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
  9. WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
  10. WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
  11. BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
  12. OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  13. WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
  14. ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
  15. SOFTWARE.
  16. ]]--
  17. -- We don't need to be gen-notified of temples because only dungeons will be generated
  18. -- if a biome defines the dungeon nodes
  19. minetest.set_gen_notify({dungeon = true})
  20. -- Content ids
  21. local c_air = minetest.get_content_id("air")
  22. local c_netherrack = minetest.get_content_id("nether:rack")
  23. local c_netherrack_deep = minetest.get_content_id("nether:rack_deep")
  24. local c_dungeonbrick = minetest.get_content_id("nether:brick")
  25. local c_dungeonbrick_alt = minetest.get_content_id("nether:brick_cracked")
  26. local c_netherbrick_slab = minetest.get_content_id("stairs:slab_nether_brick")
  27. local c_netherfence = minetest.get_content_id("nether:fence_nether_brick")
  28. local c_glowstone = minetest.get_content_id("nether:glowstone")
  29. local c_glowstone_deep = minetest.get_content_id("nether:glowstone_deep")
  30. local c_lava_source = minetest.get_content_id("default:lava_source")
  31. -- Misc math functions
  32. -- avoid needing table lookups each time a common math function is invoked
  33. local math_max, math_min = math.max, math.min
  34. -- Dungeon excavation functions
  35. function is_dungeon_brick(node_id)
  36. return node_id == c_dungeonbrick or node_id == c_dungeonbrick_alt
  37. end
  38. nether.mapgen.build_dungeon_room_list = function(data, area)
  39. local result = {}
  40. -- Unfortunately gennotify only returns dungeon rooms, not corridors.
  41. -- We don't need to check for temples because only dungeons are generated in biomes
  42. -- that define their own dungeon nodes.
  43. local gennotify = minetest.get_mapgen_object("gennotify")
  44. local roomLocations = gennotify["dungeon"] or {}
  45. -- Excavation should still know to stop if a cave or corridor has removed the dungeon wall.
  46. -- See MapgenBasic::generateDungeons in mapgen.cpp for max room sizes.
  47. local maxRoomSize = 18
  48. local maxRoomRadius = math.ceil(maxRoomSize / 2)
  49. local xStride, yStride, zStride = 1, area.ystride, area.zstride
  50. local minEdge, maxEdge = area.MinEdge, area.MaxEdge
  51. for _, roomPos in ipairs(roomLocations) do
  52. if area:containsp(roomPos) then -- this safety check does not appear to be necessary, but lets make it explicit
  53. local room_vi = area:indexp(roomPos)
  54. --data[room_vi] = minetest.get_content_id("default:torch") -- debug
  55. local startPos = vector.new(roomPos)
  56. if roomPos.y + 1 <= maxEdge.y and data[room_vi + yStride] == c_air then
  57. -- The roomPos coords given by gennotify are at floor level, but whenever possible we
  58. -- want to be performing searches a node higher than floor level to avoids dungeon chests.
  59. startPos.y = startPos.y + 1
  60. room_vi = area:indexp(startPos)
  61. end
  62. local bound_min_x = math_max(minEdge.x, roomPos.x - maxRoomRadius)
  63. local bound_min_y = math_max(minEdge.y, roomPos.y - 1) -- room coords given by gennotify are on the floor
  64. local bound_min_z = math_max(minEdge.z, roomPos.z - maxRoomRadius)
  65. local bound_max_x = math_min(maxEdge.x, roomPos.x + maxRoomRadius)
  66. local bound_max_y = math_min(maxEdge.y, roomPos.y + maxRoomSize) -- room coords given by gennotify are on the floor
  67. local bound_max_z = math_min(maxEdge.z, roomPos.z + maxRoomRadius)
  68. local room_min = vector.new(startPos)
  69. local room_max = vector.new(startPos)
  70. local vi = room_vi
  71. while room_max.y < bound_max_y and data[vi + yStride] == c_air do
  72. room_max.y = room_max.y + 1
  73. vi = vi + yStride
  74. end
  75. vi = room_vi
  76. while room_min.y > bound_min_y and data[vi - yStride] == c_air do
  77. room_min.y = room_min.y - 1
  78. vi = vi - yStride
  79. end
  80. vi = room_vi
  81. while room_max.z < bound_max_z and data[vi + zStride] == c_air do
  82. room_max.z = room_max.z + 1
  83. vi = vi + zStride
  84. end
  85. vi = room_vi
  86. while room_min.z > bound_min_z and data[vi - zStride] == c_air do
  87. room_min.z = room_min.z - 1
  88. vi = vi - zStride
  89. end
  90. vi = room_vi
  91. while room_max.x < bound_max_x and data[vi + xStride] == c_air do
  92. room_max.x = room_max.x + 1
  93. vi = vi + xStride
  94. end
  95. vi = room_vi
  96. while room_min.x > bound_min_x and data[vi - xStride] == c_air do
  97. room_min.x = room_min.x - 1
  98. vi = vi - xStride
  99. end
  100. local roomInfo = vector.new(roomPos)
  101. roomInfo.minp = room_min
  102. roomInfo.maxp = room_max
  103. result[#result + 1] = roomInfo
  104. end
  105. end
  106. return result;
  107. end
  108. -- Only partially excavates dungeons, the rest is left as an exercise for the player ;)
  109. -- (Corridors and the parts of rooms which extend beyond the emerge boundary will remain filled)
  110. nether.mapgen.excavate_dungeons = function(data, area, rooms)
  111. local vi, node_id
  112. -- any air from the native mapgen has been replaced by netherrack, but
  113. -- we don't want this inside dungeons, so fill dungeon rooms with air
  114. for _, roomInfo in ipairs(rooms) do
  115. local room_min = roomInfo.minp
  116. local room_max = roomInfo.maxp
  117. for z = room_min.z, room_max.z do
  118. for y = room_min.y, room_max.y do
  119. vi = area:index(room_min.x, y, z)
  120. for x = room_min.x, room_max.x do
  121. node_id = data[vi]
  122. if node_id == c_netherrack or node_id == c_netherrack_deep then data[vi] = c_air end
  123. vi = vi + 1
  124. end
  125. end
  126. end
  127. end
  128. -- clear netherrack from dungeon stairways
  129. if #rooms > 0 then
  130. local stairPositions = minetest.find_nodes_in_area(area.MinEdge, area.MaxEdge, minetest.registered_biomes["nether_caverns"].node_dungeon_stair)
  131. for _, stairPos in ipairs(stairPositions) do
  132. vi = area:indexp(stairPos)
  133. for i = 1, 4 do
  134. if stairPos.y + i > area.MaxEdge.y then break end
  135. vi = vi + area.ystride
  136. node_id = data[vi]
  137. -- searching forward of the stairs could also be done
  138. if node_id == c_netherrack or node_id == c_netherrack_deep then data[vi] = c_air end
  139. end
  140. end
  141. end
  142. end
  143. -- Since we already know where all the rooms and their walls are, and have all the nodes stored
  144. -- in a voxelmanip already, we may as well add a little Nether flair to the dungeons found here.
  145. nether.mapgen.decorate_dungeons = function(data, area, rooms)
  146. local xStride, yStride, zStride = 1, area.ystride, area.zstride
  147. local minEdge, maxEdge = area.MinEdge, area.MaxEdge
  148. for _, roomInfo in ipairs(rooms) do
  149. local room_min, room_max = roomInfo.minp, roomInfo.maxp
  150. local room_size = vector.distance(room_min, room_max)
  151. if room_size > 10 then
  152. local room_seed = roomInfo.x + 3 * roomInfo.z + 13 * roomInfo.y
  153. local window_y = roomInfo.y + math_min(2, room_max.y - roomInfo.y - 1)
  154. local roomWidth = room_max.x - room_min.x + 1
  155. local roomLength = room_max.z - room_min.z + 1
  156. if room_seed % 3 == 0 and room_max.y < maxEdge.y then
  157. -- Glowstone chandelier (feel free to replace with a fancy schematic)
  158. local vi = area:index(roomInfo.x, room_max.y + 1, roomInfo.z)
  159. if is_dungeon_brick(data[vi]) then data[vi] = c_glowstone end
  160. elseif room_seed % 4 == 0 and room_min.y > minEdge.y
  161. and room_min.x > minEdge.x and room_max.x < maxEdge.x
  162. and room_min.z > minEdge.z and room_max.z < maxEdge.z then
  163. -- lava well (feel free to replace with a fancy schematic)
  164. local vi = area:index(roomInfo.x, room_min.y, roomInfo.z)
  165. if is_dungeon_brick(data[vi - yStride]) then
  166. data[vi - yStride] = c_lava_source
  167. if data[vi - zStride] == c_air then data[vi - zStride] = c_netherbrick_slab end
  168. if data[vi + zStride] == c_air then data[vi + zStride] = c_netherbrick_slab end
  169. if data[vi - xStride] == c_air then data[vi - xStride] = c_netherbrick_slab end
  170. if data[vi + xStride] == c_air then data[vi + xStride] = c_netherbrick_slab end
  171. end
  172. end
  173. -- Barred windows
  174. if room_seed % 7 < 5 and roomWidth >= 5 and roomLength >= 5
  175. and window_y >= minEdge.y and window_y + 1 <= maxEdge.y
  176. and room_min.x > minEdge.x and room_max.x < maxEdge.x
  177. and room_min.z > minEdge.z and room_max.z < maxEdge.z then
  178. --data[area:indexp(roomInfo)] = minetest.get_content_id("default:mese_post_light") -- debug
  179. -- Can't use glass panes because they need the param data set.
  180. -- Until whisper glass is added, every window will be made of netherbrick fence (rather
  181. -- than material depending on room_seed)
  182. local window_node = c_netherfence
  183. --if c_netherglass ~= nil and room_seed % 20 >= 12 then window_node = c_crystallight end
  184. local function placeWindow(vi, viOutsideOffset, windowNo)
  185. if is_dungeon_brick(data[vi]) and is_dungeon_brick(data[vi + yStride]) then
  186. data[vi] = window_node
  187. if room_seed % 19 == windowNo then
  188. -- place a glowstone light behind the window
  189. local node_id = data[vi + viOutsideOffset]
  190. if node_id == c_netherrack then
  191. data[vi + viOutsideOffset] = c_glowstone
  192. elseif node_id == c_netherrack_deep then
  193. data[vi + viOutsideOffset] = c_glowstone_deep
  194. end
  195. end
  196. end
  197. end
  198. local vi_min = area:index(room_min.x - 1, window_y, roomInfo.z)
  199. local vi_max = area:index(room_max.x + 1, window_y, roomInfo.z)
  200. local locations = {-zStride, zStride, -zStride + yStride, zStride + yStride}
  201. for i, offset in ipairs(locations) do
  202. placeWindow(vi_min + offset, -1, i)
  203. placeWindow(vi_max + offset, 1, i + #locations)
  204. end
  205. vi_min = area:index(roomInfo.x, window_y, room_min.z - 1)
  206. vi_max = area:index(roomInfo.x, window_y, room_max.z + 1)
  207. locations = {-xStride, xStride, -xStride + yStride, xStride + yStride}
  208. for i, offset in ipairs(locations) do
  209. placeWindow(vi_min + offset, -zStride, i + #locations * 2)
  210. placeWindow(vi_max + offset, zStride, i + #locations * 3)
  211. end
  212. end
  213. -- pillars or mezzanine floor
  214. if room_seed % 43 > 10 and roomWidth >= 6 and roomLength >= 6 then
  215. local pillar_vi = {}
  216. local pillarHeight = 0
  217. local wallDist = 1 + math.floor((roomWidth + roomLength) / 14)
  218. local roomHeight = room_max.y - room_min.y
  219. if roomHeight >= 7 then
  220. -- mezzanine floor
  221. local mezzMax = {
  222. x = room_min.x + math.floor(roomWidth / 7 * 4),
  223. y = room_min.y + math.floor(roomHeight / 5 * 3),
  224. z = room_max.z
  225. }
  226. pillarHeight = mezzMax.y - room_min.y - 1
  227. pillar_vi = {
  228. area:index(mezzMax.x, room_min.y, room_min.z + wallDist),
  229. area:index(mezzMax.x, room_min.y, room_max.z - wallDist),
  230. }
  231. if is_dungeon_brick(data[pillar_vi[1] - yStride]) and is_dungeon_brick(data[pillar_vi[2] - yStride]) then
  232. -- The floor of the dungeon looks like it exists (i.e. not erased by nether
  233. -- cavern), so add the mezzanine floor
  234. for z = 0, roomLength - 1 do
  235. local vi = area:index(room_min.x, mezzMax.y, room_min.z + z)
  236. for x = room_min.x, mezzMax.x do
  237. if data[vi] == c_air then data[vi] = c_dungeonbrick end
  238. vi = vi + 1
  239. end
  240. end
  241. end
  242. elseif roomHeight >= 4 then
  243. -- 4 pillars
  244. pillarHeight = roomHeight
  245. pillar_vi = {
  246. area:index(room_min.x + wallDist, room_min.y, room_min.z + wallDist),
  247. area:index(room_min.x + wallDist, room_min.y, room_max.z - wallDist),
  248. area:index(room_max.x - wallDist, room_min.y, room_min.z + wallDist),
  249. area:index(room_max.x - wallDist, room_min.y, room_max.z - wallDist)
  250. }
  251. end
  252. for i = #pillar_vi, 1, -1 do
  253. if not is_dungeon_brick(data[pillar_vi[i] - yStride]) then
  254. -- there's no dungeon floor under this pillar so skip it, it's probably been cut away by nether cavern.
  255. table.remove(pillar_vi, i)
  256. end
  257. end
  258. for y = 0, pillarHeight do
  259. for _, vi in ipairs(pillar_vi) do
  260. if data[vi + y * yStride] == c_air then data[vi + y * yStride] = c_dungeonbrick end
  261. end
  262. end
  263. end
  264. -- Weeds on the floor once Nether weeds are added
  265. end
  266. end
  267. end