--[[ Trigger logic. This file handles the triggers of the level. All trigger data is stored completely separately from the world. To actually make use of the trigger system, nodes need to store a trigger_id in their node metadata, which can be obtained by lzr_triggers.add_trigger. ]] lzr_triggers = {} local S = minetest.get_translator("lzr_triggers") -- Abbreviated signal types and receiver types have -- a separate textdomain to avoid collisions. local SA = minetest.get_translator("_lzr_triggers_abbreviations") --[[ A 'trigger' is a node that is able to send and/or receive signals. Triggers have an unique trigger_id, a location (either in the world or in the player inventory), and a signal type (which specifies how receivers react to the signal). The trigger_id is an unique identifier for each trigger and must be constructed from minetest.pos_to_string(pos), where is the *original* position of the trigger node that the node spawned in. The initial location of every trigger MUST be a position. A minimal trigger is created with lzr_triggers.add_triggers, which must be eventually be followed by lzr_triggers.set_trigger_location. ]] local triggers = { --[[ [trigger_id_1] = { location = or "player", signal_type = , receiver_type = , }, -- [trigger_id_2] = ... -- ... ]] } -- This table contains the signals. A signal is a 1:n relation -- between a sender and its receivers. -- Each sender may send to any number of receivers. -- The signal list should only be constructed after the level -- was built. local signals = { --[[ [trigger_id_of_sender_1] = { }, [trigger_id_of_sender_2] = { }, -- ... ]] } --[[ SIGNAL TYPES ]] -- List of all supported signal types. -- When a sender gets activated or deactivated, -- it may send a signal depending on the state -- change. Senders can send an ON, OFF or TOGGLE -- signal which turns on, off or toggles the -- receiver, respectively. -- Example: SIGNAL_TYPE_SYNC sends an ON signal -- when the sender is activated and an OFF -- signal when it is deactivated. -- activate → ON; deactivate → OFF (default) lzr_triggers.SIGNAL_TYPE_SYNC = 0 -- activate → OFF; deactivate → ON lzr_triggers.SIGNAL_TYPE_SYNC_INV = 1 -- activate → TOGGLE; deactivate → TOGGLE lzr_triggers.SIGNAL_TYPE_TOGGLE = 2 -- activate → ON; deactivate → ON lzr_triggers.SIGNAL_TYPE_TOGGLE_ON = 3 -- activate → OFF; deactivate → OFF lzr_triggers.SIGNAL_TYPE_TOGGLE_OFF = 4 -- activate → TOGGLE; deactivate → (no signal) lzr_triggers.SIGNAL_TYPE_ACTIVATE_TOGGLE = 5 -- activate → ON; deactivate → (no signal) lzr_triggers.SIGNAL_TYPE_ACTIVATE_ON = 6 -- activate → OFF; deactivate → (no signal) lzr_triggers.SIGNAL_TYPE_ACTIVATE_OFF = 7 -- activate → (no signal); deactivate → TOGGLE lzr_triggers.SIGNAL_TYPE_DEACTIVATE_TOGGLE = 8 -- activate → (no signal); deactivate → ON lzr_triggers.SIGNAL_TYPE_DEACTIVATE_ON = 9 -- activate → (no signal); deactivate → OFF lzr_triggers.SIGNAL_TYPE_DEACTIVATE_OFF = 10 -- Maximum signal type ID (for iterations) lzr_triggers.MAX_SIGNAL_TYPE = 10 -- Icons for each signal type lzr_triggers.SIGNAL_TYPE_ICONS = { [lzr_triggers.SIGNAL_TYPE_ACTIVATE_OFF] = "lzr_triggers_icon_signal_type_activate_off.png", [lzr_triggers.SIGNAL_TYPE_ACTIVATE_ON] = "lzr_triggers_icon_signal_type_activate_on.png", [lzr_triggers.SIGNAL_TYPE_ACTIVATE_TOGGLE] = "lzr_triggers_icon_signal_type_activate_toggle.png", [lzr_triggers.SIGNAL_TYPE_DEACTIVATE_OFF] = "lzr_triggers_icon_signal_type_deactivate_off.png", [lzr_triggers.SIGNAL_TYPE_DEACTIVATE_ON] = "lzr_triggers_icon_signal_type_deactivate_on.png", [lzr_triggers.SIGNAL_TYPE_DEACTIVATE_TOGGLE] = "lzr_triggers_icon_signal_type_deactivate_toggle.png", [lzr_triggers.SIGNAL_TYPE_SYNC] = "lzr_triggers_icon_signal_type_sync.png", [lzr_triggers.SIGNAL_TYPE_SYNC_INV] = "lzr_triggers_icon_signal_type_sync_inv.png", [lzr_triggers.SIGNAL_TYPE_TOGGLE] = "lzr_triggers_icon_signal_type_toggle.png", [lzr_triggers.SIGNAL_TYPE_TOGGLE_OFF] = "lzr_triggers_icon_signal_type_toggle_off.png", [lzr_triggers.SIGNAL_TYPE_TOGGLE_ON] = "lzr_triggers_icon_signal_type_toggle_on.png", } -- Very short forms of the signal type names lzr_triggers.SIGNAL_TYPE_NAMES_SHORT = { --!+! Short signal type name: Activate OFF [lzr_triggers.SIGNAL_TYPE_ACTIVATE_OFF] = SA("A.OFF"), --!+! Short signal type name: Activate ON [lzr_triggers.SIGNAL_TYPE_ACTIVATE_ON] = SA("A.ON"), --!+! Short signal type name: Activate TOGGLE [lzr_triggers.SIGNAL_TYPE_ACTIVATE_TOGGLE] = SA("A.TOG"), --!+! Short signal type name: Deactivate OFF [lzr_triggers.SIGNAL_TYPE_DEACTIVATE_OFF] = SA("D.OFF"), --!+! Short signal type name: Deactivate ON [lzr_triggers.SIGNAL_TYPE_DEACTIVATE_ON] = SA("D.ON"), --!+! Short signal type name: Deactivate TOGGLE [lzr_triggers.SIGNAL_TYPE_DEACTIVATE_TOGGLE] = SA("D.TOG"), --!+! Short signal type name: Synchronized [lzr_triggers.SIGNAL_TYPE_SYNC] = SA("Sync"), --!+! Short signal type name: Synchronized inverted [lzr_triggers.SIGNAL_TYPE_SYNC_INV] = SA("Sync inv"), --!+! Short signal type name: Toggle [lzr_triggers.SIGNAL_TYPE_TOGGLE] = SA("T."), --!+! Short signal type name: Toggle OFF [lzr_triggers.SIGNAL_TYPE_TOGGLE_OFF] = SA("T.OFF"), --!+! Short signal type name: Toggle ON [lzr_triggers.SIGNAL_TYPE_TOGGLE_ON] = SA("T.ON"), } -- Long form of the signal type names lzr_triggers.SIGNAL_TYPE_NAMES = { --~ Signal type name [lzr_triggers.SIGNAL_TYPE_ACTIVATE_OFF] = S("Activate OFF"), --~ Signal type name [lzr_triggers.SIGNAL_TYPE_ACTIVATE_ON] = S("Activate ON"), --~ Signal type name [lzr_triggers.SIGNAL_TYPE_ACTIVATE_TOGGLE] = S("Activate TOGGLE"), --~ Signal type name [lzr_triggers.SIGNAL_TYPE_DEACTIVATE_OFF] = S("Deactivate OFF"), --~ Signal type name [lzr_triggers.SIGNAL_TYPE_DEACTIVATE_ON] = S("Deactivate ON"), --~ Signal type name [lzr_triggers.SIGNAL_TYPE_DEACTIVATE_TOGGLE] = S("Deactivate TOGGLE"), --~ Signal type name [lzr_triggers.SIGNAL_TYPE_SYNC] = S("Synchronous"), --~ Signal type name [lzr_triggers.SIGNAL_TYPE_SYNC_INV] = S("Synchronous inverted"), --~ Signal type name [lzr_triggers.SIGNAL_TYPE_TOGGLE] = S("Toggle"), --~ Signal type name [lzr_triggers.SIGNAL_TYPE_TOGGLE_OFF] = S("Toggle OFF"), --~ Signal type name [lzr_triggers.SIGNAL_TYPE_TOGGLE_ON] = S("Toggle ON"), } -- Descriptions of signal types lzr_triggers.SIGNAL_TYPE_DESCRIPTIONS = { --~ Signal type description for signal type 'Synchronous' [lzr_triggers.SIGNAL_TYPE_SYNC] = S("send ON signal when activated, send OFF signal when deactivated"), --~ Signal type description for signal type 'Synchronous inverted' [lzr_triggers.SIGNAL_TYPE_SYNC_INV] = S("send OFF signal when activated, send ON signal when deactivated"), --~ Signal type description for signal type 'Toggle' [lzr_triggers.SIGNAL_TYPE_TOGGLE] = S("send TOGGLE signal when toggled"), --~ Signal type description for signal type 'Toggle ON' [lzr_triggers.SIGNAL_TYPE_TOGGLE_ON] = S("send ON signal when toggled"), --~ Signal type description for signal type 'Toggle OFF' [lzr_triggers.SIGNAL_TYPE_TOGGLE_OFF] = S("send OFF signal when toggled"), --~ Signal type description for signal type 'Activate ON' [lzr_triggers.SIGNAL_TYPE_ACTIVATE_ON] = S("send ON signal when activated"), --~ Signal type description for signal type 'Activate TOGGLE' [lzr_triggers.SIGNAL_TYPE_ACTIVATE_TOGGLE] = S("send TOGGLE signal when activated"), --~ Signal type description for signal type 'Activate OFF' [lzr_triggers.SIGNAL_TYPE_ACTIVATE_OFF] = S("send OFF signal when activated"), --~ Signal type description for signal type 'Deactivate ON' [lzr_triggers.SIGNAL_TYPE_DEACTIVATE_ON] = S("send ON signal when deactivated"), --~ Signal type description for signal type 'Deactivate TOGGLE' [lzr_triggers.SIGNAL_TYPE_DEACTIVATE_TOGGLE] = S("send TOGGLE signal when deactivated"), --~ Signal type description for signal type 'Deactivate OFF' [lzr_triggers.SIGNAL_TYPE_DEACTIVATE_OFF] = S("send OFF signal when deactivated"), } --[[ RECEIVER TYPES ]] -- The receiver reacts to any incoming signal (default) -- -- RECOMMENDED USE: Best used for receivers that are -- triggered by only one sender, although multiple -- senders are still allowed. But be aware that ANY -- signal may change the state, so carefully test the -- level if you want to build complex setups. lzr_triggers.RECEIVER_TYPE_ANY = 0 -- This is basically a logical AND of its senders. -- If the receiver receives any signal -- (ON, OFF or TOGGLE), it does the following: -- First, it checks if it has any senders -- of type SIGNAL_TYPE SYNC or SIGNAL_TYPE_SYNC_INV. -- If it doesn't, the receiver activates. -- If it does, it asks two questions: -- 1) Are all my senders of type SIGNAL_TYPE_SYNC active? (if there are none, answer "yes") -- 2) Are all my senders of type SIGNAL_TYPE_SYNC_INV inactive? (if there are none, answer "yes") -- If the answer is "yes" for both questions -- the receiver activates. Otherwise, it deactivates. -- -- RECOMMENDED USE: This receiver type is best used with -- a number of senders of type SIGNAL_TYPE_SYNC if you want -- all of them to be active to activate the receiver. -- Alternatively, use SIGNAL_TYPE_SYNC_INV for the senders -- if you expect all senders to be inactive in order -- to activate the receiver. You may also mix both signal -- types for more complex levels. -- Using other signal types with this receiver is possible -- but mostly pointless. lzr_triggers.RECEIVER_TYPE_SYNC_AND = 1 -- Maximum receiver type ID (for iterations) lzr_triggers.MAX_RECEIVER_TYPE = 1 -- Icons for each receiver type lzr_triggers.RECEIVER_TYPE_ICONS = { [lzr_triggers.RECEIVER_TYPE_ANY] = "lzr_triggers_icon_receiver_type_any.png", [lzr_triggers.RECEIVER_TYPE_SYNC_AND] = "lzr_triggers_icon_receiver_type_sync_and.png", } -- Very short forms of the receiver type names lzr_triggers.RECEIVER_TYPE_NAMES_SHORT = { --!+! Short receiver type name: Any [lzr_triggers.RECEIVER_TYPE_ANY] = SA("Any"), --!+! Short receiver type name: Synchronized AND [lzr_triggers.RECEIVER_TYPE_SYNC_AND] = SA("S.AND"), } -- Long form of the receiver type names lzr_triggers.RECEIVER_TYPE_NAMES = { --~ Receiver type name [lzr_triggers.RECEIVER_TYPE_ANY] = S("Any"), --~ Receiver type name [lzr_triggers.RECEIVER_TYPE_SYNC_AND] = S("Synchronous AND"), } -- Descriptions of receiver types lzr_triggers.RECEIVER_TYPE_DESCRIPTIONS = { --~ Receiver type description for type 'Any' [lzr_triggers.RECEIVER_TYPE_ANY] = S("react to any signal"), --~ Receiver type description for type 'Synchronous AND' [lzr_triggers.RECEIVER_TYPE_SYNC_AND] = S("activates when receiving a signal and all its synchronous senders are active and all its inverted synchronous senders are inactive; deactivates when receiving a signal when that’s not the case"), } lzr_triggers.reset_triggers = function() triggers = {} signals = {} end -- Marks a node as a trigger. This function should be -- called on all sender and receiver nodes right -- after the level was built. -- This MUST NOT be called if the trigger already exists. lzr_triggers.add_trigger = function(pos) local id = minetest.pos_to_string(pos) if triggers[id] then minetest.log("error", "[lzr_triggers] Tried to add trigger '"..id.."' twice!") return end triggers[id] = { location = table.copy(pos), signal_type = 0, receiver_type = 0, } signals[id] = {} minetest.log("verbose", "[lzr_triggers] Added trigger '"..id.."'") return id end -- Returns true if the trigger with the given ID -- exists. lzr_triggers.trigger_exists = function(id) return triggers[id] ~= nil end -- Removes a trigger from the trigger database. -- If the trigger doesn't exist, nothing happens. lzr_triggers.remove_trigger = function(id) if not triggers[id] then return end triggers[id] = nil signals[id] = nil -- Also remove the trigger from all signal lists for sender_id, receiver_ids in pairs(signals) do for r=1, #receiver_ids do local receiver_id = receiver_ids[r] if receiver_id == id then table.remove(receiver_ids, r) break end end end minetest.log("verbose", "[lzr_triggers] Removed trigger '"..id.."'") end -- Set the list of signals at once for the given sender. -- Will replace the old list. -- * sender_trigger_id: trigger_id of sender -- * receiver_trigger_ids: list of trigger_ids for all receivers to send to. lzr_triggers.set_signals = function(sender_trigger_id, receiver_trigger_ids) if not signals[sender_trigger_id] then minetest.log("error", "[lzr_triggers] Cannot add signal for '"..tostring(sender_trigger_id).."', trigger does not exist yet") return false end signals[sender_trigger_id] = receiver_trigger_ids minetest.log("verbose", "[lzr_triggers] Set signals for '"..sender_trigger_id.."'") return true end -- Add a single signal from sender to receiver (each given by trigger_id), -- but not if the signal already exists. lzr_triggers.add_signal = function(sender_trigger_id, receiver_trigger_id) if not signals[sender_trigger_id] then minetest.log("error", "[lzr_triggers] Cannot add signal for '"..tostring(sender_trigger_id).."', trigger does not exist yet") return false end -- Check if this signal already exists local my_signals = signals[sender_trigger_id] for s=1, #my_signals do if my_signals[s] == receiver_trigger_id then return false end end -- Add the signal table.insert(signals[sender_trigger_id], receiver_trigger_id) minetest.log("verbose", "[lzr_triggers] Added signal to '"..receiver_trigger_id.."' for '"..sender_trigger_id.."'") return true end -- Returns the trigger of the given ID lzr_triggers.get_trigger = function(id) return triggers[id] end -- Returns a list of all triggers lzr_triggers.get_triggers = function() return table.copy(triggers) end -- Returns the list of triggers the given trigger sends to lzr_triggers.get_receivers = function(id) if signals[id] then return signals[id] else return {} end end -- Returns the list of triggers the given trigger receives from lzr_triggers.get_senders = function(id) local senders = {} for sender_id, receivers in pairs(signals) do for r=1, #receivers do if receivers[r] == id then table.insert(senders, sender_id) break end end end return senders end lzr_triggers.set_trigger_location = function(id, location) triggers[id].location = location local locdump if type(location) == "table" then locdump = minetest.pos_to_string(location) else locdump = tostring(location) end minetest.log("verbose", "[lzr_triggers] Set trigger location for '"..id.."' to "..locdump) end lzr_triggers.set_trigger_signal_type = function(id, signal_type) triggers[id].signal_type = signal_type minetest.log("verbose", "[lzr_triggers] Set trigger signal type for '"..id.."' to "..signal_type) end lzr_triggers.set_trigger_receiver_type = function(id, receiver_type) triggers[id].receiver_type = receiver_type minetest.log("verbose", "[lzr_triggers] Set trigger receiver type for '"..id.."' to "..receiver_type) end lzr_triggers.internal_trigger_export = function() local exported_triggers = table.copy(triggers) for trigger_id, receivers in pairs(signals) do exported_triggers[trigger_id].send_to = table.copy(receivers) end return exported_triggers end -- Checks if player has a node item with the given Trigger ID in -- inventory ("main" list). If found, returns: -- , -- If not found, returns nil. lzr_triggers.find_trigger_in_player_inventory = function(player, trigger_id) if trigger_id == "" or trigger_id == nil then return end local inv = player:get_inventory() for i=1, #inv:get_list("main") do local item = inv:get_stack("main", i) local imeta = item:get_meta() local this_trigger_id = imeta:get_string("trigger_id") if this_trigger_id == trigger_id then return item, i end end end -- Helper function to check triggers for validity dofile(minetest.get_modpath("lzr_triggers").."/check.lua") -- Trigger debug dialog dofile(minetest.get_modpath("lzr_triggers").."/dialog.lua")