aboutsummaryrefslogtreecommitdiff
path: root/src/script/lua_api/l_async_events.h
blob: 9d42b07cf2d2744fcf5bb8733dd3437f9566c4f1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/*
Minetest
Copyright (C) 2013 sapier, <sapier AT gmx DOT net>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef L_ASYNC_EVENTS_H_
#define L_ASYNC_EVENTS_H_

#include <vector>
#include <map>

/******************************************************************************/
/* Includes                                                                   */
/******************************************************************************/
#include "jthread/jthread.h"
#include "jthread/jmutex.h"
#include "jthread/jsemaphore.h"
#include "debug.h"
#include "lua.h"

/******************************************************************************/
/* Typedefs and macros                                                        */
/******************************************************************************/
#define MAINMENU_NUMBER_OF_ASYNC_THREADS                   4

/******************************************************************************/
/* forward declarations                                                       */
/******************************************************************************/
class AsyncEngine;

/******************************************************************************/
/* declarations                                                               */
/******************************************************************************/

/** a struct to encapsulate data required to queue a job **/
struct LuaJobInfo {
	/** function to be called in async environment **/
	std::string serializedFunction;
	/** parameter table to be passed to function **/
	std::string serializedParams;
	/** result of function call **/
	std::string serializedResult;
	/** jobid used to identify a job and match it to callback **/
	unsigned int JobId;
	/** valid marker **/
	bool valid;
};

/** class encapsulating a asynchronous working environment **/
class AsyncWorkerThread : public JThread {
public:
	/**
	 * default constructor
	 * @param pointer to job dispatcher
	 */
	AsyncWorkerThread(AsyncEngine* jobdispatcher, unsigned int threadnumber);

	/**
	 * default destructor
	 */
	virtual ~AsyncWorkerThread();

	/**
	 * thread function
	 */
	void* Thread() {
			ThreadStarted();
			return worker_thread_wrapper(this);
	}

private:
	/**
	 * helper function to run a lua script
	 * @param path of script
	 */
	bool runScript(std::string script);

	/**
	 * main function of thread
	 */
	void* worker_thread_main();

	/**
	 * static wrapper for thread creation
	 * @param this pointer to the thread to be created
	 */
	static void* worker_thread_wrapper(void* thread);

	/**
	 * pointer to job dispatcher
	 */
	AsyncEngine* m_JobDispatcher;

	/**
	 * the lua stack to run at
	 */
	lua_State*   m_LuaStack;

	/**
	 * lua internal stack number of error handler
	 */
	int m_luaerrorhandler;

	/**
	 * thread number used for debug output
	 */
	unsigned int m_threadnum;

};

/** asynchornous thread and job management **/
class AsyncEngine {
	friend class AsyncWorkerThread;
public:
	/**
	 * default constructor
	 */
	AsyncEngine();
	/**
	 * default destructor
	 */
	~AsyncEngine();

	/**
	 * register function to be used within engines
	 * @param name function name to be used within lua environment
	 * @param fct c-function to be called
	 */
	bool registerFunction(const char* name, lua_CFunction fct);

	/**
	 * create async engine tasks and lock function registration
	 * @param numengines number of async threads to be started
	 */
	void Initialize(unsigned int numengines);

	/**
	 * queue/run a async job
	 * @param fct serialized lua function
	 * @param params serialized parameters
	 * @return jobid the job is queued
	 */
	unsigned int doAsyncJob(std::string fct, std::string params);

	/**
	 * engine step to process finished jobs
	 *   the engine step is one way to pass events back, PushFinishedJobs another
	 * @param L the lua environment to do the step in
	 */
	void Step(lua_State *L);


	void PushFinishedJobs(lua_State* L);

protected:
	/**
	 * Get a Job from queue to be processed
	 *  this function blocks until a job is ready
	 * @return a job to be processed
	 */
	LuaJobInfo getJob();

	/**
	 * put a Job result back to result queue
	 * @param result result of completed job
	 */
	void putJobResult(LuaJobInfo result);

