Make Buffers become implicitly their own TCBs and signals when interlocking is enabled

This commit is contained in:
orwell 2024-11-25 22:31:26 +01:00
parent 73c393e223
commit 922e654b7b
10 changed files with 242 additions and 33 deletions

View File

@ -3,15 +3,15 @@
local mrules_wallsignal = advtrains.meseconrules local mrules_wallsignal = advtrains.meseconrules
local function can_dig_func(pos) local function can_dig_func(pos, player)
if advtrains.interlocking then if advtrains.interlocking then
return advtrains.interlocking.signal.can_dig(pos) return advtrains.interlocking.signal.can_dig(pos, player)
end end
return true return true
end end
local function after_dig_func(pos) local function after_dig_func(pos, oldnode, oldmetadata, digger)
if advtrains.interlocking then if advtrains.interlocking then
return advtrains.interlocking.signal.after_dig(pos) return advtrains.interlocking.signal.after_dig(pos, oldnode, oldmetadata, digger)
end end
return true return true
end end

View File

@ -152,12 +152,14 @@ local function check_or_bend_rail(origin, dir, pname, commit)
end end
end end
local function track_place_node(pos, node, ndef) local function track_place_node(pos, node, ndef, pname)
--atdebug("track_place_node: ",pos, node) --atdebug("track_place_node: ",pos, node)
advtrains.ndb.swap_node(pos, node) advtrains.ndb.swap_node(pos, node)
local ndef = minetest.registered_nodes[node.name] local ndef = minetest.registered_nodes[node.name]
if ndef and ndef.after_place_node then if ndef and ndef.after_place_node then
ndef.after_place_node(pos) -- resolve player again
local player = pname and core.get_player_by_name(pname) or nil
ndef.after_place_node(pos, player) -- note: itemstack and pointed_thing are NOT available here anymore (crap!)
end end
end end
@ -191,16 +193,16 @@ function tp.place_track(pos, tpg, pname, yaw)
for k1, conn1 in ipairs(cand) do for k1, conn1 in ipairs(cand) do
for k2, conn2 in ipairs(cand) do for k2, conn2 in ipairs(cand) do
if k1~=k2 then if k1~=k2 then
-- order of conn1/conn2: prefer conn2 being in the direction of the player facing. -- order of conn1/conn2: prefer conn1 being in the direction of the player facing.
-- the combination the other way round will be run through in a later loop iteration -- the combination the other way round will be run through in a later loop iteration
if advtrains.yawToDirection(yaw, conn1, conn2) == conn2 then if advtrains.yawToDirection(yaw, conn1, conn2) == conn1 then
-- does there exist a suitable double-connection rail? -- does there exist a suitable double-connection rail?
--atdebug("Try double conn: ",conn1, conn2) --atdebug("Try double conn: ",conn1, conn2)
local node = g.double[conn1.."_"..conn2] local node = g.double[conn1.."_"..conn2]
if node then if node then
check_or_bend_rail(pos, conn1, pname, true) check_or_bend_rail(pos, conn1, pname, true)
check_or_bend_rail(pos, conn2, pname, true) check_or_bend_rail(pos, conn2, pname, true)
track_place_node(pos, node) -- calls after_place_node implicitly track_place_node(pos, node, pname) -- calls after_place_node implicitly
return true return true
end end
end end
@ -220,13 +222,13 @@ function tp.place_track(pos, tpg, pname, yaw)
local node = single[conn1] local node = single[conn1]
if node then if node then
check_or_bend_rail(pos, conn1, pname, true) check_or_bend_rail(pos, conn1, pname, true)
track_place_node(pos, node) -- calls after_place_node implicitly track_place_node(pos, node, nil, pname) -- calls after_place_node implicitly
return true return true
end end
end end
-- 4. if nothing worked, set the default -- 4. if nothing worked, set the default
local node = g.default local node = g.default
track_place_node(pos, node) -- calls after_place_node implicitly track_place_node(pos, node, nil, pname) -- calls after_place_node implicitly
return true return true
end end

View File

