Add PortalShape_Platform

and other work on the portal examples
also documentation and fixing issue where apples prevented volume_is_natural() from returning true
This commit is contained in:
Treer 2020-01-02 23:42:20 +11:00 committed by SmallJoker
parent b9e85582f9
commit 6752964c96
5 changed files with 223 additions and 69 deletions

View File

@ -31,13 +31,16 @@ nether.get_translator = S
nether.DEPTH = -5000 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.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.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 -- Load files
dofile(nether.path .. "/portal_api.lua") dofile(nether.path .. "/portal_api.lua")
dofile(nether.path .. "/nodes.lua") dofile(nether.path .. "/nodes.lua")
dofile(nether.path .. "/mapgen.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 -- Portals are ignited by right-clicking with a mese crystal fragment
nether.register_portal_ignition_item( nether.register_portal_ignition_item(

View File

@ -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 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. is forwards and backwards compatible with.
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
they define the bounding volume for the portal. 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 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. to. It can also be used to test whether a portal is active.
AnchorPos Introduced by the portal_api, this should AnchorPos Introduced by the portal_api. Coordinates for portals are normally given in terms of
Usually an orientation is required with an AnchorPos 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 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 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: as follows:
. .
+--------+--------+--------+--------+ +--------+--------+--------+--------+
@ -59,8 +63,8 @@ Portal shapes
| |Wormhole | | | |Wormhole | |
| | Pos | | | | Pos | |
+--------+--------+--------+--------+ +--------+--------+--------+--------+
AnchorPos| Node | | | AnchorPos|TimerPos| | |
| p1 | Timer | | | | p1 | | | |
+--------+--------+--------+--------+ +--------+--------+--------+--------+
+X/East or +Z/North -----> +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 with earlier versions of nether mod (which only records portal corners p1 & p2 in the node
metadata). metadata).
Orientation is 0 or 90, 0 meaning a portal that faces north/south - i.e. obsidian running Orientation is yaw, either 0 or 90, 0 meaning a portal that faces north/south - i.e. obsidian
east/west. running east/west.
]] ]]
-- This object defines a portal's shape, segregating the shape logic code from portal behaviour code. -- 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(). Examples follow -- functions if you wish to register a custom shaped portal in register_portal(). Examples of other
-- after PortalShape_Traditional. -- shapes follow after PortalShape_Traditional.
-- 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
nether.PortalShape_Traditional = { nether.PortalShape_Traditional = {
name = "Traditional", name = "Traditional",
@ -355,6 +359,92 @@ nether.PortalShape_Circular = {
} -- End of PortalShape_Circular class } -- 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 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 -- 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") 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 -- wormhole nodes have a paramtype2 of colorfacedir, which means the
-- high 3 bits are palette, followed by 3 direction bits and 2 rotation bits. -- high 3 bits are palette, followed by 3 direction bits and 2 rotation bits.
-- We set the palette bits and rotation -- We set the palette bits and rotation
return (orientation / 90) + color * 32 return rotation + axis_direction * 4 + color * 32
end end
local function get_orientation_from_param2(param2) local function get_orientation_from_colorfacedir(param2)
-- Strip off the top 6 bits, unfortunately MT lua has no bitwise '&'
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) -- (high 3 bits are palette, followed by 3 direction bits then 2 rotation bits)
if param2 >= 128 then param2 = param2 - 128 end if param2 >= 128 then param2 = param2 - 128 end
if param2 >= 64 then param2 = param2 - 64 end if param2 >= 64 then param2 = param2 - 64 end
if param2 >= 32 then param2 = param2 - 32 end if param2 >= 32 then param2 = param2 - 32 end
if param2 >= 16 then param2 = param2 - 16 end if param2 >= 16 then param2 = param2 - 16; axis_direction = axis_direction + 4 end
if param2 >= 8 then param2 = param2 - 8 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 end
-- Combining frame_node_name, p1, and p2 will always be enough to uniquely identify a portal_definition -- 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. -- they define the bounding volume for the portal.
local p1, p2 = portal_definition.shape:get_p1_and_p2_from_anchorPos(anchorPos, orientation) 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 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 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 -- set the param2 on wormhole nodes to ensure they are the right color
local wormholeNode = { local wormholeNode = {
name = portal_definition.wormhole_node_name, 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( portal_definition.shape.apply_func_to_wormhole_nodes(
anchorPos, 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 if dest_wormhole_node.name == portal_definition.wormhole_node_name then
-- portal exists -- 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) local destination_anchorPos = portal_definition.shape.get_anchorPos_from_wormholePos(destination_wormholePos, destination_orientation)
portal_definition.shape.disable_portal_trap(destination_anchorPos, destination_orientation) portal_definition.shape.disable_portal_trap(destination_anchorPos, destination_orientation)
@ -1417,13 +1541,14 @@ end)
-- The fallback defaults for registered portaldef tables -- The fallback defaults for registered portaldef tables
local portaldef_default = { local portaldef_default = {
shape = PortalShape_Traditional, shape = PortalShape_Traditional,
wormhole_node_name = "nether:portal", wormhole_node_name = "nether:portal",
wormhole_node_color = 0, wormhole_node_color = 0,
frame_node_name = "default:obsidian", wormhole_node_is_horizontal = false,
particle_texture = "nether_particle.png", frame_node_name = "default:obsidian",
particle_texture_animation = nil, particle_texture = "nether_particle.png",
particle_texture_scale = 1, particle_texture_animation = nil,
particle_texture_scale = 1,
sounds = { sounds = {
ambient = {name = "nether_portal_ambient", gain = 0.6, length = 3}, ambient = {name = "nether_portal_ambient", gain = 0.6, length = 3},
ignite = {name = "nether_portal_ignite", gain = 0.7}, 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) local vi = area:index(pos1.x, y, z)
for x = pos1.x, pos2.x do for x = pos1.x, pos2.x do
local id = data[vi] -- Existing node 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 name = minetest.get_name_from_content_id(id)
local nodedef = minetest.registered_nodes[name] local nodedef = minetest.registered_nodes[name]
if not nodedef.is_ground_content then if not nodedef.is_ground_content then
-- 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) then if node_groups == nil or (node_groups.tree == nil and node_groups.leaves == nil and node_groups.leafdecay == nil) then
return false return false
end end
end end
@ -1602,7 +1728,15 @@ function nether.find_surface_target_y(target_x, target_z, portal_name)
groundNode = minetest.get_node(shouldBeGroundPos) groundNode = minetest.get_node(shouldBeGroundPos)
end end
if not groundNode.is_ground_content then 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 end
-- Check volume for non-natural nodes -- Check volume for non-natural nodes
local minp = {x = target_x - 1, y = surface_level - 1, z = target_z - 2} 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 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("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 -- 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

@ -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 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. 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 Realms
------ ------
@ -152,7 +158,7 @@ Used by `nether.register_portal`.
find_realm_anchorPos = function(surface_anchorPos), find_realm_anchorPos = function(surface_anchorPos),
-- 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)
-- If orientation is not specified then the orientation of the surface -- If orientation is not specified then the orientation of the surface
-- portal will be used. -- portal will be used.
-- If the location of an existing portal is returned then include the -- 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) on_ignite = function(portalDef, anochorPos, orientation)
-- invoked when a player or mesecon ignites a portal -- invoked when a player or mesecon ignites a portal
on_created = function(portalDef, anochorPos, orientation) on_created = function(portalDef, anochorPos, orientation)
-- invoked when a portal creates a remote twin, usually when a -- invoked when a portal creates a remote twin, this is usually when
-- player travels through a portal for the first time. -- a player travels through a portal for the first time.
} }

View File

@ -2,8 +2,8 @@
Nether mod portal examples for Minetest Nether mod portal examples for Minetest
To use this file, add the following line to init.lua: These portal API examples are independent of the Nether.
dofile(nether.path .. "/portal_examples.lua") To use this file, set nether.ENABLE_EXAMPLE_PORTALS to true in init.lua
Copyright (C) 2019 Treer 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 local S = nether.get_translator
nether.register_portal("floatlands_portal", { nether.register_portal("floatlands_portal", {
shape = nether.PortalShape_Traditional, shape = nether.PortalShape_Platform,
frame_node_name = "default:ice", frame_node_name = "default:ice",
wormhole_node_color = 7, -- 2 is blue wormhole_node_color = 7, -- 2 is blue
wormhole_node_is_horizontal = true, -- indicate the wormhole surface is horizontal
particle_texture = { particle_texture = {
name = "nether_particle_anim1.png", name = "nether_particle_anim1.png",
animation = { 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. -- be assigned later in this file.
local get_moore_distance -- will be function get_moore_distance(cell_count, x, y): integer 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 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, shape = nether.PortalShape_Circular,
frame_node_name = "default:cobble", frame_node_name = "default:cobble",
wormhole_node_color = 4, -- 4 is cyan wormhole_node_color = 4, -- 4 is cyan
@ -117,7 +125,7 @@ nether.register_portal("circular_portal", {
These
]] .. "\u{25A9}"), ]] .. "\u{25A9}"),
is_within_realm = function(pos) 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. -- surface (following a Moore curve) so will be using a different x and z to realm_anchorPos.
local cellCount = 512 local cellCount = 512
local travelDistanceInCells = 10
local maxDistFromOrigin = 30000 -- the world edges are at X=30927, X=30912, Z=30927 and Z=30912 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 -- 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 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 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) 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_x = moore_pos.x * divisor - maxDistFromOrigin
local target_z = moore_pos.y * 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 if minetest.get_spawn_level ~= nil then -- older versions of Minetest don't have this
moore_pos.x * 65732 + -- Deterministically look for a location in the cell where get_spawn_level() can give
moore_pos.y * 729 + -- us a surface height, since nether.find_surface_target_y() works much better when
minetest.get_mapgen_setting("seed") * 3 -- 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 radius = divisor / 2 - 5
local attemptLimit = 10 local attemptLimit = 10 -- how many attempts we'll make to find a good location
local adj_x, adj_z for attempt = 1, attemptLimit do
for attempt = 1, attemptLimit do adj_x = math.floor(prng:rand_normal_dist(-radius, radius, 2) + 0.5)
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)
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)
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
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)
-- 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")
minetest.chat_send_all("x " .. target_x + adj_x .. ", z " .. target_z + adj_z .. " is suitable") break
break end
end end
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}
-- a y_factor of 0 makes the search ignore the altitude of the portals -- 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 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, "circular_portal") destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "surface_portal")
return destination_pos return destination_pos
end end
end end
@ -192,7 +203,7 @@ nether.register_portal("circular_portal", {
-- Hilbert curve and Moore curve functions -- -- 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 -- to place portals. https://en.wikipedia.org/wiki/Moore_curve

Binary file not shown.