	/**
	 * initialize environment with current registred functions
	 *  this function adds all functions registred by registerFunction to the
	 *  passed lua stack
	 * @param L lua stack to initialize
	 * @param top stack position
	 */
	void PrepareEnvironment(lua_State* L, int top);

private:

	/** variable locking the engine against further modification **/
	bool m_initDone;

	/** internal store for registred functions **/
	std::map<std::string,lua_CFunction> m_FunctionList;

	/** internal counter to create job id's **/
	unsigned int m_JobIdCounter;

	/** mutex to protect job queue **/
	JMutex m_JobQueueMutex;
	/** job queue **/
	std::vector<LuaJobInfo> m_JobQueue;

	/** mutext to protect result queue **/
	JMutex m_ResultQueueMutex;
	/** result queue **/
	std::vector<LuaJobInfo> m_ResultQueue;

	/** list of current worker threads **/
	std::vector<AsyncWorkerThread*> m_WorkerThreads;

	/** counter semaphore for job dispatching **/
	JSemaphore m_JobQueueCounter;
};

#endif /* L_ASYNC_EVENTS_H_ */
3 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726
if S.trains == nil then S.trains = {} end
if not S.odsd then S.odsd = {} end
if not S.cpoints then S.cpoints = {} end
S.pd = {}
S.pdt = {}
__approach_callback_mode = 1

F.odj = {}

-- F.dst - Get summer time
function F.dst()
  if os.date().isdst then return "CEST" else return "CET" end
end

function F.pccheck(pos, state)
  if is_passive(pos) then return true end
  return (getstate(pos) == state)
end

-- F.error - Send error with error code
function F.error(errorcode)
  error(F.errlist[errorcode])
end

-- Level crossing handling
if not S.crossings then S.crossings = {} end

local crossing_light_count = {
  NRG_1 = 2,
  NRG_2 = 4,
  NRG_3 = 3,
  NRG_4 = 2,
  NRG_5 = 4,
  NRG_6 = 4,
  NRG_7 = 2,
  NRG_8 = 4,
  SWALD_1 = 2,
  SWALD_2 = 4,
}

local function update_light(loc, k, v)
  local t = S.crossings[loc]
  if not t then
    t = {}
    S.crossings[loc] = t
  end
  t[k] = v
  local val = "on"
  if next(t) == nil then
    val = "off"
  end
  local pfx = loc .. "_"
  local count = crossing_light_count[loc]
  if not count then return end
  for i = 1, count do
    setstate(pfx .. i, val)
  end
end

function F.crossing_on(t)
  for loc, k in pairs(t) do
    update_light(loc, k, true)
  end
end

function F.x_on(t)
  if event.type == "train" and atc_arrow then
    return F.crossing_on(t)
  end
end

function F.ax_on(t)
  __approach_callback_mode = 1
  if (event.type == "approach" or event.type == "train") and atc_arrow then
    return F.crossing_on(t)
  end
end

function F.sx_on(t)
  if event.type == "schedule" and (event.msg or {}).depart then
    return F.crossing_on(t)
  end
end

function F.x_off(t)
  if event.type ~= "train" then return end
  if not atc_arrow then return end
  for loc, k in pairs(t) do
    update_light(loc, k, nil)
  end
end

--[[ F.stnbasic - Basis for all station functions:
(By default, nil is defined as false or "no changes")
stn        = Station Code
side       = Door opening side
optime     = Length of time before train departs
reverse    = Reverse train before departing
acc        = Departure Speed
out        = Change outside text
reventry   = Allow trains to pass from the reverse direction
predepart  = Function to execute before departure
postdepart = Function to execute after departure
next       = Next stop
]]

