Gui = {} ---Toggles mod GUI on or off ---@param player_index uint Player index ---@param state boolean true -> on, false -> off function Gui.toggle(player_index, state) local playerdata = get_make_playerdata(player_index) state = state or not playerdata.is_active if state then playerdata.is_active = true -- Create mod gui and register event handler Gui.make_gui(player_index) register_inventory_monitoring(true) register_logistics_monitoring(true) else playerdata.is_active = false playerdata.job = { area={}, ghosts={}, requests={}, requests_sorted={} } -- Destroy mod GUI and remove references to it if playerdata.gui.root and playerdata.gui.root.valid then local last_location = playerdata.gui.root.location --[[@as GuiLocation.0]] playerdata.gui.root.destroy() playerdata.gui = {last_location=last_location} end -- Unbind event hooks if no no longer needed if not is_inventory_monitoring_needed() then register_inventory_monitoring(false) end if not is_logistics_monitoring_needed() then register_logistics_monitoring(false) end end end ---Make mod GUI ---@param player_index uint Player index function Gui.make_gui(player_index) local playerdata = get_make_playerdata(player_index) local screen = playerdata.luaplayer.gui.screen local window_loc_x, window_loc_y -- Restore previous saved location, if any if playerdata.gui.last_location then local location = playerdata.gui.last_location local resolution = playerdata.luaplayer.display_resolution window_loc_x = location.x < resolution.width and location.x or nil window_loc_y = location.y < resolution.height and location.y or nil end -- Destory existing mod GUI if one exists if screen[NAME.gui.root_frame] then local location = screen[NAME.gui.root_frame].location window_loc_x, window_loc_y = location.x, location.y screen[NAME.gui.root_frame].destroy() end playerdata.gui.root = screen.add{ type="frame", name=NAME.gui.root_frame, direction="vertical", style=NAME.style.root_frame } do local resolution = playerdata.luaplayer.display_resolution local x = window_loc_x or 50 local y = window_loc_y or (resolution.height / 2) - 300 playerdata.gui.root.location = {x, y} end -- Create title bar local titlebar_flow = playerdata.gui.root.add{ type="flow", direction="horizontal", style=NAME.style.titlebar_flow } titlebar_flow.drag_target = playerdata.gui.root titlebar_flow.add{ type="label", caption="Ghost Counter", ignored_by_interaction=true, style="frame_title" } titlebar_flow.add{ type="empty-widget", ignored_by_interaction=true, style=NAME.style.titlebar_space_header } local hide_empty = playerdata.options.hide_empty_requests titlebar_flow.add{ type="sprite-button", name=NAME.gui.hide_empty_button, tooltip={"ghost-counter-gui.hide-empty-requests-tooltip"}, sprite=hide_empty and NAME.sprite.hide_empty_black or NAME.sprite.hide_empty_white, hovered_sprite=NAME.sprite.hide_empty_black, clicked_sprite=hide_empty and NAME.sprite.hide_empty_white or NAME.sprite.hide_empty_black, style=hide_empty and NAME.style.titlebar_button_active or NAME.style.titlebar_button } titlebar_flow.add{ type="sprite-button", name=NAME.gui.close_button, sprite="utility/close_white", hovered_sprite="utility/close_black", clicked_sprite="utility/close_black", tooltip={"ghost-counter-gui.close-button-tooltip"}, style="close_button" } local deep_frame = playerdata.gui.root.add{ type="frame", direction="vertical", style=NAME.style.inside_deep_frame } local toolbar = deep_frame.add{ type="frame", direction="horizontal", style=NAME.style.topbar_frame } toolbar.add{ type="sprite-button", name=NAME.gui.get_signals_button, sprite=NAME.sprite.get_signals_white, hovered_sprite=NAME.sprite.get_signals_black, clicked_sprite=NAME.sprite.get_signals_black, tooltip={"ghost-counter-gui.get-signals-tooltip"}, style=NAME.style.get_signals_button } toolbar.add{ type="empty-widget", style=NAME.style.topbar_space } toolbar.add{ type="sprite-button", name=NAME.gui.craft_all_button, sprite=NAME.sprite.craft_all_white, hovered_sprite=NAME.sprite.craft_all_black, clicked_sprite=NAME.sprite.craft_all_black, tooltip={"ghost-counter-gui.craft-all-tooltip"}, style=NAME.style.get_signals_button } toolbar.add{ type="button", name=NAME.gui.request_all_button, caption={"ghost-counter-gui.request-all-caption"}, tooltip={"ghost-counter-gui.request-all-tooltip"}, style=NAME.style.ghost_request_all_button } toolbar.add{ type="sprite-button", name=NAME.gui.cancel_all_button, sprite=NAME.sprite.cancel_white, hovered_sprite=NAME.sprite.cancel_black, clicked_sprite=NAME.sprite.cancel_black, tooltip={"ghost-counter-gui.cancel-all-tooltip"}, style=NAME.style.ghost_cancel_all_button } playerdata.gui.requests_container = deep_frame.add{ type="scroll-pane", name=NAME.gui.scroll_pane, style=NAME.style.scroll_pane } Gui.make_list(player_index) end ---Creates the list of request frames in the GUI ---@param player_index uint Player index function Gui.make_list(player_index) local playerdata = get_make_playerdata(player_index) -- Create a new row frame for each request playerdata.gui.requests = {} for _, request in pairs(playerdata.job.requests_sorted) do Gui.make_row(player_index, request) end end ---Returns request button properties based on request fulfillment and other criteria ---@param request table `request` table ---@param one_time_request table `playerdata.logistic_requests[request.name]` ---@return boolean enabled Whehter button should be enabled ---@return string style Style that should be applied to the button ---@return LocalisedString tooltip Tooltip shown for button function make_request_button_properties(request, one_time_request) local logistic_request = request.logistic_request or {} local enabled = ((logistic_request.min or 0) < request.count) or one_time_request and true or false local style = ((logistic_request.min or 0) < request.count) and NAME.style.ghost_request_button or NAME.style.ghost_request_active_button local str = "[item=" .. request.name .. "] " local tooltip if enabled then tooltip = ((logistic_request.min or 0) < request.count) and {"ghost-counter-gui.set-temporary-request-tooltip", request.count, str} or {"ghost-counter-gui.unset-temporary-request-tooltip"} else tooltip = {"ghost-counter-gui.existing-logistic-request-tooltip"} end return enabled, style, tooltip end ---Updates the list of request frames in the GUI ---@param player_index uint Player index function Gui.update_list(player_index) local playerdata = get_make_playerdata(player_index) if not playerdata.is_active or not playerdata.gui.requests then return end local indices = {count=1, sprite=2, label=3, inventory=4, request=5} -- Update gui elements with new values for name, frame in pairs(playerdata.gui.requests) do local request = playerdata.job.requests[name] if request.count > 0 or not playerdata.options.hide_empty_requests then frame.visible = true -- Update ghost count frame.children[indices.count].caption = request.count -- Update amont in inventory frame.children[indices.inventory].caption = request.inventory -- Calculate amount missing local diff = request.count - request.inventory -- If amount needed exceeds amount in inventory, show request button local request_element = frame.children[indices.request] if diff > 0 then local enabled, style, tooltip = make_request_button_properties(request, playerdata.logistic_requests[request.name]) if request_element.type == "button" then request_element.enabled = enabled request_element.style = style request_element.caption = diff request_element.tooltip = tooltip else frame.children[indices.request].destroy() frame.add{ type="button", caption=diff, enabled=enabled, style=style, tooltip=tooltip, tags={ghost_counter_request=request.name} } end -- Otherwise create request-fulfilled checkmark previous element was a request button elseif request_element.type == "button" then request_element.destroy() local sprite_container = frame.add{ type="flow", direction="horizontal", style=NAME.style.ghost_request_fulfilled_flow } sprite_container.add{ type="sprite", sprite="utility/check_mark_white", resize_to_sprite=false, style=NAME.style.ghost_request_fulfilled_sprite } end else frame.visible = false end end end ---Generates the row frame for a given request table ---@param player_index uint Player index ---@param request table `request` table, containing name, count, inventory, etc. function Gui.make_row(player_index, request) local playerdata = get_make_playerdata(player_index) local parent = playerdata.gui.requests_container local localized_name = game.item_prototypes[request.name].localised_name -- Row frame local frame = parent.add{type="frame", direction="horizontal", style=NAME.style.row_frame} playerdata.gui.requests[request.name] = frame -- Ghost (item) count frame.add{type="label", caption=request.count, style=NAME.style.ghost_number_label} -- Item sprite frame.add{ type="sprite", sprite="item/" .. request.name, resize_to_sprite=false, style=NAME.style.ghost_sprite } -- Item or tile localized name frame.add{type="label", caption=localized_name, style=NAME.style.ghost_name_label} -- Amount in inventory frame.add{type="label", caption=request.inventory, style=NAME.style.inventory_number_label} -- Calculate amount missing local diff = request.count - request.inventory -- Show one-time request logistic button if diff > 0 then local enabled, style, tooltip = make_request_button_properties(request, playerdata.logistic_requests[request.name]) frame.add{ type="button", caption=diff, enabled=enabled, style=style, tooltip=tooltip, tags={ghost_counter_request=request.name} } else -- Show request fulfilled sprite local sprite_container = frame.add{ type="flow", direction="horizontal", style=NAME.style.ghost_request_fulfilled_flow } sprite_container.add{ type="sprite", sprite="utility/check_mark_white", resize_to_sprite=false, style=NAME.style.ghost_request_fulfilled_sprite } end -- Hide frame if ghost count is 0 and player toggled hide empty requests frame.visible = request.count > 0 or not playerdata.options.hide_empty_requests and true or false end ---Event handler for GUI button clicks ---@param event EventData.on_gui_click Event table function Gui.on_gui_click(event) local player_index = event.player_index local element = event.element local element_name = element.name if element.name == NAME.gui.close_button then -- Close button Gui.toggle(player_index, false) elseif element.tags and element.tags.ghost_counter_request then -- One-time logistic request/craft button local playerdata = get_make_playerdata(player_index) local request_name = element.tags.ghost_counter_request --[[@as string]] if event.shift == true then local request = playerdata.job.requests[request_name] if request then local result, crafted = craft_request(event.player_index, request) local player = game.get_player(player_index) --[[@as LuaPlayer]] if result == "no-crafts-needed" then player.create_local_flying_text{ text={"ghost-counter-message.crafts-not-needed"}, create_at_cursor=true } elseif result == "attempted" and crafted == 0 then player.create_local_flying_text{ text={"ghost-counter-message.crafts-attempted-none"}, create_at_cursor=true } end end else if not playerdata.logistic_requests[request_name] then make_one_time_logistic_request(player_index, request_name) Gui.update_list(player_index) else restore_prior_logistic_request(player_index, request_name) Gui.update_list(player_index) end end elseif element_name == NAME.gui.hide_empty_button then local playerdata = get_make_playerdata(player_index) local new_state = not playerdata.options.hide_empty_requests playerdata.options.hide_empty_requests = new_state element.style = new_state and NAME.style.titlebar_button_active or NAME.style.titlebar_button element.sprite = new_state and NAME.sprite.hide_empty_black or NAME.sprite.hide_empty_white element.clicked_sprite = new_state and NAME.sprite.hide_empty_white or NAME.sprite.hide_empty_black Gui.update_list(player_index) elseif element_name == NAME.gui.get_signals_button then make_combinators_blueprint(event.player_index) elseif element_name == NAME.gui.craft_all_button then local playerdata = get_make_playerdata(player_index) for _, request in pairs(playerdata.job.requests) do craft_request(player_index, request) end elseif element_name == NAME.gui.request_all_button then local playerdata = get_make_playerdata(player_index) for _, request in pairs(playerdata.job.requests) do if request.count > 0 and not playerdata.logistic_requests[request.name] then make_one_time_logistic_request(player_index, request.name) end end Gui.update_list(player_index) elseif element_name == NAME.gui.cancel_all_button then cancel_all_one_time_requests(player_index) Gui.update_list(player_index) end end script.on_event(defines.events.on_gui_click, Gui.on_gui_click)