From 6fd845baec0f5aa8b7cdee1adf8d05061a643242 Mon Sep 17 00:00:00 2001 From: orwell Date: Thu, 23 May 2024 00:58:24 +0200 Subject: Connect the ropes, start on making the UI work --- advtrains_interlocking/database.lua | 2 +- advtrains_interlocking/distant.lua | 200 ----------- advtrains_interlocking/init.lua | 3 - advtrains_interlocking/route_ui.lua | 18 +- advtrains_interlocking/routesetting.lua | 15 +- advtrains_interlocking/signal_api.lua | 220 +++---------- advtrains_interlocking/signal_aspect_accessors.lua | 163 --------- advtrains_interlocking/signal_aspect_ui.lua | 366 ++++++++------------- advtrains_interlocking/tcb_ts_ui.lua | 11 +- 9 files changed, 207 insertions(+), 791 deletions(-) delete mode 100644 advtrains_interlocking/distant.lua delete mode 100644 advtrains_interlocking/signal_aspect_accessors.lua (limited to 'advtrains_interlocking') diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua index 4213c3d..e2df547 100644 --- a/advtrains_interlocking/database.lua +++ b/advtrains_interlocking/database.lua @@ -1006,7 +1006,7 @@ end function ildb.get_ip_signal_asp(pts, connid) local p = ildb.get_ip_signal(pts, connid) if p then - local asp = advtrains.interlocking.signal_get_aspect(p) + local asp = advtrains.interlocking.signal.get_aspect(p) if not asp then atlog("Clearing orphaned signal influence point", pts, "/", connid) ildb.clear_ip_signal(pts, connid) diff --git a/advtrains_interlocking/distant.lua b/advtrains_interlocking/distant.lua deleted file mode 100644 index 32ada82..0000000 --- a/advtrains_interlocking/distant.lua +++ /dev/null @@ -1,200 +0,0 @@ ---- Distant signaling. --- This module implements a database backend for distant signal assignments. --- The actual modifications to signal aspects are still done by signal aspect accessors. --- @module advtrains.interlocking.distant - -local db_distant = {} -local db_distant_of = {} - -local pts = advtrains.encode_pos -local stp = advtrains.decode_pos - ---- Replace the distant signal assignment database. --- @function load --- @param db The new database to load. -local function db_load(x) - if type(x) ~= "table" then - return - end - db_distant = x.distant - db_distant_of = x.distant_of -end - ---- Retrieve the current distant signal assignment database. --- @function save --- @return The current database. -local function db_save() - return { - distant = db_distant, - distant_of = db_distant_of, - } -end - -local update_signal, update_main, update_dst - ---- Unassign a distant signal. --- @function unassign_dst --- @param dst The position of the distant signal. --- @param[opt=false] force Whether to skip callbacks. -local function unassign_dst(dst, force) - local pts_dst = pts(dst) - local main = db_distant_of[pts_dst] - db_distant_of[pts_dst] = nil - if main then - local pts_main = main[1] - local t = db_distant[pts_main] - if t then - t[pts_dst] = nil - end - end - if not force then - update_dst(dst) - end -end - ---- Unassign a main signal. --- @function unassign_main --- @param main The position of the main signal. --- @param[opt=false] force Whether to skip callbacks. -local function unassign_main(main, force) - local pts_main = pts(main) - local t = db_distant[pts_main] - if not t then - return - end - for pts_dst in pairs(t) do - local realmain = db_distant_of[pts_dst] - if realmain and realmain[1] == pts_main then - db_distant_of[pts_dst] = nil - if not force then - local dst = stp(pts_dst) - update_dst(dst) - end - end - end - db_distant[pts_main] = nil -end - ---- Remove all (main and distant) signal assignments from a signal. --- @function unassign_all --- @param pos The position of the signal. --- @param[opt=false] force Whether to skip callbacks. -local function unassign_all(pos, force) - unassign_main(pos) - unassign_dst(pos, force) -end - ---- Check whether a signal is "appropriate" for the distant signal system. --- Currently, a signal is considered appropriate if its signal aspect can be set. --- @function appropriate_signal --- @param pos The position of the signal -local function appropriate_signal(pos) - local node = advtrains.ndb.get_node(pos) - local ndef = minetest.registered_nodes[node.name] or {} - if not ndef then - return false - end - local atdef = ndef.advtrains - if not atdef then - return false - end - return atdef.supported_aspects and atdef.set_aspect and true -end - ---- Assign a distant signal to a main signal. --- @function assign --- @param main The position of the main signal. --- @param dst The position of the distant signal. --- @param[opt="manual"] by The method of assignment. --- @param[opt=false] skip_update Whether to skip callbacks. -local function assign(main, dst, by, skip_update) - if not (appropriate_signal(main) and appropriate_signal(dst)) then - return - end - local pts_main = pts(main) - local pts_dst = pts(dst) - local t = db_distant[pts_main] - if not t then - t = {} - db_distant[pts_main] = t - end - if not by then - by = "manual" - end - unassign_dst(dst, true) - t[pts_dst] = by - db_distant_of[pts_dst] = {pts_main, by} - if not skip_update then - update_dst(dst) - end -end - ---- Get the distant signals assigned to a main signal. --- @function get_distant --- @param main The position of the main signal. --- @treturn {[pos]=by,...} A table of distant signals, with the positions encoded using `advtrains.encode_pos`. -local function get_distant(main) - local pts_main = pts(main) - return db_distant[pts_main] or {} -end - ---- Get the main signal assigned the a distant signal. --- @function get_main --- @param dst The position of the distant signal. --- @return The position of the main signal. --- @return The method of assignment. -local function get_main(dst) - local pts_dst = pts(dst) - local main = db_distant_of[pts_dst] - if not main then - return - end - if main[1] then - return stp(main[1]), unpack(main, 2) - else - return unpack(main) - end -end - ---- Update all distant signals assigned to a main signal. --- @function update_main --- @param main The position of the main signal. -update_main = function(main) - local pts_main = pts(main) - local t = get_distant(main) - for pts_dst in pairs(t) do - local dst = stp(pts_dst) - advtrains.interlocking.signal_readjust_aspect(dst) - end -end - ---- Update the aspect of a distant signal. --- @function update_dst --- @param dst The position of the distant signal. -update_dst = function(dst) - advtrains.interlocking.signal_readjust_aspect(dst) -end - ---- Update the aspect of a combined (main and distant) signal and all distant signals assigned to it. --- @function update_signal --- @param pos The position of the signal. -update_signal = function(pos) - update_main(pos) - update_dst(pos) -end - -advtrains.distant = { - load = db_load, - save = db_save, - assign = assign, - unassign_dst = unassign_dst, - unassign_main = unassign_main, - unassign_all = unassign_all, - get_distant = get_distant, - get_dst = get_distant, - get_main = get_main, - update_main = update_main, - update_dst = update_dst, - update_signal = update_signal, - appropriate_signal = appropriate_signal, -} diff --git a/advtrains_interlocking/init.lua b/advtrains_interlocking/init.lua index dd08b4a..c397aa6 100644 --- a/advtrains_interlocking/init.lua +++ b/advtrains_interlocking/init.lua @@ -15,9 +15,6 @@ local modpath = minetest.get_modpath(minetest.get_current_modname()) .. DIR_DELI --advtrains.interlocking.aspect = dofile(modpath.."aspect.lua") dofile(modpath.."database.lua") -dofile(modpath.."distant.lua") -dofile(modpath.."distant_ui.lua") -dofile(modpath.."signal_aspect_accessors.lua") dofile(modpath.."signal_api.lua") dofile(modpath.."signal_aspect_ui.lua") dofile(modpath.."demosignals.lua") diff --git a/advtrains_interlocking/route_ui.lua b/advtrains_interlocking/route_ui.lua index a1a331d..982c579 100644 --- a/advtrains_interlocking/route_ui.lua +++ b/advtrains_interlocking/route_ui.lua @@ -33,7 +33,7 @@ function atil.show_route_edit_form(pname, sigd, routeid) local function itab(t) tab[#tab+1] = minetest.formspec_escape(string.gsub(t, ",", " ")) end - itab("TCB "..sigd_to_string(sigd).." ("..(tcbs.signal_name or "")..") Route #"..routeid) + itab("("..(tcbs.signal_name or "+")..") Route #"..routeid) -- this code is partially copy-pasted from routesetting.lua -- we start at the tc designated by signal @@ -56,13 +56,14 @@ function atil.show_route_edit_form(pname, sigd, routeid) c_rseg = route[i] c_lckp = {} - itab(""..i.." Entry "..sigd_to_string(c_sigd).." -> Sec. "..(c_ts and c_ts.name or "-").." -> Exit "..(c_rseg.next and sigd_to_string(c_rseg.next) or "END")) + itab(""..i.." "..sigd_to_string(c_sigd)) + itab("= "..(c_ts and c_ts.name or "-").." =") if c_rseg.locks then for pts, state in pairs(c_rseg.locks) do local pos = minetest.string_to_pos(pts) - itab(" Lock: "..pts.." -> "..state) + itab("L "..pts.." -> "..state) if not advtrains.is_passive(pos) then itab("-!- No passive component at "..pts..". Please reconfigure route!") break @@ -75,16 +76,17 @@ function atil.show_route_edit_form(pname, sigd, routeid) end if c_sigd then local e_tcbs = ildb.get_tcbs(c_sigd) - itab("Route end: "..sigd_to_string(c_sigd).." ("..(e_tcbs and e_tcbs.signal_name or "-")..")") + local signame = "-" + if e_tcbs and e_tcbs.signal then signame = e_tcbs.signal_name or "+" end + itab("E "..sigd_to_string(c_sigd).." ("..signame..")") else - itab("Route ends on dead-end") + itab("E (none)") end - form = form.."textlist[0.5,2;7.75,3.9;rtelog;"..table.concat(tab, ",").."]" + form = form.."textlist[0.5,2;3,3.9;rtelog;"..table.concat(tab, ",").."]" form = form.."button[0.5,6;3,1;back;<<< Back to signal]" - form = form.."button[4.5,6;2,1;aspect;Signal Aspect]" - form = form.."button[6.5,6;2,1;delete;Delete Route]" + form = form.."button[5.5,6;3,1;delete;Delete Route]" --atdebug(route.ars) form = form.."style[ars;font=mono]" diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua index 24b3199..a576139 100644 --- a/advtrains_interlocking/routesetting.lua +++ b/advtrains_interlocking/routesetting.lua @@ -138,7 +138,7 @@ function ilrs.set_route(signal, route, try) } if c_tcbs.signal then c_tcbs.route_committed = true - c_tcbs.aspect = route.aspect or advtrains.interlocking.FULL_FREE + c_tcbs.aspect = advtrains.interlocking.signal.MASP_FREE c_tcbs.route_origin = signal signals[#signals+1] = c_tcbs end @@ -166,7 +166,7 @@ function ilrs.set_route(signal, route, try) if (not nodst) and (not assigned_by or assigned_by == "routesetting") then advtrains.distant.assign(lastsig, pos, "routesetting", true) end - advtrains.interlocking.update_signal_aspect(tcbs, i ~= 1) + advtrains.interlocking.signal.update_route_aspect(tcbs, i ~= 1) end end @@ -278,14 +278,7 @@ function ilrs.cancel_route_from(sigd) c_tcbs.route_auto = nil c_tcbs.route_origin = nil - if c_tcbs.signal then - local pos = c_tcbs.signal - local _, assigned_by = advtrains.distant.get_main(pos) - if assigned_by == "routesetting" then - advtrains.distant.unassign_dst(pos, true) - end - end - advtrains.interlocking.update_signal_aspect(c_tcbs) + advtrains.interlocking.signal.update_route_aspect(c_tcbs) c_ts_id = c_tcbs.ts_id if not c_tcbs then @@ -370,7 +363,7 @@ function ilrs.update_route(sigd, tcbs, newrte, cancel) end if has_changed_aspect then -- FIX: prevent an minetest.after() loop caused by update_signal_aspect dispatching path invalidation, which in turn calls ARS again - advtrains.interlocking.update_signal_aspect(tcbs) + advtrains.interlocking.signal.update_route_aspect(tcbs) end advtrains.interlocking.update_player_forms(sigd) end diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index d27a045..5216594 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -5,9 +5,15 @@ local F = advtrains.formspec local signal = {} signal.MASP_HALT = { - name = "halt", - description = "HALT", - halt = true, + name = nil, + speed = nil, + remote = nil, +} + +signal.MASP_FREE = { + name = "_free", + speed = -1, + remote = nil, } signal.ASPI_HALT = { @@ -50,11 +56,12 @@ Note that once apply_aspect returns, there is no need for advtrains anymore to q When the signal, for any reason, wants to change its aspect by itself *without* going through the signal API then it should update the aspect info cache by calling advtrains.interlocking.signal.update_aspect_info(pos) -Note that the apply_aspect function MUST accept the following main aspect, even if it is not defined in the main_aspects table: -{ name = "halt", halt = true } -It should cause the signal to show its most restrictive aspect. Typically it is a halt aspect, but e.g. for distant-only +Apply_aspect may also receive nil as the main aspect. It usually means that the signal is not assigned to anything particular, +and it should cause the signal to show its most restrictive aspect. Typically it is a halt aspect, but e.g. for distant-only signals this would be "expect stop". +Main aspect names starting with underscore (e.g. "_default") are reserved and must not be used! + == Aspect Info == The actual signal aspect in the already-known format. This is what the trains use to determine halt/proceed and speed. asp = { @@ -152,7 +159,7 @@ end -- - Storing the main aspect and dst pos for this signal permanently (until next change) -- - Assigning the distant signal for this signal -- - Calling apply_aspect() in the signal's node definition to make the signal show the aspect --- - Calling apply_aspect() again whenever the distant signal changes its aspect +-- - Calling apply_aspect() again whenever the remote signal changes its aspect -- - Notifying this signal's distant signals about changes to this signal (unless skip_dst_notify is specified) function signal.set_aspect(pos, main_asp_name, main_asp_speed, rem_pos, skip_dst_notify) local main_pts = advtrains.encode_pos(pos) @@ -252,10 +259,7 @@ function signal.get_aspect(pos) end local function cache_mainaspects(ndefat) - ndefat.main_aspects_lookup = { - -- always define halt aspect - halt = signal.MASP_HALT - } + ndefat.main_aspects_lookup = {} for _,ma in ipairs(ndefat.main_aspects) do ndefat.main_aspects_lookup[ma.name] = ma end @@ -264,7 +268,7 @@ end function signal.get_aspect_internal(pos, aspt) if not aspt then -- oh, no main aspect, nevermind - return nil, aspt.remote, nil + return nil, nil, nil end atdebug("get_aspect_internal",pos,aspt) -- look aspect in nodedef @@ -277,6 +281,10 @@ function signal.get_aspect_internal(pos, aspt) cache_mainaspects(ndefat) end local masp = ndefat.main_aspects_lookup[aspt.name] + -- special handling for the default free aspect ("_free") + if aspt.name == "_free" then + masp = ndefat.main_aspects[1] + end if not masp then atwarn(pos,"invalid main aspect",aspt.name,"valid are",ndefat.main_aspects_lookup) return nil, aspt.remote, node, ndef @@ -355,10 +363,30 @@ end function signal.update_route_aspect(tcbs, skip_dst_notify) if tcbs.signal then local asp = tcbs.aspect or signal.MASP_HALT - signal.set_aspect(tcbs.signal, asp, skip_dst_notify) + signal.set_aspect(tcbs.signal, asp.name, asp.speed, asp.remote, skip_dst_notify) end end +-- Returns how capable the signal is with regards to aspect setting +-- 0: not a signal at all +-- 1: signal has get_aspect_info() but the aspect is not variable (e.g. a sign) +-- 2: signal has apply_aspect() but does not have main aspects (e.g. a pure distant signal) +-- 3: Full capabilities, signal has main aspects and can be used as main/shunt signal (can be start/endpoint of a route) +function signal.get_signal_cap_level(pos) + local node = advtrains.ndb.get_node_or_nil(pos) + local ndef = node and minetest.registered_nodes[node.name] + local ndefat = ndef and ndef.advtrains + if ndefat and ndefat.get_aspect_info then + if ndefat.apply_aspect then + if ndefat.main_aspects then + return 3 + end + return 2 + end + return 1 + end + return 0 +end ---------------- @@ -366,7 +394,7 @@ function signal.can_dig(pos) return not advtrains.interlocking.db.get_sigd_for_signal(pos) end -function advtrains.interlocking.signal_after_dig(pos) +function signal.after_dig(pos) -- TODO clear influence point advtrains.interlocking.signal.clear_aspect(pos) end @@ -374,169 +402,7 @@ end function signal.on_rightclick(pos, node, player, itemstack, pointed_thing) local pname = player:get_player_name() local control = player:get_player_control() - if control.aux1 then - advtrains.interlocking.show_ip_form(pos, pname) - return - end - advtrains.interlocking.show_signal_form(pos, node, pname) -end - -function advtrains.interlocking.show_signal_form(pos, node, pname) - local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) - if sigd then - advtrains.interlocking.show_signalling_form(sigd, pname) - else - local ndef = minetest.registered_nodes[node.name] - if ndef.advtrains and ndef.advtrains.set_aspect then - -- permit to set aspect manually - local function callback(pname, aspect) - signal.set_aspect(pos, aspect) - end - local isasp = advtrains.interlocking.signal_get_aspect(pos, node) - - advtrains.interlocking.show_signal_aspect_selector( - pname, - ndef.advtrains.supported_aspects, - pos, callback, - isasp) - else - --static signal - only IP - advtrains.interlocking.show_ip_form(pos, pname) - end - end -end - -local players_assign_ip = {} - -local function ipmarker(ipos, connid) - local node_ok, conns, rhe = advtrains.get_rail_info_at(ipos, advtrains.all_tracktypes) - if not node_ok then return end - local yaw = advtrains.dir_to_angle(conns[connid].c) - - -- using tcbmarker here - local obj = minetest.add_entity(vector.add(ipos, {x=0, y=0.2, z=0}), "advtrains_interlocking:tcbmarker") - if not obj then return end - obj:set_yaw(yaw) - obj:set_properties({ - textures = { "at_il_signal_ip.png" }, - }) -end - -function advtrains.interlocking.make_ip_formspec_component(pos, x, y, w) - advtrains.interlocking.db.check_for_duplicate_ip(pos) - local pts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos) - if pts then - return table.concat { - F.S_label(x, y, "Influence point is set at @1.", string.format("%s/%s", pts, connid)), - F.S_button_exit(x, y+0.5, w/2-0.125, "ip_set", "Modify"), - F.S_button_exit(x+w/2+0.125, y+0.5, w/2-0.125, "ip_clear", "Clear"), - }, pts, connid - else - return table.concat { - F.S_label(x, y, "Influence point is not set."), - F.S_button_exit(x, y+0.5, w, "ip_set", "Set influence point"), - } - end -end - --- shows small info form for signal properties --- This function is named show_ip_form because it was originally only intended --- for assigning/changing the influence point. --- only_notset: show only if it is not set yet (used by signal tcb assignment) -function advtrains.interlocking.show_ip_form(pos, pname, only_notset) - if not minetest.check_player_privs(pname, "interlocking") then - return - end - local ipform, pts, connid = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 0.5, 7) - local form = { - "formspec_version[4]", - "size[8,2.25]", - ipform, - } - if pts then - local ipos = minetest.string_to_pos(pts) - ipmarker(ipos, connid) - end - if advtrains.distant.appropriate_signal(pos) then - form[#form+1] = advtrains.interlocking.make_dst_formspec_component(pos, 0.5, 2, 7, 4.25) - form[2] = "size[8,6.75]" - end - form = table.concat(form) - if not only_notset or not pts then - minetest.show_formspec(pname, "at_il_propassign_"..minetest.pos_to_string(pos), form) - end + advtrains.interlocking.show_signal_form(pos, node, pname, control.aux1) end -function advtrains.interlocking.handle_ip_formspec_fields(pname, pos, fields) - if not (pos and minetest.check_player_privs(pname, {train_operator=true, interlocking=true})) then - return - end - if fields.ip_set then - advtrains.interlocking.signal_init_ip_assign(pos, pname) - elseif fields.ip_clear then - advtrains.interlocking.db.clear_ip_by_signalpos(pos) - end -end - -minetest.register_on_player_receive_fields(function(player, formname, fields) - local pname = player:get_player_name() - local pts = string.match(formname, "^at_il_propassign_([^_]+)$") - local pos - if pts then - pos = minetest.string_to_pos(pts) - end - if pos then - advtrains.interlocking.handle_ip_formspec_fields(pname, pos, fields) - advtrains.interlocking.handle_dst_formspec_fields(pname, pos, fields) - end -end) - --- inits the signal IP assignment process -function signal.init_ip_assign(pos, pname) - if not minetest.check_player_privs(pname, "interlocking") then - minetest.chat_send_player(pname, "Insufficient privileges to use this!") - return - end - --remove old IP - --advtrains.interlocking.db.clear_ip_by_signalpos(pos) - minetest.chat_send_player(pname, "Configuring Signal: Please look in train's driving direction and punch rail to set influence point.") - - players_assign_ip[pname] = pos -end - -minetest.register_on_punchnode(function(pos, node, player, pointed_thing) - local pname = player:get_player_name() - if not minetest.check_player_privs(pname, "interlocking") then - return - end - -- IP assignment - local signalpos = players_assign_ip[pname] - if signalpos then - if vector.distance(pos, signalpos)<=50 then - local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes) - if node_ok and #conns == 2 then - - local yaw = player:get_look_horizontal() - local plconnid = advtrains.yawToClosestConn(yaw, conns) - - -- add assignment if not already present. - local pts = advtrains.roundfloorpts(pos) - if not advtrains.interlocking.db.get_ip_signal_asp(pts, plconnid) then - advtrains.interlocking.db.set_ip_signal(pts, plconnid, signalpos) - ipmarker(pos, plconnid) - minetest.chat_send_player(pname, "Configuring Signal: Successfully set influence point") - else - minetest.chat_send_player(pname, "Configuring Signal: Influence point of another signal is already present!") - end - else - minetest.chat_send_player(pname, "Configuring Signal: This is not a normal two-connection rail! Aborted.") - end - else - minetest.chat_send_player(pname, "Configuring Signal: Node is too far away. Aborted.") - end - players_assign_ip[pname] = nil - end -end) - - advtrains.interlocking.signal = signal diff --git a/advtrains_interlocking/signal_aspect_accessors.lua b/advtrains_interlocking/signal_aspect_accessors.lua deleted file mode 100644 index d91df31..0000000 --- a/advtrains_interlocking/signal_aspect_accessors.lua +++ /dev/null @@ -1,163 +0,0 @@ ---- Signal aspect accessors --- @module advtrains.interlocking - -local A = advtrains.interlocking.aspect -local D = advtrains.distant -local I = advtrains.interlocking -local N = advtrains.ndb -local pts = advtrains.roundfloorpts - -local get_aspect - -local supposed_aspects = {} - ---- Replace the signal aspect cache. --- @function load_supposed_aspects --- @param db The new database. -function I.load_supposed_aspects(tbl) - if tbl then - supposed_aspects = {} - for k, v in pairs(tbl) do - supposed_aspects[k] = A(v) - end - end -end - ---- Retrieve the signal aspect cache. --- @function save_supposed_aspects --- @return The current database in use. -function I.save_supposed_aspects() - local t = {} - for k, v in pairs(supposed_aspects) do - t[k] = v:plain(true) - end - return t -end - ---- Read the aspect of a signal strictly from cache. --- @param pos The position of the signal. --- @return[1] The aspect of the signal (if present in cache). --- @return[2] The nil constant (otherwise). -local function get_supposed_aspect(pos) - return supposed_aspects[pts(pos)] -end - ---- Update the signal aspect information in cache. --- @param pos The position of the signal. --- @param asp The new signal aspect -local function set_supposed_aspect(pos, asp) - supposed_aspects[pts(pos)] = asp -end - ---- Get the definition of a node. --- @param pos The position of the node. --- @return[1] The definition of the node (if present). --- @return[2] An empty table (otherwise). -local function get_ndef(pos) - local node = N.get_node(pos) - return (minetest.registered_nodes[node.name] or {}), node -end - ---- Get the aspects supported by a signal. --- @function signal_get_supported_aspects --- @param pos The position of the signal. --- @return[1] The table of supported aspects (if present). --- @return[2] The nil constant (otherwise). -local function get_supported_aspects(pos) - local ndef = get_ndef(pos) - if ndef.advtrains and ndef.advtrains.supported_aspects then - return ndef.advtrains.supported_aspects - end - return nil -end - ---- Adjust a new signal aspect to fit a signal. --- @param pos The position of the signal. --- @param asp The new signal aspect. --- @return The adjusted signal aspect. --- @return The information to pass to the `advtrains.set_aspect` field in the node definitions. -local function adjust_aspect(pos, asp) - local asp = A(asp) - - local mainpos = D.get_main(pos) - local nxtasp - if mainpos then - nxtasp = get_aspect(mainpos) - end - local suppasp = get_supported_aspects(pos) - if not suppasp then - return asp - end - return asp:adjust_distant(nxtasp, suppasp.dst_shift):to_group(suppasp.group) -end - ---- Get the aspect of a signal without accessing the cache. --- For most cases, `get_aspect` should be used instead. --- @function signal_get_real_aspect --- @param pos The position of the signal. --- @return[1] The signal aspect adjusted using `adjust_aspect` (if present). --- @return[2] The nil constant (otherwise). -local function get_real_aspect(pos) - local ndef, node = get_ndef(pos) - if ndef.advtrains and ndef.advtrains.get_aspect then - local asp = ndef.advtrains.get_aspect(pos, node) or I.DANGER - return adjust_aspect(pos, asp) - end - return nil -end - ---- Get the aspect of a signal. --- @function signal_get_aspect --- @param pos The position of the signal. --- @return[1] The aspect of the signal (if present). --- @return[2] The nil constant (otherwise). -get_aspect = function(pos) - local asp = get_supposed_aspect(pos) - if not asp then - asp = get_real_aspect(pos) - set_supposed_aspect(pos, asp) - end - return asp -end - ---- Set the aspect of a signal. --- @function signal_set_aspect --- @param pos The position of the signal. --- @param asp The new signal aspect. --- @param[opt=false] skipdst Whether to skip updating distant signals. -local function set_aspect(pos, asp, skipdst) - local node = N.get_node(pos) - local ndef = minetest.registered_nodes[node.name] - if ndef and ndef.advtrains and ndef.advtrains.set_aspect then - local oldasp = I.signal_get_aspect(pos) or DANGER - local newasp = adjust_aspect(pos, asp) - set_supposed_aspect(pos, newasp) - ndef.advtrains.set_aspect(pos, node, newasp) - I.signal_on_aspect_changed(pos) - local aspect_changed = oldasp ~= newasp - if (not skipdst) and aspect_changed then - D.update_main(pos) - end - end -end - ---- Remove a signal from cache. --- @function signal_clear_aspect --- @param pos The position of the signal. -local function clear_aspect(pos) - set_supposed_aspect(pos, nil) -end - ---- Readjust the aspect of a signal. --- @function signal_readjust_aspect --- @param pos The position of the signal. -local function readjust_aspect(pos) - set_aspect(pos, get_aspect(pos)) -end - -I.signal_get_supported_aspects = get_supported_aspects -I.signal_get_real_aspect = get_real_aspect -I.signal_get_aspect = get_aspect -I.signal_set_aspect = set_aspect -I.signal_clear_aspect = clear_aspect -I.signal_readjust_aspect = readjust_aspect diff --git a/advtrains_interlocking/signal_aspect_ui.lua b/advtrains_interlocking/signal_aspect_ui.lua index a81b7fe..e5d2003 100644 --- a/advtrains_interlocking/signal_aspect_ui.lua +++ b/advtrains_interlocking/signal_aspect_ui.lua @@ -1,262 +1,188 @@ local F = advtrains.formspec -local players_aspsel = {} -local function describe_main_aspect(spv) - if spv == 0 then - return attrans("Danger (halt)") - elseif spv == -1 then - return attrans("Continue at maximum speed") - elseif not spv then - return attrans("Continue with current speed limit") +function advtrains.interlocking.show_signal_form(pos, node, pname, aux_key) + local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) + if sigd and not aux_key then + advtrains.interlocking.show_signalling_form(sigd, pname) else - return attrans("Continue with the speed limit of @1", tostring(spv)) - end -end - -local function describe_shunt_aspect(shunt) - if shunt then - return attrans("Shunting allowed") - else - return attrans("No shunting") + if advtrains.interlocking.signal.get_signal_cap_level(pos) >= 2 then + advtrains.interlocking.show_ip_sa_form(pos, pname) + else + advtrains.interlocking.show_ip_form(pos, pname) + end end end -local function describe_distant_aspect(spv) - if spv == 0 then - return attrans("Expect to stop at the next signal") - elseif spv == -1 then - return attrans("Expect to continue at maximum speed") - elseif not spv then - return attrans("No distant signal information") - else - return attrans("Expect to continue with a speed limit of @1", tostring(spv)) - end +local players_assign_ip = {} +local players_assign_distant = {} + +local function ipmarker(ipos, connid) + local node_ok, conns, rhe = advtrains.get_rail_info_at(ipos, advtrains.all_tracktypes) + if not node_ok then return end + local yaw = advtrains.dir_to_angle(conns[connid].c) + + -- using tcbmarker here + local obj = minetest.add_entity(vector.add(ipos, {x=0, y=0.2, z=0}), "advtrains_interlocking:tcbmarker") + if not obj then return end + obj:set_yaw(yaw) + obj:set_properties({ + textures = { "at_il_signal_ip.png" }, + }) end -advtrains.interlocking.describe_main_aspect = describe_main_aspect -advtrains.interlocking.describe_shunt_aspect = describe_shunt_aspect -advtrains.interlocking.describe_distant_aspect = describe_distant_aspect - -local function dsel(p, q, x, y) - if p == nil then - if q then - return x - else - return y - end - elseif p then - return x +function advtrains.interlocking.make_ip_formspec_component(pos, x, y, w) + advtrains.interlocking.db.check_for_duplicate_ip(pos) + local pts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos) + if pts then + -- display marker + local ipos = minetest.string_to_pos(pts) + ipmarker(ipos, connid) + return table.concat { + F.S_label(x, y, "Influence point is set at @1.", string.format("%s/%s", pts, connid)), + F.S_button_exit(x, y+0.5, w/2-0.125, "ip_set", "Modify"), + F.S_button_exit(x+w/2+0.125, y+0.5, w/2-0.125, "ip_clear", "Clear"), + } else - return y + return table.concat { + F.S_label(x, y, "Influence point is not set."), + F.S_button_exit(x, y+0.5, w, "ip_set", "Set influence point"), + } end end -local function describe_supported_aspects(suppasp, isasp) - local t = {} - - local entries = {attrans("Use default value")} - local selid = 0 - local mainasps = suppasp.main - if type(mainasps) ~= "table" then - mainasps = {mainasps} - end - for idx, spv in ipairs(mainasps) do - if isasp and spv == rawget(isasp, "main") then - selid = idx - end - entries[idx+1] = describe_main_aspect(spv) - end - t.main = entries - t.main_current = selid+1 - t.main_string = tostring(isasp.main) - if t.main == nil then - t.main_string = "" +-- shows small formspec to set the signal influence point +-- only_notset: show only if it is not set yet (used by signal tcb assignment) +function advtrains.interlocking.show_ip_form(pos, pname, only_notset) + if not minetest.check_player_privs(pname, "interlocking") then + return end - - t.shunt = { - attrans("No shunting"), - attrans("Shunting allowed"), - attrans("Proceed as main"), + local ipform = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 0.5, 7) + local form = { + "formspec_version[4]", + "size[8,2.25]", + ipform, } - - t.shunt_current = dsel(suppasp.shunt, isasp.shunt, 2, 1) - if dsel(suppasp.proceed_as_main, isasp.proceed_as_main, t.shunt_current == 1) then - t.shunt_current = 3 - end - t.shunt_const = suppasp.shunt ~= nil - - if suppasp.group then - local gdef = advtrains.interlocking.aspect.get_group_definition(suppasp.group) - if gdef then - t.group = suppasp.group - t.groupdef = gdef - local entries = {} - local selid = 1 - for idx, name in ipairs(suppasp.name or {}) do - entries[idx] = gdef.aspects[name].label - if suppasp.group == isasp.group and name == isasp.name then - selid = idx - end - end - t.name = entries - t.name_current = selid - end + if not only_notset or not pts then + minetest.show_formspec(pname, "at_il_ipsaform_"..minetest.pos_to_string(pos), table.concat(form)) end - - return t end -advtrains.interlocking.describe_supported_aspects = describe_supported_aspects - -local function make_signal_aspect_selector(suppasp, purpose, isasp) - local t = describe_supported_aspects(suppasp, isasp) - local formmode = 1 - - local pos - if type(purpose) == "table" then - formmode = 2 - pos = purpose.pos +-- shows larger formspec to set the signal influence point, main aspect and distant signal pos +-- only_notset: show only if it is not set yet (used by signal tcb assignment) +function advtrains.interlocking.show_ip_sa_form(pos, pname) + if not minetest.check_player_privs(pname, "interlocking") then + return end - + local ipform = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 0.5, 7) + local ma, rpos = advtrains.interlocking.signal.get_aspect(pos) + local saform = F.S_button_exit(0, 2, 4, "sa_dst_assign", rpos and minetest.pos_to_string(rpos) or "") + .. F.S_button_exit(0, 3, 2, "sa_tmp_mainfree", "Main to free") .. F.S_button_exit(2, 3, 2, "sa_tmp_mainhalt", "Main to halt") local form = { "formspec_version[4]", - string.format("size[8,%f]", ({5.75, 10.75})[formmode]), - F.S_label(0.5, 0.5, "Select signal aspect"), + "size[8,4]", + ipform, + saform, } - local h0 = ({0, 1.5})[formmode] - form[#form+1] = F.S_label(0.5, 1.5+h0, "Main aspect") - form[#form+1] = F.S_label(0.5, 3+h0, "Shunt aspect") - form[#form+1] = F.S_button_exit(0.5, 4.5+h0, 7, "asp_save", "Save signal aspect") - if formmode == 1 then - form[#form+1] = F.label(0.5, 1, purpose) - form[#form+1] = F.field(0.5, 2, 7, "asp_mainval", "", t.main_string) - elseif formmode == 2 then - if t.group then - form[#form+1] = F.S_label(0.5, 1.5, "Signal aspect group: @1", t.groupdef.label) - form[#form+1] = F.dropdown(0.5, 2, 7, "asp_namesel", t.name, t.name_current, true) - else - form[#form+1] = F.S_label(0.5, 1.5, "This signal does not belong to a signal aspect group.") - form[#form+1] = F.S_label(0.5, 2, "You can not use a predefined signal aspect.") - end - form[#form+1] = F.S_label(0.5, 1, "Signal at @1", minetest.pos_to_string(pos)) - form[#form+1] = F.dropdown(0.5, 3.5, 7, "asp_mainsel", t.main, t.main_current, true) - form[#form+1] = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 7, 7) - form[#form+1] = advtrains.interlocking.make_short_dst_formspec_component(pos, 0.5, 8.5, 7) - end + minetest.show_formspec(pname, "at_il_ipsaform_"..minetest.pos_to_string(pos), table.concat(form)) +end - if formmode == 2 and t.shunt_const then - form[#form+1] = F.label(0.5, 3.5+h0, t.shunt[t.shunt_current]) - form[#form+1] = F.S_label(0.5, 4+h0, "The shunt aspect cannot be changed.") - else - form[#form+1] = F.dropdown(0.5, 3.5+h0, 7, "asp_shunt", t.shunt, t.shunt_current, true) +function advtrains.interlocking.handle_ip_sa_formspec_fields(pname, pos, fields) + if not (pos and minetest.check_player_privs(pname, {train_operator=true, interlocking=true})) then + return + end + if fields.ip_set then + advtrains.interlocking.init_ip_assign(pos, pname) + elseif fields.ip_clear then + advtrains.interlocking.db.clear_ip_by_signalpos(pos) + elseif fields.sa_dst_assign then + advtrains.interlocking.init_distant_assign(pos, pname) + elseif fields.sa_tmp_mainfree then + local ma, rpos = advtrains.interlocking.signal.get_aspect(pos) + advtrains.interlocking.signal.set_aspect(pos, "_free", -1, rpos) + elseif fields.sa_tmp_mainhalt then + local ma, rpos = advtrains.interlocking.signal.get_aspect(pos) + advtrains.interlocking.signal.set_aspect(pos, nil, nil, rpos) end - - return table.concat(form) end -function advtrains.interlocking.show_signal_aspect_selector(pname, p_suppasp, p_purpose, callback, isasp) - local suppasp = p_suppasp or { - main = {0, -1}, - dst = {false}, - shunt = false, - info = {}, - } - local purpose = p_purpose or "" +minetest.register_on_player_receive_fields(function(player, formname, fields) + local pname = player:get_player_name() + local pts = string.match(formname, "^at_il_ipsaform_([^_]+)$") local pos - if type(p_purpose) == "table" then - pos = p_purpose - purpose = {pname = pname, pos = pos} + if pts then + pos = minetest.string_to_pos(pts) end - - local form = make_signal_aspect_selector(suppasp, purpose, isasp) - if not form then - return + if pos then + advtrains.interlocking.handle_ip_sa_formspec_fields(pname, pos, fields) end +end) - local token = advtrains.random_id() - minetest.show_formspec(pname, "at_il_sigaspdia_"..token, form) - minetest.after(0, function() - players_aspsel[pname] = { - purpose = purpose, - suppasp = suppasp, - callback = callback, - token = token, - } - end) -end - -local function usebool(sup, val, free) - if sup == nil then - return val == free - else - return sup +-- inits the signal IP assignment process +function advtrains.interlocking.init_ip_assign(pos, pname) + if not minetest.check_player_privs(pname, "interlocking") then + minetest.chat_send_player(pname, "Insufficient privileges to use this!") + return end + --remove old IP + --advtrains.interlocking.db.clear_ip_by_signalpos(pos) + minetest.chat_send_player(pname, "Configuring Signal: Please look in train's driving direction and punch rail to set influence point.") + + players_assign_ip[pname] = pos end -local function get_aspect_from_formspec(suppasp, fields, psl) - local namei, group, name = tonumber(fields.asp_namesel), suppasp.group, nil - local gdef = advtrains.interlocking.aspect.get_group_definition(group) - if gdef then - local names = suppasp.name or {} - name = names[namei] or names[names] - else - group = nil - end - local maini = tonumber(fields.asp_mainsel) - local main = (suppasp.main or {})[(maini or 0)-1] - if not maini then - local mainval = fields.asp_mainval - if mainval == "-1" then - main = -1 - elseif mainval == "x" then - main = false - elseif string.match(mainval, "^%d+$") then - main = tonumber(mainval) - else - main = nil - end - elseif maini <= 1 then - main = nil - end - local shunti = tonumber(fields.asp_shunt) - local shunt = suppasp.shunt - if shunt == nil then - shunt = shunti == 2 - end - local proceed_as_main = suppasp.proceed_as_main - if proceed_as_main == nil then - proceed_as_main = shunti == 3 +-- inits the distant signal assignment process +function advtrains.interlocking.init_distant_assign(pos, pname) + if not minetest.check_player_privs(pname, "interlocking") then + minetest.chat_send_player(pname, "Insufficient privileges to use this!") + return end - return advtrains.interlocking.aspect { - main = main, - shunt = shunt, - proceed_as_main = proceed_as_main, - info = {}, - name = name, - group = group, - } + minetest.chat_send_player(pname, "Set distant signal: Punch the main signal to assign!") + + players_assign_distant[pname] = pos end -minetest.register_on_player_receive_fields(function(player, formname, fields) +minetest.register_on_punchnode(function(pos, node, player, pointed_thing) local pname = player:get_player_name() - local psl = players_aspsel[pname] - if psl then - if formname == "at_il_sigaspdia_"..psl.token then - local suppasp = psl.suppasp - if fields.asp_save then - local asp - asp = get_aspect_from_formspec(suppasp, fields, psl) - if asp then - psl.callback(pname, asp) + if not minetest.check_player_privs(pname, "interlocking") then + return + end + -- IP assignment + local signalpos = players_assign_ip[pname] + if signalpos then + if vector.distance(pos, signalpos)<=50 then + local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes) + if node_ok and #conns == 2 then + + local yaw = player:get_look_horizontal() + local plconnid = advtrains.yawToClosestConn(yaw, conns) + + -- add assignment if not already present. + local pts = advtrains.roundfloorpts(pos) + if not advtrains.interlocking.db.get_ip_signal_asp(pts, plconnid) then + advtrains.interlocking.db.set_ip_signal(pts, plconnid, signalpos) + ipmarker(pos, plconnid) + minetest.chat_send_player(pname, "Configuring Signal: Successfully set influence point") + else + minetest.chat_send_player(pname, "Configuring Signal: Influence point of another signal is already present!") end - end - if type(psl.purpose) == "table" then - local pos = psl.purpose.pos - advtrains.interlocking.handle_ip_formspec_fields(pname, pos, fields) - advtrains.interlocking.handle_dst_formspec_fields(pname, pos, fields) + else + minetest.chat_send_player(pname, "Configuring Signal: This is not a normal two-connection rail! Aborted.") end else - players_aspsel[pname] = nil + minetest.chat_send_player(pname, "Configuring Signal: Node is too far away. Aborted.") end + players_assign_ip[pname] = nil + end + -- DST assignment + signalpos = players_assign_distant[pname] + if signalpos then + -- get current mainaspect + local ma, rpos = advtrains.interlocking.signal.get_aspect(signalpos) + -- if punched pos is valid signal then set it as the new remote, otherwise nil + local nrpos + if advtrains.interlocking.signal.get_signal_cap_level(pos) > 1 then nrpos = pos end + advtrains.interlocking.signal.set_aspect(signalpos, ma.name, ma.speed, nrpos) + players_assign_distant[pname] = nil end end) + diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua index bfec648..caf22e3 100755 --- a/advtrains_interlocking/tcb_ts_ui.lua +++ b/advtrains_interlocking/tcb_ts_ui.lua @@ -186,7 +186,7 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) local is_signal = minetest.get_item_group(node.name, "advtrains_signal") >= 2 if is_signal then local ndef = minetest.registered_nodes[node.name] - if ndef and ndef.advtrains and ndef.advtrains.set_aspect then + if ndef and ndef.advtrains and ndef.advtrains.apply_aspect then local tcbs = ildb.get_tcbs(sigd) if tcbs then tcbs.signal = pos @@ -464,7 +464,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) ts.route = nil for _, sigd in ipairs(ts.tc_breaks) do local tcbs = ildb.get_tcbs(sigd) - advtrains.interlocking.update_signal_aspect(tcbs) + advtrains.interlocking.signal.update_route_aspect(tcbs) end minetest.chat_send_player(pname, "Reset track section "..ts_id.."!") end @@ -642,7 +642,6 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle form = form.."button[0.5,8;2.5,1;newroute;New Route]" form = form.."button[ 3,8;2.5,1;unassign;Unassign Signal]" form = form..string.format("checkbox[0.5,8.75;ars;Automatic routesetting;%s]", not tcbs.ars_disabled) - form = form..string.format("checkbox[0.5,9.25;dst;Distant signalling;%s]", not tcbs.nodst) end elseif sigd_equal(tcbs.route_origin, sigd) then -- something has gone wrong: tcbs.routeset should have been set... @@ -660,7 +659,7 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, calle -- always a good idea to update the signal aspect if not called_from_form_update then -- FIX prevent a callback loop - advtrains.interlocking.update_signal_aspect(tcbs) + advtrains.interlocking.signal.update_route_aspect(tcbs) end end @@ -769,10 +768,6 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if fields.ars then tcbs.ars_disabled = not minetest.is_yes(fields.ars) end - - if fields.dst then - tcbs.nodst = not minetest.is_yes(fields.dst) - end if fields.auto then tcbs.route_auto = true -- cgit v1.2.3