Connect the ropes, start on making the UI work

This commit is contained in:
orwell 2024-05-23 00:58:24 +02:00
parent 852da6bcae
commit 6fd845baec
12 changed files with 244 additions and 833 deletions

View File

@ -57,7 +57,7 @@ function advtrains.setstate(parpos, newstate, pnode)
return false, "train_here"
end
if advtrains.interlocking and advtrains.interlocking.route.has_route_lock(minetest.encode_pos(pos)) then
if advtrains.interlocking and advtrains.interlocking.route.has_route_lock(advtrains.encode_pos(pos)) then
return false, "route_lock_here"
end

View File

@ -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)

View File

@ -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,
}

View File

@ -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")

View File

@ -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]"

View File

@ -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

View File

@ -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)
advtrains.interlocking.show_signal_form(pos, node, pname, control.aux1)
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
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

View File

@ -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

View File

@ -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))
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_shunt_aspect(shunt)
if shunt then
return attrans("Shunting allowed")
else
return attrans("No shunting")
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
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
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
else
return y
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 = ""
end
t.shunt = {
attrans("No shunting"),
attrans("Shunting allowed"),
attrans("Proceed as main"),
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 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"),
}
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
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
end
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"),
}
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
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)
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 ""
local pos
if type(p_purpose) == "table" then
pos = p_purpose
purpose = {pname = pname, pos = pos}
end
local form = make_signal_aspect_selector(suppasp, purpose, isasp)
if not form then
-- 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
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,
local ipform = advtrains.interlocking.make_ip_formspec_component(pos, 0.5, 0.5, 7)
local form = {
"formspec_version[4]",
"size[8,2.25]",
ipform,
}
end)
end
local function usebool(sup, val, free)
if sup == nil then
return val == free
else
return sup
if not only_notset or not pts then
minetest.show_formspec(pname, "at_il_ipsaform_"..minetest.pos_to_string(pos), table.concat(form))
end
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
-- 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 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
end
return advtrains.interlocking.aspect {
main = main,
shunt = shunt,
proceed_as_main = proceed_as_main,
info = {},
name = name,
group = group,
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 "<distant signal>")
.. F.S_button_exit(0, 3, 2, "sa_tmp_mainfree", "Main to free") .. F.S_button_exit(2, 3, 2, "sa_tmp_mainhalt", "Main to halt")
local form = {
"formspec_version[4]",
"size[8,4]",
ipform,
saform,
}
minetest.show_formspec(pname, "at_il_ipsaform_"..minetest.pos_to_string(pos), table.concat(form))
end
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
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.asp_save then
local asp
asp = get_aspect_from_formspec(suppasp, fields, psl)
if asp then
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
local pts = string.match(formname, "^at_il_ipsaform_([^_]+)$")
local pos
if pts then
pos = minetest.string_to_pos(pts)
end
if pos then
advtrains.interlocking.handle_ip_sa_formspec_fields(pname, pos, fields)
end
end)
-- 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
-- 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
minetest.chat_send_player(pname, "Set distant signal: Punch the main signal to assign!")
players_assign_distant[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
-- 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)

View File

@ -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
@ -770,10 +769,6 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
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
end

View File

@ -374,10 +374,6 @@ for sigtype, sigdata in pairs {
--sigdefs["rpt_"..sigtype] = process_signal(sigtype, sigdata, true)
end
for k in pairs(sigdefs) do
advtrains.trackplacer.register_tracktype("advtrains_signals_japan:"..k)
end
for _, rtab in ipairs {
{rot = "0", ici = true},
{rot = "30"},
@ -455,7 +451,7 @@ for _, rtab in ipairs {
can_dig = advtrains.interlocking.signal_can_dig,
after_dig_node = advtrains.interlocking.signal_after_dig,
})
advtrains.trackplacer.add_worked("advtrains_signals_japan:"..sigtype, asp, "_"..rot)
--advtrains.trackplacer.add_worked("advtrains_signals_japan:"..sigtype, asp, "_"..rot)
end
end
end

View File

@ -50,15 +50,18 @@ end
local applyaspectf_main = function(rot)
return function(pos, node, main_aspect, dst_aspect, dst_aspect_info)
if not main_aspect then
-- halt aspect, set red and don't do anything further
advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_danger_"..rot, param2 = node.param2})
setzs3v(pos, nil, rot)
return
end
-- set zs3 signal to show speed according to main_aspect
setzs3(pos, main_aspect.zs3, rot)
-- select appropriate lamps based on mainaspect and dst
if main_aspect.shunt then
advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_shunt_"..rot, param2 = node.param2})
setzs3v(pos, nil, rot)
elseif main_aspect.halt then
advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_danger_"..rot, param2 = node.param2})
setzs3v(pos, nil, rot)
else
if not dst_aspect_info
or not dst_aspect_info.main
@ -128,7 +131,8 @@ local mainaspects_main = {
local applyaspectf_ra = function(rot)
-- we get here the full main_aspect table
return function(pos, node, main_aspect, dst_aspect, dst_aspect_info)
if main_aspect.shunt then
if main_aspect then
-- any main aspect is fine, there's only one anyway
advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:ra_shuntd_"..rot, param2 = node.param2})
else
advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:ra_danger_"..rot, param2 = node.param2})
@ -144,11 +148,6 @@ local mainaspects_shunt = {
description = "Shunt",
shunt = true,
},
{
name = "halt",
description = "Halt",
halt = true,
},
}
for _, rtab in ipairs({
@ -225,9 +224,9 @@ for _, rtab in ipairs({
apply_aspect = applyaspectf_main(rot),
get_aspect_info = afunc,
},
on_rightclick = advtrains.interlocking.signal_rc_handler,
can_dig = advtrains.interlocking.signal_can_dig,
after_dig_node = advtrains.interlocking.signal_after_dig,
on_rightclick = advtrains.interlocking.signal.on_rightclick,
can_dig = advtrains.interlocking.signal.can_dig,
after_dig_node = advtrains.interlocking.signal.after_dig,
})
-- rotatable by trackworker
--TODO add rotation using trackworker