mirror of
https://git.minetest.land/VoxeLibre/VoxeLibre.git
synced 2024-11-09 01:31:06 +01:00
340 lines
9.5 KiB
Lua
340 lines
9.5 KiB
Lua
local vector = vector
|
|
local mod = mcl_minecarts
|
|
local table_merge = mcl_util.table_merge
|
|
|
|
function get_path(base, first, ...)
|
|
if not first then return base end
|
|
if not base then return end
|
|
return get_path(base[first], ...)
|
|
end
|
|
|
|
local function force_get_node(pos)
|
|
local node = minetest.get_node(pos)
|
|
if node.name ~= "ignore" then return node end
|
|
|
|
local vm = minetest.get_voxel_manip()
|
|
local emin, emax = vm:read_from_map(pos, pos)
|
|
local area = VoxelArea:new{
|
|
MinEdge = emin,
|
|
MaxEdge = emax,
|
|
}
|
|
local data = vm:get_data()
|
|
local param_data = vm:get_light_data()
|
|
local param2_data = vm:get_param2_data()
|
|
|
|
local vi = area:indexp(pos)
|
|
return {
|
|
name = minetest.get_name_from_content_id(data[vi]),
|
|
param = param_data[vi],
|
|
param2 = param2_data[vi]
|
|
}
|
|
end
|
|
|
|
function mcl_minecarts:get_sign(z)
|
|
if z == 0 then
|
|
return 0
|
|
else
|
|
return z / math.abs(z)
|
|
end
|
|
end
|
|
|
|
function mcl_minecarts:velocity_to_dir(v)
|
|
if math.abs(v.x) > math.abs(v.z) then
|
|
return vector.new(
|
|
mcl_minecarts:get_sign(v.x),
|
|
mcl_minecarts:get_sign(v.y),
|
|
0
|
|
)
|
|
else
|
|
return vector.new(
|
|
0,
|
|
mcl_minecarts:get_sign(v.y),
|
|
mcl_minecarts:get_sign(v.z)
|
|
)
|
|
end
|
|
end
|
|
|
|
function mcl_minecarts:is_rail(pos, railtype)
|
|
local node_name = force_get_node(pos).name
|
|
|
|
if minetest.get_item_group(node_name, "rail") == 0 then
|
|
return false
|
|
end
|
|
if not railtype then
|
|
return true
|
|
end
|
|
return minetest.get_item_group(node_name, "connect_to_raillike") == railtype
|
|
end
|
|
|
|
--[[
|
|
Returns a string description of a direction, with optional _up/_down suffix
|
|
]]
|
|
function mcl_minecarts:name_from_dir(dir, vertical)
|
|
local res = ""
|
|
|
|
if dir.z == 1 then res = res .. "n" end
|
|
if dir.z == -1 then res = res .. "s" end
|
|
|
|
if dir.x == -1 then res = res .. "w" end
|
|
if dir.x == 1 then res = res .. "e" end
|
|
|
|
if vertical then
|
|
if dir.y == 1 then res = res .. "_up" end
|
|
if dir.y == 1 then res = res .. "_down" end
|
|
end
|
|
|
|
return res
|
|
end
|
|
|
|
|
|
local north = vector.new( 0, 0, 1); local N = 1
|
|
local south = vector.new( 0, 0,-1); local S = 2 -- Note: S is overwritten below with the translator
|
|
local east = vector.new( 1, 0, 0); local E = 4
|
|
local west = vector.new(-1, 0, 0); local W = 8
|
|
|
|
-- Share. Consider moving this to some shared location
|
|
mod.north = north
|
|
mod.south = south
|
|
mod.east = east
|
|
mod.west = west
|
|
|
|
local CONNECTIONS = { north, south, east, west }
|
|
local HORIZONTAL_STANDARD_RULES = {
|
|
[N] = { "", 0, mask = N, score = 1, can_slope = true },
|
|
[S] = { "", 0, mask = S, score = 1, can_slope = true },
|
|
[N+S] = { "", 0, mask = N+S, score = 2, can_slope = true },
|
|
|
|
[E] = { "", 1, mask = E, score = 1, can_slope = true },
|
|
[W] = { "", 1, mask = W, score = 1, can_slope = true },
|
|
[E+W] = { "", 1, mask = E+W, score = 2, can_slope = true },
|
|
}
|
|
mod.HORIZONTAL_STANDARD_RULES = HORIZONTAL_STANDARD_RULES
|
|
|
|
local HORIZONTAL_CURVES_RULES = {
|
|
[N+E] = { "_corner", 3, name = "ne corner", mask = N+E, score = 3 },
|
|
[N+W] = { "_corner", 2, name = "nw corner", mask = N+W, score = 3 },
|
|
[S+E] = { "_corner", 0, name = "se corner", mask = S+E, score = 3 },
|
|
[S+W] = { "_corner", 1, name = "sw corner", mask = S+W, score = 3 },
|
|
|
|
[N+E+W] = { "_tee_off", 3, mask = N+E+W, score = 4 },
|
|
[S+E+W] = { "_tee_off", 1, mask = S+E+W, score = 4 },
|
|
[N+S+E] = { "_tee_off", 0, mask = N+S+E, score = 4 },
|
|
[N+S+W] = { "_tee_off", 2, mask = N+S+W, score = 4 },
|
|
|
|
[N+S+E+W] = { "_cross", 0, mask = N+S+E+W, score = 5 },
|
|
}
|
|
table_merge(HORIZONTAL_CURVES_RULES, HORIZONTAL_STANDARD_RULES)
|
|
mod.HORIZONTAL_CURVES_RULES = HORIZONTAL_CURVES_RULES
|
|
|
|
local HORIZONTAL_RULES_BY_RAIL_GROUP = {
|
|
[1] = HORIZONTAL_STANDARD_RULES,
|
|
[2] = HORIZONTAL_CURVES_RULES,
|
|
}
|
|
|
|
local function check_connection_rule(pos, connections, rule)
|
|
-- All bits in the mask must be set for the connection to be possible
|
|
if bit.band(rule.mask,connections) ~= rule.mask then
|
|
--print("Mask mismatch ("..tostring(rule.mask)..","..tostring(connections)..")")
|
|
return false
|
|
end
|
|
|
|
-- If there is an allow filter, that mush also return true
|
|
if rule.allow and rule.allow(rule, connections, pos) then
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function make_sloped_if_straight(pos, dir)
|
|
local node = minetest.get_node(pos)
|
|
local nodedef = minetest.registered_nodes[node.name]
|
|
|
|
local param2 = 0
|
|
if dir == east then
|
|
param2 = 3
|
|
elseif dir == west then
|
|
param2 = 1
|
|
elseif dir == north then
|
|
param2 = 2
|
|
elseif dir == south then
|
|
param2 = 0
|
|
end
|
|
|
|
if get_path( nodedef, "_mcl_minecarts", "railtype" ) == "straight" then
|
|
minetest.swap_node(pos, {name = nodedef._mcl_minecarts.base_name .. "_sloped", param2 = param2})
|
|
end
|
|
end
|
|
|
|
local function get_rail_connections(pos, opt)
|
|
local legacy = opt and opt.legacy
|
|
local ignore_neighbor_connections = opt and opt.ignore_neighbor_connections
|
|
|
|
local connections = 0
|
|
for i,dir in ipairs(CONNECTIONS) do
|
|
local neighbor = vector.add(pos, dir)
|
|
local node = minetest.get_node(neighbor)
|
|
local nodedef = minetest.registered_nodes[node.name]
|
|
|
|
-- Only allow connections to the open ends of rails, as decribed by get_next_dir
|
|
if get_path(nodedef, "groups", "rail") and ( legacy or get_path(nodedef, "_mcl_minecarts", "get_next_dir" ) ) then
|
|
local rev_dir = vector.direction(dir,vector.new(0,0,0))
|
|
if ignore_neighbor_connections or mcl_minecarts:is_connection(neighbor, rev_dir) then
|
|
connections = connections + bit.lshift(1,i - 1)
|
|
end
|
|
end
|
|
end
|
|
return connections
|
|
end
|
|
mod.get_rail_connections = get_rail_connections
|
|
|
|
local function update_rail_connections(pos, opt)
|
|
local ignore_neighbor_connections = opt and opt.ignore_neighbor_connections
|
|
|
|
local node = minetest.get_node(pos)
|
|
local nodedef = minetest.registered_nodes[node.name]
|
|
if not nodedef._mcl_minecarts then
|
|
minetest.log("warning", "attemting to rail connect "..node.name)
|
|
return
|
|
end
|
|
|
|
-- Get the mappings to use
|
|
local rules = HORIZONTAL_RULES_BY_RAIL_GROUP[nodedef.groups.rail]
|
|
if nodedef._mcl_minecarts and nodedef._mcl_minecarts.connection_rules then -- Custom connection rules
|
|
rules = nodedef._mcl_minecarts.connection_rules
|
|
end
|
|
if not rules then return end
|
|
|
|
-- Horizontal rules, Check for rails on each neighbor
|
|
local connections = get_rail_connections(pos, opt)
|
|
|
|
-- Check for rasing rails to slopes
|
|
for i,dir in ipairs(CONNECTIONS) do
|
|
local neighbor = vector.add(pos, dir)
|
|
make_sloped_if_straight( vector.offset(neighbor, 0, -1, 0), dir )
|
|
end
|
|
|
|
-- Select the best allowed connection
|
|
local rule = nil
|
|
local score = 0
|
|
for k,r in pairs(rules) do
|
|
if check_connection_rule(pos, connections, r) then
|
|
if r.score > score then
|
|
--print("Best rule so far is "..dump(r))
|
|
score = r.score
|
|
rule = r
|
|
end
|
|
end
|
|
end
|
|
|
|
if rule then
|
|
-- Apply the mapping
|
|
local new_name = nodedef._mcl_minecarts.base_name..rule[1]
|
|
if new_name ~= node.name or node.param2 ~= rule[2] then
|
|
--print("swapping "..node.name.." for "..new_name..","..tostring(rule[2]).." at "..tostring(pos))
|
|
node.name = new_name
|
|
node.param2 = rule[2]
|
|
minetest.swap_node(pos, node)
|
|
end
|
|
|
|
if rule.after then
|
|
rule.after(rule, pos, connections)
|
|
end
|
|
end
|
|
|
|
local node_def = minetest.registered_nodes[node.name]
|
|
if get_path(node_def, "_mcl_minecarts", "can_slope") then
|
|
for _,dir in ipairs(CONNECTIONS) do
|
|
local higher_rail_pos = vector.offset(pos,dir.x,1,dir.z)
|
|
local rev_dir = vector.direction(dir,vector.new(0,0,0))
|
|
if mcl_minecarts:is_rail(higher_rail_pos) and mcl_minecarts:is_connection(higher_rail_pos, rev_dir) then
|
|
make_sloped_if_straight(pos, rev_dir)
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
mod.update_rail_connections = update_rail_connections
|
|
|
|
--[[
|
|
An array of (u,v,w) positions to check. Actual direction is u * dir + v * right + w * up
|
|
]]
|
|
local rail_checks = {
|
|
{ 1, 0, 0 }, -- forwards
|
|
{ 1, 0, 1 }, -- forwards and up
|
|
{ 1, 0, -1 }, -- forwards and down
|
|
|
|
{ 1, 1, 0 }, -- diagonal left
|
|
{ 0, 1, 0 }, -- left
|
|
{ 0, 1, 1 }, -- left and up
|
|
{ 0, 1, -1 }, -- left and down
|
|
|
|
{ 1, -1, 0 }, -- diagonal right
|
|
{ 0, -1, 0 }, -- right
|
|
{ 0, -1, 1 }, -- right and up
|
|
{ 0, -1, -1 }, -- right and down
|
|
|
|
{ -1, 0, 0 }, -- backwards
|
|
}
|
|
|
|
local rail_checks_diagonal = {
|
|
{ 1, 1, 0 }, -- forward along diagonal
|
|
{ 1, 0, 0 }, -- left
|
|
{ 0, 1, 0 }, -- right
|
|
}
|
|
|
|
local north = vector.new(0,0,1)
|
|
local south = vector.new(0,0,-1)
|
|
local east = vector.new(1,0,0)
|
|
local west = vector.new(-1,0,0)
|
|
|
|
-- Rotate diagonal directions 45 degrees clockwise
|
|
local diagonal_convert = {
|
|
nw = west,
|
|
ne = north,
|
|
se = east,
|
|
sw = south,
|
|
}
|
|
|
|
function mcl_minecarts:is_connection(pos, dir)
|
|
local node = force_get_node(pos)
|
|
local nodedef = minetest.registered_nodes[node.name]
|
|
|
|
local get_next_dir = get_path(nodedef, "_mcl_minecarts", "get_next_dir")
|
|
if not get_next_dir then return end
|
|
|
|
return get_next_dir(pos, dir, node) == dir
|
|
end
|
|
|
|
function mcl_minecarts:get_rail_direction(pos_, dir, ctrl, old_switch, railtype)
|
|
local pos = vector.round(pos_)
|
|
|
|
-- Handle new track types that have track-specific direction handler
|
|
local node = minetest.get_node(pos)
|
|
local node_def = minetest.registered_nodes[node.name]
|
|
if node_def and node_def._mcl_minecarts and node_def._mcl_minecarts.get_next_dir then
|
|
return node_def._mcl_minecarts.get_next_dir(pos, dir, node)
|
|
end
|
|
|
|
-- Diagonal conversion
|
|
local checks = rail_checks
|
|
if dir.x ~= 0 and dir.z ~= 0 then
|
|
dir = diagonal_convert[ mcl_minecarts:name_from_dir(dir, false) ]
|
|
checks = rail_checks_diagonal
|
|
end
|
|
|
|
-- Calculate coordinate space
|
|
local right = vector.new( dir.z, dir.y, -dir.x)
|
|
local up = vector.new(0,1,0)
|
|
|
|
-- Perform checks
|
|
for _,check in ipairs(checks) do
|
|
local check_dir = dir * check[1] + right * check[2] + up * check[3]
|
|
local check_pos = pos + check_dir
|
|
if mcl_minecarts:is_rail(check_pos,railtype) then
|
|
return check_dir
|
|
end
|
|
end
|
|
|
|
return vector.new(0,0,0)
|
|
end
|