--[[-- Gui Module - Autofill - Adds a button to enable Autofill @gui Autofill @alias autofill ]] local Game = require 'utils.game' --- @dep utils.game local Gui = require 'expcore.gui' --- @dep expcore.gui local Global = require 'utils.global' --- @dep utils.global local config = require 'config.gui.autofill' --- @dep config.gui.autofill local Event = require 'utils.event' --- @dep utils.event local table = require 'overrides.table' --- @dep overrides.table local print_text = Game.print_floating_text -- (surface, position, text, color) --- Table that stores if autofill is enabled or not local autofill_player_settings = {} Global.register(autofill_player_settings, function(tbl) autofill_player_settings = tbl end) local autofill_container local function rich_img(type, value) return '[img='..type..'/'..value..']' end --- Toggle enitity button, used for toggling autofill for the specific entity -- All entity autofill settings will be ignored if its disabled -- @element toggle_item_button local toggle_section = Gui.element{ type = 'sprite-button', sprite = 'utility/expand_dark', hovered_sprite = 'utility/expand', tooltip = {'autofill.toggle-section-tooltip'} } toggle_section :style(Gui.sprite_style(20)) :on_click(function(_, element, _) local header_flow = element.parent local flow_name = header_flow.caption local flow = header_flow.parent.parent[flow_name] if Gui.toggle_visible_state(flow) then element.sprite = 'utility/collapse_dark' element.hovered_sprite = 'utility/collapse' element.tooltip = {'autofill.toggle-section-collapse-tooltip'} else element.sprite = 'utility/expand_dark' element.hovered_sprite = 'utility/expand' element.tooltip = {'autofill.toggle-section-tooltip'} end end) --- Used to assign an event to the header label to trigger a toggle -- @element header_toggle local header_toggle = Gui.element() :on_click(function(_, element, event) event.element = element.parent.alignment[toggle_section.name] toggle_section:raise_custom_event(event) end) --- Used to assign an event to the header label to trigger a toggle -- @element header_toggle local entity_toggle = Gui.element(function(event_trigger, parent, entity_name) return parent.add{ name = event_trigger, type = 'sprite-button', sprite = 'utility/confirm_slot', tooltip = {'autofill.toggle-entity-tooltip', rich_img('item', entity_name)}, style = 'shortcut_bar_button_green' } end) :style(Gui.sprite_style(22)) :on_click(function(player, element, _) local entity_name = string.sub(element.parent.parent.name, 0, -(1 + string.len('-header'))) if not autofill_player_settings[player.name] then return end local setting = autofill_player_settings[player.name][entity_name] if not setting then return end if setting.enabled then setting.enabled = false element.sprite = 'utility/close_black' element.style = 'shortcut_bar_button_red' else setting.enabled = true element.sprite = 'utility/confirm_slot' element.style = 'shortcut_bar_button_green' end end) --- Draw a section header and main scroll -- @element autofill_section_container local section = Gui.element(function(_, parent, section_name, table_size) -- Draw the header for the section local header = Gui.header( parent, {'autofill.toggle-section-caption', rich_img('item', section_name), {'entity-name.'..section_name}}, {'autofill.toggle-section-tooltip'}, true, section_name..'-header', header_toggle.name ) -- Right aligned button to toggle the section header.caption = section_name entity_toggle(header, section_name) toggle_section(header) local section_table = parent.add{ type = 'table', name = section_name, column_count = table_size } section_table.visible = false return section_table end) --- Toggle item button, used for toggling autofill for the specific item -- @element toggle_item_button local toggle_item_button = Gui.element(function(event_trigger, parent, item) return parent.add{ name = event_trigger, type = 'sprite-button', sprite = 'item/'..item.name, tooltip = {'autofill.toggle-tooltip', rich_img('item', item.name), item.category}, style = 'shortcut_bar_button_red' } end) :style(Gui.sprite_style(32, nil, { right_margin = -3 })) :on_click(function(player, element) local item_name = string.sub(element.parent.name, 1 + string.len('toggle-setting-'), -1) local entity_name = element.parent.parent.parent.parent.name if not autofill_player_settings[player.name] then return end local setting = autofill_player_settings[player.name][entity_name] if not setting then return end local item = setting.items[item_name] if not item then return end if item.enabled then item.enabled = false element.style = 'shortcut_bar_button_red' else item.enabled = true element.style = 'shortcut_bar_button_green' end end) --- Amount text field for a autofill item -- @element amount_textfield local amount_textfield = Gui.element(function(event_trigger, parent, item) return parent.add{ name = event_trigger, type = 'textfield', text = item.amount, tooltip = {'autofill.amount-tooltip', item.category, rich_img('item', item.entity) }, clear_and_focus_on_right_click = true, numeric = true, allow_decimal = false, allow_negative = false } end) :style{ maximal_width = 40, height = 31, padding = -2 } :on_text_changed(function(player, element, _) local value = tonumber(element.text) if not value then value = 0 end local clamped = math.clamp(value, 0, 1000) local item_name = string.sub(element.parent.name, 1 + string.len('toggle-setting-'), -1) local entity_name = element.parent.parent.parent.parent.name if not autofill_player_settings[player.name] then return end local setting = autofill_player_settings[player.name][entity_name] if not setting then return end local item = setting.items[item_name] if not item then return end item.amount = clamped if clamped ~= value then element.text = clamped player.print({'autofill.invalid', item.amount, rich_img('item', item.name), rich_img('entity', entity_name) }) return end end) --- Autofill setting, contains a button and a textbox -- @element add_autofill_setting local add_autofill_setting = Gui.element(function(_, parent, item) local toggle_flow = parent.add{ type = 'flow', name = 'toggle-setting-'..item.name } local amount_flow = parent.add{ type = 'flow', name = 'amount-setting-'..item.name } toggle_flow.style.padding = 0 amount_flow.style.padding = 0 toggle_item_button(toggle_flow, item) amount_textfield(amount_flow, item) end) --- Toggle item button empty, just a filler gui element -- @element toggle_item_button_empty local toggle_item_button_empty = Gui.element(function(_, parent, i) return parent.add{ name = 'toggle-setting-empty-frame-'..i, type = 'sprite-button' } end) :style(Gui.sprite_style(32, nil, { right_margin = -3 })) --- Amount text field empty, just a filler gui element -- @element amount_textfield_empty local amount_textfield_empty = Gui.element(function(_, parent, i) return parent.add{ name = 'amount-setting-empty-frame-'..i, type = 'textfield' } end) :style{ maximal_width = 40, height = 31, padding = -2 } --- Autofill setting empty, contains filler button and textfield gui elements -- @element add_empty_autofill_setting local add_empty_autofill_setting = Gui.element(function(_, parent, i) local toggle_flow = parent.add{ type = 'flow', name = 'toggle-setting-empty-'..i } local amount_flow = parent.add{ type = 'flow', name = 'amount-setting-empty-'..i } toggle_flow.style.padding = 0 amount_flow.style.padding = 0 local toggle_element = toggle_item_button_empty(toggle_flow, i) toggle_element.enabled = false local amount_element = amount_textfield_empty(amount_flow, i) amount_element.enabled = false end) --- Main gui container for the left flow -- @element autofill_container autofill_container = Gui.element(function(event_trigger, parent) -- Draw the internal container local container = Gui.container(parent, event_trigger) container.parent.style.minimal_width = 257 -- Draw the scroll container local scroll_table = Gui.scroll_table(container, 400, 1, 'autofill-scroll-table') scroll_table.style.vertical_spacing = 0 scroll_table.parent.vertical_scroll_policy = 'always' -- Loop over each default entity config for _, setting in pairs(config.default_entities) do local table_sizes = {} local tables = {} -- Draw a section for the element local entity_table = section(scroll_table, setting.entity, 3) -- Loop over each item category for _, category in pairs(config.categories) do if not table_sizes[category] then table_sizes[category] = 0 end -- Draw a alignment gui to make sure the gui is floating to the top of the parent local alignment = Gui.alignment(entity_table, category..'-'..setting.entity, 'center', 'top') alignment.style.padding = 0 -- Draw table local category_table = alignment.add{ type = 'table', name = 'category-table', column_count = 2 } category_table.style.vertical_spacing = 1 category_table.parent.style.padding = 0 tables[category] = category_table -- Add item autofill setting gui elements to the table for _, item in pairs(setting.items) do if item.category == category then add_autofill_setting(category_table, item) table_sizes[category] = table_sizes[category] + 1 end end end -- Add empty gui elements for the categories with less items than the other categories local t = table.get_values(table_sizes) table.sort(t) local biggest = t[#t] for category, size in pairs(table_sizes) do for i=biggest-size,1,-1 do add_empty_autofill_setting(tables[category], i) end end end -- Return the external container return container.parent end) :add_to_left_flow() --- Button on the top flow used to toggle autofill container -- @element autofill_toggle Gui.left_toolbar_button(config.icon, {'autofill.main-tooltip'}, autofill_container) --- When a player is created make sure they have the default autofill settings Event.add(defines.events.on_player_created, function(event) local player = game.players[event.player_index] if not autofill_player_settings[player.name] then autofill_player_settings[player.name] = table.deep_copy(config.default_entities) end end) local function entity_build(event) -- Check if player exists local player = game.players[event.player_index] if not player then return end -- Check if the entity is in the config and enabled local entity = event.created_entity -- Check if player has settings if not autofill_player_settings[player.name] then return end local entity_settings = autofill_player_settings[player.name][entity.name] -- Check if autofill for the entity is enabled if not entity_settings then return end if not entity_settings.enabled then return end -- Get the inventory of the player local player_inventory = player.get_main_inventory() local text_position = { x = entity.position.x, y = entity.position.y } -- Loop over all possible items to insert into the entity for _, item in pairs(entity_settings.items) do -- Check if the item is enabled or goto next item if not item.enabled then goto end_item end -- Get the inventory of the entity or goto next item local entity_inventory = entity.get_inventory(item.type) if not entity_inventory then goto end_item end local preferd_amount = item.amount local item_amount = player_inventory.get_item_count(item.name) if item_amount ~= 0 then local inserted text_position.y = text_position.y - 0.2 if item_amount >= preferd_amount then -- Can item be inserted? no, goto next item! if not entity_inventory.can_insert({name=item.name, count=preferd_amount}) then goto end_item end inserted = entity_inventory.insert({name=item.name, count=preferd_amount}) player_inventory.remove({name=item.name, count=inserted}) print_text(entity.surface, text_position, {'autofill.filled', rich_img('entity', entity.name), inserted, rich_img('item', item.name) }, { r = 0, g = 255, b = 0, a = 1}) else inserted = entity_inventory.insert({name=item.name, count=item_amount}) player_inventory.remove({name=item.name, count=inserted}) print_text(entity.surface, text_position, {'autofill.filled', rich_img('entity', entity.name), inserted, rich_img('item', item.name) }, { r = 255, g = 165, b = 0, a = 1}) end end ::end_item:: end end Event.add(defines.events.on_built_entity, entity_build)