From 885e43e42a6225c6998105b2cb40fec11a41edcc Mon Sep 17 00:00:00 2001
From: autocommitter <autocommitter@linux-forks.de>
Date: Tue, 31 Dec 2024 00:00:00 +0100
Subject: State at 2024-12-31

---
 far/init_code.lua           | 531 ++++++++++++++++++++++++++++++++++++++++++++
 far/nodes/(1753,8,1570).lua |  16 ++
 far/nodes/(1755,8,1570).lua |   9 -
 3 files changed, 547 insertions(+), 9 deletions(-)
 create mode 100644 far/nodes/(1753,8,1570).lua
 delete mode 100644 far/nodes/(1755,8,1570).lua

(limited to 'far')

diff --git a/far/init_code.lua b/far/init_code.lua
index e6fbcb5..15dfbf8 100644
--- a/far/init_code.lua
+++ b/far/init_code.lua
@@ -56,6 +56,10 @@ F.send_route = function(passive_name, route, show_print)
   return return_value
 end
 
+--[[
+F.save_train(POS(26201,24,1417), "east")
+]]
+
 F.save_train = function(pos, direction)
   if event.train then
     if not atc_id then return end
@@ -214,3 +218,530 @@ F.slow_train_down = function(id)
     F.print("Train ID " .. id .. " is slowed down to B1")
   end
 end
