mapgen.lua 30 KB

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