From b19033b224f4f8ec33f10ba40327f1d811c04fbb Mon Sep 17 00:00:00 2001
From: orwell96 <mono96.mml@gmail.com>
Date: Thu, 2 Feb 2017 16:40:51 +0100
Subject: LuaAutomation - Basic component implementation Implements the base
 code for LuaAutomation, an ATC rail and a punch-operated 'operation panel' as
 well as interface for passive components. Changes in advtrains code where
 neccessary. Supported passive components are light signals, switches and
 mesecon switches

---
 advtrains/advtrains/init.lua                       |   8 +-
 advtrains/advtrains/tracks.lua                     |   1 +
 .../advtrains_luaautomation/active_common.lua      | 118 ++++++++++
 advtrains/advtrains_luaautomation/atc_rail.lua     |  92 ++++++++
 advtrains/advtrains_luaautomation/chatcmds.lua     |   0
 advtrains/advtrains_luaautomation/depends.txt      |   2 +
 advtrains/advtrains_luaautomation/environment.lua  | 253 +++++++++++++++++++++
 advtrains/advtrains_luaautomation/init.lua         |  98 ++++++++
 advtrains/advtrains_luaautomation/interrupt.lua    |  48 ++++
 .../advtrains_luaautomation/operation_panel.lua    |  23 ++
 advtrains/advtrains_luaautomation/p_display.lua    |   0
 .../advtrains_luaautomation/p_mesecon_iface.lua    |  60 +++++
 advtrains/advtrains_luaautomation/passive.lua      |  29 +++
 advtrains/advtrains_luaautomation/passive_api.txt  |  23 ++
 .../textures/atlatc_oppanel.png                    | Bin 0 -> 631 bytes
 15 files changed, 753 insertions(+), 2 deletions(-)
 create mode 100644 advtrains/advtrains_luaautomation/active_common.lua
 create mode 100644 advtrains/advtrains_luaautomation/atc_rail.lua
 create mode 100644 advtrains/advtrains_luaautomation/chatcmds.lua
 create mode 100644 advtrains/advtrains_luaautomation/depends.txt
 create mode 100644 advtrains/advtrains_luaautomation/environment.lua
 create mode 100644 advtrains/advtrains_luaautomation/init.lua
 create mode 100644 advtrains/advtrains_luaautomation/interrupt.lua
 create mode 100644 advtrains/advtrains_luaautomation/operation_panel.lua
 create mode 100644 advtrains/advtrains_luaautomation/p_display.lua
 create mode 100644 advtrains/advtrains_luaautomation/p_mesecon_iface.lua
 create mode 100644 advtrains/advtrains_luaautomation/passive.lua
 create mode 100644 advtrains/advtrains_luaautomation/passive_api.txt
 create mode 100644 advtrains/advtrains_luaautomation/textures/atlatc_oppanel.png

(limited to 'advtrains')

diff --git a/advtrains/advtrains/init.lua b/advtrains/advtrains/init.lua
index 536293d..ad50dfe 100644
--- a/advtrains/advtrains/init.lua
+++ b/advtrains/advtrains/init.lua
@@ -11,7 +11,7 @@ advtrains = {trains={}, wagon_save={}}
 
 advtrains.modpath = minetest.get_modpath("advtrains")
 
-local function print_concat_table(a)
+function advtrains.print_concat_table(a)
 	local str=""
 	local stra=""
 	for i=1,50 do
@@ -43,7 +43,11 @@ local function print_concat_table(a)
 end
 atprint=function() end
 if minetest.setting_getbool("advtrains_debug") then
-	atprint=function(t, ...) minetest.log("action", "[advtrains]"..print_concat_table({t, ...})) minetest.chat_send_all("[advtrains]"..print_concat_table({t, ...})) end
+	atprint=function(t, ...)
+		local text=advtrains.print_concat_table({t, ...})
+		minetest.log("action", "[advtrains]"..text)
+		minetest.chat_send_all("[advtrains]"..text)
+	end
 end
 sid=function(id) return string.sub(id, -4) end
 
