l; charset=UTF-8 Last-Modified: Thu, 22 Oct 2026 06:24:15 GMT Expires: Sun, 19 Oct 2036 06:24:15 GMT passive.lua « advtrains - advtrains.git - Advtrains mod for minetest.
aboutsummaryrefslogtreecommitdiff
path: root/advtrains/passive.lua
blob: fe4790cd92db177f121897e65e5de36bf477ab95 (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
-- passive.lua
-- API to passive components, as described in passive_api.txt of advtrains_luaautomation
-- This has been moved to the advtrains core in turn with the interlocking system,
-- to prevent a dependency on luaautomation.

local deprecation_warned = {}

function advtrains.getstate(parpos, pnode)
	local pos
	if atlatc then
		pos = atlatc.pcnaming.resolve_pos(parpos)
	else
		pos = advtrains.round_vector_floor_y(parpos)
	end
	if type(pos)~="table" or (not pos.x or not pos.y or not pos.z) then
		debug.sethook()
		error("Invalid position supplied to getstate")
	end
	local node=pnode or advtrains.ndb.get_node(pos)
	local ndef=minetest.registered_nodes[node.name]
	local st
	if ndef and ndef.advtrains and ndef.advtrains.getstate then
		 st=ndef.advtrains.getstate
	elseif ndef and ndef.luaautomation and ndef.luaautomation.getstate then
		if not deprecation_warned[node.name] then
			minetest.log("warning", node.name.." uses deprecated definition of ATLATC functions in the 'luaautomation' field. Please move them to the 'advtrains' field!")
		end
		st=ndef.luaautomation.getstate
	else
		return nil
	end
	if type(st)=="function" then
		return st(pos, node)
	else
		return st
	end
end

function advtrains.setstate(parpos, newstate, pnode)
	local pos
	if atlatc then
		pos = atlatc.pcnaming.resolve_pos(parpos)
	else
		pos = advtrains.round_vector_floor_y(parpos)
	end
	if type(pos)~="table" or (not pos.x or not pos.y or not pos.z) then
		debug.sethook()
		error("Invalid position supplied to getstate")
	end
	local node=pnode or advtrains.ndb.get_node(pos)
	local ndef=minetest.registered_nodes[node.name]
	local st
	if ndef and ndef.advtrains and ndef.advtrains.setstate then
		 st=ndef.advtrains.setstate
	elseif ndef and ndef.luaautomation and ndef.luaautomation.setstate then
		if not deprecation_warned[node.name] then
			minetest.log("warning", node.name.." uses deprecated definition of ATLATC functions in the 'luaautomation' field. Please move them to the 'advtrains' field!")
		end
		st=ndef.luaautomation.setstate
	else
		return nil
	end
	
	if advtrains.get_train_at_pos(pos) then
		return false
	end
	
	if advtrains.interlocking and advtrains.interlocking.route.has_route_lock(minetest.pos_to_string(pos)) then
		return false
	end
	
	st(pos, node, newstate)
	return true
end

function advtrains.is_passive(parpos, pnode)
	local pos
	if atlatc then
		pos = atlatc.pcnaming.resolve_pos(parpos)
	else
		pos = advtrains.round_vector_floor_y(parpos)
	end
	if type(pos)~="table" or (not pos.x or not pos.y or not pos.z) then
		debug.sethook()
		error("Invalid position supplied to getstate")
	end
	local node=pnode or advtrains.ndb.get_node(pos)
	local ndef=minetest.registered_nodes[node.name]
	if ndef and ndef.advtrains and ndef.advtrains.getstate then
		return true
	elseif ndef and ndef.luaautomation and ndef.luaautomation.getstate then
		if not deprecation_warned[node.name] then
			minetest.log("warning", node.name.." uses deprecated definition of ATLATC functions in the 'luaautomation' field. Please move them to the 'advtrains' field!")
		end
		return true
	else
		return false
	end
end

-- switches a node back to fallback state, if defined. Doesn't support pcnaming.
function advtrains.set_fallback_state(pos, pnode)
	local node=pnode or advtrains.ndb.get_node(pos)
	local ndef=minetest.registered_nodes[node.name]
	local st
	if ndef and ndef.advtrains and ndef.advtrains.setstate
			and ndef.advtrains.fallback_state then
		if advtrains.get_train_at_pos(pos) then
			return false
		end
		
		if advtrains.interlocking and advtrains.interlocking.route.has_route_lock(minetest.pos_to_string(pos)) then
			return false
		end
		
		ndef.advtrains.setstate(pos, node, ndef.advtrains.fallback_state)
		return true
	end
	
	
end
str">"select", "tonumber", "tostring", "type", "unpack", "_VERSION" } local function safe_date(f, t) if not f then -- fall back to old behavior return(os.date("*t",os.time())) else --pass parameters return os.date(f,t) end 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. -- Note: Disabled security since there are enough security leaks and this would be unneccessary anyway to DoS the server 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 static_env = { --core LUA functions 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 = advtrains.getstate, setstate = advtrains.setstate, is_passive = advtrains.is_passive, --interrupts are handled per node, position unknown. (same goes for digilines) --however external interrupts can be set here. interrupt_pos = function(parpos, imesg) local pos=atlatc.pcnaming.resolve_pos(parpos, "interrupt_pos") atlatc.interrupt.add(0, pos, {type="ext_int", ext_int=true, message=imesg}) end, train_parts = function(train_id) if not train_id then return false end local train = advtrains.trains[train_id] if not train then return false end return table.copy(train.trainparts or {}) end, -- sends an atc command to train regardless of where it is in the world atc_send_to_train = function(train_id, command) assertt(command, "string") local train = advtrains.trains[train_id] if train then advtrains.atc.train_set_command(train, command, true) return true else return false end end, get_slowdown = function() return advtrains.global_slowdown end } -- If interlocking is present, enable route setting functions if advtrains.interlocking then local function gen_checks(signal, route_name, noroutesearch) assertt(route_name, "string") local pos = atlatc.pcnaming.resolve_pos(signal) local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) if not sigd then error("There's no signal at "..minetest.pos_to_string(pos)) end local tcbs = advtrains.interlocking.db.get_tcbs(sigd) if not tcbs then error("Inconsistent configuration, no tcbs for signal at "..minetest.pos_to_string(pos)) end local routeid, route if not noroutesearch then for routeidt, routet in ipairs(tcbs.routes) do if routet.name == route_name then routeid = routeidt route = routet break end end if not route then error("No route called "..route_name.." at "..minetest.pos_to_string(pos)) end end return pos, sigd, tcbs, routeid, route end static_env.can_set_route = function(signal, route_name) local pos, sigd, tcbs, routeid, route = gen_checks(signal, route_name) -- if route is already set on signal, return whether it's committed if tcbs.routeset == routeid then return tcbs.route_committed end -- actually try setting route (parameter 'true' designates try-run local ok = advtrains.interlocking.route.set_route(sigd, route, true) return ok end static_env.set_route = function(signal, route_name) local pos, sigd, tcbs, routeid, route = gen_checks(signal, route_name) return advtrains.interlocking.route.update_route(sigd, tcbs, routeid, nil, S("LuaATC component")) end static_env.cancel_route = function(signal) local pos, sigd, tcbs, routeid, route = gen_checks(signal, "", true) return advtrains.interlocking.route.update_route(sigd, tcbs, nil, true) end static_env.get_aspect = function(signal) local pos = atlatc.pcnaming.resolve_pos(signal) return advtrains.interlocking.signal.get_aspect_info(pos) end static_env.set_aspect = function(signal, main_asp, rem_signal) if type(main_asp) == "table" then error("set_aspect: Parameters of this method have changed to (signal, main_asp, rem_signal) with introduction of distant signalling: parameter 2 is now the main aspect name (a string)") end local pos = atlatc.pcnaming.resolve_pos(signal) local rem_pos = rem_signal and atlatc.pcnaming.resolve_pos(rem_signal) return advtrains.interlocking.signal_set_aspect(pos, main_asp, rem_pos) end --section_occupancy() static_env.section_occupancy = function(ts_id) if not ts_id then return nil end ts_id = tostring(ts_id) local response = advtrains.interlocking.db.get_ts(ts_id) if not response then return false end return (response.trains and table.copy(response.trains)) or {} end end -- Lines-specific: if advtrains.lines then local atlrwt = advtrains.lines.rwt static_env.rwt = { now = atlrwt.now, new_t = atlrwt.new_t, new_i = atlrwt.new_i, to_table = atlrwt.to_table, t_sec = atlrwt.t_sec, i_sec = atlrwt.i_sec, to_secs = atlrwt.to_secs, -- deprecated t_str = atlrwt.t_str, i_str = atlrwt.i_str, to_string = atlrwt.to_string, -- deprecated add = atlrwt.add, diff = atlrwt.diff, sub = atlrwt.sub, is_after = atlrwt.is_after, is_before = atlrwt.is_before, next_rpt = atlrwt.next_rpt, last_rpt = atlrwt.last_rpt, time_from_last_rpt = atlrwt.time_from_last_rpt, time_to_next_rpt = atlrwt.time_to_next_rpt, } end atlatc.register_function = function (name, f) static_env[name] = f end 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. local proxy_env={} --proxy_env gets a new metatable in every run, but is the shared environment of all functions ever defined. -- returns: true, fenv if successful; nil, error if error function env_proto:execute_code(localenv, code, evtdata, customfct) -- create us a print function specific for this environment if not self.safe_print_func then local myenv = self self.safe_print_func = function(...) myenv:log("info", ...) end end local metatbl ={ __index = function(t, 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] elseif localenv and localenv[i] then return localenv[i] elseif i=="print" then return self.safe_print_func 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 localenv[i]=v end, } setmetatable(proxy_env, metatbl) local fun, err=loadstring(code) if not fun then return false, err end setfenv(fun, proxy_env) local succ, data = pcall(fun) if succ then data=localenv end return succ, data end function env_proto:run_initcode() if self.init_code and self.init_code~="" then local old_fdata=self.fdata self.fdata = {} --atprint("[atlatc]Running initialization code for environment '"..self.name.."'") local succ, err = self:execute_code({}, self.init_code, {type="init", init=true}) if not succ then self:log("error", "Executing InitCode for '"..self.name.."' failed:"..err) self.init_err=err if old_fdata then self.fdata=old_fdata self:log("warning", "The 'F' table has been restored to the previous state.") end end end end -- log to environment subscribers. severity can be "error", "warning" or "info" (used by internal print) function env_proto:log(severity, ...) local text=advtrains.print_concat_table({"[atlatc "..self.name.." "..severity.."]", ...}) minetest.log("action", text) for _, pname in ipairs(self.subscribers) do minetest.chat_send_player(pname, text) end end -- env.subscribers table may be directly altered by callers. --- class interface function atlatc.env_new(name) local newenv={ name=name, init_code="", sdata={}, subscribers={}, } 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