mapgen.lua 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  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_abs, math_floor = math.max, math.min, math.abs, math.floor -- 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 = "nether:brick_cracked",
  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 = "nether:lava_crust",
  135. wherein = "nether:basalt",
  136. clust_scarcity = 16 * 16 * 16,
  137. clust_num_ores = 3,
  138. clust_size = 2,
  139. y_max = NETHER_CEILING,
  140. y_min = NETHER_FLOOR,
  141. })
  142. minetest.register_ore({
  143. ore_type = "scatter",
  144. ore = "default:lava_source",
  145. wherein = {"nether:rack", "nether:basalt"},
  146. clust_scarcity = 32 * 32 * 32,
  147. clust_num_ores = 4,
  148. clust_size = 2,
  149. y_max = NETHER_CEILING,
  150. y_min = NETHER_FLOOR,
  151. })
  152. minetest.register_ore({
  153. ore_type = "scatter",
  154. ore = "epic:stone_with_titanium",
  155. wherein = {"nether:rack"},
  156. clust_scarcity = 20 * 20 * 20,
  157. clust_num_ores = 6,
  158. clust_size = 2,
  159. y_max = NETHER_CEILING,
  160. y_min = NETHER_FLOOR,
  161. })
  162. minetest.register_ore({
  163. ore_type = "scatter",
  164. ore = "epic:nether_with_gold",
  165. wherein = {"nether:rack"},
  166. clust_scarcity = 16 * 16 * 16,
  167. clust_num_ores = 12,
  168. clust_size = 4,
  169. y_max = NETHER_CEILING,
  170. y_min = NETHER_FLOOR,
  171. })
  172. minetest.register_ore({
  173. ore_type = "scatter",
  174. ore = "epic:nether_with_diamond",
  175. wherein = {"nether:rack"},
  176. clust_scarcity = 17 * 17 * 17,
  177. clust_num_ores = 12,
  178. clust_size = 4,
  179. y_max = NETHER_CEILING,
  180. y_min = NETHER_FLOOR,
  181. })
  182. minetest.register_ore({
  183. ore_type = "blob",
  184. ore = "nether:sand",
  185. wherein = "nether:rack",
  186. clust_scarcity = 14 * 14 * 14,
  187. clust_size = 8,
  188. y_max = NETHER_CEILING,
  189. y_min = NETHER_FLOOR
  190. })
  191. -- Mapgen
  192. -- 3D noise
  193. local np_cave = {
  194. offset = 0,
  195. scale = 1,
  196. spread = {x = 384, y = 128, z = 384}, -- squashed 3:1
  197. seed = 59033,
  198. octaves = 5,
  199. persist = 0.7,
  200. lacunarity = 2.0,
  201. --flags = ""
  202. }
  203. -- Buffers and objects we shouldn't recreate every on_generate
  204. local nobj_cave = nil
  205. local nbuf_cave = {}
  206. local dbuf = {}
  207. local yblmin = NETHER_FLOOR + BLEND * 2
  208. local yblmax = NETHER_CEILING - BLEND * 2
  209. -- Content ids
  210. local c_air = minetest.get_content_id("air")
  211. local c_netherrack = minetest.get_content_id("nether:rack")
  212. local c_dungeonbrick = minetest.get_content_id("nether:brick")
  213. local c_dungeonbrick_alt = minetest.get_content_id("nether:brick_cracked")
  214. local c_netherbrick_slab = minetest.get_content_id("stairs:slab_nether_brick")
  215. local c_netherfence = minetest.get_content_id("nether:fence_nether_brick")
  216. local c_glowstone = minetest.get_content_id("nether:glowstone")
  217. local c_lava_source = minetest.get_content_id("default:lava_source")
  218. local c_lavasea_source = minetest.get_content_id("nether:lava_source") -- same as lava but with staggered animation to look better as an ocean
  219. local c_lava_crust = minetest.get_content_id("nether:lava_crust")
  220. local c_basalt = minetest.get_content_id("nether:basalt")
  221. local c_native_mapgen = minetest.get_content_id("nether:native_mapgen")
  222. local c_debug = minetest.get_content_id("default:glass")
  223. -- Dungeon excavation functions
  224. function is_dungeon_brick(node_id)
  225. return node_id == c_dungeonbrick or node_id == c_dungeonbrick_alt
  226. end
  227. function build_dungeon_room_list(data, area)
  228. local result = {}
  229. -- Unfortunately gennotify only returns dungeon rooms, not corridors.
  230. -- We don't need to check for temples because only dungeons are generated in biomes
  231. -- that define their own dungeon nodes.
  232. local gennotify = minetest.get_mapgen_object("gennotify")
  233. local roomLocations = gennotify["dungeon"] or {}
  234. -- Excavation should still know to stop if a cave or corridor has removed the dungeon wall.
  235. -- See MapgenBasic::generateDungeons in mapgen.cpp for max room sizes.
  236. local maxRoomSize = 18
  237. local maxRoomRadius = math.ceil(maxRoomSize / 2)
  238. local xStride, yStride, zStride = 1, area.ystride, area.zstride
  239. local minEdge, maxEdge = area.MinEdge, area.MaxEdge
  240. for _, roomPos in ipairs(roomLocations) do
  241. if area:containsp(roomPos) then -- this safety check does not appear to be necessary, but lets make it explicit
  242. local room_vi = area:indexp(roomPos)
  243. --data[room_vi] = minetest.get_content_id("default:torch") -- debug
  244. local startPos = vector.new(roomPos)
  245. if roomPos.y + 1 <= maxEdge.y and data[room_vi + yStride] == c_air then
  246. -- The roomPos coords given by gennotify are at floor level, but whenever possible we
  247. -- want to be performing searches a node higher than floor level to avoids dungeon chests.
  248. startPos.y = startPos.y + 1
  249. room_vi = area:indexp(startPos)
  250. end
  251. local bound_min_x = math_max(minEdge.x, roomPos.x - maxRoomRadius)
  252. local bound_min_y = math_max(minEdge.y, roomPos.y - 1) -- room coords given by gennotify are on the floor
  253. local bound_min_z = math_max(minEdge.z, roomPos.z - maxRoomRadius)
  254. local bound_max_x = math_min(maxEdge.x, roomPos.x + maxRoomRadius)
  255. local bound_max_y = math_min(maxEdge.y, roomPos.y + maxRoomSize) -- room coords given by gennotify are on the floor
  256. local bound_max_z = math_min(maxEdge.z, roomPos.z + maxRoomRadius)
  257. local room_min = vector.new(startPos)
  258. local room_max = vector.new(startPos)
  259. local vi = room_vi
  260. while room_max.y < bound_max_y and data[vi + yStride] == c_air do
  261. room_max.y = room_max.y + 1
  262. vi = vi + yStride
  263. end
  264. vi = room_vi
  265. while room_min.y > bound_min_y and data[vi - yStride] == c_air do
  266. room_min.y = room_min.y - 1
  267. vi = vi - yStride
  268. end
  269. vi = room_vi
  270. while room_max.z < bound_max_z and data[vi + zStride] == c_air do
  271. room_max.z = room_max.z + 1
  272. vi = vi + zStride
  273. end
  274. vi = room_vi
  275. while room_min.z > bound_min_z and data[vi - zStride] == c_air do
  276. room_min.z = room_min.z - 1
  277. vi = vi - zStride
  278. end
  279. vi = room_vi
  280. while room_max.x < bound_max_x and data[vi + xStride] == c_air do
  281. room_max.x = room_max.x + 1
  282. vi = vi + xStride
  283. end
  284. vi = room_vi
  285. while room_min.x > bound_min_x and data[vi - xStride] == c_air do
  286. room_min.x = room_min.x - 1
  287. vi = vi - xStride
  288. end
  289. local roomInfo = vector.new(roomPos)
  290. roomInfo.minp = room_min
  291. roomInfo.maxp = room_max
  292. result[#result + 1] = roomInfo
  293. end
  294. end
  295. return result;
  296. end
  297. -- Only partially excavates dungeons, the rest is left as an exercise for the player ;)
  298. -- (Corridors and the parts of rooms which extend beyond the emerge boundary will remain filled)
  299. function excavate_dungeons(data, area, rooms)
  300. local vi, node_id
  301. -- any air from the native mapgen has been replaced by netherrack, but
  302. -- we don't want this inside dungeons, so fill dungeon rooms with air
  303. for _, roomInfo in ipairs(rooms) do
  304. local room_min = roomInfo.minp
  305. local room_max = roomInfo.maxp
  306. for z = room_min.z, room_max.z do
  307. for y = room_min.y, room_max.y do
  308. vi = area:index(room_min.x, y, z)
  309. for x = room_min.x, room_max.x do
  310. node_id = data[vi]
  311. if node_id == c_netherrack or node_id == c_basalt then data[vi] = c_air end
  312. vi = vi + 1
  313. end
  314. end
  315. end
  316. end
  317. end
  318. -- Since we already know where all the rooms and their walls are, and have all the nodes stored
  319. -- in a voxelmanip already, we may as well add a little Nether flair to the dungeons found here.
  320. function decorate_dungeons(data, area, rooms)
  321. local xStride, yStride, zStride = 1, area.ystride, area.zstride
  322. local minEdge, maxEdge = area.MinEdge, area.MaxEdge
  323. for _, roomInfo in ipairs(rooms) do
  324. local room_min, room_max = roomInfo.minp, roomInfo.maxp
  325. local room_size = vector.distance(room_min, room_max)
  326. if room_size > 10 then
  327. local room_seed = roomInfo.x + 3 * roomInfo.z + 13 * roomInfo.y
  328. local window_y = roomInfo.y + math_min(2, room_max.y - roomInfo.y - 1)
  329. if room_seed % 3 == 0 and room_max.y < maxEdge.y then
  330. -- Glowstone chandelier (feel free to replace with a fancy schematic)
  331. local vi = area:index(roomInfo.x, room_max.y + 1, roomInfo.z)
  332. if is_dungeon_brick(data[vi]) then data[vi] = c_glowstone end
  333. elseif room_seed % 4 == 0 and room_min.y > minEdge.y
  334. and room_min.x > minEdge.x and room_max.x < maxEdge.x
  335. and room_min.z > minEdge.z and room_max.z < maxEdge.z then
  336. -- lava well (feel free to replace with a fancy schematic)
  337. local vi = area:index(roomInfo.x, room_min.y, roomInfo.z)
  338. if is_dungeon_brick(data[vi - yStride]) then
  339. data[vi - yStride] = c_lava_source
  340. if data[vi - zStride] == c_air then data[vi - zStride] = c_netherbrick_slab end
  341. if data[vi + zStride] == c_air then data[vi + zStride] = c_netherbrick_slab end
  342. if data[vi - xStride] == c_air then data[vi - xStride] = c_netherbrick_slab end
  343. if data[vi + xStride] == c_air then data[vi + xStride] = c_netherbrick_slab end
  344. end
  345. end
  346. -- Barred windows
  347. if room_seed % 7 < 5 and room_max.x - room_min.x >= 4 and room_max.z - room_min.z >= 4
  348. and window_y >= minEdge.y and window_y + 1 <= maxEdge.y
  349. and room_min.x > minEdge.x and room_max.x < maxEdge.x
  350. and room_min.z > minEdge.z and room_max.z < maxEdge.z then
  351. --data[area:indexp(roomInfo)] = minetest.get_content_id("default:mese_post_light") -- debug
  352. -- Until whisper glass is added, every window will be made of netherbrick fence (rather
  353. -- than material depending on room_seed)
  354. local window_node = c_netherfence
  355. local vi_min = area:index(room_min.x - 1, window_y, roomInfo.z)
  356. local vi_max = area:index(room_max.x + 1, window_y, roomInfo.z)
  357. local locations = {-zStride, zStride, -zStride + yStride, zStride + yStride}
  358. for _, offset in ipairs(locations) do
  359. if is_dungeon_brick(data[vi_min + offset]) then data[vi_min + offset] = window_node end
  360. if is_dungeon_brick(data[vi_max + offset]) then data[vi_max + offset] = window_node end
  361. end
  362. vi_min = area:index(roomInfo.x, window_y, room_min.z - 1)
  363. vi_max = area:index(roomInfo.x, window_y, room_max.z + 1)
  364. locations = {-xStride, xStride, -xStride + yStride, xStride + yStride}
  365. for _, offset in ipairs(locations) do
  366. if is_dungeon_brick(data[vi_min + offset]) then data[vi_min + offset] = window_node end
  367. if is_dungeon_brick(data[vi_max + offset]) then data[vi_max + offset] = window_node end
  368. end
  369. end
  370. -- Weeds on the floor once Nether weeds are added
  371. end
  372. end
  373. end
  374. local REGION_BUFFER_THICKNESS = 0.2
  375. local CENTER_REGION_LIMIT = TCAVE - REGION_BUFFER_THICKNESS
  376. local BASALT_LIMIT = CENTER_REGION_LIMIT - 0.1
  377. local SURFACE_CRUST_LIMIT = BASALT_LIMIT * 0.25
  378. local CRUST_LIMIT = BASALT_LIMIT * 0.85
  379. -- Returns (absolute height, fractional distance from ceiling or sea floor)
  380. -- the fractional distance from ceiling or sea floor is a value between 0 and 1 (inclusive)
  381. -- Note it may find the most relevent sea-level - not necesssarily the one you are closest
  382. -- to, since the space above the sea reaches much higher than the depth below the sea.
  383. local function find_nearest_lava_sealevel(y)
  384. -- todo: put oceans near the bottom of chunks to improve ability to generate tunnels to the center
  385. -- todo: constrain y to be not near the bounds of the nether
  386. -- todo: add some random adj at each level, seeded only by the level height
  387. local sealevel = math.floor((y + 100) / 200) * 200
  388. local cavern_limits_fraction
  389. local height_above_sea = y - sealevel
  390. if height_above_sea >= 0 then
  391. cavern_limits_fraction = math_min(1, height_above_sea / 95)
  392. else
  393. -- approaches 1 much faster as the lava sea is shallower than the cavern above it
  394. cavern_limits_fraction = math_min(1, -height_above_sea / 40)
  395. end
  396. return sealevel, cavern_limits_fraction
  397. end
  398. local caveperlin
  399. minetest.register_chatcommand("whereami",
  400. {
  401. description = "Describes which region of the nether the player is in",
  402. func = function(name, param)
  403. local player = minetest.get_player_by_name(name)
  404. if player == nil then return false, "Unknown player position" end
  405. local pos = vector.round(player:get_pos())
  406. if pos.y > NETHER_CEILING or pos.y < NETHER_FLOOR then
  407. return true, "The Overworld"
  408. end
  409. caveperlin = caveperlin or minetest.get_perlin(np_cave)
  410. local densityNoise = caveperlin:get_3d(pos)
  411. local sea_level, cavern_limit_distance = find_nearest_lava_sealevel(pos.y)
  412. local desc
  413. if densityNoise > 0.6 then
  414. desc = "Positive nether"
  415. elseif densityNoise < -0.6 then
  416. desc = "Negative nether"
  417. elseif math_abs(densityNoise) < CENTER_REGION_LIMIT then
  418. desc = "Nether Core"
  419. local cavern_noise_adj = CENTER_REGION_LIMIT * (cavern_limit_distance * cavern_limit_distance * cavern_limit_distance)
  420. if math_abs(densityNoise) + cavern_noise_adj < BASALT_LIMIT then
  421. desc = desc .. " inside cavern"
  422. else
  423. desc = desc .. " but outside cavern"
  424. end
  425. elseif densityNoise > 0 then
  426. desc = "Shell between positive nether and center region"
  427. else
  428. desc = "Shell between negative nether and center region"
  429. end
  430. local sea_pos = pos.y - sea_level
  431. if sea_pos > 0 then
  432. desc = desc .. ", " .. sea_pos .. "m above lava-sea level"
  433. else
  434. desc = desc .. ", " .. sea_pos .. "m below lava-sea level"
  435. end
  436. return true, "[Perlin " .. (math_floor(densityNoise * 1000) / 1000) .. "] " .. desc
  437. end
  438. }
  439. )
  440. function draw_pathway(data, area, nether_pos, center_pos)
  441. local dist = math_floor(vector.distance(nether_pos, center_pos))
  442. local step = vector.subtract(center_pos, nether_pos)
  443. local ystride = area.ystride
  444. local zstride = area.zstride
  445. -- first pass: record path details
  446. local linedata = {}
  447. local last_pos = {}
  448. local line_index = 1
  449. local first_filled_index, boundary_index, last_filled_index
  450. for i = 0, dist do
  451. pos = vector.round(vector.add(nether_pos, vector.multiply(step, i / dist))) -- bresenham's line would be good here, but too much lua code
  452. if not vector.equals(pos, last_pos) then
  453. local vi = area:indexp(pos)
  454. local node_id = data[vi]
  455. linedata[line_index] = {
  456. pos = pos,
  457. vi = vi,
  458. node_id = node_id
  459. }
  460. if boundary_index == nil and node_id == c_basalt then boundary_index = line_index end
  461. if node_id == c_air then
  462. if boundary_index ~= nil and last_filled_index == nil then last_filled_index = line_index end
  463. else
  464. if first_filled_index == nil then first_filled_index = line_index end
  465. end
  466. line_index = line_index + 1
  467. last_pos = pos
  468. end
  469. end
  470. first_filled_index = first_filled_index or 1
  471. last_filled_index = last_filled_index or #linedata
  472. boundary_index = boundary_index or last_filled_index
  473. --minetest.chat_send_all((last_filled_index - first_filled_index) .. " instead of " .. dist .. ", with steps reduced to " .. #linedata)
  474. -- second pass: excavate
  475. -- excavation radius should be limited to half the sampling distance so we don't end up
  476. -- exceeding minp-maxp and having excavation filled in when the next chunk is generated.
  477. local start_index, stop_index = math_max(1, first_filled_index - 4), math_min(#linedata, last_filled_index + 4)
  478. for i = start_index, stop_index do
  479. local vi = linedata[i].vi
  480. local radius_squared = 26.5 - 3 * (i / (stop_index - start_index + 1))
  481. for x = -5, 5 do
  482. for y = -5, 5 do
  483. if x * x + y * y < radius_squared then
  484. data[vi + y * ystride + x] = c_air
  485. data[vi + y * ystride + x * zstride] = c_air
  486. data[vi + y * zstride + x] = c_air
  487. end
  488. end
  489. end
  490. end
  491. -- thrid pass: decorate
  492. --for i = start_index, stop_index do data[linedata[i].vi] = c_glowstone end
  493. -- add glowstone to make tunnels easyier to find
  494. local vi = linedata[boundary_index].vi
  495. local glowcount = 0
  496. for x = -6, 6 do
  497. for y = -6, 6 do
  498. if glowcount > 3 then break end
  499. local radius_squared = x * x + y * y
  500. if radius_squared < 27 and radius_squared >= 25 then
  501. if data[vi + y * ystride + x] ~= c_air then data[vi + y * ystride + x] = c_glowstone glowcount = glowcount + 1 end
  502. if data[vi + y * ystride + x * zstride] ~= c_air then data[vi + y * ystride + x * zstride] = c_glowstone glowcount = glowcount + 1 end
  503. if data[vi + y * zstride + x] ~= c_air then data[vi + y * zstride + x] = c_glowstone glowcount = glowcount + 1 end
  504. end
  505. end
  506. end
  507. end
  508. function excavate_tunnel_to_center_of_the_nether(data, area, nvals_cave, minp, maxp)
  509. local extent = vector.subtract(maxp, minp)
  510. local skip = 10 -- sampling rate of 1 in 10
  511. local highest = -1000
  512. local lowest = 1000
  513. local lowest_vi
  514. local highest_vi
  515. local yCaveStride = maxp.x - minp.x + 1
  516. local zCaveStride = yCaveStride * yCaveStride
  517. local vi_offset = area:indexp(vector.add(minp, math_floor(skip / 2))) -- start half the sampling distance away from minp
  518. local vi, ni
  519. for y = 0, extent.y - 1, skip do
  520. local sealevel = find_nearest_lava_sealevel(minp.y + y)
  521. if minp.y + y > sealevel then -- only create tunnels above sea level
  522. for z = 0, extent.z - 1, skip do
  523. vi = vi_offset + y * area.ystride + z * area.zstride + 1
  524. ni = z * zCaveStride + y * yCaveStride + 1
  525. for x = 0, extent.x - 1, skip do
  526. local noise = math_abs(nvals_cave[ni])
  527. if noise < lowest then
  528. lowest = noise
  529. lowest_vi = vi
  530. end
  531. if noise > highest then
  532. highest = noise
  533. highest_vi = vi
  534. end
  535. ni = ni + skip
  536. vi = vi + skip
  537. end
  538. end
  539. end
  540. end
  541. if lowest < BASALT_LIMIT and highest > TCAVE + 0.03 then
  542. local sealevel, cavern_limit_distance = find_nearest_lava_sealevel(area:position(lowest_vi).y)
  543. local cavern_noise_adj = CENTER_REGION_LIMIT * (cavern_limit_distance * cavern_limit_distance * cavern_limit_distance)
  544. if lowest + cavern_noise_adj < BASALT_LIMIT then
  545. draw_pathway(data, area, area:position(highest_vi), area:position(lowest_vi))
  546. end
  547. end
  548. end
  549. local pathway_chunk_count = 0
  550. local total_chunk_count = 0
  551. -- On-generated function
  552. local function on_generated(minp, maxp, seed)
  553. if minp.y > NETHER_CEILING or maxp.y < NETHER_FLOOR then
  554. return
  555. end
  556. local vm, emerge_min, emerge_max = minetest.get_mapgen_object("voxelmanip")
  557. local area = VoxelArea:new{MinEdge=emerge_min, MaxEdge=emerge_max}
  558. local data = vm:get_data(dbuf)
  559. local x0, y0, z0 = minp.x, math_max(minp.y, NETHER_FLOOR), minp.z
  560. local x1, y1, z1 = maxp.x, math_min(maxp.y, NETHER_CEILING), maxp.z
  561. local yCaveStride = x1 - x0 + 1
  562. local zCaveStride = yCaveStride * yCaveStride
  563. local chulens = {x = yCaveStride, y = yCaveStride, z = yCaveStride}
  564. nobj_cave = nobj_cave or minetest.get_perlin_map(np_cave, chulens)
  565. local nvals_cave = nobj_cave:get_3d_map_flat(minp, nbuf_cave)
  566. local dungeonRooms = build_dungeon_room_list(data, area)
  567. local abs_cave_noise, abs_cave_noise_adjusted
  568. local contains_nether = false
  569. local contains_shell = false
  570. local contains_center = false
  571. local contains_ocean = false
  572. for y = y0, y1 do -- Y loop first to minimise tcave & lava-sea calculations
  573. local tcave = TCAVE
  574. if y > yblmax then tcave = TCAVE + ((y - yblmax) / BLEND) ^ 2 end
  575. if y < yblmin then tcave = TCAVE + ((yblmin - y) / BLEND) ^ 2 end
  576. local sea_level, cavern_limit_distance = find_nearest_lava_sealevel(y)
  577. local above_lavasea = y > sea_level
  578. local below_lavasea = y < sea_level
  579. local cavern_noise_adj = CENTER_REGION_LIMIT * (cavern_limit_distance * cavern_limit_distance * cavern_limit_distance)
  580. for z = z0, z1 do
  581. local vi = area:index(x0, y, z) -- Initial voxelmanip index
  582. local ni = (z - z0) * zCaveStride + (y - y0) * yCaveStride + 1
  583. for x = x0, x1 do
  584. local id = data[vi] -- Existing node
  585. local cave_noise = nvals_cave[ni]
  586. if cave_noise > tcave then
  587. -- prime region
  588. data[vi] = c_air
  589. contains_nether = true
  590. elseif -cave_noise > tcave then
  591. -- secondary/spare region
  592. data[vi] = c_air
  593. else
  594. -- netherrack walls and/or center region
  595. abs_cave_noise = math_abs(cave_noise)
  596. -- abs_cave_noise_adjusted makes the center region smaller as distance from the lava ocean
  597. -- increases, we do this by pretending the abs_cave_noise value is higher.
  598. abs_cave_noise_adjusted = abs_cave_noise + cavern_noise_adj
  599. if above_lavasea and abs_cave_noise_adjusted < BASALT_LIMIT then
  600. data[vi] = c_air
  601. contains_center = true
  602. elseif abs_cave_noise_adjusted < SURFACE_CRUST_LIMIT or (below_lavasea and abs_cave_noise_adjusted < CRUST_LIMIT) then
  603. data[vi] = c_lavasea_source
  604. contains_ocean = true
  605. elseif abs_cave_noise_adjusted < BASALT_LIMIT then
  606. data[vi] = c_lava_crust
  607. contains_ocean = true
  608. elseif id == c_air or id == c_native_mapgen then
  609. if abs_cave_noise < CENTER_REGION_LIMIT then
  610. data[vi] = c_basalt
  611. else
  612. -- the shell seperating the basalt realm from the rest of the nether...
  613. -- put some holes in it
  614. data[vi] = c_netherrack -- excavate_dungeons() will mostly reverse this inside dungeons
  615. contains_shell = true
  616. end
  617. end
  618. end
  619. ni = ni + 1
  620. vi = vi + 1
  621. end
  622. end
  623. end
  624. total_chunk_count = total_chunk_count + 1
  625. if contains_nether and contains_shell and contains_center and contains_ocean then
  626. pathway_chunk_count = pathway_chunk_count + 1
  627. end
  628. --if total_chunk_count % 50 == 0 then
  629. -- minetest.chat_send_all(pathway_chunk_count .. " of " .. total_chunk_count .. " chunks contain both nether and lava-sea (" .. math_floor(pathway_chunk_count * 100 / total_chunk_count) .. "%)")
  630. --end
  631. if contains_nether and contains_center then
  632. excavate_tunnel_to_center_of_the_nether(data, area, nvals_cave, minp, maxp)
  633. end
  634. -- any air from the native mapgen has been replaced by netherrack, but we
  635. -- don't want netherrack inside dungeons, so fill known dungeon rooms with air.
  636. excavate_dungeons(data, area, dungeonRooms)
  637. decorate_dungeons(data, area, dungeonRooms)
  638. vm:set_data(data)
  639. minetest.generate_ores(vm)
  640. -- avoid generating decorations on the underside of the bottom of the nether
  641. if minp.y > NETHER_FLOOR and maxp.y < NETHER_CEILING then minetest.generate_decorations(vm) end
  642. vm:set_lighting({day = 0, night = 0}, minp, maxp)
  643. vm:calc_lighting()
  644. vm:update_liquids()
  645. vm:write_to_map()
  646. end
  647. -- use knowledge of the nether mapgen algorithm to return a suitable ground level for placing a portal.
  648. function nether.find_nether_ground_y(target_x, target_z, start_y)
  649. local nobj_cave_point = minetest.get_perlin(np_cave)
  650. local air = 0 -- Consecutive air nodes found
  651. for y = start_y, math_max(NETHER_FLOOR + BLEND, start_y - 4096), -1 do
  652. local nval_cave = nobj_cave_point:get3d({x = target_x, y = y, z = target_z})
  653. if nval_cave > TCAVE then -- Cavern
  654. air = air + 1
  655. else -- Not cavern, check if 4 nodes of space above
  656. if air >= 4 then
  657. -- Check volume for non-natural nodes
  658. local minp = {x = target_x - 1, y = y , z = target_z - 2}
  659. local maxp = {x = target_x + 2, y = y + 4, z = target_z + 2}
  660. if nether.volume_is_natural(minp, maxp) then
  661. return y + 1
  662. else -- Restart search a little lower
  663. nether.find_nether_ground_y(target_x, target_z, y - 16)
  664. end
  665. else -- Not enough space, reset air to zero
  666. air = 0
  667. end
  668. end
  669. end
  670. return math_max(start_y, NETHER_FLOOR + BLEND) -- Fallback
  671. end
  672. -- We don't need to be gen-notified of temples because only dungeons will be generated
  673. -- if a biome defines the dungeon nodes
  674. minetest.set_gen_notify({dungeon = true})
  675. minetest.register_on_generated(on_generated)