mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-11-12 15:54:52 +09:00
Review points from Cooldude: - Feature.register(name[, config]) is required again and returns a feature object. Each feature has its config, its fields, and its overrides, so one registry holds all the data. Only the overrides are stored. On load each feature points at its own stored table again. - The methods are feature:guard(lib), feature:update_config(enabled, values), and feature:is_enabled(). Feature.get(name) looks up a feature, and commands flag the feature object. - update_config raises on_config_updated once per changed value, with the feature name, path, old value, and new value. The gui refresh only reacts to the "enabled" path. - apply only sets values. on_apply is gone. Lookup sets built from lists use Feature.to_set, which is cached by the list itself. A replaced list gets a new set, so the sets are also correct after a load. Protection exposes getters for its protected names and types. Web: settings use small inputs, the same size as the config pages. Fields are shaped like clusterio config field definitions. A field with an inputComponent renders the component registered with the web interface, so the role setting now uses core's role input, and other plugin inputs can be used later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
408 lines
15 KiB
Lua
408 lines
15 KiB
Lua
--[[-- Gui - Autofill
|
|
Adds a config menu for setting autofill of placed entities
|
|
]]
|
|
|
|
local Feature = require("modules/exp_scenario/features")
|
|
local Gui = require("modules/exp_gui")
|
|
local Roles = require("modules/exp_roles")
|
|
local config = require("modules.exp_legacy.config.gui.autofill")
|
|
|
|
local feature = Feature.register("autofill")
|
|
local FlyingText = require("modules/exp_util/flying_text")
|
|
|
|
local min = math.min
|
|
local string_format = string.format
|
|
|
|
--- @class ExpGui_Autofill.elements
|
|
local Elements = {}
|
|
|
|
--- Format a type and name to a rich text image
|
|
--- @param type string
|
|
--- @param name string
|
|
--- @return string
|
|
local function rich_img(type, name)
|
|
return string_format("[img=%s/%s]", type, name)
|
|
end
|
|
|
|
--- Toggle the visible state of a section
|
|
--- @class ExpGui_Autofill.elements.toggle_section_button: ExpElement
|
|
--- @field data table<LuaGuiElement, LuaGuiElement>
|
|
--- @overload fun(parent: LuaGuiElement, section: LuaGuiElement): LuaGuiElement
|
|
Elements.toggle_section_button = Gui.define("autofill/toggle_section_button")
|
|
:draw{
|
|
type = "sprite-button",
|
|
sprite = "utility/expand",
|
|
tooltip = { "exp-gui_autofill.tooltip-toggle-section-expand" },
|
|
style = "frame_action_button",
|
|
}
|
|
:style{
|
|
size = 20,
|
|
padding = -2,
|
|
}
|
|
:element_data(
|
|
Gui.from_argument(1)
|
|
)
|
|
:on_click(function(def, player, element)
|
|
--- @cast def ExpGui_Autofill.elements.toggle_section_button
|
|
local section = def.data[element]
|
|
if Gui.toggle_visible_state(section) then
|
|
element.sprite = "utility/collapse"
|
|
element.tooltip = { "exp-gui_autofill.tooltip-toggle-section-collapse" }
|
|
else
|
|
element.sprite = "utility/expand"
|
|
element.tooltip = { "exp-gui_autofill.tooltip-toggle-section-expand" }
|
|
end
|
|
end) --[[@as any]]
|
|
|
|
--- Toggle if an entity will be autofilled when played
|
|
--- @class ExpGui_Autofill.elements.toggle_entity_button: ExpElement
|
|
--- @field data table<LuaGuiElement, ExpGui_Autofill.entity_settings>
|
|
--- @overload fun(parent: LuaGuiElement, entity_settings: ExpGui_Autofill.entity_settings): LuaGuiElement
|
|
Elements.toggle_entity_button = Gui.define("autofill/toggle_entity_button")
|
|
:draw(function(_, parent, entity_settings)
|
|
--- @cast entity_settings ExpGui_Autofill.entity_settings
|
|
local enabled = entity_settings.enabled
|
|
return parent.add{
|
|
type = "sprite-button",
|
|
tooltip = { "exp-gui_autofill.tooltip-toggle-entity", rich_img("item", entity_settings.entity) },
|
|
sprite = enabled and "utility/confirm_slot" or "utility/close_black",
|
|
style = enabled and "shortcut_bar_button_green" or "shortcut_bar_button_red",
|
|
}
|
|
end)
|
|
:style{
|
|
size = 22,
|
|
padding = -2,
|
|
}
|
|
:element_data(
|
|
Gui.from_argument(1)
|
|
)
|
|
:on_click(function(def, player, element)
|
|
--- @cast def ExpGui_Autofill.elements.toggle_entity_button
|
|
local entity_settings = def.data[element]
|
|
local enabled = not entity_settings.enabled
|
|
entity_settings.enabled = enabled
|
|
|
|
-- Update the sprite and style
|
|
element.sprite = enabled and "utility/confirm_slot" or "utility/close_black"
|
|
element.style = enabled and "shortcut_bar_button_green" or "shortcut_bar_button_red"
|
|
|
|
-- Correct the button size
|
|
local style = element.style
|
|
style.padding = 0
|
|
style.height = 22
|
|
style.width = 22
|
|
end) --[[@as any]]
|
|
|
|
--- Toggle if an item will be inserted into an entity
|
|
--- @class ExpGui_Autofill.elements.toggle_item_button: ExpElement
|
|
--- @field data table<LuaGuiElement, ExpGui_Autofill.item_settings>
|
|
--- @overload fun(parent: LuaGuiElement, item_settings: ExpGui_Autofill.item_settings): LuaGuiElement
|
|
Elements.toggle_item_button = Gui.define("autofill/toggle_item_button")
|
|
:draw(function(_, parent, item_settings)
|
|
--- @cast item_settings ExpGui_Autofill.item_settings
|
|
return parent.add{
|
|
type = "sprite-button",
|
|
sprite = "item/" .. item_settings.name,
|
|
tooltip = { "exp-gui_autofill.tooltip-toggle-item", rich_img("item", item_settings.name), item_settings.category },
|
|
style = item_settings.enabled and "shortcut_bar_button_green" or "shortcut_bar_button_red",
|
|
}
|
|
end)
|
|
:style{
|
|
size = 32,
|
|
right_margin = -3,
|
|
padding = -1,
|
|
}
|
|
:element_data(
|
|
Gui.from_argument(1)
|
|
)
|
|
:on_click(function(def, player, element)
|
|
--- @cast def ExpGui_Autofill.elements.toggle_item_button
|
|
local item_settings = def.data[element]
|
|
local enabled = not item_settings.enabled
|
|
item_settings.enabled = enabled
|
|
|
|
-- Update the style
|
|
element.style = enabled and "shortcut_bar_button_green" or "shortcut_bar_button_red"
|
|
|
|
-- Correct the button size
|
|
local style = element.style
|
|
style.right_margin = -3
|
|
style.padding = -2
|
|
style.height = 32
|
|
style.width = 32
|
|
end) --[[@as any]]
|
|
|
|
--- The amount of an item to insert
|
|
--- @class ExpGui_Autofill.elements.amount_textfield: ExpElement
|
|
--- @field data table<LuaGuiElement, ExpGui_Autofill.item_settings>
|
|
--- @overload fun(parent: LuaGuiElement, item_settings: ExpGui_Autofill.item_settings): LuaGuiElement
|
|
Elements.amount_textfield = Gui.define("autofill/amount_textfield")
|
|
:draw(function(_, parent, item_settings)
|
|
--- @cast item_settings ExpGui_Autofill.item_settings
|
|
return parent.add{
|
|
type = "textfield",
|
|
tooltip = { "exp-gui_autofill.tooltip-amount", item_settings.category },
|
|
text = tostring(item_settings.amount) or "",
|
|
clear_and_focus_on_right_click = true,
|
|
numeric = true,
|
|
allow_decimal = false,
|
|
allow_negative = false,
|
|
}
|
|
end)
|
|
:style{
|
|
horizontally_stretchable = true,
|
|
minimal_width = 40,
|
|
height = 31,
|
|
padding = -2,
|
|
}
|
|
:element_data(
|
|
Gui.from_argument(1)
|
|
)
|
|
:on_text_changed(function(def, player, element, event)
|
|
--- @cast def ExpGui_Autofill.elements.amount_textfield
|
|
local value = tonumber(element.text) or 0
|
|
local clamped = math.clamp(value, 0, 999)
|
|
local item_settings = def.data[element]
|
|
item_settings.amount = clamped
|
|
if clamped ~= value then
|
|
element.text = tostring(clamped)
|
|
player.print{ "exp-gui_autofill.invalid", clamped, rich_img("item", item_settings.name), rich_img("entity", item_settings.entity) }
|
|
end
|
|
end) --[[@as any]]
|
|
|
|
--- A disabled version of the autofill settings used as a filler
|
|
Elements.disabled_autofill_setting = Gui.define("autofill/empty_autofill_setting")
|
|
:draw(function(_, parent)
|
|
local toggle_element_style = parent.add{
|
|
type = "sprite-button",
|
|
enabled = false,
|
|
}.style
|
|
toggle_element_style.right_margin = -3
|
|
toggle_element_style.width = 32
|
|
toggle_element_style.height = 32
|
|
|
|
local amount_element_style = parent.add{
|
|
type = "textfield",
|
|
enabled = false,
|
|
}.style
|
|
amount_element_style.horizontally_stretchable = true
|
|
amount_element_style.minimal_width = 40
|
|
amount_element_style.height = 31
|
|
amount_element_style.padding = -2
|
|
|
|
return Gui.no_return()
|
|
end)
|
|
|
|
--- Section representing an entity
|
|
--- @class ExpGui_Autofill.elements.section: ExpElement
|
|
--- @field data table<LuaGuiElement, LuaGuiElement|ExpGui_Autofill.entity_settings>
|
|
--- @overload fun(parent: LuaGuiElement, entity_settings: ExpGui_Autofill.entity_settings): LuaGuiElement
|
|
Elements.section = Gui.define("autofill/section")
|
|
:draw(function(def, parent, entity_settings)
|
|
--- @cast def ExpGui_Autofill.elements.section
|
|
--- @cast entity_settings ExpGui_Autofill.entity_settings
|
|
local header = Gui.elements.header(parent, {
|
|
caption = { "exp-gui_autofill.caption-section-header", rich_img("item", entity_settings.entity), { "entity-name." .. entity_settings.entity } },
|
|
tooltip = { "exp-gui_autofill.tooltip-toggle-section" },
|
|
})
|
|
|
|
local section_table = parent.add{
|
|
type = "table",
|
|
column_count = 3,
|
|
visible = false,
|
|
}
|
|
|
|
section_table.style.padding = 3
|
|
|
|
local header_label = header.label
|
|
Elements.toggle_entity_button(header, entity_settings)
|
|
def.data[header_label] = Elements.toggle_section_button(header, section_table)
|
|
def.data[section_table] = entity_settings
|
|
|
|
def:link_element(header_label)
|
|
return def:unlink_element(section_table)
|
|
end)
|
|
:on_click(function(def, player, element, event)
|
|
--- @cast def ExpGui_Autofill.elements.section
|
|
event.element = def.data[element] --[[@as LuaGuiElement]]
|
|
Elements.toggle_section_button:raise_event(event)
|
|
end) --[[@as any]]
|
|
|
|
--- Add an item category to a section, at most three can exist
|
|
--- @param section LuaGuiElement
|
|
--- @param category_name string
|
|
--- @return LuaGuiElement, number
|
|
function Elements.section.add_category(section, category_name)
|
|
local category = section.add{
|
|
type = "table",
|
|
column_count = 2,
|
|
}
|
|
|
|
category.style.vertical_spacing = 1
|
|
|
|
local ctn = 0
|
|
local entity_settings = Elements.section.data[section] --[[@as ExpGui_Autofill.entity_settings]]
|
|
for _, item_data in pairs(entity_settings.items) do
|
|
if item_data.category == category_name then
|
|
Elements.toggle_item_button(category, item_data)
|
|
Elements.amount_textfield(category, item_data)
|
|
ctn = ctn + 1
|
|
end
|
|
end
|
|
|
|
return category, ctn
|
|
end
|
|
|
|
--- @class ExpGui_Autofill.item_settings
|
|
--- @field entity string
|
|
--- @field category string
|
|
--- @field inv defines.inventory
|
|
--- @field name string
|
|
--- @field amount number
|
|
--- @field enabled boolean
|
|
|
|
--- @class ExpGui_Autofill.entity_settings
|
|
--- @field entity string
|
|
--- @field enabled boolean
|
|
--- @field items ExpGui_Autofill.item_settings[]
|
|
|
|
--- Container added to the left gui flow
|
|
--- @class ExpGui_Autofill.elements.container: ExpElement
|
|
--- @field data table<LuaPlayer, table<string, ExpGui_Autofill.entity_settings>>
|
|
Elements.container = Gui.define("autofill/container")
|
|
:draw(function(def, parent)
|
|
--- @cast def ExpGui_Autofill.elements.container
|
|
local container = Gui.elements.container(parent)
|
|
local scroll_pane = Gui.elements.scroll_pane(container, 524)
|
|
scroll_pane.style.padding = 0
|
|
|
|
-- Cant modify vertical spacing on scroll pane style so need a sub flow
|
|
scroll_pane = scroll_pane.add{ type = "flow", direction = "vertical" }
|
|
scroll_pane.style.vertical_spacing = 0
|
|
scroll_pane.style.padding = 0
|
|
|
|
-- Add a header
|
|
Gui.elements.header(scroll_pane, {
|
|
caption = { "exp-gui_autofill.caption-main" },
|
|
})
|
|
|
|
-- Setup the player data, this is used by section and item category so needs to be done here
|
|
local player = Gui.get_player(parent)
|
|
--- @type table<string, ExpGui_Autofill.entity_settings>
|
|
local player_data = def.data[player] or table.deep_copy(config.default_entities)
|
|
def.data[player] = player_data
|
|
|
|
-- Add sections for each entity
|
|
for _, entity_settings in pairs(player_data) do
|
|
local section = Elements.section(scroll_pane, entity_settings)
|
|
|
|
-- Add the categories
|
|
local categories, largest = {}, 0
|
|
for _, category_name in pairs(config.categories) do
|
|
local category, size = Elements.section.add_category(section, category_name)
|
|
if largest < size then
|
|
largest = size
|
|
end
|
|
categories[category] = size
|
|
end
|
|
|
|
-- Fill in blanks for smaller categories
|
|
for category, size in pairs(categories) do
|
|
for i = size, largest - 1 do
|
|
Elements.disabled_autofill_setting(category)
|
|
end
|
|
end
|
|
end
|
|
|
|
return container.parent
|
|
end) --[[@as any]]
|
|
|
|
--- Get the autofill settings for a player
|
|
--- @param player LuaPlayer
|
|
--- @param entity_name string
|
|
--- @return ExpGui_Autofill.entity_settings
|
|
function Elements.container.get_autofill_settings(player, entity_name)
|
|
return Elements.container.data[player][entity_name]
|
|
end
|
|
|
|
--- Add the element to the left flow with a toolbar button
|
|
Gui.add_left_element(Elements.container, false)
|
|
Gui.toolbar.create_button{
|
|
name = "toggle_autofill",
|
|
left_element = Elements.container,
|
|
sprite = config.icon,
|
|
tooltip = { "exp-gui_autofill.tooltip-main" },
|
|
visible = function(player, element)
|
|
return feature:is_enabled() and Roles.player_has_permission(player, "exp_scenario.gui.autofill")
|
|
end
|
|
}
|
|
|
|
--- @param event EventData.on_built_entity
|
|
local function on_built_entity(event)
|
|
local player = Gui.get_player(event)
|
|
|
|
-- Check if the entity is in the config and enabled
|
|
local entity = event.entity
|
|
local entity_settings = Elements.container.get_autofill_settings(player, entity.name)
|
|
if not entity_settings or not entity_settings.enabled then
|
|
return
|
|
end
|
|
|
|
-- Get the inventory of the player
|
|
local player_inventory = player.get_main_inventory() --- @cast player_inventory -nil
|
|
local player_get_item_count = player_inventory.get_item_count
|
|
local player_remove = player_inventory.remove
|
|
|
|
-- Setup the tables being used
|
|
local offset = { x = 0, y = 0 }
|
|
local item = { name = "", count = 0 }
|
|
local color = { r = 0, g = 255, b = 0, a = 255 }
|
|
local flyingText = {
|
|
target_entity = entity,
|
|
text = "",
|
|
offset = offset,
|
|
player = player,
|
|
color = color,
|
|
}
|
|
|
|
for _, item_settings in pairs(entity_settings.items) do
|
|
-- Check if the item is enabled or goto next item
|
|
if not item_settings.enabled then goto continue end
|
|
|
|
-- Get the inventory of the entity or goto next item
|
|
local entity_inventory = entity.get_inventory(item_settings.inv)
|
|
if not entity_inventory then goto continue end
|
|
|
|
local preferred_amount = item_settings.amount
|
|
local item_amount = player_get_item_count(item_settings.name)
|
|
if item_amount ~= 0 then
|
|
item.name = item_settings.name
|
|
item.count = min(preferred_amount, item_amount)
|
|
if not entity_inventory.can_insert(item) then goto continue end
|
|
local inserted = entity_inventory.insert(item)
|
|
|
|
local ran_out = item_amount < preferred_amount
|
|
color.r = ran_out and 255 or 0
|
|
color.g = ran_out and 165 or 255
|
|
|
|
item.count = inserted
|
|
player_remove(item)
|
|
|
|
flyingText.text = { "exp-gui_autofill.inserted", inserted, rich_img("item", item_settings.name), rich_img("entity", entity.name) }
|
|
FlyingText.create_above_entity(flyingText)
|
|
offset.y = offset.y - 0.33
|
|
end
|
|
|
|
::continue::
|
|
end
|
|
end
|
|
|
|
local e = defines.events
|
|
|
|
return feature:guard{
|
|
elements = Elements,
|
|
events = {
|
|
[e.on_built_entity] = on_built_entity,
|
|
},
|
|
}
|