aboutsummaryrefslogtreecommitdiff
path: root/advtrains_line_automation
diff options
context:
space:
mode:
Diffstat (limited to 'advtrains_line_automation')
-rw-r--r--advtrains_line_automation/init.lua47
-rw-r--r--advtrains_line_automation/mod.conf7
-rw-r--r--advtrains_line_automation/railwaytime.lua308
-rw-r--r--advtrains_line_automation/readme.txt5
-rw-r--r--advtrains_line_automation/scheduler.lua133
-rw-r--r--advtrains_line_automation/settingtypes.txt6
-rw-r--r--advtrains_line_automation/stoprail.lua229
-rw-r--r--advtrains_line_automation/textures/advtrains_dtrack_shared_stop.pngbin0 -> 3306 bytes
-rw-r--r--advtrains_line_automation/textures/advtrains_dtrack_stop_placer.pngbin0 -> 1238 bytes
9 files changed, 735 insertions, 0 deletions
diff --git a/advtrains_line_automation/init.lua b/advtrains_line_automation/init.lua
new file mode 100644
index 0000000..7b758bc
--- /dev/null
+++ b/advtrains_line_automation/init.lua
@@ -0,0 +1,47 @@
+-- Advtrains line automation system
+
+advtrains.lines = {
+ -- [station code] = {name=..., owner=...}
+ stations = {},
+
+ --[[ [new pos hash] = {
+ stn = <station code>,
+ track = <platform identifier>,
+ doors = <door side L,R,C>
+ wait = <least wait time>
+ reverse = <boolean>
+ signal = <position of signal that is the "exit signal" for this platform>
+ }]]
+ stops = {},
+}
+
+
+local modpath = minetest.get_modpath(minetest.get_current_modname()) .. DIR_DELIM
+
+dofile(modpath.."railwaytime.lua")
+dofile(modpath.."scheduler.lua")
+dofile(modpath.."stoprail.lua")
+
+
+function advtrains.lines.load(data)
+ if data then
+ advtrains.lines.stations = data.stations or {}
+ advtrains.lines.stops = data.stops or {}
+ advtrains.lines.rwt.set_time(data.rwt_time)
+ advtrains.lines.sched.load(data.scheduler_queue)
+ end
+end
+
+function advtrains.lines.save()
+ return {
+ stations = advtrains.lines.stations,
+ stops = advtrains.lines.stops,
+ rwt_time = advtrains.lines.rwt.get_time(),
+ scheduler_queue = advtrains.lines.sched.save()
+ }
+end
+
+function advtrains.lines.step(dtime)
+ advtrains.lines.rwt.step(dtime)
+ advtrains.lines.sched.run()
+end
diff --git a/advtrains_line_automation/mod.conf b/advtrains_line_automation/mod.conf
new file mode 100644
index 0000000..e9851c8
--- /dev/null
+++ b/advtrains_line_automation/mod.conf
@@ -0,0 +1,7 @@
+name=advtrains_line_automation
+title=Advanced Trains Line Automation
+description=Tools for automatic train lines
+author=orwell96
+
+depends=advtrains_interlocking
+optional_depends=advtrains_train_track
diff --git a/advtrains_line_automation/railwaytime.lua b/advtrains_line_automation/railwaytime.lua
new file mode 100644
index 0000000..258009e
--- /dev/null
+++ b/advtrains_line_automation/railwaytime.lua
@@ -0,0 +1,308 @@
+-- railwaytime.lua
+-- Advtrains uses a desynchronized time for train movement. Everything is counted relative to this time counter.
+-- The advtrains-internal time is in no way synchronized to the real-life time, due to:
+-- - Lag
+-- - Server stops/restarts
+-- However, this means that implementing a "timetable" system using the "real time" is not practical. Therefore,
+-- we introduce a custom time system, the RWT(Railway Time), which has nothing to do with RLT(Real-Life Time)
+-- RWT has a time cycle of 1 hour. This should be sufficient for most train lines that will ever be built in Minetest.
+-- A RWT looks like this: 37;25
+-- The ; is to distinguish it from a normal RLT (which has colons e.g. 12:34:56). Left number is minutes, right number is seconds.
+-- The minimum RWT is 00;00, the maximum is 59;59.
+-- It is OK to leave one places out at either end, esp. when writing relative times, such as:
+-- 43;3 22;0 2;30 0;10 ;10
+-- Those places are then filled with zeroes. Indeed, ";" would be valid for 00;00 .
+
+-- There is an "adapt mode", which was proposed by gpcf, and results in RWT automatically adapting itself to real-world time.
+-- It works by shifting the minute/second after the realtime minute/second, adjusting the cycle value as needed.
+
+-- Using negative times is discouraged. If you need a negative time, you may insert a minus (-) ONLY in the "c" place
+
+--[[
+1;23;45 = {
+ s=45,
+ m=23,
+ c=1, -- Cycle(~hour), not displayed most time
+}
+
+Railway times can exist in 3 forms:
+- as table (see above)
+- as string (like "12;34")
+- as number (of seconds)
+
+Forms are automagically converted as needed by the converter functions to_*
+To be sure a rwt is in the required form, explicitly use a converter.
+
+]]
+
+local rwt = {}
+
+--Time Stamp (Seconds since start of world)
+local e_time = 0
+local e_has_loaded = false
+
+local setting_rwt_real = minetest.settings:get("advtrains_lines_rwt_realtime")
+if setting_rwt_real=="" then
+ setting_rwt_real = "independent"
+end
+
+local e_last_epoch -- last real-time timestamp
+
+-- Advance RWT to match minute/second to the current real-world time
+-- only accounts for the minute/second part, leaves hour/cycle untouched
+local function adapt_real_time()
+ local datetab = os.date("*t")
+ local real_sectotal = 60*datetab.min + datetab.sec
+
+ local rwttab = rwt.now()
+ local rwt_sectotal = 60*rwttab.m + rwttab.s
+
+ --calculate the difference and take it %3600 (seconds/hour) to always move forward
+ local secsfwd = (real_sectotal - rwt_sectotal) % 3600
+
+ atlog("[lines][rwt] Skipping",secsfwd,"seconds forward to sync rwt (",rwt.to_string(rwttab),") to real time (",os.date("%H:%M:%S"),")")
+
+ e_time = e_time + secsfwd
+end
+
+function rwt.set_time(t)
+ e_time = t or 0
+ if setting_rwt_real == "adapt_real" then
+ adapt_real_time()
+ end
+ atlog("[lines][rwt] Initialized railway time: ",rwt.to_string(e_time))
+ e_last_epoch = os.time()
+
+ e_has_loaded = true
+end
+
+function rwt.get_time()
+ return e_time
+end
+
+function rwt.step(dt)
+ if not e_has_loaded then
+ rwt.set_time(0)
+ end
+
+ if setting_rwt_real=="independent" then
+ -- Regular stepping with dtime
+ e_time = e_time + dt
+ else
+ -- advance with real-world time
+ local diff = os.time() - e_last_epoch
+ e_last_epoch = os.time()
+
+ if diff>0 then
+ e_time = e_time + diff
+ end
+ end
+end
+
+function rwt.now()
+ return rwt.to_table(e_time)
+end
+
+function rwt.new(c, m, s)
+ return {
+ c = c or 0,
+ m = m or 0,
+ s = s or 0
+ }
+end
+function rwt.copy(rwtime)
+ local rwtimet = rwt.to_table(rwtime)
+ return {
+ c = rwtimet.c or 0,
+ m = rwtimet.m or 0,
+ s = rwtimet.s or 0
+ }
+end
+
+function rwt.to_table(rwtime)
+ if type(rwtime) == "table" then
+ return rwtime
+ elseif type(rwtime) == "string" then
+ return rwt.parse(rwtime)
+ elseif type(rwtime) == "number" then
+ local res = {}
+ local seconds = atfloor(rwtime)
+ res.s = seconds % 60
+ local minutes = atfloor(seconds/60)
+ res.m = minutes % 60
+ res.c = atfloor(minutes/60)
+ return res
+ end
+end
+
+function rwt.to_secs(rwtime, c_over)
+ local res = rwtime
+ if type(rwtime) == "string" then
+ res = rwt.parse(rwtime)
+ elseif type(rwtime) == "number" then
+ return rwtime
+ end
+ if type(res)=="table" then
+ return (c_over or res.c)*60*60 + res.m*60 + res.s
+ end
+end
+
+function rwt.to_string(rwtime_p, no_cycle)
+ local rwtime = rwt.to_table(rwtime_p)
+ if rwtime.c~=0 and not no_cycle then
+ return string.format("%d;%02d;%02d", rwtime.c, rwtime.m, rwtime.s)
+ else
+ return string.format("%02d;%02d", rwtime.m, rwtime.s)
+ end
+end
+
+---
+
+local function v_n(str, cpl)
+ if not str then return nil end
+ if str == "" then
+ return 0
+ end
+ local n = tonumber(str)
+ if not cpl and (n<0 or n>59) then
+ return nil
+ end
+ return n
+end
+
+function rwt.parse(str)
+ --atdebug("parse",str)
+ --3-value form
+ local str_c, str_m, str_s = string.match(str, "^(%-?%d?%d?);(%d%d);(%d?%d?)$")
+ if str_c and str_m and str_s then
+ --atdebug("3v",str_c, str_m, str_s)
+ local c, m, s = v_n(str_c, true), v_n(str_m), v_n(str_s)
+ if c and m and s then
+ return rwt.new(c,m,s)
+ end
+ end
+ --2-value form
+ local str_m, str_s = string.match(str, "^(%d?%d?);(%d?%d?)$")
+ if str_m and str_s then
+ --atdebug("2v",str_m, str_s)
+ local m, s = v_n(str_m), v_n(str_s)
+ if m and s then
+ return rwt.new(0,m,s)
+ end
+ end
+end
+
+---
+
+function rwt.add(t1, t2)
+ local t1s = rwt.to_secs(t1)
+ local t2s = rwt.to_secs(t2)
+ return rwt.to_table(t1s + t2s)
+end
+
+-- How many seconds FROM t1 TO t2
+function rwt.diff(t1, t2)
+ local t1s = rwt.to_secs(t1)
+ local t2s = rwt.to_secs(t2)
+ return t2s - t1s
+end
+
+-- Subtract t2 from t1 (inverted argument order compared to diff())
+function rwt.sub(t1, t2)
+ return rwt.to_table(rwt.diff(t2, t1))
+end
+
+-- Adjusts t2 by thresh and then returns time from t1 to t2
+function rwt.adj_diff(t1, t2, thresh)
+ local newc = rwt.adjust_cycle(t2, thresh, t1)
+ local t1s = rwt.to_secs(t1)
+ local t2s = rwt.to_secs(t2, newc)
+ return t1s - t2s
+end
+
+
+
+-- Threshold values
+-- "reftime" is the time to which this is made relative and defaults to now.
+rwt.CA_FUTURE = 60*60 - 1 -- Selected so that time lies at or in the future of reftime (at nearest point in time)
+rwt.CA_FUTURES = 60*60 -- Same, except when times are equal, advances one full cycle
+rwt.CA_PAST = 0 -- Selected so that time lies at or in the past of reftime
+rwt.CA_PASTS = -1 -- Same, except when times are equal, goes back one full cycle
+rwt.CA_CENTER = 30*60 -- If time is within past 30 minutes of reftime, selected as past, else selected as future.
+
+-- Adjusts the "cycle" value of a railway time to be in some relation to reftime.
+-- Returns new cycle
+function rwt.adjust_cycle(rwtime, reftime_p, thresh)
+ local reftime = reftime_p or rwt.now()
+
+ local reftimes = rwt.to_secs(reftime)
+
+ local rwtimes = rwt.to_secs(rwtime, 0)
+ local timeres = reftimes + thresh - rwtimes
+ local cycles = atfloor(timeres / (60*60))
+
+ return cycles
+end
+
+function rwt.adjust(rwtime, reftime, thresh)
+ local cp = rwt.copy(rwtime)
+ cp.c = rwt.adjust_cycle(rwtime, reftime, thresh)
+ return cp
+end
+
+-- Useful for departure times: returns time (in seconds)
+-- until the next (adjusted FUTURE) occurence of deptime is reached
+-- in this case, rwtime is used as reftime and deptime should lie in the future of rwtime
+-- rwtime defaults to NOW
+function rwt.get_time_until(deptime, rwtime_p)
+ local rwtime = rwtime_p or rwt.now()
+ return rwt.adj_diff(rwtime, deptime, rwt.CA_FUTURE)
+end
+
+
+-- Helper functions for handling "repeating times" (rpt)
+-- Those are generic declarations for time intervals like "every 5 minutes", with an optional offset
+-- ( /02;00-00;45 in timetable syntax
+
+-- Get the time (in seconds) until the next time this rpt occurs
+function rwt.time_to_next_rpt(rwtime, rpt_interval, rpt_offset)
+ local rpti_s = rwt.to_secs(rpt_interval)
+
+ return (rpti_s - rwt.time_from_last_rpt(rwtime, rpti_s, rpt_offset)) % rpti_s
+ -- Modulo is just there to clip a false value of rpti_s to 0
+end
+
+
+-- Get the time (in seconds) since the last time this rpt occured
+function rwt.time_from_last_rpt(rwtime, rpt_interval, rpt_offset)
+ local rwtime_s = rwt.to_secs(rwtime)
+ local rpti_s = rwt.to_secs(rpt_interval)
+ local rpto_s = rwt.to_secs(rpt_offset)
+
+ return ((rwtime_s - rpto_s) % rpti_s)
+end
+
+-- From rwtime, get the next time that is divisible by rpt_interval offset by rpt_offset
+function rwt.next_rpt(rwtime, rpt_interval, rpt_offset)
+ local rwtime_s = rwt.to_secs(rwtime)
+ local rpti_s = rwt.to_secs(rpt_interval)
+ local time_from_last = rwt.time_from_last_rpt(rwtime_s, rpti_s, rpt_offset)
+
+ local res_s = rwtime_s - time_from_last + rpti_s
+
+ return rwt.to_table(res_s)
+end
+
+-- from rwtime, get the last time that this rpt matched (which is actually just next_rpt - rpt_offset
+function rwt.last_rpt(rwtime, rpt_interval, rpt_offset)
+ local rwtime_s = rwt.to_sec(rwtime)
+ local rpti_s = rwt.to_sec(rpt_interval)
+ local time_from_last = rwt.time_from_last_rpt(rwtime, rpt_interval, rpt_offset)
+
+ local res_s = rwtime_s - time_from_last
+
+ return rwt.to_table(res_s)
+end
+
+
+advtrains.lines.rwt = rwt
diff --git a/advtrains_line_automation/readme.txt b/advtrains_line_automation/readme.txt
new file mode 100644
index 0000000..3280ce9
--- /dev/null
+++ b/advtrains_line_automation/readme.txt
@@ -0,0 +1,5 @@
+== advtrains_line_automation
+This mod provides an extension to the interlocking system which allows to automatically operate trains on train lines.
+
+This extension makes use of the table
+advtrains.lines \ No newline at end of file
diff --git a/advtrains_line_automation/scheduler.lua b/advtrains_line_automation/scheduler.lua
new file mode 100644
index 0000000..6025b02
--- /dev/null
+++ b/advtrains_line_automation/scheduler.lua
@@ -0,0 +1,133 @@
+-- scheduler.lua
+-- Implementation of a Railway time schedule queue
+-- In contrast to the LuaATC interrupt queue, this one can handle many different
+-- event receivers. This is done by registering a callback with the scheduler
+
+local ln = advtrains.lines
+local sched = {}
+
+local UNITS_THRESH = 10
+local MAX_PER_ITER = 10
+
+local callbacks = {}
+
+-- Register a handler callback to handle scheduler items.
+-- e - a handler identifier (corresponds to "handler" in enqueue() )
+-- func - a function(evtdata) to be executed when a schedule item expires
+-- evtdata - arbitrary data that has been passed into enqueue()
+function sched.register_callback(e, func)
+ callbacks[e] = func
+end
+
+--[[
+{
+ t = <railway time in seconds>
+ e = <handler callback>
+ d = <data table>
+ u = <unit identifier>
+}
+The "unit identifier" is there to prevent schedule overflows. It can be, for example, the position hash
+of a node or a train ID. If the number of schedules for a unit exceeds UNITS_THRESH, further schedules are
+blocked.
+]]--
+local queue = {}
+
+local units_cnt = {}
+
+function sched.load(data)
+ if data then
+ for i,elem in ipairs(data) do
+ table.insert(queue, elem)
+ units_cnt[elem.u] = (units_cnt[elem.u] or 0) + 1
+ end
+ atlog("[lines][scheduler] Loaded the schedule queue,",#data,"items.")
+ end
+end
+function sched.save()
+ return queue
+end
+
+function sched.run()
+ local ctime = ln.rwt.get_time()
+ local cnt = 0
+ local ucn, elem
+ while cnt <= MAX_PER_ITER do
+ elem = queue[1]
+ if elem and elem.t <= ctime then
+ table.remove(queue, 1)
+ if callbacks[elem.e] then
+ -- run it
+ callbacks[elem.e](elem.d)
+ else
+ atwarn("[lines][scheduler] No callback to handle schedule",elem)
+ end
+ cnt=cnt+1
+ ucn = units_cnt[elem.u]
+ if ucn and ucn>0 then
+ units_cnt[elem.u] = ucn - 1
+ end
+ else
+ break
+ end
+ end
+end
+
+-- Enqueue a new scheduled item to be executed at "rwtime"
+-- handler: a string identifying the handler to use (registered with sched.register_callback())
+-- evtdata: Arbitrary Lua data to be passed to the handler callback
+-- unitid: An arbitrary string uniquely identifying the thing that is issuing this enqueue.
+-- used to prevent expotentially growing "scheduler bombs"
+-- unitlim: Custom override for UNITS_THRESH (see there)
+function sched.enqueue(rwtime, handler, evtdata, unitid, unitlim)
+ local qtime = ln.rwt.to_secs(rwtime)
+ assert(type(handler)=="string")
+ assert(type(unitid)=="string")
+ assert(type(unitlim)=="number")
+
+ local cnt=1
+ local ucn, elem
+
+ ucn = (units_cnt[unitid] or 0)
+ local ulim=(unitlim or UNITS_THRESH)
+ if ucn >= ulim then
+ atlog("[lines][scheduler] discarding enqueue for",handler,"(limit",ulim,") because unit",unitid,"has already",ucn,"schedules enqueued")
+ return false
+ end
+
+ while true do
+ elem = queue[cnt]
+ if not elem or elem.t > qtime then
+ table.insert(queue, cnt, {
+ t=qtime,
+ e=handler,
+ d=evtdata,
+ u=unitid,
+ })
+ units_cnt[unitid] = ucn + 1
+ return true
+ end
+ cnt = cnt+1
+ end
+end
+
+-- See enqueue(). Same meaning, except that rwtime is relative to now.
+function sched.enqueue_in(rwtime, handler, evtdata, unitid, unitlim)
+ local ctime = ln.rwt.get_time()
+ local rwtime_s = ln.rwt.to_secs(rwtime)
+ sched.enqueue(ctime + rwtime_s, handler, evtdata, unitid, unitlim)
+end
+
+-- Discards all schedules for unit "unitid" (removes them from the queue)
+function sched.discard_all(unitid)
+ local i = 1
+ while i<=#queue do
+ if queue[i].u == unitid then
+ table.remove(queue,i)
+ else
+ i=i+1
+ end
+ end
+ units_cnt[unitid] = 0
+end
+
+ln.sched = sched
diff --git a/advtrains_line_automation/settingtypes.txt b/advtrains_line_automation/settingtypes.txt
new file mode 100644
index 0000000..ea3f66e
--- /dev/null
+++ b/advtrains_line_automation/settingtypes.txt
@@ -0,0 +1,6 @@
+# This controls how the Railway Time relates to real-world time:
+# *independent - RWT counts independent of real time synchronized to ingame steps. Recommended for singleplayer and ad-hoc servers.
+# *follow_real - RWT is independent of real time, but counts up in real-world time speed
+# *adapt_real - RWT adapts its minute and second to real-world time. When the server is stopped and restarted, this results in a "time jump".
+# Time will jump for 1 cycle (59;59) at maximum.
+advtrains_lines_rwt_realtime (RWT real time adaption) enum independent independent,follow_real,adapt_real \ No newline at end of file
diff --git a/advtrains_line_automation/stoprail.lua b/advtrains_line_automation/stoprail.lua
new file mode 100644
index 0000000..9c1470a
--- /dev/null
+++ b/advtrains_line_automation/stoprail.lua
@@ -0,0 +1,229 @@
+-- stoprail.lua
+-- adds "stop rail". Recognized by lzb. (part of behavior is implemented there)
+
+
+local function to_int(n)
+ --- Disallow floating-point numbers
+ local k = tonumber(n)
+ if k then
+ return math.floor(k)
+ end
+end
+
+local function updatemeta(pos)
+ local meta = minetest.get_meta(pos)
+ local pe = advtrains.encode_pos(pos)
+ local stdata = advtrains.lines.stops[pe]
+ if not stdata then
+ meta:set_string("infotext", "Error")
+ end
+
+ meta:set_string("infotext", "Stn. "..stdata.stn.." T. "..stdata.track)
+end
+
+local door_dropdown = {L=1, R=2, C=3}
+local door_dropdown_rev = {Right="R", Left="L", Closed="C"}
+
+local function show_stoprailform(pos, player)
+ local pe = advtrains.encode_pos(pos)
+ local pname = player:get_player_name()
+ if minetest.is_protected(pos, pname) then
+ minetest.chat_send_player(pname, "Position is protected!")
+ return
+ end
+
+ local stdata = advtrains.lines.stops[pe]
+ if not stdata then
+ advtrains.lines.stops[pe] = {
+ stn="", track="", doors="R", wait=10, ars={default=true}, ddelay=1,speed="M"
+ }
+ stdata = advtrains.lines.stops[pe]
+ end
+
+ local stn = advtrains.lines.stations[stdata.stn]
+ local stnname = stn and stn.name or ""
+ if not stdata.ddelay then
+ stdata.ddelay = 1
+ end
+ if not stdata.speed then
+ stdata.speed = "M"
+ end
+
+ local form = "size[8,7]"
+ form = form.."field[0.5,0.5;7,1;stn;"..attrans("Station Code")..";"..minetest.formspec_escape(stdata.stn).."]"
+ form = form.."field[0.5,1.5;7,1;stnname;"..attrans("Station Name")..";"..minetest.formspec_escape(stnname).."]"
+ form = form.."field[0.5,2.5;1.5,1;ddelay;"..attrans("Door Delay")..";"..minetest.formspec_escape(stdata.ddelay).."]"
+ form = form.."field[2,2.5;2,1;speed;"..attrans("Departure Speed")..";"..minetest.formspec_escape(stdata.speed).."]"
+ form = form.."checkbox[5,1.75;reverse;"..attrans("Reverse train")..";"..(stdata.reverse and "true" or "false").."]"
+ form = form.."checkbox[5,2.0;kick;"..attrans("Kick out passengers")..";"..(stdata.kick and "true" or "false").."]"
+ form = form.."label[0.5,3;Door side:]"
+ form = form.."dropdown[0.5,3;2;doors;Left,Right,Closed;"..door_dropdown[stdata.doors].."]"
+ form = form.."field[5,3.5;2,1;track;"..attrans("Track")..";"..minetest.formspec_escape(stdata.track).."]"
+ form = form.."field[5,4.5;2,1;wait;"..attrans("Stop Time")..";"..stdata.wait.."]"
+
+ form = form.."textarea[0.5,4;4,2;ars;Trains stopping here (ARS rules);"..advtrains.interlocking.ars_to_text(stdata.ars).."]"
+
+ form = form.."button[0.5,6;7,1;save;"..attrans("Save").."]"
+
+ minetest.show_formspec(pname, "at_lines_stop_"..pe, form)
+end
+local tmp_checkboxes = {}
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ local pname = player:get_player_name()
+ local pe = string.match(formname, "^at_lines_stop_(............)$")
+ local pos = advtrains.decode_pos(pe)
+ if pos then
+ if minetest.is_protected(pos, pname) then
+ minetest.chat_send_player(pname, "Position is protected!")
+ return
+ end
+
+ local stdata = advtrains.lines.stops[pe]
+ if not tmp_checkboxes[pe] then
+ tmp_checkboxes[pe] = {}
+ end
+ if fields.kick then -- handle checkboxes due to MT's weird handling
+ tmp_checkboxes[pe].kick = (fields.kick == "true")
+ end
+ if fields.reverse then
+ tmp_checkboxes[pe].reverse = (fields.reverse == "true")
+ end
+ if fields.save then
+ if fields.stn and stdata.stn ~= fields.stn then
+ if fields.stn ~= "" then
+ local stn = advtrains.lines.stations[fields.stn]
+ if stn then
+ if (stn.owner == pname or minetest.check_player_privs(pname, "train_admin")) then
+ stdata.stn = fields.stn
+ else
+ minetest.chat_send_player(pname, "Station code '"..fields.stn.."' does already exist and is owned by "..stn.owner)
+ end
+ else
+ advtrains.lines.stations[fields.stn] = {name = fields.stnname, owner = pname}
+ stdata.stn = fields.stn
+ end
+ end
+ updatemeta(pos)
+ show_stoprailform(pos, player)
+ return
+ end
+ local stn = advtrains.lines.stations[stdata.stn]
+ if stn and fields.stnname and fields.stnname ~= stn.name then
+ if (stn.owner == pname or minetest.check_player_privs(pname, "train_admin")) then
+ stn.name = fields.stnname
+ else
+ minetest.chat_send_player(pname, "Not allowed to edit station name, owned by "..stn.owner)
+ end
+ end
+
+ -- dropdowns
+ if fields.doors then
+ stdata.doors = door_dropdown_rev[fields.doors] or "C"
+ end
+
+ if fields.track then
+ stdata.track = fields.track
+ end
+ if fields.wait then
+ stdata.wait = to_int(fields.wait) or 10
+ end
+
+ if fields.ars then
+ stdata.ars = advtrains.interlocking.text_to_ars(fields.ars)
+ end
+
+ if fields.ddelay then
+ stdata.ddelay = to_int(fields.ddelay) or 1
+ end
+ if fields.speed then
+ stdata.speed = to_int(fields.speed) or "M"
+ end
+
+ for k,v in pairs(tmp_checkboxes[pe]) do --handle checkboxes
+ stdata[k] = v or nil
+ end
+ tmp_checkboxes[pe] = nil
+ --TODO: signal
+ updatemeta(pos)
+ show_stoprailform(pos, player)
+ end
+ end
+
+end)
+
+
+local adefunc = function(def, preset, suffix, rotation)
+ return {
+ after_place_node=function(pos)
+ local pe = advtrains.encode_pos(pos)
+ advtrains.lines.stops[pe] = {
+ stn="", track="", doors="R", wait=10
+ }
+ updatemeta(pos)
+ end,
+ after_dig_node=function(pos)
+ local pe = advtrains.encode_pos(pos)
+ advtrains.lines.stops[pe] = nil
+ end,
+ on_rightclick = function(pos, node, player)
+ show_stoprailform(pos, player)
+ end,
+ advtrains = {
+ on_train_approach = function(pos,train_id, train, index, has_entered)
+ if has_entered then return end -- do not stop again!
+ if train.path_cn[index] == 1 then
+ local pe = advtrains.encode_pos(pos)
+ local stdata = advtrains.lines.stops[pe]
+ if stdata and stdata.stn then
+
+ --TODO REMOVE AFTER SOME TIME (only migration)
+ if not stdata.ars then
+ stdata.ars = {default=true}
+ end
+ if stdata.ars and (stdata.ars.default or advtrains.interlocking.ars_check_rule_match(stdata.ars, train) ) then
+ advtrains.lzb_add_checkpoint(train, index, 2, nil)
+ local stn = advtrains.lines.stations[stdata.stn]
+ local stnname = stn and stn.name or "Unknown Station"
+ train.text_inside = "Next Stop:\n"..stnname
+ advtrains.interlocking.ars_set_disable(train, true)
+ end
+ end
+ end
+ end,
+ on_train_enter = function(pos, train_id, train, index)
+ if train.path_cn[index] == 1 then
+ local pe = advtrains.encode_pos(pos)
+ local stdata = advtrains.lines.stops[pe]
+ if not stdata then
+ return
+ end
+
+ if stdata.ars and (stdata.ars.default or advtrains.interlocking.ars_check_rule_match(stdata.ars, train) ) then
+ local stn = advtrains.lines.stations[stdata.stn]
+ local stnname = stn and stn.name or "Unknown Station"
+
+ -- Send ATC command and set text
+ advtrains.atc.train_set_command(train, "B0 W O"..stdata.doors..(stdata.kick and "K" or "").." D"..stdata.wait.." OC "..(stdata.reverse and "R" or "").."D"..(stdata.ddelay or 1) .. " A1 S" ..(stdata.speed or "M"), true)
+ train.text_inside = stnname
+ if tonumber(stdata.wait) then
+ minetest.after(tonumber(stdata.wait), function() train.text_inside = "" end)
+ end
+ end
+ end
+ end
+ },
+ }
+end
+
+if minetest.get_modpath("advtrains_train_track") ~= nil then
+ advtrains.register_tracks("default", {
+ nodename_prefix="advtrains_line_automation:dtrack_stop",
+ texture_prefix="advtrains_dtrack_stop",
+ models_prefix="advtrains_dtrack",
+ models_suffix=".b3d",
+ shared_texture="advtrains_dtrack_shared_stop.png",
+ description="Station/Stop Rail",
+ formats={},
+ get_additional_definiton = adefunc,
+ }, advtrains.trackpresets.t_30deg_straightonly)
+end
diff --git a/advtrains_line_automation/textures/advtrains_dtrack_shared_stop.png b/advtrains_line_automation/textures/advtrains_dtrack_shared_stop.png
new file mode 100644
index 0000000..b6629cf
--- /dev/null
+++ b/advtrains_line_automation/textures/advtrains_dtrack_shared_stop.png
Binary files differ
diff --git a/advtrains_line_automation/textures/advtrains_dtrack_stop_placer.png b/advtrains_line_automation/textures/advtrains_dtrack_stop_placer.png
new file mode 100644
index 0000000..0d1c769
--- /dev/null
+++ b/advtrains_line_automation/textures/advtrains_dtrack_stop_placer.png
Binary files differ