From e1ebbff23c10213c4bc9f54bff0870810430cf9d Mon Sep 17 00:00:00 2001 From: orwell96 Date: Thu, 21 Jun 2018 20:34:20 +0200 Subject: Add track section concept and rework TCB design, implement new linking behavior --- advtrains/init.lua | 8 ++ advtrains_interlocking/database.lua | 213 +++++++++++++++++++++------- advtrains_interlocking/init.lua | 2 +- advtrains_interlocking/tcb.lua | 180 ------------------------ advtrains_interlocking/tcb_ts_ui.lua | 265 +++++++++++++++++++++++++++++++++++ 5 files changed, 437 insertions(+), 231 deletions(-) delete mode 100644 advtrains_interlocking/tcb.lua create mode 100644 advtrains_interlocking/tcb_ts_ui.lua diff --git a/advtrains/init.lua b/advtrains/init.lua index de72ea1..e676944 100644 --- a/advtrains/init.lua +++ b/advtrains/init.lua @@ -200,6 +200,9 @@ function advtrains.avt_load() advtrains.player_to_train_mapping = tbl.ptmap or {} advtrains.ndb.load_data(tbl.ndb) advtrains.atc.load_data(tbl.atc) + if advtrains.interlocking then + --advtrains.interlocking.db.load(tbl.interlocking) + end --remove wagon_save entries that are not part of a train local todel=advtrains.merge_tables(advtrains.wagon_save) for tid, train in pairs(advtrains.trains) do @@ -291,12 +294,17 @@ advtrains.avt_save = function(remove_players_from_wagons) --versions: -- 1 - Initial new save format. + local il_save + if advtrains.interlocking then + il_save = advtrains.interlocking.db.save() + end local save_tbl={ trains = tmp_trains, wagon_save = advtrains.wagons, ptmap = advtrains.player_to_train_mapping, atc = advtrains.atc.save_data(), ndb = advtrains.ndb.save_data(), + interlocking = il_save, version = 1, } local datastr = minetest.serialize(save_tbl) diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index e318dd2..b4932ee 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -93,13 +93,20 @@ local TRAVERSER_LIMIT = 100 local ildb = {} local track_circuit_breaks = {} +local track_sections = {} function ildb.load(data) - + if not data then return end + if data.tcbs then + track_circuit_breaks = data.tcbs + end + if data.ts then + track_sections = data.ts + end end function ildb.save() - return {} + return {tcbs = track_circuit_breaks, ts=track_sections} end -- @@ -107,17 +114,7 @@ end TCB data structure { [1] = { -- Variant: with adjacent TCs. - == Synchronized properties == Apply to the whole TC - adjacent = { ,... } -- Adjacent TCBs, forms a TC with these - conflict = { ,... } -- Conflicting TC's (chosen as a representative TCB member) - -- Used e.g. for crossing rails that do not have nodes in common (like it's currently done) - incomplete = -- Set when the recursion counter hit during traverse. Probably needs to add - -- another tcb at some far-away place - route = {origin = , in_dir = } - -- Set whenever a route has been set through this TC. It saves the origin tcb id and side - -- (=the origin signal). in_dir is set when the train will enter the TC from this side - - == Unsynchronized properties == Apply only to this side of the TC + ts_id = -- ID of the assigned track section signal = -- optional: when set, routes can be set from this tcb/direction and signal -- aspect will be set accordingly. routetar = -- Route set from this signal. This is the entry that is cleared once @@ -128,12 +125,25 @@ TCB data structure -- is set true, clearing the signal }, [2] = { -- Variant: end of track-circuited area (initial state of TC) - end_of_interlocking = true, + ts_id = nil, -- this is the indication for end_of_interlocking section_free = , --this can be set by an exit node via mesecons or atlatc, -- or from the tc formspec. } } -Signal specifier (a pair of TCB/Side): + +Track section +[id] = { + name = "Some human-readable name" + tc_breaks = { ,... } -- Bounding TC's (signal specifiers) + -- Can be direct ends (auto-detected), conflicting routes or TCBs that are too far away from each other + route = {origin = , from_tcb = } + -- Set whenever a route has been set through this TC. It saves the origin tcb id and side + -- (=the origin signal). index is the TCB of the route origin + trains = {, ...} -- Set whenever a train (or more) reside in this TC +} + + +Signal specifier (sigd) (a pair of TCB/Side): {p = , s = <1/2>} ]] @@ -141,8 +151,8 @@ Signal specifier (a pair of TCB/Side): -- function ildb.create_tcb(pos) local new_tcb = { - [1] = {end_of_interlocking = true}, - [2] = {end_of_interlocking = true}, + [1] = {}, + [2] = {}, } local pts = advtrains.roundfloorpts(pos) track_circuit_breaks[pts] = new_tcb @@ -153,6 +163,48 @@ function ildb.get_tcb(pos) return track_circuit_breaks[pts] end +function ildb.get_tcbs(sigd) + local tcb = ildb.get_tcb(sigd.p) + if not tcb then return nil end + return tcb[sigd.s] +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) + return track_sections[id] +end + + + +-- various helper functions handling sigd's +local function sigd_equal(sigd, cmp) + return vector.equals(sigd.p, cmp.p) and sigd.s==cmp.s +end +local function insert_sigd_nodouble(list, sigd) + for idx, cmp in pairs(list) do + if sigd_equal(sigd, cmp) then + return + end + end + table.insert(list, sigd) +end + + -- This function will actually handle the node that is in connid direction from the node at pos -- so, this needs the conns of the node at pos, since these are already calculated local function traverser(found_tcbs, pos, conns, connid, count) @@ -166,13 +218,14 @@ local function traverser(found_tcbs, pos, conns, connid, count) local tcb = ildb.get_tcb(adj_pos) if tcb then -- done with this branch - table.insert(found_tcbs, {p=adj_pos, s=adj_connid}) + atdebug("Traverser found tcb at",adj_pos, adj_connid) + insert_sigd_nodouble(found_tcbs, {p=adj_pos, s=adj_connid}) return end end -- recursion abort condition if count > TRAVERSER_LIMIT then - atdebug("Traverser hit counter at",adj_pos, adj_connid,"found tcb's:",found_tcbs) + atdebug("Traverser hit counter at",adj_pos, adj_connid) return true end -- continue traversing @@ -185,62 +238,122 @@ local function traverser(found_tcbs, pos, conns, connid, count) return counter_hit end -local function sigd_equal(sigd, cmp) - return vector.equals(sigd.p, cmp.p) and sigd.s==cmp.s -end - +-- Merges the TS with merge_id into root_id and then deletes merge_id +local function merge_ts(root_id, merge_id) + local rts = ildb.get_ts(root_id) + local mts = ildb.get_ts(merge_id) + + -- 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 + end + -- done + track_sections[merge_id] = nil +end +-- TODO temporary +local lntrans = { "A", "B" } +local function sigd_to_string(sigd) + return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s] +end --- Updates the neighbors of this TCB using the traverser function (see comments above) --- returns true if the traverser hit the counter, which means that there could be another --- TCB outside of the traversed range. -function ildb.update_tcb_neighbors(pos, connid) +-- Check for near TCBs and connect to their TS if they have one, and syncs their data. +function ildb.sync_tcb_neighbors(pos, connid) local found_tcbs = { {p = pos, s = connid} } local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes) if not node_ok then error("update_tcb_neighbors but node is NOK: "..minetest.pos_to_string(pos)) end + atdebug("Traversing from ",pos, connid) local counter_hit = traverser(found_tcbs, pos, conns, connid, 0, hit_counter) + 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 tcb = ildb.get_tcb(sigd.p) - local tcbs = tcb[sigd.s] - - tcbs.end_of_interlocking = nil - tcbs.incomplete = counter_hit - tcbs.adjacent = {} - - for idx2, other_sigd in pairs(found_tcbs) do - if idx~=idx2 then - ildb.add_adjacent(tcbs, sigd.p, sigd.s, other_sigd) + 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) + end + for _, mts in ipairs(ts_to_merge) do + merge_ts(ts_id, mts) end end - - return hit_counter end --- Add the adjacency entry into the tcbs, but without duplicating it --- and without adding a self-reference -function ildb.add_adjacent(tcbs, this_pos, this_connid, sigd) - if sigd_equal(sigd, {p=this_pos, s=this_connid}) then +function ildb.link_track_sections(merge_id, root_id) + if merge_id == root_id then return end - tcbs.end_of_interlocking = nil - if not tcbs.adjacent then - tcbs.adjacent = {} - end - for idx, cmp in pairs(tcbs.adjacent) do - if sigd_equal(sigd, cmp) then + merge_ts(root_id, merge_id) +end + +function ildb.remove_from_interlocking(sigd) + local tcbs = ildb.get_tcbs(sigd) + 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 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 - table.insert(tcbs.adjacent, sigd) end + + advtrains.interlocking.db = ildb diff --git a/advtrains_interlocking/init.lua b/advtrains_interlocking/init.lua index e3be234..54fdb68 100644 --- a/advtrains_interlocking/init.lua +++ b/advtrains_interlocking/init.lua @@ -6,6 +6,6 @@ advtrains.interlocking = {} local modpath = minetest.get_modpath(minetest.get_current_modname()) .. DIR_DELIM dofile(modpath.."database.lua") -dofile(modpath.."tcb.lua") +dofile(modpath.."tcb_ts_ui.lua") dofile(modpath.."signal_api.lua") dofile(modpath.."demosignals.lua") diff --git a/advtrains_interlocking/tcb.lua b/advtrains_interlocking/tcb.lua deleted file mode 100644 index 742bb62..0000000 --- a/advtrains_interlocking/tcb.lua +++ /dev/null @@ -1,180 +0,0 @@ --- Track Circuit Breaks - Player interaction - -local players_assign_tcb = {} -local players_addfar_tcb = {} - -local lntrans = { "A", "B" } - -local function sigd_to_string(sigd) - return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s] -end - -minetest.register_node("advtrains_interlocking:tcb_node", { - drawtype = "mesh", - paramtype="light", - paramtype2="facedir", - walkable = true, - selection_box = { - type = "fixed", - fixed = {-1/4, -1/2, -1/4, 1/4, 1/2, 1/4}, - }, - 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, - }, - 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 meta = minetest.get_meta(pos) - local tcbpts = meta:get_string("tcb_pos") - local pname = player:get_player_name() - if tcbpts ~= "" then - local tcbpos = minetest.string_to_pos(tcbpts) - advtrains.interlocking.show_tcb_form(tcbpos, pname) - else - --unconfigured - --TODO security - minetest.chat_send_player(pname, "Configuring TCB: Please punch the rail you want to assign this TCB to.") - - players_assign_tcb[pname] = pos - end - end, - on_punch = function(pos, node, player) - local meta = minetest.get_meta(pos) - atwarn("Would show tcb marker.") - -- TODO TCB-Marker anzeigen - end, -}) - -minetest.register_on_punchnode(function(pos, node, player, pointed_thing) - local pname = player:get_player_name() - local tcbnpos = players_assign_tcb[pname] - if tcbnpos then - if vector.distance(pos, tcbnpos)<=20 then - local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes) - if node_ok and #conns == 2 then - advtrains.interlocking.db.create_tcb(pos) - - advtrains.interlocking.db.update_tcb_neighbors(pos, 1) - advtrains.interlocking.db.update_tcb_neighbors(pos, 2) - - local 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") - else - minetest.chat_send_player(pname, "Configuring TCB: This is not a normal two-connection rail! Aborted.") - end - else - minetest.chat_send_player(pname, "Configuring TCB: Node is too far away. Aborted.") - end - players_assign_tcb[pname] = nil - end -end) - - -local function mkformspec(tcbs, btnpref, offset, pname) - local form = "label[0.5,"..offset..";Side "..btnpref..": "..(tcbs.end_of_interlocking and "End of interlocking" or "Track Circuit").."]" - if tcbs.end_of_interlocking then - form = form.."button[0.5,"..(offset+1)..";3,1;"..btnpref.."_clearadj;Activate Interlocking]" - if tcbs.section_free then - form = form.."button[4.5,"..(offset+1)..";3,1;"..btnpref.."_setlocked;Section is free]" - else - form = form.."button[4.5,"..(offset+1)..";3,1;"..btnpref.."_setfree;Section is blocked]" - end - else - local strtab = {} - for idx, sigd in ipairs(tcbs.adjacent) do - strtab[idx] = minetest.formspec_escape(sigd_to_string(sigd)) - end - form = form.."textlist[0.5,"..(offset+1)..";5,3;"..btnpref.."_adjlist;"..table.concat(strtab, ",").."]" - if players_addfar_tcb[pname] then - local sigd = players_addfar_tcb[pname] - form = form.."button[5.5,"..(offset+2)..";2.5,1;"..btnpref.."_addadj;Link TCB to "..sigd_to_string(sigd).."]" - form = form.."button[8,"..(offset+2)..";0.5,1;"..btnpref.."_canceladdadj;X]" - else - form = form.."button[5.5,"..(offset+2)..";3,1;"..btnpref.."_addadj;Add far TCB]" - end - form = form.."button[5.5,"..(offset+1)..";3,1;"..btnpref.."_clearadj;Clear&Update]" - form = form.."button[5.5,"..(offset+3)..";3,1;"..btnpref.."_mknonint;Make non-interlocked]" - if tcbs.incomplete then - form = form.."label[0.5,"..(offset+0.5)..";Warning: You possibly need to add TCBs manually!]" - end - end - return form -end - - - -function advtrains.interlocking.show_tcb_form(pos, pname) - local tcb = advtrains.interlocking.db.get_tcb(pos) - if not tcb then return end - - local form = "size[10,10] label[0.5,0.5;Track Circuit Break Configuration]" - form = form .. mkformspec(tcb[1], "A", 1, pname) - form = form .. mkformspec(tcb[2], "B", 6, pname) - - minetest.show_formspec(pname, "at_il_tcbconfig_"..minetest.pos_to_string(pos), form) -end - - -minetest.register_on_player_receive_fields(function(player, formname, fields) - local pname = player:get_player_name() - local pts = string.match(formname, "^at_il_tcbconfig_(.+)$") - local pos - if pts then - pos = minetest.string_to_pos(pts) - end - if pos and not fields.quit then - local tcb = advtrains.interlocking.db.get_tcb(pos) - if not tcb then return end - local f_clearadj = {fields.A_clearadj, fields.B_clearadj} - local f_addadj = {fields.A_addadj, fields.B_addadj} - local f_canceladdadj = {fields.A_canceladdadj, fields.B_canceladdadj} - local f_setlocked = {fields.A_setlocked, fields.B_setlocked} - local f_setfree = {fields.A_setfree, fields.B_setfree} - local f_mknonint = {fields.A_mknonint, fields.B_mknonint} - - for connid=1,2 do - if f_clearadj[connid] then - advtrains.interlocking.db.update_tcb_neighbors(pos, connid) - end - if f_mknonint[connid] then - --TODO: remove this from the other tcb's - tcb[connid].end_of_interlocking = true - end - if f_addadj[connid] then - if players_addfar_tcb[pname] then - local sigd = players_addfar_tcb[pname] - advtrains.interlocking.db.add_adjacent(tcb[connid], pos, connid, sigd) - players_addfar_tcb[pname] = nil - else - players_addfar_tcb[pname] = {p = pos, s = connid} - end - end - if f_canceladdadj[connid] then - players_addfar_tcb[pname] = nil - end - if f_setfree[connid] then - tcb[connid].section_free = true - end - if f_setlocked[connid] then - tcb[connid].section_free = nil - end - end - advtrains.interlocking.show_tcb_form(pos, pname) - end - -end) - - - - - diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua new file mode 100644 index 0000000..b2f1ef8 --- /dev/null +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -0,0 +1,265 @@ +-- Track Circuit Breaks and Track Sections - Player interaction + +local players_assign_tcb = {} +local players_link_ts = {} + +local lntrans = { "A", "B" } + +local function sigd_to_string(sigd) + return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s] +end + +minetest.register_node("advtrains_interlocking:tcb_node", { + drawtype = "mesh", + paramtype="light", + paramtype2="facedir", + walkable = true, + selection_box = { + type = "fixed", + fixed = {-1/4, -1/2, -1/4, 1/4, 1/2, 1/4}, + }, + 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, + }, + 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 meta = minetest.get_meta(pos) + local tcbpts = meta:get_string("tcb_pos") + local pname = player:get_player_name() + if tcbpts ~= "" then + local tcbpos = minetest.string_to_pos(tcbpts) + advtrains.interlocking.show_tcb_form(tcbpos, pname) + else + --unconfigured + --TODO security + minetest.chat_send_player(pname, "Configuring TCB: Please punch the rail you want to assign this TCB to.") + + players_assign_tcb[pname] = pos + end + end, + on_punch = function(pos, node, player) + local meta = minetest.get_meta(pos) + atwarn("Would show tcb marker.") + -- TODO TCB-Marker anzeigen + end, +}) + +minetest.register_on_punchnode(function(pos, node, player, pointed_thing) + local pname = player:get_player_name() + local tcbnpos = players_assign_tcb[pname] + if tcbnpos then + if vector.distance(pos, tcbnpos)<=20 then + local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes) + if node_ok and #conns == 2 then + advtrains.interlocking.db.create_tcb(pos) + + advtrains.interlocking.db.sync_tcb_neighbors(pos, 1) + advtrains.interlocking.db.sync_tcb_neighbors(pos, 2) + + local 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") + else + minetest.chat_send_player(pname, "Configuring TCB: This is not a normal two-connection rail! Aborted.") + end + else + minetest.chat_send_player(pname, "Configuring TCB: Node is too far away. Aborted.") + end + players_assign_tcb[pname] = nil + end +end) + + +-- TCB Form + +local function mktcbformspec(tcbs, btnpref, offset, pname) + local form = "" + local ts + if tcbs.ts_id then + ts = advtrains.interlocking.db.get_ts(tcbs.ts_id) + end + if ts then + form = form.."label[0.5,"..offset..";Side "..btnpref..": "..ts.name.."]" + form = form.."button[0.5,"..(offset+1)..";5,1;"..btnpref.."_gotots;Show track section]" + form = form.."button[0.5,"..(offset+2)..";2.5,1;"..btnpref.."_update;Update near TCBs]" + form = form.."button[3 ,"..(offset+2)..";2.5,1;"..btnpref.."_remove;Remove from section]" + else + tcbs.ts_id = nil + form = form.."label[0.5,"..offset..";Side "..btnpref..": ".."End of interlocking]" + form = form.."button[0.5,"..(offset+1)..";5,1;"..btnpref.."_makeil;Create Interlocked Track Section]" + if tcbs.section_free then + form = form.."button[0.5,"..(offset+2)..";5,1;"..btnpref.."_setlocked;Section is free]" + else + form = form.."button[0.5,"..(offset+2)..";5,1;"..btnpref.."_setfree;Section is blocked]" + end + end + return form +end + + +function advtrains.interlocking.show_tcb_form(pos, pname) + local tcb = advtrains.interlocking.db.get_tcb(pos) + if not tcb then return end + + local form = "size[6,7] label[0.5,0.5;Track Circuit Break Configuration]" + form = form .. mktcbformspec(tcb[1], "A", 1, pname) + form = form .. mktcbformspec(tcb[2], "B", 4, pname) + + minetest.show_formspec(pname, "at_il_tcbconfig_"..minetest.pos_to_string(pos), form) +end + +--helper: length of nil table is 0 +local function nlen(t) + if not t then return 0 end + return #t +end + + +minetest.register_on_player_receive_fields(function(player, formname, fields) + local pname = player:get_player_name() + local pts = string.match(formname, "^at_il_tcbconfig_(.+)$") + local pos + if pts then + pos = minetest.string_to_pos(pts) + end + if pos and not fields.quit then + local tcb = advtrains.interlocking.db.get_tcb(pos) + if not tcb then return end + 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_setlocked = {fields.A_setlocked, fields.B_setlocked} + local f_setfree = {fields.A_setfree, fields.B_setfree} + + for connid=1,2 do + local tcbs = tcb[connid] + if tcbs.ts_id then + if f_gotots[connid] then + advtrains.interlocking.show_ts_form(tcbs.ts_id, pname) + return + end + if f_update[connid] then + advtrains.interlocking.db.sync_tcb_neighbors(pos, connid) + end + if f_remove[connid] then + advtrains.interlocking.db.remove_from_interlocking({p=pos, s=connid}) + end + else + if f_makeil[connid] then + advtrains.interlocking.db.create_ts({p=pos, s=connid}) + advtrains.interlocking.db.sync_tcb_neighbors(pos, connid) + 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 + advtrains.interlocking.show_tcb_form(pos, pname) + end + +end) + + + +-- TS Formspec + +function advtrains.interlocking.show_ts_form(ts_id, pname, sel_tcb) + local ts = advtrains.interlocking.db.get_ts(ts_id) + if not ts_id then return end + + 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;"..ts.name.."]" + form = form.."button[5.5,1.7;1,1;setname;Set]" + local hint + + local strtab = {} + for idx, sigd in ipairs(ts.tc_breaks) do + strtab[#strtab+1] = minetest.formspec_escape(sigd_to_string(sigd)) + end + + form = form.."textlist[0.5,3;5,3;tcblist;"..table.concat(strtab, ",").."]" + if players_link_ts[pname] then + local other_id = players_link_ts[pname] + local other_ts = advtrains.interlocking.db.get_ts(other_id) + if other_ts then + form = form.."button[5.5,3.5;3.5,1;mklink;Join with "..other_ts.name.."]" + form = form.."button[9 ,3.5;0.5,1;cancellink;X]" + end + else + form = form.."button[5.5,3.5;4,1;link;Join into other section]" + hint = 1 + end + if sel_tcb then + form = form.."button[5.5,4.5;4,1;del_tcb;Remove selected TCB]" + hint = 2 + end + if hint == 1 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;Removing a TCB will set it to non-interlocked mode.]" + form = form.."label[0.5,1;Trying to remove 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() + local ts_id = string.match(formname, "^at_il_tsconfig_(.+)$") + if ts_id and not fields.quit then + local ts = advtrains.interlocking.db.get_ts(ts_id) + 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 + end + + if players_link_ts[pname] then + if fields.cancellink then + players_link_ts[pname] = nil + elseif fields.mklink then + advtrains.interlocking.db.link_track_sections(players_link_ts[pname], ts_id) + end + end + + if fields.del_tcb and sel_tcb and sel_tcb > 0 and sel_tcb <= #ts.tc_breaks then + advtrains.interlocking.db.remove_from_interlocking(ts.tc_breaks[sel_tcb]) + end + + if fields.link then + players_link_ts[pname] = ts_id + end + + if fields.setname then + ts.name = fields.name + if ts.name == "" then + ts.name = "Section "..ts_id + end + end + + + advtrains.interlocking.show_ts_form(ts_id, pname, sel_tcb) + end + +end) + + -- cgit v1.2.3