Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Sys Quatre 2020-07-31 22:23:04 +02:00
commit 97cf3250e4
8 changed files with 244 additions and 103 deletions

View File

@ -23,6 +23,7 @@ read_globals = {
"PseudoRandom", "PseudoRandom",
"stairs", "stairs",
"stairsplus", "stairsplus",
"string.split",
table = { fields = { "copy", "getn" } }, table = { fields = { "copy", "getn" } },
"vector", "vector",
"VoxelArea", "VoxelArea",

View File

@ -19,6 +19,13 @@
]]-- ]]--
-- Set DEBUG_FLAGS to determine the behavior of nether.debug():
-- 0 = off
-- 1 = print(...)
-- 2 = minetest.chat_send_all(...)
-- 4 = minetest.log("info", ...)
local DEBUG_FLAGS = 0
local S local S
if minetest.get_translator ~= nil then if minetest.get_translator ~= nil then
S = minetest.get_translator("nether") S = minetest.get_translator("nether")
@ -63,6 +70,48 @@ 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.
-- A debug-print function that understands vectors etc. and does not
-- evaluate when debugging is turned off.
-- Works like string.format(), treating the message as a format string.
-- nils, tables, and vectors passed as arguments to nether.debug() are
-- converted to strings and can be included inside the message with %s
function nether.debug(message, ...)
local args = {...}
local argCount = select("#", ...)
for i = 1, argCount do
local arg = args[i]
if arg == nil then
-- convert nils to strings
args[i] = '<nil>'
elseif type(arg) == "table" then
local tableCount = 0
for _,_ in pairs(arg) do tableCount = tableCount + 1 end
if tableCount == 3 and arg.x ~= nil and arg.y ~= nil and arg.z ~= nil then
-- convert vectors to strings
args[i] = minetest.pos_to_string(arg)
else
-- convert tables to strings
-- (calling function can use dump() if a multi-line listing is desired)
args[i] = string.gsub(dump(arg, ""), "\n", " ")
end
end
end
local composed_message = string.format(message, unpack(args))
if math.floor(DEBUG_FLAGS / 1) % 2 == 1 then print(composed_message) end
if math.floor(DEBUG_FLAGS / 2) % 2 == 1 then minetest.chat_send_all(composed_message) end
if math.floor(DEBUG_FLAGS / 4) % 2 == 1 then minetest.log("info", composed_message) end
end
if DEBUG_FLAGS == 0 then
-- do as little evaluation as possible
nether.debug = function() end
end
-- Load files -- Load files
dofile(nether.path .. "/portal_api.lua") dofile(nether.path .. "/portal_api.lua")
dofile(nether.path .. "/nodes.lua") dofile(nether.path .. "/nodes.lua")
@ -101,7 +150,7 @@ The expedition parties have found no diamonds or gold, and after an experienced
return pos.y < nether.DEPTH_CEILING return pos.y < nether.DEPTH_CEILING
end, end,
find_realm_anchorPos = function(surface_anchorPos) find_realm_anchorPos = function(surface_anchorPos, player_name)
-- divide x and z by a factor of 8 to implement Nether fast-travel -- divide x and z by a factor of 8 to implement Nether fast-travel
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
@ -116,12 +165,12 @@ The expedition parties have found no diamonds or gold, and after an experienced
return existing_portal_location, existing_portal_orientation return existing_portal_location, existing_portal_orientation
else else
local start_y = nether.DEPTH_CEILING - math.random(500, 1500) -- Search starting altitude local start_y = nether.DEPTH_CEILING - math.random(500, 1500) -- Search starting altitude
destination_pos.y = nether.find_nether_ground_y(destination_pos.x, destination_pos.z, start_y) destination_pos.y = nether.find_nether_ground_y(destination_pos.x, destination_pos.z, start_y, player_name)
return destination_pos return destination_pos
end end
end, end,
find_surface_anchorPos = function(realm_anchorPos) find_surface_anchorPos = function(realm_anchorPos, player_name)
-- A portal definition doesn't normally need to provide a find_surface_anchorPos() function, -- A portal definition doesn't normally need to provide a find_surface_anchorPos() function,
-- since find_surface_target_y() will be used by default, but Nether portals also scale position -- since find_surface_target_y() will be used by default, but Nether portals also scale position
-- to create fast-travel. -- to create fast-travel.
@ -140,7 +189,7 @@ The expedition parties have found no diamonds or gold, and after an experienced
if existing_portal_location ~= nil then if existing_portal_location ~= nil then
return existing_portal_location, existing_portal_orientation return existing_portal_location, existing_portal_orientation
else else
destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "nether_portal") destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "nether_portal", player_name)
return destination_pos return destination_pos
end end
end, end,

View File

