Make ShiftExistingBiomes available via API (#40)

* bug fixes

Fixes wrong new_y_min when shift_existing_biomes() shifts a biome below the nether floor,
find_surface_anchorPos() no longer assumes y=0 will be outside the nether.
Nil-reference fixed when a mods tries to register a portal after mods are finished loading, but the portal shape+material was already registered by another mod.

* Make ShiftExistingBiomes available via API

makes the ShiftExistingBiomes function available to other mods via the nether global, since it's not a simple function and biomes would also need to be shifted if another mod wants to add a second nether layer.

* Allow layers to extend the depth of nether effects

Mods can set/lower nether.DEPTH_FLOOR_LAYERS when creating a layer under the nether. This allows multiple layer mods to know where their ceiling should start, and to be included in the effects which only happen in the nether.

* document nether API

More of a tentative interop guide than an API.
Use snake_case for API functions.
This commit is contained in:
Treer 2021-07-18 16:44:37 +10:00 committed by GitHub
parent 54613d673a
commit 52038017f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 122 additions and 30 deletions

View File

@ -76,6 +76,19 @@ if nether.DEPTH_FLOOR + 1000 > nether.DEPTH_CEILING then
end end
nether.DEPTH = nether.DEPTH_CEILING -- Deprecated, use nether.DEPTH_CEILING instead. nether.DEPTH = nether.DEPTH_CEILING -- Deprecated, use nether.DEPTH_CEILING instead.
-- DEPTH_FLOOR_LAYERS gives the bottom Y of all locations that wish to be
-- considered part of the Nether.
-- DEPTH_FLOOR_LAYERS Allows mods to insert extra layers below the
-- Nether, by knowing where their layer ceiling should start, and letting
-- the layers be included in effects which only happen in the Nether.
-- If a mod wishes to add a layer below the Nether it should read
-- nether.DEPTH_FLOOR_LAYERS to find the bottom Y of the Nether and any
-- other layers already under the Nether. The mod should leave a small gap
-- between DEPTH_FLOOR_LAYERS and its ceiling (e.g. use DEPTH_FLOOR_LAYERS - 6
-- for its ceiling Y, so there is room to shift edge-case biomes), then set
-- nether.DEPTH_FLOOR_LAYERS to reflect the mod's floor Y value, and call
-- shift_existing_biomes() with DEPTH_FLOOR_LAYERS as the floor_y argument.
nether.DEPTH_FLOOR_LAYERS = nether.DEPTH_FLOOR
-- A debug-print function that understands vectors etc. and does not -- A debug-print function that understands vectors etc. and does not
-- evaluate when debugging is turned off. -- evaluate when debugging is turned off.
@ -163,7 +176,7 @@ The expedition parties have found no diamonds or gold, and after an experienced
local destination_pos = vector.divide(surface_anchorPos, nether.FASTTRAVEL_FACTOR) local destination_pos = vector.divide(surface_anchorPos, nether.FASTTRAVEL_FACTOR)
destination_pos.x = math.floor(0.5 + destination_pos.x) -- round to int destination_pos.x = math.floor(0.5 + destination_pos.x) -- round to int
destination_pos.z = math.floor(0.5 + destination_pos.z) -- round to int destination_pos.z = math.floor(0.5 + destination_pos.z) -- round to int
destination_pos.y = nether.DEPTH_CEILING - 1000 -- temp value so find_nearest_working_portal() returns nether portals destination_pos.y = nether.DEPTH_CEILING - 1 -- temp value so find_nearest_working_portal() returns nether portals
-- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are in the Nether) -- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are in the Nether)
local existing_portal_location, existing_portal_orientation = local existing_portal_location, existing_portal_orientation =
@ -188,7 +201,7 @@ The expedition parties have found no diamonds or gold, and after an experienced
local destination_pos = vector.multiply(realm_anchorPos, nether.FASTTRAVEL_FACTOR) local destination_pos = vector.multiply(realm_anchorPos, nether.FASTTRAVEL_FACTOR)
destination_pos.x = math.min(30900, math.max(-30900, destination_pos.x)) -- clip to world boundary destination_pos.x = math.min(30900, math.max(-30900, destination_pos.x)) -- clip to world boundary
destination_pos.z = math.min(30900, math.max(-30900, destination_pos.z)) -- clip to world boundary destination_pos.z = math.min(30900, math.max(-30900, destination_pos.z)) -- clip to world boundary
destination_pos.y = 0 -- temp value so find_nearest_working_portal() doesn't return nether portals destination_pos.y = nether.DEPTH_CEILING + 1 -- temp value so find_nearest_working_portal() doesn't return nether portals
-- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are outside the Nether) -- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are outside the Nether)
local existing_portal_location, existing_portal_orientation = local existing_portal_location, existing_portal_orientation =
@ -253,12 +266,12 @@ The expedition parties have found no diamonds or gold, and after an experienced
if pos.y <= nether.DEPTH_CEILING and pos.y >= nether.DEPTH_FLOOR then if pos.y <= nether.DEPTH_CEILING and pos.y >= nether.DEPTH_FLOOR then
result = "nether" result = "nether"
-- since mapgen_nobiomes.lua has no regions it doesn't implement getRegion(), -- since mapgen_nobiomes.lua has no regions it doesn't implement get_region(),
-- so only use getRegion() if it exists -- so only use get_region() if it exists
if nether.mapgen.getRegion ~= nil then if nether.mapgen.get_region ~= nil then
-- the biomes-based mapgen supports 2 extra regions -- the biomes-based mapgen supports 2 extra regions
local regions = nether.mapgen.RegionEnum local regions = nether.mapgen.RegionEnum
local region = nether.mapgen.getRegion(pos) local region = nether.mapgen.get_region(pos)
if region == regions.CENTER or region == regions.CENTERSHELL then if region == regions.CENTER or region == regions.CENTERSHELL then
result = "mantle" result = "mantle"
elseif region == regions.NEGATIVE or region == regions.NEGATIVESHELL then elseif region == regions.NEGATIVE or region == regions.NEGATIVESHELL then

