Auto-Repair Track Sections/TCBs (automatically when adding/removing or triggered by interlocking tool)

This commit is contained in:
orwell96 2023-03-19 15:20:03 +01:00
parent f284d395d7
commit 6a5540878f
5 changed files with 528 additions and 399 deletions

View File

@ -342,9 +342,10 @@ function advtrains.get_matching_conn(conn, nconns)
return connlku[nconns][conn] return connlku[nconns][conn]
end end
function advtrains.random_id() function advtrains.random_id(lenp)
local idst="" local idst=""
for i=0,5 do local len = lenp or 6
for i=1,len do
idst=idst..(math.random(0,9)) idst=idst..(math.random(0,9))
end end
return idst return idst
@ -476,6 +477,14 @@ end
-- Metatable: -- Metatable:
local trackiter_mt = { local trackiter_mt = {
-- Internal State:
-- branches: A list of {pos, connid, limit} for where to restart
-- pos: The *next* position that the track iterator will return
-- bconnid: The connid of the connection of the rail at pos that points backward
-- tconns: The connections of the rail at pos
-- limit: the current limit
-- visited: a key-boolean table of already visited rails
-- get whether there are still unprocessed branches -- get whether there are still unprocessed branches
has_next_branch = function(self) has_next_branch = function(self)
return #self.branches > 0 return #self.branches > 0
@ -491,13 +500,18 @@ local trackiter_mt = {
self.tconns = adj_conns self.tconns = adj_conns
self.limit = br.limit - 1 self.limit = br.limit - 1
self.visited[advtrains.encode_pos(br.pos)] = true self.visited[advtrains.encode_pos(br.pos)] = true
self.last_track_already_visited = false
return br.pos, br.connid return br.pos, br.connid
end, end,
-- get the next track along the current branch, -- get the next track along the current branch,
-- potentially adding branching tracks to the unprocessed branches list -- potentially adding branching tracks to the unprocessed branches list
-- returns status, track_pos, track_connid -- returns track_pos, track_connid, track_backwards_connid
-- status is true(ok), false(track has ended), nil(traversing limit has been reached) (when status~=true, track_pos and track_connid are nil) -- On error, returns nil, reason; reason is one of "track_end", "limit_hit", "already_visited"
next_track = function(self) next_track = function(self)
if self.last_track_already_visited then
-- see comment below
return nil, "already_visited"
end
local pos = self.pos local pos = self.pos
if not pos then if not pos then
-- last run found track end. Return false -- last run found track end. Return false
@ -507,12 +521,17 @@ local trackiter_mt = {
if self.limit <= 0 then if self.limit <= 0 then
return nil, "limit_hit" return nil, "limit_hit"
end end
if self.visited[advtrains.encode_pos(pos)] then
-- node was already seen. do not continue
return nil, "already_visited"
end
-- select next conn (main conn to follow is the associated connection) -- select next conn (main conn to follow is the associated connection)
local old_bconnid = self.bconnid
local mconnid = advtrains.get_matching_conn(self.bconnid, #self.tconns) local mconnid = advtrains.get_matching_conn(self.bconnid, #self.tconns)
if self.visited[advtrains.encode_pos(pos)] then
-- node was already seen
-- Due to special requirements for the track section updater, return this first already visited track once
-- but do not process any further rails on this branch
-- The next call will then throw already_visited error
self.last_track_already_visited = true
return pos, mconnid, old_bconnid
end
-- If there are more connections, add these to branches -- If there are more connections, add these to branches
for nconnid,_ in ipairs(self.tconns) do for nconnid,_ in ipairs(self.tconns) do
if nconnid~=mconnid and nconnid~=self.bconnid then if nconnid~=mconnid and nconnid~=self.bconnid then
@ -526,7 +545,16 @@ local trackiter_mt = {
self.tconns = adj_conns self.tconns = adj_conns
self.limit = self.limit - 1 self.limit = self.limit - 1
self.visited[advtrains.encode_pos(pos)] = true self.visited[advtrains.encode_pos(pos)] = true
return pos, mconnid self.last_track_already_visited = false
return pos, mconnid, old_bconnid
end,
add_branch = function(self, pos, connid)
table.insert(self.branches, {pos = pos, connid = connid, limit=self.limit})
end,
is_visited = function(self, pos)
return self.visited[advtrains.encode_pos(pos)]
end, end,
} }
@ -569,8 +597,8 @@ while ti:has_next_branch() do
repeat repeat
<do something with the track> <do something with the track>
if <track satisfies an abort condition> then break end --for example, when traversing should stop at TCBs this can check if there is a tcb here if <track satisfies an abort condition> then break end --for example, when traversing should stop at TCBs this can check if there is a tcb here
ok, pos, connid = ti:next_track() pos, connid = ti:next_track()
until not ok -- this stops the loop when either the track end is reached or the limit is hit until not pos -- this stops the loop when either the track end is reached or the limit is hit
-- while loop continues with the next branch ( diverging branch of one of the switches/crossings) until no more are left -- while loop continues with the next branch ( diverging branch of one of the switches/crossings) until no more are left
end end

View File

@ -37,6 +37,22 @@ Another case of this:
The / here is a non-interlocked turnout (to a non-frequently used siding). For some reason, there is no exit node there, The / here is a non-interlocked turnout (to a non-frequently used siding). For some reason, there is no exit node there,
so the route is set to the signal at the right end. The train is taking the exit to the siding and frees the TC, without ever so the route is set to the signal at the right end. The train is taking the exit to the siding and frees the TC, without ever
having touched the right TC. having touched the right TC.
== Terminology / Variable Names ==
"tcb" : A TCB table (as in track_circuit_breaks)
"tcbs" : One side of a tcb (that is tcb == {[1] = tcbs, [2] = tcbs})
"sigd" : A table of format {p=<position>, s=<side aka connid>} by which a "tcbs" is uniqely identified.
== Section Autorepair & Turnout Cache ==
As fundamental part of reworked route programming mechanism, Track Section objects become weak now. They are created and destroyed on demand.
ildb.repair_tcb automatically checks all nearby sections for issues and repairs them automatically.
Also the database now holds a cache of the turnouts in the section and their position for all possible driving paths.
Every time a repair operation takes place, and on every track edit operation, the affected sections need to have their cache updated.
]]-- ]]--
local TRAVERSER_LIMIT = 1000 local TRAVERSER_LIMIT = 1000
@ -59,7 +75,22 @@ advtrains.interlocking.npr_rails = {}
function ildb.load(data) function ildb.load(data)
if not data then return end if not data then return end
if data.tcbs then if data.tcbs then
track_circuit_breaks = data.tcbs if data.tcbpts_conversion_applied then
track_circuit_breaks = data.tcbs
else
-- Convert legacy pos_to_string tcbs to new advtrains.encode_pos position strings
for pts, tcb in pairs(data.tcbs) do
local pos = minetest.string_to_pos(pts)
if pos then
-- that was a pos_to_string
local epos = advtrains.encode_pos(pos)
track_circuit_breaks[epos] = tcb
else
-- keep entry, it is already new
track_circuit_breaks[pts] = tcb
end
end
end
end end
if data.ts then if data.ts then
track_sections = data.ts track_sections = data.ts
@ -121,6 +152,7 @@ function ildb.save()
rs_callbacks = advtrains.interlocking.route.rte_callbacks, rs_callbacks = advtrains.interlocking.route.rte_callbacks,
influence_points = influence_points, influence_points = influence_points,
npr_rails = advtrains.interlocking.npr_rails, npr_rails = advtrains.interlocking.npr_rails,
tcbpts_conversion_applied = true, -- remark that legacy pos conversion has taken place
} }
end end
@ -147,8 +179,6 @@ TCB data structure
-- This is the "B" side of the TCB -- This is the "B" side of the TCB
[2] = { -- Variant: end of track-circuited area (initial state of TC) [2] = { -- Variant: end of track-circuited area (initial state of TC)
ts_id = nil, -- this is the indication for end_of_interlocking ts_id = nil, -- this is the indication for end_of_interlocking
section_free = <boolean>, --this can be set by an exit node via mesecons or atlatc,
-- or from the tc formspec.
} }
} }
@ -156,7 +186,11 @@ Track section
[id] = { [id] = {
name = "Some human-readable name" name = "Some human-readable name"
tc_breaks = { <signal specifier>,... } -- Bounding TC's (signal specifiers) tc_breaks = { <signal specifier>,... } -- Bounding TC's (signal specifiers)
-- Can be direct ends (auto-detected), conflicting routes or TCBs that are too far away from each other rs_cache = { [<x>-<y>] = { [<encoded pos>] = "state" } }
-- Saves the turnout states that need to be locked when a route is set from tcb#x to tcb#y
-- e.g. "1-2" = { "800080008000" = "st" }
-- Recalculated on every change via update_ts_cache
route = { route = {
origin = <signal>, -- route origin origin = <signal>, -- route origin
entry = <sigd>, -- supposed train entry point entry = <sigd>, -- supposed train entry point
@ -172,7 +206,9 @@ Track section
-- first says whether to clear the routesetting status from the origin signal. -- first says whether to clear the routesetting status from the origin signal.
-- locks contains the positions where locks are held by this ts. -- locks contains the positions where locks are held by this ts.
-- 'route' is cleared when train enters the section, while 'route_post' cleared when train leaves section. -- 'route' is cleared when train enters the section, while 'route_post' cleared when train leaves section.
trains = {<id>, ...} -- Set whenever a train (or more) reside in this TC trains = {<id>, ...} -- Set whenever a train (or more) reside in this TC
-- Note: The same train ID may be contained in this mapping multiple times, when it has entered the section in two different places.
} }
@ -185,24 +221,13 @@ signal_assignments = {
} }
]] ]]
-- Maximum scan length for track iterator
local TS_MAX_SCAN = 1000
-- -- basic functions
function ildb.create_tcb(pos)
local new_tcb = {
[1] = {},
[2] = {},
}
local pts = advtrains.roundfloorpts(pos)
if not track_circuit_breaks[pts] then
track_circuit_breaks[pts] = new_tcb
return true
else
return false
end
end
function ildb.get_tcb(pos) function ildb.get_tcb(pos)
local pts = advtrains.roundfloorpts(pos) local pts = advtrains.encode_pos(pos)
return track_circuit_breaks[pts] return track_circuit_breaks[pts]
end end
@ -212,99 +237,281 @@ function ildb.get_tcbs(sigd)
return tcb[sigd.s] return tcb[sigd.s]
end end
function ildb.create_ts(sigd)
local tcbs = ildb.get_tcbs(sigd)
local id = advtrains.random_id()
while track_sections[id] do
id = advtrains.random_id()
end
track_sections[id] = {
name = "Section "..id,
tc_breaks = { sigd }
}
tcbs.ts_id = id
end
function ildb.get_ts(id) function ildb.get_ts(id)
return track_sections[id] return track_sections[id]
end end
-- retrieve full tables. Please use only read-only!
function ildb.get_all_tcb()
-- various helper functions handling sigd's return track_circuit_breaks
local sigd_equal = advtrains.interlocking.sigd_equal end
local function insert_sigd_nodouble(list, sigd) function ildb.get_all_ts()
for idx, cmp in pairs(list) do return track_sections
if sigd_equal(sigd, cmp) then
return
end
end
table.insert(list, sigd)
end end
-- Checks the consistency of the track section at the given position
-- This function will actually handle the node that is in connid direction from the node at pos -- This method attempts to autorepair track sections if they are inconsistent
-- so, this needs the conns of the node at pos, since these are already calculated -- @param pos: the position to start from
local function traverser(found_tcbs, pos, conns, connid, count, brk_when_found_n) -- Returns:
local adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(pos, conns, connid, advtrains.all_tracktypes) -- ts_id - the track section that was found
if not adj_pos then -- nil - No track section exists
--atdebug("Traverser found end-of-track at",pos, connid) function ildb.check_and_repair_ts_at_pos(pos)
return atdebug("check_and_repair_ts_at_pos", pos)
end -- STEP 1: Ensure that only one section is at this place
-- look whether there is a TCB here -- get all TCBs adjacent to this
if #next_conns == 2 then --if not, don't even try! local all_tcbs = ildb.get_all_tcbs_adjacent(pos, nil)
local tcb = ildb.get_tcb(adj_pos) local first_ts = true
if tcb then local ts_id
-- done with this branch for _,sigd in ipairs(all_tcbs) do
--atdebug("Traverser found tcb at",adj_pos, adj_connid) ildb.tcbs_ensure_ts_ref_exists(sigd)
insert_sigd_nodouble(found_tcbs, {p=adj_pos, s=adj_connid}) local tcbs_ts_id = sigd.tcbs.ts_id
return if first_ts then
end -- this one determines
end ts_id = tcbs_ts_id
-- recursion abort condition first_ts = false
if count > TRAVERSER_LIMIT then else
--atdebug("Traverser hit counter at",adj_pos, adj_connid) -- these must be the same as the first
return true if ts_id ~= tcbs_ts_id then
end -- inconsistency is found, repair it
-- continue traversing atdebug("check_and_repair_ts_at_pos: Inconsistency is found!")
local counter_hit = false return ildb.repair_ts_merge_all(all_tcbs)
for nconnid, nconn in ipairs(next_conns) do -- Step2 check is no longer necessary since we just created that new section
if adj_connid ~= nconnid then
counter_hit = counter_hit or traverser(found_tcbs, adj_pos, next_conns, nconnid, count + 1, brk_when_found_n)
if brk_when_found_n and #found_tcbs>=brk_when_found_n then
break
end end
end end
end end
return counter_hit -- only one found (it is either nil or a ts id)
atdebug("check_and_repair_ts_at_pos: TS consistent id=",ts_id,"")
if not ts_id then
return
-- All TCBs agreed that there is no section here.
end
local ts = ildb.get_ts(ts_id)
if not ts then
-- This branch may never be reached, because ildb.tcbs_ensure_ts_ref_exists(sigd) is already supposed to clear out missing sections
error("check_and_repair_ts_at_pos: Resolved to nonexisting section although ildb.tcbs_ensure_ts_ref_exists(sigd) was supposed to prevent this. Panic!")
end
ildb.purge_ts_tcb_refs(ts_id)
-- STEP 2: Ensure that all_tcbs is equal to the track section's TCB list. If there are extra TCBs then the section should be split
-- ildb.tcbs_ensure_ts_ref_exists(sigd) has already make sure that all tcbs are found in the ts's tc_breaks list
-- That means it is sufficient to compare the LENGTHS of both lists, if one is longer then it is inconsistent
if #ts.tc_breaks ~= #all_tcbs then
atdebug("check_and_repair_ts_at_pos: Partition is found!")
return ildb.repair_ts_merge_all(all_tcbs)
end
return ts_id
end
-- Helper function to prevent duplicates
local function insert_sigd_if_not_present(tab, sigd)
local found = false
for _, ssigd in ipairs(tab) do
if vector.equals(sigd.p, ssigd.p) and sigd.s==ssigd.s then
found = true
end
end
if not found then
table.insert(tab, sigd)
end
return not found
end
-- Starting from a position, search all TCBs that can be reached from this position.
-- In a non-faulty setup, all of these should have the same track section assigned.
-- This function does not trigger a repair.
-- @param inipos: the initial position
-- @param inidir: the initial direction, or nil to search in all directions
-- Returns: a list of sigd's describing the TCBs found (sigd's point inward):
-- {p=<pos>, s=<side>, tcbs=<ref to tcbs table>}
function ildb.get_all_tcbs_adjacent(inipos, inidir)
atdebug("get_all_tcbs_adjacent: inipos",inipos,"inidir",inidir,"")
local found_sigd = {}
local ti = advtrains.get_track_iterator(inipos, inidir, TS_MAX_SCAN, true)
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
repeat
tcb = ildb.get_tcb(pos)
if tcb then
-- 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]})
break
end
end
pos, connid, bconnid = ti:next_track()
--atdebug("get_all_tcbs_adjacent: TRACK: ",pos, connid, bconnid)
until not pos
end
return found_sigd
end
-- Called by frontend functions when multiple tcbs's that logically belong to one section have been determined to have different sections
-- Parameter is the output of ildb.get_all_tcbs_adjacent(pos)
-- Returns the ID of the track section that results after the merge
function ildb.repair_ts_merge_all(all_tcbs, force_create)
atdebug("repair_ts_merge_all: Instructed to merge sections of following TCBs:")
-- The first loop does the following for each TCBS:
-- a) Store the TS ID in the set of TS to update
-- b) Set the TS ID to nil, so that the TCBS gets removed from the section
local ts_to_update = {}
local ts_name_repo = {}
local any_ts = false
for _,sigd in ipairs(all_tcbs) do
local ts_id = sigd.tcbs.ts_id
atdebug(sigd, "ts=", ts_id)
if ts_id then
local ts = track_sections[ts_id]
if ts then
any_ts = true
ts_to_update[ts_id] = true
-- if nonstandard name, store this
if ts.name and not string.match(ts.name, "^Section") then
ts_name_repo[#ts_name_repo+1] = ts.name
end
end
end
sigd.tcbs.ts_id = nil
end
if not any_ts and not force_create then
-- nothing to do at all, just no interlocking. Why were we even called
atdebug("repair_ts_merge_all: No track section present, will not create a new one")
return nil
end
-- Purge every TS in turn. TS's that are now empty will be deleted. TS's that still have TCBs will be kept
for ts_id, _ in pairs(ts_to_update) do
local remain_ts = ildb.purge_ts_tcb_refs(ts_id)
end
-- Create a new fresh track section with all the TCBs we have in our collection
local new_ts_id, new_ts = ildb.create_ts_from_tcb_list(all_tcbs)
return new_ts_id
end
-- For the specified TS, go through the list of TCBs and purge all TCBs that have no corresponding backreference in their TCBS table.
-- If the track section ends up empty, it is deleted in this process.
-- Should the track section still exist after the purge operation, it is returned.
function ildb.purge_ts_tcb_refs(ts_id)
local ts = track_sections[ts_id]
if not ts then
return nil
end
local has_changed = false
local i = 1
while ts.tc_breaks[i] do
-- get TCBS
local sigd = ts.tc_breaks[i]
local tcbs = ildb.get_tcbs(sigd)
if tcbs then
if tcbs.ts_id == ts_id then
-- this one is legit
i = i+1
else
-- this one is to be purged
atdebug("purge_ts_tcb_refs(",ts_id,"): purging",sigd,"(backreference = ",tcbs.ts_id,")")
table.remove(ts.tc_breaks, i)
has_changed = true
end
else
-- if not tcbs: was anyway an orphan, remove it
atdebug("purge_ts_tcb_refs(",ts_id,"): purging",sigd,"(referred nonexisting TCB)")
table.remove(ts.tc_breaks, i)
has_changed = true
end
end
if #ts.tc_breaks == 0 then
-- remove the section completely
atdebug("purge_ts_tcb_refs(",ts_id,"): after purging, the section is empty, is being deleted")
track_sections[ts_id] = nil
return nil
else
if has_changed then
-- needs to update route cache
ildb.update_ts_cache(ts_id)
end
return ts
end
end
-- For the specified TCBS, make sure that the track section referenced by it
-- (a) exists and
-- (b) has a backreference to this TCBS stored in its tc_breaks list
function ildb.tcbs_ensure_ts_ref_exists(sigd)
local tcbs = sigd.tcbs or ildb.get_tcbs(sigd)
if not tcbs or not tcbs.ts_id then return end
local ts = ildb.get_ts(tcbs.ts_id)
if not ts then
atdebug("tcbs_ensure_ts_ref_exists(",sigd,"): TS does not exist, setting to nil")
-- TS is deleted, clear own ts id
tcbs.ts_id = nil
return
end
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)
end
end
function ildb.create_ts_from_tcb_list(sigd_list)
local id = advtrains.random_id(8)
while track_sections[id] do
id = advtrains.random_id(8)
end
atdebug("create_ts_from_tcb_list: sigd_list=",sigd_list, "new ID will be ",id)
local tcbr = {}
-- makes a copy of the sigd list, for use in repair mechanisms where sigd may contain a tcbs field which we dont want
for _, sigd in ipairs(sigd_list) do
table.insert(tcbr, {p=sigd.p, s=sigd.s})
local tcbs = sigd.tcbs or ildb.get_tcbs(sigd)
if tcbs.ts_id then
error("Trying to create TS with TCBS that is already assigned to other section")
end
tcbs.ts_id = id
end
local new_ts = {
tc_breaks = tcbr
}
track_sections[id] = new_ts
-- update the TCB markers
for _, sigd in ipairs(sigd_list) do
advtrains.interlocking.show_tcb_marker(sigd.p)
end
ildb.update_ts_cache(id)
return id, new_ts
end end
-- Updates the turnout cache of the given track section
-- Merges the TS with merge_id into root_id and then deletes merge_id function ildb.update_ts_cache(ts_id)
local function merge_ts(root_id, merge_id) local ts = ildb.get_ts(ts_id)
local rts = ildb.get_ts(root_id) if not ts then
local mts = ildb.get_ts(merge_id) error("Update TS Cache called with nonexisting ts_id "..(ts_id or "nil"))
if not mts then return end -- This may be the case when sync_tcb_neighbors
-- inserts the same id twice. do nothing.
if not ildb.may_modify_ts(rts) then return false end
if not ildb.may_modify_ts(mts) then return false end
-- cobble together the list of TCBs
for _, msigd in ipairs(mts.tc_breaks) do
local tcbs = ildb.get_tcbs(msigd)
if tcbs then
insert_sigd_nodouble(rts.tc_breaks, msigd)
tcbs.ts_id = root_id
end
advtrains.interlocking.show_tcb_marker(msigd.p)
end end
-- done local rscache = {}
track_sections[merge_id] = nil -- start on every of the TS's TCBs, walk the track forward and store locks along the way
-- TODO: Need change in handling of switches
atdebug("update_ts_cache",ts_id,"TODO: implement")
end end
local lntrans = { "A", "B" } local lntrans = { "A", "B" }
@ -312,127 +519,65 @@ local function sigd_to_string(sigd)
return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s] return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s]
end end
-- Check for near TCBs and connect to their TS if they have one, and syncs their data. -- Create a new TCB at the position and update/repair the adjoining sections
function ildb.sync_tcb_neighbors(pos, connid) function ildb.create_tcb_at(pos)
local found_tcbs = { {p = pos, s = connid} } atdebug("create_tcb_at",pos)
local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes) local pts = advtrains.encode_pos(pos)
if not node_ok then track_circuit_breaks[pts] = {[1] = {}, [2] = {}}
atwarn("update_tcb_neighbors but node is NOK: "..minetest.pos_to_string(pos)) local all_tcbs_1 = ildb.get_all_tcbs_adjacent(pos, 1)
return atdebug("TCBs on A side",all_tcbs_1)
end local all_tcbs_2 = ildb.get_all_tcbs_adjacent(pos, 2)
atdebug("TCBs on B side",all_tcbs_2)
--atdebug("Traversing from ",pos, connid) -- perform TS repair
local counter_hit = traverser(found_tcbs, pos, conns, connid, 0) ildb.repair_ts_merge_all(all_tcbs_1)
ildb.repair_ts_merge_all(all_tcbs_2)
local ts_id
local list_eoi = {}
local list_ok = {}
local list_mismatch = {}
local ts_to_merge = {}
for idx, sigd in pairs(found_tcbs) do
local tcbs = ildb.get_tcbs(sigd)
if not tcbs.ts_id then
--atdebug("Sync: put",sigd_to_string(sigd),"into list_eoi")
table.insert(list_eoi, sigd)
elseif not ts_id and tcbs.ts_id then
if not ildb.get_ts(tcbs.ts_id) then
atwarn("Track section database is inconsistent, there's no TS with ID=",tcbs.ts_id)
tcbs.ts_id = nil
table.insert(list_eoi, sigd)
else
--atdebug("Sync: put",sigd_to_string(sigd),"into list_ok")
ts_id = tcbs.ts_id
table.insert(list_ok, sigd)
end
elseif ts_id and tcbs.ts_id and tcbs.ts_id ~= ts_id then
atwarn("Track section database is inconsistent, sections share track!")
atwarn("Merging",tcbs.ts_id,"into",ts_id,".")
table.insert(list_mismatch, sigd)
table.insert(ts_to_merge, tcbs.ts_id)
end
end
if ts_id then
local ts = ildb.get_ts(ts_id)
for _, sigd in ipairs(list_eoi) do
local tcbs = ildb.get_tcbs(sigd)
tcbs.ts_id = ts_id
table.insert(ts.tc_breaks, sigd)
advtrains.interlocking.show_tcb_marker(sigd.p)
end
for _, mts in ipairs(ts_to_merge) do
merge_ts(ts_id, mts)
end
end
end end
function ildb.link_track_sections(merge_id, root_id) -- Create a new TCB at the position and update/repair the now joined section
if merge_id == root_id then function ildb.remove_tcb_at(pos)
return atdebug("remove_tcb_at",pos)
end local pts = advtrains.encode_pos(pos)
merge_ts(root_id, merge_id) local old_tcb = track_circuit_breaks[pts]
end
function ildb.remove_from_interlocking(sigd)
local tcbs = ildb.get_tcbs(sigd)
if not ildb.may_modify_tcbs(tcbs) then return false end
if tcbs.ts_id then
local tsid = tcbs.ts_id
local ts = ildb.get_ts(tsid)
if not ts then
tcbs.ts_id = nil
return true
end
-- remove entry from the list
local idx = 1
while idx <= #ts.tc_breaks do
local cmp = ts.tc_breaks[idx]
if sigd_equal(sigd, cmp) then
table.remove(ts.tc_breaks, idx)
else
idx = idx + 1
end
end
tcbs.ts_id = nil
--ildb.sync_tcb_neighbors(sigd.p, sigd.s)
if #ts.tc_breaks == 0 then
track_sections[tsid] = nil
end
end
advtrains.interlocking.show_tcb_marker(sigd.p)
if tcbs.signal then
return false
end
return true
end
function ildb.remove_tcb(pos)
local pts = advtrains.roundfloorpts(pos)
if not track_circuit_breaks[pts] then
return true --FIX: not an error, because tcb is already removed
end
for connid=1,2 do
if not ildb.remove_from_interlocking({p=pos, s=connid}) then
return false
end
end
track_circuit_breaks[pts] = nil track_circuit_breaks[pts] = nil
-- purge the track sections adjacent
if old_tcb[1].ts_id then
ildb.purge_ts_tcb_refs(old_tcb[1].ts_id)
end
if old_tcb[2].ts_id then
ildb.purge_ts_tcb_refs(old_tcb[2].ts_id)
end
advtrains.interlocking.remove_tcb_marker(pos)
-- If needed, merge the track sections here
ildb.check_and_repair_ts_at_pos(pos)
return true return true
end end
function ildb.dissolve_ts(ts_id) function ildb.create_ts_from_tcbs(sigd)
local ts = ildb.get_ts(ts_id) atdebug("create_ts_from_tcbs",sigd)
if not ildb.may_modify_ts(ts) then return false end local all_tcbs = ildb.get_all_tcbs_adjacent(sigd.p, sigd.s)
local tcbr = advtrains.merge_tables(ts.tc_breaks) ildb.repair_ts_merge_all(all_tcbs, true)
for _,sigd in ipairs(tcbr) do end
ildb.remove_from_interlocking(sigd)
-- Remove the given track section, leaving its TCBs with no section assigned
function ildb.remove_ts(ts_id)
atdebug("remove_ts",ts_id)
local ts = track_sections[ts_id]
if not ts then
error("remove_ts: "..ts_id.." doesn't exist")
end end
-- Note: ts gets removed in the moment of the removal of the last TCB. while ts.tc_breaks[i] do
return true -- get TCBS
local sigd = ts.tc_breaks[i]
local tcbs = ildb.get_tcbs(sigd)
if tcbs then
atdebug("cleared TCB",sigd)
tcbs.ts_id = nil
else
atdebug("orphan TCB",sigd)
end
i = i+1
end
track_sections[ts_id] = nil
end end
-- Returns true if it is allowed to modify any property of a track section, such as -- Returns true if it is allowed to modify any property of a track section, such as
@ -457,38 +602,8 @@ function ildb.may_modify_tcbs(tcbs)
return true return true
end end
-- Utilize the traverser to find the track section at the specified position
-- Returns: -- Signals/IP --
-- ts_id, origin - the first found ts and the sigd of the found tcb
-- nil - there were no TCBs in TRAVERSER_MAX range of the position
-- false - the first found TCB stated End-Of-Interlocking, or track ends were reached
function ildb.get_ts_at_pos(pos)
local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
if not node_ok then
error("get_ts_at_pos but node is NOK: "..minetest.pos_to_string(pos))
end
local limit_hit = false
local found_tcbs = {}
for connid, conn in ipairs(conns) do -- Note: a breadth-first-search would be better for performance
limit_hit = limit_hit or traverser(found_tcbs, pos, conns, connid, 0, 1)
if #found_tcbs >= 1 then
local tcbs = ildb.get_tcbs(found_tcbs[1])
local ts
if tcbs.ts_id then
return tcbs.ts_id, found_tcbs[1]
else
return false
end
end
end
if limit_hit then
-- there was at least one limit hit
return nil
else
-- all traverser ends were track ends
return false
end
end
-- returns the sigd the signal at pos belongs to, if this is known -- returns the sigd the signal at pos belongs to, if this is known

