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_aspect_ui.lua | 165 ++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 advtrains_interlocking/signal_aspect_ui.lua (limited to 'advtrains_interlocking/signal_aspect_ui.lua') diff --git a/advtrains_interlocking/signal_aspect_ui.lua b/advtrains_interlocking/signal_aspect_ui.lua new file mode 100644 index 0000000..edf3ab1 --- /dev/null +++ b/advtrains_interlocking/signal_aspect_ui.lua @@ -0,0 +1,165 @@ +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) + + 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 + end + form[#form+1] = F.dropdown(0.5, 2, 6, "main", entries, selid, true) + + 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"), + } + form[#form+1] = F.dropdown(0.5, 3.5, 6, "shunt_free", entries, st, true) + end + + form[#form+1] = F.S_label(0.5, 4.5, "Distant aspect") + local entries = {} + local 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 + end + form[#form+1] = F.dropdown(0.5, 5, 6, "dst", entries, selid, true) + + form[#form+1] = F.S_button_exit(0.5, 6, 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 def = advtrains.interlocking.aspects.get_type2_definition(suppasp.group) + if not def then + return nil + 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 + 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") + 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 "" + + local form + if suppasp.type == 2 then + form = make_signal_aspect_selector_t2(suppasp, purpose, isasp) + else + form = make_signal_aspect_selector_t1(suppasp, purpose, isasp) + end + if not form then + return + end + + 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 + +local function get_aspect_from_formspec_t1(suppasp, fields) + local maini = tonumber(fields.main) + if not maini then return end + local dsti = tonumber(fields.dst) + if not dsti then return end + return { + main = suppasp.main[maini], + dst = suppasp.dst[dsti], + shunt = usebool(suppasp.shunt, fields.shunt_free, "2"), + info = {}, + } +end + +local function get_aspect_from_formspec_t2(suppasp, fields) + local asp = advtrains.interlocking.aspects.type2main_to_type1(suppasp.group, tonumber(fields.asp)) + return asp +end + +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 + local suppasp = psl.suppasp + if fields.save then + local asp + if suppasp.type == 2 then + asp = get_aspect_from_formspec_t2(suppasp, fields) + else + asp = get_aspect_from_formspec_t1(suppasp, fields) + end + if asp then + psl.callback(pname, asp) + end + end + else + players_aspsel[pname] = nil + end + 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_aspect_ui.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_aspect_ui.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 30a0f862488a0cb67f9a8e2aecaf17797ad44a93 Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Mon, 24 Oct 2022 13:51:03 +0200 Subject: Properly handle repeater signals --- advtrains_interlocking/signal_aspect_accessors.lua | 41 +++++++++++++++++----- advtrains_interlocking/signal_aspect_ui.lua | 2 +- advtrains_interlocking/signal_aspects.lua | 40 +++++++++++++-------- .../spec/basic_signalling_spec.lua | 2 +- advtrains_interlocking/tcb_ts_ui.lua | 28 ++++++++------- advtrains_signals_japan/init.lua | 1 + 6 files changed, 77 insertions(+), 37 deletions(-) (limited to 'advtrains_interlocking/signal_aspect_ui.lua') diff --git a/advtrains_interlocking/signal_aspect_accessors.lua b/advtrains_interlocking/signal_aspect_accessors.lua index a1cbd4e..060f923 100644 --- a/advtrains_interlocking/signal_aspect_accessors.lua +++ b/advtrains_interlocking/signal_aspect_accessors.lua @@ -4,6 +4,27 @@ 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 = {} @@ -11,6 +32,9 @@ local supposed_aspects = {} function I.load_supposed_aspects(tbl) if tbl then supposed_aspects = tbl + for _, v in pairs(tbl) do + setmetatable(v, signal_aspect_metatable) + end end end @@ -41,11 +65,14 @@ end local function adjust_aspect(pos, asp) asp = table.copy(I.signal_convert_aspect_if_necessary(asp)) + setmetatable(asp, signal_aspect_metatable) local mainpos = D.get_main(pos) local nxtasp - if asp.main ~= 0 and mainpos then + if mainpos then nxtasp = get_aspect(mainpos) + end + if asp.main ~= 0 and mainpos then asp.dst = nxtasp.main else asp.dst = nil @@ -59,7 +86,10 @@ local function adjust_aspect(pos, asp) if stype == 2 then local group = suppasp.group local name - if asp.main ~= 0 and nxtasp and nxtasp.type2group == group and nxtasp.type2name then + 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) @@ -79,7 +109,7 @@ local function get_real_aspect(pos) 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) + asp = A.type2_to_type1(suppasp, asp) end return adjust_aspect(pos, asp) end @@ -108,11 +138,6 @@ 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 diff --git a/advtrains_interlocking/signal_aspect_ui.lua b/advtrains_interlocking/signal_aspect_ui.lua index 30b5165..ccedb01 100644 --- a/advtrains_interlocking/signal_aspect_ui.lua +++ b/advtrains_interlocking/signal_aspect_ui.lua @@ -219,7 +219,7 @@ local function get_aspect_from_formspec_t1(suppasp, fields) end local function get_aspect_from_formspec_t2(suppasp, fields) - local asp = advtrains.interlocking.aspects.type2main_to_type1(suppasp.group, tonumber(fields.asp)) + local asp = advtrains.interlocking.aspects.type2_to_type1(suppasp, tonumber(fields.asp)) return asp end diff --git a/advtrains_interlocking/signal_aspects.lua b/advtrains_interlocking/signal_aspects.lua index 5c4948b..c381fd2 100644 --- a/advtrains_interlocking/signal_aspects.lua +++ b/advtrains_interlocking/signal_aspects.lua @@ -67,7 +67,9 @@ local function get_type2_dst(group, name) return def.main[math.max(1, aspidx-1)].name end -local function type2main_to_type1(name, asp) +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 @@ -78,18 +80,26 @@ local function type2main_to_type1(name, asp) else aspidx = def.main[asp] or 2 end - local asptbl = def.main[aspidx] + 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 dst = def.main[math.min(#def.main, aspidx+1)].main + 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 local t = { - main = asptbl.main, - shunt = asptbl.shunt, + main = main, + shunt = shunt, proceed_as_main = asptbl.proceed_as_main, type2name = asp, type2group = name, @@ -101,31 +111,28 @@ local function type2main_to_type1(name, asp) return t end -local function type1_to_type2main(asp, group) +local function type1_to_type2main(asp, group, shift) local def = type2defs[group] if not def then return nil end - if group == asp.type2group and def.main[asp.type2name] then - return asp.type2name - end local t_main = def.main local idx - if not asp.main or asp.main == -1 then + 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 = math.max(#t_main-1, 1) + idx = #t_main-1 end - return t_main[idx].name + return t_main[math.max(1, idx-(shift or 0))].name end local function equalp(asp1, asp2) if asp1 == asp2 then -- same reference return true - elseif asp1.type2group and asp1.type2group == asp2.type2group then -- type2 with the same group - return asp1.type2name == asp2.type2name else for _, k in pairs {"main", "shunt", "dst"} do if asp1[k] ~= asp2[k] then @@ -133,6 +140,9 @@ local function equalp(asp1, asp2) end end end + if asp1.type2group and asp1.type2group == asp2.type2group then + return asp1.type2name == asp2.type2name + end return true end @@ -144,7 +154,7 @@ return { register_type2 = register_type2, get_type2_definition = get_type2_definition, get_type2_dst = get_type2_dst, - type2main_to_type1 = type2main_to_type1, + 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 0b79972..720b274 100644 --- a/advtrains_interlocking/spec/basic_signalling_spec.lua +++ b/advtrains_interlocking/spec/basic_signalling_spec.lua @@ -44,7 +44,7 @@ world.layout { describe("API for supposed signal aspects", function() it("should load and save data properly", function() - local tbl = {_foo = true} + local tbl = {_foo = {}} I.load_supposed_aspects(tbl) assert.same(tbl, I.save_supposed_aspects()) end) diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index 9f88296..3898d73 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -198,20 +198,24 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) if is_signal then local ndef = minetest.registered_nodes[node.name] if ndef and ndef.advtrains and ndef.advtrains.set_aspect then - local tcbs = ildb.get_tcbs(sigd) - if tcbs then - tcbs.signal = pos - if not tcbs.signal_name then - tcbs.signal_name = "Signal at "..minetest.pos_to_string(sigd.p) + if ndef.advtrains.supported_aspects and not ndef.advtrains.supported_aspects.dst_shift then + local tcbs = ildb.get_tcbs(sigd) + if tcbs then + tcbs.signal = pos + 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 + ildb.set_sigd_for_signal(pos, sigd) + minetest.chat_send_player(pname, "Configuring TCB: Successfully assigned signal.") + advtrains.interlocking.show_ip_form(pos, pname, true) + else + minetest.chat_send_player(pname, "Configuring TCB: Internal error, TCBS doesn't exist. Aborted.") end - if not tcbs.routes then - tcbs.routes = {} - end - ildb.set_sigd_for_signal(pos, sigd) - minetest.chat_send_player(pname, "Configuring TCB: Successfully assigned signal.") - advtrains.interlocking.show_ip_form(pos, pname, true) else - minetest.chat_send_player(pname, "Configuring TCB: Internal error, TCBS doesn't exist. Aborted.") + minetest.chat_send_player(pname, "Configuring TCB: Cannot use distant signal. Aborted.") end else minetest.chat_send_player(pname, "Configuring TCB: Cannot use static signals for routesetting. Aborted.") diff --git a/advtrains_signals_japan/init.lua b/advtrains_signals_japan/init.lua index 2062a21..59640b1 100644 --- a/advtrains_signals_japan/init.lua +++ b/advtrains_signals_japan/init.lua @@ -401,6 +401,7 @@ for _, rtab in ipairs { supported_aspects = { type = 2, group = siginfo.typename, + dst_shift = siginfo.isdst and 0, }, get_aspect = function() return asp -- 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_aspect_ui.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_aspect_ui.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 e61fe3176a2ef9c41a3fe16a3ffd58bd03fd96a6 Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Sat, 7 Jan 2023 14:17:17 +0100 Subject: Show IP form for repeater signals --- advtrains_interlocking/signal_aspect_ui.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'advtrains_interlocking/signal_aspect_ui.lua') diff --git a/advtrains_interlocking/signal_aspect_ui.lua b/advtrains_interlocking/signal_aspect_ui.lua index ddab793..186d2fe 100644 --- a/advtrains_interlocking/signal_aspect_ui.lua +++ b/advtrains_interlocking/signal_aspect_ui.lua @@ -199,12 +199,20 @@ function advtrains.interlocking.show_signal_aspect_selector(pname, p_suppasp, p_ info = {}, } local purpose = p_purpose or "" + local pos if type(p_purpose) == "table" then - purpose = {pname = pname, pos = p_purpose} + pos = p_purpose + 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) -- 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_aspect_ui.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 2afe8027986b2a8172d2cb47236e53e8f24b30f5 Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Sun, 26 Mar 2023 16:25:45 +0200 Subject: Allow selecting named aspect --- advtrains_interlocking/aspect.lua | 3 +- advtrains_interlocking/signal_aspect_ui.lua | 82 ++++++++++++++++++----------- advtrains_signals_japan/init.lua | 58 +++++++++++++------- 3 files changed, 91 insertions(+), 52 deletions(-) (limited to 'advtrains_interlocking/signal_aspect_ui.lua') diff --git a/advtrains_interlocking/aspect.lua b/advtrains_interlocking/aspect.lua index 1575fb1..6d6cb93 100644 --- a/advtrains_interlocking/aspect.lua +++ b/advtrains_interlocking/aspect.lua @@ -149,7 +149,7 @@ function signal_aspect:to_group(group) end end self.group = group - self.name = group.aspects[newidx][1] + self.name = gdef.aspects[newidx][1] return self end @@ -169,6 +169,7 @@ function signal_aspect:adjust_distant(dst, shift) self.dst = dst.main else self.dst = nil + return self end local dgdef = registered_groups[dst.group] if dgdef then diff --git a/advtrains_interlocking/signal_aspect_ui.lua b/advtrains_interlocking/signal_aspect_ui.lua index d36c6bc..a81b7fe 100644 --- a/advtrains_interlocking/signal_aspect_ui.lua +++ b/advtrains_interlocking/signal_aspect_ui.lua @@ -58,7 +58,7 @@ local function describe_supported_aspects(suppasp, isasp) local selid = 0 local mainasps = suppasp.main if type(mainasps) ~= "table" then - mainasps = {mainasps or false} + mainasps = {mainasps} end for idx, spv in ipairs(mainasps) do if isasp and spv == rawget(isasp, "main") then @@ -85,16 +85,24 @@ local function describe_supported_aspects(suppasp, isasp) end t.shunt_const = suppasp.shunt ~= nil - entries = {} - selid = 1 - for idx, spv in ipairs(suppasp.dst or {}) do - if isasp and spv == (isasp.dst or false) then - selid = idx + 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 - entries[idx] = describe_distant_aspect(spv) end - t.dst = entries - t.dst_current = selid + return t end @@ -112,35 +120,35 @@ local function make_signal_aspect_selector(suppasp, purpose, isasp) local form = { "formspec_version[4]", - string.format("size[8,%f]", ({5.75, 9.25})[formmode]), + string.format("size[8,%f]", ({5.75, 10.75})[formmode]), F.S_label(0.5, 0.5, "Select signal aspect"), } + 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) - 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, 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) + 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 - 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.") + 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, 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) + form[#form+1] = F.dropdown(0.5, 3.5+h0, 7, "asp_shunt", t.shunt, t.shunt_current, true) end return table.concat(form) @@ -186,12 +194,22 @@ local function usebool(sup, val, free) 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[(maini or 0)-1] + 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 @@ -209,11 +227,13 @@ local function get_aspect_from_formspec(suppasp, fields, psl) if proceed_as_main == nil then proceed_as_main = shunti == 3 end - return { + return advtrains.interlocking.aspect { main = main, shunt = shunt, proceed_as_main = proceed_as_main, info = {}, + name = name, + group = group, } end diff --git a/advtrains_signals_japan/init.lua b/advtrains_signals_japan/init.lua index 7d8dc1e..84373a9 100644 --- a/advtrains_signals_japan/init.lua +++ b/advtrains_signals_japan/init.lua @@ -275,6 +275,34 @@ 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"), + aspects = { + danger = { + label = S"Danger (halt)", + main = 0, + }, + restrictedspeed = { + label = S"Restricted speed", + }, + caution = { + label = S"Caution", + }, + reducedspeed = { + label = S"Reduced speed", + }, + clear = { + label = S"Clear (proceed)", + }, + "clear", + "reducedspeed", + "caution", + "restrictedspeed", + "danger", + } +} + local sigdefs = {} local lightcolors = { red = "red", @@ -282,22 +310,9 @@ local lightcolors = { yellow = "orange", distant = "purple", } -local aspnames = { - danger = "Danger (halt)", - restrictedspeed = "Restricted speed", - caution = "Caution", - reducedspeed = "Reduced speed", - clear = "Clear (proceed)", -} local function process_signal(name, sigdata, isrpt) - local typename = "advtrains_signals_japan:" .. 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 def.textures = tx def.desc = sigdata.desc def.isdst = isrpt @@ -307,6 +322,7 @@ local function process_signal(name, sigdata, isrpt) lightcount = lightcount+1 end def.lightcount = lightcount + def.suppasp_names = {} for idx, asp in ipairs(sigdata.aspects) do local aspname = asp.name local tt = { @@ -322,8 +338,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, ":") - groupdef.aspects[idx] = {asp.name} - groupdef.aspects[asp.name] = {label = S(aspnames[asp.name]), main = asp.main, proceed_as_main = true} + def.suppasp_names[idx] = aspname end local invimg = { string.format("[combine:%dx%d", lightcount*4+1, lightcount*4+1), @@ -337,9 +352,6 @@ local function process_signal(name, sigdata, isrpt) invimg[lightcount+2] = string.format("%d,%d=(advtrains_hud_bg.png\\^[resize\\:3x3\\^[colorize\\:%s)", 2*lightcount-1, 4*lightcount-3, lightcolors.distant) end def.inventory_image = table.concat(invimg, ":") - if not isrpt then - advtrains.interlocking.aspect.register_group(groupdef) - end return def end for sigtype, sigdata in pairs { @@ -401,11 +413,17 @@ for _, rtab in ipairs { drop = "advtrains_signals_japan:"..sigtype.."_danger_0", advtrains = { supported_aspects = { - group = siginfo.typename, + group = "advtrains_signals_japan:5a", + name = siginfo.suppasp_names, dst_shift = siginfo.isdst and 0, + main = (not siginfo.isdst) and {} or false }, get_aspect = function() - return {group = siginfo.typename, name = asp} + local main + if siginfo.isdst then + main = false + end + return {group = "advtrains_signals_japan:5a", name = asp, main = main} end, set_aspect = function(pos, node, asp) advtrains.ndb.swap_node(pos, {name = "advtrains_signals_japan:"..sigtype.."_"..(asp.name).."_"..rot, param2 = node.param2}) -- cgit v1.2.3