diff --git a/advtrains/advtrains/tracks.lua b/advtrains/advtrains/tracks.lua
index cde2b35..a63ff4d 100644
--- a/advtrains/advtrains/tracks.lua
+++ b/advtrains/advtrains/tracks.lua
@@ -250,6 +250,7 @@ function advtrains.register_tracks(tracktype, def, preset)
 			if newstate~=is_state then
 				advtrains.ndb.swap_node(pos, {name=def.nodename_prefix.."_"..suffix_target, param2=node.param2})
 			end
+			advtrains.invalidate_all_paths()
 		end
 		local mesec
 		if mesecon_state then -- if mesecons is not wanted, do not.
diff --git a/advtrains/advtrains_luaautomation/active_common.lua b/advtrains/advtrains_luaautomation/active_common.lua
new file mode 100644
index 0000000..b94a260
--- /dev/null
+++ b/advtrains/advtrains_luaautomation/active_common.lua
@@ -0,0 +1,118 @@
+
+
+local ac = {nodes={}}
+
+function ac.load(data)
+	ac.nodes=data and data.nodes or {}
+end
+function ac.save()
+	return {nodes = ac.nodes}
+end
+
+function ac.after_place_node(pos, player)
+	advtrains.ndb.update(pos)
+	local meta=minetest.get_meta(pos)
+	meta:set_string("formspec", ac.getform(pos, meta))
+	meta:set_string("infotext", "LuaAutomation component, unconfigured.")
+	local ph=minetest.hash_node_position(pos)
+	--just get first available key!
+	for en,_ in pairs(atlatc.envs) do
+		ac.nodes[ph]={env=en}
+		return
+	end
+end
+function ac.getform(pos, meta_p)
+	local meta = meta_p or minetest.get_meta(pos)
+	local envs_asvalues={}
+	
+	local ph=minetest.hash_node_position(pos)
+	local nodetbl = ac.nodes[ph]
+	local env, code, err = nil, "", ""
+	if nodetbl then
+		code=nodetbl.code or ""
+		err=nodetbl.err or ""
+		env=nodetbl.env or ""
+	end
+	local sel = 1
+	for n,_ in pairs(atlatc.envs) do
+		envs_asvalues[#envs_asvalues+1]=n
+		if n==env then
+			sel=#envs_asvalues
+		end
+	end
+	local form = "size[10,10]dropdown[0,0;3;env;"..table.concat(envs_asvalues, ",")..";"..sel.."]"
+		.."button[4,0;2,1;save;Save]button[7,0;2,1;cle;Clear local env] textarea[0.2,1;10,10;code;Code;"..minetest.formspec_escape(code).."]"
+		.."label[0,9.8;"..err.."]"
+	return form
+end
+
+function ac.after_dig_node(pos, node, player)
+	advtrains.invalidate_all_paths()
+	advtrains.ndb.clear(pos)
+	local ph=minetest.hash_node_position(pos)
+	ac.nodes[ph]=nil
+end
+
+function ac.on_receive_fields(pos, formname, fields, player)
+	if not minetest.check_player_privs(player:get_player_name(), {atlatc=true}) then
+		minetest.chat_send_player(player:get_player_name(), "Missing privilege: atlatc - Operation cancelled!")
+	end
+	
+	local meta=minetest.get_meta(pos)
+	local ph=minetest.hash_node_position(pos)
+	local nodetbl = ac.nodes[ph] or {}
+	--if fields.quit then return end
+	if fields.env then
+		nodetbl.env=fields.env
+	end
+	if fields.code then
+		nodetbl.code=fields.code
+	end
+	if fields.save then
+		nodetbl.err=nil
+	end
+	if fields.cle then
+		nodetbl.data={}
+	end
+	meta:set_string("formspec", ac.getform(pos, meta))
+	
+	ac.nodes[ph]=nodetbl
+	if nodetbl.env then
+		meta:set_string("infotext", "LuaAutomation component, assigned to environment '"..nodetbl.env.."'")
+	else
+		meta:set_string("infotext", "LuaAutomation component, invalid enviroment set!")
+	end
+end
+
+function ac.run_in_env(pos, evtdata, customfct)
+	local ph=minetest.hash_node_position(pos)
+	local nodetbl = ac.nodes[ph] or {}
+	
+	local meta
+	if minetest.get_node(pos) then
+		meta=minetest.get_meta(pos)
+	end
+	
+	if not nodetbl.env or not atlatc.envs[nodetbl.env] then
+		return false, "Not an existing environment: "..(nodetbl.env or "<nil>")
+	end
+	if not nodetbl.code or nodetbl.code=="" then
+		return false, "No code to run!"
+	end
+	
+	local datain=nodetbl.data or {}
+	local succ, dataout = atlatc.envs[nodetbl.env]:execute_code(datain, nodetbl.code, evtdata, customfct)
+	if succ then
+		atlatc.active.nodes[ph].data=atlatc.remove_invalid_data(dataout)
+	else
+		atlatc.active.nodes[ph].err=dataout
+		if meta then
+			meta:set_string("infotext", "LuaAutomation ATC interface rail, ERROR:"..dataout)
+		end
+	end
+	if meta then
+		meta:set_string("formspec", ac.getform(pos, meta))
+	end
+end
+
+atlatc.active=ac
diff --git a/advtrains/advtrains_luaautomation/atc_rail.lua b/advtrains/advtrains_luaautomation/atc_rail.lua
new file mode 100644
index 0000000..2af03cf
--- /dev/null
+++ b/advtrains/advtrains_luaautomation/atc_rail.lua
@@ -0,0 +1,92 @@
+-- atc_rail.lua
+-- registers and handles the ATC rail. Active component.
+-- This is the only component that can interface with trains, so train interface goes here too.
+
+--Using subtable
+local r={}
+
+function r.fire_event(pos, evtdata)
+	
+	local ph=minetest.hash_node_position(pos)
+	local railtbl = atlatc.active.nodes[ph] or {}
+	
+	local arrowconn = railtbl.arrowconn
+	
+	--prepare ingame API for ATC. Regenerate each time since pos needs to be known
+	local atc_valid, atc_arrow
+	local train_id=advtrains.detector.on_node[ph]
+	local train=advtrains.trains[train_id]
+	if not train then return false end
+	if not train.path then
+		--we happened to get in between an invalidation step
+		--delay
+		atlatc.interrupt.add(0,pos,evtdata)
+		return
+	end
+	for index, ppos in pairs(train.path) do
+		if vector.equals(advtrains.round_vector_floor_y(ppos), pos) then
+			atc_arrow =
+					vector.equals(
+							advtrains.dirCoordSet(pos, arrowconn),
+							advtrains.round_vector_floor_y(train.path[index+train.movedir])
+					)
+			atc_valid = true
+		end
+	end
+	local customfct={
+		atc_send = function(cmd)
+			advtrains.atc.train_reset_command(train_id)
+			if atc_valid then
+				train.atc_command=cmd
+				train.atc_arrow=atc_arrow
+				return atc_valid
+			end
+		end,
+		atc_reset = function(cmd)
+			advtrains.atc.train_reset_command(train_id)
+			return true
+		end,
+		atc_arrow = atc_arrow
+	}
+	
+	atlatc.active.run_in_env(pos, evtdata, customfct)
+	
+end
+
+advtrains.register_tracks("default", {
+	nodename_prefix="advtrains_luaautomation:dtrack",
+	texture_prefix="advtrains_dtrack_atc",
+	models_prefix="advtrains_dtrack_detector",
+	models_suffix=".b3d",
+	shared_texture="advtrains_dtrack_rail_atc.png",
+	description=atltrans("LuaAutomation ATC Rail"),
+	formats={},
+	get_additional_definiton = function(def, preset, suffix, rotation)
+		return {
+			after_place_node = atlatc.active.after_place_node,
+			after_dig_node = atlatc.active.after_dig_node,
+
+			on_receive_fields = function(pos, ...)
+				atlatc.active.on_receive_fields(pos, ...)
+				
+				--set arrowconn (for ATC)
+				local ph=minetest.hash_node_position(pos)
+				local _, conn1=advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
+				atlatc.active.nodes[ph].arrowconn=conn1
+			end,
+
+			advtrains = {
+				on_train_enter = function(pos, train_id)
+					--do async. Event is fired in train steps
+					atlatc.interrupt.add(0, pos, {type="train", id=train_id})
+				end,
+			},
+			luaautomation = {
+				fire_event=r.fire_event
+			}
+		}
+	end
+}, advtrains.trackpresets.t_30deg_straightonly)
+
+
+atlatc.rail = r
diff --git a/advtrains/advtrains_luaautomation/chatcmds.lua b/advtrains/advtrains_luaautomation/chatcmds.lua
new file mode 100644
index 0000000..e69de29
diff --git a/advtrains/advtrains_luaautomation/depends.txt b/advtrains/advtrains_luaautomation/depends.txt
new file mode 100644
index 0000000..366cad5
--- /dev/null
+++ b/advtrains/advtrains_luaautomation/depends.txt
@@ -0,0 +1,2 @@
+advtrains
+mesecons?
\ No newline at end of file
diff --git a/advtrains/advtrains_luaautomation/environment.lua b/advtrains/advtrains_luaautomation/environment.lua
new file mode 100644
index 0000000..d04563a
--- /dev/null
+++ b/advtrains/advtrains_luaautomation/environment.lua
@@ -0,0 +1,253 @@
+-------------
+-- lua sandboxed environment
+
+-- function to cross out functions and userdata.
+-- modified from dump()
+function atlatc.remove_invalid_data(o, nested)
+	if o==nil then return nil end
+	local valid_dt={["nil"]=true, boolean=true, number=true, string=true}
+	if type(o) ~= "table" then
+		--check valid data type
+		if not valid_dt[type(o)] then
+			return nil
+		end
+		return o
+	end
+	-- Contains table -> true/nil of currently nested tables
+	nested = nested or {}
+	if nested[o] then
+		return nil
+	end
+	nested[o] = true
+	for k, v in pairs(o) do
+		v = atlatc.remove_invalid_data(v, nested)
+	end
+	nested[o] = nil
+	return o
+end
+
+
+local env_proto={
+	load = function(self, envname, data)
+		self.name=envname
+		self.sdata=data.sdata and atlatc.remove_invalid_data(data.sdata) or {}
+		self.fdata={}
+		self.init_code=data.init_code or ""
+		self.step_code=data.step_code or ""
+	end,
+	save = function(self)
+		-- throw any function values out of the sdata table
+		self.sdata = atlatc.remove_invalid_data(self.sdata)
+		return {sdata = self.sdata, init_code=self.init_code, step_code=self.step_code}
+	end,
+}
+
+--Environment
+--Code modified from mesecons_luacontroller (credit goes to Jeija and mesecons contributors)
+
+local safe_globals = {
+	"assert", "error", "ipairs", "next", "pairs", "select",
+	"tonumber", "tostring", "type", "unpack", "_VERSION"
+}
+
+--print is actually minetest.chat_send_all()
+--using advtrains.print_concat_table because it's cool
+local function safe_print(t, ...)
+	local str=advtrains.print_concat_table({t, ...})
+	minetest.log("action", "[atlatc] "..str)
+	minetest.chat_send_all(str)
+end
+
+local function safe_date()
+	return(os.date("*t",os.time()))
+end
+
+-- string.rep(str, n) with a high value for n can be used to DoS
+-- the server. Therefore, limit max. length of generated string.
+local function safe_string_rep(str, n)
+	if #str * n > 2000 then
+		debug.sethook() -- Clear hook
+		error("string.rep: string length overflow", 2)
+	end
+
+	return string.rep(str, n)
+end
+
+-- string.find with a pattern can be used to DoS the server.
+-- Therefore, limit string.find to patternless matching.
+local function safe_string_find(...)
+	if (select(4, ...)) ~= true then
+		debug.sethook() -- Clear hook
+		error("string.find: 'plain' (fourth parameter) must always be true for security reasons.")
+	end
+
+	return string.find(...)
+end
+
+local mp=minetest.get_modpath("advtrains_luaautomation")
+local p_api_getstate, p_api_setstate = dofile(mp.."/passive.lua")
+
+local static_env = {
+	--core LUA functions
+	print = safe_print,
+	string = {
+		byte = string.byte,
+		char = string.char,
+		format = string.format,
+		len = string.len,
+		lower = string.lower,
+		upper = string.upper,
+		rep = safe_string_rep,
+		reverse = string.reverse,
+		sub = string.sub,
+		find = safe_string_find,
+	},
+	math = {
+		abs = math.abs,
+		acos = math.acos,
+		asin = math.asin,
+		atan = math.atan,
+		atan2 = math.atan2,
+		ceil = math.ceil,
+		cos = math.cos,
+		cosh = math.cosh,
+		deg = math.deg,
+		exp = math.exp,
+		floor = math.floor,
+		fmod = math.fmod,
+		frexp = math.frexp,
+		huge = math.huge,
+		ldexp = math.ldexp,
+		log = math.log,
+		log10 = math.log10,
+		max = math.max,
+		min = math.min,
+		modf = math.modf,
+		pi = math.pi,
+		pow = math.pow,
+		rad = math.rad,
+		random = math.random,
+		sin = math.sin,
+		sinh = math.sinh,
+		sqrt = math.sqrt,
+		tan = math.tan,
+		tanh = math.tanh,
+	},
+	table = {
+		concat = table.concat,
+		insert = table.insert,
+		maxn = table.maxn,
+		remove = table.remove,
+		sort = table.sort,
+	},
+	os = {
+		clock = os.clock,
+		difftime = os.difftime,
+		time = os.time,
+		date = safe_date,
+	},
+	POS = function(x,y,z) return {x=x, y=y, z=z} end,
+	getstate = p_api_getstate,
+	setstate = p_api_setstate,
+	
+}
+
+for _, name in pairs(safe_globals) do
+	static_env[name] = _G[name]
+end
+
+
+--The environment all code calls get is a table that has set static_env as metatable.
+--In general, every variable is local to a single code chunk, but kept persistent over code re-runs. Data is also saved, but functions and userdata and circular references are removed
+--Init code and step code's environments are not saved
+-- S - Table that can contain any save data global to the environment. Will be saved statically. Can't contain functions or userdata or circular references.
+-- F - Table global to the environment, can contain volatile data that is deleted when server quits.
+--     The init code should populate this table with functions and other definitions.
+
+-- returns: true, fenv if successful; nil, error if error 
+function env_proto:execute_code(fenv, code, evtdata, customfct)
+	local metatbl ={
+		__index = function(t, i)
+			print("index metamethod:",i)
+			if i=="S" then
+				return self.sdata
+			elseif i=="F" then
+				return self.fdata
+			elseif i=="event" then
+				return evtdata
+			elseif customfct and customfct[i] then
+				return customfct[i]
+			end
+			return static_env[i]
+		end,
+		__newindex = function(t, i, v)
+			if i=="S" or i=="F" or i=="event" or (customfct and customfct[i]) or static_env[i] then
+				debug.sethook()
+				error("Trying to overwrite environment contents")
+			end
+			rawset(t,i,v)
+		end,
+	}
+	setmetatable(fenv, metatbl)
+	local fun, err=loadstring(code)
+	if not fun then
+		return false, err
+	end
+	setfenv(fun, fenv)
+	local succ, data = pcall(fun)
+	if succ then
+		data=fenv
+	end
+	return succ, data
+end
+
+function env_proto:run_initcode()
+	if self.init_code and self.init_code~="" then
+		local succ, err = self:execute_code(self.init_code, nil, {}, "Global init code")
+		if not succ then
+			--TODO
+		end
+	end
+end
+function env_proto:run_stepcode()
+	if self.step_code and self.step_code~="" then
+		local succ, err = self:execute_code({}, self.step_code, nil, {})
+		if not succ then
+			--TODO
+		end
+	end
+end
+
+--- class interface
+
+function atlatc.env_new(name)
+	local newenv={
+		name=name,
+		init_code="",
+		step_code="",
+		sdata={}
+	}
+	setmetatable(newenv, {__index=env_proto})
+	return newenv
+end
+function atlatc.env_load(name, data)
+	local newenv={}
+	setmetatable(newenv, {__index=env_proto})
+	newenv:load(name, data)
+	return newenv
+end
+
+function atlatc.run_initcode()
+	for envname, env in pairs(atlatc.envs) do
+		env:run_initcode()
+	end
+end
+function atlatc.run_stepcode()
+	for envname, env in pairs(atlatc.envs) do
+		env:run_stepcode()
+	end
+end
+
+
+
+
diff --git a/advtrains/advtrains_luaautomation/init.lua b/advtrains/advtrains_luaautomation/init.lua
new file mode 100644
index 0000000..12e1b1d
--- /dev/null
+++ b/advtrains/advtrains_luaautomation/init.lua
@@ -0,0 +1,98 @@
+-- advtrains_luaautomation/init.lua
+-- Lua automation features for advtrains
+-- Uses global table 'atlatc' (AdvTrains_LuaATC)
+
+-- Boilerplate to support localized strings if intllib mod is installed.
+if minetest.get_modpath("intllib") then
+    atltrans = intllib.Getter()
+else
+    atltrans = function(s,a,...)a={a,...}return s:gsub("@(%d+)",function(n)return a[tonumber(n)]end)end
+end
+
+--Privilege
+--Only trusted players should be enabled to build stuff which can break the server.
+
+atlatc = { envs = {}}
+
+minetest.register_privilege("atlatc", { description = "Player can place and modify LUA ATC components. Grant with care! Allows to execute bad LUA code.", give_to_singleplayer = false, default= false })
+
+local mp=minetest.get_modpath("advtrains_luaautomation")
+if not mp then
+	error("Mod name error: Mod folder is not named 'advtrains_luaautomation'!")
+end
+dofile(mp.."/environment.lua")
+dofile(mp.."/interrupt.lua")
+dofile(mp.."/active_common.lua")
+dofile(mp.."/atc_rail.lua")
+dofile(mp.."/operation_panel.lua")
+dofile(mp.."/p_mesecon_iface.lua")
+
+local filename=minetest.get_worldpath().."/advtrains_luaautomation"
+local file, err = io.open(filename, "r")
+if not file then
+	minetest.log("error", " Failed to read advtrains_luaautomation save data from file "..filename..": "..(err or "Unknown Error"))
+else
+	local tbl = minetest.deserialize(file:read("*a"))
+	if type(tbl) == "table" then
+		if tbl.version==1 then
+			for envname, data in pairs(tbl.envs) do
+				atlatc.envs[envname]=atlatc.env_load(envname, data)
+			end
+			atlatc.active.load(tbl.active)
+			atlatc.interrupt.load(tbl.interrupt)
+		end
+	else
+		minetest.log("error", " Failed to read advtrains_luaautomation save data from file "..filename..": Not a table!")
+	end
+	file:close()
+end
+
+-- run init code of all environments
+atlatc.run_initcode()
+
+atlatc.save = function()
+	--versions:
+	-- 1 - Initial save format.
+	
+	local envdata={}
+	for envname, env in pairs(atlatc.envs) do
+		envdata[envname]=env:save()
+	end
+	local save_tbl={
+		version = 1,
+		envs=envdata,
+		active = atlatc.active.save(),
+		interrupt = atlatc.interrupt.save(),
+	}
+	
+	local datastr = minetest.serialize(save_tbl)
+	if not datastr then
+		minetest.log("error", " Failed to save advtrains_luaautomation save data to file "..filename..": Can't serialize!")
+		return
+	end
+	local file, err = io.open(filename, "w")
+	if err then
+		minetest.log("error", " Failed to save advtrains_luaautomation save data to file "..filename..": "..(err or "Unknown Error"))
+		return
+	end
+	file:write(datastr)
+	file:close()
+end
+
+-- globalstep for step code
+local timer, step_int=0, 2
+local stimer, sstep_int=0, 10
+
+minetest.register_globalstep(function(dtime)
+	timer=timer+dtime
+	if timer>step_int then
+		timer=0
+		atlatc.run_stepcode()
+	end
+	stimer=stimer+dtime
+	if stimer>sstep_int then
+		stimer=0
+		atlatc.save()
+	end
+end)
+minetest.register_on_shutdown(atlatc.save)
diff --git a/advtrains/advtrains_luaautomation/interrupt.lua b/advtrains/advtrains_luaautomation/interrupt.lua
new file mode 100644
index 0000000..e9ad443
--- /dev/null
+++ b/advtrains/advtrains_luaautomation/interrupt.lua
@@ -0,0 +1,48 @@
+-- interrupt.lua
+-- implements interrupt queue
+
+--to be saved: pos and evtdata
+local iq={}
+local queue={}
+local timer=0
+local run=false
+
+function iq.load(data)
+	local d=data or {}
+	queue = d.queue or {}
+	timer = d.timer or 0
+end
+function iq.save()
+	return {queue = queue}
+end
+
+function iq.add(t, pos, evtdata)
+	queue[#queue+1]={t=t+timer, p=pos, e=evtdata}
+	run=true
+end
+
+minetest.register_globalstep(function(dtime)
+	if run then
+		timer=timer + math.min(dtime, 0.2)
+		for i=1,#queue do
+			local qe=queue[i]
+			if not qe then
+				table.remove(queue, i)
+				i=i-1
+			elseif timer>qe.t then
+				local pos, evtdata=queue[i].p, queue[i].e
+				local node=advtrains.ndb.get_node(pos)
+				local ndef=minetest.registered_nodes[node.name]
+				if ndef and ndef.luaautomation and ndef.luaautomation.fire_event then
+					ndef.luaautomation.fire_event(pos, evtdata)
+				end
+				table.remove(queue, i)
+				i=i-1
+			end
+		end
+	end
+end)
+
+
+
+atlatc.interrupt=iq
diff --git a/advtrains/advtrains_luaautomation/operation_panel.lua b/advtrains/advtrains_luaautomation/operation_panel.lua
new file mode 100644
index 0000000..1d585f7
--- /dev/null
+++ b/advtrains/advtrains_luaautomation/operation_panel.lua
@@ -0,0 +1,23 @@
+
+local function on_punch(pos, player)
+	atlatc.interrupt.add(0, pos, {type="punch", punch=true})
+end
+
+
+minetest.register_node("advtrains_luaautomation:oppanel", {
+	drawtype = "normal",
+	tiles={"atlatc_oppanel.png"},
+	description = "LuaAutomation operation panel",
+	groups = {
+		choppy = 1,
+		save_in_nodedb=1,
+	},
+	after_place_node = atlatc.active.after_place_node,
+	after_dig_node = atlatc.active.after_dig_node,
+	on_receive_fields = atlatc.active.on_receive_fields,
+	on_punch = on_punch,
+	luaautomation = {
+		fire_event=atlatc.active.run_in_env
+	}
+	
+})
diff --git a/advtrains/advtrains_luaautomation/p_display.lua b/advtrains/advtrains_luaautomation/p_display.lua
new file mode 100644
index 0000000..e69de29
diff --git a/advtrains/advtrains_luaautomation/p_mesecon_iface.lua b/advtrains/advtrains_luaautomation/p_mesecon_iface.lua
new file mode 100644
index 0000000..d7e1052
--- /dev/null
+++ b/advtrains/advtrains_luaautomation/p_mesecon_iface.lua
@@ -0,0 +1,60 @@
+-- p_mesecon_iface.lua
+-- Mesecons interface by overriding the switch
+
+if not mesecon then return end
+
+minetest.override_item("mesecons_switch:mesecon_switch_off", {
+	groups = {
+		dig_immediate=2,
+		save_in_nodedb=1,
+	},
+	on_rightclick = function (pos, node)
+		if(mesecon.flipstate(pos, node) == "on") then
+			mesecon.receptor_on(pos)
+		else
+			mesecon.receptor_off(pos)
+		end
+		minetest.sound_play("mesecons_switch", {pos=pos})
+		advtrains.ndb.update(pos, node)
+	end,
+	on_updated_from_nodedb = function(pos, node)
+		mesecon.receptor_off(pos)
+	end,
+	luaautomation = {
+		getstate = "off",
+		setstate = function(pos, node, newstate)
+			if newstate=="on" then
+				advtrains.ndb.swap_node(pos, {name="mesecons_switch:mesecon_switch_on", param2=node.param2})
+				mesecon.receptor_on(pos)
+			end
+		end,
+	},
+})
+
+minetest.override_item("mesecons_switch:mesecon_switch_on", {
+	groups = {
+		dig_immediate=2,
+		save_in_nodedb=1,
+	},
+	on_rightclick = function (pos, node)
+		if(mesecon.flipstate(pos, node) == "on") then
+			mesecon.receptor_on(pos)
+		else
+			mesecon.receptor_off(pos)
+		end
+		minetest.sound_play("mesecons_switch", {pos=pos})
+		advtrains.ndb.update(pos, node)
+	end,
+	on_updated_from_nodedb = function(pos, node)
+		mesecon.receptor_on(pos)
+	end,
+	luaautomation = {
+		getstate = "on",
+		setstate = function(pos, node, newstate)
+			if newstate=="off" then
+				advtrains.ndb.swap_node(pos, {name="mesecons_switch:mesecon_switch_off", param2=node.param2})
+				mesecon.receptor_off(pos)
+			end
+		end,
+	},
+})
diff --git a/advtrains/advtrains_luaautomation/passive.lua b/advtrains/advtrains_luaautomation/passive.lua
new file mode 100644
index 0000000..78b8c2d
--- /dev/null
+++ b/advtrains/advtrains_luaautomation/passive.lua
@@ -0,0 +1,29 @@
+-- passive.lua
+-- API to passive components, as described in passive_api.txt
+
+local function getstate(pos)
+	local node=advtrains.ndb.get_node(pos)
+	local ndef=minetest.registered_nodes[node.name]
+	if ndef and ndef.luaautomation and ndef.luaautomation.getstate then
+		local st=ndef.luaautomation.getstate
+		if type(st)=="function" then
+			return st(pos, node)
+		else
+			return st
+		end
+	end
+	return nil
+end
+
+local function setstate(pos, newstate)
+	local node=advtrains.ndb.get_node(pos)
+	local ndef=minetest.registered_nodes[node.name]
+	if ndef and ndef.luaautomation and ndef.luaautomation.setstate then
+		local st=ndef.luaautomation.setstate
+		st(pos, node, newstate)
+	end
+end
+
+-- gets called from environment.lua
+-- return the values here to keep them local
+return getstate, setstate
diff --git a/advtrains/advtrains_luaautomation/passive_api.txt b/advtrains/advtrains_luaautomation/passive_api.txt
new file mode 100644
index 0000000..a735208
--- /dev/null
+++ b/advtrains/advtrains_luaautomation/passive_api.txt
@@ -0,0 +1,23 @@
+Lua Automation - Passive Component API
+
+Passive components are nodes that do not have code running in them. However, active components can query these and request actions from them. Examples:
+Switches
+Signals
+Displays
+Mesecon Transmitter
+
+All passive components have a table called 'luaautomation' in their node definition and have the group 'save_in_nodedb' set, so they work in unloaded chunks.
+Example for a switch:
+luaautomation = {
+	getstate = function(pos, node)
+		return "st"
+	end,
+	-- OR
+	getstate = "st",
+
+	setstate = function(pos, node, newstate)
+		if newstate=="cr" then
+			advtrains.ndb.swap_node(pos, <corresponding switch alt>)
+		end
+	end
+}
\ No newline at end of file
diff --git a/advtrains/advtrains_luaautomation/textures/atlatc_oppanel.png b/advtrains/advtrains_luaautomation/textures/atlatc_oppanel.png
new file mode 100644
index 0000000..96eb30e
Binary files /dev/null and b/advtrains/advtrains_luaautomation/textures/atlatc_oppanel.png differ
-- 
cgit v1.2.3