diff --git a/init.lua b/init.lua index f8b2b8f..06f1124 100644 --- a/init.lua +++ b/init.lua @@ -31,13 +31,16 @@ nether.get_translator = S nether.DEPTH = -5000 nether.FASTTRAVEL_FACTOR = 8 -- 10 could be better value for Minetest, since there's no sprint, but ex-Minecraft players will be mathing for 8 nether.PORTAL_BOOK_LOOT_WEIGHTING = 0.9 -- Likelyhood of finding the Book of Portals (guide) in dungeon chests. Set to 0 to disable. - +nether.ENABLE_EXAMPLE_PORTALS = false -- Enables extra portal types - examples of how to create your own portal types using the Nether's portal API. Once enabled, their shapes will be shown in the book of portals. -- Load files dofile(nether.path .. "/portal_api.lua") dofile(nether.path .. "/nodes.lua") dofile(nether.path .. "/mapgen.lua") +if nether.ENABLE_EXAMPLE_PORTALS then + dofile(nether.path .. "/portal_examples.lua") +end -- Portals are ignited by right-clicking with a mese crystal fragment nether.register_portal_ignition_item( diff --git a/portal_api.lua b/portal_api.lua index 6ed7b20..2b5980f 100644 --- a/portal_api.lua +++ b/portal_api.lua @@ -25,25 +25,29 @@ Positions p1 & p2 p1 and p2 is the system used by earlier versions of the nether mod, which the portal_api is forwards and backwards compatible with. - p1 is the bottom/west/south corner of the portal, and p2 is the opposite corner, together - they define the bounding volume for the portal. - The value of p1 and p2 is kept in the metadata of every node in the portal + p1 is the bottom/west/south corner of the portal, and p2 is the opposite corner, together + they define the bounding volume for the portal. + The value of p1 and p2 is kept in the metadata of every node in the portal -WormholePos The location of the node a portal's target is set to, and a player is teleported +WormholePos The location of the node that a portal's target is set to, and a player is teleported to. It can also be used to test whether a portal is active. -AnchorPos Introduced by the portal_api, this should - Usually an orientation is required with an AnchorPos - +AnchorPos Introduced by the portal_api. Coordinates for portals are normally given in terms of + the AnchorPos. The AnchorPos does not change with portal orientation - portals rotate + around the AnchorPos. Ideally an AnchorPos would be near the bottom center of a portal + shape, but this is not the case with PortalShape_Traditional to keep comptaibility with + earlier versions of the nether mod. + Usually an orientation is required with an AnchorPos. + TimerPos The portal_api replaces ABMs with a single node timer per portal, and the TimerPos is the - node in which that timer is located. Extra metadata is also kept in the TimerNode. + node in which that timer is located. Extra metadata is also kept in the TimerPos node. Portal shapes ============= - For the PortalShape_Traditional implementation, anchorPos, wormholdPos and TimerPos are defined + For the PortalShape_Traditional implementation, p1, p2, anchorPos, wormholdPos and TimerPos are defined as follows: . +--------+--------+--------+--------+ @@ -59,8 +63,8 @@ Portal shapes | |Wormhole | | | | Pos | | +--------+--------+--------+--------+ - AnchorPos| Node | | | - | p1 | Timer | | | + AnchorPos|TimerPos| | | + | p1 | | | | +--------+--------+--------+--------+ +X/East or +Z/North -----> @@ -71,15 +75,15 @@ or vice-versa, however AnchorPos is in the bottom/south/west-corner to keep comp 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 -east/west. +Orientation is yaw, either 0 or 90, 0 meaning a portal that faces north/south - i.e. obsidian +running east/west. ]] -- 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 --- functions if you wish to register a custom shaped portal in register_portal(). Examples follow --- after PortalShape_Traditional. +-- functions if you wish to register a custom shaped portal in register_portal(). Examples of other +-- shapes follow after PortalShape_Traditional. -- Since it's symmetric, this PortalShape definition has only implemented orientations of 0 and 90 nether.PortalShape_Traditional = { name = "Traditional", @@ -355,6 +359,92 @@ nether.PortalShape_Circular = { } -- End of PortalShape_Circular class +-- Example alternative PortalShape +-- This platform shape is symmetrical around the y-axis, so the orientation value never matters. +nether.PortalShape_Platform = { + name = "Platform", + size = vector.new(5, 2, 5), -- size of the portal, and not necessarily the size of the schematic, + -- which may clear area around the portal. + schematic_filename = nether.path .. "/schematics/nether_portal_platform.mts", + + -- returns the coords for minetest.place_schematic() that will place the schematic on the anchorPos + get_schematicPos_from_anchorPos = function(anchorPos, orientation) + return {x = anchorPos.x - 2, y = anchorPos.y, z = anchorPos.z - 2} + end, + + get_wormholePos_from_anchorPos = function(anchorPos, orientation) + -- wormholePos is the node above anchorPos + return {x = anchorPos.x, y = anchorPos.y + 1, z = anchorPos.z} + end, + + get_anchorPos_from_wormholePos = function(wormholePos, orientation) + -- wormholePos is the node above anchorPos + return {x = wormholePos.x, y = wormholePos.y - 1, z = wormholePos.z} + end, + + -- p1 and p2 are used 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 + -- they define the bounding volume for the portal. + get_p1_and_p2_from_anchorPos = function(self, anchorPos, orientation) + assert(self ~= nil and self.name == nether.PortalShape_Platform.name, "Must pass self as first argument, or use shape:func() instead of shape.func()") + local p1 = {x = anchorPos.x - 2, y = anchorPos.y, z = anchorPos.z - 2} + local p2 = {x = anchorPos.x + 2, y = anchorPos.y + 1, z = anchorPos.z + 2} + return p1, p2 + end, + + get_anchorPos_and_orientation_from_p1_and_p2 = function(p1, p2) + return {x= p1.x + 2, y = p1.y, z = p1.z + 2}, 0 + end, + + apply_func_to_frame_nodes = function(anchorPos, orientation, func) + local shortCircuited + local yPlus1 = anchorPos.y + 1 + -- use short-circuiting of boolean evaluation to allow func() to cause an abort by returning true + shortCircuited = + func({x = anchorPos.x - 2, y = yPlus1, z = anchorPos.z - 1}) or func({x = anchorPos.x + 2, y = yPlus1, z = anchorPos.z - 1}) or + func({x = anchorPos.x - 2, y = yPlus1, z = anchorPos.z }) or func({x = anchorPos.x + 2, y = yPlus1, z = anchorPos.z }) or + func({x = anchorPos.x - 2, y = yPlus1, z = anchorPos.z + 1}) or func({x = anchorPos.x + 2, y = yPlus1, z = anchorPos.z + 1}) or + + func({x = anchorPos.x - 1, y = yPlus1, z = anchorPos.z - 2}) or func({x = anchorPos.x - 1, y = yPlus1, z = anchorPos.z + 2}) or + func({x = anchorPos.x , y = yPlus1, z = anchorPos.z - 2}) or func({x = anchorPos.x , y = yPlus1, z = anchorPos.z + 2}) or + func({x = anchorPos.x + 1, y = yPlus1, z = anchorPos.z - 2}) or func({x = anchorPos.x + 1, y = yPlus1, z = anchorPos.z + 2}) or + + func({x = anchorPos.x - 1, y = anchorPos.y, z = anchorPos.z - 1}) or + func({x = anchorPos.x - 1, y = anchorPos.y, z = anchorPos.z }) or + func({x = anchorPos.x - 1, y = anchorPos.y, z = anchorPos.z + 1}) or + func({x = anchorPos.x , y = anchorPos.y, z = anchorPos.z - 1}) or + func({x = anchorPos.x , y = anchorPos.y, z = anchorPos.z }) or + func({x = anchorPos.x , y = anchorPos.y, z = anchorPos.z + 1}) or + func({x = anchorPos.x + 1, y = anchorPos.y, z = anchorPos.z - 1}) or + func({x = anchorPos.x + 1, y = anchorPos.y, z = anchorPos.z }) or + func({x = anchorPos.x + 1, y = anchorPos.y, z = anchorPos.z + 1}) + return not shortCircuited + end, + + -- returns true if function was applied to all wormhole nodes + apply_func_to_wormhole_nodes = function(anchorPos, orientation, func) + local shortCircuited + local yPlus1 = anchorPos.y + 1 + -- use short-circuiting of boolean evaluation to allow func() to cause an abort by returning true + shortCircuited = + func({x = anchorPos.x - 1, y = yPlus1, z = anchorPos.z - 1}) or + func({x = anchorPos.x - 1, y = yPlus1, z = anchorPos.z }) or + func({x = anchorPos.x - 1, y = yPlus1, z = anchorPos.z + 1}) or + func({x = anchorPos.x , y = yPlus1, z = anchorPos.z - 1}) or + func({x = anchorPos.x , y = yPlus1, z = anchorPos.z }) or + func({x = anchorPos.x , y = yPlus1, z = anchorPos.z + 1}) or + func({x = anchorPos.x + 1, y = yPlus1, z = anchorPos.z - 1}) or + func({x = anchorPos.x + 1, y = yPlus1, z = anchorPos.z }) or + func({x = anchorPos.x + 1, y = yPlus1, z = anchorPos.z + 1}) + return not shortCircuited + end, + + -- Check for suffocation + disable_portal_trap = function(anchorPos, orientation) + + -- Not implemented. + end +} -- End of PortalShape_Platform class --====================================================-- @@ -397,27 +487,61 @@ local function get_timerPos_from_p1_and_p2(p1, p2) } end --- orientation is the rotation degrees passed to place_schematic: 0, 90, 180, or 270 +-- orientation is the yaw rotation degrees passed to place_schematic: 0, 90, 180, or 270 -- color is a value from 0 to 7 corresponding to the color of pixels in nether_portals_palette.png -local function get_param2_from_color_and_orientation(color, orientation) +-- portal_is_horizontal is a bool indicating whether the portal lies flat or stands vertically +local function get_colorfacedir_from_color_and_orientation(color, orientation, portal_is_horizontal) assert(orientation, "no orientation passed") + local axis_direction, rotation + local dir = math.floor((orientation % 360) / 90 + 0.5) + + -- if the portal is vertical then node axis direction will be +Y (up) and portal orientation + -- will set the node's rotation. + -- if the portal is horizontal then the node axis direction reflects the yaw orientation and + -- the node's rotation will be whatever's needed to keep the texture horizontal (either 0 or 1) + if portal_is_horizontal then + if dir == 0 then axis_direction = 1 end -- North + if dir == 1 then axis_direction = 3 end -- East + if dir == 2 then axis_direction = 2 end -- South + if dir == 3 then axis_direction = 4 end -- West + rotation = math.floor(axis_direction / 2); -- a rotation is only needed if axis_direction is east or west + else + axis_direction = 0 -- 0 is up, or +Y + rotation = dir + end + -- wormhole nodes have a paramtype2 of colorfacedir, which means the -- high 3 bits are palette, followed by 3 direction bits and 2 rotation bits. -- We set the palette bits and rotation - return (orientation / 90) + color * 32 + return rotation + axis_direction * 4 + color * 32 end -local function get_orientation_from_param2(param2) - -- Strip off the top 6 bits, unfortunately MT lua has no bitwise '&' +local function get_orientation_from_colorfacedir(param2) + + local axis_direction = 0 + -- Strip off the top 6 bits to leave the 2 rotation bits, unfortunately MT lua has no bitwise '&' -- (high 3 bits are palette, followed by 3 direction bits then 2 rotation bits) if param2 >= 128 then param2 = param2 - 128 end if param2 >= 64 then param2 = param2 - 64 end if param2 >= 32 then param2 = param2 - 32 end - if param2 >= 16 then param2 = param2 - 16 end - if param2 >= 8 then param2 = param2 - 8 end + if param2 >= 16 then param2 = param2 - 16; axis_direction = axis_direction + 4 end + if param2 >= 8 then param2 = param2 - 8; axis_direction = axis_direction + 2 end + if param2 >= 4 then param2 = param2 - 4; axis_direction = axis_direction + 1 end - return param2 * 90 + -- if the portal is vertical then node axis direction will be +Y (up) and portal orientation + -- will set the node's rotation. + -- if the portal is horizontal then the node axis direction reflects the yaw orientation and + -- the node's rotation will be whatever's needed to keep the texture horizontal (either 0 or 1) + if axis_direction == 0 or axis_direction == 5 then + -- portal is vertical + return param2 * 90 + else + if axis_direction == 1 then return 0 end + if axis_direction == 3 then return 90 end + if axis_direction == 2 then return 180 end + if axis_direction == 4 then return 270 end + end end -- Combining frame_node_name, p1, and p2 will always be enough to uniquely identify a portal_definition @@ -629,7 +753,7 @@ local function set_portal_metadata(portal_definition, anchorPos, orientation, de -- they define the bounding volume for the portal. local p1, p2 = portal_definition.shape:get_p1_and_p2_from_anchorPos(anchorPos, orientation) local p1_string, p2_string = minetest.pos_to_string(p1), minetest.pos_to_string(p2) - local param2 = get_param2_from_color_and_orientation(portal_definition.wormhole_node_color, orientation) + local param2 = get_colorfacedir_from_color_and_orientation(portal_definition.wormhole_node_color, orientation, portal_definition.wormhole_node_is_horizontal) local update_aborted-- using closures to allow the updateFunc to return extra information - by setting this variable @@ -793,7 +917,7 @@ local function build_portal(portal_definition, anchorPos, orientation, destinati -- set the param2 on wormhole nodes to ensure they are the right color local wormholeNode = { name = portal_definition.wormhole_node_name, - param2 = get_param2_from_color_and_orientation(portal_definition.wormhole_node_color, orientation) + param2 = get_colorfacedir_from_color_and_orientation(portal_definition.wormhole_node_color, orientation, portal_definition.wormhole_node_is_horizontal) } portal_definition.shape.apply_func_to_wormhole_nodes( anchorPos, @@ -974,7 +1098,7 @@ local function ensure_remote_portal_then_teleport(player, portal_definition, loc if dest_wormhole_node.name == portal_definition.wormhole_node_name then -- portal exists - local destination_orientation = get_orientation_from_param2(dest_wormhole_node.param2) + local destination_orientation = get_orientation_from_colorfacedir(dest_wormhole_node.param2) local destination_anchorPos = portal_definition.shape.get_anchorPos_from_wormholePos(destination_wormholePos, destination_orientation) portal_definition.shape.disable_portal_trap(destination_anchorPos, destination_orientation) @@ -1417,13 +1541,14 @@ end) -- The fallback defaults for registered portaldef tables local portaldef_default = { - shape = PortalShape_Traditional, - wormhole_node_name = "nether:portal", - wormhole_node_color = 0, - frame_node_name = "default:obsidian", - particle_texture = "nether_particle.png", - particle_texture_animation = nil, - particle_texture_scale = 1, + shape = PortalShape_Traditional, + wormhole_node_name = "nether:portal", + wormhole_node_color = 0, + wormhole_node_is_horizontal = false, + frame_node_name = "default:obsidian", + particle_texture = "nether_particle.png", + particle_texture_animation = nil, + particle_texture_scale = 1, sounds = { ambient = {name = "nether_portal_ambient", gain = 0.6, length = 3}, ignite = {name = "nether_portal_ignite", gain = 0.7}, @@ -1565,13 +1690,14 @@ function nether.volume_is_natural(minp, maxp) local vi = area:index(pos1.x, y, z) for x = pos1.x, pos2.x do local id = data[vi] -- Existing node - if id ~= c_air and id ~= c_ignore then -- These are natural + 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 -- These are natural or not emerged local name = minetest.get_name_from_content_id(id) local nodedef = minetest.registered_nodes[name] if not nodedef.is_ground_content then -- 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) then + if node_groups == nil or (node_groups.tree == nil and node_groups.leaves == nil and node_groups.leafdecay == nil) then return false end end @@ -1602,7 +1728,15 @@ function nether.find_surface_target_y(target_x, target_z, portal_name) groundNode = minetest.get_node(shouldBeGroundPos) end if not groundNode.is_ground_content then - surface_level = surface_level - 1 + if DEBUG then minetest.chat_send_all("find_surface_target_y dropped spawn_level by 1") end + surface_level = surface_level - 1 + + shouldBeGroundPos.y = shouldBeGroundPos.y - 1 + groundNode = minetest.get_node_or_nil(shouldBeGroundPos) + if groundNode ~= nil and not groundNode.is_ground_content then + if DEBUG then minetest.chat_send_all("find_surface_target_y dropped spawn_level by 2") end + surface_level = surface_level - 1 + end end -- Check volume for non-natural nodes local minp = {x = target_x - 1, y = surface_level - 1, z = target_z - 2} @@ -1662,7 +1796,7 @@ function nether.find_nearest_working_portal(portal_name, anchorPos, distance_lim if portalFound then return portal_info.anchorPos, portal_info.orientation else - if DEBUG then minetest.chat_send_all("removing portal from mod_storage at " .. minetest.pos_to_string(portal_info.anchorPos) .. " orientation " .. portal_info.orientation) end + 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 -- The portal at that location must have been destroyed remove_portal_location_info(portal_name, portal_info.anchorPos) end diff --git a/portal_api.txt b/portal_api.txt index 2ee5a33..9ff46b2 100644 --- a/portal_api.txt +++ b/portal_api.txt @@ -21,6 +21,12 @@ one kind of portal with the same frame material — such as obsidian — provide the size of the PortalShape is distinct from any other type of portal that is using the same node as its frame, and portal sizes remain small. +Stone is not a good choice for portal frame nodes as the Minetest engine may +convert it into terrain nodes if the biome-pass happens after the portal is +created. Similarly, avoid using nodes which may be replaced by ABMs etc. in the +game without the node's on_destruct being triggered. + + Realms ------ @@ -152,7 +158,7 @@ Used by `nether.register_portal`. find_realm_anchorPos = function(surface_anchorPos), -- Required. Return a position in the realm that a portal created at -- surface_anchorPos will link to. - -- Return an anchorPos or anchorPos, orientation + -- Return an anchorPos or (anchorPos, orientation) -- If orientation is not specified then the orientation of the surface -- portal will be used. -- If the location of an existing portal is returned then include the @@ -179,6 +185,6 @@ Used by `nether.register_portal`. on_ignite = function(portalDef, anochorPos, orientation) -- invoked when a player or mesecon ignites a portal on_created = function(portalDef, anochorPos, orientation) - -- invoked when a portal creates a remote twin, usually when a - -- player travels through a portal for the first time. + -- invoked when a portal creates a remote twin, this is usually when + -- a player travels through a portal for the first time. } diff --git a/portal_examples.lua b/portal_examples.lua index 64b515d..94b6873 100644 --- a/portal_examples.lua +++ b/portal_examples.lua @@ -2,8 +2,8 @@ Nether mod portal examples for Minetest - To use this file, add the following line to init.lua: - dofile(nether.path .. "/portal_examples.lua") + These portal API examples are independent of the Nether. + To use this file, set nether.ENABLE_EXAMPLE_PORTALS to true in init.lua Copyright (C) 2019 Treer @@ -23,12 +23,20 @@ ]]-- +-- Sets how far a Surface Portal will travel, measured in cells along the Moore curve, +-- which are about 117 nodes square each. Larger numbers will generally mean further distance +-- as-the-crow-flies, but for small adjustments this will not always be true due to the how +-- the Moore curve frequently doubles back upon itself. +local SURFACE_TRAVEL_DISTANCE = 15 + + local S = nether.get_translator nether.register_portal("floatlands_portal", { - shape = nether.PortalShape_Traditional, + shape = nether.PortalShape_Platform, frame_node_name = "default:ice", wormhole_node_color = 7, -- 2 is blue + wormhole_node_is_horizontal = true, -- indicate the wormhole surface is horizontal particle_texture = { name = "nether_particle_anim1.png", animation = { @@ -97,12 +105,12 @@ Requiring 14 blocks of ice, but otherwise constructed the same as the portal to }) --- These Moore Curve functions requred by circular_portal's find_surface_anchorPos() will +-- These Moore Curve functions requred by surface_portal's find_surface_anchorPos() will -- be assigned later in this file. local get_moore_distance -- will be function get_moore_distance(cell_count, x, y): integer local get_moore_coords -- will be function get_moore_coords(cell_count, distance): pos2d -nether.register_portal("circular_portal", { +nether.register_portal("surface_portal", { shape = nether.PortalShape_Circular, frame_node_name = "default:cobble", wormhole_node_color = 4, -- 4 is cyan @@ -117,7 +125,7 @@ nether.register_portal("circular_portal", {       └─╚═╤═╤═┼─┘             └─┴─┴─┘ - +These ]] .. "\u{25A9}"), is_within_realm = function(pos) @@ -138,7 +146,6 @@ nether.register_portal("circular_portal", { -- surface (following a Moore curve) so will be using a different x and z to realm_anchorPos. local cellCount = 512 - local travelDistanceInCells = 10 local maxDistFromOrigin = 30000 -- the world edges are at X=30927, X=−30912, Z=30927 and Z=−30912 -- clip realm_anchorPos to maxDistFromOrigin, and move the origin so that all values are positive @@ -147,40 +154,44 @@ nether.register_portal("circular_portal", { local divisor = math.ceil(maxDistFromOrigin * 2 / cellCount) local distance = get_moore_distance(cellCount, math.floor(x / divisor + 0.5), math.floor(z / divisor + 0.5)) - local destination_distance = (distance + travelDistanceInCells) % (cellCount * cellCount) + local destination_distance = (distance + SURFACE_TRAVEL_DISTANCE) % (cellCount * cellCount) local moore_pos = get_moore_coords(cellCount, destination_distance) - -- deterministically look for a location where get_spawn_level() gives us a height local target_x = moore_pos.x * divisor - maxDistFromOrigin local target_z = moore_pos.y * divisor - maxDistFromOrigin + local adj_x, adj_z = 0, 0 - local prng = PcgRandom( -- seed the prng so that all portals for these Moore Curve coords will use the same random location - moore_pos.x * 65732 + - moore_pos.y * 729 + - minetest.get_mapgen_setting("seed") * 3 - ) + if minetest.get_spawn_level ~= nil then -- older versions of Minetest don't have this + -- Deterministically look for a location in the cell where get_spawn_level() can give + -- us a surface height, since nether.find_surface_target_y() works much better when + -- it can use get_spawn_level() + local prng = PcgRandom( -- seed the prng so that all portals for these Moore Curve coords will use the same random location + moore_pos.x * 65732 + + moore_pos.y * 729 + + minetest.get_mapgen_setting("seed") * 3 + ) - local radius = divisor / 2 - 2 - local attemptLimit = 10 - local adj_x, adj_z - for attempt = 1, attemptLimit do - adj_x = math.floor(prng:rand_normal_dist(-radius, radius, 2) + 0.5) - adj_z = math.floor(prng:rand_normal_dist(-radius, radius, 2) + 0.5) - minetest.chat_send_all(attempt .. ": x " .. target_x + adj_x .. ", z " .. target_z + adj_z) - if minetest.get_spawn_level(target_x + adj_x, target_z + adj_z) ~= nil then - -- found a location which will be at ground level (unless a player has built there) - minetest.chat_send_all("x " .. target_x + adj_x .. ", z " .. target_z + adj_z .. " is suitable") - break + local radius = divisor / 2 - 5 + local attemptLimit = 10 -- how many attempts we'll make to find a good location + for attempt = 1, attemptLimit do + adj_x = math.floor(prng:rand_normal_dist(-radius, radius, 2) + 0.5) + adj_z = math.floor(prng:rand_normal_dist(-radius, radius, 2) + 0.5) + minetest.chat_send_all(attempt .. ": x " .. target_x + adj_x .. ", z " .. target_z + adj_z) + if minetest.get_spawn_level(target_x + adj_x, target_z + adj_z) ~= nil then + -- found a location which will be at ground level (unless a player has built there) + minetest.chat_send_all("x " .. target_x + adj_x .. ", z " .. target_z + adj_z .. " is suitable") + break + end end end local destination_pos = {x = target_x + adj_x, y = 0, z = target_z + adj_z} -- a y_factor of 0 makes the search ignore the altitude of the portals - local existing_portal_location, existing_portal_orientation = nether.find_nearest_working_portal("circular_portal", destination_pos, radius, 0) + local existing_portal_location, existing_portal_orientation = nether.find_nearest_working_portal("surface_portal", destination_pos, radius, 0) 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, "circular_portal") + destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "surface_portal") return destination_pos end end @@ -192,7 +203,7 @@ nether.register_portal("circular_portal", { -- Hilbert curve and Moore curve functions -- --=========================================-- --- These are space-filling curves, used by the circular_portal example as a way to determine where +-- These are space-filling curves, used by the surface_portal example as a way to determine where -- to place portals. https://en.wikipedia.org/wiki/Moore_curve diff --git a/schematics/nether_portal_platform.mts b/schematics/nether_portal_platform.mts new file mode 100644 index 0000000..4f510d0 Binary files /dev/null and b/schematics/nether_portal_platform.mts differ