View File

@ -81,7 +81,8 @@ local math_max, math_min, math_abs, math_floor = math.max, math.min, math.abs, m
-- Inject nether_caverns biome -- Inject nether_caverns biome
local function override_underground_biomes() -- 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 -- 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 -- 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 -- way of my own underground biomes without disturbing the other biomes registered by
@ -124,21 +125,21 @@ local function override_underground_biomes()
if type(new_biome_def.y_min) == 'number' then biome_y_min = new_biome_def.y_min 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 type(new_biome_def.y_max) == 'number' then biome_y_max = new_biome_def.y_max end
if biome_y_max > NETHER_FLOOR and biome_y_min < NETHER_CEILING then 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. -- This biome occupies some or all of the depth of the Nether, shift/crop it.
local new_y_min, new_y_max local new_y_min, new_y_max
local spaceOccupiedAbove = biome_y_max - NETHER_CEILING local spaceOccupiedAbove = biome_y_max - ceiling_y
local spaceOccupiedBelow = NETHER_FLOOR - biome_y_min local spaceOccupiedBelow = floor_y - biome_y_min
if spaceOccupiedAbove >= spaceOccupiedBelow or biome_y_min <= -30000 then if spaceOccupiedAbove >= spaceOccupiedBelow or biome_y_min <= -30000 then
-- place the biome above the Nether -- place the biome above the Nether
-- We also shift biomes which extend to the bottom of the map above the Nether, since they -- 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. -- likely only extend that deep as a catch-all, and probably have a role nearer the surface.
new_y_min = NETHER_CEILING + 1 new_y_min = ceiling_y + 1
new_y_max = math_max(biome_y_max, NETHER_CEILING + 2) new_y_max = math_max(biome_y_max, ceiling_y + 2)
else else
-- shift the biome to below the Nether -- shift the biome to below the Nether
new_y_max = NETHER_FLOOR - 1 new_y_max = floor_y - 1
new_y_min = math_min(biome_y_min, NETHER_CEILING - 2) new_y_min = math_min(biome_y_min, floor_y - 2)
end 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) 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)
@ -162,7 +163,7 @@ local function override_underground_biomes()
end end
-- Shift any overlapping biomes out of the way before we create the Nether biomes -- Shift any overlapping biomes out of the way before we create the Nether biomes
override_underground_biomes() mapgen.shift_existing_biomes(NETHER_FLOOR, NETHER_CEILING)
-- nether:native_mapgen is used to prevent ores and decorations being generated according -- nether:native_mapgen is used to prevent ores and decorations being generated according
-- to landforms created by the native mapgen. -- to landforms created by the native mapgen.
@ -259,12 +260,12 @@ mapgen.np_cave = {
local cavePointPerlin = nil local cavePointPerlin = nil
mapgen.getCavePointPerlin = function() mapgen.get_cave_point_perlin = function()
cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave) cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave)
return cavePointPerlin return cavePointPerlin
end end
mapgen.getCavePerlinAt = function(pos) mapgen.get_cave_perlin_at = function(pos)
cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave) cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave)
return cavePointPerlin:get_3d(pos) return cavePointPerlin:get_3d(pos)
end end
@ -477,7 +478,7 @@ end
-- use knowledge of the nether mapgen algorithm to return a suitable ground level for placing a portal. -- 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. -- 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) function nether.find_nether_ground_y(target_x, target_z, start_y, player_name)
local nobj_cave_point = mapgen.getCavePointPerlin() local nobj_cave_point = mapgen.get_cave_point_perlin()
local air = 0 -- Consecutive air nodes found 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_schem, maxp_schem = nether.get_schematic_volume({x = target_x, y = 0, z = target_z}, nil, "nether_portal")

