-- route_ui.lua-- User interface for showing and editing routes-- Get current translatorlocal S = advtrains.interlocking.translate
local atil = advtrains.interlocking
local ildb = atil.db
local F = advtrains.formspec
-- TODO duplicatelocal lntrans = {"A","B"}local functionsigd_to_string(sigd)return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s]end-- indexed by pnamelocal sel_rpartcache = {}function atil.show_route_edit_form(pname, sigd, routeid, sel_rpartidx)if not minetest.check_player_privs(pname, {train_operator=true, interlocking=true})then
minetest.chat_send_player(pname,S("Insufficient privileges to use this!"))returnendlocal tcbs = atil.db.get_tcbs(sigd)if not tcbs then return endlocal route = tcbs.routes[routeid]if not route then return endlocal form ="size[9,11]label[0.5,0.2;"..S("Route overview").."]"
form = form.."field[0.8,1.2;6.5,1;name;"..S("Route name")..";"..minetest.formspec_escape(route.name).."]"
form = form.."button[7.0,0.9;1.5,1;setname;"..S("Set").."]"-- construct textlist for route informationlocal tab = {}local tabref = {}local functionitab(rseg, t, rty, rpara)
tab[#tab+1] = minetest.formspec_escape(string.gsub(t,","," "))
tabref[#tab] = { [rty] =true, param = rpara, seg = rseg, idx = #tab }enditab(1,"("..(tcbs.signal_name or"+")..") Route #"..routeid,"signal", sigd)-- this code is partially copy-pasted from routesetting.lua-- we start at the tc designated by signallocal c_sigd = sigd
local i =1local c_tcbs, c_ts_id, c_ts, c_rseg
while c_sigd and i<=#route do
c_tcbs = ildb.get_tcbs(c_sigd)if not c_tcbs thenitab(i,"-!- "..S("TCB at @1 is missing",sigd_to_string(c_sigd)),"err",nil)breakend
c_ts_id = c_tcbs.ts_id
if not c_ts_id thenitab(i,"-!- "..S("Track section after @1 missing",sigd_to_string(c_sigd)),"err",nil)breakend
c_ts = ildb.get_ts(c_ts_id)
c_rseg = route[i]local signame ="-"if c_tcbs and c_tcbs.signal then signame = c_tcbs.signal_name or"o"enditab(i,""..i.." "..sigd_to_string(c_sigd).." ("..signame..")","signal", c_sigd)itab(i,"= "..(c_ts and c_ts.name or c_ts_id).." ="..(c_rseg.call_on and" [CO]"or""),"section", c_ts_id)if c_rseg.locks thenfor pts, state inpairs(c_rseg.locks)dolocal pos = advtrains.decode_pos(pts)itab(i,"L "..pts.." -> "..state,"lock", pos)if not advtrains.is_passive(pos)thenitab(i,"-!- "..S("Turnout/component missing at @1", minetest.pos_to_string(pos)),"err",nil)breakendendend-- sanity check, is section at next the same as the current?local nvar = c_rseg.nextif nvar thenlocal re_tcbs = ildb.get_tcbs({p = nvar.p, s = (nvar.s==1)and2or1})if not re_tcbs thenitab(i,"-!- "..S("TCB at @1 is missing", minetest.pos_to_string(nvar.p)),"err",nil)breakelseif not re_tcbs.ts_id thenitab(i,"-!- "..S("TCB at @1 is not assigned to previous track section", minetest.pos_to_string(nvar.p)),"err",nil)breakelseif re_tcbs.ts_id~=c_ts_id thenitab(i,"-!- "..S("TCB at @1 has different section than previous TCB", minetest.pos_to_string(nvar.p)),"err",nil)breakendend-- advance
c_sigd = nvar
i = i +1endif c_sigd thenlocal e_tcbs = ildb.get_tcbs(c_sigd)local signame ="-"if e_tcbs and e_tcbs.signal then signame = e_tcbs.signal_name or"o"enditab(i,"E "..sigd_to_string(c_sigd).." ("..signame..")","end", c_sigd)elseitab(i,"E (none)","end",nil)enditab(i,S("(More Options)"),"advanced",nil)if not sel_rpartidx then sel_rpartidx =1end
form = form.."textlist[0.5,2;3.5,3.9;routelog;"..table.concat(tab,",")..";"..(sel_rpartidx or1)..";false]"-- to the right of rtelog, controls are displayed for the thing in focus-- What is in focus is determined by the parameter sel_rpartidxlocal sel_rpart = tabref[sel_rpartidx]--atdebug("sel rpart",sel_rpart)if sel_rpart and sel_rpart.signal then-- get TCBS here and rseg selectedlocal s_tcbs = ildb.get_tcbs(sel_rpart.param)local rseg = route[sel_rpart.seg]-- main aspect listlocal signalpos = s_tcbs and s_tcbs.signal
if signalpos and rseg then
form = form..F.label(4.5,2,S("Signal Aspect:"))local ndef = signalpos and advtrains.ndb.get_ndef(signalpos)if ndef and ndef.advtrains and ndef.advtrains.main_aspects thenlocal entries = {S("<Default Aspect>") }local sel =1for i, mae inipairs(ndef.advtrains.main_aspects)do
entries[i+1] = mae.description
if mae.name == rseg.main_aspect then
sel = i+1endend
form = form..F.dropdown(4.5,3.0,4,"sa_main_aspect", entries, sel,true)end-- checkbox for assign distant signallocal assign_dst = rseg.assign_dst
if assign_dst ==nil then
assign_dst =true-- special behavior when assign_dst is nil (and not false):-- defaults to true (= minimal user configuration overhead) (changed on 2025-09-23 after some consideration)-- Note: on save, the value will be fixed at either false or trueend
form = form..string.format("checkbox[4.5,4.0;sa_distant;"..S("Announce distant signal")..";%s]", assign_dst)else
form = form..F.label(4.5,2,S("No Signal at this TCB"))endelseif sel_rpart and sel_rpart.section thenlocal rseg = route[sel_rpart.seg]if rseg then
form = form..F.label(4.5,2,S("Section Options:"))-- checkbox for call-on
form = form..string.format("checkbox[4.5,4.0;se_callon;"..S("Call-on (section may be occupied)")..";%s]", rseg.call_on)endelseif sel_rpart and sel_rpart.advanced then
form = form..F.label(4.5,2,S("Advanced Options:"))-- checkbox for call-on
form = form..string.format("checkbox[4.5,4.0;ad_use_rscache;"..S("Auto lock turnouts")..";%s]", route.use_rscache)elseif sel_rpart and sel_rpart.err then
form = form.."textarea[4.5,2.5;4,4;errorta;"..S("Error:")..";"..tab[sel_rpartidx].."]"else
form = form..F.label(4.5,2,S("<< Select a route part to edit options"))end
form = form.."button[0.5,6;1,1;prev;<<<]"
form = form.."button[1.5,6;1,1;back;"..routeid.."/"..#tcbs.routes.."]"
form = form.."button[2.5,6;1,1;next;>>>]"--if route.smartroute_generated or route.default_autoworking then-- form = form.."button[3.5,6;2,1;noautogen;Clr Autogen]"--end
form = form.."button[5.5,6;3,1;delete;"..S("Delete Route").."]"
form = form.."button[0.5,7;3,1;back;"..S("Back to signal").."]"
form = form.."button[3.5,7;2,1;clone;"..S("Clone Route").."]"
form = form.."button[5.5,7;3,1;newfrom;"..S("New From Route").."]"--atdebug(route.ars)
form = form.."style[ars;font=mono]"
form = form.."textarea[0.8,8.3;5,3;ars;"..S("ARS Rule List")..";"..atil.ars_to_text(route.ars).."]"
form = form.."button[5.5,8.23;3,1;savears;"..S("Save ARS List").."]"local formname ="at_il_routeedit_"..minetest.pos_to_string(sigd.p).."_"..sigd.s.."_"..routeid
minetest.show_formspec(pname, formname, form)-- record selected entry from routelog for receive fields callback
sel_rpartcache[pname] = sel_rpart
end
minetest.register_on_player_receive_fields(function(player, formname, fields)local pname = player:get_player_name()-- retreive sel_rpart from the cache in any case and clear it outlocal sel_rpart = sel_rpartcache[pname]if not minetest.check_player_privs(pname, {train_operator=true, interlocking=true})thenreturnendlocal pts, connids, routeids =string.match(formname,"^at_il_routeedit_([^_]+)_(%d)_(%d+)$")local pos, connid, routeid
if pts then
pos = minetest.string_to_pos(pts)
connid =tonumber(connids)
routeid =tonumber(routeids)if not connid or connid<1or connid>2then return endif not routeid then return endendif pos and connid and routeid and not fields.quit thenlocal sigd = {p=pos, s=connid}local tcbs = ildb.get_tcbs(sigd)if not tcbs then return endlocal route = tcbs.routes[routeid]if not route then return endif fields.prev then
atil.show_route_edit_form(pname, sigd, routeid -1)returnendif fields.nextthen
atil.show_route_edit_form(pname, sigd, routeid +1)returnendif fields.setname and fields.name then
route.name = fields.name
endif fields.sa_main_aspect and sel_rpart and sel_rpart.signal thenlocal idx =tonumber(fields.sa_main_aspect)-- get TCBS here and rseg selectedlocal s_tcbs = ildb.get_tcbs(sel_rpart.param)local rseg = route[sel_rpart.seg]-- main aspect listlocal signalpos = s_tcbs and s_tcbs.signal
if rseg then
rseg.main_aspect =nilif idx >1thenlocal ndef = signalpos and advtrains.ndb.get_ndef(signalpos)if ndef and ndef.advtrains and ndef.advtrains.main_aspects then
rseg.main_aspect = ndef.advtrains.main_aspects[idx -1].name
endendendendif fields.sa_distant and sel_rpart and sel_rpart.signal thenlocal rseg = route[sel_rpart.seg]if rseg then
rseg.assign_dst = minetest.is_yes(fields.sa_distant)endendif fields.se_callon and sel_rpart and sel_rpart.section thenlocal rseg = route[sel_rpart.seg]if rseg then
rseg.call_on = minetest.is_yes(fields.se_callon)-- reshow form to update CO marker
atil.show_route_edit_form(pname, sigd, routeid, sel_rpart.idx)returnendendif fields.ad_use_rscache then
route.use_rscache = minetest.is_yes(fields.ad_use_rscache)end--if fields.noautogen then-- route.smartroute_generated = nil-- route.default_autoworking = nil-- -- reshow form for the button to disappear-- atil.show_route_edit_form(pname, sigd, routeid, sel_rpart and sel_rpart.idx)-- return--endif fields.delete then-- if something set the route in the meantime, make sure this doesn't break.
atil.route.update_route(sigd, tcbs,nil,true)table.remove(tcbs.routes, routeid)
advtrains.interlocking.show_signalling_form(sigd, pname)-- cleanup
sel_rpartcache[pname] =nilreturnendif fields.clone then-- if something set the route in the meantime, make sure this doesn't break.
atil.route.update_route(sigd, tcbs,nil,true)local rcopy =table.copy(route)
rcopy.name = route.name.."_copy"
rcopy.smartroute_generated =niltable.insert(tcbs.routes, routeid+1, rcopy)
advtrains.interlocking.show_signalling_form(sigd, pname)-- cleanup
sel_rpartcache[pname] =nilreturnendif fields.newfrom then
advtrains.interlocking.init_route_prog(pname, sigd, route)
minetest.close_formspec(pname, formname)
tcbs.ars_ignore_next =nil-- cleanup
sel_rpartcache[pname] =nilreturnendif fields.ars and fields.savears then
route.ars = atil.text_to_ars(fields.ars)--atdebug(route.ars)endif fields.back then
advtrains.interlocking.show_signalling_form(sigd, pname)-- cleanup
sel_rpartcache[pname] =nilreturnend-- if an entry was selected in the textlist (and its not the current one) update the formif fields.routelog thenlocal prev_idx = sel_rpart and sel_rpart.idx or1local tev = minetest.explode_textlist_event(fields.routelog)local new_idx = tev and tev.index
if new_idx and new_idx ~= prev_idx then
atil.show_route_edit_form(pname, sigd, routeid, new_idx)returnendendif fields.quit then-- cleanup
sel_rpartcache[pname] =nilendendend)