function F.stnbasic(stn, side, optime, reverse, acc, out, reventry, predepart, postdepart, next,track)
  if not event then return end
  if event.type == "train" then
    if atc_arrow then
      if (get_rc() or ""):match ("FREIGHT") then
        atc_send (reverse and "RSM" or "SM")
        return
      end
      local cmdstr=""
      local t_sched = ((optime or 15) + 1)
      if out then
        atc_set_text_outside (out)
      end
      local intext = "Unknown Station"
      if stn then
        intext = F.stnlist[stn] or stn
      end
      atc_set_text_inside (intext)
      cmdstr = "BBWO" .. (side or "R")
      if type (predepart) == "function" then 
        predepart ()
      end
      S.trains[atc_id] = {
        v = 1,
        l = (get_line() or ""),
        s = stn,
        t = (trk or "N/A"),
      }
      cmdstr = cmdstr .."D" .. math.floor((optime or 15)) .. "OCD1"
      if reverse then
        cmdstr = cmdstr .. "R"
      end
      cmdstr = cmdstr .. "S" .. (acc or "M")
      atc_send (cmdstr)
      interrupt (t_sched,"")
    end
  elseif event.type == "int" then
    if atc_id then
      local intext = ""
      if next then
        intext = "Next stop: " .. (F.stnlist[next] or next)
      end
      if type (postdepart) == "function" then
        postdepart ()
      end
      atc_set_text_inside (intext)
    end
  end
end

-- F.hst - preset function for small stations (Haltestelle)
function F.hst(cur, nxt, side, spd, out, trk)
  F.stnbasic(cur, side, 10, false, spd, out, true, nil, nil, nxt, trk)
end

-- F.bhf - preset function for large stations (Bahnhof)
function F.bhf(cur, nxt, side, spd, out, trk)
  F.stnbasic(cur, side, 15, false, spd, out, true, nil, nil, nxt, trk)
end

-- F.kbhf - preset function for termini
function F.kbhf(cur, nxt, side, spd, out, trk)
  F.stnbasic(cur, side, 15, true, spd, out, true, nil, nil, nxt, trk)
end

-- F.timing - station with timing
function F.timing(d_off, d_int, cur, nxt, side, spd, out, trk, term, pre, post)
		  local timenow = os.time()
	  	local timesincelast = (timenow+d_off) % d_int
  local wait = d_int - timesincelast
  F.stnbasic(cur, side, wait, term, spd, out, true, pre, post, nxt, trk)
end

-- F.brk(direction) - sends ATC B2 command
function F.brk(dir)
  if event.type == "train" then
    if atc_arrow == dir then
      atc_send("B2S2")
    end
  end
end

