mesecon_controller.lua 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. -- mesecon_controller.lua
  2. -- Mesecon-interfaceable Operation Panel alternative
  3. -- Looks like a Mesecon Luacontroller
  4. -- Luacontroller Adapted Code
  5. -- From Mesecons mod https://mesecons.net/
  6. -- (c) Jeija and Contributors
  7. local BASENAME = "advtrains_luaautomation:mesecon_controller"
  8. local rules = {
  9. a = {x = -1, y = 0, z = 0, name="A"},
  10. b = {x = 0, y = 0, z = 1, name="B"},
  11. c = {x = 1, y = 0, z = 0, name="C"},
  12. d = {x = 0, y = 0, z = -1, name="D"},
  13. }
  14. local function generate_name(ports)
  15. local d = ports.d and 1 or 0
  16. local c = ports.c and 1 or 0
  17. local b = ports.b and 1 or 0
  18. local a = ports.a and 1 or 0
  19. return BASENAME..d..c..b..a
  20. end
  21. local function set_port(pos, rule, state)
  22. if state then
  23. mesecon.receptor_on(pos, {rule})
  24. else
  25. mesecon.receptor_off(pos, {rule})
  26. end
  27. end
  28. local function clean_port_states(ports)
  29. ports.a = ports.a and true or false
  30. ports.b = ports.b and true or false
  31. ports.c = ports.c and true or false
  32. ports.d = ports.d and true or false
  33. end
  34. -- Local table for storing which Mesecons off events should be ignored
  35. -- Indexed by hex encoded position
  36. local ignored_off_events = {}
  37. local function set_port_states(pos, ports)
  38. local node = advtrains.ndb.get_node(pos)
  39. local name = node.name
  40. clean_port_states(ports)
  41. local vports = minetest.registered_nodes[name].virtual_portstates
  42. local new_name = generate_name(ports)
  43. if name ~= new_name and vports then
  44. -- Problem:
  45. -- We need to place the new node first so that when turning
  46. -- off some port, it won't stay on because the rules indicate
  47. -- there is an onstate output port there.
  48. -- When turning the output off then, it will however cause feedback
  49. -- so that the luacontroller will receive an "off" event by turning
  50. -- its output off.
  51. -- Solution / Workaround:
  52. -- Remember which output was turned off and ignore next "off" event.
  53. local ph=minetest.pos_to_string(pos)
  54. local railtbl = atlatc.active.nodes[ph]
  55. if not railtbl then return end
  56. local ign = railtbl.ignored_off_events or {}
  57. if ports.a and not vports.a and not mesecon.is_powered(pos, rules.a) then ign.A = true end
  58. if ports.b and not vports.b and not mesecon.is_powered(pos, rules.b) then ign.B = true end
  59. if ports.c and not vports.c and not mesecon.is_powered(pos, rules.c) then ign.C = true end
  60. if ports.d and not vports.d and not mesecon.is_powered(pos, rules.d) then ign.D = true end
  61. railtbl.ignored_off_events = ign
  62. advtrains.ndb.swap_node(pos, {name = new_name, param2 = node.param2})
  63. -- Apply mesecon state only if node loaded
  64. -- If node is not loaded, mesecon update will occur on next load via on_updated_from_nodedb
  65. if advtrains.is_node_loaded(pos) then
  66. if ports.a ~= vports.a then set_port(pos, rules.a, ports.a) end
  67. if ports.b ~= vports.b then set_port(pos, rules.b, ports.b) end
  68. if ports.c ~= vports.c then set_port(pos, rules.c, ports.c) end
  69. if ports.d ~= vports.d then set_port(pos, rules.d, ports.d) end
  70. end
  71. end
  72. end
  73. local function on_updated_from_nodedb(pos, newnode, oldnode)
  74. -- Switch appropriate Mesecon receptors depending on the node change
  75. local vports = minetest.registered_nodes[oldnode.name].virtual_portstates
  76. local ports = minetest.registered_nodes[newnode.name].virtual_portstates
  77. if ports.a ~= vports.a then set_port(pos, rules.a, ports.a) end
  78. if ports.b ~= vports.b then set_port(pos, rules.b, ports.b) end
  79. if ports.c ~= vports.c then set_port(pos, rules.c, ports.c) end
  80. if ports.d ~= vports.d then set_port(pos, rules.d, ports.d) end
  81. end
  82. local function ignore_offevent(pos, rule)
  83. local ph=minetest.pos_to_string(pos)
  84. local railtbl = atlatc.active.nodes[ph]
  85. if not railtbl then return nil end
  86. local ign = railtbl.ignored_off_events
  87. if ign and ign[rule.name] then
  88. ign[rule.name] = nil
  89. return true
  90. end
  91. return false
  92. end
  93. local valid_ports = {a=true, b=true, c=true, d=true}
  94. local function fire_event(pos, evtdata)
  95. local customfct={
  96. set_mesecon_outputs = function(states)
  97. assertt(states, "table")
  98. set_port_states(pos, states)
  99. end,
  100. get_mesecon_input = function(port)
  101. local portl = string.lower(port)
  102. if not valid_ports[portl] then
  103. error("get_mesecon_input: Invalid port (expected a,b,c,d)")
  104. end
  105. if mesecon.is_powered(pos, rules[portl]) then
  106. return true
  107. end
  108. return false
  109. end,
  110. }
  111. atlatc.active.run_in_env(pos, evtdata, customfct, true)
  112. end
  113. local output_rules = {}
  114. local input_rules = {}
  115. local node_box = {
  116. type = "fixed",
  117. fixed = {
  118. {-8/16, -8/16, -8/16, 8/16, -7/16, 8/16}, -- Bottom slab
  119. {-5/16, -7/16, -5/16, 5/16, -6/16, 5/16}, -- Circuit board
  120. {-3/16, -6/16, -3/16, 3/16, -5/16, 3/16}, -- IC
  121. }
  122. }
  123. local selection_box = {
  124. type = "fixed",
  125. fixed = { -8/16, -8/16, -8/16, 8/16, -5/16, 8/16 },
  126. }
  127. for a = 0, 1 do -- 0 = off 1 = on
  128. for b = 0, 1 do
  129. for c = 0, 1 do
  130. for d = 0, 1 do
  131. local cid = tostring(d)..tostring(c)..tostring(b)..tostring(a)
  132. local node_name = BASENAME..cid
  133. local top = "atlatc_luacontroller_top.png"
  134. if a == 1 then
  135. top = top.."^atlatc_luacontroller_LED_A.png"
  136. end
  137. if b == 1 then
  138. top = top.."^atlatc_luacontroller_LED_B.png"
  139. end
  140. if c == 1 then
  141. top = top.."^atlatc_luacontroller_LED_C.png"
  142. end
  143. if d == 1 then
  144. top = top.."^atlatc_luacontroller_LED_D.png"
  145. end
  146. local groups
  147. if a + b + c + d ~= 0 then
  148. groups = {dig_immediate=2, not_in_creative_inventory=1, save_in_at_nodedb=1}
  149. else
  150. groups = {dig_immediate=2, save_in_at_nodedb=1}
  151. end
  152. output_rules[cid] = {}
  153. input_rules[cid] = {}
  154. if a == 1 then table.insert(output_rules[cid], rules.a) end
  155. if b == 1 then table.insert(output_rules[cid], rules.b) end
  156. if c == 1 then table.insert(output_rules[cid], rules.c) end
  157. if d == 1 then table.insert(output_rules[cid], rules.d) end
  158. if a == 0 then table.insert( input_rules[cid], rules.a) end
  159. if b == 0 then table.insert( input_rules[cid], rules.b) end
  160. if c == 0 then table.insert( input_rules[cid], rules.c) end
  161. if d == 0 then table.insert( input_rules[cid], rules.d) end
  162. local mesecons = {
  163. effector = {
  164. rules = input_rules[cid],
  165. action_change = function (pos, _, rule_name, new_state)
  166. if new_state == "off" then
  167. -- check for ignored off event on this node
  168. if ignore_offevent(pos, rule_name) then
  169. return
  170. end
  171. end
  172. --Note: rule_name is not a *name* but actually the full rule table (position + name field)
  173. --Event format consistent with Mesecons Luacontroller event
  174. atlatc.interrupt.add(0, pos, {type=new_state, [new_state]=true, pin=rule_name})
  175. end,
  176. },
  177. receptor = {
  178. state = mesecon.state.on,
  179. rules = output_rules[cid]
  180. },
  181. }
  182. minetest.register_node(node_name, {
  183. description = "LuaATC Mesecon Controller",
  184. drawtype = "nodebox",
  185. tiles = {
  186. top,
  187. "atlatc_luacontroller_bottom.png",
  188. "atlatc_luacontroller_sides.png",
  189. "atlatc_luacontroller_sides.png",
  190. "atlatc_luacontroller_sides.png",
  191. "atlatc_luacontroller_sides.png"
  192. },
  193. inventory_image = top,
  194. paramtype = "light",
  195. is_ground_content = false,
  196. groups = groups,
  197. drop = BASENAME.."0000",
  198. sunlight_propagates = true,
  199. selection_box = selection_box,
  200. node_box = node_box,
  201. mesecons = mesecons,
  202. -- Virtual portstates are the ports that
  203. -- the node shows as powered up (light up).
  204. virtual_portstates = {
  205. a = a == 1,
  206. b = b == 1,
  207. c = c == 1,
  208. d = d == 1,
  209. },
  210. after_dig_node = function (pos, node, player)
  211. mesecon.receptor_off(pos, output_rules)
  212. atlatc.active.after_dig_node(pos, node, player)
  213. end,
  214. after_place_node = atlatc.active.after_place_node,
  215. on_receive_fields = atlatc.active.on_receive_fields,
  216. advtrains = {
  217. on_updated_from_nodedb = on_updated_from_nodedb
  218. },
  219. luaautomation = {
  220. fire_event=fire_event
  221. },
  222. digiline = {
  223. receptor = {},
  224. effector = {
  225. action = atlatc.active.on_digiline_receive
  226. },
  227. },
  228. })
  229. end
  230. end
  231. end
  232. end