+
+-- init code for FAR timetable env
+
+-- stop, scheduled departure every
+-- d_int: Departure every n seconds (epoch modulo)
+-- d_off: Departure time offset
+function F.stop_sd(st_name, doors, departcommand, minstoptime, d_int, d_off)
+  if event.train then
+    local timenow = os.time()
+    local timerdy = timenow + minstoptime
+    local wait = d_int - ((timerdy-d_off) % d_int)
+   local waitcorr = math.floor(wait*0.66)
+    digiline_send("monitor", "Departure scheduled for: | "..os.date("%H:%M:%S", timenow+wait))
+    atc_send("B0 W O"..doors.." D"..waitcorr.." OCD1"..departcommand)
+  else
+    local timenow = os.time()
+    digiline_send("monitor", "Time: "..os.date("%H:%M:%S", timenow))
+  end
+end
+
+function F.stop_sd_sched(st_name, doors, departcommand, minstoptime, d_int, d_off)
+  depart = false
+  if event.train then
+    local time_now = rwt.now()
+    local next_dep_time = rwt.next_rpt(rwt.add(time_now, minstoptime), d_int, d_off)
+    digiline_send("monitor", "Departure scheduled for: | "..rwt.to_string(next_dep_time, true))
+    atc_set_text_inside(st_name.."\nDeparture: "..rwt.to_string(next_dep_time, true))
+    atc_send("B0 W O"..doors)
+    schedule(next_dep_time, "depart")
+  elseif event.schedule then
+    atc_send("OCD1"..departcommand)
+    digiline_send("monitor", "Last Departure: | "..rwt.to_string(rwt.now(), true))
+    atc_set_text_inside("")
+    depart = true
+  end
+end
+
+function F.timedisplay()
+  digiline_send("time", "Time: | "..rwt.to_string(rwt.now(),true).." | "..os.date("%H:%M:%S"))
+  schedule(rwt.next_rpt(rwt.now(),5,0), "")
+end
+
+-- Stat counter and timetaking utilities
+-- Stat from subway
+F.stat=function(line, init)
+  -- statistics
+  -- init
+  if init then
+    reftrain = atc_id
+    a_tbt = 30
+    a_tbtmax = 30
+    a_rtt = 500
+    a_not = 0
+    c_not = 0
+    c_tbtmax = 0
+    time_lt = os.time()
+    time_rt=os.time()
+  end
+  if not a_tbtmax then a_tbtmax = 30 end
+  if not c_tbtmax then c_tbtmax = 0 end
+  --real code
+  if event.train then
+    local time = os.time()
+    c_not = c_not + 1
+    a_tbt = (a_tbt + (time - time_lt)) / 2
+    c_tbtmax = math.max(c_tbtmax, (time - time_lt))
+    if atc_id == reftrain then
+      a_rtt = (a_rtt*0.2 + (time - time_rt)*0.8)
+      a_not = c_not
+      c_not = 0
+      a_tbtmax = (a_tbtmax + c_tbtmax) / 2
+      c_tbtmax = 0
+    end
+    digiline_send("stats", "Stat: "..line..
+      " NoT:"..a_not.."("..c_not..")"..
+      " TbT:"..math.floor(a_tbt).."("..(time-time_lt)..")"..
+      " Tmx:"..math.floor(a_tbtmax).."("..c_tbtmax..")"..
+      " R:"..math.floor(a_rtt).."("..(time - time_rt)..")"
+      )
+    time_lt = time
+    if atc_id == reftrain then
+      time_rt = time
+    end
+  end
+end
+
+S.timetake = {}
+function F.timetake_start(ttname)
+  if not atc_id then return end
+  local nouw = rwt.to_secs(rwt.now())
+  if not S.timetake[ttname] then
+    S.timetake[ttname] = {}
+  end
+  S.timetake[ttname][atc_id] = nouw
+end
+
+--L100
+function F.timetake_end(ttname)
+  if not atc_id then return end
+  if not S.timetake[ttname] or not S.timetake[ttname][atc_id] then
+    digiline_send("timetake", "No start time for "..atc_id)
+    return
+  end
+  local first = S.timetake[ttname][atc_id]
+  local nouw = rwt.to_secs(rwt.now())
+  local tdiff = nouw - first
+  local cavg = S.timetake[ttname].avg
+  local cmax = S.timetake[ttname].max
+  local cmin = S.timetake[ttname].min
+  if cavg and cmax and cmin then
+    S.timetake[ttname].avg = tdiff*0.1 + cavg*0.9
+    S.timetake[ttname].min = math.min(tdiff, cmin)
+    S.timetake[ttname].max = math.max(tdiff, cmax)
+  else
+    S.timetake[ttname].avg = tdiff
+    S.timetake[ttname].min = tdiff
+    S.timetake[ttname].max = tdiff
+  end
+  digiline_send("timetake", ttname..
+    " this:"..tdiff..
+    " min:"..math.floor(S.timetake[ttname].min)..
+    " avg:"..math.floor(S.timetake[ttname].avg)..
+    " max:"..math.floor(S.timetake[ttname].max)
+    )
+end
+
+--== Timetable prototype (TTP) ===
+--[[ table structures:
+F.ttp - static timetable data - see below
+S.ttp[tt_name] = { - dynamic tt data
+  recording_train = <id of the train that is recording travel times, or nil>
+  travel_times = {
+    <station name> = <time in seconds from initial departure at first station of the line to departure at this station>
+  }
+  station_order = { <station 1>, <station 2>...}
+}
+S.ttt[train_id] = { - trains
+      timetable = <timetable ID that the train is currently using>,
+      initial_dep = <departure at first station of the line>,
+      location = <Station where the train was last seen>,
+      desired_dep = <Departure time as in timetable>,
+      planned_dep = <real departure time calculated as the train reaches station>,
+      actual_dep = <actual departure time at last station. is nil while train is stopped>,
+      last_delay = <last known delay of the train - calculated every departure>,
+    }
+}
+]]
+
+local STOP_TIME = 10
+local STOPCMD="B0WO"
+local DEPCMD="A1OCD1SM"
+local RDEPCMD="RA1OCD1SM"
+local DYNAMIC_THR = 10
+local DYNAMIC_EN = false
+
+if not S.ttp then S.ttp = {} end
+if not S.ttt then S.ttt = {} end
+F.ttp={
+  FAR_E = {
+    outside_text = "[FAR] Fareast End\nvia Halfway, Bayonne, Fucking",
+    inside_line_desc = "FAR to Fareast End",
+    stn_display = "FAR Fareast End",
+  },
+  FAR_W = {
+    outside_text = "[FAR] Salt Factory\nvia Fucking,  Bayonne, Halfway",
+    inside_line_desc = "FAR to Salt Factory",
+    stn_display = "FAR Salt Factory",
+  },
+}
+
+--[[
+Timetable entry point. The train finalizes its last timetable and
+registers itself on the given timetable instance. It departs at the next time slot
+(given by interval and offset).
+F.ttp_begin({
+ stn = "Warmoneaye", -- station name
+ tt = "CFE_S", -- timetable ID
+ depint = "05;00", --departure slot interval
+ depoff = "00;00", --departure slot offset
+ doorside = "L",
+ reverse = true,
+ only_lines = nil, --if given a table, only trains where only_lines[get_line()] is true are considered
+ force_tt_reset = false, -- force reset of travel times for this timetable
+})
+]]
+-- Make train depart at the next time slot, and save its start time
+function F.ttp_begin(p)
+  __approach_callback_mode = 1
+
+  if not F.ttp[p.tt] then error("No TT instance "..p.tt) end
+  if not atc_id then
+    print(p.stn,"missing train!",event)
+    return
+  end
+  if not atc_arrow then return end
+  if p.only_lines and not p.only_lines[get_line()] then return end
+  if not S.ttp[p.tt] then S.ttp[p.tt] = {} end
+  local tti = S.ttp[p.tt]
+  --L150
+  if event.approach and not event.has_entered then
+    -- make the train stop
+    atc_set_ars_disable(true)
+    atc_set_lzb_tsr(2)
+    atc_set_text_inside("Next stop: "..p.stn.."\nTerminal Station.\nThis train continues as "..F.ttp[p.tt].inside_line_desc)
+  end
+  if event.train then
+    -- train arrived, planning departure
+    atc_send(STOPCMD .. p.doorside)
+
+    local time_now = rwt.now()
+    -- Train might have had another TT before, do the cleanup from ttp_end here.
+    local trno = S.ttt[atc_id]
+    if trno then
+      local ttio = S.ttp[trno.timetable]
+      if ttio.recording_train == atc_id then
+        ttio.travel_times[p.stn] = rwt.diff(trno.initial_dep, time_now)
+        ttio.station_order[#ttio.station_order+1] = p.stn
+        print(atc_id,"for",p.tt,"at",p.stn,"-> travel time",rwt.to_string(ttio.travel_times[p.stn]),"-route end")
+      end
+    end
+
+    local next_dep_time = rwt.next_rpt(rwt.add(time_now, 10), p.depint, p.depoff)
+    schedule(next_dep_time, "departure")
+    S.ttt[atc_id] = {
+      timetable = p.tt,
+      initial_dep = next_dep_time,
+      location = p.stn,
+      desired_dep = next_dep_time,
+      planned_dep = next_dep_time,
+      last_delay = 0,
+    }
+    -- if no travel times are available yet, set this train as recording
+    if not tti.travel_times or p.force_tt_reset or tti.force_tt_reset then
+      tti.travel_times = {}
+      tti.station_order = {p.stn}
+      tti.recording_train = atc_id
+      tti.force_tt_reset = false
+      print(atc_id,"starting TT recording for",p.tt)
+    elseif tti.recording_train == atc_id then
+      tti.recording_train = nil
+    end
+    atc_set_text_outside(F.ttp[p.tt].outside_text)
+    atc_set_text_inside(p.stn.."\nArr: "
+        ..rwt.to_string(time_now, true).." Dep: "
+        ..rwt.to_string(next_dep_time, true))
+  end
+  if event.schedule then
+    -- departure. save actual departure time in tt
+    if S.ttt[atc_id] then -- failsafe: if entry is deleted externally somehow, train just departs and is not tracked by tt (makes resetting S.ttt possible)
+     S.ttt[atc_id].actual_dep = rwt.now()
+     local delay = rwt.diff(S.ttt[atc_id].desired_dep, S.ttt[atc_id].actual_dep)
+     atc_set_text_inside(F.ttp[p.tt].inside_line_desc .. "\nDelay: " .. rwt.to_string(delay, true))
+     S.ttt[atc_id].last_delay = delay
+    end
+    if p.reverse then
+      atc_send(RDEPCMD)
+    else
+      atc_send(DEPCMD)
+    end
+  end
+end
+--[[
+Generic stop on timetable. Any train that has a TT instance registered
+stops here, waits STOP_TIME and continues. Behavior can be altered by options:
+F.ttp_stop({
+ stn = "Personhood West", -- station name
+ doorside = "L",
+ only_lines = nil, --if given a table, only trains where only_lines[get_line()] is true are considered
+ end_of_tt = { TT_ID = true },
+   -- if present and key is true for a TT identifier, this is the last station on this timetable. Trains will stop recording timetable and be deregistered.
+ departure = { TT_ID = RWT relative to initial departure },
+   -- If present, override desired departure time. Defaults to travel time + STOP_TIME if not provided
+ no_disable_ars = nil,
+   -- if true, does not disable ARS on approach (used for example at INTERCAL)
+})
+]]
+function F.ttp_stop(p)
+  -- set my approach callback mode
+  __approach_callback_mode = 1
+  if not atc_id then
+    print(p.stn,"missing train!",event)
+    return
+  end
+  if not S.ttt[atc_id] then return end
+  if p.only_lines and not p.only_lines[get_line()] then return end
+  local trn = S.ttt[atc_id]
+  local tt = trn.timetable
+  if not F.ttp[tt] then
+    S.ttt[atc_id] = nil
+  end
+  local tti = S.ttp[tt]
+  if event.approach and not event.has_entered then
+    -- make the train stop
+    if not p.no_disable_ars then
+     atc_set_ars_disable(true)
+    end
+    atc_set_lzb_tsr(2)
+    atc_set_text_inside("Next stop: "..p.stn)
+  end
+
+--!-- disaster recovery --!--
+--  if event.approach and event.has_entered then
+--    print(atc_id,p.stn,"Disaster Recovery...")
+--    atc_send(DEPCMD)
+--  end
+
+
+  if event.train then
+    -- train arrived, planning departure
+    atc_send(STOPCMD..p.doorside)
+    local time_now = rwt.now()
+    -- update our location and determine desired and planned departure
+--L200
+    local next_dep_time = rwt.add(time_now, STOP_TIME)
+    trn.location = p.stn
+    trn.desired_dep = nil
+    trn.actual_dep = nil
+
+    -- calculate desired departure nouw
+    if p.departure and p.departure[tt] then
+      trn.desired_dep = rwt.add(trn.initial_dep or 0,
+          p.departure[tt])
+    elseif tti.travel_times[p.stn] then
+      trn.desired_dep = rwt.add(trn.initial_dep or 0,
+          tti.travel_times[p.stn] + STOP_TIME)
+      -- dyn travel time
+      if DYNAMIC_EN then
+        local ttpd = rwt.diff(next_dep_time, trn.desired_dep)
+        if ttpd > DYNAMIC_THR then
+          local new_trav = rwt.diff(trn.initial_dep, time_now) + DYNAMIC_THR
+          print(atc_id,tt,"arrived at",p.stn,ttpd,"s early, TT",tti.travel_times[p.stn],"->",new_trav)
+          tti.travel_times[p.stn] = new_trav
+          trn.desired_dep = rwt.add(trn.initial_dep or 0,
+              new_trav + STOP_TIME)
+        end
+      end
+    end
+
+    if trn.desired_dep then
+      -- if we had a source for desired departure, update planned daparture time
+      if rwt.to_secs(next_dep_time) < rwt.to_secs(trn.desired_dep) then
+        -- don't depart before the planned departure time
+        next_dep_time = trn.desired_dep
+      end
+      atc_set_text_inside(p.stn.."\nArr: "
+        ..rwt.to_string(time_now, true).." Plan: "
+        ..rwt.to_string(trn.desired_dep, true).." Dep: "
+        ..rwt.to_string(next_dep_time, true))
+
+      local delay = rwt.diff(trn.desired_dep, next_dep_time)
+      trn.last_delay = delay
+    else
+      atc_set_text_inside(p.stn.."\nAa "
+        ..rwt.to_string(time_now, true).." Dd ? Da"
+        ..rwt.to_string(next_dep_time, true))
+    end
+
+    if tti.recording_train == atc_id then
+      -- we are recording. save travel time
+      tti.travel_times[p.stn] = rwt.diff(trn.initial_dep or 0, time_now)
+      print(atc_id,"for",tt,"at",p.stn,"-> travel time",rwt.to_string(tti.travel_times[p.stn]))
+      tti.station_order[#tti.station_order+1] = p.stn
+      atc_set_text_inside(p.stn.."\nRec TT "
+        ..rwt.to_string(tti.travel_times[p.stn], true).." Da"
+        ..rwt.to_string(next_dep_time, true))
+    end
+
+    trn.planned_dep = next_dep_time
+    schedule(next_dep_time, "departure")
+  end
+  if event.schedule then
+    -- departure. save actual departure time in tt
+    trn.actual_dep = rwt.now()
+    local delay = rwt.diff(trn.desired_dep or trn.actual_dep, trn.actual_dep)
+    atc_set_text_inside(F.ttp[tt].inside_line_desc
+      .."\nDelay: "..rwt.to_string(delay, true))
+    S.ttt[atc_id].last_delay = delay
+    atc_send(DEPCMD)
+    if p.end_of_tt and p.end_of_tt[tt] then
+      -- end of timetable. Deregister train
+      if tti.recording_train == atc_id then
+        tti.recording_train = nil
+      end
+      S.ttt[atc_id] = nil
+    end
+  end
+end
+
+
+function F.ttp_info_times(tt, starttime)
+  --L307
+  local ttf = F.ttp[tt]
+  local tti = S.ttp[tt]
+  local p = {}
+  if tti.recording_train then
+    p[#p+1] = ("recording "..tti.recording_train)
+  end
+  p[#p+1] = ("Di "..rwt.to_string(starttime, false).." "..tti.station_order[1])
+  for i=2,#tti.station_order do
+    local ap = rwt.add(starttime, tti.travel_times[tti.station_order[i]])
+    p[#p+1] = ("Ap "..rwt.to_string(ap, true)..
+      " Dp "..rwt.to_string(rwt.add(ap, STOP_TIME), false)..
+      " "..tti.station_order[i])
+  end
+  return p
+end
+
+function F.ttp_info_trains(tt, starttime)
+  --L307
+  local ttf = F.ttp[tt]
+  local tti = S.ttp[tt]
+  local p = {}
+  for tid,trn in pairs(S.ttt) do
+    if trn.timetable==tt then
+      if trn.actual_dep then
+        p[#p+1] = ("Trn "..tid..
+          " after "..trn.location..
+          " Dd "..rwt.to_string(trn.desired_dep or 0, false)..
+          " Da "..rwt.to_string(trn.actual_dep, false)..
+          " Delay "..rwt.to_string(trn.last_delay or "59;59"))
+      else
+        p[#p+1] = ("Trn "..tid..
+          " at "..trn.location..
+          " Dd "..rwt.to_string(trn.desired_dep or 0, false)..
+          " Delay "..rwt.to_string(trn.last_delay or "59;59"))
+      end
+    end
+  end
+  return p
+end
+
+--[[F.ttp_station_display({
+ lines = {"CFE_S", "NX_S", "E1_S"},
+ departure = {},
+ station = "The Cube",
+ title = "The Cube (Track 2)",
+ interval = 30,
+ display1 = "d1",
+ display2 = "d2",
+ display3 = "d3",
+ show_trainid = false,
+}]]
+function F.ttp_station_display(p)
+  --L425
+  -- { dep, text }
+  local next_trains = {}
+  local function is_past_station(tstn, stnorder)
+    for _,s in ipairs(stnorder) do
+      if s==p.station then return true end
+      if s==tstn then return false end
+    end
+    return true
+  end
+
+  local function add_train(deptime, line, train, tid)
+    local tent = {dep = deptime, text =
+       rwt.to_string(deptime,true)
+       .." "..(p.show_trainid and tid.." " or "")
+       ..F.ttp[line].stn_display
+       .." +"..train.last_delay}
+    for i,ntrn in ipairs(next_trains) do
+      if rwt.diff(ntrn.dep, deptime)<0 then
+        table.insert(next_trains, i, tent)
+        return
+      end
+    end
+    table.insert(next_trains, tent)
+  end
+
+  for _,line in ipairs(p.lines) do
+    local fttp = F.ttp[line]
+    local sttp = S.ttp[line]
+    -- find all trains on this line
+    for id, train in pairs(S.ttt) do
+      if train.timetable == line then
+        if train.location == p.station and not train.actual_dep then
+          -- the train is currently standing at this station
+          add_train(train.planned_dep, line, train, id)
+        elseif not is_past_station(train.location, sttp.station_order) then
+          -- train is still approaching, calculate arrival time
+          local trav_dep = rwt.add(train.initial_dep, (sttp.travel_times[p.station] or 0) + STOP_TIME)
+          local act_dep = rwt.add(trav_dep, train.last_delay)
+          if p.departure and p.departure[line] then
+            local plan_dep = rwt.add(train.initial_dep, p.departure[line])
+            if rwt.to_secs(act_dep) < rwt.to_secs(plan_dep) then
+              act_dep = plan_dep
+            end
+          end
+          add_train(act_dep, line, train, id)
+        end
+      end
+    end
+  end
+
+  -- make output
+  local i
+  local text1 = p.title .. " * "..rwt.to_string(rwt.now(), true).." * "
+  for i=1,3 do
+    if next_trains[i] then
+      text1 = text1 .. "\n".. next_trains[i].text
+    end
+  end
+
+  digiline_send(p.display1, text1)
+  if p.display2 then
+    local text2 = ""
+    for i=4,7 do
+      if next_trains[i] then
+        text2 = text2 .. next_trains[i].text .. "\n"
+      end
+    end
+    digiline_send(p.display2, text2)
+  end
+
+  if p.display3 then
+    local text3 = ""
+    for i=8,11 do
+      if next_trains[i] then
+        text3 = text3 .. next_trains[i].text .. "\n"
+      end
+    end
+    digiline_send(p.display3, text3)
+  end
+  --if not p.notimer then
+  --  schedule_in(p.interval or 30,"foo")
+  --end
+end
diff --git a/far/nodes/(1753,8,1570).lua b/far/nodes/(1753,8,1570).lua
new file mode 100644
index 0000000..52fa2df
--- /dev/null
+++ b/far/nodes/(1753,8,1570).lua
@@ -0,0 +1,16 @@
+-- far_luaatctrack_spot_check_01.lua
+
+--[[
+F.ttp_begin({
+ stn = "Salt Factory", -- station name
+ tt = "FAR_E", -- timetable ID
+ depint = "05;00", --departure slot interval
+ depoff = "03;15", --departure slot offset
+ doorside = "L",
+ reverse = true,
+ only_lines = {['FAR'] = true},
+ force_tt_reset = false,
+})
+]]
+
+F.save_train(POS(1755,8,1570), "west")
diff --git a/far/nodes/(1755,8,1570).lua b/far/nodes/(1755,8,1570).lua
deleted file mode 100644
index 3b21dbf..0000000
--- a/far/nodes/(1755,8,1570).lua
+++ /dev/null
@@ -1,9 +0,0 @@
--- far_luaatctrack_spot_check_01.lua
--- POS(dd,dd,dd)
---[[
-POS(1755,8,1570)
-]]
-
-local show_print = false
-
-F.save_train(POS(1755,8,1570), "west")
-- 
cgit v1.2.3