mapgen_dungeons.lua 13 KB

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