Allow reignition of portals in player-built areas

e.g. allow remote ignition to a portal in a "portal room"
This commit is contained in:
Treer 2019-06-30 13:55:38 +10:00 committed by SmallJoker
parent 7a4d9ebf01
commit edf961907e
1 changed files with 41 additions and 32 deletions

View File

@ -25,7 +25,7 @@
local NETHER_DEPTH = -5000 local NETHER_DEPTH = -5000
local TCAVE = 0.6 local TCAVE = 0.6
local BLEND = 128 local BLEND = 128
local DEBUG = true local DEBUG = false
-- 3D noise -- 3D noise
@ -77,14 +77,15 @@ netherportal = {} -- portal API
A better location for AnchorPos would be directly under WormholePos, as it's more centered A better location for AnchorPos would be directly under WormholePos, as it's more centered
and you don't need to know the portal's orientation to find AnchorPos from the WormholePos and you don't need to know the portal's orientation to find AnchorPos from the WormholePos
or vice-versa, however AnchorPos is in the bottom/south/west-corner to keep compatibility or vice-versa, however AnchorPos is in the bottom/south/west-corner to keep compatibility
with earlier versions of this mod (which only records portal corners p1 & p2 in the node metadata). with earlier versions of nether mod (which only records portal corners p1 & p2 in the node
metadata).
Orientation is 0 or 90, 0 meaning a portal that faces north/south - i.e. obsidian running Orientation is 0 or 90, 0 meaning a portal that faces north/south - i.e. obsidian running
east/west. east/west.
]] ]]
-- This object defines a portal's shape, segregating the shape logic code from portal physics. -- This object defines a portal's shape, segregating the shape logic code from portal behaviour code.
-- You can create a new "PortalShape" definition object which implements the same -- You can create a new "PortalShape" definition object which implements the same
-- functions if you wish to register a custom shaped portal in register_portal(). -- functions if you wish to register a custom shaped portal in register_portal().
-- Since it's symmetric, this PortalShape definition has only implemented orientations of 0 and 90 -- Since it's symmetric, this PortalShape definition has only implemented orientations of 0 and 90
@ -140,15 +141,15 @@ local TraditionalPortalShape = {
end, end,
get_anchorPos_and_orientation_from_p1_and_p2 = function(p1, p2) get_anchorPos_and_orientation_from_p1_and_p2 = function(p1, p2)
if p1.z == p2.z then if p1.z == p2.z then
return p1, 0 return p1, 0
elseif p1.x == p2.x then elseif p1.x == p2.x then
return p1, 90 return p1, 90
else else
-- this KISS implementation will break you've made a 3D PortalShape definition, and will need to be reimplemented -- this KISS implementation will break you've made a 3D PortalShape definition, and will need to be reimplemented
minetest.log("error", "get_anchorPos_and_orientation_from_p1_and_p2 failed on p1=" .. meta:get_string("p1") .. " p2=" .. meta:get_string("p2")) minetest.log("error", "get_anchorPos_and_orientation_from_p1_and_p2 failed on p1=" .. meta:get_string("p1") .. " p2=" .. meta:get_string("p2"))
end end
end, end,
apply_func_to_frame_nodes = function(anchorPos, orientation, func) apply_func_to_frame_nodes = function(anchorPos, orientation, func)
-- a 4x5 portal is small enough that hardcoded positions is simpler that procedural code -- a 4x5 portal is small enough that hardcoded positions is simpler that procedural code
@ -218,7 +219,7 @@ local TraditionalPortalShape = {
end, end,
-- Check for whether the portal is blocked in, and if so then provide a safe way -- Check for whether the portal is blocked in, and if so then provide a safe way
-- on one side for the player to step out of the portal. Suggest including a roof -- on one side for the player to step out of the portal. Suggest including a roof
-- incase the portal was blocked with lava flowing from above. -- incase the portal was blocked with lava flowing from above.
disable_portal_trap = function(anchorPos, orientation) disable_portal_trap = function(anchorPos, orientation)
assert(orientation, "no orientation passed") assert(orientation, "no orientation passed")
@ -226,7 +227,7 @@ local TraditionalPortalShape = {
-- Not implemented yet. It may not need to be implemented because if you -- Not implemented yet. It may not need to be implemented because if you
-- wait in a portal long enough you teleport again. So a trap portal would have to link -- wait in a portal long enough you teleport again. So a trap portal would have to link
-- to one of two blocked-in portals which link to each other - which is possible, but -- to one of two blocked-in portals which link to each other - which is possible, but
-- quite extreme. -- quite extreme.
end end
} }
@ -246,18 +247,18 @@ local registered_portals = {
local function get_timerPos_from_p1_and_p2(p1, p2) local function get_timerPos_from_p1_and_p2(p1, p2)
-- Pick a frame node for the portal's timer. -- Pick a frame node for the portal's timer.
-- --
-- The timer event will need to know the portal definition, which can be determined by -- The timer event will need to know the portal definition, which can be determined by
-- what the portal frame is made from, so the timer node should be on the frame. -- what the portal frame is made from, so the timer node should be on the frame.
-- The timer event will also need to know its portal orientation, but unless someone -- The timer event will also need to know its portal orientation, but unless someone
-- makes a cubic portal shape, orientation can be determined from p1 and p2 in the node's -- makes a cubic portal shape, orientation can be determined from p1 and p2 in the node's
-- metadata (frame nodes don't have orientation set in param2 like wormhole nodes do). -- metadata (frame nodes don't have orientation set in param2 like wormhole nodes do).
-- --
-- We shouldn't pick p1 (or p2) as it's possible for two orthogonal portals to share -- We shouldn't pick p1 (or p2) as it's possible for two orthogonal portals to share
-- the same p1, etc. -- the same p1, etc.
-- --
-- I'll pick the bottom center node of the portal, since that works for rectangular portals -- I'll pick the bottom center node of the portal, since that works for rectangular portals
-- and if someone want to make a circular portal then that positon will still likely be part -- and if someone want to make a circular portal then that positon will still likely be part
-- of the frame. -- of the frame.
return { return {
@ -291,11 +292,12 @@ local function set_portal_metadata(portal_definition, anchorPos, orientation, de
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
meta:set_string("p1", minetest.pos_to_string(p1)) meta:set_string("p1", minetest.pos_to_string(p1))
meta:set_string("p2", minetest.pos_to_string(p2)) meta:set_string("p2", minetest.pos_to_string(p2))
meta:set_string("target", minetest.pos_to_string(destination_wormholePos)) meta:set_string("target", minetest.pos_to_string(destination_wormholePos))
-- including "frame_node_name" in the metadata lets us know which kind of portal this is. -- including "frame_node_name" in the metadata lets us know which kind of portal this is.
-- It's not strictly necessary for TraditionalPortalShape as we know that p1 is part of -- It's not strictly necessary for TraditionalPortalShape as we know that p1 is part of
-- the frame, and legacy portals don't have this extra metadata, but p1 isn't always loaded -- the frame, and legacy portals don't have this extra metadata - indicating obsidian,
-- and reading this from the metadata saves an extra call to minetest.getnode(). -- but p1 isn't always loaded so reading this from the metadata saves an extra call to
-- minetest.getnode().
meta:set_string("frame_node_name", portal_definition.frame_node_name) meta:set_string("frame_node_name", portal_definition.frame_node_name)
end end
@ -515,13 +517,20 @@ Also, the search for non-natural nodes doesn't actually guarantee avoiding playe
Each placement position search has to search a volume of nodes for non-natural nodes, this is not lightweight, and many searches may happen if there a lot of underground player builds present. So the code has been written to avoid intensive procedures. Each placement position search has to search a volume of nodes for non-natural nodes, this is not lightweight, and many searches may happen if there a lot of underground player builds present. So the code has been written to avoid intensive procedures.
https://github.com/minetest-mods/nether/issues/5#issuecomment-506983676 https://github.com/minetest-mods/nether/issues/5#issuecomment-506983676
]] ]]
local function find_surface_target_y(target_x, target_z, start_y) local function find_surface_target_y(portal_definition, target_x, target_z, start_y)
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} local minp = {x = target_x - 1, y = y - 1, z = target_z - 2}
local maxp = {x = target_x + 2, y = y + 3, z = target_z + 2} local maxp = {x = target_x + 2, y = y + 3, z = target_z + 2}
if volume_is_natural(minp, maxp) then if volume_is_natural(minp, maxp) then
return y return y
else
-- players have built here - don't grief.
-- but reigniting existing portals in portal rooms is fine - desirable even.
local anchorPos, orientation, is_ignited = is_portal_frame(portal_definition, {x = target_x, y = y, z = target_z})
if anchorPos ~= nil then
return y
end
end end
end end
@ -557,7 +566,7 @@ local function ignite_portal(ignition_pos)
-- pick a destination -- pick a destination
local destination_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(anchorPos, orientation) local destination_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(anchorPos, orientation)
if anchorPos.y < NETHER_DEPTH then if anchorPos.y < NETHER_DEPTH then
destination_wormholePos.y = find_surface_target_y(destination_wormholePos.x, destination_wormholePos.z, -16) destination_wormholePos.y = find_surface_target_y(portal_definition, destination_wormholePos.x, destination_wormholePos.z, -16)
else else
local start_y = NETHER_DEPTH - math.random(500, 1500) -- Search start local start_y = NETHER_DEPTH - math.random(500, 1500) -- Search start
destination_wormholePos.y = find_nether_target_y(destination_wormholePos.x, destination_wormholePos.z, start_y) destination_wormholePos.y = find_nether_target_y(destination_wormholePos.x, destination_wormholePos.z, start_y)
@ -751,7 +760,7 @@ function run_wormhole(pos, time_elapsed)
local p1, p2, frame_node_name local p1, p2, frame_node_name
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
if meta ~= nil then if meta ~= nil then
p1 = minetest.string_to_pos(meta:get_string("p1")) p1 = minetest.string_to_pos(meta:get_string("p1"))
p2 = minetest.string_to_pos(meta:get_string("p2")) p2 = minetest.string_to_pos(meta:get_string("p2"))
--frame_node_name = minetest.string_to_pos(meta:get_string("frame_node_name")) don't rely on this yet until you're sure everything works with old portals that don't have this set --frame_node_name = minetest.string_to_pos(meta:get_string("frame_node_name")) don't rely on this yet until you're sure everything works with old portals that don't have this set
@ -761,11 +770,11 @@ function run_wormhole(pos, time_elapsed)
if frame_node_name == nil then frame_node_name = minetest.get_node(pos).name end -- pos should be a frame node if frame_node_name == nil then frame_node_name = minetest.get_node(pos).name end -- pos should be a frame node
local portal_definition = registered_portals[frame_node_name] local portal_definition = registered_portals[frame_node_name]
if portal_definition == nil then if portal_definition == nil then
minetest.log("error", "No portal with a \"" .. frame_node_name .. "\" frame is registered. run_wormhole" .. minetest.pos_to_string(pos) .. " was invoked but that location contains \"" .. frame_node_name .. "\"") minetest.log("error", "No portal with a \"" .. frame_node_name .. "\" frame is registered. run_wormhole" .. minetest.pos_to_string(pos) .. " was invoked but that location contains \"" .. frame_node_name .. "\"")
else else
local anchorPos, orientation = portal_definition.shape.get_anchorPos_and_orientation_from_p1_and_p2(p1, p2) local anchorPos, orientation = portal_definition.shape.get_anchorPos_and_orientation_from_p1_and_p2(p1, p2)
portal_definition.shape.apply_func_to_wormhole_nodes(anchorPos, orientation, run_wormhole_node_func) portal_definition.shape.apply_func_to_wormhole_nodes(anchorPos, orientation, run_wormhole_node_func)
end end
end end
end end
@ -776,11 +785,11 @@ minetest.register_lbm({
nodenames = {"nether:portal"}, nodenames = {"nether:portal"},
run_at_every_load = false, run_at_every_load = false,
action = function(pos, node) action = function(pos, node)
local p1 local p1, p2
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
if meta ~= nil then if meta ~= nil then
p1 = minetest.string_to_pos(meta:get_string("p1")) p1 = minetest.string_to_pos(meta:get_string("p1"))
p2 = minetest.string_to_pos(meta:get_string("p1")) p2 = minetest.string_to_pos(meta:get_string("p1"))
end end
if p1 ~= nil and p2 ~= nil then if p1 ~= nil and p2 ~= nil then
local timerPos = get_timerPos_from_p1_and_p2(p1, p2) local timerPos = get_timerPos_from_p1_and_p2(p1, p2)
@ -853,14 +862,14 @@ minetest.register_node(":default:obsidian", {
sounds = default.node_sound_stone_defaults(), sounds = default.node_sound_stone_defaults(),
groups = {cracky = 1, level = 2}, groups = {cracky = 1, level = 2},
mesecons = {effector = { mesecons = {effector = {
action_on = function (pos, node) action_on = function (pos, node)
ignite_portal(pos, node.name) ignite_portal(pos, node.name)
end, end,
action_off = function (pos, node) action_off = function (pos, node)
extinguish_portal(pos, node.name) extinguish_portal(pos, node.name)
end end
}}, }},
on_destruct = function(pos) on_destruct = function(pos)
extinguish_portal(pos, "default:obsidian") extinguish_portal(pos, "default:obsidian")
end, end,