init.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. -- Reference
  2. -- ports = get_real_portstates(pos): gets if inputs are powered from outside
  3. -- newport = merge_portstates(state1, state2): just does result = state1 or state2 for every port
  4. -- action_setports(pos, rule, state): activates/deactivates the mesecons according to the portstates (helper for action)
  5. -- action(pos, ports): Applies new portstates to a luacontroller at pos
  6. -- lc_update(pos): updates the controller at pos by executing the code
  7. -- reset_meta (pos, code, errmsg): performs a software-reset, installs new code and prints error messages
  8. -- reset (pos): performs a hardware reset, turns off all ports
  9. --
  10. -- The Sandbox
  11. -- The whole code of the controller runs in a sandbox,
  12. -- a very restricted environment.
  13. -- However, as this does not prevent you from using e.g. loops,
  14. -- we need to check for these prohibited commands first.
  15. -- Actually the only way to damage the server is to
  16. -- use too much memory from the sandbox.
  17. -- You can add more functions to the environment
  18. -- (see where local env is defined)
  19. -- Something nice to play is is appending minetest.env to it.
  20. local BASENAME = "mesecons_luacontroller:luacontroller"
  21. local rules = {}
  22. rules.a = {x = -1, y = 0, z = 0, name="A"}
  23. rules.b = {x = 0, y = 0, z = 1, name="B"}
  24. rules.c = {x = 1, y = 0, z = 0, name="C"}
  25. rules.d = {x = 0, y = 0, z = -1, name="D"}
  26. ------------------
  27. -- Action stuff --
  28. ------------------
  29. -- These helpers are required to set the portstates of the luacontroller
  30. local get_real_portstates = function(pos) -- determine if ports are powered (by itself or from outside)
  31. ports = {
  32. a = mesecon:is_power_on(mesecon:addPosRule(pos, rules.a))
  33. and mesecon:rules_link(mesecon:addPosRule(pos, rules.a), pos),
  34. b = mesecon:is_power_on(mesecon:addPosRule(pos, rules.b))
  35. and mesecon:rules_link(mesecon:addPosRule(pos, rules.b), pos),
  36. c = mesecon:is_power_on(mesecon:addPosRule(pos, rules.c))
  37. and mesecon:rules_link(mesecon:addPosRule(pos, rules.c), pos),
  38. d = mesecon:is_power_on(mesecon:addPosRule(pos, rules.d))
  39. and mesecon:rules_link(mesecon:addPosRule(pos, rules.d), pos),
  40. }
  41. return ports
  42. end
  43. local merge_portstates = function (ports, vports)
  44. local npo = {a=false, b=false, c=false, d=false}
  45. npo.a = vports.a or ports.a
  46. npo.b = vports.b or ports.b
  47. npo.c = vports.c or ports.c
  48. npo.d = vports.d or ports.d
  49. return npo
  50. end
  51. local generate_name = function (ports)
  52. local overwrite = overwrite or {}
  53. local d = ports.d and 1 or 0
  54. local c = ports.c and 1 or 0
  55. local b = ports.b and 1 or 0
  56. local a = ports.a and 1 or 0
  57. return BASENAME..d..c..b..a
  58. end
  59. local setport = function (pos, rule, state)
  60. if state then
  61. mesecon:receptor_on(pos, {rule})
  62. else
  63. mesecon:receptor_off(pos, {rule})
  64. end
  65. end
  66. local action = function (pos, ports)
  67. local name = minetest.env:get_node(pos).name
  68. local vports = minetest.registered_nodes[name].virtual_portstates
  69. local newname = generate_name(ports)
  70. if name ~= newname and vports then
  71. local rules_on = {}
  72. local rules_off = {}
  73. mesecon:swap_node(pos, newname)
  74. if ports.a ~= vports.a then setport(pos, rules.a, ports.a) end
  75. if ports.b ~= vports.b then setport(pos, rules.b, ports.b) end
  76. if ports.c ~= vports.c then setport(pos, rules.c, ports.c) end
  77. if ports.d ~= vports.d then setport(pos, rules.d, ports.d) end
  78. end
  79. end
  80. --------------------
  81. -- Overheat stuff --
  82. --------------------
  83. local heat = function (meta) -- warm up
  84. h = meta:get_int("heat")
  85. if h ~= nil then
  86. meta:set_int("heat", h + 1)
  87. end
  88. end
  89. local cool = function (meta) -- cool down after a while
  90. h = meta:get_int("heat")
  91. if h ~= nil then
  92. meta:set_int("heat", h - 1)
  93. end
  94. end
  95. local overheat = function (meta) -- determine if too hot
  96. h = meta:get_int("heat")
  97. if h == nil then return true end -- if nil then overheat
  98. if h > 20 then
  99. return true
  100. else
  101. return false
  102. end
  103. end
  104. local overheat_off = function(pos)
  105. mesecon:receptor_off(pos, mesecon.rules.flat)
  106. end
  107. -------------------
  108. -- Parsing stuff --
  109. -------------------
  110. local code_prohibited = function(code)
  111. -- Clean code
  112. local prohibited = {"while", "for", "repeat", "until", "function"}
  113. for _, p in ipairs(prohibited) do
  114. if string.find(code, p) then
  115. return "Prohibited command: "..p
  116. end
  117. end
  118. end
  119. local safeprint = function(param)
  120. print(dump(param))
  121. end
  122. local interrupt = function(params)
  123. lc_update(params.pos, {type="interrupt", iid = params.iid})
  124. end
  125. local getinterrupt = function(pos)
  126. local interrupt = function (time, iid) -- iid = interrupt id
  127. if type(time) ~= "number" then return end
  128. local iid = iid or math.random()
  129. local meta = minetest.env:get_meta(pos)
  130. local interrupts = minetest.deserialize(meta:get_string("lc_interrupts")) or {}
  131. local found = false
  132. for _, i in ipairs(interrupts) do
  133. if minetest.serialize(i) == minetest.serialize(iid) then
  134. found = true
  135. break
  136. end
  137. end
  138. if not found then
  139. table.insert(interrupts, iid)
  140. meta:set_string("lc_interrupts", minetest.serialize(interrupts))
  141. end
  142. minetest.after(time, interrupt, {pos=pos, iid = iid})
  143. end
  144. return interrupt
  145. end
  146. local getdigiline_send = function (pos)
  147. local digiline_send = function (channel, msg)
  148. if digiline then
  149. digiline:receptor_send(pos, digiline.rules.default, channel, msg)
  150. end
  151. end
  152. return digiline_send
  153. end
  154. local create_environment = function(pos, mem, event)
  155. -- Gather variables for the environment
  156. local vports = minetest.registered_nodes[minetest.env:get_node(pos).name].virtual_portstates
  157. vports = {a = vports.a, b = vports.b, c = vports.c, d = vports.d}
  158. local rports = get_real_portstates(pos)
  159. return {
  160. print = safeprint,
  161. pin = merge_portstates(vports, rports),
  162. port = vports,
  163. interrupt = getinterrupt(pos),
  164. digiline_send = getdigiline_send(pos),
  165. mem = mem,
  166. tostring = tostring,
  167. tonumber = tonumber,
  168. string = {
  169. byte = string.byte,
  170. char = string.char,
  171. find = string.find,
  172. format = string.format,
  173. gmatch = string.gmatch,
  174. gsub = string.gsub,
  175. len = string.len,
  176. lower = string.lower,
  177. match = string.match,
  178. rep = string.rep,
  179. reverse = string.reverse,
  180. sub = string.sub,
  181. },
  182. math = {
  183. abs = math.abs,
  184. acos = math.acos,
  185. asin = math.asin,
  186. atan = math.atan,
  187. atan2 = math.atan2,
  188. ceil = math.ceil,
  189. cos = math.cos,
  190. cosh = math.cosh,
  191. deg = math.deg,
  192. exp = math.exp,
  193. floor = math.floor,
  194. fmod = math.fmod,
  195. frexp = math.frexp,
  196. huge = math.huge,
  197. ldexp = math.ldexp,
  198. log = math.log,
  199. log10 = math.log10,
  200. max = math.max,
  201. min = math.min,
  202. modf = math.modf,
  203. pi = math.pi,
  204. pow = math.pow,
  205. rad = math.rad,
  206. random = math.random,
  207. sin = math.sin,
  208. sinh = math.sinh,
  209. sqrt = math.sqrt,
  210. tan = math.tan,
  211. tanh = math.tanh,
  212. },
  213. table = {
  214. insert = table.insert,
  215. maxn = table.maxn,
  216. remove = table.remove,
  217. sort = table.sort
  218. },
  219. event = event,
  220. }
  221. end
  222. local create_sandbox = function (code, env)
  223. -- Create Sandbox
  224. if code:byte(1) == 27 then
  225. return _, "You Hacker You! Don't use binary code!"
  226. end
  227. f, msg = loadstring(code)
  228. if not f then return _, msg end
  229. setfenv(f, env)
  230. return f
  231. end
  232. local do_overheat = function (pos, meta)
  233. -- Overheat protection
  234. heat(meta)
  235. minetest.after(0.5, cool, meta)
  236. if overheat(meta) then
  237. mesecon:swap_node(pos, BASENAME.."_burnt")
  238. minetest.env:get_meta(pos):set_string("lc_interrupts", "")
  239. minetest.after(0.2, overheat_off, pos) -- wait for pending operations
  240. return true
  241. end
  242. end
  243. local load_memory = function(meta)
  244. return minetest.deserialize(meta:get_string("lc_memory")) or {}
  245. end
  246. local save_memory = function(meta, mem)
  247. meta:set_string("lc_memory", minetest.serialize(mem))
  248. end
  249. local interrupt_allow = function (meta, event)
  250. if event.type ~= "interrupt" then return true end
  251. local interrupts = minetest.deserialize(meta:get_string("lc_interrupts")) or {}
  252. for _, i in ipairs(interrupts) do
  253. if minetest.serialize(i) == minetest.serialize(event.iid) then
  254. return true
  255. end
  256. end
  257. return false
  258. end
  259. local ports_invalid = function (var)
  260. if type(var) == "table" then
  261. return false
  262. end
  263. return "The ports you set are invalid"
  264. end
  265. ----------------------
  266. -- Parsing function --
  267. ----------------------
  268. lc_update = function (pos, event)
  269. local meta = minetest.env:get_meta(pos)
  270. if not interrupt_allow(meta, event) then return end
  271. if do_overheat(pos, meta) then return end
  272. -- load code & mem from memory
  273. local mem = load_memory(meta)
  274. local code = meta:get_string("code")
  275. -- make sure code is ok and create environment
  276. local prohibited = code_prohibited(code)
  277. if prohibited then return prohibited end
  278. local env = create_environment(pos, mem, event)
  279. -- create the sandbox and execute code
  280. local chunk, msg = create_sandbox (code, env)
  281. if not chunk then return msg end
  282. local success, msg = pcall(f)
  283. if not success then return msg end
  284. if ports_invalid(env.port) then return ports_invalid(env.port) end
  285. save_memory(meta, mem)
  286. -- Actually set the ports
  287. minetest.after(0, action, pos, env.port)
  288. end
  289. local reset_meta = function(pos, code, errmsg)
  290. local meta = minetest.env:get_meta(pos)
  291. meta:set_string("code", code)
  292. if minetest.formspec_escape then
  293. code = minetest.formspec_escape(code or "")
  294. errmsg = minetest.formspec_escape(errmsg or "")
  295. else
  296. code = string.gsub(code or "", "%[", "(") -- would otherwise
  297. code = string.gsub(code, "%]", ")") -- corrupt formspec
  298. errmsg = string.gsub(errmsg or "", "%[", "(") -- would otherwise
  299. errmsg = string.gsub(errmsg, "%]", ")") -- corrupt formspec
  300. end
  301. meta:set_string("formspec", "size[10,8]"..
  302. "background[-0.2,-0.25;10.4,8.75;jeija_luac_background.png]"..
  303. "textarea[0.2,0.6;10.2,5;code;;"..code.."]"..
  304. "image_button[3.75,6;2.5,1;jeija_luac_runbutton.png;program;]"..
  305. "image_button_exit[9.72,-0.25;0.425,0.4;jeija_close_window.png;exit;]"..
  306. "label[0.1,5;"..errmsg.."]")
  307. meta:set_int("heat", 0)
  308. end
  309. local reset = function (pos)
  310. minetest.env:get_meta(pos):set_string("lc_interrupts", "")
  311. action(pos, {a=false, b=false, c=false, d=false}, true)
  312. end
  313. -- ______
  314. -- |
  315. -- | | |
  316. -- |___| | __ ___ _ __ _ _
  317. -- | | | | |\ | | |_| | | | | |_ |_|
  318. -- | |______ |__| | \| | | \ |__| |_ |_ |_ |\
  319. --
  320. -----------------------
  321. -- Node Registration --
  322. -----------------------
  323. local output_rules={}
  324. local input_rules={}
  325. local nodebox = {
  326. type = "fixed",
  327. fixed = {
  328. { -8/16, -8/16, -8/16, 8/16, -7/16, 8/16 }, -- bottom slab
  329. { -5/16, -7/16, -5/16, 5/16, -6/16, 5/16 }, -- circuit board
  330. { -3/16, -6/16, -3/16, 3/16, -5/16, 3/16 }, -- IC
  331. }
  332. }
  333. local selectionbox = {
  334. type = "fixed",
  335. fixed = { -8/16, -8/16, -8/16, 8/16, -5/16, 8/16 },
  336. }
  337. local digiline = {
  338. receptor = {},
  339. effector = {
  340. action = function (pos, node, channel, msg)
  341. lc_update (pos, {type = "digiline", channel = channel, msg = msg})
  342. end
  343. }
  344. }
  345. for a = 0, 1 do -- 0 = off; 1 = on
  346. for b = 0, 1 do
  347. for c = 0, 1 do
  348. for d = 0, 1 do
  349. local cid = tostring(d)..tostring(c)..tostring(b)..tostring(a)
  350. local nodename = BASENAME..cid
  351. local top = "jeija_luacontroller_top.png"
  352. if a == 1 then
  353. top = top.."^jeija_luacontroller_LED_A.png"
  354. end
  355. if b == 1 then
  356. top = top.."^jeija_luacontroller_LED_B.png"
  357. end
  358. if c == 1 then
  359. top = top.."^jeija_luacontroller_LED_C.png"
  360. end
  361. if d == 1 then
  362. top = top.."^jeija_luacontroller_LED_D.png"
  363. end
  364. if a + b + c + d ~= 0 then
  365. groups = {dig_immediate=2, not_in_creative_inventory=1}
  366. else
  367. groups = {dig_immediate=2}
  368. end
  369. output_rules[cid] = {}
  370. input_rules[cid] = {}
  371. if (a == 1) then table.insert(output_rules[cid], rules.a) end
  372. if (b == 1) then table.insert(output_rules[cid], rules.b) end
  373. if (c == 1) then table.insert(output_rules[cid], rules.c) end
  374. if (d == 1) then table.insert(output_rules[cid], rules.d) end
  375. if (a == 0) then table.insert(input_rules[cid], rules.a) end
  376. if (b == 0) then table.insert(input_rules[cid], rules.b) end
  377. if (c == 0) then table.insert(input_rules[cid], rules.c) end
  378. if (d == 0) then table.insert(input_rules[cid], rules.d) end
  379. local mesecons = {
  380. effector =
  381. {
  382. rules = input_rules[cid],
  383. action_change = function (pos, _, rulename, newstate)
  384. lc_update(pos, {type=newstate, pin=rulename})
  385. end,
  386. },
  387. receptor =
  388. {
  389. state = mesecon.state.on,
  390. rules = output_rules[cid]
  391. }
  392. }
  393. minetest.register_node(nodename, {
  394. description = "Luacontroller",
  395. drawtype = "nodebox",
  396. tiles = {
  397. top,
  398. "jeija_microcontroller_bottom.png",
  399. "jeija_microcontroller_sides.png",
  400. "jeija_microcontroller_sides.png",
  401. "jeija_microcontroller_sides.png",
  402. "jeija_microcontroller_sides.png"
  403. },
  404. inventory_image = top,
  405. paramtype = "light",
  406. groups = groups,
  407. drop = BASENAME.."0000",
  408. sunlight_propagates = true,
  409. selection_box = selectionbox,
  410. node_box = nodebox,
  411. on_construct = reset_meta,
  412. on_receive_fields = function(pos, formname, fields)
  413. reset(pos)
  414. reset_meta(pos, fields.code)
  415. local err = lc_update(pos, {type="program"})
  416. if err then print(err) end
  417. reset_meta(pos, fields.code, err)
  418. end,
  419. sounds = default.node_sound_stone_defaults(),
  420. mesecons = mesecons,
  421. digiline = digiline,
  422. is_luacontroller = true,
  423. virtual_portstates = { a = a == 1, -- virtual portstates are
  424. b = b == 1, -- the ports the the
  425. c = c == 1, -- controller powers itself
  426. d = d == 1},-- so those that light up
  427. after_dig_node = function (pos, node)
  428. mesecon:receptor_off(pos, output_rules)
  429. end,
  430. })
  431. end
  432. end
  433. end
  434. end
  435. --overheated luacontroller
  436. minetest.register_node(BASENAME .. "_burnt", {
  437. drawtype = "nodebox",
  438. tiles = {
  439. "jeija_luacontroller_burnt_top.png",
  440. "jeija_microcontroller_bottom.png",
  441. "jeija_microcontroller_sides.png",
  442. "jeija_microcontroller_sides.png",
  443. "jeija_microcontroller_sides.png",
  444. "jeija_microcontroller_sides.png"
  445. },
  446. inventory_image = "jeija_luacontroller_burnt_top.png",
  447. paramtype = "light",
  448. groups = {dig_immediate=2, not_in_creative_inventory=1},
  449. drop = BASENAME.."0000",
  450. sunlight_propagates = true,
  451. selection_box = selectionbox,
  452. node_box = nodebox,
  453. on_construct = reset_meta,
  454. on_receive_fields = function(pos, formname, fields)
  455. reset(pos)
  456. reset_meta(pos, fields.code)
  457. local err = lc_update(pos, {type="program"})
  458. if err then print(err) end
  459. reset_meta(pos, fields.code, err)
  460. end,
  461. sounds = default.node_sound_stone_defaults(),
  462. is_luacontroller = true,
  463. virtual_portstates = {a = false, b = false, c = false, d = false},
  464. })
  465. ------------------------
  466. -- Craft Registration --
  467. ------------------------
  468. minetest.register_craft({
  469. output = BASENAME.."0000 2",
  470. recipe = {
  471. {'mesecons_materials:silicon', 'mesecons_materials:silicon', 'group:mesecon_conductor_craftable'},
  472. {'mesecons_materials:silicon', 'mesecons_materials:silicon', 'group:mesecon_conductor_craftable'},
  473. {'group:mesecon_conductor_craftable', 'group:mesecon_conductor_craftable', ''},
  474. }
  475. })