View File

@ -88,12 +88,8 @@ minetest.register_node("advtrains_interlocking:tcb_node", {
local tcb = ildb.get_tcb(tcbpos) local tcb = ildb.get_tcb(tcbpos)
if not tcb then return true end if not tcb then return true end
for connid=1,2 do for connid=1,2 do
if tcb[connid].ts_id or tcb[connid].signal then if tcb[connid].signal then
minetest.chat_send_player(pname, "Can't remove TCB: Both sides must have no track section and no signal assigned!") minetest.chat_send_player(pname, "Can't remove TCB: Both sides must have no signal assigned!")
return false
end
if not ildb.may_modify_tcbs(tcb[connid]) then
minetest.chat_send_player(pname, "Can't remove TCB: Side "..connid.." forbids modification (shouldn't happen).")
return false return false
end end
end end
@ -105,15 +101,7 @@ minetest.register_node("advtrains_interlocking:tcb_node", {
local tcbpts = oldmetadata.fields.tcb_pos local tcbpts = oldmetadata.fields.tcb_pos
if tcbpts and tcbpts ~= "" then if tcbpts and tcbpts ~= "" then
local tcbpos = minetest.string_to_pos(tcbpts) local tcbpos = minetest.string_to_pos(tcbpts)
local success = ildb.remove_tcb(tcbpos) ildb.remove_tcb_at(tcbpos)
if success and player then
minetest.chat_send_player(player:get_player_name(), "TCB has been removed.")
else
minetest.chat_send_player(player:get_player_name(), "Failed to remove TCB!")
minetest.set_node(pos, oldnode)
local meta = minetest.get_meta(pos)
meta:set_string("tcb_pos", minetest.pos_to_string(tcbpos))
end
end end
end, end,
}) })
@ -167,15 +155,13 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
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, advtrains.all_tracktypes)
if node_ok and #conns == 2 then if node_ok and #conns == 2 then
local ok = ildb.create_tcb(pos) -- if there is already a tcb here, reassign it
if ildb.get_tcb(pos) then
if not ok then minetest.chat_send_player(pname, "Configuring TCB: Already existed at this position, it is now linked to this TCB marker")
minetest.chat_send_player(pname, "Configuring TCB: TCB already exists at this position! It has now been re-assigned.") else
ildb.create_tcb_at(pos)
end end
ildb.sync_tcb_neighbors(pos, 1)
ildb.sync_tcb_neighbors(pos, 2)
local meta = minetest.get_meta(tcbnpos) local meta = minetest.get_meta(tcbnpos)
meta:set_string("tcb_pos", minetest.pos_to_string(pos)) meta:set_string("tcb_pos", minetest.pos_to_string(pos))
meta:set_string("infotext", "TCB assigned to "..minetest.pos_to_string(pos)) meta:set_string("infotext", "TCB assigned to "..minetest.pos_to_string(pos))
@ -234,22 +220,12 @@ local function mktcbformspec(tcbs, btnpref, offset, pname)
ts = ildb.get_ts(tcbs.ts_id) ts = ildb.get_ts(tcbs.ts_id)
end end
if ts then if ts then
form = form.."label[0.5,"..offset..";Side "..btnpref..": "..minetest.formspec_escape(ts.name).."]" form = form.."label[0.5,"..offset..";Side "..btnpref..": "..minetest.formspec_escape(ts.name or tcbs.ts_id).."]"
form = form.."button[0.5,"..(offset+0.5)..";5,1;"..btnpref.."_gotots;Show track section]" form = form.."button[0.5,"..(offset+0.5)..";5,1;"..btnpref.."_gotots;Show track section]"
if ildb.may_modify_tcbs(tcbs) then
-- Note: the security check to prohibit those actions is located in database.lua in the corresponding functions.
form = form.."button[0.5,"..(offset+1.5)..";2.5,1;"..btnpref.."_update;Update near TCBs]"
form = form.."button[3 ,"..(offset+1.5)..";2.5,1;"..btnpref.."_remove;Remove from section]"
end
else else
tcbs.ts_id = nil tcbs.ts_id = nil
form = form.."label[0.5,"..offset..";Side "..btnpref..": ".."End of interlocking]" 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]" 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
end end
if tcbs.signal then if tcbs.signal then
form = form.."button[0.5,"..(offset+2.5)..";5,1;"..btnpref.."_sigdia;Signalling]" form = form.."button[0.5,"..(offset+2.5)..";5,1;"..btnpref.."_sigdia;Signalling]"
@ -297,11 +273,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local tcb = ildb.get_tcb(pos) local tcb = ildb.get_tcb(pos)
if not tcb then return end if not tcb then return end
local f_gotots = {fields.A_gotots, fields.B_gotots} local f_gotots = {fields.A_gotots, fields.B_gotots}
local f_update = {fields.A_update, fields.B_update}
local f_remove = {fields.A_remove, fields.B_remove}
local f_makeil = {fields.A_makeil, fields.B_makeil} local f_makeil = {fields.A_makeil, fields.B_makeil}
local f_setlocked = {fields.A_setlocked, fields.B_setlocked}
local f_setfree = {fields.A_setfree, fields.B_setfree}
local f_asnsig = {fields.A_asnsig, fields.B_asnsig} local f_asnsig = {fields.A_asnsig, fields.B_asnsig}
local f_sigdia = {fields.A_sigdia, fields.B_sigdia} local f_sigdia = {fields.A_sigdia, fields.B_sigdia}
@ -312,29 +284,12 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
advtrains.interlocking.show_ts_form(tcbs.ts_id, pname) advtrains.interlocking.show_ts_form(tcbs.ts_id, pname)
return return
end end
if f_update[connid] then
ildb.sync_tcb_neighbors(pos, connid)
end
if f_remove[connid] then
ildb.remove_from_interlocking({p=pos, s=connid})
end
else else
if f_makeil[connid] then if f_makeil[connid] then
-- try sinc_tcb_neighbors first
ildb.sync_tcb_neighbors(pos, connid)
-- if that didn't work, create new section
if not tcbs.ts_id then if not tcbs.ts_id then
ildb.create_ts({p=pos, s=connid}) ildb.create_ts_from_tcbs({p=pos, s=connid})
ildb.sync_tcb_neighbors(pos, connid)
end end
end end
-- non-interlocked
if f_setfree[connid] then
tcbs.section_free = true
end
if f_setlocked[connid] then
tcbs.section_free = nil
end
end end
if f_asnsig[connid] and not tcbs.signal then if f_asnsig[connid] and not tcbs.signal then
minetest.chat_send_player(pname, "Configuring TCB: Please punch the signal to assign.") minetest.chat_send_player(pname, "Configuring TCB: Please punch the signal to assign.")
@ -357,10 +312,7 @@ end)
-- TS Formspec -- TS Formspec
-- textlist selection temporary storage function advtrains.interlocking.show_ts_form(ts_id, pname)
local ts_pselidx = {}
function advtrains.interlocking.show_ts_form(ts_id, pname, sel_tcb)
if not minetest.check_player_privs(pname, "interlocking") then if not minetest.check_player_privs(pname, "interlocking") then
minetest.chat_send_player(pname, "Insufficient privileges to use this!") minetest.chat_send_player(pname, "Insufficient privileges to use this!")
return return
@ -369,7 +321,7 @@ function advtrains.interlocking.show_ts_form(ts_id, pname, sel_tcb)
if not ts_id then return end if not ts_id then return end
local form = "size[10,10]label[0.5,0.5;Track Section Detail - "..ts_id.."]" local form = "size[10,10]label[0.5,0.5;Track Section Detail - "..ts_id.."]"
form = form.."field[0.8,2;5.2,1;name;Section name;"..minetest.formspec_escape(ts.name).."]" form = form.."field[0.8,2;5.2,1;name;Section name;"..minetest.formspec_escape(ts.name or "").."]"
form = form.."button[5.5,1.7;1,1;setname;Set]" form = form.."button[5.5,1.7;1,1;setname;Set]"
local hint local hint
@ -382,26 +334,8 @@ function advtrains.interlocking.show_ts_form(ts_id, pname, sel_tcb)
form = form.."textlist[0.5,3;5,3;tcblist;"..table.concat(strtab, ",").."]" form = form.."textlist[0.5,3;5,3;tcblist;"..table.concat(strtab, ",").."]"
if ildb.may_modify_ts(ts) then if ildb.may_modify_ts(ts) then
form = form.."button[5.5,4;4,1;remove;Remove Section]"
if players_link_ts[pname] then
local other_id = players_link_ts[pname]
local other_ts = ildb.get_ts(other_id)
if other_ts then
if ildb.may_modify_ts(other_ts) then
form = form.."button[5.5,3;3.5,1;mklink;Join with "..minetest.formspec_escape(other_ts.name).."]"
form = form.."button[9 ,3;0.5,1;cancellink;X]"
end
end
else
form = form.."button[5.5,3;4,1;link;Join into other section]"
hint = 1
end
form = form.."button[5.5,4;4,1;dissolve;Dissolve Section]"
form = form.."tooltip[dissolve;This will remove the track section and set all its end points to End Of Interlocking]" form = form.."tooltip[dissolve;This will remove the track section and set all its end points to End Of Interlocking]"
if sel_tcb then
form = form.."button[5.5,5;4,1;del_tcb;Unlink selected TCB]"
hint = 2
end
else else
hint=3 hint=3
end end
@ -421,16 +355,11 @@ function advtrains.interlocking.show_ts_form(ts_id, pname, sel_tcb)
form = form.."button[5.5,7;4,1;reset;Reset section state]" form = form.."button[5.5,7;4,1;reset;Reset section state]"
if hint == 1 then if hint == 3 then
form = form.."label[0.5,0.75;Use the 'Join' button to designate rail crosses and link not listed far-away TCBs]"
elseif hint == 2 then
form = form.."label[0.5,0.75;Unlinking a TCB will set it to non-interlocked mode.]"
elseif hint == 3 then
form = form.."label[0.5,0.75;You cannot modify track sections when a route is set or a train is on the section.]" form = form.."label[0.5,0.75;You cannot modify track sections when a route is set or a train is on the section.]"
--form = form.."label[0.5,1;Trying to unlink a TCB directly connected to this track will not work.]" --form = form.."label[0.5,1;Trying to unlink a TCB directly connected to this track will not work.]"
end end
ts_pselidx[pname]=sel_tcb
minetest.show_formspec(pname, "at_il_tsconfig_"..ts_id, form) minetest.show_formspec(pname, "at_il_tsconfig_"..ts_id, form)
end end
@ -442,45 +371,15 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
return return
end end
-- independent of the formspec, clear this whenever some formspec event happens -- independent of the formspec, clear this whenever some formspec event happens
local tpsi = ts_pselidx[pname]
ts_pselidx[pname] = nil
local ts_id = string.match(formname, "^at_il_tsconfig_(.+)$") local ts_id = string.match(formname, "^at_il_tsconfig_(.+)$")
if ts_id and not fields.quit then if ts_id and not fields.quit then
local ts = ildb.get_ts(ts_id) local ts = ildb.get_ts(ts_id)
if not ts then return end if not ts then return end
local sel_tcb
if fields.tcblist then
local tev = minetest.explode_textlist_event(fields.tcblist)
sel_tcb = tev.index
ts_pselidx[pname] = sel_tcb
elseif tpsi then
sel_tcb = tpsi
end
if ildb.may_modify_ts(ts) then if ildb.may_modify_ts(ts) then
if players_link_ts[pname] then if fields.remove then
if fields.cancellink then ildb.remove_ts(ts_id)
players_link_ts[pname] = nil
elseif fields.mklink then
ildb.link_track_sections(players_link_ts[pname], ts_id)
players_link_ts[pname] = nil
end
end
if fields.del_tcb and sel_tcb and sel_tcb > 0 and sel_tcb <= #ts.tc_breaks then
if not ildb.remove_from_interlocking(ts.tc_breaks[sel_tcb]) then
minetest.chat_send_player(pname, "Please unassign signal first!")
end
sel_tcb = nil
end
if fields.link then
players_link_ts[pname] = ts_id
end
if fields.dissolve then
ildb.dissolve_ts(ts_id)
minetest.close_formspec(pname, formname) minetest.close_formspec(pname, formname)
return return
end end
@ -489,7 +388,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
if fields.setname then if fields.setname then
ts.name = fields.name ts.name = fields.name
if ts.name == "" then if ts.name == "" then
ts.name = "Section "..ts_id ts.name = nil
end end
end end
@ -566,7 +465,7 @@ function advtrains.interlocking.show_tcb_marker(pos)
ts = ildb.get_ts(tcbs.ts_id) ts = ildb.get_ts(tcbs.ts_id)
end end
if ts then if ts then
itex[connid] = ts.name itex[connid] = ts.name or tcbs.ts_id or "???"
else else
itex[connid] = "--EOI--" itex[connid] = "--EOI--"
end end
@ -589,6 +488,47 @@ function advtrains.interlocking.show_tcb_marker(pos)
markerent[pts] = obj markerent[pts] = obj
end end
function advtrains.interlocking.remove_tcb_marker(pos)
local pts = advtrains.roundfloorpts(pos)
if markerent[pts] then
markerent[pts]:remove()
end
markerent[pts] = nil
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
end
end
-- Signalling formspec - set routes a.s.o -- Signalling formspec - set routes a.s.o
-- textlist selection temporary storage -- textlist selection temporary storage

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -3,14 +3,64 @@
local ilrs = advtrains.interlocking.route local ilrs = advtrains.interlocking.route
local function node_right_click(pos, pname)
if advtrains.is_passive(pos) then
local form = "size[7,5]label[0.5,0.5;Route lock inspector]"
local pts = minetest.pos_to_string(pos)
local rtl = ilrs.has_route_lock(pts)
if rtl then
form = form.."label[0.5,1;Route locks currently put:\n"..rtl.."]"
form = form.."button_exit[0.5,3.5; 5,1;clear;Clear]"
else
form = form.."label[0.5,1;No route locks set]"
form = form.."button_exit[0.5,3.5; 5,1;emplace;Emplace manual lock]"
end
minetest.show_formspec(pname, "at_il_rtool_"..pts, form)
return
end
-- If not a turnout, check the track section and show a form
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
end
local ts_id = advtrains.interlocking.db.check_and_repair_ts_at_pos(pos)
if ts_id then
advtrains.interlocking.show_ts_form(ts_id, pname)
else
minetest.chat_send_player(pname, "No track section at this location!")
end
end
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
end
local ts_id = advtrains.interlocking.db.check_and_repair_ts_at_pos(pos)
if ts_id then
advtrains.interlocking.highlight_track_section(pos)
else
minetest.chat_send_player(pname, "No track section at this location!")
end
end
minetest.register_craftitem("advtrains_interlocking:tool",{ minetest.register_craftitem("advtrains_interlocking:tool",{
description = "Interlocking tool\nright-click turnouts to inspect route locks", description = "Interlocking tool\nPunch: Highlight track section\nPlace: check route locks/show track section info",
groups = {cracky=1}, -- key=name, value=rating; rating=1..3. groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "at_il_tool.png", inventory_image = "at_il_tool.png",
wield_image = "at_il_tool.png", wield_image = "at_il_tool.png",
stack_max = 1, stack_max = 1,
on_place = function(itemstack, placer, pointed_thing) on_place = function(itemstack, player, pointed_thing)
local pname = placer:get_player_name() local pname = player:get_player_name()
if not pname then if not pname then
return return
end end
@ -20,27 +70,23 @@ minetest.register_craftitem("advtrains_interlocking:tool",{
end end
if pointed_thing.type=="node" then if pointed_thing.type=="node" then
local pos=pointed_thing.under local pos=pointed_thing.under
if advtrains.is_passive(pos) then node_right_click(pos, pname)
local form = "size[7,5]label[0.5,0.5;Route lock inspector]"
local pts = minetest.pos_to_string(pos)
local rtl = ilrs.has_route_lock(pts)
if rtl then
form = form.."label[0.5,1;Route locks currently put:\n"..rtl.."]"
form = form.."button_exit[0.5,3.5; 5,1;clear;Clear]"
else
form = form.."label[0.5,1;No route locks set]"
form = form.."button_exit[0.5,3.5; 5,1;emplace;Emplace manual lock]"
end
minetest.show_formspec(pname, "at_il_rtool_"..pts, form)
else
minetest.chat_send_player(pname, "Cannot use this here.")
return
end
end end
end, end,
on_use = function(itemstack, player, pointed_thing)
local pname = player:get_player_name()
if not pname then
return
end
if not minetest.check_player_privs(pname, {interlocking=true}) then
minetest.chat_send_player(pname, "Insufficient privileges to use this!")
return
end
if pointed_thing.type=="node" then
local pos=pointed_thing.under
node_left_click(pos, pname)
end
end
}) })
minetest.register_on_player_receive_fields(function(player, formname, fields) minetest.register_on_player_receive_fields(function(player, formname, fields)