-- Second version of the station function
function F.stn2gen(stn, trk, door, ret, chout, depspeed)
  __approach_callback_mode = 1
  if not stn then return end
  if not trk then return end
  if not door then door = "R" end
  if door~="L" and door~="R" and door~="C" then return end
  if not atc_arrow then return end
  if not F.stndet[stn] then return end
  if not F.stndet[stn][trk] then return end
  local t = F.stndet[stn][trk]
  local stop = false
  if #t==0 then return end
  local l = get_line()
  if (not l) or l==" " then return end
  local rc = get_rc() or ""
  if rc:match("FREIGHT") and atc_id then
    atc_send (ret and "BBWRSM" or "SM")
    return
  end
  if event.type == "approach" and not event.has_entered then
    for i = 1, #t, 1 do
      if t[i][1]==l then
        atc_set_ars_disable(true)
        atc_set_lzb_tsr(2)
        local intext = ("Arriving at: %s (Track: %s)"):format(F.stnlist[stn] or stn, trk)
        atc_set_text_inside(intext)
        S.trains[atc_id] = {
          v = 2,
          l = get_line() or "",
          s = stn,
          t = trk or "N/A",
          ts = os.time(),
          mode = "APP",
        }
        return
      end
    end
    return
  elseif event.type == "train" then
    for i = 1, #t, 1 do
      if t[i][1]==l then
        stop = true
        local timenow = rwt.to_secs(rwt.now())
        local interval = F.lines[l].interval
        local offset = t[i][7]
        local arroff, arrts, depts
        if interval and offset then
          arroff = timenow%(F.lines[l].rtt or interval)
          interval = (offset-timenow)%interval
          interval = math.max(5, math.min(45, interval))
          arrts = ("%s (%s)"):format(rwt.to_string(timenow), rwt.to_string(arroff, true))
          depts = ("%s (%s)"):format(rwt.to_string(timenow+interval), rwt.to_string(arroff+interval, true))
        else
          interval = 15
          arrts = rwt.to_string(timenow)
          depts = rwt.to_string(timenow+interval)
        end
        --atc_send(string.format("A0B0WO%sD%dOCD1%sA1S%s", door, interval, (ret and "R" or ""), tostring(depspeed or "M")))
        atc_send(("B0WO%s"):format(door))
        local nxt = t[i][2]
        local intext_nxt = ""
        local nxtdisp = ""
        if F.stnlist[nxt] then
          intext_nxt = F.stnlist[nxt] and ("Next station: %s (Track: %s)"):format(F.stnlist[nxt], t[i][3] or "?") or ""
          nxtdisp = intext_nxt
        end
        local intext = ("%s\n%s\nArrival: %s\nDeparture: %s"):format(F.stnlist[stn] or stn, intext_nxt, arrts, depts)
        local outtext = F.lines[l].name or l
        if t[i].dest then
          chout = true
          outtext = outtext..": "..t[i].dest
        elseif t[i][4] then
          outtext = outtext..": "..(F.stnlist[t[i][4]] or t[i][4])
        end
        t[i][5] = t[i][5] or (timenow-(F.depint[l] or 300))
        -- note that the "average" time is weighted
        t[i][6] = t[i][6] and (t[i][6]+timenow-t[i][5]+30)/2 or (timenow-t[i][5])
        t[i][5] = timenow
        atc_set_text_inside(intext)
        if chout then
          if type(chout) == "string" then
            atc_set_text_outside(chout)
          else
            atc_set_text_outside(outtext)
          end
        end
        S.trains[atc_id] = {
          v = 2,
          l = (get_line() or ""),
          s = stn,
          t = (trk or "N/A"),
          ts = os.time(),
          mode = "ARR",
        }
        schedule_in(interval, {intext = nxtdisp, depart = true})
        break
      end
    end
    if (not stop) then
      atc_send((ret and "BBWR" or "").."A1S"..(depspeed or "M"))
    end
  elseif event.type == "schedule" then
    if atc_id then
      local msg = event.msg or {}
      if msg.intext then
        atc_set_text_inside(msg.intext)
      end
      atc_send("OCD1"..((ret and atc_arrow) and "B0WR" or "").."A1S"..(depspeed or "M"))
      S.trains[atc_id] = {
        v = 2,
        l = get_line() or "",
        s = stn,
        t = trk or "N/A",
        ts = os.time(),
        mode = "DEP",
      }
    end
  end
end

function F.tram2gen(stn,trk,ret)
  return F.stn2gen(stn,trk,"C",ret,true,8)
end

