diff options
Diffstat (limited to 'advtrains_interlocking/signal_api.lua')
-rw-r--r-- | advtrains_interlocking/signal_api.lua | 446 |
1 files changed, 69 insertions, 377 deletions
diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index a969378..ce8854a 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -1,173 +1,9 @@ -- 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 F = advtrains.formspec local DANGER = { main = 0, - dst = false, shunt = false, } advtrains.interlocking.DANGER = DANGER @@ -177,32 +13,18 @@ advtrains.interlocking.GENERIC_FREE = { shunt = false, dst = false, } +advtrains.interlocking.FULL_FREE = { + main = -1, + shunt = false, + proceed_as_main = true, +} -local function convert_aspect_if_necessary(asp) - if type(asp.main) == "table" then - local newasp = {} - if asp.main.free then - newasp.main = asp.main.speed - else - newasp.main = 0 - end - if asp.dst and asp.dst.free then - newasp.dst = asp.dst.speed - else - newasp.dst = 0 - end - newasp.proceed_as_main = asp.shunt.proceed_as_main - newasp.shunt = asp.shunt.free - -- Note: info table not transferred, it's not used right now - return newasp - end - return asp -end +advtrains.interlocking.signal_convert_aspect_if_necessary = advtrains.interlocking.aspect -function advtrains.interlocking.update_signal_aspect(tcbs) +function advtrains.interlocking.update_signal_aspect(tcbs, skipdst) if tcbs.signal then local asp = tcbs.aspect or DANGER - advtrains.interlocking.signal_set_aspect(tcbs.signal, asp) + advtrains.interlocking.signal_set_aspect(tcbs.signal, asp, skipdst) end end @@ -212,17 +34,9 @@ end function advtrains.interlocking.signal_after_dig(pos) -- clear influence point - advtrains.interlocking.db.clear_ip_by_signalpos(pos) -end -function advtrains.interlocking.signal_set_aspect(pos, asp) - asp = convert_aspect_if_necessary(asp) - local node=advtrains.ndb.get_node(pos) - local ndef=minetest.registered_nodes[node.name] - if ndef and ndef.advtrains and ndef.advtrains.set_aspect then - ndef.advtrains.set_aspect(pos, node, asp) - advtrains.interlocking.signal_on_aspect_changed(pos) - end + advtrains.interlocking.signal_clear_aspect(pos) + advtrains.distant.unassign_all(pos, true) end -- should be called when aspect has changed on this signal. @@ -230,8 +44,10 @@ function advtrains.interlocking.signal_on_aspect_changed(pos) local ipts, iconn = advtrains.interlocking.db.get_ip_by_signalpos(pos) if not ipts then return end local ipos = minetest.string_to_pos(ipts) - - advtrains.invalidate_all_paths_ahead(ipos) + + -- FIXME: invalidate_all_paths_ahead does not appear to always work as expected + --advtrains.invalidate_all_paths_ahead(ipos) + minetest.after(0, advtrains.invalidate_all_paths, ipos) end function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, pointed_thing) @@ -241,7 +57,10 @@ function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, advtrains.interlocking.show_ip_form(pos, pname) return end - + advtrains.interlocking.show_signal_form(pos, node, pname) +end + +function advtrains.interlocking.show_signal_form(pos, node, pname) local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) if sigd then advtrains.interlocking.show_signalling_form(sigd, pname) @@ -252,12 +71,12 @@ function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, local function callback(pname, aspect) advtrains.interlocking.signal_set_aspect(pos, aspect) end - local isasp = ndef.advtrains.get_aspect(pos, node) - + local isasp = advtrains.interlocking.signal_get_aspect(pos, node) + advtrains.interlocking.show_signal_aspect_selector( pname, ndef.advtrains.supported_aspects, - "Set aspect manually", callback, + pos, callback, isasp) else --static signal - only IP @@ -278,38 +97,13 @@ function advtrains.interlocking.signal_get_supposed_aspect(pos) return DANGER; end --- Returns the actual aspect of the signal at position, as returned by the nodedef. --- returns nil when there's no signal at the position -function advtrains.interlocking.signal_get_aspect(pos) - local node=advtrains.ndb.get_node(pos) - local ndef=minetest.registered_nodes[node.name] - if ndef and ndef.advtrains and ndef.advtrains.get_aspect then - local asp = ndef.advtrains.get_aspect(pos, node) - if not asp then asp = DANGER end - return convert_aspect_if_necessary(asp) - end - return nil -end - --- Returns the "supported_aspects" of the signal at position, as returned by the nodedef. --- returns nil when there's no signal at the position -function advtrains.interlocking.signal_get_supported_aspects(pos) - local node=advtrains.ndb.get_node(pos) - local ndef=minetest.registered_nodes[node.name] - if ndef and ndef.advtrains and ndef.advtrains.supported_aspects then - local asp = ndef.advtrains.supported_aspects - return asp - end - return nil -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 @@ -319,48 +113,72 @@ local function ipmarker(ipos, connid) }) end --- shows small info form for signal IP state/assignment +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 form = "size[7,5]label[0.5,0.5;Signal at "..minetest.pos_to_string(pos).."]" - local pts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos) + 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 - form = form.."label[0.5,1.5;Influence point is set at "..pts.."/"..connid.."]" - form = form.."button_exit[0.5,2.5; 5,1;set;Move]" - form = form.."button_exit[0.5,3.5; 5,1;clear;Clear]" local ipos = minetest.string_to_pos(pts) ipmarker(ipos, connid) - else - form = form.."label[0.5,1.5;Influence point is not set.]" - form = form.."label[0.5,2.0;It is recommended to set an influence point.]" - form = form.."label[0.5,2.5;This is the point where trains will obey the signal.]" - - form = form.."button_exit[0.5,3.5; 5,1;set;Set]" 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_ipassign_"..minetest.pos_to_string(pos), form) + minetest.show_formspec(pname, "at_il_propassign_"..minetest.pos_to_string(pos), form) end end -minetest.register_on_player_receive_fields(function(player, formname, fields) - local pname = player:get_player_name() - if not minetest.check_player_privs(pname, {train_operator=true, interlocking=true}) then +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 - local pts = string.match(formname, "^at_il_ipassign_([^_]+)$") + 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 - if fields.set then - advtrains.interlocking.signal_init_ip_assign(pos, pname) - elseif fields.clear then - advtrains.interlocking.db.clear_ip_by_signalpos(pos) - end + advtrains.interlocking.handle_ip_formspec_fields(pname, pos, fields) + advtrains.interlocking.handle_dst_formspec_fields(pname, pos, fields) end end) @@ -410,129 +228,3 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) players_assign_ip[pname] = nil end end) - - ---== aspect selector ==-- - -local players_aspsel = {} - ---[[ -suppasp: "supported_aspects" table -purpose: form title string -callback: func(pname, aspect) called on form submit -isasp: aspect currently set -]] -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 form = "size[7,7]label[0.5,0.5;Select Signal Aspect:]" - form = form.."label[0.5,1;"..purpose.."]" - - form = form.."label[0.5,1.5;== Main Signal ==]" - local selid = 1 - local entries = {} - for idx, spv in ipairs(suppasp.main) do - local entry - if spv == 0 then - entry = "Halt" - elseif spv == -1 then - entry = "Continue at maximum speed" - elseif not spv then - entry = "Continue\\, speed limit unchanged (no info)" - else - entry = "Continue at speed of "..spv - end - -- hack: the crappy formspec system returns the label, not the index. save the index in it. - entries[idx] = idx.."| "..entry - if isasp and spv == (isasp.main or false) then - selid = idx - end - end - form = form.."dropdown[0.5,2;6;main;"..table.concat(entries, ",")..";"..selid.."]" - - - form = form.."label[0.5,3;== Shunting ==]" - if suppasp.shunt == nil then - local st = 1 - if isasp and isasp.shunt then st=2 end - form = form.."dropdown[0.5,3.5;6;shunt_free;---,allowed;"..st.."]" - end - - form = form.."label[0.5,4.5;== Distant Signal ==]" - local selid = 1 - local entries = {} - for idx, spv in ipairs(suppasp.dst) do - local entry - if spv == 0 then - entry = "Expect to stop at the next signal" - elseif spv == -1 then - entry = "Expect to pass the next signal at maximum speed" - elseif not spv then - entry = "No info" - else - entry = string.format("Expect to pass the next signal at speed of %d", spv) - end - entries[idx] = idx.."| "..entry - if isasp and spv == (isasp.dst or false) then - selid = idx - end - end - form = form.."dropdown[0.5,5;6;dst;"..table.concat(entries, ",")..";"..selid.."]" - - form = form.."button_exit[0.5,6;5,1;save;Save signal aspect]" - - local token = advtrains.random_id() - - minetest.show_formspec(pname, "at_il_sigaspdia_"..token, form) - - minetest.after(1, function() - players_aspsel[pname] = { - suppasp = suppasp, - callback = callback, - token = token, - } - end) -end - -local function usebool(sup, val, free) - if sup == nil then - return val==free - else - return sup - end -end - --- other side of hack: extract the index -local function ddindex(val) - return tonumber(string.match(val, "^(%d+)|")) -end - --- TODO use non-hacky way to parse outputs - -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 - if fields.save then - local maini = ddindex(fields.main) - if not maini then return end - local dsti = ddindex(fields.dst) - if not dsti then return end - local asp = { - main = psl.suppasp.main[maini], - dst = psl.suppasp.dst[dsti], - shunt = usebool(psl.suppasp.shunt, fields.shunt_free, "allowed"), - info = {} - } - psl.callback(pname, asp) - end - else - players_aspsel[pname] = nil - end - end - -end) |