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