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

570 lines
18 KiB
Lua

local dictionary = require("__flib__.dictionary-lite")
local gui = require("__flib__.gui")
local migration = require("__flib__.migration")
local on_tick_n = require("__flib__.on-tick-n")
local table = require("__flib__.table")
local constants = require("constants")
local database = require("scripts.database")
local formatter = require("scripts.formatter")
local global_data = require("scripts.global-data")
local migrations = require("scripts.migrations")
local player_data = require("scripts.player-data")
local remote_interface = require("scripts.remote-interface")
local util = require("scripts.util")
-- -----------------------------------------------------------------------------
-- GLOBALS
INFO_GUI = require("scripts.gui.info.index")
QUICK_REF_GUI = require("scripts.gui.quick-ref.index")
SEARCH_GUI = require("scripts.gui.search.index")
SETTINGS_GUI = require("scripts.gui.settings.index")
--- Open the given page.
--- @param player LuaPlayer
--- @param player_table PlayerTable
--- @param context Context
--- @param options table?
function OPEN_PAGE(player, player_table, context, options)
options = options or {}
--- @type InfoGui?
local Gui
if options.id then
Gui = util.get_gui(player.index, "info", options.id)
else
_, Gui = next(INFO_GUI.find_open_context(player_table, context))
end
if Gui and Gui.refs.root.visible then
Gui:update_contents({ new_context = context })
else
INFO_GUI.build(player, player_table, context, options)
end
end
--- Refresh the contents of all Recipe Book GUIs.
--- @param player LuaPlayer
--- @param player_table PlayerTable
--- @param skip_memoizer_purge boolean?
function REFRESH_CONTENTS(player, player_table, skip_memoizer_purge)
if not skip_memoizer_purge then
formatter.create_cache(player.index)
end
--- @type table<number|string, InfoGui>
local info_guis = player_table.guis.info
for id, InfoGui in pairs(info_guis) do
if not constants.ignored_info_ids[id] and InfoGui.refs.window.valid then
InfoGui:update_contents({ refresh = true })
end
end
--- @type table<string, QuickRefGui>
local quick_ref_guis = player_table.guis.quick_ref
for _, QuickRefGui in pairs(quick_ref_guis) do
if QuickRefGui.refs.window.valid then
QuickRefGui:update_contents()
end
end
--- @type SearchGui?
local SearchGui = util.get_gui(player.index, "search")
if SearchGui then
SearchGui:dispatch("update_favorites")
SearchGui:dispatch("update_history")
if SearchGui.state.search_type == "textual" then
SearchGui:dispatch("update_search_results")
elseif SearchGui.state.search_type == "visual" then
if SearchGui.refs.window.visible then
SearchGui:update_visual_contents()
else
SearchGui.state.needs_visual_update = true
end
end
end
end
-- -----------------------------------------------------------------------------
-- COMMANDS
-- User commands
commands.add_command("rb-refresh-all", { "command-help.rb-refresh-all" }, function(e)
local player = game.get_player(e.player_index) --[[@as LuaPlayer]]
if not player.admin then
player.print({ "cant-run-command-not-admin", "rb-refresh-all" })
return
end
game.print("[color=red]REFRESHING RECIPE BOOK[/color]")
game.print("Get comfortable, this could take a while!")
on_tick_n.add(game.tick + 1, { action = "refresh_all" })
end)
-- Debug commands
commands.add_command("rb-print-object", nil, function(e)
if not e.parameter then
return
end
local player = game.get_player(e.player_index) --[[@as LuaPlayer]]
if not player.admin then
player.print({ "cant-run-command-not-admin", "rb-dump-data" })
return
end
local class, name = string.match(e.parameter, "^(.+) (.+)$")
if not class or not name then
player.print("Invalid arguments format")
return
end
local obj = database[class] and database[class][name]
if not obj then
player.print("Not a valid object")
return
end
if __DebugAdapter then
__DebugAdapter.print(obj)
player.print("Object data has been printed to the debug console.")
else
log(game.table_to_json(obj))
player.print("Object data has been printed to the log file.")
end
end)
commands.add_command("rb-count-objects", nil, function(e)
local player = game.get_player(e.player_index) --[[@as LuaPlayer]]
if not player.admin then
player.print({ "cant-run-command-not-admin", "rb-dump-data" })
return
end
for name, tbl in pairs(database) do
if type(tbl) == "table" then
local output = name .. ": " .. table_size(tbl)
player.print(output)
log(output)
end
end
end)
commands.add_command("rb-dump-database", nil, function(e)
local player = game.get_player(e.player_index) --[[@as LuaPlayer]]
if not player.admin then
player.print({ "cant-run-command-not-admin", "rb-dump-data" })
return
end
if __DebugAdapter and (not e.parameter or #e.parameter == 0) then
__DebugAdapter.print(database)
game.print("Database has been dumped to the debug console.")
else
game.print("[color=red]DUMPING RECIPE BOOK DATABASE[/color]")
game.print("Get comfortable, this could take a while!")
on_tick_n.add(
game.tick + 1,
{ action = "dump_database", player_index = e.player_index, raw = e.parameter == "raw" }
)
end
end)
-- -----------------------------------------------------------------------------
-- EVENT HANDLERS
-- BOOTSTRAP
script.on_init(function()
dictionary.on_init()
on_tick_n.init()
global_data.init()
global_data.update_sync_data()
global_data.build_prototypes()
database.build()
database.check_forces()
for i, player in pairs(game.players) do
player_data.init(i)
player_data.refresh(player, global.players[i])
end
end)
script.on_load(function()
formatter.create_all_caches()
-- When mod configuration changes, don't bother to build anything because it'll have to be built again anyway
if global_data.check_should_load() then
database.build()
database.check_forces()
end
-- Load GUIs
for _, player_table in pairs(global.players) do
local guis = player_table.guis
if guis then
for _, Gui in pairs(guis.quick_ref or {}) do
QUICK_REF_GUI.load(Gui)
end
for id, Gui in pairs(guis.info or {}) do
if not constants.ignored_info_ids[id] then
INFO_GUI.load(Gui)
end
end
if guis.search then
SEARCH_GUI.load(guis.search)
end
if guis.settings then
SETTINGS_GUI.load(guis.settings)
end
end
end
end)
migration.handle_on_configuration_changed(migrations, function()
dictionary.on_configuration_changed()
global_data.update_sync_data()
global_data.build_prototypes()
database.build()
database.check_forces()
for i, player in pairs(game.players) do
player_data.refresh(player, global.players[i])
end
end)
-- DICTIONARIES
dictionary.handle_events()
script.on_event(dictionary.on_player_dictionaries_ready, function(e)
local player = game.get_player(e.player_index) --[[@as LuaPlayer]] --[[@as LuaPlayer]]
local player_table = global.players[e.player_index]
player_table.translations = dictionary.get_all(e.player_index)
if player_table.flags.can_open_gui then
REFRESH_CONTENTS(player, player_table)
else
-- Show message if needed
if player_table.flags.show_message_after_translation then
player.print({ "message.rb-can-open-gui" })
player_table.flags.show_message_after_translation = false
end
-- Create GUI
SEARCH_GUI.build(player, player_table)
-- Update flags
player_table.flags.can_open_gui = true
-- Enable shortcut
player.set_shortcut_available("rb-search", true)
end
end)
-- FORCE
script.on_event(defines.events.on_force_created, function(e)
if not global.forces or not database.generated then
return
end
global_data.add_force(e.force)
database.check_force(e.force)
end)
script.on_event({ defines.events.on_research_finished, defines.events.on_research_reversed }, function(e)
-- This can be called by other mods before we get a chance to load
if not global.players or not database.generated then
return
end
if not database[constants.classes[1]] then
return
end
database.handle_research_updated(e.research, e.name == defines.events.on_research_finished and true or nil)
-- Refresh all GUIs to reflect finished research
for _, player in pairs(e.research.force.players) do
local player_table = global.players[player.index]
if player_table and player_table.flags.can_open_gui then
REFRESH_CONTENTS(player, player_table, true)
end
end
end)
-- GUI
local function handle_gui_action(msg, e)
local Gui = util.get_gui(e.player_index, msg.gui, msg.id)
if Gui then
Gui:dispatch(msg, e)
end
end
local function read_gui_action(e)
local msg = gui.read_action(e)
if msg then
handle_gui_action(msg, e)
return true
end
return false
end
gui.hook_events(read_gui_action)
script.on_event(defines.events.on_gui_click, function(e)
-- If clicking on the Factory Planner dimmer frame
if not read_gui_action(e) and e.element.style.name == "fp_frame_semitransparent" then
-- Bring all GUIs to the front
local player_table = global.players[e.player_index]
if player_table.flags.can_open_gui then
util.dispatch_all(e.player_index, "info", "bring_to_front")
util.dispatch_all(e.player_index, "quick_ref", "bring_to_front")
--- @type SearchGui?
local SearchGui = util.get_gui(e.player_index, "search")
if SearchGui and SearchGui.refs.window.visible then
SearchGui:bring_to_front()
end
end
end
end)
script.on_event(defines.events.on_gui_closed, function(e)
if not read_gui_action(e) then
local player = game.get_player(e.player_index) --[[@as LuaPlayer]]
local player_table = global.players[e.player_index]
if player_table.flags.technology_gui_open then
player_table.flags.technology_gui_open = false
local gui_data = player_table.guis.search
if not gui_data.state.pinned then
player.opened = gui_data.refs.window
end
elseif player_table.guis.info._relative_id then
--- @type InfoGui?
local InfoGui = util.get_gui(e.player_index, "info", player_table.guis.info._relative_id)
if InfoGui then
InfoGui:dispatch("close")
end
end
end
end)
-- INTERACTION
script.on_event(defines.events.on_lua_shortcut, function(e)
if e.prototype_name == "rb-search" then
local player = game.get_player(e.player_index) --[[@as LuaPlayer]]
local player_table = global.players[e.player_index]
local cursor_stack = player.cursor_stack
if cursor_stack and cursor_stack.valid_for_read then
local data = database.item[cursor_stack.name]
if data then
OPEN_PAGE(player, player_table, { class = "item", name = cursor_stack.name })
else
-- If we're here, the selected object has no page in RB
player.create_local_flying_text({
text = { "message.rb-object-has-no-page" },
create_at_cursor = true,
})
player.play_sound({ path = "utility/cannot_build" })
end
return
end
-- Open search GUI
--- @type SearchGui?
local SearchGui = util.get_gui(e.player_index, "search")
if SearchGui then
SearchGui:toggle()
end
end
end)
local entity_type_to_gui_type = {
["infinity-container"] = defines.relative_gui_type.container_gui,
["linked-container"] = defines.relative_gui_type.container_gui,
["logistic-container"] = defines.relative_gui_type.container_gui,
}
local function get_opened_relative_gui_type(player)
local gui_type = player.opened_gui_type
local opened = player.opened
-- Attempt 1: Some GUIs can be converted straight from their gui_type
local straight_conversion = defines.relative_gui_type[table.find(defines.gui_type, gui_type) .. "_gui"]
if straight_conversion then
return { gui = straight_conversion }
end
-- Attempt 2: Specific logic
if gui_type == defines.gui_type.entity and opened.valid then
local gui = defines.relative_gui_type[string.gsub(opened.type or "", "%-", "_") .. "_gui"]
or entity_type_to_gui_type[opened.type]
if gui then
return { gui = gui, type = opened.type, name = opened.name }
end
end
if gui_type == defines.gui_type.item and opened and opened.valid then -- Sometimes items don't show up!?
if opened.object_name == "LuaEquipmentGrid" then
return { gui = defines.relative_gui_type.equipment_grid_gui }
else
local gui = defines.relative_gui_type[string.gsub(opened.type, "%-", "_") .. "_gui"]
or defines.relative_gui_type.item_with_inventory_gui
if gui then
return { gui = gui, type = opened.type, name = opened.name }
end
end
end
end
script.on_event({ "rb-search", "rb-open-selected" }, function(e)
local player = game.get_player(e.player_index) --[[@as LuaPlayer]]
local player_table = global.players[e.player_index]
if player_table.flags.can_open_gui then
if e.input_name == "rb-open-selected" then
-- Open the selected prototype
local selected_prototype = e.selected_prototype
if selected_prototype then
-- Special case: Don't open selection tools if we're holding them
if
constants.ignored_cursor_inspection_types[selected_prototype.derived_type]
and player.cursor_stack
and player.cursor_stack.valid_for_read
and player.cursor_stack.name == selected_prototype.name
then
return
end
local class = constants.type_to_class[selected_prototype.derived_type]
or constants.type_to_class[selected_prototype.base_type]
-- Not everything will have a Recipe Book entry
if class then
local name = selected_prototype.name
local obj_data = database[class][name]
if obj_data then
local options
if player_table.settings.general.interface.open_info_relative_to_gui then
local id = player_table.guis.info._relative_id
if id then
options = { id = id }
else
-- Get the context of the current opened GUI
local anchor = get_opened_relative_gui_type(player)
if anchor then
anchor.position = defines.relative_gui_position.right
options = { parent = player.gui.relative, anchor = anchor }
end
end
end
local context = { class = class, name = name }
OPEN_PAGE(player, player_table, context, options)
return
end
end
-- If we're here, the selected object has no page in RB
player.create_local_flying_text({
text = { "message.rb-object-has-no-page" },
create_at_cursor = true,
})
player.play_sound({ path = "utility/cannot_build" })
return
end
else
--- @type SearchGui?
local SearchGui = util.get_gui(e.player_index, "search")
if SearchGui then
SearchGui:toggle()
end
end
else
player.print({ "message.rb-cannot-open-gui" })
player_table.flags.show_message_after_translation = true
end
end)
script.on_event(
{ "rb-navigate-backward", "rb-navigate-forward", "rb-return-to-home", "rb-jump-to-front", "rb-linked-focus-search" },
function(e)
local player = game.get_player(e.player_index) --[[@as LuaPlayer]]
local player_table = global.players[e.player_index]
local opened = player.opened
if
player_table.flags.can_open_gui
and player.opened_gui_type == defines.gui_type.custom
and (not opened or (opened.valid and player.opened.name == "rb_search_window"))
then
local active_id = player_table.guis.info._active_id
if active_id then
--- @type InfoGui?
local InfoGui = util.get_gui(e.player_index, "info", active_id)
if InfoGui then
if e.input_name == "rb-linked-focus-search" then
InfoGui:dispatch({ action = "toggle_search" })
else
local event_properties = constants.nav_event_properties[e.input_name]
InfoGui:dispatch(
{ action = "navigate", delta = event_properties.delta },
{ player_index = e.player_index, shift = event_properties.shift }
)
end
end
end
end
end
)
-- PLAYER
script.on_event(defines.events.on_player_created, function(e)
player_data.init(e.player_index)
local player = game.get_player(e.player_index) --[[@as LuaPlayer]]
local player_table = global.players[e.player_index]
player_data.refresh(player, player_table)
formatter.create_cache(e.player_index)
end)
script.on_event(defines.events.on_player_removed, function(e)
player_data.remove(e.player_index)
end)
-- TICK
script.on_event(defines.events.on_tick, function(e)
dictionary.on_tick()
local actions = on_tick_n.retrieve(e.tick)
if actions then
for _, msg in pairs(actions) do
if msg.gui then
handle_gui_action(msg, { player_index = msg.player_index })
elseif msg.action == "dump_database" then
-- game.table_to_json() does not like functions
local output = {}
for key, value in pairs(database) do
if type(value) ~= "function" then
output[key] = value
end
end
local func = msg.raw and serpent.dump or game.table_to_json
game.write_file("rb-dump", func(output), false, msg.player_index)
game.print("[color=green]Dumped database to script-output/rb-dump[/color]")
elseif msg.action == "refresh_all" then
dictionary.on_init()
database.build()
database.check_forces()
for player_index, player in pairs(game.players) do
local player_table = global.players[player_index]
player_data.refresh(player, player_table)
player_table.flags.show_message_after_translation = true
end
game.print("[color=green]Database refresh complete, retranslating dictionaries...[/color]")
end
end
end
end)
-- -----------------------------------------------------------------------------
-- REMOTE INTERFACE
remote.add_interface("RecipeBook", remote_interface)