diff --git a/advtrains/signals.lua b/advtrains/signals.lua index 4dec7f5..8bdd877 100644 --- a/advtrains/signals.lua +++ b/advtrains/signals.lua @@ -3,15 +3,15 @@ local mrules_wallsignal = advtrains.meseconrules -local function can_dig_func(pos) +local function can_dig_func(pos, player) if advtrains.interlocking then - return advtrains.interlocking.signal.can_dig(pos) + return advtrains.interlocking.signal.can_dig(pos, player) end return true end -local function after_dig_func(pos) +local function after_dig_func(pos, oldnode, oldmetadata, digger) if advtrains.interlocking then - return advtrains.interlocking.signal.after_dig(pos) + return advtrains.interlocking.signal.after_dig(pos, oldnode, oldmetadata, digger) end return true end diff --git a/advtrains/trackplacer.lua b/advtrains/trackplacer.lua index 6a2c7a8..1543209 100644 --- a/advtrains/trackplacer.lua +++ b/advtrains/trackplacer.lua @@ -152,12 +152,14 @@ local function check_or_bend_rail(origin, dir, pname, commit) 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) advtrains.ndb.swap_node(pos, node) local ndef = minetest.registered_nodes[node.name] 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 @@ -191,16 +193,16 @@ function tp.place_track(pos, tpg, pname, yaw) for k1, conn1 in ipairs(cand) do for k2, conn2 in ipairs(cand) do 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 - if advtrains.yawToDirection(yaw, conn1, conn2) == conn2 then + if advtrains.yawToDirection(yaw, conn1, conn2) == conn1 then -- does there exist a suitable double-connection rail? --atdebug("Try double conn: ",conn1, conn2) local node = g.double[conn1.."_"..conn2] if node then check_or_bend_rail(pos, conn1, 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 end end @@ -220,13 +222,13 @@ function tp.place_track(pos, tpg, pname, yaw) local node = single[conn1] if node then 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 end end -- 4. if nothing worked, set the 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 end diff --git a/advtrains/tracks.lua b/advtrains/tracks.lua index 661da8a..fa7b702 100644 --- a/advtrains/tracks.lua +++ b/advtrains/tracks.lua @@ -253,6 +253,7 @@ end -- 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" +-- Impl Note: possibly duplicate code in "self contained TCB" - see interlocking/tcb_ts_ui.lua! function advtrains.can_dig_or_modify_track(pos) if advtrains.get_train_at_pos(pos) then return false, attrans("Position is occupied by a train.") 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" diff --git a/advtrains_train_track/init.lua b/advtrains_train_track/init.lua index 32e1235..f551ec5 100644 --- a/advtrains_train_track/init.lua +++ b/advtrains_train_track/init.lua @@ -600,6 +600,35 @@ advtrains.register_tracks("default", { --bumpers still use the old texture until the models are redone. description=attrans("Bumper"), 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) minetest.register_craft({ output = 'advtrains:dtrack_bumper_placer 2', diff --git a/advtrains_train_track/mod.conf b/advtrains_train_track/mod.conf index 2aece3e..a7fef4d 100644 --- a/advtrains_train_track/mod.conf +++ b/advtrains_train_track/mod.conf @@ -4,4 +4,4 @@ description=Default track set for Advanced Trains author=orwell96 depends=advtrains -optional_depends=mesecons,digtron +optional_depends=mesecons,digtron,advtrains_interlocking