diff --git a/init.lua b/init.lua index cbc684b..7497840 100644 --- a/init.lua +++ b/init.lua @@ -58,7 +58,7 @@ nether.NETHER_REALM_ENABLED = minetest.settings:get_bool("nether_realm_ena nether.DEPTH_CEILING = tonumber(minetest.settings:get("nether_depth_ymax") or nether.DEPTH_CEILING) nether.DEPTH_FLOOR = tonumber(minetest.settings:get("nether_depth_ymin") or nether.DEPTH_FLOOR) -if nether.DEPTH_FLOOR + 1000 > nether.DEPTH_CEILING then +if nether.DEPTH_FLOOR + 1000 > nether.DEPTH_CEILING then error("The lower limit of the Nether must be set at least 1000 lower than the upper limit, and more than 3000 is recommended. Set settingtypes.txt, or 'All Settings' -> 'Mods' -> 'nether' -> 'Nether depth'", 0) end nether.DEPTH = nether.DEPTH_CEILING -- Deprecated, use nether.DEPTH_CEILING instead. @@ -101,7 +101,7 @@ The expedition parties have found no diamonds or gold, and after an experienced return pos.y < nether.DEPTH_CEILING 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 local destination_pos = vector.divide(surface_anchorPos, nether.FASTTRAVEL_FACTOR) destination_pos.x = math.floor(0.5 + destination_pos.x) -- round to int @@ -116,12 +116,12 @@ The expedition parties have found no diamonds or gold, and after an experienced return existing_portal_location, existing_portal_orientation else 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 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, -- since find_surface_target_y() will be used by default, but Nether portals also scale position -- to create fast-travel. @@ -140,7 +140,7 @@ The expedition parties have found no diamonds or gold, and after an experienced if existing_portal_location ~= nil then return existing_portal_location, existing_portal_orientation 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 end end, diff --git a/mapgen.lua b/mapgen.lua index 982e75e..547c8cc 100644 --- a/mapgen.lua +++ b/mapgen.lua @@ -465,7 +465,8 @@ end -- 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 air = 0 -- Consecutive air nodes found @@ -479,10 +480,10 @@ function nether.find_nether_ground_y(target_x, target_z, start_y) -- Check volume for non-natural nodes local minp = {x = target_x - 1, y = y , z = target_z - 2} local maxp = {x = target_x + 2, y = y + 4, z = target_z + 2} - if nether.volume_is_natural(minp, maxp) then + if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then return y + 1 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 else -- Not enough space, reset air to zero air = 0 diff --git a/mapgen_nobiomes.lua b/mapgen_nobiomes.lua index 2d1093c..7eb53fb 100644 --- a/mapgen_nobiomes.lua +++ b/mapgen_nobiomes.lua @@ -201,11 +201,12 @@ end) -- 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 air = 0 -- Consecutive air nodes found - for y = start_y, 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}) if nval_cave > TCAVE then -- Cavern @@ -215,10 +216,10 @@ function nether.find_nether_ground_y(target_x, target_z, start_y) -- Check volume for non-natural nodes local minp = {x = target_x - 1, y = y , z = target_z - 2} local maxp = {x = target_x + 2, y = y + 4, z = target_z + 2} - if nether.volume_is_natural(minp, maxp) then + if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then return y + 1 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 else -- Not enough space, reset air to zero air = 0 @@ -226,5 +227,5 @@ function nether.find_nether_ground_y(target_x, target_z, start_y) end end - return start_y -- Fallback -end \ No newline at end of file + return math.max(start_y, NETHER_FLOOR + BLEND) -- Fallback +end diff --git a/portal_api.lua b/portal_api.lua index 9b5df54..ca3b378 100644 --- a/portal_api.lua +++ b/portal_api.lua @@ -1303,8 +1303,9 @@ end -- 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 -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 DEBUG then minetest.chat_send_all("IGNITE the " .. ignition_node_name .. " at " .. minetest.pos_to_string(ignition_pos)) end @@ -1354,9 +1355,9 @@ local function ignite_portal(ignition_pos, ignition_node_name) local destination_anchorPos, destination_orientation 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 - 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 DEBUG and destination_orientation == nil then minetest.chat_send_all("No destination_orientation given") end if destination_orientation == nil then destination_orientation = orientation end @@ -1825,7 +1826,7 @@ function register_frame_node(frame_node_name) extended_node_def.mesecons = {effector = { action_on = function (pos, node) if DEBUG then minetest.chat_send_all("portal frame material: mesecons action ON") end - ignite_portal(pos, node.name) + ignite_portal(pos, nil, node.name) end, action_off = function (pos, node) if DEBUG then minetest.chat_send_all("portal frame material: mesecons action OFF") end @@ -2081,7 +2082,7 @@ function nether.register_portal(name, portaldef) end 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 existing_portal_location, existing_portal_orientation = @@ -2089,7 +2090,7 @@ function nether.register_portal(name, portaldef) if existing_portal_location ~= nil then return existing_portal_location, existing_portal_orientation 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 end end @@ -2153,10 +2154,10 @@ end function nether.register_portal_ignition_item(item_name, ignition_failure_sound) minetest.override_item(item_name, { - on_place = function(stack, _, pt) + on_place = function(stack, placer, pt) local done = false if pt.under and nether.is_frame_node[minetest.get_node(pt.under).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 stack:take_item() end @@ -2175,22 +2176,22 @@ end -- 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. +-- 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) -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_ignore = minetest.get_content_id("ignore") local vm = minetest.get_voxel_manip() - local pos1 = {x = minp.x, y = minp.y, z = minp.z} - local pos2 = {x = maxp.x, y = maxp.y, z = maxp.z} - local emin, emax = vm:read_from_map(pos1, pos2) + local emin, emax = vm:read_from_map(minp, maxp) local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax}) local data = vm:get_data() - for z = pos1.z, pos2.z do - for y = pos1.y, pos2.y do - local vi = area:index(pos1.x, y, z) - for x = pos1.x, pos2.x do + for z = minp.z, maxp.z do + for y = minp.y, maxp.y do + local vi = area:index(minp.x, y, z) + for x = minp.x, maxp.x do local id = data[vi] -- Existing node if DEBUG and id == nil then minetest.chat_send_all("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 @@ -2200,7 +2201,7 @@ function nether.volume_is_natural(minp, maxp) -- trees are natural but not "ground content" 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 DEBUG then minetest.chat_send_all("volume_is_natural() found unnatural node " .. name) end + if DEBUG then minetest.chat_send_all("volume_is_natural_and_unprotected() found unnatural node " .. name) end return false end end @@ -2210,13 +2211,25 @@ function nether.volume_is_natural(minp, maxp) end end - if DEBUG then minetest.chat_send_all("Volume is natural") end + if minetest.is_area_protected(minp, maxp, player_name or "") then + if DEBUG then minetest.chat_send_all("Volume is protected " .. minetest.pos_to_string(minp) .. "-" .. minetest.pos_to_string(maxp)) end + return false; + end + + if DEBUG then minetest.chat_send_all("Volume is natural and unprotected for player '" .. (player_name or "") .. "', " .. minetest.pos_to_string(minp) .. "-" .. minetest.pos_to_string(maxp)) end return true end +-- Deprecated, use nether.volume_is_natural_and_unprotected() instead. +function nether.volume_is_natural(minp, maxp) + return nether.volume_is_natural_and_unprotected(minp, maxp) +end + + -- 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. -function nether.find_surface_target_y(target_x, target_z, portal_name) +-- 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()") @@ -2250,14 +2263,14 @@ function nether.find_surface_target_y(target_x, target_z, portal_name) -- Check volume for non-natural nodes 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} - if nether.volume_is_natural(minp, maxp) then + if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then return y elseif portal_name ~= nil and nether.registered_portals[portal_name] ~= nil then -- players have built here - don't grief. -- 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}) 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 + if DEBUG then minetest.chat_send_all("volume_is_natural_and_unprotected check failed, but a portal frame is here " .. minetest.pos_to_string(anchorPos) .. ", so this is still a good target y level") end return y end end diff --git a/portal_api.txt b/portal_api.txt index b07e2cb..ae55a19 100644 --- a/portal_api.txt +++ b/portal_api.txt @@ -49,17 +49,22 @@ surface. 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 player builds. It checks the area for any nodes that aren't ground or trees. Water will fail this test, unless it is unemerged. + * player_name is optional, providing it allows the player's own protected + areas to be recognized as unprotected. -* `nether.find_surface_target_y(target_x, target_z, portal_name)`: returns a - suitable anchorPos +* `nether.find_surface_target_y(target_x, target_z, portal_name, player_name)`: + returns a suitable anchorPos * 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. + * 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 (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 -- 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 -- surface_anchorPos will link to. -- Return an anchorPos or (anchorPos, orientation) @@ -218,8 +223,10 @@ Used by `nether.register_portal`. -- orientation, otherwise the existing portal could be overwritten by -- a new one with the orientation of the surface portal. -- 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 -- surface will be picked. -- Return an anchorPos or (anchorPos, orientation) @@ -233,6 +240,8 @@ Used by `nether.register_portal`. -- orientation, otherwise the existing portal could be overwritten by -- a new one with the orientation of the realm portal. -- 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), -- invoked once per second per portal diff --git a/portal_examples.lua b/portal_examples.lua index 1a33943..ac71848 100644 --- a/portal_examples.lua +++ b/portal_examples.lua @@ -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 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 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 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 minetest.log("error" , "find_realm_anchorPos called for surface portal") return {x=0, y=0, z=0} 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, -- 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. @@ -192,7 +192,7 @@ Due to such difficulties, we never learned what determines the direction and dis end 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 end