@ -253,6 +253,7 @@ end
-- Function called when a track is about to be dug or modified by the trackworker -- Function called when a track is about to be dug or modified by the trackworker
-- Returns either true (ok) or false,"translated string describing reason why it isn't allowed" -- Returns either true (ok) or false,"translated string describing reason why it isn't allowed"
-- Impl Note: possibly duplicate code in "self contained TCB" - see interlocking/tcb_ts_ui.lua!
function advtrains.can_dig_or_modify_track(pos) function advtrains.can_dig_or_modify_track(pos)
if advtrains.get_train_at_pos(pos) then if advtrains.get_train_at_pos(pos) then
return false, attrans("Position is occupied by a train.") return false, attrans("Position is occupied by a train.")

View File

@ -802,10 +802,18 @@ function ildb.create_tcb_at(pos)
end end
-- Remove TCB at the position and update/repair the now joined section -- 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) --atdebug("remove_tcb_at",pos)
local pts = advtrains.encode_pos(pos) local pts = advtrains.encode_pos(pos)
local old_tcb = track_circuit_breaks[pts] 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 track_circuit_breaks[pts] = nil
-- purge the track sections adjacent -- purge the track sections adjacent
if old_tcb[1].ts_id then if old_tcb[1].ts_id then
@ -823,7 +831,9 @@ function ildb.remove_tcb_at(pos)
end end
advtrains.interlocking.remove_tcb_marker(pos) advtrains.interlocking.remove_tcb_marker(pos)
-- If needed, merge the track sections here -- 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 return true
end end
@ -973,11 +983,34 @@ function ildb.get_sigd_for_signal(pos)
end end
return nil return nil
end 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) local pts = advtrains.roundfloorpts(pos)
signal_assignments[pts] = sigd signal_assignments[pts] = sigd
end 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 -- checks if there's any influence point set to this position
-- if purge is true, checks whether the associated signal still exists -- if purge is true, checks whether the associated signal still exists
-- and deletes the ip if not. -- and deletes the ip if not.
@ -987,7 +1020,7 @@ function ildb.is_ip_at(pos, purge)
if purge then if purge then
-- is there still a signal assigned to it? -- is there still a signal assigned to it?
for connid, sigpos in pairs(influence_points[pts]) do 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 if not asp then
atlog("Clearing orphaned signal influence point", pts, "/", connid) atlog("Clearing orphaned signal influence point", pts, "/", connid)
ildb.clear_ip_signal(pts, connid) ildb.clear_ip_signal(pts, connid)

View File

@ -4,4 +4,4 @@ description=Interlocking system for Advanced Trains
author=orwell96 author=orwell96
depends=advtrains depends=advtrains
optional_depends=advtrains_train_track #optional_depends=advtrains_train_track

View File

@ -535,9 +535,12 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
-- show formspec -- show formspec
show_routing_form(pname, tcbpos) show_routing_form(pname, tcbpos)
advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname, rp.tmp_lcks, pname) 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 return
end end
if advtrains.is_passive(pos) then if advtrains.is_passive(pos) then

View File

@ -400,7 +400,8 @@ end
-- 0: not a signal at all -- 0: not a signal at all
-- 1: signal has get_aspect_info() but the aspect is not variable (e.g. a sign) -- 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) -- 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) function signal.get_signal_cap_level(pos)
local node = advtrains.ndb.get_node_or_nil(pos) local node = advtrains.ndb.get_node_or_nil(pos)
local ndef = node and minetest.registered_nodes[node.name] 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 and ndefat.get_aspect_info then
if ndefat.apply_aspect then if ndefat.apply_aspect then
if ndefat.main_aspects 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 return 3
end end
return 2 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) local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos)
if sigd then 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) local tcbs = advtrains.interlocking.db.get_tcbs(sigd)
if tcbs.routeset then if tcbs.routeset then
return false return false
@ -434,11 +447,7 @@ function signal.after_dig(pos, oldnode, oldmetadata, player)
-- unassign signal if necessary -- unassign signal if necessary
local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos)
if sigd then if sigd then
local tcbs = advtrains.interlocking.db.get_tcbs(sigd) advtrains.interlocking.db.unassign_signal_for_tcbs(sigd)
advtrains.interlocking.db.set_sigd_for_signal(pos, nil)
tcbs.signal = nil
tcbs.route_aspect = nil
tcbs.route_remote = nil
minetest.chat_send_player(player:get_player_name(), "Signal has been unassigned. Name and routes are kept for reuse.") minetest.chat_send_player(player:get_player_name(), "Signal has been unassigned. Name and routes are kept for reuse.")
end end
-- TODO clear influence point -- TODO clear influence point

