aboutsummaryrefslogtreecommitdiff
path: root/advtrains_interlocking/signal_api.lua
diff options
context:
space:
mode:
Diffstat (limited to 'advtrains_interlocking/signal_api.lua')
-rw-r--r--advtrains_interlocking/signal_api.lua346
1 files changed, 120 insertions, 226 deletions
diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua
index d27a045..65fc787 100644
--- a/advtrains_interlocking/signal_api.lua
+++ b/advtrains_interlocking/signal_api.lua
@@ -5,11 +5,15 @@ local F = advtrains.formspec
local signal = {}
signal.MASP_HALT = {
- name = "halt",
- description = "HALT",
+ name = "_halt",
halt = true,
}
+signal.MASP_DEFAULT = {
+ name = "_default",
+ default = true,
+}
+
signal.ASPI_HALT = {
main = 0,
shunt = false,
@@ -38,9 +42,9 @@ b) the distant signal's aspect group name & aspect table
One concrete combination of lights/shapes that a signal signal shows. Handling these is at the discretion of
the signal mod defining the signal, and they are typically combinations of main aspect and distant aspect
Example:
-- A Ks signal has the aspect_group="proceed_12" set for a route
-- The signal at the end of the route shows aspect_group="proceed_8", advtrains also passes on that this means {main=8, shunt=false}
-- The ndef.advtrains.apply_aspect(pos, asp_group, dst_aspgrp, dst_aspinfo) determines that the signal should now show
+- A Ks signal has the main_aspect="proceed_12" set for a route
+- The signal at the end of the route shows main_aspect="proceed_8", advtrains also passes on that this means {main=8, shunt=false}
+- The ndef.afunction(pos, node, main_aspect, rem_aspect, rem_aspinfo) determines that the signal should now show
blinking green with main indicator 12 and dst indicator 8, and sets the nodes accordingly.
This function can now return the Aspect Info table, which will be cached by advtrains until the aspect changes again
and will be used when a train approaches the signal. If nil is returned, then the aspect will be queried next time
@@ -50,11 +54,16 @@ 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 the special main aspect { name = "_halt", halt = true }. It usually means that the signal is not assigned to anything particular,
+and it should cause the signal to show its most restrictive aspect. Typically it is a halt aspect, but e.g. for distant-only
signals this would be "expect stop".
+A special case occurs for pure distant signals: Such signals must set apply_aspect, but must not set main_aspects. Behavior is as follows:
+- Signal is uninitialized, distant signal is not assigned to a main signal, or no route is set: main_aspect == { name = "_halt", halt = true } and rem_aspect == nil
+- A remote main signal is assigned (either by user or by route): main_aspect is always { name = "_default" } and rem_aspect / rem_aspinfo give the correct information
+
+Main aspect names starting with underscore (e.g. "_default") are reserved and must not be used!
+
== Aspect Info ==
The actual signal aspect in the already-known format. This is what the trains use to determine halt/proceed and speed.
asp = {
@@ -73,26 +82,26 @@ ndef.advtrains = {
}
-- This list is mainly for the selection dialog. Order of entries determines list order in the dropdown.
-- Some fields have special meaning:
- -- name: A unique key to identify the main aspect. Only this key is saved, but APIs always receive the whole table
+ -- name: A unique key to identify the main aspect. Might be required by some code.
-- description: Text shown in UI dropdown
- -- speed: a number. When present, a speed field is shown in the UI next to the dropdown (prefilled with the value).
- -- When user selects a different speed there, this different speed replaces the value in the table whenever the main_aspect is applied.
-- Node can set any other fields at its discretion. They are not touched.
- -- Note: On first call advtrains automatically inserts into the ndef.advtrains table a main_aspects_lookup hashtable
- -- Note: Pure distant signals (that cannot show halt) should NOT have a main_aspects table
+ -- Note: Pure distant signals (that cannot show halt) should NOT have a main_aspects table.
+ -- For these signals no main aspect selection UI is shown and they cannot be startpoint of a route
apply_aspect = function(pos, node, main_aspect, rem_aspect, rem_aspinfo)
-- set the node to show the desired aspect
-- called by advtrains when this signal's aspect group or the remote signal's aspect changes
+ -- main_aspect is never nil, but can be one of the special aspects { halt = true } or { default = true }
-- MAY return the aspect_info. If it returns nil then get_aspect_info will be queried at a later point.
get_aspect_info(pos, main_aspect)
-- Returns the aspect info table (main, shunt, dst etc.)
distant_support = true or false
-- If true, signal is considered in distant signalling. If false or nil, rem_aspect and rem_aspinfo are never set.
- route_role = one of "main", "shunt", "distant", "distant_repeater", "end"
+ route_role = one of "main", "main_distant", "shunt", "distant", "distant_repeater", "end"
-- Determines how the signal behaves when routes are set. Only in effect when signal is assigned to a TCB.
-- main: The signal is a possible endpoint for a train move route. Distant signals before it refer to it.
-- shunt: The signal is a possible endpoint for a shunt move route. Ignored for distant signals.
-- distant, distant_repeater: When route is set, signal is always assigned its first main aspect. The next signal with role="main" is set as the remote signal. (currently no further distinction)
+ -- main_distant: Combination of main and distant - like "main", but additionally gets assigned to the next main like a "distant"
-- end: like main, but signifies that it marks an end of track and trains cannot continue further. (currently no practical implications above main)
}
@@ -120,7 +129,9 @@ Signals that are possible start and end points for a route must satisfy:
-- Database
-- Signal Aspect store
-- Stores for each signal the main aspect and other info, like the assigned remote signal
--- [signal encodePos] = { name = "proceed", [speed = 12], [remote = encodedPos] }
+-- [signal encodePos] = { main = <table or string>, [remote = encodedPos] }
+-- main is a string: "named aspect" is looked up in the main_aspects table of the ndef
+-- main is a table: this table directly is the main aspect (used for advanced signals with additional lights/indicators)
signal.aspects = {}
-- Distant signal notification. Records for each signal the distant signals that refer to it
@@ -152,9 +163,14 @@ 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)
+-- main_asp: either a string (==name in ndef.advtrains.main_aspects) or the main aspect table directly (for advanced signals)
+function signal.set_aspect(pos, main_asp, rem_pos, skip_dst_notify)
+ -- safeguard for the two integrated aspects (these two must be passed as string key)
+ if type(main_asp)=="table" and (main_asp.name=="_default" or main_asp.name=="_halt") then
+ error("MASP_HALT and MASP_DEFAULT must be passed via string keys _halt or _default, not as tables!")
+ end
local main_pts = advtrains.encode_pos(pos)
local old_tbl = signal.aspects[main_pts]
local old_remote = old_tbl and old_tbl.remote
@@ -166,7 +182,7 @@ function signal.set_aspect(pos, main_asp_name, main_asp_speed, rem_pos, skip_dst
signal.distant_refs[old_remote][main_pts] = nil
end
- signal.aspects[main_pts] = { name = main_asp_name, speed = main_asp_speed, remote = new_remote }
+ signal.aspects[main_pts] = { main = main_asp, remote = new_remote }
-- apply aspect on main signal, this also checks new_remote
signal.reapply_aspect(main_pts)
@@ -196,6 +212,21 @@ function signal.clear_aspect(pos, skip_dst_notify)
end
end
+-- Clear any info about aspects from this signal, without resetting/reapplying the aspect.
+-- Supposed to be used for legacy on-off signals when the on-off toggle is used
+function signal.unregister_aspect(pos)
+ local main_pts = advtrains.encode_pos(pos)
+ local old_tbl = signal.aspects[main_pts]
+ local old_remote = old_tbl and old_tbl.remote
+
+ -- unregister from old remote
+ if old_remote then
+ signal.distant_refs[old_remote][main_pts] = nil
+ end
+
+ signal.aspects[main_pts] = nil
+end
+
-- Notify distant signals of main_pts of a change in the aspect of this signal
--
function signal.notify_distants_of(main_pts, limit)
@@ -252,44 +283,44 @@ function signal.get_aspect(pos)
end
local function cache_mainaspects(ndefat)
- ndefat.main_aspects_lookup = {
- -- always define halt aspect
- halt = signal.MASP_HALT
- }
- for _,ma in ipairs(ndefat.main_aspects) do
+ ndefat.main_aspects_lookup = {}
+ for _,ma in ipairs(ndefat.main_aspects) do
ndefat.main_aspects_lookup[ma.name] = ma
- end
+ end
+ ndefat.main_aspects_lookup[signal.MASP_HALT.name] = signal.MASP_HALT.name -- halt is always defined
+ ndefat.main_aspects_lookup[signal.MASP_DEFAULT.name] = ndefat.main_aspects[1] -- default is the first one
end
+
+-- gets the main aspect. resolves named aspects to aspect table on demand
function signal.get_aspect_internal(pos, aspt)
+ -- look up node and nodedef
+ local node = advtrains.ndb.get_node_or_nil(pos)
+ local ndef = node and minetest.registered_nodes[node.name]
if not aspt then
-- oh, no main aspect, nevermind
- return nil, aspt.remote, nil
+ return signal.MASP_HALT, nil, node, ndef
end
- atdebug("get_aspect_internal",pos,aspt)
- -- look aspect in nodedef
- local node = advtrains.ndb.get_node_or_nil(pos)
- local ndef = node and minetest.registered_nodes[node.name]
- local ndefat = ndef and ndef.advtrains
- -- only if signal defines main aspect and its set in aspt
- if ndefat and ndefat.main_aspects and aspt.name then
- if not ndefat.main_aspects_lookup then
- cache_mainaspects(ndefat)
- end
- local masp = ndefat.main_aspects_lookup[aspt.name]
- if not masp then
- atwarn(pos,"invalid main aspect",aspt.name,"valid are",ndefat.main_aspects_lookup)
- return nil, aspt.remote, node, ndef
- end
- -- if speed, then apply speed
- if masp.speed and aspt.speed then
- masp = table.copy(masp)
- masp.speed = aspt.speed
+ local ndefat = ndef.advtrains or {}
+ local masp = aspt.main or signal.MASP_HALT
+
+ if type(masp) == "string" then
+ if masp=="_halt" then
+ masp = signal.MASP_HALT
+ elseif masp=="_default" and not ndefat.main_aspects then
+ -- case is fine, distant only signal
+ masp = signal.MASP_DEFAULT
+ else
+ assert(ndefat.main_aspects, "With named aspects, node needs advtrains.main_aspects table!")
+ -- resolve the main aspect from the mainaspects table
+ if not ndefat.main_aspects_lookup then
+ cache_mainaspects(ndefat)
+ end
+ masp = ndefat.main_aspects_lookup[aspt.main] or signal.MASP_DEFAULT
end
- return masp, aspt.remote, node, ndef
end
- -- invalid node or no main aspect, return nil for masp
- return nil, aspt.remote, node, ndef
+ -- return whatever the main aspect is
+ return masp, aspt.remote, node, ndef
end
-- For the signal at pos, get the "aspect info" table. This contains the speed signalling information at this location
@@ -299,7 +330,17 @@ function signal.get_aspect_info(pos)
local masp, remote, node, ndef = signal.get_aspect_internal(pos, aspt)
-- call into ndef
if ndef.advtrains and ndef.advtrains.get_aspect_info then
- return ndef.advtrains.get_aspect_info(pos, masp)
+ local ai = ndef.advtrains.get_aspect_info
+ if type(ai)=="function" then
+ ai = ai(pos, masp)
+ end
+ if type(ai)=="table" then
+ atdebug(pos,"aspectinfo",ai)
+ return ai
+ else
+ error("For node "..node.name..": ndef.advtrains.get_aspect_info must be function or table")
+ end
+
end
end
@@ -314,12 +355,8 @@ function signal.reapply_aspect(pts)
-- get aspt
local aspt = signal.aspects[pts]
atdebug("reapply_aspect",advtrains.decode_pos(pts),"aspt",aspt)
- if not aspt then
- return -- oop, nothing to do
- end
- -- resolve mainaspect table by name
local pos = advtrains.decode_pos(pts)
- -- note: masp may be nil, when aspt.name was nil. Valid case for distant-only signals
+ -- resolve mainaspect table by name
local masp, remote, node, ndef = signal.get_aspect_internal(pos, aspt)
-- if we have remote, resolve remote
local rem_masp, rem_aspi
@@ -331,13 +368,11 @@ function signal.reapply_aspect(pts)
signal.distant_refs[remote][pts] = true
local rem_aspt = signal.aspects[remote]
atdebug("resolving remote",advtrains.decode_pos(remote),"aspt",rem_aspt)
- if rem_aspt and rem_aspt.name then
- local rem_pos = advtrains.decode_pos(remote)
- rem_masp, _, _, rem_ndef = signal.get_aspect_internal(rem_pos, rem_aspt)
- if rem_masp then
- if rem_ndef.advtrains and rem_ndef.advtrains.get_aspect_info then
- rem_aspi = rem_ndef.advtrains.get_aspect_info(rem_pos, rem_masp)
- end
+ local rem_pos = advtrains.decode_pos(remote)
+ rem_masp, _, _, rem_ndef = signal.get_aspect_internal(rem_pos, rem_aspt)
+ if rem_masp then
+ if rem_ndef.advtrains and rem_ndef.advtrains.get_aspect_info then
+ rem_aspi = rem_ndef.advtrains.get_aspect_info(rem_pos, rem_masp)
end
end
end
@@ -354,11 +389,32 @@ 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)
+ local asp = tcbs.route_aspect or "_halt"
+ local rem = tcbs.route_remote
+ signal.set_aspect(tcbs.signal, asp, rem, 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 +422,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 +430,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