Aleksei-bird 7c9c708c92 Первый фикс
Пачки некоторых позиций увеличены
2024-03-01 20:54:33 +03:00

331 lines
14 KiB
Lua

-- Assembles event handlers from all the relevant files and calls them when needed
local event_listener_names = {"ui.base.main_dialog", "ui.base.compact_dialog", "ui.base.modal_dialog",
"ui.base.view_state", "ui.main.title_bar", "ui.main.subfactory_list", "ui.main.subfactory_info",
"ui.main.item_boxes", "ui.main.production_box", "ui.main.production_table", "ui.main.production_handler",
"ui.elements.module_configurator", "ui.dialogs.beacon_dialog", "ui.dialogs.generic_dialogs",
"ui.dialogs.machine_dialog", "ui.dialogs.matrix_dialog", "ui.dialogs.picker_dialog",
"ui.dialogs.picker_dialog", "ui.dialogs.porter_dialog", "ui.dialogs.preferences_dialog",
"ui.dialogs.recipe_dialog", "ui.dialogs.subfactory_dialog", "ui.dialogs.tutorial_dialog",
"ui.dialogs.utility_dialog"}
local event_listeners = {}
for _, listener_path in ipairs(event_listener_names) do
for _, listener in pairs(require(listener_path)) do
table.insert(event_listeners, listener)
end
end
-- ** GUI EVENTS **
-- These handlers go out to the first thing that it finds that registered for it.
-- They can register either by element name or by a pattern matching element names.
local gui_identifier_map = {
[defines.events.on_gui_click] = "on_gui_click",
[defines.events.on_gui_closed] = "on_gui_closed",
[defines.events.on_gui_confirmed] = "on_gui_confirmed",
[defines.events.on_gui_text_changed] = "on_gui_text_changed",
[defines.events.on_gui_checked_state_changed] = "on_gui_checked_state_changed",
[defines.events.on_gui_switch_state_changed] = "on_gui_switch_state_changed",
[defines.events.on_gui_elem_changed] = "on_gui_elem_changed",
[defines.events.on_gui_value_changed] = "on_gui_value_changed",
[defines.events.on_gui_hover] = "on_gui_hover",
[defines.events.on_gui_leave] = "on_gui_leave"
}
local gui_timeouts = {
on_gui_click = 2,
on_gui_confirmed = 20
}
-- ** SPECIAL HANDLERS **
local special_gui_handlers = {}
special_gui_handlers.on_gui_closed = (function(event, _, _)
return (event.gui_type == defines.gui_type.custom and event.element.visible)
end)
special_gui_handlers.on_gui_confirmed = (function(_, player, action_name)
if action_name then return true end -- run the standard handler if one is found
-- Otherwise, close the currently open modal dialog if possible
if util.globals.ui_state(player).modal_dialog_type ~= nil then
util.raise.close_dialog(player, "submit")
end
return false
end)
local gui_event_cache = {}
-- Create tables for all events that are being registered
for _, event_name in pairs(gui_identifier_map) do
gui_event_cache[event_name] = {
actions = {},
special_handler = special_gui_handlers[event_name]
}
end
-- Compile the list of GUI actions
for _, listener in pairs(event_listeners) do
if listener.gui then
for event_name, actions in pairs(listener.gui) do
local event_table = gui_event_cache[event_name]
for _, action in pairs(actions) do
local timeout = action.timeout or gui_timeouts[event_name] -- can be nil
local action_table = {handler = action.handler, timeout = timeout}
if event_name == "on_gui_click" and action.modifier_actions then
action_table.modifier_actions = {}
-- Transform modifier actions into a more useable form
for modifier_action_name, modifier_action in pairs(action.modifier_actions) do
local modifier_click = modifier_action[1]
action_table.modifier_actions[modifier_click] = {
name = modifier_action_name,
limitations = modifier_action[2] or {}
}
end
-- Generate all the tooltip lines for these modifier actions
local tooltip = util.actions.all_tutorial_tooltips(action_table.modifier_actions)
TUTORIAL_TOOLTIPS[action.name] = tooltip
end
event_table.actions[action.name] = action_table
end
end
end
end
local mouse_click_map = {
[defines.mouse_button_type.left] = "left",
[defines.mouse_button_type.right] = "right",
[defines.mouse_button_type.middle] = "middle"
}
local function convert_click_to_string(event)
local modifier_click = mouse_click_map[event.button]
if event.shift then modifier_click = "shift-" .. modifier_click end
if event.alt then modifier_click = "alt-" .. modifier_click end
if event.control then modifier_click = "control-" .. modifier_click end
return modifier_click
end
local function handle_gui_event(event)
if not event.element then return end
local tags = event.element.tags
if tags.mod ~= "fp" then return end
-- Guard against an event being called before the player is initialized
if not global.players[event.player_index] then return end
-- GUI events always have an associated player
local player = game.get_player(event.player_index) ---@cast player -nil
-- The event table actually contains its identifier, not its name
local event_name = gui_identifier_map[event.name]
local event_table = gui_event_cache[event_name]
local action_name = tags[event_name] -- could be nil
-- If a special handler is set, it needs to return true before proceeding with the registered handlers
local special_handler = event_table.special_handler
if special_handler and special_handler(event, player, action_name) == false then return end
-- Special handlers need to run even without an action handler, so we
-- wait until this point to check whether there is an associated action
if not action_name then return end -- meaning this event type has no action on this element
local action_table = event_table.actions[action_name]
-- Check if rate limiting allows this action to proceed
if util.actions.rate_limited(player, event.tick, action_name, action_table.timeout) then return end
local third_parameter = event -- all GUI events except on_gui_click have the event as the third parameter
-- Special modifier handling for on_gui_click if configured
if event_name == "on_gui_click" and action_table.modifier_actions then
local modifier_action = action_table.modifier_actions[convert_click_to_string(event)]
if not modifier_action then return end -- meaning the used modifiers do not have an associated action
local active_limitations = util.actions.current_limitations(player)
-- Check whether the selected action is allowed according to its limitations
if not util.actions.allowed(modifier_action.limitations, active_limitations) then return end
third_parameter = modifier_action.name
end
action_table.handler(player, tags, third_parameter) -- send the actual event
util.messages.refresh(player) -- give messages a chance to update themselves
end
-- Register all the GUI events from the identifier map
for event_id, _ in pairs(gui_identifier_map) do script.on_event(event_id, handle_gui_event) end
-- ** DIALOG EVENTS **
-- These custom events handle opening and closing modal dialogs
local dialog_event_cache = {}
-- Compile the list of dialog actions
for _, listener in pairs(event_listeners) do
if listener.dialog then
dialog_event_cache[listener.dialog.dialog] = listener.dialog
end
end
local function apply_metadata_overrides(base, overrides)
for k, v in pairs(overrides) do
local base_v = base[k]
if type(base_v) == "table" and type(v) == "table" then
apply_metadata_overrides(base_v, v)
else
base[k] = v
end
end
end
local function handle_dialog_event(event)
-- Guard against an event being called before the player is initialized
if not global.players[event.player_index] then return end
-- These custom events always have an associated player
local player = game.get_player(event.player_index) ---@cast player -nil
local ui_state = util.globals.ui_state(player)
-- Check if the action is allowed to be carried out by rate limiting
if util.actions.rate_limited(player, event.tick, event.name, 20) then return end
if event.name == CUSTOM_EVENTS.open_modal_dialog then
local listener = dialog_event_cache[event.metadata.dialog]
local metadata = event.metadata
if listener.metadata ~= nil then -- collect additional metadata
local additional_metadata = listener.metadata(metadata.modal_data)
apply_metadata_overrides(metadata, additional_metadata)
end
modal_dialog.enter(player, metadata, listener.open, listener.early_abort_check)
elseif event.name == CUSTOM_EVENTS.close_modal_dialog then
local modal_dialog_type = ui_state.modal_dialog_type
if modal_dialog_type == nil then return end
local listener = dialog_event_cache[modal_dialog_type]
modal_dialog.exit(player, event.action, event.skip_opened, listener.close)
end
end
-- Register all the misc events from the identifier map
local dialog_events = {CUSTOM_EVENTS.open_modal_dialog, CUSTOM_EVENTS.close_modal_dialog}
for _, event_id in pairs(dialog_events) do script.on_event(event_id, handle_dialog_event) end
-- ** MISC EVENTS **
-- These events call every handler that has subscribed to it by id or name. The difference to GUI events
-- is that multiple handlers can be registered to the same event, and there is no standard handler.
local misc_identifier_map = {
-- Standard events
[defines.events.on_gui_opened] = "on_gui_opened",
[defines.events.on_player_display_resolution_changed] = "on_player_display_resolution_changed",
[defines.events.on_player_display_scale_changed] = "on_player_display_scale_changed",
[defines.events.on_player_selected_area] = "on_player_selected_area",
[defines.events.on_player_cursor_stack_changed] = "on_player_cursor_stack_changed",
[defines.events.on_player_main_inventory_changed] = "on_player_main_inventory_changed",
[defines.events.on_lua_shortcut] = "on_lua_shortcut",
-- Keyboard shortcuts
["fp_toggle_interface"] = "fp_toggle_interface",
["fp_toggle_compact_view"] = "fp_toggle_compact_view",
["fp_toggle_pause"] = "fp_toggle_pause",
["fp_refresh_production"] = "fp_refresh_production",
["fp_up_floor"] = "fp_up_floor",
["fp_top_floor"] = "fp_top_floor",
["fp_cycle_production_views"] = "fp_cycle_production_views",
["fp_reverse_cycle_production_views"] = "fp_reverse_cycle_production_views",
["fp_confirm_dialog"] = "fp_confirm_dialog",
["fp_confirm_gui"] = "fp_confirm_gui",
["fp_focus_searchfield"] = "fp_focus_searchfield",
[CUSTOM_EVENTS.build_gui_element] = "build_gui_element",
[CUSTOM_EVENTS.refresh_gui_element] = "refresh_gui_element"
}
local misc_timeouts = {
fp_confirm_dialog = 20,
fp_confirm_gui = 20,
fp_refresh_production = 20
}
-- ** SPECIAL HANDLERS **
local special_misc_handlers = {}
special_misc_handlers.on_gui_opened = (function(event)
-- This should only fire when a UI not associated with FP is opened, so FP's dialogs can close properly
return (event.gui_type ~= defines.gui_type.custom or not event.element or event.element.tags.mod ~= "fp")
end)
local misc_event_cache = {}
-- Compile the list of misc handlers
for _, listener in pairs(event_listeners) do
if listener.misc then
for event_name, handler in pairs(listener.misc) do
misc_event_cache[event_name] = misc_event_cache[event_name] or {
registered_handlers = {},
special_handler = special_misc_handlers[event_name],
timeout = misc_timeouts[event_name]
}
table.insert(misc_event_cache[event_name].registered_handlers, handler)
end
end
end
local function handle_misc_event(event)
local event_name = event.input_name or event.name -- also handles keyboard shortcuts
local string_name = misc_identifier_map[event_name]
local event_handlers = misc_event_cache[string_name]
if not event_handlers then return end -- make sure the given event is even handled
-- Guard against an event being called before the player is initialized
if not global.players[event.player_index] then return end
-- We'll assume every one of the events has a player attached
local player = game.get_player(event.player_index) ---@cast player -nil
-- Check if the action is allowed to be carried out by rate limiting
if util.actions.rate_limited(player, event.tick, event_name, event_handlers.timeout) then return end
-- If a special handler is set, it needs to return true before proceeding with the registered handlers
local special_handler = event_handlers.special_handler
if special_handler and special_handler(event) == false then return end
for _, registered_handler in pairs(event_handlers.registered_handlers) do
registered_handler(player, event) -- send actual event
end
if CUSTOM_EVENTS[string_name] then return end -- don't refresh message for events inside other events
util.messages.refresh(player) -- give messages a chance to update themselves
end
-- Register all the misc events from the identifier map
for event_id, _ in pairs(misc_identifier_map) do script.on_event(event_id, handle_misc_event) end
-- ** GLOBAL HANDLERS **
-- In some situations, you need to be able to refer to a function indirectly by string name.
-- As functions can't be stored in global, these need to be collected and stored in a central placem
-- so code that wants to call them knows where to find them. This collects and stores these functions.
for _, listener in pairs(event_listeners) do
if listener.global then
for name, handler in pairs(listener.global) do
GLOBAL_HANDLERS[name] = handler
end
end
end
-- These are not registered as events, instead just made available to call directly