View File

@ -156,7 +156,7 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
local tcbnpos = players_assign_tcb[pname] local tcbnpos = players_assign_tcb[pname]
if tcbnpos then if tcbnpos then
if vector.distance(pos, tcbnpos)<=20 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 node_ok and #conns == 2 then
-- if there is already a tcb here, reassign it -- if there is already a tcb here, reassign it
if ildb.get_tcb(pos) then 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 if ndef and ndef.advtrains and ndef.advtrains.apply_aspect then
local tcbs = ildb.get_tcbs(sigd) local tcbs = ildb.get_tcbs(sigd)
if tcbs then if tcbs then
tcbs.signal = pos ildb.assign_signal_to_tcbs(pos, sigd)
if not tcbs.routes then
tcbs.routes = {}
end
ildb.set_sigd_for_signal(pos, sigd)
minetest.chat_send_player(pname, "Configuring TCB: Successfully assigned signal.") minetest.chat_send_player(pname, "Configuring TCB: Successfully assigned signal.")
advtrains.interlocking.show_ip_form(pos, pname, true) advtrains.interlocking.show_ip_form(pos, pname, true)
else else
@ -212,6 +208,138 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
end end
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 -- TCB Form
local function mktcbformspec(pos, side, tcbs, offset, pname) 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 -- 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 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) 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" -- offer user the "block signal mode"
form = form.."label[0.5,2.5;No routes are yet defined.]" form = form.."label[0.5,2.5;No routes are yet defined.]"
if hasprivs then 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" .."Sets a route into the section ahead with auto-working set on\n"
.."Short block: This signal becomes distant signal for next signal.]" .."Short block: This signal becomes distant signal for next signal.]"
end 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 else
-- signal caps say it cannot be route start/end -- 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" form = form.."label[0.5,2.5;This is a Non-Halt signal (e.g. pure distant signal)\n"

View File

@ -600,6 +600,35 @@ advtrains.register_tracks("default", {
--bumpers still use the old texture until the models are redone. --bumpers still use the old texture until the models are redone.
description=attrans("Bumper"), description=attrans("Bumper"),
formats={}, formats={},
get_additional_definiton = function(def, preset, suffix, rotation)
-- 2024-11-25: Bumpers get the additional feature of being both a signal and a self-contained TCB, when interlocking is used.
if advtrains.interlocking then
return {
-- use the special callbacks for self_tcb (see tcb_ts_ui.lua)
can_dig = advtrains.interlocking.self_tcb_make_can_dig_callback(true),
after_dig_node = advtrains.interlocking.self_tcb_make_after_dig_callback(true),
after_place_node = advtrains.interlocking.self_tcb_make_after_place_callback(true, true),
on_rightclick = advtrains.interlocking.self_tcb_make_on_rightclick_callback(false, true),
advtrains = {
main_aspects = {
-- No main aspects, it always shows Stop.
-- But we need to define the table so that signal caplevel is 3
},
apply_aspect = function(pos, node, main_aspect, rem_aspect, rem_aspinfo)
-- is a no-op for bumpers, it always shows Stop
end,
get_aspect_info = function(pos, main_aspect)
-- it always shows Stop
return advtrains.interlocking.signal.ASPI_HALT
end,
distant_support = false, -- not a distant
route_role = "end", -- the end is nigh!
}
}
else
return {} -- no additional defs when interlocking is not used
end
end,
}, advtrains.ap.t_30deg_straightonly) }, advtrains.ap.t_30deg_straightonly)
minetest.register_craft({ minetest.register_craft({
output = 'advtrains:dtrack_bumper_placer 2', output = 'advtrains:dtrack_bumper_placer 2',

View File

@ -4,4 +4,4 @@ description=Default track set for Advanced Trains
author=orwell96 author=orwell96
depends=advtrains depends=advtrains
optional_depends=mesecons,digtron optional_depends=mesecons,digtron,advtrains_interlocking