init.lua 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. -- ______
  2. -- |
  3. -- |
  4. -- | __ ___ _ __ _ _
  5. -- | | | | | |\ | | |_| | | | | |_ |_|
  6. -- |___| |______ |__| | \| | | \ |__| |_ |_ |_ |\
  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(pos): runs the code in the controller at pos
  16. -- reset_meta(pos, code, errmsg): performs a software-reset, installs new code and prints error messages
  17. -- resetn(pos): performs a hardware reset, turns off all ports
  18. --
  19. -- The Sandbox
  20. -- The whole code of the controller runs in a sandbox,
  21. -- a very restricted environment.
  22. -- However, as this does not prevent you from using e.g. loops,
  23. -- we need to check for these prohibited commands first.
  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 is appending minetest.env to it.
  29. local BASENAME = "mesecons_luacontroller:luacontroller"
  30. local rules = {
  31. a = {x = -1, y = 0, z = 0, name="A"},
  32. b = {x = 0, y = 0, z = 1, name="B"},
  33. c = {x = 1, y = 0, z = 0, name="C"},
  34. d = {x = 0, y = 0, z = -1, name="D"},
  35. }
  36. ------------------
  37. -- Action stuff --
  38. ------------------
  39. -- These helpers are required to set the port states of the luacontroller
  40. local function update_real_port_states(pos, rule_name, new_state)
  41. local meta = minetest.get_meta(pos)
  42. if rule_name == nil then
  43. meta:set_int("real_portstates", 1)
  44. return
  45. end
  46. local n = meta:get_int("real_portstates") - 1
  47. local L = {}
  48. for i = 1, 4 do
  49. L[i] = n % 2
  50. n = math.floor(n / 2)
  51. end
  52. -- (0,-1) (-1,0) (1,0) (0,1)
  53. local pos_to_side = { 4, 1, nil, 3, 2 }
  54. if rule_name.x == nil then
  55. for _, rname in ipairs(rule_name) do
  56. local port = pos_to_side[rname.x + (2 * rname.z) + 3]
  57. L[port] = (newstate == "on") and 1 or 0
  58. end
  59. else
  60. local port = pos_to_side[rule_name.x + (2 * rule_name.z) + 3]
  61. L[port] = (new_state == "on") and 1 or 0
  62. end
  63. meta:set_int("real_portstates",
  64. 1 +
  65. 1 * L[1] +
  66. 2 * L[2] +
  67. 4 * L[3] +
  68. 8 * L[4])
  69. end
  70. local port_names = {"a", "b", "c", "d"}
  71. local function get_real_port_states(pos)
  72. -- Determine if ports are powered (by itself or from outside)
  73. local meta = minetest.get_meta(pos)
  74. local L = {}
  75. local n = meta:get_int("real_portstates") - 1
  76. for _, name in ipairs(port_names) do
  77. L[name] = ((n % 2) == 1)
  78. n = math.floor(n / 2)
  79. end
  80. return L
  81. end
  82. local function merge_port_states(ports, vports)
  83. return {
  84. a = ports.a or vports.a,
  85. b = ports.b or vports.b,
  86. c = ports.c or vports.c,
  87. d = ports.d or vports.d,
  88. }
  89. end
  90. local function generate_name(ports)
  91. local d = ports.d and 1 or 0
  92. local c = ports.c and 1 or 0
  93. local b = ports.b and 1 or 0
  94. local a = ports.a and 1 or 0
  95. return BASENAME..d..c..b..a
  96. end
  97. local function set_port(pos, rule, state)
  98. if state then
  99. mesecon.receptor_on(pos, {rule})
  100. else
  101. mesecon.receptor_off(pos, {rule})
  102. end
  103. end
  104. local function clean_port_states(ports)
  105. ports.a = ports.a and true or false
  106. ports.b = ports.b and true or false
  107. ports.c = ports.c and true or false
  108. ports.d = ports.d and true or false
  109. end
  110. local function set_port_states(pos, ports)
  111. local node = minetest.get_node(pos)
  112. local name = node.name
  113. clean_port_states(ports)
  114. local vports = minetest.registered_nodes[name].virtual_portstates
  115. local new_name = generate_name(ports)
  116. if name ~= new_name and vports then
  117. -- Problem:
  118. -- We need to place the new node first so that when turning
  119. -- off some port, it won't stay on because the rules indicate
  120. -- there is an onstate output port there.
  121. -- When turning the output off then, it will however cause feedback
  122. -- so that the luacontroller will receive an "off" event by turning
  123. -- its output off.
  124. -- Solution / Workaround:
  125. -- Remember which output was turned off and ignore next "off" event.
  126. local meta = minetest.get_meta(pos)
  127. local ign = minetest.deserialize(meta:get_string("ignore_offevents")) or {}
  128. if ports.a and not vports.a and not mesecon.is_powered(pos, rules.a) then ign.A = true end
  129. if ports.b and not vports.b and not mesecon.is_powered(pos, rules.b) then ign.B = true end
  130. if ports.c and not vports.c and not mesecon.is_powered(pos, rules.c) then ign.C = true end
  131. if ports.d and not vports.d and not mesecon.is_powered(pos, rules.d) then ign.D = true end
  132. meta:set_string("ignore_offevents", minetest.serialize(ign))
  133. minetest.swap_node(pos, {name = new_name, param2 = node.param2})
  134. if ports.a ~= vports.a then set_port(pos, rules.a, ports.a) end
  135. if ports.b ~= vports.b then set_port(pos, rules.b, ports.b) end
  136. if ports.c ~= vports.c then set_port(pos, rules.c, ports.c) end
  137. if ports.d ~= vports.d then set_port(pos, rules.d, ports.d) end
  138. end
  139. end
  140. -----------------
  141. -- Overheating --
  142. -----------------
  143. local function burn_controller(pos)
  144. local node = minetest.get_node(pos)
  145. node.name = BASENAME.."_burnt"
  146. minetest.swap_node(pos, node)
  147. minetest.get_meta(pos):set_string("lc_memory", "");
  148. -- Wait for pending operations
  149. minetest.after(0.2, mesecon.receptor_off, pos, mesecon.rules.flat)
  150. end
  151. local function overheat(pos, meta)
  152. if mesecon.do_overheat(pos) then -- If too hot
  153. burn_controller(pos)
  154. return true
  155. end
  156. end
  157. ------------------------
  158. -- Ignored off events --
  159. ------------------------
  160. local function ignore_event(event, meta)
  161. if event.type ~= "off" then return false end
  162. local ignore_offevents = minetest.deserialize(meta:get_string("ignore_offevents")) or {}
  163. if ignore_offevents[event.pin.name] then
  164. ignore_offevents[event.pin.name] = nil
  165. meta:set_string("ignore_offevents", minetest.serialize(ignore_offevents))
  166. return true
  167. end
  168. end
  169. -------------------------
  170. -- Parsing and running --
  171. -------------------------
  172. local function safe_print(param)
  173. print(dump(param))
  174. end
  175. local function safe_date()
  176. return(os.date("*t",os.time()))
  177. end
  178. -- string.rep(str, n) with a high value for n can be used to DoS
  179. -- the server. Therefore, limit max. length of generated string.
  180. local function safe_string_rep(str, n)
  181. if #str * n > mesecon.setting("luacontroller_string_rep_max", 64000) then
  182. error("string.rep: string length overflow", 2)
  183. end
  184. return string.rep(str, n)
  185. end
  186. local function remove_functions(x)
  187. local tp = type(x)
  188. if tp == "table" then
  189. for key, value in pairs(x) do
  190. local key_t, val_t = type(key), type(value)
  191. if key_t == "function" or val_t == "function" then
  192. x[key] = nil
  193. else
  194. if key_t == "table" then
  195. remove_functions(key)
  196. end
  197. if val_t == "table" then
  198. remove_functions(value)
  199. end
  200. end
  201. end
  202. elseif tp == "function" then
  203. return nil
  204. end
  205. return x
  206. end
  207. local function get_interrupt(pos)
  208. -- iid = interrupt id
  209. local function interrupt(time, iid)
  210. if type(time) ~= "number" then return end
  211. local luac_id = minetest.get_meta(pos):get_int("luac_id")
  212. mesecon.queue:add_action(pos, "lc_interrupt", {luac_id, iid}, time, iid, 1)
  213. end
  214. return interrupt
  215. end
  216. local function get_digiline_send(pos)
  217. if not digiline then return end
  218. return function(channel, msg)
  219. minetest.after(0, function()
  220. digiline:receptor_send(pos, digiline.rules.default, channel, msg)
  221. end)
  222. end
  223. end
  224. local safe_globals = {
  225. "assert", "error", "ipairs", "next", "pairs", "select",
  226. "tonumber", "tostring", "type", "unpack", "_VERSION"
  227. }
  228. local function create_environment(pos, mem, event)
  229. -- Gather variables for the environment
  230. local vports = minetest.registered_nodes[minetest.get_node(pos).name].virtual_portstates
  231. local vports_copy = {}
  232. for k, v in pairs(vports) do vports_copy[k] = v end
  233. local rports = get_real_port_states(pos)
  234. -- Create new library tables on each call to prevent one LuaController
  235. -- from breaking a library and messing up other LuaControllers.
  236. local env = {
  237. pin = merge_port_states(vports, rports),
  238. port = vports_copy,
  239. event = event,
  240. mem = mem,
  241. heat = minetest.get_meta(pos):get_int("heat"),
  242. heat_max = mesecon.setting("overheat_max", 20),
  243. print = safe_print,
  244. interrupt = get_interrupt(pos),
  245. digiline_send = get_digiline_send(pos),
  246. string = {
  247. byte = string.byte,
  248. char = string.char,
  249. format = string.format,
  250. len = string.len,
  251. lower = string.lower,
  252. upper = string.upper,
  253. rep = safe_string_rep,
  254. reverse = string.reverse,
  255. sub = string.sub,
  256. },
  257. math = {
  258. abs = math.abs,
  259. acos = math.acos,
  260. asin = math.asin,
  261. atan = math.atan,
  262. atan2 = math.atan2,
  263. ceil = math.ceil,
  264. cos = math.cos,
  265. cosh = math.cosh,
  266. deg = math.deg,
  267. exp = math.exp,
  268. floor = math.floor,
  269. fmod = math.fmod,
  270. frexp = math.frexp,
  271. huge = math.huge,
  272. ldexp = math.ldexp,
  273. log = math.log,
  274. log10 = math.log10,
  275. max = math.max,
  276. min = math.min,
  277. modf = math.modf,
  278. pi = math.pi,
  279. pow = math.pow,
  280. rad = math.rad,
  281. random = math.random,
  282. sin = math.sin,
  283. sinh = math.sinh,
  284. sqrt = math.sqrt,
  285. tan = math.tan,
  286. tanh = math.tanh,
  287. },
  288. table = {
  289. concat = table.concat,
  290. insert = table.insert,
  291. maxn = table.maxn,
  292. remove = table.remove,
  293. sort = table.sort,
  294. },
  295. os = {
  296. clock = os.clock,
  297. difftime = os.difftime,
  298. time = os.time,
  299. datetable = safe_date,
  300. },
  301. }
  302. env._G = env
  303. for _, name in pairs(safe_globals) do
  304. env[name] = _G[name]
  305. end
  306. return env
  307. end
  308. local function timeout()
  309. error("Code timed out!", 2)
  310. end
  311. local function strip_squarebrackets(code, pos)
  312. if code:byte(pos) ~= 91 then return end -- [
  313. pos = pos + 1
  314. local start = pos
  315. while code:byte(pos) == 61 do -- =
  316. pos = pos + 1
  317. end
  318. if code:byte(pos) ~= 91 then return end -- [
  319. local _, last = code:find('%]'..string.rep('=', pos-start)..'%]', pos + 1)
  320. last = last or #code
  321. return code:sub(1, start - 1) .. " " .. code:sub(last + 1, #code)
  322. end
  323. local function strip_strings_and_comments(code)
  324. local i = 1
  325. local len = #code
  326. local start, new
  327. while i <= len do
  328. local c = code:byte(i)
  329. if c == 34 or c == 39 then
  330. -- " or '
  331. start = i
  332. i = i + 1
  333. while i <= len and code:byte(i) ~= c do
  334. if code:byte(i) == 92 then i = i + 1 end -- \
  335. i = i + 1
  336. end
  337. code = code:sub(1, start - 1) .. " " .. code:sub(i + 1, #code)
  338. i = start
  339. len = #code
  340. elseif c == 91 then
  341. -- [=...[ ... ]=...]
  342. new = strip_squarebrackets(code, i)
  343. if new then
  344. code = new
  345. len = #code
  346. else
  347. i = i + 1
  348. end
  349. elseif c == 45 and code:byte(i + 1) == 45 then
  350. -- --
  351. i = i + 2
  352. new = strip_squarebrackets(code, i)
  353. if new then
  354. code = new
  355. else
  356. start = code:find("[\r\n]", i) or #code
  357. code = code:sub(1, i - 1) .. code:sub(start + 1, #code)
  358. end
  359. len = #code
  360. else
  361. i = i + 1
  362. end
  363. end
  364. return code
  365. end
  366. local function code_prohibited(code)
  367. -- LuaJIT doesn't increment the instruction counter when running
  368. -- loops, so we have to sanitize inputs if we're using LuaJIT.
  369. if not rawget(_G, "jit") then
  370. return false
  371. end
  372. local prohibited = {"while", "for", "repeat", "until", "goto"}
  373. code = " "..strip_strings_and_comments(code).." "
  374. for _, p in ipairs(prohibited) do
  375. if string.find(code, "[^%w_]"..p.."[^%w_]") then
  376. return "Prohibited command: "..p
  377. end
  378. end
  379. end
  380. local function create_sandbox(code, env)
  381. if code:byte(1) == 27 then
  382. return nil, "Binary code prohibited."
  383. end
  384. local f, msg = loadstring(code)
  385. if not f then return nil, msg end
  386. setfenv(f, env)
  387. return function(...)
  388. -- Normal Lua: Use instruction counter to stop execution
  389. -- after luacontroller_maxevents.
  390. -- LuaJIT: Count function calls instead of instructions, allows usage
  391. -- of function keyword. However, LuaJIT still doesn't trigger
  392. -- lines events when using infinite loops.
  393. local maxevents = mesecon.setting("luacontroller_maxevents", 10000)
  394. if not rawget(_G, "jit") then
  395. debug.sethook(timeout, "", maxevents)
  396. else
  397. local events = 0
  398. debug.sethook(function ()
  399. events = events + 1
  400. if events > maxevents then
  401. timeout()
  402. end
  403. end, "c")
  404. end
  405. local ok, ret = pcall(f, ...)
  406. debug.sethook() -- Clear hook
  407. if not ok then error(ret, 0) end
  408. return ret
  409. end
  410. end
  411. local function load_memory(meta)
  412. return minetest.deserialize(meta:get_string("lc_memory")) or {}
  413. end
  414. local function save_memory(pos, meta, mem)
  415. local memstring = minetest.serialize(remove_functions(mem))
  416. local memsize_max = mesecon.setting("luacontroller_memsize", 100000)
  417. if (#memstring <= memsize_max) then
  418. meta:set_string("lc_memory", memstring)
  419. else
  420. print("Error: Luacontroller memory overflow. "..memsize_max.." bytes available, "
  421. ..#memstring.." required. Controller overheats.")
  422. burn_controller(pos)
  423. end
  424. end
  425. local function run(pos, event)
  426. local meta = minetest.get_meta(pos)
  427. if overheat(pos) then return end
  428. if ignore_event(event, meta) then return end
  429. -- Load code & mem from meta
  430. local mem = load_memory(meta)
  431. local code = meta:get_string("code")
  432. local err = code_prohibited(code)
  433. if err then return err end
  434. -- Create environment
  435. local env = create_environment(pos, mem, event)
  436. -- Create the sandbox and execute code
  437. local f, msg = create_sandbox(code, env)
  438. if not f then return msg end
  439. local success, msg = pcall(f)
  440. if not success then return msg end
  441. if type(env.port) ~= "table" then
  442. return "Ports set are invalid."
  443. end
  444. -- Actually set the ports
  445. set_port_states(pos, env.port)
  446. -- Save memory. This may burn the luacontroller if a memory overflow occurs.
  447. save_memory(pos, meta, env.mem)
  448. end
  449. mesecon.queue:add_function("lc_interrupt", function (pos, luac_id, iid)
  450. -- There is no luacontroller anymore / it has been reprogrammed / replaced / burnt
  451. if (minetest.get_meta(pos):get_int("luac_id") ~= luac_id) then return end
  452. if (minetest.registered_nodes[minetest.get_node(pos).name].is_burnt) then return end
  453. run(pos, {type="interrupt", iid = iid})
  454. end)
  455. local function reset_meta(pos, code, errmsg)
  456. local meta = minetest.get_meta(pos)
  457. meta:set_string("code", code)
  458. code = minetest.formspec_escape(code or "")
  459. errmsg = minetest.formspec_escape(errmsg or "")
  460. meta:set_string("formspec", "size[10,8]"..
  461. "background[-0.2,-0.25;10.4,8.75;jeija_luac_background.png]"..
  462. "textarea[0.2,0.6;10.2,5;code;;"..code.."]"..
  463. "image_button[3.75,6;2.5,1;jeija_luac_runbutton.png;program;]"..
  464. "image_button_exit[9.72,-0.25;0.425,0.4;jeija_close_window.png;exit;]"..
  465. "label[0.1,5;"..errmsg.."]")
  466. meta:set_int("heat", 0)
  467. meta:set_int("luac_id", math.random(1, 65535))
  468. end
  469. local function reset(pos)
  470. set_port_states(pos, {a=false, b=false, c=false, d=false})
  471. end
  472. -----------------------
  473. -- Node Registration --
  474. -----------------------
  475. local output_rules = {}
  476. local input_rules = {}
  477. local node_box = {
  478. type = "fixed",
  479. fixed = {
  480. {-8/16, -8/16, -8/16, 8/16, -7/16, 8/16}, -- Bottom slab
  481. {-5/16, -7/16, -5/16, 5/16, -6/16, 5/16}, -- Circuit board
  482. {-3/16, -6/16, -3/16, 3/16, -5/16, 3/16}, -- IC
  483. }
  484. }
  485. local selection_box = {
  486. type = "fixed",
  487. fixed = { -8/16, -8/16, -8/16, 8/16, -5/16, 8/16 },
  488. }
  489. local digiline = {
  490. receptor = {},
  491. effector = {
  492. action = function(pos, node, channel, msg)
  493. run(pos, {type = "digiline", channel = channel, msg = msg})
  494. end
  495. }
  496. }
  497. local function on_receive_fields(pos, form_name, fields)
  498. if not fields.program then
  499. return
  500. end
  501. reset(pos)
  502. reset_meta(pos, fields.code)
  503. local err = run(pos, {type="program"})
  504. if err then
  505. print(err)
  506. reset_meta(pos, fields.code, err)
  507. end
  508. end
  509. for a = 0, 1 do -- 0 = off 1 = on
  510. for b = 0, 1 do
  511. for c = 0, 1 do
  512. for d = 0, 1 do
  513. local cid = tostring(d)..tostring(c)..tostring(b)..tostring(a)
  514. local node_name = BASENAME..cid
  515. local top = "jeija_luacontroller_top.png"
  516. if a == 1 then
  517. top = top.."^jeija_luacontroller_LED_A.png"
  518. end
  519. if b == 1 then
  520. top = top.."^jeija_luacontroller_LED_B.png"
  521. end
  522. if c == 1 then
  523. top = top.."^jeija_luacontroller_LED_C.png"
  524. end
  525. if d == 1 then
  526. top = top.."^jeija_luacontroller_LED_D.png"
  527. end
  528. local groups
  529. if a + b + c + d ~= 0 then
  530. groups = {dig_immediate=2, not_in_creative_inventory=1, overheat = 1}
  531. else
  532. groups = {dig_immediate=2, overheat = 1}
  533. end
  534. output_rules[cid] = {}
  535. input_rules[cid] = {}
  536. if a == 1 then table.insert(output_rules[cid], rules.a) end
  537. if b == 1 then table.insert(output_rules[cid], rules.b) end
  538. if c == 1 then table.insert(output_rules[cid], rules.c) end
  539. if d == 1 then table.insert(output_rules[cid], rules.d) end
  540. if a == 0 then table.insert( input_rules[cid], rules.a) end
  541. if b == 0 then table.insert( input_rules[cid], rules.b) end
  542. if c == 0 then table.insert( input_rules[cid], rules.c) end
  543. if d == 0 then table.insert( input_rules[cid], rules.d) end
  544. local mesecons = {
  545. effector = {
  546. rules = input_rules[cid],
  547. action_change = function (pos, _, rule_name, new_state)
  548. update_real_port_states(pos, rule_name, new_state)
  549. run(pos, {type=new_state, pin=rule_name})
  550. end,
  551. },
  552. receptor = {
  553. state = mesecon.state.on,
  554. rules = output_rules[cid]
  555. }
  556. }
  557. minetest.register_node(node_name, {
  558. description = "LuaController",
  559. drawtype = "nodebox",
  560. tiles = {
  561. top,
  562. "jeija_microcontroller_bottom.png",
  563. "jeija_microcontroller_sides.png",
  564. "jeija_microcontroller_sides.png",
  565. "jeija_microcontroller_sides.png",
  566. "jeija_microcontroller_sides.png"
  567. },
  568. inventory_image = top,
  569. paramtype = "light",
  570. groups = groups,
  571. drop = BASENAME.."0000",
  572. sunlight_propagates = true,
  573. selection_box = selection_box,
  574. node_box = node_box,
  575. on_construct = reset_meta,
  576. on_receive_fields = on_receive_fields,
  577. sounds = default.node_sound_stone_defaults(),
  578. mesecons = mesecons,
  579. digiline = digiline,
  580. -- Virtual portstates are the ports that
  581. -- the node shows as powered up (light up).
  582. virtual_portstates = {
  583. a = a == 1,
  584. b = b == 1,
  585. c = c == 1,
  586. d = d == 1,
  587. },
  588. after_dig_node = function (pos, node)
  589. mesecon.receptor_off(pos, output_rules)
  590. end,
  591. is_luacontroller = true,
  592. })
  593. end
  594. end
  595. end
  596. end
  597. ------------------------------
  598. -- Overheated LuaController --
  599. ------------------------------
  600. minetest.register_node(BASENAME .. "_burnt", {
  601. drawtype = "nodebox",
  602. tiles = {
  603. "jeija_luacontroller_burnt_top.png",
  604. "jeija_microcontroller_bottom.png",
  605. "jeija_microcontroller_sides.png",
  606. "jeija_microcontroller_sides.png",
  607. "jeija_microcontroller_sides.png",
  608. "jeija_microcontroller_sides.png"
  609. },
  610. inventory_image = "jeija_luacontroller_burnt_top.png",
  611. is_burnt = true,
  612. paramtype = "light",
  613. groups = {dig_immediate=2, not_in_creative_inventory=1},
  614. drop = BASENAME.."0000",
  615. sunlight_propagates = true,
  616. selection_box = selection_box,
  617. node_box = node_box,
  618. on_construct = reset_meta,
  619. on_receive_fields = on_receive_fields,
  620. sounds = default.node_sound_stone_defaults(),
  621. virtual_portstates = {a = false, b = false, c = false, d = false},
  622. mesecons = {
  623. effector = {
  624. rules = mesecon.rules.flat,
  625. action_change = function(pos, _, rule_name, new_state)
  626. update_real_port_states(pos, rule_name, new_state)
  627. end,
  628. },
  629. },
  630. })
  631. ------------------------
  632. -- Craft Registration --
  633. ------------------------
  634. minetest.register_craft({
  635. output = BASENAME.."0000 2",
  636. recipe = {
  637. {'mesecons_materials:silicon', 'mesecons_materials:silicon', 'group:mesecon_conductor_craftable'},
  638. {'mesecons_materials:silicon', 'mesecons_materials:silicon', 'group:mesecon_conductor_craftable'},
  639. {'group:mesecon_conductor_craftable', 'group:mesecon_conductor_craftable', ''},
  640. }
  641. })