599 lines
16 KiB
Lua
599 lines
16 KiB
Lua
--[[
|
|
|
|
Minecart
|
|
========
|
|
|
|
Copyright (C) 2019-2021 Joachim Stolberg
|
|
|
|
MIT
|
|
See license.txt for more information
|
|
|
|
]]--
|
|
|
|
-- for lazy programmers
|
|
local M = minetest.get_meta
|
|
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
|
|
local P2H = minetest.hash_node_position
|
|
|
|
local get_node_lvm = minecart.get_node_lvm
|
|
|
|
local MAX_SPEED = 8
|
|
local SLOWDOWN = 0.3
|
|
local MAX_NODES = 100
|
|
|
|
--waypoint = {
|
|
-- dot = travel direction,
|
|
-- pos = destination pos,
|
|
-- speed = 10 times the section speed (as int),
|
|
-- limit = 10 times the speed limit (as int),
|
|
--}
|
|
--
|
|
-- waypoints = {facedir = waypoint,...}
|
|
|
|
local tWaypoints = {} -- {pos_hash = waypoints, ...}
|
|
|
|
local tRailsPower = {
|
|
["carts:rail"] = 0,
|
|
["carts:powerrail"] = 1,
|
|
["minecart:rail"] = 0,
|
|
["minecart:powerrail"] = 1,
|
|
["carts:brakerail"] = 0,
|
|
}
|
|
-- Real rails from the mod carts
|
|
local tRails = {
|
|
["carts:rail"] = true,
|
|
["carts:powerrail"] = true,
|
|
["carts:brakerail"] = true,
|
|
["minecart:rail"] = true,
|
|
["minecart:powerrail"] = true,
|
|
}
|
|
-- Rails plus node carts. Used to find waypoints. Added via add_raillike_nodes
|
|
local tRailsExt = {
|
|
["carts:rail"] = true,
|
|
["carts:powerrail"] = true,
|
|
["carts:brakerail"] = true,
|
|
["minecart:rail"] = true,
|
|
["minecart:powerrail"] = true,
|
|
}
|
|
|
|
local tSigns = {
|
|
["minecart:speed1"] = 1,
|
|
["minecart:speed2"] = 2,
|
|
["minecart:speed4"] = 4,
|
|
["minecart:speed8"] = 8,
|
|
}
|
|
|
|
-- Real rails from the mod carts
|
|
local lRails = {"carts:rail", "carts:powerrail", "carts:brakerail", "minecart:rail", "minecart:powerrail"}
|
|
-- Rails plus node carts used to find waypoints, , added via add_raillike_nodes
|
|
local lRailsExt = {"carts:rail", "carts:powerrail", "carts:brakerail", "minecart:rail", "minecart:powerrail"}
|
|
|
|
minecart.MAX_SPEED = MAX_SPEED
|
|
minecart.lRails = lRails
|
|
minecart.tRails = tRails
|
|
minecart.tRailsExt = tRailsExt
|
|
minecart.lRailsExt = lRailsExt
|
|
|
|
local Dot2Dir = {}
|
|
local Dir2Dot = {}
|
|
local Facedir2Dir = {[0] =
|
|
{x= 0, y=0, z= 1},
|
|
{x= 1, y=0, z= 0},
|
|
{x= 0, y=0, z=-1},
|
|
{x=-1, y=0, z= 0},
|
|
{x= 0, y=-1, z= 0},
|
|
{x= 0, y=1, z= 0},
|
|
}
|
|
|
|
local flip = {
|
|
[0] = 2,
|
|
[1] = 3,
|
|
[2] = 0,
|
|
[3] = 1,
|
|
[4] = 5,
|
|
[5] = 4,
|
|
}
|
|
|
|
-- facedir = math.floor(dot / 4)
|
|
-- y = (dot % 4) - 1
|
|
|
|
-- Create helper tables
|
|
for facedir = 0,3 do
|
|
for y = -1,1 do
|
|
local dot = 1 + facedir * 4 + y
|
|
local dir = vector.new(Facedir2Dir[facedir])
|
|
dir.y = y
|
|
Dot2Dir[dot] = dir
|
|
Dir2Dot[P2H(dir)] = dot
|
|
end
|
|
end
|
|
|
|
local function dot2dir(dot) return vector.new(Dot2Dir[dot]) end
|
|
local function facedir2dir(fd) return vector.new(Facedir2Dir[fd]) end
|
|
|
|
minecart.dot2dir = dot2dir
|
|
minecart.facedir2dir = facedir2dir
|
|
|
|
-------------------------------------------------------------------------------
|
|
-- waypoint metadata
|
|
-------------------------------------------------------------------------------
|
|
local function has_metadata(pos)
|
|
return M(pos):contains("waypoints")
|
|
end
|
|
|
|
local function get_metadata(pos)
|
|
local hash = P2H(pos)
|
|
if tWaypoints[hash] then
|
|
return tWaypoints[hash]
|
|
end
|
|
local s = M(pos):get_string("waypoints")
|
|
if s ~= "" then
|
|
tWaypoints[hash] = minetest.deserialize(s)
|
|
return tWaypoints[hash]
|
|
end
|
|
end
|
|
|
|
local function get_oldmetadata(meta)
|
|
local s = meta:get_string("waypoints")
|
|
if s ~= "" then
|
|
return minetest.deserialize(s)
|
|
end
|
|
end
|
|
|
|
local function set_metadata(pos, t)
|
|
local hash = P2H(pos)
|
|
tWaypoints[hash] = t
|
|
local s = minetest.serialize(t)
|
|
M(pos):set_string("waypoints", s)
|
|
-- visualization
|
|
local name = get_node_lvm(pos).name
|
|
if name == "carts:rail" then
|
|
minetest.swap_node(pos, {name = "minecart:rail"})
|
|
elseif name == "carts:powerrail" then
|
|
minetest.swap_node(pos, {name = "minecart:powerrail"})
|
|
end
|
|
end
|
|
|
|
local function del_metadata(pos)
|
|
local hash = P2H(pos)
|
|
tWaypoints[hash] = nil
|
|
local meta = M(pos)
|
|
if meta:contains("waypoints") then
|
|
meta:set_string("waypoints", "")
|
|
-- visualization
|
|
local name = get_node_lvm(pos).name
|
|
if name == "minecart:rail" then
|
|
minetest.swap_node(pos, {name = "carts:rail"})
|
|
elseif name == "minecart:powerrail" then
|
|
minetest.swap_node(pos, {name = "carts:powerrail"})
|
|
end
|
|
end
|
|
end
|
|
|
|
-------------------------------------------------------------------------------
|
|
-- find_next_waypoint
|
|
-------------------------------------------------------------------------------
|
|
local function check_right(pos, facedir)
|
|
local fdr = (facedir + 1) % 4 -- right
|
|
local new_pos = vector.add(pos, facedir2dir(fdr))
|
|
|
|
local name = get_node_lvm(new_pos).name
|
|
if tRailsExt[name] or tSigns[name] then
|
|
return true
|
|
end
|
|
new_pos.y = new_pos.y - 1
|
|
if tRailsExt[get_node_lvm(new_pos).name] then
|
|
return true
|
|
end
|
|
end
|
|
|
|
local function check_left(pos, facedir)
|
|
local fdl = (facedir + 3) % 4 -- left
|
|
local new_pos = vector.add(pos, facedir2dir(fdl))
|
|
|
|
local name = get_node_lvm(new_pos).name
|
|
if tRailsExt[name] or tSigns[name] then
|
|
return true
|
|
end
|
|
new_pos.y = new_pos.y - 1
|
|
if tRailsExt[get_node_lvm(new_pos).name] then
|
|
return true
|
|
end
|
|
end
|
|
|
|
local function get_next_pos(pos, facedir, y)
|
|
local new_pos = vector.add(pos, facedir2dir(facedir))
|
|
new_pos.y = new_pos.y + y
|
|
local name = get_node_lvm(new_pos).name
|
|
return tRailsExt[name] ~= nil, new_pos, tRailsPower[name] or 0
|
|
end
|
|
|
|
local function is_ramp(pos)
|
|
return tRailsExt[get_node_lvm({x = pos.x, y = pos.y + 1, z = pos.z}).name] ~= nil
|
|
end
|
|
|
|
-- Check also the next position to detect a ramp
|
|
local function slope_detection(pos, facedir)
|
|
local is_rail, new_pos = get_next_pos(pos, facedir, 0)
|
|
if not is_rail then
|
|
return is_ramp(new_pos)
|
|
end
|
|
end
|
|
|
|
local function find_next_waypoint(pos, facedir, y)
|
|
local cnt = 0
|
|
local name = get_node_lvm(pos).name
|
|
local speed = tRailsPower[name] or 0
|
|
local is_rail, new_pos, _speed
|
|
|
|
while cnt < MAX_NODES do
|
|
is_rail, new_pos, _speed = get_next_pos(pos, facedir, y)
|
|
speed = speed + _speed
|
|
if not is_rail then
|
|
return pos, y == 0 and is_ramp(new_pos), speed
|
|
end
|
|
if y == 0 then -- no slope
|
|
if check_right(new_pos, facedir) then
|
|
return new_pos, slope_detection(new_pos, facedir), speed
|
|
elseif check_left(new_pos, facedir) then
|
|
return new_pos, slope_detection(new_pos, facedir), speed
|
|
end
|
|
end
|
|
pos = new_pos
|
|
cnt = cnt + 1
|
|
end
|
|
return new_pos, false, speed
|
|
end
|
|
|
|
-------------------------------------------------------------------------------
|
|
-- find_all_next_waypoints
|
|
-------------------------------------------------------------------------------
|
|
local function check_front_up_down(pos, facedir)
|
|
local new_pos = vector.add(pos, facedir2dir(facedir))
|
|
|
|
if tRailsExt[get_node_lvm(new_pos).name] then
|
|
return 0
|
|
end
|
|
new_pos.y = new_pos.y - 1
|
|
if tRailsExt[get_node_lvm(new_pos).name] then
|
|
return -1
|
|
end
|
|
new_pos.y = new_pos.y + 2
|
|
if tRailsExt[get_node_lvm(new_pos).name] then
|
|
return 1
|
|
end
|
|
end
|
|
|
|
-- Search for rails in all 4 directions
|
|
local function find_all_rails_nearby(pos)
|
|
--print("find_all_rails_nearby")
|
|
local tbl = {}
|
|
for fd = 0, 3 do
|
|
tbl[#tbl + 1] = check_front_up_down(pos, fd, true)
|
|
end
|
|
return tbl
|
|
end
|
|
|
|
-- Recalc the value based on waypoint length and slope
|
|
local function recalc_speed(num_pow_rails, pos1, pos2, y)
|
|
local num_norm_rails = vector.distance(pos1, pos2) - num_pow_rails
|
|
local ratio, speed
|
|
|
|
if y ~= 0 then
|
|
num_norm_rails = math.floor(num_norm_rails / 1.41 + 0.5)
|
|
end
|
|
|
|
if y ~= -1 then
|
|
if num_pow_rails == 0 then
|
|
return num_norm_rails * -SLOWDOWN
|
|
else
|
|
ratio = math.floor(num_norm_rails / num_pow_rails)
|
|
ratio = minecart.range(ratio, 0, 11)
|
|
end
|
|
else
|
|
ratio = 3 + num_norm_rails * SLOWDOWN + num_pow_rails
|
|
end
|
|
|
|
if y == 1 then
|
|
speed = 7 - ratio
|
|
elseif y == -1 then
|
|
speed = 15 - ratio
|
|
else
|
|
speed = 11 - ratio
|
|
end
|
|
|
|
return minecart.range(speed, 0, 8)
|
|
end
|
|
|
|
local function find_all_next_waypoints(pos)
|
|
local wp = {}
|
|
local dots = {}
|
|
|
|
for facedir = 0,3 do
|
|
local y = check_front_up_down(pos, facedir)
|
|
if y then
|
|
local new_pos, is_ramp, speed = find_next_waypoint(pos, facedir, y)
|
|
--print("find_all_next_waypoints", P2S(new_pos), is_ramp, speed)
|
|
local dot = 1 + facedir * 4 + y
|
|
speed = recalc_speed(speed, pos, new_pos, y) * 10
|
|
wp[facedir] = {dot = dot, pos = new_pos, speed = speed, is_ramp = is_ramp}
|
|
end
|
|
end
|
|
|
|
return wp
|
|
end
|
|
|
|
-------------------------------------------------------------------------------
|
|
-- get_waypoint
|
|
-------------------------------------------------------------------------------
|
|
-- If ramp, stop 0.5 nodes earlier or later
|
|
local function ramp_correction(pos, wp, facedir)
|
|
if wp.is_ramp or pos.y < wp.pos.y then -- ramp detection
|
|
local dir = facedir2dir(facedir)
|
|
local pos = wp.pos
|
|
|
|
wp.cart_pos = {
|
|
x = pos.x - dir.x / 2,
|
|
y = pos.y,
|
|
z = pos.z - dir.z / 2}
|
|
elseif pos.y > wp.pos.y then
|
|
local dir = facedir2dir(facedir)
|
|
local pos = wp.pos
|
|
|
|
wp.cart_pos = {
|
|
x = pos.x + dir.x / 2,
|
|
y = pos.y,
|
|
z = pos.z + dir.z / 2}
|
|
end
|
|
return wp
|
|
end
|
|
|
|
-- Returns waypoint and is_junction
|
|
function minecart.get_waypoint(pos, facedir, ctrl, uturn)
|
|
local t = get_metadata(pos)
|
|
if not t then
|
|
t = find_all_next_waypoints(pos)
|
|
set_metadata(pos, t)
|
|
end
|
|
|
|
local left = (facedir + 3) % 4
|
|
local right = (facedir + 1) % 4
|
|
local back = (facedir + 2) % 4
|
|
|
|
if ctrl.right and t[right] then return t[right], t[facedir] ~= nil or t[left] ~= nil end
|
|
if ctrl.left and t[left] then return t[left] , t[facedir] ~= nil or t[right] ~= nil end
|
|
|
|
if t[facedir] then return ramp_correction(pos, t[facedir], facedir), false end
|
|
if t[right] then return ramp_correction(pos, t[right], right), false end
|
|
if t[left] then return ramp_correction(pos, t[left], left), false end
|
|
|
|
if uturn and t[back] then return t[back], false end
|
|
end
|
|
|
|
-------------------------------------------------------------------------------
|
|
-- delete waypoints
|
|
-------------------------------------------------------------------------------
|
|
local function delete_counterpart_metadata(pos, wp)
|
|
for facedir = 0,3 do
|
|
if wp[facedir] then
|
|
del_metadata(wp[facedir].pos)
|
|
end
|
|
end
|
|
del_metadata(pos)
|
|
end
|
|
|
|
local function delete_next_metadata(pos, facedir, y)
|
|
local cnt = 0
|
|
while cnt <= MAX_NODES do
|
|
local is_rail, new_pos = get_next_pos(pos, facedir, y)
|
|
if not is_rail then
|
|
return
|
|
end
|
|
|
|
if has_metadata(new_pos) then
|
|
del_metadata(new_pos)
|
|
end
|
|
|
|
pos = new_pos
|
|
cnt = cnt + 1
|
|
end
|
|
if has_metadata(pos) then
|
|
del_metadata(pos)
|
|
end
|
|
end
|
|
|
|
function minecart.delete_waypoint(pos)
|
|
if has_metadata(pos) then
|
|
local wp = get_metadata(pos)
|
|
delete_counterpart_metadata(pos, wp)
|
|
return
|
|
end
|
|
|
|
for facedir = 0,3 do
|
|
local y = check_front_up_down(pos, facedir)
|
|
if y then
|
|
local new_pos = vector.add(pos, facedir2dir(facedir))
|
|
new_pos.y = new_pos.y + y
|
|
if has_metadata(new_pos) then
|
|
local wp = get_metadata(new_pos)
|
|
delete_counterpart_metadata(new_pos, wp)
|
|
else
|
|
delete_next_metadata(pos, facedir, y)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-------------------------------------------------------------------------------
|
|
-- find next buffer (needed as starting position)
|
|
-------------------------------------------------------------------------------
|
|
local function get_next_waypoints(pos)
|
|
local t = get_metadata(pos)
|
|
if not t then
|
|
t = find_all_next_waypoints(pos)
|
|
end
|
|
return t
|
|
end
|
|
|
|
local function get_next_pos_and_facedir(waypoints, facedir)
|
|
local cnt = 0
|
|
local newpos, newfacedir
|
|
facedir = (facedir + 2) % 4 -- opposite dir
|
|
|
|
for i = 0, 3 do
|
|
if waypoints[i] then
|
|
cnt = cnt + 1
|
|
if i ~= facedir then -- not the same way back
|
|
newpos = vector.new(waypoints[i].pos)
|
|
newfacedir = i
|
|
end
|
|
end
|
|
end
|
|
|
|
-- no junction and valid facedir
|
|
if cnt < 3 and newfacedir then
|
|
return newpos, newfacedir
|
|
end
|
|
end
|
|
|
|
local function get_next_buffer(pos, facedir)
|
|
facedir = (facedir + 2) % 4 -- opposite dir
|
|
for i = 1,5 do -- limit search depth
|
|
local waypoints = get_next_waypoints(pos) or {}
|
|
local pos1, facedir1 = get_next_pos_and_facedir(waypoints, facedir)
|
|
if pos1 then
|
|
pos, facedir = pos1, facedir1
|
|
else
|
|
return minecart.find_node_near_lvm(pos, 1, {"minecart:buffer"})
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
carts:register_rail("minecart:rail", {
|
|
description = "Rail",
|
|
tiles = {
|
|
"carts_rail_straight.png^minecart_waypoint.png", "carts_rail_curved.png^minecart_waypoint.png",
|
|
"carts_rail_t_junction.png^minecart_waypoint.png", "carts_rail_crossing.png^minecart_waypoint.png"
|
|
},
|
|
inventory_image = "carts_rail_straight.png",
|
|
wield_image = "carts_rail_straight.png",
|
|
groups = carts:get_rail_groups({not_in_creative_inventory = 1}),
|
|
drop = "carts:rail",
|
|
}, {})
|
|
|
|
carts:register_rail("minecart:powerrail", {
|
|
description = "Powered Rail",
|
|
tiles = {
|
|
"carts_rail_straight_pwr.png^minecart_waypoint.png", "carts_rail_curved_pwr.png^minecart_waypoint.png",
|
|
"carts_rail_t_junction_pwr.png^minecart_waypoint.png", "carts_rail_crossing_pwr.png^minecart_waypoint.png"
|
|
},
|
|
inventory_image = "carts_rail_straight.png",
|
|
wield_image = "carts_rail_straight.png",
|
|
groups = carts:get_rail_groups({not_in_creative_inventory = 1}),
|
|
drop = "carts:powerrail",
|
|
}, {})
|
|
|
|
for name,_ in pairs(tRails) do
|
|
minetest.override_item(name, {
|
|
after_destruct = minecart.delete_waypoint,
|
|
after_place_node = minecart.delete_waypoint,
|
|
})
|
|
end
|
|
|
|
-------------------------------------------------------------------------------
|
|
-- API functions
|
|
-------------------------------------------------------------------------------
|
|
-- Return new cart pos and if an extra move cycle is needed
|
|
function minecart.get_current_cart_pos_correction(curr_pos, curr_fd, curr_y, new_dot)
|
|
if new_dot then
|
|
local new_y = (new_dot % 4) - 1
|
|
local new_fd = math.floor(new_dot / 4)
|
|
|
|
if curr_y == -1 or new_y == -1 then
|
|
local new_fd = math.floor(new_dot / 4)
|
|
local dir = facedir2dir(new_fd)
|
|
return {
|
|
x = curr_pos.x + dir.x / 2,
|
|
y = curr_pos.y,
|
|
z = curr_pos.z + dir.z / 2}, new_y == -1
|
|
elseif curr_y == 1 and curr_fd ~= new_fd then
|
|
local dir = facedir2dir(new_fd)
|
|
return {
|
|
x = curr_pos.x + dir.x / 2,
|
|
y = curr_pos.y,
|
|
z = curr_pos.z + dir.z / 2}, true
|
|
elseif curr_y == 1 or new_y == 1 then
|
|
local dir = facedir2dir(curr_fd)
|
|
return {
|
|
x = curr_pos.x - dir.x / 2,
|
|
y = curr_pos.y,
|
|
z = curr_pos.z - dir.z / 2}, false
|
|
end
|
|
end
|
|
return curr_pos, false
|
|
end
|
|
|
|
-- Called by carts, returns the speed value or nil
|
|
function minecart.get_speedlimit(pos, facedir)
|
|
local fd = (facedir + 1) % 4 -- right
|
|
local new_pos = vector.add(pos, facedir2dir(fd))
|
|
local node = get_node_lvm(new_pos)
|
|
if tSigns[node.name] and node.param2 == facedir then
|
|
return tSigns[node.name]
|
|
end
|
|
|
|
fd = (facedir + 3) % 4 -- left
|
|
new_pos = vector.add(pos, facedir2dir(fd))
|
|
node = get_node_lvm(new_pos)
|
|
if tSigns[node.name] and node.param2 == facedir then
|
|
return tSigns[node.name]
|
|
end
|
|
end
|
|
|
|
-- Called by carts, to delete temporarily created waypoints
|
|
function minecart.delete_cart_waypoint(pos)
|
|
del_metadata(pos)
|
|
end
|
|
|
|
-- Called by signs, to delete the rail waypoints nearby
|
|
function minecart.delete_signs_waypoint(pos)
|
|
local node = minetest.get_node(pos)
|
|
local facedir = (node.param2 + 1) % 4 -- right
|
|
local new_pos = vector.add(pos, facedir2dir(facedir))
|
|
if tRailsExt[get_node_lvm(new_pos).name] then
|
|
minecart.delete_waypoint(new_pos)
|
|
end
|
|
|
|
facedir = (node.param2 + 3) % 4 -- left
|
|
new_pos = vector.add(pos, facedir2dir(facedir))
|
|
if tRailsExt[get_node_lvm(new_pos).name] then
|
|
minecart.delete_waypoint(new_pos)
|
|
end
|
|
end
|
|
|
|
function minecart.is_rail(pos)
|
|
return tRails[get_node_lvm(pos).name] ~= nil
|
|
end
|
|
|
|
-- To register node cart names
|
|
function minecart.add_raillike_nodes(name)
|
|
tRailsExt[name] = true
|
|
lRailsExt[#lRailsExt + 1] = name
|
|
end
|
|
|
|
-- minecart.get_next_buffer(pos, facedir)
|
|
minecart.get_next_buffer = get_next_buffer
|
|
|
|
--minetest.register_lbm({
|
|
-- label = "Delete waypoints",
|
|
-- name = "minecart:del_meta",
|
|
-- nodenames = {"minecart:rail", "minecart:powerrail"},
|
|
-- run_at_every_load = true,
|
|
-- action = function(pos, node)
|
|
-- del_metadata(pos)
|
|
-- end,
|
|
--})
|
|
|
|
|