aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--advtrains/signals.lua26
-rw-r--r--advtrains_interlocking/database.lua66
-rw-r--r--advtrains_interlocking/route_prog.lua1
-rw-r--r--advtrains_interlocking/signal_api.lua197
-rw-r--r--advtrains_interlocking/tcb_ts_ui.lua25
-rw-r--r--advtrains_interlocking/textures/at_il_signal_ip.pngbin0 -> 285 bytes
6 files changed, 300 insertions, 15 deletions
diff --git a/advtrains/signals.lua b/advtrains/signals.lua
index 75f9213..669e825 100644
--- a/advtrains/signals.lua
+++ b/advtrains/signals.lua
@@ -10,6 +10,23 @@ local function can_dig_func(pos)
return true
end
+local function aspect(b)
+return {
+ main = {
+ free = b,
+ speed = -1,
+ },
+ shunt = {
+ free = false,
+ },
+ dst = {
+ free = true,
+ speed = -1,
+ },
+ info = {}
+}
+end
+
for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", als="green"}}) do
advtrains.trackplacer.register_tracktype("advtrains:retrosignal", "")
@@ -64,6 +81,9 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
else
advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_off"..rotation, param2 = node.param2}, true)
end
+ end,
+ get_aspect = function(pos, node)
+ return aspect(r=="on")
end
},
can_dig = can_dig_func,
@@ -117,6 +137,9 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_off"..rotation, param2 = node.param2}, true)
end
end,
+ get_aspect = function(pos, node)
+ return aspect(r=="on")
+ end,
getstate = f.ls,
setstate = function(pos, node, newstate)
if newstate == f.als then
@@ -180,6 +203,9 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_off", param2 = node.param2}, true)
end
end,
+ get_aspect = function(pos, node)
+ return aspect(r=="on")
+ end,
getstate = f.ls,
setstate = function(pos, node, newstate)
if newstate == f.als then
diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua
index f3df5e1..030a5e0 100644
--- a/advtrains_interlocking/database.lua
+++ b/advtrains_interlocking/database.lua
@@ -1,6 +1,9 @@
-- interlocking/database.lua
-- saving the location of TCB's, their neighbors and their state
--[[
+
+== THIS COMMENT IS PARTIALLY INCORRECT AND OUTDATED! ==
+
The interlocking system is based on track circuits.
Track circuit breaks must be manually set by the user. Signals must be assigned to track circuit breaks and to a direction(connid).
To simplify the whole system, there is no overlap.
@@ -96,8 +99,12 @@ local ildb = {}
local track_circuit_breaks = {}
local track_sections = {}
+-- Assignment of signals to TCBs
local signal_assignments = {}
+-- track+direction -> signal position
+local influence_points = {}
+
function ildb.load(data)
if not data then return end
if data.tcbs then
@@ -112,6 +119,9 @@ function ildb.load(data)
if data.rs_locks then
advtrains.interlocking.route.rte_locks = data.rs_locks
end
+ if data.influence_points then
+ influence_points = data.influence_points
+ end
end
function ildb.save()
@@ -120,6 +130,7 @@ function ildb.save()
ts=track_sections,
signalass = signal_assignments,
rs_locks = advtrains.interlocking.route.rte_locks,
+ influence_points = influence_points,
}
end
@@ -455,6 +466,61 @@ function ildb.set_sigd_for_signal(pos, sigd)
end
+-- checks if a signal is influencing here
+function ildb.get_ip_signal(pts, connid)
+ if influence_points[pts] then
+ return influence_points[pts][connid]
+ end
+end
+
+-- Tries to get aspect to obey here, if there
+-- is a signal ip at this location
+-- auto-clears invalid assignments
+function ildb.get_ip_signal_asp(pts, connid)
+ local p = ildb.get_ip_signal(pts, connid)
+ if p then
+ local asp = advtrains.interlocking.signal_get_aspect(p)
+ if not asp then
+ atlog("Clearing orphaned signal influence point", pts, "/", connid)
+ ildb.clear_ip_signal(pts, connid)
+ return nil
+ end
+ return asp
+ end
+ return nil
+end
+
+-- set signal assignment.
+function ildb.set_ip_signal(pts, connid, spos)
+ if not influence_points[pts] then
+ influence_points[pts] = {}
+ end
+ influence_points[pts][connid] = spos
+end
+-- clear signal assignment.
+function ildb.clear_ip_signal(pts, connid)
+ influence_points[pts][connid] = nil
+ for _,_ in pairs(influence_points[pts]) do
+ return
+ end
+ influence_points[pts] = nil
+end
+
+function ildb.get_ip_by_signalpos(spos)
+ for pts,tab in pairs(influence_points) do
+ for connid,pos in pairs(tab) do
+ if vector.equals(pos, spos) then
+ return pts, connid
+ end
+ end
+ end
+end
+-- clear signal assignment given the signal position
+function ildb.clear_ip_by_signalpos(spos)
+ local pts, connid = ildb.get_ip_by_signalpos(spos)
+ if pts then ildb.clear_ip_signal(pts, connid) end
+end
+
advtrains.interlocking.db = ildb
diff --git a/advtrains_interlocking/route_prog.lua b/advtrains_interlocking/route_prog.lua
index 7abca664..2ec9375 100644
--- a/advtrains_interlocking/route_prog.lua
+++ b/advtrains_interlocking/route_prog.lua
@@ -227,7 +227,6 @@ function advtrains.interlocking.init_route_prog(pname, sigd)
}
advtrains.interlocking.visualize_route(sigd, player_rte_prog[pname].route, "prog_"..pname, player_rte_prog[pname].tmp_lcks, pname)
minetest.chat_send_player(pname, "Route programming mode active. Punch TCBs to add route segments, punch turnouts to lock them.")
- minetest.chat_send_player(pname, "Type /at_rp_set <name> when you are done, /at_rp_discard to cancel route programming")
end
local function get_last_route_item(origin, route)
diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua
index cd1ab54..90fe1fc 100644
--- a/advtrains_interlocking/signal_api.lua
+++ b/advtrains_interlocking/signal_api.lua
@@ -1,7 +1,8 @@
-- Signal API implementation
---[[ Signal aspect table:
+--[[
+Signal aspect table:
asp = {
main = {
free = <boolean>,
@@ -9,6 +10,14 @@ asp = {
},
shunt = {
free = <boolean>,
+ -- Whether train may proceed as shunt move, on sight
+ -- main aspect takes precedence over this
+ proceed_as_main = <boolean>,
+ -- If an approaching train is a shunt move and "main.free" is set,
+ -- the train may proceed as a train move under the "main" aspect
+ -- If this is not set, shunt moves are NOT allowed to switch to
+ -- a train move, and must stop even if "main.free" is set.
+ -- This is intended to be used for "Halt for shunt moves" signs.
}
dst = {
free = <boolean>,
@@ -17,21 +26,69 @@ asp = {
info = {
call_on = <boolean>, -- Call-on route, expect train in track ahead
dead_end = <boolean>, -- Route ends on a dead end (e.g. bumper)
+ w_speed = <integer>,
+ -- "Warning speed restriction". Supposed for short-term speed
+ -- restrictions which always override any other restrictions
+ -- imposed by "speed" fields, until lifted by a value of -1
}
}
-Signals API:
+-- For "speed" and "w_speed" fields, a value of -1 means that the
+-- restriction is lifted. If they are omitted, the value imposed at
+-- the last aspect received remains valid.
+-- The "dst" subtable can be completely omitted when no explicit dst
+-- aspect should be signalled to the train. In this case, the last
+-- signalled dst aspect remains valid.
+
+== How signals actually work in here ==
+Each signal (in the advtrains universe) is some node that has at least the
+following things:
+- An "influence point" that is set somewhere on a rail
+- An aspect which trains that pass the "influence point" have to obey
+
+There can be static and dynamic signals. Static signals are, roughly
+spoken, signs, while dynamic signals are "real" signals which can display
+different things.
+
+The node definition of a signal node should contain those fields:
groups = {
- advtrains_signal = 2,
+ advtrains_signal = 2,
save_in_at_nodedb = 1,
}
advtrains = {
function set_aspect(pos, node, asp)
- ...
+ -- This function gets called whenever the signal should display
+ -- a new or changed signal aspect. It is not required that
+ -- the signal actually displays the exact same aspect, since
+ -- some signals can not do this by design.
+ -- Example: pure shunt signals can not display a "main" aspect
+ -- and have no effect on train moves, so they will only ever
+ -- honor the shunt.free field for their aspect.
+
+ -- The aspect passed in here can always be queried using the
+ -- advtrains.interlocking.signal_get_supposed_aspect(pos) function.
+
+ -- For static signals, this function should be completely omitted
+ -- If this function is ommitted, it won't be possible to use
+ -- route setting on this signal.
+ end
+ function get_aspect(pos, node)
+ -- This function gets called by the train safety system. It
+ should return the aspect that this signal actually displays,
+ not preferably the input of set_aspect.
+ -- For regular, full-featured light signals, they will probably
+ honor all entries in the original aspect, however, e.g.
+ simple shunt signals always return main.free=true regardless of
+ the set_aspect input because they can not signal "Halt" to
+ train moves.
+ -- advtrains.interlocking.DANGER contains a default "all-danger" aspect.
end
}
on_rightclick = advtrains.interlocking.signal_rc_handler
can_dig = advtrains.interlocking.signal_can_dig
+after_dig_node = advtrains.interlocking.signal_after_dig
+(If you need to specify custom can_dig or after_dig_node callbacks,
+please call those functions anyway!)
]]--
local DANGER = {
@@ -48,6 +105,7 @@ local DANGER = {
},
info = {}
}
+advtrains.interlocking.DANGER = DANGER
function advtrains.interlocking.update_signal_aspect(tcbs)
if tcbs.signal then
@@ -60,6 +118,11 @@ function advtrains.interlocking.signal_can_dig(pos)
return not advtrains.interlocking.db.get_sigd_for_signal(pos)
end
+function advtrains.interlocking.signal_after_dig(pos)
+ -- clear influence point
+ advtrains.interlocking.db.clear_ip_by_signalpos(pos)
+end
+
function advtrains.interlocking.signal_set_aspect(pos, asp)
local node=advtrains.ndb.get_node(pos)
local ndef=minetest.registered_nodes[node.name]
@@ -75,7 +138,7 @@ function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack,
advtrains.interlocking.show_signalling_form(sigd, pname)
else
-- permit to set aspect manually
- minetest.show_formspec(pname, "at_il_sigasp_"..minetest.pos_to_string(pos), "field[aspect;Set Aspect (F/D)Speed(F/D)Speed(F/D);D0D0D]")
+ minetest.show_formspec(pname, "at_il_sigasp_"..minetest.pos_to_string(pos), "field[aspect;Set Aspect (F/D)Speed(F/D)Speed(F/D) ['A' to assign IP];D0D0D]")
end
end
@@ -85,6 +148,10 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local pos
if pts then pos = minetest.string_to_pos(pts) end
if pos and fields.aspect then
+ if fields.aspect == "A" then
+ advtrains.interlocking.show_ip_form(pos, pname)
+ return
+ end
local mfs, msps, dfs, dsps, shs = string.match(fields.aspect, "^([FD])([0-9]+)([FD])([0-9]+)([FD])$")
local asp = {
main = {
@@ -108,7 +175,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end)
-- Returns the aspect the signal at pos is supposed to show
-function advtrains.interlocking.signal_get_aspect(pos)
+function advtrains.interlocking.signal_get_supposed_aspect(pos)
local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos)
if sigd then
local tcbs = advtrains.interlocking.db.get_tcbs(sigd)
@@ -118,3 +185,121 @@ function advtrains.interlocking.signal_get_aspect(pos)
end
return DANGER;
end
+
+-- Returns the actual aspect of the signal at position, as returned by the nodedef.
+-- returns nil
+function advtrains.interlocking.signal_get_aspect(pos)
+ local node=advtrains.ndb.get_node(pos)
+ local ndef=minetest.registered_nodes[node.name]
+ if ndef and ndef.advtrains and ndef.advtrains.get_aspect then
+ return ndef.advtrains.get_aspect(pos, node)
+ end
+end
+
+local players_assign_ip = {}
+
+-- shows small info form for signal IP state/assignment
+-- only_notset: show only if it is not set yet (used by signal tcb assignment)
+function advtrains.interlocking.show_ip_form(pos, pname, only_notset)
+ local form = "size[7,5]label[0.5,0.5;Signal at "..minetest.pos_to_string(pos).."]"
+ local pts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos)
+ if pts then
+ form = form.."label[0.5,1.5;Influence point is set at "..pts.."/"..connid.."]"
+ form = form.."button_exit[0.5,2.5; 5,1;show;Show]"
+ form = form.."button_exit[0.5,3.5; 5,1;clear;Clear]"
+ else
+ form = form.."label[0.5,1.5;Influence point is not set.]"
+ form = form.."label[0.5,2.0;It is recommended to set an influence point.]"
+ form = form.."label[0.5,2.5;This is the point where trains will obey the signal.]"
+
+ form = form.."button_exit[0.5,3.5; 5,1;set;Set]"
+ end
+ if not only_notset or not pts then
+ minetest.show_formspec(pname, "at_il_ipassign_"..minetest.pos_to_string(pos), form)
+ end
+end
+
+local function ipmarker(ipos, connid)
+ local node_ok, conns, rhe = advtrains.get_rail_info_at(ipos, advtrains.all_tracktypes)
+ if not node_ok then return end
+ local yaw = advtrains.dir_to_angle(conns[connid].c)
+
+ -- using tcbmarker here
+ local obj = minetest.add_entity(vector.add(ipos, {x=0, y=0.2, z=0}), "advtrains_interlocking:tcbmarker")
+ if not obj then return end
+ obj:set_yaw(yaw)
+ obj:set_properties({
+ textures = { "at_il_signal_ip.png" },
+ })
+end
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ local pname = player:get_player_name()
+ if not minetest.check_player_privs(pname, {train_operator=true, interlocking=true}) then
+ return
+ end
+ local pts = string.match(formname, "^at_il_ipassign_([^_]+)$")
+ local pos
+ if pts then
+ pos = minetest.string_to_pos(pts)
+ end
+ if pos then
+ if fields.set then
+ advtrains.interlocking.signal_init_ip_assign(pos, pname)
+ elseif fields.clear then
+ advtrains.interlocking.db.clear_ip_by_signalpos(pos)
+ elseif fields.show then
+ local ipts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos)
+ if not ipts then return end
+ local ipos = minetest.string_to_pos(ipts)
+ ipmarker(ipos, connid)
+ end
+ end
+end)
+
+-- inits the signal IP assignment process
+function advtrains.interlocking.signal_init_ip_assign(pos, pname)
+ if not minetest.check_player_privs(pname, "interlocking") then
+ minetest.chat_send_player(pname, "Insufficient privileges to use this!")
+ return
+ end
+ --remove old IP
+ advtrains.interlocking.db.clear_ip_by_signalpos(pos)
+ minetest.chat_send_player(pname, "Configuring Signal: Please look in train's driving direction and punch rail to set influence point.")
+
+ players_assign_ip[pname] = pos
+end
+
+minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
+ local pname = player:get_player_name()
+ if not minetest.check_player_privs(pname, "interlocking") then
+ return
+ end
+ -- IP assignment
+ local signalpos = players_assign_ip[pname]
+ if signalpos then
+ if vector.distance(pos, signalpos)<=50 then
+ local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
+ if node_ok and #conns == 2 then
+
+ local yaw = player:get_look_horizontal()
+ local plconnid = advtrains.yawToClosestConn(yaw, conns)
+
+ -- add assignment if not already present.
+ local pts = advtrains.roundfloorpts(pos)
+ if not advtrains.interlocking.db.get_ip_signal_asp(pts, plconnid) then
+ advtrains.interlocking.db.set_ip_signal(pts, plconnid, signalpos)
+ ipmarker(pos, plconnid)
+ minetest.chat_send_player(pname, "Configuring Signal: Successfully set influence point")
+ else
+ minetest.chat_send_player(pname, "Configuring Signal: Influence point of another signal is already present!")
+ end
+ else
+ minetest.chat_send_player(pname, "Configuring Signal: This is not a normal two-connection rail! Aborted.")
+ end
+ else
+ minetest.chat_send_player(pname, "Configuring Signal: Node is too far away. Aborted.")
+ end
+ players_assign_ip[pname] = nil
+ end
+end)
diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua
index 5ef7ca9..ea0e736 100644
--- a/advtrains_interlocking/tcb_ts_ui.lua
+++ b/advtrains_interlocking/tcb_ts_ui.lua
@@ -49,8 +49,11 @@ minetest.register_node("advtrains_interlocking:tcb_node", {
local tcbpos = minetest.string_to_pos(tcbpts)
advtrains.interlocking.show_tcb_form(tcbpos, pname)
else
+ if not minetest.check_player_privs(pname, "interlocking") then
+ minetest.chat_send_player(pname, "Insufficient privileges to use this!")
+ return
+ end
--unconfigured
- --TODO security
minetest.chat_send_player(pname, "Configuring TCB: Please punch the rail you want to assign this TCB to.")
players_assign_tcb[pname] = pos
@@ -143,6 +146,7 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
tcbs.routes = {}
ildb.set_sigd_for_signal(pos, sigd)
minetest.chat_send_player(pname, "Configuring TCB: Successfully assigned signal.")
+ advtrains.interlocking.show_ip_form(pos, pname, true)
else
minetest.chat_send_player(pname, "Configuring TCB: Internal error, TCBS doesn't exist. Aborted.")
end
@@ -173,11 +177,11 @@ local function mktcbformspec(tcbs, btnpref, offset, pname)
tcbs.ts_id = nil
form = form.."label[0.5,"..offset..";Side "..btnpref..": ".."End of interlocking]"
form = form.."button[0.5,"..(offset+0.5)..";5,1;"..btnpref.."_makeil;Create Interlocked Track Section]"
- if tcbs.section_free then
- form = form.."button[0.5,"..(offset+1.5)..";5,1;"..btnpref.."_setlocked;Section is free]"
- else
- form = form.."button[0.5,"..(offset+1.5)..";5,1;"..btnpref.."_setfree;Section is blocked]"
- end
+ --if tcbs.section_free then
+ --form = form.."button[0.5,"..(offset+1.5)..";5,1;"..btnpref.."_setlocked;Section is free]"
+ --else
+ --form = form.."button[0.5,"..(offset+1.5)..";5,1;"..btnpref.."_setfree;Section is blocked]"
+ --end
end
if tcbs.signal then
form = form.."button[0.5,"..(offset+2.5)..";5,1;"..btnpref.."_sigdia;Signalling]"
@@ -483,7 +487,7 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte)
if not tcbs.signal_name then tcbs.signal_name = "Signal at "..minetest.pos_to_string(sigd.p) end
if not tcbs.routes then tcbs.routes = {} end
- local form = "size[7,9]label[0.5,0.5;Signal at "..minetest.pos_to_string(sigd.p).."]"
+ local form = "size[7,10]label[0.5,0.5;Signal at "..minetest.pos_to_string(sigd.p).."]"
form = form.."field[0.8,1.5;5.2,1;name;Signal name;"..tcbs.signal_name.."]"
form = form.."button[5.5,1.2;1,1;setname;Set]"
@@ -524,12 +528,13 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte)
form = form.."button[0.5,7;2,1;dsproute;Show]"
if hasprivs then
form = form.."button[2.5,7;1,1;delroute;Delete]"
- form = form.."button[3.5,7;2,1;renroute;Rename]"
+ form = form.."button[3.5,7;2,1;editroute;Edit]"
end
end
if hasprivs then
form = form.."button[0.5,8;2.5,1;newroute;New Route]"
form = form.."button[ 3,8;2.5,1;unassign;Unassign Signal]"
+ form = form.."button[ 3,9;2.5,1;influp;Influence Point]"
end
end
sig_pselidx[pname] = sel_rte
@@ -617,6 +622,10 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
minetest.chat_send_player(pname, "Please cancel route first!")
end
end
+ if fields.influp and hasprivs then
+ advtrains.interlocking.show_ip_form(tcbs.signal, pname)
+ return
+ end
if fields.auto then
tcbs.route_auto = true
diff --git a/advtrains_interlocking/textures/at_il_signal_ip.png b/advtrains_interlocking/textures/at_il_signal_ip.png
new file mode 100644
index 0000000..bf1618a
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_signal_ip.png
Binary files differ