railcarts/rail.lua

522 lines
18 KiB
Lua

local dbg
if moddebug then dbg=moddebug.dbg("railcarts") else dbg={v1=function() end,v2=function() end,v3=function() end} end
--- Determine whether the given node is a rail.
-- @param node The node to check (which can be nil)
-- @return true if it's a rail
function is_rail(node)
if node == nil then return false end
name = node.name
if name == "default:rail" or
name == "railcarts:controlrail" or
name == "railcarts:controlrail_off" or
name == "railcarts:digicontrol" or
name == "railcarts:rail_switched" then
return true
end
return false
end
--- Determine whether a railtype is 'unloaded'
-- @param railtype A railtype (as per get_rail_status)
-- @return true if unloaded
function is_unloaded(railtype)
if railtype == "unloaded" then return true end
if railtype == "unloaded-near" then return true end
return false
end
---Determine if a node is a grabber of carts
--@param node The node to check (which can be nil)
--@return True if it is
function is_grabber(node)
if node == nil then return false end
if node.name == "railcarts:autolauncher" then
return true
end
if node.name == "railcarts:digilauncher" then
return true
end
if node.name == "railcarts:digilauncher_on" then
return true
end
return false
end
---Get rail control information coming from a node.
-- Only switched on control rails are considered, since if they're switched
-- off they should behave exactly like a normal rail.
--@param node The node to check (which can be nil)
--@param pos The position to check
--@param status The status to update.
--The control field might be added, containing any of the specifications
--as described in the README.
-- The digiline field might be set to the pos that should send a signal
function get_railcontrol(node, pos, status)
if node and (node.name == "railcarts:controlrail"
or node.name == "railcarts:digicontrol") then
local meta = minetest.get_meta(pos)
if meta then
local it = meta:get_string("infotext")
if it and it ~= "" then
status.control = it
end
end
if node.name == "railcarts:digicontrol" then
status.digiline = pos
end
end
end
--- Determine if a node is a launcher
-- @param node The node (which can be nil)
-- @return True if it is
function is_launcher(node)
if node == nil then return false end
if node.name == "railcarts:launcher_on" then
return true
end
if node.name == "railcarts:digilauncher_on" then
return true
end
return false
end
--- Determine if a node is an autolauncher
-- @param node The node (which can be nil)
-- @return True if it is
function is_autolauncher(node)
if node == nil then return false end
if node.name == "railcarts:autolauncher" then
return true
end
return false
end
--- Get status of rails
-- @param fppos The position to check at
-- @param direction Movement direction (can be nil, in which case some checks
-- are not possible).
-- @param speed Movement speed (only relevant if direction is not nil)
-- checks can be completed.
-- @return A table which always contains railtype. One of:
-- "inv" - invalid position
-- "unloaded" or "unloaded-near" - entering as-yet-unloaded territory
-- use is_unloaded() to check this!
-- "x", "z" - straight track in that direction (can be sloped)
-- "x+", x-", "z+", "z-" - curve (see check_curve ireturn value)
-- It can also optionally contain:
-- unloadedpos, when railtype is unloaded or unloaded-near, is the position
-- that caused the unloaded status
-- slope, if the rail is sloped, the up direction, 0-3 (and railtype can only
-- be "x" or "z"
-- eject, which if true means any player in the cart should be ejected
-- grab, which if it exists is the position of a node which should 'grab' the
-- cart into its inventory (i.e. an autolauncher)
-- launch, which is a launch direction
-- autolaunch, which is an autolaunch direction
-- control, usually nil, otherwise control information coming from the current
-- rail - values as per get_controlrail()
-- onautolauncher, which if true means there is an autolauncher below the rails
-- detectors, if present, is the pos for a cart detector to trigger
-- hopper, if present, is the pos for a hopper above the cart
-- only detected when stationary
function get_railstatus(fppos, direction, speed)
if fppos == nil then
dbg.v2("get_railstatus for nil pos!?")
return {railtype="inv"}
end
-- Use rounded position for all lookups and calculations in here
local pos = vector.round(fppos)
local current_node = minetest.get_node(pos)
if current_node.name == "ignore" then
return {railtype="unloaded", unloadedpos=pos}
end
if not is_rail(current_node) then
return {railtype="inv"}
end
local status = {}
-- Get all surrounding nodes. This builds a little cache of all the
-- surrounding nodes we might need to look at.
-- TODO: make this lazily get positions only when requested, because
-- sometimes we don't need them all
-- TODO: make this shared across a whole step (because getrailstatus
-- can be called twice, with overlap, within a step)
-- BUT!!! digirail triggers, mid-step, can change the rails,
-- although that shouldn't matter - but think about it!
-- TODO: make it less messy!
-- TODO: we're only storing the position for reading it back to debug
-- the 'unloaded' problem, we could lose it when that's fixed
local getsur = function(name, pos, surrounds)
surrounds[name] = minetest.get_node(pos)
surrounds[name.."_pos"] = pos
end
local surrounds = {}
getsur("x_prev", {x=pos.x-1,y=pos.y,z=pos.z}, surrounds)
getsur("x_next", {x=pos.x+1,y=pos.y,z=pos.z}, surrounds)
getsur("z_prev", {x=pos.x,y=pos.y,z=pos.z-1}, surrounds)
getsur("z_next", {x=pos.x,y=pos.y,z=pos.z+1}, surrounds)
getsur("x_prev_above", {x=pos.x-1,y=pos.y+1,z=pos.z}, surrounds)
getsur("x_next_above", {x=pos.x+1,y=pos.y+1,z=pos.z}, surrounds)
getsur("z_prev_above", {x=pos.x,y=pos.y+1,z=pos.z-1}, surrounds)
getsur("z_next_above", {x=pos.x,y=pos.y+1,z=pos.z+1}, surrounds)
getsur("x_prev_below", {x=pos.x-1,y=pos.y-1,z=pos.z}, surrounds)
getsur("x_next_below", {x=pos.x+1,y=pos.y-1,z=pos.z}, surrounds)
getsur("z_prev_below", {x=pos.x,y=pos.y-1,z=pos.z-1}, surrounds)
getsur("z_next_below", {x=pos.x,y=pos.y-1,z=pos.z+1}, surrounds)
getsur("below", {x=pos.x,y=pos.y-1,z=pos.z}, surrounds)
getsur("above", {x=pos.x,y=pos.y+1,z=pos.z}, surrounds)
-- We need to know what all the surrounding blocks are to be able to move
-- properly (e.g. consider a curve or switch on a block boundary) so if
-- we don't have that information we need to wait.
for k, nn in pairs(surrounds) do
if string.sub(k, -4) ~= "_pos" then
if nn.name == "ignore" then
return {railtype="unloaded-near", unloadedpos=surrounds[k.."_pos"]}
end
end
end
get_railcontrol(current_node, pos, status)
local railtype
-- Check for slopes. The order of these is the same as the order of
-- priority used by the rail drawing code (as determined by experimenation)
if is_rail(surrounds.x_next_above) then
railtype = "x"
status.slope=1
elseif is_rail(surrounds.x_prev_above) then
railtype = "x"
status.slope=3
elseif is_rail(surrounds.z_prev_above) then
railtype = "z"
status.slope=2
elseif is_rail(surrounds.z_next_above) then
railtype = "z"
status.slope=0
end
-- Check for a crossing
if not railtype then
railtype = check_crossing(direction, surrounds)
end
-- Check for a switch
if (not railtype) and direction then
railtype = check_switch(current_node, pos, direction, surrounds)
end
-- Check for a curve
if not railtype then
railtype = check_curve(surrounds)
end
-- There can only be straight rails left
if (not railtype) and (is_rail(surrounds.x_prev) or is_rail(surrounds.x_prev_below) or
is_rail(surrounds.x_next) or is_rail(surrounds.x_next_below)) then
railtype = "x"
end
if (not railtype) and (is_rail(surrounds.z_prev) or is_rail(surrounds.z_prev_below) or
is_rail(surrounds.z_next) or is_rail(surrounds.z_next_below)) then
railtype = "z"
end
if not railtype then
railtype = "inv"
else
-- Check for hoppers
if is_hopper(surrounds.above) then
status.hopper = {x=pos.x,y=pos.y+1,z=pos.z}
end
if direction and speed > 0 then
-- Check if a player should be ejected
if direction == 1 and is_ejector(surrounds.x_next_above) and is_rail(surrounds.x_next) then
status.eject = true
elseif direction == 3 and is_ejector(surrounds.x_prev_above) and is_rail(surrounds.x_prev) then
status.eject = true
elseif direction == 0 and is_ejector(surrounds.z_next_above) and is_rail(surrounds.z_next) then
status.eject = true
elseif direction == 2 and is_ejector(surrounds.z_prev_above) and is_rail(surrounds.z_prev) then
status.eject = true
end
-- Check for detectors - below the cart, or to the left
local detpos
if is_detector(surrounds.below) then
detpos = {x=pos.x,y=pos.y-1,z=pos.z}
else
if direction == 0 and is_detector(surrounds.x_prev) then
detpos = {x=pos.x-1,y=pos.y,z=pos.z}
elseif direction == 1 and is_detector(surrounds.z_next) then
detpos = {x=pos.x,y=pos.y,z=pos.z+1}
elseif direction == 2 and is_detector(surrounds.x_next) then
detpos = {x=pos.x+1,y=pos.y,z=pos.z}
elseif direction == 1 and is_detector(surrounds.z_prev) then
detpos = {x=pos.x,y=pos.y,z=pos.z-1}
end
end
if detpos then
status.detector = detpos
end
end
if speed == 0 then
-- Check if the cart should be grabbed
if is_grabber(surrounds.x_next_above) then
status.grab = {x=pos.x+1,y=pos.y+1,z=pos.z}
elseif is_grabber(surrounds.x_prev_above) then
status.grab = {x=pos.x-1,y=pos.y+1,z=pos.z}
elseif is_grabber(surrounds.z_prev_above) then
status.grab = {x=pos.x,y=pos.y+1,z=pos.z-1}
elseif is_grabber(surrounds.z_next_above) then
status.grab = {x=pos.x,y=pos.y+1,z=pos.z+1}
elseif is_grabber(surrounds.below) then
status.grab = {x=pos.x,y=pos.y-1,z=pos.z}
end
-- Check for being next to a launcher (only stationary carts)
check_launcher(surrounds, status)
end
end
if surrounds.below and surrounds.below.name == "railcarts:autolauncher" then
status.onautolauncher = true
end
status.railtype = railtype
return status
end
--- Check if the given node should cause player ejection.
-- This is currently any walkable node.
-- @param node The node to check (can be nil)
function is_ejector(node)
if not node then return false end
nd = minetest.registered_nodes[node.name]
return nd.walkable
end
--- Check if the given node is a cart detector.
-- Only unactivated detectors are relevant.
-- @param node The node to check (can be nil)
function is_detector(node)
if not node then return false end
return node.name == "railcarts:cart_detector"
end
--- Check if the given node is a hopper.
-- @param node The node to check (can be nil)
function is_hopper(node)
if not node then return false end
return node.name == "railcarts:hopper"
end
--- Check for a launcher adjacent to a position, with a rail in the other
-- direction.
-- @param surrounds The surrounding nodes
-- @param result, into which launch and autolaunch fields are inserted if
-- necessary
function check_launcher(surrounds, result)
if is_rail(surrounds.x_prev) or is_rail(surrounds.x_prev_below) then
if is_launcher(surrounds.x_next) then result.launch = 3 end
if is_autolauncher(surrounds.x_next) then result.autolaunch = 3 end
end
if is_rail(surrounds.x_next) or is_rail(surrounds.x_next_below) then
if is_launcher(surrounds.x_prev) then result.launch = 1 end
if is_autolauncher(surrounds.x_prev) then result.autolaunch = 1 end
end
if is_rail(surrounds.z_prev) or is_rail(surrounds.z_prev_below) then
if is_launcher(surrounds.z_next) then result.launch = 2 end
if is_autolauncher(surrounds.z_next) then result.autolaunch = 2 end
end
if is_rail(surrounds.z_next) or is_rail(surrounds.z_next_below) then
if is_launcher(surrounds.z_prev) then result.launch = 0 end
if is_autolauncher(surrounds.z_prev) then result.autolaunch = 0 end
end
end
--- Check for a crossing at the given location.
-- @param direction The cart direction
-- @param surrounds The surrounding nodes
--
-- @return If there's no corssing at the current position, nil is returned.
-- Otherwise, "x" or "z" is returned.
function check_crossing(direction, surrounds)
if (is_rail(surrounds.z_prev) or is_rail(surrounds.z_prev_below)) and
(is_rail(surrounds.z_next) or is_rail(surrounds.z_next_below)) and
(is_rail(surrounds.x_prev) or is_rail(surrounds.x_prev_below)) and
(is_rail(surrounds.x_next) or is_rail(surrounds.x_next_below)) then
if direction == 1 or direction == 3 then
return "x"
else
return "z"
end
end
return nil
end
--- Check for a switch at the given location.
-- @param node The node to check at
-- @param pos The position of the node
-- @param direction The cart direction
-- @param surrounds The surrounding nodes
--
-- @return If there's no switch at the current position, nil is returned.
-- Otherwise, "x" or "z" is returned for a switch which is in a straight
-- configuration (according to the current direction), and for a curve
-- one of the "x+", "x-", "z+" or "z-" curve designations, as returned
-- by check_curve.
function check_switch(node, pos, direction, surrounds)
-- junction : Z-, Z+, X+
if (is_rail(surrounds.z_prev) or is_rail(surrounds.z_prev_below)) and
(is_rail(surrounds.z_next) or is_rail(surrounds.z_next_below)) and
(is_rail(surrounds.x_next) or is_rail(surrounds.x_next_below)) then
if node.name == "railcarts:rail_switched" then
if direction == 0 then
return "z"
else
return "z+"
end
else
if direction == 2 then
return "z"
else
return "x+"
end
end
end
-- junction : Z-, Z+, X-
if (is_rail(surrounds.z_prev) or is_rail(surrounds.z_prev_below)) and
(is_rail(surrounds.z_next) or is_rail(surrounds.z_next_below)) and
(is_rail(surrounds.x_prev) or is_rail(surrounds.x_prev_below)) then
if node.name == "railcarts:rail_switched" then
if direction == 2 then
return "z"
else
return "x-"
end
else
if direction == 0 then
return "z"
else
return "z-"
end
end
end
-- junction : X-, X+, Z-
if (is_rail(surrounds.x_prev) or is_rail(surrounds.x_prev_below)) and
(is_rail(surrounds.x_next) or is_rail(surrounds.x_next_below)) and
(is_rail(surrounds.z_prev) or is_rail(surrounds.z_prev_below)) then
if node.name == "railcarts:rail_switched" then
if direction == 1 then
return "x"
else
return "x+"
end
else
if direction == 3 then
return "x"
else
return "x-"
end
end
end
-- junction : X-, X+, Z-
if (is_rail(surrounds.x_prev) or is_rail(surrounds.x_prev_below)) and
(is_rail(surrounds.x_next) or is_rail(surrounds.x_next_below)) and
(is_rail(surrounds.z_next) or is_rail(surrounds.z_next_below)) then
if node.name == "railcarts:rail_switched" then
if direction == 3 then
return "x"
else
return "z-"
end
else
if direction == 1 then
return "x"
else
return "z+"
end
end
end
return nil
end
--- Check if the given railtype is a curve
-- @param railtype A railtype (see get_rail_status)
-- @return True if it's a curve
function is_curve(railtype)
if railtype == "x+" then return true end
if railtype == "x-" then return true end
if railtype == "z+" then return true end
if railtype == "z-" then return true end
return false
end
--- Check for a curve at the given position.
-- @param surrounds The surrounding nodes
-- @return Nil if there is no curve, otherwise one of four curve designations.
-- "x+" has rails at x+1 and z-1, "x-" has rails at x-1 and z-1, "z+" has rails
-- at x+1 and z+1, and "z-" has nodes at x-1 and z+1.
function check_curve(surrounds)
if (is_rail(surrounds.z_prev) or is_rail(surrounds.z_prev_below)) and
(is_rail(surrounds.x_next) or is_rail(surrounds.x_next_below)) then
return "x+"
end
if (is_rail(surrounds.x_prev) or is_rail(surrounds.x_prev_below)) and
(is_rail(surrounds.z_prev) or is_rail(surrounds.z_prev_below)) then
return "x-"
end
if (is_rail(surrounds.z_next) or is_rail(surrounds.z_next_below)) and
(is_rail(surrounds.x_prev) or is_rail(surrounds.x_prev_below)) then
return "z-"
end
if (is_rail(surrounds.x_next) or is_rail(surrounds.x_next_below)) and
(is_rail(surrounds.z_next) or is_rail(surrounds.z_next_below)) then
return "z+"
end
return nil
end