diff options
-rw-r--r-- | advtrains_interlocking/README.md | 96 | ||||
-rw-r--r-- | advtrains_interlocking/distant.lua | 47 | ||||
-rw-r--r-- | advtrains_interlocking/init.lua | 4 | ||||
-rw-r--r-- | advtrains_interlocking/signal_api.lua | 165 | ||||
-rw-r--r-- | advtrains_interlocking/signal_aspect_accessors.lua | 52 | ||||
-rw-r--r-- | advtrains_interlocking/signal_aspects.lua | 49 |
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 |