init.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. local plg = {}
  2. plg.rules = {}
  3. -- per-player formspec positions
  4. plg.open_formspecs = {}
  5. local lcore = dofile(minetest.get_modpath(minetest.get_current_modname()) .. "/logic.lua")
  6. dofile(minetest.get_modpath(minetest.get_current_modname()) .. "/tool.lua")(plg)
  7. plg.register_nodes = function(template)
  8. -- each loop is for one of the 4 IO ports
  9. for a = 0, 1 do
  10. for b = 0, 1 do
  11. for c = 0, 1 do
  12. for d = 0, 1 do
  13. local ndef = table.copy(template)
  14. local nodename = "mesecons_fpga:fpga"
  15. .. tostring(d) .. tostring(c) .. tostring(b) .. tostring(a)
  16. -- build top texture string
  17. local texture = "jeija_fpga_top.png"
  18. if a == 1 then texture = texture .. "^jeija_microcontroller_LED_A.png" end
  19. if b == 1 then texture = texture .. "^jeija_microcontroller_LED_B.png" end
  20. if c == 1 then texture = texture .. "^jeija_microcontroller_LED_C.png" end
  21. if d == 1 then texture = texture .. "^jeija_microcontroller_LED_D.png" end
  22. ndef.tiles[1] = texture
  23. ndef.inventory_image = texture
  24. if (a + b + c + d) > 0 then
  25. ndef.groups["not_in_creative_inventory"] = 1
  26. end
  27. -- interaction with mesecons (input / output)
  28. local rules_out = {}
  29. if a == 1 then table.insert(rules_out, {x = -1, y = 0, z = 0}) end
  30. if b == 1 then table.insert(rules_out, {x = 0, y = 0, z = 1}) end
  31. if c == 1 then table.insert(rules_out, {x = 1, y = 0, z = 0}) end
  32. if d == 1 then table.insert(rules_out, {x = 0, y = 0, z = -1}) end
  33. plg.rules[nodename] = rules_out
  34. local rules_in = {}
  35. if a == 0 then table.insert(rules_in, {x = -1, y = 0, z = 0}) end
  36. if b == 0 then table.insert(rules_in, {x = 0, y = 0, z = 1}) end
  37. if c == 0 then table.insert(rules_in, {x = 1, y = 0, z = 0}) end
  38. if d == 0 then table.insert(rules_in, {x = 0, y = 0, z = -1}) end
  39. ndef.mesecons.effector.rules = rules_in
  40. if (a + b + c + d) > 0 then
  41. ndef.mesecons.receptor = {
  42. state = mesecon.state.on,
  43. rules = rules_out,
  44. }
  45. end
  46. minetest.register_node(nodename, ndef)
  47. end
  48. end
  49. end
  50. end
  51. end
  52. plg.register_nodes({
  53. description = "FPGA",
  54. drawtype = "nodebox",
  55. tiles = {
  56. "", -- replaced later
  57. "jeija_microcontroller_bottom.png",
  58. "jeija_fpga_sides.png",
  59. "jeija_fpga_sides.png",
  60. "jeija_fpga_sides.png",
  61. "jeija_fpga_sides.png"
  62. },
  63. inventory_image = "", -- replaced later
  64. is_ground_content = false,
  65. sunlight_propagates = true,
  66. paramtype = "light",
  67. walkable = true,
  68. groups = {dig_immediate = 2, mesecon = 3, overheat = 1},
  69. drop = "mesecons_fpga:fpga0000",
  70. selection_box = {
  71. type = "fixed",
  72. fixed = { -8/16, -8/16, -8/16, 8/16, -5/16, 8/16 },
  73. },
  74. node_box = {
  75. type = "fixed",
  76. fixed = {
  77. { -8/16, -8/16, -8/16, 8/16, -7/16, 8/16 }, -- bottom slab
  78. { -5/16, -7/16, -5/16, 5/16, -6/16, 5/16 }, -- circuit board
  79. { -3/16, -6/16, -3/16, 3/16, -5/16, 3/16 }, -- IC
  80. }
  81. },
  82. on_construct = function(pos)
  83. local meta = minetest.get_meta(pos)
  84. local is = { {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} }
  85. meta:set_string("instr", lcore.serialize(is))
  86. meta:set_int("valid", 0)
  87. meta:set_string("infotext", "FPGA")
  88. end,
  89. on_rightclick = function(pos, node, clicker)
  90. if not minetest.is_player(clicker) then
  91. return
  92. end
  93. local meta = minetest.get_meta(pos)
  94. local name = clicker:get_player_name()
  95. -- Erase formspecs of old FPGAs
  96. meta:set_string("formspec", "")
  97. plg.open_formspecs[name] = pos
  98. local is = lcore.deserialize(meta:get_string("instr"))
  99. minetest.show_formspec(name, "mesecons:fpga", plg.to_formspec_string(is, nil))
  100. end,
  101. sounds = default.node_sound_stone_defaults(),
  102. mesecons = {
  103. effector = {
  104. rules = {}, -- replaced later
  105. action_change = function(pos, node, rule, newstate)
  106. plg.ports_changed(pos, rule, newstate)
  107. plg.update(pos)
  108. end
  109. }
  110. },
  111. after_dig_node = function(pos, node)
  112. mesecon.receptor_off(pos, plg.rules[node.name])
  113. for name, open_pos in pairs(plg.open_formspecs) do
  114. if vector.equals(pos, open_pos) then
  115. minetest.close_formspec(name, "mesecons:fpga")
  116. plg.open_formspecs[name] = nil
  117. end
  118. end
  119. end,
  120. on_blast = mesecon.on_blastnode,
  121. on_rotate = function(pos, node, user, mode)
  122. local abcd1 = {"A", "B", "C", "D"}
  123. local abcd2 = {A = 1, B = 2, C = 3, D = 4}
  124. local ops = {"op1", "op2", "dst"}
  125. local dir = 0
  126. if mode == screwdriver.ROTATE_FACE then -- clock-wise
  127. dir = 1
  128. if user and user:is_player() then
  129. minetest.chat_send_player(user:get_player_name(),
  130. "FPGA ports have been rotated clockwise.")
  131. end
  132. elseif mode == screwdriver.ROTATE_AXIS then -- counter-clockwise
  133. dir = -1
  134. if user and user:is_player() then
  135. minetest.chat_send_player(user:get_player_name(),
  136. "FPGA ports have been rotated counter-clockwise.")
  137. end
  138. end
  139. local meta = minetest.get_meta(pos)
  140. local instr = lcore.deserialize(meta:get_string("instr"))
  141. for i = 1, #instr do
  142. for _, op in ipairs(ops) do
  143. local o = instr[i][op]
  144. if o and o.type == "io" then
  145. local num = abcd2[o.port]
  146. num = num + dir
  147. if num > 4 then num = 1
  148. elseif num < 1 then num = 4 end
  149. instr[i][op].port = abcd1[num]
  150. end
  151. end
  152. end
  153. meta:set_string("instr", lcore.serialize(instr))
  154. plg.update_meta(pos, instr)
  155. return true
  156. end,
  157. })
  158. plg.to_formspec_string = function(is, err)
  159. local function dropdown_op(x, y, name, val)
  160. local s = "dropdown[" .. tostring(x) .. "," .. tostring(y) .. ";"
  161. .. "0.75,0.5;" .. name .. ";" -- the height seems to be ignored?
  162. s = s .. " ,A,B,C,D,0,1,2,3,4,5,6,7,8,9;"
  163. if val == nil then
  164. s = s .. "0" -- actually selects no field at all
  165. elseif val.type == "io" then
  166. local mapping = {
  167. ["A"] = 1,
  168. ["B"] = 2,
  169. ["C"] = 3,
  170. ["D"] = 4,
  171. }
  172. s = s .. tostring(1 + mapping[val.port])
  173. else -- "reg"
  174. s = s .. tostring(6 + val.n)
  175. end
  176. return s .. "]"
  177. end
  178. local function dropdown_action(x, y, name, val)
  179. local selected = 0
  180. local titles = { " " }
  181. for i, data in ipairs(lcore.get_operations()) do
  182. titles[i + 1] = data.fs_name
  183. if val == data.gate then
  184. selected = i + 1
  185. end
  186. end
  187. return ("dropdown[%f,%f;1.125,0.5;%s;%s;%i]"):format(
  188. x, y, name, table.concat(titles, ","), selected)
  189. end
  190. local s = "size[9,9]"..
  191. "label[3.4,-0.15;FPGA gate configuration]"..
  192. "button[7,7.5;2,2.5;program;Program]"..
  193. "box[4.2,0.5;0.03,7;#ffffff]"..
  194. "label[0.25,0.25;op. 1]"..
  195. "label[1.0,0.25;gate type]"..
  196. "label[2.125,0.25;op. 2]"..
  197. "label[3.15,0.25;dest]"..
  198. "label[4.5,0.25;op. 1]"..
  199. "label[5.25,0.25;gate type]"..
  200. "label[6.375,0.25;op. 2]"..
  201. "label[7.4,0.25;dest]"
  202. local x = 1 - 0.75
  203. local y = 1 - 0.25
  204. for i = 1, 14 do
  205. local cur = is[i]
  206. s = s .. dropdown_op (x , y, tostring(i).."op1", cur.op1)
  207. s = s .. dropdown_action(x+0.75 , y, tostring(i).."act", cur.action)
  208. s = s .. dropdown_op (x+1.875, y, tostring(i).."op2", cur.op2)
  209. s = s .. "label[" .. tostring(x+2.625) .. "," .. tostring(y+0.1) .. "; ->]"
  210. s = s .. dropdown_op (x+2.9 , y, tostring(i).."dst", cur.dst)
  211. y = y + 1
  212. if i == 7 then
  213. x = 4.5
  214. y = 1 - 0.25
  215. end
  216. end
  217. if err then
  218. local fmsg = minetest.colorize("#ff0000", minetest.formspec_escape(err.msg))
  219. s = s .. plg.red_box_around(err.i) ..
  220. "label[0.25,8.25;The gate configuration is erroneous in the marked area:]"..
  221. "label[0.25,8.5;" .. fmsg .. "]"
  222. end
  223. return s
  224. end
  225. plg.from_formspec_fields = function(fields)
  226. local function read_op(s)
  227. if s == nil or s == " " then
  228. return nil
  229. elseif s == "A" or s == "B" or s == "C" or s == "D" then
  230. return {type = "io", port = s}
  231. else
  232. return {type = "reg", n = tonumber(s)}
  233. end
  234. end
  235. local function read_action(s)
  236. for i, data in ipairs(lcore.get_operations()) do
  237. if data.fs_name == s then
  238. return data.gate
  239. end
  240. end
  241. end
  242. local is = {}
  243. for i = 1, 14 do
  244. local cur = {}
  245. cur.op1 = read_op(fields[tonumber(i) .. "op1"])
  246. cur.action = read_action(fields[tonumber(i) .. "act"])
  247. cur.op2 = read_op(fields[tonumber(i) .. "op2"])
  248. cur.dst = read_op(fields[tonumber(i) .. "dst"])
  249. is[#is + 1] = cur
  250. end
  251. return is
  252. end
  253. plg.update_meta = function(pos, is)
  254. if type(is) == "string" then -- serialized string
  255. is = lcore.deserialize(is)
  256. end
  257. local meta = minetest.get_meta(pos)
  258. local err = lcore.validate(is)
  259. if err == nil then
  260. meta:set_int("valid", 1)
  261. meta:set_string("infotext", "FPGA (functional)")
  262. else
  263. meta:set_int("valid", 0)
  264. meta:set_string("infotext", "FPGA")
  265. end
  266. -- reset ports and run programmed logic
  267. plg.setports(pos, false, false, false, false)
  268. plg.update(pos)
  269. -- Refresh open formspecs
  270. local form = plg.to_formspec_string(is, err)
  271. for name, open_pos in pairs(plg.open_formspecs) do
  272. if vector.equals(pos, open_pos) then
  273. minetest.show_formspec(name, "mesecons:fpga", form)
  274. end
  275. end
  276. return err
  277. end
  278. plg.red_box_around = function(i)
  279. local x, y
  280. if i > 7 then
  281. x = 4.5
  282. y = 0.75 + (i - 8)
  283. else
  284. x = 0.25
  285. y = 0.75 + (i - 1)
  286. end
  287. return string.format("box[%f,%f;3.8,0.8;#ff0000]", x-0.1, y-0.05)
  288. end
  289. plg.update = function(pos)
  290. local meta = minetest.get_meta(pos)
  291. if meta:get_int("valid") ~= 1 then
  292. return
  293. elseif mesecon.do_overheat(pos) then
  294. plg.setports(pos, false, false, false, false)
  295. meta:set_int("valid", 0)
  296. meta:set_string("infotext", "FPGA (overheated)")
  297. return
  298. end
  299. local is = lcore.deserialize(meta:get_string("instr"))
  300. local A, B, C, D = plg.getports(pos)
  301. A, B, C, D = lcore.interpret(is, A, B, C, D)
  302. plg.setports(pos, A, B, C, D)
  303. end
  304. plg.ports_changed = function(pos, rule, newstate)
  305. if rule == nil then return end
  306. local meta = minetest.get_meta(pos)
  307. local states
  308. local s = meta:get_string("portstates")
  309. if s == nil then
  310. states = {false, false, false, false}
  311. else
  312. states = {
  313. s:sub(1, 1) == "1",
  314. s:sub(2, 2) == "1",
  315. s:sub(3, 3) == "1",
  316. s:sub(4, 4) == "1",
  317. }
  318. end
  319. -- trick to transform rules (see register_node) into port number
  320. local portno = ({4, 1, nil, 3, 2})[3 + rule.x + 2*rule.z]
  321. states[portno] = (newstate == "on")
  322. meta:set_string("portstates",
  323. (states[1] and "1" or "0") .. (states[2] and "1" or "0") ..
  324. (states[3] and "1" or "0") .. (states[4] and "1" or "0")
  325. )
  326. end
  327. plg.getports = function(pos) -- gets merged states of INPUT & OUTPUT
  328. local sin, sout
  329. local s = minetest.get_meta(pos):get_string("portstates")
  330. if s == nil then
  331. sin = {false, false, false, false}
  332. else
  333. sin = {
  334. s:sub(1, 1) == "1",
  335. s:sub(2, 2) == "1",
  336. s:sub(3, 3) == "1",
  337. s:sub(4, 4) == "1",
  338. }
  339. end
  340. local name = minetest.get_node(pos).name
  341. assert(name:find("mesecons_fpga:fpga") == 1)
  342. local off = #"mesecons_fpga:fpga"
  343. sout = {
  344. name:sub(off+4, off+4) == "1",
  345. name:sub(off+3, off+3) == "1",
  346. name:sub(off+2, off+2) == "1",
  347. name:sub(off+1, off+1) == "1",
  348. }
  349. return unpack({
  350. sin[1] or sout[1],
  351. sin[2] or sout[2],
  352. sin[3] or sout[3],
  353. sin[4] or sout[4],
  354. })
  355. end
  356. plg.setports = function(pos, A, B, C, D) -- sets states of OUTPUT
  357. local base = "mesecons_fpga:fpga"
  358. local name = base
  359. .. (D and "1" or "0") .. (C and "1" or "0")
  360. .. (B and "1" or "0") .. (A and "1" or "0")
  361. minetest.swap_node(pos, {name = name, param2 = minetest.get_node(pos).param2})
  362. if A ~= nil then
  363. local ru = plg.rules[base .. "0001"]
  364. if A then mesecon.receptor_on(pos, ru) else mesecon.receptor_off(pos, ru) end
  365. end
  366. if B ~= nil then
  367. local ru = plg.rules[base .. "0010"]
  368. if B then mesecon.receptor_on(pos, ru) else mesecon.receptor_off(pos, ru) end
  369. end
  370. if C ~= nil then
  371. local ru = plg.rules[base .. "0100"]
  372. if C then mesecon.receptor_on(pos, ru) else mesecon.receptor_off(pos, ru) end
  373. end
  374. if D ~= nil then
  375. local ru = plg.rules[base .. "1000"]
  376. if D then mesecon.receptor_on(pos, ru) else mesecon.receptor_off(pos, ru) end
  377. end
  378. end
  379. minetest.register_on_player_receive_fields(function(player, formname, fields)
  380. local player_name = player:get_player_name()
  381. if formname ~= "mesecons:fpga" or fields.quit then
  382. plg.open_formspecs[player_name] = nil -- potential garbage
  383. return
  384. end
  385. if not fields.program then
  386. return -- we only care when the user clicks "Program"
  387. end
  388. local pos = plg.open_formspecs[player_name]
  389. if minetest.is_protected(pos, player_name) then
  390. minetest.record_protection_violation(pos, player_name)
  391. return
  392. end
  393. local meta = minetest.get_meta(pos)
  394. local is = plg.from_formspec_fields(fields)
  395. meta:set_string("instr", lcore.serialize(is))
  396. local err = plg.update_meta(pos, is)
  397. if not err then
  398. plg.open_formspecs[player_name] = nil
  399. -- Close on success
  400. minetest.close_formspec(player_name, "mesecons:fpga")
  401. end
  402. end)
  403. minetest.register_on_leaveplayer(function(player)
  404. plg.open_formspecs[player:get_player_name()] = nil
  405. end)
  406. minetest.register_craft({
  407. output = "mesecons_fpga:fpga0000 2",
  408. recipe = {
  409. {'group:mesecon_conductor_craftable', 'group:mesecon_conductor_craftable'},
  410. {'mesecons_materials:silicon', 'mesecons_materials:silicon'},
  411. {'group:mesecon_conductor_craftable', 'group:mesecon_conductor_craftable'},
  412. }
  413. })