612 lines
19 KiB
Lua
612 lines
19 KiB
Lua
-- -------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- RAILUALIB EVENT MODULE
|
|
-- Event registration, conditional event management, GUI event filtering.
|
|
|
|
-- dependencies
|
|
local migration = require("__RaiLuaLib__.lualib.migration")
|
|
|
|
-- locals
|
|
local string_find = string.find
|
|
local table_insert = table.insert
|
|
local table_remove = table.remove
|
|
|
|
-- object
|
|
local event = {}
|
|
|
|
-- -----------------------------------------------------------------------------
|
|
-- DISPATCHING
|
|
|
|
-- holds registered events for dispatch
|
|
local events = {}
|
|
-- holds conditional event data
|
|
local conditional_events = {}
|
|
-- conditional events by group
|
|
local conditional_event_groups = {}
|
|
|
|
-- calls handler functions tied to an event
|
|
-- all non-bootstrap events go through this function
|
|
local function dispatch_event(e)
|
|
local global_data = global.__lualib.event
|
|
local con_registry = global_data.conditional_events
|
|
local player_lookup = global_data.players
|
|
local id = e.name
|
|
-- set ID for special events
|
|
if e.nth_tick then
|
|
id = -e.nth_tick
|
|
end
|
|
if e.input_name then
|
|
id = e.input_name
|
|
end
|
|
-- error checking
|
|
if not events[id] then
|
|
error("Event is registered but has no handlers!")
|
|
end
|
|
-- for later use
|
|
local elem = e.element
|
|
|
|
-- for every handler registered to this event
|
|
for _,t in ipairs(events[id]) do
|
|
local has_name = false
|
|
local options = t.options
|
|
-- check if any userdata has gone invalid since last iteration
|
|
if not options.skip_validation then
|
|
for _,v in pairs(e) do
|
|
if type(v) == "table" and v.__self and not v.valid then
|
|
return
|
|
end
|
|
end
|
|
end
|
|
-- check static GUI filters, if any
|
|
local filters = t.gui_filters
|
|
if filters then
|
|
if not elem then
|
|
log("Static event "..id.." has GUI filters but no GUI element, skipping!")
|
|
goto continue
|
|
end
|
|
if filters[elem.index] or filters[elem.name] then
|
|
goto call_handler
|
|
elseif options.match_filter_strings then
|
|
local name = elem.name
|
|
for _,filter in pairs(filters) do
|
|
-- check all string GUI filters to see if they partially match
|
|
if type(filter) == "string" and string_find(name, filter) then
|
|
goto call_handler
|
|
end
|
|
end
|
|
end
|
|
goto continue -- none of them matched
|
|
end
|
|
-- check conditional events
|
|
for name,_ in pairs(t.conditional_names) do
|
|
has_name = true
|
|
local con_data = con_registry[name]
|
|
if not con_data then error("Conditional event ["..name.."] has been raised, but has no data!") end
|
|
-- if con_data is true, then skip all checks and just call the handler
|
|
if con_data == true then
|
|
goto call_handler
|
|
else
|
|
local players = con_data.players
|
|
-- add registered players to the event
|
|
e.registered_players = players
|
|
if e.player_index then
|
|
local player_events = player_lookup[e.player_index]
|
|
-- check if this player is registered
|
|
if player_events and player_events[name] then
|
|
-- check GUI filters
|
|
local player_filters = con_data.gui_filters[e.player_index]
|
|
if player_filters then
|
|
if e.element then
|
|
if player_filters[elem.index] or player_filters[elem.name] then
|
|
goto call_handler
|
|
elseif options.match_filter_strings then
|
|
local elem_name = elem.name
|
|
for _,filter in pairs(player_filters) do
|
|
-- check all string GUI filters to see if they partially match
|
|
if type(filter) == "string" and string_find(elem_name, filter) then
|
|
goto call_handler
|
|
end
|
|
end
|
|
end
|
|
else
|
|
log("Conditional event "..name.." has GUI filters but no GUI element, skipping!")
|
|
end
|
|
else
|
|
goto call_handler
|
|
end
|
|
end
|
|
else
|
|
goto call_handler
|
|
end
|
|
end
|
|
end
|
|
|
|
-- if we're here and there was at least one conditional name, nothing caused the handler to trigger, so skip it
|
|
if has_name then goto continue end
|
|
::call_handler::
|
|
-- call the handler
|
|
t.handler(e)
|
|
::continue::
|
|
if options.force_crc then
|
|
game.force_crc()
|
|
end
|
|
end
|
|
return
|
|
end
|
|
|
|
-- BOOTSTRAP EVENTS
|
|
-- these events are handled specially and do not go through dispatch_event
|
|
|
|
script.on_init(function()
|
|
global.__lualib = {
|
|
__version = script.active_mods["RaiLuaLib"], -- current version
|
|
event = {conditional_events={}, players={}}
|
|
}
|
|
-- dispatch events
|
|
for _,t in ipairs(events.on_init or {}) do
|
|
t.handler()
|
|
end
|
|
-- dispatch postprocess events
|
|
for _,t in ipairs(events.on_init_postprocess or {}) do
|
|
t.handler()
|
|
end
|
|
end)
|
|
|
|
script.on_load(function()
|
|
-- dispatch events
|
|
for _,t in ipairs(events.on_load or {}) do
|
|
t.handler()
|
|
end
|
|
-- dispatch postprocess events
|
|
for _,t in ipairs(events.on_load_postprocess or {}) do
|
|
t.handler()
|
|
end
|
|
-- re-register conditional events
|
|
local registered = global.__lualib.event.conditional_events
|
|
for n,_ in pairs(registered) do
|
|
if conditional_events[n] then
|
|
event.enable(n, nil, nil, true)
|
|
end
|
|
end
|
|
end)
|
|
|
|
script.on_configuration_changed(function(e)
|
|
-- module migrations
|
|
if script.active_mods["RaiLuaLib"] ~= global.__lualib.__version then
|
|
migration.run(global.__lualib.__version or "0.1.0", {
|
|
["0.2.0"] = function()
|
|
-- convert all GUI filters to like-key -> value
|
|
for _,con_data in pairs(global.__lualib.event.conditional_events) do
|
|
for i,filters in pairs(con_data.gui_filters) do
|
|
local new = {}
|
|
for fi=1,#filters do
|
|
new[filters[fi]] = filters[fi]
|
|
end
|
|
con_data.gui_filters[i] = new
|
|
end
|
|
end
|
|
end
|
|
})
|
|
end
|
|
-- dispatch events
|
|
for _,t in ipairs(events.on_configuration_changed or {}) do
|
|
t.handler(e)
|
|
end
|
|
-- update lualib version
|
|
global.__lualib.__version = script.active_mods["RaiLuaLib"]
|
|
end)
|
|
|
|
-- -----------------------------------------------------------------------------
|
|
-- REGISTRATION
|
|
|
|
local bootstrap_events = {on_init=true, on_init_postprocess=true, on_load=true, on_load_postprocess=true, on_configuration_changed=true}
|
|
|
|
-- register static (non-conditional) events
|
|
-- used by register_conditional to insert the handler
|
|
-- conditional name is not to be used by the modder - it is internal only!
|
|
function event.register(id, handler, gui_filters, options, conditional_name)
|
|
options = options or {}
|
|
-- register handler
|
|
if type(id) ~= "table" then id = {id} end
|
|
for _,n in pairs(id) do
|
|
-- create event registry if it doesn't exist
|
|
if not events[n] then
|
|
events[n] = {}
|
|
end
|
|
local registry = events[n]
|
|
-- create master handler if not already created
|
|
if not bootstrap_events[n] then
|
|
if #registry == 0 then
|
|
if type(n) == "number" and n < 0 then
|
|
script.on_nth_tick(-n, dispatch_event)
|
|
else
|
|
script.on_event(n, dispatch_event)
|
|
end
|
|
end
|
|
end
|
|
-- make sure the handler has not already been registered
|
|
for _,t in ipairs(registry) do
|
|
if t.handler == handler then
|
|
-- add conditional name to the list if it's not already there
|
|
if conditional_name and not t.conditional_names[conditional_name] then
|
|
t.conditional_names[conditional_name] = true
|
|
end
|
|
-- do nothing else
|
|
return
|
|
end
|
|
end
|
|
-- format gui filters
|
|
local filters
|
|
if gui_filters then
|
|
if type(gui_filters) ~= "table" then
|
|
gui_filters = {gui_filters}
|
|
end
|
|
filters = {}
|
|
for _,filter in pairs(gui_filters) do
|
|
filters[filter] = filter
|
|
end
|
|
end
|
|
-- insert handler
|
|
local data = {handler=handler, gui_filters=filters, options=options, conditional_names={}}
|
|
if conditional_name then
|
|
data.conditional_names[conditional_name] = true
|
|
end
|
|
if options.insert_at then
|
|
table_insert(registry, options.insert_at, data)
|
|
else
|
|
table_insert(registry, data)
|
|
end
|
|
end
|
|
return
|
|
end
|
|
|
|
-- register conditional (non-static) events
|
|
-- called in on_init and on_load ONLY
|
|
function event.register_conditional(data)
|
|
for n,t in pairs(data) do
|
|
if conditional_events[n] then
|
|
error("Duplicate conditional event: "..n)
|
|
end
|
|
t.options = t.options or {}
|
|
-- add to conditional events table
|
|
conditional_events[n] = t
|
|
-- add to group lookup
|
|
local groups = t.group
|
|
if groups then
|
|
if type(groups) ~= "table" then groups = {groups} end
|
|
for i=1,#groups do
|
|
local group = conditional_event_groups[groups[i]]
|
|
if group then
|
|
group[#group+1] = n
|
|
else
|
|
conditional_event_groups[groups[i]] = {n}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- enables a conditional event
|
|
function event.enable(name, player_index, gui_filters, reregister)
|
|
local data = conditional_events[name]
|
|
if not data then
|
|
error("Conditional event ["..name.."] was not registered and has no data!")
|
|
end
|
|
local global_data = global.__lualib.event
|
|
local saved_data = global_data.conditional_events[name]
|
|
local add_player_data = false
|
|
if saved_data then
|
|
-- update existing data / add this player
|
|
if player_index then
|
|
if saved_data == true then error("Tried to add a player to a global conditional event!") end
|
|
local player_lookup = global_data.players[player_index]
|
|
-- check if they're already registered
|
|
if player_lookup and player_lookup[name] then
|
|
-- don't do anything
|
|
if not data.options.suppress_logging then
|
|
log("Tried to re-register conditional event ["..name.."] for player "..player_index..", skipping!")
|
|
end
|
|
return
|
|
else
|
|
add_player_data = true
|
|
end
|
|
elseif not reregister then
|
|
if not data.options.suppress_logging then
|
|
log("Conditional event ["..name.."] was already registered, skipping!")
|
|
end
|
|
return
|
|
end
|
|
else
|
|
-- add to global
|
|
if player_index then
|
|
global_data.conditional_events[name] = {gui_filters={}, players={}}
|
|
add_player_data = true
|
|
else
|
|
global_data.conditional_events[name] = true
|
|
end
|
|
saved_data = global_data.conditional_events[name]
|
|
end
|
|
-- nest GUI filters into an array if they're not already
|
|
if gui_filters then
|
|
if type(gui_filters) ~= "table" then
|
|
gui_filters = {gui_filters}
|
|
end
|
|
end
|
|
-- add to player lookup table
|
|
if add_player_data then
|
|
local player_lookup = global_data.players[player_index]
|
|
-- add the player to the event
|
|
if gui_filters then
|
|
local new_filters = {}
|
|
for _,filter in pairs(gui_filters) do
|
|
new_filters[filter] = filter
|
|
end
|
|
saved_data.gui_filters[player_index] = new_filters
|
|
end
|
|
table_insert(saved_data.players, player_index)
|
|
-- add to player lookup table
|
|
if not player_lookup then
|
|
global_data.players[player_index] = {[name]=true}
|
|
else
|
|
player_lookup[name] = true
|
|
end
|
|
end
|
|
-- register handler
|
|
event.register(data.id, data.handler, data.gui_filters, data.options, name)
|
|
end
|
|
|
|
-- disables a conditional event
|
|
function event.disable(name, player_index)
|
|
local data = conditional_events[name]
|
|
if not data then
|
|
error("Tried to disable conditional event ["..name.."], which does not exist!")
|
|
end
|
|
local global_data = global.__lualib.event
|
|
local saved_data = global_data.conditional_events[name]
|
|
if not saved_data then
|
|
if not data.options.suppress_logging then
|
|
log("Tried to disable conditional event ["..name.."], which is not enabled!")
|
|
end
|
|
return
|
|
end
|
|
-- remove player from / manipulate global data
|
|
if player_index then
|
|
-- check if the player is actually registered to this event
|
|
if global_data.players[player_index][name] then
|
|
-- remove from players subtable
|
|
for i,pi in ipairs(saved_data.players) do
|
|
if pi == player_index then
|
|
table_remove(saved_data.players, i)
|
|
break
|
|
end
|
|
end
|
|
-- remove GUI filters
|
|
saved_data.gui_filters[player_index] = nil
|
|
-- remove from lookup table
|
|
global_data.players[player_index][name] = nil
|
|
-- remove lookup table if it's empty
|
|
if table_size(global_data.players[player_index]) == 0 then
|
|
global_data.players[player_index] = nil
|
|
end
|
|
else
|
|
if not data.options.suppress_logging then
|
|
log("Tried to disable conditional event ["..name.."] from player "..player_index.." when it wasn't enabled for them!")
|
|
end
|
|
return
|
|
end
|
|
if #saved_data.players == 0 then
|
|
global_data.conditional_events[name] = nil
|
|
else
|
|
-- don't do anything else
|
|
return
|
|
end
|
|
else
|
|
if type(saved_data) == "table" then
|
|
-- remove from all player lookup tables
|
|
local players = global_data.players
|
|
for i=1,#saved_data.players do
|
|
players[saved_data.players[i]][name] = nil
|
|
end
|
|
end
|
|
global_data.conditional_events[name] = nil
|
|
end
|
|
-- deregister handler
|
|
local id = data.id
|
|
if type(id) ~= "table" then id = {id} end
|
|
for _,n in pairs(id) do
|
|
local registry = events[n]
|
|
-- error checking
|
|
if not registry or #registry == 0 then
|
|
log("Tried to deregister an unregistered event of id: "..n)
|
|
return
|
|
end
|
|
for i,t in ipairs(registry) do
|
|
if t.handler == data.handler then
|
|
-- remove conditional name from table
|
|
t.conditional_names[name] = nil
|
|
if table_size(t.conditional_names) > 0 then
|
|
-- don't actually remove or deregister the handler
|
|
return
|
|
end
|
|
-- remove the handler from the events tables
|
|
table_remove(registry, i)
|
|
end
|
|
end
|
|
-- de-register the master handler if it's no longer needed
|
|
if #registry == 0 then
|
|
if type(n) == "number" and n < 0 then
|
|
script.on_nth_tick(math.abs(n), nil)
|
|
else
|
|
script.on_event(n, nil)
|
|
end
|
|
events[n] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
-- enables a group of conditional events
|
|
function event.enable_group(group, player_index, gui_filters)
|
|
local group_events = conditional_event_groups[group]
|
|
if not group_events then error("Group ["..group.."] has no handlers!") end
|
|
for i=1,#group_events do
|
|
event.enable(group_events[i], player_index, gui_filters)
|
|
end
|
|
end
|
|
|
|
-- disables a group of conditional events
|
|
function event.disable_group(group, player_index)
|
|
local group_events = conditional_event_groups[group]
|
|
if not group_events then error("Group ["..group.."] has no handlers!") end
|
|
for i=1,#group_events do
|
|
event.disable(group_events[i], player_index)
|
|
end
|
|
end
|
|
|
|
-- -------------------------------------
|
|
-- SHORTCUT FUNCTIONS
|
|
|
|
-- bootstrap events
|
|
function event.on_init(handler, options)
|
|
return event.register("on_init", handler, nil, options)
|
|
end
|
|
|
|
function event.on_load(handler, options)
|
|
return event.register("on_load", handler, nil, options)
|
|
end
|
|
|
|
function event.on_configuration_changed(handler, options)
|
|
return event.register("on_configuration_changed", handler, nil, options)
|
|
end
|
|
|
|
function event.on_nth_tick(nthTick, handler, options)
|
|
return event.register(-nthTick, handler, nil, options)
|
|
end
|
|
|
|
-- defines.events
|
|
for n,id in pairs(defines.events) do
|
|
event[n] = function(handler, options)
|
|
event.register(id, handler, options)
|
|
end
|
|
end
|
|
|
|
-- -----------------------------------------------------------------------------
|
|
-- EVENT MANIPULATION
|
|
|
|
-- raises an event as if it were actually called
|
|
function event.raise(id, table)
|
|
script.raise_event(id, table)
|
|
return
|
|
end
|
|
|
|
-- set or remove event filters
|
|
function event.set_filters(id, filters)
|
|
if type(id) ~= "table" then id = {id} end
|
|
for _,n in pairs(id) do
|
|
script.set_event_filter(n, filters)
|
|
end
|
|
return
|
|
end
|
|
|
|
-- holds custom event IDs
|
|
local custom_id_registry = {}
|
|
|
|
-- generates or retrieves a custom event ID
|
|
function event.get_id(name)
|
|
if not custom_id_registry[name] then
|
|
custom_id_registry[name] = script.generate_event_name()
|
|
end
|
|
return custom_id_registry[name]
|
|
end
|
|
|
|
-- saves a custom event ID
|
|
function event.save_id(name, id)
|
|
if custom_id_registry[name] then
|
|
log("Overwriting entry in custom event registry: "..name)
|
|
end
|
|
custom_id_registry[name] = id
|
|
end
|
|
|
|
-- updates the GUI filters for the given conditional event
|
|
function event.update_gui_filters(name, player_index, filters, mode)
|
|
mode = mode or "overwrite"
|
|
if type(filters) ~= "table" then
|
|
filters = {filters}
|
|
end
|
|
local con_data = global.__lualib.event.conditional_events[name]
|
|
if not con_data then
|
|
error("Tried to update GUI filters for event ["..name.."], which is not enabled!")
|
|
end
|
|
|
|
local filters_table = con_data.gui_filters
|
|
|
|
if mode == "overwrite" then
|
|
local t = {}
|
|
for _,filter in pairs(filters) do
|
|
t[filter] = filter
|
|
end
|
|
filters_table[player_index] = t
|
|
else
|
|
-- retrieve or create player GUI filters table
|
|
local player_filters = filters_table[player_index]
|
|
if not player_filters then
|
|
filters_table[player_index] = {}
|
|
player_filters = filters_table[player_index]
|
|
end
|
|
-- modify filters
|
|
if mode == "add" then -- add each one
|
|
for _,filter in pairs(filters) do
|
|
player_filters[filter] = filter
|
|
end
|
|
elseif mode == "remove" then -- remove each one
|
|
for _,filter in pairs(filters) do
|
|
player_filters[filter] = nil
|
|
end
|
|
-- remove filters table if it is empty
|
|
if table_size(player_filters) == 0 then
|
|
filters_table[player_index] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
-- return the filters
|
|
return filters_table[player_index]
|
|
end
|
|
|
|
-- retrieves and returns GUI filters for the given conditional event and player
|
|
function event.get_gui_filters(name, player_index)
|
|
local con_data = global.__lualib.event.conditional_events[name]
|
|
if not con_data then
|
|
error("Tried to retrieve GUI filters for conditional event ["..name.."], which is not enabled!")
|
|
end
|
|
return con_data.gui_filters[player_index]
|
|
end
|
|
|
|
-- retrieves and returns the global data for the given conditional event
|
|
function event.get_event_data(name)
|
|
return global.__lualib.event.conditional_events[name]
|
|
end
|
|
|
|
-- returns true if the conditional event is registered
|
|
function event.is_enabled(name, player_index)
|
|
local global_data = global.__lualib.event
|
|
local registry = global_data.conditional_events[name]
|
|
if registry then
|
|
if player_index then
|
|
for _,i in ipairs(registry.players) do
|
|
if i == player_index then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- -----------------------------------------------------------------------------
|
|
|
|
event.events = events
|
|
event.conditional_events = conditional_events
|
|
event.conditional_event_groups = conditional_event_groups
|
|
|
|
return event |