From a14eb7fe737ffc4df4077a01f4859d74f62d0af2 Mon Sep 17 00:00:00 2001 From: orwell96 Date: Sun, 19 Mar 2023 17:19:40 +0100 Subject: TCB Xlinking added, to make nonconnected crossings possible --- advtrains/helpers.lua | 2 + advtrains_interlocking/database.lua | 168 ++++++++++++++++++++++++++++++----- advtrains_interlocking/tcb_ts_ui.lua | 95 +++++++++++++------- advtrains_interlocking/tool.lua | 13 ++- 4 files changed, 222 insertions(+), 56 deletions(-) diff --git a/advtrains/helpers.lua b/advtrains/helpers.lua index 6d22bc5..7e078fb 100644 --- a/advtrains/helpers.lua +++ b/advtrains/helpers.lua @@ -297,6 +297,7 @@ end function advtrains.get_adjacent_rail(this_posnr, this_conns_p, conn_idx, drives_on) local this_pos = advtrains.round_vector_floor_y(this_posnr) local this_conns = this_conns_p + local _ if not this_conns then _, this_conns = advtrains.get_rail_info_at(this_pos) end @@ -583,6 +584,7 @@ function advtrains.get_track_iterator(initial_pos, initial_connid, limit, follow table.insert(ti.branches, {pos = initial_pos, connid = coni, limit=limit}) end end + ti.limit = limit -- safeguard if someone adds a branch before calling anything setmetatable(ti, {__index=trackiter_mt}) return ti end diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index 38fad77..35afc8d 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -163,6 +163,9 @@ TCB data structure -- This is the "A" side of the TCB [1] = { -- Variant: with adjacent TCs. ts_id = -- ID of the assigned track section + xlink = -- If two sections of track are not physically joined but must function as one TS (e.g. knights move crossing), a bidirectional link can be added with exactly one other TCB. + -- TS search will behave as if these two TCBs were physically connected. + signal = -- optional: when set, routes can be set from this tcb/direction and signal -- aspect will be set accordingly. routeset = -- Route set from this signal. This is the entry that is cleared once @@ -249,17 +252,26 @@ function ildb.get_all_ts() return track_sections end --- Checks the consistency of the track section at the given position --- This method attempts to autorepair track sections if they are inconsistent +-- Checks the consistency of the track section at the given position, attempts to autorepair track sections if they are inconsistent +-- There are 2 operation modes: +-- 1: pos is NOT a TCB, tcb_connid MUST be nil +-- 2: pos is a TCB, tcb_connid MUST be given -- @param pos: the position to start from +-- @param tcb_connid: If provided node is a TCB, -- Returns: -- ts_id - the track section that was found -- nil - No track section exists -function ildb.check_and_repair_ts_at_pos(pos) - atdebug("check_and_repair_ts_at_pos", pos) +function ildb.check_and_repair_ts_at_pos(pos, tcb_connid) + atdebug("check_and_repair_ts_at_pos", pos, tcb_connid) + -- check prereqs + if ildb.get_tcb(pos) then + if not tcb_connid then error("check_and_repair_ts_at_pos: Startpoint is TCB, must provide tcb_connid!") end + else + if tcb_connid then error("check_and_repair_ts_at_pos: Startpoint is not TCB, must not provide tcb_connid!") end + end -- STEP 1: Ensure that only one section is at this place -- get all TCBs adjacent to this - local all_tcbs = ildb.get_all_tcbs_adjacent(pos, nil) + local all_tcbs = ildb.get_all_tcbs_adjacent(pos, tcb_connid) local first_ts = true local ts_id for _,sigd in ipairs(all_tcbs) do @@ -321,41 +333,63 @@ end -- This function does not trigger a repair. -- @param inipos: the initial position -- @param inidir: the initial direction, or nil to search in all directions +-- @param per_track_callback: A callback function called with signature (pos, connid, bconnid) for every track encountered -- Returns: a list of sigd's describing the TCBs found (sigd's point inward): -- {p=, s=, tcbs=} -function ildb.get_all_tcbs_adjacent(inipos, inidir) +function ildb.get_all_tcbs_adjacent(inipos, inidir, per_track_callback) atdebug("get_all_tcbs_adjacent: inipos",inipos,"inidir",inidir,"") local found_sigd = {} local ti = advtrains.get_track_iterator(inipos, inidir, TS_MAX_SCAN, true) + -- if initial start is TCBS and has xlink, need to add that to the TI + local inisi = {p=inipos, s=inidir}; + local initcbs = ildb.get_tcbs(inisi) + if initcbs then + ildb.validate_tcb_xlink(inisi, true) + if initcbs.xlink then + -- adding the tcb will happen when this branch is retrieved again using ti:next_branch() + atdebug("get_all_tcbs_adjacent: Putting xlink Branch for initial node",initcbs.xlink) + ti:add_branch(initcbs.xlink.p, initcbs.xlink.s) + end + end local pos, connid, bconnid, tcb while ti:has_next_branch() do pos, connid = ti:next_branch() --atdebug("get_all_tcbs_adjacent: BRANCH: ",pos, connid) bconnid = nil + is_branch_start = true repeat + -- callback + if per_track_callback then + per_track_callback(pos, connid, bconnid) + end tcb = ildb.get_tcb(pos) if tcb then + local using_connid = bconnid -- found a tcb - if not bconnid then - -- A branch start point cannot be a TCB, as that would imply that it was a turnout/crossing (illegal) - -- Only exception where this can happen is if the start point is a TCB, then we'd need to add the forward side of it to our list - if pos.x==inipos.x and pos.y==inipos.y and pos.z==inipos.z then - -- Note "connid" instead of "bconnid" - atdebug("get_all_tcbs_adjacent: Found Startpoint TCB: ",pos, connid, "ts=", tcb[connid].ts_id) - insert_sigd_if_not_present(found_sigd, {p=pos, s=connid, tcbs=tcb[connid]}) - else - -- this may not happend - error("Found TCB at TrackIterator new branch which is not the start point, this is illegal! pos="..minetest.pos_to_string(pos)) - end - - else - -- add the sigd of this tcb and a reference to the tcb table in it - atdebug("get_all_tcbs_adjacent: Found TCB: ",pos, bconnid, "ts=", tcb[bconnid].ts_id) - insert_sigd_if_not_present(found_sigd, {p=pos, s=bconnid, tcbs=tcb[bconnid]}) + if is_branch_start then + -- A branch cannot be a TCB, as that would imply that it was a turnout/crossing (illegal) + -- UNLESS: (a) it is the start point or (b) it was added via xlink + -- Then the correct conn to use is connid (pointing forward) + atdebug("get_all_tcbs_adjacent: Inserting TCB at branch start",pos, connid) + using_connid = connid + end + -- add the sigd of this tcb and a reference to the tcb table in it + atdebug("get_all_tcbs_adjacent: Found TCB: ",pos, using_connid, "ts=", tcb[using_connid].ts_id) + local si = {p=pos, s=using_connid, tcbs=tcb[using_connid]} + -- if xlink exists, add it now (only if we are not in branch start) + ildb.validate_tcb_xlink(si, true) + if not is_branch_start and si.tcbs.xlink then + -- adding the tcb will happen when this branch is retrieved again using ti:next_branch() + atdebug("get_all_tcbs_adjacent: Putting xlink Branch",si.tcbs.xlink) + ti:add_branch(si.tcbs.xlink.p, si.tcbs.xlink.s) + end + insert_sigd_if_not_present(found_sigd, si) + if not is_branch_start then break end end pos, connid, bconnid = ti:next_track() + is_branch_start = false --atdebug("get_all_tcbs_adjacent: TRACK: ",pos, connid, bconnid) until not pos end @@ -464,7 +498,7 @@ function ildb.tcbs_ensure_ts_ref_exists(sigd) local did_insert = insert_sigd_if_not_present(ts.tc_breaks, {p=sigd.p, s=sigd.s}) if did_insert then atdebug("tcbs_ensure_ts_ref_exists(",sigd,"): TCBS was missing reference in TS",tcbs.ts_id) - ildb.update_ts_cache(ts_id) + ildb.update_ts_cache(tcbs.ts_id) end end @@ -515,7 +549,7 @@ function ildb.update_ts_cache(ts_id) end local lntrans = { "A", "B" } -local function sigd_to_string(sigd) +function ildb.sigd_to_string(sigd) return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s] end @@ -546,12 +580,98 @@ function ildb.remove_tcb_at(pos) if old_tcb[2].ts_id then ildb.purge_ts_tcb_refs(old_tcb[2].ts_id) end + -- update xlink partners + if old_tcb[1].xlink then + ildb.validate_tcb_xlink(old_tcb[1].xlink) + end + if old_tcb[2].xlink then + ildb.validate_tcb_xlink(old_tcb[2].xlink) + end advtrains.interlocking.remove_tcb_marker(pos) -- If needed, merge the track sections here ildb.check_and_repair_ts_at_pos(pos) return true end +-- Xlink: Connecting not-physically-connected sections handling + +-- Ensures that the xlink of this tcbs is bidirectional +function ildb.validate_tcb_xlink(sigd, suppress_repairs) + local tcbs = sigd.tcbs or ildb.get_tcbs(sigd) + local osigd = tcbs.xlink + if not osigd then return end + local otcbs = ildb.get_tcbs(tcbs.xlink) + if not otcbs then + atdebug("validate_tcb_xlink",sigd,": Link partner ",osigd,"orphaned") + tcbs.xlink = nil + if not suppress_repairs then + ildb.check_and_repair_ts_at_pos(sigd.p, sigd.s) + end + return + end + if otcbs.xlink then + if not vector.equals(otcbs.xlink.p, sigd.p) or otcbs.xlink.s~=sigd.s then + atdebug("validate_tcb_xlink",sigd,": Link partner ",osigd,"backreferencing to someone else (namely",otcbs.xlink,") clearing it") + tcbs.xlink = nil + if not suppress_repairs then + ildb.check_and_repair_ts_at_pos(sigd.p, sigd.s) + atdebug("validate_tcb_xlink",sigd,": Link partner ",osigd," was backreferencing to someone else, now updating that") + ildb.validate_tcb_xlink(osigd) + end + end + else + atdebug("validate_tcb_xlink",sigd,": Link partner ",osigd,"wasn't backreferencing, clearing it") + tcbs.xlink = nil + if not suppress_repairs then + ildb.check_and_repair_ts_at_pos(sigd.p, sigd.s) + end + end +end + +function ildb.add_tcb_xlink(sigd1, sigd2) + atdebug("add_tcb_xlink",sigd1, sigd2) + local tcbs1 = sigd1.tcbs or ildb.get_tcbs(sigd1) + local tcbs2 = sigd2.tcbs or ildb.get_tcbs(sigd2) + if vector.equals(sigd1.p, sigd2.p) then + atdebug("add_tcb_xlink Cannot xlink with same TCB") + return + end + if not tcbs1 or not tcbs2 then + atdebug("add_tcb_xlink TCBS doesnt exist") + return + end + if tcbs1.xlink or tcbs2.xlink then + atdebug("add_tcb_xlink One already linked") + return + end + -- apply link + tcbs1.xlink = {p=sigd2.p, s=sigd2.s} + tcbs2.xlink = {p=sigd1.p, s=sigd1.s} + -- update section. It should be sufficient to call update only once because the TCBs are linked anyway now + ildb.check_and_repair_ts_at_pos(sigd1.p, sigd1.s) +end + +function ildb.remove_tcb_xlink(sigd) + atdebug("remove_tcb_xlink",sigd) + -- Validate first. If Xlink is gone already then, nothing to do + ildb.validate_tcb_xlink(sigd) + -- Checking all of these already done by validate + local tcbs = sigd.tcbs or ildb.get_tcbs(sigd) + local osigd = tcbs.xlink + if not osigd then + -- validate already cleared us + atdebug("remove_tcb_xlink: Already gone by validate") + return + end + local otcbs = ildb.get_tcbs(tcbs.xlink) + -- clear it + otcbs.xlink = nil + tcbs.xlink = nil + -- Update section for ourself and the other one + ildb.check_and_repair_ts_at_pos(sigd.p, sigd.s) + ildb.check_and_repair_ts_at_pos(osigd.p, osigd.s) +end + function ildb.create_ts_from_tcbs(sigd) atdebug("create_ts_from_tcbs",sigd) local all_tcbs = ildb.get_all_tcbs_adjacent(sigd.p, sigd.s) diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index 97c28a8..08d1c32 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -2,6 +2,7 @@ local players_assign_tcb = {} local players_assign_signal = {} +local players_assign_xlink = {} local players_link_ts = {} local ildb = advtrains.interlocking.db @@ -166,6 +167,7 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) meta:set_string("tcb_pos", minetest.pos_to_string(pos)) meta:set_string("infotext", "TCB assigned to "..minetest.pos_to_string(pos)) minetest.chat_send_player(pname, "Configuring TCB: Successfully configured TCB") + advtrains.interlocking.show_tcb_marker(pos) else minetest.chat_send_player(pname, "Configuring TCB: This is not a normal two-connection rail! Aborted.") end @@ -213,9 +215,14 @@ end) -- TCB Form -local function mktcbformspec(tcbs, btnpref, offset, pname) +local function mktcbformspec(pos, side, tcbs, offset, pname) local form = "" + local btnpref = side==1 and "A" or "B" local ts + -- ensure that mapping and xlink are up to date + ildb.tcbs_ensure_ts_ref_exists({p=pos, s=side, tcbs=tcbs}) + ildb.validate_tcb_xlink({p=pos, s=side, tcbs=tcbs}) + -- Note: repair operations may have been triggered by this if tcbs.ts_id then ts = ildb.get_ts(tcbs.ts_id) end @@ -227,6 +234,19 @@ local function mktcbformspec(tcbs, btnpref, offset, pname) 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]" end + -- xlink + if tcbs.xlink then + form = form.."label[0.5,"..(offset+1.5)..";Link:"..ildb.sigd_to_string(tcbs.xlink).."]" + form = form.."button[4.5,"..(offset+1.5)..";1,1;"..btnpref.."_xlinkdel;X]" + else + if players_assign_xlink[pname] then + form = form.."button[0.5,"..(offset+1.5)..";4,1;"..btnpref.."_xlinklink;Link "..ildb.sigd_to_string(players_assign_xlink[pname]).."]" + form = form.."button[4.5,"..(offset+1.5)..";1,1;"..btnpref.."_xlinkabrt;X]" + else + form = form.."label[0.5,"..(offset+1.5)..";No Link]" + form = form.."button[4.5,"..(offset+1.5)..";1,1;"..btnpref.."_xlinkadd;+]" + end + end if tcbs.signal then form = form.."button[0.5,"..(offset+2.5)..";5,1;"..btnpref.."_sigdia;Signalling]" else @@ -245,8 +265,8 @@ function advtrains.interlocking.show_tcb_form(pos, pname) if not tcb then return end local form = "size[6,9] label[0.5,0.5;Track Circuit Break Configuration]" - form = form .. mktcbformspec(tcb[1], "A", 1, pname) - form = form .. mktcbformspec(tcb[2], "B", 5, pname) + form = form .. mktcbformspec(pos, 1, tcb[1], 1, pname) + form = form .. mktcbformspec(pos, 2, tcb[2], 5, pname) minetest.show_formspec(pname, "at_il_tcbconfig_"..minetest.pos_to_string(pos), form) advtrains.interlocking.show_tcb_marker(pos) @@ -276,6 +296,10 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) local f_makeil = {fields.A_makeil, fields.B_makeil} local f_asnsig = {fields.A_asnsig, fields.B_asnsig} local f_sigdia = {fields.A_sigdia, fields.B_sigdia} + local f_xlinkadd = {fields.A_xlinkadd, fields.B_xlinkadd} + local f_xlinkdel = {fields.A_xlinkdel, fields.B_xlinkdel} + local f_xlinklink = {fields.A_xlinklink, fields.B_xlinklink} + local f_xlinkabrt = {fields.A_xlinkabrt, fields.B_xlinkabrt} for connid=1,2 do local tcbs = tcb[connid] @@ -291,6 +315,28 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end end end + if tcbs.xlink then + if f_xlinkdel[connid] then + ildb.remove_tcb_xlink({p=pos, s=connid}) + end + else + local osigd = players_assign_xlink[pname] + if osigd then + if f_xlinklink[connid] then + ildb.add_tcb_xlink({p=pos, s=connid}, osigd) + players_assign_xlink[pname] = nil + elseif f_xlinkabrt[connid] then + players_assign_xlink[pname] = nil + end + else + if f_xlinkadd[connid] then + players_assign_xlink[pname] = {p=pos, s=connid} + minetest.chat_send_player(pname, "TCB Link: Select linked TCB now!") + minetest.close_formspec(pname, formname) + return -- to not reopen form + end + end + end if f_asnsig[connid] and not tcbs.signal then minetest.chat_send_player(pname, "Configuring TCB: Please punch the signal to assign.") players_assign_signal[pname] = {p=pos, s=connid} @@ -496,36 +542,25 @@ function advtrains.interlocking.remove_tcb_marker(pos) markerent[pts] = nil end +local ts_showparticles_callback = function(pos, connid, bconnid) + minetest.add_particle({ + pos = pos, + velocity = {x=0, y=0, z=0}, + acceleration = {x=0, y=0, z=0}, + expirationtime = 10, + size = 7, + vertical = true, + texture = "at_il_ts_highlight_particle.png", + glow = 6, + }) +end + -- Spawns particles to highlight the clicked track section -- TODO: Adapt behavior to not dumb-walk anymore function advtrains.interlocking.highlight_track_section(pos) - local ti = advtrains.get_track_iterator(pos, nil, 100, true) - local pos, connid, bconnid, tcb - while ti:has_next_branch() do - pos, connid = ti:next_branch() - --atdebug("highlight_track_section: BRANCH: ",pos, connid) - bconnid = nil - repeat - -- spawn particles - minetest.add_particle({ - pos = pos, - velocity = {x=0, y=0, z=0}, - acceleration = {x=0, y=0, z=0}, - expirationtime = 10, - size = 7, - vertical = true, - texture = "at_il_ts_highlight_particle.png", - glow = 6, - }) - -- abort if TCB is found - tcb = ildb.get_tcb(pos) - if tcb then - advtrains.interlocking.show_tcb_marker(pos) - break - end - pos, connid, bconnid = ti:next_track() - --atdebug("highlight_track_section: TRACK: ",pos, connid, bconnid) - until not pos + local all_tcbs = ildb.get_all_tcbs_adjacent(pos, nil, ts_showparticles_callback) + for _,sigd in ipairs(all_tcbs) do + advtrains.interlocking.show_tcb_marker(sigd.p) end end diff --git a/advtrains_interlocking/tool.lua b/advtrains_interlocking/tool.lua index 4b701b4..6723f88 100644 --- a/advtrains_interlocking/tool.lua +++ b/advtrains_interlocking/tool.lua @@ -26,7 +26,11 @@ local function node_right_click(pos, pname) local node_ok, conns, rail_y=advtrains.get_rail_info_at(pos) if not node_ok then minetest.chat_send_player(pname, "Node is not a track!") - return + return + end + if advtrains.interlocking.db.get_tcb(pos) then + advtrains.interlocking.show_tcb_form(pos, pname) + return end local ts_id = advtrains.interlocking.db.check_and_repair_ts_at_pos(pos) @@ -41,7 +45,12 @@ local function node_left_click(pos, pname) local node_ok, conns, rail_y=advtrains.get_rail_info_at(pos) if not node_ok then minetest.chat_send_player(pname, "Node is not a track!") - return + return + end + + if advtrains.interlocking.db.get_tcb(pos) then + advtrains.interlocking.show_tcb_marker(pos) + return end local ts_id = advtrains.interlocking.db.check_and_repair_ts_at_pos(pos) -- cgit v1.2.3