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 Treer
parent c15fd3643d
commit 672069d798
5 changed files with 223 additions and 69 deletions

View File

@ -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(

View File

@ -29,21 +29,25 @@ p1 & p2 p1 and p2 is the system used by earlier versions of the nether mod
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
-- 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)
@ -1420,6 +1544,7 @@ local portaldef_default = {
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,
@ -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
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

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
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.
}

View File

@ -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,22 +154,25 @@ 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
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
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)
@ -173,14 +183,15 @@ nether.register_portal("circular_portal", {
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

Binary file not shown.