--Environment Code ------------------------------------------------------------------------------------- -- Yard Information if event.init then S.yards = { --[[ yard info template yard_id = { yard_name = string, active_indicator_pos = POS(), dir_indicator_pos = POS(), error_indicator_pos = POS(), headshunt_max = number, notify = empty table, notify_pos = pos, -- reserved vars -- last_id = string (the id of the last train that entered the yard last) arrival_length = number arrival_time = string (RWT) departure_length = number or "?" (set when train arrives, until train departs) departure_time = string (RWT) rts = bool (whether the entering train will exit the way it came in) }, ]]-- TY = { yard_name = "Trisiston", active_indicator_pos = POS(-4025,14,-2659), dir_indicator_pos = POS(-4025,12,-2665), error_indicator_pos = POS(-4025,13,-2671), headshunt_max = 5, notify = {}, notify_pos = POS(-4023,12,-2660), arrival_time = rwt.now(), departure_time = rwt.now() }, BY = { yard_name = "Banach", active_indicator_pos = POS(-2002,3,-1099), dir_indicator_pos = POS(-2009,3,-1101), error_indicator_pos = POS(-1999,3,-1099), headshunt_max = 5, notify = {}, notify_pos = POS(-2004,2,-1101), arrival_time = rwt.now(), departure_time = rwt.now() }, ARC = { yard_name = "Arcadius", active_indicator_pos = POS(-1952,16,840), dir_indicator_pos = POS(-1950,16,840), error_indicator_pos = POS(-1948,16,840), headshunt_max = 5, notify = {}, notify_pos = POS(-1946,16,840), arrival_time = rwt.now(), departure_time = rwt.now() }, IP = { yard_name = "Ipswich", active_indicator_pos = POS(1179,16,3848), dir_indicator_pos = POS(1177,16,3855), error_indicator_pos = POS(1179,16,3850), headshunt_max = 5, notify = {}, notify_pos = POS(1178,16,3851), arrival_time = rwt.now(), departure_time = rwt.now() }, CAN = { yard_name = "Cannery", active_indicator_pos = POS(-594,26,2486), dir_indicator_pos = POS(-594,26,2484), error_indicator_pos = POS(-594,26,2482), headshunt_max = 2, --notify = {}, --notify_pos = POS(-594,26,2485), arrival_time = rwt.now(), departure_time = rwt.now() } -- HY = { -- active_indicator_pos = POS(-4025,14,-2659), -- dir_indicator_pos = POS(-4025,13,-2665), -- error_indicator_pos = POS(-4025,13,-2671), -- }, } end ------------------------------------------------------------------------------------ -- Utility Functions F.indicator = function(indicator,set) if set ~= nil then if type(set) == string then setstate(indicator,set) else setstate(indicator,(set and "on") or "off") end end return (getstate(indicator) == "on") or false end F.get_rc_safe = function() return get_rc() or "" end F.has_rc = function(query,rc_list) -- query = string, single entry if not atc_id then return false end if rc_list == "" or query == nil or query=="" then return false end if not rc_list then rc_list = F.get_rc_safe() end for word in rc_list:gmatch("[^%s]+") do if word == query then return true end end return false end F.has_rc_match = function(query,rc_list) -- query = pattern string, single entry if not atc_id then return false end if rc_list == "" or query == nil or query=="" then return false end if not rc_list then rc_list = F.get_rc_safe() end local rc = {} for v in rc_list:gmatch("("..query..")") do table.insert(rc,v) end if rc[1] == true then return true, rc else return nil end end F.add_rc = function(rc_list) -- rc_list = string or table, eg: {"rc1","rc2"} OR "rc1 rc2" if not atc_id then return false end if type(rc_list) == "table" then rc_list = table.concat(rc_list," ") end set_rc(F.get_rc_safe().." "..rc_list) return true end F.remove_rc = function(rc_list,arrow_mode) -- rc_list = string eg: "rc1 rc2 rc3" OR table eg: {"rc1","rc2","rc3"} -- Arrow Modes: -- true: with arrow direction -- false: against arrow direction -- nil: ignores arrow direction if not atc_id then return false end if not rc_list then return false end if (arrow_mode == nil) or (atc_arrow == arrow_mode) then -- prep rc_list to useable format local rc_remove = {} if type(rc_list) == "string" then for word in rc_list:gmatch("[^%s]+") do rc_remove[word] = true end elseif type(rc_list) == "table" then for _,word in pairs(rc_list) do rc_remove[word] = true end end -- remove codes from train's rc local rc = F.get_rc_safe() local reinsert = {} for token in rc:gmatch("[^%s]+") do if not rc_remove[token] then table.insert(reinsert,token) end end -- insert new string to train's rc set_rc(table.concat(reinsert," ")) end return reinsert end F.remove_rc_match = function(rc_list) -- rc_list = pattern string, single entry, eg: "rc_%d+" if not atc_id then return false end if not rc_list then return false end local rm = {} for v in F.get_rc_safe():gmatch("("..rc_list..")") do table.insert(rm,v) end F.remove_rc(rm) return rm end ---------------------------------------------------------------------------------------------- -- Trackside Functions -- this_dir = points towards Origin/Junction Yard F.yard_arrival = function(yard_id,this_dir, force_rts) -- arrow points towards yard local yard = S.yards[yard_id] --yard ref if F.has_rc(yard_id.."_NOSHUNT") then return end local function enter_yard() if not atc_id then F.indicator(yard.error_indicator_pos,true) return end F.indicator(yard.dir_indicator_pos,this_dir) F.indicator(yard.active_indicator_pos,true) F.add_rc({yard_id.."_ARRIVE"}) local rts = false if force_rts then -- yard is designated as a terminus yard. all trains MUST rts F.add_rc(yard_id.."_RTS") rts = true elseif F.has_rc(yard_id.."_RTS") then --save the RTS flag as it's removed during the arrival procedure F.add_rc({yard_id.."_HAS_RTS"}) rts = true end atc_set_ars_disable(false) atc_send("S6") S.yards[yard_id].last_id = atc_id S.yards[yard_id].arrival_length = train_length() S.yards[yard_id].arrival_time = rwt.now() S.yards[yard_id].departure_length = "?" S.yards[yard_id].departure_time = rwt.now() S.yards[yard_id].rts = rts if S.print_debug then print(rwt.to_string(rwt.now())) print("YARD "..yard_id..": Train "..atc_id.." enters from the "..tostring(this_dir).." direction and will exit in the "..tostring(rts).." direction") print("YARD "..yard_id..": Length "..train_length()) end return end __approach_callback_mode = 1 if event.approach and not event.has_entered then atc_set_ars_disable(true) atc_set_lzb_tsr(1) return end if event.train and atc_arrow then if F.indicator(yard.active_indicator_pos) then if S.print_debug then print(rwt.to_string(rwt.now())) print("YARD "..yard_id..": Train "..atc_id.." has arrived from the "..tostring(this_dir).." direction and has to wait for the yard to deactivate.") end schedule_in(";10","recheck") return else enter_yard() return true end end if event.schedule then if F.indicator(yard.active_indicator_pos) then schedule_in(";10","recheck") return else enter_yard() return true end end end F.classification = function(yard_id, this_dir) -- arrow points towards headshunt local yard = S.yards[yard_id] --yard ref if not F.indicator(yard.active_indicator_pos) then return end if F.has_rc(yard_id.."_NOSHUNT") then return end -- this_dir == true for north end, false for south end if F.indicator(yard.active_indicator_pos) then if atc_arrow then -- loco is at working end F.remove_rc({yard_id.."_PICKUP"}) if F.has_rc(yard_id.."_ARRIVE") and F.indicator(yard.dir_indicator_pos) == this_dir then --first pass, prep train for working F.remove_rc({yard_id.."_AROUND"}) if not F.get_rc_safe():match(yard_id.."_LOCOS_%d+") or not F.get_rc_safe():match(yard_id.."_WAGONS_%d+") then local full_length = train_length() split_off_locomotive("A0B0") local loco_count = train_length() local wagon_count = 0 if full_length > loco_count then wagon_count = full_length - loco_count end F.add_rc({yard_id.."_LOCOS_"..loco_count, yard_id.."_WAGONS_"..wagon_count}) --[[ train will end up going through headshunt no matter what there shouldn't be any trains entering that pass straight through the yard any that do should run under the _NOSHUNT rc ]]-- if not F.has_rc(yard_id.."_RTS") then F.add_rc({yard_id.."_HEADSHUNT",yard_id.."_PICKUP"}) else F.indicator(yard.dir_indicator_pos,not F.indicator(yard.dir_indicator_pos)) F.remove_rc({yard_id.."_RTS"}) F.add_rc({yard_id.."_AROUND"}) -- send loco around to the other end end atc_set_ars_disable(false) return -- train has departed for headshunt/around end --else train has arrived from wagon count and headshunt/around F.remove_rc({yard_id.."_ARRIVE"}) end if this_dir == F.indicator(yard.dir_indicator_pos) then --train has bounced and is ready to classify wagon(s) local locos = tonumber(F.get_rc_safe():match(yard_id.."_LOCOS_(%d+)")) or 1 --saved loco count local wagons = tonumber(F.get_rc_safe():match(yard_id.."_WAGONS_(%d+)")) or 0 -- wagon_count from last classification split local lane = split_at_fc("A0B0",yard.headshunt_max) -- where to classify this rake -- headshunt length local this_rake = train_length() - locos if lane ~= "" then F.add_rc({yard_id.."_CLASS_"..lane}) end if this_rake == wagons then -- mark for last_classification F.add_rc({yard_id.."_LAST_CLASS"}) end F.remove_rc_match(yard_id.."_WAGONS_%d+") F.add_rc({yard_id.."_HEADSHUNT",yard_id.."_WAGONS_"..wagons-this_rake}) -- subtract these wagons from the overall wagon count atc_set_ars_disable(false) set_autocouple() else --bounce train back towards working end atc_set_ars_disable(true) atc_send("S0WRD1S3") unset_autocouple() end else -- train entering from the far end. set autocouple so it pushes all the way through to the bounce atc_set_ars_disable(true) set_autocouple() end end end F.headshunt_yard = function(yard_id,this_dir) -- arrow points toward yard local yard = S.yards[yard_id] --yard ref __approach_callback_mode = 1 if event.approach and not event.has_entered then atc_set_ars_disable(true) atc_set_lzb_tsr(1) return end if not F.indicator(yard.active_indicator_pos) then return end if F.has_rc(yard_id.."_NOSHUNT") then return end if event.train then if not atc_arrow then --train has entered headshunt from yard. bounce or depart atc_set_ars_disable(true) F.remove_rc({yard_id.."_HEADSHUNT"}) if not F.has_rc(yard_id.."_FINAL_COLLECT") then if not F.has_rc(yard_id.."_DEPART") then -- MOST TRAINS. only set when train wants to depart, otherwise train will start the reverse countdown schedule_in(";01",atc_id) return else if get_line() ~= "LHF" then -- LHF trains will always only collect the * lane for that direction. This code is for other trains local collect = F.get_rc_safe():match(yard_id.."_COLLECT_(%S+)") -- will only match to the first _COLLECT RC to avoid excessively long trains in the headshunt if collect then --train needs to collect a rake from the yard before departing F.add_rc({yard_id.."_CLASS_"..collect,yard_id.."_FINAL_COLLECT"}) schedule_in(";01",atc_id) return end else -- CLASS_* should never be set in ARS, instead being left to the * route F.add_rc({yard_id.."_FINAL_COLLECT", yard_id.."_CLASS_*"}) schedule_in(";01",atc_id) return end end --else let train proceed forward to the exit controller without a rake end --else train has already collected rake. let it exit yard else --train has bounced if F.has_rc(yard_id.."_AROUND") then if this_dir == F.indicator(yard.dir_indicator_pos) then --send train to Classification F.remove_rc({yard_id.."_AROUND"}) F.add_rc({yard_id.."_PICKUP"}) atc_set_ars_disable(false) return end --if at wrong end, do nothing. train will follow _AROUND ARS to relevant headshunt end -- if doesn't have _AROUND then let ARS classify the train atc_set_ars_disable(false) end return end if event.schedule then if not atc_id then --bounce the train that just passed atc_send_to_train(event.msg,"B0WRD1A1S3") else if atc_id == event.msg then --train hasn't fully passed yet, wait a bit longer schedule_in(";01",atc_id) else -- somehow another train crossed the controller before the first one returned. Unlikely but possible atc_send_to_train(event.msg,"BBOL") atc_send("BBOL") F.indicator(yard.error_indicator_pos,true) end end return end end F.headshunt_exit = function(yard_id,this_dir) -- arrow points out of yard local yard = S.yards[yard_id] --yard ref __approach_callback_mode = 1 if event.approach and not event.has_entered then atc_set_ars_disable(true) atc_set_lzb_tsr(1) return end if not F.indicator(yard.active_indicator_pos) then return end if F.has_rc(yard_id.."_NOSHUNT") then return end if event.train then if atc_arrow and (F.indicator(yard.dir_indicator_pos) == this_dir) then if F.has_rc(yard_id.."_DEPART") then if S.print_debug then print(rwt.to_string(rwt.now())) print("YARD "..yard_id..": Train "..atc_id.." has a length of "..train_length()) print("YARD "..yard_id..": It will depart in the "..tostring(this_dir).." direction.") end S.yards[yard_id].departure_length = train_length() unset_autocouple() F.remove_rc({yard_id.."_FINAL_COLLECT",yard_id.."_DEPART"}) F.remove_rc_match(yard_id.."_LOCOS_%d+") F.remove_rc_match(yard_id.."_WAGONS_%d+") if yard.notify_pos then interrupt_pos(yard.notify_pos,"notify") end if F.has_rc(yard_id.."_HAS_RTS") then --reinsert the RTS flag to fully return RC list to starting F.remove_rc(yard_id.."_HAS_RTS") F.add_rc({yard_id.."_RTS"}) end step_fc() atc_set_ars_disable(false) atc_send("S6D10SM") schedule_in(";05","deactivate_check") -- wait a little to ensure train is clear from headshunt to deactivate yard return end -- if not _DEPART then let the train pass as extra space for the bounce back to the yard else atc_send("BBOL") F.indicator(yard.error_indicator_pos,true) end end if event.schedule then if event.msg == "deactivate_check" then if not atc_id then F.indicator(yard.active_indicator_pos,false) S.yards[yard_id].departure_time = rwt.now() if S.print_debug then print(rwt.to_string(rwt.now())) print("YARD "..yard_id..": yard Disabled") end else schedule_in(";05","deactivate_check") return end end end end F.lane_EOL = function(yard_id,this_dir) -- arrow points towards headshunt local yard = S.yards[yard_id] --yard ref if not F.indicator(yard.active_indicator_pos) then return end if F.has_rc(yard_id.."_NOSHUNT") then return end if atc_arrow then if F.indicator(yard.dir_indicator_pos) == this_dir then --train has bounced and needs to leave the rake or depart with it if F.has_rc(yard_id.."_LAST_CLASS") then -- this is the last clasification move F.remove_rc({yard_id.."_LAST_CLASS"}) F.add_rc({yard_id.."_DEPART"}) split_off_locomotive("A0B0") elseif not F.has_rc(yard_id.."_FINAL_COLLECT") then -- disconnect loco and return to pickup split_off_locomotive("A0B0") F.add_rc({yard_id.."_PICKUP"}) end --if has FINAL_COLLECT then don't split off the loco. take the full rake F.add_rc({yard_id.."_HEADSHUNT"}) else --train needs to bounce atc_send("S0WRD1S4") end else if F.indicator(yard.dir_indicator_pos) == this_dir then local fc = F.get_rc_safe():match(yard_id.."_CLASS_(%S+)") F.remove_rc_match(yard_id.."_CLASS_%S+") if not yard.notify or not yard.notify_pos then return end if not fc then return end if fc == "*" then return end if F.has_rc(yard_id.."_COLLECT_"..fc) then return end S.yards[yard_id].notify[fc] = true else --this should never come into play as it means the train has entered from the wrong end somehow atc_send("BBOL") --stop the train and open the doors (if available) to signify assistance required F.indicator(yard.error_indicator_pos,true) end end end