function F.disp2gen(stn)
  if not stn then return end
  if not F.stndet[stn] then return end
  local s = F.stndet[stn]
  local sn = F.stnlist[stn] or stn
  local d={{
    {sn.."\nTrack    Line"},
    {(string.sub(sn, 28, 54) or "").." \nDestination"},
    {" \nEstimated Arrival"}
  }}
  local c=2
  local t={}
  for i,_ in pairs(s) do t[#t+1]=i end
  table.sort(t)
  for i = 1,#t,1 do
    local trkname = t[i]
    local trk = s[trkname]
    for j = 1,#trk,1 do
      local det = trk[j]
      local r = (c%4==0) and (c/4+1) or ((c-c%4)/4+1)
      if not d[r] then d[r]={{},{},{}} end
      d[r][1][#d[r][1]+1] = string.format("%-8s %s", tostring(trkname), F.lines[det[1]].name)
      d[r][2][#d[r][2]+1] = (F.lines[det[1]].ring and "Ring Line" or (F.stnlist[det[4]] or det[4] or ""))
      c=c+1
      local earr = (det[5] or rwt.to_secs(rwt.now()))+(det[6] or F.depint[det[1]] or 600)
      d[r][3][#d[r][3]+1] = rwt.to_string(earr)
    end
  end
  for i = 1, #d, 1 do
    for j = 1, #d[i], 1 do
      digiline_send("d_"..tostring(i).."_"..tostring(j), table.concat(d[i][j],"\n"))
    end
  end
end

--function F.disp2gen() end

function F.pdisp(stn, trk, dn)
  if not (stn and trk and dn) then return end
  if not F.stndet[stn] or not F.stndet[stn][trk] then return end
  if not S.pd[stn] then S.pd[stn] = { [trk] = {[dn] = 0} } end
  if not S.pd[stn][trk] then S.pd[stn][trk] = {[dn] = 0} end
  if not S.pd[stn][trk][dn] then S.pd[stn][trk][dn] = 0 end
  if not S.pdt[stn] then S.pdt[stn] = {} end
  if not S.pdt[stn][trk] then
    local t = {}
    for i = 1, #F.stndet[stn][trk], 1 do
      local v = F.stndet[stn][trk][i]
      t[#t+1] = ("Track: %s\n%s\nTo: %s\nNext: %s"):format(
        trk, F.lines[v[1]].name, F.stnlist[v[4] or "N/A"] or v[4] or "N/A",
        F.stnlist[v[2] or "N/A"] or v[2] or "N/A")
    end
    S.pdt[stn][trk] = t
  end
  local n = S.pd[stn][trk][dn] + 1
  local t = S.pdt[stn][trk]
  digiline_send("track"..trk,t[n])
  if n >= #t then n = 0 end
  S.pd[stn][trk][dn] = n
end

function F.dirsign(stn, trk, ln, arrow)
  if event.type == "init" or event.type == "punch" then
    if not (stn and trk and ln and arrow) then return end
    local t = F.stndet[stn]
    if not t then return end
    t = t[trk]
    if not t then return end
    for i = 1, #t, 1 do
      if t[i][1] == ln then
        digiline_send("lcd",("%s T. %s | %s | %s"):format(arrow, trk, F.lines[ln].name, F.stnlist[t[i][4]]))
        break
      end
    end
  end
end

function F.eval(expr, rettrue, retfalse)
  if expr then return rettrue else return retfalse end
end

function F.checkpoint(name,arrow,opp)
  if event.train then
    S.trains[atc_id] = "C "..name.." "
    if atc_arrow then
      S.trains[atc_id] = S.trains[atc_id]..arrow
    else
      S.trains[atc_id] = S.trains[atc_id]..opp
    end
  end
end

function F.atc (cmd, intext, outtext)
  if event.type == "train" then
    if atc_arrow then
      if cmd then
        atc_send (cmd)
      end
      if intext then
        atc_set_text_inside (intext)
      end
      if outtext then
        atc_set_text_outside (outtext)
      end
    end
  end
end

F.stnlist = {
  ["N/A"]          = "N/A",
  Bts              = "Berton St.",
  cg               = "Colored Grasses",
  clockwise        = "Clockwise",
  counterclockwise = "Counterclockwise",
  cras             = "ARSE7's Shop",
  crbfost          = "Station East",
  crbfsm           = "Station St. Central",
  crbfso           = "Station St. East",
  crbfsw           = "Station St. West",
  crch             = "Crossroads City Hall",
  crchs            = "City Hall South",
  crmtrail         = "Mountain Railway Terminus",
  crshelter1       = "Shelter I",
  crsfterm         = "South Forest St. Terminal",
  crsmacker        = "Smacker's Station",
  crwm             = "West Mountains",
  elchateau        = "Chateau d'Erstazi",
  elgp             = "Greener Pastures",
  elsf             = "Salt Factory",
  evo              = "EVO",
  grsc             = "Grassy Scarp",
  krasnograd       = "Krasnograd-FTNT",
  mushroom         = "Mushroom Land",
  neverbuild       = "Neverbuild",
  nvbcentral       = "Neverbuild Central",
  nvbold           = "Old Terminus",
  nvboutskirts     = "Neverbuild Outskirts",
  ["NRG-bplatz"]   = "Berliner Platz",
  ["NRG-CW"]       = "Dörfle",
  ["NRG-harbor"]   = "Hafengebiet",
  ["NRG-krstr"]    = "Krasnograder Straße",
  ["NRG-museum"]   = "Museum",
  ["NRG-pek"]      = "Pekinger Straße",
  ["NRG-south"]    = "Südbahnhof",
  ["NRG-townhall"] = "Rathaus",
  ["NRG-townhall2"] = "Hintere Rathausstraße",
  ["NRG-yard"]     = "Betriebshof",
  oc               = "Ocean City",
  occh             = "City Hall",
  occrt            = "CRT Office",
  ocmushroom       = "Mushroom Market",
  ocoutskirts      = "Ocean City Outskirts",
  phsc             = "Southern Crossing",
  phwest           = "Personhood West",
  scc              = "Silver Coast Central",
  scn              = "Silver Coast North",
  scs              = "Silver Coast South",
  ["SWALD-harbor"] = "Sakurawäldle Hafen",
  ["SWALD-south"]  = "Sakurawäldle Süd",
  ["SWALD-stn"]    = "Sakurawäldle",
  ["SWALD-west"]   = "Sakurawäldle West",
  thecube          = "The Cube",
  ["TSV-island"]   = "West Island",
  ["TSV-mountain"] = "Mountain Cabin",
  ["TSV-pond"]     = "Rexan Road",
  ["TSV-sharbor"]  = "South Harbor",
}

for k, v in pairs{[1] = "Station",} do
  for _, i in pairs{6,} do
    F.stnlist[string.format("crc_%d_%d", k, i)] = string.format("%d %s St.", i, v)
  end
end

for k, v in pairs{[5] = "Fifth", [6] = "Sixth", [7] = "Seventh", [8] = "Eighth",} do
  for l, w in pairs{[1] = "First", [3] = "Third", [5] = "Fifth",} do
    F.stnlist[string.format("crs_%d_%d", k, l)] = string.format("%s St./%s Alley", v, w)
  end
end

F.lines = {
  ["AB"] = {
    name = "ATL-B Commuter", short = "ATL-B/C",
    ring = false,
    interval = 240,
    [1] = { {"crch","2"}, --[[ {"crwm","3"},]] {"scs","N1"}, {"scc","N1"}, {"scn","N1"}, {"cg","1"}, {"thecube","4"}, --[[{"phsc","N"},]] {"grsc", "4"}, {"phwest","1"} },
    [2] = { {"phwest","1"}, {"grsc", "3"}, --[[{"phsc","S"},]] {"thecube","1"}, {"cg","2"}, {"scn","S1"}, {"scc","S1"}, {"scs","S1"}, --[[ {"crwm","4"}, ]] {"crch","2"} },
  },
  ["ABE"] = {
    name = "ATL-B Express", short = "ATL-B/E",
    ring = false,
    interval = 240,
    [1] = {{"crch","1"}, {"scc","N2"}, {"cg","1"}, {"thecube", "4"}, {"phwest", "2"}},
    [2] = {{"phwest", "2"}, {"thecube", "1"}, {"cg", "2"}, {"scc", "S2"}, {"crch", "1"}},
  },
  ["AZ"] = {
    name = "ATL-Z", short = "ATL-Z",
    ring = false,
    interval = 480,
    [1] = { {"Bts","3"}, {"evo","N/A"}, {"scs","N1"}, {"scc","N1"}, {"scn","N1"}, {"oc","K1"}, --[[{"elgp","N"}, {"elsf","E"}, {"elchateau","S"}]] },
    [2] = { --[[{"elchateau","S"}, {"elsf","W"}, {"elgp","S"},]] {"oc","K1"}, {"scn","S1"}, {"scc", "S1"}, {"scs","S1"}, {"evo","N/A"}, {"Bts","3"}, },
  },
  ["CRT1"] = {
    name = "1",
    ring = false,
    [1] = { {"crsmacker", "R3"}, --[[{"crch", "U1"},]] {"crchs", "W"}, {"cras", "N"} },
    [2] = { {"cras", "N"}, {"crchs", "E"}, --[[{"crch", "U2"},]] {"crsmacker", "R3"} },
  },
  ["CRT4"] = {
    name = "4",
    ring = false,
    rtt = 120,
    interval = 60,
    [1] = { {"crch", "11", 0}, {"crsfterm", "1N", 0}, },
    [2] = { {"crsfterm", "1N", 0}, {"crch", "11", 0}, },
  },
  ["CRT12"] = {
    name = "12",
    ring = false,
    [1] = {{"crch", "M"}, {"crwm", "3"}},
    [2] = {{"crwm", "3"}, {"crch", "M"}},
  },
  ["CRT21"] = {
    name = "21",
    ring = false,
    rtt = 120,
    interval = 120,
    [1] = { {"crch", "12", 15}, {"crshelter1", "T", 75}, },
    [2] = { {"crshelter1", "T", 75}, {"crch", "12", 15}, },
  },
  ["NRG1"] = {
    name = "1",
    ring = "counterclockwise",
    rtt = 120,
    interval = 150,
    [1] = { {"NRG-townhall","E",50}, {"NRG-south","N",90}, {"NRG-museum","S", 120}, },
  },
  ["NRG2"] = {
    name = "2",
    ring = false,
    rtt = 300,
    interval = 300,
    [1] = { {"NRG-harbor", "S", 35}, {"NRG-townhall2", "W", 70}, {"NRG-yard", "W", 145}, },
    [2] = { {"NRG-yard", "W", 145}, {"NRG-museum", "S", 210}, {"NRG-townhall", "E", 255}, {"NRG-bplatz", "S", 295}, {"NRG-harbor", "S", 335}, },
  },
  ["NRG3"] = {
    name = "3",
    ring = false,
    rtt = 1200,
    interval = 300,
    [1] = {
      {"TSV-pond", "T", 40, dest = "New Roses Gardens\nvia Sakurawäldle, Krasnograd"}, {"TSV-sharbor", "S", 95}, {"TSV-island", "W", 145}, {"TSV-mountain", "W", 200},
      {"SWALD-harbor", "W", 305, dest = "New Roses Gardens\nvia Krasnograd"}, {"SWALD-south", "W", 345}, {"SWALD-stn", "N", 385}, {"SWALD-west", "W", 415},
      {"krasnograd", "2", 495, dest = "Betriebshof"},
      {"NRG-pek", "2", 535}, {"NRG-CW", "S", 580}, {"NRG-krstr", "S", 610}, {"NRG-yard", "W", 640},
    },
    [2] = {
      {"NRG-yard", "W", 640, dest = "Tung Sha Tsuen\nvia Krasnograd, Sakurawäldle"}, {"NRG-krstr", "N", 670}, {"NRG-CW", "N", 700}, {"NRG-pek", "1", 740},
      {"krasnograd", "1", 795, dest = "Tung Sha Tsuen\nvia Sakurawäldle"},
      {"SWALD-west", "E", 875, dest = "Tung Sha Tsuen\nRexan Road"}, {"SWALD-stn", "S", 905}, {"SWALD-south", "E", 945}, {"SWALD-harbor", "E", 985},
      {"TSV-mountain", "E", 1090, dest = "Rexan Road"}, {"TSV-island", "E", 1145}, {"TSV-sharbor", "N", 1195}, {"TSV-pond", "T", 1240},
    },
  },
}

for k, v in pairs{["B"] = {6, 5, 3, 20},} do
  local t = {{"crc_1_"..v[1], "E", v[4]}}
  for i = 5, 8 do
    t[#t+1] = {string.format("crs_%d_%d", i, v[2]),"S", v[4] + 45 + (i-5)*30}
  end
  for i = 8, 5, -1 do
    t[#t+1] = {string.format("crs_%d_%d", i, v[3]),"N", v[4] + 175 + (8-i)*30}
  end
  F.lines["CRT1"..k] = {
    name = "1"..k,
    ring = "clockwise",
    rtt = 320,
    interval = 80,
    [1] = t
  }
end

for k, v in pairs{["A"] = {5}, ["B"]={6}, ["C"]={7}, ["D"]={8}} do
  local t = {}
  local u = {}
  local phase = ((v[1]-5)%2)*15
  t[1] = {"crsfterm", (v[1]-4).."S", phase}
  for i = 1, 5, 2 do
    t[#t+1] = {string.format("crs_%d_%d",v[1],i), "E", 0}
  end
  for i = 5, 1, -2 do
    u[#u+1] = {string.format("crs_%d_%d",v[1],i), "W", 0}
  end
  u[#u+1] = {"crsfterm", (v[1]-4).."S", phase}
  F.lines["CRT3"..k] = {
    name = "3"..k, ring = false, rtt = 30, interval = 30,
    [1] = t, [2] = u,
  }
end

F.stndet = {}
for i, l in pairs(F.lines) do
  for j = 1, #l, 1 do
    local dir = l[j]
    for k = 1, #dir, 1 do
      local s = dir[k][1]
      local t = dir[k][2]
      local o = dir[k][3]
      local dest = dir[k].dest
      if not F.stndet[s] then F.stndet[s] = {} end
      if not F.stndet[s][t] then F.stndet[s][t] = {} end
      local det = F.stndet[s][t]
      if k~=#dir or l.ring then
        local nxt = dir[k==#dir and 1 or k+1]
        det[#det+1] = {i, nxt[1], nxt[2], l.ring or dir[#dir][1], nil, nil, o, dest = dest}
      else
        local addent = true
        for m = 1, #l, 1 do
          addent = addent and (l[m][1]==dir[k])
        end
        if addent then
          det[#det+1] = {i, nil, nil, s, nil, nil, o, dest = dest}
        end
      end
    end
  end
end

F.errlist = {
  ["incorrect_setup"]   = "Incorrect LuaATC track setup!",
  ["runaway_train"]     = "Runaway train found!",
  ["train_disappeared"] = "Train has disappeared!",
  ["unexpected_train"]  = "Train is not expected to pass!",
  ["wrong_direction"]   = "Train passed in wrong direction!",
}

F.depint = {
  ["AB"]   =  90,
  ["AG"]   =  120,
  ["CRT1"] =  30,
}


F.dpts = {
  ['CRT1'] = POS(1835,5,1000),
  ["CRT2"] = POS(1741,-1,1020),
  ['AG'] = POS(1399,4,2020),
}

function F.odd(ln)
  if event.type=='ext_int' then
    if atc_id then
      set_rc(event.message.rc)
      atc_send('SM')
    else
      if type(F.odj[ln])~='table' then F.odj[ln] = {} end
      for i = 1,(#F.odj[ln]) do
        if F.odj[ln][i]==event then return end
      end
      table.insert(F.odj[ln],1,event)
    end
  end
  if event.type=='train' then
    if F.odj[ln] and #F.odj[ln]~=0 then
      ev = table.remove(F.odj[ln])
      if ev then
        set_rc(ev.message.rc)
        atc_send('BBWRSM')
        return
      else
        F.odj[ln] = {}
      end
    end
    atc_send('BBWR')
  end
end

function F.ods(rc, side, rev)
  if event.type=='train' then
    if atc_arrow then
      if string.find((get_rc() or ''),(rc or '')) then
        atc_send('BBWO' .. (side or 'R') .. 'D10OCD1' .. ((rev or string.find((get_rc() or ''), 'R' .. rc)) and 'R' or '')..'SM')
      elseif rev then
        atc_send('BBWRSM')
      end
    end
  end
end

function F.odc(ln, rc)
  interrupt_pos(F.dpts[ln],{['rc']=rc})
end

function F.cpoint(name, int)
  __approach_callback_mode = 1
  local last = S.cpoints[name] or 0
  local next = last+int
  local now = os.time()
  if event.type == "approach" then
    if now < next then atc_set_lzb_tsr(2) end
  elseif event.type == "train" and atc_arrow then
    if now < next then
      atc_send(string.format("A0B0WD%dA1SM", next-now))
      schedule_in(next-now)
    else
      S.cpoints[name] = now
      atc_send("A1SM")
    end
  elseif event.type == "schedule" and atc_arrow then
    S.cpoints[name] = now
    atc_send("A1SM")
  end
end