Make signal influence point (~halt point) specifiable

Also extend signal api necessarily
master
orwell96 2018-10-09 11:36:34 +02:00
parent 8df7bcf6b6
commit 8f8f009425
6 changed files with 300 additions and 15 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B