-- Track Circuit Breaks and Track Sections - Player interaction-- Get current translatorlocal S = advtrains.interlocking.translate
local players_assign_tcb = {}local players_assign_signal = {}local players_assign_xlink = {}local players_link_ts = {}local players_assign_fixedlocks = {}local atil = advtrains.interlocking
local ildb = advtrains.interlocking.db
local ilrs = advtrains.interlocking.route
local sigd_equal = advtrains.interlocking.sigd_equal
local lntrans = {"A","B"}local functionsigd_to_string(sigd)return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s]end
advtrains.interlocking.sigd_to_string = sigd_to_string
minetest.register_node("advtrains_interlocking:tcb_node", {
drawtype ="mesh",
paramtype="light",
paramtype2="facedir",
walkable =false,
selection_box = {type="fixed",
fixed = {-1/6, -1/2, -1/6,1/6,1/4,1/6},},
mesh ="at_il_tcb_node.obj",
tiles = {"at_il_tcb_node.png"},
description=S("Track Circuit Break"),
sunlight_propagates=true,
groups = {
cracky=3,
not_blocking_trains=1,--save_in_at_nodedb=2,
at_il_track_circuit_break =1,},
after_place_node =function(pos, node, player)local meta = minetest.get_meta(pos)
meta:set_string("infotext",S("Unconfigured Track Circuit Break, right-click to assign."))end,
on_rightclick =function(pos, node, player)local pname = player:get_player_name()if not minetest.check_player_privs(pname,"interlocking")then
minetest.chat_send_player(pname,S("Insufficient privileges to use this!"))returnendlocal meta = minetest.get_meta(pos)local tcbpts = meta:get_string("tcb_pos")if tcbpts ~=""thenlocal tcbpos = minetest.string_to_pos(tcbpts)local tcb = ildb.get_tcb(tcbpos)if tcb then
advtrains.interlocking.show_tcb_form(tcbpos, pname)else
minetest.chat_send_player(pname,S("This TCB has been removed. Please dig marker."))endelse--unconfigured
minetest.chat_send_player(pname,S("Configuring TCB: Please punch the rail you want to assign this TCB to."))
players_assign_tcb[pname] = pos
endend,--on_punch = function(pos, node, player)-- local meta = minetest.get_meta(pos)-- local tcbpts = meta:get_string("tcb_pos")-- if tcbpts ~= "" then-- local tcbpos = minetest.string_to_pos(tcbpts)-- advtrains.interlocking.show_tcb_marker(tcbpos)-- end --end,
can_dig =function(pos, player)if player ==nil then return false endlocal pname = player:get_player_name()-- Those markers can only be dug when all adjacent TS's are set-- as EOI.local meta = minetest.get_meta(pos)local tcbpts = meta:get_string("tcb_pos")if tcbpts ~=""thenif not minetest.check_player_privs(pname,"interlocking")then
minetest.chat_send_player(pname,S("Insufficient privileges to use this!"))returnendlocal tcbpos = minetest.string_to_pos(tcbpts)local tcb = ildb.get_tcb(tcbpos)if not tcb then return true endfor connid=1,2doif tcb[connid].signal then
minetest.chat_send_player(pname,S("Can't remove TCB: Both sides must have no signal assigned!"))return falseendendendreturn trueend,
after_dig_node =function(pos, oldnode, oldmetadata, player)if not oldmetadata or not oldmetadata.fields then return endlocal pname = player:get_player_name()local tcbpts = oldmetadata.fields.tcb_pos
if tcbpts and tcbpts ~=""thenlocal tcbpos = minetest.string_to_pos(tcbpts)
ildb.remove_tcb_at(tcbpos, pname)endend,})-- Crafting-- set some fallbackslocal tcb_core ="default:mese_crystal"local tcb_secondary ="default:mese_crystal_fragment"--alternative recipe items--coreif minetest.get_modpath("basic_materials")then
tcb_core ="basic_materials:ic"elseif minetest.get_modpath("technic")then
tcb_core ="technic:control_logic_unit"end--print("TCB Core: "..tcb_core)--secondaryif minetest.get_modpath("mesecons")then
tcb_secondary ='mesecons:wire_00000000_off'end--print("TCB Secondary: "..tcb_secondary)
minetest.register_craft({
output ='advtrains_interlocking:tcb_node 4',
recipe = {{tcb_secondary,tcb_core,tcb_secondary},{'advtrains:dtrack_placer','','advtrains:dtrack_placer'}},--actually use track in the tcb recipe
replacements = {{"advtrains:dtrack_placer","advtrains:dtrack_placer"},{"advtrains:dtrack_placer","advtrains:dtrack_placer"},}})--nil the temp crafting variables
tcb_core=nil
tcb_secondary =nil
minetest.register_on_punchnode(function(pos, node, player, pointed_thing)local pname = player:get_player_name()if not minetest.check_player_privs(pname,"interlocking")thenreturnend-- TCB assignmentlocal tcbnpos = players_assign_tcb[pname]if tcbnpos thenif vector.distance(pos, tcbnpos)<=20thenlocal node_ok, conns, rhe = advtrains.get_rail_info_at(pos)if node_ok and#conns ==2then-- if there is already a tcb here, reassign itif ildb.get_tcb(pos)then
minetest.chat_send_player(pname,S("Configuring TCB: Already existed at this position, it is now linked to this TCB marker"))else
ildb.create_tcb_at(pos, pname)endlocal meta = minetest.get_meta(tcbnpos)
meta:set_string("tcb_pos", minetest.pos_to_string(pos))
meta:set_string("infotext",S("TCB assigned to @1", minetest.pos_to_string(pos)))
minetest.chat_send_player(pname,S("Configuring TCB: Successfully configured TCB"))
advtrains.interlocking.show_tcb_marker(pos)else
minetest.chat_send_player(pname,S("Configuring TCB: This is not a normal two-connection rail! Aborted."))endelse
minetest.chat_send_player(pname,S("Configuring TCB: Node is too far away. Aborted."))end
players_assign_tcb[pname] =nilend-- Signal assignmentlocal sigd = players_assign_signal[pname]if sigd thenif vector.distance(pos, sigd.p)<=50thenlocal is_signal = minetest.get_item_group(node.name,"advtrains_signal") >=2if is_signal thenlocal ndef = minetest.registered_nodes[node.name]if ndef and ndef.advtrains and ndef.advtrains.apply_aspect thenlocal tcbs = ildb.get_tcbs(sigd)if tcbs then
ildb.assign_signal_to_tcbs(pos, sigd)-- use auto-naming
advtrains.interlocking.add_autoname_to_tcbs(tcbs, pname)
minetest.chat_send_player(pname,S("Configuring TCB: Successfully assigned signal."))
advtrains.interlocking.show_ip_form(pos, pname,true)else
minetest.chat_send_player(pname,S("Configuring TCB: Internal error, TCBS doesn't exist. Aborted."))endelse
minetest.chat_send_player(pname,S("Configuring TCB: Cannot use static signals for routesetting. Aborted."))endelse
minetest.chat_send_player(pname,S("Configuring TCB: Not a compatible signal. Aborted."))endelse
minetest.chat_send_player(pname,S("Configuring TCB: Node is too far away. Aborted."))end
players_assign_signal[pname] =nilend-- FixedLocks assignmentlocal ts_id = players_assign_fixedlocks[pname]if ts_id thenif advtrains.is_passive(pos)thenlocal pts = advtrains.encode_pos(pos)local state = advtrains.getstate(pos)local ts = ildb.get_ts(ts_id)if ts and ts.fixed_locks then
minetest.chat_send_player(pname,S("@1 locks in state @2", minetest.pos_to_string(pos), state))
ts.fixed_locks[pts] = state
else
minetest.chat_send_player(pname,S("Error: TS modified, abort!"))
players_assign_fixedlocks[pname] =nilendelse
minetest.chat_send_player(pname,S("Setting fixed locks finished!"))
players_assign_fixedlocks[pname] =nil
ildb.update_rs_cache(ts_id)
advtrains.interlocking.show_ts_form(ts_id, pname)endendend)-- "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)local pname = player:get_player_name()if not minetest.check_player_privs(pname,"interlocking")thenif not fail_silently_on_noprivs then
minetest.chat_send_player(pname,S("Insufficient privileges to use this!"))endreturnendif ildb.get_tcb(pos)then
minetest.chat_send_player(pname,S("TCB already existed at this position, now linked to this node"))else
ildb.create_tcb_at(pos, pname)endif auto_create_self_signal thenlocal sigd = { p = pos, s =1}local tcbs = ildb.get_tcbs(sigd)-- make sure signal doesn't already existif tcbs.signal then
minetest.chat_send_player(pname,S("Signal on B side already assigned!"))returnend
ildb.assign_signal_to_tcbs(pos, sigd)-- assign influence point to itself
ildb.set_ip_signal(advtrains.roundfloorpts(pos),1, pos)-- use auto-naming
advtrains.interlocking.add_autoname_to_tcbs(tcbs, pname)endendend-- 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 calledfunction 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.luaif advtrains.get_train_at_pos(pos)then
minetest.chat_send_player(pname,S("Can't remove track, a train is here!"))return falseend-- end of standard checkslocal 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 thenreturn advtrains.interlocking.signal.can_dig(pos, player)elsereturn trueendend-- TCB existsif not minetest.check_player_privs(pname,"interlocking")thenreturn falseend-- fine to remove (unless signal callback interjects)if is_signal thenreturn advtrains.interlocking.signal.can_dig(pos, player)elsereturn trueendendend-- 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 calledfunction 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)endif ildb.get_tcb(pos)then-- remove the TCB
ildb.remove_tcb_at(pos, pname,true)endendend-- 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 rightclickfunction 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")thenif not fail_silently_on_noprivs then
minetest.chat_send_player(pname,S("Insufficient privileges to use this!"))endreturnendif ildb.get_tcb(pos)then-- TCB already here. go onelse-- otherwise create tcb
ildb.create_tcb_at(pos, pname)endif auto_create_self_signal thenlocal sigd = { p = pos, s =1}local tcbs = ildb.get_tcbs(sigd)-- make sure signal doesn't already existif 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)-- use auto-naming
advtrains.interlocking.add_autoname_to_tcbs(tcbs, pname)end-- in any case open the signalling form nouwlocal control = player:get_player_control()
advtrains.interlocking.show_signal_form(pos, node, pname, control.aux1)returnelse-- not an autosignal. Then show the TCB form
advtrains.interlocking.show_tcb_form(pos, pname)returnendendend-- TCB Formlocal functionmktcbformspec(pos, side, tcbs, offset, pname)local form =""local btnpref = side==1and"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 thisif tcbs.ts_id then
ts = ildb.get_ts(tcbs.ts_id)endif ts then
form = form.."label[0.5,"..offset..";"..S("Side @1", btnpref)..": "..minetest.formspec_escape(ts.name or tcbs.ts_id).."]"
form = form.."button[0.5,"..(offset+0.5)..";5,1;"..btnpref.."_gotots;"..S("Show track section").."]"else
tcbs.ts_id =nil
form = form.."label[0.5,"..offset..";Side "..btnpref..": "..S("End of interlocking").."]"
form = form.."button[0.5,"..(offset+0.5)..";5,1;"..btnpref.."_makeil;"..S("Create Interlocked Track Section").."]"end-- xlinkif tcbs.xlink then
form = form.."label[0.5,"..(offset+1.5)..";"..S("Link: @1", ildb.sigd_to_string(tcbs.xlink)).."]"
form = form.."button[4.5,"..(offset+1.5)..";1,1;"..btnpref.."_xlinkdel;X]"elseif players_assign_xlink[pname]then
form = form.."button[0.5,"..(offset+1.5)..";4,1;"..btnpref.."_xlinklink;"..S("Link @1", 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)..";"..S("No Link").."]"
form = form.."button[4.5,"..(offset+1.5)..";1,1;"..btnpref.."_xlinkadd;+]"endendif tcbs.signal then
form = form.."button[0.5,"..(offset+2.5)..";5,1;"..btnpref.."_sigdia;"..S("Signalling").."]"else
form = form.."button[0.5,"..(offset+2.5)..";5,1;"..btnpref.."_asnsig;"..S("Assign a signal").."]"endreturn form
endfunction advtrains.interlocking.show_tcb_form(pos, pname)if not minetest.check_player_privs(pname,"interlocking")then
minetest.chat_send_player(pname,S("Insufficient privileges to use this!"))returnendlocal tcb = ildb.get_tcb(pos)if not tcb then return endlocal form ="size[6,9] label[0.5,0.5;"..S("Track Circuit Break Configuration").."]"
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)end--helper: length of nil table is 0local functionnlen(t)if not t then return0endreturn#t
end
minetest.register_on_player_receive_fields(function(player, formname, fields)local pname = player:get_player_name()if not minetest.check_player_privs(pname,"interlocking")thenreturnendlocal pts =string.match(formname,"^at_il_tcbconfig_(.+)$")local pos
if pts then
pos = minetest.string_to_pos(pts)endif pos and not fields.quit thenlocal tcb = ildb.get_tcb(pos)if not tcb then return endlocal f_gotots = {fields.A_gotots, fields.B_gotots}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,2dolocal tcbs = tcb[connid]if tcbs.ts_id thenif f_gotots[connid]then
advtrains.interlocking.show_ts_form(tcbs.ts_id, pname)returnendelseif f_makeil[connid]thenif not tcbs.ts_id then
ildb.create_ts_from_tcbs({p=pos, s=connid})endendendif tcbs.xlink thenif f_xlinkdel[connid]then
ildb.remove_tcb_xlink({p=pos, s=connid})endelselocal osigd = players_assign_xlink[pname]if osigd thenif f_xlinklink[connid]then
ildb.add_tcb_xlink({p=pos, s=connid}, osigd)
players_assign_xlink[pname] =nilelseif f_xlinkabrt[connid]then
players_assign_xlink[pname] =nilendelseif f_xlinkadd[connid]then
players_assign_xlink[pname] = {p=pos, s=connid}
minetest.chat_send_player(pname,S("TCB Link: Select linked TCB now!"))
minetest.close_formspec(pname, formname)return-- to not reopen formendendendif f_asnsig[connid]and not tcbs.signal then
minetest.chat_send_player(pname,S("Configuring TCB: Please punch the signal to assign."))
players_assign_signal[pname] = {p=pos, s=connid}
minetest.close_formspec(pname, formname)returnendif f_sigdia[connid]and tcbs.signal then
advtrains.interlocking.show_signalling_form({p=pos, s=connid}, pname)returnendend
advtrains.interlocking.show_tcb_form(pos, pname)endend)-- TS Formspecfunction advtrains.interlocking.show_ts_form(ts_id, pname)if not minetest.check_player_privs(pname,"interlocking")then
minetest.chat_send_player(pname,S("Insufficient privileges to use this!"))returnendlocal ts = ildb.get_ts(ts_id)if not ts_id then return endlocal form ="size[10.5,10]label[0.5,0.5;"..S("Track Section Detail - @1", ts_id).."]"
form = form.."field[0.8,2;5.2,1;name;"..S("Section name")..";"..minetest.formspec_escape(ts.name or"").."]"
form = form.."button[5.5,1.7;1,1;setname;"..S("Set").."]"local hint
local strtab = {}for idx, sigd inipairs(ts.tc_breaks)do
strtab[#strtab+1] = minetest.formspec_escape(sigd_to_string(sigd))
advtrains.interlocking.show_tcb_marker(sigd.p)end
form = form.."label[0.5,2.5;"..S("Boundary TCBs:").."]"
form = form.."textlist[0.5,3;4,3;tcblist;"..table.concat(strtab,",").."]"-- additional route locks (e.g. for level crossings)
strtab = {}if ts.fixed_locks thenfor pts, state inpairs(ts.fixed_locks)do
strtab[#strtab+1] = minetest.formspec_escape(
minetest.pos_to_string(advtrains.decode_pos(pts)).." = "..state)endend
form = form.."label[5.5,2.5;"..S("Fixed route locks (e.g. level crossings):").."]"
form = form.."textlist[5.5,3;4,3;fixedlocks;"..table.concat(strtab,",").."]"if ildb.may_modify_ts(ts)then
form = form.."button[5.5,6;2,1;flk_add;"..S("Add locks").."]"
form = form.."button[7.5,6;2,1;flk_clear;"..S("Clear locks").."]"
form = form.."button[5.5,8;4,1;remove;"..S("Remove Section").."]"
form = form.."tooltip[remove;"..S("This will remove the track section and set all its end points to End Of Interlocking").."]"else
hint=3endif ts.route then
form = form.."label[0.5,6.1;"..S("Route is set: ")..ts.route.rsn.."]"elseif ts.route_post then
form = form.."label[0.5,6.1;"..S("Section holds @1 route locks.", #(ts.route_post.lcks or{})).."]"end-- occupying trainsif ts.trains and#ts.trains>0then
form = form.."label[0.5,7.1;"..S("Trains on this section:").."]"
form = form.."textlist[0.5,7.7;3,2;trnlist;"..table.concat(ts.trains,",").."]"else
form = form.."label[0.5,7.1;"..S("No trains on this section.").."]"end
form = form.."button[5.5,7;4,1;reset;"..S("Reset section state").."]"if hint ==3then
form = form.."label[0.5,0.75;"..S("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.]"end
minetest.show_formspec(pname,"at_il_tsconfig_"..ts_id, form)end
minetest.register_on_player_receive_fields(function(player, formname, fields)local pname = player:get_player_name()if not minetest.check_player_privs(pname,"interlocking")thenreturnend-- independent of the formspec, clear this whenever some formspec event happenslocal ts_id =string.match(formname,"^at_il_tsconfig_(.+)$")if ts_id and not fields.quit thenlocal ts = ildb.get_ts(ts_id)if not ts then return endif ildb.may_modify_ts(ts)thenif fields.removethen
ildb.remove_ts(ts_id)
minetest.close_formspec(pname, formname)returnendendif fields.setname then
ts.name = fields.name
if ts.name ==""then
ts.name =nilendendif fields.flk_add thenif not ts.fixed_locks then
ts.fixed_locks = {}end
players_assign_fixedlocks[pname] = ts_id
minetest.chat_send_player(pname,S("Punch components to add fixed locks. (punch anything else = end)"))
minetest.close_formspec(pname, formname)returnelseif fields.flk_clear then
ts.fixed_locks =nilendif fields.reset then-- User requested resetting the section-- Show him what this means...local form ="size[7,5]label[0.5,0.5;"..S("Reset track section").."]"
form = form.."label[0.5,1;"..S("This will clear the list of trains\nand the routesetting status of this section.\nAre you sure?").."]"
form = form.."button_exit[0.5,2.5; 5,1;reset;"..S("Yes").."]"
form = form.."button_exit[0.5,3.5; 5,1;cancel;"..S("Cancel").."]"
minetest.show_formspec(pname,"at_il_tsreset_"..ts_id, form)returnend
advtrains.interlocking.show_ts_form(ts_id, pname, sel_tcb)returnend
ts_id =string.match(formname,"^at_il_tsreset_(.+)$")if ts_id and fields.reset thenlocal ts = ildb.get_ts(ts_id)if not ts then return end
ts.trains = {}if ts.route_post then
advtrains.interlocking.route.free_route_locks(ts_id, ts.route_post.locks)end
ts.route_post =nil
ts.route =nilfor _, sigd inipairs(ts.tc_breaks)dolocal tcbs = ildb.get_tcbs(sigd)
advtrains.interlocking.signal.update_route_aspect(tcbs)end
minetest.chat_send_player(pname,S("Reset track section @1!", ts_id))endend)-- TCB marker entities-- table with objectRefslocal markerent = {}
minetest.register_entity("advtrains_interlocking:tcbmarker", {
visual ="mesh",
mesh ="trackplane.b3d",
textures = {"at_il_tcb_marker.png"},
collisionbox = {-1,-0.5,-1,1,-0.4,1},
visual_size = {x=10, y=10},
on_punch =function(self)
self.object:remove()end,
on_rightclick =function(self, player)if self.tcbpos and player then
advtrains.interlocking.show_tcb_form(self.tcbpos, player:get_player_name())endend,
get_staticdata =function()return"STATIC"end,
on_activate =function(self, sdata)if sdata=="STATIC"then self.object:remove()end end,
static_save =false,})function advtrains.interlocking.remove_tcb_marker_pts(pts)if markerent[pts]then
markerent[pts]:remove()
markerent[pts] =nilendendfunction advtrains.interlocking.show_tcb_marker(pos)--atdebug("showing tcb marker",pos)local tcb = ildb.get_tcb(pos)if not tcb then return endlocal node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)if not node_ok then return endlocal yaw = advtrains.conn_angle_median(conns[2].c, conns[1].c)local itex = {}for connid=1,2dolocal tcbs = tcb[connid]local ts
if tcbs.ts_id then
ts = ildb.get_ts(tcbs.ts_id)endif ts then
itex[connid] = ts.name or tcbs.ts_id or"???"else
itex[connid] ="--EOI--"endendlocal pts = advtrains.roundfloorpts(pos)
advtrains.interlocking.remove_tcb_marker_pts(pts)local obj = minetest.add_entity(pos,"advtrains_interlocking:tcbmarker")if not obj then return end
obj:set_yaw(yaw)
obj:set_properties({
infotext ="A = "..itex[1].."\nB = "..itex[2]})local le = obj:get_luaentity()if le then le.tcbpos = pos end
markerent[pts] = obj
endfunction advtrains.interlocking.remove_tcb_marker(pos)local pts = advtrains.roundfloorpts(pos)if markerent[pts]then
markerent[pts]:remove()end
markerent[pts] =nilendlocal 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 anymorefunction advtrains.interlocking.highlight_track_section(pos)local all_tcbs = ildb.get_all_tcbs_adjacent(pos,nil, ts_showparticles_callback)for _,sigd inipairs(all_tcbs)do
advtrains.interlocking.show_tcb_marker(sigd.p)endend-- checks that the given route is still valid (i.e. all its TCBs, sections and locks exist)-- returns true (ok) or false, reason (on issue)function advtrains.interlocking.check_route_valid(route, 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 thenreturn false,S("TCB at @1 is missing",sigd_to_string(c_sigd))end
c_ts_id = c_tcbs.ts_id
if not c_ts_id thenreturn false,S("Track section after @1 missing",sigd_to_string(c_sigd))end
c_ts = ildb.get_ts(c_ts_id)
c_rseg = route[i]if c_rseg.locks thenfor pts, state inpairs(c_rseg.locks)dolocal pos = advtrains.decode_pos(pts)if not advtrains.is_passive(pos)thenreturn false,S("Turnout/component missing at @1", minetest.pos_to_string(pos))endendend-- 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 thenreturn false,S("TCB at @1 is missing", minetest.pos_to_string(nvar.p))elseif not re_tcbs.ts_id thenreturn false,S("TCB at @1 is not assigned to previous track section", minetest.pos_to_string(nvar.p))elseif re_tcbs.ts_id~=c_ts_id thenreturn false,S("TCB at @1 has different section than previous TCB", minetest.pos_to_string(nvar.p))endend-- advance
c_sigd = nvar
i = i +1end-- check end TCBif not c_sigd thenreturn false,S("Final TCBS unset (legacy-style buffer route)")end
c_tcbs = ildb.get_tcbs(c_sigd)if not c_tcbs thenreturn false,S("TCB at @1 is missing",sigd_to_string(c_sigd))endreturn true,nil, c_sigd
end-- Signalling formspec - set routes a.s.o-- textlist selection temporary storagelocal sig_pselidx = {}-- Players having a signalling form openlocal p_open_sig_form = {}function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, called_from_form_update)if not minetest.check_player_privs(pname,"train_operator")then
minetest.chat_send_player(pname,S("Insufficient privileges to use this!"))returnendlocal hasprivs = minetest.check_player_privs(pname,"interlocking")local tcbs = ildb.get_tcbs(sigd)if not tcbs.signal then return endif not tcbs.routes then tcbs.routes = {}endlocal form ="size[7,10.25]label[0.5,0.5;"..S("Signal at @1", minetest.pos_to_string(sigd.p)).."]"
form = form.."field[0.8,1.5;5.2,1;name;"..S("Signal name")..";"..minetest.formspec_escape(tcbs.signal_name or"").."]"
form = form.."button[5.5,1.2;1,1;setname;"..S("Set").."]"if tcbs.routeset theniftype(tcbs.routeset)=="table"thenlocal rtenames = {}for midx,rteid inipairs(tcbs.routeset)dolocal rte = tcbs.routes[rteid]if not rte thenatwarn("Unknown route set from signal!")
tcbs.routeset =nilreturnend
rtenames[midx] = rte.name
end
form = form.."label[0.5,2.5;"..S("Multiple routes are requested (first available is set):").."]"
form = form.."label[0.5,3.0;"..minetest.formspec_escape(table.concat(rtenames,", ")).."]"elselocal rte = tcbs.routes[tcbs.routeset]if not rte thenatwarn("Unknown route set from signal!")
tcbs.routeset =nilreturnend
form = form.."label[0.5,2.5;"..S("A route is requested from this signal:").."]"
form = form.."label[0.5,3.0;"..minetest.formspec_escape(rte.name).."]"end
form = form .."label[0.5,3.5;".. core.formspec_escape(S("Routesetting trigger: @1", tcbs.routeset_trigger orS("Unknown reason"))) .."]"if tcbs.route_committed then
form = form.."label[0.5,4;"..S("Route has been set.").."]"else
form = form.."label[0.5,4;"..S("Waiting for route to be set...").."]"if tcbs.route_rsn then
form = form.."label[0.5,4.5;"..minetest.formspec_escape(tcbs.route_rsn).."]"endendif not tcbs.route_auto then
form = form.."button[0.5,7; 5,1;auto;"..S("Enable Automatic Working").."]"else
form = form.."label[0.5,7 ;"..S("Automatic Working is active.").."]"
form = form.."label[0.5,7.3;"..S("Route is re-set when a train passed.").."]"
form = form.."button[0.5,7.7; 5,1;noauto;"..S("Disable Automatic Working").."]"end
form = form.."button[0.5,6; 5,1;cancelroute;"..S("Cancel Route").."]"elseif not tcbs.route_origin thenif#tcbs.routes >0then-- at least one route is defined, show normal dialoglocal strtab = {}for idx, route inipairs(tcbs.routes)dolocal rname = route.name
local valid = atil.check_route_valid(route, sigd)local clr =""if not valid then
clr ="#FF5555"
rname = rname..S(" (invalid)")elseif route.ars then
clr ="#FFFF55"if route.ars.default then
clr ="#55FF55"endend
strtab[#strtab+1] = clr .. minetest.formspec_escape(rname)end
form = form.."label[0.5,2.5;"..S("Routes:").."]"
form = form.."textlist[0.5,3;5,3;rtelist;"..table.concat(strtab,",")if sel_rte then
form = form ..";".. sel_rte .."]"
form = form.."button[0.5,6; 5,1;setroute;"..S("Set Route").."]"
form = form.."button[0.5,7;2,1;dsproute;"..S("Show").."]"if hasprivs then
form = form.."button[5.5,3.3;1,0.3;setarsdefault;D]tooltip[setarsdefault;"..S("Set ARS default route").."]"
form = form.."button[3.5,7;2,1;editroute;"..S("Edit").."]"if sel_rte >1then
form = form .."button[5.5,4;1,0.3;moveup;↑]"endif sel_rte < #strtab then
form = form .."button[5.5,4.7;1,0.3;movedown;↓]"end
form = form.."button[5.5,5.4;1,0.3;delroute;X]tooltip[delroute;"..S("Delete this route").."]"endelse
form = form .."]"if tcbs.ars_disabled then
form = form.."label[0.5,6 ;"..S("NOTE: ARS is disabled.").."]"
form = form.."label[0.5,6.5;"..S("Routes are not automatically set.").."]"endendif hasprivs then
form = form.."button[0.5,8;2.5,1;smartroute;"..S("New (auto-search)").."]"
form = form.."button[ 3,8;2.5,1;newroute;"..S("New (manual)").."]"
form = form.."button_exit[5.5,8;1.5,1;unassign;"..S("Unassign\nSignal").."]"
form = form..string.format("checkbox[0.5,8.75;ars;"..S("Automatic routesetting")..";%s]",not tcbs.ars_disabled)
form = form..string.format("checkbox[0.5,9.25;dstarstrig;"..S("Distant signal triggers ARS")..";%s]",not tcbs.no_dst_ars_trig)endelse-- no route is active, and no route is so far definedif not tcbs.signal thenatwarn("signalling form missing signal?!", pos)return end-- safeguard, nothing else in this function checks tcbs.signallocal caps = advtrains.interlocking.signal.get_signal_cap_level(tcbs.signal)if caps >=4then-- offer user the "block signal mode"
form = form.."label[0.5,2.5;"..S("No routes are yet defined.").."]"if hasprivs then
form = form.."button[0.5,4;2.5,1;smartroute;"..S("Smart Route").."]"
form = form.."button[ 3,4;2.5,1;newroute;"..S("New (Manual)").."]"
form = form.."button_exit[5.5,4;1.5,1;unassign;"..S("Unassign\nSignal").."]"endelseif caps >=3then-- it's a buffer!
form = form.."label[0.5,2.5;"..S("This is an always-halt signal (e.g. a buffer)\nNo routes can be set from here.").."]"
form = form.."button_exit[5.5,4;1.5,1;unassign;"..S("Unassign\nSignal").."]"else-- signal caps say it cannot be route start/end
form = form.."label[0.5,2.5;"..S("This is a pure distant signal\nNo route is currently set through.").."]"
form = form.."button_exit[5.5,4;1.5,1;unassign;"..S("Unassign\nSignal").."]"endendelseifsigd_equal(tcbs.route_origin, sigd)then-- something has gone wrong: tcbs.routeset should have been set...
form = form.."label[0.5,2.5;".."Inconsistent state: route_origin is same TCBS but no route set. Try again.".."]"
ilrs.cancel_route_from(sigd)else
form = form.."label[0.5,2.5;"..S("Route is set over this signal by:").."\n"..sigd_to_string(tcbs.route_origin).."]"
form = form.."label[0.5,4;"..S("Wait for this route to be cancelled in order to do anything here.").."]"endend
sig_pselidx[pname] = sel_rte
minetest.show_formspec(pname,"at_il_signalling_"..minetest.pos_to_string(sigd.p).."_"..sigd.s, form)
p_open_sig_form[pname] = sigd
-- always a good idea to update the signal aspectif not called_from_form_update then-- FIX prevent a callback loop
advtrains.interlocking.signal.update_route_aspect(tcbs)endendfunction advtrains.interlocking.update_player_forms(sigd)for pname, tsigd inpairs(p_open_sig_form)doif advtrains.interlocking.sigd_equal(sigd, tsigd)then
advtrains.interlocking.show_signalling_form(sigd, pname,nil,true)endendend
minetest.register_on_player_receive_fields(function(player, formname, fields)local pname = player:get_player_name()if not minetest.check_player_privs(pname,"train_operator")thenreturnendlocal hasprivs = minetest.check_player_privs(pname,"interlocking")local tpsi = sig_pselidx[pname]local pts, connids =string.match(formname,"^at_il_signalling_([^_]+)_(%d)$")local pos, connid
if pts then
pos = minetest.string_to_pos(pts)
connid =tonumber(connids)if not connid or connid<1or connid>2then return endendif pos and connid thenlocal sigd = {p=pos, s=connid}local tcbs = ildb.get_tcbs(sigd)if not tcbs then return endif fields.quit and not fields.unassign then
sig_pselidx[pname] =nil
p_open_sig_form[pname] =nil-- form quit: disable temporary ARS ignore
tcbs.ars_ignore_next =nilreturnendlocal sel_rte
if fields.rtelist thenlocal tev = minetest.explode_textlist_event(fields.rtelist)if tev.type~="INV"then
sel_rte = tev.index
endelseif tpsi then
sel_rte = tpsi
endif fields.setname and fields.name and hasprivs thenif fields.name ==""then
tcbs.signal_name =nil-- do not save a signal name if it isnt used (equivalent to track sections)else
tcbs.signal_name = fields.name
endendif tcbs.routeset and fields.cancelroute then
tcbs.ars_ignore_next =true-- if route committed, cancel route ts info
ilrs.update_route(sigd, tcbs,nil,true)endif not tcbs.routeset thenif fields.newroute and hasprivs then
advtrains.interlocking.init_route_prog(pname, sigd)
minetest.close_formspec(pname, formname)
tcbs.ars_ignore_next =nil
p_open_sig_form[pname] =nil-- form is closed/left, do not reopenreturnendif fields.smartroute and hasprivs then
advtrains.interlocking.smartroute.start(pname, sigd)
tcbs.ars_ignore_next =nil
p_open_sig_form[pname] =nil-- form is closed/left, do not reopenreturnendif sel_rte and tcbs.routes[sel_rte]thenif fields.setroute then
ilrs.update_route(sigd, tcbs, sel_rte,nil,S("Manual routesetting by @1", pname))endif fields.dsproute thenlocal t = os.clock()
advtrains.interlocking.visualize_route(sigd, tcbs.routes[sel_rte],"disp_"..t)
minetest.after(10,function() advtrains.interlocking.clear_visu_context("disp_"..t)end)endif fields.editroute and hasprivs then
advtrains.interlocking.show_route_edit_form(pname, sigd, sel_rte)
p_open_sig_form[pname] =nil-- form is closed/left, do not reopenreturnendif fields.setarsdefault and hasprivs thenfor rid, route inipairs(tcbs.routes)dolocal isdefault = rid == sel_rte
if route.ars thenif route.ars.default and isdefault then-- D button pressed but route was already default - remove ars default field!
route.ars.default =nilelseif isdefault then
route.ars.default =trueelse
route.ars.default =nilend-- if the table is nouw empty delete itif notnext(route.ars)then
route.ars =nilendelseif isdefault then
route.ars = {default =true}endendendif fields.delroute and hasprivs thenif tcbs.routes[sel_rte]and tcbs.routes[sel_rte].ars then
minetest.chat_send_player(pname,S("Cannot delete route which has ARS rules, please review and then delete through edit dialog!"))elsetable.remove(tcbs.routes,sel_rte)endendendendif fields.unassign and hasprivs then-- unassigning the signal from the tcbs-- only when no route is set.-- Routes and name remain saved, in case the player wants to reassign a new signalif not tcbs.routeset thenlocal signal_pos = tcbs.signal
ildb.set_sigd_for_signal(signal_pos,nil)
tcbs.signal =nil
tcbs.route_aspect =nil
tcbs.route_remote =nil
p_open_sig_form[pname] =nil-- form is closed/left, do not reopen
minetest.chat_send_player(pname,S("Signal has been unassigned. Name and routes are kept for reuse."))returnelse
minetest.chat_send_player(pname,S("Please cancel route first!"))endendif fields.ars then
tcbs.ars_disabled =not minetest.is_yes(fields.ars)endif fields.dstarstrig then
tcbs.no_dst_ars_trig =not minetest.is_yes(fields.dstarstrig)endif fields.auto then
tcbs.route_auto =trueendif fields.noauto then
tcbs.route_auto =falseendif sel_rte and tcbs.routes[sel_rte]and not tcbs.routeset thenif fields.moveup thenif tcbs.routes[sel_rte -1]then
tcbs.routes[sel_rte -1], tcbs.routes[sel_rte] = tcbs.routes[sel_rte], tcbs.routes[sel_rte -1]
sel_rte = sel_rte -1endelseif fields.movedown thenif tcbs.routes[sel_rte +1]then
tcbs.routes[sel_rte +1], tcbs.routes[sel_rte] = tcbs.routes[sel_rte], tcbs.routes[sel_rte +1]
sel_rte = sel_rte +1endendend
advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte,true)returnendend)