mapgen.lua 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. --[[
  2. Nether mod for minetest
  3. "mapgen.lua" is the modern biomes-based Nether mapgen, which
  4. requires Minetest v5.1 or greater
  5. "mapgen_nobiomes.lua" is the legacy version of the mapgen, only used
  6. in older versions of Minetest or in v6 worlds.
  7. Copyright (C) 2013 PilzAdam
  8. Permission to use, copy, modify, and/or distribute this software for
  9. any purpose with or without fee is hereby granted, provided that the
  10. above copyright notice and this permission notice appear in all copies.
  11. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
  12. WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
  13. WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
  14. BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
  15. OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  16. WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
  17. ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
  18. SOFTWARE.
  19. ]]--
  20. -- Parameters
  21. local NETHER_CEILING = nether.DEPTH_CEILING
  22. local NETHER_FLOOR = nether.DEPTH_FLOOR
  23. local TCAVE = 0.6
  24. local BLEND = 128
  25. -- parameters for central region
  26. local REGION_BUFFER_THICKNESS = 0.2
  27. local CENTER_REGION_LIMIT = TCAVE - REGION_BUFFER_THICKNESS -- Netherrack gives way to Deep-Netherrack here
  28. local CENTER_CAVERN_LIMIT = CENTER_REGION_LIMIT - 0.1 -- Deep-Netherrack gives way to air here
  29. local SURFACE_CRUST_LIMIT = CENTER_CAVERN_LIMIT * 0.25 -- Crusted-lava at the surface of the lava ocean gives way to liquid lava here
  30. local CRUST_LIMIT = CENTER_CAVERN_LIMIT * 0.85 -- Crusted-lava under the surface of the lava ocean gives way to liquid lava here
  31. local BASALT_COLUMN_UPPER_LIMIT = CENTER_CAVERN_LIMIT * 0.9 -- Basalt columns may appear between these upper and lower limits
  32. local BASALT_COLUMN_LOWER_LIMIT = CENTER_CAVERN_LIMIT * 0.25 -- This value is close to SURFACE_CRUST_LIMIT so basalt columns give way to "flowing" lava rivers
  33. -- Shared Nether mapgen namespace
  34. -- For mapgen files to share functions and constants
  35. local mapgen = nether.mapgen
  36. mapgen.TCAVE = TCAVE -- const needed in mapgen_mantle.lua
  37. mapgen.BLEND = BLEND -- const needed in mapgen_mantle.lua
  38. mapgen.CENTER_REGION_LIMIT = CENTER_REGION_LIMIT -- const needed in mapgen_mantle.lua
  39. mapgen.CENTER_CAVERN_LIMIT = CENTER_CAVERN_LIMIT -- const needed in mapgen_mantle.lua
  40. mapgen.BASALT_COLUMN_UPPER_LIMIT = BASALT_COLUMN_UPPER_LIMIT -- const needed in mapgen_mantle.lua
  41. mapgen.BASALT_COLUMN_LOWER_LIMIT = BASALT_COLUMN_LOWER_LIMIT -- const needed in mapgen_mantle.lua
  42. mapgen.ore_ceiling = NETHER_CEILING - BLEND -- leave a solid 128 node cap of netherrack before introducing ores
  43. mapgen.ore_floor = NETHER_FLOOR + BLEND
  44. local debugf = nether.debug
  45. if minetest.read_schematic == nil then
  46. -- Using biomes to create the Nether requires the ability for biomes to set "node_cave_liquid = air".
  47. -- This feature was introduced by paramat in b1b40fef1 on 2019-05-19, but we can't test for
  48. -- it directly. However b2065756c was merged a few months later (in 2019-08-14) and it is easy
  49. -- to directly test for - it adds minetest.read_schematic() - so we use this as a proxy-test
  50. -- for whether the Minetest engine is recent enough to have implemented node_cave_liquid=air
  51. error("This " .. nether.modname .. " mapgen requires Minetest v5.1 or greater, use mapgen_nobiomes.lua instead.", 0)
  52. end
  53. -- Load specialty helper functions
  54. dofile(nether.path .. "/mapgen_dungeons.lua")
  55. dofile(nether.path .. "/mapgen_mantle.lua")
  56. dofile(nether.path .. "/mapgen_geodes.lua")
  57. -- Misc math functions
  58. -- avoid needing table lookups each time a common math function is invoked
  59. local math_max, math_min, math_abs, math_floor = math.max, math.min, math.abs, math.floor
  60. -- Inject nether_caverns biome
  61. -- Move any existing biomes out of the y-range specified by 'floor_y' and 'ceiling_y'
  62. mapgen.shift_existing_biomes = function(floor_y, ceiling_y)
  63. -- https://forum.minetest.net/viewtopic.php?p=257522#p257522
  64. -- Q: Is there a way to override an already-registered biome so I can get it out of the
  65. -- way of my own underground biomes without disturbing the other biomes registered by
  66. -- default?
  67. -- A: No, all you can do is use a mod to clear all biomes then re-register the complete
  68. -- set but with your changes. It has been described as hacky but this is actually the
  69. -- official way to alter biomes, most mods and subgames would want to completely change
  70. -- all biomes anyway.
  71. -- To avoid the engine side of mapgen becoming overcomplex the approach is to require mods
  72. -- to do slightly more complex stuff in Lua.
  73. -- take a copy of all biomes, decorations, and ores. Regregistering a biome changes its ID, so
  74. -- any decorations or ores using the 'biomes' field must afterwards be cleared and re-registered.
  75. -- https://github.com/minetest/minetest/issues/9288
  76. local registered_biomes_copy = {}
  77. local registered_decorations_copy = {}
  78. local registered_ores_copy = {}
  79. for old_biome_key, old_biome_def in pairs(minetest.registered_biomes) do
  80. registered_biomes_copy[old_biome_key] = old_biome_def
  81. end
  82. for old_decoration_key, old_decoration_def in pairs(minetest.registered_decorations) do
  83. registered_decorations_copy[old_decoration_key] = old_decoration_def
  84. end
  85. for old_ore_key, old_ore_def in pairs(minetest.registered_ores) do
  86. registered_ores_copy[old_ore_key] = old_ore_def
  87. end
  88. -- clear biomes, decorations, and ores
  89. minetest.clear_registered_decorations()
  90. minetest.clear_registered_ores()
  91. minetest.clear_registered_biomes()
  92. -- Restore biomes, adjusted to not overlap the Nether
  93. for biome_key, new_biome_def in pairs(registered_biomes_copy) do
  94. -- follow similar min_pos/max_pos processing logic as read_biome_def() in l_mapgen.cpp
  95. local biome_y_max, biome_y_min = 31000, -31000
  96. if type(new_biome_def.min_pos) == 'table' and type(new_biome_def.min_pos.y) == 'number' then biome_y_min = new_biome_def.min_pos.y end
  97. if type(new_biome_def.max_pos) == 'table' and type(new_biome_def.max_pos.y) == 'number' then biome_y_max = new_biome_def.max_pos.y end
  98. if type(new_biome_def.y_min) == 'number' then biome_y_min = new_biome_def.y_min end
  99. if type(new_biome_def.y_max) == 'number' then biome_y_max = new_biome_def.y_max end
  100. if biome_y_max > floor_y and biome_y_min < ceiling_y then
  101. -- This biome occupies some or all of the depth of the Nether, shift/crop it.
  102. local new_y_min, new_y_max
  103. local spaceOccupiedAbove = biome_y_max - ceiling_y
  104. local spaceOccupiedBelow = floor_y - biome_y_min
  105. if spaceOccupiedAbove >= spaceOccupiedBelow or biome_y_min <= -30000 then
  106. -- place the biome above the Nether
  107. -- We also shift biomes which extend to the bottom of the map above the Nether, since they
  108. -- likely only extend that deep as a catch-all, and probably have a role nearer the surface.
  109. new_y_min = ceiling_y + 1
  110. new_y_max = math_max(biome_y_max, ceiling_y + 2)
  111. else
  112. -- shift the biome to below the Nether
  113. new_y_max = floor_y - 1
  114. new_y_min = math_min(biome_y_min, floor_y - 2)
  115. end
  116. debugf("Moving biome \"%s\" from %s..%s to %s..%s", new_biome_def.name, new_biome_def.y_min, new_biome_def.y_max, new_y_min, new_y_max)
  117. if type(new_biome_def.min_pos) == 'table' and type(new_biome_def.min_pos.y) == 'number' then new_biome_def.min_pos.y = new_y_min end
  118. if type(new_biome_def.max_pos) == 'table' and type(new_biome_def.max_pos.y) == 'number' then new_biome_def.max_pos.y = new_y_max end
  119. new_biome_def.y_min = new_y_min -- Ensure the new heights are saved, even if original biome never specified one
  120. new_biome_def.y_max = new_y_max
  121. end
  122. minetest.register_biome(new_biome_def)
  123. end
  124. -- Restore biome decorations
  125. for decoration_key, new_decoration_def in pairs(registered_decorations_copy) do
  126. minetest.register_decoration(new_decoration_def)
  127. end
  128. -- Restore biome ores
  129. for ore_key, new_ore_def in pairs(registered_ores_copy) do
  130. minetest.register_ore(new_ore_def)
  131. end
  132. end
  133. -- Shift any overlapping biomes out of the way before we create the Nether biomes
  134. mapgen.shift_existing_biomes(NETHER_FLOOR, NETHER_CEILING)
  135. -- nether:native_mapgen is used to prevent ores and decorations being generated according
  136. -- to landforms created by the native mapgen.
  137. -- Ores and decorations can be registered against "nether:rack" instead, and the lua
  138. -- on_generate() callback will carve the Nether with nether:rack before invoking
  139. -- generate_decorations and generate_ores.
  140. minetest.register_node("nether:native_mapgen", {})
  141. minetest.register_biome({
  142. name = "nether_caverns",
  143. node_stone = "nether:native_mapgen", -- nether:native_mapgen is used here to prevent the native mapgen from placing ores and decorations.
  144. node_filler = "nether:native_mapgen", -- The lua on_generate will transform nether:native_mapgen into nether:rack then decorate and add ores.
  145. node_dungeon = "nether:brick",
  146. node_dungeon_alt = "nether:brick_cracked",
  147. node_dungeon_stair = "stairs:stair_nether_brick",
  148. -- Setting node_cave_liquid to "air" avoids the need to filter lava and water out of the mapchunk and
  149. -- surrounding shell (overdraw nodes beyond the mapchunk).
  150. -- This feature was introduced by paramat in b1b40fef1 on 2019-05-19, and this mapgen.lua file should only
  151. -- be run if the Minetest version includes it. The earliest tag made after 2019-05-19 is 5.1.0 on 2019-10-13,
  152. -- however we shouldn't test version numbers. minetest.read_schematic() was added by b2065756c and merged in
  153. -- 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
  154. -- Minetest version is recent enough to have implemented node_cave_liquid=air
  155. node_cave_liquid = "air",
  156. y_max = NETHER_CEILING,
  157. y_min = NETHER_FLOOR,
  158. vertical_blend = 0,
  159. heat_point = 50,
  160. humidity_point = 50,
  161. })
  162. -- Ores and decorations
  163. dofile(nether.path .. "/mapgen_decorations.lua")
  164. minetest.register_ore({
  165. ore_type = "scatter",
  166. ore = "nether:glowstone",
  167. wherein = "nether:rack",
  168. clust_scarcity = 11 * 11 * 11,
  169. clust_num_ores = 3,
  170. clust_size = 2,
  171. y_max = mapgen.ore_ceiling,
  172. y_min = mapgen.ore_floor
  173. })
  174. minetest.register_ore({
  175. ore_type = "scatter",
  176. ore = "nether:lava_crust", -- crusted lava replaces scattered glowstone in the mantle
  177. wherein = "nether:rack_deep",
  178. clust_scarcity = 16 * 16 * 16,
  179. clust_num_ores = 4,
  180. clust_size = 2,
  181. y_max = mapgen.ore_ceiling,
  182. y_min = mapgen.ore_floor
  183. })
  184. minetest.register_ore({
  185. ore_type = "scatter",
  186. ore = "default:lava_source",
  187. wherein = {"nether:rack", "nether:rack_deep"},
  188. clust_scarcity = 36 * 36 * 36,
  189. clust_num_ores = 4,
  190. clust_size = 2,
  191. y_max = mapgen.ore_ceiling,
  192. y_min = mapgen.ore_floor
  193. })
  194. minetest.register_ore({
  195. ore_type = "blob",
  196. ore = "nether:sand",
  197. wherein = "nether:rack",
  198. clust_scarcity = 14 * 14 * 14,
  199. clust_size = 8,
  200. y_max = mapgen.ore_ceiling,
  201. y_min = mapgen.ore_floor
  202. })
  203. -- Mapgen
  204. -- 3D noise
  205. mapgen.np_cave = {
  206. offset = 0,
  207. scale = 1,
  208. spread = {x = 384, y = 128, z = 384}, -- squashed 3:1
  209. seed = 59033,
  210. octaves = 5,
  211. persist = 0.7,
  212. lacunarity = 2.0,
  213. --flags = ""
  214. }
  215. local cavePointPerlin = nil
  216. mapgen.get_cave_point_perlin = function()
  217. cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave)
  218. return cavePointPerlin
  219. end
  220. mapgen.get_cave_perlin_at = function(pos)
  221. cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave)
  222. return cavePointPerlin:get_3d(pos)
  223. end
  224. -- Buffers and objects we shouldn't recreate every on_generate
  225. local nobj_cave = nil
  226. local nbuf_cave = {}
  227. local dbuf = {}
  228. -- Content ids
  229. local c_air = minetest.get_content_id("air")
  230. local c_netherrack = minetest.get_content_id("nether:rack")
  231. local c_netherrack_deep = minetest.get_content_id("nether:rack_deep")
  232. local c_crystaldark = minetest.get_content_id("nether:geode")
  233. local c_lavasea_source = minetest.get_content_id("nether:lava_source") -- same as lava but with staggered animation to look better as an ocean
  234. local c_lava_crust = minetest.get_content_id("nether:lava_crust")
  235. local c_native_mapgen = minetest.get_content_id("nether:native_mapgen")
  236. local yblmin = NETHER_FLOOR + BLEND * 2
  237. local yblmax = NETHER_CEILING - BLEND * 2
  238. -- At both the top and bottom of the Nether, as set by NETHER_CEILING and NETHER_FLOOR,
  239. -- there is a 128 deep cap of solid netherrack, followed by a 128-deep blending zone
  240. -- where Nether caverns may start to appear.
  241. -- The solid zones and blending zones are achieved by adjusting the np_cave noise to be
  242. -- outside the range where caverns form, this function returns that adjustment.
  243. --
  244. -- Returns two values: the noise limit adjustment for nether caverns, and the
  245. -- noise limit adjustment for the central region / mantle caverns
  246. mapgen.get_mapgenblend_adjustments = function(y)
  247. -- floorAndCeilingBlend will normally be 0, but shifts toward 1 in the
  248. -- blending zone, and goes higher than 1 in the solid zone between the
  249. -- blending zone and the end of the nether.
  250. local floorAndCeilingBlend = 0
  251. if y > yblmax then floorAndCeilingBlend = ((y - yblmax) / BLEND) ^ 2 end
  252. if y < yblmin then floorAndCeilingBlend = ((yblmin - y) / BLEND) ^ 2 end
  253. -- the nether caverns exist when np_cave noise is greater than TCAVE, so
  254. -- to fade out the nether caverns, adjust TCAVE upward.
  255. local tcave_adj = floorAndCeilingBlend
  256. -- the central regions exists when np_cave noise is below CENTER_REGION_LIMIT,
  257. -- so to fade out the mantle caverns adjust CENTER_REGION_LIMIT downward.
  258. local centerRegionLimit_adj = -(CENTER_REGION_LIMIT * floorAndCeilingBlend)
  259. return tcave_adj, centerRegionLimit_adj
  260. end
  261. -- On-generated function
  262. local tunnelCandidate_count = 0
  263. local tunnel_count = 0
  264. local total_chunk_count = 0
  265. local function on_generated(minp, maxp, seed)
  266. if minp.y > NETHER_CEILING or maxp.y < NETHER_FLOOR then
  267. return
  268. end
  269. local vm, emerge_min, emerge_max = minetest.get_mapgen_object("voxelmanip")
  270. local area = VoxelArea:new{MinEdge=emerge_min, MaxEdge=emerge_max}
  271. local data = vm:get_data(dbuf)
  272. local x0, y0, z0 = minp.x, math_max(minp.y, NETHER_FLOOR), minp.z
  273. local x1, y1, z1 = maxp.x, math_min(maxp.y, NETHER_CEILING), maxp.z
  274. local yCaveStride = x1 - x0 + 1
  275. local zCaveStride = yCaveStride * yCaveStride
  276. local chulens = {x = yCaveStride, y = yCaveStride, z = yCaveStride}
  277. nobj_cave = nobj_cave or minetest.get_perlin_map(mapgen.np_cave, chulens)
  278. local nvals_cave = nobj_cave:get_3d_map_flat(minp, nbuf_cave)
  279. local dungeonRooms = mapgen.build_dungeon_room_list(data, area) -- function from mapgen_dungeons.lua
  280. local abs_cave_noise, abs_cave_noise_adjusted
  281. local contains_nether = false
  282. local contains_mantle = false
  283. local contains_ocean = false
  284. for y = y0, y1 do -- Y loop first to minimise tcave & lava-sea calculations
  285. local sea_level, cavern_limit_distance = mapgen.find_nearest_lava_sealevel(y) -- function from mapgen_mantle.lua
  286. local above_lavasea = y > sea_level
  287. local below_lavasea = y < sea_level
  288. local tcave_adj, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(y)
  289. local tcave = TCAVE + tcave_adj
  290. local tmantle = CENTER_REGION_LIMIT + centerRegionLimit_adj -- cavern_noise_adj already contains central_region_limit_adj, so tmantle is only for comparisons when cavern_noise_adj hasn't been added to the noise value
  291. -- cavern_noise_adj gets added to noise value instead of added to the limit np_noise
  292. -- is compared against, so subtract centerRegionLimit_adj instead of adding
  293. local cavern_noise_adj =
  294. CENTER_REGION_LIMIT * (cavern_limit_distance * cavern_limit_distance * cavern_limit_distance) -
  295. centerRegionLimit_adj
  296. for z = z0, z1 do
  297. local vi = area:index(x0, y, z) -- Initial voxelmanip index
  298. local ni = (z - z0) * zCaveStride + (y - y0) * yCaveStride + 1
  299. local noise2di = 1 + (z - z0) * yCaveStride
  300. for x = x0, x1 do
  301. local cave_noise = nvals_cave[ni]
  302. if cave_noise > tcave then
  303. -- Prime region
  304. -- This was the only region in initial versions of the Nether mod.
  305. -- It is the only region which portals from the surface will open into,
  306. -- getting to any other regions in the Nether will require Shanks' Pony.
  307. data[vi] = c_air
  308. contains_nether = true
  309. elseif -cave_noise > tcave then
  310. -- Secondary/spare region
  311. -- This secondary region is unused until someone decides to do something cool or novel with it.
  312. -- Reaching here would require the player to first find and journey through the central region,
  313. -- as it's always separated from the Prime region by the central region.
  314. data[vi] = mapgen.getGeodeInteriorNodeId(x, y, z)-- function from mapgen_geodes.lua
  315. -- Only set contains_nether to true here if you want tunnels created between the secondary region
  316. -- and the central region.
  317. contains_nether = true
  318. else
  319. -- netherrack walls and/or center region/mantle
  320. abs_cave_noise = math_abs(cave_noise)
  321. -- abs_cave_noise_adjusted makes the center region smaller as distance from the lava ocean
  322. -- increases, we do this by pretending the abs_cave_noise value is higher.
  323. abs_cave_noise_adjusted = abs_cave_noise + cavern_noise_adj
  324. if abs_cave_noise_adjusted >= CENTER_CAVERN_LIMIT then
  325. local id = data[vi] -- Check existing node to avoid removing dungeons
  326. if id == c_air or id == c_native_mapgen then
  327. if abs_cave_noise < tmantle then
  328. data[vi] = c_netherrack_deep
  329. else
  330. -- the shell seperating the mantle from the rest of the nether...
  331. if cave_noise > 0 then
  332. data[vi] = c_netherrack -- excavate_dungeons() will mostly reverse this inside dungeons
  333. else
  334. data[vi] = c_crystaldark
  335. end
  336. end
  337. end
  338. elseif above_lavasea then
  339. data[vi] = c_air
  340. contains_mantle = true
  341. elseif abs_cave_noise_adjusted < SURFACE_CRUST_LIMIT or (below_lavasea and abs_cave_noise_adjusted < CRUST_LIMIT) then
  342. data[vi] = c_lavasea_source
  343. contains_ocean = true
  344. else
  345. data[vi] = c_lava_crust
  346. contains_ocean = true
  347. end
  348. end
  349. ni = ni + 1
  350. vi = vi + 1
  351. noise2di = noise2di + 1
  352. end
  353. end
  354. end
  355. if contains_mantle or contains_ocean then
  356. mapgen.add_basalt_columns(data, area, minp, maxp) -- function from mapgen_mantle.lua
  357. end
  358. if contains_nether and contains_mantle then
  359. tunnelCandidate_count = tunnelCandidate_count + 1
  360. local success = mapgen.excavate_tunnel_to_center_of_the_nether(data, area, nvals_cave, minp, maxp) -- function from mapgen_mantle.lua
  361. if success then tunnel_count = tunnel_count + 1 end
  362. end
  363. total_chunk_count = total_chunk_count + 1
  364. if total_chunk_count % 50 == 0 then
  365. debugf(
  366. "%s of %s chunks contain both nether and lava-sea (%s%%), %s chunks generated a pathway (%s%%)",
  367. tunnelCandidate_count,
  368. total_chunk_count,
  369. math_floor(tunnelCandidate_count * 100 / total_chunk_count),
  370. tunnel_count,
  371. math_floor(tunnel_count * 100 / total_chunk_count)
  372. )
  373. end
  374. -- any air from the native mapgen has been replaced by netherrack, but we
  375. -- don't want netherrack inside dungeons, so fill known dungeon rooms with air.
  376. mapgen.excavate_dungeons(data, area, dungeonRooms) -- function from mapgen_dungeons.lua
  377. mapgen.decorate_dungeons(data, area, dungeonRooms) -- function from mapgen_dungeons.lua
  378. vm:set_data(data)
  379. minetest.generate_ores(vm)
  380. minetest.generate_decorations(vm)
  381. vm:set_lighting({day = 0, night = 0}, minp, maxp)
  382. vm:calc_lighting()
  383. vm:update_liquids()
  384. vm:write_to_map()
  385. end
  386. -- use knowledge of the nether mapgen algorithm to return a suitable ground level for placing a portal.
  387. -- player_name is optional, allowing a player to spawn a remote portal in their own protected areas.
  388. function nether.find_nether_ground_y(target_x, target_z, start_y, player_name)
  389. local nobj_cave_point = mapgen.get_cave_point_perlin()
  390. local air = 0 -- Consecutive air nodes found
  391. local minp_schem, maxp_schem = nether.get_schematic_volume({x = target_x, y = 0, z = target_z}, nil, "nether_portal")
  392. local minp = {x = minp_schem.x, y = 0, z = minp_schem.z}
  393. local maxp = {x = maxp_schem.x, y = 0, z = maxp_schem.z}
  394. for y = start_y, math_max(NETHER_FLOOR + BLEND, start_y - 4096), -1 do
  395. local nval_cave = nobj_cave_point:get_3d({x = target_x, y = y, z = target_z})
  396. if nval_cave > TCAVE then -- Cavern
  397. air = air + 1
  398. else -- Not cavern, check if 4 nodes of space above
  399. if air >= 4 then
  400. local portal_y = y + 1
  401. -- Check volume for non-natural nodes
  402. minp.y = minp_schem.y + portal_y
  403. maxp.y = maxp_schem.y + portal_y
  404. if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then
  405. return portal_y
  406. else -- Restart search a little lower
  407. nether.find_nether_ground_y(target_x, target_z, y - 16, player_name)
  408. end
  409. else -- Not enough space, reset air to zero
  410. air = 0
  411. end
  412. end
  413. end
  414. return math_max(start_y, NETHER_FLOOR + BLEND) -- Fallback
  415. end
  416. minetest.register_on_generated(on_generated)