aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--advtrains_interlocking/README.md96
-rw-r--r--advtrains_interlocking/distant.lua47
-rw-r--r--advtrains_interlocking/init.lua4
-rw-r--r--advtrains_interlocking/signal_api.lua165
-rw-r--r--advtrains_interlocking/signal_aspect_accessors.lua52
-rw-r--r--advtrains_interlocking/signal_aspects.lua49
6 files changed, 237 insertions, 176 deletions
diff --git a/advtrains_interlocking/README.md b/advtrains_interlocking/README.md
new file mode 100644
index 0000000..636ad67
--- /dev/null
+++ b/advtrains_interlocking/README.md
@@ -0,0 +1,96 @@
+# Interlocking for Advtrains
+
+The `advtrains_interlocking` mod provides various interlocking and signaling features for Advtrains.
+
+## Signal types
+There are two types of signals in Advtrains:
+
+* Type 1 (speed signals): These signals only give speed information.
+* Type 2 (route signals): These signals mainly provide route information, but sometimes also provide speed information.
+
+## Signal aspect tables
+
+Signal aspects are represented using tables with the following (optional) fields:
+
+* `main`: The main signal aspect. It provides information on the permitted speed after passing the signal.
+* `dst`: The distant signal aspect. It provides information on the permitted speed after passing the next signal.
+* `shunt`: Whether the train may proceed in shunt mode and, if the main aspect is danger, proceed in shunt mode.
+* `proceed_as_main`: Whether the train should exit shunt mode when proceeding.
+* `type2group`: The type 2 group of the signal.
+* `type2name`: The type 2 signal aspect name.
+
+The `main` and `dst` fields may be:
+
+* An positive number indicating the permitted speed,
+* The number 0, indicating that the train should expect to stop at the current signal (or, for the `dst` field, the next signal),
+* The number -1, indicating that the train can proceed (or, for the `dst` field, expect to proceed) at maximum speed, or
+* The constant `false` or `nil`, indicating no change to the speed restriction.
+
+### Node definitions
+
+Signals should belong the following node groups:
+
+* `advtrains_signal`: `1` for static signals, `2` for signals with variable aspects.
+* `save_in_at_nodedb`: This should be set to `1` to make sure that Advtrains always has access to the signal.
+* `not_blocking_trains`: Setting this to `1` prevents trains from colliding with the signal. Setting this is not necessary, but recommended.
+
+The node definition should contain an `advtrains` field.
+
+The `advtrains` field of the node definition should contain a `supported_aspects` table for signals with variable aspects.
+
+For type 1 signals, the `supported_aspects` table should contain the following fields:
+
+* `main`: A list of values supported for the main aspect.
+* `dst`: A list of values supported for the distant aspect.
+* `shunt`: The value for the `shunt` field of the signal aspect or `nil` if the value is variable.
+* `proceed_as_main`: The value for the `proceed_as_main` field of the signal aspect.
+
+For type 2 signals, the `supported_aspects` table should contain the following fields:
+
+* `type`: The numeric constant `2`.
+* `group`: The type 2 signal group.
+* `dst_shift`: The phase shift for distant/repeater signals. This field should not be set for main signals.
+
+The `advtrains` field of the node definition should contain a `get_aspect` function. This function is given the position of the signal and the node at the position. It should return the signal aspect table or, in the case of type 2 signals, the name of the signal aspect.
+
+For signals with variable aspects, a corresponding `set_aspect` function should also be defined. This function is given the position of the signal, the node at the position, and the new aspect (or, in the case of type 2 signals, the name of the new signal aspect). For type 1 signals, the new aspect is not guranteed to be supported by the signal itself.
+
+Signals should also have the following callbacks set:
+
+* `on_rightclick` should be set to `advtrains.interlocking.signal_rc_handler`
+* `can_dig` should be set to `advtrains.interlocking.signal_can_dig`
+* `after_dig_node` should be set to `advtrains.interlocking.signal_after_dig`
+
+Alternatively, custom callbacks should call the respective functions.
+
+## Type 2 signal groups
+
+Type 2 signals belong to signal gruops, which are registered using `advtrains.interlocking.aspects.register_type2`.
+
+Signal group definitions include the following fields:
+
+* `name`: The internal name of the signal group. It is recommended to use the mod name as a prefix to avoid name collisions.
+* `label`: The description of the signal group.
+* `main`: A list of signal aspects, from the least restrictive (i.e. proceed) to the most restrictive (i.e. danger).
+
+Each aspect in the signal group definition table should contain the following fields:
+
+* `name`: The internal name of the signal aspect.
+* `label`: The description of the signal aspect.
+* `main`, `shunt`, `proceed_as_main`: The fields corresponding to the ones in signal aspect tables.
+
+Type 2 signal aspects are then referred to with the aspect names within the group.
+
+## Notes
+
+It is allowed to provide other methods of setting the signal aspect. However:
+
+* These changes are ignored by the routesetting system.
+* Please call `advtrains.interlocking.signal_readjust_aspect` after the signal aspect has changed.
+
+## Examples
+An example of type 1 signals can be found in `advtrains_signals_ks`, which provides a subset of German signals.
+
+An example of type 2 signals can be found in `advtrains_signals_japan`, which provides a subset of Japanese signals.
+
+The mods mentioned above are also used for demonstation purposes and can also be used for testing.
diff --git a/advtrains_interlocking/distant.lua b/advtrains_interlocking/distant.lua
index f62ca36..22f1c9d 100644
--- a/advtrains_interlocking/distant.lua
+++ b/advtrains_interlocking/distant.lua
@@ -1,3 +1,8 @@
+--- Distant signaling.
+-- This module implements a database backend for distant signal assignments.
+-- The actual modifications to signal aspects are still done by signal aspect accessors.
+-- @module advtrains.interlocking.distant
+
local db_distant = {}
local db_distant_of = {}
@@ -5,6 +10,9 @@ local A = advtrains.interlocking.aspects
local pts = advtrains.encode_pos
local stp = advtrains.decode_pos
+--- Replace the distant signal assignment database.
+-- @function load
+-- @param db The new database to load.
local function db_load(x)
if type(x) ~= "table" then
return
@@ -13,6 +21,9 @@ local function db_load(x)
db_distant_of = x.distant_of
end
+--- Retrieve the current distant signal assignment database.
+-- @function save
+-- @return The current database.
local function db_save()
return {
distant = db_distant,
@@ -22,6 +33,10 @@ end
local update_signal, update_main, update_dst
+--- Unassign a distant signal.
+-- @function unassign_dst
+-- @param dst The position of the distant signal.
+-- @param[opt=false] force Whether to skip callbacks.
local function unassign_dst(dst, force)
local pts_dst = pts(dst)
local main = db_distant_of[pts_dst]
@@ -38,6 +53,10 @@ local function unassign_dst(dst, force)
end
end
+--- Unassign a main signal.
+-- @function unassign_main
+-- @param main The position of the main signal.
+-- @param[opt=false] force Whether to skip callbacks.
local function unassign_main(main, force)
local pts_main = pts(main)
local t = db_distant[pts_main]
@@ -57,11 +76,21 @@ local function unassign_main(main, force)
db_distant[pts_main] = nil
end
+--- Remove all (main and distant) signal assignments from a signal.
+-- @function unassign_all
+-- @param pos The position of the signal.
+-- @param[opt=false] force Whether to skip callbacks.
local function unassign_all(pos, force)
unassign_main(pos)
unassign_dst(pos, force)
end
+--- Assign a distant signal to a main signal.
+-- @function assign
+-- @param main The position of the main signal.
+-- @param dst The position of the distant signal.
+-- @param[opt="manual"] by The method of assignment.
+-- @param[opt=false] skip_update Whether to skip callbacks.
local function assign(main, dst, by, skip_update)
local pts_main = pts(main)
local pts_dst = pts(dst)
@@ -87,11 +116,20 @@ local function pre_occupy(dst, by)
db_distant_of[pts_dst] = {nil, by}
end
+--- Get the distant signals assigned to a main signal.
+-- @function get_distant
+-- @param main The position of the main signal.
+-- @treturn {[pos]=by,...} A table of distant signals, with the positions encoded using `advtrains.encode_pos`.
local function get_distant(main)
local pts_main = pts(main)
return db_distant[pts_main] or {}
end
+--- Get the main signal assigned the a distant signal.
+-- @function get_main
+-- @param dst The position of the distant signal.
+-- @return The position of the main signal.
+-- @return The method of assignment.
local function get_main(dst)
local pts_dst = pts(dst)
local main = db_distant_of[pts_dst]
@@ -105,6 +143,9 @@ local function get_main(dst)
end
end
+--- Update all distant signals assigned to a main signal.
+-- @function update_main
+-- @param main The position of the main signal.
update_main = function(main)
local pts_main = pts(main)
local t = get_distant(main)
@@ -114,10 +155,16 @@ update_main = function(main)
end
end
+--- Update the aspect of a distant signal.
+-- @function update_dst
+-- @param dst The position of the distant signal.
update_dst = function(dst)
advtrains.interlocking.signal_readjust_aspect(dst)
end
+--- Update the aspect of a combined (main and distant) signal and all distant signals assigned to it.
+-- @function update_signal
+-- @param pos The position of the signal.
update_signal = function(pos)
update_main(pos)
update_dst(pos)
diff --git a/advtrains_interlocking/init.lua b/advtrains_interlocking/init.lua
index 908d998..1a8ef07 100644
--- a/advtrains_interlocking/init.lua
+++ b/advtrains_interlocking/init.lua
@@ -1,5 +1,5 @@
--- Advtrains interlocking system
--- See database.lua for a detailed explanation
+--- Advtrains interlocking system.
+-- @module advtrains.interlocking
advtrains.interlocking = {}
diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua
index e615692..54202f0 100644
--- a/advtrains_interlocking/signal_api.lua
+++ b/advtrains_interlocking/signal_api.lua
@@ -1,170 +1,5 @@
-- Signal API implementation
-
---[[
-Signal aspect table:
-Note: All speeds are measured in m/s, aka the number of + signs in the HUD.
-asp = {
- main = <int speed>,
- -- Main signal aspect, tells state and permitted speed of next section
- -- 0 = section is blocked
- -- >0 = section is free, speed limit is this value
- -- -1 = section is free, maximum speed permitted
- -- false/nil = Signal doesn't provide main signal information, retain current speed limit.
- shunt = <boolean>,
- -- Whether train may proceed as shunt move, on sight
- -- main aspect takes precedence over this
- -- When main==0, train switches to shunt move and is restricted to speed 6
- proceed_as_main = <boolean>,
- -- If an approaching train is a shunt move and 'shunt' is false,
- -- the train may proceed as a train move under the "main" aspect
- -- if the main aspect permits it (i.e. main!=0)
- -- If this is not set, shunt moves are NOT allowed to switch to
- -- a train move, and must stop even if "main" would permit passing.
- -- This is intended to be used for "Halt for shunt moves" signs.
-
- dst = <int speed>,
- -- Distant signal aspect, tells state and permitted speed of the section after next section
- -- The character of these information is purely informational
- -- At this time, this field is not actively used
- -- 0 = section is blocked
- -- >0 = section is free, speed limit is this value
- -- -1 = section is free, maximum speed permitted
- -- false/nil = Signal doesn't provide distant signal information.
-
- -- the character of call_on and dead_end is purely informative
- call_on = <boolean>, -- Call-on route, expect train in track ahead (not implemented yet)
- dead_end = <boolean>, -- Route ends on a dead end (e.g. bumper) (not implemented yet)
-
- w_speed = <integer>,
- -- "Warning speed restriction". Supposed for short-term speed
- -- restrictions which always override any other restrictions
- -- imposed by "speed" fields, until lifted by a value of -1
- -- (Example: german Langsamfahrstellen-Signale)
- }
-}
-
-== How signals actually work in here ==
-Each signal (in the advtrains universe) is some node that has at least the
-following things:
-- An "influence point" that is set somewhere on a rail
-- An aspect which trains that pass the "influence point" have to obey
-
-There can be static and dynamic signals. Static signals are, roughly
-spoken, signs, while dynamic signals are "real" signals which can display
-different things.
-
-The node definition of a signal node should contain those fields:
-groups = {
- advtrains_signal = 2,
- save_in_at_nodedb = 1,
-}
-advtrains = {
- set_aspect = function(pos, node, asp)
- -- This function gets called whenever the signal should display
- -- a new or changed signal aspect. It is not required that
- -- the signal actually displays the exact same aspect, since
- -- some signals can not do this by design. However, it must
- -- display an aspect that is at least as restrictive as the passed
- -- aspect as far as it is capable of doing so.
- -- Examples:
- -- - pure shunt signals can not display a "main" aspect
- -- and have no effect on train moves, so they will only ever
- -- honor the shunt.free field for their aspect.
- -- - the german Hl system can only signal speeds of 40, 60
- -- and 100 km/h, a speed of 80km/h should then be signalled
- -- as 60 km/h instead.
- -- In turn, it is not guaranteed that the aspect will fulfill the
- -- criteria put down in supported_aspects.
- -- If set_aspect is present, supported_aspects should also be declared.
-
- -- The aspect passed in here can always be queried using the
- -- advtrains.interlocking.signal_get_supposed_aspect(pos) function.
- -- It is always DANGER when the signal is not used as route signal.
-
- -- For static signals, this function should be completely omitted
- -- If this function is omitted, it won't be possible to use
- -- route setting on this signal.
- end,
- supported_aspects = {
- -- A table which tells which different types of aspects this signal
- -- is able to display. It is used to construct the "aspect editing"
- -- formspec for route programming (and others) It should always be
- -- present alongside with set_aspect. If this is not specified but
- -- set_aspect is, the user will be allowed to select any aspect.
- -- Any of the fields marked with <boolean/nil> support 3 types of values:
- nil: if this signal can switch between free/blocked
- false: always shows "blocked", unchangable
- true: always shows "free", unchangable
- -- Any of the "speed" fields should contain a list of possible values
- -- to be set as restriction. If omitted, the value of the described
- -- field is always assumed to be false (no information)
- -- A speed of 0 means that the signal can show a "blocked" aspect
- -- (which is probably the case for most signals)
- -- If the signal can signal "no information" on one of the fields
- -- (thus false is an acceptable value), include false in the list
- -- If your signal can only display a single speed (may it be -1),
- -- always enclose that single value into a list. (such as {-1})
- main = {<speed1>, ..., <speedn>} or nil,
- dst = {<speed1>, ..., <speedn>} or nil,
- shunt = <boolean/nil>,
-
- call_on = <boolean/nil>,
- dead_end = <boolean/nil>,
- w_speed = {<speed1>, ..., <speedn>} or nil,
-
- },
- Example for supported_aspects:
- supported_aspects = {
- main = {0, 6, -1}, -- can show either "Section blocked", "Proceed at speed 6" or "Proceed at maximum speed"
- dst = {0, false}, -- can show only if next signal shows "blocked", no other information.
- shunt = false, -- shunting by this signal is never allowed.
-
- call_on = false,
- dead_end = false,
- w_speed = nil,
- -- none of the information can be shown by the signal
-
- },
-
- get_aspect = function(pos, node)
- -- This function gets called by the train safety system. It
- should return the aspect that this signal actually displays,
- not preferably the input of set_aspect.
- -- For regular, full-featured light signals, they will probably
- honor all entries in the original aspect, however, e.g.
- simple shunt signals always return main=false regardless of
- the set_aspect input because they can not signal "Halt" to
- train moves.
- -- advtrains.interlocking.DANGER contains a default "all-danger" aspect.
- -- If your signal does not cover certain sub-tables of the aspect,
- the following reasonable defaults are automatically assumed:
- main = false (unchanged)
- dst = false (unchanged)
- shunt = false (shunting not allowed)
- info = {} (no further information)
- end,
-}
-on_rightclick = advtrains.interlocking.signal_rc_handler
-can_dig = advtrains.interlocking.signal_can_dig
-after_dig_node = advtrains.interlocking.signal_after_dig
-
-(If you need to specify custom can_dig or after_dig_node callbacks,
-please call those functions anyway!)
-
-Important note: If your signal should support external ways to set its
-aspect (e.g. via mesecons), there are some things that need to be considered:
-- advtrains.interlocking.signal_get_supposed_aspect(pos) won't respect this
-- Whenever you change the signal aspect, and that aspect change
-did not happen through a call to
-advtrains.interlocking.signal_set_aspect(pos, asp), you are
-*required* to call this function:
-advtrains.interlocking.signal_on_aspect_changed(pos)
-in order to notify trains about the aspect change.
-This function will query get_aspect to retrieve the new aspect.
-
-]]--
-
local DANGER = {
main = 0,
shunt = false,
diff --git a/advtrains_interlocking/signal_aspect_accessors.lua b/advtrains_interlocking/signal_aspect_accessors.lua
index 060f923..e55814e 100644
--- a/advtrains_interlocking/signal_aspect_accessors.lua
+++ b/advtrains_interlocking/signal_aspect_accessors.lua
@@ -1,3 +1,6 @@
+--- Signal aspect accessors
+-- @module advtrains.interlocking
+
local A = advtrains.interlocking.aspects
local D = advtrains.distant
local I = advtrains.interlocking
@@ -29,6 +32,9 @@ local get_aspect
local supposed_aspects = {}
+--- Replace the signal aspect cache.
+-- @function load_supposed_aspects
+-- @param db The new database.
function I.load_supposed_aspects(tbl)
if tbl then
supposed_aspects = tbl
@@ -38,23 +44,42 @@ function I.load_supposed_aspects(tbl)
end
end
+--- Retrieve the signal aspect cache.
+-- @function save_supposed_aspects
+-- @return The current database in use.
function I.save_supposed_aspects()
return supposed_aspects
end
+--- Read the aspect of a signal strictly from cache.
+-- @param pos The position of the signal.
+-- @return[1] The aspect of the signal (if present in cache).
+-- @return[2] The nil constant (otherwise).
local function get_supposed_aspect(pos)
return supposed_aspects[pts(pos)]
end
+--- Update the signal aspect information in cache.
+-- @param pos The position of the signal.
+-- @param asp The new signal aspect
local function set_supposed_aspect(pos, asp)
supposed_aspects[pts(pos)] = asp
end
+--- Get the definition of a node.
+-- @param pos The position of the node.
+-- @return[1] The definition of the node (if present).
+-- @return[2] An empty table (otherwise).
local function get_ndef(pos)
local node = N.get_node(pos)
return minetest.registered_nodes[node.name] or {}
end
+--- Get the aspects supported by a signal.
+-- @function signal_get_supported_aspects
+-- @param pos The position of the signal.
+-- @return[1] The table of supported aspects (if present).
+-- @return[2] The nil constant (otherwise).
local function get_supported_aspects(pos)
local ndef = get_ndef(pos)
if ndef.advtrains and ndef.advtrains.supported_aspects then
@@ -63,6 +88,11 @@ local function get_supported_aspects(pos)
return nil
end
+--- Adjust a new signal aspect to fit a signal.
+-- @param pos The position of the signal.
+-- @param asp The new signal aspect.
+-- @return The adjusted signal aspect.
+-- @return The information to pass to the `advtrains.set_aspect` field in the node definitions.
local function adjust_aspect(pos, asp)
asp = table.copy(I.signal_convert_aspect_if_necessary(asp))
setmetatable(asp, signal_aspect_metatable)
@@ -103,6 +133,12 @@ local function adjust_aspect(pos, asp)
return asp, asp
end
+--- Get the aspect of a signal without accessing the cache.
+-- For most cases, `get_aspect` should be used instead.
+-- @function signal_get_real_aspect
+-- @param pos The position of the signal.
+-- @return[1] The signal aspect adjusted using `adjust_aspect` (if present).
+-- @return[2] The nil constant (otherwise).
local function get_real_aspect(pos)
local ndef = get_ndef(pos)
if ndef.advtrains and ndef.advtrains.get_aspect then
@@ -116,6 +152,11 @@ local function get_real_aspect(pos)
return nil
end
+--- Get the aspect of a signal.
+-- @function signal_get_aspect
+-- @param pos The position of the signal.
+-- @return[1] The aspect of the signal (if present).
+-- @return[2] The nil constant (otherwise).
get_aspect = function(pos)
local asp = get_supposed_aspect(pos)
if not asp then
@@ -125,6 +166,11 @@ get_aspect = function(pos)
return asp
end
+--- Set the aspect of a signal.
+-- @function signal_set_aspect
+-- @param pos The position of the signal.
+-- @param asp The new signal aspect.
+-- @param[opt=false] skipdst Whether to skip updating distant signals.
local function set_aspect(pos, asp, skipdst)
local node = N.get_node(pos)
local ndef = minetest.registered_nodes[node.name]
@@ -141,10 +187,16 @@ local function set_aspect(pos, asp, skipdst)
end
end
+--- Remove a signal from cache.
+-- @function signal_clear_aspect
+-- @param pos The position of the signal.
local function clear_aspect(pos)
set_supposed_aspect(pos, nil)
end
+--- Readjust the aspect of a signal.
+-- @function signal_readjust_aspect
+-- @param pos The position of the signal.
local function readjust_aspect(pos)
set_aspect(pos, get_aspect(pos))
end
diff --git a/advtrains_interlocking/signal_aspects.lua b/advtrains_interlocking/signal_aspects.lua
index c381fd2..37af7aa 100644
--- a/advtrains_interlocking/signal_aspects.lua
+++ b/advtrains_interlocking/signal_aspects.lua
@@ -1,5 +1,11 @@
+--- Signal aspect handling.
+-- @module advtrains.interlocking.aspects
+
local type2defs = {}
+--- Register a type 2 signal group.
+-- @function register_type2
+-- @param def The definition table.
local function register_type2(def)
local t = {type = 2}
local name = def.name
@@ -42,19 +48,21 @@ local function register_type2(def)
type2defs[name] = t
end
+--- Get the definition of a type 2 signal group.
+-- @function get_type2_definition
+-- @param name The name of the signal group.
+-- @return[1] The definition for the signal group (if present).
+-- @return[2] The nil constant (otherwise).
local function get_type2_definition(name)
return type2defs[name]
end
-local function get_type2_danger(group)
- local def = type2defs[group]
- if not def then
- return nil
- end
- local main = def.main
- return main[#main]
-end
-
+--- Get the name of the distant aspect before the current aspect.
+-- @function get_type2_dst
+-- @param group The name of the group.
+-- @param name The name of the current aspect.
+-- @return[1] The name of the distant aspect (if present).
+-- @return[2] The nil constant (otherwise).
local function get_type2_dst(group, name)
local def = type2defs[group]
if not def then
@@ -67,6 +75,12 @@ local function get_type2_dst(group, name)
return def.main[math.max(1, aspidx-1)].name
end
+--- Convert a type 2 signal aspect to a type 1 signal aspect.
+-- @function type2_to_type1
+-- @param suppasp The table of supported aspects for the signal.
+-- @param asp The name of the signal aspect.
+-- @return[1] The type 1 signal aspect table (if present).
+-- @return[2] The nil constant (otherwise).
local function type2_to_type1(suppasp, asp)
local name = suppasp.group
local shift = suppasp.dst_shift
@@ -111,6 +125,13 @@ local function type2_to_type1(suppasp, asp)
return t
end
+--- Convert a type 1 signal aspect table to a type 2 signal aspect.
+-- @function type1_to_type2main
+-- @param asp The type 1 signal aspect table
+-- @param group The signal aspect group
+-- @param[opt=0] shift The shift for the signal aspect.
+-- @return[1] The name of the signal aspect (if present).
+-- @return[2] The nil constant (otherwise).
local function type1_to_type2main(asp, group, shift)
local def = type2defs[group]
if not def then
@@ -130,6 +151,11 @@ local function type1_to_type2main(asp, group, shift)
return t_main[math.max(1, idx-(shift or 0))].name
end
+--- Compare two signal aspect tables.
+-- @function equalp
+-- @param asp1 The first signal aspect table.
+-- @param asp2 The second signal aspect table.
+-- @return Whether the two signal aspect tables give the same (type 1 aspect) information.
local function equalp(asp1, asp2)
if asp1 == asp2 then -- same reference
return true
@@ -146,6 +172,11 @@ local function equalp(asp1, asp2)
return true
end
+--- Compare two signal aspect tables.
+-- @function not_equalp
+-- @param asp1 The first signal aspect table.
+-- @param asp2 The second signal aspect table.
+-- @return The negation of `equalp``(asp1, asp2)`.
local function not_equalp(asp1, asp2)
return not equalp(asp1, asp2)
end