Nether mod for Minetest
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

501 lines
18KB

  1. --[[
  2. Nether mod for minetest
  3. Copyright (C) 2013 PilzAdam
  4. Permission to use, copy, modify, and/or distribute this software for
  5. any purpose with or without fee is hereby granted, provided that the
  6. above copyright notice and this permission notice appear in all copies.
  7. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
  8. WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
  9. WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
  10. BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
  11. OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  12. WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
  13. ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
  14. SOFTWARE.
  15. ]]--
  16. -- Parameters
  17. local NETHER_CEILING = nether.DEPTH_CEILING
  18. local NETHER_FLOOR = nether.DEPTH_FLOOR
  19. local TCAVE = 0.6
  20. local BLEND = 128
  21. -- Stuff
  22. local math_max, math_min = math.max, math.min -- avoid needing table lookups each time a common math function is invoked
  23. if minetest.read_schematic == nil then
  24. -- Using biomes to create the Nether requires the ability for biomes to set "node_cave_liquid = air".
  25. -- This feature was introduced by paramat in b1b40fef1 on 2019-05-19, but we can't test for
  26. -- it directly. However b2065756c was merged a few months later (in 2019-08-14) and it is easy
  27. -- to directly test for - it adds minetest.read_schematic() - so we use this as a proxy-test
  28. -- for whether the Minetest engine is recent enough to have implemented node_cave_liquid=air
  29. error("This " .. nether.modname .. " mapgen requires Minetest v5.1 or greater, use mapgen_nobiomes.lua instead.", 0)
  30. end
  31. local function override_underground_biomes()
  32. -- https://forum.minetest.net/viewtopic.php?p=257522#p257522
  33. -- Q: Is there a way to override an already-registered biome so I can get it out of the
  34. -- way of my own underground biomes without disturbing the other biomes registered by
  35. -- default?
  36. -- A: No, all you can do is use a mod to clear all biomes then re-register the complete
  37. -- set but with your changes. It has been described as hacky but this is actually the
  38. -- official way to alter biomes, most mods and subgames would want to completely change
  39. -- all biomes anyway.
  40. -- To avoid the engine side of mapgen becoming overcomplex the approach is to require mods
  41. -- to do slightly more complex stuff in Lua.
  42. -- take a copy of all biomes, decorations, and ores. Regregistering a biome changes its ID, so
  43. -- any decorations or ores using the 'biomes' field must afterwards be cleared and re-registered.
  44. -- https://github.com/minetest/minetest/issues/9288
  45. local registered_biomes_copy = {}
  46. local registered_decorations_copy = {}
  47. local registered_ores_copy = {}
  48. for old_biome_key, old_biome_def in pairs(minetest.registered_biomes) do
  49. registered_biomes_copy[old_biome_key] = old_biome_def
  50. end
  51. for old_decoration_key, old_decoration_def in pairs(minetest.registered_decorations) do
  52. registered_decorations_copy[old_decoration_key] = old_decoration_def
  53. end
  54. for old_ore_key, old_ore_def in pairs(minetest.registered_ores) do
  55. registered_ores_copy[old_ore_key] = old_ore_def
  56. end
  57. -- clear biomes, decorations, and ores
  58. minetest.clear_registered_decorations()
  59. minetest.clear_registered_ores()
  60. minetest.clear_registered_biomes()
  61. -- Restore biomes, adjusted to not overlap the Nether
  62. for biome_key, new_biome_def in pairs(registered_biomes_copy) do
  63. local biome_y_max, biome_y_min = tonumber(new_biome_def.y_max), tonumber(new_biome_def.y_min)
  64. if biome_y_max > NETHER_FLOOR and biome_y_min < NETHER_CEILING then
  65. -- This biome occupies some or all of the depth of the Nether, shift/crop it.
  66. local spaceOccupiedAbove = biome_y_max - NETHER_CEILING
  67. local spaceOccupiedBelow = NETHER_FLOOR - biome_y_min
  68. if spaceOccupiedAbove >= spaceOccupiedBelow or biome_y_min <= -30000 then
  69. -- place the biome above the Nether
  70. -- We also shift biomes which extend to the bottom of the map above the Nether, since they
  71. -- likely only extend that deep as a catch-all, and probably have a role nearer the surface.
  72. new_biome_def.y_min = NETHER_CEILING + 1
  73. new_biome_def.y_max = math_max(biome_y_max, NETHER_CEILING + 2)
  74. else
  75. -- shift the biome to below the Nether
  76. new_biome_def.y_max = NETHER_FLOOR - 1
  77. new_biome_def.y_min = math_min(biome_y_min, NETHER_CEILING - 2)
  78. end
  79. end
  80. minetest.register_biome(new_biome_def)
  81. end
  82. -- Restore biome decorations
  83. for decoration_key, new_decoration_def in pairs(registered_decorations_copy) do
  84. minetest.register_decoration(new_decoration_def)
  85. end
  86. -- Restore biome ores
  87. for ore_key, new_ore_def in pairs(registered_ores_copy) do
  88. minetest.register_ore(new_ore_def)
  89. end
  90. end
  91. -- Shift any overlapping biomes out of the way before we create the Nether biomes
  92. override_underground_biomes()
  93. -- nether:native_mapgen is used to prevent ores and decorations being generated according
  94. -- to landforms created by the native mapgen.
  95. -- Ores and decorations are registered against "nether:rack" instead, and the lua
  96. -- on_generate() callback will carve the Nether with nether:rack before invoking
  97. -- generate_decorations and generate_ores.
  98. minetest.register_node("nether:native_mapgen", {})
  99. minetest.register_biome({
  100. name = "nether_caverns",
  101. node_stone = "nether:native_mapgen", -- nether:native_mapgen is used here to prevent the native mapgen from placing ores and decorations.
  102. node_filler = "nether:native_mapgen", -- The lua on_generate will transform nether:rack_native into nether:rack then decorate and add ores.
  103. node_dungeon = "nether:brick",
  104. --node_dungeon_alt = "default:mossycobble",
  105. node_dungeon_stair = "stairs:stair_nether_brick",
  106. -- Setting node_cave_liquid to "air" avoids the need to filter lava and water out of the mapchunk and
  107. -- surrounding shell (overdraw nodes beyond the mapchunk).
  108. -- This feature was introduced by paramat in b1b40fef1 on 2019-05-19, and this mapgen.lua file should only
  109. -- be run if the Minetest version includes it. The earliest tag made after 2019-05-19 is 5.1.0 on 2019-10-13,
  110. -- however we shouldn't test version numbers. minetest.read_schematic() was added by b2065756c and merged in
  111. -- 2019-08-14 and is easy to test for, we don't use it but it should make a good proxy-test for whether the
  112. -- Minetest version is recent enough to have implemented node_cave_liquid=air
  113. node_cave_liquid = "air",
  114. y_max = NETHER_CEILING,
  115. y_min = NETHER_FLOOR,
  116. vertical_blend = 0,
  117. heat_point = 50,
  118. humidity_point = 50,
  119. })
  120. -- Ores and decorations
  121. dofile(nether.path .. "/mapgen_decorations.lua")
  122. minetest.register_ore({
  123. ore_type = "scatter",
  124. ore = "nether:glowstone",
  125. wherein = "nether:rack",
  126. clust_scarcity = 11 * 11 * 11,
  127. clust_num_ores = 3,
  128. clust_size = 2,
  129. y_max = NETHER_CEILING,
  130. y_min = NETHER_FLOOR,
  131. })
  132. minetest.register_ore({
  133. ore_type = "scatter",
  134. ore = "default:lava_source",
  135. wherein = "nether:rack",
  136. clust_scarcity = 32 * 32 * 32,
  137. clust_num_ores = 4,
  138. clust_size = 2,
  139. y_max = NETHER_CEILING,
  140. y_min = NETHER_FLOOR,
  141. })
  142. minetest.register_ore({
  143. ore_type = "blob",
  144. ore = "nether:sand",
  145. wherein = "nether:rack",
  146. clust_scarcity = 14 * 14 * 14,
  147. clust_size = 8,
  148. y_max = NETHER_CEILING,
  149. y_min = NETHER_FLOOR
  150. })
  151. -- Mapgen
  152. -- 3D noise
  153. local np_cave = {
  154. offset = 0,
  155. scale = 1,
  156. spread = {x = 384, y = 128, z = 384}, -- squashed 3:1
  157. seed = 59033,
  158. octaves = 5,
  159. persist = 0.7,
  160. lacunarity = 2.0,
  161. --flags = ""
  162. }
  163. -- Buffers and objects we shouldn't recreate every on_generate
  164. local nobj_cave = nil
  165. local nbuf_cave = {}
  166. local dbuf = {}
  167. local yblmin = NETHER_FLOOR + BLEND * 2
  168. local yblmax = NETHER_CEILING - BLEND * 2
  169. -- Content ids
  170. local c_air = minetest.get_content_id("air")
  171. local c_netherrack = minetest.get_content_id("nether:rack")
  172. local c_netherbrick = minetest.get_content_id("nether:brick")
  173. local c_netherbrick_slab = minetest.get_content_id("stairs:slab_nether_brick")
  174. local c_netherfence = minetest.get_content_id("nether:fence_nether_brick")
  175. local c_glowstone = minetest.get_content_id("nether:glowstone")
  176. local c_lava_source = minetest.get_content_id("default:lava_source")
  177. local c_native_mapgen = minetest.get_content_id("nether:native_mapgen")
  178. -- Dungeon excavation functions
  179. function build_dungeon_room_list(data, area)
  180. local result = {}
  181. -- Unfortunately gennotify only returns dungeon rooms, not corridors.
  182. -- We don't need to check for temples because only dungeons are generated in biomes
  183. -- that define their own dungeon nodes.
  184. local gennotify = minetest.get_mapgen_object("gennotify")
  185. local roomLocations = gennotify["dungeon"] or {}
  186. -- Excavation should still know to stop if a cave or corridor has removed the dungeon wall.
  187. -- See MapgenBasic::generateDungeons in mapgen.cpp for max room sizes.
  188. local maxRoomSize = 18
  189. local maxRoomRadius = math.ceil(maxRoomSize / 2)
  190. local xStride, yStride, zStride = 1, area.ystride, area.zstride
  191. local minEdge, maxEdge = area.MinEdge, area.MaxEdge
  192. for _, roomPos in ipairs(roomLocations) do
  193. if area:containsp(roomPos) then -- this safety check does not appear to be necessary, but lets make it explicit
  194. local room_vi = area:indexp(roomPos)
  195. --data[room_vi] = minetest.get_content_id("default:torch") -- debug
  196. local startPos = vector.new(roomPos)
  197. if roomPos.y + 1 <= maxEdge.y and data[room_vi + yStride] == c_air then
  198. -- The roomPos coords given by gennotify are at floor level, but whenever possible we
  199. -- want to be performing searches a node higher than floor level to avoids dungeon chests.
  200. startPos.y = startPos.y + 1
  201. room_vi = area:indexp(startPos)
  202. end
  203. local bound_min_x = math_max(minEdge.x, roomPos.x - maxRoomRadius)
  204. local bound_min_y = math_max(minEdge.y, roomPos.y - 1) -- room coords given by gennotify are on the floor
  205. local bound_min_z = math_max(minEdge.z, roomPos.z - maxRoomRadius)
  206. local bound_max_x = math_min(maxEdge.x, roomPos.x + maxRoomRadius)
  207. local bound_max_y = math_min(maxEdge.y, roomPos.y + maxRoomSize) -- room coords given by gennotify are on the floor
  208. local bound_max_z = math_min(maxEdge.z, roomPos.z + maxRoomRadius)
  209. local room_min = vector.new(startPos)
  210. local room_max = vector.new(startPos)
  211. local vi = room_vi
  212. while room_max.y < bound_max_y and data[vi + yStride] == c_air do
  213. room_max.y = room_max.y + 1
  214. vi = vi + yStride
  215. end
  216. vi = room_vi
  217. while room_min.y > bound_min_y and data[vi - yStride] == c_air do
  218. room_min.y = room_min.y - 1
  219. vi = vi - yStride
  220. end
  221. vi = room_vi
  222. while room_max.z < bound_max_z and data[vi + zStride] == c_air do
  223. room_max.z = room_max.z + 1
  224. vi = vi + zStride
  225. end
  226. vi = room_vi
  227. while room_min.z > bound_min_z and data[vi - zStride] == c_air do
  228. room_min.z = room_min.z - 1
  229. vi = vi - zStride
  230. end
  231. vi = room_vi
  232. while room_max.x < bound_max_x and data[vi + xStride] == c_air do
  233. room_max.x = room_max.x + 1
  234. vi = vi + xStride
  235. end
  236. vi = room_vi
  237. while room_min.x > bound_min_x and data[vi - xStride] == c_air do
  238. room_min.x = room_min.x - 1
  239. vi = vi - xStride
  240. end
  241. local roomInfo = vector.new(roomPos)
  242. roomInfo.minp = room_min
  243. roomInfo.maxp = room_max
  244. result[#result + 1] = roomInfo
  245. end
  246. end
  247. return result;
  248. end
  249. -- Only partially excavates dungeons, the rest is left as an exercise for the player ;)
  250. -- (Corridors and the parts of rooms which extend beyond the emerge boundary will remain filled)
  251. function excavate_dungeons(data, area, rooms)
  252. -- any air from the native mapgen has been replaced by netherrack, but
  253. -- we don't want this inside dungeons, so fill dungeon rooms with air
  254. for _, roomInfo in ipairs(rooms) do
  255. local room_min = roomInfo.minp
  256. local room_max = roomInfo.maxp
  257. for z = room_min.z, room_max.z do
  258. for y = room_min.y, room_max.y do
  259. local vi = area:index(room_min.x, y, z)
  260. for x = room_min.x, room_max.x do
  261. if data[vi] == c_netherrack then data[vi] = c_air end
  262. vi = vi + 1
  263. end
  264. end
  265. end
  266. end
  267. end
  268. -- Since we already know where all the rooms and their walls are, and have all the nodes stored
  269. -- in a voxelmanip already, we may as well add a little Nether flair to the dungeons found here.
  270. function decorate_dungeons(data, area, rooms)
  271. local xStride, yStride, zStride = 1, area.ystride, area.zstride
  272. local minEdge, maxEdge = area.MinEdge, area.MaxEdge
  273. for _, roomInfo in ipairs(rooms) do
  274. local room_min, room_max = roomInfo.minp, roomInfo.maxp
  275. local room_size = vector.distance(room_min, room_max)
  276. if room_size > 10 then
  277. local room_seed = roomInfo.x + 3 * roomInfo.z + 13 * roomInfo.y
  278. local window_y = roomInfo.y + math_min(2, room_max.y - roomInfo.y - 1)
  279. if room_seed % 3 == 0 and room_max.y < maxEdge.y then
  280. -- Glowstone chandelier
  281. local vi = area:index(roomInfo.x, room_max.y + 1, roomInfo.z)
  282. if data[vi] == c_netherbrick then data[vi] = c_glowstone end
  283. elseif room_seed % 4 == 0 and room_min.y > minEdge.y
  284. and room_min.x > minEdge.x and room_max.x < maxEdge.x
  285. and room_min.z > minEdge.z and room_max.z < maxEdge.z then
  286. -- lava well (feel free to replace with a fancy schematic)
  287. local vi = area:index(roomInfo.x, room_min.y, roomInfo.z)
  288. if data[vi - yStride] == c_netherbrick then data[vi - yStride] = c_lava_source end
  289. if data[vi - zStride] == c_air then data[vi - zStride] = c_netherbrick_slab end
  290. if data[vi + zStride] == c_air then data[vi + zStride] = c_netherbrick_slab end
  291. if data[vi - xStride] == c_air then data[vi - xStride] = c_netherbrick_slab end
  292. if data[vi + xStride] == c_air then data[vi + xStride] = c_netherbrick_slab end
  293. end
  294. -- Barred windows
  295. if room_seed % 7 < 5 and room_max.x - room_min.x >= 4 and room_max.z - room_min.z >= 4
  296. and window_y >= minEdge.y and window_y + 1 <= maxEdge.y
  297. and room_min.x > minEdge.x and room_max.x < maxEdge.x
  298. and room_min.z > minEdge.z and room_max.z < maxEdge.z then
  299. --data[area:indexp(roomInfo)] = minetest.get_content_id("default:mese_post_light") -- debug
  300. -- Until whisper glass is added, every window will be made of netherbrick fence (rather
  301. -- than material depending on room_seed)
  302. local window_node = c_netherfence
  303. local vi_min = area:index(room_min.x - 1, window_y, roomInfo.z)
  304. local vi_max = area:index(room_max.x + 1, window_y, roomInfo.z)
  305. local locations = {-zStride, zStride, -zStride + yStride, zStride + yStride}
  306. for _, offset in ipairs(locations) do
  307. if data[vi_min + offset] == c_netherbrick then data[vi_min + offset] = window_node end
  308. if data[vi_max + offset] == c_netherbrick then data[vi_max + offset] = window_node end
  309. end
  310. vi_min = area:index(roomInfo.x, window_y, room_min.z - 1)
  311. vi_max = area:index(roomInfo.x, window_y, room_max.z + 1)
  312. locations = {-xStride, xStride, -xStride + yStride, xStride + yStride}
  313. for _, offset in ipairs(locations) do
  314. if data[vi_min + offset] == c_netherbrick then data[vi_min + offset] = window_node end
  315. if data[vi_max + offset] == c_netherbrick then data[vi_max + offset] = window_node end
  316. end
  317. end
  318. -- Weeds on the floor once Nether weeds are added
  319. end
  320. end
  321. end
  322. -- On-generated function
  323. local function on_generated(minp, maxp, seed)
  324. if minp.y > NETHER_CEILING or maxp.y < NETHER_FLOOR then
  325. return
  326. end
  327. local vm, emerge_min, emerge_max = minetest.get_mapgen_object("voxelmanip")
  328. local area = VoxelArea:new{MinEdge=emerge_min, MaxEdge=emerge_max}
  329. local data = vm:get_data(dbuf)
  330. local x0, y0, z0 = minp.x, math_max(minp.y, NETHER_FLOOR), minp.z
  331. local x1, y1, z1 = maxp.x, math_min(maxp.y, NETHER_CEILING), maxp.z
  332. local yCaveStride = x1 - x0 + 1
  333. local zCaveStride = yCaveStride * yCaveStride
  334. local chulens = {x = yCaveStride, y = yCaveStride, z = yCaveStride}
  335. nobj_cave = nobj_cave or minetest.get_perlin_map(np_cave, chulens)
  336. local nvals_cave = nobj_cave:get_3d_map_flat(minp, nbuf_cave)
  337. local dungeonRooms = build_dungeon_room_list(data, area)
  338. for y = y0, y1 do -- Y loop first to minimise tcave calculations
  339. local tcave = TCAVE
  340. if y > yblmax then tcave = TCAVE + ((y - yblmax) / BLEND) ^ 2 end
  341. if y < yblmin then tcave = TCAVE + ((yblmin - y) / BLEND) ^ 2 end
  342. for z = z0, z1 do
  343. local vi = area:index(x0, y, z) -- Initial voxelmanip index
  344. local ni = (z - z0) * zCaveStride + (y - y0) * yCaveStride + 1
  345. for x = x0, x1 do
  346. local id = data[vi] -- Existing node
  347. if nvals_cave[ni] > tcave then
  348. data[vi] = c_air
  349. elseif id == c_air or id == c_native_mapgen then
  350. data[vi] = c_netherrack -- excavate_dungeons() will mostly reverse this inside dungeons
  351. end
  352. ni = ni + 1
  353. vi = vi + 1
  354. end
  355. end
  356. end
  357. -- any air from the native mapgen has been replaced by netherrack, but we
  358. -- don't want netherrack inside dungeons, so fill known dungeon rooms with air.
  359. excavate_dungeons(data, area, dungeonRooms)
  360. decorate_dungeons(data, area, dungeonRooms)
  361. vm:set_data(data)
  362. -- avoid generating decorations on the underside of the bottom of the nether
  363. if minp.y > NETHER_FLOOR and maxp.y < NETHER_CEILING then minetest.generate_decorations(vm) end
  364. minetest.generate_ores(vm)
  365. vm:set_lighting({day = 0, night = 0}, minp, maxp)
  366. vm:calc_lighting()
  367. vm:update_liquids()
  368. vm:write_to_map()
  369. end
  370. -- use knowledge of the nether mapgen algorithm to return a suitable ground level for placing a portal.
  371. function nether.find_nether_ground_y(target_x, target_z, start_y)
  372. local nobj_cave_point = minetest.get_perlin(np_cave)
  373. local air = 0 -- Consecutive air nodes found
  374. for y = start_y, math_max(NETHER_FLOOR + BLEND, start_y - 4096), -1 do
  375. local nval_cave = nobj_cave_point:get3d({x = target_x, y = y, z = target_z})
  376. if nval_cave > TCAVE then -- Cavern
  377. air = air + 1
  378. else -- Not cavern, check if 4 nodes of space above
  379. if air >= 4 then
  380. -- Check volume for non-natural nodes
  381. local minp = {x = target_x - 1, y = y , z = target_z - 2}
  382. local maxp = {x = target_x + 2, y = y + 4, z = target_z + 2}
  383. if nether.volume_is_natural(minp, maxp) then
  384. return y + 1
  385. else -- Restart search a little lower
  386. nether.find_nether_ground_y(target_x, target_z, y - 16)
  387. end
  388. else -- Not enough space, reset air to zero
  389. air = 0
  390. end
  391. end
  392. end
  393. return math_max(start_y, NETHER_FLOOR + BLEND) -- Fallback
  394. end
  395. -- We don't need to be gen-notified of temples because only dungeons will be generated
  396. -- if a biome defines the dungeon nodes
  397. minetest.set_gen_notify({dungeon = true})
  398. minetest.register_on_generated(on_generated)