@ -113,7 +113,7 @@ override_underground_biomes()
-- 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.
-- Ores and decorations are registered against "nether:rack" instead, and the lua -- 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 -- on_generate() callback will carve the Nether with nether:rack before invoking
-- generate_decorations and generate_ores. -- generate_decorations and generate_ores.
minetest.register_node("nether:native_mapgen", {}) minetest.register_node("nether:native_mapgen", {})
@ -160,7 +160,7 @@ minetest.register_ore({
ore_type = "scatter", ore_type = "scatter",
ore = "default:lava_source", ore = "default:lava_source",
wherein = "nether:rack", wherein = "nether:rack",
clust_scarcity = 32 * 32 * 32, clust_scarcity = 36 * 36 * 36,
clust_num_ores = 4, clust_num_ores = 4,
clust_size = 2, clust_size = 2,
y_max = NETHER_CEILING, y_max = NETHER_CEILING,
@ -465,10 +465,15 @@ 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.
function nether.find_nether_ground_y(target_x, target_z, start_y) -- 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 = minetest.get_perlin(np_cave) local nobj_cave_point = minetest.get_perlin(np_cave)
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 = {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 for y = start_y, math_max(NETHER_FLOOR + BLEND, start_y - 4096), -1 do
local nval_cave = nobj_cave_point:get3d({x = target_x, y = y, z = target_z}) local nval_cave = nobj_cave_point:get3d({x = target_x, y = y, z = target_z})
@ -476,13 +481,14 @@ function nether.find_nether_ground_y(target_x, target_z, start_y)
air = air + 1 air = air + 1
else -- Not cavern, check if 4 nodes of space above else -- Not cavern, check if 4 nodes of space above
if air >= 4 then if air >= 4 then
local portal_y = y + 1
-- Check volume for non-natural nodes -- Check volume for non-natural nodes
local minp = {x = target_x - 1, y = y , z = target_z - 2} minp.y = minp_schem.y + portal_y
local maxp = {x = target_x + 2, y = y + 4, z = target_z + 2} maxp.y = maxp_schem.y + portal_y
if nether.volume_is_natural(minp, maxp) then if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then
return y + 1 return portal_y
else -- Restart search a little lower else -- Restart search a little lower
nether.find_nether_ground_y(target_x, target_z, y - 16) nether.find_nether_ground_y(target_x, target_z, y - 16, player_name)
end end
else -- Not enough space, reset air to zero else -- Not enough space, reset air to zero
air = 0 air = 0

View File

@ -113,7 +113,7 @@ minetest.register_decoration({
deco_type = "schematic", deco_type = "schematic",
place_on = "nether:rack", place_on = "nether:rack",
sidelen = 80, sidelen = 80,
fill_ratio = 0.0004, fill_ratio = 0.0003,
biomes = {"nether_caverns"}, biomes = {"nether_caverns"},
y_max = nether.DEPTH_CEILING, -- keep compatibility with mapgen_nobiomes.lua y_max = nether.DEPTH_CEILING, -- keep compatibility with mapgen_nobiomes.lua
y_min = nether.DEPTH_FLOOR, y_min = nether.DEPTH_FLOOR,
@ -127,7 +127,7 @@ minetest.register_decoration({
deco_type = "schematic", deco_type = "schematic",
place_on = "nether:rack", place_on = "nether:rack",
sidelen = 80, sidelen = 80,
fill_ratio = 0.0007, fill_ratio = 0.0008,
biomes = {"nether_caverns"}, biomes = {"nether_caverns"},
y_max = nether.DEPTH_CEILING, -- keep compatibility with mapgen_nobiomes.lua y_max = nether.DEPTH_CEILING, -- keep compatibility with mapgen_nobiomes.lua
y_min = nether.DEPTH_FLOOR, y_min = nether.DEPTH_FLOOR,

View File

@ -201,24 +201,30 @@ 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.
function nether.find_nether_ground_y(target_x, target_z, start_y) -- 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 = minetest.get_perlin(np_cave) local nobj_cave_point = minetest.get_perlin(np_cave)
local air = 0 -- Consecutive air nodes found local air = 0 -- Consecutive air nodes found
for y = start_y, start_y - 4096, -1 do 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:get3d({x = target_x, y = y, z = target_z}) local nval_cave = nobj_cave_point:get3d({x = target_x, y = y, z = target_z})
if nval_cave > TCAVE then -- Cavern if nval_cave > TCAVE then -- Cavern
air = air + 1 air = air + 1
else -- Not cavern, check if 4 nodes of space above else -- Not cavern, check if 4 nodes of space above
if air >= 4 then if air >= 4 then
local portal_y = y + 1
-- Check volume for non-natural nodes -- Check volume for non-natural nodes
local minp = {x = target_x - 1, y = y , z = target_z - 2} minp.y = minp_schem.y + portal_y
local maxp = {x = target_x + 2, y = y + 4, z = target_z + 2} maxp.y = maxp_schem.y + portal_y
if nether.volume_is_natural(minp, maxp) then if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then
return y + 1 return portal_y
else -- Restart search a little lower else -- Restart search a little lower
nether.find_nether_ground_y(target_x, target_z, y - 16) nether.find_nether_ground_y(target_x, target_z, y - 16, player_name)
end end
else -- Not enough space, reset air to zero else -- Not enough space, reset air to zero
air = 0 air = 0
@ -226,5 +232,5 @@ function nether.find_nether_ground_y(target_x, target_z, start_y)
end end
end end
return start_y -- Fallback return math.max(start_y, NETHER_FLOOR + BLEND) -- Fallback
end end

View File

@ -23,8 +23,9 @@
]]-- ]]--
local DEBUG = false -- setting DEBUG_IGNORE_MODSTORAGE true prevents portals from knowing where other
local DEBUG_IGNORE_MODSTORAGE = false -- setting true prevents portals from knowing where other portals are, forcing find_realm_anchorpos() etc. to be executed every time -- portals are, forcing find_realm_anchorpos() etc. to be executed every time.
local DEBUG_IGNORE_MODSTORAGE = false
nether.registered_portals = {} nether.registered_portals = {}
nether.registered_portals_count = 0 nether.registered_portals_count = 0
@ -662,8 +663,9 @@ nether.PortalShape_Platform = {
-- Portal implementation functions -- -- Portal implementation functions --
-- =============================== -- -- =============================== --
local ignition_item_name
local S = nether.get_translator local S = nether.get_translator
local debugf = nether.debug
local ignition_item_name
local mod_storage = minetest.get_mod_storage() local mod_storage = minetest.get_mod_storage()
local meseconsAvailable = minetest.get_modpath("mesecon") ~= nil and minetest.global_exists("mesecon") local meseconsAvailable = minetest.get_modpath("mesecon") ~= nil and minetest.global_exists("mesecon")
local book_added_as_treasure = false local book_added_as_treasure = false
@ -821,7 +823,7 @@ end
local function store_portal_location_info(portal_name, anchorPos, orientation, ignited) local function store_portal_location_info(portal_name, anchorPos, orientation, ignited)
if not DEBUG_IGNORE_MODSTORAGE then if not DEBUG_IGNORE_MODSTORAGE then
local key = minetest.pos_to_string(anchorPos) .. " is " .. portal_name local key = minetest.pos_to_string(anchorPos) .. " is " .. portal_name
if DEBUG then minetest.chat_send_all("Adding/updating portal in mod_storage: " .. key) end debugf("Adding/updating portal in mod_storage: " .. key)
mod_storage:set_string( mod_storage:set_string(
key, key,
minetest.serialize({orientation = orientation, active = ignited}) minetest.serialize({orientation = orientation, active = ignited})
@ -834,7 +836,7 @@ end
local function remove_portal_location_info(portal_name, anchorPos) local function remove_portal_location_info(portal_name, anchorPos)
if not DEBUG_IGNORE_MODSTORAGE then if not DEBUG_IGNORE_MODSTORAGE then
local key = minetest.pos_to_string(anchorPos) .. " is " .. portal_name local key = minetest.pos_to_string(anchorPos) .. " is " .. portal_name
if DEBUG then minetest.chat_send_all("Removing portal from mod_storage: " .. key) end debugf("Removing portal from mod_storage: " .. key)
mod_storage:set_string(key, "") mod_storage:set_string(key, "")
end end
end end
@ -872,7 +874,7 @@ local function list_closest_portals(portal_definition, anchorPos, distance_limit
local distance = math.hypot(y * y_factor, math.hypot(x, z)) local distance = math.hypot(y * y_factor, math.hypot(x, z))
if distance <= distance_limit or distance_limit < 0 then if distance <= distance_limit or distance_limit < 0 then
local info = minetest.deserialize(value) or {} local info = minetest.deserialize(value) or {}
if DEBUG then minetest.chat_send_all("found " .. found_name .. " listed at distance " .. distance .. " (within " .. distance_limit .. ") from dest " .. minetest.pos_to_string(anchorPos) .. ", found: " .. minetest.pos_to_string(found_anchorPos) .. " orientation " .. info.orientation) end debugf("found %s listed at distance %.2f (within %.2f) from dest %s, found: %s orientation %s", found_name, distance, distance_limit, anchorPos, found_anchorPos, info.orientation)
info.anchorPos = found_anchorPos info.anchorPos = found_anchorPos
info.distance = distance info.distance = distance
result[distance] = info result[distance] = info
@ -924,14 +926,14 @@ end
function extinguish_portal(pos, node_name, frame_was_destroyed) function extinguish_portal(pos, node_name, frame_was_destroyed)
-- mesecons seems to invoke action_off() 6 times every time you place a block? -- mesecons seems to invoke action_off() 6 times every time you place a block?
if DEBUG then minetest.chat_send_all("extinguish_portal" .. minetest.pos_to_string(pos) .. " " .. node_name) end debugf("extinguish_portal %s %s", pos, node_name)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
local p1 = minetest.string_to_pos(meta:get_string("p1")) local p1 = minetest.string_to_pos(meta:get_string("p1"))
local p2 = minetest.string_to_pos(meta:get_string("p2")) local p2 = minetest.string_to_pos(meta:get_string("p2"))
local target = minetest.string_to_pos(meta:get_string("target")) local target = minetest.string_to_pos(meta:get_string("target"))
if p1 == nil or p2 == nil then if p1 == nil or p2 == nil then
if DEBUG then minetest.chat_send_all(" no active portal found to extinguish") end debugf(" no active portal found to extinguish")
return false return false
end end
@ -983,7 +985,7 @@ function extinguish_portal(pos, node_name, frame_was_destroyed)
end end
if target ~= nil then if target ~= nil then
if DEBUG then minetest.chat_send_all(" attempting to also extinguish target with wormholePos " .. minetest.pos_to_string(target)) end debugf(" attempting to also extinguish target with wormholePos %s", target)
extinguish_portal(target, node_name) extinguish_portal(target, node_name)
end end
@ -1000,7 +1002,8 @@ end
-- Note: will extinguish any portal using the same nodes that are being set -- Note: will extinguish any portal using the same nodes that are being set
local function set_portal_metadata(portal_definition, anchorPos, orientation, destination_wormholePos, ignite) local function set_portal_metadata(portal_definition, anchorPos, orientation, destination_wormholePos, ignite)
if DEBUG then minetest.chat_send_all("set_portal_metadata(ignite=" .. tostring(ignite) .. ") at " .. minetest.pos_to_string(anchorPos) .. " orient " .. orientation .. ", setting to target " .. minetest.pos_to_string(destination_wormholePos)) end ignite = ignite or false;
debugf("set_portal_metadata(ignite=%s) at %s orient %s, setting to target %s", ignite, anchorPos, orientation, destination_wormholePos)
-- Portal position is stored in metadata as p1 and p2 to keep maps compatible with earlier versions of this mod. -- Portal position is stored in metadata as p1 and p2 to keep maps compatible with earlier versions of this mod.
-- p1 is the bottom/west/south corner of the portal, and p2 is the opposite corner, together -- p1 is the bottom/west/south corner of the portal, and p2 is the opposite corner, together
@ -1028,7 +1031,7 @@ local function set_portal_metadata(portal_definition, anchorPos, orientation, de
if existing_p1 ~= "" then if existing_p1 ~= "" then
local existing_p2 = meta:get_string("p2") local existing_p2 = meta:get_string("p2")
if existing_p1 ~= p1_string or existing_p2 ~= p2_string then if existing_p1 ~= p1_string or existing_p2 ~= p2_string then
if DEBUG then minetest.chat_send_all("set_portal_metadata() found existing metadata from another portal: existing_p1 " .. existing_p1 .. ", existing_p2" .. existing_p2 .. ", p1 " .. p1_string .. ", p2 " .. p2_string .. ", will existinguish existing portal...") end debugf("set_portal_metadata() found existing metadata from another portal: existing_p1 %s, existing_p2 %s, p1 %s, p2 %s, will extinguish existing portal...", existing_p1, existing_p2, p1_string, p2_string)
-- this node is already part of another portal, so extinguish that, because nodes only -- this node is already part of another portal, so extinguish that, because nodes only
-- contain a link in the metadata to one portal, and being part of two allows a slew of bugs -- contain a link in the metadata to one portal, and being part of two allows a slew of bugs
extinguish_portal(pos, node_name, false) extinguish_portal(pos, node_name, false)
@ -1092,7 +1095,7 @@ local function is_portal_at_anchorPos(portal_definition, anchorPos, orientation,
-- area isn't loaded, force loading/emerge of check area -- area isn't loaded, force loading/emerge of check area
minetest.get_voxel_manip():read_from_map(check_pos, check_pos) minetest.get_voxel_manip():read_from_map(check_pos, check_pos)
foundName = minetest.get_node(check_pos).name foundName = minetest.get_node(check_pos).name
if DEBUG then minetest.chat_send_all("Forced loading of 'ignore' node at " .. minetest.pos_to_string(check_pos) .. ", got " .. foundName) end debugf("Forced loading of 'ignore' node at %s, got %s", check_pos, foundName)
if foundName ~= frame_node_name then if foundName ~= frame_node_name then
nodes_are_valid = false nodes_are_valid = false
@ -1202,7 +1205,7 @@ local function build_portal(portal_definition, anchorPos, orientation, destinati
function(pos) minetest.swap_node(pos, wormholeNode) end function(pos) minetest.swap_node(pos, wormholeNode) end
) )
if DEBUG then minetest.chat_send_all("Placed " .. portal_definition.name .. " portal schematic at " .. minetest.pos_to_string(portal_definition.shape.get_schematicPos_from_anchorPos(anchorPos, orientation)) .. ", orientation " .. orientation) end debugf("Placed %s portal schematic at %s, orientation %s", portal_definition.name, portal_definition.shape.get_schematicPos_from_anchorPos(anchorPos, orientation), orientation)
set_portal_metadata(portal_definition, anchorPos, orientation, destination_wormholePos) set_portal_metadata(portal_definition, anchorPos, orientation, destination_wormholePos)
@ -1216,7 +1219,7 @@ end
-- Make portals immortal for ~20 seconds after creation -- Make portals immortal for ~20 seconds after creation
local function remote_portal_checkup(elapsed, portal_definition, anchorPos, orientation, destination_wormholePos) local function remote_portal_checkup(elapsed, portal_definition, anchorPos, orientation, destination_wormholePos)
if DEBUG then minetest.chat_send_all("portal checkup at " .. elapsed .. " seconds") end debugf("portal checkup at %d seconds", elapsed)
local wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(anchorPos, orientation) local wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(anchorPos, orientation)
local wormhole_node = minetest.get_node_or_nil(wormholePos) local wormhole_node = minetest.get_node_or_nil(wormholePos)
@ -1231,7 +1234,7 @@ local function remote_portal_checkup(elapsed, portal_definition, anchorPos, orie
-- ruh roh -- ruh roh
local message = "Newly created portal at " .. minetest.pos_to_string(anchorPos) .. " was overwritten. Attempting to recreate. Issue spotted after " .. elapsed .. " seconds" local message = "Newly created portal at " .. minetest.pos_to_string(anchorPos) .. " was overwritten. Attempting to recreate. Issue spotted after " .. elapsed .. " seconds"
minetest.log("warning", message) minetest.log("warning", message)
if DEBUG then minetest.chat_send_all("!!! " .. message) end debugf("!!! " .. message)
-- A pre-existing portal frame wouldn't have been immediately overwritten, so no need to check for one, just place the portal. -- A pre-existing portal frame wouldn't have been immediately overwritten, so no need to check for one, just place the portal.
build_portal(portal_definition, anchorPos, orientation, destination_wormholePos) build_portal(portal_definition, anchorPos, orientation, destination_wormholePos)
@ -1259,7 +1262,7 @@ end
-- specified if an existing portal was already found there. -- specified if an existing portal was already found there.
local function locate_or_build_portal(portal_definition, suggested_wormholePos, suggested_orientation, destination_wormholePos) local function locate_or_build_portal(portal_definition, suggested_wormholePos, suggested_orientation, destination_wormholePos)
if DEBUG then minetest.chat_send_all("locate_or_build_portal() called at wormholePos" .. minetest.pos_to_string(suggested_wormholePos) .. " with suggested orient " .. suggested_orientation .. ", targetted to " .. minetest.pos_to_string(destination_wormholePos)) end debugf("locate_or_build_portal() called at wormholePos%s with suggested orient %s, targeted to %s", suggested_wormholePos, suggested_orientation, destination_wormholePos)
local result_anchorPos; local result_anchorPos;
local result_orientation; local result_orientation;
@ -1280,13 +1283,13 @@ local function locate_or_build_portal(portal_definition, suggested_wormholePos,
if result_target ~= nil and vector.equals(result_target, destination_wormholePos) then if result_target ~= nil and vector.equals(result_target, destination_wormholePos) then
-- It already links back to the portal the player is teleporting from, so don't -- It already links back to the portal the player is teleporting from, so don't
-- extinguish it or the player's portal will also extinguish. -- extinguish it or the player's portal will also extinguish.
if DEBUG then minetest.chat_send_all(" Build unnecessary: already a lit portal that links back here at " .. minetest.pos_to_string(found_anchorPos) .. ", orientation " .. result_orientation) end debugf(" Build unnecessary: already a lit portal that links back here at %s, orientation %s", found_anchorPos, result_orientation)
else else
if DEBUG then minetest.chat_send_all(" Build unnecessary: already a lit portal at " .. minetest.pos_to_string(found_anchorPos) .. ", orientation " .. result_orientation .. ", linking to " .. result_target_str .. ". Extinguishing...") end debugf(" Build unnecessary: already a lit portal at %s, orientation %s, linking to %s. Extinguishing...", found_anchorPos, result_orientation, result_target_str)
extinguish_portal(found_anchorPos, portal_definition.frame_node_name, false) extinguish_portal(found_anchorPos, portal_definition.frame_node_name, false)
end end
else else
if DEBUG then minetest.chat_send_all(" Build unnecessary: already an unlit portal at " .. minetest.pos_to_string(found_anchorPos) .. ", orientation " .. result_orientation) end debugf(" Build unnecessary: already an unlit portal at %s, orientation %s", found_anchorPos, result_orientation)
end end
-- ignite the portal -- ignite the portal
set_portal_metadata_and_ignite(portal_definition, result_anchorPos, result_orientation, destination_wormholePos) set_portal_metadata_and_ignite(portal_definition, result_anchorPos, result_orientation, destination_wormholePos)
@ -1303,11 +1306,12 @@ end
-- invoked when a player attempts to turn obsidian nodes into an open portal -- invoked when a player attempts to turn obsidian nodes into an open portal
-- player_name is optional, allowing a player to spawn a remote portal in their own protected area
-- ignition_node_name is optional -- ignition_node_name is optional
local function ignite_portal(ignition_pos, ignition_node_name) local function ignite_portal(ignition_pos, player_name, ignition_node_name)
if ignition_node_name == nil then ignition_node_name = minetest.get_node(ignition_pos).name end if ignition_node_name == nil then ignition_node_name = minetest.get_node(ignition_pos).name end
if DEBUG then minetest.chat_send_all("IGNITE the " .. ignition_node_name .. " at " .. minetest.pos_to_string(ignition_pos)) end debugf("IGNITE the %s at %s", ignition_node_name, ignition_pos)
-- find which sort of portals are made from the node that was clicked on -- find which sort of portals are made from the node that was clicked on
local portal_definition_list = list_portal_definitions_for_frame_node(ignition_node_name) local portal_definition_list = list_portal_definitions_for_frame_node(ignition_node_name)
@ -1318,7 +1322,7 @@ local function ignite_portal(ignition_pos, ignition_node_name)
-- check it was a portal frame that the player is trying to ignite -- check it was a portal frame that the player is trying to ignite
local anchorPos, orientation, is_ignited = is_within_portal_frame(portal_definition, ignition_pos) local anchorPos, orientation, is_ignited = is_within_portal_frame(portal_definition, ignition_pos)
if anchorPos == nil then if anchorPos == nil then
if DEBUG then minetest.chat_send_all("No " .. portal_definition.name .. " portal frame found at " .. minetest.pos_to_string(ignition_pos)) end debugf("No %s portal frame found at ", portal_definition.name, ignition_pos)
continue = true -- no portal is here, but perhaps there's more than one portal type we need to search for continue = true -- no portal is here, but perhaps there's more than one portal type we need to search for
elseif is_ignited then elseif is_ignited then
-- Found a portal, check its metadata and timer is healthy. -- Found a portal, check its metadata and timer is healthy.
@ -1330,10 +1334,10 @@ local function ignite_portal(ignition_pos, ignition_node_name)
-- metadata is missing, the portal frame node must have been removed without calling -- metadata is missing, the portal frame node must have been removed without calling
-- on_destruct - perhaps by an ABM, then replaced - presumably by a player. -- on_destruct - perhaps by an ABM, then replaced - presumably by a player.
-- allowing reigniting will repair the portal -- allowing reigniting will repair the portal
if DEBUG then minetest.chat_send_all("Broken portal detected, allowing reignition/repair") end debugf("Broken portal detected, allowing reignition/repair")
repair = true repair = true
else else
if DEBUG then minetest.chat_send_all("This portal links to " .. meta:get_string("target") .. ". p1=" .. meta:get_string("p1") .. " p2=" .. meta:get_string("p2")) end debugf("This portal links to %s. p1=%s p2=%s", meta:get_string("target"), meta:get_string("p1"), meta:get_string("p2"))
-- Check the portal's timer is running, and fix if it's not. -- Check the portal's timer is running, and fix if it's not.
-- A portal's timer can stop running if the game is played without that portal type being -- A portal's timer can stop running if the game is played without that portal type being
@ -1341,7 +1345,7 @@ local function ignite_portal(ignition_pos, ignition_node_name)
-- (if this is a frequent problem, then change the value of "run_at_every_load" in the lbm) -- (if this is a frequent problem, then change the value of "run_at_every_load" in the lbm)
local timer = minetest.get_node_timer(get_timerPos_from_p1_and_p2(minetest.string_to_pos(p1), minetest.string_to_pos(p2))) local timer = minetest.get_node_timer(get_timerPos_from_p1_and_p2(minetest.string_to_pos(p1), minetest.string_to_pos(p2)))
if timer ~= nil and timer:get_timeout() == 0 then if timer ~= nil and timer:get_timeout() == 0 then
if DEBUG then minetest.chat_send_all("Portal timer was not running: restarting the timer.") end debugf("Portal timer was not running: restarting the timer.")
timer:start(1) timer:start(1)
end end
end end
@ -1350,23 +1354,25 @@ local function ignite_portal(ignition_pos, ignition_node_name)
end end
if continue == false then if continue == false then
if DEBUG then minetest.chat_send_all("Found portal frame. Looked at " .. minetest.pos_to_string(ignition_pos) .. ", found at " .. minetest.pos_to_string(anchorPos) .. " orientation " .. orientation) end debugf("Found portal frame. Looked at %s, found at %s orientation %s", ignition_pos, anchorPos, orientation)
local destination_anchorPos, destination_orientation local destination_anchorPos, destination_orientation
if portal_definition.is_within_realm(ignition_pos) then if portal_definition.is_within_realm(ignition_pos) then
destination_anchorPos, destination_orientation = portal_definition.find_surface_anchorPos(anchorPos) destination_anchorPos, destination_orientation = portal_definition.find_surface_anchorPos(anchorPos, player_name or "")
else else
destination_anchorPos, destination_orientation = portal_definition.find_realm_anchorPos(anchorPos) destination_anchorPos, destination_orientation = portal_definition.find_realm_anchorPos(anchorPos, player_name or "")
end
if destination_orientation == nil then
debugf("No destination_orientation given")
destination_orientation = orientation
end end
if DEBUG and destination_orientation == nil then minetest.chat_send_all("No destination_orientation given") end
if destination_orientation == nil then destination_orientation = orientation end
if destination_anchorPos == nil then if destination_anchorPos == nil then
if DEBUG then minetest.chat_send_all("No portal destination available here!") end debugf("No portal destination available here!")
return false return false
else else
local destination_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(destination_anchorPos, destination_orientation) local destination_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(destination_anchorPos, destination_orientation)
if DEBUG then minetest.chat_send_all("Destination set to " .. minetest.pos_to_string(destination_anchorPos)) end debugf("Destination set to %s", destination_anchorPos)
-- ignition/BURN_BABY_BURN -- ignition/BURN_BABY_BURN
set_portal_metadata_and_ignite(portal_definition, anchorPos, orientation, destination_wormholePos) set_portal_metadata_and_ignite(portal_definition, anchorPos, orientation, destination_wormholePos)
@ -1405,7 +1411,7 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
local local_p1, local_p2 = portal_definition.shape:get_p1_and_p2_from_anchorPos(local_anchorPos, local_orientation) local local_p1, local_p2 = portal_definition.shape:get_p1_and_p2_from_anchorPos(local_anchorPos, local_orientation)
local p1_at_playerPos = minetest.string_to_pos(meta:get_string("p1")) local p1_at_playerPos = minetest.string_to_pos(meta:get_string("p1"))
if p1_at_playerPos == nil or not vector.equals(local_p1, p1_at_playerPos) then if p1_at_playerPos == nil or not vector.equals(local_p1, p1_at_playerPos) then
if DEBUG then minetest.chat_send_all("the player already teleported from " .. minetest.pos_to_string(local_anchorPos) .. ", and is now standing in a different portal - " .. meta:get_string("p1")) end debugf("the player already teleported from %s, and is now standing in a different portal - %s", local_anchorPos, meta:get_string("p1"))
return -- the player already teleported, and is now standing in a different portal return -- the player already teleported, and is now standing in a different portal
end end
@ -1413,7 +1419,7 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
if dest_wormhole_node == nil then if dest_wormhole_node == nil then
-- area not emerged yet, delay and retry -- area not emerged yet, delay and retry
if DEBUG then minetest.chat_send_all("ensure_remote_portal_then_teleport() could not find anything yet at " .. minetest.pos_to_string(destination_wormholePos)) end debugf("ensure_remote_portal_then_teleport() could not find anything yet at %s", destination_wormholePos)
minetest.after(1, ensure_remote_portal_then_teleport, playerName, portal_definition, local_anchorPos, local_orientation, destination_wormholePos) minetest.after(1, ensure_remote_portal_then_teleport, playerName, portal_definition, local_anchorPos, local_orientation, destination_wormholePos)
else else
local local_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(local_anchorPos, local_orientation) local local_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(local_anchorPos, local_orientation)
@ -1430,9 +1436,9 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
local remoteMeta = minetest.get_meta(destination_wormholePos) local remoteMeta = minetest.get_meta(destination_wormholePos)
local remoteTarget = minetest.string_to_pos(remoteMeta:get_string("target")) local remoteTarget = minetest.string_to_pos(remoteMeta:get_string("target"))
if remoteTarget == nil then if remoteTarget == nil then
if DEBUG then minetest.chat_send_all("Failed to test whether target portal links back to this one") end debugf("Failed to test whether target portal links back to this one")
elseif not vector.equals(remoteTarget, local_wormholePos) then elseif not vector.equals(remoteTarget, local_wormholePos) then
if DEBUG then minetest.chat_send_all("Target portal is already linked, extinguishing then relighting to point back at this one") end debugf("Target portal is already linked, extinguishing then relighting to point back at this one")
extinguish_portal(remoteTarget, portal_definition.frame_node_name, false) extinguish_portal(remoteTarget, portal_definition.frame_node_name, false)
set_portal_metadata_and_ignite( set_portal_metadata_and_ignite(
portal_definition, portal_definition,
@ -1442,7 +1448,7 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
) )
end end
if DEBUG then minetest.chat_send_all("Teleporting player from wormholePos" .. minetest.pos_to_string(local_wormholePos) .. " to wormholePos" .. minetest.pos_to_string(destination_wormholePos)) end debugf("Teleporting player from wormholePos%s to wormholePos%s", local_wormholePos, destination_wormholePos)
-- play the teleport sound -- play the teleport sound
if portal_definition.sounds.teleport ~= nil then if portal_definition.sounds.teleport ~= nil then
@ -1469,7 +1475,7 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
-- which will leave a confused player. -- which will leave a confused player.
-- I don't think this is worth preventing, but I document it incase someone describes entering a portal -- I don't think this is worth preventing, but I document it incase someone describes entering a portal
-- and then the portal turning off. -- and then the portal turning off.
if DEBUG then minetest.chat_send_all("ensure_remote_portal_then_teleport() saw " .. dest_wormhole_node.name .. " at " .. minetest.pos_to_string(destination_wormholePos) .. " rather than a wormhole. Calling locate_or_build_portal()") end debugf("ensure_remote_portal_then_teleport() saw %s at %s rather than a wormhole. Calling locate_or_build_portal()", dest_wormhole_node.name, destination_wormholePos)
local new_dest_anchorPos, new_dest_orientation = locate_or_build_portal(portal_definition, destination_wormholePos, local_orientation, local_wormholePos) local new_dest_anchorPos, new_dest_orientation = locate_or_build_portal(portal_definition, destination_wormholePos, local_orientation, local_wormholePos)
local new_dest_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(new_dest_anchorPos, new_dest_orientation) local new_dest_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(new_dest_anchorPos, new_dest_orientation)
@ -1486,10 +1492,10 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
-- local portal to also be extinguished. -- local portal to also be extinguished.
local message = "Local portal at " .. minetest.pos_to_string(local_anchorPos) .. " was extinguished while linking to existing portal at " .. minetest.pos_to_string(new_dest_anchorPos) local message = "Local portal at " .. minetest.pos_to_string(local_anchorPos) .. " was extinguished while linking to existing portal at " .. minetest.pos_to_string(new_dest_anchorPos)
minetest.log("error", message) minetest.log("error", message)
if DEBUG then minetest.chat_send_all("!ERROR! - " .. message) end debugf("!ERROR! - " .. message)
else else
destination_wormholePos = new_dest_wormholePos destination_wormholePos = new_dest_wormholePos
if DEBUG then minetest.chat_send_all(" updating target to where remote portal was found - " .. minetest.pos_to_string(destination_wormholePos)) end debugf(" updating target to where remote portal was found - %s", destination_wormholePos)
set_portal_metadata( set_portal_metadata(
portal_definition, portal_definition,
@ -1824,22 +1830,22 @@ function register_frame_node(frame_node_name)
extended_node_def.replaced_by_portalapi.mesecons = extended_node_def.mesecons extended_node_def.replaced_by_portalapi.mesecons = extended_node_def.mesecons
extended_node_def.mesecons = {effector = { extended_node_def.mesecons = {effector = {
action_on = function (pos, node) action_on = function (pos, node)
if DEBUG then minetest.chat_send_all("portal frame material: mesecons action ON") end debugf("portal frame material: mesecons action ON")
ignite_portal(pos, node.name) ignite_portal(pos, nil, node.name)
end, end,
action_off = function (pos, node) action_off = function (pos, node)
if DEBUG then minetest.chat_send_all("portal frame material: mesecons action OFF") end debugf("portal frame material: mesecons action OFF")
extinguish_portal(pos, node.name, false) extinguish_portal(pos, node.name, false)
end end
}} }}
extended_node_def.replaced_by_portalapi.on_destruct = extended_node_def.on_destruct extended_node_def.replaced_by_portalapi.on_destruct = extended_node_def.on_destruct
extended_node_def.on_destruct = function(pos) extended_node_def.on_destruct = function(pos)
if DEBUG then minetest.chat_send_all("portal frame material: destruct") end debugf("portal frame material: destruct")
extinguish_portal(pos, frame_node_name, true) extinguish_portal(pos, frame_node_name, true)
end end
extended_node_def.replaced_by_portalapi.on_blast = extended_node_def.on_blast extended_node_def.replaced_by_portalapi.on_blast = extended_node_def.on_blast
extended_node_def.on_blast = function(pos, intensity) extended_node_def.on_blast = function(pos, intensity)
if DEBUG then minetest.chat_send_all("portal frame material: blast") end debugf("portal frame material: blast")
extinguish_portal(pos, frame_node_name, extended_node_def.replaced_by_portalapi.on_blast == nil) extinguish_portal(pos, frame_node_name, extended_node_def.replaced_by_portalapi.on_blast == nil)
if extended_node_def.replaced_by_portalapi.on_blast ~= nil then if extended_node_def.replaced_by_portalapi.on_blast ~= nil then
extended_node_def.replaced_by_portalapi.on_blast(pos, intensity) extended_node_def.replaced_by_portalapi.on_blast(pos, intensity)
@ -1935,9 +1941,9 @@ minetest.register_lbm({
local timer = minetest.get_node_timer(timerPos) local timer = minetest.get_node_timer(timerPos)
if timer ~= nil then if timer ~= nil then
timer:start(1) timer:start(1)
if DEBUG then minetest.chat_send_all("LBM started portal timer " .. minetest.pos_to_string(timerPos)) end debugf("LBM started portal timer %s", timerPos)
elseif DEBUG then else
minetest.chat_send_all("get_node_timer" .. minetest.pos_to_string(timerPos) .. " returned null") debugf("get_node_timer%s returned null", timerPos)
end end
end end
end end
@ -2081,7 +2087,7 @@ function nether.register_portal(name, portaldef)
end end
if portaldef.find_surface_anchorPos == nil then -- default to using find_surface_target_y() if portaldef.find_surface_anchorPos == nil then -- default to using find_surface_target_y()
portaldef.find_surface_anchorPos = function(pos) portaldef.find_surface_anchorPos = function(pos, player_name)
local destination_pos = {x = pos.x, y = 0, z = pos.z} local destination_pos = {x = pos.x, y = 0, z = pos.z}
local existing_portal_location, existing_portal_orientation = local existing_portal_location, existing_portal_orientation =
@ -2089,7 +2095,7 @@ function nether.register_portal(name, portaldef)
if existing_portal_location ~= nil then if existing_portal_location ~= nil then
return existing_portal_location, existing_portal_orientation return existing_portal_location, existing_portal_orientation
else else
destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, name) destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, name, player_name)
return destination_pos return destination_pos
end end
end end
@ -2159,7 +2165,7 @@ function nether.register_portal_ignition_item(item_name, ignition_failure_sound)
local done = false local done = false
if pt.under and nether.is_frame_node[node.name] then if pt.under and nether.is_frame_node[node.name] then
done = ignite_portal(pt.under) done = ignite_portal(pt.under, placer:get_player_name())
if done and not minetest.settings:get_bool("creative_mode") then if done and not minetest.settings:get_bool("creative_mode") then
stack:take_item() stack:take_item()
end end
@ -2180,24 +2186,24 @@ end
-- use this when determining where to spawn a portal, to avoid overwriting player builds -- use this when determining where to spawn a portal, to avoid overwriting player builds
-- It checks the area for any nodes that aren't ground or trees. -- It checks the area for any nodes that aren't ground or trees.
-- player_name is optional, allowing a player to spawn a remote portal in their own protected areas.
-- (Water also fails this test, unless it is unemerged) -- (Water also fails this test, unless it is unemerged)
function nether.volume_is_natural(minp, maxp) function nether.volume_is_natural_and_unprotected(minp, maxp, player_name)
local c_air = minetest.get_content_id("air") local c_air = minetest.get_content_id("air")
local c_ignore = minetest.get_content_id("ignore") local c_ignore = minetest.get_content_id("ignore")
local vm = minetest.get_voxel_manip() local vm = minetest.get_voxel_manip()
local pos1 = {x = minp.x, y = minp.y, z = minp.z} local emin, emax = vm:read_from_map(minp, maxp)
local pos2 = {x = maxp.x, y = maxp.y, z = maxp.z}
local emin, emax = vm:read_from_map(pos1, pos2)
local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax}) local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
local data = vm:get_data() local data = vm:get_data()
for z = pos1.z, pos2.z do for z = minp.z, maxp.z do
for y = pos1.y, pos2.y do for y = minp.y, maxp.y do
local vi = area:index(pos1.x, y, z) local vi = area:index(minp.x, y, z)
for x = pos1.x, pos2.x do for x = minp.x, maxp.x do
local id = data[vi] -- Existing node local id = data[vi] -- Existing node
if DEBUG and id == nil then minetest.chat_send_all("nil block at index " .. vi) end if id == nil then debugf("nil block at index " .. vi) end
if id ~= c_air and id ~= c_ignore and id ~= nil then -- checked for common natural or not emerged if id ~= c_air and id ~= c_ignore and id ~= nil then -- checked for common natural or not emerged
local name = minetest.get_name_from_content_id(id) local name = minetest.get_name_from_content_id(id)
local nodedef = minetest.registered_nodes[name] local nodedef = minetest.registered_nodes[name]
@ -2205,7 +2211,7 @@ function nether.volume_is_natural(minp, maxp)
-- trees are natural but not "ground content" -- trees are natural but not "ground content"
local node_groups = nodedef.groups local node_groups = nodedef.groups
if node_groups == nil or (node_groups.tree == nil and node_groups.leaves == nil and node_groups.leafdecay == nil) then if node_groups == nil or (node_groups.tree == nil and node_groups.leaves == nil and node_groups.leafdecay == nil) then
if DEBUG then minetest.chat_send_all("volume_is_natural() found unnatural node " .. name) end debugf("volume_is_natural_and_unprotected() found unnatural node %s", name)
return false return false
end end
end end
@ -2215,13 +2221,73 @@ function nether.volume_is_natural(minp, maxp)
end end
end end
if DEBUG then minetest.chat_send_all("Volume is natural") end if minetest.is_area_protected(minp, maxp, player_name or "") then
debugf("Volume is protected %s-%s", minp, maxp)
return false;
end
debugf("Volume is natural and unprotected for player '%s', %s-%s", player_name, minp, maxp)
return true return true
end end
-- Deprecated, use nether.volume_is_natural_and_unprotected() instead.
function nether.volume_is_natural(minp, maxp)
if nether.deprecation_warning_volume_is_natural == nil then
local stack = debug.traceback("", 2);
local calling_func = (string.split(stack, "\n", false, 2, false)[2] or ""):trim()
minetest.log("warning",
"Deprecated function \"nether.volume_is_natural()\" invoked, use \"nether.volume_is_natural_and_unprotected()\" instead. " ..
calling_func)
nether.deprecation_warning_volume_is_natural = true;
end
return nether.volume_is_natural_and_unprotected(minp, maxp)
end
-- Gets the volume that may be altered if a portal is placed at the anchor_pos
-- orientation is optional, but specifying it will reduce the volume returned
-- portal_name is optional, but specifying it will reduce the volume returned
-- returns minp, maxp
function nether.get_schematic_volume(anchor_pos, orientation, portal_name)
if orientation == nil then
-- Return a volume large enough for any orientation
local minp0, maxp0 = nether.get_schematic_volume(anchor_pos, 0, portal_name)
local minp1, maxp1 = nether.get_schematic_volume(anchor_pos, 1, portal_name)
-- ToDo: If an asymmetric portal is used with an anchor not at the center of the
-- schematic then we will also need to check orientations 3 and 4.
-- (The currently existing portal-shapes are not affected)
return
{x = math.min(minp0.x, minp1.x), y = math.min(minp0.y, minp1.y), z = math.min(minp0.z, minp1.z)},
{x = math.max(maxp0.x, maxp1.x), y = math.max(maxp0.y, maxp1.y), z = math.max(maxp0.z, maxp1.z)}
end
-- Assume the largest possible portal shape unless we know it's a smaller one.
local shape_defintion = nether.PortalShape_Circular
if portal_name ~= nil and nether.registered_portals[portal_name] ~= nil then
shape_defintion = nether.registered_portals[portal_name].shape
end
local size = shape_defintion.schematic.size
local minp = shape_defintion.get_schematicPos_from_anchorPos(anchor_pos, orientation);
local maxp
if (orientation % 2) == 0 then
maxp = {x = minp.x + size.x - 1, y = minp.y + size.y - 1, z = minp.z + size.z - 1}
else
maxp = {x = minp.x + size.z - 1, y = minp.y + size.y - 1, z = minp.z + size.x - 1}
end
return minp, maxp
end
-- Can be used when implementing custom find_surface_anchorPos() functions -- Can be used when implementing custom find_surface_anchorPos() functions
-- portal_name is optional, providing it allows existing portals on the surface to be reused. -- portal_name is optional, providing it allows existing portals on the surface to be reused, and
function nether.find_surface_target_y(target_x, target_z, portal_name) -- a potentially smaller volume to be checked by volume_is_natural_and_unprotected().
-- player_name is optional, allowing a player to spawn a remote portal in their own protected areas.
function nether.find_surface_target_y(target_x, target_z, portal_name, player_name)
assert(target_x ~= nil and target_z ~= nil, "Arguments `target_x` and `target_z` cannot be nil when calling find_surface_target_y()") assert(target_x ~= nil and target_z ~= nil, "Arguments `target_x` and `target_z` cannot be nil when calling find_surface_target_y()")
@ -2251,18 +2317,22 @@ function nether.find_surface_target_y(target_x, target_z, portal_name)
end end
end end
local minp_schem, maxp_schem = nether.get_schematic_volume({x = target_x, y = 0, z = target_z}, nil, portal_name)
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, start_y - 256, -16 do for y = start_y, start_y - 256, -16 do
-- Check volume for non-natural nodes -- Check volume for non-natural nodes
local minp = {x = target_x - 1, y = y - 1, z = target_z - 2} minp.y = minp_schem.y + y
local maxp = {x = target_x + 2, y = y + 3, z = target_z + 2} maxp.y = maxp_schem.y + y
if nether.volume_is_natural(minp, maxp) then if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then
return y return y
elseif portal_name ~= nil and nether.registered_portals[portal_name] ~= nil then elseif portal_name ~= nil and nether.registered_portals[portal_name] ~= nil then
-- players have built here - don't grief. -- players have built here - don't grief.
-- but reigniting existing portals in portal rooms is fine - desirable even. -- but reigniting existing portals in portal rooms is fine - desirable even.
local anchorPos, orientation, is_ignited = is_within_portal_frame(nether.registered_portals[portal_name], {x = target_x, y = y, z = target_z}) local anchorPos, orientation, is_ignited = is_within_portal_frame(nether.registered_portals[portal_name], {x = target_x, y = y, z = target_z})
if anchorPos ~= nil then if anchorPos ~= nil then
if DEBUG then minetest.chat_send_all("Volume_is_natural check failed, but a portal frame is here " .. minetest.pos_to_string(anchorPos) .. ", so this is still a good target y level") end debugf("volume_is_natural_and_unprotected check failed, but a portal frame is here %s, so this is still a good target y level", anchorPos)
return y return y
end end
end end
@ -2291,7 +2361,7 @@ function nether.find_nearest_working_portal(portal_name, anchorPos, distance_lim
for _, dist in ipairs(dist_list) do for _, dist in ipairs(dist_list) do
local portal_info = contenders[dist] local portal_info = contenders[dist]
if DEBUG then minetest.chat_send_all("checking portal from mod_storage at " .. minetest.pos_to_string(portal_info.anchorPos) .. " orientation " .. portal_info.orientation) end debugf("checking portal from mod_storage at %s orientation %s", portal_info.anchorPos, portal_info.orientation)
-- the mod_storage list of portals is unreliable - e.g. it won't know if inactive portals have been -- the mod_storage list of portals is unreliable - e.g. it won't know if inactive portals have been
-- destroyed, so check the portal is still there -- destroyed, so check the portal is still there
@ -2300,7 +2370,7 @@ function nether.find_nearest_working_portal(portal_name, anchorPos, distance_lim
if portalFound then if portalFound then
return portal_info.anchorPos, portal_info.orientation return portal_info.anchorPos, portal_info.orientation
else else
if DEBUG then minetest.chat_send_all("Portal wasn't found, removing portal from mod_storage at " .. minetest.pos_to_string(portal_info.anchorPos) .. " orientation " .. portal_info.orientation) end debugf("Portal wasn't found, removing portal from mod_storage at %s orientation %s", portal_info.anchorPos, portal_info.orientation)
-- The portal at that location must have been destroyed -- The portal at that location must have been destroyed
remove_portal_location_info(portal_name, portal_info.anchorPos) remove_portal_location_info(portal_name, portal_info.anchorPos)
end end

View File

@ -49,17 +49,22 @@ surface.
Helper functions Helper functions
---------------- ----------------
* `nether.volume_is_natural(minp, maxp)`: returns a boolean * `nether.volume_is_natural_and_unprotected(minp, maxp, player_name)`: returns
a boolean.
* use this when determining where to spawn a portal, to avoid overwriting * use this when determining where to spawn a portal, to avoid overwriting
player builds. It checks the area for any nodes that aren't ground or player builds. It checks the area for any nodes that aren't ground or
trees. trees.
Water will fail this test, unless it is unemerged. Water will fail this test, unless it is unemerged.
* player_name is optional, providing it allows the player's own protected
areas to be treated as unprotected.
* `nether.find_surface_target_y(target_x, target_z, portal_name)`: returns a * `nether.find_surface_target_y(target_x, target_z, portal_name, player_name)`:
suitable anchorPos returns a suitable anchorPos
* Can be used when implementing custom find_surface_anchorPos() functions * Can be used when implementing custom find_surface_anchorPos() functions
* portal_name is optional, providing it allows existing portals on the * portal_name is optional, providing it allows existing portals on the
surface to be reused. surface to be reused.
* player_name is optional, providing it prevents the exclusion of surface
target areas which are protected by the player.
* `nether.find_nearest_working_portal(portal_name, anchorPos, distance_limit, y_factor)`: returns * `nether.find_nearest_working_portal(portal_name, anchorPos, distance_limit, y_factor)`: returns
(anchorPos, orientation), or nil if no portal was found within the (anchorPos, orientation), or nil if no portal was found within the
@ -208,7 +213,7 @@ Used by `nether.register_portal`.
-- Ideally implementations are fast, as this function can be used to -- Ideally implementations are fast, as this function can be used to
-- sift through a list of portals. -- sift through a list of portals.
find_realm_anchorPos = function(surface_anchorPos), find_realm_anchorPos = function(surface_anchorPos, player_name),
-- Required. Return a position in the realm that a portal created at -- Required. Return a position in the realm that a portal created at
-- surface_anchorPos will link to. -- surface_anchorPos will link to.
-- Return an anchorPos or (anchorPos, orientation) -- Return an anchorPos or (anchorPos, orientation)
@ -218,8 +223,10 @@ Used by `nether.register_portal`.
-- orientation, otherwise the existing portal could be overwritten by -- orientation, otherwise the existing portal could be overwritten by
-- a new one with the orientation of the surface portal. -- a new one with the orientation of the surface portal.
-- Return nil to prevent the portal from igniting. -- Return nil to prevent the portal from igniting.
-- 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.
find_surface_anchorPos = function(realm_anchorPos), find_surface_anchorPos = function(realm_anchorPos, player_name),
-- Optional. If you don't implement this then a position near the -- Optional. If you don't implement this then a position near the
-- surface will be picked. -- surface will be picked.
-- Return an anchorPos or (anchorPos, orientation) -- Return an anchorPos or (anchorPos, orientation)
@ -233,6 +240,8 @@ Used by `nether.register_portal`.
-- orientation, otherwise the existing portal could be overwritten by -- orientation, otherwise the existing portal could be overwritten by
-- a new one with the orientation of the realm portal. -- a new one with the orientation of the realm portal.
-- Return nil to prevent the portal from igniting. -- Return nil to prevent the portal from igniting.
-- 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, anochorPos, orientation),
-- invoked once per second per portal -- invoked once per second per portal

View File

@ -85,7 +85,7 @@ This portal is different to the others, rather than acting akin to a doorway it
return pos.y > FLOATLAND_LEVEL - 200 return pos.y > FLOATLAND_LEVEL - 200
end, end,
find_realm_anchorPos = function(surface_anchorPos) find_realm_anchorPos = function(surface_anchorPos, player_name)
-- TODO: Once paramat finishes adjusting the floatlands, implement a surface algorithm that finds land -- TODO: Once paramat finishes adjusting the floatlands, implement a surface algorithm that finds land
local destination_pos = {x = surface_anchorPos.x ,y = FLOATLAND_LEVEL + 2, z = surface_anchorPos.z} local destination_pos = {x = surface_anchorPos.x ,y = FLOATLAND_LEVEL + 2, z = surface_anchorPos.z}
@ -131,13 +131,13 @@ Due to such difficulties, we never learned what determines the direction and dis
return true return true
end, end,
find_realm_anchorPos = function(surface_anchorPos) find_realm_anchorPos = function(surface_anchorPos, player_name)
-- This function isn't needed, since this type of portal always goes to the surface -- This function isn't needed, since this type of portal always goes to the surface
minetest.log("error" , "find_realm_anchorPos called for surface portal") minetest.log("error" , "find_realm_anchorPos called for surface portal")
return {x=0, y=0, z=0} return {x=0, y=0, z=0}
end, end,
find_surface_anchorPos = function(realm_anchorPos) find_surface_anchorPos = function(realm_anchorPos, player_name)
-- A portal definition doesn't normally need to provide a find_surface_anchorPos() function, -- A portal definition doesn't normally need to provide a find_surface_anchorPos() function,
-- since find_surface_target_y() will be used by default, but these portals travel around the -- since find_surface_target_y() will be used by default, but these portals travel around the
-- surface (following a Moore curve) so will be calculating a different x and z to realm_anchorPos. -- surface (following a Moore curve) so will be calculating a different x and z to realm_anchorPos.
@ -192,7 +192,7 @@ Due to such difficulties, we never learned what determines the direction and dis
end end
local destination_pos = {x = target_x + adj_x, y = 0, z = target_z + adj_z} local destination_pos = {x = target_x + adj_x, y = 0, z = target_z + adj_z}
destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "surface_portal") destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "surface_portal", player_name)
return destination_pos return destination_pos
end end