You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1089 lines
37KB

  1. -- ______
  2. -- |
  3. -- |
  4. -- | __ ___ _ __ _ _
  5. -- | | | | | |\ | | |_| | | | | |_ |_|
  6. -- |___| |______ |__| | \| | | \ |__| |_ |_ |_ |\ tube
  7. -- |
  8. -- |
  9. --
  10. -- Reference
  11. -- ports = get_real_port_states(pos): gets if inputs are powered from outside
  12. -- newport = merge_port_states(state1, state2): just does result = state1 or state2 for every port
  13. -- set_port(pos, rule, state): activates/deactivates the mesecons according to the port states
  14. -- set_port_states(pos, ports): Applies new port states to a Luacontroller at pos
  15. -- run_inner(pos, code, event): runs code on the controller at pos and event
  16. -- reset_formspec(pos, code, errmsg): installs new code and prints error messages, without resetting LCID
  17. -- reset_meta(pos, code, errmsg): performs a software-reset, installs new code and prints error message
  18. -- run(pos, event): a wrapper for run_inner which gets code & handles errors via reset_meta
  19. -- resetn(pos): performs a hardware reset, turns off all ports
  20. --
  21. -- The Sandbox
  22. -- The whole code of the controller runs in a sandbox,
  23. -- a very restricted environment.
  24. -- Actually the only way to damage the server is to
  25. -- use too much memory from the sandbox.
  26. -- You can add more functions to the environment
  27. -- (see where local env is defined)
  28. -- Something nice to play is appending minetest.env to it.
  29. local BASENAME = "pipeworks:lua_tube"
  30. local rules = {
  31. red = {x = -1, y = 0, z = 0, name = "red"},
  32. blue = {x = 1, y = 0, z = 0, name = "blue"},
  33. yellow = {x = 0, y = -1, z = 0, name = "yellow"},
  34. green = {x = 0, y = 1, z = 0, name = "green"},
  35. black = {x = 0, y = 0, z = -1, name = "black"},
  36. white = {x = 0, y = 0, z = 1, name = "white"},
  37. }
  38. local digiline_rules_luatube = {
  39. {x=0, y=0, z=-1},
  40. {x=1, y=0, z=0},
  41. {x=-1, y=0, z=0},
  42. {x=0, y=0, z=1},
  43. {x=1, y=1, z=0},
  44. {x=1, y=-1, z=0},
  45. {x=-1, y=1, z=0},
  46. {x=-1, y=-1, z=0},
  47. {x=0, y=1, z=1},
  48. {x=0, y=-1, z=1},
  49. {x=0, y=1, z=-1},
  50. {x=0, y=-1, z=-1},
  51. -- vertical connectivity
  52. {x=0, y=1, z=0},
  53. {x=0, y=-1, z=0},
  54. }
  55. ------------------
  56. -- Action stuff --
  57. ------------------
  58. -- These helpers are required to set the port states of the lua_tube
  59. local function update_real_port_states(pos, rule_name, new_state)
  60. local meta = minetest.get_meta(pos)
  61. if rule_name == nil then
  62. meta:set_int("real_portstates", 1)
  63. return
  64. end
  65. local n = meta:get_int("real_portstates") - 1
  66. local L = {}
  67. for i = 1, 6 do
  68. L[i] = n % 2
  69. n = math.floor(n / 2)
  70. end
  71. -- (0,0,-1) (0,-1,0) (-1,0,0) (1,0,0) (0,1,0) (0,0,1)
  72. local pos_to_side = { 5, 3, 1, nil, 2, 4, 6 }
  73. if rule_name.x == nil then
  74. for _, rname in ipairs(rule_name) do
  75. local port = pos_to_side[rname.x + (2 * rname.y) + (3 * rname.z) + 4]
  76. L[port] = (new_state == "on") and 1 or 0
  77. end
  78. else
  79. local port = pos_to_side[rule_name.x + (2 * rule_name.y) + (3 * rule_name.z) + 4]
  80. L[port] = (new_state == "on") and 1 or 0
  81. end
  82. meta:set_int("real_portstates",
  83. 1 +
  84. 1 * L[1] +
  85. 2 * L[2] +
  86. 4 * L[3] +
  87. 8 * L[4] +
  88. 16 * L[5] +
  89. 32 * L[6])
  90. end
  91. local port_names = {"red", "blue", "yellow", "green", "black", "white"}
  92. local function get_real_port_states(pos)
  93. -- Determine if ports are powered (by itself or from outside)
  94. local meta = minetest.get_meta(pos)
  95. local L = {}
  96. local n = meta:get_int("real_portstates") - 1
  97. for _, name in ipairs(port_names) do
  98. L[name] = ((n % 2) == 1)
  99. n = math.floor(n / 2)
  100. end
  101. return L
  102. end
  103. local function merge_port_states(ports, vports)
  104. return {
  105. red = ports.red or vports.red,
  106. blue = ports.blue or vports.blue,
  107. yellow = ports.yellow or vports.yellow,
  108. green = ports.green or vports.green,
  109. black = ports.black or vports.black,
  110. white = ports.white or vports.white,
  111. }
  112. end
  113. local function generate_name(ports)
  114. local red = ports.red and 1 or 0
  115. local blue = ports.blue and 1 or 0
  116. local yellow = ports.yellow and 1 or 0
  117. local green = ports.green and 1 or 0
  118. local black = ports.black and 1 or 0
  119. local white = ports.white and 1 or 0
  120. return BASENAME..white..black..green..yellow..blue..red
  121. end
  122. local function set_port(pos, rule, state)
  123. if state then
  124. mesecon.receptor_on(pos, {rule})
  125. else
  126. mesecon.receptor_off(pos, {rule})
  127. end
  128. end
  129. local function clean_port_states(ports)
  130. ports.red = ports.red and true or false
  131. ports.blue = ports.blue and true or false
  132. ports.yellow = ports.yellow and true or false
  133. ports.green = ports.green and true or false
  134. ports.black = ports.black and true or false
  135. ports.white = ports.white and true or false
  136. end
  137. local function set_port_states(pos, ports)
  138. local node = minetest.get_node(pos)
  139. local name = node.name
  140. clean_port_states(ports)
  141. local vports = minetest.registered_nodes[name].virtual_portstates
  142. local new_name = generate_name(ports)
  143. if name ~= new_name and vports then
  144. -- Problem:
  145. -- We need to place the new node first so that when turning
  146. -- off some port, it won't stay on because the rules indicate
  147. -- there is an onstate output port there.
  148. -- When turning the output off then, it will however cause feedback
  149. -- so that the lua_tube will receive an "off" event by turning
  150. -- its output off.
  151. -- Solution / Workaround:
  152. -- Remember which output was turned off and ignore next "off" event.
  153. local meta = minetest.get_meta(pos)
  154. local ign = minetest.deserialize(meta:get_string("ignore_offevents"), true) or {}
  155. if ports.red and not vports.red and not mesecon.is_powered(pos, rules.red) then ign.red = true end
  156. if ports.blue and not vports.blue and not mesecon.is_powered(pos, rules.blue) then ign.blue = true end
  157. if ports.yellow and not vports.yellow and not mesecon.is_powered(pos, rules.yellow) then ign.yellow = true end
  158. if ports.green and not vports.green and not mesecon.is_powered(pos, rules.green) then ign.green = true end
  159. if ports.black and not vports.black and not mesecon.is_powered(pos, rules.black) then ign.black = true end
  160. if ports.white and not vports.white and not mesecon.is_powered(pos, rules.white) then ign.white = true end
  161. meta:set_string("ignore_offevents", minetest.serialize(ign))
  162. minetest.swap_node(pos, {name = new_name, param2 = node.param2})
  163. if ports.red ~= vports.red then set_port(pos, rules.red, ports.red) end
  164. if ports.blue ~= vports.blue then set_port(pos, rules.blue, ports.blue) end
  165. if ports.yellow ~= vports.yellow then set_port(pos, rules.yellow, ports.yellow) end
  166. if ports.green ~= vports.green then set_port(pos, rules.green, ports.green) end
  167. if ports.black ~= vports.black then set_port(pos, rules.black, ports.black) end
  168. if ports.white ~= vports.white then set_port(pos, rules.white, ports.white) end
  169. end
  170. end
  171. -----------------
  172. -- Overheating --
  173. -----------------
  174. local function burn_controller(pos)
  175. local node = minetest.get_node(pos)
  176. node.name = BASENAME.."_burnt"
  177. minetest.swap_node(pos, node)
  178. minetest.get_meta(pos):set_string("lc_memory", "");
  179. -- Wait for pending operations
  180. minetest.after(0.2, mesecon.receptor_off, pos, mesecon.rules.flat)
  181. end
  182. local function overheat(pos, meta)
  183. if mesecon.do_overheat(pos) then -- If too hot
  184. burn_controller(pos)
  185. return true
  186. end
  187. end
  188. ------------------------
  189. -- Ignored off events --
  190. ------------------------
  191. local function ignore_event(event, meta)
  192. if event.type ~= "off" then return false end
  193. local ignore_offevents = minetest.deserialize(meta:get_string("ignore_offevents"), true) or {}
  194. if ignore_offevents[event.pin.name] then
  195. ignore_offevents[event.pin.name] = nil
  196. meta:set_string("ignore_offevents", minetest.serialize(ignore_offevents))
  197. return true
  198. end
  199. end
  200. -------------------------
  201. -- Parsing and running --
  202. -------------------------
  203. local function safe_print(param)
  204. local string_meta = getmetatable("")
  205. local sandbox = string_meta.__index
  206. string_meta.__index = string -- Leave string sandbox temporarily
  207. print(dump(param))
  208. string_meta.__index = sandbox -- Restore string sandbox
  209. end
  210. local function safe_date()
  211. return(os.date("*t",os.time()))
  212. end
  213. -- string.rep(str, n) with a high value for n can be used to DoS
  214. -- the server. Therefore, limit max. length of generated string.
  215. local function safe_string_rep(str, n)
  216. if #str * n > mesecon.setting("luacontroller_string_rep_max", 64000) then
  217. debug.sethook() -- Clear hook
  218. error("string.rep: string length overflow", 2)
  219. end
  220. return string.rep(str, n)
  221. end
  222. -- string.find with a pattern can be used to DoS the server.
  223. -- Therefore, limit string.find to patternless matching.
  224. local function safe_string_find(...)
  225. if (select(4, ...)) ~= true then
  226. debug.sethook() -- Clear hook
  227. error("string.find: 'plain' (fourth parameter) must always be true in a lua controlled tube")
  228. end
  229. return string.find(...)
  230. end
  231. local function remove_functions(x)
  232. local tp = type(x)
  233. if tp == "function" then
  234. return nil
  235. end
  236. -- Make sure to not serialize the same table multiple times, otherwise
  237. -- writing mem.test = mem in the lua controlled tube will lead to infinite recursion
  238. local seen = {}
  239. local function rfuncs(x)
  240. if x == nil then return end
  241. if seen[x] then return end
  242. seen[x] = true
  243. if type(x) ~= "table" then return end
  244. for key, value in pairs(x) do
  245. if type(key) == "function" or type(value) == "function" then
  246. x[key] = nil
  247. else
  248. if type(key) == "table" then
  249. rfuncs(key)
  250. end
  251. if type(value) == "table" then
  252. rfuncs(value)
  253. end
  254. end
  255. end
  256. end
  257. rfuncs(x)
  258. return x
  259. end
  260. -- The setting affects API so is not intended to be changeable at runtime
  261. local get_interrupt
  262. if mesecon.setting("luacontroller_lightweight_interrupts", false) then
  263. -- use node timer
  264. get_interrupt = function(pos, itbl, send_warning)
  265. return (function(time, iid)
  266. if type(time) ~= "number" then error("Delay must be a number") end
  267. if iid ~= nil then send_warning("Interrupt IDs are disabled on this server") end
  268. table.insert(itbl, function() minetest.get_node_timer(pos):start(time) end)
  269. end)
  270. end
  271. else
  272. -- use global action queue
  273. -- itbl: Flat table of functions to run after sandbox cleanup, used to prevent various security hazards
  274. get_interrupt = function(pos, itbl, send_warning)
  275. -- iid = interrupt id
  276. local function interrupt(time, iid)
  277. -- NOTE: This runs within string metatable sandbox, so don't *rely* on anything of the form (""):y
  278. -- Hence the values get moved out. Should take less time than original, so totally compatible
  279. if type(time) ~= "number" then error("Delay must be a number") end
  280. table.insert(itbl, function ()
  281. -- Outside string metatable sandbox, can safely run this now
  282. local luac_id = minetest.get_meta(pos):get_int("luac_id")
  283. -- Check if IID is dodgy, so you can't use interrupts to store an infinite amount of data.
  284. -- Note that this is safe from alter-after-free because this code gets run after the sandbox has ended.
  285. -- This runs outside of the timer and *shouldn't* harm perf. unless dodgy data is being sent in the first place
  286. iid = remove_functions(iid)
  287. local msg_ser = minetest.serialize(iid)
  288. if #msg_ser <= mesecon.setting("luacontroller_interruptid_maxlen", 256) then
  289. mesecon.queue:add_action(pos, "pipeworks:lc_tube_interrupt", {luac_id, iid}, time, iid, 1)
  290. else
  291. send_warning("An interrupt ID was too large!")
  292. end
  293. end)
  294. end
  295. return interrupt
  296. end
  297. end
  298. -- Given a message object passed to digiline_send, clean it up into a form
  299. -- which is safe to transmit over the network and compute its "cost" (a very
  300. -- rough estimate of its memory usage).
  301. --
  302. -- The cleaning comprises the following:
  303. -- 1. Functions (and userdata, though user scripts ought not to get hold of
  304. -- those in the first place) are removed, because they break the model of
  305. -- Digilines as a network that carries basic data, and they could exfiltrate
  306. -- references to mutable objects from one Luacontroller to another, allowing
  307. -- inappropriate high-bandwidth, no-wires communication.
  308. -- 2. Tables are duplicated because, being mutable, they could otherwise be
  309. -- modified after the send is complete in order to change what data arrives
  310. -- at the recipient, perhaps in violation of the previous cleaning rule or
  311. -- in violation of the message size limit.
  312. --
  313. -- The cost indication is only approximate; it’s not a perfect measurement of
  314. -- the number of bytes of memory used by the message object.
  315. --
  316. -- Parameters:
  317. -- msg -- the message to clean
  318. -- back_references -- for internal use only; do not provide
  319. --
  320. -- Returns:
  321. -- 1. The cleaned object.
  322. -- 2. The approximate cost of the object.
  323. local function clean_and_weigh_digiline_message(msg, back_references)
  324. local t = type(msg)
  325. if t == "string" then
  326. -- Strings are immutable so can be passed by reference, and cost their
  327. -- length plus the size of the Lua object header (24 bytes on a 64-bit
  328. -- platform) plus one byte for the NUL terminator.
  329. return msg, #msg + 25
  330. elseif t == "number" then
  331. -- Numbers are passed by value so need not be touched, and cost 8 bytes
  332. -- as all numbers in Lua are doubles.
  333. return msg, 8
  334. elseif t == "boolean" then
  335. -- Booleans are passed by value so need not be touched, and cost 1
  336. -- byte.
  337. return msg, 1
  338. elseif t == "table" then
  339. -- Tables are duplicated. Check if this table has been seen before
  340. -- (self-referential or shared table); if so, reuse the cleaned value
  341. -- of the previous occurrence, maintaining table topology and avoiding
  342. -- infinite recursion, and charge zero bytes for this as the object has
  343. -- already been counted.
  344. back_references = back_references or {}
  345. local bref = back_references[msg]
  346. if bref then
  347. return bref, 0
  348. end
  349. -- Construct a new table by cleaning all the keys and values and adding
  350. -- up their costs, plus 8 bytes as a rough estimate of table overhead.
  351. local cost = 8
  352. local ret = {}
  353. back_references[msg] = ret
  354. for k, v in pairs(msg) do
  355. local k_cost, v_cost
  356. k, k_cost = clean_and_weigh_digiline_message(k, back_references)
  357. v, v_cost = clean_and_weigh_digiline_message(v, back_references)
  358. if k ~= nil and v ~= nil then
  359. -- Only include an element if its key and value are of legal
  360. -- types.
  361. ret[k] = v
  362. end
  363. -- If we only counted the cost of a table element when we actually
  364. -- used it, we would be vulnerable to the following attack:
  365. -- 1. Construct a huge table (too large to pass the cost limit).
  366. -- 2. Insert it somewhere in a table, with a function as a key.
  367. -- 3. Insert it somewhere in another table, with a number as a key.
  368. -- 4. The first occurrence doesn’t pay the cost because functions
  369. -- are stripped and therefore the element is dropped.
  370. -- 5. The second occurrence doesn’t pay the cost because it’s in
  371. -- back_references.
  372. -- By counting the costs regardless of whether the objects will be
  373. -- included, we avoid this attack; it may overestimate the cost of
  374. -- some messages, but only those that won’t be delivered intact
  375. -- anyway because they contain illegal object types.
  376. cost = cost + k_cost + v_cost
  377. end
  378. return ret, cost
  379. else
  380. return nil, 0
  381. end
  382. end
  383. -- itbl: Flat table of functions to run after sandbox cleanup, used to prevent various security hazards
  384. local function get_digiline_send(pos, itbl, send_warning)
  385. if not minetest.global_exists("digilines") then return end
  386. local chan_maxlen = mesecon.setting("luacontroller_digiline_channel_maxlen", 256)
  387. local maxlen = mesecon.setting("luacontroller_digiline_maxlen", 50000)
  388. return function(channel, msg)
  389. -- NOTE: This runs within string metatable sandbox, so don't *rely* on anything of the form (""):y
  390. -- or via anything that could.
  391. -- Make sure channel is string, number or boolean
  392. if type(channel) == "string" then
  393. if #channel > chan_maxlen then
  394. send_warning("Channel string too long.")
  395. return false
  396. end
  397. elseif (type(channel) ~= "string" and type(channel) ~= "number" and type(channel) ~= "boolean") then
  398. send_warning("Channel must be string, number or boolean.")
  399. return false
  400. end
  401. local msg_cost
  402. msg, msg_cost = clean_and_weigh_digiline_message(msg)
  403. if msg == nil or msg_cost > maxlen then
  404. send_warning("Message was too complex, or contained invalid data.")
  405. return false
  406. end
  407. table.insert(itbl, function ()
  408. -- Runs outside of string metatable sandbox
  409. local luac_id = minetest.get_meta(pos):get_int("luac_id")
  410. mesecon.queue:add_action(pos, "pipeworks:lt_digiline_relay", {channel, luac_id, msg})
  411. end)
  412. return true
  413. end
  414. end
  415. local safe_globals = {
  416. -- Don't add pcall/xpcall unless willing to deal with the consequences (unless very careful, incredibly likely to allow killing server indirectly)
  417. "assert", "error", "ipairs", "next", "pairs", "select",
  418. "tonumber", "tostring", "type", "unpack", "_VERSION"
  419. }
  420. local function create_environment(pos, mem, event, itbl, send_warning)
  421. -- Make sure the tube hasn't broken.
  422. local vports = minetest.registered_nodes[minetest.get_node(pos).name].virtual_portstates
  423. if not vports then return {} end
  424. -- Gather variables for the environment
  425. local vports_copy = {}
  426. for k, v in pairs(vports) do vports_copy[k] = v end
  427. local rports = get_real_port_states(pos)
  428. -- Create new library tables on each call to prevent one Luacontroller
  429. -- from breaking a library and messing up other Luacontrollers.
  430. local env = {
  431. pin = merge_port_states(vports, rports),
  432. port = vports_copy,
  433. event = event,
  434. mem = mem,
  435. heat = mesecon.get_heat(pos),
  436. heat_max = mesecon.setting("overheat_max", 20),
  437. print = safe_print,
  438. interrupt = get_interrupt(pos, itbl, send_warning),
  439. digiline_send = get_digiline_send(pos, itbl, send_warning),
  440. string = {
  441. byte = string.byte,
  442. char = string.char,
  443. format = string.format,
  444. len = string.len,
  445. lower = string.lower,
  446. upper = string.upper,
  447. rep = safe_string_rep,
  448. reverse = string.reverse,
  449. sub = string.sub,
  450. find = safe_string_find,
  451. },
  452. math = {
  453. abs = math.abs,
  454. acos = math.acos,
  455. asin = math.asin,
  456. atan = math.atan,
  457. atan2 = math.atan2,
  458. ceil = math.ceil,
  459. cos = math.cos,
  460. cosh = math.cosh,
  461. deg = math.deg,
  462. exp = math.exp,
  463. floor = math.floor,
  464. fmod = math.fmod,
  465. frexp = math.frexp,
  466. huge = math.huge,
  467. ldexp = math.ldexp,
  468. log = math.log,
  469. log10 = math.log10,
  470. max = math.max,
  471. min = math.min,
  472. modf = math.modf,
  473. pi = math.pi,
  474. pow = math.pow,
  475. rad = math.rad,
  476. random = math.random,
  477. sin = math.sin,
  478. sinh = math.sinh,
  479. sqrt = math.sqrt,
  480. tan = math.tan,
  481. tanh = math.tanh,
  482. },
  483. table = {
  484. concat = table.concat,
  485. insert = table.insert,
  486. maxn = table.maxn,
  487. remove = table.remove,
  488. sort = table.sort,
  489. },
  490. os = {
  491. clock = os.clock,
  492. difftime = os.difftime,
  493. time = os.time,
  494. datetable = safe_date,
  495. },
  496. }
  497. env._G = env
  498. for _, name in pairs(safe_globals) do
  499. env[name] = _G[name]
  500. end
  501. return env
  502. end
  503. local function timeout()
  504. debug.sethook() -- Clear hook
  505. error("Code timed out!", 2)
  506. end
  507. local function create_sandbox(code, env)
  508. if code:byte(1) == 27 then
  509. return nil, "Binary code prohibited."
  510. end
  511. local f, msg = loadstring(code)
  512. if not f then return nil, msg end
  513. setfenv(f, env)
  514. -- Turn off JIT optimization for user code so that count
  515. -- events are generated when adding debug hooks
  516. if rawget(_G, "jit") then
  517. jit.off(f, true)
  518. end
  519. local maxevents = mesecon.setting("luacontroller_maxevents", 10000)
  520. return function(...)
  521. -- NOTE: This runs within string metatable sandbox, so the setting's been moved out for safety
  522. -- Use instruction counter to stop execution
  523. -- after luacontroller_maxevents
  524. debug.sethook(timeout, "", maxevents)
  525. local ok, ret = pcall(f, ...)
  526. debug.sethook() -- Clear hook
  527. if not ok then error(ret, 0) end
  528. return ret
  529. end
  530. end
  531. local function load_memory(meta)
  532. return minetest.deserialize(meta:get_string("lc_memory"), true) or {}
  533. end
  534. local function save_memory(pos, meta, mem)
  535. local memstring = minetest.serialize(remove_functions(mem))
  536. local memsize_max = mesecon.setting("luacontroller_memsize", 100000)
  537. if (#memstring <= memsize_max) then
  538. meta:set_string("lc_memory", memstring)
  539. meta:mark_as_private("lc_memory")
  540. else
  541. print("Error: lua_tube memory overflow. "..memsize_max.." bytes available, "
  542. ..#memstring.." required. Controller overheats.")
  543. burn_controller(pos)
  544. end
  545. end
  546. -- Returns success (boolean), errmsg (string), retval(any, return value of the user supplied code)
  547. -- run (as opposed to run_inner) is responsible for setting up meta according to this output
  548. local function run_inner(pos, code, event)
  549. local meta = minetest.get_meta(pos)
  550. -- Note: These return success, presumably to avoid changing LC ID.
  551. if overheat(pos) then return true, "", nil end
  552. if ignore_event(event, meta) then return true, "", nil end
  553. -- Load code & mem from meta
  554. local mem = load_memory(meta)
  555. local code = meta:get_string("code")
  556. -- 'Last warning' label.
  557. local warning = ""
  558. local function send_warning(str)
  559. warning = "Warning: " .. str
  560. end
  561. -- Create environment
  562. local itbl = {}
  563. local env = create_environment(pos, mem, event, itbl, send_warning)
  564. -- Create the sandbox and execute code
  565. local f, msg = create_sandbox(code, env)
  566. if not f then return false, msg, nil end
  567. -- Start string true sandboxing
  568. local onetruestring = getmetatable("")
  569. -- If a string sandbox is already up yet inconsistent, something is very wrong
  570. assert(onetruestring.__index == string)
  571. onetruestring.__index = env.string
  572. local success, msg = pcall(f)
  573. onetruestring.__index = string
  574. -- End string true sandboxing
  575. if not success then return false, msg, nil end
  576. if type(env.port) ~= "table" then
  577. return false, "Ports set are invalid.", nil
  578. end
  579. -- Actually set the ports
  580. set_port_states(pos, env.port)
  581. -- Save memory. This may burn the luacontroller if a memory overflow occurs.
  582. save_memory(pos, meta, env.mem)
  583. -- Execute deferred tasks
  584. for _, v in ipairs(itbl) do
  585. local failure = v()
  586. if failure then
  587. return false, failure, nil
  588. end
  589. end
  590. return true, warning, msg
  591. end
  592. local function reset_formspec(meta, code, errmsg)
  593. meta:set_string("code", code)
  594. meta:mark_as_private("code")
  595. code = minetest.formspec_escape(code or "")
  596. errmsg = minetest.formspec_escape(tostring(errmsg or ""))
  597. meta:set_string("formspec", "size[12,10]"
  598. .."background[-0.2,-0.25;12.4,10.75;jeija_luac_background.png]"
  599. .."label[0.1,8.3;"..errmsg.."]"
  600. .."textarea[0.2,0.2;12.2,9.5;code;;"..code.."]"
  601. .."image_button[4.75,8.75;2.5,1;jeija_luac_runbutton.png;program;]"
  602. .."image_button_exit[11.72,-0.25;0.425,0.4;jeija_close_window.png;exit;]"
  603. )
  604. end
  605. local function reset_meta(pos, code, errmsg)
  606. local meta = minetest.get_meta(pos)
  607. reset_formspec(meta, code, errmsg)
  608. meta:set_int("luac_id", math.random(1, 65535))
  609. end
  610. -- Wraps run_inner with LC-reset-on-error
  611. local function run(pos, event)
  612. local meta = minetest.get_meta(pos)
  613. local code = meta:get_string("code")
  614. local ok, errmsg, retval = run_inner(pos, code, event)
  615. if not ok then
  616. reset_meta(pos, code, errmsg)
  617. else
  618. reset_formspec(meta, code, errmsg)
  619. end
  620. return ok, errmsg, retval
  621. end
  622. local function reset(pos)
  623. set_port_states(pos, {red = false, blue = false, yellow = false,
  624. green = false, black = false, white = false})
  625. end
  626. local function node_timer(pos)
  627. if minetest.registered_nodes[minetest.get_node(pos).name].is_burnt then
  628. return false
  629. end
  630. run(pos, {type="interrupt"})
  631. return false
  632. end
  633. -----------------------
  634. -- A.Queue callbacks --
  635. -----------------------
  636. mesecon.queue:add_function("pipeworks:lc_tube_interrupt", function (pos, luac_id, iid)
  637. -- There is no lua_tube anymore / it has been reprogrammed / replaced / burnt
  638. if (minetest.get_meta(pos):get_int("luac_id") ~= luac_id) then return end
  639. if (minetest.registered_nodes[minetest.get_node(pos).name].is_burnt) then return end
  640. run(pos, {type="interrupt", iid = iid})
  641. end)
  642. mesecon.queue:add_function("pipeworks:lt_digiline_relay", function (pos, channel, luac_id, msg)
  643. if not digiline then return end
  644. -- This check is only really necessary because in case of server crash, old actions can be thrown into the future
  645. if (minetest.get_meta(pos):get_int("luac_id") ~= luac_id) then return end
  646. if (minetest.registered_nodes[minetest.get_node(pos).name].is_burnt) then return end
  647. -- The actual work
  648. digiline:receptor_send(pos, digiline_rules_luatube, channel, msg)
  649. end)
  650. -----------------------
  651. -- Node Registration --
  652. -----------------------
  653. local output_rules = {}
  654. local input_rules = {}
  655. local node_box = {
  656. type = "fixed",
  657. fixed = {
  658. pipeworks.tube_leftstub[1], -- tube segment against -X face
  659. pipeworks.tube_rightstub[1], -- tube segment against +X face
  660. pipeworks.tube_bottomstub[1], -- tube segment against -Y face
  661. pipeworks.tube_topstub[1], -- tube segment against +Y face
  662. pipeworks.tube_frontstub[1], -- tube segment against -Z face
  663. pipeworks.tube_backstub[1], -- tube segment against +Z face
  664. }
  665. }
  666. local selection_box = {
  667. type = "fixed",
  668. fixed = pipeworks.tube_selectboxes,
  669. }
  670. local digiline = {
  671. receptor = {},
  672. effector = {
  673. action = function(pos, node, channel, msg)
  674. msg = clean_and_weigh_digiline_message(msg)
  675. run(pos, {type = "digiline", channel = channel, msg = msg})
  676. end
  677. },
  678. wire = {
  679. rules = pipeworks.digilines_rules
  680. },
  681. }
  682. local function get_program(pos)
  683. local meta = minetest.get_meta(pos)
  684. return meta:get_string("code")
  685. end
  686. local function set_program(pos, code)
  687. reset(pos)
  688. reset_meta(pos, code)
  689. return run(pos, {type="program"})
  690. end
  691. local function on_receive_fields(pos, form_name, fields, sender)
  692. if not fields.program then
  693. return
  694. end
  695. local name = sender:get_player_name()
  696. if minetest.is_protected(pos, name) and not minetest.check_player_privs(name, {protection_bypass=true}) then
  697. minetest.record_protection_violation(pos, name)
  698. return
  699. end
  700. local ok, err = set_program(pos, fields.code)
  701. if not ok then
  702. -- it's not an error from the server perspective
  703. minetest.log("action", "Lua controller programming error: " .. tostring(err))
  704. end
  705. end
  706. local function go_back(velocity)
  707. local adjlist={{x=0,y=0,z=1},{x=0,y=0,z=-1},{x=0,y=1,z=0},{x=0,y=-1,z=0},{x=1,y=0,z=0},{x=-1,y=0,z=0}}
  708. local speed = math.abs(velocity.x + velocity.y + velocity.z)
  709. if speed == 0 then
  710. speed = 1
  711. end
  712. local vel = {x = velocity.x/speed, y = velocity.y/speed, z = velocity.z/speed,speed=speed}
  713. if speed >= 4.1 then
  714. speed = 4
  715. elseif speed >= 1.1 then
  716. speed = speed - 0.1
  717. else
  718. speed = 1
  719. end
  720. vel.speed = speed
  721. return pipeworks.notvel(adjlist, vel)
  722. end
  723. local tiles_base = {
  724. "pipeworks_mese_tube_plain_4.png", "pipeworks_mese_tube_plain_3.png",
  725. "pipeworks_mese_tube_plain_2.png", "pipeworks_mese_tube_plain_1.png",
  726. "pipeworks_mese_tube_plain_6.png", "pipeworks_mese_tube_plain_5.png"}
  727. for red = 0, 1 do -- 0 = off 1 = on
  728. for blue = 0, 1 do
  729. for yellow = 0, 1 do
  730. for green = 0, 1 do
  731. for black = 0, 1 do
  732. for white = 0, 1 do
  733. local cid = tostring(white)..tostring(black)..tostring(green)..
  734. tostring(yellow)..tostring(blue)..tostring(red)
  735. local node_name = BASENAME..cid
  736. local tiles = table.copy(tiles_base)
  737. if red == 1 then
  738. tiles[1] = tiles[1].."^(pipeworks_lua_tube_port_on.png^[transformR90)"
  739. tiles[2] = tiles[2].."^(pipeworks_lua_tube_port_on.png^[transformR90)"
  740. tiles[5] = tiles[5].."^(pipeworks_lua_tube_port_on.png^[transformR270)"
  741. tiles[6] = tiles[6].."^(pipeworks_lua_tube_port_on.png^[transformR90)"
  742. else
  743. tiles[1] = tiles[1].."^(pipeworks_lua_tube_port_off.png^[transformR90)"
  744. tiles[2] = tiles[2].."^(pipeworks_lua_tube_port_off.png^[transformR90)"
  745. tiles[5] = tiles[5].."^(pipeworks_lua_tube_port_off.png^[transformR270)"
  746. tiles[6] = tiles[6].."^(pipeworks_lua_tube_port_off.png^[transformR90)"
  747. end
  748. if blue == 1 then
  749. tiles[1] = tiles[1].."^(pipeworks_lua_tube_port_on.png^[transformR270)"
  750. tiles[2] = tiles[2].."^(pipeworks_lua_tube_port_on.png^[transformR270)"
  751. tiles[5] = tiles[5].."^(pipeworks_lua_tube_port_on.png^[transformR90)"
  752. tiles[6] = tiles[6].."^(pipeworks_lua_tube_port_on.png^[transformR270)"
  753. else
  754. tiles[1] = tiles[1].."^(pipeworks_lua_tube_port_off.png^[transformR270)"
  755. tiles[2] = tiles[2].."^(pipeworks_lua_tube_port_off.png^[transformR270)"
  756. tiles[5] = tiles[5].."^(pipeworks_lua_tube_port_off.png^[transformR90)"
  757. tiles[6] = tiles[6].."^(pipeworks_lua_tube_port_off.png^[transformR270)"
  758. end
  759. if yellow == 1 then
  760. tiles[3] = tiles[3].."^(pipeworks_lua_tube_port_on.png^[transformR180)"
  761. tiles[4] = tiles[4].."^(pipeworks_lua_tube_port_on.png^[transformR180)"
  762. tiles[5] = tiles[5].."^(pipeworks_lua_tube_port_on.png^[transformR180)"
  763. tiles[6] = tiles[6].."^(pipeworks_lua_tube_port_on.png^[transformR180)"
  764. else
  765. tiles[3] = tiles[3].."^(pipeworks_lua_tube_port_off.png^[transformR180)"
  766. tiles[4] = tiles[4].."^(pipeworks_lua_tube_port_off.png^[transformR180)"
  767. tiles[5] = tiles[5].."^(pipeworks_lua_tube_port_off.png^[transformR180)"
  768. tiles[6] = tiles[6].."^(pipeworks_lua_tube_port_off.png^[transformR180)"
  769. end
  770. if green == 1 then
  771. tiles[3] = tiles[3].."^pipeworks_lua_tube_port_on.png"
  772. tiles[4] = tiles[4].."^pipeworks_lua_tube_port_on.png"
  773. tiles[5] = tiles[5].."^pipeworks_lua_tube_port_on.png"
  774. tiles[6] = tiles[6].."^pipeworks_lua_tube_port_on.png"
  775. else
  776. tiles[3] = tiles[3].."^pipeworks_lua_tube_port_off.png"
  777. tiles[4] = tiles[4].."^pipeworks_lua_tube_port_off.png"
  778. tiles[5] = tiles[5].."^pipeworks_lua_tube_port_off.png"
  779. tiles[6] = tiles[6].."^pipeworks_lua_tube_port_off.png"
  780. end
  781. if black == 1 then
  782. tiles[1] = tiles[1].."^(pipeworks_lua_tube_port_on.png^[transformR180)"
  783. tiles[2] = tiles[2].."^pipeworks_lua_tube_port_on.png"
  784. tiles[3] = tiles[3].."^(pipeworks_lua_tube_port_on.png^[transformR90)"
  785. tiles[4] = tiles[4].."^(pipeworks_lua_tube_port_on.png^[transformR270)"
  786. else
  787. tiles[1] = tiles[1].."^(pipeworks_lua_tube_port_off.png^[transformR180)"
  788. tiles[2] = tiles[2].."^pipeworks_lua_tube_port_off.png"
  789. tiles[3] = tiles[3].."^(pipeworks_lua_tube_port_off.png^[transformR90)"
  790. tiles[4] = tiles[4].."^(pipeworks_lua_tube_port_off.png^[transformR270)"
  791. end
  792. if white == 1 then
  793. tiles[1] = tiles[1].."^pipeworks_lua_tube_port_on.png"
  794. tiles[2] = tiles[2].."^(pipeworks_lua_tube_port_on.png^[transformR180)"
  795. tiles[3] = tiles[3].."^(pipeworks_lua_tube_port_on.png^[transformR270)"
  796. tiles[4] = tiles[4].."^(pipeworks_lua_tube_port_on.png^[transformR90)"
  797. else
  798. tiles[1] = tiles[1].."^pipeworks_lua_tube_port_off.png"
  799. tiles[2] = tiles[2].."^(pipeworks_lua_tube_port_off.png^[transformR180)"
  800. tiles[3] = tiles[3].."^(pipeworks_lua_tube_port_off.png^[transformR270)"
  801. tiles[4] = tiles[4].."^(pipeworks_lua_tube_port_off.png^[transformR90)"
  802. end
  803. local groups = {snappy = 3, tube = 1, tubedevice = 1, overheat = 1}
  804. if red + blue + yellow + green + black + white ~= 0 then
  805. groups.not_in_creative_inventory = 1
  806. end
  807. output_rules[cid] = {}
  808. input_rules[cid] = {}
  809. if red == 1 then table.insert(output_rules[cid], rules.red) end
  810. if blue == 1 then table.insert(output_rules[cid], rules.blue) end
  811. if yellow == 1 then table.insert(output_rules[cid], rules.yellow) end
  812. if green == 1 then table.insert(output_rules[cid], rules.green) end
  813. if black == 1 then table.insert(output_rules[cid], rules.black) end
  814. if white == 1 then table.insert(output_rules[cid], rules.white) end
  815. if red == 0 then table.insert( input_rules[cid], rules.red) end
  816. if blue == 0 then table.insert( input_rules[cid], rules.blue) end
  817. if yellow == 0 then table.insert( input_rules[cid], rules.yellow) end
  818. if green == 0 then table.insert( input_rules[cid], rules.green) end
  819. if black == 0 then table.insert( input_rules[cid], rules.black) end
  820. if white == 0 then table.insert( input_rules[cid], rules.white) end
  821. local mesecons = {
  822. effector = {
  823. rules = input_rules[cid],
  824. action_change = function (pos, _, rule_name, new_state)
  825. update_real_port_states(pos, rule_name, new_state)
  826. run(pos, {type=new_state, pin=rule_name})
  827. end,
  828. },
  829. receptor = {
  830. state = mesecon.state.on,
  831. rules = output_rules[cid]
  832. },
  833. luacontroller = {
  834. get_program = get_program,
  835. set_program = set_program,
  836. },
  837. }
  838. minetest.register_node(node_name, {
  839. description = "Lua controlled Tube",
  840. drawtype = "nodebox",
  841. tiles = tiles,
  842. paramtype = "light",
  843. is_ground_content = false,
  844. groups = groups,
  845. drop = BASENAME.."000000",
  846. sunlight_propagates = true,
  847. selection_box = selection_box,
  848. node_box = node_box,
  849. on_construct = reset_meta,
  850. on_receive_fields = on_receive_fields,
  851. sounds = default.node_sound_wood_defaults(),
  852. mesecons = mesecons,
  853. digiline = digiline,
  854. -- Virtual portstates are the ports that
  855. -- the node shows as powered up (light up).
  856. virtual_portstates = {
  857. red = red == 1,
  858. blue = blue == 1,
  859. yellow = yellow == 1,
  860. green = green == 1,
  861. black = black == 1,
  862. white = white == 1,
  863. },
  864. after_dig_node = function(pos, node)
  865. mesecon.do_cooldown(pos)
  866. mesecon.receptor_off(pos, output_rules)
  867. pipeworks.after_dig(pos, node)
  868. end,
  869. is_luacontroller = true,
  870. on_timer = node_timer,
  871. tubelike = 1,
  872. tube = {
  873. connect_sides = {front = 1, back = 1, left = 1, right = 1, top = 1, bottom = 1},
  874. priority = 50,
  875. can_go = function(pos, node, velocity, stack)
  876. local src = {name = nil}
  877. -- add color of the incoming tube explicitly; referring to rules, in case they change later
  878. for color, rule in pairs(rules) do
  879. if (-velocity.x == rule.x and -velocity.y == rule.y and -velocity.z == rule.z) then
  880. src.name = rule.name
  881. break
  882. end
  883. end
  884. local succ, _, msg = run(pos, {
  885. type = "item",
  886. pin = src,
  887. itemstring = stack:to_string(),
  888. item = stack:to_table(),
  889. velocity = velocity,
  890. })
  891. if not succ or type(msg) ~= "string" then
  892. return go_back(velocity)
  893. end
  894. local r = rules[msg]
  895. return r and {r} or go_back(velocity)
  896. end,
  897. },
  898. after_place_node = pipeworks.after_place,
  899. on_blast = function(pos, intensity)
  900. if not intensity or intensity > 1 + 3^0.5 then
  901. minetest.remove_node(pos)
  902. return {string.format("%s_%s", name, dropname)}
  903. end
  904. minetest.swap_node(pos, {name = "pipeworks:broken_tube_1"})
  905. pipeworks.scan_for_tube_objects(pos)
  906. end,
  907. })
  908. end
  909. end
  910. end
  911. end
  912. end
  913. end
  914. ------------------------------------
  915. -- Overheated Lua controlled Tube --
  916. ------------------------------------
  917. local tiles_burnt = table.copy(tiles_base)
  918. tiles_burnt[1] = tiles_burnt[1].."^(pipeworks_lua_tube_port_burnt.png^[transformR90)"
  919. tiles_burnt[2] = tiles_burnt[2].."^(pipeworks_lua_tube_port_burnt.png^[transformR90)"
  920. tiles_burnt[5] = tiles_burnt[5].."^(pipeworks_lua_tube_port_burnt.png^[transformR270)"
  921. tiles_burnt[6] = tiles_burnt[6].."^(pipeworks_lua_tube_port_burnt.png^[transformR90)"
  922. tiles_burnt[1] = tiles_burnt[1].."^(pipeworks_lua_tube_port_burnt.png^[transformR270)"
  923. tiles_burnt[2] = tiles_burnt[2].."^(pipeworks_lua_tube_port_burnt.png^[transformR270)"
  924. tiles_burnt[5] = tiles_burnt[5].."^(pipeworks_lua_tube_port_burnt.png^[transformR90)"
  925. tiles_burnt[6] = tiles_burnt[6].."^(pipeworks_lua_tube_port_burnt.png^[transformR270)"
  926. tiles_burnt[3] = tiles_burnt[3].."^(pipeworks_lua_tube_port_burnt.png^[transformR180)"
  927. tiles_burnt[4] = tiles_burnt[4].."^(pipeworks_lua_tube_port_burnt.png^[transformR180)"
  928. tiles_burnt[5] = tiles_burnt[5].."^(pipeworks_lua_tube_port_burnt.png^[transformR180)"
  929. tiles_burnt[6] = tiles_burnt[6].."^(pipeworks_lua_tube_port_burnt.png^[transformR180)"
  930. tiles_burnt[3] = tiles_burnt[3].."^pipeworks_lua_tube_port_burnt.png"
  931. tiles_burnt[4] = tiles_burnt[4].."^pipeworks_lua_tube_port_burnt.png"
  932. tiles_burnt[5] = tiles_burnt[5].."^pipeworks_lua_tube_port_burnt.png"
  933. tiles_burnt[6] = tiles_burnt[6].."^pipeworks_lua_tube_port_burnt.png"
  934. tiles_burnt[1] = tiles_burnt[1].."^(pipeworks_lua_tube_port_burnt.png^[transformR180)"
  935. tiles_burnt[2] = tiles_burnt[2].."^pipeworks_lua_tube_port_burnt.png"
  936. tiles_burnt[3] = tiles_burnt[3].."^(pipeworks_lua_tube_port_burnt.png^[transformR90)"
  937. tiles_burnt[4] = tiles_burnt[4].."^(pipeworks_lua_tube_port_burnt.png^[transformR270)"
  938. tiles_burnt[1] = tiles_burnt[1].."^pipeworks_lua_tube_port_burnt.png"
  939. tiles_burnt[2] = tiles_burnt[2].."^(pipeworks_lua_tube_port_burnt.png^[transformR180)"
  940. tiles_burnt[3] = tiles_burnt[3].."^(pipeworks_lua_tube_port_burnt.png^[transformR270)"
  941. tiles_burnt[4] = tiles_burnt[4].."^(pipeworks_lua_tube_port_burnt.png^[transformR90)"
  942. minetest.register_node(BASENAME .. "_burnt", {
  943. drawtype = "nodebox",
  944. tiles = tiles_burnt,
  945. is_burnt = true,
  946. paramtype = "light",
  947. is_ground_content = false,
  948. groups = {snappy = 3, tube = 1, tubedevice = 1, not_in_creative_inventory=1},
  949. drop = BASENAME.."000000",
  950. sunlight_propagates = true,
  951. selection_box = selection_box,
  952. node_box = node_box,
  953. on_construct = reset_meta,
  954. on_receive_fields = on_receive_fields,
  955. sounds = default.node_sound_wood_defaults(),
  956. virtual_portstates = {red = false, blue = false, yellow = false,
  957. green = false, black = false, white = false},
  958. mesecons = {
  959. effector = {
  960. rules = mesecon.rules.alldirs,
  961. action_change = function(pos, _, rule_name, new_state)
  962. update_real_port_states(pos, rule_name, new_state)
  963. end,
  964. },
  965. },
  966. tubelike = 1,
  967. tube = {
  968. connect_sides = {front = 1, back = 1, left = 1, right = 1, top = 1, bottom = 1},
  969. priority = 50,
  970. },
  971. after_place_node = pipeworks.after_place,
  972. after_dig_node = pipeworks.after_dig,
  973. on_blast = function(pos, intensity)
  974. if not intensity or intensity > 1 + 3^0.5 then
  975. minetest.remove_node(pos)
  976. return {string.format("%s_%s", name, dropname)}
  977. end
  978. minetest.swap_node(pos, {name = "pipeworks:broken_tube_1"})
  979. pipeworks.scan_for_tube_objects(pos)
  980. end,
  981. })
  982. ------------------------
  983. -- Craft Registration --
  984. ------------------------
  985. minetest.register_craft({
  986. type = "shapeless",
  987. output = BASENAME.."000000",
  988. recipe = {"pipeworks:mese_tube_000000", "mesecons_luacontroller:luacontroller0000"},
  989. })