From 922e654b7bef51c7ddaf510ec70880d48181dd35 Mon Sep 17 00:00:00 2001 From: orwell Date: Mon, 25 Nov 2024 22:31:26 +0100 Subject: Make Buffers become implicitly their own TCBs and signals when interlocking is enabled --- advtrains_interlocking/database.lua | 41 +++++++++- advtrains_interlocking/mod.conf | 2 +- advtrains_interlocking/route_prog.lua | 7 +- advtrains_interlocking/signal_api.lua | 23 ++++-- advtrains_interlocking/tcb_ts_ui.lua | 146 ++++++++++++++++++++++++++++++++-- 5 files changed, 198 insertions(+), 21 deletions(-) (limited to 'advtrains_interlocking') diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index e77d073..75247de 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -802,10 +802,18 @@ function ildb.create_tcb_at(pos) end -- Remove TCB at the position and update/repair the now joined section -function ildb.remove_tcb_at(pos) +-- skip_tsrepair: should be set to true when the rail node at the TCB position is already gone. +-- Assumes the track sections are now separated and does not attempt the repair process. +function ildb.remove_tcb_at(pos, pname, skip_tsrepair) --atdebug("remove_tcb_at",pos) local pts = advtrains.encode_pos(pos) local old_tcb = track_circuit_breaks[pts] + -- unassign signals if defined + for connid=1,2 do + if old_tcb[connid].signal then + ildb.set_sigd_for_signal(old_tcb[connid].signal, nil) + end + end track_circuit_breaks[pts] = nil -- purge the track sections adjacent if old_tcb[1].ts_id then @@ -823,7 +831,9 @@ function ildb.remove_tcb_at(pos) end advtrains.interlocking.remove_tcb_marker(pos) -- If needed, merge the track sections here - ildb.check_and_repair_ts_at_pos(pos, nil) + if not skip_tsrepair then + ildb.check_and_repair_ts_at_pos(pos, pname) + end return true end @@ -973,11 +983,34 @@ function ildb.get_sigd_for_signal(pos) end return nil end -function ildb.set_sigd_for_signal(pos, sigd) +function ildb.set_sigd_for_signal(pos, sigd) -- do not use! local pts = advtrains.roundfloorpts(pos) signal_assignments[pts] = sigd end +-- Assign the signal at pos to the given TCB side. +function ildb.assign_signal_to_tcbs(pos, sigd) + local tcbs = ildb.get_tcbs(sigd) + assert(tcbs, "assign_signal_to_tcbs invalid sigd!") + tcbs.signal = pos + if not tcbs.routes then + tcbs.routes = {} + end + ildb.set_sigd_for_signal(pos, sigd) +end + +-- unassign the signal from the given sigd (looks in tcbs.signal for the signalpos) +function ildb.unassign_signal_for_tcbs(sigd) + local tcbs = advtrains.interlocking.db.get_tcbs(sigd) + if not tcbs then return end + local pos = tcbs.signal + if not pos then return end + ildb.set_sigd_for_signal(pos, nil) + tcbs.signal = nil + tcbs.route_aspect = nil + tcbs.route_remote = nil +end + -- checks if there's any influence point set to this position -- if purge is true, checks whether the associated signal still exists -- and deletes the ip if not. @@ -987,7 +1020,7 @@ function ildb.is_ip_at(pos, purge) if purge then -- is there still a signal assigned to it? for connid, sigpos in pairs(influence_points[pts]) do - local asp = advtrains.interlocking.signal_get_aspect(sigpos) + local asp = advtrains.interlocking.signal.get_aspect(sigpos) if not asp then atlog("Clearing orphaned signal influence point", pts, "/", connid) ildb.clear_ip_signal(pts, connid) diff --git a/advtrains_interlocking/mod.conf b/advtrains_interlocking/mod.conf index 3b2d029..9191dd9 100644 --- a/advtrains_interlocking/mod.conf +++ b/advtrains_interlocking/mod.conf @@ -4,4 +4,4 @@ description=Interlocking system for Advanced Trains author=orwell96 depends=advtrains -optional_depends=advtrains_train_track +#optional_depends=advtrains_train_track diff --git a/advtrains_interlocking/route_prog.lua b/advtrains_interlocking/route_prog.lua index 2f0f8ee..3bdf6d6 100644 --- a/advtrains_interlocking/route_prog.lua +++ b/advtrains_interlocking/route_prog.lua @@ -535,9 +535,12 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) -- show formspec show_routing_form(pname, tcbpos) - advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname, rp.tmp_lcks, pname) - + return + elseif advtrains.interlocking.database.get_tcb(pos) then + -- the punched node itself is a TCB + show_routing_form(pname, pos) + advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname, rp.tmp_lcks, pname) return end if advtrains.is_passive(pos) then diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 9b0479f..8347b1c 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -400,7 +400,8 @@ end -- 0: not a signal at all -- 1: signal has get_aspect_info() but the aspect is not variable (e.g. a sign) -- 2: signal has apply_aspect() but does not have main aspects (e.g. a pure distant signal) --- 3: Full capabilities, signal has main aspects and can be used as main/shunt signal (can be start/endpoint of a route) +-- 3: signal has main signal role but can only ever display a halt aspect, such as a bumper (can be endpoint, but not startpoint, of a route) +-- 4: Full capabilities, signal has main aspects and can be used as main/shunt signal (can be start/endpoint of a route) function signal.get_signal_cap_level(pos) local node = advtrains.ndb.get_node_or_nil(pos) local ndef = node and minetest.registered_nodes[node.name] @@ -408,6 +409,10 @@ function signal.get_signal_cap_level(pos) if ndefat and ndefat.get_aspect_info then if ndefat.apply_aspect then if ndefat.main_aspects then + -- if the table contains anything, 4, otherwise 3 + for _,_ in pairs(ndefat.main_aspects) do + return 4 + end return 3 end return 2 @@ -419,9 +424,17 @@ end ---------------- -function signal.can_dig(pos) +function signal.can_dig(pos, player) local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) if sigd then + -- check privileges + if not player or not minetest.check_player_privs(player:get_player_name(), "interlocking") then + if not player then -- intermediate debug to uncover hard-to-find bugz + atdebug("advtrains.interlocking.signal.can_dig(",pos,") called with player==nil!") + end + return false + end + -- check if route is set local tcbs = advtrains.interlocking.db.get_tcbs(sigd) if tcbs.routeset then return false @@ -434,11 +447,7 @@ function signal.after_dig(pos, oldnode, oldmetadata, player) -- unassign signal if necessary local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) if sigd then - local tcbs = advtrains.interlocking.db.get_tcbs(sigd) - advtrains.interlocking.db.set_sigd_for_signal(pos, nil) - tcbs.signal = nil - tcbs.route_aspect = nil - tcbs.route_remote = nil + advtrains.interlocking.db.unassign_signal_for_tcbs(sigd) minetest.chat_send_player(player:get_player_name(), "Signal has been unassigned. Name and routes are kept for reuse.") end -- TODO clear influence point diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index 1cdbb29..2b93234 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -156,7 +156,7 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) local tcbnpos = players_assign_tcb[pname] if tcbnpos then if vector.distance(pos, tcbnpos)<=20 then - local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes) + local node_ok, conns, rhe = advtrains.get_rail_info_at(pos) if node_ok and #conns == 2 then -- if there is already a tcb here, reassign it if ildb.get_tcb(pos) then @@ -189,11 +189,7 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) if ndef and ndef.advtrains and ndef.advtrains.apply_aspect then local tcbs = ildb.get_tcbs(sigd) if tcbs then - tcbs.signal = pos - if not tcbs.routes then - tcbs.routes = {} - end - ildb.set_sigd_for_signal(pos, sigd) + ildb.assign_signal_to_tcbs(pos, sigd) minetest.chat_send_player(pname, "Configuring TCB: Successfully assigned signal.") advtrains.interlocking.show_ip_form(pos, pname, true) else @@ -212,6 +208,138 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) end end) +-- "Self-contained TCB" +-- 2024-11-25: Buffers should become their own TCB (and signal) automatically to permit setting routes to them +-- These are support functions for this kind of node. + +-- Create an after_place_node callback for a self-contained TCB node. The parameters control additional behavior: +-- fail_silently_on_noprivs: (boolean) Does not give an error in case the placer does not have the interlocking privilege +-- auto_create_self_signal: (boolean) Automatically assign this same node as signal to the A side of the newly-created TCB +-- (this is useful for buffers as they serve both as TCB and as an always-halt signal) +function advtrains.interlocking.self_tcb_make_after_place_callback(fail_silently_on_noprivs, auto_create_self_signal) + return function(pos, player, itemstack, pointed_thing) + atdebug("selftcb apn ",pos, player, itemstack, pointed_thing) + local pname = player:get_player_name() + if not minetest.check_player_privs(pname, "interlocking") then + if not fail_silently_on_noprivs then + minetest.chat_send_player(pname, "Insufficient privileges to use this!") + end + return + end + if ildb.get_tcb(pos) then + minetest.chat_send_player(pname, "TCB already existed at this position, now linked to this node") + else + ildb.create_tcb_at(pos, pname) + end + if auto_create_self_signal then + local sigd = { p = pos, s = 1 } + local tcbs = ildb.get_tcbs(sigd) + -- make sure signal doesn't already exist + if tcbs.signal then + minetest.chat_send_player(pname, "Signal on B side already assigned!") + return + end + ildb.assign_signal_to_tcbs(pos, sigd) + -- assign influence point to itself + ildb.set_ip_signal(advtrains.roundfloorpts(pos), 1, pos) + end + end +end + +-- Create an can_dig callback for a self-contained TCB node. The parameters control additional behavior: +-- is_signal: (boolean) Whether this node is also a signal (in addition to being a TCB), e.g. when auto_create_self_signal was set. +-- Causes also the signal API's can_dig to be called +function advtrains.interlocking.self_tcb_make_can_dig_callback(is_signal) + return function(pos, player) + local pname = player and player:get_player_name() or "" + -- need to duplicate logic of the regular "can_dig_or_modify_track()" function in core/tracks.lua + if advtrains.get_train_at_pos(pos) then + minetest.chat_send_player(pname, "Can't remove track, a train is here!") + return false + end + -- end of standard checks + local tcb = ildb.get_tcb(pos) + if not tcb then + -- digging always allowed because the TCB hasn't been created (unless signal callback interjects) + if is_signal then + return advtrains.interlocking.signal.can_dig(pos, player) + else + return true + end + end + -- TCB exists + if not minetest.check_player_privs(pname, "interlocking") then + return false + end + -- fine to remove (unless signal callback interjects) + if is_signal then + return advtrains.interlocking.signal.can_dig(pos, player) + else + return true + end + end +end + +-- Create an after_dig_node callback for a self-contained TCB node. The parameters control additional behavior: +-- is_signal: (boolean) Whether this node is also a signal (in addition to being a TCB), e.g. when auto_create_self_signal was set. +-- Causes also the signal API's after_dig_node to be called +function advtrains.interlocking.self_tcb_make_after_dig_callback(is_signal) + return function(pos, oldnode, oldmetadata, player) + local pname = player:get_player_name() + if is_signal then + -- "dig" the signal first + advtrains.interlocking.signal.after_dig(pos, oldnode, oldmetadata, player) + end + if ildb.get_tcb(pos) then + -- remove the TCB + ildb.remove_tcb_at(pos, pname, true) + end + end +end + +-- Create an on_rightclick callback for a self-contained TCB node. The rightclick callback tries to repeat the TCB assignment +-- if necessary and otherwise shows the TCB formspec. The parameters control additional behavior: +-- fail_silently_on_noprivs: (boolean) Does not give an error in case the placer does not have the interlocking privilege +-- auto_create_self_signal: (boolean) Automatically assign this same node as signal to the B side of the +-- newly-created TCB if that has not already happened during place. +-- Otherwise, opens the signal dialog instead of the TCB dialog on rightclick +function advtrains.interlocking.self_tcb_make_on_rightclick_callback(fail_silently_on_noprivs, auto_create_self_signal) + return function(pos, node, player, itemstack, pointed_thing) + local pname = player:get_player_name() + if not minetest.check_player_privs(pname, "interlocking") then + if not fail_silently_on_noprivs then + minetest.chat_send_player(pname, "Insufficient privileges to use this!") + end + return + end + if ildb.get_tcb(pos) then + -- TCB already here. go on + else + -- otherwise create tcb + ildb.create_tcb_at(pos, pname) + end + if auto_create_self_signal then + local sigd = { p = pos, s = 1 } + local tcbs = ildb.get_tcbs(sigd) + -- make sure signal doesn't already exist + if not tcbs.signal then + -- go ahead and assign signal + ildb.assign_signal_to_tcbs(pos, sigd) + -- assign influence point to itself + ildb.set_ip_signal(advtrains.roundfloorpts(pos), 1, pos) + end + -- in any case open the signalling form nouw + local control = player:get_player_control() + advtrains.interlocking.show_signal_form(pos, node, pname, control.aux1) + return + else + -- not an autosignal. Then show the TCB form + advtrains.interlocking.show_tcb_form(pos, pname) + return + end + end +end + -- TCB Form local function mktcbformspec(pos, side, tcbs, offset, pname) @@ -666,7 +794,7 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle -- no route is active, and no route is so far defined if not tcbs.signal then atwarn("signalling form missing signal?!", pos) return end -- safeguard, nothing else in this function checks tcbs.signal local caps = advtrains.interlocking.signal.get_signal_cap_level(tcbs.signal) - if caps >= 3 then + if caps >= 4 then -- offer user the "block signal mode" form = form.."label[0.5,2.5;No routes are yet defined.]" if hasprivs then @@ -683,6 +811,10 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle .."Sets a route into the section ahead with auto-working set on\n" .."Short block: This signal becomes distant signal for next signal.]" end + elseif caps >= 3 then + -- it's a buffer! + form = form.."label[0.5,2.5;This is an always-halt signal (e.g. a buffer)\n" + .."No routes can be set from here.]" else -- signal caps say it cannot be route start/end form = form.."label[0.5,2.5;This is a Non-Halt signal (e.g. pure distant signal)\n" -- cgit v1.2.3