From 5c8962b39bd4f6871ec87a988ac43d7bfad04d2b Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Mon, 11 Apr 2022 16:55:50 +0200 Subject: Implement basic route signaling with Japanese signals for demo --- advtrains_interlocking/signal_api.lua | 146 ++++------------------------------ 1 file changed, 17 insertions(+), 129 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 83fae4a..0a9e6ea 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -220,8 +220,17 @@ function advtrains.interlocking.signal_set_aspect(pos, asp) local node=advtrains.ndb.get_node(pos) local ndef=minetest.registered_nodes[node.name] if ndef and ndef.advtrains and ndef.advtrains.set_aspect then + local oldasp = advtrains.interlocking.signal_get_aspect(pos) or DANGER + local suppasp = advtrains.interlocking.signal_get_supported_aspects(pos) or {} + if suppasp.type == 2 then + asp = advtrains.interlocking.aspects.type1_to_type2main(asp, suppasp.group) + end ndef.advtrains.set_aspect(pos, node, asp) - advtrains.interlocking.signal_on_aspect_changed(pos) + local actualasp = advtrains.interlocking.signal_get_aspect(pos) or DANGER + local aspect_changed = advtrains.interlocking.aspects.not_equalp(oldasp, actualasp) + if aspect_changed then + advtrains.interlocking.signal_on_aspect_changed(pos) + end end end @@ -252,7 +261,7 @@ function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, local function callback(pname, aspect) advtrains.interlocking.signal_set_aspect(pos, aspect) end - local isasp = ndef.advtrains.get_aspect(pos, node) + local isasp = advtrains.interlocking.signal_get_aspect(pos, node) advtrains.interlocking.show_signal_aspect_selector( pname, @@ -285,8 +294,13 @@ function advtrains.interlocking.signal_get_aspect(pos) local ndef=minetest.registered_nodes[node.name] if ndef and ndef.advtrains and ndef.advtrains.get_aspect then local asp = ndef.advtrains.get_aspect(pos, node) + local suppasp = advtrains.interlocking.signal_get_supported_aspects(pos) or {} + if suppasp.type == 2 then + asp = advtrains.interlocking.aspects.type2main_to_type1(suppasp.group, asp) + end if not asp then asp = DANGER end - return convert_aspect_if_necessary(asp) + asp = convert_aspect_if_necessary(asp) + return asp end return nil end @@ -411,129 +425,3 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) players_assign_ip[pname] = nil end end) - - ---== aspect selector ==-- - -local players_aspsel = {} - ---[[ -suppasp: "supported_aspects" table -purpose: form title string -callback: func(pname, aspect) called on form submit -isasp: aspect currently set -]] -function advtrains.interlocking.show_signal_aspect_selector(pname, p_suppasp, p_purpose, callback, isasp) - local suppasp = p_suppasp or { - main = {0, -1}, dst = {false}, shunt = false, info = {}, - } - local purpose = p_purpose or "" - - local form = "size[7,7]label[0.5,0.5;Select Signal Aspect:]" - form = form.."label[0.5,1;"..purpose.."]" - - form = form.."label[0.5,1.5;== Main Signal ==]" - local selid = 1 - local entries = {} - for idx, spv in ipairs(suppasp.main) do - local entry - if spv == 0 then - entry = "Halt" - elseif spv == -1 then - entry = "Continue at maximum speed" - elseif not spv then - entry = "Continue\\, speed limit unchanged (no info)" - else - entry = "Continue at speed of "..spv - end - -- hack: the crappy formspec system returns the label, not the index. save the index in it. - entries[idx] = idx.."| "..entry - if isasp and spv == (isasp.main or false) then - selid = idx - end - end - form = form.."dropdown[0.5,2;6;main;"..table.concat(entries, ",")..";"..selid.."]" - - - form = form.."label[0.5,3;== Shunting ==]" - if suppasp.shunt == nil then - local st = 1 - if isasp and isasp.shunt then st=2 end - form = form.."dropdown[0.5,3.5;6;shunt_free;---,allowed;"..st.."]" - end - - form = form.."label[0.5,4.5;== Distant Signal ==]" - local selid = 1 - local entries = {} - for idx, spv in ipairs(suppasp.dst) do - local entry - if spv == 0 then - entry = "Expect to stop at the next signal" - elseif spv == -1 then - entry = "Expect to pass the next signal at maximum speed" - elseif not spv then - entry = "No info" - else - entry = string.format("Expect to pass the next signal at speed of %d", spv) - end - entries[idx] = idx.."| "..entry - if isasp and spv == (isasp.dst or false) then - selid = idx - end - end - form = form.."dropdown[0.5,5;6;dst;"..table.concat(entries, ",")..";"..selid.."]" - - form = form.."button_exit[0.5,6;5,1;save;Save signal aspect]" - - local token = advtrains.random_id() - - minetest.show_formspec(pname, "at_il_sigaspdia_"..token, form) - - minetest.after(1, function() - players_aspsel[pname] = { - suppasp = suppasp, - callback = callback, - token = token, - } - end) -end - -local function usebool(sup, val, free) - if sup == nil then - return val==free - else - return sup - end -end - --- other side of hack: extract the index -local function ddindex(val) - return tonumber(string.match(val, "^(%d+)|")) -end - --- TODO use non-hacky way to parse outputs - -minetest.register_on_player_receive_fields(function(player, formname, fields) - local pname = player:get_player_name() - local psl = players_aspsel[pname] - if psl then - if formname == "at_il_sigaspdia_"..psl.token then - if fields.save then - local maini = ddindex(fields.main) - if not maini then return end - local dsti = ddindex(fields.dst) - if not dsti then return end - local asp = { - main = psl.suppasp.main[maini], - dst = psl.suppasp.dst[dsti], - shunt = usebool(psl.suppasp.shunt, fields.shunt_free, "allowed"), - info = {} - } - psl.callback(pname, asp) - end - else - players_aspsel[pname] = nil - end - end - -end) -- cgit v1.2.3 From 220563012d2aa2c753c791fa9faa38346f1355a5 Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Fri, 6 May 2022 21:36:21 +0200 Subject: Record signal aspect to avoid excessive get_aspect calls; report testing coverage --- .build.yml | 4 +- advtrains_interlocking/database.lua | 4 ++ advtrains_interlocking/signal_api.lua | 38 ++++++++++++++-- .../spec/fixtures/advtrains_helpers.lua | 1 + advtrains_interlocking/spec/mineunit.conf | 0 advtrains_interlocking/spec/signal_api_spec.lua | 50 ++++++++++++++++++++++ 6 files changed, 93 insertions(+), 4 deletions(-) create mode 120000 advtrains_interlocking/spec/fixtures/advtrains_helpers.lua create mode 100644 advtrains_interlocking/spec/mineunit.conf create mode 100644 advtrains_interlocking/spec/signal_api_spec.lua (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/.build.yml b/.build.yml index 40e97ff..1a51e58 100644 --- a/.build.yml +++ b/.build.yml @@ -39,7 +39,9 @@ tasks: ~/.luarocks/bin/busted for i in {advtrains,advtrains_interlocking}; do cd ../$i - ~/.luarocks/bin/mineunit + ~/.luarocks/bin/mineunit -c + ~/.luarocks/bin/mineunit -r + sed -n '/^File/,$p' luacov.report.out done - activate_test_env: | cd advtrains diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index 6787c50..efa5eb8 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -131,6 +131,9 @@ function ildb.load(data) if data.npr_rails then advtrains.interlocking.npr_rails = data.npr_rails end + if data.supposed_aspects then + advtrains.interlocking.load_supposed_aspects(data.supposed_aspects) + end --COMPATIBILITY to Signal aspect format -- TODO remove in time... @@ -173,6 +176,7 @@ function ildb.save() rs_callbacks = advtrains.interlocking.route.rte_callbacks, influence_points = influence_points, npr_rails = advtrains.interlocking.npr_rails, + supposed_aspects = advtrains.interlocking.save_supposed_aspects(), } end diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 0a9e6ea..a25e1f6 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -178,6 +178,8 @@ advtrains.interlocking.GENERIC_FREE = { dst = false, } +local supposed_aspects = {} + local function convert_aspect_if_necessary(asp) if type(asp.main) == "table" then local newasp = {} @@ -199,6 +201,24 @@ local function convert_aspect_if_necessary(asp) return asp end +function advtrains.interlocking.load_supposed_aspects(tbl) + if tbl then + supposed_aspects = tbl + end +end + +function advtrains.interlocking.save_supposed_aspects() + return supposed_aspects +end + +local function set_supposed_aspect(pos, asp) + supposed_aspects[advtrains.roundfloorpts(pos)] = asp +end + +local function get_supposed_aspect(pos) + return supposed_aspects[advtrains.roundfloorpts(pos)] +end + function advtrains.interlocking.update_signal_aspect(tcbs) if tcbs.signal then local asp = tcbs.aspect or DANGER @@ -213,6 +233,7 @@ end function advtrains.interlocking.signal_after_dig(pos) -- clear influence point advtrains.interlocking.db.clear_ip_by_signalpos(pos) + set_supposed_aspect(pos, nil) end function advtrains.interlocking.signal_set_aspect(pos, asp) @@ -222,12 +243,13 @@ function advtrains.interlocking.signal_set_aspect(pos, asp) if ndef and ndef.advtrains and ndef.advtrains.set_aspect then local oldasp = advtrains.interlocking.signal_get_aspect(pos) or DANGER local suppasp = advtrains.interlocking.signal_get_supported_aspects(pos) or {} + local newasp = asp if suppasp.type == 2 then asp = advtrains.interlocking.aspects.type1_to_type2main(asp, suppasp.group) end + set_supposed_aspect(pos, newasp) ndef.advtrains.set_aspect(pos, node, asp) - local actualasp = advtrains.interlocking.signal_get_aspect(pos) or DANGER - local aspect_changed = advtrains.interlocking.aspects.not_equalp(oldasp, actualasp) + local aspect_changed = advtrains.interlocking.aspects.not_equalp(oldasp, newasp) if aspect_changed then advtrains.interlocking.signal_on_aspect_changed(pos) end @@ -289,7 +311,7 @@ end -- Returns the actual aspect of the signal at position, as returned by the nodedef. -- returns nil when there's no signal at the position -function advtrains.interlocking.signal_get_aspect(pos) +function advtrains.interlocking.signal_get_real_aspect(pos) local node=advtrains.ndb.get_node(pos) local ndef=minetest.registered_nodes[node.name] if ndef and ndef.advtrains and ndef.advtrains.get_aspect then @@ -305,6 +327,16 @@ function advtrains.interlocking.signal_get_aspect(pos) return nil end +-- Returns the signal aspect as reported in the suppasp table. +function advtrains.interlocking.signal_get_aspect(pos) + local asp = get_supposed_aspect(pos) + if not asp then + asp = advtrains.interlocking.signal_get_real_aspect(pos) + set_supposed_aspect(pos) + end + return asp +end + -- Returns the "supported_aspects" of the signal at position, as returned by the nodedef. -- returns nil when there's no signal at the position function advtrains.interlocking.signal_get_supported_aspects(pos) diff --git a/advtrains_interlocking/spec/fixtures/advtrains_helpers.lua b/advtrains_interlocking/spec/fixtures/advtrains_helpers.lua new file mode 120000 index 0000000..9b0ab67 --- /dev/null +++ b/advtrains_interlocking/spec/fixtures/advtrains_helpers.lua @@ -0,0 +1 @@ +../../../advtrains/helpers.lua \ No newline at end of file diff --git a/advtrains_interlocking/spec/mineunit.conf b/advtrains_interlocking/spec/mineunit.conf new file mode 100644 index 0000000..e69de29 diff --git a/advtrains_interlocking/spec/signal_api_spec.lua b/advtrains_interlocking/spec/signal_api_spec.lua new file mode 100644 index 0000000..2659380 --- /dev/null +++ b/advtrains_interlocking/spec/signal_api_spec.lua @@ -0,0 +1,50 @@ +require("mineunit") + +mineunit("core") + +_G.advtrains = { + interlocking = { + aspects = sourcefile("signal_aspects"), + --aspects = fixture("../../signal_aspects"), + }, + ndb = { + get_node = minetest.get_node, + } +} + +fixture("advtrains_helpers") +fixture("../../database") +sourcefile("signal_api") + +local stub_aspect_t1 = { main = math.random() } +local stub_pos_t1 = {x = 1, y = 0, z = 1} + +minetest.register_node(":stubsignal_t1", { + advtrains = { + supported_aspects = {}, + get_aspect = function () return stub_aspect_t1 end, + set_aspect = function () end, + }, + groups = { advtrains_signal = 2 }, +}) + +world.layout { + {stub_pos_t1, "stubsignal_t1"}, +} + +describe("API for supposed signal aspects", function() + it("should load and save data properly", function() + local tbl = {_foo = true} + advtrains.interlocking.load_supposed_aspects(tbl) + assert.same(tbl, advtrains.interlocking.save_supposed_aspects()) + end) + it("should set and get type 1 signals properly", function () + local pos = stub_pos_t1 + local asp = stub_aspect_t1 + local newasp = { dst = math.random() } + assert.same(asp, advtrains.interlocking.signal_get_aspect(pos)) + advtrains.interlocking.signal_set_aspect(pos, newasp) + assert.same(newasp, advtrains.interlocking.signal_get_aspect(pos)) + assert.same(asp, advtrains.interlocking.signal_get_real_aspect(pos)) + end) +end) -- cgit v1.2.3 From d1a0d8f2654d6ee64c1a43de7958b1eadfaff6b0 Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Fri, 10 Jun 2022 22:21:54 +0200 Subject: Use tabs to switch between signaling and IP forms --- .gitignore | 1 + advtrains/formspec.lua | 36 +++++- advtrains_interlocking/signal_api.lua | 11 +- advtrains_interlocking/signal_aspect_ui.lua | 165 ++++++++++++++++++------ advtrains_interlocking/signal_aspects.lua | 2 +- advtrains_interlocking/signal_main_ui.lua | 0 advtrains_interlocking/spec/signal_api_spec.lua | 3 +- advtrains_interlocking/tcb_ts_ui.lua | 25 ++-- 8 files changed, 176 insertions(+), 67 deletions(-) create mode 100644 advtrains_interlocking/signal_main_ui.lua (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/.gitignore b/.gitignore index b3180de..bef77f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ ## Eclipse project files & directories .project .settings +luacov.* diff --git a/advtrains/formspec.lua b/advtrains/formspec.lua index 91e300d..aa5aa69 100644 --- a/advtrains/formspec.lua +++ b/advtrains/formspec.lua @@ -1,6 +1,14 @@ local sformat = string.format local fsescape = minetest.formspec_escape +local function make_list(entries) + local t = {} + for k, v in ipairs(entries) do + t[k] = fsescape(v) + end + return table.concat(t, ",") +end + local function f_button_exit(x, y, w, h, id, text) return sformat("button_exit[%f,%f;%f,%f;%s;%s]", x, y, w, h, id, text) end @@ -10,12 +18,8 @@ local function S_button_exit(x, y, w, h, id, ...) end local function f_dropdown(x, y, w, id, entries, sel, indexed) - local t = {} - for k, v in pairs(entries) do - t[k] = fsescape(v) - end return sformat("dropdown[%f,%f;%f;%s;%s;%d%s]", - x, y, w, id, table.concat(t, ","), + x, y, w, id, make_list(entries), sel or 1, indexed and ";true" or "") end @@ -28,10 +32,32 @@ local function S_label(x, y, ...) return f_label(x, y, attrans(...)) end +local function f_tabheader(x, y, w, h, id, entries, sel, transparent, border) + local st = {string.format("%f,%f",x, y)} + if h then + if w then + st[#st+1] = string.format("%f,%f", w, h) + else + st[#st+1] = tostring(h) + end + end + st[#st+1] = tostring(id) + st[#st+1] = make_list(entries) + st[#st+1] = tostring(sel) + if transparent ~= nil then + st[#st+1] = tostring(transparent) + if border ~= nil then + st[#st+1] = tostring(border) + end + end + return string.format("tabheader[%s]", table.concat(st, ";")) +end + return { button_exit = f_button_exit, S_button_exit = S_button_exit, dropdown = f_dropdown, label = f_label, S_label = S_label, + tabheader = f_tabheader, } diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index a25e1f6..5b3baf8 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -272,7 +272,10 @@ function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, advtrains.interlocking.show_ip_form(pos, pname) return end + advtrains.interlocking.show_signal_form(pos, node, pname) +end +function advtrains.interlocking.show_signal_form(pos, node, pname) local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) if sigd then advtrains.interlocking.show_signalling_form(sigd, pname) @@ -288,7 +291,7 @@ function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, advtrains.interlocking.show_signal_aspect_selector( pname, ndef.advtrains.supported_aspects, - "Set aspect manually", callback, + pos, callback, isasp) else --static signal - only IP @@ -332,7 +335,7 @@ function advtrains.interlocking.signal_get_aspect(pos) local asp = get_supposed_aspect(pos) if not asp then asp = advtrains.interlocking.signal_get_real_aspect(pos) - set_supposed_aspect(pos) + set_supposed_aspect(pos, asp) end return asp end @@ -372,6 +375,7 @@ function advtrains.interlocking.show_ip_form(pos, pname, only_notset) return end local form = "size[7,5]label[0.5,0.5;Signal at "..minetest.pos_to_string(pos).."]" + form = form .. advtrains.interlocking.make_signal_formspec_tabheader(pname, pos, 7, 2) advtrains.interlocking.db.check_for_duplicate_ip(pos) local pts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos) if pts then @@ -394,6 +398,9 @@ end minetest.register_on_player_receive_fields(function(player, formname, fields) local pname = player:get_player_name() + if advtrains.interlocking.handle_signal_formspec_tabheader_fields(pname, fields) then + return true + end if not minetest.check_player_privs(pname, {train_operator=true, interlocking=true}) then return end diff --git a/advtrains_interlocking/signal_aspect_ui.lua b/advtrains_interlocking/signal_aspect_ui.lua index edf3ab1..4b41187 100644 --- a/advtrains_interlocking/signal_aspect_ui.lua +++ b/advtrains_interlocking/signal_aspect_ui.lua @@ -1,63 +1,136 @@ local F = advtrains.formspec local players_aspsel = {} -local function make_signal_aspect_selector_t1(suppasp, purpose, isasp) - local form = {"size[7,7.5]"} - form[#form+1] = F.S_label(0.5, 0.5, "Select signal aspect") - form[#form+1] = F.label(0.5, 1, purpose) +local function describe_t1_main_aspect(spv) + if spv == 0 then + return attrans("Danger (halt)") + elseif spv == -1 then + return attrans("Continue at maximum speed") + elseif not spv then + return attrans("Continue with current speed limit") + else + return attrans("Continue with the speed limit of @1", tostring(spv)) + end +end + +local function describe_t1_shunt_aspect(shunt) + if shunt then + return attrans("Shunting allowed") + else + return attrans("No shunting") + end +end + +local function describe_t1_distant_aspect(spv) + if spv == 0 then + return attrans("Expect to stop at the next signal") + elseif spv == -1 then + return attrans("Expect to continue at maximum speed") + elseif not spv then + return attrans("No distant signal information") + else + return attrans("Expect to continue with a speed limit of @1", tostring(spv)) + end +end + +advtrains.interlocking.describe_t1_main_aspect = describe_t1_main_aspect +advtrains.interlocking.describe_t1_shunt_aspect = describe_t1_shunt_aspect +advtrains.interlocking.describe_t1_distant_aspect = describe_t1_distant_aspect + +local function describe_supported_aspects_t1(suppasp, isasp) + local t = {} - form[#form+1] = F.S_label(0.5, 1.5, "Main aspect") local entries = {} local selid = 1 for idx, spv in ipairs(suppasp.main) do - local entry if isasp and spv == isasp.main then selid = idx end - if spv == 0 then - entry = attrans("Danger (halt)") - elseif spv == -1 then - entry = attrans("Continue at maximum speed") - elseif not spv then - entry = attrans("Continue with current speed limit") - else - entry = attrans("Continue with the speed limit of @1", spv) - end - entries[idx] = entry + entries[idx] = describe_t1_main_aspect(spv) end - form[#form+1] = F.dropdown(0.5, 2, 6, "main", entries, selid, true) + t.main = entries + t.main_current = selid - form[#form+1] = F.S_label(0.5, 3, "Shunt aspect") if suppasp.shunt == nil then - local st = 1 - if isasp and isasp.shunt then st = 2 end - local entries = { - attrans("No shunting"), - attrans("Shunting allowed"), + selid = 1 + if isasp and isasp.shunt then + selid = 2 + end + entries = { + describe_t1_shunt_aspect(false), + describe_t1_shunt_aspect(true), } - form[#form+1] = F.dropdown(0.5, 3.5, 6, "shunt_free", entries, st, true) + t.shunt = entries + t.shunt_current = selid end - form[#form+1] = F.S_label(0.5, 4.5, "Distant aspect") - local entries = {} - local selid = 1 + entries = {} + selid = 1 for idx, spv in ipairs(suppasp.dst) do - local entry if isasp and spv == isasp.dst then selid = idx end - if spv == 0 then - entry = attrans("Expect to stop at the next signal") - elseif spv == -1 then - entry = attrans("Expect to continue at maximum speed") - elseif not spv then - entry = attrans("No information on distant signal") - else - entry = attrans("Expect to continue with a speed limit of @1", spv) - end - entries[idx] = entry + entries[idx] = describe_t1_distant_aspect(spv) end - form[#form+1] = F.dropdown(0.5, 5, 6, "dst", entries, selid, true) + t.dst = entries + t.dst_current = selid + return t +end + +advtrains.interlocking.describe_supported_aspects_t1 = describe_supported_aspects_t1 + +local signal_tabheader_map = {} + +local function make_signal_formspec_tabheader(pname, pos, width, selid) + signal_tabheader_map[pname] = pos + local options = { + attrans("Signal aspect"), + attrans("Influence point"), + attrans("Distant signalling"), + } + return F.tabheader(0, 0, nil, nil, "signal_tab", options, selid) +end + +local function handle_signal_formspec_tabheader_fields(pname, fields) + local n = tonumber(fields.signal_tab) + local pos = signal_tabheader_map[pname] + if not (n and pos) then + return false + end + if n == 1 then + local node = advtrains.ndb.get_node(pos) + advtrains.interlocking.show_signal_form(pos, node, pname) + elseif n == 2 then + advtrains.interlocking.show_ip_form(pos, pname) + end + return true +end + +advtrains.interlocking.make_signal_formspec_tabheader = make_signal_formspec_tabheader +advtrains.interlocking.handle_signal_formspec_tabheader_fields = handle_signal_formspec_tabheader_fields + +local function make_signal_aspect_selector_t1(suppasp, purpose, isasp) + local form = {"size[7,7.25]"} + local t = describe_supported_aspects_t1(suppasp, isasp) + if type(purpose) == "table" then + form[#form+1] = make_signal_formspec_tabheader(purpose.pname, purpose.pos, 7, 1) + purpose = "" + end + form[#form+1] = F.S_label(0.5, 0.5, "Select signal aspect") + form[#form+1] = F.label(0.5, 1, purpose) + + form[#form+1] = F.S_label(0.5, 1.5, "Main aspect") + form[#form+1] = F.dropdown(0.5, 2, 6, "main", t.main, t.main_current, true) + + form[#form+1] = F.S_label(0.5, 3, "Shunt aspect") + if t.shunt then + form[#form+1] = F.dropdown(0.5, 3.5, 6, "shunt_free", t.shunt, t.shunt_current, true) + else + form[#form+1] = F.S_label(0.5, 3.5, "The shunt aspect cannot be changed") + end + + form[#form+1] = F.S_label(0.5, 4.5, "Distant aspect") + form[#form+1] = F.dropdown(0.5, 5, 6, "dst", t.dst, t.dst_current, true) form[#form+1] = F.S_button_exit(0.5, 6, 6, 1, "save", "Save signal aspect") return table.concat(form) @@ -69,6 +142,10 @@ local function make_signal_aspect_selector_t2(suppasp, purpose, isasp) if not def then return nil end + if type(purpose) == "table" then + form[#form+1] = make_signal_formspec_tabheader(purpose.pname, purpose.pos, 7, 1) + purpose = "" + end form[#form+1] = F.S_label(0.5, 0.5, "Select signal aspect") form[#form+1] = F.label(0.5, 1, purpose) @@ -93,6 +170,9 @@ function advtrains.interlocking.show_signal_aspect_selector(pname, p_suppasp, p_ info = {}, } local purpose = p_purpose or "" + if type(p_purpose) == "table" then + purpose = {pname = pname, pos = p_purpose} + end local form if suppasp.type == 2 then @@ -106,13 +186,13 @@ function advtrains.interlocking.show_signal_aspect_selector(pname, p_suppasp, p_ local token = advtrains.random_id() minetest.show_formspec(pname, "at_il_sigaspdia_"..token, form) - minetest.after(1, function() + --minetest.after(1, function() players_aspsel[pname] = { suppasp = suppasp, callback = callback, token = token, } - end) + --end) end local function usebool(sup, val, free) @@ -147,6 +227,9 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if psl then if formname == "at_il_sigaspdia_"..psl.token then local suppasp = psl.suppasp + if handle_signal_formspec_tabheader_fields(pname, fields) then + return true + end if fields.save then local asp if suppasp.type == 2 then diff --git a/advtrains_interlocking/signal_aspects.lua b/advtrains_interlocking/signal_aspects.lua index a70d176..eebb4ba 100644 --- a/advtrains_interlocking/signal_aspects.lua +++ b/advtrains_interlocking/signal_aspects.lua @@ -1,4 +1,4 @@ -type2defs = {} +local type2defs = {} local function register_type2(def) local t = {type = 2} diff --git a/advtrains_interlocking/signal_main_ui.lua b/advtrains_interlocking/signal_main_ui.lua new file mode 100644 index 0000000..e69de29 diff --git a/advtrains_interlocking/spec/signal_api_spec.lua b/advtrains_interlocking/spec/signal_api_spec.lua index 2659380..cd7a1d1 100644 --- a/advtrains_interlocking/spec/signal_api_spec.lua +++ b/advtrains_interlocking/spec/signal_api_spec.lua @@ -4,8 +4,7 @@ mineunit("core") _G.advtrains = { interlocking = { - aspects = sourcefile("signal_aspects"), - --aspects = fixture("../../signal_aspects"), + aspects = fixture("../../signal_aspects"), }, ndb = { get_node = minetest.get_node, diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index 0cc10da..b3b8221 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -608,7 +608,8 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle if not tcbs.signal_name then tcbs.signal_name = "Signal at "..minetest.pos_to_string(sigd.p) end if not tcbs.routes then tcbs.routes = {} end - local form = "size[7,10]label[0.5,0.5;Signal at "..minetest.pos_to_string(sigd.p).."]" + local form = "size[7,9.75]label[0.5,0.5;Signal at "..minetest.pos_to_string(sigd.p).."]" + form = form .. advtrains.interlocking.make_signal_formspec_tabheader(pname, tcbs.signal, 7, 1) form = form.."field[0.8,1.5;5.2,1;name;Signal name;"..minetest.formspec_escape(tcbs.signal_name).."]" form = form.."button[5.5,1.2;1,1;setname;Set]" @@ -668,12 +669,7 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle if hasprivs then form = form.."button[0.5,8;2.5,1;newroute;New Route]" form = form.."button[ 3,8;2.5,1;unassign;Unassign Signal]" - form = form.."button[ 3,9;2.5,1;influp;Influence Point]" - end - if tcbs.ars_disabled then - form = form.."button[0.5,9;2.5,1;arsenable;Enable ARS]" - else - form = form.."button[0.5,9;2.5,1;arsdisable;Disable ARS]" + form = form..string.format("checkbox[0.5,8.75;ars;Automatic routesetting;%s]", not tcbs.ars_disabled) end elseif sigd_equal(tcbs.route_origin, sigd) then -- something has gone wrong: tcbs.routeset should have been set... @@ -706,6 +702,10 @@ end minetest.register_on_player_receive_fields(function(player, formname, fields) local pname = player:get_player_name() + if string.find(formname, "^at_il_signalling_") + and advtrains.interlocking.handle_signal_formspec_tabheader_fields(pname, fields) then + return true + end if not minetest.check_player_privs(pname, "train_operator") then return end @@ -792,16 +792,9 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) minetest.chat_send_player(pname, "Please cancel route first!") end end - if fields.influp and hasprivs then - advtrains.interlocking.show_ip_form(tcbs.signal, pname) - return - end - if tcbs.ars_disabled and fields.arsenable then - tcbs.ars_disabled = nil - end - if not tcbs.ars_disabled and fields.arsdisable then - tcbs.ars_disabled = true + if fields.ars then + tcbs.ars_disabled = not minetest.is_yes(fields.ars) end if fields.auto then -- cgit v1.2.3 From 98c37108762c6d7c9f1d691b84f49bfa65b81b28 Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Sat, 11 Jun 2022 18:07:00 +0200 Subject: Implement primitive distant signaling --- advtrains/formspec.lua | 10 ++ advtrains_interlocking/database.lua | 4 + advtrains_interlocking/demosignals.lua | 6 +- advtrains_interlocking/distant.lua | 137 +++++++++++++++++++++ advtrains_interlocking/distant_ui.lua | 76 ++++++++++++ advtrains_interlocking/init.lua | 3 + advtrains_interlocking/routesetting.lua | 21 ++++ advtrains_interlocking/signal_api.lua | 85 +------------ advtrains_interlocking/signal_aspect_accessors.lua | 127 +++++++++++++++++++ advtrains_interlocking/signal_aspect_ui.lua | 6 +- advtrains_interlocking/signal_aspects.lua | 26 +++- advtrains_interlocking/signal_main_ui.lua | 0 .../spec/basic_signalling_spec.lua | 87 +++++++++++++ advtrains_interlocking/spec/signal_api_spec.lua | 49 -------- advtrains_interlocking/tcb_ts_ui.lua | 8 +- 15 files changed, 507 insertions(+), 138 deletions(-) create mode 100644 advtrains_interlocking/distant.lua create mode 100644 advtrains_interlocking/distant_ui.lua create mode 100644 advtrains_interlocking/signal_aspect_accessors.lua delete mode 100644 advtrains_interlocking/signal_main_ui.lua create mode 100644 advtrains_interlocking/spec/basic_signalling_spec.lua delete mode 100644 advtrains_interlocking/spec/signal_api_spec.lua (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains/formspec.lua b/advtrains/formspec.lua index aa5aa69..20dab59 100644 --- a/advtrains/formspec.lua +++ b/advtrains/formspec.lua @@ -9,6 +9,14 @@ local function make_list(entries) return table.concat(t, ",") end +local function f_button(x, y, w, h, id, text) + return sformat("button[%f,%f;%f,%f;%s;%s]", x, y, w, h, id, text) +end + +local function S_button(x, y, w, h, id, ...) + return f_button(x, y, w, h, id, attrans(...)) +end + local function f_button_exit(x, y, w, h, id, text) return sformat("button_exit[%f,%f;%f,%f;%s;%s]", x, y, w, h, id, text) end @@ -54,6 +62,8 @@ local function f_tabheader(x, y, w, h, id, entries, sel, transparent, border) end return { + button = f_button, + S_button = S_button, button_exit = f_button_exit, S_button_exit = S_button_exit, dropdown = f_dropdown, diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index efa5eb8..c5ae906 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -134,6 +134,9 @@ function ildb.load(data) if data.supposed_aspects then advtrains.interlocking.load_supposed_aspects(data.supposed_aspects) end + if data.distant then + advtrains.distant.load(data.distant) + end --COMPATIBILITY to Signal aspect format -- TODO remove in time... @@ -177,6 +180,7 @@ function ildb.save() influence_points = influence_points, npr_rails = advtrains.interlocking.npr_rails, supposed_aspects = advtrains.interlocking.save_supposed_aspects(), + distant = advtrains.distant.save(), } end diff --git a/advtrains_interlocking/demosignals.lua b/advtrains_interlocking/demosignals.lua index 1c1b8b2..de6926a 100644 --- a/advtrains_interlocking/demosignals.lua +++ b/advtrains_interlocking/demosignals.lua @@ -50,7 +50,7 @@ minetest.register_node("advtrains_interlocking:ds_danger", { }, on_rightclick = advtrains.interlocking.signal_rc_handler, can_dig = advtrains.interlocking.signal_can_dig, - after_dig_node = advtrains.interlocking.signal_after_dig, + after_destruct = advtrains.interlocking.signal_after_dig, }) minetest.register_node("advtrains_interlocking:ds_free", { description = "Demo signal at Free", @@ -71,7 +71,7 @@ minetest.register_node("advtrains_interlocking:ds_free", { }, on_rightclick = advtrains.interlocking.signal_rc_handler, can_dig = advtrains.interlocking.signal_can_dig, - after_dig_node = advtrains.interlocking.signal_after_dig, + after_destruct = advtrains.interlocking.signal_after_dig, }) minetest.register_node("advtrains_interlocking:ds_slow", { description = "Demo signal at Slow", @@ -92,6 +92,6 @@ minetest.register_node("advtrains_interlocking:ds_slow", { }, on_rightclick = advtrains.interlocking.signal_rc_handler, can_dig = advtrains.interlocking.signal_can_dig, - after_dig_node = advtrains.interlocking.signal_after_dig, + after_destruct = advtrains.interlocking.signal_after_dig, }) diff --git a/advtrains_interlocking/distant.lua b/advtrains_interlocking/distant.lua new file mode 100644 index 0000000..ffa9e08 --- /dev/null +++ b/advtrains_interlocking/distant.lua @@ -0,0 +1,137 @@ +local db_distant = {} +local db_distant_of = {} + +local A = advtrains.interlocking.aspects +local pts = advtrains.encode_pos +local stp = advtrains.decode_pos + +local function db_load(x) + if type(x) ~= "table" then + return + end + db_distant = x.distant + db_distant_of = x.distant_of +end + +local function db_save() + return { + distant = db_distant, + distant_of = db_distant_of, + } +end + +local update_signal, update_main, update_dst + +local function unassign_dst(dst, force) + local pts_dst = pts(dst) + local main = db_distant_of[pts_dst] + db_distant_of[pts_dst] = nil + if main then + local pts_main = main[1] + local t = db_distant[pts_main] + if t then + t[pts_dst] = nil + end + end + if not force then + update_dst(dst) + end +end + +local function unassign_main(main, force) + local pts_main = pts(main) + local t = db_distant[pts_main] + if not t then + return + end + for pts_dst in pairs(t) do + local realmain = db_distant_of[pts_dst] + if realmain and realmain[1] == pts_main then + db_distant_of[pts_dst] = nil + if not force then + local dst = stp(pts_dst) + update_dst(dst) + end + end + end + db_distant[pts_main] = nil +end + +local function unassign_all(pos, force) + unassign_main(pos) + unassign_dst(pos, force) +end + +local function assign(main, dst, by) + local pts_main = pts(main) + local pts_dst = pts(dst) + local t = db_distant[pts_main] + if not t then + t = {} + db_distant[pts_main] = t + end + if not by then + by = "manual" + end + unassign_dst(dst, true) + t[pts_dst] = by + db_distant_of[pts_dst] = {pts_main, by} + update_dst(dst) +end + +local function pre_occupy(dst, by) + local pts_dst = pts(dst) + unassign_dst(dst) + db_distant_of[pts_dst] = {nil, by} +end + +local function get_distant(main) + local pts_main = pts(main) + return db_distant[pts_main] or {} +end + +local function get_main(dst) + local pts_dst = pts(dst) + local main = db_distant_of[pts_dst] + if not main then + return + end + if main[1] then + return stp(main[1]), unpack(main, 2) + else + return unpack(main) + end +end + +update_main = function(main) + local pts_main = pts(main) + local t = get_distant(main) + for pts_dst in pairs(t) do + local dst = stp(pts_dst) + advtrains.interlocking.signal_readjust_aspect(dst) + end +end + +update_dst = function(dst) + advtrains.interlocking.signal_readjust_aspect(dst) +end + +update_signal = function(pos) + update_main(pos) + update_dst(pos) +end + +advtrains.distant = { + load = db_load, + save = db_save, + assign = assign, + unassign_dst = unassign_dst, + unassign_main = unassign_main, + unassign_all = unassign_all, + get_distant = get_distant, + get_dst = get_distant, + get_main = get_main, + update_main = update_main, + update_dst = update_dst, + update_signal = update_signal, +} diff --git a/advtrains_interlocking/distant_ui.lua b/advtrains_interlocking/distant_ui.lua new file mode 100644 index 0000000..4ec2255 --- /dev/null +++ b/advtrains_interlocking/distant_ui.lua @@ -0,0 +1,76 @@ +local F = advtrains.formspec +local D = advtrains.distant +local I = advtrains.interlocking + +function advtrains.interlocking.show_distant_signal_form(pos, pname) + local form = {"size[7,7]"} + form[#form+1] = advtrains.interlocking.make_signal_formspec_tabheader(pname, pos, 7, 3) + local main, set_by = D.get_main(pos) + if main then + local pts_main = minetest.pos_to_string(main) + form[#form+1] = F.S_label(0.5, 0.5, "This signal is a distant signal of @1.", pts_main) + if set_by == "manual" then + form[#form+1] = F.S_label(0.5, 1, "The assignment is made manually.") + elseif set_by == "routesetting" then + form[#form+1] = F.S_label(0.5, 1, "The assignment is made by the routesetting system.") + end + else + form[#form+1] = F.S_label(0.5, 0.5, "This signal is not assigned to a main signal.") + form[#form+1] = F.S_label(0.5, 1, "The distant aspect of the signal is not used.") + end + if set_by ~= nil then + form[#form+1] = F.S_button_exit(0.5, 1.5, 3, 1, "unassign_dst", "Unassign") + form[#form+1] = F.S_button_exit(3.5, 1.5, 3, 1, "assign_dst", "Reassign") + else + form[#form+1] = F.S_button_exit(0.5, 1.5, 6, 1, "assign_dst", "Assign") + end + minetest.show_formspec(pname, "advtrains:distant_" .. minetest.pos_to_string(pos), table.concat(form)) +end + +local signal_pos = {} +local function init_signal_assignment(pname, pos) + if not minetest.check_player_privs(pname, "interlocking") then + minetest.chat_send_player(pname, attrans("This operation is not allowed without the @1 privilege.", "interlocking")) + return + end + signal_pos[pname] = pos + minetest.chat_send_player(pname, attrans("Please punch the signal to use as the main signal.")) +end + +minetest.register_on_punchnode(function(pos, node, player, pointed_thing) + local pname = player:get_player_name() + if not minetest.check_player_privs(pname, "interlocking") then + return + end + local spos = signal_pos[pname] + if not spos then + return + end + signal_pos[pname] = nil + local is_signal = minetest.get_item_group(node.name, "advtrains_signal") >= 2 + if not is_signal then + minetest.chat_send_player(pname, attrans("Incompatible signal.")) + return + end + minetest.chat_send_player(pname, attrans("Successfully assigned signal.")) + D.assign(pos, spos, "manual") +end) + +minetest.register_on_player_receive_fields(function(player, formname, fields) + local pname = player:get_player_name() + local pos = minetest.string_to_pos(string.match(formname, "^advtrains:distant_(.+)$") or "") + if not pos then + return + end + if not minetest.check_player_privs(pname, "interlocking") then + return + end + if advtrains.interlocking.handle_signal_formspec_tabheader_fields(pname, fields) then + return true + end + if fields.unassign_dst then + D.unassign_dst(pos) + elseif fields.assign_dst then + init_signal_assignment(pname, pos) + end +end) diff --git a/advtrains_interlocking/init.lua b/advtrains_interlocking/init.lua index d0b75a8..908d998 100644 --- a/advtrains_interlocking/init.lua +++ b/advtrains_interlocking/init.lua @@ -15,6 +15,9 @@ local modpath = minetest.get_modpath(minetest.get_current_modname()) .. DIR_DELI advtrains.interlocking.aspects = dofile(modpath.."signal_aspects.lua") dofile(modpath.."database.lua") +dofile(modpath.."distant.lua") +dofile(modpath.."distant_ui.lua") +dofile(modpath.."signal_aspect_accessors.lua") dofile(modpath.."signal_api.lua") dofile(modpath.."signal_aspect_ui.lua") dofile(modpath.."demosignals.lua") diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua index 67efaea..f1b4455 100644 --- a/advtrains_interlocking/routesetting.lua +++ b/advtrains_interlocking/routesetting.lua @@ -45,6 +45,7 @@ function ilrs.set_route(signal, route, try) local rtename = route.name local signalname = ildb.get_tcbs(signal).signal_name local c_tcbs, c_ts_id, c_ts, c_rseg, c_lckp + local signals = {} while c_sigd and i<=#route do c_tcbs = ildb.get_tcbs(c_sigd) if not c_tcbs then @@ -115,6 +116,7 @@ function ilrs.set_route(signal, route, try) c_tcbs.aspect = route.aspect or advtrains.interlocking.GENERIC_FREE c_tcbs.route_origin = signal advtrains.interlocking.update_signal_aspect(c_tcbs) + signals[#signals+1] = c_tcbs.signal end end -- advance @@ -122,6 +124,25 @@ function ilrs.set_route(signal, route, try) c_sigd = c_rseg.next i = i + 1 end + + -- Distant signaling + local lastsig = nil + if c_sigd then + local e_tcbs = ildb.get_tcbs(c_sigd) + local pos = e_tcbs and e_tcbs.signal + if pos then + lastsig = pos + end + end + for i = #signals, 1, -1 do + if lastsig then + local pos = signals[i] + local _, assigned_by = advtrains.distant.get_main(pos) + if assigned_by ~= "manual" then + advtrains.distant.assign(lastsig, signals[i], "routesetting") + end + end + end return true end diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 5b3baf8..1fd4e34 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -167,7 +167,6 @@ This function will query get_aspect to retrieve the new aspect. local DANGER = { main = 0, - dst = false, shunt = false, } advtrains.interlocking.DANGER = DANGER @@ -178,8 +177,6 @@ advtrains.interlocking.GENERIC_FREE = { dst = false, } -local supposed_aspects = {} - local function convert_aspect_if_necessary(asp) if type(asp.main) == "table" then local newasp = {} @@ -200,24 +197,7 @@ local function convert_aspect_if_necessary(asp) end return asp end - -function advtrains.interlocking.load_supposed_aspects(tbl) - if tbl then - supposed_aspects = tbl - end -end - -function advtrains.interlocking.save_supposed_aspects() - return supposed_aspects -end - -local function set_supposed_aspect(pos, asp) - supposed_aspects[advtrains.roundfloorpts(pos)] = asp -end - -local function get_supposed_aspect(pos) - return supposed_aspects[advtrains.roundfloorpts(pos)] -end +advtrains.interlocking.signal_convert_aspect_if_necessary = convert_aspect_if_necessary function advtrains.interlocking.update_signal_aspect(tcbs) if tcbs.signal then @@ -233,27 +213,8 @@ end function advtrains.interlocking.signal_after_dig(pos) -- clear influence point advtrains.interlocking.db.clear_ip_by_signalpos(pos) - set_supposed_aspect(pos, nil) -end - -function advtrains.interlocking.signal_set_aspect(pos, asp) - asp = convert_aspect_if_necessary(asp) - local node=advtrains.ndb.get_node(pos) - local ndef=minetest.registered_nodes[node.name] - if ndef and ndef.advtrains and ndef.advtrains.set_aspect then - local oldasp = advtrains.interlocking.signal_get_aspect(pos) or DANGER - local suppasp = advtrains.interlocking.signal_get_supported_aspects(pos) or {} - local newasp = asp - if suppasp.type == 2 then - asp = advtrains.interlocking.aspects.type1_to_type2main(asp, suppasp.group) - end - set_supposed_aspect(pos, newasp) - ndef.advtrains.set_aspect(pos, node, asp) - local aspect_changed = advtrains.interlocking.aspects.not_equalp(oldasp, newasp) - if aspect_changed then - advtrains.interlocking.signal_on_aspect_changed(pos) - end - end + advtrains.interlocking.signal_clear_aspect(pos) + advtrains.distant.unassign_all(pos, true) end -- should be called when aspect has changed on this signal. @@ -312,46 +273,6 @@ function advtrains.interlocking.signal_get_supposed_aspect(pos) return DANGER; end --- Returns the actual aspect of the signal at position, as returned by the nodedef. --- returns nil when there's no signal at the position -function advtrains.interlocking.signal_get_real_aspect(pos) - local node=advtrains.ndb.get_node(pos) - local ndef=minetest.registered_nodes[node.name] - if ndef and ndef.advtrains and ndef.advtrains.get_aspect then - local asp = ndef.advtrains.get_aspect(pos, node) - local suppasp = advtrains.interlocking.signal_get_supported_aspects(pos) or {} - if suppasp.type == 2 then - asp = advtrains.interlocking.aspects.type2main_to_type1(suppasp.group, asp) - end - if not asp then asp = DANGER end - asp = convert_aspect_if_necessary(asp) - return asp - end - return nil -end - --- Returns the signal aspect as reported in the suppasp table. -function advtrains.interlocking.signal_get_aspect(pos) - local asp = get_supposed_aspect(pos) - if not asp then - asp = advtrains.interlocking.signal_get_real_aspect(pos) - set_supposed_aspect(pos, asp) - end - return asp -end - --- Returns the "supported_aspects" of the signal at position, as returned by the nodedef. --- returns nil when there's no signal at the position -function advtrains.interlocking.signal_get_supported_aspects(pos) - local node=advtrains.ndb.get_node(pos) - local ndef=minetest.registered_nodes[node.name] - if ndef and ndef.advtrains and ndef.advtrains.supported_aspects then - local asp = ndef.advtrains.supported_aspects - return asp - end - return nil -end - local players_assign_ip = {} local function ipmarker(ipos, connid) diff --git a/advtrains_interlocking/signal_aspect_accessors.lua b/advtrains_interlocking/signal_aspect_accessors.lua new file mode 100644 index 0000000..02a03ea --- /dev/null +++ b/advtrains_interlocking/signal_aspect_accessors.lua @@ -0,0 +1,127 @@ +local A = advtrains.interlocking.aspects +local D = advtrains.distant +local I = advtrains.interlocking +local N = advtrains.ndb +local pts = advtrains.roundfloorpts + +local get_aspect + +local supposed_aspects = {} + +function I.load_supposed_aspects(tbl) + if tbl then + supposed_aspects = tbl + end +end + +function I.save_supposed_aspects() + return supposed_aspects +end + +local function get_supposed_aspect(pos) + return supposed_aspects[pts(pos)] +end + +local function set_supposed_aspect(pos, asp) + supposed_aspects[pts(pos)] = asp +end + +local function get_ndef(pos) + local node = N.get_node(pos) + return minetest.registered_nodes[node.name] or {} +end + +local function get_supported_aspects(pos) + local ndef = get_ndef(pos) + if ndef.advtrains and ndef.advtrains.supported_aspects then + return ndef.advtrains.supported_aspects + end + return nil +end + +local function adjust_aspect(pos, asp) + asp = table.copy(I.signal_convert_aspect_if_necessary(asp)) + + local mainpos = D.get_main(pos) + local nxtasp + if asp.main ~= 0 and mainpos then + nxtasp = get_aspect(mainpos) + asp.dst = nxtasp.main + else + asp.dst = nil + end + + local suppasp = get_supported_aspects(pos) + if not suppasp then + return asp, asp + end + local stype = suppasp.type + if stype == 2 then + local group = suppasp.group + local name + if asp.main ~= 0 and nxtasp and nxtasp.type2group == group and nxtasp.type2name then + name = A.get_type2_dst(group, nxtasp.type2name) + else + name = A.type1_to_type2main(asp, group) + end + asp.type2group = group + asp.type2name = name + return asp, name + end + asp.type2name = nil + asp.type2group = nil + return asp, asp +end + +local function get_real_aspect(pos) + local ndef = get_ndef(pos) + if ndef.advtrains and ndef.advtrains.get_aspect then + local asp = ndef.advtrains.get_aspect(pos, node) or I.DANGER + local suppasp = get_supported_aspects(pos) + if suppasp.type == 2 then + asp = A.type2main_to_type1(suppasp.group, asp) + end + return adjust_aspect(pos, asp) + end + return nil +end + +get_aspect = function(pos) + local asp = get_supposed_aspect(pos) + if not asp then + asp = get_real_aspect(pos) + set_supposed_aspect(pos, asp) + end + return asp +end + +local function set_aspect(pos, asp) + local node = N.get_node(pos) + local ndef = minetest.registered_nodes[node.name] + if ndef and ndef.advtrains and ndef.advtrains.set_aspect then + local oldasp = I.signal_get_aspect(pos) or DANGER + local newasp, aspval = adjust_aspect(pos, asp) + set_supposed_aspect(pos, newasp) + ndef.advtrains.set_aspect(pos, node, aspval) + local aspect_changed = A.not_equalp(oldasp, newasp) + if aspect_changed then + I.signal_on_aspect_changed(pos) + D.update_main(pos) + end + end +end + +local function clear_aspect(pos) + set_supposed_aspect(pos, nil) +end + +local function readjust_aspect(pos) + set_aspect(pos, get_aspect(pos)) +end + +I.signal_get_supported_aspects = get_supported_aspects +I.signal_get_real_aspect = get_real_aspect +I.signal_get_aspect = get_aspect +I.signal_set_aspect = set_aspect +I.signal_clear_aspect = clear_aspect +I.signal_readjust_aspect = readjust_aspect diff --git a/advtrains_interlocking/signal_aspect_ui.lua b/advtrains_interlocking/signal_aspect_ui.lua index 4b41187..30b5165 100644 --- a/advtrains_interlocking/signal_aspect_ui.lua +++ b/advtrains_interlocking/signal_aspect_ui.lua @@ -43,7 +43,7 @@ local function describe_supported_aspects_t1(suppasp, isasp) local entries = {} local selid = 1 for idx, spv in ipairs(suppasp.main) do - if isasp and spv == isasp.main then + if isasp and spv == (isasp.main or false) then selid = idx end entries[idx] = describe_t1_main_aspect(spv) @@ -67,7 +67,7 @@ local function describe_supported_aspects_t1(suppasp, isasp) entries = {} selid = 1 for idx, spv in ipairs(suppasp.dst) do - if isasp and spv == isasp.dst then + if isasp and spv == (isasp.dst or false) then selid = idx end entries[idx] = describe_t1_distant_aspect(spv) @@ -102,6 +102,8 @@ local function handle_signal_formspec_tabheader_fields(pname, fields) advtrains.interlocking.show_signal_form(pos, node, pname) elseif n == 2 then advtrains.interlocking.show_ip_form(pos, pname) + elseif n == 3 then + advtrains.interlocking.show_distant_signal_form(pos, pname) end return true end diff --git a/advtrains_interlocking/signal_aspects.lua b/advtrains_interlocking/signal_aspects.lua index eebb4ba..2866ae1 100644 --- a/advtrains_interlocking/signal_aspects.lua +++ b/advtrains_interlocking/signal_aspects.lua @@ -44,6 +44,27 @@ local function get_type2_definition(name) return type2defs[name] end +local function get_type2_danger(group) + local def = type2defs[group] + if not def then + return nil + end + local main = def.main + return main[#main] +end + +local function get_type2_dst(group, name) + local def = type2defs[group] + if not def then + return nil + end + local aspidx = name + if type(name) ~= "number" then + aspidx = def.main[name] or 1 + end + return def.main[math.max(1, aspidx-1)].name +end + local function type2main_to_type1(name, asp) local def = type2defs[name] if not def then @@ -53,7 +74,7 @@ local function type2main_to_type1(name, asp) if type(asp) == "number" then aspidx = asp else - aspidx = def.main[asp] + aspidx = def.main[asp] or 2 end local asptbl = def.main[aspidx] if not asptbl then @@ -62,11 +83,13 @@ local function type2main_to_type1(name, asp) if type(asp) == "number" then asp = asptbl.name end + local dst = def.main[math.min(#def.main, aspidx+1)].main local t = { main = asptbl.main, type2name = asp, type2group = name, + dst = dst, } if aspidx > 1 and aspidx < #asptbl then t.dst = asptbl[aspidx+1].main @@ -116,6 +139,7 @@ end return { register_type2 = register_type2, get_type2_definition = get_type2_definition, + get_type2_dst = get_type2_dst, type2main_to_type1 = type2main_to_type1, type1_to_type2main = type1_to_type2main, equalp = equalp, diff --git a/advtrains_interlocking/signal_main_ui.lua b/advtrains_interlocking/signal_main_ui.lua deleted file mode 100644 index e69de29..0000000 diff --git a/advtrains_interlocking/spec/basic_signalling_spec.lua b/advtrains_interlocking/spec/basic_signalling_spec.lua new file mode 100644 index 0000000..0b79972 --- /dev/null +++ b/advtrains_interlocking/spec/basic_signalling_spec.lua @@ -0,0 +1,87 @@ +--[[ +This file tests a large part of the signaling system, as a lot of tests for the +signaling system tend to overlap for various parts of the system. +]] + +require("mineunit") +mineunit("core") + +_G.advtrains = { + interlocking = { + aspects = fixture("../../signal_aspects"), + }, + ndb = { + get_node = minetest.get_node, + swap_node = minetest.swap_node, + } +} + +fixture("advtrains_helpers") +fixture("../../database") +sourcefile("distant") +sourcefile("signal_api") +sourcefile("signal_aspect_accessors") +fixture("../../demosignals") + +local D = advtrains.distant +local I = advtrains.interlocking + +local stub_aspect_t1 = { + free = {main = -1}, + slow = {main = 6}, + danger = {main = 0, shunt = false}, +} +local stub_pos_t1 = {} +for i = 1, 3 do + stub_pos_t1[i] = {x = 1, y = 0, z = i} +end + +world.layout { + {stub_pos_t1[1], "advtrains_interlocking:ds_danger"}, + {stub_pos_t1[2], "advtrains_interlocking:ds_slow"}, + {stub_pos_t1[3], "advtrains_interlocking:ds_free"}, +} + +describe("API for supposed signal aspects", function() + it("should load and save data properly", function() + local tbl = {_foo = true} + I.load_supposed_aspects(tbl) + assert.same(tbl, I.save_supposed_aspects()) + end) + it("should set and get type 1 signals properly", function () + local pos = stub_pos_t1[2] + local asp = stub_aspect_t1.slow + local newasp = { main = math.random(1,5) } + assert.same(asp, I.signal_get_aspect(pos)) + I.signal_set_aspect(pos, newasp) + assert.same(newasp, I.signal_get_aspect(pos)) + assert.same(asp, I.signal_get_real_aspect(pos)) + I.signal_set_aspect(pos, asp) + end) +end) + +describe("Distant signaling", function() + it("should assign distant signals and set the distant aspect correspondingly", function() + for i = 1, 2 do + D.assign(stub_pos_t1[i], stub_pos_t1[i+1]) + end + assert.same(stub_aspect_t1.danger, I.signal_get_aspect(stub_pos_t1[1])) + assert.same({main = 6, dst = 0}, I.signal_get_aspect(stub_pos_t1[2])) + assert.same({main = -1, dst = 6}, I.signal_get_aspect(stub_pos_t1[3])) + end) + it("should report assignments properly", function() + assert.same({stub_pos_t1[1], "manual"}, {D.get_main(stub_pos_t1[2])}) + assert.same({[advtrains.encode_pos(stub_pos_t1[3])] = "manual"}, D.get_dst(stub_pos_t1[2])) + end) + it("should update distant aspects automatically", function() + I.signal_set_aspect(stub_pos_t1[2], {main = 2, dst = -1}) + assert.same({main = 2, dst = 0}, I.signal_get_aspect(stub_pos_t1[2])) + assert.same({main = -1, dst = 2}, I.signal_get_aspect(stub_pos_t1[3])) + end) + it("should unassign signals when one is removed", function() + world.set_node(stub_pos_t1[2], "air") + assert.same({}, D.get_dst(stub_pos_t1[1])) + assert.same({}, {D.get_main(stub_pos_t1[3])}) + assert.same(stub_aspect_t1.free, I.signal_get_aspect(stub_pos_t1[3])) + end) +end) diff --git a/advtrains_interlocking/spec/signal_api_spec.lua b/advtrains_interlocking/spec/signal_api_spec.lua deleted file mode 100644 index cd7a1d1..0000000 --- a/advtrains_interlocking/spec/signal_api_spec.lua +++ /dev/null @@ -1,49 +0,0 @@ -require("mineunit") - -mineunit("core") - -_G.advtrains = { - interlocking = { - aspects = fixture("../../signal_aspects"), - }, - ndb = { - get_node = minetest.get_node, - } -} - -fixture("advtrains_helpers") -fixture("../../database") -sourcefile("signal_api") - -local stub_aspect_t1 = { main = math.random() } -local stub_pos_t1 = {x = 1, y = 0, z = 1} - -minetest.register_node(":stubsignal_t1", { - advtrains = { - supported_aspects = {}, - get_aspect = function () return stub_aspect_t1 end, - set_aspect = function () end, - }, - groups = { advtrains_signal = 2 }, -}) - -world.layout { - {stub_pos_t1, "stubsignal_t1"}, -} - -describe("API for supposed signal aspects", function() - it("should load and save data properly", function() - local tbl = {_foo = true} - advtrains.interlocking.load_supposed_aspects(tbl) - assert.same(tbl, advtrains.interlocking.save_supposed_aspects()) - end) - it("should set and get type 1 signals properly", function () - local pos = stub_pos_t1 - local asp = stub_aspect_t1 - local newasp = { dst = math.random() } - assert.same(asp, advtrains.interlocking.signal_get_aspect(pos)) - advtrains.interlocking.signal_set_aspect(pos, newasp) - assert.same(newasp, advtrains.interlocking.signal_get_aspect(pos)) - assert.same(asp, advtrains.interlocking.signal_get_real_aspect(pos)) - end) -end) diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index b3b8221..9f88296 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -14,6 +14,7 @@ local lntrans = { "A", "B" } local function sigd_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", @@ -608,7 +609,7 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle if not tcbs.signal_name then tcbs.signal_name = "Signal at "..minetest.pos_to_string(sigd.p) end if not tcbs.routes then tcbs.routes = {} end - local form = "size[7,9.75]label[0.5,0.5;Signal at "..minetest.pos_to_string(sigd.p).."]" + local form = "size[7,10.25]label[0.5,0.5;Signal at "..minetest.pos_to_string(sigd.p).."]" form = form .. advtrains.interlocking.make_signal_formspec_tabheader(pname, tcbs.signal, 7, 1) form = form.."field[0.8,1.5;5.2,1;name;Signal name;"..minetest.formspec_escape(tcbs.signal_name).."]" form = form.."button[5.5,1.2;1,1;setname;Set]" @@ -670,6 +671,7 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle form = form.."button[0.5,8;2.5,1;newroute;New Route]" form = form.."button[ 3,8;2.5,1;unassign;Unassign Signal]" 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;dst;Distant signalling;%s]", not tcbs.nodst) end elseif sigd_equal(tcbs.route_origin, sigd) then -- something has gone wrong: tcbs.routeset should have been set... @@ -796,6 +798,10 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if fields.ars then tcbs.ars_disabled = not minetest.is_yes(fields.ars) end + + if fields.dst then + tcbs.nodst = not minetest.is_yes(fields.dst) + end if fields.auto then tcbs.route_auto = true -- cgit v1.2.3 From 875968f078ef89b13afae6d02a2dabe5ea7897cb Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Sun, 3 Jul 2022 12:45:27 +0200 Subject: Unassign distant signals when canceling route --- advtrains_interlocking/routesetting.lua | 11 +++++++++-- advtrains_interlocking/signal_api.lua | 7 ++++++- advtrains_interlocking/signal_aspect_accessors.lua | 2 +- advtrains_interlocking/signal_aspects.lua | 4 ++++ advtrains_signals_japan/init.lua | 2 +- 5 files changed, 21 insertions(+), 5 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua index f1b4455..4ce6fd3 100644 --- a/advtrains_interlocking/routesetting.lua +++ b/advtrains_interlocking/routesetting.lua @@ -113,7 +113,7 @@ function ilrs.set_route(signal, route, try) } if c_tcbs.signal then c_tcbs.route_committed = true - c_tcbs.aspect = route.aspect or advtrains.interlocking.GENERIC_FREE + c_tcbs.aspect = route.aspect or advtrains.interlocking.FULL_FREE c_tcbs.route_origin = signal advtrains.interlocking.update_signal_aspect(c_tcbs) signals[#signals+1] = c_tcbs.signal @@ -138,7 +138,7 @@ function ilrs.set_route(signal, route, try) if lastsig then local pos = signals[i] local _, assigned_by = advtrains.distant.get_main(pos) - if assigned_by ~= "manual" then + if not assigned_by or assigned_by == "routesetting" then advtrains.distant.assign(lastsig, signals[i], "routesetting") end end @@ -251,6 +251,13 @@ function ilrs.cancel_route_from(sigd) c_tcbs.route_origin = nil advtrains.interlocking.update_signal_aspect(c_tcbs) + if c_tcbs.signal then + local pos = c_tcbs.signal + local _, assigned_by = advtrains.distant.get_main(pos) + if assigned_by == "routesetting" then + advtrains.distant.unassign_dst(pos) + end + end c_ts_id = c_tcbs.ts_id if not c_tcbs then diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 1fd4e34..eec70f9 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -176,6 +176,11 @@ advtrains.interlocking.GENERIC_FREE = { shunt = false, dst = false, } +advtrains.interlocking.FULL_FREE = { + main = -1, + shunt = false, + proceed_as_main = true, +} local function convert_aspect_if_necessary(asp) if type(asp.main) == "table" then @@ -212,7 +217,7 @@ end function advtrains.interlocking.signal_after_dig(pos) -- clear influence point - advtrains.interlocking.db.clear_ip_by_signalpos(pos) + advtrains.interlocking.signal_clear_aspect(pos) advtrains.distant.unassign_all(pos, true) end diff --git a/advtrains_interlocking/signal_aspect_accessors.lua b/advtrains_interlocking/signal_aspect_accessors.lua index 02a03ea..bdbb803 100644 --- a/advtrains_interlocking/signal_aspect_accessors.lua +++ b/advtrains_interlocking/signal_aspect_accessors.lua @@ -103,9 +103,9 @@ local function set_aspect(pos, asp) local newasp, aspval = adjust_aspect(pos, asp) set_supposed_aspect(pos, newasp) ndef.advtrains.set_aspect(pos, node, aspval) + I.signal_on_aspect_changed(pos) local aspect_changed = A.not_equalp(oldasp, newasp) if aspect_changed then - I.signal_on_aspect_changed(pos) D.update_main(pos) end end diff --git a/advtrains_interlocking/signal_aspects.lua b/advtrains_interlocking/signal_aspects.lua index 2866ae1..5c4948b 100644 --- a/advtrains_interlocking/signal_aspects.lua +++ b/advtrains_interlocking/signal_aspects.lua @@ -32,6 +32,8 @@ local function register_type2(def) t.label = label t.main = asp.main + t.shunt = asp.shunt + t.proceed_as_main = asp.proceed_as_main mainasps[idx] = t mainasps[name] = idx end @@ -87,6 +89,8 @@ local function type2main_to_type1(name, asp) local t = { main = asptbl.main, + shunt = asptbl.shunt, + proceed_as_main = asptbl.proceed_as_main, type2name = asp, type2group = name, dst = dst, diff --git a/advtrains_signals_japan/init.lua b/advtrains_signals_japan/init.lua index 9ccf66b..2062a21 100644 --- a/advtrains_signals_japan/init.lua +++ b/advtrains_signals_japan/init.lua @@ -321,7 +321,7 @@ local function process_signal(name, sigdata, isrpt) tt[#tt+1] = string.format("0,%d=(advtrains_hud_bg.png\\^[colorize\\:%s)", lightcount-1, color) end tx[aspname] = table.concat(tt, ":") - type2def.main[idx] = {name = asp.name, label = S(aspnames[asp.name]), main = asp.main or false} + type2def.main[idx] = {name = asp.name, label = S(aspnames[asp.name]), main = asp.main, proceed_as_main = true} end local invimg = { string.format("[combine:%dx%d", lightcount*4+1, lightcount*4+1), -- cgit v1.2.3 From 4a3d442601a800e28a274026392461bd1a7cb127 Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Sun, 3 Jul 2022 15:34:42 +0200 Subject: Reduce number of set_aspect calls --- advtrains_interlocking/distant.lua | 6 ++++-- advtrains_interlocking/routesetting.lua | 13 +++++++------ advtrains_interlocking/signal_api.lua | 4 ++-- advtrains_interlocking/signal_aspect_accessors.lua | 4 ++-- 4 files changed, 15 insertions(+), 12 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/distant.lua b/advtrains_interlocking/distant.lua index ffa9e08..f62ca36 100644 --- a/advtrains_interlocking/distant.lua +++ b/advtrains_interlocking/distant.lua @@ -62,7 +62,7 @@ local function unassign_all(pos, force) unassign_dst(pos, force) end -local function assign(main, dst, by) +local function assign(main, dst, by, skip_update) local pts_main = pts(main) local pts_dst = pts(dst) local t = db_distant[pts_main] @@ -76,7 +76,9 @@ local function assign(main, dst, by) unassign_dst(dst, true) t[pts_dst] = by db_distant_of[pts_dst] = {pts_main, by} - update_dst(dst) + if not skip_update then + update_dst(dst) + end end local function pre_occupy(dst, by) diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua index 4ce6fd3..857a681 100644 --- a/advtrains_interlocking/routesetting.lua +++ b/advtrains_interlocking/routesetting.lua @@ -115,8 +115,7 @@ function ilrs.set_route(signal, route, try) c_tcbs.route_committed = true c_tcbs.aspect = route.aspect or advtrains.interlocking.FULL_FREE c_tcbs.route_origin = signal - advtrains.interlocking.update_signal_aspect(c_tcbs) - signals[#signals+1] = c_tcbs.signal + signals[#signals+1] = c_tcbs end end -- advance @@ -136,11 +135,13 @@ function ilrs.set_route(signal, route, try) end for i = #signals, 1, -1 do if lastsig then - local pos = signals[i] + local tcbs = signals[i] + local pos = tcbs.signal local _, assigned_by = advtrains.distant.get_main(pos) if not assigned_by or assigned_by == "routesetting" then - advtrains.distant.assign(lastsig, signals[i], "routesetting") + advtrains.distant.assign(lastsig, pos, "routesetting", true) end + advtrains.interlocking.update_signal_aspect(tcbs, i ~= 1) end end @@ -250,14 +251,14 @@ function ilrs.cancel_route_from(sigd) c_tcbs.route_auto = nil c_tcbs.route_origin = nil - advtrains.interlocking.update_signal_aspect(c_tcbs) if c_tcbs.signal then local pos = c_tcbs.signal local _, assigned_by = advtrains.distant.get_main(pos) if assigned_by == "routesetting" then - advtrains.distant.unassign_dst(pos) + advtrains.distant.unassign_dst(pos, true) end end + advtrains.interlocking.update_signal_aspect(c_tcbs) c_ts_id = c_tcbs.ts_id if not c_tcbs then diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index eec70f9..1c6ed27 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -204,10 +204,10 @@ local function convert_aspect_if_necessary(asp) end advtrains.interlocking.signal_convert_aspect_if_necessary = convert_aspect_if_necessary -function advtrains.interlocking.update_signal_aspect(tcbs) +function advtrains.interlocking.update_signal_aspect(tcbs, skipdst) if tcbs.signal then local asp = tcbs.aspect or DANGER - advtrains.interlocking.signal_set_aspect(tcbs.signal, asp) + advtrains.interlocking.signal_set_aspect(tcbs.signal, asp, skipdst) end end diff --git a/advtrains_interlocking/signal_aspect_accessors.lua b/advtrains_interlocking/signal_aspect_accessors.lua index bdbb803..e23aa13 100644 --- a/advtrains_interlocking/signal_aspect_accessors.lua +++ b/advtrains_interlocking/signal_aspect_accessors.lua @@ -95,7 +95,7 @@ get_aspect = function(pos) return asp end -local function set_aspect(pos, asp) +local function set_aspect(pos, asp, skipdst) local node = N.get_node(pos) local ndef = minetest.registered_nodes[node.name] if ndef and ndef.advtrains and ndef.advtrains.set_aspect then @@ -105,7 +105,7 @@ local function set_aspect(pos, asp) ndef.advtrains.set_aspect(pos, node, aspval) I.signal_on_aspect_changed(pos) local aspect_changed = A.not_equalp(oldasp, newasp) - if aspect_changed then + if (not skipdst) and aspect_changed then D.update_main(pos) end end -- cgit v1.2.3 From a4abbf8824f893558861bff2893181ae02ed284d Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Sat, 13 Aug 2022 16:16:17 +0200 Subject: Add hotfix for path invalidation --- advtrains_interlocking/routesetting.lua | 3 ++- advtrains_interlocking/signal_api.lua | 6 ++++-- advtrains_interlocking/signal_aspect_accessors.lua | 5 +++++ 3 files changed, 11 insertions(+), 3 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua index e792d28..9973569 100644 --- a/advtrains_interlocking/routesetting.lua +++ b/advtrains_interlocking/routesetting.lua @@ -341,7 +341,8 @@ function ilrs.update_route(sigd, tcbs, newrte, cancel) end else --atdebug("Committed Route:",tcbs.routeset) - has_changed_aspect = true + -- set_route now sets the signal aspects + --has_changed_aspect = true end end if has_changed_aspect then diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 1c6ed27..e615692 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -227,8 +227,10 @@ function advtrains.interlocking.signal_on_aspect_changed(pos) local ipts, iconn = advtrains.interlocking.db.get_ip_by_signalpos(pos) if not ipts then return end local ipos = minetest.string_to_pos(ipts) - - advtrains.invalidate_all_paths_ahead(ipos) + + -- FIXME: invalidate_all_paths_ahead does not appear to always work as expected + --advtrains.invalidate_all_paths_ahead(ipos) + minetest.after(0, advtrains.invalidate_all_paths, ipos) end function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, pointed_thing) diff --git a/advtrains_interlocking/signal_aspect_accessors.lua b/advtrains_interlocking/signal_aspect_accessors.lua index e23aa13..a1cbd4e 100644 --- a/advtrains_interlocking/signal_aspect_accessors.lua +++ b/advtrains_interlocking/signal_aspect_accessors.lua @@ -108,6 +108,11 @@ local function set_aspect(pos, asp, skipdst) if (not skipdst) and aspect_changed then D.update_main(pos) end + --[[ + local dbgmsg = string.format("[%s]set_aspect(%s,%s,%s)", os.clock(), minetest.pos_to_string(pos), minetest.serialize(asp), tostring(skipdst)) + dbgmsg = debug.traceback(dbgmsg, 2) + minetest.chat_send_all(dbgmsg) + --]] end end -- cgit v1.2.3 From 7c9fd9179dfcec06107ad1a66346418827bdc4eb Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Mon, 31 Oct 2022 10:57:37 +0100 Subject: Add API documentation --- advtrains_interlocking/README.md | 96 ++++++++++++ advtrains_interlocking/distant.lua | 47 ++++++ advtrains_interlocking/init.lua | 4 +- advtrains_interlocking/signal_api.lua | 165 --------------------- advtrains_interlocking/signal_aspect_accessors.lua | 52 +++++++ advtrains_interlocking/signal_aspects.lua | 49 ++++-- 6 files changed, 237 insertions(+), 176 deletions(-) create mode 100644 advtrains_interlocking/README.md (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/README.md b/advtrains_interlocking/README.md new file mode 100644 index 0000000..636ad67 --- /dev/null +++ b/advtrains_interlocking/README.md @@ -0,0 +1,96 @@ +# Interlocking for Advtrains + +The `advtrains_interlocking` mod provides various interlocking and signaling features for Advtrains. + +## Signal types +There are two types of signals in Advtrains: + +* Type 1 (speed signals): These signals only give speed information. +* Type 2 (route signals): These signals mainly provide route information, but sometimes also provide speed information. + +## Signal aspect tables + +Signal aspects are represented using tables with the following (optional) fields: + +* `main`: The main signal aspect. It provides information on the permitted speed after passing the signal. +* `dst`: The distant signal aspect. It provides information on the permitted speed after passing the next signal. +* `shunt`: Whether the train may proceed in shunt mode and, if the main aspect is danger, proceed in shunt mode. +* `proceed_as_main`: Whether the train should exit shunt mode when proceeding. +* `type2group`: The type 2 group of the signal. +* `type2name`: The type 2 signal aspect name. + +The `main` and `dst` fields may be: + +* An positive number indicating the permitted speed, +* The number 0, indicating that the train should expect to stop at the current signal (or, for the `dst` field, the next signal), +* The number -1, indicating that the train can proceed (or, for the `dst` field, expect to proceed) at maximum speed, or +* The constant `false` or `nil`, indicating no change to the speed restriction. + +### Node definitions + +Signals should belong the following node groups: + +* `advtrains_signal`: `1` for static signals, `2` for signals with variable aspects. +* `save_in_at_nodedb`: This should be set to `1` to make sure that Advtrains always has access to the signal. +* `not_blocking_trains`: Setting this to `1` prevents trains from colliding with the signal. Setting this is not necessary, but recommended. + +The node definition should contain an `advtrains` field. + +The `advtrains` field of the node definition should contain a `supported_aspects` table for signals with variable aspects. + +For type 1 signals, the `supported_aspects` table should contain the following fields: + +* `main`: A list of values supported for the main aspect. +* `dst`: A list of values supported for the distant aspect. +* `shunt`: The value for the `shunt` field of the signal aspect or `nil` if the value is variable. +* `proceed_as_main`: The value for the `proceed_as_main` field of the signal aspect. + +For type 2 signals, the `supported_aspects` table should contain the following fields: + +* `type`: The numeric constant `2`. +* `group`: The type 2 signal group. +* `dst_shift`: The phase shift for distant/repeater signals. This field should not be set for main signals. + +The `advtrains` field of the node definition should contain a `get_aspect` function. This function is given the position of the signal and the node at the position. It should return the signal aspect table or, in the case of type 2 signals, the name of the signal aspect. + +For signals with variable aspects, a corresponding `set_aspect` function should also be defined. This function is given the position of the signal, the node at the position, and the new aspect (or, in the case of type 2 signals, the name of the new signal aspect). For type 1 signals, the new aspect is not guranteed to be supported by the signal itself. + +Signals should also have the following callbacks set: + +* `on_rightclick` should be set to `advtrains.interlocking.signal_rc_handler` +* `can_dig` should be set to `advtrains.interlocking.signal_can_dig` +* `after_dig_node` should be set to `advtrains.interlocking.signal_after_dig` + +Alternatively, custom callbacks should call the respective functions. + +## Type 2 signal groups + +Type 2 signals belong to signal gruops, which are registered using `advtrains.interlocking.aspects.register_type2`. + +Signal group definitions include the following fields: + +* `name`: The internal name of the signal group. It is recommended to use the mod name as a prefix to avoid name collisions. +* `label`: The description of the signal group. +* `main`: A list of signal aspects, from the least restrictive (i.e. proceed) to the most restrictive (i.e. danger). + +Each aspect in the signal group definition table should contain the following fields: + +* `name`: The internal name of the signal aspect. +* `label`: The description of the signal aspect. +* `main`, `shunt`, `proceed_as_main`: The fields corresponding to the ones in signal aspect tables. + +Type 2 signal aspects are then referred to with the aspect names within the group. + +## Notes + +It is allowed to provide other methods of setting the signal aspect. However: + +* These changes are ignored by the routesetting system. +* Please call `advtrains.interlocking.signal_readjust_aspect` after the signal aspect has changed. + +## Examples +An example of type 1 signals can be found in `advtrains_signals_ks`, which provides a subset of German signals. + +An example of type 2 signals can be found in `advtrains_signals_japan`, which provides a subset of Japanese signals. + +The mods mentioned above are also used for demonstation purposes and can also be used for testing. diff --git a/advtrains_interlocking/distant.lua b/advtrains_interlocking/distant.lua index f62ca36..22f1c9d 100644 --- a/advtrains_interlocking/distant.lua +++ b/advtrains_interlocking/distant.lua @@ -1,3 +1,8 @@ +--- Distant signaling. +-- This module implements a database backend for distant signal assignments. +-- The actual modifications to signal aspects are still done by signal aspect accessors. +-- @module advtrains.interlocking.distant + local db_distant = {} local db_distant_of = {} @@ -5,6 +10,9 @@ local A = advtrains.interlocking.aspects local pts = advtrains.encode_pos local stp = advtrains.decode_pos +--- Replace the distant signal assignment database. +-- @function load +-- @param db The new database to load. local function db_load(x) if type(x) ~= "table" then return @@ -13,6 +21,9 @@ local function db_load(x) db_distant_of = x.distant_of end +--- Retrieve the current distant signal assignment database. +-- @function save +-- @return The current database. local function db_save() return { distant = db_distant, @@ -22,6 +33,10 @@ end local update_signal, update_main, update_dst +--- Unassign a distant signal. +-- @function unassign_dst +-- @param dst The position of the distant signal. +-- @param[opt=false] force Whether to skip callbacks. local function unassign_dst(dst, force) local pts_dst = pts(dst) local main = db_distant_of[pts_dst] @@ -38,6 +53,10 @@ local function unassign_dst(dst, force) end end +--- Unassign a main signal. +-- @function unassign_main +-- @param main The position of the main signal. +-- @param[opt=false] force Whether to skip callbacks. local function unassign_main(main, force) local pts_main = pts(main) local t = db_distant[pts_main] @@ -57,11 +76,21 @@ local function unassign_main(main, force) db_distant[pts_main] = nil end +--- Remove all (main and distant) signal assignments from a signal. +-- @function unassign_all +-- @param pos The position of the signal. +-- @param[opt=false] force Whether to skip callbacks. local function unassign_all(pos, force) unassign_main(pos) unassign_dst(pos, force) end +--- Assign a distant signal to a main signal. +-- @function assign +-- @param main The position of the main signal. +-- @param dst The position of the distant signal. +-- @param[opt="manual"] by The method of assignment. +-- @param[opt=false] skip_update Whether to skip callbacks. local function assign(main, dst, by, skip_update) local pts_main = pts(main) local pts_dst = pts(dst) @@ -87,11 +116,20 @@ local function pre_occupy(dst, by) db_distant_of[pts_dst] = {nil, by} end +--- Get the distant signals assigned to a main signal. +-- @function get_distant +-- @param main The position of the main signal. +-- @treturn {[pos]=by,...} A table of distant signals, with the positions encoded using `advtrains.encode_pos`. local function get_distant(main) local pts_main = pts(main) return db_distant[pts_main] or {} end +--- Get the main signal assigned the a distant signal. +-- @function get_main +-- @param dst The position of the distant signal. +-- @return The position of the main signal. +-- @return The method of assignment. local function get_main(dst) local pts_dst = pts(dst) local main = db_distant_of[pts_dst] @@ -105,6 +143,9 @@ local function get_main(dst) end end +--- Update all distant signals assigned to a main signal. +-- @function update_main +-- @param main The position of the main signal. update_main = function(main) local pts_main = pts(main) local t = get_distant(main) @@ -114,10 +155,16 @@ update_main = function(main) end end +--- Update the aspect of a distant signal. +-- @function update_dst +-- @param dst The position of the distant signal. update_dst = function(dst) advtrains.interlocking.signal_readjust_aspect(dst) end +--- Update the aspect of a combined (main and distant) signal and all distant signals assigned to it. +-- @function update_signal +-- @param pos The position of the signal. update_signal = function(pos) update_main(pos) update_dst(pos) diff --git a/advtrains_interlocking/init.lua b/advtrains_interlocking/init.lua index 908d998..1a8ef07 100644 --- a/advtrains_interlocking/init.lua +++ b/advtrains_interlocking/init.lua @@ -1,5 +1,5 @@ --- Advtrains interlocking system --- See database.lua for a detailed explanation +--- Advtrains interlocking system. +-- @module advtrains.interlocking advtrains.interlocking = {} diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index e615692..54202f0 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -1,170 +1,5 @@ -- Signal API implementation - ---[[ -Signal aspect table: -Note: All speeds are measured in m/s, aka the number of + signs in the HUD. -asp = { - main = , - -- Main signal aspect, tells state and permitted speed of next section - -- 0 = section is blocked - -- >0 = section is free, speed limit is this value - -- -1 = section is free, maximum speed permitted - -- false/nil = Signal doesn't provide main signal information, retain current speed limit. - shunt = , - -- Whether train may proceed as shunt move, on sight - -- main aspect takes precedence over this - -- When main==0, train switches to shunt move and is restricted to speed 6 - proceed_as_main = , - -- If an approaching train is a shunt move and 'shunt' is false, - -- the train may proceed as a train move under the "main" aspect - -- if the main aspect permits it (i.e. main!=0) - -- If this is not set, shunt moves are NOT allowed to switch to - -- a train move, and must stop even if "main" would permit passing. - -- This is intended to be used for "Halt for shunt moves" signs. - - dst = , - -- Distant signal aspect, tells state and permitted speed of the section after next section - -- The character of these information is purely informational - -- At this time, this field is not actively used - -- 0 = section is blocked - -- >0 = section is free, speed limit is this value - -- -1 = section is free, maximum speed permitted - -- false/nil = Signal doesn't provide distant signal information. - - -- the character of call_on and dead_end is purely informative - call_on = , -- Call-on route, expect train in track ahead (not implemented yet) - dead_end = , -- Route ends on a dead end (e.g. bumper) (not implemented yet) - - w_speed = , - -- "Warning speed restriction". Supposed for short-term speed - -- restrictions which always override any other restrictions - -- imposed by "speed" fields, until lifted by a value of -1 - -- (Example: german Langsamfahrstellen-Signale) - } -} - -== How signals actually work in here == -Each signal (in the advtrains universe) is some node that has at least the -following things: -- An "influence point" that is set somewhere on a rail -- An aspect which trains that pass the "influence point" have to obey - -There can be static and dynamic signals. Static signals are, roughly -spoken, signs, while dynamic signals are "real" signals which can display -different things. - -The node definition of a signal node should contain those fields: -groups = { - advtrains_signal = 2, - save_in_at_nodedb = 1, -} -advtrains = { - set_aspect = function(pos, node, asp) - -- This function gets called whenever the signal should display - -- a new or changed signal aspect. It is not required that - -- the signal actually displays the exact same aspect, since - -- some signals can not do this by design. However, it must - -- display an aspect that is at least as restrictive as the passed - -- aspect as far as it is capable of doing so. - -- Examples: - -- - pure shunt signals can not display a "main" aspect - -- and have no effect on train moves, so they will only ever - -- honor the shunt.free field for their aspect. - -- - the german Hl system can only signal speeds of 40, 60 - -- and 100 km/h, a speed of 80km/h should then be signalled - -- as 60 km/h instead. - -- In turn, it is not guaranteed that the aspect will fulfill the - -- criteria put down in supported_aspects. - -- If set_aspect is present, supported_aspects should also be declared. - - -- The aspect passed in here can always be queried using the - -- advtrains.interlocking.signal_get_supposed_aspect(pos) function. - -- It is always DANGER when the signal is not used as route signal. - - -- For static signals, this function should be completely omitted - -- If this function is omitted, it won't be possible to use - -- route setting on this signal. - end, - supported_aspects = { - -- A table which tells which different types of aspects this signal - -- is able to display. It is used to construct the "aspect editing" - -- formspec for route programming (and others) It should always be - -- present alongside with set_aspect. If this is not specified but - -- set_aspect is, the user will be allowed to select any aspect. - -- Any of the fields marked with support 3 types of values: - nil: if this signal can switch between free/blocked - false: always shows "blocked", unchangable - true: always shows "free", unchangable - -- Any of the "speed" fields should contain a list of possible values - -- to be set as restriction. If omitted, the value of the described - -- field is always assumed to be false (no information) - -- A speed of 0 means that the signal can show a "blocked" aspect - -- (which is probably the case for most signals) - -- If the signal can signal "no information" on one of the fields - -- (thus false is an acceptable value), include false in the list - -- If your signal can only display a single speed (may it be -1), - -- always enclose that single value into a list. (such as {-1}) - main = {, ..., } or nil, - dst = {, ..., } or nil, - shunt = , - - call_on = , - dead_end = , - w_speed = {, ..., } or nil, - - }, - Example for supported_aspects: - supported_aspects = { - main = {0, 6, -1}, -- can show either "Section blocked", "Proceed at speed 6" or "Proceed at maximum speed" - dst = {0, false}, -- can show only if next signal shows "blocked", no other information. - shunt = false, -- shunting by this signal is never allowed. - - call_on = false, - dead_end = false, - w_speed = nil, - -- none of the information can be shown by the signal - - }, - - get_aspect = function(pos, node) - -- This function gets called by the train safety system. It - should return the aspect that this signal actually displays, - not preferably the input of set_aspect. - -- For regular, full-featured light signals, they will probably - honor all entries in the original aspect, however, e.g. - simple shunt signals always return main=false regardless of - the set_aspect input because they can not signal "Halt" to - train moves. - -- advtrains.interlocking.DANGER contains a default "all-danger" aspect. - -- If your signal does not cover certain sub-tables of the aspect, - the following reasonable defaults are automatically assumed: - main = false (unchanged) - dst = false (unchanged) - shunt = false (shunting not allowed) - info = {} (no further information) - end, -} -on_rightclick = advtrains.interlocking.signal_rc_handler -can_dig = advtrains.interlocking.signal_can_dig -after_dig_node = advtrains.interlocking.signal_after_dig - -(If you need to specify custom can_dig or after_dig_node callbacks, -please call those functions anyway!) - -Important note: If your signal should support external ways to set its -aspect (e.g. via mesecons), there are some things that need to be considered: -- advtrains.interlocking.signal_get_supposed_aspect(pos) won't respect this -- Whenever you change the signal aspect, and that aspect change -did not happen through a call to -advtrains.interlocking.signal_set_aspect(pos, asp), you are -*required* to call this function: -advtrains.interlocking.signal_on_aspect_changed(pos) -in order to notify trains about the aspect change. -This function will query get_aspect to retrieve the new aspect. - -]]-- - local DANGER = { main = 0, shunt = false, diff --git a/advtrains_interlocking/signal_aspect_accessors.lua b/advtrains_interlocking/signal_aspect_accessors.lua index 060f923..e55814e 100644 --- a/advtrains_interlocking/signal_aspect_accessors.lua +++ b/advtrains_interlocking/signal_aspect_accessors.lua @@ -1,3 +1,6 @@ +--- Signal aspect accessors +-- @module advtrains.interlocking + local A = advtrains.interlocking.aspects local D = advtrains.distant local I = advtrains.interlocking @@ -29,6 +32,9 @@ local get_aspect local supposed_aspects = {} +--- Replace the signal aspect cache. +-- @function load_supposed_aspects +-- @param db The new database. function I.load_supposed_aspects(tbl) if tbl then supposed_aspects = tbl @@ -38,23 +44,42 @@ function I.load_supposed_aspects(tbl) end end +--- Retrieve the signal aspect cache. +-- @function save_supposed_aspects +-- @return The current database in use. function I.save_supposed_aspects() return supposed_aspects end +--- Read the aspect of a signal strictly from cache. +-- @param pos The position of the signal. +-- @return[1] The aspect of the signal (if present in cache). +-- @return[2] The nil constant (otherwise). local function get_supposed_aspect(pos) return supposed_aspects[pts(pos)] end +--- Update the signal aspect information in cache. +-- @param pos The position of the signal. +-- @param asp The new signal aspect local function set_supposed_aspect(pos, asp) supposed_aspects[pts(pos)] = asp end +--- Get the definition of a node. +-- @param pos The position of the node. +-- @return[1] The definition of the node (if present). +-- @return[2] An empty table (otherwise). local function get_ndef(pos) local node = N.get_node(pos) return minetest.registered_nodes[node.name] or {} end +--- Get the aspects supported by a signal. +-- @function signal_get_supported_aspects +-- @param pos The position of the signal. +-- @return[1] The table of supported aspects (if present). +-- @return[2] The nil constant (otherwise). local function get_supported_aspects(pos) local ndef = get_ndef(pos) if ndef.advtrains and ndef.advtrains.supported_aspects then @@ -63,6 +88,11 @@ local function get_supported_aspects(pos) return nil end +--- Adjust a new signal aspect to fit a signal. +-- @param pos The position of the signal. +-- @param asp The new signal aspect. +-- @return The adjusted signal aspect. +-- @return The information to pass to the `advtrains.set_aspect` field in the node definitions. local function adjust_aspect(pos, asp) asp = table.copy(I.signal_convert_aspect_if_necessary(asp)) setmetatable(asp, signal_aspect_metatable) @@ -103,6 +133,12 @@ local function adjust_aspect(pos, asp) return asp, asp end +--- Get the aspect of a signal without accessing the cache. +-- For most cases, `get_aspect` should be used instead. +-- @function signal_get_real_aspect +-- @param pos The position of the signal. +-- @return[1] The signal aspect adjusted using `adjust_aspect` (if present). +-- @return[2] The nil constant (otherwise). local function get_real_aspect(pos) local ndef = get_ndef(pos) if ndef.advtrains and ndef.advtrains.get_aspect then @@ -116,6 +152,11 @@ local function get_real_aspect(pos) return nil end +--- Get the aspect of a signal. +-- @function signal_get_aspect +-- @param pos The position of the signal. +-- @return[1] The aspect of the signal (if present). +-- @return[2] The nil constant (otherwise). get_aspect = function(pos) local asp = get_supposed_aspect(pos) if not asp then @@ -125,6 +166,11 @@ get_aspect = function(pos) return asp end +--- Set the aspect of a signal. +-- @function signal_set_aspect +-- @param pos The position of the signal. +-- @param asp The new signal aspect. +-- @param[opt=false] skipdst Whether to skip updating distant signals. local function set_aspect(pos, asp, skipdst) local node = N.get_node(pos) local ndef = minetest.registered_nodes[node.name] @@ -141,10 +187,16 @@ local function set_aspect(pos, asp, skipdst) end end +--- Remove a signal from cache. +-- @function signal_clear_aspect +-- @param pos The position of the signal. local function clear_aspect(pos) set_supposed_aspect(pos, nil) end +--- Readjust the aspect of a signal. +-- @function signal_readjust_aspect +-- @param pos The position of the signal. local function readjust_aspect(pos) set_aspect(pos, get_aspect(pos)) end diff --git a/advtrains_interlocking/signal_aspects.lua b/advtrains_interlocking/signal_aspects.lua index c381fd2..37af7aa 100644 --- a/advtrains_interlocking/signal_aspects.lua +++ b/advtrains_interlocking/signal_aspects.lua @@ -1,5 +1,11 @@ +--- Signal aspect handling. +-- @module advtrains.interlocking.aspects + local type2defs = {} +--- Register a type 2 signal group. +-- @function register_type2 +-- @param def The definition table. local function register_type2(def) local t = {type = 2} local name = def.name @@ -42,19 +48,21 @@ local function register_type2(def) type2defs[name] = t end +--- Get the definition of a type 2 signal group. +-- @function get_type2_definition +-- @param name The name of the signal group. +-- @return[1] The definition for the signal group (if present). +-- @return[2] The nil constant (otherwise). local function get_type2_definition(name) return type2defs[name] end -local function get_type2_danger(group) - local def = type2defs[group] - if not def then - return nil - end - local main = def.main - return main[#main] -end - +--- Get the name of the distant aspect before the current aspect. +-- @function get_type2_dst +-- @param group The name of the group. +-- @param name The name of the current aspect. +-- @return[1] The name of the distant aspect (if present). +-- @return[2] The nil constant (otherwise). local function get_type2_dst(group, name) local def = type2defs[group] if not def then @@ -67,6 +75,12 @@ local function get_type2_dst(group, name) return def.main[math.max(1, aspidx-1)].name end +--- Convert a type 2 signal aspect to a type 1 signal aspect. +-- @function type2_to_type1 +-- @param suppasp The table of supported aspects for the signal. +-- @param asp The name of the signal aspect. +-- @return[1] The type 1 signal aspect table (if present). +-- @return[2] The nil constant (otherwise). local function type2_to_type1(suppasp, asp) local name = suppasp.group local shift = suppasp.dst_shift @@ -111,6 +125,13 @@ local function type2_to_type1(suppasp, asp) return t end +--- Convert a type 1 signal aspect table to a type 2 signal aspect. +-- @function type1_to_type2main +-- @param asp The type 1 signal aspect table +-- @param group The signal aspect group +-- @param[opt=0] shift The shift for the signal aspect. +-- @return[1] The name of the signal aspect (if present). +-- @return[2] The nil constant (otherwise). local function type1_to_type2main(asp, group, shift) local def = type2defs[group] if not def then @@ -130,6 +151,11 @@ local function type1_to_type2main(asp, group, shift) return t_main[math.max(1, idx-(shift or 0))].name end +--- Compare two signal aspect tables. +-- @function equalp +-- @param asp1 The first signal aspect table. +-- @param asp2 The second signal aspect table. +-- @return Whether the two signal aspect tables give the same (type 1 aspect) information. local function equalp(asp1, asp2) if asp1 == asp2 then -- same reference return true @@ -146,6 +172,11 @@ local function equalp(asp1, asp2) return true end +--- Compare two signal aspect tables. +-- @function not_equalp +-- @param asp1 The first signal aspect table. +-- @param asp2 The second signal aspect table. +-- @return The negation of `equalp``(asp1, asp2)`. local function not_equalp(asp1, asp2) return not equalp(asp1, asp2) end -- cgit v1.2.3 From 67efae9c9ad6f93aaa418d512c4ae1817eed6d24 Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Sun, 18 Dec 2022 11:01:26 +0100 Subject: Adjust signal aspect formspecs to be of similar size --- advtrains/formspec.lua | 10 +++++ advtrains_interlocking/distant_ui.lua | 8 ++-- advtrains_interlocking/signal_api.lua | 14 ++++--- advtrains_interlocking/signal_aspect_ui.lua | 57 +++++++++++++++-------------- 4 files changed, 53 insertions(+), 36 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains/formspec.lua b/advtrains/formspec.lua index 58968da..743d3f3 100644 --- a/advtrains/formspec.lua +++ b/advtrains/formspec.lua @@ -17,6 +17,14 @@ local function S_button(x, y, w, h, id, ...) return f_button(x, y, w, h, id, attrans(...)) end +local function f_checkbox(x, y, name, selected, label) + return sformat("checkbox[%f,%f;%s;%s;%s]", x, y, name, label, selected and "true" or "false") +end + +local function S_checkbox(x, y, name, selected, ...) + return f_checkbox(x, y, name, selected, attrans(...)) +end + local function f_button_exit(x, y, w, h, id, text) return sformat("button_exit[%f,%f;%f,%f;%s;%s]", x, y, w, h, id, text) end @@ -88,6 +96,8 @@ end return { button = f_button, S_button = S_button, + checkbox = f_checkbox, + S_checkbox = S_checkbox, button_exit = f_button_exit, S_button_exit = S_button_exit, dropdown = f_dropdown, diff --git a/advtrains_interlocking/distant_ui.lua b/advtrains_interlocking/distant_ui.lua index e1e14b7..a7ff406 100644 --- a/advtrains_interlocking/distant_ui.lua +++ b/advtrains_interlocking/distant_ui.lua @@ -3,7 +3,7 @@ local D = advtrains.distant local I = advtrains.interlocking function advtrains.interlocking.show_distant_signal_form(pos, pname) - local form = {"size[7,7]"} + local form = {"size[7,6.5]"} form[#form+1] = advtrains.interlocking.make_signal_formspec_tabheader(pname, pos, 7, 3) local main, set_by = D.get_main(pos) if main then @@ -31,9 +31,9 @@ function advtrains.interlocking.show_distant_signal_form(pos, pname) dstlist[#dstlist+1] = minetest.pos_to_string(advtrains.decode_pos(pos)) end form[#form+1] = F.S_label(0.5, 2.5, "This signal has the following distant signals:") - form[#form+1] = F.textlist(0.5, 3, 4.5, 3.5, "dstlist", dstlist) - form[#form+1] = F.image_button_exit(5.5, 3.5, 1, 1, "cdb_add.png", "dst_add", "") - form[#form+1] = F.image_button_exit(5.5, 5, 1, 1, "cdb_clear.png", "dst_del", "") + form[#form+1] = F.textlist(0.5, 3, 4.5, 3, "dstlist", dstlist) + form[#form+1] = F.image_button_exit(5.5, 3.25, 1, 1, "cdb_add.png", "dst_add", "") + form[#form+1] = F.image_button_exit(5.5, 4.75, 1, 1, "cdb_clear.png", "dst_del", "") minetest.show_formspec(pname, "advtrains:distant_" .. minetest.pos_to_string(pos), table.concat(form)) end diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 54202f0..8bb92bf 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -137,14 +137,18 @@ function advtrains.interlocking.show_ip_form(pos, pname, only_notset) if not minetest.check_player_privs(pname, "interlocking") then return end - local form = "size[7,5]label[0.5,0.5;Signal at "..minetest.pos_to_string(pos).."]" - form = form .. advtrains.interlocking.make_signal_formspec_tabheader(pname, pos, 7, 2) + local form = "size[7,6.5]label[0.5,0.5;Signal at "..minetest.pos_to_string(pos).."]" + local node = advtrains.ndb.get_node(pos) + local ndef = minetest.registered_nodes[node.name] or {} + if ndef.advtrains and ndef.advtrains.set_aspect then + form = form .. advtrains.interlocking.make_signal_formspec_tabheader(pname, pos, 7, 2) + end advtrains.interlocking.db.check_for_duplicate_ip(pos) local pts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos) if pts then form = form.."label[0.5,1.5;Influence point is set at "..pts.."/"..connid.."]" - form = form.."button_exit[0.5,2.5; 5,1;set;Move]" - form = form.."button_exit[0.5,3.5; 5,1;clear;Clear]" + form = form.."button_exit[0.5,4.25; 6,1;set;Move]" + form = form.."button_exit[0.5,5.25; 6,1;clear;Clear]" local ipos = minetest.string_to_pos(pts) ipmarker(ipos, connid) else @@ -152,7 +156,7 @@ function advtrains.interlocking.show_ip_form(pos, pname, only_notset) form = form.."label[0.5,2.0;It is recommended to set an influence point.]" form = form.."label[0.5,2.5;This is the point where trains will obey the signal.]" - form = form.."button_exit[0.5,3.5; 5,1;set;Set]" + form = form.."button_exit[0.5,5.25; 6,1;set;Set]" end if not only_notset or not pts then minetest.show_formspec(pname, "at_il_ipassign_"..minetest.pos_to_string(pos), form) diff --git a/advtrains_interlocking/signal_aspect_ui.lua b/advtrains_interlocking/signal_aspect_ui.lua index ccedb01..6de21e6 100644 --- a/advtrains_interlocking/signal_aspect_ui.lua +++ b/advtrains_interlocking/signal_aspect_ui.lua @@ -52,16 +52,8 @@ local function describe_supported_aspects_t1(suppasp, isasp) t.main_current = selid if suppasp.shunt == nil then - selid = 1 - if isasp and isasp.shunt then - selid = 2 - end - entries = { - describe_t1_shunt_aspect(false), - describe_t1_shunt_aspect(true), - } - t.shunt = entries - t.shunt_current = selid + t.shunt = true + t.shunt_current = isasp and isasp.shunt end entries = {} @@ -83,8 +75,12 @@ local signal_tabheader_map = {} local function make_signal_formspec_tabheader(pname, pos, width, selid) signal_tabheader_map[pname] = pos + local firstlabel = attrans("Signal aspect") + if advtrains.interlocking.db.get_sigd_for_signal(pos) then + firstlabel = attrans("Routesetting") + end local options = { - attrans("Signal aspect"), + firstlabel, attrans("Influence point"), attrans("Distant signalling"), } @@ -112,7 +108,7 @@ advtrains.interlocking.make_signal_formspec_tabheader = make_signal_formspec_tab advtrains.interlocking.handle_signal_formspec_tabheader_fields = handle_signal_formspec_tabheader_fields local function make_signal_aspect_selector_t1(suppasp, purpose, isasp) - local form = {"size[7,7.25]"} + local form = {"size[7,6.5]"} local t = describe_supported_aspects_t1(suppasp, isasp) if type(purpose) == "table" then form[#form+1] = make_signal_formspec_tabheader(purpose.pname, purpose.pos, 7, 1) @@ -124,22 +120,21 @@ local function make_signal_aspect_selector_t1(suppasp, purpose, isasp) form[#form+1] = F.S_label(0.5, 1.5, "Main aspect") form[#form+1] = F.dropdown(0.5, 2, 6, "main", t.main, t.main_current, true) - form[#form+1] = F.S_label(0.5, 3, "Shunt aspect") + form[#form+1] = F.S_label(0.5, 3, "Distant aspect") + form[#form+1] = F.dropdown(0.5, 3.5, 6, "dst", t.dst, t.dst_current, true) + if t.shunt then - form[#form+1] = F.dropdown(0.5, 3.5, 6, "shunt_free", t.shunt, t.shunt_current, true) + form[#form+1] = F.S_checkbox(0.5, 4.25, "shunt", t.shunt_current, "Allow shunting") else - form[#form+1] = F.S_label(0.5, 3.5, "The shunt aspect cannot be changed") + form[#form+1] = F.S_label(0.5, 4.5, "The shunt aspect cannot be changed.") end - form[#form+1] = F.S_label(0.5, 4.5, "Distant aspect") - form[#form+1] = F.dropdown(0.5, 5, 6, "dst", t.dst, t.dst_current, true) - - form[#form+1] = F.S_button_exit(0.5, 6, 6, 1, "save", "Save signal aspect") + form[#form+1] = F.S_button_exit(0.5, 5.25, 6, 1, "save", "Save signal aspect") return table.concat(form) end local function make_signal_aspect_selector_t2(suppasp, purpose, isasp) - local form = {"size[7,4]"} + local form = {"size[7,6.5]"} local def = advtrains.interlocking.aspects.get_type2_definition(suppasp.group) if not def then return nil @@ -159,8 +154,13 @@ local function make_signal_aspect_selector_t2(suppasp, purpose, isasp) end entries[idx] = spv.label end - form[#form+1] = F.dropdown(0.5, 1.5, 6, "asp", entries, selid, true) - form[#form+1] = F.S_button_exit(0.5, 2.5, 6, 1, "save", "Save signal aspect") + form[#form+1] = F.S_label(0.5, 1.5, "Signal group: @1", def.label) + form[#form+1] = F.dropdown(0.5, 2, 6, "asp", entries, selid, true) + form[#form+1] = F.S_label(0.5, 3, "Aspect in effect:") + form[#form+1] = F.label(0.5, 3.5, describe_t1_main_aspect(isasp.main)) + form[#form+1] = F.label(0.5, 4, describe_t1_distant_aspect(isasp.dst)) + form[#form+1] = F.label(0.5, 4.5, describe_t1_shunt_aspect(isasp.shunt)) + form[#form+1] = F.S_button_exit(0.5, 5.25, 6, 1, "save", "Save signal aspect") return table.concat(form) end @@ -205,7 +205,7 @@ local function usebool(sup, val, free) end end -local function get_aspect_from_formspec_t1(suppasp, fields) +local function get_aspect_from_formspec_t1(suppasp, fields, psl) local maini = tonumber(fields.main) if not maini then return end local dsti = tonumber(fields.dst) @@ -213,12 +213,12 @@ local function get_aspect_from_formspec_t1(suppasp, fields) return { main = suppasp.main[maini], dst = suppasp.dst[dsti], - shunt = usebool(suppasp.shunt, fields.shunt_free, "2"), + shunt = usebool(suppasp.shunt, psl.shunt, "true"), info = {}, } end -local function get_aspect_from_formspec_t2(suppasp, fields) +local function get_aspect_from_formspec_t2(suppasp, fields, psl) local asp = advtrains.interlocking.aspects.type2_to_type1(suppasp, tonumber(fields.asp)) return asp end @@ -232,12 +232,15 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if handle_signal_formspec_tabheader_fields(pname, fields) then return true end + if fields.shunt then + psl.shunt = fields.shunt + end if fields.save then local asp if suppasp.type == 2 then - asp = get_aspect_from_formspec_t2(suppasp, fields) + asp = get_aspect_from_formspec_t2(suppasp, fields, psl) else - asp = get_aspect_from_formspec_t1(suppasp, fields) + asp = get_aspect_from_formspec_t1(suppasp, fields, psl) end if asp then psl.callback(pname, asp) -- cgit v1.2.3 From 2d072cdc67ec3018eaba36b7c79c2b19e574a94d Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Fri, 6 Jan 2023 14:45:03 +0100 Subject: Rework formspecs; add description to JP signal group --- advtrains/formspec.lua | 47 +++--- advtrains_interlocking/distant_ui.lua | 76 ++++----- advtrains_interlocking/signal_api.lua | 75 +++++---- advtrains_interlocking/signal_aspect_ui.lua | 232 +++++++++++++++++----------- advtrains_interlocking/tcb_ts_ui.lua | 5 - advtrains_signals_japan/init.lua | 1 + 6 files changed, 252 insertions(+), 184 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains/formspec.lua b/advtrains/formspec.lua index 743d3f3..8894354 100644 --- a/advtrains/formspec.lua +++ b/advtrains/formspec.lua @@ -9,32 +9,28 @@ local function make_list(entries) return table.concat(t, ",") end -local function f_button(x, y, w, h, id, text) - return sformat("button[%f,%f;%f,%f;%s;%s]", x, y, w, h, id, text) +local function S_wrapper(f, i0) + return function(...) + local args = {...} + args[i0] = attrans(unpack(args,i0)) + return f(unpack(args,1,i0)) + end end -local function S_button(x, y, w, h, id, ...) - return f_button(x, y, w, h, id, attrans(...)) +local function f_button(x, y, w, id, text) + return sformat("button[%f,%f;%f,0.75;%s;%s]", x, y, w, id, text) end local function f_checkbox(x, y, name, selected, label) - return sformat("checkbox[%f,%f;%s;%s;%s]", x, y, name, label, selected and "true" or "false") -end - -local function S_checkbox(x, y, name, selected, ...) - return f_checkbox(x, y, name, selected, attrans(...)) + return sformat("checkbox[%f,%f;%s;%s;%s]", x, y+0.25, name, label, selected and "true" or "false") end -local function f_button_exit(x, y, w, h, id, text) - return sformat("button_exit[%f,%f;%f,%f;%s;%s]", x, y, w, h, id, text) -end - -local function S_button_exit(x, y, w, h, id, ...) - return f_button_exit(x, y, w, h, id, attrans(...)) +local function f_button_exit(x, y, w, id, text) + return sformat("button_exit[%f,%f;%f,0.75;%s;%s]", x, y, w, id, text) end local function f_dropdown(x, y, w, id, entries, sel, indexed) - return sformat("dropdown[%f,%f;%f;%s;%s;%d%s]", + return sformat("dropdown[%f,%f;%f,0.75;%s;%s;%d%s]", x, y, w, id, make_list(entries), sel or 1, indexed and ";true" or "") @@ -56,11 +52,15 @@ local function f_image_button_exit(x, y, w, h, texture, id, label) end local function f_label(x, y, text) - return sformat("label[%f,%f;%s]", x, y, fsescape(text)) + return sformat("label[%f,%f;%s]", x, y+0.25, fsescape(text)) +end + +local function f_field_aux(x, y, w, id, default) + return sformat("field[%f,%f;%f,0.75;%s;;%s]", x, y, w, id, default) end -local function S_label(x, y, ...) - return f_label(x, y, attrans(...)) +local function f_field(x, y, w, id, label, default) + return f_label(x, y-0.5, label) .. f_field_aux(x, y, w, id, default) end local function f_tabheader(x, y, w, h, id, entries, sel, transparent, border) @@ -95,16 +95,17 @@ end return { button = f_button, - S_button = S_button, + S_button = S_wrapper(f_button, 5), checkbox = f_checkbox, - S_checkbox = S_checkbox, + S_checkbox = S_wrapper(f_checkbox, 5), button_exit = f_button_exit, - S_button_exit = S_button_exit, + S_button_exit = S_wrapper(f_button_exit, 5), dropdown = f_dropdown, + field = f_field, image_button = f_image_button, image_button_exit = f_image_button_exit, label = f_label, - S_label = S_label, + S_label = S_wrapper(f_label, 3), tabheader = f_tabheader, textlist = f_textlist, } diff --git a/advtrains_interlocking/distant_ui.lua b/advtrains_interlocking/distant_ui.lua index a7ff406..0907684 100644 --- a/advtrains_interlocking/distant_ui.lua +++ b/advtrains_interlocking/distant_ui.lua @@ -2,39 +2,52 @@ local F = advtrains.formspec local D = advtrains.distant local I = advtrains.interlocking -function advtrains.interlocking.show_distant_signal_form(pos, pname) - local form = {"size[7,6.5]"} - form[#form+1] = advtrains.interlocking.make_signal_formspec_tabheader(pname, pos, 7, 3) +function I.make_short_dst_formspec_component(pos, x, y, w) local main, set_by = D.get_main(pos) if main then local pts_main = minetest.pos_to_string(main) - form[#form+1] = F.S_label(0.5, 0.5, "This signal is a distant signal of @1.", pts_main) + local desc = attrans("The assignment is made with an unknown method.") if set_by == "manual" then - form[#form+1] = F.S_label(0.5, 1, "The assignment is made manually.") + desc = attrans("The assignment is made manually.") elseif set_by == "routesetting" then - form[#form+1] = F.S_label(0.5, 1, "The assignment is made by the routesetting system.") + desc = attrans("The assignment is made by the routesetting system.") end + return table.concat { + F.S_label(x, y, "This signal is a distant signal of @1.", pts_main), + F.label(x, y+0.5, desc), + F.S_button_exit(x, y+1, w/2-0.125, "dst_assign", "Reassign"), + F.S_button_exit(x+w/2+0.125, y+1, w/2-0.125, "dst_unassign", "Unassign"), + } else - form[#form+1] = F.S_label(0.5, 0.5, "This signal is not assigned to a main signal.") - form[#form+1] = F.S_label(0.5, 1, "The distant aspect of the signal is not used.") - end - if set_by ~= nil then - form[#form+1] = F.S_button_exit(0.5, 1.5, 3, 1, "unassign_dst", "Unassign") - form[#form+1] = F.S_button_exit(3.5, 1.5, 3, 1, "assign_dst", "Reassign") - else - form[#form+1] = F.S_button_exit(0.5, 1.5, 6, 1, "assign_dst", "Assign") + return table.concat { + F.S_label(x, y, "This signal is not assigned to a main signal."), + F.S_label(x, y+0.5, "The distant aspect of the signal is not used."), + F.S_button_exit(x, y+1, w, "dst_assign", "Assign") + } end +end - local dsts = D.get_dst(pos) +function I.make_dst_list_formspec_component(pos, x, y, w, h) + local ymid = y+0.25+h/2 local dstlist = {} - for pos, _ in pairs(dsts) do - dstlist[#dstlist+1] = minetest.pos_to_string(advtrains.decode_pos(pos)) + for pos, _ in pairs(D.get_dst(pos)) do + table.insert(dstlist, minetest.pos_to_string(advtrains.decode_pos(pos))) end - form[#form+1] = F.S_label(0.5, 2.5, "This signal has the following distant signals:") - form[#form+1] = F.textlist(0.5, 3, 4.5, 3, "dstlist", dstlist) - form[#form+1] = F.image_button_exit(5.5, 3.25, 1, 1, "cdb_add.png", "dst_add", "") - form[#form+1] = F.image_button_exit(5.5, 4.75, 1, 1, "cdb_clear.png", "dst_del", "") - minetest.show_formspec(pname, "advtrains:distant_" .. minetest.pos_to_string(pos), table.concat(form)) + return table.concat { + F.S_label(x, y, "Distant signals:"), + F.textlist(x, y+0.5, w-1, h-0.5, "dstlist", dstlist), + F.image_button_exit(x+w-0.75, ymid-0.875, 0.75, 0.75, "cdb_add.png", "dst_add", ""), + F.image_button_exit(x+w-0.75, ymid+0.125, 0.75, 0.75, "cdb_clear.png", "dst_del", ""), + } +end + +function I.make_dst_formspec_component(pos, x, y, w, h) + return I.make_short_dst_formspec_component(pos, x, y, w, h) + .. I.make_dst_list_formspec_component(pos, x, y+2, w, h-2) +end + +function I.show_distant_signal_form(pos, pname) + return I.show_ip_form(pos, pname) end local signal_pos = {} @@ -87,21 +100,14 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) end) local dstsel = {} -minetest.register_on_player_receive_fields(function(player, formname, fields) - local pname = player:get_player_name() - local pos = minetest.string_to_pos(string.match(formname, "^advtrains:distant_(.+)$") or "") - if not pos then - return - end - if not minetest.check_player_privs(pname, "interlocking") then + +function advtrains.interlocking.handle_dst_formspec_fields(pname, pos, fields) + if not (pos and minetest.check_player_privs(pname, "interlocking")) then return end - if advtrains.interlocking.handle_signal_formspec_tabheader_fields(pname, fields) then - return true - end - if fields.unassign_dst then + if fields.dst_unassign then D.unassign_dst(pos) - elseif fields.assign_dst then + elseif fields.dst_assign then init_signal_assignment(pname, pos) elseif fields.dst_add then init_distant_assignment(pname, pos) @@ -124,4 +130,4 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end end end -end) +end diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 8bb92bf..cd408d7 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -1,5 +1,7 @@ -- Signal API implementation +local F = advtrains.formspec + local DANGER = { main = 0, shunt = false, @@ -131,57 +133,68 @@ local function ipmarker(ipos, connid) }) end --- shows small info form for signal IP state/assignment +function advtrains.interlocking.make_ip_formspec_component(pos, x, y, w) + advtrains.interlocking.db.check_for_duplicate_ip(pos) + local pts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos) + if pts then + return table.concat { + F.S_label(x, y, "Influence point is set at @1.", string.format("%s/%s", pts, connid)), + F.S_button_exit(x, y+0.5, w/2-0.125, "ip_set", "Modify"), + F.S_button_exit(x+w/2+0.125, y+0.5, w/2-0.125, "ip_clear", "Clear"), + }, pts, connid + else + return table.concat { + F.S_label(x, y, "Influence point is not set."), + F.S_button_exit(x, y+0.5, w, "ip_set", "Set influence point"), + } + end +end + +-- shows small info form for signal properties +-- This function is named show_ip_form because it was originally only intended +-- for assigning/changing the influence point. -- only_notset: show only if it is not set yet (used by signal tcb assignment) function advtrains.interlocking.show_ip_form(pos, pname, only_notset) if not minetest.check_player_privs(pname, "interlocking") then return end - local form = "size[7,6.5]label[0.5,0.5;Signal at "..minetest.pos_to_string(pos).."]" - local node = advtrains.ndb.get_node(pos) - local ndef = minetest.registered_nodes[node.name] or {} - if ndef.advtrains and ndef.advtrains.set_aspect then - form = form .. advtrains.interlocking.make_signal_formspec_tabheader(pname, pos, 7, 2) - end - advtrains.interlocking.db.check_for_duplicate_ip(pos) - local pts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos) + local ipform, pts, connid = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 0.5, 7) + local form = table.concat { + "formspec_version[4]", + "size[8,6.75]", + ipform, + advtrains.interlocking.make_dst_formspec_component(pos, 0.5, 2, 7, 4.25), + } if pts then - form = form.."label[0.5,1.5;Influence point is set at "..pts.."/"..connid.."]" - form = form.."button_exit[0.5,4.25; 6,1;set;Move]" - form = form.."button_exit[0.5,5.25; 6,1;clear;Clear]" local ipos = minetest.string_to_pos(pts) ipmarker(ipos, connid) - else - form = form.."label[0.5,1.5;Influence point is not set.]" - form = form.."label[0.5,2.0;It is recommended to set an influence point.]" - form = form.."label[0.5,2.5;This is the point where trains will obey the signal.]" - - form = form.."button_exit[0.5,5.25; 6,1;set;Set]" end if not only_notset or not pts then - minetest.show_formspec(pname, "at_il_ipassign_"..minetest.pos_to_string(pos), form) + minetest.show_formspec(pname, "at_il_propassign_"..minetest.pos_to_string(pos), form) end end -minetest.register_on_player_receive_fields(function(player, formname, fields) - local pname = player:get_player_name() - if advtrains.interlocking.handle_signal_formspec_tabheader_fields(pname, fields) then - return true - end - if not minetest.check_player_privs(pname, {train_operator=true, interlocking=true}) then +function advtrains.interlocking.handle_ip_formspec_fields(pname, pos, fields) + if not (pos and minetest.check_player_privs(pname, {train_operator=true, interlocking=true})) then return end - local pts = string.match(formname, "^at_il_ipassign_([^_]+)$") + if fields.ip_set then + advtrains.interlocking.signal_init_ip_assign(pos, pname) + elseif fields.ip_clear then + advtrains.interlocking.db.clear_ip_by_signalpos(pos) + end +end + +minetest.register_on_player_receive_fields(function(player, formname, fields) + local pname = player:get_player_name() + local pts = string.match(formname, "^at_il_propassign_([^_]+)$") local pos if pts then pos = minetest.string_to_pos(pts) end if pos then - if fields.set then - advtrains.interlocking.signal_init_ip_assign(pos, pname) - elseif fields.clear then - advtrains.interlocking.db.clear_ip_by_signalpos(pos) - end + advtrains.interlocking.handle_ip_formspec_fields(pname, pos, fields) + advtrains.interlocking.handle_dst_formspec_fields(pname, pos, fields) end end) diff --git a/advtrains_interlocking/signal_aspect_ui.lua b/advtrains_interlocking/signal_aspect_ui.lua index 6de21e6..ddab793 100644 --- a/advtrains_interlocking/signal_aspect_ui.lua +++ b/advtrains_interlocking/signal_aspect_ui.lua @@ -37,6 +37,20 @@ advtrains.interlocking.describe_t1_main_aspect = describe_t1_main_aspect advtrains.interlocking.describe_t1_shunt_aspect = describe_t1_shunt_aspect advtrains.interlocking.describe_t1_distant_aspect = describe_t1_distant_aspect +local function dsel(p, q, x, y) + if p == nil then + if q then + return x + else + return y + end + elseif p then + return x + else + return y + end +end + local function describe_supported_aspects_t1(suppasp, isasp) local t = {} @@ -50,11 +64,22 @@ local function describe_supported_aspects_t1(suppasp, isasp) end t.main = entries t.main_current = selid + t.main_string = tostring(isasp.main) + if t.main == nil then + t.main_string = "" + end - if suppasp.shunt == nil then - t.shunt = true - t.shunt_current = isasp and isasp.shunt + t.shunt = { + attrans("No shunting"), + attrans("Shunting allowed"), + attrans("Proceed as main"), + } + + t.shunt_current = dsel(suppasp.shunt, isasp.shunt, 2, 1) + if dsel(suppasp.proceed_as_main, isasp.proceed_as_main, t.shunt_current == 1) then + t.shunt_current = 3 end + t.shunt_const = suppasp.shunt ~= nil entries = {} selid = 1 @@ -71,96 +96,98 @@ end advtrains.interlocking.describe_supported_aspects_t1 = describe_supported_aspects_t1 -local signal_tabheader_map = {} - -local function make_signal_formspec_tabheader(pname, pos, width, selid) - signal_tabheader_map[pname] = pos - local firstlabel = attrans("Signal aspect") - if advtrains.interlocking.db.get_sigd_for_signal(pos) then - firstlabel = attrans("Routesetting") - end - local options = { - firstlabel, - attrans("Influence point"), - attrans("Distant signalling"), - } - return F.tabheader(0, 0, nil, nil, "signal_tab", options, selid) -end - -local function handle_signal_formspec_tabheader_fields(pname, fields) - local n = tonumber(fields.signal_tab) - local pos = signal_tabheader_map[pname] - if not (n and pos) then - return false - end - if n == 1 then - local node = advtrains.ndb.get_node(pos) - advtrains.interlocking.show_signal_form(pos, node, pname) - elseif n == 2 then - advtrains.interlocking.show_ip_form(pos, pname) - elseif n == 3 then - advtrains.interlocking.show_distant_signal_form(pos, pname) - end - return true -end - -advtrains.interlocking.make_signal_formspec_tabheader = make_signal_formspec_tabheader -advtrains.interlocking.handle_signal_formspec_tabheader_fields = handle_signal_formspec_tabheader_fields - local function make_signal_aspect_selector_t1(suppasp, purpose, isasp) - local form = {"size[7,6.5]"} local t = describe_supported_aspects_t1(suppasp, isasp) + local formmode = 1 + + local pos if type(purpose) == "table" then - form[#form+1] = make_signal_formspec_tabheader(purpose.pname, purpose.pos, 7, 1) - purpose = "" + formmode = 2 + pos = purpose.pos end - form[#form+1] = F.S_label(0.5, 0.5, "Select signal aspect") - form[#form+1] = F.label(0.5, 1, purpose) - form[#form+1] = F.S_label(0.5, 1.5, "Main aspect") - form[#form+1] = F.dropdown(0.5, 2, 6, "main", t.main, t.main_current, true) + local form = { + "formspec_version[4]", + string.format("size[8,%f]", ({5.75, 9.25})[formmode]), + F.S_label(0.5, 0.5, "Select signal aspect"), + } + if formmode == 1 then + form[#form+1] = F.label(0.5, 1, purpose) + else + form[#form+1] = F.S_label(0.5, 1, "Signal at @1", minetest.pos_to_string(pos)) + end - form[#form+1] = F.S_label(0.5, 3, "Distant aspect") - form[#form+1] = F.dropdown(0.5, 3.5, 6, "dst", t.dst, t.dst_current, true) + form[#form+1] = F.S_label(0.5, 1.5, "Main aspect") + if formmode == 1 then + form[#form+1] = F.field(0.5, 2, 7, "asp_mainval", "", t.main_string) + else + form[#form+1] = F.dropdown(0.5, 2, 7, "asp_mainsel", t.main, t.main_current, true) + end - if t.shunt then - form[#form+1] = F.S_checkbox(0.5, 4.25, "shunt", t.shunt_current, "Allow shunting") + form[#form+1] = F.S_label(0.5, 3, "Shunt aspect") + if formmode == 2 and t.shunt_const then + form[#form+1] = F.label(0.5, 3.5, t.shunt[t.shunt_current]) + form[#form+1] = F.S_label(0.5, 4, "The shunt aspect cannot be changed.") else - form[#form+1] = F.S_label(0.5, 4.5, "The shunt aspect cannot be changed.") + form[#form+1] = F.dropdown(0.5, 3.5, 7, "asp_shunt", t.shunt, t.shunt_current, true) + end + + form[#form+1] = F.S_button_exit(0.5, 4.5, 7, "asp_save", "Save signal aspect") + + if formmode == 2 then + form[#form+1] = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 5.5, 7) + form[#form+1] = advtrains.interlocking.make_short_dst_formspec_component(pos, 0.5, 7, 7) end - form[#form+1] = F.S_button_exit(0.5, 5.25, 6, 1, "save", "Save signal aspect") return table.concat(form) end local function make_signal_aspect_selector_t2(suppasp, purpose, isasp) - local form = {"size[7,6.5]"} local def = advtrains.interlocking.aspects.get_type2_definition(suppasp.group) if not def then return nil end + local formmode = 1 + + local pos if type(purpose) == "table" then - form[#form+1] = make_signal_formspec_tabheader(purpose.pname, purpose.pos, 7, 1) - purpose = "" + formmode = 2 + pos = purpose.pos + end + local form = { + "formspec_version[4]", + string.format("size[8,%f]", ({4.25, 10.25})[formmode]), + F.S_label(0.5, 0.5, "Select signal aspect") + } + if formmode == 1 then + form[#form+1] = F.label(0.5, 1, purpose) + else + form[#form+1] = F.S_label(0.5, 1, "Signal at @1", minetest.pos_to_string(pos)) end - form[#form+1] = F.S_label(0.5, 0.5, "Select signal aspect") - form[#form+1] = F.label(0.5, 1, purpose) local entries = {} - local selid = 1 - for idx, spv in ipairs(def.main) do - if isasp and isasp.type2name == spv.name then - selid = idx + local selid = #def.main + if isasp then + if isasp.type2name ~= def.main[selid].name then + selid = 1 end - entries[idx] = spv.label end + if selid > 1 then + selid = 2 + end + local entries = { + def.main[1].label, + def.main[#def.main].label, + } form[#form+1] = F.S_label(0.5, 1.5, "Signal group: @1", def.label) - form[#form+1] = F.dropdown(0.5, 2, 6, "asp", entries, selid, true) - form[#form+1] = F.S_label(0.5, 3, "Aspect in effect:") - form[#form+1] = F.label(0.5, 3.5, describe_t1_main_aspect(isasp.main)) - form[#form+1] = F.label(0.5, 4, describe_t1_distant_aspect(isasp.dst)) - form[#form+1] = F.label(0.5, 4.5, describe_t1_shunt_aspect(isasp.shunt)) - form[#form+1] = F.S_button_exit(0.5, 5.25, 6, 1, "save", "Save signal aspect") + form[#form+1] = F.dropdown(0.5, 2, 7, "asp_sel", entries, selid, true) + form[#form+1] = F.S_button_exit(0.5, 3, 7, "asp_save", "Save signal aspect") + + if formmode == 2 then + form[#form+1] = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 4, 7) + form[#form+1] = advtrains.interlocking.make_dst_formspec_component(pos, 0.5, 5.5, 7, 4.25) + end + return table.concat(form) end @@ -188,13 +215,14 @@ function advtrains.interlocking.show_signal_aspect_selector(pname, p_suppasp, p_ local token = advtrains.random_id() minetest.show_formspec(pname, "at_il_sigaspdia_"..token, form) - --minetest.after(1, function() - players_aspsel[pname] = { - suppasp = suppasp, - callback = callback, - token = token, - } - --end) + minetest.after(0, function() + players_aspsel[pname] = { + purpose = purpose, + suppasp = suppasp, + callback = callback, + token = token, + } + end) end local function usebool(sup, val, free) @@ -206,20 +234,45 @@ local function usebool(sup, val, free) end local function get_aspect_from_formspec_t1(suppasp, fields, psl) - local maini = tonumber(fields.main) - if not maini then return end - local dsti = tonumber(fields.dst) - if not dsti then return end + local maini = tonumber(fields.asp_mainsel) + local main = suppasp.main[maini] + if not maini then + local mainval = fields.asp_mainval + if mainval == "-1" then + main = -1 + elseif string.match(mainval, "^%d+$") then + main = tonumber(mainval) + else + main = nil + end + end + local shunti = tonumber(fields.asp_shunt) + local shunt = suppasp.shunt + if shunt == nil then + shunt = shunti == 2 + end + local proceed_as_main = suppasp.proceed_as_main + if proceed_as_main == nil then + proceed_as_main = shunti == 3 + end return { - main = suppasp.main[maini], - dst = suppasp.dst[dsti], - shunt = usebool(suppasp.shunt, psl.shunt, "true"), + main = main, + shunt = shunt, + proceed_as_main = proceed_as_main, info = {}, } end local function get_aspect_from_formspec_t2(suppasp, fields, psl) - local asp = advtrains.interlocking.aspects.type2_to_type1(suppasp, tonumber(fields.asp)) + local sel = tonumber(fields.asp_sel) + local def = advtrains.interlocking.aspects.get_type2_definition(suppasp.group) + if not (sel and def) then + return + end + if sel ~= 1 then + sel = #def.main + end + local asp = advtrains.interlocking.aspects.type2_to_type1(suppasp, sel) return asp end @@ -229,13 +282,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if psl then if formname == "at_il_sigaspdia_"..psl.token then local suppasp = psl.suppasp - if handle_signal_formspec_tabheader_fields(pname, fields) then - return true - end - if fields.shunt then - psl.shunt = fields.shunt - end - if fields.save then + if fields.asp_save then local asp if suppasp.type == 2 then asp = get_aspect_from_formspec_t2(suppasp, fields, psl) @@ -246,6 +293,11 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) psl.callback(pname, asp) end end + if type(psl.purpose) == "table" then + local pos = psl.purpose.pos + advtrains.interlocking.handle_ip_formspec_fields(pname, pos, fields) + advtrains.interlocking.handle_dst_formspec_fields(pname, pos, fields) + end else players_aspsel[pname] = nil end diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index 3898d73..9aea18c 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -614,7 +614,6 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle if not tcbs.routes then tcbs.routes = {} end local form = "size[7,10.25]label[0.5,0.5;Signal at "..minetest.pos_to_string(sigd.p).."]" - form = form .. advtrains.interlocking.make_signal_formspec_tabheader(pname, tcbs.signal, 7, 1) form = form.."field[0.8,1.5;5.2,1;name;Signal name;"..minetest.formspec_escape(tcbs.signal_name).."]" form = form.."button[5.5,1.2;1,1;setname;Set]" @@ -708,10 +707,6 @@ end minetest.register_on_player_receive_fields(function(player, formname, fields) local pname = player:get_player_name() - if string.find(formname, "^at_il_signalling_") - and advtrains.interlocking.handle_signal_formspec_tabheader_fields(pname, fields) then - return true - end if not minetest.check_player_privs(pname, "train_operator") then return end diff --git a/advtrains_signals_japan/init.lua b/advtrains_signals_japan/init.lua index a28478f..fe74259 100644 --- a/advtrains_signals_japan/init.lua +++ b/advtrains_signals_japan/init.lua @@ -294,6 +294,7 @@ local function process_signal(name, sigdata, isrpt) local type2def = {} type2def.name = typename type2def.main = {} + type2def.label = S(string.format("Japanese signal (type %s)", string.upper(name))) local def = {} local tx = {} def.typename = typename -- cgit v1.2.3 From d443d8e07af89665a6bb3d87af91f43f08a6c47e Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Fri, 6 Jan 2023 18:23:15 +0100 Subject: Distant signaling: avoid signal signs --- advtrains_interlocking/distant.lua | 21 +++++++++++++++++++++ advtrains_interlocking/distant_ui.lua | 10 +++++++++- advtrains_interlocking/signal_api.lua | 10 +++++++--- advtrains_interlocking/signal_aspect_accessors.lua | 2 +- .../spec/basic_signalling_spec.lua | 17 ++++++++++++++++- 5 files changed, 54 insertions(+), 6 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/distant.lua b/advtrains_interlocking/distant.lua index 726c2b3..4175875 100644 --- a/advtrains_interlocking/distant.lua +++ b/advtrains_interlocking/distant.lua @@ -85,6 +85,23 @@ local function unassign_all(pos, force) unassign_dst(pos, force) end +--- Check whether a signal is "appropriate" for the distant signal system. +-- Currently, a signal is considered appropriate if its signal aspect can be set. +-- @function appropriate_signal +-- @param pos The position of the signal +local function appropriate_signal(pos) + local node = advtrains.ndb.get_node(pos) + local ndef = minetest.registered_nodes[node.name] or {} + if not ndef then + return false + end + local atdef = ndef.advtrains + if not atdef then + return false + end + return atdef.supported_aspects and atdef.set_aspect and true +end + --- Assign a distant signal to a main signal. -- @function assign -- @param main The position of the main signal. @@ -92,6 +109,9 @@ end -- @param[opt="manual"] by The method of assignment. -- @param[opt=false] skip_update Whether to skip callbacks. local function assign(main, dst, by, skip_update) + if not (appropriate_signal(main) and appropriate_signal(dst)) then + return + end local pts_main = pts(main) local pts_dst = pts(dst) local t = db_distant[pts_main] @@ -177,4 +197,5 @@ advtrains.distant = { update_main = update_main, update_dst = update_dst, update_signal = update_signal, + appropriate_signal = appropriate_signal, } diff --git a/advtrains_interlocking/distant_ui.lua b/advtrains_interlocking/distant_ui.lua index 0907684..bb66dc4 100644 --- a/advtrains_interlocking/distant_ui.lua +++ b/advtrains_interlocking/distant_ui.lua @@ -56,6 +56,10 @@ local function init_signal_assignment(pname, pos) minetest.chat_send_player(pname, attrans("This operation is not allowed without the @1 privilege.", "interlocking")) return end + if not D.appropriate_signal(pos) then + minetest.chat_send_player(pname, attrans("Incompatible signal.")) + return + end signal_pos[pname] = pos minetest.chat_send_player(pname, attrans("Please punch the signal to use as the main signal.")) end @@ -66,6 +70,10 @@ local function init_distant_assignment(pname, pos) minetest.send_chat_player(pname, attrans("This operation is now allowed without the @1 privilege.", "interlocking")) return end + if not D.appropriate_signal(pos) then + minetest.chat_send_player(pname, attrans("Incompatible signal.")) + return + end distant_pos[pname] = pos minetest.chat_send_player(pname, attrans("Please punch the signal to use as the distant signal.")) end @@ -87,7 +95,7 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) signal_pos[pname] = nil distant_pos[pname] = nil local is_signal = minetest.get_item_group(node.name, "advtrains_signal") >= 2 - if not is_signal then + if not (is_signal and D.appropriate_signal(pos)) then minetest.chat_send_player(pname, attrans("Incompatible signal.")) return end diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index cd408d7..1b4a21c 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -159,16 +159,20 @@ function advtrains.interlocking.show_ip_form(pos, pname, only_notset) return end local ipform, pts, connid = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 0.5, 7) - local form = table.concat { + local form = { "formspec_version[4]", - "size[8,6.75]", + "size[8,2.25]", ipform, - advtrains.interlocking.make_dst_formspec_component(pos, 0.5, 2, 7, 4.25), } if pts then local ipos = minetest.string_to_pos(pts) ipmarker(ipos, connid) end + if advtrains.distant.appropriate_signal(pos) then + form[#form+1] = advtrains.interlocking.make_dst_formspec_component(pos, 0.5, 2, 7, 4.25) + form[2] = "size[8,6.75]" + end + form = table.concat(form) if not only_notset or not pts then minetest.show_formspec(pname, "at_il_propassign_"..minetest.pos_to_string(pos), form) end diff --git a/advtrains_interlocking/signal_aspect_accessors.lua b/advtrains_interlocking/signal_aspect_accessors.lua index e55814e..e419515 100644 --- a/advtrains_interlocking/signal_aspect_accessors.lua +++ b/advtrains_interlocking/signal_aspect_accessors.lua @@ -144,7 +144,7 @@ local function get_real_aspect(pos) if ndef.advtrains and ndef.advtrains.get_aspect then local asp = ndef.advtrains.get_aspect(pos, node) or I.DANGER local suppasp = get_supported_aspects(pos) - if suppasp.type == 2 then + if suppasp and suppasp.type == 2 then asp = A.type2_to_type1(suppasp, asp) end return adjust_aspect(pos, asp) diff --git a/advtrains_interlocking/spec/basic_signalling_spec.lua b/advtrains_interlocking/spec/basic_signalling_spec.lua index 720b274..cce0f15 100644 --- a/advtrains_interlocking/spec/basic_signalling_spec.lua +++ b/advtrains_interlocking/spec/basic_signalling_spec.lua @@ -23,6 +23,12 @@ sourcefile("signal_api") sourcefile("signal_aspect_accessors") fixture("../../demosignals") +minetest.register_node("advtrains_interlocking:signal_sign", { + advtrains = { + get_aspcet = function() return {main = 19} end + } +}) + local D = advtrains.distant local I = advtrains.interlocking @@ -32,7 +38,7 @@ local stub_aspect_t1 = { danger = {main = 0, shunt = false}, } local stub_pos_t1 = {} -for i = 1, 3 do +for i = 1, 4 do stub_pos_t1[i] = {x = 1, y = 0, z = i} end @@ -40,6 +46,7 @@ world.layout { {stub_pos_t1[1], "advtrains_interlocking:ds_danger"}, {stub_pos_t1[2], "advtrains_interlocking:ds_slow"}, {stub_pos_t1[3], "advtrains_interlocking:ds_free"}, + {stub_pos_t1[4], "advtrains_interlocking:signal_sign"}, } describe("API for supposed signal aspects", function() @@ -84,4 +91,12 @@ describe("Distant signaling", function() assert.same({}, {D.get_main(stub_pos_t1[3])}) assert.same(stub_aspect_t1.free, I.signal_get_aspect(stub_pos_t1[3])) end) + it("should reject signal signs", function() + D.assign(stub_pos_t1[1], stub_pos_t1[4]) + assert.same({}, D.get_dst(stub_pos_t1[1])) + assert.same({}, {D.get_main(stub_pos_t1[4])}) + D.assign(stub_pos_t1[4], stub_pos_t1[1]) + assert.same({}, D.get_dst(stub_pos_t1[4])) + assert.same({}, {D.get_main(stub_pos_t1[1])}) + end) end) -- cgit v1.2.3 From e25b1c744dad7daa561ee0b23b006bc616f88f23 Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Sun, 26 Mar 2023 11:53:00 +0200 Subject: Cancel type 2 signals; introduce signal groups for all signals --- advtrains_interlocking/README.md | 41 ++- advtrains_interlocking/aspect.lua | 290 +++++++++++++++++++++ advtrains_interlocking/distant.lua | 1 - advtrains_interlocking/init.lua | 2 +- advtrains_interlocking/signal_api.lua | 28 +- advtrains_interlocking/signal_aspect_accessors.lua | 80 ++---- advtrains_interlocking/signal_aspect_ui.lua | 127 ++------- advtrains_interlocking/signal_aspects.lua | 202 -------------- .../spec/basic_signalling_spec.lua | 26 +- advtrains_interlocking/spec/signal_group_spec.lua | 95 +++++++ advtrains_interlocking/spec/type2_spec.lua | 117 --------- advtrains_signals_japan/init.lua | 18 +- 12 files changed, 474 insertions(+), 553 deletions(-) create mode 100644 advtrains_interlocking/aspect.lua delete mode 100644 advtrains_interlocking/signal_aspects.lua create mode 100644 advtrains_interlocking/spec/signal_group_spec.lua delete mode 100644 advtrains_interlocking/spec/type2_spec.lua (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/README.md b/advtrains_interlocking/README.md index 636ad67..d4a2699 100644 --- a/advtrains_interlocking/README.md +++ b/advtrains_interlocking/README.md @@ -2,12 +2,6 @@ The `advtrains_interlocking` mod provides various interlocking and signaling features for Advtrains. -## Signal types -There are two types of signals in Advtrains: - -* Type 1 (speed signals): These signals only give speed information. -* Type 2 (route signals): These signals mainly provide route information, but sometimes also provide speed information. - ## Signal aspect tables Signal aspects are represented using tables with the following (optional) fields: @@ -16,15 +10,16 @@ Signal aspects are represented using tables with the following (optional) fields * `dst`: The distant signal aspect. It provides information on the permitted speed after passing the next signal. * `shunt`: Whether the train may proceed in shunt mode and, if the main aspect is danger, proceed in shunt mode. * `proceed_as_main`: Whether the train should exit shunt mode when proceeding. -* `type2group`: The type 2 group of the signal. -* `type2name`: The type 2 signal aspect name. +* `group`: The name of the signal group. +* `name`: The name of the signal aspect. The `main` and `dst` fields may be: * An positive number indicating the permitted speed, * The number 0, indicating that the train should expect to stop at the current signal (or, for the `dst` field, the next signal), -* The number -1, indicating that the train can proceed (or, for the `dst` field, expect to proceed) at maximum speed, or -* The constant `false` or `nil`, indicating no change to the speed restriction. +* The number -1, indicating that the train can proceed (or, for the `dst` field, expect to proceed) at maximum speed, +* The constant `false`, indicating no change to the speed restriction, or +* The constant `nil`, indicating that the default value for the name aspect (if present) is used. If no valid signal aspect is named, or the signal aspect does not provide a default value, the value is assumed to be `false`. ### Node definitions @@ -38,22 +33,19 @@ The node definition should contain an `advtrains` field. The `advtrains` field of the node definition should contain a `supported_aspects` table for signals with variable aspects. -For type 1 signals, the `supported_aspects` table should contain the following fields: +The `supported_aspects` table should contain the following fields: * `main`: A list of values supported for the main aspect. * `dst`: A list of values supported for the distant aspect. * `shunt`: The value for the `shunt` field of the signal aspect or `nil` if the value is variable. * `proceed_as_main`: The value for the `proceed_as_main` field of the signal aspect. - -For type 2 signals, the `supported_aspects` table should contain the following fields: - -* `type`: The numeric constant `2`. -* `group`: The type 2 signal group. +* `group`: The name of the signal group. +* `name`: A list of supported (named) aspects. * `dst_shift`: The phase shift for distant/repeater signals. This field should not be set for main signals. The `advtrains` field of the node definition should contain a `get_aspect` function. This function is given the position of the signal and the node at the position. It should return the signal aspect table or, in the case of type 2 signals, the name of the signal aspect. -For signals with variable aspects, a corresponding `set_aspect` function should also be defined. This function is given the position of the signal, the node at the position, and the new aspect (or, in the case of type 2 signals, the name of the new signal aspect). For type 1 signals, the new aspect is not guranteed to be supported by the signal itself. +For signals with variable aspects, a corresponding `set_aspect` function should also be defined. This function is given the position of the signal, the node at the position, and the new aspect. The new aspect is not guaranteed to be supported by the signal itself. Signals should also have the following callbacks set: @@ -63,23 +55,20 @@ Signals should also have the following callbacks set: Alternatively, custom callbacks should call the respective functions. -## Type 2 signal groups +## Signal groups -Type 2 signals belong to signal gruops, which are registered using `advtrains.interlocking.aspects.register_type2`. +Signals may belong to signal groups are registered using `advtrains.interlocking.aspect.register_group`. Signal group definitions include the following fields: * `name`: The internal name of the signal group. It is recommended to use the mod name as a prefix to avoid name collisions. * `label`: The description of the signal group. -* `main`: A list of signal aspects, from the least restrictive (i.e. proceed) to the most restrictive (i.e. danger). +* `aspects`: A table of signal aspects. Entries with string indices define the signal aspect with the name. Entries with numeric indices (starting from 1, counting upward) contain a list of corresponding aspect names (the first entry is preferred) and are mainly used for routing, where larger indices indicate that the signal with the aspect is closer to the signal with the "danger" (or similar) aspect. Each aspect in the signal group definition table should contain the following fields: -* `name`: The internal name of the signal aspect. * `label`: The description of the signal aspect. -* `main`, `shunt`, `proceed_as_main`: The fields corresponding to the ones in signal aspect tables. - -Type 2 signal aspects are then referred to with the aspect names within the group. +* `main`, `shunt`, `proceed_as_main`: The default values for the aspect. Note that the `dst` field has no default value as it is automatically adjusted. ## Notes @@ -89,8 +78,8 @@ It is allowed to provide other methods of setting the signal aspect. However: * Please call `advtrains.interlocking.signal_readjust_aspect` after the signal aspect has changed. ## Examples -An example of type 1 signals can be found in `advtrains_signals_ks`, which provides a subset of German signals. +An example of speed signals can be found in `advtrains_signals_ks`, which provides a subset of German signals. -An example of type 2 signals can be found in `advtrains_signals_japan`, which provides a subset of Japanese signals. +An example of route signals can be found in `advtrains_signals_japan`, which provides a subset of Japanese signals. The mods mentioned above are also used for demonstation purposes and can also be used for testing. diff --git a/advtrains_interlocking/aspect.lua b/advtrains_interlocking/aspect.lua new file mode 100644 index 0000000..1575fb1 --- /dev/null +++ b/advtrains_interlocking/aspect.lua @@ -0,0 +1,290 @@ +--- Signal aspect handling. +-- @module advtrains.interlocking.aspect + +local registered_groups = {} + +local named_aspect_aspfields = {main = true, shunt = true, proceed_as_main = true} + +local signal_aspect = {} + +local signal_aspect_metatable = { + __eq = function(asp1, asp2) + for _, k in pairs {"main", "dst", "shunt", "proceed_as_main"} do + local v1, v2 = (asp1[k] or false), (asp2[k] or false) + if v1 ~= v2 then + return false + end + end + if asp1.group and asp1.group == asp2.group then + return asp1.name == asp2.name + end + return true + end, + __index = function(asp, field) + local method = signal_aspect[field] + if method then + return method + end + if not named_aspect_aspfields[field] then + return nil + end + local group = registered_groups[rawget(asp, "group")] + if not group then + return false + end + local aspdef = group.aspects[rawget(asp, "name")] + if not aspdef then + return false + end + return aspdef[field] or false + end, + __tostring = function(asp) + local st = {} + if asp.group and asp.name then + table.insert(st, ("%q in %q"):format(asp.name, asp.group)) + end + if asp.main then + table.insert(st, ("current %d"):format(asp.main)) + end + if asp.main ~= 0 then + if asp.dst then + table.insert(st, string.format("next %d", asp.dst)) + end + end + if asp.main ~= 0 and asp.proceed_as_main then + table.insert(st, "proceed as main") + end + return ("[%s]"):format(table.concat(st, ", ")) + end, +} + +local function quicknew(t) + return setmetatable(t, signal_aspect_metatable) +end + +--- Signal aspect class. +-- @type signal_aspect + +--- Return a plain version of the signal aspect. +-- @param[opt=false] raw Bypass metamethods when fetching signal aspects +-- @return A plain copy of the signal aspect object. +function signal_aspect:plain(raw) + local t = {} + for _, k in pairs {"main", "dst", "shunt", "proceed_as_main", "group", "name"} do + local v + if raw then + v = rawget(self, k) + else + v = self[k] + end + t[k] = v + end + return t +end + +--- Create (or copy) a signal aspect object. +-- Note that signal aspect objects can also be created by calling the `advtrains.interlocking.aspect` table. +-- @return The newly created signal aspect object. +function signal_aspect:new() + if type(self) ~= "table" then + return quicknew{} + end + local newasp = {} + for _, k in pairs {"main", "dst"} do + if type(self[k]) == "table" then + if self[k].free then + newasp[k] = self[k].speed + else + newasp[k] = 0 + end + else + newasp[k] = self[k] + end + end + if type(self.shunt) == "table" then + newasp.shunt = self.shunt.free + newasp.proceed_as_main = self.shunt.proceed_as_main + else + newasp.shunt = self.shunt + end + for _, k in pairs {"group", "name"} do + newasp[k] = self[k] + end + return quicknew(newasp) +end + +--- Modify the signal aspect in-place to fit in the specific signal group. +-- @param group The signal group. The `nil` indicates a generic group. +-- @return The (now modified) signal aspect itself. +function signal_aspect:to_group(group) + local cg = self.group + local gdef = registered_groups[group] + if type(self.name) ~= "string" then + self.name = nil + end + if not gdef then + for k in pairs(named_aspect_aspfields) do + rawset(self, k, self[k]) + end + self.group = nil + self.name = nil + return self + elseif cg == group and gdef.aspects[self.name] then + return self + end + local newidx = 1 + if self.main == 0 then + newidx = #gdef.aspects + end + local cgdef = registered_groups[cg] + if cgdef then + local idx = (cgdef.aspects[self.name] or {}).index + if idx then + if idx >= #cgdef.aspects then + idx = #gdef.aspects + elseif idx >= #gdef.aspects then + idx = #gdef.aspects-1 + end + newidx = idx + end + end + self.group = group + self.name = group.aspects[newidx][1] + return self +end + +--- Modify the signal aspect in-place to indicate a specific distant aspect. +-- @param dst The distant aspect +-- @param[opt=1] shift The phase shift of the current signal. +-- @return The (now modified) signal aspect itself. +function signal_aspect:adjust_distant(dst, shift) + if (shift or -1) < 0 then + shift = 1 + end + if not dst then + self.dst = nil + return self + end + if self.main ~= 0 then + self.dst = dst.main + else + self.dst = nil + end + local dgdef = registered_groups[dst.group] + if dgdef then + if self.group == dst.group and shift == 0 then + self.name = dst.name + else + local idx = (dgdef.aspects[dst.name] or {}).index + if idx then + idx = math.max(idx-shift, 1) + self.group = dst.group + self.name = dgdef.aspects[idx][1] + end + end + end + return self +end + +--- Signal groups. +-- @section signal_group + +--- Register a signal group. +-- @function register_group +-- @param def The definition table. +local function register_group(def) + local t = {} + local name = def.name + if type(name) ~= "string" then + return error("Expected signal group name to be a string, got " .. type(name)) + elseif registered_groups[name] then + return error(string.format("Attempt to redefine signal group %q, previously defined in %s", name, registered_groups[name].defined)) + end + t.name = name + + t.defined = debug.getinfo(2, "S").short_src or "[?]" + + local label = def.label or name + if type(label) ~= "string" then + return error("Label is not a string") + end + t.label = label + + local mainasps = {} + for idx, asp in pairs(def.aspects) do + local idxtp = type(idx) + if idxtp == "string" then + local t = {} + t.name = idx + + local label = asp.label or idx + if type(label) ~= "string" then + return error("Aspect label is not a string") + end + t.label = label + + for k in pairs(named_aspect_aspfields) do + t[k] = asp[k] + end + + mainasps[idx] = t + end + end + if #def.aspects < 2 then + return error("Insufficient entries in signal aspect list") + end + for idx, asplist in ipairs(def.aspects) do + if type(asplist) ~= "table" then + asplist = {asplist} + else + asplist = table.copy(asplist) + end + if #asplist < 1 then + error("Invalid entry in signal aspect list") + end + for _, k in ipairs(asplist) do + if type(k) ~= "string" then + return error("Invalid signal aspect ID") + end + local asp = mainasps[k] + if not asp then + return error("Invalid signal aspect ID") + end + if asp.index ~= nil then + return error("Attempt to assign a signal aspect to multiple numeric indices") + end + asp.index = idx + end + mainasps[idx] = asplist + end + t.aspects = mainasps + + registered_groups[name] = t +end + +--- Get the definition of a signal group. +-- @function get_group_definition +-- @param name The name of the signal group. +-- @return[1] The definition for the signal group (if present). +-- @return[2] The nil constant (otherwise). +local function get_group_definition(name) + local t = registered_groups[name] + if t then + return table.copy(t) + else + return nil + end +end + +local lib = { + register_group = register_group, + get_group_definition = get_group_definition, +} + +local libmt = { + __call = function(_, ...) + return signal_aspect.new(...) + end, +} + +return setmetatable(lib, libmt) diff --git a/advtrains_interlocking/distant.lua b/advtrains_interlocking/distant.lua index 4175875..32ada82 100644 --- a/advtrains_interlocking/distant.lua +++ b/advtrains_interlocking/distant.lua @@ -6,7 +6,6 @@ local db_distant = {} local db_distant_of = {} -local A = advtrains.interlocking.aspects local pts = advtrains.encode_pos local stp = advtrains.decode_pos diff --git a/advtrains_interlocking/init.lua b/advtrains_interlocking/init.lua index 1a8ef07..4d959cc 100644 --- a/advtrains_interlocking/init.lua +++ b/advtrains_interlocking/init.lua @@ -12,7 +12,7 @@ end local modpath = minetest.get_modpath(minetest.get_current_modname()) .. DIR_DELIM -advtrains.interlocking.aspects = dofile(modpath.."signal_aspects.lua") +advtrains.interlocking.aspect = dofile(modpath.."aspect.lua") dofile(modpath.."database.lua") dofile(modpath.."distant.lua") diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 1b4a21c..ce8854a 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -19,27 +19,7 @@ advtrains.interlocking.FULL_FREE = { proceed_as_main = true, } -local function convert_aspect_if_necessary(asp) - if type(asp.main) == "table" then - local newasp = {} - if asp.main.free then - newasp.main = asp.main.speed - else - newasp.main = 0 - end - if asp.dst and asp.dst.free then - newasp.dst = asp.dst.speed - else - newasp.dst = 0 - end - newasp.proceed_as_main = asp.shunt.proceed_as_main - newasp.shunt = asp.shunt.free - -- Note: info table not transferred, it's not used right now - return newasp - end - return asp -end -advtrains.interlocking.signal_convert_aspect_if_necessary = convert_aspect_if_necessary +advtrains.interlocking.signal_convert_aspect_if_necessary = advtrains.interlocking.aspect function advtrains.interlocking.update_signal_aspect(tcbs, skipdst) if tcbs.signal then @@ -79,7 +59,7 @@ function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, end advtrains.interlocking.show_signal_form(pos, node, pname) end - + function advtrains.interlocking.show_signal_form(pos, node, pname) local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) if sigd then @@ -92,7 +72,7 @@ function advtrains.interlocking.show_signal_form(pos, node, pname) advtrains.interlocking.signal_set_aspect(pos, aspect) end local isasp = advtrains.interlocking.signal_get_aspect(pos, node) - + advtrains.interlocking.show_signal_aspect_selector( pname, ndef.advtrains.supported_aspects, @@ -123,7 +103,7 @@ local function ipmarker(ipos, connid) local node_ok, conns, rhe = advtrains.get_rail_info_at(ipos, advtrains.all_tracktypes) if not node_ok then return end local yaw = advtrains.dir_to_angle(conns[connid].c) - + -- using tcbmarker here local obj = minetest.add_entity(vector.add(ipos, {x=0, y=0.2, z=0}), "advtrains_interlocking:tcbmarker") if not obj then return end diff --git a/advtrains_interlocking/signal_aspect_accessors.lua b/advtrains_interlocking/signal_aspect_accessors.lua index e419515..d91df31 100644 --- a/advtrains_interlocking/signal_aspect_accessors.lua +++ b/advtrains_interlocking/signal_aspect_accessors.lua @@ -1,33 +1,12 @@ --- Signal aspect accessors -- @module advtrains.interlocking -local A = advtrains.interlocking.aspects +local A = advtrains.interlocking.aspect local D = advtrains.distant local I = advtrains.interlocking local N = advtrains.ndb local pts = advtrains.roundfloorpts -local signal_aspect_metatable = { - __tostring = function(asp) - local st = {} - if asp.type2group and asp.type2name then - table.insert(st, string.format("%q in group %q", asp.type2name, asp.type2group)) - end - if asp.main then - table.insert(st, string.format("current %d", asp.main)) - end - if asp.main ~= 0 then - if asp.dst then - table.insert(st, string.format("next %d", asp.dst)) - end - if asp.proceed_as_main then - table.insert(st, "proceed as main") - end - end - return string.format("[%s]", table.concat(st, ", ")) - end, -} - local get_aspect local supposed_aspects = {} @@ -37,9 +16,9 @@ local supposed_aspects = {} -- @param db The new database. function I.load_supposed_aspects(tbl) if tbl then - supposed_aspects = tbl - for _, v in pairs(tbl) do - setmetatable(v, signal_aspect_metatable) + supposed_aspects = {} + for k, v in pairs(tbl) do + supposed_aspects[k] = A(v) end end end @@ -48,7 +27,11 @@ end -- @function save_supposed_aspects -- @return The current database in use. function I.save_supposed_aspects() - return supposed_aspects + local t = {} + for k, v in pairs(supposed_aspects) do + t[k] = v:plain(true) + end + return t end --- Read the aspect of a signal strictly from cache. @@ -72,7 +55,7 @@ end -- @return[2] An empty table (otherwise). local function get_ndef(pos) local node = N.get_node(pos) - return minetest.registered_nodes[node.name] or {} + return (minetest.registered_nodes[node.name] or {}), node end --- Get the aspects supported by a signal. @@ -94,43 +77,18 @@ end -- @return The adjusted signal aspect. -- @return The information to pass to the `advtrains.set_aspect` field in the node definitions. local function adjust_aspect(pos, asp) - asp = table.copy(I.signal_convert_aspect_if_necessary(asp)) - setmetatable(asp, signal_aspect_metatable) + local asp = A(asp) local mainpos = D.get_main(pos) local nxtasp if mainpos then nxtasp = get_aspect(mainpos) end - if asp.main ~= 0 and mainpos then - asp.dst = nxtasp.main - else - asp.dst = nil - end - local suppasp = get_supported_aspects(pos) if not suppasp then - return asp, asp - end - local stype = suppasp.type - if stype == 2 then - local group = suppasp.group - local name - if suppasp.dst_shift and nxtasp then - asp.main = nil - name = A.type1_to_type2main(nxtasp, group, suppasp.dst_shift) - elseif asp.main ~= 0 and nxtasp and nxtasp.type2group == group and nxtasp.type2name then - name = A.get_type2_dst(group, nxtasp.type2name) - else - name = A.type1_to_type2main(asp, group) - end - asp.type2group = group - asp.type2name = name - return asp, name + return asp end - asp.type2name = nil - asp.type2group = nil - return asp, asp + return asp:adjust_distant(nxtasp, suppasp.dst_shift):to_group(suppasp.group) end --- Get the aspect of a signal without accessing the cache. @@ -140,13 +98,9 @@ end -- @return[1] The signal aspect adjusted using `adjust_aspect` (if present). -- @return[2] The nil constant (otherwise). local function get_real_aspect(pos) - local ndef = get_ndef(pos) + local ndef, node = get_ndef(pos) if ndef.advtrains and ndef.advtrains.get_aspect then local asp = ndef.advtrains.get_aspect(pos, node) or I.DANGER - local suppasp = get_supported_aspects(pos) - if suppasp and suppasp.type == 2 then - asp = A.type2_to_type1(suppasp, asp) - end return adjust_aspect(pos, asp) end return nil @@ -176,11 +130,11 @@ local function set_aspect(pos, asp, skipdst) local ndef = minetest.registered_nodes[node.name] if ndef and ndef.advtrains and ndef.advtrains.set_aspect then local oldasp = I.signal_get_aspect(pos) or DANGER - local newasp, aspval = adjust_aspect(pos, asp) + local newasp = adjust_aspect(pos, asp) set_supposed_aspect(pos, newasp) - ndef.advtrains.set_aspect(pos, node, aspval) + ndef.advtrains.set_aspect(pos, node, newasp) I.signal_on_aspect_changed(pos) - local aspect_changed = A.not_equalp(oldasp, newasp) + local aspect_changed = oldasp ~= newasp if (not skipdst) and aspect_changed then D.update_main(pos) end diff --git a/advtrains_interlocking/signal_aspect_ui.lua b/advtrains_interlocking/signal_aspect_ui.lua index 186d2fe..d36c6bc 100644 --- a/advtrains_interlocking/signal_aspect_ui.lua +++ b/advtrains_interlocking/signal_aspect_ui.lua @@ -1,7 +1,7 @@ local F = advtrains.formspec local players_aspsel = {} -local function describe_t1_main_aspect(spv) +local function describe_main_aspect(spv) if spv == 0 then return attrans("Danger (halt)") elseif spv == -1 then @@ -13,7 +13,7 @@ local function describe_t1_main_aspect(spv) end end -local function describe_t1_shunt_aspect(shunt) +local function describe_shunt_aspect(shunt) if shunt then return attrans("Shunting allowed") else @@ -21,7 +21,7 @@ local function describe_t1_shunt_aspect(shunt) end end -local function describe_t1_distant_aspect(spv) +local function describe_distant_aspect(spv) if spv == 0 then return attrans("Expect to stop at the next signal") elseif spv == -1 then @@ -33,9 +33,9 @@ local function describe_t1_distant_aspect(spv) end end -advtrains.interlocking.describe_t1_main_aspect = describe_t1_main_aspect -advtrains.interlocking.describe_t1_shunt_aspect = describe_t1_shunt_aspect -advtrains.interlocking.describe_t1_distant_aspect = describe_t1_distant_aspect +advtrains.interlocking.describe_main_aspect = describe_main_aspect +advtrains.interlocking.describe_shunt_aspect = describe_shunt_aspect +advtrains.interlocking.describe_distant_aspect = describe_distant_aspect local function dsel(p, q, x, y) if p == nil then @@ -51,19 +51,23 @@ local function dsel(p, q, x, y) end end -local function describe_supported_aspects_t1(suppasp, isasp) +local function describe_supported_aspects(suppasp, isasp) local t = {} - local entries = {} - local selid = 1 - for idx, spv in ipairs(suppasp.main) do - if isasp and spv == (isasp.main or false) then + local entries = {attrans("Use default value")} + local selid = 0 + local mainasps = suppasp.main + if type(mainasps) ~= "table" then + mainasps = {mainasps or false} + end + for idx, spv in ipairs(mainasps) do + if isasp and spv == rawget(isasp, "main") then selid = idx end - entries[idx] = describe_t1_main_aspect(spv) + entries[idx+1] = describe_main_aspect(spv) end t.main = entries - t.main_current = selid + t.main_current = selid+1 t.main_string = tostring(isasp.main) if t.main == nil then t.main_string = "" @@ -83,21 +87,21 @@ local function describe_supported_aspects_t1(suppasp, isasp) entries = {} selid = 1 - for idx, spv in ipairs(suppasp.dst) do + for idx, spv in ipairs(suppasp.dst or {}) do if isasp and spv == (isasp.dst or false) then selid = idx end - entries[idx] = describe_t1_distant_aspect(spv) + entries[idx] = describe_distant_aspect(spv) end t.dst = entries t.dst_current = selid return t end -advtrains.interlocking.describe_supported_aspects_t1 = describe_supported_aspects_t1 +advtrains.interlocking.describe_supported_aspects = describe_supported_aspects -local function make_signal_aspect_selector_t1(suppasp, purpose, isasp) - local t = describe_supported_aspects_t1(suppasp, isasp) +local function make_signal_aspect_selector(suppasp, purpose, isasp) + local t = describe_supported_aspects(suppasp, isasp) local formmode = 1 local pos @@ -142,55 +146,6 @@ local function make_signal_aspect_selector_t1(suppasp, purpose, isasp) return table.concat(form) end -local function make_signal_aspect_selector_t2(suppasp, purpose, isasp) - local def = advtrains.interlocking.aspects.get_type2_definition(suppasp.group) - if not def then - return nil - end - local formmode = 1 - - local pos - if type(purpose) == "table" then - formmode = 2 - pos = purpose.pos - end - local form = { - "formspec_version[4]", - string.format("size[8,%f]", ({4.25, 10.25})[formmode]), - F.S_label(0.5, 0.5, "Select signal aspect") - } - if formmode == 1 then - form[#form+1] = F.label(0.5, 1, purpose) - else - form[#form+1] = F.S_label(0.5, 1, "Signal at @1", minetest.pos_to_string(pos)) - end - - local entries = {} - local selid = #def.main - if isasp then - if isasp.type2name ~= def.main[selid].name then - selid = 1 - end - end - if selid > 1 then - selid = 2 - end - local entries = { - def.main[1].label, - def.main[#def.main].label, - } - form[#form+1] = F.S_label(0.5, 1.5, "Signal group: @1", def.label) - form[#form+1] = F.dropdown(0.5, 2, 7, "asp_sel", entries, selid, true) - form[#form+1] = F.S_button_exit(0.5, 3, 7, "asp_save", "Save signal aspect") - - if formmode == 2 then - form[#form+1] = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 4, 7) - form[#form+1] = advtrains.interlocking.make_dst_formspec_component(pos, 0.5, 5.5, 7, 4.25) - end - - return table.concat(form) -end - function advtrains.interlocking.show_signal_aspect_selector(pname, p_suppasp, p_purpose, callback, isasp) local suppasp = p_suppasp or { main = {0, -1}, @@ -205,18 +160,7 @@ function advtrains.interlocking.show_signal_aspect_selector(pname, p_suppasp, p_ purpose = {pname = pname, pos = pos} end - local form - if suppasp.type == 2 then - if suppasp.dst_shift then - if pos then - advtrains.interlocking.show_ip_form(pos, pname) - end - return - end - form = make_signal_aspect_selector_t2(suppasp, purpose, isasp) - else - form = make_signal_aspect_selector_t1(suppasp, purpose, isasp) - end + local form = make_signal_aspect_selector(suppasp, purpose, isasp) if not form then return end @@ -241,9 +185,9 @@ local function usebool(sup, val, free) end end -local function get_aspect_from_formspec_t1(suppasp, fields, psl) +local function get_aspect_from_formspec(suppasp, fields, psl) local maini = tonumber(fields.asp_mainsel) - local main = suppasp.main[maini] + local main = suppasp.main[(maini or 0)-1] if not maini then local mainval = fields.asp_mainval if mainval == "-1" then @@ -253,6 +197,8 @@ local function get_aspect_from_formspec_t1(suppasp, fields, psl) else main = nil end + elseif maini <= 1 then + main = nil end local shunti = tonumber(fields.asp_shunt) local shunt = suppasp.shunt @@ -271,19 +217,6 @@ local function get_aspect_from_formspec_t1(suppasp, fields, psl) } end -local function get_aspect_from_formspec_t2(suppasp, fields, psl) - local sel = tonumber(fields.asp_sel) - local def = advtrains.interlocking.aspects.get_type2_definition(suppasp.group) - if not (sel and def) then - return - end - if sel ~= 1 then - sel = #def.main - end - local asp = advtrains.interlocking.aspects.type2_to_type1(suppasp, sel) - return asp -end - minetest.register_on_player_receive_fields(function(player, formname, fields) local pname = player:get_player_name() local psl = players_aspsel[pname] @@ -292,11 +225,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) local suppasp = psl.suppasp if fields.asp_save then local asp - if suppasp.type == 2 then - asp = get_aspect_from_formspec_t2(suppasp, fields, psl) - else - asp = get_aspect_from_formspec_t1(suppasp, fields, psl) - end + asp = get_aspect_from_formspec(suppasp, fields, psl) if asp then psl.callback(pname, asp) end diff --git a/advtrains_interlocking/signal_aspects.lua b/advtrains_interlocking/signal_aspects.lua deleted file mode 100644 index 14e04c7..0000000 --- a/advtrains_interlocking/signal_aspects.lua +++ /dev/null @@ -1,202 +0,0 @@ ---- Signal aspect handling. --- @module advtrains.interlocking.aspects - -local type2defs = {} - ---- Register a type 2 signal group. --- @function register_type2 --- @param def The definition table. -local function register_type2(def) - local t = {type = 2} - local name = def.name - if type(name) ~= "string" then - return error("Name is not a string") - elseif type2defs[name] then - return error(string.format("Attempt to redefine type 2 signal aspect group %q, previously defined in %s", name, type2defs[name].defined)) - end - t.name = name - - t.defined = debug.getinfo(2, "S").short_src or "[?]" - - local label = def.label or name - if type(label) ~= "string" then - return error("Label is not a string") - end - t.label = label - - local mainasps = {} - for idx, asp in ipairs(def.main) do - local t = {} - local name = asp.name - if type(name) ~= "string" then - return error("Aspect name is not a string") - end - t.name = name - - local label = asp.label or name - if type(label) ~= "string" then - return error("Aspect label is not a string") - end - t.label = label - - t.main = asp.main - t.shunt = asp.shunt - t.proceed_as_main = asp.proceed_as_main - mainasps[idx] = t - mainasps[name] = idx - end - t.main = mainasps - - type2defs[name] = t -end - ---- Get the definition of a type 2 signal group. --- @function get_type2_definition --- @param name The name of the signal group. --- @return[1] The definition for the signal group (if present). --- @return[2] The nil constant (otherwise). -local function get_type2_definition(name) - local t = type2defs[name] - if t then - return table.copy(t) - else - return nil - end -end - ---- Get the name of the distant aspect before the current aspect. --- @function get_type2_dst --- @param group The name of the group. --- @param name The name of the current aspect. --- @return[1] The name of the distant aspect (if present). --- @return[2] The nil constant (otherwise). -local function get_type2_dst(group, name) - local def = type2defs[group] - if not def then - return nil - end - local aspidx = name - if type(name) ~= "number" then - aspidx = def.main[name] or 1 - end - return def.main[math.max(1, aspidx-1)].name -end - ---- Convert a type 2 signal aspect to a type 1 signal aspect. --- @function type2_to_type1 --- @param suppasp The table of supported aspects for the signal. --- @param asp The name of the signal aspect. --- @return[1] The type 1 signal aspect table (if present). --- @return[2] The nil constant (otherwise). -local function type2_to_type1(suppasp, asp) - local name = suppasp.group - local shift = suppasp.dst_shift - local def = type2defs[name] - if not def then - return nil - end - local aspidx - if type(asp) == "number" then - aspidx = asp - else - aspidx = def.main[asp] or 2 - end - local realidx = math.min(#def.main, aspidx+(shift or 0)) - local asptbl = def.main[realidx] - if not asptbl then - return nil - end - if type(asp) == "number" then - asp = asptbl.name - end - local main, shunt, dst - if shift then - dst = asptbl.main - else - main = asptbl.main - shunt = asptbl.shunt - dst = def.main[math.min(#def.main, aspidx+1)].main - end - if main == 0 then - dst = nil - end - - local t = { - main = main, - shunt = shunt, - proceed_as_main = asptbl.proceed_as_main, - type2name = asptbl.name, - type2group = name, - dst = dst, - } - if aspidx > 1 and aspidx < #asptbl then - t.dst = asptbl[aspidx+1].main - end - return t -end - ---- Convert a type 1 signal aspect table to a type 2 signal aspect. --- @function type1_to_type2main --- @param asp The type 1 signal aspect table --- @param group The signal aspect group --- @param[opt=0] shift The shift for the signal aspect. --- @return[1] The name of the signal aspect (if present). --- @return[2] The nil constant (otherwise). -local function type1_to_type2main(asp, group, shift) - local def = type2defs[group] - if not def then - return nil - end - local t_main = def.main - local idx - if group == asp.type2group and t_main[asp.type2name] then - idx = t_main[asp.type2name] - elseif not asp.main or asp.main == -1 then - idx = 1 - elseif asp.main == 0 then - idx = #t_main - else - idx = #t_main-1 - end - return t_main[math.max(1, idx-(shift or 0))].name -end - ---- Compare two type 1 signal aspect tables. --- @function equalp --- @param asp1 The first signal aspect table. --- @param asp2 The second signal aspect table. --- @return Whether the two signal aspect tables give the same (type 1 aspect) information. -local function equalp(asp1, asp2) - if asp1 == asp2 then -- same reference - return true - else - for _, k in pairs {"main", "shunt", "dst"} do - if asp1[k] ~= asp2[k] then - return false - end - end - end - if asp1.type2group and asp1.type2group == asp2.type2group then - return asp1.type2name == asp2.type2name - end - return true -end - ---- Compare two signal aspect tables. --- @function not_equalp --- @param asp1 The first signal aspect table. --- @param asp2 The second signal aspect table. --- @return The negation of `equalp``(asp1, asp2)`. -local function not_equalp(asp1, asp2) - return not equalp(asp1, asp2) -end - -return { - register_type2 = register_type2, - get_type2_definition = get_type2_definition, - get_type2_dst = get_type2_dst, - type2_to_type1 = type2_to_type1, - type1_to_type2main = type1_to_type2main, - equalp = equalp, - not_equalp = not_equalp, -} diff --git a/advtrains_interlocking/spec/basic_signalling_spec.lua b/advtrains_interlocking/spec/basic_signalling_spec.lua index cce0f15..a4e1e3a 100644 --- a/advtrains_interlocking/spec/basic_signalling_spec.lua +++ b/advtrains_interlocking/spec/basic_signalling_spec.lua @@ -8,7 +8,7 @@ mineunit("core") _G.advtrains = { interlocking = { - aspects = fixture("../../signal_aspects"), + aspect = fixture("../../aspect"), }, ndb = { get_node = minetest.get_node, @@ -31,12 +31,16 @@ minetest.register_node("advtrains_interlocking:signal_sign", { local D = advtrains.distant local I = advtrains.interlocking +local A = I.aspect local stub_aspect_t1 = { free = {main = -1}, slow = {main = 6}, danger = {main = 0, shunt = false}, } +for k, v in pairs(stub_aspect_t1) do + stub_aspect_t1[k] = A(v) +end local stub_pos_t1 = {} for i = 1, 4 do stub_pos_t1[i] = {x = 1, y = 0, z = i} @@ -55,14 +59,14 @@ describe("API for supposed signal aspects", function() I.load_supposed_aspects(tbl) assert.same(tbl, I.save_supposed_aspects()) end) - it("should set and get type 1 signals properly", function () + it("should set and get signals properly", function () local pos = stub_pos_t1[2] local asp = stub_aspect_t1.slow - local newasp = { main = math.random(1,5) } - assert.same(asp, I.signal_get_aspect(pos)) + local newasp = A{ main = math.random(1,5) } + assert.equal(asp, I.signal_get_aspect(pos)) I.signal_set_aspect(pos, newasp) - assert.same(newasp, I.signal_get_aspect(pos)) - assert.same(asp, I.signal_get_real_aspect(pos)) + assert.equal(newasp, I.signal_get_aspect(pos)) + assert.equal(asp, I.signal_get_real_aspect(pos)) I.signal_set_aspect(pos, asp) end) end) @@ -72,9 +76,9 @@ describe("Distant signaling", function() for i = 1, 2 do D.assign(stub_pos_t1[i], stub_pos_t1[i+1]) end - assert.same(stub_aspect_t1.danger, I.signal_get_aspect(stub_pos_t1[1])) - assert.same({main = 6, dst = 0}, I.signal_get_aspect(stub_pos_t1[2])) - assert.same({main = -1, dst = 6}, I.signal_get_aspect(stub_pos_t1[3])) + assert.equal(stub_aspect_t1.danger, I.signal_get_aspect(stub_pos_t1[1])) + assert.equal(A{main = 6, dst = 0}, I.signal_get_aspect(stub_pos_t1[2])) + assert.equal(A{main = -1, dst = 6}, I.signal_get_aspect(stub_pos_t1[3])) end) it("should report assignments properly", function() assert.same({stub_pos_t1[1], "manual"}, {D.get_main(stub_pos_t1[2])}) @@ -82,8 +86,8 @@ describe("Distant signaling", function() end) it("should update distant aspects automatically", function() I.signal_set_aspect(stub_pos_t1[2], {main = 2, dst = -1}) - assert.same({main = 2, dst = 0}, I.signal_get_aspect(stub_pos_t1[2])) - assert.same({main = -1, dst = 2}, I.signal_get_aspect(stub_pos_t1[3])) + assert.equal(A{main = 2, dst = 0}, I.signal_get_aspect(stub_pos_t1[2])) + assert.equal(A{main = -1, dst = 2}, I.signal_get_aspect(stub_pos_t1[3])) end) it("should unassign signals when one is removed", function() world.set_node(stub_pos_t1[2], "air") diff --git a/advtrains_interlocking/spec/signal_group_spec.lua b/advtrains_interlocking/spec/signal_group_spec.lua new file mode 100644 index 0000000..bc9d007 --- /dev/null +++ b/advtrains_interlocking/spec/signal_group_spec.lua @@ -0,0 +1,95 @@ +require "mineunit" +mineunit("core") + +_G.advtrains = { + interlocking = { + aspect = sourcefile("aspect"), + }, + ndb = { + get_node = minetest.get_node, + swap_node = minetest.swap_node, + } +} + +fixture("advtrains_helpers") +sourcefile("database") +sourcefile("signal_api") +sourcefile("distant") +sourcefile("signal_aspect_accessors") + +local A = advtrains.interlocking.aspect +local D = advtrains.distant +local I = advtrains.interlocking +local N = advtrains.ndb + +local groupdef = { + name = "foo", + aspects = { + proceed = {main = -1}, + caution = {}, + danger = {main = 0}, + "proceed", + {"caution"}, + "danger", + }, +} + +for k, v in pairs(groupdef.aspects) do + minetest.register_node("advtrains_interlocking:" .. k, { + advtrains = { + supported_aspects = { + group = "foo", + }, + get_aspect = function() return A{group = "foo", name = k} end, + set_aspect = function(pos, _, name) + N.swap_node(pos, {name = "advtrains_interlocking:" .. name}) + end, + } + }) +end + +local origin = vector.new(0, 0, 0) +local dstpos = vector.new(0, 0, 1) + +world.layout { + {origin, "advtrains_interlocking:danger"}, + {dstpos, "advtrains_interlocking:proceed"}, +} + +describe("signal group registration", function() + it("should work", function() + A.register_group(groupdef) + assert(A.get_group_definition("foo")) + end) + it("should only be allowed once for the same group", function() + assert.has.errors(function() A.register_group(type2def) end) + end) + it("should handle nonexistant groups", function() + assert.is_nil(A.get_group_definition("something_else")) + end) + it("should reject invalid definitions", function() + assert.has.errors(function() A.register_group({}) end) + assert.has.errors(function() A.register_group({name="",label={}}) end) + assert.has.errors(function() A.register_group({name="",aspects={}}) end) + end) +end) + +describe("signal aspect", function() + it("should handle empty fields properly", function() + assert.equal(A{main = 0}, A{group="foo", name="danger"}:to_group()) + end) + it("should be converted properly", function() + assert.equal(A{main = 0}, A{group="foo", name="danger"}) + assert.equal(A{}, A{group="foo", name="caution"}) + assert.equal(A{main = -1}, A{group="foo", name="proceed"}) + end) +end) + +describe("signals in groups", function() + it("should support distant signaling", function() + assert.equal("caution", A():adjust_distant(A{group="foo",name="danger"}).name) + assert.equal("proceed", A():adjust_distant(A{group="foo",name="caution"}).name) + assert.equal("proceed", A():adjust_distant(A{group="foo",name="proceed"}).name) + assert.equal("danger", A{group="foo",name="danger"}:adjust_distant{}.name) + end) +end) diff --git a/advtrains_interlocking/spec/type2_spec.lua b/advtrains_interlocking/spec/type2_spec.lua deleted file mode 100644 index ac23574..0000000 --- a/advtrains_interlocking/spec/type2_spec.lua +++ /dev/null @@ -1,117 +0,0 @@ -require "mineunit" -mineunit("core") - -_G.advtrains = { - interlocking = { - aspects = sourcefile("signal_aspects"), - }, - ndb = { - get_node = minetest.get_node, - swap_node = minetest.swap_node, - } -} - -fixture("advtrains_helpers") -sourcefile("database") -sourcefile("signal_api") -sourcefile("distant") -sourcefile("signal_aspect_accessors") - -local A = advtrains.interlocking.aspects -local D = advtrains.distant -local I = advtrains.interlocking -local N = advtrains.ndb - -local type2def = { - name = "foo", - main = { - {name = "proceed", main = -1}, - {name = "caution"}, - {name = "danger", main = 0}, - }, -} - -for _, v in pairs(type2def.main) do - minetest.register_node("advtrains_interlocking:" .. v.name, { - advtrains = { - supported_aspects = { - type = 2, - group = "foo", - }, - get_aspect = function() return v.name end, - set_aspect = function(pos, _, name) - N.swap_node(pos, {name = "advtrains_interlocking:" .. name}) - end, - } - }) -end - -local function asp(group, name, dst) - return A.type2_to_type1({group = group, dst_shift = shift}, name) -end - -local origin = vector.new(0, 0, 0) -local dstpos = vector.new(0, 0, 1) - -world.layout { - {origin, "advtrains_interlocking:danger"}, - {dstpos, "advtrains_interlocking:proceed"}, -} - -describe("type 2 signal group registration", function() - it("should work", function() - A.register_type2(type2def) - assert(A.get_type2_definition("foo")) - end) - it("should only be allowed once for the same group", function() - assert.has.errors(function() A.register_type2(type2def) end) - end) - it("should handle nonexistant groups", function() - assert.is_nil(A.get_type2_definition("something_else")) - end) - it("should reject invalid definitions", function() - assert.has.errors(function() A.register_type2({}) end) - assert.has.errors(function() A.register_type2({name="",label={}}) end) - assert.has.errors(function() A.register_type2({name="",main={{name={}}}}) end) - assert.has.errors(function() A.register_type2({name="",main={{name="",label={}}}}) end) - end) -end) - -describe("signal aspect conversion", function() - it("should work for converting from type 1 to type 2", function() - assert.equal("danger", A.type1_to_type2main({main = 0}, "foo")) - assert.equal("caution", A.type1_to_type2main({main = 6}, "foo")) - assert.equal("proceed", A.type1_to_type2main({}, "foo")) - end) - it("should reject invalid type 2 signal information", function() - assert.is_nil(A.type1_to_type2main({}, "?")) - assert.is_nil(A.type2_to_type1({}, "x")) - assert.same(asp("foo","caution"), asp("foo", "x")) - end) - it("should accept integer indices for type 2 signal aspects", function() - assert.same(asp("foo", "caution"), asp("foo", 2)) - assert.same(asp("foo", "danger"), asp("foo", 10)) - assert.same(asp("foo", "proceed"), asp("foo", 1)) - assert.is_nil(asp("foo", -0.5)) - end) -end) - -describe("type 2 signals", function() - it("should support distant signaling", function() - assert.equal("caution", A.get_type2_dst("foo", 3)) - assert.equal("proceed", A.get_type2_dst("foo", "caution")) - assert.equal("proceed", A.get_type2_dst("foo", "proceed")) - end) - it("should work with accessors", function() - assert.same(asp("foo","danger"), I.signal_get_aspect(origin)) - local newasp = {type2group = "foo", type2name = "proceed", main = 6} - I.signal_set_aspect(origin, newasp) - assert.same(newasp, I.signal_get_aspect(origin)) - end) - it("should work with distant signaling", function() - assert.same(asp("foo","proceed"), I.signal_get_aspect(dstpos)) - local dstasp = {type2group = "foo", type2name = "proceed", dst = 6, main = -1} - D.assign(origin, dstpos) - assert.same(dstasp, I.signal_get_aspect(dstpos)) - end) -end) diff --git a/advtrains_signals_japan/init.lua b/advtrains_signals_japan/init.lua index fe74259..7d8dc1e 100644 --- a/advtrains_signals_japan/init.lua +++ b/advtrains_signals_japan/init.lua @@ -291,10 +291,10 @@ local aspnames = { } local function process_signal(name, sigdata, isrpt) local typename = "advtrains_signals_japan:" .. name - local type2def = {} - type2def.name = typename - type2def.main = {} - type2def.label = S(string.format("Japanese signal (type %s)", string.upper(name))) + local groupdef = {} + groupdef.name = typename + groupdef.aspects = {} + groupdef.label = S(string.format("Japanese signal (type %s)", string.upper(name))) local def = {} local tx = {} def.typename = typename @@ -322,7 +322,8 @@ local function process_signal(name, sigdata, isrpt) tt[#tt+1] = string.format("0,%d=(advtrains_hud_bg.png\\^[colorize\\:%s)", lightcount-1, color) end tx[aspname] = table.concat(tt, ":") - type2def.main[idx] = {name = asp.name, label = S(aspnames[asp.name]), main = asp.main, proceed_as_main = true} + groupdef.aspects[idx] = {asp.name} + groupdef.aspects[asp.name] = {label = S(aspnames[asp.name]), main = asp.main, proceed_as_main = true} end local invimg = { string.format("[combine:%dx%d", lightcount*4+1, lightcount*4+1), @@ -337,7 +338,7 @@ local function process_signal(name, sigdata, isrpt) end def.inventory_image = table.concat(invimg, ":") if not isrpt then - advtrains.interlocking.aspects.register_type2(type2def) + advtrains.interlocking.aspect.register_group(groupdef) end return def end @@ -400,15 +401,14 @@ for _, rtab in ipairs { drop = "advtrains_signals_japan:"..sigtype.."_danger_0", advtrains = { supported_aspects = { - type = 2, group = siginfo.typename, dst_shift = siginfo.isdst and 0, }, get_aspect = function() - return asp + return {group = siginfo.typename, name = asp} end, set_aspect = function(pos, node, asp) - advtrains.ndb.swap_node(pos, {name = "advtrains_signals_japan:"..sigtype.."_"..asp.."_"..rot, param2 = node.param2}) + advtrains.ndb.swap_node(pos, {name = "advtrains_signals_japan:"..sigtype.."_"..(asp.name).."_"..rot, param2 = node.param2}) end, }, on_rightclick = advtrains.interlocking.signal_rc_handler, -- cgit v1.2.3 From aa9033f9010b01e7ad28f2b8bb0c002f065ddc08 Mon Sep 17 00:00:00 2001 From: orwell Date: Sun, 28 Jan 2024 23:42:56 +0100 Subject: Implementation Plan --- advtrains_interlocking/signal_api.lua | 53 +++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index ce8854a..743e8e1 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -19,6 +19,59 @@ advtrains.interlocking.FULL_FREE = { proceed_as_main = true, } +--[[ +Implementation plan orwell 2024-01-28: +Most parts of ywang's implementation are fine, especially I like the formspecs. But I would like to change a few aspects (no pun intended) of this. +- Signal gets distant assigned via field in signal aspect table (instead of explicitly) +- Signal speed/shunt are no longer free-text but rather they need to be predefined in the node definition +To do this: Differentiation between: +== Aspect Group == +This is what a signal is assigned by either the route system or the user. +It is a string key which has an appropriate entry in the node definition (where it has a description assigned) +The signal mod defines a function to set a signal to the most appropriate aspect. This function gets +a) the aspect group name +b) the distant signal's aspect group name & aspect table +EVERY signal must define the special aspect group "halt". This must always be the most restrictive aspect possible. +The "halt" aspect group should ignore any distant info, in most cases it is called without them anyway. + +== Aspect == +One concrete combination of lights/shapes that a signal signal shows. Handling these is at the discretion of +the signal mod defining the signal, and they are typically combinations of main aspect and distant aspect +Example: +- A Ks signal has the aspect_group="proceed_12" set for a route +- The signal at the end of the route shows aspect_group="proceed_8", advtrains also passes on that this means {main=8, shunt=false} +- The ndef.advtrains.apply_aspect(pos, asp_group, dst_aspgrp, dst_aspinfo) determines that the signal should now show + blinking green with main indicator 12 and dst indicator 8, and sets the nodes accordingly. + This function can now return the Aspect Info table, which will be cached by advtrains until the aspect changes again + and will be used when a train approaches the signal. If nil is returned, then the aspect will be queried next time + by calling ndef.advtrains.get_aspect_info(pos) + +Note that once apply_aspect returns, there is no need for advtrains anymore to query the aspect info. +When the signal, for any reason, wants to change its aspect by itself *without* going through the signal API then +it should update the aspect info cache by calling advtrains.interlocking.signal.update_aspect_info(pos) + +== Aspect Info == +The actual signal aspect in the already-known format. This is what the trains use to determine halt/proceed and speed. In this, the dst field has to be resolved. +asp = { + main = 0 (halt) / -1 (max speed) / false (no info) / (speed limit) + shunt = true (shunt free) / false (shunt not free) + proceed_as_main = true (shunt move can proceed and become train move when main!=0) / false (no) + dst = (like main, informative character, not actually used) +} + +Node definition of signals: +- The signal needs some logic to figure out, for each combination of its own aspect group and the distant signal's aspect, what aspect info it can/will show. +ndef.advtrains = { + aspect_groups = { [name] = { description = "Proceed at full speed", } } + apply_aspect = function(pos, asp_group, dst_aspgrp, dst_aspinfo) + -- set the node to show the desired aspect + -- called by advtrains when this signal's aspect group or the distant signal's aspect changes + -- MAY return the aspect_info. If it returns nil then get_aspect_info will be queried at a later point. + get_aspect_info(pos) + -- Returns the aspect info table (main, shunt, dst etc.)W +} +]] + advtrains.interlocking.signal_convert_aspect_if_necessary = advtrains.interlocking.aspect function advtrains.interlocking.update_signal_aspect(tcbs, skipdst) -- cgit v1.2.3 From 2dab59f05572fe6cf73cde353446a5a501550b41 Mon Sep 17 00:00:00 2001 From: orwell Date: Tue, 6 Feb 2024 21:10:40 +0100 Subject: Start changing APIs and applying proof-of-concept to ks signals --- advtrains_interlocking/init.lua | 2 +- advtrains_interlocking/signal_api.lua | 105 +++++++++++++++++--------- advtrains_signals_ks/init.lua | 138 +++++++++++++++++++++------------- 3 files changed, 156 insertions(+), 89 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/init.lua b/advtrains_interlocking/init.lua index 4d959cc..9aa0c06 100644 --- a/advtrains_interlocking/init.lua +++ b/advtrains_interlocking/init.lua @@ -12,7 +12,7 @@ end local modpath = minetest.get_modpath(minetest.get_current_modname()) .. DIR_DELIM -advtrains.interlocking.aspect = dofile(modpath.."aspect.lua") +--advtrains.interlocking.aspect = dofile(modpath.."aspect.lua") dofile(modpath.."database.lua") dofile(modpath.."distant.lua") diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 743e8e1..c2cc08b 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -2,18 +2,19 @@ local F = advtrains.formspec -local DANGER = { - main = 0, - shunt = false, +local signal = {} + +signal.MASP_HALT = { + name = "halt" + halt = true, } -advtrains.interlocking.DANGER = DANGER -advtrains.interlocking.GENERIC_FREE = { - main = -1, +signal.ASPI_HALT = { + main = 0, shunt = false, - dst = false, } -advtrains.interlocking.FULL_FREE = { + +signal.ASPI_FREE = { main = -1, shunt = false, proceed_as_main = true, @@ -25,14 +26,12 @@ Most parts of ywang's implementation are fine, especially I like the formspecs. - Signal gets distant assigned via field in signal aspect table (instead of explicitly) - Signal speed/shunt are no longer free-text but rather they need to be predefined in the node definition To do this: Differentiation between: -== Aspect Group == +== Main Aspect == This is what a signal is assigned by either the route system or the user. It is a string key which has an appropriate entry in the node definition (where it has a description assigned) The signal mod defines a function to set a signal to the most appropriate aspect. This function gets -a) the aspect group name +a) the main aspect table (straight from node def) b) the distant signal's aspect group name & aspect table -EVERY signal must define the special aspect group "halt". This must always be the most restrictive aspect possible. -The "halt" aspect group should ignore any distant info, in most cases it is called without them anyway. == Aspect == One concrete combination of lights/shapes that a signal signal shows. Handling these is at the discretion of @@ -50,6 +49,11 @@ Note that once apply_aspect returns, there is no need for advtrains anymore to q When the signal, for any reason, wants to change its aspect by itself *without* going through the signal API then it should update the aspect info cache by calling advtrains.interlocking.signal.update_aspect_info(pos) +Note that the apply_aspect function MUST accept the following main aspect, even if it is not defined in the main_aspects table: +{ name = "halt", halt = true } +It should cause the signal to show its most restrictive aspect. Typically it is a halt aspect, but e.g. for distant-only +signals this would be "expect stop". + == Aspect Info == The actual signal aspect in the already-known format. This is what the trains use to determine halt/proceed and speed. In this, the dst field has to be resolved. asp = { @@ -62,7 +66,10 @@ asp = { Node definition of signals: - The signal needs some logic to figure out, for each combination of its own aspect group and the distant signal's aspect, what aspect info it can/will show. ndef.advtrains = { - aspect_groups = { [name] = { description = "Proceed at full speed", } } + main_aspects = { + { name = "proceed" description = "Proceed at full speed", } + { name = "proceed2" description = "Proceed at full speed", } + } -- The numerical order determines the layout of the list in the selection dialog. apply_aspect = function(pos, asp_group, dst_aspgrp, dst_aspinfo) -- set the node to show the desired aspect -- called by advtrains when this signal's aspect group or the distant signal's aspect changes @@ -72,28 +79,61 @@ ndef.advtrains = { } ]] -advtrains.interlocking.signal_convert_aspect_if_necessary = advtrains.interlocking.aspect +-- Set a signal's aspect. +-- Signal aspects should only be set through this function. It takes care of: +-- - Storing the main aspect and dst pos for this signal permanently (until next change) +-- - Assigning the distant signal for this signal +-- - Calling apply_aspect() in the signal's node definition to make the signal show the aspect +-- - Calling apply_aspect() again whenever the distant signal changes its aspect +-- - Notifying this signal's distant signals about changes to this signal (unless skip_dst_notify is specified) +function signal.set_aspect(pos, main_aspect, dst_pos, skip_dst_notify) + -- TODO +end + +-- Gets the stored main aspect and distant signal position for this signal +-- This information equals the information last passed to set_aspect +-- It does not take into consideration the actual speed signalling, please use +-- get_aspect_info() for this +-- returns: main_aspect, dst_pos +function signal.get_aspect(pos) + --TODO +end + +function signal.get_distant_signals_of(pos) + --TODO +end -function advtrains.interlocking.update_signal_aspect(tcbs, skipdst) +-- Called when either this signal has changed its main aspect +-- or when this distant signal's currently assigned main signal has changed its aspect +-- It retrieves the signal's main aspect and aspect info and calls apply_aspect of the node definition +-- to update the signal's appearance and aspect info +-- pts: The signal position to update as encoded_pos +function signal.reapply_aspect(pts, p_mainaspect) + --TODO +end + +-- Update this signal's aspect based on the set route +-- +function signal.update_route_aspect(tcbs, skip_dst_notify) if tcbs.signal then - local asp = tcbs.aspect or DANGER - advtrains.interlocking.signal_set_aspect(tcbs.signal, asp, skipdst) + local asp = tcbs.aspect or signal.MASP_HALT + signal.set_aspect(tcbs.signal, asp, skip_dst_notify) end end -function advtrains.interlocking.signal_can_dig(pos) +function signal.can_dig(pos) return not advtrains.interlocking.db.get_sigd_for_signal(pos) end function advtrains.interlocking.signal_after_dig(pos) -- clear influence point - advtrains.interlocking.signal_clear_aspect(pos) - advtrains.distant.unassign_all(pos, true) + advtrains.distant.unassign_all(pos, true) -- TODO end --- should be called when aspect has changed on this signal. -function advtrains.interlocking.signal_on_aspect_changed(pos) +-- Update waiting trains and distant signals about a changed signal aspect +function signal.notify_on_aspect_changed(pos, skip_dst_notify) + --TODO update distant? local ipts, iconn = advtrains.interlocking.db.get_ip_by_signalpos(pos) if not ipts then return end local ipos = minetest.string_to_pos(ipts) @@ -103,7 +143,7 @@ function advtrains.interlocking.signal_on_aspect_changed(pos) minetest.after(0, advtrains.invalidate_all_paths, ipos) end -function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, pointed_thing) +function signal.on_rightclick(pos, node, player, itemstack, pointed_thing) local pname = player:get_player_name() local control = player:get_player_control() if control.aux1 then @@ -122,7 +162,7 @@ function advtrains.interlocking.show_signal_form(pos, node, pname) if ndef.advtrains and ndef.advtrains.set_aspect then -- permit to set aspect manually local function callback(pname, aspect) - advtrains.interlocking.signal_set_aspect(pos, aspect) + signal.set_aspect(pos, aspect) end local isasp = advtrains.interlocking.signal_get_aspect(pos, node) @@ -138,18 +178,6 @@ function advtrains.interlocking.show_signal_form(pos, node, pname) end end --- Returns the aspect the signal at pos is supposed to show -function advtrains.interlocking.signal_get_supposed_aspect(pos) - local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) - if sigd then - local tcbs = advtrains.interlocking.db.get_tcbs(sigd) - if tcbs.aspect then - return convert_aspect_if_necessary(tcbs.aspect) - end - end - return DANGER; -end - local players_assign_ip = {} local function ipmarker(ipos, connid) @@ -236,7 +264,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end) -- inits the signal IP assignment process -function advtrains.interlocking.signal_init_ip_assign(pos, pname) +function signal.init_ip_assign(pos, pname) if not minetest.check_player_privs(pname, "interlocking") then minetest.chat_send_player(pname, "Insufficient privileges to use this!") return @@ -281,3 +309,6 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) players_assign_ip[pname] = nil end end) + + +advtrains.interlocking.signal = signal diff --git a/advtrains_signals_ks/init.lua b/advtrains_signals_ks/init.lua index 7e285ae..c91b4ec 100755 --- a/advtrains_signals_ks/init.lua +++ b/advtrains_signals_ks/init.lua @@ -11,10 +11,9 @@ local function asp_to_zs3type(asp) return math.min(16,4*math.floor(n/4)) end -local function setzs3(msp, lim, rot) +local function setzs3(msp, asp, rot) local pos = {x = msp.x, y = msp.y+1, z = msp.z} local node = advtrains.ndb.get_node(pos) - local asp = asp_to_zs3type(lim) if node.name:find("^advtrains_signals_ks:zs3_") then advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:zs3_"..asp.."_"..rot, param2 = node.param2}) end @@ -50,67 +49,106 @@ local function getzs3v(msp) end local setaspectf = function(rot) - return function(pos, node, asp) - setzs3(pos, asp.main, rot) - if asp.main == 0 then - if asp.shunt then - advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_shunt_"..rot, param2 = node.param2}) - else - advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_danger_"..rot, param2 = node.param2}) - end + return function(pos, node, main_aspect, dst_aspect, dst_aspect_info) + -- set zs3 signal to show speed according to main_aspect + setzs3(pos, asp.zs3, rot) + -- select appropriate lamps based on mainaspect and dst + if main_aspect.shunt then + advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_shunt_"..rot, param2 = node.param2}) + setzs3v(pos, nil, rot) + elseif main_aspect.halt then + advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_danger_"..rot, param2 = node.param2}) setzs3v(pos, nil, rot) else - if not asp.dst or asp.dst == -1 then + if not dst_aspect_info + or not dst_aspect_info.main + or dst_aspect_info.main == -1 then advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_free_"..rot, param2 = node.param2}) - elseif asp.dst == 0 then + setzs3v(pos, nil, rot) + elseif dst_aspect_info.main == 0 then advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_slow_"..rot, param2 = node.param2}) + setzs3v(pos, nil, rot) else advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_nextslow_"..rot, param2 = node.param2}) + setzs3v(pos, dst_aspect_info.main, rot) end - setzs3v(pos, asp.dst, rot) end end end - -local suppasp = { - main = {0, 4, 6, 8, 12, 16, -1}, - dst = {0, 4, 6, 8, 12, 16, -1, false}, - shunt = nil, - proceed_as_main = true, - info = { - call_on = false, - dead_end = false, - w_speed = nil, - } +-- Main aspects main signal +-- These aspects tell only the speed signalization at this signal. +-- Actual signal aspect is chosen based on this and the Dst signal. +local mainaspects_main = { + { + name = "proceed" + description = "Proceed", + zs3 = "off" + }, + { + name = "shunt" + description = "Shunt", + zs3 = "off", + shunt = true, + }, + { + name = "proceed_16" + description = "Proceed (speed 16)", + zs3 = "16", + }, + { + name = "proceed_12" + description = "Proceed (speed 12)", + zs3 = "12", + }, + { + name = "proceed_8" + description = "Proceed (speed 8)", + zs3 = "8", + }, + { + name = "proceed_6" + description = "Proceed (speed 6)", + zs3 = "6", + }, + { + name = "proceed_4" + description = "Proceed (speed 4)", + zs3 = "4", + }, + { + name = "halt" + description = "Halt", + zs3 = "off", + halt = true, + }, } --Rangiersignal -local setaspectf_ra = function(rot) - return function(pos, node, asp) - if asp.shunt then +local applyaspectf_ra = function(rot) + -- we get here the full main_aspect table + return function(pos, node, main_aspect, dst_aspect, dst_aspect_info) + if main_aspect.shunt then advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:ra_shuntd_"..rot, param2 = node.param2}) else advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:ra_danger_"..rot, param2 = node.param2}) end - local meta = minetest.get_meta(pos) - if meta then - meta:set_string("infotext", minetest.serialize(asp)) - end end end -local suppasp_ra = { - main = { false }, - dst = { false }, - shunt = nil, - proceed_as_main = false, - - info = { - call_on = false, - dead_end = false, - w_speed = nil, - } +-- Main aspects shunt signal +-- Shunt signals have only two states, distant doesn't matter +local mainaspects_shunt = { + { + name = "shunt" + description = "Shunt", + shunt = true, + }, + { + name = "halt" + description = "Halt", + halt = true, + }, } advtrains.trackplacer.register_tracktype("advtrains_signals_ks:hs") @@ -192,9 +230,9 @@ for _, rtab in ipairs({ drop = "advtrains_signals_ks:hs_danger_0", inventory_image = "advtrains_signals_ks_hs_inv.png", advtrains = { - set_aspect = setaspectf(rot), - supported_aspects = suppasp, - get_aspect = afunc, + main_aspects = mainaspects_main + apply_aspect = applyaspectf_main(rot), + get_aspect_info = afunc, }, on_rightclick = advtrains.interlocking.signal_rc_handler, can_dig = advtrains.interlocking.signal_can_dig, @@ -235,11 +273,9 @@ for _, rtab in ipairs({ drop = "advtrains_signals_ks:ra_danger_0", inventory_image = "advtrains_signals_ks_ra_inv.png", advtrains = { - set_aspect = setaspectf_ra(rot), - supported_aspects = suppasp_ra, - get_aspect = function(pos, node) - return prts.asp - end, + main_aspects = mainaspects_ra, + apply_aspect = applyaspectf_ra(rot), + get_aspect_info = prts.asp, }, on_rightclick = advtrains.interlocking.signal_rc_handler, can_dig = advtrains.interlocking.signal_can_dig, @@ -276,7 +312,7 @@ for _, rtab in ipairs({ drop = "advtrains_signals_ks:"..prefix.."_"..dtyp.."_0", inventory_image = inv, advtrains = { - get_aspect = function() return asp end + get_aspect_info = asp }, on_rightclick = advtrains.interlocking.signal_rc_handler, can_dig = advtrains.interlocking.signal_can_dig, -- cgit v1.2.3 From eb03b5f301c4244bbc79a101dd9f990b01503ab5 Mon Sep 17 00:00:00 2001 From: orwell Date: Fri, 5 Apr 2024 00:35:40 +0200 Subject: Continue with new-ks rework --- advtrains_interlocking/database.lua | 13 +- advtrains_interlocking/signal_api.lua | 274 ++++++++++++++++++++++++++++++---- advtrains_signals_ks/init.lua | 2 +- 3 files changed, 250 insertions(+), 39 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index c5ae906..e23b0e5 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -131,12 +131,9 @@ function ildb.load(data) if data.npr_rails then advtrains.interlocking.npr_rails = data.npr_rails end - if data.supposed_aspects then - advtrains.interlocking.load_supposed_aspects(data.supposed_aspects) - end - if data.distant then - advtrains.distant.load(data.distant) - end + + -- let signal_api load data + advtrains.interlocking.signal.load(data) --COMPATIBILITY to Signal aspect format -- TODO remove in time... @@ -171,7 +168,7 @@ function ildb.load(data) end function ildb.save() - return { + local data = { tcbs = track_circuit_breaks, ts=track_sections, signalass = signal_assignments, @@ -182,6 +179,8 @@ function ildb.save() supposed_aspects = advtrains.interlocking.save_supposed_aspects(), distant = advtrains.distant.save(), } + advtrains.interlocking.signal.save(data) + return data end -- diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index c2cc08b..ce3b03f 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -5,7 +5,8 @@ local F = advtrains.formspec local signal = {} signal.MASP_HALT = { - name = "halt" + name = "halt", + description = "HALT", halt = true, } @@ -55,12 +56,12 @@ It should cause the signal to show its most restrictive aspect. Typically it is signals this would be "expect stop". == Aspect Info == -The actual signal aspect in the already-known format. This is what the trains use to determine halt/proceed and speed. In this, the dst field has to be resolved. +The actual signal aspect in the already-known format. This is what the trains use to determine halt/proceed and speed. asp = { main = 0 (halt) / -1 (max speed) / false (no info) / (speed limit) shunt = true (shunt free) / false (shunt not free) proceed_as_main = true (shunt move can proceed and become train move when main!=0) / false (no) - dst = (like main, informative character, not actually used) + dst = speed of the remote signal (like main, informative character, not actually used) } Node definition of signals: @@ -68,17 +69,84 @@ Node definition of signals: ndef.advtrains = { main_aspects = { { name = "proceed" description = "Proceed at full speed", } - { name = "proceed2" description = "Proceed at full speed", } - } -- The numerical order determines the layout of the list in the selection dialog. - apply_aspect = function(pos, asp_group, dst_aspgrp, dst_aspinfo) + { name = "reduced" description = "Proceed at reduced speed", } + } + -- This list is mainly for the selection dialog. Order of entries determines list order in the dropdown. + -- Some fields have special meaning: + -- name: A unique key to identify the main aspect. Only this key is saved, but APIs always receive the whole table + -- description: Text shown in UI dropdown + -- speed: a number. When present, a speed field is shown in the UI next to the dropdown (prefilled with the value). + -- When user selects a different speed there, this different speed replaces the value in the table whenever the main_aspect is applied. + -- Node can set any other fields at its discretion. They are not touched. + -- Note: On first call advtrains automatically inserts into the ndef.advtrains table a main_aspects_lookup hashtable + -- Note: Pure distant signals (that cannot show halt) should NOT have a main_aspects table + apply_aspect = function(pos, main_aspect, rem_aspect, rem_aspinfo) -- set the node to show the desired aspect - -- called by advtrains when this signal's aspect group or the distant signal's aspect changes + -- called by advtrains when this signal's aspect group or the remote signal's aspect changes -- MAY return the aspect_info. If it returns nil then get_aspect_info will be queried at a later point. - get_aspect_info(pos) - -- Returns the aspect info table (main, shunt, dst etc.)W + get_aspect_info(pos, main_aspect) + -- Returns the aspect info table (main, shunt, dst etc.) + distant_support = true or false + -- If true, signal is considered in distant signalling. If false or nil, rem_aspect and rem_aspinfo are never set. + route_role = one of "main", "shunt", "distant", "distant_repeater", "end" + -- Determines how the signal behaves when routes are set. Only in effect when signal is assigned to a TCB. + -- main: The signal is a possible endpoint for a train move route. Distant signals before it refer to it. + -- shunt: The signal is a possible endpoint for a shunt move route. Ignored for distant signals. + -- distant, distant_repeater: When route is set, signal is always assigned its first main aspect. The next signal with role="main" is set as the remote signal. (currently no further distinction) + -- end: like main, but signifies that it marks an end of track and trains cannot continue further. (currently no practical implications above main) } + +== Nomenclature == +The distant/main relation is named as follows: + V M +=====>====> +Main signal (main) always refers to the signal that is in focus right now (even if that is a distant-only signal) +From the standpoint of M, V is the distant (dst) signal. M does not need to concern itself with V's aspect but needs to notify V when it changes +From the standpoint of V, M is the remote (rem) signal. V needs to show an aspect that matches its remote signal M + +== Criteria for which signals are eligible for routes == + +All signals must define: +- get_aspect_info() + +Signals that can be assigned to a TCB must satisfy: +- apply_aspect() defined + +Signals that are possible start and end points for a route must satisfy: +- main_aspects defined (note, pure distant signals should therefore not define main_aspects) + ]] +-- Database +-- Signal Aspect store +-- Stores for each signal the main aspect and other info, like the assigned remote signal +-- [signal encodePos] = { main_aspect = "proceed", [speed = 12], [remote = encodedPos] } +signal.aspects = {} + +-- Distant signal notification. Records for each signal the distant signals that refer to it +-- Note: this mapping is weak. Needs always backreference check. +-- [signal encodePos] = { [distant signal encodePos] = true } +signal.distant_refs = {} + +function signal.load(data) + signal.aspects = data.aspects or {} + -- rebuild distant_refs after load + signal.distant_refs = {} + for main, aspt in pairs(signal.aspects) do + if aspt.remote then + if not signal.distant_refs[aspt.remote] then + signal.distant_refs[aspt.remote] = {} + end + signal.distant_refs[aspt.remote][main] = true + end + end +end + +function signal.save(data) + data.aspects = signal.aspects +end + + -- Set a signal's aspect. -- Signal aspects should only be set through this function. It takes care of: -- - Storing the main aspect and dst pos for this signal permanently (until next change) @@ -86,30 +154,184 @@ ndef.advtrains = { -- - Calling apply_aspect() in the signal's node definition to make the signal show the aspect -- - Calling apply_aspect() again whenever the distant signal changes its aspect -- - Notifying this signal's distant signals about changes to this signal (unless skip_dst_notify is specified) -function signal.set_aspect(pos, main_aspect, dst_pos, skip_dst_notify) - -- TODO +function signal.set_aspect(pos, main_asp_name, main_asp_speed, rem_pos, skip_dst_notify) + local main_pts = advtrains.encode_pos(pos) + local old_tbl = signal.aspects[main_pts] + local old_remote = old_tbl and old_tbl.remote + local new_remote = rem_pos and advtrains.encode_pos(rem_pos) + + -- if remote has changed, unregister from old remote + if old_remote and old_remote~=new_remote and signal.distant_refs[old_remote] then + signal.distant_refs[old_remote][main_pts] = nil + end + + signal.aspects[main_pts] = { main_aspect = main_asp_name, speed = main_asp_speed, remote = new_remote } + -- apply aspect on main signal, this also checks new_remote + signal.reapply_aspect(main_pts) + + -- notify my distants about this change (with limit 2) + if not skip_dst_notify then + signal.notify_distants_of(main_pts, 2) + end +end + +function signal.clear_aspect(pos, skip_dst_notify) + local main_pts = advtrains.encode_pos(pos) + local old_tbl = signal.aspects[main_pts] + local old_remote = old_tbl and old_tbl.remote + + -- unregister from old remote + if old_remote then + signal.distant_refs[old_remote][main_pts] = nil + end + + signal.aspects[main_pts] = nil + -- apply aspect on main signal, this also checks new_remote + signal.reapply_aspect(main_pts) + + -- notify my distants about this change (with limit 2) + if not skip_dst_notify then + signal.notify_distants_of(main_pts, 2) + end +end + +-- Notify distant signals of main_pts of a change in the aspect of this signal +-- +function signal.notify_distants_of(main_pts, limit) + if limit <= 0 then + return + end + local dstrefs = signal.distant_refs[main_pts] + if dstrefs then + for dst,_ in pairs(dstrefs) do + -- ensure that the backref is still valid + local dst_asp = signal.aspects[dst] + if dst_asp and dst_asp.remote == main_pts then + signal.reapply_aspect(dst) + signal.notify_distants_of(dst, limit - 1) + else + atwarn("Distant signal backref is not purged: main =",main_pts,", distant =",dst,", remote =",dst_asp.remote,"") + end + end + end +end + +function signal.notify_trains(pos) + local ipts, iconn = advtrains.interlocking.db.get_ip_by_signalpos(pos) + if not ipts then return end + local ipos = minetest.string_to_pos(ipts) + + -- FIXME: invalidate_all_paths_ahead does not appear to always work as expected + --advtrains.invalidate_all_paths_ahead(ipos) + minetest.after(0, advtrains.invalidate_all_paths, ipos) +end + +-- Update waiting trains and distant signals about a changed signal aspect +-- Must be called when a signal's aspect changes through some other means +-- and not via the signal mechanism +function signal.notify_on_aspect_changed(pos, skip_dst_notify) + signal.notify_trains(pos) + if not skip_dst_notify then + signal.notify_distants_of(advtrains.encode_pos(pos), 2) + end end -- Gets the stored main aspect and distant signal position for this signal -- This information equals the information last passed to set_aspect -- It does not take into consideration the actual speed signalling, please use -- get_aspect_info() for this +-- pos: the position of the signal -- returns: main_aspect, dst_pos function signal.get_aspect(pos) - --TODO + local aspt = signal.aspects[advtrains.encode_pos(pos)] + local ma,dp = signal.get_aspect_internal(pos, aspt) + return ma, advtrains.decode_pos(dp) +end + +local function cache_mainaspects(ndefat) + ndefat.main_aspects_lookup = { + -- always define halt aspect + halt = signal.MASP_HALT + } + for _,ma in ipairs(ndefat.main_aspects) then + ndefat.main_aspects_lookup[ma.name] = ma + end +end + +function signal.get_aspect_internal(pos, aspt) + if not aspt then + -- oh, no main aspect, nevermind + return nil, aspt.remote, nil + end + -- look aspect in nodedef + local node = advtrains.ndb.get_node_or_nil(pos) + local ndef = node and minetest.registered_nodes[node.name] + local ndefat = ndef and ndef.advtrains + -- only if signal defines main aspect and its set in aspt + if ndefat and ndefat.main_aspects and aspt.main_aspect then + if not ndefat.main_aspects_lookup then + cache_mainaspects(ndefat) + end + local masp = ndefat.main_aspects_lookup[aspt.name] + -- if speed, then apply speed + if masp.speed and aspt.speed then + masp = table.copy(masp) + masp.speed = aspt.speed + end + return masp, aspt.remote, ndef + end + -- invalid node or no main aspect, return nil for masp + return nil, aspt.remote, ndef end -function signal.get_distant_signals_of(pos) - --TODO +-- For the signal at pos, get the "aspect info" table. This contains the speed signalling information at this location +function signal.get_aspect_info(pos) + -- get aspect internal + local aspt = signal.aspects[advtrains.encode_pos(pos)] + local masp, remote, ndef = signal.get_aspect_internal(pos, aspt) + -- call into ndef + if ndef.advtrains and ndef.advtrains.get_aspect_info then + return ndef.advtrains.get_aspect_info(pos, masp) + end end + -- Called when either this signal has changed its main aspect -- or when this distant signal's currently assigned main signal has changed its aspect -- It retrieves the signal's main aspect and aspect info and calls apply_aspect of the node definition -- to update the signal's appearance and aspect info -- pts: The signal position to update as encoded_pos -function signal.reapply_aspect(pts, p_mainaspect) - --TODO +-- returns: the return value of the nodedef call which may be aspect_info +function signal.reapply_aspect(pts) + -- get aspt + local aspt = signal.aspects[pts] + if not aspt then + return -- oop, nothing to do + end + -- resolve mainaspect table by name + local pos = advtrains.decode_pos(pts) + -- note: masp may be nil, when aspt.main_aspect was nil. Valid case for distant-only signals + local masp, remote, ndef = signal.get_aspect_internal(pos, aspt) + -- if we have remote, resolve remote + local rem_masp, rem_aspi + if remote then + local rem_aspt = signal.aspects[remote] + if rem_aspt and rem_aspt.name then + local rem_pos = advtrains.decode_pos(remote) + rem_masp, _, rem_ndef = signal.get_aspect_internal(rem_pos, rem_aspt) + if rem_masp then + if rem_ndef.advtrains and rem_ndef.advtrains.get_aspect_info then + rem_aspi = rem_ndef.advtrains.get_aspect_info(pos, rem_masp) + end + end + end + end + -- call into ndef + if ndef.advtrains and ndef.advtrains.apply_aspect then + return ndef.advtrains.apply_aspect(pos, masp, rem_masp, rem_aspi) + end + -- notify trains + signal.notify_trains(pos) end -- Update this signal's aspect based on the set route @@ -121,26 +343,16 @@ function signal.update_route_aspect(tcbs, skip_dst_notify) end end + +---------------- + function signal.can_dig(pos) return not advtrains.interlocking.db.get_sigd_for_signal(pos) end function advtrains.interlocking.signal_after_dig(pos) - -- clear influence point - advtrains.interlocking.signal_clear_aspect(pos) - advtrains.distant.unassign_all(pos, true) -- TODO -end - --- Update waiting trains and distant signals about a changed signal aspect -function signal.notify_on_aspect_changed(pos, skip_dst_notify) - --TODO update distant? - local ipts, iconn = advtrains.interlocking.db.get_ip_by_signalpos(pos) - if not ipts then return end - local ipos = minetest.string_to_pos(ipts) - - -- FIXME: invalidate_all_paths_ahead does not appear to always work as expected - --advtrains.invalidate_all_paths_ahead(ipos) - minetest.after(0, advtrains.invalidate_all_paths, ipos) + -- TODO clear influence point + advtrains.interlocking.signal.clear_aspect(pos) end function signal.on_rightclick(pos, node, player, itemstack, pointed_thing) diff --git a/advtrains_signals_ks/init.lua b/advtrains_signals_ks/init.lua index c91b4ec..0a43db0 100755 --- a/advtrains_signals_ks/init.lua +++ b/advtrains_signals_ks/init.lua @@ -48,7 +48,7 @@ local function getzs3v(msp) return speed end -local setaspectf = function(rot) +local applyaspectf_main = function(rot) return function(pos, node, main_aspect, dst_aspect, dst_aspect_info) -- set zs3 signal to show speed according to main_aspect setzs3(pos, asp.zs3, rot) -- cgit v1.2.3 From 1f74697e85d456e97e201cdd9edef91a2df4fc14 Mon Sep 17 00:00:00 2001 From: orwell Date: Mon, 8 Apr 2024 21:46:43 +0200 Subject: Fully implement and test new aspect support/distant signalling on ks and japan signals --- advtrains_interlocking/signal_api.lua | 42 ++++++++++++++++++++++++----------- advtrains_signals_japan/init.lua | 36 ++++++++++++++++++++++++------ advtrains_signals_japan/mod.conf | 6 +++++ advtrains_signals_ks/init.lua | 26 +++++++++++----------- 4 files changed, 77 insertions(+), 33 deletions(-) create mode 100644 advtrains_signals_japan/mod.conf (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index ce3b03f..d27a045 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -80,7 +80,7 @@ ndef.advtrains = { -- Node can set any other fields at its discretion. They are not touched. -- Note: On first call advtrains automatically inserts into the ndef.advtrains table a main_aspects_lookup hashtable -- Note: Pure distant signals (that cannot show halt) should NOT have a main_aspects table - apply_aspect = function(pos, main_aspect, rem_aspect, rem_aspinfo) + apply_aspect = function(pos, node, main_aspect, rem_aspect, rem_aspinfo) -- set the node to show the desired aspect -- called by advtrains when this signal's aspect group or the remote signal's aspect changes -- MAY return the aspect_info. If it returns nil then get_aspect_info will be queried at a later point. @@ -120,7 +120,7 @@ Signals that are possible start and end points for a route must satisfy: -- Database -- Signal Aspect store -- Stores for each signal the main aspect and other info, like the assigned remote signal --- [signal encodePos] = { main_aspect = "proceed", [speed = 12], [remote = encodedPos] } +-- [signal encodePos] = { name = "proceed", [speed = 12], [remote = encodedPos] } signal.aspects = {} -- Distant signal notification. Records for each signal the distant signals that refer to it @@ -162,10 +162,11 @@ function signal.set_aspect(pos, main_asp_name, main_asp_speed, rem_pos, skip_dst -- if remote has changed, unregister from old remote if old_remote and old_remote~=new_remote and signal.distant_refs[old_remote] then + atdebug("unregister old remote: ",old_remote,"from",main_pts) signal.distant_refs[old_remote][main_pts] = nil end - signal.aspects[main_pts] = { main_aspect = main_asp_name, speed = main_asp_speed, remote = new_remote } + signal.aspects[main_pts] = { name = main_asp_name, speed = main_asp_speed, remote = new_remote } -- apply aspect on main signal, this also checks new_remote signal.reapply_aspect(main_pts) @@ -198,10 +199,12 @@ end -- Notify distant signals of main_pts of a change in the aspect of this signal -- function signal.notify_distants_of(main_pts, limit) + atdebug("notify_distants_of",advtrains.decode_pos(main_pts),"limit",limit) if limit <= 0 then return end local dstrefs = signal.distant_refs[main_pts] + atdebug("dstrefs",dstrefs,"") if dstrefs then for dst,_ in pairs(dstrefs) do -- ensure that the backref is still valid @@ -253,7 +256,7 @@ local function cache_mainaspects(ndefat) -- always define halt aspect halt = signal.MASP_HALT } - for _,ma in ipairs(ndefat.main_aspects) then + for _,ma in ipairs(ndefat.main_aspects) do ndefat.main_aspects_lookup[ma.name] = ma end end @@ -263,32 +266,37 @@ function signal.get_aspect_internal(pos, aspt) -- oh, no main aspect, nevermind return nil, aspt.remote, nil end + atdebug("get_aspect_internal",pos,aspt) -- look aspect in nodedef local node = advtrains.ndb.get_node_or_nil(pos) local ndef = node and minetest.registered_nodes[node.name] local ndefat = ndef and ndef.advtrains -- only if signal defines main aspect and its set in aspt - if ndefat and ndefat.main_aspects and aspt.main_aspect then + if ndefat and ndefat.main_aspects and aspt.name then if not ndefat.main_aspects_lookup then cache_mainaspects(ndefat) end local masp = ndefat.main_aspects_lookup[aspt.name] + if not masp then + atwarn(pos,"invalid main aspect",aspt.name,"valid are",ndefat.main_aspects_lookup) + return nil, aspt.remote, node, ndef + end -- if speed, then apply speed if masp.speed and aspt.speed then masp = table.copy(masp) masp.speed = aspt.speed end - return masp, aspt.remote, ndef + return masp, aspt.remote, node, ndef end -- invalid node or no main aspect, return nil for masp - return nil, aspt.remote, ndef + return nil, aspt.remote, node, ndef end -- For the signal at pos, get the "aspect info" table. This contains the speed signalling information at this location function signal.get_aspect_info(pos) -- get aspect internal local aspt = signal.aspects[advtrains.encode_pos(pos)] - local masp, remote, ndef = signal.get_aspect_internal(pos, aspt) + local masp, remote, node, ndef = signal.get_aspect_internal(pos, aspt) -- call into ndef if ndef.advtrains and ndef.advtrains.get_aspect_info then return ndef.advtrains.get_aspect_info(pos, masp) @@ -305,30 +313,38 @@ end function signal.reapply_aspect(pts) -- get aspt local aspt = signal.aspects[pts] + atdebug("reapply_aspect",advtrains.decode_pos(pts),"aspt",aspt) if not aspt then return -- oop, nothing to do end -- resolve mainaspect table by name local pos = advtrains.decode_pos(pts) - -- note: masp may be nil, when aspt.main_aspect was nil. Valid case for distant-only signals - local masp, remote, ndef = signal.get_aspect_internal(pos, aspt) + -- note: masp may be nil, when aspt.name was nil. Valid case for distant-only signals + local masp, remote, node, ndef = signal.get_aspect_internal(pos, aspt) -- if we have remote, resolve remote local rem_masp, rem_aspi if remote then + -- register in remote signal as distant + if not signal.distant_refs[remote] then + signal.distant_refs[remote] = {} + end + signal.distant_refs[remote][pts] = true local rem_aspt = signal.aspects[remote] + atdebug("resolving remote",advtrains.decode_pos(remote),"aspt",rem_aspt) if rem_aspt and rem_aspt.name then local rem_pos = advtrains.decode_pos(remote) - rem_masp, _, rem_ndef = signal.get_aspect_internal(rem_pos, rem_aspt) + rem_masp, _, _, rem_ndef = signal.get_aspect_internal(rem_pos, rem_aspt) if rem_masp then if rem_ndef.advtrains and rem_ndef.advtrains.get_aspect_info then - rem_aspi = rem_ndef.advtrains.get_aspect_info(pos, rem_masp) + rem_aspi = rem_ndef.advtrains.get_aspect_info(rem_pos, rem_masp) end end end end -- call into ndef + atdebug("applying to",pos,": main_asp",masp,"rem_masp",rem_masp,"rem_aspi",rem_aspi) if ndef.advtrains and ndef.advtrains.apply_aspect then - return ndef.advtrains.apply_aspect(pos, masp, rem_masp, rem_aspi) + ndef.advtrains.apply_aspect(pos, node, masp, rem_masp, rem_aspi) end -- notify trains signal.notify_trains(pos) diff --git a/advtrains_signals_japan/init.lua b/advtrains_signals_japan/init.lua index 84373a9..eb39f85 100644 --- a/advtrains_signals_japan/init.lua +++ b/advtrains_signals_japan/init.lua @@ -275,6 +275,7 @@ minetest.register_node("advtrains_signals_japan:pole_0", { drop = "advtrains_signals_japan:pole_0", }) +--[[ advtrains.interlocking.aspect.register_group { name = "advtrains_signals_japan:5a", label = S("Japanese signal"), @@ -301,7 +302,7 @@ advtrains.interlocking.aspect.register_group { "restrictedspeed", "danger", } -} +}]] local sigdefs = {} local lightcolors = { @@ -316,6 +317,7 @@ local function process_signal(name, sigdata, isrpt) def.textures = tx def.desc = sigdata.desc def.isdst = isrpt + def.aspects = sigdata.aspects local lights = sigdata.lights local lightcount = #lights if isrpt then @@ -359,16 +361,17 @@ for sigtype, sigdata in pairs { desc = "5A", lights = {"yellow", "yellow", "red", "yellow", "green"}, aspects = { - {name = "clear", lights = {5}, main = -1}, - {name = "reducedspeed", lights = {2, 5}}, - {name = "caution", lights = {4}}, - {name = "restrictedspeed", lights = {1, 4}}, - {name = "danger", lights = {3}, main = 0}, + {name = "clear", description = S"Clear (proceed)", lights = {5}, main = -1}, + {name = "reducedspeed", description = S"Reduced speed", lights = {2, 5}, main = 12}, + {name = "caution", description = S"Caution", lights = {4}}, + {name = "restrictedspeed", description = S"Restricted speed", lights = {1, 4}, main = 6}, + {name = "danger", description = S"Danger (halt)", lights = {3}, main = 0}, } } } do sigdefs["main_"..sigtype] = process_signal(sigtype, sigdata) - sigdefs["rpt_"..sigtype] = process_signal(sigtype, sigdata, true) + -- TODO re-enable this once ready + --sigdefs["rpt_"..sigtype] = process_signal(sigtype, sigdata, true) end for k in pairs(sigdefs) do @@ -412,6 +415,24 @@ for _, rtab in ipairs { inventory_image = siginfo.inventory_image, drop = "advtrains_signals_japan:"..sigtype.."_danger_0", advtrains = { + main_aspects = siginfo.aspects, + apply_aspect = function(pos, node, main_aspect, rem_aspect, rem_aspinfo) + local asp_name = main_aspect and main_aspect.name or "danger" + -- if this signal is clear and remote signal is restrictive (<= 10) then degrade to caution aspect + if not main_aspect or main_aspect.name == "halt" then + asp_name = "danger" + elseif main_aspect.name == "clear" and rem_aspinfo and rem_aspinfo.main and rem_aspinfo.main >= 0 and rem_aspinfo.main <= 10 then + asp_name = "caution" + end + advtrains.ndb.swap_node(pos, {name="advtrains_signals_japan:"..sigtype.."_"..asp_name.."_"..rot, param2 = node.param2}) + end, + get_aspect_info = function(pos, main_aspect) + return { + main = main_aspect.main, + proceed_as_main = true, + } + end, + --[[ supported_aspects = { group = "advtrains_signals_japan:5a", name = siginfo.suppasp_names, @@ -428,6 +449,7 @@ for _, rtab in ipairs { set_aspect = function(pos, node, asp) advtrains.ndb.swap_node(pos, {name = "advtrains_signals_japan:"..sigtype.."_"..(asp.name).."_"..rot, param2 = node.param2}) end, + ]] }, on_rightclick = advtrains.interlocking.signal_rc_handler, can_dig = advtrains.interlocking.signal_can_dig, diff --git a/advtrains_signals_japan/mod.conf b/advtrains_signals_japan/mod.conf new file mode 100644 index 0000000..8cb2cb3 --- /dev/null +++ b/advtrains_signals_japan/mod.conf @@ -0,0 +1,6 @@ +name=advtrains_signals_japan +title=Advtrains Interlocking Signal Set - Japanese Signals +description=Japanese signal set for the Advanced Trains Interlocking system +author=yw05 + +depends=advtrains_interlocking diff --git a/advtrains_signals_ks/init.lua b/advtrains_signals_ks/init.lua index 0a43db0..0ed03bb 100755 --- a/advtrains_signals_ks/init.lua +++ b/advtrains_signals_ks/init.lua @@ -51,7 +51,7 @@ end local applyaspectf_main = function(rot) return function(pos, node, main_aspect, dst_aspect, dst_aspect_info) -- set zs3 signal to show speed according to main_aspect - setzs3(pos, asp.zs3, rot) + setzs3(pos, main_aspect.zs3, rot) -- select appropriate lamps based on mainaspect and dst if main_aspect.shunt then advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_shunt_"..rot, param2 = node.param2}) @@ -81,43 +81,43 @@ end -- Actual signal aspect is chosen based on this and the Dst signal. local mainaspects_main = { { - name = "proceed" + name = "proceed", description = "Proceed", zs3 = "off" }, { - name = "shunt" + name = "shunt", description = "Shunt", zs3 = "off", shunt = true, }, { - name = "proceed_16" + name = "proceed_16", description = "Proceed (speed 16)", zs3 = "16", }, { - name = "proceed_12" + name = "proceed_12", description = "Proceed (speed 12)", zs3 = "12", }, { - name = "proceed_8" + name = "proceed_8", description = "Proceed (speed 8)", zs3 = "8", }, { - name = "proceed_6" + name = "proceed_6", description = "Proceed (speed 6)", zs3 = "6", }, { - name = "proceed_4" + name = "proceed_4", description = "Proceed (speed 4)", zs3 = "4", }, { - name = "halt" + name = "halt", description = "Halt", zs3 = "off", halt = true, @@ -140,12 +140,12 @@ end -- Shunt signals have only two states, distant doesn't matter local mainaspects_shunt = { { - name = "shunt" + name = "shunt", description = "Shunt", shunt = true, }, { - name = "halt" + name = "halt", description = "Halt", halt = true, }, @@ -168,7 +168,7 @@ for _, rtab in ipairs({ }) do local rot = rtab.rot for typ, prts in pairs({ - danger = {asp = advtrains.interlocking.DANGER, n = "slow", ici=true}, + danger = {asp = advtrains.interlocking.signal.ASPI_HALT, n = "slow", ici=true}, slow = { asp = function(pos) return { main = getzs3(pos) or -1, proceed_as_main = true, dst = 0 } @@ -230,7 +230,7 @@ for _, rtab in ipairs({ drop = "advtrains_signals_ks:hs_danger_0", inventory_image = "advtrains_signals_ks_hs_inv.png", advtrains = { - main_aspects = mainaspects_main + main_aspects = mainaspects_main, apply_aspect = applyaspectf_main(rot), get_aspect_info = afunc, }, -- cgit v1.2.3 From 6fd845baec0f5aa8b7cdee1adf8d05061a643242 Mon Sep 17 00:00:00 2001 From: orwell Date: Thu, 23 May 2024 00:58:24 +0200 Subject: Connect the ropes, start on making the UI work --- advtrains/passive.lua | 2 +- advtrains_interlocking/database.lua | 2 +- advtrains_interlocking/distant.lua | 200 ----------- advtrains_interlocking/init.lua | 3 - advtrains_interlocking/route_ui.lua | 18 +- advtrains_interlocking/routesetting.lua | 15 +- advtrains_interlocking/signal_api.lua | 220 +++---------- advtrains_interlocking/signal_aspect_accessors.lua | 163 --------- advtrains_interlocking/signal_aspect_ui.lua | 366 ++++++++------------- advtrains_interlocking/tcb_ts_ui.lua | 11 +- advtrains_signals_japan/init.lua | 6 +- advtrains_signals_ks/init.lua | 23 +- 12 files changed, 220 insertions(+), 809 deletions(-) delete mode 100644 advtrains_interlocking/distant.lua delete mode 100644 advtrains_interlocking/signal_aspect_accessors.lua (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains/passive.lua b/advtrains/passive.lua index 231da82..37b79e4 100644 --- a/advtrains/passive.lua +++ b/advtrains/passive.lua @@ -57,7 +57,7 @@ function advtrains.setstate(parpos, newstate, pnode) return false, "train_here" end - if advtrains.interlocking and advtrains.interlocking.route.has_route_lock(minetest.encode_pos(pos)) then + if advtrains.interlocking and advtrains.interlocking.route.has_route_lock(advtrains.encode_pos(pos)) then return false, "route_lock_here" end diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index 4213c3d..e2df547 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -1006,7 +1006,7 @@ end function ildb.get_ip_signal_asp(pts, connid) local p = ildb.get_ip_signal(pts, connid) if p then - local asp = advtrains.interlocking.signal_get_aspect(p) + local asp = advtrains.interlocking.signal.get_aspect(p) if not asp then atlog("Clearing orphaned signal influence point", pts, "/", connid) ildb.clear_ip_signal(pts, connid) diff --git a/advtrains_interlocking/distant.lua b/advtrains_interlocking/distant.lua deleted file mode 100644 index 32ada82..0000000 --- a/advtrains_interlocking/distant.lua +++ /dev/null @@ -1,200 +0,0 @@ ---- Distant signaling. --- This module implements a database backend for distant signal assignments. --- The actual modifications to signal aspects are still done by signal aspect accessors. --- @module advtrains.interlocking.distant - -local db_distant = {} -local db_distant_of = {} - -local pts = advtrains.encode_pos -local stp = advtrains.decode_pos - ---- Replace the distant signal assignment database. --- @function load --- @param db The new database to load. -local function db_load(x) - if type(x) ~= "table" then - return - end - db_distant = x.distant - db_distant_of = x.distant_of -end - ---- Retrieve the current distant signal assignment database. --- @function save --- @return The current database. -local function db_save() - return { - distant = db_distant, - distant_of = db_distant_of, - } -end - -local update_signal, update_main, update_dst - ---- Unassign a distant signal. --- @function unassign_dst --- @param dst The position of the distant signal. --- @param[opt=false] force Whether to skip callbacks. -local function unassign_dst(dst, force) - local pts_dst = pts(dst) - local main = db_distant_of[pts_dst] - db_distant_of[pts_dst] = nil - if main then - local pts_main = main[1] - local t = db_distant[pts_main] - if t then - t[pts_dst] = nil - end - end - if not force then - update_dst(dst) - end -end - ---- Unassign a main signal. --- @function unassign_main --- @param main The position of the main signal. --- @param[opt=false] force Whether to skip callbacks. -local function unassign_main(main, force) - local pts_main = pts(main) - local t = db_distant[pts_main] - if not t then - return - end - for pts_dst in pairs(t) do - local realmain = db_distant_of[pts_dst] - if realmain and realmain[1] == pts_main then - db_distant_of[pts_dst] = nil - if not force then - local dst = stp(pts_dst) - update_dst(dst) - end - end - end - db_distant[pts_main] = nil -end - ---- Remove all (main and distant) signal assignments from a signal. --- @function unassign_all --- @param pos The position of the signal. --- @param[opt=false] force Whether to skip callbacks. -local function unassign_all(pos, force) - unassign_main(pos) - unassign_dst(pos, force) -end - ---- Check whether a signal is "appropriate" for the distant signal system. --- Currently, a signal is considered appropriate if its signal aspect can be set. --- @function appropriate_signal --- @param pos The position of the signal -local function appropriate_signal(pos) - local node = advtrains.ndb.get_node(pos) - local ndef = minetest.registered_nodes[node.name] or {} - if not ndef then - return false - end - local atdef = ndef.advtrains - if not atdef then - return false - end - return atdef.supported_aspects and atdef.set_aspect and true -end - ---- Assign a distant signal to a main signal. --- @function assign --- @param main The position of the main signal. --- @param dst The position of the distant signal. --- @param[opt="manual"] by The method of assignment. --- @param[opt=false] skip_update Whether to skip callbacks. -local function assign(main, dst, by, skip_update) - if not (appropriate_signal(main) and appropriate_signal(dst)) then - return - end - local pts_main = pts(main) - local pts_dst = pts(dst) - local t = db_distant[pts_main] - if not t then - t = {} - db_distant[pts_main] = t - end - if not by then - by = "manual" - end - unassign_dst(dst, true) - t[pts_dst] = by - db_distant_of[pts_dst] = {pts_main, by} - if not skip_update then - update_dst(dst) - end -end - ---- Get the distant signals assigned to a main signal. --- @function get_distant --- @param main The position of the main signal. --- @treturn {[pos]=by,...} A table of distant signals, with the positions encoded using `advtrains.encode_pos`. -local function get_distant(main) - local pts_main = pts(main) - return db_distant[pts_main] or {} -end - ---- Get the main signal assigned the a distant signal. --- @function get_main --- @param dst The position of the distant signal. --- @return The position of the main signal. --- @return The method of assignment. -local function get_main(dst) - local pts_dst = pts(dst) - local main = db_distant_of[pts_dst] - if not main then - return - end - if main[1] then - return stp(main[1]), unpack(main, 2) - else - return unpack(main) - end -end - ---- Update all distant signals assigned to a main signal. --- @function update_main --- @param main The position of the main signal. -update_main = function(main) - local pts_main = pts(main) - local t = get_distant(main) - for pts_dst in pairs(t) do - local dst = stp(pts_dst) - advtrains.interlocking.signal_readjust_aspect(dst) - end -end - ---- Update the aspect of a distant signal. --- @function update_dst --- @param dst The position of the distant signal. -update_dst = function(dst) - advtrains.interlocking.signal_readjust_aspect(dst) -end - ---- Update the aspect of a combined (main and distant) signal and all distant signals assigned to it. --- @function update_signal --- @param pos The position of the signal. -update_signal = function(pos) - update_main(pos) - update_dst(pos) -end - -advtrains.distant = { - load = db_load, - save = db_save, - assign = assign, - unassign_dst = unassign_dst, - unassign_main = unassign_main, - unassign_all = unassign_all, - get_distant = get_distant, - get_dst = get_distant, - get_main = get_main, - update_main = update_main, - update_dst = update_dst, - update_signal = update_signal, - appropriate_signal = appropriate_signal, -} diff --git a/advtrains_interlocking/init.lua b/advtrains_interlocking/init.lua index dd08b4a..c397aa6 100644 --- a/advtrains_interlocking/init.lua +++ b/advtrains_interlocking/init.lua @@ -15,9 +15,6 @@ local modpath = minetest.get_modpath(minetest.get_current_modname()) .. DIR_DELI --advtrains.interlocking.aspect = dofile(modpath.."aspect.lua") dofile(modpath.."database.lua") -dofile(modpath.."distant.lua") -dofile(modpath.."distant_ui.lua") -dofile(modpath.."signal_aspect_accessors.lua") dofile(modpath.."signal_api.lua") dofile(modpath.."signal_aspect_ui.lua") dofile(modpath.."demosignals.lua") diff --git a/advtrains_interlocking/route_ui.lua b/advtrains_interlocking/route_ui.lua index a1a331d..982c579 100644 --- a/advtrains_interlocking/route_ui.lua +++ b/advtrains_interlocking/route_ui.lua @@ -33,7 +33,7 @@ function atil.show_route_edit_form(pname, sigd, routeid) local function itab(t) tab[#tab+1] = minetest.formspec_escape(string.gsub(t, ",", " ")) end - itab("TCB "..sigd_to_string(sigd).." ("..(tcbs.signal_name or "")..") Route #"..routeid) + itab("("..(tcbs.signal_name or "+")..") Route #"..routeid) -- this code is partially copy-pasted from routesetting.lua -- we start at the tc designated by signal @@ -56,13 +56,14 @@ function atil.show_route_edit_form(pname, sigd, routeid) c_rseg = route[i] c_lckp = {} - itab(""..i.." Entry "..sigd_to_string(c_sigd).." -> Sec. "..(c_ts and c_ts.name or "-").." -> Exit "..(c_rseg.next and sigd_to_string(c_rseg.next) or "END")) + itab(""..i.." "..sigd_to_string(c_sigd)) + itab("= "..(c_ts and c_ts.name or "-").." =") if c_rseg.locks then for pts, state in pairs(c_rseg.locks) do local pos = minetest.string_to_pos(pts) - itab(" Lock: "..pts.." -> "..state) + itab("L "..pts.." -> "..state) if not advtrains.is_passive(pos) then itab("-!- No passive component at "..pts..". Please reconfigure route!") break @@ -75,16 +76,17 @@ function atil.show_route_edit_form(pname, sigd, routeid) end if c_sigd then local e_tcbs = ildb.get_tcbs(c_sigd) - itab("Route end: "..sigd_to_string(c_sigd).." ("..(e_tcbs and e_tcbs.signal_name or "-")..")") + local signame = "-" + if e_tcbs and e_tcbs.signal then signame = e_tcbs.signal_name or "+" end + itab("E "..sigd_to_string(c_sigd).." ("..signame..")") else - itab("Route ends on dead-end") + itab("E (none)") end - form = form.."textlist[0.5,2;7.75,3.9;rtelog;"..table.concat(tab, ",").."]" + form = form.."textlist[0.5,2;3,3.9;rtelog;"..table.concat(tab, ",").."]" form = form.."button[0.5,6;3,1;back;<<< Back to signal]" - form = form.."button[4.5,6;2,1;aspect;Signal Aspect]" - form = form.."button[6.5,6;2,1;delete;Delete Route]" + form = form.."button[5.5,6;3,1;delete;Delete Route]" --atdebug(route.ars) form = form.."style[ars;font=mono]" diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua index 24b3199..a576139 100644 --- a/advtrains_interlocking/routesetting.lua +++ b/advtrains_interlocking/routesetting.lua @@ -138,7 +138,7 @@ function ilrs.set_route(signal, route, try) } if c_tcbs.signal then c_tcbs.route_committed = true - c_tcbs.aspect = route.aspect or advtrains.interlocking.FULL_FREE + c_tcbs.aspect = advtrains.interlocking.signal.MASP_FREE c_tcbs.route_origin = signal signals[#signals+1] = c_tcbs end @@ -166,7 +166,7 @@ function ilrs.set_route(signal, route, try) if (not nodst) and (not assigned_by or assigned_by == "routesetting") then advtrains.distant.assign(lastsig, pos, "routesetting", true) end - advtrains.interlocking.update_signal_aspect(tcbs, i ~= 1) + advtrains.interlocking.signal.update_route_aspect(tcbs, i ~= 1) end end @@ -278,14 +278,7 @@ function ilrs.cancel_route_from(sigd) c_tcbs.route_auto = nil c_tcbs.route_origin = nil - if c_tcbs.signal then - local pos = c_tcbs.signal - local _, assigned_by = advtrains.distant.get_main(pos) - if assigned_by == "routesetting" then - advtrains.distant.unassign_dst(pos, true) - end - end - advtrains.interlocking.update_signal_aspect(c_tcbs) + advtrains.interlocking.signal.update_route_aspect(c_tcbs) c_ts_id = c_tcbs.ts_id if not c_tcbs then @@ -370,7 +363,7 @@ function ilrs.update_route(sigd, tcbs, newrte, cancel) end if has_changed_aspect then -- FIX: prevent an minetest.after() loop caused by update_signal_aspect dispatching path invalidation, which in turn calls ARS again - advtrains.interlocking.update_signal_aspect(tcbs) + advtrains.interlocking.signal.update_route_aspect(tcbs) end advtrains.interlocking.update_player_forms(sigd) end diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index d27a045..5216594 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -5,9 +5,15 @@ local F = advtrains.formspec local signal = {} signal.MASP_HALT = { - name = "halt", - description = "HALT", - halt = true, + name = nil, + speed = nil, + remote = nil, +} + +signal.MASP_FREE = { + name = "_free", + speed = -1, + remote = nil, } signal.ASPI_HALT = { @@ -50,11 +56,12 @@ Note that once apply_aspect returns, there is no need for advtrains anymore to q When the signal, for any reason, wants to change its aspect by itself *without* going through the signal API then it should update the aspect info cache by calling advtrains.interlocking.signal.update_aspect_info(pos) -Note that the apply_aspect function MUST accept the following main aspect, even if it is not defined in the main_aspects table: -{ name = "halt", halt = true } -It should cause the signal to show its most restrictive aspect. Typically it is a halt aspect, but e.g. for distant-only +Apply_aspect may also receive nil as the main aspect. It usually means that the signal is not assigned to anything particular, +and it should cause the signal to show its most restrictive aspect. Typically it is a halt aspect, but e.g. for distant-only signals this would be "expect stop". +Main aspect names starting with underscore (e.g. "_default") are reserved and must not be used! + == Aspect Info == The actual signal aspect in the already-known format. This is what the trains use to determine halt/proceed and speed. asp = { @@ -152,7 +159,7 @@ end -- - Storing the main aspect and dst pos for this signal permanently (until next change) -- - Assigning the distant signal for this signal -- - Calling apply_aspect() in the signal's node definition to make the signal show the aspect --- - Calling apply_aspect() again whenever the distant signal changes its aspect +-- - Calling apply_aspect() again whenever the remote signal changes its aspect -- - Notifying this signal's distant signals about changes to this signal (unless skip_dst_notify is specified) function signal.set_aspect(pos, main_asp_name, main_asp_speed, rem_pos, skip_dst_notify) local main_pts = advtrains.encode_pos(pos) @@ -252,10 +259,7 @@ function signal.get_aspect(pos) end local function cache_mainaspects(ndefat) - ndefat.main_aspects_lookup = { - -- always define halt aspect - halt = signal.MASP_HALT - } + ndefat.main_aspects_lookup = {} for _,ma in ipairs(ndefat.main_aspects) do ndefat.main_aspects_lookup[ma.name] = ma end @@ -264,7 +268,7 @@ end function signal.get_aspect_internal(pos, aspt) if not aspt then -- oh, no main aspect, nevermind - return nil, aspt.remote, nil + return nil, nil, nil end atdebug("get_aspect_internal",pos,aspt) -- look aspect in nodedef @@ -277,6 +281,10 @@ function signal.get_aspect_internal(pos, aspt) cache_mainaspects(ndefat) end local masp = ndefat.main_aspects_lookup[aspt.name] + -- special handling for the default free aspect ("_free") + if aspt.name == "_free" then + masp = ndefat.main_aspects[1] + end if not masp then atwarn(pos,"invalid main aspect",aspt.name,"valid are",ndefat.main_aspects_lookup) return nil, aspt.remote, node, ndef @@ -355,10 +363,30 @@ end function signal.update_route_aspect(tcbs, skip_dst_notify) if tcbs.signal then local asp = tcbs.aspect or signal.MASP_HALT - signal.set_aspect(tcbs.signal, asp, skip_dst_notify) + signal.set_aspect(tcbs.signal, asp.name, asp.speed, asp.remote, skip_dst_notify) end end +-- Returns how capable the signal is with regards to aspect setting +-- 0: not a signal at all +-- 1: signal has get_aspect_info() but the aspect is not variable (e.g. a sign) +-- 2: signal has apply_aspect() but does not have main aspects (e.g. a pure distant signal) +-- 3: Full capabilities, signal has main aspects and can be used as main/shunt signal (can be start/endpoint of a route) +function signal.get_signal_cap_level(pos) + local node = advtrains.ndb.get_node_or_nil(pos) + local ndef = node and minetest.registered_nodes[node.name] + local ndefat = ndef and ndef.advtrains + if ndefat and ndefat.get_aspect_info then + if ndefat.apply_aspect then + if ndefat.main_aspects then + return 3 + end + return 2 + end + return 1 + end + return 0 +end ---------------- @@ -366,7 +394,7 @@ function signal.can_dig(pos) return not advtrains.interlocking.db.get_sigd_for_signal(pos) end -function advtrains.interlocking.signal_after_dig(pos) +function signal.after_dig(pos) -- TODO clear influence point advtrains.interlocking.signal.clear_aspect(pos) end @@ -374,169 +402,7 @@ end function signal.on_rightclick(pos, node, player, itemstack, pointed_thing) local pname = player:get_player_name() local control = player:get_player_control() - if control.aux1 then - advtrains.interlocking.show_ip_form(pos, pname) - return - end - advtrains.interlocking.show_signal_form(pos, node, pname) -end - -function advtrains.interlocking.show_signal_form(pos, node, pname) - local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) - if sigd then - advtrains.interlocking.show_signalling_form(sigd, pname) - else - local ndef = minetest.registered_nodes[node.name] - if ndef.advtrains and ndef.advtrains.set_aspect then - -- permit to set aspect manually - local function callback(pname, aspect) - signal.set_aspect(pos, aspect) - end - local isasp = advtrains.interlocking.signal_get_aspect(pos, node) - - advtrains.interlocking.show_signal_aspect_selector( - pname, - ndef.advtrains.supported_aspects, - pos, callback, - isasp) - else - --static signal - only IP - advtrains.interlocking.show_ip_form(pos, pname) - end - end -end - -local players_assign_ip = {} - -local function ipmarker(ipos, connid) - local node_ok, conns, rhe = advtrains.get_rail_info_at(ipos, advtrains.all_tracktypes) - if not node_ok then return end - local yaw = advtrains.dir_to_angle(conns[connid].c) - - -- using tcbmarker here - local obj = minetest.add_entity(vector.add(ipos, {x=0, y=0.2, z=0}), "advtrains_interlocking:tcbmarker") - if not obj then return end - obj:set_yaw(yaw) - obj:set_properties({ - textures = { "at_il_signal_ip.png" }, - }) -end - -function advtrains.interlocking.make_ip_formspec_component(pos, x, y, w) - advtrains.interlocking.db.check_for_duplicate_ip(pos) - local pts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos) - if pts then - return table.concat { - F.S_label(x, y, "Influence point is set at @1.", string.format("%s/%s", pts, connid)), - F.S_button_exit(x, y+0.5, w/2-0.125, "ip_set", "Modify"), - F.S_button_exit(x+w/2+0.125, y+0.5, w/2-0.125, "ip_clear", "Clear"), - }, pts, connid - else - return table.concat { - F.S_label(x, y, "Influence point is not set."), - F.S_button_exit(x, y+0.5, w, "ip_set", "Set influence point"), - } - end -end - --- shows small info form for signal properties --- This function is named show_ip_form because it was originally only intended --- for assigning/changing the influence point. --- only_notset: show only if it is not set yet (used by signal tcb assignment) -function advtrains.interlocking.show_ip_form(pos, pname, only_notset) - if not minetest.check_player_privs(pname, "interlocking") then - return - end - local ipform, pts, connid = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 0.5, 7) - local form = { - "formspec_version[4]", - "size[8,2.25]", - ipform, - } - if pts then - local ipos = minetest.string_to_pos(pts) - ipmarker(ipos, connid) - end - if advtrains.distant.appropriate_signal(pos) then - form[#form+1] = advtrains.interlocking.make_dst_formspec_component(pos, 0.5, 2, 7, 4.25) - form[2] = "size[8,6.75]" - end - form = table.concat(form) - if not only_notset or not pts then - minetest.show_formspec(pname, "at_il_propassign_"..minetest.pos_to_string(pos), form) - end + advtrains.interlocking.show_signal_form(pos, node, pname, control.aux1) end -function advtrains.interlocking.handle_ip_formspec_fields(pname, pos, fields) - if not (pos and minetest.check_player_privs(pname, {train_operator=true, interlocking=true})) then - return - end - if fields.ip_set then - advtrains.interlocking.signal_init_ip_assign(pos, pname) - elseif fields.ip_clear then - advtrains.interlocking.db.clear_ip_by_signalpos(pos) - end -end - -minetest.register_on_player_receive_fields(function(player, formname, fields) - local pname = player:get_player_name() - local pts = string.match(formname, "^at_il_propassign_([^_]+)$") - local pos - if pts then - pos = minetest.string_to_pos(pts) - end - if pos then - advtrains.interlocking.handle_ip_formspec_fields(pname, pos, fields) - advtrains.interlocking.handle_dst_formspec_fields(pname, pos, fields) - end -end) - --- inits the signal IP assignment process -function signal.init_ip_assign(pos, pname) - if not minetest.check_player_privs(pname, "interlocking") then - minetest.chat_send_player(pname, "Insufficient privileges to use this!") - return - end - --remove old IP - --advtrains.interlocking.db.clear_ip_by_signalpos(pos) - minetest.chat_send_player(pname, "Configuring Signal: Please look in train's driving direction and punch rail to set influence point.") - - players_assign_ip[pname] = pos -end - -minetest.register_on_punchnode(function(pos, node, player, pointed_thing) - local pname = player:get_player_name() - if not minetest.check_player_privs(pname, "interlocking") then - return - end - -- IP assignment - local signalpos = players_assign_ip[pname] - if signalpos then - if vector.distance(pos, signalpos)<=50 then - local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes) - if node_ok and #conns == 2 then - - local yaw = player:get_look_horizontal() - local plconnid = advtrains.yawToClosestConn(yaw, conns) - - -- add assignment if not already present. - local pts = advtrains.roundfloorpts(pos) - if not advtrains.interlocking.db.get_ip_signal_asp(pts, plconnid) then - advtrains.interlocking.db.set_ip_signal(pts, plconnid, signalpos) - ipmarker(pos, plconnid) - minetest.chat_send_player(pname, "Configuring Signal: Successfully set influence point") - else - minetest.chat_send_player(pname, "Configuring Signal: Influence point of another signal is already present!") - end - else - minetest.chat_send_player(pname, "Configuring Signal: This is not a normal two-connection rail! Aborted.") - end - else - minetest.chat_send_player(pname, "Configuring Signal: Node is too far away. Aborted.") - end - players_assign_ip[pname] = nil - end -end) - - advtrains.interlocking.signal = signal diff --git a/advtrains_interlocking/signal_aspect_accessors.lua b/advtrains_interlocking/signal_aspect_accessors.lua deleted file mode 100644 index d91df31..0000000 --- a/advtrains_interlocking/signal_aspect_accessors.lua +++ /dev/null @@ -1,163 +0,0 @@ ---- Signal aspect accessors --- @module advtrains.interlocking - -local A = advtrains.interlocking.aspect -local D = advtrains.distant -local I = advtrains.interlocking -local N = advtrains.ndb -local pts = advtrains.roundfloorpts - -local get_aspect - -local supposed_aspects = {} - ---- Replace the signal aspect cache. --- @function load_supposed_aspects --- @param db The new database. -function I.load_supposed_aspects(tbl) - if tbl then - supposed_aspects = {} - for k, v in pairs(tbl) do - supposed_aspects[k] = A(v) - end - end -end - ---- Retrieve the signal aspect cache. --- @function save_supposed_aspects --- @return The current database in use. -function I.save_supposed_aspects() - local t = {} - for k, v in pairs(supposed_aspects) do - t[k] = v:plain(true) - end - return t -end - ---- Read the aspect of a signal strictly from cache. --- @param pos The position of the signal. --- @return[1] The aspect of the signal (if present in cache). --- @return[2] The nil constant (otherwise). -local function get_supposed_aspect(pos) - return supposed_aspects[pts(pos)] -end - ---- Update the signal aspect information in cache. --- @param pos The position of the signal. --- @param asp The new signal aspect -local function set_supposed_aspect(pos, asp) - supposed_aspects[pts(pos)] = asp -end - ---- Get the definition of a node. --- @param pos The position of the node. --- @return[1] The definition of the node (if present). --- @return[2] An empty table (otherwise). -local function get_ndef(pos) - local node = N.get_node(pos) - return (minetest.registered_nodes[node.name] or {}), node -end - ---- Get the aspects supported by a signal. --- @function signal_get_supported_aspects --- @param pos The position of the signal. --- @return[1] The table of supported aspects (if present). --- @return[2] The nil constant (otherwise). -local function get_supported_aspects(pos) - local ndef = get_ndef(pos) - if ndef.advtrains and ndef.advtrains.supported_aspects then - return ndef.advtrains.supported_aspects - end - return nil -end - ---- Adjust a new signal aspect to fit a signal. --- @param pos The position of the signal. --- @param asp The new signal aspect. --- @return The adjusted signal aspect. --- @return The information to pass to the `advtrains.set_aspect` field in the node definitions. -local function adjust_aspect(pos, asp) - local asp = A(asp) - - local mainpos = D.get_main(pos) - local nxtasp - if mainpos then - nxtasp = get_aspect(mainpos) - end - local suppasp = get_supported_aspects(pos) - if not suppasp then - return asp - end - return asp:adjust_distant(nxtasp, suppasp.dst_shift):to_group(suppasp.group) -end - ---- Get the aspect of a signal without accessing the cache. --- For most cases, `get_aspect` should be used instead. --- @function signal_get_real_aspect --- @param pos The position of the signal. --- @return[1] The signal aspect adjusted using `adjust_aspect` (if present). --- @return[2] The nil constant (otherwise). -local function get_real_aspect(pos) - local ndef, node = get_ndef(pos) - if ndef.advtrains and ndef.advtrains.get_aspect then - local asp = ndef.advtrains.get_aspect(pos, node) or I.DANGER - return adjust_aspect(pos, asp) - end - return nil -end - ---- Get the aspect of a signal. --- @function signal_get_aspect --- @param pos The position of the signal. --- @return[1] The aspect of the signal (if present). --- @return[2] The nil constant (otherwise). -get_aspect = function(pos) - local asp = get_supposed_aspect(pos) - if not asp then - asp = get_real_aspect(pos) - set_supposed_aspect(pos, asp) - end - return asp -end - ---- Set the aspect of a signal. --- @function signal_set_aspect --- @param pos The position of the signal. --- @param asp The new signal aspect. --- @param[opt=false] skipdst Whether to skip updating distant signals. -local function set_aspect(pos, asp, skipdst) - local node = N.get_node(pos) - local ndef = minetest.registered_nodes[node.name] - if ndef and ndef.advtrains and ndef.advtrains.set_aspect then - local oldasp = I.signal_get_aspect(pos) or DANGER - local newasp = adjust_aspect(pos, asp) - set_supposed_aspect(pos, newasp) - ndef.advtrains.set_aspect(pos, node, newasp) - I.signal_on_aspect_changed(pos) - local aspect_changed = oldasp ~= newasp - if (not skipdst) and aspect_changed then - D.update_main(pos) - end - end -end - ---- Remove a signal from cache. --- @function signal_clear_aspect --- @param pos The position of the signal. -local function clear_aspect(pos) - set_supposed_aspect(pos, nil) -end - ---- Readjust the aspect of a signal. --- @function signal_readjust_aspect --- @param pos The position of the signal. -local function readjust_aspect(pos) - set_aspect(pos, get_aspect(pos)) -end - -I.signal_get_supported_aspects = get_supported_aspects -I.signal_get_real_aspect = get_real_aspect -I.signal_get_aspect = get_aspect -I.signal_set_aspect = set_aspect -I.signal_clear_aspect = clear_aspect -I.signal_readjust_aspect = readjust_aspect diff --git a/advtrains_interlocking/signal_aspect_ui.lua b/advtrains_interlocking/signal_aspect_ui.lua index a81b7fe..e5d2003 100644 --- a/advtrains_interlocking/signal_aspect_ui.lua +++ b/advtrains_interlocking/signal_aspect_ui.lua @@ -1,262 +1,188 @@ local F = advtrains.formspec -local players_aspsel = {} -local function describe_main_aspect(spv) - if spv == 0 then - return attrans("Danger (halt)") - elseif spv == -1 then - return attrans("Continue at maximum speed") - elseif not spv then - return attrans("Continue with current speed limit") +function advtrains.interlocking.show_signal_form(pos, node, pname, aux_key) + local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) + if sigd and not aux_key then + advtrains.interlocking.show_signalling_form(sigd, pname) else - return attrans("Continue with the speed limit of @1", tostring(spv)) - end -end - -local function describe_shunt_aspect(shunt) - if shunt then - return attrans("Shunting allowed") - else - return attrans("No shunting") + if advtrains.interlocking.signal.get_signal_cap_level(pos) >= 2 then + advtrains.interlocking.show_ip_sa_form(pos, pname) + else + advtrains.interlocking.show_ip_form(pos, pname) + end end end -local function describe_distant_aspect(spv) - if spv == 0 then - return attrans("Expect to stop at the next signal") - elseif spv == -1 then - return attrans("Expect to continue at maximum speed") - elseif not spv then - return attrans("No distant signal information") - else - return attrans("Expect to continue with a speed limit of @1", tostring(spv)) - end +local players_assign_ip = {} +local players_assign_distant = {} + +local function ipmarker(ipos, connid) + local node_ok, conns, rhe = advtrains.get_rail_info_at(ipos, advtrains.all_tracktypes) + if not node_ok then return end + local yaw = advtrains.dir_to_angle(conns[connid].c) + + -- using tcbmarker here + local obj = minetest.add_entity(vector.add(ipos, {x=0, y=0.2, z=0}), "advtrains_interlocking:tcbmarker") + if not obj then return end + obj:set_yaw(yaw) + obj:set_properties({ + textures = { "at_il_signal_ip.png" }, + }) end -advtrains.interlocking.describe_main_aspect = describe_main_aspect -advtrains.interlocking.describe_shunt_aspect = describe_shunt_aspect -advtrains.interlocking.describe_distant_aspect = describe_distant_aspect - -local function dsel(p, q, x, y) - if p == nil then - if q then - return x - else - return y - end - elseif p then - return x +function advtrains.interlocking.make_ip_formspec_component(pos, x, y, w) + advtrains.interlocking.db.check_for_duplicate_ip(pos) + local pts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos) + if pts then + -- display marker + local ipos = minetest.string_to_pos(pts) + ipmarker(ipos, connid) + return table.concat { + F.S_label(x, y, "Influence point is set at @1.", string.format("%s/%s", pts, connid)), + F.S_button_exit(x, y+0.5, w/2-0.125, "ip_set", "Modify"), + F.S_button_exit(x+w/2+0.125, y+0.5, w/2-0.125, "ip_clear", "Clear"), + } else - return y + return table.concat { + F.S_label(x, y, "Influence point is not set."), + F.S_button_exit(x, y+0.5, w, "ip_set", "Set influence point"), + } end end -local function describe_supported_aspects(suppasp, isasp) - local t = {} - - local entries = {attrans("Use default value")} - local selid = 0 - local mainasps = suppasp.main - if type(mainasps) ~= "table" then - mainasps = {mainasps} - end - for idx, spv in ipairs(mainasps) do - if isasp and spv == rawget(isasp, "main") then - selid = idx - end - entries[idx+1] = describe_main_aspect(spv) - end - t.main = entries - t.main_current = selid+1 - t.main_string = tostring(isasp.main) - if t.main == nil then - t.main_string = "" +-- shows small formspec to set the signal influence point +-- only_notset: show only if it is not set yet (used by signal tcb assignment) +function advtrains.interlocking.show_ip_form(pos, pname, only_notset) + if not minetest.check_player_privs(pname, "interlocking") then + return end - - t.shunt = { - attrans("No shunting"), - attrans("Shunting allowed"), - attrans("Proceed as main"), + local ipform = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 0.5, 7) + local form = { + "formspec_version[4]", + "size[8,2.25]", + ipform, } - - t.shunt_current = dsel(suppasp.shunt, isasp.shunt, 2, 1) - if dsel(suppasp.proceed_as_main, isasp.proceed_as_main, t.shunt_current == 1) then - t.shunt_current = 3 - end - t.shunt_const = suppasp.shunt ~= nil - - if suppasp.group then - local gdef = advtrains.interlocking.aspect.get_group_definition(suppasp.group) - if gdef then - t.group = suppasp.group - t.groupdef = gdef - local entries = {} - local selid = 1 - for idx, name in ipairs(suppasp.name or {}) do - entries[idx] = gdef.aspects[name].label - if suppasp.group == isasp.group and name == isasp.name then - selid = idx - end - end - t.name = entries - t.name_current = selid - end + if not only_notset or not pts then + minetest.show_formspec(pname, "at_il_ipsaform_"..minetest.pos_to_string(pos), table.concat(form)) end - - return t end -advtrains.interlocking.describe_supported_aspects = describe_supported_aspects - -local function make_signal_aspect_selector(suppasp, purpose, isasp) - local t = describe_supported_aspects(suppasp, isasp) - local formmode = 1 - - local pos - if type(purpose) == "table" then - formmode = 2 - pos = purpose.pos +-- shows larger formspec to set the signal influence point, main aspect and distant signal pos +-- only_notset: show only if it is not set yet (used by signal tcb assignment) +function advtrains.interlocking.show_ip_sa_form(pos, pname) + if not minetest.check_player_privs(pname, "interlocking") then + return end - + local ipform = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 0.5, 7) + local ma, rpos = advtrains.interlocking.signal.get_aspect(pos) + local saform = F.S_button_exit(0, 2, 4, "sa_dst_assign", rpos and minetest.pos_to_string(rpos) or "") + .. F.S_button_exit(0, 3, 2, "sa_tmp_mainfree", "Main to free") .. F.S_button_exit(2, 3, 2, "sa_tmp_mainhalt", "Main to halt") local form = { "formspec_version[4]", - string.format("size[8,%f]", ({5.75, 10.75})[formmode]), - F.S_label(0.5, 0.5, "Select signal aspect"), + "size[8,4]", + ipform, + saform, } - local h0 = ({0, 1.5})[formmode] - form[#form+1] = F.S_label(0.5, 1.5+h0, "Main aspect") - form[#form+1] = F.S_label(0.5, 3+h0, "Shunt aspect") - form[#form+1] = F.S_button_exit(0.5, 4.5+h0, 7, "asp_save", "Save signal aspect") - if formmode == 1 then - form[#form+1] = F.label(0.5, 1, purpose) - form[#form+1] = F.field(0.5, 2, 7, "asp_mainval", "", t.main_string) - elseif formmode == 2 then - if t.group then - form[#form+1] = F.S_label(0.5, 1.5, "Signal aspect group: @1", t.groupdef.label) - form[#form+1] = F.dropdown(0.5, 2, 7, "asp_namesel", t.name, t.name_current, true) - else - form[#form+1] = F.S_label(0.5, 1.5, "This signal does not belong to a signal aspect group.") - form[#form+1] = F.S_label(0.5, 2, "You can not use a predefined signal aspect.") - end - form[#form+1] = F.S_label(0.5, 1, "Signal at @1", minetest.pos_to_string(pos)) - form[#form+1] = F.dropdown(0.5, 3.5, 7, "asp_mainsel", t.main, t.main_current, true) - form[#form+1] = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 7, 7) - form[#form+1] = advtrains.interlocking.make_short_dst_formspec_component(pos, 0.5, 8.5, 7) - end + minetest.show_formspec(pname, "at_il_ipsaform_"..minetest.pos_to_string(pos), table.concat(form)) +end - if formmode == 2 and t.shunt_const then - form[#form+1] = F.label(0.5, 3.5+h0, t.shunt[t.shunt_current]) - form[#form+1] = F.S_label(0.5, 4+h0, "The shunt aspect cannot be changed.") - else - form[#form+1] = F.dropdown(0.5, 3.5+h0, 7, "asp_shunt", t.shunt, t.shunt_current, true) +function advtrains.interlocking.handle_ip_sa_formspec_fields(pname, pos, fields) + if not (pos and minetest.check_player_privs(pname, {train_operator=true, interlocking=true})) then + return + end + if fields.ip_set then + advtrains.interlocking.init_ip_assign(pos, pname) + elseif fields.ip_clear then + advtrains.interlocking.db.clear_ip_by_signalpos(pos) + elseif fields.sa_dst_assign then + advtrains.interlocking.init_distant_assign(pos, pname) + elseif fields.sa_tmp_mainfree then + local ma, rpos = advtrains.interlocking.signal.get_aspect(pos) + advtrains.interlocking.signal.set_aspect(pos, "_free", -1, rpos) + elseif fields.sa_tmp_mainhalt then + local ma, rpos = advtrains.interlocking.signal.get_aspect(pos) + advtrains.interlocking.signal.set_aspect(pos, nil, nil, rpos) end - - return table.concat(form) end -function advtrains.interlocking.show_signal_aspect_selector(pname, p_suppasp, p_purpose, callback, isasp) - local suppasp = p_suppasp or { - main = {0, -1}, - dst = {false}, - shunt = false, - info = {}, - } - local purpose = p_purpose or "" +minetest.register_on_player_receive_fields(function(player, formname, fields) + local pname = player:get_player_name() + local pts = string.match(formname, "^at_il_ipsaform_([^_]+)$") local pos - if type(p_purpose) == "table" then - pos = p_purpose - purpose = {pname = pname, pos = pos} + if pts then + pos = minetest.string_to_pos(pts) end - - local form = make_signal_aspect_selector(suppasp, purpose, isasp) - if not form then - return + if pos then + advtrains.interlocking.handle_ip_sa_formspec_fields(pname, pos, fields) end +end) - local token = advtrains.random_id() - minetest.show_formspec(pname, "at_il_sigaspdia_"..token, form) - minetest.after(0, function() - players_aspsel[pname] = { - purpose = purpose, - suppasp = suppasp, - callback = callback, - token = token, - } - end) -end - -local function usebool(sup, val, free) - if sup == nil then - return val == free - else - return sup +-- inits the signal IP assignment process +function advtrains.interlocking.init_ip_assign(pos, pname) + if not minetest.check_player_privs(pname, "interlocking") then + minetest.chat_send_player(pname, "Insufficient privileges to use this!") + return end + --remove old IP + --advtrains.interlocking.db.clear_ip_by_signalpos(pos) + minetest.chat_send_player(pname, "Configuring Signal: Please look in train's driving direction and punch rail to set influence point.") + + players_assign_ip[pname] = pos end -local function get_aspect_from_formspec(suppasp, fields, psl) - local namei, group, name = tonumber(fields.asp_namesel), suppasp.group, nil - local gdef = advtrains.interlocking.aspect.get_group_definition(group) - if gdef then - local names = suppasp.name or {} - name = names[namei] or names[names] - else - group = nil - end - local maini = tonumber(fields.asp_mainsel) - local main = (suppasp.main or {})[(maini or 0)-1] - if not maini then - local mainval = fields.asp_mainval - if mainval == "-1" then - main = -1 - elseif mainval == "x" then - main = false - elseif string.match(mainval, "^%d+$") then - main = tonumber(mainval) - else - main = nil - end - elseif maini <= 1 then - main = nil - end - local shunti = tonumber(fields.asp_shunt) - local shunt = suppasp.shunt - if shunt == nil then - shunt = shunti == 2 - end - local proceed_as_main = suppasp.proceed_as_main - if proceed_as_main == nil then - proceed_as_main = shunti == 3 +-- inits the distant signal assignment process +function advtrains.interlocking.init_distant_assign(pos, pname) + if not minetest.check_player_privs(pname, "interlocking") then + minetest.chat_send_player(pname, "Insufficient privileges to use this!") + return end - return advtrains.interlocking.aspect { - main = main, - shunt = shunt, - proceed_as_main = proceed_as_main, - info = {}, - name = name, - group = group, - } + minetest.chat_send_player(pname, "Set distant signal: Punch the main signal to assign!") + + players_assign_distant[pname] = pos end -minetest.register_on_player_receive_fields(function(player, formname, fields) +minetest.register_on_punchnode(function(pos, node, player, pointed_thing) local pname = player:get_player_name() - local psl = players_aspsel[pname] - if psl then - if formname == "at_il_sigaspdia_"..psl.token then - local suppasp = psl.suppasp - if fields.asp_save then - local asp - asp = get_aspect_from_formspec(suppasp, fields, psl) - if asp then - psl.callback(pname, asp) + if not minetest.check_player_privs(pname, "interlocking") then + return + end + -- IP assignment + local signalpos = players_assign_ip[pname] + if signalpos then + if vector.distance(pos, signalpos)<=50 then + local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes) + if node_ok and #conns == 2 then + + local yaw = player:get_look_horizontal() + local plconnid = advtrains.yawToClosestConn(yaw, conns) + + -- add assignment if not already present. + local pts = advtrains.roundfloorpts(pos) + if not advtrains.interlocking.db.get_ip_signal_asp(pts, plconnid) then + advtrains.interlocking.db.set_ip_signal(pts, plconnid, signalpos) + ipmarker(pos, plconnid) + minetest.chat_send_player(pname, "Configuring Signal: Successfully set influence point") + else + minetest.chat_send_player(pname, "Configuring Signal: Influence point of another signal is already present!") end - end - if type(psl.purpose) == "table" then - local pos = psl.purpose.pos - advtrains.interlocking.handle_ip_formspec_fields(pname, pos, fields) - advtrains.interlocking.handle_dst_formspec_fields(pname, pos, fields) + else + minetest.chat_send_player(pname, "Configuring Signal: This is not a normal two-connection rail! Aborted.") end else - players_aspsel[pname] = nil + minetest.chat_send_player(pname, "Configuring Signal: Node is too far away. Aborted.") end + players_assign_ip[pname] = nil + end + -- DST assignment + signalpos = players_assign_distant[pname] + if signalpos then + -- get current mainaspect + local ma, rpos = advtrains.interlocking.signal.get_aspect(signalpos) + -- if punched pos is valid signal then set it as the new remote, otherwise nil + local nrpos + if advtrains.interlocking.signal.get_signal_cap_level(pos) > 1 then nrpos = pos end + advtrains.interlocking.signal.set_aspect(signalpos, ma.name, ma.speed, nrpos) + players_assign_distant[pname] = nil end end) + diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index bfec648..caf22e3 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -186,7 +186,7 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) local is_signal = minetest.get_item_group(node.name, "advtrains_signal") >= 2 if is_signal then local ndef = minetest.registered_nodes[node.name] - if ndef and ndef.advtrains and ndef.advtrains.set_aspect then + if ndef and ndef.advtrains and ndef.advtrains.apply_aspect then local tcbs = ildb.get_tcbs(sigd) if tcbs then tcbs.signal = pos @@ -464,7 +464,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) ts.route = nil for _, sigd in ipairs(ts.tc_breaks) do local tcbs = ildb.get_tcbs(sigd) - advtrains.interlocking.update_signal_aspect(tcbs) + advtrains.interlocking.signal.update_route_aspect(tcbs) end minetest.chat_send_player(pname, "Reset track section "..ts_id.."!") end @@ -642,7 +642,6 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle form = form.."button[0.5,8;2.5,1;newroute;New Route]" form = form.."button[ 3,8;2.5,1;unassign;Unassign Signal]" 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;dst;Distant signalling;%s]", not tcbs.nodst) end elseif sigd_equal(tcbs.route_origin, sigd) then -- something has gone wrong: tcbs.routeset should have been set... @@ -660,7 +659,7 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle -- always a good idea to update the signal aspect if not called_from_form_update then -- FIX prevent a callback loop - advtrains.interlocking.update_signal_aspect(tcbs) + advtrains.interlocking.signal.update_route_aspect(tcbs) end end @@ -769,10 +768,6 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if fields.ars then tcbs.ars_disabled = not minetest.is_yes(fields.ars) end - - if fields.dst then - tcbs.nodst = not minetest.is_yes(fields.dst) - end if fields.auto then tcbs.route_auto = true diff --git a/advtrains_signals_japan/init.lua b/advtrains_signals_japan/init.lua index eb39f85..d7cf035 100644 --- a/advtrains_signals_japan/init.lua +++ b/advtrains_signals_japan/init.lua @@ -374,10 +374,6 @@ for sigtype, sigdata in pairs { --sigdefs["rpt_"..sigtype] = process_signal(sigtype, sigdata, true) end -for k in pairs(sigdefs) do - advtrains.trackplacer.register_tracktype("advtrains_signals_japan:"..k) -end - for _, rtab in ipairs { {rot = "0", ici = true}, {rot = "30"}, @@ -455,7 +451,7 @@ for _, rtab in ipairs { can_dig = advtrains.interlocking.signal_can_dig, after_dig_node = advtrains.interlocking.signal_after_dig, }) - advtrains.trackplacer.add_worked("advtrains_signals_japan:"..sigtype, asp, "_"..rot) + --advtrains.trackplacer.add_worked("advtrains_signals_japan:"..sigtype, asp, "_"..rot) end end end diff --git a/advtrains_signals_ks/init.lua b/advtrains_signals_ks/init.lua index 67e0fec..abfb194 100755 --- a/advtrains_signals_ks/init.lua +++ b/advtrains_signals_ks/init.lua @@ -50,15 +50,18 @@ end local applyaspectf_main = function(rot) return function(pos, node, main_aspect, dst_aspect, dst_aspect_info) + if not main_aspect then + -- halt aspect, set red and don't do anything further + advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_danger_"..rot, param2 = node.param2}) + setzs3v(pos, nil, rot) + return + end -- set zs3 signal to show speed according to main_aspect setzs3(pos, main_aspect.zs3, rot) -- select appropriate lamps based on mainaspect and dst if main_aspect.shunt then advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_shunt_"..rot, param2 = node.param2}) setzs3v(pos, nil, rot) - elseif main_aspect.halt then - advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_danger_"..rot, param2 = node.param2}) - setzs3v(pos, nil, rot) else if not dst_aspect_info or not dst_aspect_info.main @@ -128,7 +131,8 @@ local mainaspects_main = { local applyaspectf_ra = function(rot) -- we get here the full main_aspect table return function(pos, node, main_aspect, dst_aspect, dst_aspect_info) - if main_aspect.shunt then + if main_aspect then + -- any main aspect is fine, there's only one anyway advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:ra_shuntd_"..rot, param2 = node.param2}) else advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:ra_danger_"..rot, param2 = node.param2}) @@ -144,11 +148,6 @@ local mainaspects_shunt = { description = "Shunt", shunt = true, }, - { - name = "halt", - description = "Halt", - halt = true, - }, } for _, rtab in ipairs({ @@ -225,9 +224,9 @@ for _, rtab in ipairs({ apply_aspect = applyaspectf_main(rot), get_aspect_info = afunc, }, - on_rightclick = advtrains.interlocking.signal_rc_handler, - can_dig = advtrains.interlocking.signal_can_dig, - after_dig_node = advtrains.interlocking.signal_after_dig, + on_rightclick = advtrains.interlocking.signal.on_rightclick, + can_dig = advtrains.interlocking.signal.can_dig, + after_dig_node = advtrains.interlocking.signal.after_dig, }) -- rotatable by trackworker --TODO add rotation using trackworker -- cgit v1.2.3 From 9af6e32e644cfa53641e83d7550c0af2d31553fd Mon Sep 17 00:00:00 2001 From: orwell Date: Thu, 23 May 2024 20:25:35 +0200 Subject: Add proper UI aspect selection for static dialog --- advtrains/nodedb.lua | 4 ++ advtrains_interlocking/routesetting.lua | 11 +---- advtrains_interlocking/signal_api.lua | 70 +++++++++++++++------------ advtrains_interlocking/signal_aspect_ui.lua | 75 ++++++++++++++++++++++++----- advtrains_signals_ks/init.lua | 12 ++--- 5 files changed, 111 insertions(+), 61 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains/nodedb.lua b/advtrains/nodedb.lua index ff07df4..62405ce 100644 --- a/advtrains/nodedb.lua +++ b/advtrains/nodedb.lua @@ -212,6 +212,10 @@ function ndb.get_node(pos) end return n end +function ndb.get_ndef(pos) + local n=ndb.get_node_or_nil(pos) + return n and minetest.registered_nodes[n.name] +end function ndb.get_node_raw(pos) local cid=ndbget(pos.x, pos.y, pos.z) if cid then diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua index a576139..51709dc 100644 --- a/advtrains_interlocking/routesetting.lua +++ b/advtrains_interlocking/routesetting.lua @@ -159,15 +159,8 @@ function ilrs.set_route(signal, route, try) end end for i = #signals, 1, -1 do - if lastsig then - local tcbs = signals[i] - local pos = tcbs.signal - local _, assigned_by = advtrains.distant.get_main(pos) - if (not nodst) and (not assigned_by or assigned_by == "routesetting") then - advtrains.distant.assign(lastsig, pos, "routesetting", true) - end - advtrains.interlocking.signal.update_route_aspect(tcbs, i ~= 1) - end + -- TODO add logic for distant signal assign + advtrains.interlocking.signal.update_route_aspect(signals[i], i ~= 1) end return true diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 5216594..7826d30 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -5,13 +5,14 @@ local F = advtrains.formspec local signal = {} signal.MASP_HALT = { - name = nil, - speed = nil, + name = "_halt", + speed = 0, + halt = true, remote = nil, } signal.MASP_FREE = { - name = "_free", + name = "_default", speed = -1, remote = nil, } @@ -44,9 +45,9 @@ b) the distant signal's aspect group name & aspect table One concrete combination of lights/shapes that a signal signal shows. Handling these is at the discretion of the signal mod defining the signal, and they are typically combinations of main aspect and distant aspect Example: -- A Ks signal has the aspect_group="proceed_12" set for a route -- The signal at the end of the route shows aspect_group="proceed_8", advtrains also passes on that this means {main=8, shunt=false} -- The ndef.advtrains.apply_aspect(pos, asp_group, dst_aspgrp, dst_aspinfo) determines that the signal should now show +- A Ks signal has the main_aspect="proceed_12" set for a route +- The signal at the end of the route shows main_aspect="proceed_8", advtrains also passes on that this means {main=8, shunt=false} +- The ndef.afunction(pos, node, main_aspect, rem_aspect, rem_aspinfo) determines that the signal should now show blinking green with main indicator 12 and dst indicator 8, and sets the nodes accordingly. This function can now return the Aspect Info table, which will be cached by advtrains until the aspect changes again and will be used when a train approaches the signal. If nil is returned, then the aspect will be queried next time @@ -56,10 +57,14 @@ Note that once apply_aspect returns, there is no need for advtrains anymore to q When the signal, for any reason, wants to change its aspect by itself *without* going through the signal API then it should update the aspect info cache by calling advtrains.interlocking.signal.update_aspect_info(pos) -Apply_aspect may also receive nil as the main aspect. It usually means that the signal is not assigned to anything particular, +Apply_aspect may also receive the special main aspect { name = "_halt", halt = true }. It usually means that the signal is not assigned to anything particular, and it should cause the signal to show its most restrictive aspect. Typically it is a halt aspect, but e.g. for distant-only signals this would be "expect stop". +A special case occurs for pure distant signals: Such signals must set apply_aspect, but must not set main_aspects. Behavior is as follows: +- Signal is uninitialized, distant signal is not assigned to a main signal, or no route is set: main_aspect == { name = "_halt", halt = true } and rem_aspect == nil +- A remote main signal is assigned (either by user or by route): main_aspect is always { name = "_default" } and rem_aspect / rem_aspinfo give the correct information + Main aspect names starting with underscore (e.g. "_default") are reserved and must not be used! == Aspect Info == @@ -90,6 +95,7 @@ ndef.advtrains = { apply_aspect = function(pos, node, main_aspect, rem_aspect, rem_aspinfo) -- set the node to show the desired aspect -- called by advtrains when this signal's aspect group or the remote signal's aspect changes + -- main_aspect is never nil, but can be one of the special aspects { name = "_halt", halt = true } or { name = "_default" } -- MAY return the aspect_info. If it returns nil then get_aspect_info will be queried at a later point. get_aspect_info(pos, main_aspect) -- Returns the aspect info table (main, shunt, dst etc.) @@ -268,36 +274,41 @@ end function signal.get_aspect_internal(pos, aspt) if not aspt then -- oh, no main aspect, nevermind - return nil, nil, nil + return signal.MASP_HALT, nil, nil end atdebug("get_aspect_internal",pos,aspt) -- look aspect in nodedef local node = advtrains.ndb.get_node_or_nil(pos) local ndef = node and minetest.registered_nodes[node.name] local ndefat = ndef and ndef.advtrains - -- only if signal defines main aspect and its set in aspt - if ndefat and ndefat.main_aspects and aspt.name then - if not ndefat.main_aspects_lookup then - cache_mainaspects(ndefat) - end - local masp = ndefat.main_aspects_lookup[aspt.name] - -- special handling for the default free aspect ("_free") - if aspt.name == "_free" then - masp = ndefat.main_aspects[1] - end - if not masp then - atwarn(pos,"invalid main aspect",aspt.name,"valid are",ndefat.main_aspects_lookup) - return nil, aspt.remote, node, ndef - end - -- if speed, then apply speed - if masp.speed and aspt.speed then - masp = table.copy(masp) - masp.speed = aspt.speed + if ndefat and ndefat.apply_aspect then + -- only if signal defines main aspect and its set in aspt + if ndefat.main_aspects and aspt.name then + if not ndefat.main_aspects_lookup then + cache_mainaspects(ndefat) + end + local masp = ndefat.main_aspects_lookup[aspt.name] + -- special handling for the default free aspect ("_default") + if aspt.name == "_default" then + masp = ndefat.main_aspects[1] + end + if not masp then + atwarn(pos,"invalid main aspect",aspt.name,"valid are",ndefat.main_aspects_lookup) + return signal.MASP_HALT, aspt.remote, node, ndef + end + -- if speed, then apply speed + if masp.speed and aspt.speed then + masp = table.copy(masp) + masp.speed = aspt.speed + end + return masp, aspt.remote, node, ndef + elseif aspt.name then + -- Distant-only signal, still supports kind of default aspect + return { name = aspt.name, speed = aspt.speed }, aspt.remote, node, ndef end - return masp, aspt.remote, node, ndef end - -- invalid node or no main aspect, return nil for masp - return nil, aspt.remote, node, ndef + -- invalid node or no main aspect, return default halt aspect for masp + return signal.MASP_HALT, aspt.remote, node, ndef end -- For the signal at pos, get the "aspect info" table. This contains the speed signalling information at this location @@ -327,7 +338,6 @@ function signal.reapply_aspect(pts) end -- resolve mainaspect table by name local pos = advtrains.decode_pos(pts) - -- note: masp may be nil, when aspt.name was nil. Valid case for distant-only signals local masp, remote, node, ndef = signal.get_aspect_internal(pos, aspt) -- if we have remote, resolve remote local rem_masp, rem_aspi diff --git a/advtrains_interlocking/signal_aspect_ui.lua b/advtrains_interlocking/signal_aspect_ui.lua index e5d2003..785e6d4 100644 --- a/advtrains_interlocking/signal_aspect_ui.lua +++ b/advtrains_interlocking/signal_aspect_ui.lua @@ -75,14 +75,34 @@ function advtrains.interlocking.show_ip_sa_form(pos, pname) end local ipform = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 0.5, 7) local ma, rpos = advtrains.interlocking.signal.get_aspect(pos) - local saform = F.S_button_exit(0, 2, 4, "sa_dst_assign", rpos and minetest.pos_to_string(rpos) or "") - .. F.S_button_exit(0, 3, 2, "sa_tmp_mainfree", "Main to free") .. F.S_button_exit(2, 3, 2, "sa_tmp_mainhalt", "Main to halt") local form = { "formspec_version[4]", - "size[8,4]", + "size[8,4.5]", ipform, - saform, } + -- Create Signal aspect formspec elements + local ndef = advtrains.ndb.get_ndef(pos) + if ndef and ndef.advtrains then + -- main aspect list + if ndef.advtrains.main_aspects then + local entries = { "" } + local sel = 1 + for i, mae in ipairs(ndef.advtrains.main_aspects) do + entries[i+1] = mae.description + if ma and ma.name == mae.name then + sel = i+1 + end + end + form[#form+1] = F.dropdown(0.5, 2.5, 4, "sa_mainaspect", entries, sel, true) + end + -- distant signal assign (is shown either when main_aspect is not none, or when pure distant signal) + if rpos then + form[#form+1] = F.button_exit(0.5, 3.5, 4, "sa_undistant", "Dst: " .. minetest.pos_to_string(rpos)) + elseif (ma and not ma.halt) or not ndef.advtrains.main_aspects then + form[#form+1] = F.button_exit(0.5, 3.5, 4, "sa_distant", "") + end + end + minetest.show_formspec(pname, "at_il_ipsaform_"..minetest.pos_to_string(pos), table.concat(form)) end @@ -90,18 +110,42 @@ function advtrains.interlocking.handle_ip_sa_formspec_fields(pname, pos, fields) if not (pos and minetest.check_player_privs(pname, {train_operator=true, interlocking=true})) then return end + local ma, rpos = advtrains.interlocking.signal.get_aspect(pos) + -- mainaspect dropdown + if fields.sa_mainaspect then + local idx = tonumber(fields.sa_mainaspect) + local new_ma = nil + if idx > 1 then + local ndef = advtrains.ndb.get_ndef(pos) + if ndef and ndef.advtrains and ndef.advtrains.main_aspects then + new_ma = ndef.advtrains.main_aspects[idx - 1] + end + end + if new_ma and (new_ma.name ~= ma.name or new_ma.speed ~= ma.speed) then + advtrains.interlocking.signal.set_aspect(pos, new_ma.name, new_ma.speed, rpos) + elseif not new_ma then + -- reset everything + advtrains.interlocking.signal.set_aspect(pos, nil, nil, nil) + end + + end + -- buttons if fields.ip_set then advtrains.interlocking.init_ip_assign(pos, pname) + return elseif fields.ip_clear then advtrains.interlocking.db.clear_ip_by_signalpos(pos) - elseif fields.sa_dst_assign then + return + elseif fields.sa_distant then advtrains.interlocking.init_distant_assign(pos, pname) - elseif fields.sa_tmp_mainfree then - local ma, rpos = advtrains.interlocking.signal.get_aspect(pos) - advtrains.interlocking.signal.set_aspect(pos, "_free", -1, rpos) - elseif fields.sa_tmp_mainhalt then - local ma, rpos = advtrains.interlocking.signal.get_aspect(pos) - advtrains.interlocking.signal.set_aspect(pos, nil, nil, rpos) + return + elseif fields.sa_undistant then + advtrains.interlocking.signal.set_aspect(pos, ma.name, ma.speed, nil) + return + end + -- show the form again unless one of the buttons was clicked + if not fields.quit then + advtrains.interlocking.show_ip_sa_form(pos, pname) end end @@ -180,8 +224,13 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) local ma, rpos = advtrains.interlocking.signal.get_aspect(signalpos) -- if punched pos is valid signal then set it as the new remote, otherwise nil local nrpos - if advtrains.interlocking.signal.get_signal_cap_level(pos) > 1 then nrpos = pos end - advtrains.interlocking.signal.set_aspect(signalpos, ma.name, ma.speed, nrpos) + if advtrains.interlocking.signal.get_signal_cap_level(pos) > 1 then + nrpos = pos + if not ma then -- make sure that dst is never set without a main aspect (esp. for pure distant signal case) + ma = { name = "_default" } + end + advtrains.interlocking.signal.set_aspect(signalpos, ma.name, ma.speed, nrpos) + end players_assign_distant[pname] = nil end end) diff --git a/advtrains_signals_ks/init.lua b/advtrains_signals_ks/init.lua index abfb194..9cb0262 100755 --- a/advtrains_signals_ks/init.lua +++ b/advtrains_signals_ks/init.lua @@ -50,7 +50,7 @@ end local applyaspectf_main = function(rot) return function(pos, node, main_aspect, dst_aspect, dst_aspect_info) - if not main_aspect then + if main_aspect.halt then -- halt aspect, set red and don't do anything further advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_danger_"..rot, param2 = node.param2}) setzs3v(pos, nil, rot) @@ -119,20 +119,14 @@ local mainaspects_main = { description = "Proceed (speed 4)", zs3 = "4", }, - { - name = "halt", - description = "Halt", - zs3 = "off", - halt = true, - }, } --Rangiersignal local applyaspectf_ra = function(rot) -- we get here the full main_aspect table return function(pos, node, main_aspect, dst_aspect, dst_aspect_info) - if main_aspect then - -- any main aspect is fine, there's only one anyway + if not main_aspect.halt then + -- any non-halt main aspect is fine, there's only one anyway advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:ra_shuntd_"..rot, param2 = node.param2}) else advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:ra_danger_"..rot, param2 = node.param2}) -- cgit v1.2.3 From f52653209aecb2310c1fb9598391c86113296f27 Mon Sep 17 00:00:00 2001 From: orwell Date: Fri, 24 May 2024 00:00:12 +0200 Subject: Fix working of the legacy signals under new system --- advtrains/passive.lua | 6 ++ advtrains/signals.lua | 111 ++++++++++++--------------------- advtrains_interlocking/database.lua | 2 +- advtrains_interlocking/demosignals.lua | 97 ---------------------------- advtrains_interlocking/init.lua | 1 - advtrains_interlocking/signal_api.lua | 15 +++-- 6 files changed, 55 insertions(+), 177 deletions(-) delete mode 100644 advtrains_interlocking/demosignals.lua (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains/passive.lua b/advtrains/passive.lua index 37b79e4..aad309e 100644 --- a/advtrains/passive.lua +++ b/advtrains/passive.lua @@ -70,6 +70,12 @@ function advtrains.setstate(parpos, newstate, pnode) end -- invalidate paths (only relevant if this is a track) advtrains.invalidate_all_paths(pos) + -- hack for old signals. Compatibility only, DO NOT USE for new signals! + if advtrains.interlocking and ndef.advtrains._is_passivenode_signal then + -- forcefully clears any set aspect, so that aspect system doesnt override it again + -- implicitly does an signal.notify_trains(pos) + advtrains.interlocking.signal.clear_aspect(pos) + end return true end diff --git a/advtrains/signals.lua b/advtrains/signals.lua index 58d28a5..0b874bf 100644 --- a/advtrains/signals.lua +++ b/advtrains/signals.lua @@ -5,13 +5,13 @@ local mrules_wallsignal = advtrains.meseconrules local function can_dig_func(pos) if advtrains.interlocking then - return advtrains.interlocking.signal_can_dig(pos) + return advtrains.interlocking.signal.can_dig(pos) end return true end local function after_dig_func(pos) if advtrains.interlocking then - return advtrains.interlocking.signal_after_dig(pos) + return advtrains.interlocking.signal.after_dig(pos) end return true end @@ -26,18 +26,20 @@ return { } end -local suppasp = { - main = {0, -1}, - dst = {false}, - shunt = nil, - proceed_as_main = true, - info = { - call_on = false, - dead_end = false, - w_speed = nil, - } +local main_aspects = { + { name = "free", description = "Free" } } +local function simple_apply_aspect(offname, onname) + return function(pos, node, main_aspect, rem_aspect, rem_aspinfo) + if main_aspect.halt then + advtrains.ndb.swap_node(pos, {name = offname, param2 = node.param2}) + else + advtrains.ndb.swap_node(pos, {name = onname, param2 = node.param2}) + end + end +end + for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", als="green"}}) do for rotid, rotation in ipairs({"", "_30", "_45", "_60"}) do @@ -71,7 +73,9 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", ["action_"..f.as] = function (pos, node) advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2}, true) if advtrains.interlocking then - advtrains.interlocking.signal_on_aspect_changed(pos) + -- forcefully clears any set aspect, so that aspect system doesnt override it again + -- implicitly does an signal.notify_trains(pos) + advtrains.interlocking.signal.clear_aspect(pos) end end }}, @@ -85,23 +89,17 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", elseif advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2}, true) if advtrains.interlocking then - advtrains.interlocking.signal_on_aspect_changed(pos) + -- forcefully clears any set aspect, so that aspect system doesnt override it again + -- implicitly does an signal.notify_trains(pos) + advtrains.interlocking.signal.clear_aspect(pos) end end end, - -- new signal API + -- very new signal API advtrains = { - set_aspect = function(pos, node, asp) - if asp.main ~= 0 then - advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_on"..rotation, param2 = node.param2}, true) - else - advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_off"..rotation, param2 = node.param2}, true) - end - end, - get_aspect = function(pos, node) - return aspect(r=="on") - end, - supported_aspects = suppasp, + main_aspects = main_aspects, + apply_aspect = simple_apply_aspect("advtrains:retrosignal_off"..rotation, "advtrains:retrosignal_on"..rotation), + get_aspect_info = function() return aspect(r=="on") end, }, can_dig = can_dig_func, after_dig_node = after_dig_func, @@ -136,7 +134,7 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", ["action_"..f.as] = function (pos, node) advtrains.setstate(pos, f.als, node) if advtrains.interlocking then - advtrains.interlocking.signal_on_aspect_changed(pos) + advtrains.interlocking.signal.notify_on_aspect_changed(pos) end end }}, @@ -149,30 +147,16 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", advtrains.interlocking.show_ip_form(pos, pname) elseif advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then advtrains.setstate(pos, f.als, node) - if advtrains.interlocking then - advtrains.interlocking.signal_on_aspect_changed(pos) - end end end, - -- new signal API + -- very new signal API advtrains = { - set_aspect = function(pos, node, asp) - if asp.main ~= 0 then - advtrains.ndb.swap_node(pos, {name = "advtrains:signal_on"..rotation, param2 = node.param2}, true) - else - advtrains.ndb.swap_node(pos, {name = "advtrains:signal_off"..rotation, param2 = node.param2}, true) - end - end, - get_aspect = function(pos, node) - return aspect(r=="on") - end, - supported_aspects = suppasp, - getstate = f.ls, - setstate = function(pos, node, newstate) - if newstate == f.als then - advtrains.ndb.swap_node(pos, {name = "advtrains:signal_"..f.as..rotation, param2 = node.param2}, true) - end - end, + main_aspects = main_aspects, + apply_aspect = simple_apply_aspect("advtrains:signal_off"..rotation, "advtrains:signal_on"..rotation), + get_aspect_info = function() return aspect(r=="on") end, + node_state = f.ls, + node_state_map = { red = "advtrains:signal_off"..rotation, green = "advtrains:signal_on"..rotation}, + _is_passivenode_signal = true }, can_dig = can_dig_func, after_dig_node = after_dig_func, @@ -211,9 +195,6 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", rules = mrules_wallsignal, ["action_"..f.as] = function (pos, node) advtrains.setstate(pos, f.als, node) - if advtrains.interlocking then - advtrains.interlocking.signal_on_aspect_changed(pos) - end end }}, on_rightclick=function(pos, node, player) @@ -225,30 +206,16 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", advtrains.interlocking.show_ip_form(pos, pname) elseif advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then advtrains.setstate(pos, f.als, node) - if advtrains.interlocking then - advtrains.interlocking.signal_on_aspect_changed(pos) - end end end, - -- new signal API + -- very new signal API advtrains = { - set_aspect = function(pos, node, asp) - if asp.main ~= 0 then - advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_on", param2 = node.param2}, true) - else - advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_off", param2 = node.param2}, true) - end - end, - get_aspect = function(pos, node) - return aspect(r=="on") - end, - supported_aspects = suppasp, - getstate = f.ls, - setstate = function(pos, node, newstate) - if newstate == f.als then - advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_"..f.as, param2 = node.param2}, true) - end - end, + main_aspects = main_aspects, + apply_aspect = simple_apply_aspect("advtrains:signal_wall_"..loc.."_off", "advtrains:signal_wall_"..loc.."_on"), + get_aspect_info = function() return aspect(r=="on") end, + node_state = f.ls, + node_state_map = { red = "advtrains:signal_wall_"..loc.."_off", green = "advtrains:signal_wall_"..loc.."_on" }, + _is_passivenode_signal = true }, can_dig = can_dig_func, after_dig_node = after_dig_func, diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index e2df547..49ca13d 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -1006,7 +1006,7 @@ end function ildb.get_ip_signal_asp(pts, connid) local p = ildb.get_ip_signal(pts, connid) if p then - local asp = advtrains.interlocking.signal.get_aspect(p) + local asp = advtrains.interlocking.signal.get_aspect_info(p) if not asp then atlog("Clearing orphaned signal influence point", pts, "/", connid) ildb.clear_ip_signal(pts, connid) diff --git a/advtrains_interlocking/demosignals.lua b/advtrains_interlocking/demosignals.lua deleted file mode 100644 index de6926a..0000000 --- a/advtrains_interlocking/demosignals.lua +++ /dev/null @@ -1,97 +0,0 @@ --- Demonstration signals --- Those can display the 3 main aspects of Ks signals - --- Note that the group value of advtrains_signal is 2, which means "step 2 of signal capabilities" --- advtrains_signal=1 is meant for signals that do not implement set_aspect. - - -local setaspect = function(pos, node, asp) - if asp.main == 0 then - advtrains.ndb.swap_node(pos, {name="advtrains_interlocking:ds_danger"}) - else - if asp.dst ~= 0 and asp.main == -1 then - advtrains.ndb.swap_node(pos, {name="advtrains_interlocking:ds_free"}) - else - advtrains.ndb.swap_node(pos, {name="advtrains_interlocking:ds_slow"}) - end - end - local meta = minetest.get_meta(pos) - if meta then - meta:set_string("infotext", minetest.serialize(asp)) - end -end - -local suppasp = { - main = {0, 6, -1}, - dst = {0, false}, - shunt = false, - proceed_as_main = true, - info = { - call_on = false, - dead_end = false, - w_speed = nil, - } -} - -minetest.register_node("advtrains_interlocking:ds_danger", { - description = "Demo signal at Danger", - tiles = {"at_il_signal_asp_danger.png"}, - groups = { - cracky = 3, - advtrains_signal = 2, - save_in_at_nodedb = 1, - }, - advtrains = { - set_aspect = setaspect, - supported_aspects = suppasp, - get_aspect = function(pos, node) - return advtrains.interlocking.DANGER - end, - }, - on_rightclick = advtrains.interlocking.signal_rc_handler, - can_dig = advtrains.interlocking.signal_can_dig, - after_destruct = advtrains.interlocking.signal_after_dig, -}) -minetest.register_node("advtrains_interlocking:ds_free", { - description = "Demo signal at Free", - tiles = {"at_il_signal_asp_free.png"}, - groups = { - cracky = 3, - advtrains_signal = 2, - save_in_at_nodedb = 1, - }, - advtrains = { - set_aspect = setaspect, - supported_aspects = suppasp, - get_aspect = function(pos, node) - return { - main = -1, - } - end, - }, - on_rightclick = advtrains.interlocking.signal_rc_handler, - can_dig = advtrains.interlocking.signal_can_dig, - after_destruct = advtrains.interlocking.signal_after_dig, -}) -minetest.register_node("advtrains_interlocking:ds_slow", { - description = "Demo signal at Slow", - tiles = {"at_il_signal_asp_slow.png"}, - groups = { - cracky = 3, - advtrains_signal = 2, - save_in_at_nodedb = 1, - }, - advtrains = { - set_aspect = setaspect, - supported_aspects = suppasp, - get_aspect = function(pos, node) - return { - main = 6, - } - end, - }, - on_rightclick = advtrains.interlocking.signal_rc_handler, - can_dig = advtrains.interlocking.signal_can_dig, - after_destruct = advtrains.interlocking.signal_after_dig, -}) - diff --git a/advtrains_interlocking/init.lua b/advtrains_interlocking/init.lua index c397aa6..a4ddbad 100644 --- a/advtrains_interlocking/init.lua +++ b/advtrains_interlocking/init.lua @@ -17,7 +17,6 @@ local modpath = minetest.get_modpath(minetest.get_current_modname()) .. DIR_DELI dofile(modpath.."database.lua") dofile(modpath.."signal_api.lua") dofile(modpath.."signal_aspect_ui.lua") -dofile(modpath.."demosignals.lua") dofile(modpath.."train_sections.lua") dofile(modpath.."route_prog.lua") dofile(modpath.."routesetting.lua") diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 7826d30..6f52816 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -272,14 +272,14 @@ local function cache_mainaspects(ndefat) end function signal.get_aspect_internal(pos, aspt) - if not aspt then - -- oh, no main aspect, nevermind - return signal.MASP_HALT, nil, nil - end atdebug("get_aspect_internal",pos,aspt) -- look aspect in nodedef local node = advtrains.ndb.get_node_or_nil(pos) local ndef = node and minetest.registered_nodes[node.name] + if not aspt then + -- oh, no main aspect, nevermind + return signal.MASP_HALT, nil, node, ndef + end local ndefat = ndef and ndef.advtrains if ndefat and ndefat.apply_aspect then -- only if signal defines main aspect and its set in aspt @@ -318,7 +318,9 @@ function signal.get_aspect_info(pos) local masp, remote, node, ndef = signal.get_aspect_internal(pos, aspt) -- call into ndef if ndef.advtrains and ndef.advtrains.get_aspect_info then - return ndef.advtrains.get_aspect_info(pos, masp) + local ai = ndef.advtrains.get_aspect_info(pos, masp) + atdebug(pos,"aspectinfo",ai) + return ai end end @@ -333,11 +335,12 @@ function signal.reapply_aspect(pts) -- get aspt local aspt = signal.aspects[pts] atdebug("reapply_aspect",advtrains.decode_pos(pts),"aspt",aspt) + local pos = advtrains.decode_pos(pts) if not aspt then + signal.notify_trains(pos) return -- oop, nothing to do end -- resolve mainaspect table by name - local pos = advtrains.decode_pos(pts) local masp, remote, node, ndef = signal.get_aspect_internal(pos, aspt) -- if we have remote, resolve remote local rem_masp, rem_aspi -- cgit v1.2.3 From 44a8cef1d5f2433502f5982f1d57410f49bb3afc Mon Sep 17 00:00:00 2001 From: orwell Date: Mon, 3 Jun 2024 23:38:12 +0200 Subject: set_aspect: Flexibility, set aspect either via name or allow to fully specify table (for advanced signals) --- advtrains_interlocking/signal_api.lua | 96 +++++++++++++---------------- advtrains_interlocking/signal_aspect_ui.lua | 16 ++--- advtrains_signals_ks/init.lua | 1 + advtrains_signals_muc_ubahn/init.lua | 2 +- 4 files changed, 53 insertions(+), 62 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 6f52816..a300ab1 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -6,15 +6,12 @@ local signal = {} signal.MASP_HALT = { name = "_halt", - speed = 0, halt = true, - remote = nil, } -signal.MASP_FREE = { +signal.MASP_DEFAULT = { name = "_default", - speed = -1, - remote = nil, + default = true, } signal.ASPI_HALT = { @@ -85,17 +82,15 @@ ndef.advtrains = { } -- This list is mainly for the selection dialog. Order of entries determines list order in the dropdown. -- Some fields have special meaning: - -- name: A unique key to identify the main aspect. Only this key is saved, but APIs always receive the whole table + -- name: A unique key to identify the main aspect. Might be required by some code. -- description: Text shown in UI dropdown - -- speed: a number. When present, a speed field is shown in the UI next to the dropdown (prefilled with the value). - -- When user selects a different speed there, this different speed replaces the value in the table whenever the main_aspect is applied. -- Node can set any other fields at its discretion. They are not touched. - -- Note: On first call advtrains automatically inserts into the ndef.advtrains table a main_aspects_lookup hashtable - -- Note: Pure distant signals (that cannot show halt) should NOT have a main_aspects table + -- Note: Pure distant signals (that cannot show halt) should NOT have a main_aspects table. + -- For these signals no main aspect selection UI is shown and they cannot be startpoint of a route apply_aspect = function(pos, node, main_aspect, rem_aspect, rem_aspinfo) -- set the node to show the desired aspect -- called by advtrains when this signal's aspect group or the remote signal's aspect changes - -- main_aspect is never nil, but can be one of the special aspects { name = "_halt", halt = true } or { name = "_default" } + -- main_aspect is never nil, but can be one of the special aspects { halt = true } or { default = true } -- MAY return the aspect_info. If it returns nil then get_aspect_info will be queried at a later point. get_aspect_info(pos, main_aspect) -- Returns the aspect info table (main, shunt, dst etc.) @@ -133,7 +128,9 @@ Signals that are possible start and end points for a route must satisfy: -- Database -- Signal Aspect store -- Stores for each signal the main aspect and other info, like the assigned remote signal --- [signal encodePos] = { name = "proceed", [speed = 12], [remote = encodedPos] } +-- [signal encodePos] = { main = , [remote = encodedPos] } +-- main is a string: "named aspect" is looked up in the main_aspects table of the ndef +-- main is a table: this table directly is the main aspect (used for advanced signals with additional lights/indicators) signal.aspects = {} -- Distant signal notification. Records for each signal the distant signals that refer to it @@ -167,7 +164,12 @@ end -- - Calling apply_aspect() in the signal's node definition to make the signal show the aspect -- - Calling apply_aspect() again whenever the remote signal changes its aspect -- - Notifying this signal's distant signals about changes to this signal (unless skip_dst_notify is specified) -function signal.set_aspect(pos, main_asp_name, main_asp_speed, rem_pos, skip_dst_notify) +-- main_asp: either a string (==name in ndef.advtrains.main_aspects) or the main aspect table directly (for advanced signals) +function signal.set_aspect(pos, main_asp, rem_pos, skip_dst_notify) + -- safeguard for the two integrated aspects (these two must be passed as string key) + if type(main_asp)=="table" and (main_asp.name=="_default" or main_asp.name=="_halt") then + error("MASP_HALT and MASP_DEFAULT must be passed via string keys _halt or _default, not as tables!") + end local main_pts = advtrains.encode_pos(pos) local old_tbl = signal.aspects[main_pts] local old_remote = old_tbl and old_tbl.remote @@ -179,7 +181,7 @@ function signal.set_aspect(pos, main_asp_name, main_asp_speed, rem_pos, skip_dst signal.distant_refs[old_remote][main_pts] = nil end - signal.aspects[main_pts] = { name = main_asp_name, speed = main_asp_speed, remote = new_remote } + signal.aspects[main_pts] = { main = main_asp, remote = new_remote } -- apply aspect on main signal, this also checks new_remote signal.reapply_aspect(main_pts) @@ -266,49 +268,43 @@ end local function cache_mainaspects(ndefat) ndefat.main_aspects_lookup = {} - for _,ma in ipairs(ndefat.main_aspects) do + for _,ma in ipairs(ndefat.main_aspects) do ndefat.main_aspects_lookup[ma.name] = ma - end + end + ndefat.main_aspects_lookup[signal.MASP_HALT.name] = signal.MASP_HALT.name -- halt is always defined + ndefat.main_aspects_lookup[signal.MASP_DEFAULT.name] = ndefat.main_aspects[1] -- default is the first one end + +-- gets the main aspect. resolves named aspects to aspect table on demand function signal.get_aspect_internal(pos, aspt) - atdebug("get_aspect_internal",pos,aspt) - -- look aspect in nodedef + -- look up node and nodedef local node = advtrains.ndb.get_node_or_nil(pos) local ndef = node and minetest.registered_nodes[node.name] if not aspt then -- oh, no main aspect, nevermind return signal.MASP_HALT, nil, node, ndef end - local ndefat = ndef and ndef.advtrains - if ndefat and ndefat.apply_aspect then - -- only if signal defines main aspect and its set in aspt - if ndefat.main_aspects and aspt.name then + local ndefat = ndef.advtrains or {} + local masp = aspt.main or signal.MASP_HALT + + if type(masp) == "string" then + if masp=="_halt" then + masp = signal.MASP_HALT + elseif masp=="_default" and not ndefat.main_aspects then + -- case is fine, distant only signal + masp = signal.MASP_DEFAULT + else + assert(ndefat.main_aspects, "With named aspects, node needs advtrains.main_aspects table!") + -- resolve the main aspect from the mainaspects table if not ndefat.main_aspects_lookup then cache_mainaspects(ndefat) end - local masp = ndefat.main_aspects_lookup[aspt.name] - -- special handling for the default free aspect ("_default") - if aspt.name == "_default" then - masp = ndefat.main_aspects[1] - end - if not masp then - atwarn(pos,"invalid main aspect",aspt.name,"valid are",ndefat.main_aspects_lookup) - return signal.MASP_HALT, aspt.remote, node, ndef - end - -- if speed, then apply speed - if masp.speed and aspt.speed then - masp = table.copy(masp) - masp.speed = aspt.speed - end - return masp, aspt.remote, node, ndef - elseif aspt.name then - -- Distant-only signal, still supports kind of default aspect - return { name = aspt.name, speed = aspt.speed }, aspt.remote, node, ndef + masp = ndefat.main_aspects_lookup[aspt.main] or signal.MASP_DEFAULT end end - -- invalid node or no main aspect, return default halt aspect for masp - return signal.MASP_HALT, aspt.remote, node, ndef + -- return whatever the main aspect is + return masp, aspt.remote, node, ndef end -- For the signal at pos, get the "aspect info" table. This contains the speed signalling information at this location @@ -336,10 +332,6 @@ function signal.reapply_aspect(pts) local aspt = signal.aspects[pts] atdebug("reapply_aspect",advtrains.decode_pos(pts),"aspt",aspt) local pos = advtrains.decode_pos(pts) - if not aspt then - signal.notify_trains(pos) - return -- oop, nothing to do - end -- resolve mainaspect table by name local masp, remote, node, ndef = signal.get_aspect_internal(pos, aspt) -- if we have remote, resolve remote @@ -352,13 +344,11 @@ function signal.reapply_aspect(pts) signal.distant_refs[remote][pts] = true local rem_aspt = signal.aspects[remote] atdebug("resolving remote",advtrains.decode_pos(remote),"aspt",rem_aspt) - if rem_aspt and rem_aspt.name then - local rem_pos = advtrains.decode_pos(remote) - rem_masp, _, _, rem_ndef = signal.get_aspect_internal(rem_pos, rem_aspt) - if rem_masp then - if rem_ndef.advtrains and rem_ndef.advtrains.get_aspect_info then - rem_aspi = rem_ndef.advtrains.get_aspect_info(rem_pos, rem_masp) - end + local rem_pos = advtrains.decode_pos(remote) + rem_masp, _, _, rem_ndef = signal.get_aspect_internal(rem_pos, rem_aspt) + if rem_masp then + if rem_ndef.advtrains and rem_ndef.advtrains.get_aspect_info then + rem_aspi = rem_ndef.advtrains.get_aspect_info(rem_pos, rem_masp) end end end diff --git a/advtrains_interlocking/signal_aspect_ui.lua b/advtrains_interlocking/signal_aspect_ui.lua index 785e6d4..5ec4c50 100644 --- a/advtrains_interlocking/signal_aspect_ui.lua +++ b/advtrains_interlocking/signal_aspect_ui.lua @@ -121,11 +121,11 @@ function advtrains.interlocking.handle_ip_sa_formspec_fields(pname, pos, fields) new_ma = ndef.advtrains.main_aspects[idx - 1] end end - if new_ma and (new_ma.name ~= ma.name or new_ma.speed ~= ma.speed) then - advtrains.interlocking.signal.set_aspect(pos, new_ma.name, new_ma.speed, rpos) - elseif not new_ma then + if new_ma then + advtrains.interlocking.signal.set_aspect(pos, new_ma.name, rpos) + else -- reset everything - advtrains.interlocking.signal.set_aspect(pos, nil, nil, nil) + advtrains.interlocking.signal.clear_aspect(pos) end end @@ -140,7 +140,7 @@ function advtrains.interlocking.handle_ip_sa_formspec_fields(pname, pos, fields) advtrains.interlocking.init_distant_assign(pos, pname) return elseif fields.sa_undistant then - advtrains.interlocking.signal.set_aspect(pos, ma.name, ma.speed, nil) + advtrains.interlocking.signal.set_aspect(pos, ma.name, nil) return end -- show the form again unless one of the buttons was clicked @@ -226,10 +226,10 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) local nrpos if advtrains.interlocking.signal.get_signal_cap_level(pos) > 1 then nrpos = pos - if not ma then -- make sure that dst is never set without a main aspect (esp. for pure distant signal case) - ma = { name = "_default" } + if not ma or ma.halt then -- make sure that dst is never set without a main aspect (esp. for pure distant signal case) + ma = "_default" end - advtrains.interlocking.signal.set_aspect(signalpos, ma.name, ma.speed, nrpos) + advtrains.interlocking.signal.set_aspect(signalpos, ma.name, nrpos) end players_assign_distant[pname] = nil end diff --git a/advtrains_signals_ks/init.lua b/advtrains_signals_ks/init.lua index 52f8c58..46b9971 100755 --- a/advtrains_signals_ks/init.lua +++ b/advtrains_signals_ks/init.lua @@ -53,6 +53,7 @@ local applyaspectf_main = function(rot) if main_aspect.halt then -- halt aspect, set red and don't do anything further advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_danger_"..rot, param2 = node.param2}) + setzs3(pos, "off", rot) setzs3v(pos, nil, rot) return end diff --git a/advtrains_signals_muc_ubahn/init.lua b/advtrains_signals_muc_ubahn/init.lua index baf5d82..4088ba6 100755 --- a/advtrains_signals_muc_ubahn/init.lua +++ b/advtrains_signals_muc_ubahn/init.lua @@ -46,7 +46,7 @@ local function applyaspect_distant(loc) end for r,f in pairs(all_sigs) do - for loc, sbox in pairs({l={-1/2, -1/2, -1/4, 0, 1/2, 1/4}, r={0, -1/2, -1/4, 1/2, 1/2, 1/4}, t={-1/2, 0, -1/4, 1/2, 1/2, 1/4}}) do + for loc, sbox in pairs({l={-1/2, -1/4, -1/8, -1/4, 1/4, 1/8}, r={1/4, -1/4, -1/8, 1/2, 1/4, 1/8}, t={-1/4, 1/4, -1/8, 1/4, 1/2, 1/8}}) do minetest.register_node("advtrains_signals_muc_ubahn:signal_wall_"..loc.."_"..r, { drawtype = "mesh", paramtype="light", -- cgit v1.2.3 From 72cae1327527672afe2bbb47662d42a44f086942 Mon Sep 17 00:00:00 2001 From: orwell Date: Tue, 11 Jun 2024 22:30:40 +0200 Subject: Fix more setstate stuff with new API (compat legacy) --- advtrains/p_mesecon_iface.lua | 24 ++++++++---------- advtrains/passive.lua | 6 ----- advtrains/signals.lua | 47 +++++++++++++++++++---------------- advtrains_interlocking/signal_api.lua | 15 +++++++++++ advtrains_signals_japan/init.lua | 6 ++--- 5 files changed, 54 insertions(+), 44 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains/p_mesecon_iface.lua b/advtrains/p_mesecon_iface.lua index 33fcecd..0b99891 100644 --- a/advtrains/p_mesecon_iface.lua +++ b/advtrains/p_mesecon_iface.lua @@ -14,13 +14,11 @@ minetest.override_item("mesecons_switch:mesecon_switch_off", { minetest.sound_play("mesecons_switch", {pos=pos}) end, advtrains = { - getstate = "off", - setstate = function(pos, node, newstate) - if newstate=="on" then - advtrains.ndb.swap_node(pos, {name="mesecons_switch:mesecon_switch_on", param2=node.param2}) - if advtrains.is_node_loaded(pos) then - mesecon.receptor_on(pos) - end + node_state = "off", + node_state_map = { on = "mesecons_switch:mesecon_switch_on", off = "mesecons_switch:mesecon_switch_off" }, + node_on_switch_state = function(pos, new_node, old_state, new_state) + if advtrains.is_node_loaded(pos) then + mesecon.receptor_on(pos) end end, on_updated_from_nodedb = function(pos, node) @@ -41,13 +39,11 @@ minetest.override_item("mesecons_switch:mesecon_switch_on", { minetest.sound_play("mesecons_switch", {pos=pos}) end, advtrains = { - getstate = "on", - setstate = function(pos, node, newstate) - if newstate=="off" then - advtrains.ndb.swap_node(pos, {name="mesecons_switch:mesecon_switch_off", param2=node.param2}) - if advtrains.is_node_loaded(pos) then - mesecon.receptor_off(pos) - end + node_state = "on", + node_state_map = { on = "mesecons_switch:mesecon_switch_on", off = "mesecons_switch:mesecon_switch_off" }, + node_on_switch_state = function(pos, new_node, old_state, new_state) + if advtrains.is_node_loaded(pos) then + mesecon.receptor_off(pos) end end, fallback_state = "off", diff --git a/advtrains/passive.lua b/advtrains/passive.lua index aad309e..37b79e4 100644 --- a/advtrains/passive.lua +++ b/advtrains/passive.lua @@ -70,12 +70,6 @@ function advtrains.setstate(parpos, newstate, pnode) end -- invalidate paths (only relevant if this is a track) advtrains.invalidate_all_paths(pos) - -- hack for old signals. Compatibility only, DO NOT USE for new signals! - if advtrains.interlocking and ndef.advtrains._is_passivenode_signal then - -- forcefully clears any set aspect, so that aspect system doesnt override it again - -- implicitly does an signal.notify_trains(pos) - advtrains.interlocking.signal.clear_aspect(pos) - end return true end diff --git a/advtrains/signals.lua b/advtrains/signals.lua index 0b874bf..3f736c9 100644 --- a/advtrains/signals.lua +++ b/advtrains/signals.lua @@ -74,8 +74,9 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2}, true) if advtrains.interlocking then -- forcefully clears any set aspect, so that aspect system doesnt override it again - -- implicitly does an signal.notify_trains(pos) - advtrains.interlocking.signal.clear_aspect(pos) + advtrains.interlocking.signal.unregister_aspect(pos) + -- notify trains + advtrains.interlocking.signal.notify_trains(pos) end end }}, @@ -90,8 +91,9 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2}, true) if advtrains.interlocking then -- forcefully clears any set aspect, so that aspect system doesnt override it again - -- implicitly does an signal.notify_trains(pos) - advtrains.interlocking.signal.clear_aspect(pos) + advtrains.interlocking.signal.unregister_aspect(pos) + -- notify trains + advtrains.interlocking.signal.notify_trains(pos) end end end, @@ -133,9 +135,6 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", rules=advtrains.meseconrules, ["action_"..f.as] = function (pos, node) advtrains.setstate(pos, f.als, node) - if advtrains.interlocking then - advtrains.interlocking.signal.notify_on_aspect_changed(pos) - end end }}, on_rightclick=function(pos, node, player) @@ -156,7 +155,14 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", get_aspect_info = function() return aspect(r=="on") end, node_state = f.ls, node_state_map = { red = "advtrains:signal_off"..rotation, green = "advtrains:signal_on"..rotation}, - _is_passivenode_signal = true + node_on_switch_state = function(pos, new_node, old_state, new_state) + if advtrains.interlocking then + -- forcefully clears any set aspect, so that aspect system doesnt override it again + advtrains.interlocking.signal.unregister_aspect(pos) + -- notify trains + advtrains.interlocking.signal.notify_trains(pos) + end + end, }, can_dig = can_dig_func, after_dig_node = after_dig_func, @@ -215,7 +221,14 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", get_aspect_info = function() return aspect(r=="on") end, node_state = f.ls, node_state_map = { red = "advtrains:signal_wall_"..loc.."_off", green = "advtrains:signal_wall_"..loc.."_on" }, - _is_passivenode_signal = true + node_on_switch_state = function(pos, new_node, old_state, new_state) + if advtrains.interlocking then + -- forcefully clears any set aspect, so that aspect system doesnt override it again + advtrains.interlocking.signal.unregister_aspect(pos) + -- notify trains + advtrains.interlocking.signal.notify_trains(pos) + end + end, }, can_dig = can_dig_func, after_dig_node = after_dig_func, @@ -253,12 +266,8 @@ minetest.register_node("advtrains:across_off", { end }}, advtrains = { - getstate = "off", - setstate = function(pos, node, newstate) - if newstate == "on" then - advtrains.ndb.swap_node(pos, {name = "advtrains:across_on", param2 = node.param2}, true) - end - end, + node_state = "off", + node_state_map = { on = "advtrains:across_on", off = "advtrains:across_off" }, }, on_rightclick=function(pos, node, player) if advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then @@ -294,12 +303,8 @@ minetest.register_node("advtrains:across_on", { end }}, advtrains = { - getstate = "on", - setstate = function(pos, node, newstate) - if newstate == "off" then - advtrains.ndb.swap_node(pos, {name = "advtrains:across_off", param2 = node.param2}, true) - end - end, + node_state = "on", + node_state_map = { on = "advtrains:across_on", off = "advtrains:across_off" }, fallback_state = "off", }, on_rightclick=function(pos, node, player) diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index a300ab1..b1e8b20 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -211,6 +211,21 @@ function signal.clear_aspect(pos, skip_dst_notify) end end +-- Clear any info about aspects from this signal, without resetting/reapplying the aspect. +-- Supposed to be used for legacy on-off signals when the on-off toggle is used +function signal.unregister_aspect(pos) + local main_pts = advtrains.encode_pos(pos) + local old_tbl = signal.aspects[main_pts] + local old_remote = old_tbl and old_tbl.remote + + -- unregister from old remote + if old_remote then + signal.distant_refs[old_remote][main_pts] = nil + end + + signal.aspects[main_pts] = nil +end + -- Notify distant signals of main_pts of a change in the aspect of this signal -- function signal.notify_distants_of(main_pts, limit) diff --git a/advtrains_signals_japan/init.lua b/advtrains_signals_japan/init.lua index d7cf035..fc33e63 100644 --- a/advtrains_signals_japan/init.lua +++ b/advtrains_signals_japan/init.lua @@ -447,9 +447,9 @@ for _, rtab in ipairs { end, ]] }, - on_rightclick = advtrains.interlocking.signal_rc_handler, - can_dig = advtrains.interlocking.signal_can_dig, - after_dig_node = advtrains.interlocking.signal_after_dig, + on_rightclick = advtrains.interlocking.signal.on_rightclick, + can_dig = advtrains.interlocking.signal.can_dig, + after_dig_node = advtrains.interlocking.signal.after_dig, }) --advtrains.trackplacer.add_worked("advtrains_signals_japan:"..sigtype, asp, "_"..rot) end -- cgit v1.2.3 From dcceb65ff04573375016f2460edcbd349e506a4e Mon Sep 17 00:00:00 2001 From: orwell Date: Wed, 12 Jun 2024 00:25:14 +0200 Subject: Respect route_role of signals during routesetting, assign distant signals in routes --- advtrains_interlocking/routesetting.lua | 46 +++++++++++++++++++++++-------- advtrains_interlocking/signal_api.lua | 22 +++++++++++---- advtrains_interlocking/tcb_ts_ui.lua | 3 +- advtrains_interlocking/train_sections.lua | 33 ++++++++-------------- advtrains_signals_japan/init.lua | 1 + advtrains_signals_ks/init.lua | 2 ++ advtrains_signals_muc_ubahn/init.lua | 1 + 7 files changed, 69 insertions(+), 39 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua index 51709dc..d619aac 100644 --- a/advtrains_interlocking/routesetting.lua +++ b/advtrains_interlocking/routesetting.lua @@ -45,6 +45,7 @@ function ilrs.set_route(signal, route, try) local rtename = route.name local signalname = (ildb.get_tcbs(signal).signal_name or "") .. sigd_to_string(signal) local c_tcbs, c_ts_id, c_ts, c_rseg, c_lckp + -- signals = { { pos = ., tcbs_ref = , role = "main_distant", masp_override = nil, dst_type = "next_main" or "none" } local signals = {} local nodst while c_sigd and i<=#route do @@ -67,11 +68,11 @@ function ilrs.set_route(signal, route, try) if c_ts.route then if not try then atwarn("Encountered ts lock during a real run of routesetting routine, at ts=",c_ts_id,"while setting route",rtename,"of",signal) end - return false, "Section '"..c_ts.name.."' already has route set from "..sigd_to_string(c_ts.route.origin)..":\n"..c_ts.route.rsn, c_ts_id, nil + return false, "Section '"..(c_ts.name or c_ts_id).."' already has route set from "..sigd_to_string(c_ts.route.origin)..":\n"..c_ts.route.rsn, c_ts_id, nil end if c_ts.trains and #c_ts.trains>0 then if not try then atwarn("Encountered ts occupied during a real run of routesetting routine, at ts=",c_ts_id,"while setting route",rtename,"of",signal) end - return false, "Section '"..c_ts.name.."' is occupied!", c_ts_id, nil + return false, "Section '"..(c_ts.name or c_ts_id).."' is occupied!", c_ts_id, nil end -- collect locks from rs cache and from route def @@ -138,9 +139,17 @@ function ilrs.set_route(signal, route, try) } if c_tcbs.signal then c_tcbs.route_committed = true - c_tcbs.aspect = advtrains.interlocking.signal.MASP_FREE c_tcbs.route_origin = signal - signals[#signals+1] = c_tcbs + -- determine route role + local ndef = advtrains.ndb.get_ndef(c_tcbs.signal) + local sig_table = { + pos = c_tcbs.signal, + tcbs_ref = c_tcbs, + role = ndef and ndef.advtrains and ndef.advtrains.route_role, + masp_override = c_rseg.masp_override, --TODO implement masp_override on UI side + dst_type = "next_main", --TODO allow user differentiate + } + signals[#signals+1] = sig_table end end -- advance @@ -149,18 +158,31 @@ function ilrs.set_route(signal, route, try) i = i + 1 end - -- Distant signaling - local lastsig = nil + -- Get reference to signal at end of route + local last_mainsig = nil if c_sigd then local e_tcbs = ildb.get_tcbs(c_sigd) local pos = e_tcbs and e_tcbs.signal if pos then - lastsig = pos + last_mainsig = pos end end for i = #signals, 1, -1 do - -- TODO add logic for distant signal assign - advtrains.interlocking.signal.update_route_aspect(signals[i], i ~= 1) + -- note the signals are iterated backwards. Switch depending on the role + local sig = signals[i] + -- apply mainaspect + sig.tcbs_ref.route_aspect = sig.masp_override or "_default" + if sig.role == "distant" or sig.role == "distant_repeater" or sig.role == "main_distant" then + -- assign the remote as the last mainsig + sig.tcbs_ref.route_remote = last_mainsig + end + if sig.role == "main" or sig.role == "main_distant" or sig.role == "end" then + -- record this as the new last mainsig + last_mainsig = sig.pos + end + -- for shunt signals nothing happens + -- update the signal aspect on map + advtrains.interlocking.signal.update_route_aspect(sig.tcbs_ref, i ~= 1) end return true @@ -266,7 +288,8 @@ function ilrs.cancel_route_from(sigd) --atdebug("cancelling",c_ts.route.rsn) -- clear signal aspect and routesetting state c_tcbs.route_committed = nil - c_tcbs.aspect = nil + c_tcbs.route_aspect = nil + c_tcbs.route_remote = nil c_tcbs.routeset = nil c_tcbs.route_auto = nil c_tcbs.route_origin = nil @@ -321,7 +344,8 @@ function ilrs.update_route(sigd, tcbs, newrte, cancel) advtrains.interlocking.route.cancel_route_from(sigd) end tcbs.route_committed = nil - tcbs.aspect = nil + tcbs.route_aspect = nil + tcbs.route_remote = nil has_changed_aspect = true tcbs.routeset = nil tcbs.route_auto = nil diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index b1e8b20..65fc787 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -96,11 +96,12 @@ ndef.advtrains = { -- Returns the aspect info table (main, shunt, dst etc.) distant_support = true or false -- If true, signal is considered in distant signalling. If false or nil, rem_aspect and rem_aspinfo are never set. - route_role = one of "main", "shunt", "distant", "distant_repeater", "end" + route_role = one of "main", "main_distant", "shunt", "distant", "distant_repeater", "end" -- Determines how the signal behaves when routes are set. Only in effect when signal is assigned to a TCB. -- main: The signal is a possible endpoint for a train move route. Distant signals before it refer to it. -- shunt: The signal is a possible endpoint for a shunt move route. Ignored for distant signals. -- distant, distant_repeater: When route is set, signal is always assigned its first main aspect. The next signal with role="main" is set as the remote signal. (currently no further distinction) + -- main_distant: Combination of main and distant - like "main", but additionally gets assigned to the next main like a "distant" -- end: like main, but signifies that it marks an end of track and trains cannot continue further. (currently no practical implications above main) } @@ -329,9 +330,17 @@ function signal.get_aspect_info(pos) local masp, remote, node, ndef = signal.get_aspect_internal(pos, aspt) -- call into ndef if ndef.advtrains and ndef.advtrains.get_aspect_info then - local ai = ndef.advtrains.get_aspect_info(pos, masp) - atdebug(pos,"aspectinfo",ai) - return ai + local ai = ndef.advtrains.get_aspect_info + if type(ai)=="function" then + ai = ai(pos, masp) + end + if type(ai)=="table" then + atdebug(pos,"aspectinfo",ai) + return ai + else + error("For node "..node.name..": ndef.advtrains.get_aspect_info must be function or table") + end + end end @@ -380,8 +389,9 @@ end -- function signal.update_route_aspect(tcbs, skip_dst_notify) if tcbs.signal then - local asp = tcbs.aspect or signal.MASP_HALT - signal.set_aspect(tcbs.signal, asp.name, asp.speed, asp.remote, skip_dst_notify) + local asp = tcbs.route_aspect or "_halt" + local rem = tcbs.route_remote + signal.set_aspect(tcbs.signal, asp, rem, skip_dst_notify) end end diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index caf22e3..60be5f3 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -756,7 +756,8 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) local signal_pos = tcbs.signal ildb.set_sigd_for_signal(signal_pos, nil) tcbs.signal = nil - tcbs.aspect = nil + tcbs.route_aspect = nil + tcbs.route_remote = nil minetest.close_formspec(pname, formname) minetest.chat_send_player(pname, "Signal has been unassigned. Name and routes are kept for reuse.") return diff --git a/advtrains_interlocking/train_sections.lua b/advtrains_interlocking/train_sections.lua index 260f5a4..41da747 100644 --- a/advtrains_interlocking/train_sections.lua +++ b/advtrains_interlocking/train_sections.lua @@ -86,31 +86,22 @@ local function setsection(tid, train, ts_id, ts, sigd) advtrains.interlocking.route.cancel_route_from(ts.route.origin) atwarn("Route was cancelled.") else - -- train entered route regularily. Reset route and signal - tcbs.route_committed = nil - tcbs.route_comitted = nil -- TODO compatibility cleanup - tcbs.aspect = nil - tcbs.route_origin = nil - if tcbs.signal then - local spos = tcbs.signal - local _, setter = advtrains.distant.get_main(spos) - if setter == "routesetting" then - advtrains.distant.unassign_dst(spos, true) - end - end - advtrains.interlocking.update_signal_aspect(tcbs) - if tcbs.signal and sigd_equal(ts.route.entry, ts.route.origin) then - if tcbs.route_auto and tcbs.routeset then - --atdebug("Resetting route (",ts.route.origin,")") - advtrains.interlocking.route.update_route(ts.route.origin, tcbs) - else - tcbs.routeset = nil - end - end + -- train entered route regularily. end ts.route = nil end if tcbs.signal then + -- Reset route and signal + -- Note that the hit-route case is already handled by cancel_route_from + -- this code only handles signal at entering tcb and also triggers for non-route ts + tcbs.route_committed = nil + tcbs.route_aspect = nil + tcbs.route_remote = nil + tcbs.route_origin = nil + if not tcbs.route_auto then + tcbs.routeset = nil + end + advtrains.interlocking.signal.update_route_aspect(tcbs) advtrains.interlocking.route.update_route(sigd, tcbs) end end diff --git a/advtrains_signals_japan/init.lua b/advtrains_signals_japan/init.lua index fc33e63..a659410 100644 --- a/advtrains_signals_japan/init.lua +++ b/advtrains_signals_japan/init.lua @@ -428,6 +428,7 @@ for _, rtab in ipairs { proceed_as_main = true, } end, + route_role = "main_distant", --[[ supported_aspects = { group = "advtrains_signals_japan:5a", diff --git a/advtrains_signals_ks/init.lua b/advtrains_signals_ks/init.lua index 46b9971..6afc66d 100755 --- a/advtrains_signals_ks/init.lua +++ b/advtrains_signals_ks/init.lua @@ -218,6 +218,7 @@ for _, rtab in ipairs({ main_aspects = mainaspects_main, apply_aspect = applyaspectf_main(rot), get_aspect_info = afunc, + route_role = "main_distant", }, on_rightclick = advtrains.interlocking.signal.on_rightclick, can_dig = advtrains.interlocking.signal.can_dig, @@ -261,6 +262,7 @@ for _, rtab in ipairs({ main_aspects = mainaspects_ra, apply_aspect = applyaspectf_ra(rot), get_aspect_info = prts.asp, + route_role = "shunt", }, on_rightclick = advtrains.interlocking.signal.on_rightclick, can_dig = advtrains.interlocking.signal.can_dig, diff --git a/advtrains_signals_muc_ubahn/init.lua b/advtrains_signals_muc_ubahn/init.lua index 4088ba6..59d0337 100755 --- a/advtrains_signals_muc_ubahn/init.lua +++ b/advtrains_signals_muc_ubahn/init.lua @@ -77,6 +77,7 @@ for r,f in pairs(all_sigs) do main_aspects = not f.distant and mainaspects, -- main aspects only for main apply_aspect = f.distant and applyaspect_distant(loc) or applyaspect_main(loc), get_aspect_info = function() return f.asp end, + route_role = f.distant and "distant" or "main" }, }) end -- cgit v1.2.3 From 3606a9bdfcd3dda888ff528b7e5071d35df05ef9 Mon Sep 17 00:00:00 2001 From: orwell Date: Sat, 22 Jun 2024 19:35:48 +0200 Subject: Unassign signal on dig instead of needing button in signal form --- advtrains_interlocking/signal_api.lua | 23 ++++++++++++++++++++--- advtrains_interlocking/tcb_ts_ui.lua | 28 ++++++++-------------------- 2 files changed, 28 insertions(+), 23 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 65fc787..eddf9da 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -419,12 +419,29 @@ end ---------------- function signal.can_dig(pos) - return not advtrains.interlocking.db.get_sigd_for_signal(pos) + local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) + if sigd then + local tcbs = advtrains.interlocking.db.get_tcbs(sigd) + if tcbs.routeset then + return false + end + end + return true end -function signal.after_dig(pos) +function signal.after_dig(pos, oldnode, oldmetadata, player) + -- unassign signal if necessary + local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) + if sigd then + local tcbs = advtrains.interlocking.db.get_tcbs(sigd) + advtrains.interlocking.db.set_sigd_for_signal(pos, nil) + tcbs.signal = nil + tcbs.route_aspect = nil + tcbs.route_remote = nil + minetest.chat_send_player(player:get_player_name(), "Signal has been unassigned. Name and routes are kept for reuse.") + end -- TODO clear influence point - advtrains.interlocking.signal.clear_aspect(pos) + advtrains.interlocking.signal.unregister_aspect(pos) end function signal.on_rightclick(pos, node, player, itemstack, pointed_thing) diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index 60be5f3..bdb0a18 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -639,8 +639,8 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle end end if hasprivs then - form = form.."button[0.5,8;2.5,1;newroute;New Route]" - form = form.."button[ 3,8;2.5,1;unassign;Unassign Signal]" + 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) end elseif sigd_equal(tcbs.route_origin, sigd) then @@ -730,6 +730,12 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) tcbs.ars_ignore_next = nil return end + if fields.smartroute and hasprivs then + advtrains.interlocking.smartroute.init(pname, sigd) + minetest.close_formspec(pname, formname) + tcbs.ars_ignore_next = nil + return + end if sel_rte and tcbs.routes[sel_rte] then if fields.setroute then ilrs.update_route(sigd, tcbs, sel_rte) @@ -748,24 +754,6 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end end - if 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 signal - if not tcbs.routeset then - local signal_pos = tcbs.signal - ildb.set_sigd_for_signal(signal_pos, nil) - tcbs.signal = nil - tcbs.route_aspect = nil - tcbs.route_remote = nil - minetest.close_formspec(pname, formname) - minetest.chat_send_player(pname, "Signal has been unassigned. Name and routes are kept for reuse.") - return - else - minetest.chat_send_player(pname, "Please cancel route first!") - end - end - if fields.ars then tcbs.ars_disabled = not minetest.is_yes(fields.ars) end -- cgit v1.2.3 From baa50c03920fd0a563fce09929f3d56c3374e8bd Mon Sep 17 00:00:00 2001 From: orwell Date: Sat, 20 Jul 2024 18:02:33 +0200 Subject: ARS supports triggering distant signal, other bugfixes --- advtrains/lzb.lua | 2 +- advtrains/path.lua | 2 +- advtrains/trackplacer.lua | 8 ++-- advtrains/trainlogic.lua | 6 +-- advtrains_interlocking/approach.lua | 5 +-- advtrains_interlocking/ars.lua | 66 ++++++++++++++++++++----------- advtrains_interlocking/route_prog.lua | 4 +- advtrains_interlocking/routesetting.lua | 4 +- advtrains_interlocking/signal_api.lua | 2 +- advtrains_interlocking/tcb_ts_ui.lua | 7 +++- advtrains_interlocking/train_sections.lua | 7 +++- advtrains_signals_ks/init.lua | 22 +++++++++-- 12 files changed, 87 insertions(+), 48 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains/lzb.lua b/advtrains/lzb.lua index 64e4553..52c2289 100644 --- a/advtrains/lzb.lua +++ b/advtrains/lzb.lua @@ -48,7 +48,7 @@ local params = { ZONE_HOLD = 5, -- added on top of ZONE_ROLL ZONE_VSLOW = 3, -- When speed is <2, still allow accelerating - DST_FACTOR = 1.5, + DST_FACTOR = 3,--1.5, SHUNT_SPEED_MAX = advtrains.SHUNT_SPEED_MAX, } diff --git a/advtrains/path.lua b/advtrains/path.lua index 28df529..d54aebe 100644 --- a/advtrains/path.lua +++ b/advtrains/path.lua @@ -256,7 +256,7 @@ function advtrains.path_get(train, index) if next_connmap then -- only needs to be done when this track is a turnout (>2 conns) local origin_conn = train.path_ori_cp[advtrains.encode_pos(adj_pos)] if origin_conn then - atdebug("Train",train.id,"at",adj_pos,"restoring turnout origin CP",origin_conn,"for path item",index) + --atdebug("Train",train.id,"at",adj_pos,"restoring turnout origin CP",origin_conn,"for path item",index) mconnid = origin_conn end end diff --git a/advtrains/trackplacer.lua b/advtrains/trackplacer.lua index e6111dc..597e8ec 100644 --- a/advtrains/trackplacer.lua +++ b/advtrains/trackplacer.lua @@ -288,8 +288,8 @@ minetest.register_craftitem("advtrains:trackworker",{ advtrains.ndb.swap_node(pos, new_node) end end, - on_use=function(itemstack, user, pointed_thing) - local name = user:get_player_name() + on_use=function(itemstack, player, pointed_thing) + local name = player:get_player_name() if not name then return end @@ -305,7 +305,7 @@ minetest.register_craftitem("advtrains:trackworker",{ local ndef = minetest.registered_nodes[node.name] if not ndef.advtrains or not ndef.advtrains.trackworker_next_var then - minetest.chat_send_player(placer:get_player_name(), attrans("This node can't be changed using the trackworker!")) + minetest.chat_send_player(name, attrans("This node can't be changed using the trackworker!")) return end @@ -318,7 +318,7 @@ minetest.register_craftitem("advtrains:trackworker",{ if reason then str = str .. " " .. reason end - minetest.chat_send_player(placer:get_player_name(), str) + minetest.chat_send_player(name, str) return end end diff --git a/advtrains/trainlogic.lua b/advtrains/trainlogic.lua index cb1f9a6..9e9f214 100644 --- a/advtrains/trainlogic.lua +++ b/advtrains/trainlogic.lua @@ -865,7 +865,7 @@ local function tnc_call_enter_callback(pos, train_id, train, index) -- check for split points if mregnode and mregnode.at_conn_map then -- If this node has >2 conns (and a connmap), remember the connection where we came from to handle split points - atdebug("Train",train_id,"at",pos,"saving turnout origin CP",train.path_cp[index],"for path item",index) + --atdebug("Train",train_id,"at",pos,"saving turnout origin CP",train.path_cp[index],"for path item",index) train.path_ori_cp[advtrains.encode_pos(pos)] = train.path_cp[index] end end @@ -883,7 +883,7 @@ local function tnc_call_leave_callback(pos, train_id, train, index) -- split points do not matter anymore. clear them if mregnode and mregnode.at_conn_map then -- If this node has >2 conns (and a connmap), remember the connection where we came from to handle split points - atdebug("Train",train_id,"at",pos,"removing turnout origin CP for path item",index," because train has left it") + --atdebug("Train",train_id,"at",pos,"removing turnout origin CP for path item",index," because train has left it") train.path_ori_cp[advtrains.encode_pos(pos)] = nil end end @@ -1188,7 +1188,7 @@ function advtrains.invert_train(train_id) local pos = advtrains.path_get(train, index) local ok, conns, railheight, connmap = advtrains.get_rail_info_at(pos) if ok and connmap then - atdebug("Reversing Train",train.id," ori_cp Checks: at",pos,"saving turnout origin CP",train.path_cn[index],"for path item",index) + --atdebug("Reversing Train",train.id," ori_cp Checks: at",pos,"saving turnout origin CP",train.path_cn[index],"for path item",index) ori_cp_after_flip[advtrains.encode_pos(pos)] = train.path_cn[index] end end diff --git a/advtrains_interlocking/approach.lua b/advtrains_interlocking/approach.lua index eecf09a..eaf0248 100644 --- a/advtrains_interlocking/approach.lua +++ b/advtrains_interlocking/approach.lua @@ -64,10 +64,7 @@ advtrains.tnc_register_on_approach(function(pos, id, train, index, has_entered, -- resetting the path does not matter to the set route and ARS doesn't need to be called again. if spos and ars_enabled then --atdebug(id,"IL Spos (ARS)",spos,asp) - local sigd = il.db.get_sigd_for_signal(spos) - if sigd then - il.ars_check(sigd, train) - end + il.ars_check(spos, train) end --atdebug("trav: ",pos, cn, asp, spos, "travsht=", lzb.travsht) local lspd diff --git a/advtrains_interlocking/ars.lua b/advtrains_interlocking/ars.lua index 4f50df9..b3065ee 100644 --- a/advtrains_interlocking/ars.lua +++ b/advtrains_interlocking/ars.lua @@ -129,29 +129,49 @@ function il.ars_check_rule_match(ars, train) return nil end -function advtrains.interlocking.ars_check(sigd, train) - local tcbs = il.db.get_tcbs(sigd) - if not tcbs or not tcbs.routes then return end - - if tcbs.ars_disabled or tcbs.ars_ignore_next then - -- No-ARS mode of signal. - -- ignore... - -- Note: ars_ignore_next is set by signalling formspec when route is cancelled - tcbs.ars_ignore_next = nil - return - end - - if tcbs.routeset then - -- ARS is not in effect when a route is already set - -- just "punch" routesetting, just in case callback got lost. - minetest.after(0, il.route.update_route, sigd, tcbs, nil, nil) - return +function advtrains.interlocking.ars_check(signalpos, train, trig_from_dst) + -- check for distant signal + -- this whole check must be delayed until after the route setting has taken place, + -- because before that the distant signal is yet unknown + if not trig_from_dst then + minetest.after(0.5, function() + -- does signal have dst? + local _, remote = il.signal.get_aspect(signalpos) + if remote then + advtrains.interlocking.ars_check(remote, train, true) + end + end) end - - local rteid = find_rtematch(tcbs.routes, train) - if rteid then - --delay routesetting, it should not occur inside train step - -- using after here is OK because that gets called on every path recalculation - minetest.after(0, il.route.update_route, sigd, tcbs, rteid, nil) + + local sigd = il.db.get_sigd_for_signal(signalpos) + local tcbs = sigd and il.db.get_tcbs(sigd) + -- trigger ARS on this signal + if tcbs and tcbs.routes then + + if tcbs.ars_disabled or tcbs.ars_ignore_next then + -- No-ARS mode of signal. + -- ignore... + -- Note: ars_ignore_next is set by signalling formspec when route is cancelled + tcbs.ars_ignore_next = nil + return + end + if trig_from_dst and tcbs.no_dst_ars_trig then + -- signal not to be triggered from distant + return + end + + if tcbs.routeset then + -- ARS is not in effect when a route is already set + -- just "punch" routesetting, just in case callback got lost. + minetest.after(0, il.route.update_route, sigd, tcbs, nil, nil) + return + end + + local rteid = find_rtematch(tcbs.routes, train) + if rteid then + --delay routesetting, it should not occur inside train step + -- using after here is OK because that gets called on every path recalculation + minetest.after(0, il.route.update_route, sigd, tcbs, rteid, nil) + end end end diff --git a/advtrains_interlocking/route_prog.lua b/advtrains_interlocking/route_prog.lua index 34807cd..37f751a 100644 --- a/advtrains_interlocking/route_prog.lua +++ b/advtrains_interlocking/route_prog.lua @@ -209,7 +209,7 @@ function advtrains.interlocking.visualize_route(origin, route, context, tmp_lcks end -- display locks set by player for pts, state in pairs(tmp_lcks) do - local pos = minetest.string_to_pos(pts) + local pos = advtrains.decode_pos(pts) routesprite(context, pos, "fixp"..pts, "at_il_route_lock_edit.png", "Fixed in state '"..state.."' by route "..route.name.." (punch to unfix)", function() clear_lock(tmp_lcks, pname, pts) end) end @@ -536,7 +536,7 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) return end if advtrains.is_passive(pos) then - local pts = advtrains.roundfloorpts(pos) + local pts = advtrains.encode_pos(pos) if rp.tmp_lcks[pts] then clear_lock(rp.tmp_lcks, pname, pts) else diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua index d619aac..34a273a 100644 --- a/advtrains_interlocking/routesetting.lua +++ b/advtrains_interlocking/routesetting.lua @@ -92,8 +92,8 @@ function ilrs.set_route(signal, route, try) end -- add all from locks, these override the rscache for lpts,lst in pairs(c_rseg.locks) do - atdebug("Add lock from Routedef:",lp,"->",lst,"overrides",c_locks[lp] or "none") - c_locks[lp] = lst + atdebug("Add lock from Routedef:",lpts,"->",lst,"overrides",c_locks[lpts] or "none") + c_locks[lpts] = lst end for lp, state in pairs(c_locks) do diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index eddf9da..cede405 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -279,7 +279,7 @@ end function signal.get_aspect(pos) local aspt = signal.aspects[advtrains.encode_pos(pos)] local ma,dp = signal.get_aspect_internal(pos, aspt) - return ma, advtrains.decode_pos(dp) + return ma, dp and advtrains.decode_pos(dp) end local function cache_mainaspects(ndefat) diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index 7f75bb9..82a57cf 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -645,6 +645,7 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle 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) end elseif sigd_equal(tcbs.route_origin, sigd) then -- something has gone wrong: tcbs.routeset should have been set... @@ -669,7 +670,7 @@ end function advtrains.interlocking.update_player_forms(sigd) for pname, tsigd in pairs(p_open_sig_form) do if advtrains.interlocking.sigd_equal(sigd, tsigd) then - advtrains.interlocking.show_signalling_form(sigd, pname, nil) + advtrains.interlocking.show_signalling_form(sigd, pname, nil, true) end end end @@ -761,6 +762,10 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) tcbs.ars_disabled = not minetest.is_yes(fields.ars) end + if fields.dstarstrig then + tcbs.no_dst_ars_trig = not minetest.is_yes(fields.dstarstrig) + end + if fields.auto then tcbs.route_auto = true end diff --git a/advtrains_interlocking/train_sections.lua b/advtrains_interlocking/train_sections.lua index 41da747..083676b 100644 --- a/advtrains_interlocking/train_sections.lua +++ b/advtrains_interlocking/train_sections.lua @@ -75,7 +75,10 @@ local function setsection(tid, train, ts_id, ts, sigd) end -- routes - local tcbs = advtrains.interlocking.db.get_tcbs(sigd) + local tcbs + if sigd then + tcbs = advtrains.interlocking.db.get_tcbs(sigd) + end -- route setting - clear route state if ts.route then @@ -90,7 +93,7 @@ local function setsection(tid, train, ts_id, ts, sigd) end ts.route = nil end - if tcbs.signal then + if tcbs and tcbs.signal then -- Reset route and signal -- Note that the hit-route case is already handled by cancel_route_from -- this code only handles signal at entering tcb and also triggers for non-route ts diff --git a/advtrains_signals_ks/init.lua b/advtrains_signals_ks/init.lua index d0ba6cd..c449416 100755 --- a/advtrains_signals_ks/init.lua +++ b/advtrains_signals_ks/init.lua @@ -258,16 +258,16 @@ for _, rtab in ipairs({ -- Vorsignal (NEU!) for typ, prts in pairs({ -- note: the names are taken from the main signal equivalent so that the same names for the lamp images can be used - slow = {asp = advtrains.interlocking.signal.ASPI_HALT, n = "nextslow", ici=true}, + slow = {asp = function(pos) return { dst = 0, shunt = true } end, n = "nextslow", ici=true}, nextslow = { asp = function(pos) - return { dst = getzs3v(pos) or 6 } + return { dst = getzs3v(pos) or 6, shunt = true } end, n = "free" }, free = { asp = function(pos) - return { dst = -1 } + return { dst = -1, shunt = true } end, n = "slow" }, @@ -399,7 +399,9 @@ for _, rtab in ipairs({ drop = "advtrains_signals_ks:"..prefix.."_"..dtyp.."_0", inventory_image = inv, advtrains = { - get_aspect_info = asp + get_aspect_info = asp, + trackworker_next_rot = "advtrains_signals_ks:"..prefix.."_"..typ.."_"..rtab.nextrot, + trackworker_rot_incr_param2 = (rot=="60") }, on_rightclick = advtrains.interlocking.signal_rc_handler, can_dig = advtrains.interlocking.signal_can_dig, @@ -506,6 +508,10 @@ for _, rtab in ipairs({ t.mesh = "advtrains_signals_ks_zs_top_smr"..rot..".obj" t.drop = "advtrains_signals_ks:zs3_off_0" t.selection_box.fixed[1][5] = 0 + t.advtrains = { + trackworker_next_rot = "advtrains_signals_ks:zs3_"..typ.."_"..rtab.nextrot, + trackworker_rot_incr_param2 = (rot=="60") + }, minetest.register_node("advtrains_signals_ks:zs3_"..typ.."_"..rot, t) --TODO add rotation using trackworker @@ -515,6 +521,10 @@ for _, rtab in ipairs({ t.mesh = "advtrains_signals_ks_zs_bottom_smr"..rot..".obj" t.drop = "advtrains_signals_ks:zs3v_off_0" t.tiles[3] = t.tiles[3] .. "^[multiply:yellow" + t.advtrains = { + trackworker_next_rot = "advtrains_signals_ks:zs3v_"..typ.."_"..rtab.nextrot, + trackworker_rot_incr_param2 = (rot=="60") + }, minetest.register_node("advtrains_signals_ks:zs3v_"..typ.."_"..rot, t) --TODO add rotation using trackworker end @@ -539,6 +549,10 @@ for _, rtab in ipairs({ not_blocking_trains = 1, not_in_creative_inventory = (rtab.ici) and 0 or 1, }, + advtrains = { + trackworker_next_rot = "advtrains_signals_ks:mast_mast_"..rtab.nextrot, + trackworker_rot_incr_param2 = (rot=="60") + }, drop = "advtrains_signals_ks:mast_mast_0", }) --TODO add rotation using trackworker -- cgit v1.2.3 From 69e4b8f99aac08c404a733c2338f9de9cddab829 Mon Sep 17 00:00:00 2001 From: orwell Date: Sat, 20 Jul 2024 21:04:17 +0200 Subject: Support signal aspect selection for routes again --- advtrains_interlocking/database.lua | 7 ++++ advtrains_interlocking/route_ui.lua | 58 ++++++++++++++++++++++++++------- advtrains_interlocking/routesetting.lua | 16 ++++++--- advtrains_interlocking/signal_api.lua | 3 +- 4 files changed, 68 insertions(+), 16 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index 49ca13d..a8d9584 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -254,6 +254,13 @@ routes = { ars = { } use_rscache = false -- if true, the track section's rs_cache will be used to set locks in addition to the locks table -- this is disabled for legacy routes, but enabled for all new routes by default + -- Fields to specify the signal aspect of the signal + main_aspect = "_free" -- The main aspect that the route start signal is to show + assign_dst = false -- Whether to assign distant signal (affects only the signal at the start of the route) + -- false: start signal does not set distant signal (the default), for long blocks + -- it is assumed that the next main signal will have its own distant sig + -- true: start signal sets distant signal to the next signal on the route with route_role "main" (typically the end signal) + -- for short blocks where end signal doesn't have its own distant sig -- Fields used by the autorouter: ar_end_sigd = -- the sigd describing the end of the route. Used for merging route options on recalculation } diff --git a/advtrains_interlocking/route_ui.lua b/advtrains_interlocking/route_ui.lua index 863fe11..b01d449 100644 --- a/advtrains_interlocking/route_ui.lua +++ b/advtrains_interlocking/route_ui.lua @@ -3,6 +3,7 @@ local atil = advtrains.interlocking local ildb = atil.db +local F = advtrains.formspec -- TODO duplicate local lntrans = { "A", "B" } @@ -83,9 +84,32 @@ function atil.show_route_edit_form(pname, sigd, routeid) itab("E (none)") end - form = form.."textlist[0.5,2;3,3.9;rtelog;"..table.concat(tab, ",").."]" + form = form.."textlist[0.5,2;3.5,3.9;rtelog;"..table.concat(tab, ",").."]" + + -- to the right of rtelog a signal aspect selection for the start signal + form = form..F.label(4.5, 2, "Signal Aspect:") + -- main aspect list + local signalpos = tcbs.signal + local ndef = signalpos and advtrains.ndb.get_ndef(signalpos) + if ndef and ndef.advtrains and ndef.advtrains.main_aspects then + local entries = { "" } + local sel = 1 + for i, mae in ipairs(ndef.advtrains.main_aspects) do + entries[i+1] = mae.description + if mae.name == route.main_aspect then + sel = i+1 + end + end + form = form..F.dropdown(4.5, 3.0, 4, "sa_main_aspect", entries, sel, true) + -- checkbox for assign distant signal + form = form..string.format("checkbox[4.5,4.0;sa_distant;Announce distant signal;%s]", route.assign_dst) + end + + form = form.."button[0.5,6;1,1;prev;<<<]" + form = form.."button[1.5,6;1,1;back;Back]" + form = form.."button[2.5,6;1,1;next;>>>]" + - form = form.."button[0.5,6;3,1;back;<<< Back to signal]" if route.smartroute_generated then form = form.."button[3.5,6;2,1;noautogen;Clr Autogen]" end @@ -123,20 +147,32 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) local route = tcbs.routes[routeid] if not route then return end + if fields.prev then + atil.show_route_edit_form(pname, sigd, routeid - 1) + return + end + if fields.next then + atil.show_route_edit_form(pname, sigd, routeid + 1) + return + end + if fields.setname and fields.name then route.name = fields.name end - if fields.aspect then - local suppasp = advtrains.interlocking.signal_get_supported_aspects(tcbs.signal) - - local callback = function(pname, asp) - route.aspect = asp - advtrains.interlocking.show_route_edit_form(pname, sigd, routeid) + if fields.sa_main_aspect then + local idx = tonumber(fields.sa_main_aspect) + route.main_aspect = nil + if idx > 1 then + local signalpos = tcbs.signal + local ndef = signalpos and advtrains.ndb.get_ndef(signalpos) + if ndef and ndef.advtrains and ndef.advtrains.main_aspects then + route.main_aspect = ndef.advtrains.main_aspects[idx - 1].name + end end - - advtrains.interlocking.show_signal_aspect_selector(pname, suppasp, route.name, callback, route.aspect or advtrains.interlocking.GENERIC_FREE) - return + end + if fields.sa_distant then + route.assign_dst = minetest.is_yes(fields.sa_distant) end if fields.noautogen then diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua index 34a273a..0207519 100644 --- a/advtrains_interlocking/routesetting.lua +++ b/advtrains_interlocking/routesetting.lua @@ -147,7 +147,7 @@ function ilrs.set_route(signal, route, try) tcbs_ref = c_tcbs, role = ndef and ndef.advtrains and ndef.advtrains.route_role, masp_override = c_rseg.masp_override, --TODO implement masp_override on UI side - dst_type = "next_main", --TODO allow user differentiate + assign_dst = (i~=1) or route.assign_dst -- Behavior: for first signal assign depending on route.assign_dst, all others always assign } signals[#signals+1] = sig_table end @@ -171,10 +171,18 @@ function ilrs.set_route(signal, route, try) -- note the signals are iterated backwards. Switch depending on the role local sig = signals[i] -- apply mainaspect - sig.tcbs_ref.route_aspect = sig.masp_override or "_default" + sig.tcbs_ref.route_aspect = sig.masp_override or route.main_aspect or "_default" if sig.role == "distant" or sig.role == "distant_repeater" or sig.role == "main_distant" then - -- assign the remote as the last mainsig - sig.tcbs_ref.route_remote = last_mainsig + if last_mainsig then + -- assign the remote as the last mainsig if desired + if sig.assign_dst then + sig.tcbs_ref.route_remote = last_mainsig + end + -- if it wasn't a distant_repeater clear the mainsig + if sig.role ~= "distant_repeater" then + last_mainsig = false + end + end end if sig.role == "main" or sig.role == "main_distant" or sig.role == "end" then -- record this as the new last mainsig diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index cede405..bf14247 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -100,7 +100,8 @@ ndef.advtrains = { -- Determines how the signal behaves when routes are set. Only in effect when signal is assigned to a TCB. -- main: The signal is a possible endpoint for a train move route. Distant signals before it refer to it. -- shunt: The signal is a possible endpoint for a shunt move route. Ignored for distant signals. - -- distant, distant_repeater: When route is set, signal is always assigned its first main aspect. The next signal with role="main" is set as the remote signal. (currently no further distinction) + -- distant, distant_repeater: The next signal with role="main" is set as the remote signal. main_aspects may be undefined, the main aspect passed to apply_aspect is a dummy one in this case. + -- distant: if more than one distant signal is before a main signal, only the last one is assigned (but any number of distant_repeater signals are allowed) -- main_distant: Combination of main and distant - like "main", but additionally gets assigned to the next main like a "distant" -- end: like main, but signifies that it marks an end of track and trains cannot continue further. (currently no practical implications above main) } -- cgit v1.2.3 From fe57e7dd089653e2361a4ebb0b34137a3261e198 Mon Sep 17 00:00:00 2001 From: orwell Date: Thu, 14 Nov 2024 00:03:38 +0100 Subject: Add Blocksignal mode for signals to autocreate simple block route --- advtrains_interlocking/database.lua | 7 +- advtrains_interlocking/route_prog.lua | 10 +- advtrains_interlocking/route_ui.lua | 3 +- advtrains_interlocking/routesetting.lua | 11 +- advtrains_interlocking/signal_api.lua | 2 +- advtrains_interlocking/tcb_ts_ui.lua | 177 ++++++++++++++++++++++---------- advtrains_signals_ks/init.lua | 8 +- 7 files changed, 143 insertions(+), 75 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index a8d9584..e77d073 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -261,8 +261,9 @@ routes = { -- it is assumed that the next main signal will have its own distant sig -- true: start signal sets distant signal to the next signal on the route with route_role "main" (typically the end signal) -- for short blocks where end signal doesn't have its own distant sig - -- Fields used by the autorouter: - ar_end_sigd = -- the sigd describing the end of the route. Used for merging route options on recalculation + terminal = -- the sigd describing the end of the route (e.g. the "next" entry in the final route segment). + -- Might be missing or wrong. Routesetting currently does not care about this value being present. + default_autoworking = false -- if true, when route is set autoworking will be by default on. Used for Blocksignal mode } } @@ -771,7 +772,7 @@ function ildb.update_rs_cache(ts_id) end -- warn about superfluous entry for sup_end_pkey, sup_entry in pairs(result_table) do - --atwarn("In update_rs_cache for section",ts_id,"found superfluous endpoint",sup_end_pkey,"->",sup_entry) + atwarn("In update_rs_cache for section",ts_id,"found superfluous endpoint",sup_end_pkey,"->",sup_entry) end end ts.rs_cache = rscache diff --git a/advtrains_interlocking/route_prog.lua b/advtrains_interlocking/route_prog.lua index 3ab5686..2f0f8ee 100644 --- a/advtrains_interlocking/route_prog.lua +++ b/advtrains_interlocking/route_prog.lua @@ -111,15 +111,7 @@ end --[[ Route definition: -route = { - name = - [n] = { - next = , -- of the next (note: next) TCB on the route - locks = { = "state"} -- route locks of this route segment - } - terminal = , - aspect = ,--note, might change in future -} +=== See database.lua L238 The first item in the TCB path (namely i=0) is always the start signal of this route, so this is left out. All subsequent entries, starting from 1, contain: diff --git a/advtrains_interlocking/route_ui.lua b/advtrains_interlocking/route_ui.lua index 89580a8..2b79f68 100644 --- a/advtrains_interlocking/route_ui.lua +++ b/advtrains_interlocking/route_ui.lua @@ -110,7 +110,7 @@ function atil.show_route_edit_form(pname, sigd, routeid) form = form.."button[2.5,6;1,1;next;>>>]" - if route.smartroute_generated then + if route.smartroute_generated or route.default_autoworking then form = form.."button[3.5,6;2,1;noautogen;Clr Autogen]" end form = form.."button[5.5,6;3,1;delete;Delete Route]" @@ -180,6 +180,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if fields.noautogen then route.smartroute_generated = nil + route.default_autoworking = nil end if fields.delete then diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua index a72f644..f2a00cd 100644 --- a/advtrains_interlocking/routesetting.lua +++ b/advtrains_interlocking/routesetting.lua @@ -171,7 +171,7 @@ function ilrs.set_route(signal, route, try) -- note the signals are iterated backwards. Switch depending on the role local sig = signals[i] -- apply mainaspect - sig.tcbs_ref.route_aspect = sig.masp_override or route.main_aspect or "_default" + sig.tcbs_ref.route_aspect = sig.masp_override or "_default" -- or route.main_aspect : TODO this does not work if a distant signal is on the path! Implement per-sig aspects! if sig.role == "distant" or sig.role == "distant_repeater" or sig.role == "main_distant" then if last_mainsig then -- assign the remote as the last mainsig if desired @@ -192,6 +192,8 @@ function ilrs.set_route(signal, route, try) -- update the signal aspect on map advtrains.interlocking.signal.update_route_aspect(sig.tcbs_ref, i ~= 1) end + -- Only for the first signal on the route, set route aspect. TODO: remove when masp_overrides are implemented + signal.route_aspect = route.main_aspect or "_default" return true end @@ -366,8 +368,9 @@ function ilrs.update_route(sigd, tcbs, newrte, cancel) if newrte then tcbs.routeset = newrte end --atdebug("Setting:",tcbs.routeset) local succ, rsn, cbts, cblk - if tcbs.routes[tcbs.routeset] then - succ, rsn, cbts, cblk = ilrs.set_route(sigd, tcbs.routes[tcbs.routeset]) + local route = tcbs.routes[tcbs.routeset] + if route then + succ, rsn, cbts, cblk = ilrs.set_route(sigd, route) else succ = false rsn = attrans("Route state changed.") @@ -390,6 +393,8 @@ function ilrs.update_route(sigd, tcbs, newrte, cancel) --atdebug("Committed Route:",tcbs.routeset) -- set_route now sets the signal aspects --has_changed_aspect = true + -- route success. apply default_autoworking flag if requested + tcbs.route_auto = route.default_autoworking end end if has_changed_aspect then diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index bf14247..9b0479f 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -312,7 +312,7 @@ function signal.get_aspect_internal(pos, aspt) -- case is fine, distant only signal masp = signal.MASP_DEFAULT else - assert(ndefat.main_aspects, "With named aspects, node needs advtrains.main_aspects table!") + assert(ndefat.main_aspects, "With named aspects, node "..node.name.." needs advtrains.main_aspects table!") -- resolve the main aspect from the mainaspects table if not ndefat.main_aspects_lookup then cache_mainaspects(ndefat) diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index e7ff685..1cdbb29 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -618,48 +618,78 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle form = form.."button[0.5,6; 5,1;cancelroute;Cancel Route]" else if not tcbs.route_origin then - local strtab = {} - for idx, route in ipairs(tcbs.routes) do - local clr = "" - if route.smartroute_generated then - clr = "#FFFF55" - end - if route.ars then - clr = "#FF5555" - if route.ars.default then - clr = "#55FF55" + if #tcbs.routes > 0 then + -- at least one route is defined, show normal dialog + local strtab = {} + for idx, route in ipairs(tcbs.routes) do + local clr = "" + if route.smartroute_generated then + clr = "#FFFF55" + end + if route.ars then + clr = "#FF5555" + if route.ars.default then + clr = "#55FF55" + end end + strtab[#strtab+1] = clr .. minetest.formspec_escape(route.name) end - 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[3.5,7;2,1;editroute;Edit]" - if sel_rte > 1 then - form = form .. "button[5.5,4;0.5,0.3;moveup;↑]" + 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[3.5,7;2,1;editroute;Edit]" + if sel_rte > 1 then + form = form .. "button[5.5,4;0.5,0.3;moveup;↑]" + end + if sel_rte < #strtab then + form = form .. "button[5.5,4.7;0.5,0.3;movedown;↓]" + end end - if sel_rte < #strtab then - form = form .. "button[5.5,4.7;0.5,0.3;movedown;↓]" + else + 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.]" end end + if 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) + end else - 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.]" + -- no route is active, and no route is so far defined + if not tcbs.signal then atwarn("signalling form missing signal?!", pos) return end -- safeguard, nothing else in this function checks tcbs.signal + local caps = advtrains.interlocking.signal.get_signal_cap_level(tcbs.signal) + if caps >= 3 then + -- 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.]" + end + 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.]" end end - if 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) - end + elseif sigd_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.]" @@ -753,6 +783,63 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) tcbs.ars_ignore_next = nil return end + if (fields.setupblocklong or fields.setupblockshort) and hasprivs then + -- check adjacent section + if not tcbs.ts_id then + minetest.chat_send_player(pname, "Block route not possible: No track section ahead") + return + end + local ts = ildb.get_ts(tcbs.ts_id) + if #ts.tc_breaks ~= 2 then + minetest.chat_send_player(pname, "Block route not possible: Section "..(ts.name or "-").." ("..tcbs.ts_id..") has "..#ts.tc_breaks.." ends, must be 2") + return + end + local 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==1 and 2 or 1} + elseif vector.equals(ts.tc_breaks[2].p, pos) then + e_sigd = { p = ts.tc_breaks[1].p, + s = ts.tc_breaks[1].s==1 and 2 or 1} + else + minetest.chat_send_player(pname, "Block route not possible: Section "..(ts.name or "-").." ("..tcbs.ts_id..") TCBs are inconsistent, check section!") + return + end + local 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!") + return + end + -- now we have the TCB at the end of the following section. check that signal is set + if not e_tcbs.signal then + minetest.chat_send_player(pname, "Block route not possible: Adjacent TCB has no signal assigned!") + return + end + local caps = advtrains.interlocking.signal.get_signal_cap_level(e_tcbs.signal) + if caps < 3 then + minetest.chat_send_player(pname, "Block route not possible: Following signal is not capable of displaying a Halt aspect (caplevel "..caps..")") + return + end + -- all preconditions checked! go ahead and create route + local 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 = + 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) + return + end if sel_rte and tcbs.routes[sel_rte] then if fields.setroute then ilrs.update_route(sigd, tcbs, sel_rte) @@ -764,8 +851,6 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end if fields.editroute and hasprivs then advtrains.interlocking.show_route_edit_form(pname, sigd, sel_rte) - --local rte = tcbs.routes[sel_rte] - --minetest.show_formspec(pname, formname.."_renroute_"..sel_rte, "field[name;Enter new route name;"..rte.name.."]") return end end @@ -803,24 +888,4 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, true) return end - - - if not hasprivs then return end - -- rename route - local rind, rte_id - pts, connids, rind = string.match(formname, "^at_il_signalling_([^_]+)_(%d)_renroute_(%d+)$") - if pts then - pos = minetest.string_to_pos(pts) - connid = tonumber(connids) - rte_id = tonumber(rind) - if not connid or connid<1 or connid>2 then return end - end - if pos and connid and rind and fields.name then - local sigd = {p=pos, s=connid} - local tcbs = ildb.get_tcbs(sigd) - if tcbs.routes[rte_id] then - tcbs.routes[rte_id].name = fields.name - advtrains.interlocking.show_signalling_form(sigd, pname) - end - end end) diff --git a/advtrains_signals_ks/init.lua b/advtrains_signals_ks/init.lua index c0e74ea..a85bec9 100755 --- a/advtrains_signals_ks/init.lua +++ b/advtrains_signals_ks/init.lua @@ -161,7 +161,7 @@ end -- Main aspects shunt signal -- Shunt signals have only two states, distant doesn't matter -local mainaspects_shunt = { +local mainaspects_ra = { { name = "shunt", description = "Shunt", @@ -335,6 +335,10 @@ for _, rtab in ipairs({ }) do local sbox = table.copy(rtab.sbox) sbox[5] = 0 + local afunc = prts.asp + if type(afunc) == "table" then + afunc = function() return prts.asp end + end minetest.register_node("advtrains_signals_ks:ra_"..typ.."_"..rot, { description = "Ks Shunting Signal", drawtype = "mesh", @@ -366,7 +370,7 @@ for _, rtab in ipairs({ advtrains = { main_aspects = mainaspects_ra, apply_aspect = applyaspectf_ra(rot), - get_aspect_info = prts.asp, + get_aspect_info = afunc, route_role = "shunt", trackworker_next_rot = "advtrains_signals_ks:ra_"..typ.."_"..rtab.nextrot, trackworker_rot_incr_param2 = (rot=="60") -- cgit v1.2.3 From 922e654b7bef51c7ddaf510ec70880d48181dd35 Mon Sep 17 00:00:00 2001 From: orwell Date: Mon, 25 Nov 2024 22:31:26 +0100 Subject: Make Buffers become implicitly their own TCBs and signals when interlocking is enabled --- advtrains/signals.lua | 8 +- advtrains/trackplacer.lua | 16 ++-- advtrains/tracks.lua | 1 + advtrains_interlocking/database.lua | 41 +++++++++- advtrains_interlocking/mod.conf | 2 +- advtrains_interlocking/route_prog.lua | 7 +- advtrains_interlocking/signal_api.lua | 23 ++++-- advtrains_interlocking/tcb_ts_ui.lua | 146 ++++++++++++++++++++++++++++++++-- advtrains_train_track/init.lua | 29 +++++++ advtrains_train_track/mod.conf | 2 +- 10 files changed, 242 insertions(+), 33 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains/signals.lua b/advtrains/signals.lua index 4dec7f5..8bdd877 100644 --- a/advtrains/signals.lua +++ b/advtrains/signals.lua @@ -3,15 +3,15 @@ local mrules_wallsignal = advtrains.meseconrules -local function can_dig_func(pos) +local function can_dig_func(pos, player) if advtrains.interlocking then - return advtrains.interlocking.signal.can_dig(pos) + return advtrains.interlocking.signal.can_dig(pos, player) end return true end -local function after_dig_func(pos) +local function after_dig_func(pos, oldnode, oldmetadata, digger) if advtrains.interlocking then - return advtrains.interlocking.signal.after_dig(pos) + return advtrains.interlocking.signal.after_dig(pos, oldnode, oldmetadata, digger) end return true end diff --git a/advtrains/trackplacer.lua b/advtrains/trackplacer.lua index 6a2c7a8..1543209 100644 --- a/advtrains/trackplacer.lua +++ b/advtrains/trackplacer.lua @@ -152,12 +152,14 @@ local function check_or_bend_rail(origin, dir, pname, commit) end end -local function track_place_node(pos, node, ndef) +local function track_place_node(pos, node, ndef, pname) --atdebug("track_place_node: ",pos, node) advtrains.ndb.swap_node(pos, node) local ndef = minetest.registered_nodes[node.name] if ndef and ndef.after_place_node then - ndef.after_place_node(pos) + -- resolve player again + local player = pname and core.get_player_by_name(pname) or nil + ndef.after_place_node(pos, player) -- note: itemstack and pointed_thing are NOT available here anymore (crap!) end end @@ -191,16 +193,16 @@ function tp.place_track(pos, tpg, pname, yaw) for k1, conn1 in ipairs(cand) do for k2, conn2 in ipairs(cand) do if k1~=k2 then - -- order of conn1/conn2: prefer conn2 being in the direction of the player facing. + -- order of conn1/conn2: prefer conn1 being in the direction of the player facing. -- the combination the other way round will be run through in a later loop iteration - if advtrains.yawToDirection(yaw, conn1, conn2) == conn2 then + if advtrains.yawToDirection(yaw, conn1, conn2) == conn1 then -- does there exist a suitable double-connection rail? --atdebug("Try double conn: ",conn1, conn2) local node = g.double[conn1.."_"..conn2] if node then check_or_bend_rail(pos, conn1, pname, true) check_or_bend_rail(pos, conn2, pname, true) - track_place_node(pos, node) -- calls after_place_node implicitly + track_place_node(pos, node, pname) -- calls after_place_node implicitly return true end end @@ -220,13 +222,13 @@ function tp.place_track(pos, tpg, pname, yaw) local node = single[conn1] if node then check_or_bend_rail(pos, conn1, pname, true) - track_place_node(pos, node) -- calls after_place_node implicitly + track_place_node(pos, node, nil, pname) -- calls after_place_node implicitly return true end end -- 4. if nothing worked, set the default local node = g.default - track_place_node(pos, node) -- calls after_place_node implicitly + track_place_node(pos, node, nil, pname) -- calls after_place_node implicitly return true end diff --git a/advtrains/tracks.lua b/advtrains/tracks.lua index 661da8a..fa7b702 100644 --- a/advtrains/tracks.lua +++ b/advtrains/tracks.lua @@ -253,6 +253,7 @@ end -- Function called when a track is about to be dug or modified by the trackworker -- Returns either true (ok) or false,"translated string describing reason why it isn't allowed" +-- Impl Note: possibly duplicate code in "self contained TCB" - see interlocking/tcb_ts_ui.lua! function advtrains.can_dig_or_modify_track(pos) if advtrains.get_train_at_pos(pos) then return false, attrans("Position is occupied by a train.") diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index e77d073..75247de 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -802,10 +802,18 @@ function ildb.create_tcb_at(pos) end -- Remove TCB at the position and update/repair the now joined section -function ildb.remove_tcb_at(pos) +-- skip_tsrepair: should be set to true when the rail node at the TCB position is already gone. +-- Assumes the track sections are now separated and does not attempt the repair process. +function ildb.remove_tcb_at(pos, pname, skip_tsrepair) --atdebug("remove_tcb_at",pos) local pts = advtrains.encode_pos(pos) local old_tcb = track_circuit_breaks[pts] + -- unassign signals if defined + for connid=1,2 do + if old_tcb[connid].signal then + ildb.set_sigd_for_signal(old_tcb[connid].signal, nil) + end + end track_circuit_breaks[pts] = nil -- purge the track sections adjacent if old_tcb[1].ts_id then @@ -823,7 +831,9 @@ function ildb.remove_tcb_at(pos) end advtrains.interlocking.remove_tcb_marker(pos) -- If needed, merge the track sections here - ildb.check_and_repair_ts_at_pos(pos, nil) + if not skip_tsrepair then + ildb.check_and_repair_ts_at_pos(pos, pname) + end return true end @@ -973,11 +983,34 @@ function ildb.get_sigd_for_signal(pos) end return nil end -function ildb.set_sigd_for_signal(pos, sigd) +function ildb.set_sigd_for_signal(pos, sigd) -- do not use! local pts = advtrains.roundfloorpts(pos) signal_assignments[pts] = sigd end +-- Assign the signal at pos to the given TCB side. +function ildb.assign_signal_to_tcbs(pos, sigd) + local tcbs = ildb.get_tcbs(sigd) + assert(tcbs, "assign_signal_to_tcbs invalid sigd!") + tcbs.signal = pos + if not tcbs.routes then + tcbs.routes = {} + end + ildb.set_sigd_for_signal(pos, sigd) +end + +-- unassign the signal from the given sigd (looks in tcbs.signal for the signalpos) +function ildb.unassign_signal_for_tcbs(sigd) + local tcbs = advtrains.interlocking.db.get_tcbs(sigd) + if not tcbs then return end + local pos = tcbs.signal + if not pos then return end + ildb.set_sigd_for_signal(pos, nil) + tcbs.signal = nil + tcbs.route_aspect = nil + tcbs.route_remote = nil +end + -- checks if there's any influence point set to this position -- if purge is true, checks whether the associated signal still exists -- and deletes the ip if not. @@ -987,7 +1020,7 @@ function ildb.is_ip_at(pos, purge) if purge then -- is there still a signal assigned to it? for connid, sigpos in pairs(influence_points[pts]) do - local asp = advtrains.interlocking.signal_get_aspect(sigpos) + local asp = advtrains.interlocking.signal.get_aspect(sigpos) if not asp then atlog("Clearing orphaned signal influence point", pts, "/", connid) ildb.clear_ip_signal(pts, connid) diff --git a/advtrains_interlocking/mod.conf b/advtrains_interlocking/mod.conf index 3b2d029..9191dd9 100644 --- a/advtrains_interlocking/mod.conf +++ b/advtrains_interlocking/mod.conf @@ -4,4 +4,4 @@ description=Interlocking system for Advanced Trains author=orwell96 depends=advtrains -optional_depends=advtrains_train_track +#optional_depends=advtrains_train_track diff --git a/advtrains_interlocking/route_prog.lua b/advtrains_interlocking/route_prog.lua index 2f0f8ee..3bdf6d6 100644 --- a/advtrains_interlocking/route_prog.lua +++ b/advtrains_interlocking/route_prog.lua @@ -535,9 +535,12 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) -- show formspec show_routing_form(pname, tcbpos) - advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname, rp.tmp_lcks, pname) - + return + elseif advtrains.interlocking.database.get_tcb(pos) then + -- the punched node itself is a TCB + show_routing_form(pname, pos) + advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname, rp.tmp_lcks, pname) return end if advtrains.is_passive(pos) then diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 9b0479f..8347b1c 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -400,7 +400,8 @@ end -- 0: not a signal at all -- 1: signal has get_aspect_info() but the aspect is not variable (e.g. a sign) -- 2: signal has apply_aspect() but does not have main aspects (e.g. a pure distant signal) --- 3: Full capabilities, signal has main aspects and can be used as main/shunt signal (can be start/endpoint of a route) +-- 3: signal has main signal role but can only ever display a halt aspect, such as a bumper (can be endpoint, but not startpoint, of a route) +-- 4: Full capabilities, signal has main aspects and can be used as main/shunt signal (can be start/endpoint of a route) function signal.get_signal_cap_level(pos) local node = advtrains.ndb.get_node_or_nil(pos) local ndef = node and minetest.registered_nodes[node.name] @@ -408,6 +409,10 @@ function signal.get_signal_cap_level(pos) if ndefat and ndefat.get_aspect_info then if ndefat.apply_aspect then if ndefat.main_aspects then + -- if the table contains anything, 4, otherwise 3 + for _,_ in pairs(ndefat.main_aspects) do + return 4 + end return 3 end return 2 @@ -419,9 +424,17 @@ end ---------------- -function signal.can_dig(pos) +function signal.can_dig(pos, player) local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) if sigd then + -- check privileges + if not player or not minetest.check_player_privs(player:get_player_name(), "interlocking") then + if not player then -- intermediate debug to uncover hard-to-find bugz + atdebug("advtrains.interlocking.signal.can_dig(",pos,") called with player==nil!") + end + return false + end + -- check if route is set local tcbs = advtrains.interlocking.db.get_tcbs(sigd) if tcbs.routeset then return false @@ -434,11 +447,7 @@ function signal.after_dig(pos, oldnode, oldmetadata, player) -- unassign signal if necessary local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) if sigd then - local tcbs = advtrains.interlocking.db.get_tcbs(sigd) - advtrains.interlocking.db.set_sigd_for_signal(pos, nil) - tcbs.signal = nil - tcbs.route_aspect = nil - tcbs.route_remote = nil + advtrains.interlocking.db.unassign_signal_for_tcbs(sigd) minetest.chat_send_player(player:get_player_name(), "Signal has been unassigned. Name and routes are kept for reuse.") end -- TODO clear influence point diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index 1cdbb29..2b93234 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -156,7 +156,7 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) 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) + local node_ok, conns, rhe = advtrains.get_rail_info_at(pos) if node_ok and #conns == 2 then -- if there is already a tcb here, reassign it if ildb.get_tcb(pos) then @@ -189,11 +189,7 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) if ndef and ndef.advtrains and ndef.advtrains.apply_aspect then local tcbs = ildb.get_tcbs(sigd) if tcbs then - tcbs.signal = pos - if not tcbs.routes then - tcbs.routes = {} - end - ildb.set_sigd_for_signal(pos, sigd) + 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 @@ -212,6 +208,138 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) end end) +-- "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) + atdebug("selftcb apn ",pos, player, itemstack, pointed_thing) + local pname = player:get_player_name() + if not minetest.check_player_privs(pname, "interlocking") then + if not fail_silently_on_noprivs then + minetest.chat_send_player(pname, "Insufficient privileges to use this!") + end + return + end + if 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) + end + if auto_create_self_signal then + local sigd = { p = pos, s = 1 } + local tcbs = ildb.get_tcbs(sigd) + -- make sure signal doesn't already exist + if tcbs.signal then + minetest.chat_send_player(pname, "Signal on B side already assigned!") + return + end + ildb.assign_signal_to_tcbs(pos, sigd) + -- assign influence point to itself + ildb.set_ip_signal(advtrains.roundfloorpts(pos), 1, pos) + end + end +end + +-- 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 called +function 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.lua + if advtrains.get_train_at_pos(pos) then + minetest.chat_send_player(pname, "Can't remove track, a train is here!") + return false + end + -- end of standard checks + local 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 then + return advtrains.interlocking.signal.can_dig(pos, player) + else + return true + end + end + -- TCB exists + if not minetest.check_player_privs(pname, "interlocking") then + return false + end + -- fine to remove (unless signal callback interjects) + if is_signal then + return advtrains.interlocking.signal.can_dig(pos, player) + else + return true + end + end +end + +-- 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 called +function 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) + end + if ildb.get_tcb(pos) then + -- remove the TCB + ildb.remove_tcb_at(pos, pname, true) + end + end +end + +-- 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 rightclick +function 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") then + if not fail_silently_on_noprivs then + minetest.chat_send_player(pname, "Insufficient privileges to use this!") + end + return + end + if ildb.get_tcb(pos) then + -- TCB already here. go on + else + -- otherwise create tcb + ildb.create_tcb_at(pos, pname) + end + if auto_create_self_signal then + local sigd = { p = pos, s = 1 } + local tcbs = ildb.get_tcbs(sigd) + -- make sure signal doesn't already exist + if 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 nouw + local control = player:get_player_control() + advtrains.interlocking.show_signal_form(pos, node, pname, control.aux1) + return + else + -- not an autosignal. Then show the TCB form + advtrains.interlocking.show_tcb_form(pos, pname) + return + end + end +end + -- TCB Form local function mktcbformspec(pos, side, tcbs, offset, pname) @@ -666,7 +794,7 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle -- no route is active, and no route is so far defined if not tcbs.signal then atwarn("signalling form missing signal?!", pos) return end -- safeguard, nothing else in this function checks tcbs.signal local caps = advtrains.interlocking.signal.get_signal_cap_level(tcbs.signal) - if caps >= 3 then + if caps >= 4 then -- offer user the "block signal mode" form = form.."label[0.5,2.5;No routes are yet defined.]" if hasprivs then @@ -683,6 +811,10 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle .."Sets a route into the section ahead with auto-working set on\n" .."Short block: This signal becomes distant signal for next signal.]" end + elseif caps >= 3 then + -- 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" diff --git a/advtrains_train_track/init.lua b/advtrains_train_track/init.lua index 32e1235..f551ec5 100644 --- a/advtrains_train_track/init.lua +++ b/advtrains_train_track/init.lua @@ -600,6 +600,35 @@ advtrains.register_tracks("default", { --bumpers still use the old texture until the models are redone. description=attrans("Bumper"), formats={}, + get_additional_definiton = function(def, preset, suffix, rotation) + -- 2024-11-25: Bumpers get the additional feature of being both a signal and a self-contained TCB, when interlocking is used. + if advtrains.interlocking then + return { + -- use the special callbacks for self_tcb (see tcb_ts_ui.lua) + can_dig = advtrains.interlocking.self_tcb_make_can_dig_callback(true), + after_dig_node = advtrains.interlocking.self_tcb_make_after_dig_callback(true), + after_place_node = advtrains.interlocking.self_tcb_make_after_place_callback(true, true), + on_rightclick = advtrains.interlocking.self_tcb_make_on_rightclick_callback(false, true), + advtrains = { + main_aspects = { + -- No main aspects, it always shows Stop. + -- But we need to define the table so that signal caplevel is 3 + }, + apply_aspect = function(pos, node, main_aspect, rem_aspect, rem_aspinfo) + -- is a no-op for bumpers, it always shows Stop + end, + get_aspect_info = function(pos, main_aspect) + -- it always shows Stop + return advtrains.interlocking.signal.ASPI_HALT + end, + distant_support = false, -- not a distant + route_role = "end", -- the end is nigh! + } + } + else + return {} -- no additional defs when interlocking is not used + end + end, }, advtrains.ap.t_30deg_straightonly) minetest.register_craft({ output = 'advtrains:dtrack_bumper_placer 2', diff --git a/advtrains_train_track/mod.conf b/advtrains_train_track/mod.conf index 2aece3e..a7fef4d 100644 --- a/advtrains_train_track/mod.conf +++ b/advtrains_train_track/mod.conf @@ -4,4 +4,4 @@ description=Default track set for Advanced Trains author=orwell96 depends=advtrains -optional_depends=mesecons,digtron +optional_depends=mesecons,digtron,advtrains_interlocking -- cgit v1.2.3 From 95faa8f1baa68c3f6a104160d61713ade276676b Mon Sep 17 00:00:00 2001 From: orwell Date: Mon, 25 Nov 2024 22:36:23 +0100 Subject: Remove debug prints of distant signalling system --- advtrains_interlocking/routesetting.lua | 4 ++-- advtrains_interlocking/signal_api.lua | 14 +++++++------- advtrains_interlocking/tcb_ts_ui.lua | 1 - 3 files changed, 9 insertions(+), 10 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua index f2a00cd..5d7357d 100644 --- a/advtrains_interlocking/routesetting.lua +++ b/advtrains_interlocking/routesetting.lua @@ -83,7 +83,7 @@ function ilrs.set_route(signal, route, try) end_pkey = advtrains.encode_pos(c_rseg.next.p) if c_ts.rs_cache[start_pkey] and c_ts.rs_cache[start_pkey][end_pkey] then for lp,lst in pairs(c_ts.rs_cache[start_pkey][end_pkey]) do - atdebug("Add lock from RSCache:",lp,"->",lst) + --atdebug("Add lock from RSCache:",lp,"->",lst) c_locks[lp] = lst end elseif not try then @@ -92,7 +92,7 @@ function ilrs.set_route(signal, route, try) end -- add all from locks, these override the rscache for lpts,lst in pairs(c_rseg.locks) do - atdebug("Add lock from Routedef:",lpts,"->",lst,"overrides",c_locks[lpts] or "none") + --atdebug("Add lock from Routedef:",lpts,"->",lst,"overrides",c_locks[lpts] or "none") c_locks[lpts] = lst end diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 8347b1c..a7ef724 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -179,7 +179,7 @@ function signal.set_aspect(pos, main_asp, rem_pos, skip_dst_notify) -- if remote has changed, unregister from old remote if old_remote and old_remote~=new_remote and signal.distant_refs[old_remote] then - atdebug("unregister old remote: ",old_remote,"from",main_pts) + --atdebug("unregister old remote: ",old_remote,"from",main_pts) signal.distant_refs[old_remote][main_pts] = nil end @@ -231,12 +231,12 @@ end -- Notify distant signals of main_pts of a change in the aspect of this signal -- function signal.notify_distants_of(main_pts, limit) - atdebug("notify_distants_of",advtrains.decode_pos(main_pts),"limit",limit) + --atdebug("notify_distants_of",advtrains.decode_pos(main_pts),"limit",limit) if limit <= 0 then return end local dstrefs = signal.distant_refs[main_pts] - atdebug("dstrefs",dstrefs,"") + --atdebug("dstrefs",dstrefs,"") if dstrefs then for dst,_ in pairs(dstrefs) do -- ensure that the backref is still valid @@ -336,7 +336,7 @@ function signal.get_aspect_info(pos) ai = ai(pos, masp) end if type(ai)=="table" then - atdebug(pos,"aspectinfo",ai) + --atdebug(pos,"aspectinfo",ai) return ai else error("For node "..node.name..": ndef.advtrains.get_aspect_info must be function or table") @@ -355,7 +355,7 @@ end function signal.reapply_aspect(pts) -- get aspt local aspt = signal.aspects[pts] - atdebug("reapply_aspect",advtrains.decode_pos(pts),"aspt",aspt) + --atdebug("reapply_aspect",advtrains.decode_pos(pts),"aspt",aspt) local pos = advtrains.decode_pos(pts) -- resolve mainaspect table by name local masp, remote, node, ndef = signal.get_aspect_internal(pos, aspt) @@ -368,7 +368,7 @@ function signal.reapply_aspect(pts) end signal.distant_refs[remote][pts] = true local rem_aspt = signal.aspects[remote] - atdebug("resolving remote",advtrains.decode_pos(remote),"aspt",rem_aspt) + --atdebug("resolving remote",advtrains.decode_pos(remote),"aspt",rem_aspt) local rem_pos = advtrains.decode_pos(remote) rem_masp, _, _, rem_ndef = signal.get_aspect_internal(rem_pos, rem_aspt) if rem_masp then @@ -378,7 +378,7 @@ function signal.reapply_aspect(pts) end end -- call into ndef - atdebug("applying to",pos,": main_asp",masp,"rem_masp",rem_masp,"rem_aspi",rem_aspi) + --atdebug("applying to",pos,": main_asp",masp,"rem_masp",rem_masp,"rem_aspi",rem_aspi) if ndef.advtrains and ndef.advtrains.apply_aspect then ndef.advtrains.apply_aspect(pos, node, masp, rem_masp, rem_aspi) end diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index 2b93234..54ffe9b 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -218,7 +218,6 @@ end) -- (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) - atdebug("selftcb apn ",pos, player, itemstack, pointed_thing) local pname = player:get_player_name() if not minetest.check_player_privs(pname, "interlocking") then if not fail_silently_on_noprivs then -- cgit v1.2.3 From 38acdbbe1d3a9f2c91ced0281cd58b6c29cff401 Mon Sep 17 00:00:00 2001 From: orwell Date: Tue, 7 Jan 2025 21:54:31 +0100 Subject: Change logic for pure distant signals: remain assigned even after train clears TCB (mirror real life behavior) --- advtrains_interlocking/database.lua | 2 +- advtrains_interlocking/signal_api.lua | 14 ++++++++++---- advtrains_signals_ks/init.lua | 12 +++++++++++- 3 files changed, 22 insertions(+), 6 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index 38b1bc8..077e58a 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -1029,7 +1029,7 @@ function ildb.is_ip_at(pos, purge) if purge then -- is there still a signal assigned to it? for connid, sigpos in pairs(influence_points[pts]) do - local asp = advtrains.interlocking.signal.get_aspect(sigpos) + local asp = advtrains.interlocking.signal.get_aspect_info(sigpos) if not asp then atlog("Clearing orphaned signal influence point", pts, "/", connid) ildb.clear_ip_signal(pts, connid) diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index a7ef724..ae8f6fa 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -330,7 +330,7 @@ function signal.get_aspect_info(pos) local aspt = signal.aspects[advtrains.encode_pos(pos)] local masp, remote, node, ndef = signal.get_aspect_internal(pos, aspt) -- call into ndef - if ndef.advtrains and ndef.advtrains.get_aspect_info then + if ndef and ndef.advtrains and ndef.advtrains.get_aspect_info then local ai = ndef.advtrains.get_aspect_info if type(ai)=="function" then ai = ai(pos, masp) @@ -390,6 +390,12 @@ end -- function signal.update_route_aspect(tcbs, skip_dst_notify) if tcbs.signal then + if not tcbs.route_aspect and signal.get_signal_cap_level(tcbs.signal) == 2 then + return + -- Special behavior for pure-distant signals assigned to TCBs: retain their last assigned main signal + -- and do not fall back to halt. This mirrors real-life, where the distant signal goes back to + -- expect halt only when the main signal falls into halt + end local asp = tcbs.route_aspect or "_halt" local rem = tcbs.route_remote signal.set_aspect(tcbs.signal, asp, rem, skip_dst_notify) @@ -399,7 +405,7 @@ end -- Returns how capable the signal is with regards to aspect setting -- 0: not a signal at all -- 1: signal has get_aspect_info() but the aspect is not variable (e.g. a sign) --- 2: signal has apply_aspect() but does not have main aspects (e.g. a pure distant signal) +-- 2: signal has apply_aspect() and main aspects but has "pure_distant" flag set (cannot be start/endpoint of a route, special behavior that its route aspect is not cleared on train pass) -- 3: signal has main signal role but can only ever display a halt aspect, such as a bumper (can be endpoint, but not startpoint, of a route) -- 4: Full capabilities, signal has main aspects and can be used as main/shunt signal (can be start/endpoint of a route) function signal.get_signal_cap_level(pos) @@ -407,8 +413,8 @@ function signal.get_signal_cap_level(pos) local ndef = node and minetest.registered_nodes[node.name] local ndefat = ndef and ndef.advtrains if ndefat and ndefat.get_aspect_info then - if ndefat.apply_aspect then - if ndefat.main_aspects then + if ndefat.apply_aspect and ndefat.main_aspects then + if not ndefat.pure_distant then -- if the table contains anything, 4, otherwise 3 for _,_ in pairs(ndefat.main_aspects) do return 4 diff --git a/advtrains_signals_ks/init.lua b/advtrains_signals_ks/init.lua index eb23930..7c78dea 100755 --- a/advtrains_signals_ks/init.lua +++ b/advtrains_signals_ks/init.lua @@ -146,6 +146,15 @@ local applyaspectf_distant = function(rot) end end +-- Main aspects distant signal +-- Only one aspect for "expect free". Whether green or yellow lamp is shown and which speed indicator is determined by remote signal +local mainaspects_dst = { + { + name = "expectclear", + description = "Expect Clear", + }, +} + --Rangiersignal local applyaspectf_ra = function(rot) -- we get here the full main_aspect table @@ -313,10 +322,11 @@ for _, rtab in ipairs({ drop = "advtrains_signals_ks:vs_slow_0", inventory_image = "advtrains_signals_ks_vs_inv.png", advtrains = { - -- no mainaspect + main_aspects = mainaspects_dst, apply_aspect = applyaspectf_distant(rot), get_aspect_info = afunc, route_role = "distant", + pure_distant = true, trackworker_next_rot = "advtrains_signals_ks:vs_"..typ.."_"..rtab.nextrot, trackworker_rot_incr_param2 = (rot=="60") }, -- cgit v1.2.3 From 265d1e519bbc1ed956bb1a489b2ceaddb3082004 Mon Sep 17 00:00:00 2001 From: orwell Date: Tue, 7 Jan 2025 23:57:15 +0100 Subject: Update docs, fix the other signals --- advtrains_interlocking/signal_api.lua | 11 ++++++++--- advtrains_signals_japan/init.lua | 5 ++++- advtrains_signals_muc_ubahn/init.lua | 8 ++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index ae8f6fa..9989907 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -85,12 +85,11 @@ ndef.advtrains = { -- name: A unique key to identify the main aspect. Might be required by some code. -- description: Text shown in UI dropdown -- Node can set any other fields at its discretion. They are not touched. - -- Note: Pure distant signals (that cannot show halt) should NOT have a main_aspects table. - -- For these signals no main aspect selection UI is shown and they cannot be startpoint of a route + -- Note: Pure distant signals should set one main aspect, and set the "pure_distant = true" field apply_aspect = function(pos, node, main_aspect, rem_aspect, rem_aspinfo) -- set the node to show the desired aspect -- called by advtrains when this signal's aspect group or the remote signal's aspect changes - -- main_aspect is never nil, but can be one of the special aspects { halt = true } or { default = true } + -- main_aspect is never nil, but can be the special aspect { name = "_halt", halt = true } -- MAY return the aspect_info. If it returns nil then get_aspect_info will be queried at a later point. get_aspect_info(pos, main_aspect) -- Returns the aspect info table (main, shunt, dst etc.) @@ -104,6 +103,12 @@ ndef.advtrains = { -- distant: if more than one distant signal is before a main signal, only the last one is assigned (but any number of distant_repeater signals are allowed) -- main_distant: Combination of main and distant - like "main", but additionally gets assigned to the next main like a "distant" -- end: like main, but signifies that it marks an end of track and trains cannot continue further. (currently no practical implications above main) + pure_distant = true / false + -- If true, this signal is assumed to be a pure distant signal (its halt aspect is rather "expect halt" and it cannot show a true "stop here") + -- The following special behavior applies when this signal is assigned to a TCB: When a train passes the signal, the aspect is not reset to the + -- halt aspect and it continues to announce the remote signal's aspect (like in real life) + -- Typically such signals have one main aspect, their appearance depends almost exclusively on their remote signal and the halt aspect is the same as + -- the aspect shown when the main aspect is set but the remote signal is at halt. } == Nomenclature == diff --git a/advtrains_signals_japan/init.lua b/advtrains_signals_japan/init.lua index a659410..1140b6b 100644 --- a/advtrains_signals_japan/init.lua +++ b/advtrains_signals_japan/init.lua @@ -415,7 +415,7 @@ for _, rtab in ipairs { apply_aspect = function(pos, node, main_aspect, rem_aspect, rem_aspinfo) local asp_name = main_aspect and main_aspect.name or "danger" -- if this signal is clear and remote signal is restrictive (<= 10) then degrade to caution aspect - if not main_aspect or main_aspect.name == "halt" then + if not main_aspect or main_aspect.halt then asp_name = "danger" elseif main_aspect.name == "clear" and rem_aspinfo and rem_aspinfo.main and rem_aspinfo.main >= 0 and rem_aspinfo.main <= 10 then asp_name = "caution" @@ -423,6 +423,9 @@ for _, rtab in ipairs { advtrains.ndb.swap_node(pos, {name="advtrains_signals_japan:"..sigtype.."_"..asp_name.."_"..rot, param2 = node.param2}) end, get_aspect_info = function(pos, main_aspect) + if main_aspect.halt then + return { main = 0 } -- generic halt + end return { main = main_aspect.main, proceed_as_main = true, diff --git a/advtrains_signals_muc_ubahn/init.lua b/advtrains_signals_muc_ubahn/init.lua index 17ecac2..182a3dc 100755 --- a/advtrains_signals_muc_ubahn/init.lua +++ b/advtrains_signals_muc_ubahn/init.lua @@ -20,6 +20,9 @@ local mainaspects = { { name = "hp2", description = "Hp2: Reduced Speed" }, { name = "hp3", description = "Hp3: Shunt" }, } +local dstaspects = { + { name = "vr1", description = "Vr1: Expect Full speed" }, +} local function applyaspect_main(loc) return function(pos, node, main_aspect, rem_aspect, rem_aspinfo) @@ -38,7 +41,7 @@ end local function applyaspect_distant(loc) return function(pos, node, main_aspect, rem_aspect, rem_aspinfo) local ma_node = "vr0" -- show expect stop by default - if not main_aspect.halt and rem_aspinfo and (not rem_aspinfo.main or rem_aspinfo.main>12 or rem_aspinfo.main==-1) then + if not main_aspect.halt and (not rem_aspinfo or not rem_aspinfo.main or rem_aspinfo.main>12 or rem_aspinfo.main==-1) then ma_node = "vr1" -- show free when dst is at least 12 end advtrains.ndb.swap_node(pos, {name = "advtrains_signals_muc_ubahn:signal_wall_"..loc.."_"..ma_node, param2 = node.param2}) @@ -74,8 +77,9 @@ for r,f in pairs(all_sigs) do after_dig_node = advtrains.interlocking.signal.after_dig, -- new signal API advtrains = { - main_aspects = not f.distant and mainaspects, -- main aspects only for main + main_aspects = f.distant and dstaspects or mainaspects, -- main aspects only for main apply_aspect = f.distant and applyaspect_distant(loc) or applyaspect_main(loc), + pure_distant = f.distant, get_aspect_info = function() return f.asp end, route_role = f.distant and "distant" or "main" }, -- cgit v1.2.3 From 9bd34f738663cdc268db6399ecb6aaba2d4c9a28 Mon Sep 17 00:00:00 2001 From: orwell Date: Wed, 8 Jan 2025 00:02:03 +0100 Subject: Remove Smartroute debug prints --- advtrains_interlocking/database.lua | 8 ++++---- advtrains_interlocking/signal_api.lua | 2 +- advtrains_interlocking/smartroute.lua | 24 ++++++++++++------------ 3 files changed, 17 insertions(+), 17 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index 0a5094b..9c72a72 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -84,7 +84,7 @@ function ildb.load(data) if pos then -- that was a pos_to_string local epos = advtrains.encode_pos(pos) - atdebug("ILDB converting TCB position format",pts,"->",epos) + --atdebug("ILDB converting TCB position format",pts,"->",epos) track_circuit_breaks[epos] = tcb else -- keep entry, it is already new @@ -100,7 +100,7 @@ function ildb.load(data) local lpos = minetest.string_to_pos(lpts) if lpos then local epos = advtrains.encode_pos(lpos) - atdebug("ILDB converting tcb",pts,"side",t_side,"route",t_route,"lock position format",lpts,"->",epos) + --atdebug("ILDB converting tcb",pts,"side",t_side,"route",t_route,"lock position format",lpts,"->",epos) locks_n[epos] = state else -- already correct format @@ -131,7 +131,7 @@ function ildb.load(data) if pos then -- that was a pos_to_string local epos = advtrains.encode_pos(pos) - atdebug("ILDB converting Route Lock position format",pts,"->",epos) + --atdebug("ILDB converting Route Lock position format",pts,"->",epos) advtrains.interlocking.route.rte_locks[epos] = lta else -- keep entry, it is already new @@ -535,7 +535,7 @@ function ildb.repair_ts_merge_all(all_tcbs, force_create, notify_pname) end -- Create a new fresh track section with all the TCBs we have in our collection local new_ts_id, new_ts = ildb.create_ts_from_tcb_list(all_tcbs) - tsrepair_notify(notify_pname, "Created track section",new_ts_id,"from TCBs:", all_tcbs) + tsrepair_notify(notify_pname, "Created track section",new_ts_id,"from",#all_tcbs,"TCBs") return new_ts_id end diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 9989907..b607750 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -441,7 +441,7 @@ function signal.can_dig(pos, player) -- check privileges if not player or not minetest.check_player_privs(player:get_player_name(), "interlocking") then if not player then -- intermediate debug to uncover hard-to-find bugz - atdebug("advtrains.interlocking.signal.can_dig(",pos,") called with player==nil!") + atwarn("advtrains.interlocking.signal.can_dig(",pos,") called with player==nil!") end return false end diff --git a/advtrains_interlocking/smartroute.lua b/advtrains_interlocking/smartroute.lua index 6479e44..f03ece0 100644 --- a/advtrains_interlocking/smartroute.lua +++ b/advtrains_interlocking/smartroute.lua @@ -64,7 +64,7 @@ function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) end if not cur_restart then -- we have no candidates left. Give up and return what we have - atdebug("(SR) No Candidates left, end rescan") + --atdebug("(SR) No Candidates left, end rescan") return found_routes end -- check if we need to stop due to having found enough routes @@ -72,14 +72,14 @@ function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) if cur_len > last_len then -- one level is finished, check if enoufh routes are found if #found_routes > find_more_than then - atdebug("(SR) Layer finished and enough routes found, end rescan") + --atdebug("(SR) Layer finished and enough routes found, end rescan") return found_routes end last_len = cur_len end -- our current restart point is nouw in cur_restart local c_sigd = cur_restart.sigd - atdebug("(SR) Search continues at",c_sigd,"seqlen",#cur_restart.tcbseq) + --atdebug("(SR) Search continues at",c_sigd,"seqlen",#cur_restart.tcbseq) -- do a TS repair, this also updates the RS cache should it be out of date local c_ts_id = ildb.check_and_repair_ts_at_pos(c_sigd.p, c_sigd.s, pname, false) if c_ts_id then @@ -90,7 +90,7 @@ function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) for _, end_sigd in ipairs(c_ts.tc_breaks) do end_pkey = advtrains.encode_pos(end_sigd.p) if rsout[end_pkey] then - atdebug("(SR) Section",c_ts_id,c_ts.name,"has way",c_sigd,"->",end_sigd) + --atdebug("(SR) Section",c_ts_id,c_ts.name,"has way",c_sigd,"->",end_sigd) local nsigd = {p=end_sigd.p, s = end_sigd.s==1 and 2 or 1} -- invert to other side -- record nsigd in the tcbseq local ntcbseq = table.copy(cur_restart.tcbseq) @@ -105,7 +105,7 @@ function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) or ndef.advtrains.route_role == "end" or ndef.advtrains.route_role == "shunt" then -- signal is suitable target local is_mainsignal = ndef.advtrains.route_role ~= "shunt" - atdebug("(SR) Suitable end signal at",nsigd,", recording route!") + --atdebug("(SR) Suitable end signal at",nsigd,", recording route!") -- record the found route in the results found_routes[#found_routes+1] = { tcbseq = ntcbseq, @@ -114,7 +114,7 @@ function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) } -- if this is a main signal and/or we are only searching shunt routes, stop the search here if is_mainsignal or searching_shunt then - atdebug("(SR) Not continuing this branch!") + --atdebug("(SR) Not continuing this branch!") shall_continue = false end end @@ -127,10 +127,10 @@ function sr.rescan(pname, sigd, tcbs, find_more_than, searching_shunt, pname) end end else - atdebug("(SR) Section",c_ts_id,c_ts.name,"found no rscache entry for start ",bgn_pts) + --atdebug("(SR) Section",c_ts_id,c_ts.name,"found no rscache entry for start ",bgn_pts) end else - atdebug("(SR) Stop at",c_sigd,"because no sec ahead") + --atdebug("(SR) Stop at",c_sigd,"because no sec ahead") end end end @@ -200,10 +200,10 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) local endpoint = route[#route].next -- 'next' field of the last route segment (the segment with index==len) if valid and endpoint then local endstr = advtrains.interlocking.sigd_to_string(endpoint) - atdebug("(Smartroute) Find existing endpoint:",route.name,"ends at",endstr) + --atdebug("(Smartroute) Find existing endpoint:",route.name,"ends at",endstr) ex_endpts[endstr] = route.name else - atdebug("(Smartroute) Find existing endpoint:",route.name," not considered, endpoint",endpoint,"valid",valid) + --atdebug("(Smartroute) Find existing endpoint:",route.name," not considered, endpoint",endpoint,"valid",valid) end end local new_frte = {} @@ -213,7 +213,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if not ex_endpts[endstr] then new_frte[#new_frte+1] = froute else - atdebug("(Smartroute) Throwing away",froute.name,"because endpoint",endstr,"already reached by route",ex_endpts[endstr]) + --atdebug("(Smartroute) Throwing away",froute.name,"because endpoint",endstr,"already reached by route",ex_endpts[endstr]) end end @@ -244,7 +244,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end end end - atdebug("Smartroute done!") + --atdebug("Smartroute done!") advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte) players_smartroute_actions[pname] = nil end -- cgit v1.2.3 From 8c91ce1ec178d6d49f3ddefc57d99eecd0caef3c Mon Sep 17 00:00:00 2001 From: orwell Date: Thu, 9 Jan 2025 00:57:32 +0100 Subject: Various bugfixes found in lunixforks debug --- advtrains/occupation.lua | 4 ++-- advtrains/trainlogic.lua | 4 ++-- advtrains/wagons.lua | 2 +- advtrains_interlocking/database.lua | 9 +++++---- advtrains_interlocking/route_ui.lua | 2 +- advtrains_interlocking/routesetting.lua | 9 ++++++--- advtrains_interlocking/signal_api.lua | 1 + advtrains_interlocking/tcb_ts_ui.lua | 5 ++++- advtrains_luaautomation/environment.lua | 10 +++++++--- 9 files changed, 29 insertions(+), 17 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains/occupation.lua b/advtrains/occupation.lua index 26e1f79..20a986e 100644 --- a/advtrains/occupation.lua +++ b/advtrains/occupation.lua @@ -89,8 +89,8 @@ function o.set_item(train_id, pos, idx) assert(idx) local i = 1 while t[i] do - if t[i]==train_id and t[i+1]==index then - break + if t[i]==train_id and t[i+1]==idx then + return end i = i + 2 end diff --git a/advtrains/trainlogic.lua b/advtrains/trainlogic.lua index e4939df..0e588c7 100644 --- a/advtrains/trainlogic.lua +++ b/advtrains/trainlogic.lua @@ -630,7 +630,7 @@ function advtrains.train_step_b(id, train, dtime) local ocn = otrn.path_cn[ob_idx] local ocp = otrn.path_cp[ob_idx] - local target_is_inside, ref_index, facing + local target_is_inside, ref_index, facing, same_dir if base_cn == ocn then -- same direction @@ -1049,7 +1049,7 @@ function advtrains.update_trainpart_properties(train_id, invert_flipstate) if not wagon then local ent = advtrains.wagon_objects[w_id] local pdesc - if ent then + if ent and ent:get_pos() then pdesc = "at " .. minetest.pos_to_string(ent:get_pos()) elseif train.last_pos then pdesc = "near " .. minetest.pos_to_string(train.last_pos) diff --git a/advtrains/wagons.lua b/advtrains/wagons.lua index ef057e5..01c60ec 100644 --- a/advtrains/wagons.lua +++ b/advtrains/wagons.lua @@ -1333,7 +1333,7 @@ function advtrains.get_wagon_prototype(data) end local rt, proto = advtrains.resolve_wagon_alias(wt) if not rt then - atwarn("Unable to load wagon type",wt,", using placeholder") + --atwarn("Unable to load wagon type",wt,", using placeholder") rt = "advtrains:wagon_placeholder" proto = advtrains.wagon_prototypes[rt] end diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index 9c72a72..d80fb76 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -84,7 +84,7 @@ function ildb.load(data) if pos then -- that was a pos_to_string local epos = advtrains.encode_pos(pos) - --atdebug("ILDB converting TCB position format",pts,"->",epos) + atdebug("ILDB converting TCB position format",pts,"->",epos) track_circuit_breaks[epos] = tcb else -- keep entry, it is already new @@ -100,7 +100,7 @@ function ildb.load(data) local lpos = minetest.string_to_pos(lpts) if lpos then local epos = advtrains.encode_pos(lpos) - --atdebug("ILDB converting tcb",pts,"side",t_side,"route",t_route,"lock position format",lpts,"->",epos) + atdebug("ILDB converting tcb",pts,"side",t_side,"route",t_route,"lock position format",lpts,"->",epos) locks_n[epos] = state else -- already correct format @@ -131,7 +131,7 @@ function ildb.load(data) if pos then -- that was a pos_to_string local epos = advtrains.encode_pos(pos) - --atdebug("ILDB converting Route Lock position format",pts,"->",epos) + atdebug("ILDB converting Route Lock position format",pts,"->",epos) advtrains.interlocking.route.rte_locks[epos] = lta else -- keep entry, it is already new @@ -412,6 +412,7 @@ function ildb.check_and_repair_ts_at_pos(pos, tcb_connid, notify_pname, force_cr return ildb.repair_ts_merge_all(all_tcbs, force_create, notify_pname) end --tsrepair_notify(notify_pname, "Found section", ts.name or ts_id, "here.") + ildb.update_rs_cache(ts_id) return ts_id end @@ -457,7 +458,7 @@ function ildb.get_all_tcbs_adjacent(inipos, inidir, per_track_callback) pos, connid = ti:next_branch() --atdebug("get_all_tcbs_adjacent: BRANCH: ",pos, connid) bconnid = nil - is_branch_start = true + local is_branch_start = true repeat -- callback if per_track_callback then diff --git a/advtrains_interlocking/route_ui.lua b/advtrains_interlocking/route_ui.lua index 7dddc6e..3c7bd64 100644 --- a/advtrains_interlocking/route_ui.lua +++ b/advtrains_interlocking/route_ui.lua @@ -67,7 +67,7 @@ function atil.show_route_edit_form(pname, sigd, routeid, sel_rpartidx) if c_rseg.locks then for pts, state in pairs(c_rseg.locks) do - local pos = minetest.string_to_pos(pts) + local pos = advtrains.decode_pos(pts) itab(i, "L "..pts.." -> "..state, "lock", pos) if not advtrains.is_passive(pos) then itab(i, "-!- No passive component at "..pts..". Please reconfigure route!", "err", nil) diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua index 28c8c3c..0668e62 100644 --- a/advtrains_interlocking/routesetting.lua +++ b/advtrains_interlocking/routesetting.lua @@ -104,7 +104,7 @@ function ilrs.set_route(signal, route, try) end for lp, state in pairs(c_locks) do - local confl = ilrs.has_route_lock(pts, state) + local confl = ilrs.has_route_lock(lp, state) local pos = advtrains.decode_pos(lp) if advtrains.is_passive(pos) then @@ -131,7 +131,8 @@ function ilrs.set_route(signal, route, try) local nvar = c_rseg.next if nvar then local re_tcbs = ildb.get_tcbs({p = nvar.p, s = (nvar.s==1) and 2 or 1}) - if not re_tcbs or not re_tcbs.ts_id or re_tcbs.ts_id~=c_ts_id then + if (not re_tcbs or not re_tcbs.ts_id or re_tcbs.ts_id~=c_ts_id) + and route[i+1] then --FIX 2025-01-08: in old worlds the final TCB may be wrong (it didn't matter back then), don't error out here (route still shown invalid in UI) if not try then atwarn("Encountered inconsistent ts (front~=back) while a real run of routesetting routine, at position",pts,"while setting route",rtename,"of",signal) end return false, "TCB at "..minetest.pos_to_string(nvar.p).." has different section than previous TCB. Please update track section or reconfigure route!" end @@ -413,7 +414,9 @@ function ilrs.update_route(sigd, tcbs, newrte, cancel) -- set_route now sets the signal aspects --has_changed_aspect = true -- route success. apply default_autoworking flag if requested - tcbs.route_auto = route.default_autoworking + if route.default_autoworking then + tcbs.route_auto = true --FIX 2025-01-08: never set it to false if it was true! + end end end if has_changed_aspect then diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index b607750..e92658d 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -375,6 +375,7 @@ function signal.reapply_aspect(pts) local rem_aspt = signal.aspects[remote] --atdebug("resolving remote",advtrains.decode_pos(remote),"aspt",rem_aspt) local rem_pos = advtrains.decode_pos(remote) + local _,rem_ndef rem_masp, _, _, rem_ndef = signal.get_aspect_internal(rem_pos, rem_aspt) if rem_masp then if rem_ndef.advtrains and rem_ndef.advtrains.get_aspect_info then diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index 59d3be4..814a11a 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -776,7 +776,7 @@ function advtrains.interlocking.check_route_valid(route, sigd) if c_rseg.locks then for pts, state in pairs(c_rseg.locks) do - local pos = minetest.string_to_pos(pts) + local pos = advtrains.decode_pos(pts) if not advtrains.is_passive(pos) then return false, "No passive component for lock at "..pts end @@ -795,6 +795,9 @@ function advtrains.interlocking.check_route_valid(route, sigd) i = i + 1 end -- check end TCB + if not c_sigd then + return false, "Final TCBS unset (legacy-style buffer route)" + end c_tcbs = ildb.get_tcbs(c_sigd) if not c_tcbs then return false, "Final TCBS missing at "..sigd_to_string(c_sigd) diff --git a/advtrains_luaautomation/environment.lua b/advtrains_luaautomation/environment.lua index b54d45c..a6ed2c7 100644 --- a/advtrains_luaautomation/environment.lua +++ b/advtrains_luaautomation/environment.lua @@ -226,11 +226,15 @@ if advtrains.interlocking then end static_env.get_aspect = function(signal) local pos = atlatc.pcnaming.resolve_pos(signal) - return advtrains.interlocking.signal_get_aspect(pos) + return advtrains.interlocking.signal.get_aspect_info(pos) end - static_env.set_aspect = function(signal, asp) + static_env.set_aspect = function(signal, main_asp, rem_signal) + if type(main_asp) == "table" then + error("set_aspect: Parameters of this method have changed to (signal, main_asp, rem_signal) with introduction of distant signalling: parameter 2 is now the main aspect name (a string)") + end local pos = atlatc.pcnaming.resolve_pos(signal) - return advtrains.interlocking.signal_set_aspect(pos,asp) + local rem_pos = rem_signal and atlatc.pcnaming.resolve_pos(rem_signal) + return advtrains.interlocking.signal_set_aspect(pos, main_asp, rem_pos) end --section_occupancy() -- cgit v1.2.3 From a45a9e27197dc98952f4ada7842f49d81899ab07 Mon Sep 17 00:00:00 2001 From: orwell Date: Wed, 15 Jan 2025 23:19:26 +0100 Subject: Fix bugs found in the video --- advtrains_interlocking/route_prog.lua | 2 +- advtrains_interlocking/signal_api.lua | 6 +++++- advtrains_interlocking/signal_aspect_ui.lua | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) (limited to 'advtrains_interlocking/signal_api.lua') diff --git a/advtrains_interlocking/route_prog.lua b/advtrains_interlocking/route_prog.lua index 3bdf6d6..71ebdf3 100644 --- a/advtrains_interlocking/route_prog.lua +++ b/advtrains_interlocking/route_prog.lua @@ -537,7 +537,7 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) show_routing_form(pname, tcbpos) advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname, rp.tmp_lcks, pname) return - elseif advtrains.interlocking.database.get_tcb(pos) then + elseif advtrains.interlocking.db.get_tcb(pos) then -- the punched node itself is a TCB show_routing_form(pname, pos) advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname, rp.tmp_lcks, pname) diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index e92658d..f624f7a 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -462,7 +462,11 @@ function signal.after_dig(pos, oldnode, oldmetadata, player) advtrains.interlocking.db.unassign_signal_for_tcbs(sigd) minetest.chat_send_player(player:get_player_name(), "Signal has been unassigned. Name and routes are kept for reuse.") end - -- TODO clear influence point + -- clear influence point + local ipts,iconnid = advtrains.interlocking.db.get_ip_by_signalpos(pos) + if ipts then + advtrains.interlocking.db.clear_ip_signal(ipts, iconnid) + end advtrains.interlocking.signal.unregister_aspect(pos) end diff --git a/advtrains_interlocking/signal_aspect_ui.lua b/advtrains_interlocking/signal_aspect_ui.lua index d67572c..98a332a 100644 --- a/advtrains_interlocking/signal_aspect_ui.lua +++ b/advtrains_interlocking/signal_aspect_ui.lua @@ -247,7 +247,9 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) ipmarker(pos, plconnid) minetest.chat_send_player(pname, "Configuring Signal: Successfully set influence point") -- Try to find a TCB ahead and auto assign this signal there - try_auto_assign_to_tcb(signalpos, pos, plconnid, pname) + if advtrains.interlocking.signal.get_signal_cap_level(signalpos) >= 2 then + try_auto_assign_to_tcb(signalpos, pos, plconnid, pname) + end else minetest.chat_send_player(pname, "Configuring Signal: Influence point of another signal is already present!") end -- cgit v1.2.3