From 8f8f009425a4d3341d3d00e6a537b5af320b5639 Mon Sep 17 00:00:00 2001 From: orwell96 Date: Tue, 9 Oct 2018 11:36:34 +0200 Subject: Make signal influence point (~halt point) specifiable Also extend signal api necessarily --- advtrains_interlocking/database.lua | 66 +++++++ advtrains_interlocking/route_prog.lua | 1 - advtrains_interlocking/signal_api.lua | 197 ++++++++++++++++++++- advtrains_interlocking/tcb_ts_ui.lua | 25 ++- .../textures/at_il_signal_ip.png | Bin 0 -> 285 bytes 5 files changed, 274 insertions(+), 15 deletions(-) create mode 100644 advtrains_interlocking/textures/at_il_signal_ip.png (limited to 'advtrains_interlocking') 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 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 = , @@ -9,6 +10,14 @@ asp = { }, shunt = { free = , + -- Whether train may proceed as shunt move, on sight + -- main aspect takes precedence over this + proceed_as_main = , + -- 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 = , @@ -17,21 +26,69 @@ asp = { info = { call_on = , -- Call-on route, expect train in track ahead dead_end = , -- Route ends on a dead end (e.g. bumper) + w_speed = , + -- "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 Binary files /dev/null and b/advtrains_interlocking/textures/at_il_signal_ip.png differ -- cgit v1.2.3