-- ars.lua -- automatic routesetting --[[ The "ARS table" and its effects: Every route has (or can have) an associated ARS table. This can either be ars = { [n] = {ln=""}/{rc=""}/{c=""} } a list of rules involving either line or routingcode matchers (or comments, those are ignored) The first matching rule determines the route to set. - or - ars = {default = true} this means that all trains that no other rule matches on should use this route Compound ("and") conjunctions are not supported (--TODO should they?) For editing, those tables are transformed into lines in a text area: {ln=...} -> LN ... {rc=...} -> RC ... {c=...} -> #... {default=true} -> * See also route_ui.lua ]] local il = advtrains.interlocking -- The ARS data are saved in a table format, but are entered in text format. Utility functions to transform between both. function il.ars_to_text(arstab) if not arstab then return "" end local txt = {} for i, arsent in ipairs(arstab) do local n = "" if arsent.n then n = "!" end if arsent.ln then txt[#txt+1] = n.."LN "..arsent.ln elseif arsent.rc then txt[#txt+1] = n.."RC "..arsent.rc elseif arsent.c then txt[#txt+1] = "#"..arsent.c end end if arstab.default then return "*\n" .. table.concat(txt, "\n") end return table.concat(txt, "\n") end function il.text_to_ars(t) if t=="" then return nil elseif t=="*" then return {default=true} end local arstab = {} for line in string.gmatch(t, "[^\r\n]+") do if line=="*" then arstab.default = true else local c, v = string.match(line, "^(...?)%s(.*)$") if c and v then local n = nil if string.sub(c,1,1) == "!" then n = true c = string.sub(c,2) end local tt=string.upper(c) if tt=="LN" then arstab[#arstab+1] = {ln=v, n=n} elseif tt=="RC" then arstab[#arstab+1] = {rc=v, n=n} end else local ct = string.match(line, "^#(.*)$") if ct then arstab[#arstab+1] = {c = ct} end end end end return arstab end local function find_rtematch(routes, train) local default for rteid, route in ipairs(routes) do if route.ars then if route.ars.default then default = rteid else if il.ars_check_rule_match(route.ars, train) then return rteid end end end end return default end -- Checks whether ARS rule explicitly matches. This does not take into account the "default" field, since a wider context is required for this. -- Returns the rule number that matched, or nil if nothing matched function il.ars_check_rule_match(ars, train) if not ars then return nil end local line = train.line local routingcode = train.routingcode for arskey, arsent in ipairs(ars) do --atdebug(arsent, line, routingcode) if arsent.n then -- rule is inverse... if arsent.ln and (not line or arsent.ln ~= line) then return arskey elseif arsent.rc and (not routingcode or not string.find(" "..routingcode.." ", " "..arsent.rc.." ", nil, true)) then return arskey end return nil end if arsent.ln and line and arsent.ln == line then return arskey elseif arsent.rc and routingcode and string.find(" "..routingcode.." ", " "..arsent.rc.." ", nil, true) then return arskey end end return nil end function advtrains.interlocking.ars_check(signalpos, train, trig_from_dst) -- check for distant signal -- this whole check must be delayed until after the route setting has taken place, -- because before that the distant signal is yet unknown if not trig_from_dst then minetest.after(0.5, function() -- does signal have dst? local _, remote = il.signal.get_aspect(signalpos) if remote then advtrains.interlocking.ars_check(remote, train, true) end end) end local sigd = il.db.get_sigd_for_signal(signalpos) local tcbs = sigd and il.db.get_tcbs(sigd) -- trigger ARS on this signal if tcbs and tcbs.routes then if tcbs.ars_disabled or tcbs.ars_ignore_next then -- No-ARS mode of signal. -- ignore... -- Note: ars_ignore_next is set by signalling formspec when route is cancelled tcbs.ars_ignore_next = nil return end if trig_from_dst and tcbs.no_dst_ars_trig then -- signal not to be triggered from distant return end if tcbs.routeset then -- ARS is not in effect when a route is already set -- just "punch" routesetting, just in case callback got lost. minetest.after(0, il.route.update_route, sigd, tcbs, nil, nil) return end local rteid = find_rtematch(tcbs.routes, train) if rteid then --delay routesetting, it should not occur inside train step -- using after here is OK because that gets called on every path recalculation minetest.after(0, il.route.update_route, sigd, tcbs, rteid, nil) end end end