View File

@ -119,7 +119,7 @@ mapgen.add_basalt_columns = function(data, area, minp, maxp)
local yStride = area.ystride local yStride = area.ystride
local yCaveStride = x1 - x0 + 1 local yCaveStride = x1 - x0 + 1
local cavePerlin = mapgen.getCavePointPerlin() local cavePerlin = mapgen.get_cave_point_perlin()
nobj_basalt = nobj_basalt or minetest.get_perlin_map(np_basalt, {x = yCaveStride, y = yCaveStride}) nobj_basalt = nobj_basalt or minetest.get_perlin_map(np_basalt, {x = yCaveStride, y = yCaveStride})
local nvals_basalt = nobj_basalt:get_2d_map_flat({x=minp.x, y=minp.z}, {x=yCaveStride, y=yCaveStride}, nbuf_basalt) local nvals_basalt = nobj_basalt:get_2d_map_flat({x=minp.x, y=minp.z}, {x=yCaveStride, y=yCaveStride}, nbuf_basalt)
@ -431,13 +431,13 @@ mapgen.RegionEnum = {
-- Returns (region, noise) where region is a value from mapgen.RegionEnum -- Returns (region, noise) where region is a value from mapgen.RegionEnum
-- and noise is the unadjusted cave perlin value -- and noise is the unadjusted cave perlin value
mapgen.getRegion = function(pos) mapgen.get_region = function(pos)
if pos.y > nether.DEPTH_CEILING or pos.y < nether.DEPTH_FLOOR then if pos.y > nether.DEPTH_CEILING or pos.y < nether.DEPTH_FLOOR then
return mapgen.RegionEnum.OVERWORLD, nil return mapgen.RegionEnum.OVERWORLD, nil
end end
local caveNoise = mapgen.getCavePerlinAt(pos) local caveNoise = mapgen.get_cave_perlin_at(pos)
local sealevel, cavern_limit_distance = mapgen.find_nearest_lava_sealevel(pos.y) local sealevel, cavern_limit_distance = mapgen.find_nearest_lava_sealevel(pos.y)
local tcave_adj, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(pos.y) local tcave_adj, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(pos.y)
local tcave = mapgen.TCAVE + tcave_adj local tcave = mapgen.TCAVE + tcave_adj
@ -482,7 +482,7 @@ minetest.register_chatcommand("nether_whereami",
if player == nil then return false, S("Unknown player position") end if player == nil then return false, S("Unknown player position") end
local playerPos = vector.round(player:get_pos()) local playerPos = vector.round(player:get_pos())
local region, caveNoise = mapgen.getRegion(playerPos) local region, caveNoise = mapgen.get_region(playerPos)
local seaLevel, cavernLimitDistance = mapgen.find_nearest_lava_sealevel(playerPos.y) local seaLevel, cavernLimitDistance = mapgen.find_nearest_lava_sealevel(playerPos.y)
local tcave_adj, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(playerPos.y) local tcave_adj, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(playerPos.y)

78
nether_api.txt Normal file
View File

@ -0,0 +1,78 @@
Modding/interop guide to Nether
===============================
For portals API see portal_api.txt
The Nether mod exposes some of its functions and data via the lua global
`nether` and `nether.mapgen`
* `nether.DEPTH_CEILING`: [read-only] Y value of the top of the Nether.
* `nether.DEPTH_FLOOR`: [read-only] Y value of the bottom of the Nether.
* `nether.DEPTH_FLOOR_LAYERS`: [writable] Gives the bottom Y of all
locations that wish to be considered part of the Nether.
DEPTH_FLOOR_LAYERS Allows mods to insert extra layers below the
Nether, by knowing where their layer ceiling should start, and letting
the layers be included in effects which only happen in the Nether.
If a mod wishes to add a layer below the Nether it should read
`nether.DEPTH_FLOOR_LAYERS` to find the bottom Y of the Nether and any
other layers already under the Nether. The mod should leave a small gap
between DEPTH_FLOOR_LAYERS and its ceiling (e.g. use DEPTH_FLOOR_LAYERS - 6
for its ceiling Y, so there is room to shift edge-case biomes), then set
`nether.DEPTH_FLOOR_LAYERS` to reflect the mod's floor Y value, and call
`shift_existing_biomes()` with DEPTH_FLOOR_LAYERS as the `floor_y` argument.
* `nether.NETHER_REALM_ENABLED`: [read-only] Gets the value of the "Enable
Nether realm & portal" setting the nether mod exposes in Minetest's
"All Settings" -> "Mods" -> "nether" options.
When false, the entire nether mapgen is disabled (not run), and the portal
to it is not registered. Reasons someone might disable the Nether realm
include if a nether-layer mod was to be used as the Nether instead, or if
the portal mechanic was desired in a game without the Nether, etc.
* `nether.useBiomes`: [read-only] When this is false, the Nether interop
functions below are not available (nil).
Indicates that the biomes-enabled mapgen is in use. The Nether mod falls back
to older mapgen code for v6 maps and old versions of Minetest, the older
mapgen code doesn't use biomes and doesn't provide API/interop functions.
Mapgen functions available when nether.useBiomes is true
--------------------------------------------------------
The following functions are nil if `nether.useBiomes` is false,
and also nil if `nether.NETHER_REALM_ENABLED` is false.
* `nether.mapgen.shift_existing_biomes(floor_y, ceiling_y)` Move any existing
biomes out of the y-range specified by `floor_y` and `ceiling_y`.
* `nether.mapgen.get_region(pos)`: Returns two values, (region, noise) where
`region` is a value from `nether.mapgen.RegionEnum` and `noise` is the
unadjusted cave perlin value.
* `nether.mapgen.RegionEnum` values are tables which contain an invariant
`name` and a localized `desc`. Current region names include overworld,
positive, positive shell, center, center shell, negative, and negative
shell.
"positive" corresponds to conventional Nether caverns, and "center"
corresponds to the Mantle region.
* `nether.mapgen.get_cave_point_perlin()`: Returns the PerlinNoise object for
the Nether's cavern noise.
* `nether.mapgen.get_cave_perlin_at(pos)`: Returns the Nether cavern noise
value at a given 3D position.
Other mapgen functions
-------------------------------------------
If the Nether realm is enabled, then this function will be available
regardless of whether `nether.useBiomes` is true:
* `nether.find_nether_ground_y(target_x, target_z, start_y, player_name)`
Uses 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.

View File

@ -337,7 +337,7 @@ nether.cool_lava = function(pos, node)
-- Evaporate water sitting above lava, if it's in the Nether. -- Evaporate water sitting above lava, if it's in the Nether.
-- (we don't want Nether mod to affect overworld lava mechanics) -- (we don't want Nether mod to affect overworld lava mechanics)
if minetest.get_item_group(node_above.name, "water") > 0 and if minetest.get_item_group(node_above.name, "water") > 0 and
pos.y < nether.DEPTH_CEILING and pos.y > nether.DEPTH_FLOOR then pos.y < nether.DEPTH_CEILING and pos.y > nether.DEPTH_FLOOR_LAYERS then
-- cools_lava might be a better group to check for, but perhaps there's -- cools_lava might be a better group to check for, but perhaps there's
-- something in that group that isn't a liquid and shouldn't be evaporated? -- something in that group that isn't a liquid and shouldn't be evaporated?
minetest.swap_node(pos_above, {name="air"}) minetest.swap_node(pos_above, {name="air"})
@ -604,7 +604,7 @@ local function fumarole_onTimer(pos, elapsed)
-- Fumaroles in the Nether can catch fire. -- Fumaroles in the Nether can catch fire.
-- (if taken to the surface and used as cottage chimneys, they don't catch fire) -- (if taken to the surface and used as cottage chimneys, they don't catch fire)
local inNether = pos.y <= nether.DEPTH and pos.y >= nether.DEPTH_FLOOR local inNether = pos.y <= nether.DEPTH and pos.y >= nether.DEPTH_FLOOR_LAYERS
local canCatchFire = inNether and minetest.registered_nodes["fire:permanent_flame"] ~= nil local canCatchFire = inNether and minetest.registered_nodes["fire:permanent_flame"] ~= nil
local smoke_offset = 0 local smoke_offset = 0
local timeout_factor = 1 local timeout_factor = 1

View File

@ -2073,7 +2073,7 @@ function nether.register_portal(name, portaldef)
end end
portaldef.name = name portaldef.name = name
portaldef.mod_name = minetest.get_current_modname() portaldef.mod_name = minetest.get_current_modname() or "<mod name not recorded>"
-- use portaldef_default for any values missing from portaldef or portaldef.sounds -- use portaldef_default for any values missing from portaldef or portaldef.sounds
if portaldef.sounds ~= nil then setmetatable(portaldef.sounds, {__index = portaldef_default.sounds}) end if portaldef.sounds ~= nil then setmetatable(portaldef.sounds, {__index = portaldef_default.sounds}) end

View File

@ -250,16 +250,16 @@ Used by `nether.register_portal`.
-- player_name may be "", e.g. if the portal was ignited by a mesecon, -- player_name may be "", e.g. if the portal was ignited by a mesecon,
-- and is provided for use with volume_is_natural_and_unprotected() etc. -- and is provided for use with volume_is_natural_and_unprotected() etc.
on_run_wormhole = function(portalDef, anochorPos, orientation), on_run_wormhole = function(portalDef, anchorPos, orientation),
-- invoked once per second per portal -- invoked once per second per portal
on_extinguish = function(portalDef, anochorPos, orientation), on_extinguish = function(portalDef, anchorPos, orientation),
-- invoked when a portal is extinguished, including when the portal -- invoked when a portal is extinguished, including when the portal
-- it connected to was extinguished. -- it connected to was extinguished.
on_player_teleported = function(portalDef, player, oldPos, newPos), on_player_teleported = function(portalDef, player, oldPos, newPos),
-- invoked immediately after a player is teleported -- invoked immediately after a player is teleported
on_ignite = function(portalDef, anochorPos, orientation) on_ignite = function(portalDef, anchorPos, orientation)
-- invoked when a player or mesecon ignites a portal -- invoked when a player or mesecon ignites a portal
on_created = function(portalDef, anochorPos, orientation) on_created = function(portalDef, anchorPos, orientation)
-- invoked when a portal creates a remote twin, this is usually when -- invoked when a portal creates a remote twin, this is usually when
-- a player travels through a portal for the first time. -- a player travels through a portal for the first time.
} }

View File

@ -106,7 +106,7 @@ end
-- Surface-travel portal, playable code example -- -- Surface-travel portal, playable code example --
--==============================================-- --==============================================--
-- These Moore Curve functions requred by surface_portal's find_surface_anchorPos() will -- These Moore Curve functions required by surface_portal's find_surface_anchorPos() will
-- be assigned later in this file. -- be assigned later in this file.
local get_moore_distance -- will be function get_moore_distance(cell_count, x, y): integer local get_moore_distance -- will be function get_moore_distance(cell_count, x, y): integer
local get_moore_coords -- will be function get_moore_coords(cell_count, distance): pos2d local get_moore_coords -- will be function get_moore_coords(cell_count, distance): pos2d