Skip to content

Commit

Permalink
Make ShiftExistingBiomes available via API (#40)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
Treer authored Jul 18, 2021
1 parent 54613d6 commit 5203801
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 30 deletions.
25 changes: 19 additions & 6 deletions init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ if nether.DEPTH_FLOOR + 1000 > nether.DEPTH_CEILING then
end
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
-- evaluate when debugging is turned off.
Expand Down Expand Up @@ -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)
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.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)
local existing_portal_location, existing_portal_orientation =
Expand All @@ -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)
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.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)
local existing_portal_location, existing_portal_orientation =
Expand Down Expand Up @@ -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
result = "nether"

-- since mapgen_nobiomes.lua has no regions it doesn't implement getRegion(),
-- so only use getRegion() if it exists
if nether.mapgen.getRegion ~= nil then
-- since mapgen_nobiomes.lua has no regions it doesn't implement get_region(),
-- so only use get_region() if it exists
if nether.mapgen.get_region ~= nil then
-- the biomes-based mapgen supports 2 extra regions
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
result = "mantle"
elseif region == regions.NEGATIVE or region == regions.NEGATIVESHELL then
Expand Down
25 changes: 13 additions & 12 deletions mapgen.lua
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ local math_max, math_min, math_abs, math_floor = math.max, math.min, math.abs, m

-- 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
-- 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
Expand Down Expand Up @@ -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_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.
local new_y_min, new_y_max
local spaceOccupiedAbove = biome_y_max - NETHER_CEILING
local spaceOccupiedBelow = NETHER_FLOOR - biome_y_min
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 = NETHER_CEILING + 1
new_y_max = math_max(biome_y_max, NETHER_CEILING + 2)
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 = NETHER_FLOOR - 1
new_y_min = math_min(biome_y_min, NETHER_CEILING - 2)
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)
Expand All @@ -162,7 +163,7 @@ local function override_underground_biomes()
end

-- 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
-- to landforms created by the native mapgen.
Expand Down Expand Up @@ -259,12 +260,12 @@ mapgen.np_cave = {

local cavePointPerlin = nil

mapgen.getCavePointPerlin = function()
mapgen.get_cave_point_perlin = function()
cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave)
return cavePointPerlin
end

mapgen.getCavePerlinAt = function(pos)
mapgen.get_cave_perlin_at = function(pos)
cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave)
return cavePointPerlin:get_3d(pos)
end
Expand Down Expand Up @@ -477,7 +478,7 @@ 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.getCavePointPerlin()
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")
Expand Down
8 changes: 4 additions & 4 deletions mapgen_mantle.lua
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ mapgen.add_basalt_columns = function(data, area, minp, maxp)
local yStride = area.ystride
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})
local nvals_basalt = nobj_basalt:get_2d_map_flat({x=minp.x, y=minp.z}, {x=yCaveStride, y=yCaveStride}, nbuf_basalt)

Expand Down Expand Up @@ -431,13 +431,13 @@ mapgen.RegionEnum = {

-- Returns (region, noise) where region is a value from mapgen.RegionEnum
-- 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
return mapgen.RegionEnum.OVERWORLD, nil
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 tcave_adj, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(pos.y)
local tcave = mapgen.TCAVE + tcave_adj
Expand Down Expand Up @@ -482,7 +482,7 @@ minetest.register_chatcommand("nether_whereami",
if player == nil then return false, S("Unknown player position") end
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 tcave_adj, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(playerPos.y)

Expand Down
78 changes: 78 additions & 0 deletions nether_api.txt
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions nodes.lua
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ nether.cool_lava = function(pos, node)
-- Evaporate water sitting above lava, if it's in the Nether.
-- (we don't want Nether mod to affect overworld lava mechanics)
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
-- something in that group that isn't a liquid and shouldn't be evaporated?
minetest.swap_node(pos_above, {name="air"})
Expand Down Expand Up @@ -604,7 +604,7 @@ local function fumarole_onTimer(pos, elapsed)

-- Fumaroles in the Nether can 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 smoke_offset = 0
local timeout_factor = 1
Expand Down
2 changes: 1 addition & 1 deletion portal_api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2073,7 +2073,7 @@ function nether.register_portal(name, portaldef)
end

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
if portaldef.sounds ~= nil then setmetatable(portaldef.sounds, {__index = portaldef_default.sounds}) end
Expand Down
8 changes: 4 additions & 4 deletions portal_api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -250,16 +250,16 @@ Used by `nether.register_portal`.
-- 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.

on_run_wormhole = function(portalDef, anochorPos, orientation),
on_run_wormhole = function(portalDef, anchorPos, orientation),
-- 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
-- it connected to was extinguished.
on_player_teleported = function(portalDef, player, oldPos, newPos),
-- 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
on_created = function(portalDef, anochorPos, orientation)
on_created = function(portalDef, anchorPos, orientation)
-- invoked when a portal creates a remote twin, this is usually when
-- a player travels through a portal for the first time.
}
2 changes: 1 addition & 1 deletion portal_examples.lua
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ end
-- 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.
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
Expand Down

0 comments on commit 5203801

Please sign in to comment.