mirror of
https://git.minetest.land/VoxeLibre/VoxeLibre.git
synced 2024-12-20 23:29:31 +01:00
b299b95fac
This touches a lot of the existing Mesecons mods. Also add mesecons_wires.
599 lines
18 KiB
Lua
599 lines
18 KiB
Lua
-- Internal.lua - The core of mesecons
|
|
--
|
|
-- For more practical developer resources see http://mesecons.net/developers.php
|
|
--
|
|
-- Function overview
|
|
-- mesecon.get_effector(nodename) --> Returns the mesecons.effector -specifictation in the nodedef by the nodename
|
|
-- mesecon.get_receptor(nodename) --> Returns the mesecons.receptor -specifictation in the nodedef by the nodename
|
|
-- mesecon.get_conductor(nodename) --> Returns the mesecons.conductor-specifictation in the nodedef by the nodename
|
|
-- mesecon.get_any_inputrules (node) --> Returns the rules of a node if it is a conductor or an effector
|
|
-- mesecon.get_any_outputrules (node) --> Returns the rules of a node if it is a conductor or a receptor
|
|
|
|
-- RECEPTORS
|
|
-- mesecon.is_receptor(nodename) --> Returns true if nodename is a receptor
|
|
-- mesecon.is_receptor_on(nodename --> Returns true if nodename is an receptor with state = mesecon.state.on
|
|
-- mesecon.is_receptor_off(nodename) --> Returns true if nodename is an receptor with state = mesecon.state.off
|
|
-- mesecon.receptor_get_rules(node) --> Returns the rules of the receptor (mesecon.rules.default if none specified)
|
|
|
|
-- EFFECTORS
|
|
-- mesecon.is_effector(nodename) --> Returns true if nodename is an effector
|
|
-- mesecon.is_effector_on(nodename) --> Returns true if nodename is an effector with nodedef.mesecons.effector.action_off
|
|
-- mesecon.is_effector_off(nodename) --> Returns true if nodename is an effector with nodedef.mesecons.effector.action_on
|
|
-- mesecon.effector_get_rules(node) --> Returns the input rules of the effector (mesecon.rules.default if none specified)
|
|
|
|
-- SIGNALS
|
|
-- mesecon.activate(pos, node, depth) --> Activates the effector node at the specific pos (calls nodedef.mesecons.effector.action_on), higher depths are executed later
|
|
-- mesecon.deactivate(pos, node, depth) --> Deactivates the effector node at the specific pos (calls nodedef.mesecons.effector.action_off), higher depths are executed later
|
|
-- 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
|
|
|
|
-- CONDUCTORS
|
|
-- mesecon.is_conductor(nodename) --> Returns true if nodename is a conductor
|
|
-- mesecon.is_conductor_on(node --> Returns true if node is a conductor with state = mesecon.state.on
|
|
-- mesecon.is_conductor_off(node) --> Returns true if node is a conductor with state = mesecon.state.off
|
|
-- mesecon.get_conductor_on(node_off) --> Returns the onstate nodename of the conductor
|
|
-- mesecon.get_conductor_off(node_on) --> Returns the offstate nodename of the conductor
|
|
-- mesecon.conductor_get_rules(node) --> Returns the input+output rules of a conductor (mesecon.rules.default if none specified)
|
|
|
|
-- HIGH-LEVEL Internals
|
|
-- mesecon.is_power_on(pos) --> Returns true if pos emits power in any way
|
|
-- mesecon.is_power_off(pos) --> Returns true if pos does not emit power in any way
|
|
-- mesecon.is_powered(pos) --> Returns true if pos is powered by a receptor or a conductor
|
|
|
|
-- RULES ROTATION helpers
|
|
-- mesecon.rotate_rules_right(rules)
|
|
-- mesecon.rotate_rules_left(rules)
|
|
-- mesecon.rotate_rules_up(rules)
|
|
-- mesecon.rotate_rules_down(rules)
|
|
-- These functions return rules that have been rotated in the specific direction
|
|
|
|
-- General
|
|
function mesecon.get_effector(nodename)
|
|
if minetest.registered_nodes[nodename]
|
|
and minetest.registered_nodes[nodename].mesecons
|
|
and minetest.registered_nodes[nodename].mesecons.effector then
|
|
return minetest.registered_nodes[nodename].mesecons.effector
|
|
end
|
|
end
|
|
|
|
function mesecon.get_receptor(nodename)
|
|
if minetest.registered_nodes[nodename]
|
|
and minetest.registered_nodes[nodename].mesecons
|
|
and minetest.registered_nodes[nodename].mesecons.receptor then
|
|
return minetest.registered_nodes[nodename].mesecons.receptor
|
|
end
|
|
end
|
|
|
|
function mesecon.get_conductor(nodename)
|
|
if minetest.registered_nodes[nodename]
|
|
and minetest.registered_nodes[nodename].mesecons
|
|
and minetest.registered_nodes[nodename].mesecons.conductor then
|
|
return minetest.registered_nodes[nodename].mesecons.conductor
|
|
end
|
|
end
|
|
|
|
function mesecon.get_any_outputrules(node)
|
|
if not node then return nil end
|
|
|
|
if mesecon.is_conductor(node.name) then
|
|
return mesecon.conductor_get_rules(node)
|
|
elseif mesecon.is_receptor(node.name) then
|
|
return mesecon.receptor_get_rules(node)
|
|
end
|
|
end
|
|
|
|
function mesecon.get_any_inputrules(node)
|
|
if not node then return nil end
|
|
|
|
if mesecon.is_conductor(node.name) then
|
|
return mesecon.conductor_get_rules(node)
|
|
elseif mesecon.is_effector(node.name) then
|
|
return mesecon.effector_get_rules(node)
|
|
end
|
|
end
|
|
|
|
function mesecon.get_any_rules(node)
|
|
return mesecon.mergetable(mesecon.get_any_inputrules(node) or {},
|
|
mesecon.get_any_outputrules(node) or {})
|
|
end
|
|
|
|
-- Receptors
|
|
-- Nodes that can power mesecons
|
|
function mesecon.is_receptor_on(nodename)
|
|
local receptor = mesecon.get_receptor(nodename)
|
|
if receptor and receptor.state == mesecon.state.on then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function mesecon.is_receptor_off(nodename)
|
|
local receptor = mesecon.get_receptor(nodename)
|
|
if receptor and receptor.state == mesecon.state.off then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function mesecon.is_receptor(nodename)
|
|
local receptor = mesecon.get_receptor(nodename)
|
|
if receptor then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function mesecon.receptor_get_rules(node)
|
|
local receptor = mesecon.get_receptor(node.name)
|
|
if receptor then
|
|
local rules = receptor.rules
|
|
if type(rules) == 'function' then
|
|
return rules(node)
|
|
elseif rules then
|
|
return rules
|
|
end
|
|
end
|
|
|
|
return mesecon.rules.default
|
|
end
|
|
|
|
-- Effectors
|
|
-- Nodes that can be powered by mesecons
|
|
function mesecon.is_effector_on(nodename)
|
|
local effector = mesecon.get_effector(nodename)
|
|
if effector and effector.action_off then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function mesecon.is_effector_off(nodename)
|
|
local effector = mesecon.get_effector(nodename)
|
|
if effector and effector.action_on then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function mesecon.is_effector(nodename)
|
|
local effector = mesecon.get_effector(nodename)
|
|
if effector then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function mesecon.effector_get_rules(node)
|
|
local effector = mesecon.get_effector(node.name)
|
|
if effector then
|
|
local rules = effector.rules
|
|
if type(rules) == 'function' then
|
|
return rules(node)
|
|
elseif rules then
|
|
return rules
|
|
end
|
|
end
|
|
return mesecon.rules.default
|
|
end
|
|
|
|
-- #######################
|
|
-- # Signals (effectors) #
|
|
-- #######################
|
|
|
|
-- Activation:
|
|
mesecon.queue:add_function("activate", function (pos, rulename)
|
|
local node = mesecon.get_node_force(pos)
|
|
if not node then return end
|
|
|
|
local effector = mesecon.get_effector(node.name)
|
|
|
|
if effector and effector.action_on then
|
|
effector.action_on(pos, node, rulename)
|
|
end
|
|
end)
|
|
|
|
function mesecon.activate(pos, node, rulename, depth)
|
|
if rulename == nil then
|
|
for _,rule in ipairs(mesecon.effector_get_rules(node)) do
|
|
mesecon.activate(pos, node, rule, depth + 1)
|
|
end
|
|
return
|
|
end
|
|
mesecon.queue:add_action(pos, "activate", {rulename}, nil, rulename, 1 / depth)
|
|
end
|
|
|
|
|
|
-- Deactivation
|
|
mesecon.queue:add_function("deactivate", function (pos, rulename)
|
|
local node = mesecon.get_node_force(pos)
|
|
if not node then return end
|
|
|
|
local effector = mesecon.get_effector(node.name)
|
|
|
|
if effector and effector.action_off then
|
|
effector.action_off(pos, node, rulename)
|
|
end
|
|
end)
|
|
|
|
function mesecon.deactivate(pos, node, rulename, depth)
|
|
if rulename == nil then
|
|
for _,rule in ipairs(mesecon.effector_get_rules(node)) do
|
|
mesecon.deactivate(pos, node, rule, depth + 1)
|
|
end
|
|
return
|
|
end
|
|
mesecon.queue:add_action(pos, "deactivate", {rulename}, nil, rulename, 1 / depth)
|
|
end
|
|
|
|
|
|
-- Change
|
|
mesecon.queue:add_function("change", function (pos, rulename, changetype)
|
|
local node = mesecon.get_node_force(pos)
|
|
if not node then return end
|
|
|
|
local effector = mesecon.get_effector(node.name)
|
|
|
|
if effector and effector.action_change then
|
|
effector.action_change(pos, node, rulename, changetype)
|
|
end
|
|
end)
|
|
|
|
function mesecon.changesignal(pos, node, rulename, newstate, depth)
|
|
if rulename == nil then
|
|
for _,rule in ipairs(mesecon.effector_get_rules(node)) do
|
|
mesecon.changesignal(pos, node, rule, newstate, depth + 1)
|
|
end
|
|
return
|
|
end
|
|
|
|
-- Include "change" in overwritecheck so that it cannot be overwritten
|
|
-- by "active" / "deactivate" that will be called upon the node at the same time.
|
|
local overwritecheck = {"change", rulename}
|
|
mesecon.queue:add_action(pos, "change", {rulename, newstate}, nil, overwritecheck, 1 / depth)
|
|
end
|
|
|
|
-- Conductors
|
|
|
|
function mesecon.is_conductor_on(node, rulename)
|
|
if not node then return false end
|
|
|
|
local conductor = mesecon.get_conductor(node.name)
|
|
if conductor then
|
|
if conductor.state then
|
|
return conductor.state == mesecon.state.on
|
|
end
|
|
if conductor.states then
|
|
if not rulename then
|
|
return mesecon.getstate(node.name, conductor.states) ~= 1
|
|
end
|
|
local bit = mesecon.rule2bit(rulename, mesecon.conductor_get_rules(node))
|
|
local binstate = mesecon.getbinstate(node.name, conductor.states)
|
|
return mesecon.get_bit(binstate, bit)
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function mesecon.is_conductor_off(node, rulename)
|
|
if not node then return false end
|
|
|
|
local conductor = mesecon.get_conductor(node.name)
|
|
if conductor then
|
|
if conductor.state then
|
|
return conductor.state == mesecon.state.off
|
|
end
|
|
if conductor.states then
|
|
if not rulename then
|
|
return mesecon.getstate(node.name, conductor.states) == 1
|
|
end
|
|
local bit = mesecon.rule2bit(rulename, mesecon.conductor_get_rules(node))
|
|
local binstate = mesecon.getbinstate(node.name, conductor.states)
|
|
return not mesecon.get_bit(binstate, bit)
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function mesecon.is_conductor(nodename)
|
|
local conductor = mesecon.get_conductor(nodename)
|
|
if conductor then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function mesecon.get_conductor_on(node_off, rulename)
|
|
local conductor = mesecon.get_conductor(node_off.name)
|
|
if conductor then
|
|
if conductor.onstate then
|
|
return conductor.onstate
|
|
end
|
|
if conductor.states then
|
|
local bit = mesecon.rule2bit(rulename, mesecon.conductor_get_rules(node_off))
|
|
local binstate = mesecon.getbinstate(node_off.name, conductor.states)
|
|
binstate = mesecon.set_bit(binstate, bit, "1")
|
|
return conductor.states[tonumber(binstate,2)+1]
|
|
end
|
|
end
|
|
return offstate
|
|
end
|
|
|
|
function mesecon.get_conductor_off(node_on, rulename)
|
|
local conductor = mesecon.get_conductor(node_on.name)
|
|
if conductor then
|
|
if conductor.offstate then
|
|
return conductor.offstate
|
|
end
|
|
if conductor.states then
|
|
local bit = mesecon.rule2bit(rulename, mesecon.conductor_get_rules(node_on))
|
|
local binstate = mesecon.getbinstate(node_on.name, conductor.states)
|
|
binstate = mesecon.set_bit(binstate, bit, "0")
|
|
return conductor.states[tonumber(binstate,2)+1]
|
|
end
|
|
end
|
|
return onstate
|
|
end
|
|
|
|
function mesecon.conductor_get_rules(node)
|
|
local conductor = mesecon.get_conductor(node.name)
|
|
if conductor then
|
|
local rules = conductor.rules
|
|
if type(rules) == 'function' then
|
|
return rules(node)
|
|
elseif rules then
|
|
return rules
|
|
end
|
|
end
|
|
return mesecon.rules.default
|
|
end
|
|
|
|
-- some more general high-level stuff
|
|
|
|
function mesecon.is_power_on(pos, rulename)
|
|
local node = mesecon.get_node_force(pos)
|
|
if node and (mesecon.is_conductor_on(node, rulename) or mesecon.is_receptor_on(node.name)) then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function mesecon.is_power_off(pos, rulename)
|
|
local node = mesecon.get_node_force(pos)
|
|
if node and (mesecon.is_conductor_off(node, rulename) or mesecon.is_receptor_off(node.name)) then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Turn off an equipotential section starting at `pos`, which outputs in the direction of `link`.
|
|
-- Breadth-first search. Map is abstracted away in a voxelmanip.
|
|
-- Follow all all conductor paths replacing conductors that were already
|
|
-- looked at, activating / changing all effectors along the way.
|
|
function mesecon.turnon(pos, link)
|
|
local frontiers = {{pos = pos, link = link}}
|
|
|
|
local depth = 1
|
|
while frontiers[1] do
|
|
local f = table.remove(frontiers, 1)
|
|
local node = mesecon.get_node_force(f.pos)
|
|
|
|
if not node then
|
|
-- Area does not exist; do nothing
|
|
elseif mesecon.is_conductor_off(node, f.link) then
|
|
local rules = mesecon.conductor_get_rules(node)
|
|
|
|
-- Call turnon on neighbors
|
|
for _, r in ipairs(mesecon.rule2meta(f.link, rules)) do
|
|
local np = vector.add(f.pos, r)
|
|
for _, l in ipairs(mesecon.rules_link_rule_all(f.pos, r)) do
|
|
table.insert(frontiers, {pos = np, link = l})
|
|
end
|
|
end
|
|
|
|
mesecon.swap_node_force(f.pos, mesecon.get_conductor_on(node, f.link))
|
|
elseif mesecon.is_effector(node.name) then
|
|
mesecon.changesignal(f.pos, node, f.link, mesecon.state.on, depth)
|
|
if mesecon.is_effector_off(node.name) then
|
|
mesecon.activate(f.pos, node, f.link, depth)
|
|
end
|
|
end
|
|
depth = depth + 1
|
|
end
|
|
end
|
|
|
|
-- Turn on an equipotential section starting at `pos`, which outputs in the direction of `link`.
|
|
-- Breadth-first search. Map is abstracted away in a voxelmanip.
|
|
-- Follow all all conductor paths replacing conductors that were already
|
|
-- looked at, deactivating / changing all effectors along the way.
|
|
-- In case an onstate receptor is discovered, abort the process by returning false, which will
|
|
-- cause `receptor_off` to discard all changes made in the voxelmanip.
|
|
-- Contrary to turnon, turnoff has to cache all change and deactivate signals so that they will only
|
|
-- be called in the very end when we can be sure that no conductor was found along the path.
|
|
--
|
|
-- Signal table entry structure:
|
|
-- {
|
|
-- pos = position of effector,
|
|
-- node = node descriptor (name, param1 and param2),
|
|
-- link = link the effector is connected to,
|
|
-- depth = indicates order in which signals wire fired, higher is later
|
|
-- }
|
|
function mesecon.turnoff(pos, link)
|
|
local frontiers = {{pos = pos, link = link}}
|
|
local signals = {}
|
|
|
|
local depth = 1
|
|
while frontiers[1] do
|
|
local f = table.remove(frontiers, 1)
|
|
local node = mesecon.get_node_force(f.pos)
|
|
|
|
if not node then
|
|
-- Area does not exist; do nothing
|
|
elseif mesecon.is_conductor_on(node, f.link) then
|
|
local rules = mesecon.conductor_get_rules(node)
|
|
for _, r in ipairs(mesecon.rule2meta(f.link, rules)) do
|
|
local np = vector.add(f.pos, r)
|
|
|
|
-- Check if an onstate receptor is connected. If that is the case,
|
|
-- abort this turnoff process by returning false. `receptor_off` will
|
|
-- discard all the changes that we made in the voxelmanip:
|
|
for _, l in ipairs(mesecon.rules_link_rule_all_inverted(f.pos, r)) do
|
|
if mesecon.is_receptor_on(mesecon.get_node_force(np).name) then
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- Call turnoff on neighbors
|
|
for _, l in ipairs(mesecon.rules_link_rule_all(f.pos, r)) do
|
|
table.insert(frontiers, {pos = np, link = l})
|
|
end
|
|
end
|
|
|
|
mesecon.swap_node_force(f.pos, mesecon.get_conductor_off(node, f.link))
|
|
elseif mesecon.is_effector(node.name) then
|
|
table.insert(signals, {
|
|
pos = f.pos,
|
|
node = node,
|
|
link = f.link,
|
|
depth = depth
|
|
})
|
|
end
|
|
depth = depth + 1
|
|
end
|
|
|
|
for _, sig in ipairs(signals) do
|
|
mesecon.changesignal(sig.pos, sig.node, sig.link, mesecon.state.off, sig.depth)
|
|
if mesecon.is_effector_on(sig.node.name) and not mesecon.is_powered(sig.pos) then
|
|
mesecon.deactivate(sig.pos, sig.node, sig.link, sig.depth)
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
-- Get all linking inputrules of inputnode (effector or conductor) that is connected to
|
|
-- outputnode (receptor or conductor) at position `output` and has an output in direction `rule`
|
|
function mesecon.rules_link_rule_all(output, rule)
|
|
local input = vector.add(output, rule)
|
|
local inputnode = mesecon.get_node_force(input)
|
|
local inputrules = mesecon.get_any_inputrules(inputnode)
|
|
if not inputrules then
|
|
return {}
|
|
end
|
|
local rules = {}
|
|
|
|
for _, inputrule in ipairs(mesecon.flattenrules(inputrules)) do
|
|
-- Check if input accepts from output
|
|
if vector.equals(vector.add(input, inputrule), output) then
|
|
table.insert(rules, inputrule)
|
|
end
|
|
end
|
|
|
|
return rules
|
|
end
|
|
|
|
-- Get all linking outputnodes of outputnode (receptor or conductor) that is connected to
|
|
-- inputnode (effector or conductor) at position `input` and has an input in direction `rule`
|
|
function mesecon.rules_link_rule_all_inverted(input, rule)
|
|
local output = vector.add(input, rule)
|
|
local outputnode = mesecon.get_node_force(output)
|
|
local outputrules = mesecon.get_any_outputrules(outputnode)
|
|
if not outputrules then
|
|
return {}
|
|
end
|
|
local rules = {}
|
|
|
|
for _, outputrule in ipairs(mesecon.flattenrules(outputrules)) do
|
|
if vector.equals(vector.add(output, outputrule), input) then
|
|
table.insert(rules, mesecon.invertRule(outputrule))
|
|
end
|
|
end
|
|
return rules
|
|
end
|
|
|
|
function mesecon.is_powered(pos, rule)
|
|
local node = mesecon.get_node_force(pos)
|
|
local rules = mesecon.get_any_inputrules(node)
|
|
if not rules then return false end
|
|
|
|
-- List of nodes that send out power to pos
|
|
local sourcepos = {}
|
|
|
|
if not rule then
|
|
for _, rule in ipairs(mesecon.flattenrules(rules)) do
|
|
local rulenames = mesecon.rules_link_rule_all_inverted(pos, rule)
|
|
for _, rname in ipairs(rulenames) do
|
|
local np = vector.add(pos, rname)
|
|
local nn = mesecon.get_node_force(np)
|
|
|
|
if (mesecon.is_conductor_on(nn, mesecon.invertRule(rname))
|
|
or mesecon.is_receptor_on(nn.name)) then
|
|
table.insert(sourcepos, np)
|
|
end
|
|
end
|
|
end
|
|
else
|
|
local rulenames = mesecon.rules_link_rule_all_inverted(pos, rule)
|
|
for _, rname in ipairs(rulenames) do
|
|
local np = vector.add(pos, rname)
|
|
local nn = mesecon.get_node_force(np)
|
|
if (mesecon.is_conductor_on (nn, mesecon.invertRule(rname))
|
|
or mesecon.is_receptor_on (nn.name)) then
|
|
table.insert(sourcepos, np)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Return FALSE if not powered, return list of sources if is powered
|
|
if (#sourcepos == 0) then return false
|
|
else return sourcepos end
|
|
end
|
|
|
|
--Rules rotation Functions:
|
|
function mesecon.rotate_rules_right(rules)
|
|
local nr = {}
|
|
for i, rule in ipairs(rules) do
|
|
table.insert(nr, {
|
|
x = -rule.z,
|
|
y = rule.y,
|
|
z = rule.x,
|
|
name = rule.name})
|
|
end
|
|
return nr
|
|
end
|
|
|
|
function mesecon.rotate_rules_left(rules)
|
|
local nr = {}
|
|
for i, rule in ipairs(rules) do
|
|
table.insert(nr, {
|
|
x = rule.z,
|
|
y = rule.y,
|
|
z = -rule.x,
|
|
name = rule.name})
|
|
end
|
|
return nr
|
|
end
|
|
|
|
function mesecon.rotate_rules_down(rules)
|
|
local nr = {}
|
|
for i, rule in ipairs(rules) do
|
|
table.insert(nr, {
|
|
x = -rule.y,
|
|
y = rule.x,
|
|
z = rule.z,
|
|
name = rule.name})
|
|
end
|
|
return nr
|
|
end
|
|
|
|
function mesecon.rotate_rules_up(rules)
|
|
local nr = {}
|
|
for i, rule in ipairs(rules) do
|
|
table.insert(nr, {
|
|
x = rule.y,
|
|
y = -rule.x,
|
|
z = rule.z,
|
|
name = rule.name})
|
|
end
|
|
return nr
|
|
end
|