-- Track Circuit Breaks and Track Sections - Player interactionlocal players_assign_tcb = {}local players_assign_signal = {}local players_assign_xlink = {}local players_link_ts = {}local players_assign_fixedlocks = {}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="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","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,"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,"This TCB has been removed. Please dig marker.")endelse--unconfigured
minetest.chat_send_player(pname,"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,"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,"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,"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","TCB assigned to "..minetest.pos_to_string(pos))
minetest.chat_send_player(pname,"Configuring TCB: Successfully configured TCB")
advtrains.interlocking.show_tcb_marker(pos)else
minetest.chat_send_player(pname,"Configuring TCB: This is not a normal two-connection rail! Aborted.")endelse
minetest.chat_send_player(pname,"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)
minetest.chat_send_player(pname,"Configuring TCB: Successfully assigned signal.")
advtrains.interlocking.show_ip_form(pos, pname,true)else
minetest.chat_send_player(pname,"Configuring TCB: Internal error, TCBS doesn't exist. Aborted.")endelse
minetest.chat_send_player(pname,"Configuring TCB: Cannot use static signals for routesetting. Aborted.")endelse
minetest.chat_send_player(pname,"Configuring TCB: Not a compatible signal. Aborted.")endelse
minetest.chat_send_player(pname,"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, minetest.pos_to_string(pos).." locks in state "..state)
ts.fixed_locks[pts] = state
else
minetest.chat_send_player(pname,"Error: TS modified, abort!")
players_assign_fixedlocks[pname] =nilendelse
minetest.chat_send_player(pname,"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,"Insufficient privileges to use this!")endreturnendif ildb.get_tcb(pos)then
minetest.chat_send_player(pname,"TCB already existed at this position, now linked to this node")else
ildb.create_tcb_at(pos, pname)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,"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)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,"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,"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)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..";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]"else
tcbs.ts_id =nil
form = form.."label[0.5,"..offset..";Side "..btnpref..": ".."End of interlocking]"
form = form.."button[0.5,"..(offset+0.5)..";5,1;"..btnpref.."_makeil;Create Interlocked Track Section]"end-- xlinkif tcbs.xlink then
form = form.."label[0.5,"..(offset+1.5)..";Link:"..ildb.sigd_to_string(tcbs.xlink).."]"
form = form.."button[4.5,"..(offset+1.5)..";1,1;"..btnpref.."_xlinkdel;X]"elseif players_assign_xlink[pname]then
form = form.."button[0.5,"..(offset+1.5)..";4,1;"..btnpref.."_xlinklink;Link "..ildb.sigd_to_string(players_assign_xlink[pname]).."]"
form = form.."button[4.5,"..(offset+1.5)..";1,1;"..btnpref.."_xlinkabrt;X]"else
form = form.."label[0.5,"..(offset+1.5)..";No Link]"
form = form.."button[4.5,"..(offset+1.5)..";1,1;"..btnpref.."_xlinkadd;+]"endendif tcbs.signal then
form = form.."button[0.5,"..(offset+2.5)..";5,1;"..btnpref.."_sigdia;Signalling]"else
form = form.."button[0.5,"..(offset+2.5)..";5,1;"..btnpref.."_asnsig;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,"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;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,"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,"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,"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;Track Section Detail - "..ts_id.."]"
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]"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;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;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;Add locks]"
form = form.."button[7.5,6;2,1;flk_clear;Clear locks]"
form = form.."button[5.5,8;4,1;remove;Remove Section]"
form = form.."tooltip[remove;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;Route is set: "..ts.route.rsn.."]"elseif ts.route_post then
form = form.."label[0.5,6.1;Section holds "..#(ts.route_post.lcks or{}).." route locks.]"end-- occupying trainsif ts.trains and#ts.trains>0then
form = form.."label[0.5,7.1;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;No trains on this section.]"end
form = form.."button[5.5,7;4,1;reset;Reset section state]"if hint ==3then
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.]"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,"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;Reset track section]"
form = form.."label[0.5,1;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;Yes]"
form = form.."button_exit[0.5,3.5; 5,1;cancel;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,"Reset track section "..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-- 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,"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;Signal at "..minetest.pos_to_string(sigd.p).."]"
form = form.."field[0.8,1.5;5.2,1;name;Signal name;"..minetest.formspec_escape(tcbs.signal_name or"").."]"
form = form.."button[5.5,1.2;1,1;setname;Set]"if tcbs.routeset thenlocal rte = tcbs.routes[tcbs.routeset]if not rte thenatwarn("Unknown route set from signal!")
tcbs.routeset =nilreturnend
form = form.."label[0.5,2.5;A route is requested from this signal:]"
form = form.."label[0.5,3.0;"..minetest.formspec_escape(rte.name).."]"if tcbs.route_committed then
form = form.."label[0.5,3.5;Route has been set.]"else
form = form.."label[0.5,3.5;Waiting for route to be set...]"if tcbs.route_rsn then
form = form.."label[0.5,4;"..minetest.formspec_escape(tcbs.route_rsn).."]"endendif not tcbs.route_auto then
form = form.."button[0.5,7; 5,1;auto;Enable Automatic Working]"else
form = form.."label[0.5,7 ;Automatic Working is active.]"
form = form.."label[0.5,7.3;Route is re-set when a train passed.]"
form = form.."button[0.5,7.7; 5,1;noauto;Disable Automatic Working]"end
form = form.."button[0.5,6; 5,1;cancelroute;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 clr =""if route.smartroute_generated then
clr ="#FFFF55"endif route.ars then
clr ="#FF5555"if route.ars.default then
clr ="#55FF55"endend
strtab[#strtab+1] = clr .. minetest.formspec_escape(route.name)end
form = form.."label[0.5,2.5;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;Set Route]"
form = form.."button[0.5,7;2,1;dsproute;Show]"if hasprivs then
form = form.."button[2.5,7;1,1;setarsdefault;Set Def.]"
form = form.."button[3.5,7;2,1;editroute;Edit]"if sel_rte >1then
form = form .."button[5.5,4;0.5,0.3;moveup;↑]"endif sel_rte < #strtab then
form = form .."button[5.5,4.7;0.5,0.3;movedown;↓]"endendelse
form = form .."]"if tcbs.ars_disabled then
form = form.."label[0.5,6 ;NOTE: ARS is disabled.]"
form = form.."label[0.5,6.5;Routes are not automatically set.]"endendif hasprivs then
form = form.."button[0.5,8;2.5,1;smartroute;Smart Route]"
form = form.."button[ 3,8;2.5,1;newroute;New (Manual)]"
form = form..string.format("checkbox[0.5,8.75;ars;Automatic routesetting;%s]",not tcbs.ars_disabled)
form = form..string.format("checkbox[0.5,9.25;dstarstrig;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;No routes are yet defined.]"if hasprivs then
form = form.."button[0.5,4;2.5,1;smartroute;Smart Route]"
form = form.."button[ 3,4;2.5,1;newroute;New (Manual)]"
form = form.."label[0.5,5.5;Setup block signal route (up to following signal):]"
form = form.."button[0.5,6;2.5,1;setupblocklong;Long (No Dst)]"
form = form.."tooltip[setupblocklong;Following track section must have no turnouts and end at another signal.\n".."Sets a route into the section ahead with auto-working set on\n".."Long block: This signal does not become distant signal.]"
form = form.."button[ 3,6;2.5,1;setupblockshort;Short (With Dst)]"
form = form.."tooltip[setupblockshort;Following track section must have no turnouts and end at another signal.\n".."Sets a route into the section ahead with auto-working set on\n".."Short block: This signal becomes distant signal for next signal.]"endelseif caps >=3then-- it's a buffer!
form = form.."label[0.5,2.5;This is an always-halt signal (e.g. a buffer)\n".."No routes can be set from here.]"else-- signal caps say it cannot be route start/end
form = form.."label[0.5,2.5;This is a Non-Halt signal (e.g. pure distant signal)\n".."No route is currently set through.]"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;Route is set over this signal by:\n"..sigd_to_string(tcbs.route_origin).."]"
form = form.."label[0.5,4;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")-- independent of the formspec, clear this whenever some formspec event happenslocal tpsi = sig_pselidx[pname]
sig_pselidx[pname] =nil
p_open_sig_form[pname] =nillocal 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 then-- form quit: disable temporary ARS ignore
tcbs.ars_ignore_next =nilreturnendlocal sel_rte
if fields.rtelist thenlocal tev = minetest.explode_textlist_event(fields.rtelist)
sel_rte = tev.index
elseif 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 thenif tcbs.routes[tcbs.routeset]and tcbs.routes[tcbs.routeset].ars then
tcbs.ars_ignore_next =trueend-- 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 =nilreturnendif fields.smartroute and hasprivs then
advtrains.interlocking.smartroute.init(pname, sigd)
minetest.close_formspec(pname, formname)
tcbs.ars_ignore_next =nilreturnendif(fields.setupblocklong or fields.setupblockshort)and hasprivs then-- check adjacent sectionif not tcbs.ts_id then
minetest.chat_send_player(pname,"Block route not possible: No track section ahead")returnendlocal ts = ildb.get_ts(tcbs.ts_id)if#ts.tc_breaks ~=2then
minetest.chat_send_player(pname,"Block route not possible: Section "..(ts.name or"-").." ("..tcbs.ts_id..") has "..#ts.tc_breaks.." ends, must be 2")returnendlocal e_sigd
if vector.equals(ts.tc_breaks[1].p, pos)then
e_sigd = { p = ts.tc_breaks[2].p,
s = ts.tc_breaks[2].s==1and2or1}elseif vector.equals(ts.tc_breaks[2].p, pos)then
e_sigd = { p = ts.tc_breaks[1].p,
s = ts.tc_breaks[1].s==1and2or1}else
minetest.chat_send_player(pname,"Block route not possible: Section "..(ts.name or"-").." ("..tcbs.ts_id..") TCBs are inconsistent, check section!")returnendlocal e_tcbs = ildb.get_tcbs(e_sigd)if not e_tcbs then
minetest.chat_send_player(pname,"Block route not possible: Adjacent TCB not found, check section!")returnend-- now we have the TCB at the end of the following section. check that signal is setif not e_tcbs.signal then
minetest.chat_send_player(pname,"Block route not possible: Adjacent TCB has no signal assigned!")returnendlocal caps = advtrains.interlocking.signal.get_signal_cap_level(e_tcbs.signal)if caps <3then
minetest.chat_send_player(pname,"Block route not possible: Following signal is not capable of displaying a Halt aspect (caplevel "..caps..")")returnend-- all preconditions checked! go ahead and create routelocal route = {
name ="BS",[1] = {next= e_sigd,-- of the next (note: next) TCB on the route
locks = {}-- route locks of this route segment},
terminal = e_sigd,
use_rscache =true,-- main_aspect = <use default>
assign_dst = fields.setupblockshort and true,-- assign dst, if short block was selected
default_autoworking =true,}local rid = #tcbs.routes +1-- typically 1
tcbs.routes[rid] = route
-- directly set our newly created route
ilrs.update_route(sigd, tcbs, rid)
advtrains.interlocking.show_signalling_form(sigd, pname,nil,true)returnendif sel_rte and tcbs.routes[sel_rte]thenif fields.setroute then
ilrs.update_route(sigd, tcbs, sel_rte)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)returnendif fields.setarsdefault and hasprivs thenfor rid, route inipairs(tcbs.routes)dolocal isdefault = rid == sel_rte
if route.ars then
route.ars.default = isdefault
elseif isdefault then
route.ars = {default =true}endendendendendif 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)