1 -- Internal.lua - The core of mesecons
3 -- For more practical developer resources see http://mesecons.net/developers.php
6 -- mesecon.get_effector(nodename) --> Returns the mesecons.effector -specifictation in the nodedef by the nodename
7 -- mesecon.get_receptor(nodename) --> Returns the mesecons.receptor -specifictation in the nodedef by the nodename
8 -- mesecon.get_conductor(nodename) --> Returns the mesecons.conductor-specifictation in the nodedef by the nodename
9 -- mesecon.get_any_inputrules (node) --> Returns the rules of a node if it is a conductor or an effector
10 -- mesecon.get_any_outputrules (node) --> Returns the rules of a node if it is a conductor or a receptor
13 -- mesecon.is_receptor(nodename) --> Returns true if nodename is a receptor
14 -- mesecon.is_receptor_on(nodename --> Returns true if nodename is an receptor with state = mesecon.state.on
15 -- mesecon.is_receptor_off(nodename) --> Returns true if nodename is an receptor with state = mesecon.state.off
16 -- mesecon.receptor_get_rules(node) --> Returns the rules of the receptor (mesecon.rules.default if none specified)
19 -- mesecon.is_effector(nodename) --> Returns true if nodename is an effector
20 -- mesecon.is_effector_on(nodename) --> Returns true if nodename is an effector with nodedef.mesecons.effector.action_off
21 -- mesecon.is_effector_off(nodename) --> Returns true if nodename is an effector with nodedef.mesecons.effector.action_on
22 -- mesecon.effector_get_rules(node) --> Returns the input rules of the effector (mesecon.rules.default if none specified)
25 -- mesecon.activate(pos, node, depth) --> Activates the effector node at the specific pos (calls nodedef.mesecons.effector.action_on), higher depths are executed later
26 -- mesecon.deactivate(pos, node, depth) --> Deactivates the effector node at the specific pos (calls nodedef.mesecons.effector.action_off), higher depths are executed later
27 -- mesecon.changesignal(pos, node, rulename, newstate, depth) --> Changes the effector node at the specific pos (calls nodedef.mesecons.effector.action_change), higher depths are executed later
30 -- mesecon.is_conductor(nodename) --> Returns true if nodename is a conductor
31 -- mesecon.is_conductor_on(node --> Returns true if node is a conductor with state = mesecon.state.on
32 -- mesecon.is_conductor_off(node) --> Returns true if node is a conductor with state = mesecon.state.off
33 -- mesecon.get_conductor_on(node_off) --> Returns the onstate nodename of the conductor
34 -- mesecon.get_conductor_off(node_on) --> Returns the offstate nodename of the conductor
35 -- mesecon.conductor_get_rules(node) --> Returns the input+output rules of a conductor (mesecon.rules.default if none specified)
37 -- HIGH-LEVEL Internals
38 -- mesecon.is_power_on(pos) --> Returns true if pos emits power in any way
39 -- mesecon.is_power_off(pos) --> Returns true if pos does not emit power in any way
40 -- mesecon.is_powered(pos) --> Returns bool, spread. bool is true if pos is powered by a receptor, a conductor or an opaque block.
41 -- spread is true if it is powered AND also transmits its power one block further.
43 -- RULES ROTATION helpers
44 -- mesecon.rotate_rules_right(rules)
45 -- mesecon.rotate_rules_left(rules)
46 -- mesecon.rotate_rules_up(rules)
47 -- mesecon.rotate_rules_down(rules)
48 -- These functions return rules that have been rotated in the specific direction
51 function mesecon
.get_effector(nodename
)
52 if minetest
.registered_nodes
[nodename
]
53 and minetest
.registered_nodes
[nodename
].mesecons
54 and minetest
.registered_nodes
[nodename
].mesecons
.effector
then
55 return minetest
.registered_nodes
[nodename
].mesecons
.effector
59 function mesecon
.get_receptor(nodename
)
60 if minetest
.registered_nodes
[nodename
]
61 and minetest
.registered_nodes
[nodename
].mesecons
62 and minetest
.registered_nodes
[nodename
].mesecons
.receptor
then
63 return minetest
.registered_nodes
[nodename
].mesecons
.receptor
67 function mesecon
.get_conductor(nodename
)
68 if minetest
.registered_nodes
[nodename
]
69 and minetest
.registered_nodes
[nodename
].mesecons
70 and minetest
.registered_nodes
[nodename
].mesecons
.conductor
then
71 return minetest
.registered_nodes
[nodename
].mesecons
.conductor
75 function mesecon
.get_any_outputrules(node
)
76 if not node
then return nil end
78 if mesecon
.is_conductor(node
.name
) then
79 return mesecon
.conductor_get_rules(node
)
80 elseif mesecon
.is_receptor(node
.name
) then
81 return mesecon
.receptor_get_rules(node
)
82 elseif minetest
.get_item_group(node
.name
, "opaque") == 1 then
83 return mesecon
.rules
.alldirs
87 function mesecon
.get_any_inputrules(node
)
88 if not node
then return nil end
90 if mesecon
.is_conductor(node
.name
) then
91 return mesecon
.conductor_get_rules(node
)
92 elseif mesecon
.is_effector(node
.name
) then
93 return mesecon
.effector_get_rules(node
)
94 elseif minetest
.get_item_group(node
.name
, "opaque") == 1 then
95 return mesecon
.rules
.alldirs
99 function mesecon
.get_any_rules(node
)
100 return mesecon
.mergetable(mesecon
.get_any_inputrules(node
) or {},
101 mesecon
.get_any_outputrules(node
) or {})
105 -- Nodes that can power mesecons
106 function mesecon
.is_receptor_on(nodename
)
107 local receptor
= mesecon
.get_receptor(nodename
)
108 if receptor
and receptor
.state
== mesecon
.state
.on
then
114 function mesecon
.is_receptor_off(nodename
)
115 local receptor
= mesecon
.get_receptor(nodename
)
116 if receptor
and receptor
.state
== mesecon
.state
.off
then
122 function mesecon
.is_receptor(nodename
)
123 local receptor
= mesecon
.get_receptor(nodename
)
130 function mesecon
.receptor_get_rules(node
)
131 local receptor
= mesecon
.get_receptor(node
.name
)
133 local rules
= receptor
.rules
134 if type(rules
) == 'function' then
141 return mesecon
.rules
.default
145 -- Nodes that can be powered by mesecons
146 function mesecon
.is_effector_on(nodename
)
147 local effector
= mesecon
.get_effector(nodename
)
148 if effector
and effector
.action_off
then
154 function mesecon
.is_effector_off(nodename
)
155 local effector
= mesecon
.get_effector(nodename
)
156 if effector
and effector
.action_on
then
162 function mesecon
.is_effector(nodename
)
163 local effector
= mesecon
.get_effector(nodename
)
170 function mesecon
.effector_get_rules(node
)
171 local effector
= mesecon
.get_effector(node
.name
)
173 local rules
= effector
.rules
174 if type(rules
) == 'function' then
180 return mesecon
.rules
.default
183 -- #######################
184 -- # Signals (effectors) #
185 -- #######################
188 mesecon
.queue
:add_function("activate", function (pos
, rulename
)
189 local node
= mesecon
.get_node_force(pos
)
190 if not node
then return end
192 local effector
= mesecon
.get_effector(node
.name
)
194 if effector
and effector
.action_on
then
195 effector
.action_on(pos
, node
, rulename
)
199 function mesecon
.activate(pos
, node
, rulename
, depth
)
200 if rulename
== nil then
201 for _
,rule
in ipairs(mesecon
.effector_get_rules(node
)) do
202 mesecon
.activate(pos
, node
, rule
, depth
+ 1)
206 mesecon
.queue
:add_action(pos
, "activate", {rulename
}, nil, rulename
, 1 / depth
)
211 mesecon
.queue
:add_function("deactivate", function (pos
, rulename
)
212 local node
= mesecon
.get_node_force(pos
)
213 if not node
then return end
215 local effector
= mesecon
.get_effector(node
.name
)
217 if effector
and effector
.action_off
then
218 effector
.action_off(pos
, node
, rulename
)
222 function mesecon
.deactivate(pos
, node
, rulename
, depth
)
223 if rulename
== nil then
224 for _
,rule
in ipairs(mesecon
.effector_get_rules(node
)) do
225 mesecon
.deactivate(pos
, node
, rule
, depth
+ 1)
229 mesecon
.queue
:add_action(pos
, "deactivate", {rulename
}, nil, rulename
, 1 / depth
)
234 mesecon
.queue
:add_function("change", function (pos
, rulename
, changetype
)
235 local node
= mesecon
.get_node_force(pos
)
236 if not node
then return end
238 local effector
= mesecon
.get_effector(node
.name
)
240 if effector
and effector
.action_change
then
241 effector
.action_change(pos
, node
, rulename
, changetype
)
245 function mesecon
.changesignal(pos
, node
, rulename
, newstate
, depth
)
246 if rulename
== nil then
247 for _
,rule
in ipairs(mesecon
.effector_get_rules(node
)) do
248 mesecon
.changesignal(pos
, node
, rule
, newstate
, depth
+ 1)
253 -- Include "change" in overwritecheck so that it cannot be overwritten
254 -- by "active" / "deactivate" that will be called upon the node at the same time.
255 local overwritecheck
= {"change", rulename
}
256 mesecon
.queue
:add_action(pos
, "change", {rulename
, newstate
}, nil, overwritecheck
, 1 / depth
)
261 function mesecon
.is_conductor_on(node
, rulename
)
262 if not node
then return false end
264 local conductor
= mesecon
.get_conductor(node
.name
)
266 if conductor
.state
then
267 return conductor
.state
== mesecon
.state
.on
269 if conductor
.states
then
271 return mesecon
.getstate(node
.name
, conductor
.states
) ~= 1
273 local bit
= mesecon
.rule2bit(rulename
, mesecon
.conductor_get_rules(node
))
274 local binstate
= mesecon
.getbinstate(node
.name
, conductor
.states
)
275 return mesecon
.get_bit(binstate
, bit
)
282 function mesecon
.is_conductor_off(node
, rulename
)
283 if not node
then return false end
285 local conductor
= mesecon
.get_conductor(node
.name
)
287 if conductor
.state
then
288 return conductor
.state
== mesecon
.state
.off
290 if conductor
.states
then
292 return mesecon
.getstate(node
.name
, conductor
.states
) == 1
294 local bit
= mesecon
.rule2bit(rulename
, mesecon
.conductor_get_rules(node
))
295 local binstate
= mesecon
.getbinstate(node
.name
, conductor
.states
)
296 return not mesecon
.get_bit(binstate
, bit
)
303 function mesecon
.is_conductor(nodename
)
304 local conductor
= mesecon
.get_conductor(nodename
)
311 function mesecon
.get_conductor_on(node_off
, rulename
)
312 local conductor
= mesecon
.get_conductor(node_off
.name
)
314 if conductor
.onstate
then
315 return conductor
.onstate
317 if conductor
.states
then
318 local bit
= mesecon
.rule2bit(rulename
, mesecon
.conductor_get_rules(node_off
))
319 local binstate
= mesecon
.getbinstate(node_off
.name
, conductor
.states
)
320 binstate
= mesecon
.set_bit(binstate
, bit
, "1")
321 return conductor
.states
[tonumber(binstate
,2)+1]
327 function mesecon
.get_conductor_off(node_on
, rulename
)
328 local conductor
= mesecon
.get_conductor(node_on
.name
)
330 if conductor
.offstate
then
331 return conductor
.offstate
333 if conductor
.states
then
334 local bit
= mesecon
.rule2bit(rulename
, mesecon
.conductor_get_rules(node_on
))
335 local binstate
= mesecon
.getbinstate(node_on
.name
, conductor
.states
)
336 binstate
= mesecon
.set_bit(binstate
, bit
, "0")
337 return conductor
.states
[tonumber(binstate
,2)+1]
343 function mesecon
.conductor_get_rules(node
)
344 local conductor
= mesecon
.get_conductor(node
.name
)
346 local rules
= conductor
.rules
347 if type(rules
) == 'function' then
353 return mesecon
.rules
.default
356 -- some more general high-level stuff
358 function mesecon
.is_power_on(pos
, rulename
)
359 local node
= mesecon
.get_node_force(pos
)
360 if node
and (mesecon
.is_conductor_on(node
, rulename
) or mesecon
.is_receptor_on(node
.name
)) then
366 function mesecon
.is_power_off(pos
, rulename
)
367 local node
= mesecon
.get_node_force(pos
)
368 if node
and (mesecon
.is_conductor_off(node
, rulename
) or mesecon
.is_receptor_off(node
.name
)) then
374 -- Turn off an equipotential section starting at `pos`, which outputs in the direction of `link`.
375 -- Breadth-first search. Map is abstracted away in a voxelmanip.
376 -- Follow all all conductor paths replacing conductors that were already
377 -- looked at, activating / changing all effectors along the way.
378 function mesecon
.turnon(pos
, link
)
379 local frontiers
= {{pos
= pos
, link
= link
}}
382 while frontiers
[1] do
383 local f
= table.remove(frontiers
, 1)
384 local node
= mesecon
.get_node_force(f
.pos
)
387 -- Area does not exist; do nothing
388 elseif mesecon
.is_conductor_off(node
, f
.link
) then
389 local rules
= mesecon
.conductor_get_rules(node
)
391 -- Call turnon on neighbors
392 for _
, r
in ipairs(mesecon
.rule2meta(f
.link
, rules
)) do
393 local np
= vector
.add(f
.pos
, r
)
394 for _
, l
in ipairs(mesecon
.rules_link_rule_all(f
.pos
, r
)) do
395 table.insert(frontiers
, {pos
= np
, link
= l
})
399 mesecon
.swap_node_force(f
.pos
, mesecon
.get_conductor_on(node
, f
.link
))
400 elseif mesecon
.is_effector(node
.name
) then
401 mesecon
.changesignal(f
.pos
, node
, f
.link
, mesecon
.state
.on
, depth
)
402 if mesecon
.is_effector_off(node
.name
) then
403 mesecon
.activate(f
.pos
, node
, f
.link
, depth
)
406 if node
and f
.link
.spread
and minetest
.get_item_group(node
.name
, "opaque") == 1 then
407 -- Call turnon on neighbors
408 -- Warning: A LOT of nodes need to be looked at for this to work
409 for _
, r
in ipairs(mesecon
.rule2meta(f
.link
, mesecon
.rules
.mcl_alldirs_spread
)) do
410 local np
= vector
.add(f
.pos
, r
)
411 for _
, l
in ipairs(mesecon
.rules_link_rule_all(f
.pos
, r
)) do
412 local nlink
= table.copy(l
)
414 table.insert(frontiers
, {pos
= np
, link
= nlink
})
423 -- Turn off an equipotential section starting at `pos`, which outputs in the direction of `link`.
424 -- Breadth-first search. Map is abstracted away in a voxelmanip.
425 -- Follow all all conductor paths replacing conductors that were already
426 -- looked at, deactivating / changing all effectors along the way.
427 -- In case an onstate receptor is discovered, abort the process by returning false, which will
428 -- cause `receptor_off` to discard all changes made in the voxelmanip.
429 -- Contrary to turnon, turnoff has to cache all change and deactivate signals so that they will only
430 -- be called in the very end when we can be sure that no conductor was found along the path.
432 -- Signal table entry structure:
434 -- pos = position of effector,
435 -- node = node descriptor (name, param1 and param2),
436 -- link = link the effector is connected to,
437 -- depth = indicates order in which signals wire fired, higher is later
439 function mesecon
.turnoff(pos
, link
)
440 local frontiers
= {{pos
= pos
, link
= link
}}
444 while frontiers
[1] do
445 local f
= table.remove(frontiers
, 1)
446 local node
= mesecon
.get_node_force(f
.pos
)
450 elseif mesecon
.is_conductor_on(node
, f
.link
) then
451 local rules
= mesecon
.conductor_get_rules(node
)
452 for _
, r
in ipairs(mesecon
.rule2meta(f
.link
, rules
)) do
453 local np
= vector
.add(f
.pos
, r
)
455 -- Check if an onstate receptor is connected. If that is the case,
456 -- abort this turnoff process by returning false. `receptor_off` will
457 -- discard all the changes that we made in the voxelmanip:
458 for _
, l
in ipairs(mesecon
.rules_link_rule_all_inverted(f
.pos
, r
)) do
459 if mesecon
.is_receptor_on(mesecon
.get_node_force(np
).name
) then
464 -- Call turnoff on neighbors
465 for _
, l
in ipairs(mesecon
.rules_link_rule_all(f
.pos
, r
)) do
466 table.insert(frontiers
, {pos
= np
, link
= l
})
470 mesecon
.swap_node_force(f
.pos
, mesecon
.get_conductor_off(node
, f
.link
))
471 elseif mesecon
.is_effector(node
.name
) then
472 table.insert(signals
, {
480 if node
and f
.link
.spread
and minetest
.get_item_group(node
.name
, "opaque") == 1 then
481 -- Call turnoff on neighbors
482 -- Warning: A LOT of nodes need to be looked at for this to work
483 for _
, r
in ipairs(mesecon
.rule2meta(f
.link
, mesecon
.rules
.mcl_alldirs_spread
)) do
484 local np
= vector
.add(f
.pos
, r
)
485 local n
= mesecon
.get_node_force(np
)
486 if mesecon
.is_receptor_on(n
.name
) then
487 local receptorrules
= mesecon
.receptor_get_rules(n
)
488 for _
, rr
in pairs(receptorrules
) do
489 if rr
.spread
and vector
.equals(mesecon
.invertRule(rr
), r
) then
494 for _
, l
in ipairs(mesecon
.rules_link_rule_all(f
.pos
, r
)) do
495 local nlink
= table.copy(l
)
497 table.insert(frontiers
, {pos
= np
, link
= nlink
})
505 for _
, sig
in ipairs(signals
) do
506 mesecon
.changesignal(sig
.pos
, sig
.node
, sig
.link
, mesecon
.state
.off
, sig
.depth
)
507 if mesecon
.is_effector_on(sig
.node
.name
) and not mesecon
.is_powered(sig
.pos
) then
508 mesecon
.deactivate(sig
.pos
, sig
.node
, sig
.link
, sig
.depth
)
515 -- Get all linking inputrules of inputnode (effector or conductor) that is connected to
516 -- outputnode (receptor or conductor) at position `output` and has an output in direction `rule`
517 function mesecon
.rules_link_rule_all(output
, rule
)
518 local input
= vector
.add(output
, rule
)
519 local inputnode
= mesecon
.get_node_force(input
)
520 local inputrules
= mesecon
.get_any_inputrules(inputnode
)
521 if not inputrules
then
526 for _
, inputrule
in ipairs(mesecon
.flattenrules(inputrules
)) do
527 -- Check if input accepts from output
528 if vector
.equals(vector
.add(input
, inputrule
), output
) then
529 local newrule
= table.copy(inputrule
)
530 newrule
.spread
= rule
.spread
531 table.insert(rules
, newrule
)
538 -- Get all linking outputnodes of outputnode (receptor or conductor) that is connected to
539 -- inputnode (effector or conductor) at position `input` and has an input in direction `rule`
540 function mesecon
.rules_link_rule_all_inverted(input
, rule
)
541 local output
= vector
.add(input
, rule
)
542 local outputnode
= mesecon
.get_node_force(output
)
543 local outputrules
= mesecon
.get_any_outputrules(outputnode
)
544 if not outputrules
then
549 for _
, outputrule
in ipairs(mesecon
.flattenrules(outputrules
)) do
550 if vector
.equals(vector
.add(output
, outputrule
), input
) then
551 local newrule
= table.copy(outputrule
)
552 newrule
= mesecon
.invertRule(newrule
)
553 newrule
.spread
= rule
.spread
554 table.insert(rules
, newrule
)
560 function mesecon
.is_powered(pos
, rule
, depth
, sourcepos
, home_pos
)
561 if depth
== nil then depth
= 0 end
565 local node
= mesecon
.get_node_force(pos
)
566 local rules
= mesecon
.get_any_inputrules(node
)
574 -- List of nodes that send out power to pos
575 if sourcepos
== nil then
579 local function power_walk(pos
, home_pos
, sourcepos
, rulenames
, rule
, depth
)
581 for _
, rname
in ipairs(rulenames
) do
582 local np
= vector
.add(pos
, rname
)
583 local nn
= mesecon
.get_node_force(np
)
584 if (mesecon
.is_conductor_on (nn
, mesecon
.invertRule(rname
))
585 or mesecon
.is_receptor_on (nn
.name
)) then
586 if not vector
.equals(home_pos
, np
) then
587 local rulez
= mesecon
.get_any_outputrules(nn
)
588 local spread_tmp
= false
590 if vector
.equals(mesecon
.invertRule(rname
), rulez
[r
]) then
591 if rulez
[r
].spread
then
596 if depth
== 0 or spread_tmp
then
597 table.insert(sourcepos
, np
)
603 elseif depth
== 0 and minetest
.get_item_group(nn
.name
, "opaque") == 1 then
604 local more_sourcepos
= mesecon
.is_powered(np
, nil, depth
+ 1, sourcepos
, home_pos
)
605 if more_sourcepos
and #more_sourcepos
> 0 then
606 mesecon
.mergetable(sourcepos
, more_sourcepos
)
610 return sourcepos
, spread
615 for _
, rule
in ipairs(mesecon
.flattenrules(rules
)) do
617 local rulenames
= mesecon
.rules_link_rule_all_inverted(pos
, rule
)
618 sourcepos
, spread_temp
= power_walk(pos
, home_pos
, sourcepos
, rulenames
, rule
, depth
)
624 local rulenames
= mesecon
.rules_link_rule_all_inverted(pos
, rule
)
625 sourcepos
, spread
= power_walk(pos
, home_pos
, sourcepos
, rulenames
, rule
, depth
)
628 -- Return FALSE if not powered, return list of sources if is powered
630 if (#sourcepos
== 0) then
633 return sourcepos
, spread