minetest-mm/mods/advtrains/advtrains_luaautomation/atc_rail.lua
2024-12-19 12:55:40 +01:00

290 lines
9.2 KiB
Lua

-- atc_rail.lua
-- registers and handles the ATC rail. Active component.
-- This is the only component that can interface with trains, so train interface goes here too.
--Using subtable
local r={}
-- Note on appr_internal:
-- The Approach callback is a special corner case: the train is not on the node, and it is executed synchronized
-- (in the train step right during LZB traversal). We therefore need access to the train id and the lzbdata table
function r.fire_event(pos, evtdata, appr_internal)
local ph=minetest.pos_to_string(pos)
local railtbl = atlatc.active.nodes[ph]
if not railtbl then
atwarn("LuaATC interface rail at",ph,": Data not in memory! Please visit position and click 'Save'!")
return
end
--prepare ingame API for ATC. Regenerate each time since pos needs to be known
--If no train, then return false.
-- try to get the train from the event data
-- This workaround is required because the callback is one step delayed, and a fast train may have already left the node.
-- Also used for approach callback
local train_id = evtdata._train_id
local atc_arrow = evtdata._train_arrow
local train, tvel
if train_id then
train=advtrains.trains[train_id]
-- speed
tvel=train.velocity
-- if still no train_id available, try to get the train at my position
else
train_id=advtrains.get_train_at_pos(pos)
if train_id then
train=advtrains.trains[train_id]
advtrains.train_ensure_init(train_id, train)
-- look up atc_arrow
local index = advtrains.path_lookup(train, pos)
atc_arrow = (train.path_cn[index] == 1)
-- speed
tvel=train.velocity
end
end
local customfct={
atc_send = function(cmd)
if not train_id then return false end
assertt(cmd, "string")
advtrains.atc.train_set_command(train, cmd, atc_arrow)
return true
end,
split_at_index = function(index, cmd)
if not train_id then return false end
assertt(cmd, "string")
if type(index) ~= "number" or index < 2 then
return false
end
local new_id = advtrains.split_train_at_index(train, index)
if new_id then
minetest.after(1,advtrains.atc.train_set_command,advtrains.trains[new_id], cmd, atc_arrow)
return true
end
return false
end,
split_at_fc = function(cmd, len)
assertt(cmd, "string")
if not train_id then return false end
local new_id, fc = advtrains.split_train_at_fc(train, false, len)
if new_id then
minetest.after(1,advtrains.atc.train_set_command,advtrains.trains[new_id], cmd, atc_arrow)
end
return fc or ""
end,
split_off_locomotive = function(cmd, len)
assertt(cmd, "string")
if not train_id then return false end
local new_id, fc = advtrains.split_train_at_fc(train, true, len)
if new_id then
minetest.after(1,advtrains.atc.train_set_command,advtrains.trains[new_id], cmd, atc_arrow)
end
end,
train_length = function ()
if not train_id then return false end
return #train.trainparts
end,
step_fc = function()
if not train_id then return false end
advtrains.train_step_fc(train)
end,
get_fc = function()
if not train_id then return end
local fc_list = {}
for index,wagon_id in ipairs(train.trainparts) do
fc_list[index] = table.concat(advtrains.wagons[wagon_id].fc or {},"!")
end
return fc_list
end,
get_fc_index = function()
if not train_id then return end
local fc_index_list = {}
for widx, wagon_id in ipars(train.trainparts) do
fc_index_list[widx] = advtrains.wagons[wagon_id].fcind or 1
end
return fc_index_list
end,
set_fc = function(fc_list,reset_index)
assertt(fc_list, "table")
if not train_id then return false end
-- safety type-check for entered values
for _,v in ipairs(fc_list) do
if v and type(v) ~= "string" then
error("FC entries must be a string")
return
end
end
for index,wagon_id in ipairs(train.trainparts) do
if fc_list[index] then -- has FC to enter to this wagon
local data = advtrains.wagons[wagon_id]
if data then -- wagon actually exists
--effectively copyied from wagons.lua, allowing for the :split function and reset_index
data.fc = fc_list[index]:split("!")
if reset_index or not data.fcind then
data.fcind = 1
elseif data.fcind > #data.fc then
data.fcind = #data.fc
end
end
end
end
end,
set_shunt = function()
-- enable shunting mode
if not train_id then return false end
train.is_shunt = true
end,
unset_shunt = function()
if not train_id then return false end
train.is_shunt = nil
end,
set_autocouple = function ()
if not train_id then return false end
train.autocouple = true
end,
unset_autocouple = function ()
if not train_id then return false end
train.autocouple = nil
end,
set_line = function(line)
if type(line)~="string" and type(line)~="number" then
return false
end
train.line = line .. ""
minetest.after(0, advtrains.invalidate_path, train_id)
return true
end,
get_line = function()
return train.line
end,
set_rc = function(rc)
if type(rc)~="string"then
return false
end
train.routingcode = rc
minetest.after(0, advtrains.invalidate_path, train_id)
return true
end,
get_rc = function()
return train.routingcode
end,
atc_reset = function()
if not train_id then return false end
advtrains.atc.train_reset_command(train)
return true
end,
atc_arrow = atc_arrow,
atc_id = train_id,
atc_speed = tvel,
atc_set_text_outside = function(text)
if not train_id then return false end
if text then assertt(text, "string") end
advtrains.trains[train_id].text_outside=text
return true
end,
atc_set_text_inside = function(text)
if not train_id then return false end
if text then assertt(text, "string") end
advtrains.trains[train_id].text_inside=text
return true
end,
atc_get_text_outside = function()
if not train_id then return false end
return advtrains.trains[train_id].text_outside
end,
atc_get_text_inside = function(text)
if not train_id then return false end
return advtrains.trains[train_id].text_inside
end,
atc_set_lzb_tsr = function(speed)
if not appr_internal then
error("atc_set_lzb_tsr() can only be used during 'approach' events!")
end
assert(tonumber(speed), "Number expected!")
local index = appr_internal.index
advtrains.lzb_add_checkpoint(train, index, speed, nil)
return true
end,
}
-- interlocking specific
if advtrains.interlocking then
customfct.atc_set_ars_disable = function(value)
advtrains.interlocking.ars_set_disable(train, value)
end
end
atlatc.active.run_in_env(pos, evtdata, customfct)
end
advtrains.register_tracks("default", {
nodename_prefix="advtrains_luaautomation:dtrack",
texture_prefix="advtrains_dtrack_atc",
models_prefix="advtrains_dtrack",
models_suffix=".b3d",
shared_texture="advtrains_dtrack_shared_atc.png",
description=atltrans("LuaATC Track"),
formats={},
get_additional_definiton = function(def, preset, suffix, rotation)
return {
after_place_node = atlatc.active.after_place_node,
after_dig_node = atlatc.active.after_dig_node,
on_receive_fields = function(pos, ...)
atlatc.active.on_receive_fields(pos, ...)
--set arrowconn (for ATC)
local ph=minetest.pos_to_string(pos)
local _, conns=advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
local nodeent = atlatc.active.nodes[ph]
if nodeent then
nodeent.arrowconn=conns[1].c
end
end,
advtrains = {
on_train_enter = function(pos, train_id, train, index)
--do async. Event is fired in train steps
atlatc.interrupt.add(0, pos, {type="train", train=true, id=train_id,
_train_id = train_id, _train_arrow = (train.path_cn[index] == 1)})
end,
on_train_approach = function(pos, train_id, train, index, has_entered, lzbdata)
-- Insert an event only if the rail indicated that it supports approach callbacks
local ph=minetest.pos_to_string(pos)
local railtbl = atlatc.active.nodes[ph]
-- uses a "magic variable" in the local environment of the node
-- This hack is necessary because code might not be prepared to get approach events...
if railtbl and railtbl.data and railtbl.data.__approach_callback_mode then
local acm = railtbl.data.__approach_callback_mode
local in_arrow = (train.path_cn[index] == 1)
if acm==2 or (acm==1 and in_arrow) then
local evtdata = {type="approach", approach=true, id=train_id, has_entered = has_entered,
_train_id = train_id, _train_arrow = in_arrow} -- reuses code from train_enter
-- This event is *required* to run synchronously, because it might set the ars_disable flag on the train and add LZB checkpoints,
-- although this is generally discouraged because this happens right in a train step
-- At this moment, I am not aware whether this may cause side effects, and I must encourage users not to do expensive calculations here.
r.fire_event(pos, evtdata, {train_id = train_id, train = train, index = index, lzbdata = lzbdata})
end
end
end,
},
luaautomation = {
fire_event=r.fire_event
},
digiline = {
receptor = {},
effector = {
action = atlatc.active.on_digiline_receive
},
},
}
end,
}, advtrains.trackpresets.t_30deg_straightonly)
atlatc.rail = r