This commit is contained in:
2025-08-31 01:54:06 +09:00
71 changed files with 6736 additions and 3909 deletions

View File

@@ -41,7 +41,7 @@ local function format_as_pages(commands, page_size)
local description
if command.defined_at then
--- @cast command Commands.ExpCommand
--- @cast command ExpCommand
description = { "", command.help_text[2], "- ", command.description }
else
description = command.description

View File

@@ -16,7 +16,7 @@ Commands.new("_sudo", { "exp-commands_sudo.description" })
:add_flags{ "system_only" }
:register(function(_player, player, command, parameter)
--- @cast player LuaPlayer
--- @cast command Commands.ExpCommand
--- @cast command ExpCommand
--- @cast parameter string
--- @diagnostic disable-next-line: invisible

View File

@@ -57,6 +57,10 @@ end)
local ExpUtil = require("modules/exp_util")
local Search = require("modules/exp_commands/search")
--- Used as "game.player" in Commands.print when "game.player" is nil
--- @type LuaPlayer?
local _print_player
--- @class Commands
local Commands = {
color = ExpUtil.color,
@@ -65,9 +69,9 @@ local Commands = {
format_player_name = ExpUtil.format_player_name,
format_player_name_locale = ExpUtil.format_player_name_locale,
registered_commands = {}, --- @type table<string, Commands.ExpCommand> Stores a reference to all registered commands
registered_commands = {}, --- @type table<string, ExpCommand> Stores a reference to all registered commands
permission_authorities = {}, --- @type Commands.PermissionAuthority[] Stores a reference to all active permission authorities
--- @package Stores the event handlers
events = {
[defines.events.on_player_locale_changed] = Search.on_player_locale_changed,
@@ -111,7 +115,7 @@ end
--- @field aliases string[] Aliases that the command will also be registered under
--- @field defined_at? string If present then this is an ExpCommand
--- @class Commands.ExpCommand: Commands.Command
--- @class ExpCommand: Commands.Command
--- @field callback Commands.Callback The callback which is ran for the command
--- @field defined_at string The file location that the command is defined at
--- @field auto_concat boolean True if the command auto concatenates tailing parameters into a single string
@@ -119,6 +123,7 @@ end
--- @field max_arg_count number The maximum number of expected arguments
--- @field flags table Stores flags which can be used by permission authorities
--- @field arguments Commands.Argument[] The arguments for this command
--- @overload fun(player: LuaPlayer, ...: any)
Commands._prototype = {}
Commands._metatable = {
@@ -209,7 +214,7 @@ end
--- Permission Authority.
-- Functions that control who can use commands
--- @alias Commands.PermissionAuthority fun(player: LuaPlayer, command: Commands.ExpCommand): boolean|Commands.Status, LocalisedString?
--- @alias Commands.PermissionAuthority fun(player: LuaPlayer, command: ExpCommand): boolean|Commands.Status, LocalisedString?
--- Add a permission authority, a permission authority is a function which provides access control for commands, multiple can be active at once
--- When multiple are active, all authorities must give permission for the command to execute, if any deny access then the command is not ran
@@ -243,7 +248,7 @@ end
--- Check if a player has permission to use a command, calling all permission authorities
--- @param player LuaPlayer? The player to test the permission of, nil represents the server and always returns true
--- @param command Commands.ExpCommand The command the player is attempting to use
--- @param command ExpCommand The command the player is attempting to use
--- @return boolean # True if the player has permission to use the command
--- @return LocalisedString? # When permission is denied, this is the reason permission was denied
function Commands.player_has_permission(player, command)
@@ -321,14 +326,14 @@ end
-- Functions used to list and search for commands
--- Returns a list of all registered custom commands
--- @return table<string,Commands.ExpCommand> # A dictionary of commands
--- @return table<string,ExpCommand> # A dictionary of commands
function Commands.list_all()
return Commands.registered_commands
end
--- Returns a list of all registered custom commands which the given player has permission to use
--- @param player LuaPlayer? The player to get the command of, nil represents the server but list_all should be used
--- @return table<string,Commands.ExpCommand> # A dictionary of commands
--- @return table<string,ExpCommand> # A dictionary of commands
function Commands.list_for_player(player)
local rtn = {}
@@ -372,7 +377,7 @@ Commands.print_settings = {
--- @param message any The message / value to be printed
--- @param settings PrintSettings? The settings to print with
function Commands.print(message, settings)
local player = game.player
local player = game.player or _print_player
if not player then
rcon.print(ExpUtil.format_any(message))
else
@@ -403,7 +408,7 @@ end
--- Returns a new command object, this will not register the command but act as a way to start construction
--- @param name string The name of the command as it will be registered later
--- @param description LocalisedString? The description of the command displayed in the help message
--- @return Commands.ExpCommand
--- @return ExpCommand
function Commands.new(name, description)
ExpUtil.assert_argument_type(name, "string", 1, "name")
if Commands.registered_commands[name] then
@@ -429,7 +434,7 @@ end
--- @param name string The name of the argument being added
--- @param description LocalisedString? The description of the argument being added
--- @param input_parser Commands.InputParser The input parser to be used for the argument
--- @return Commands.ExpCommand
--- @return ExpCommand
function Commands._prototype:argument(name, description, input_parser)
assert_command_mutable(self)
if self.min_arg_count ~= self.max_arg_count then
@@ -450,7 +455,7 @@ end
--- @param name string The name of the argument being added
--- @param description LocalisedString? The description of the argument being added
--- @param input_parser Commands.InputParser The input parser to be used for the argument
--- @return Commands.ExpCommand
--- @return ExpCommand
function Commands._prototype:optional(name, description, input_parser)
assert_command_mutable(self)
self.max_arg_count = self.max_arg_count + 1
@@ -465,7 +470,7 @@ end
--- Set the defaults for optional arguments, any not provided will have their value as nil
--- @param defaults table<string, (fun(player: LuaPlayer): any) | any> The default values for the optional arguments, the key is the name of the argument
--- @return Commands.ExpCommand
--- @return ExpCommand
function Commands._prototype:defaults(defaults)
assert_command_mutable(self)
local matched = {}
@@ -491,7 +496,7 @@ end
--- Set the flags for the command, these can be accessed by permission authorities to check who can use a command
--- @param flags table An array of strings or a dictionary of flag names and values, when an array is used the flags values are set to true
--- @return Commands.ExpCommand
--- @return ExpCommand
function Commands._prototype:add_flags(flags)
assert_command_mutable(self)
for name, value in pairs(flags) do
@@ -507,7 +512,7 @@ end
--- Set the aliases for the command, these are alternative names that the command can be ran under
--- @param aliases string[] An array of string names to use as aliases to this command
--- @return Commands.ExpCommand
--- @return ExpCommand
function Commands._prototype:add_aliases(aliases)
assert_command_mutable(self)
local start_index = #self.aliases
@@ -519,7 +524,7 @@ function Commands._prototype:add_aliases(aliases)
end
--- Enable concatenation of all arguments after the last, this should be used for user provided reason text
--- @return Commands.ExpCommand
--- @return ExpCommand
function Commands._prototype:enable_auto_concatenation()
assert_command_mutable(self)
self.auto_concat = true
@@ -528,7 +533,7 @@ end
--- Register the command to the game with the given callback, this must be the final step as the object becomes immutable afterwards
--- @param callback Commands.Callback The function which is called to perform the command action
--- @return Commands.ExpCommand
--- @return ExpCommand
function Commands._prototype:register(callback)
assert_command_mutable(self)
Commands.registered_commands[self.name] = self
@@ -585,12 +590,32 @@ function Commands._prototype:register(callback)
return self
end
--- Run a callback for a command in the same way the command processor would, note no type validation is performed
--- @param self ExpCommand
--- @param player LuaPlayer
--- @param ... any
function Commands._metatable.__call(self, player, ...)
_print_player = player
local status, status_msg = self.callback(player, ...)
if status == nil then
local _, msg = Commands.status.success()
Commands.print(msg)
elseif not valid_command_status[status] then
error("Command \"" .. self.name .. "\" did not return a valid status got: " .. ExpUtil.get_class_name(status))
elseif status ~= Commands.status.success then
Commands.error(status_msg)
else
Commands.print(status_msg)
end
_print_player = nil
end
--- Command Runner
-- Used internally to run commands
--- Log that a command was attempted and its outcome (error / success)
--- @param comment string The main comment to include in the log
--- @param command Commands.ExpCommand The command that is being executed
--- @param command ExpCommand The command that is being executed
--- @param player LuaPlayer The player who is running the command
--- @param parameter string The raw command parameter that was used
--- @param detail any

View File

@@ -18,7 +18,7 @@ local command_objects = {} --- @type table<string, Commands.Command>
local required_translations = {} --- @type LocalisedString[]
--- Gets the descriptions of all commands, not including their aliases
--- @param custom_commands table<string, Commands.ExpCommand> The complete list of registered custom commands
--- @param custom_commands table<string, ExpCommand> The complete list of registered custom commands
function Search.prepare(custom_commands)
local known_aliases = {} --- @type table<string, string>
for name, command in pairs(custom_commands) do
@@ -85,7 +85,7 @@ end
--- Searches all game commands and the provided custom commands for the given keyword
--- @param keyword string The keyword to search for
--- @param custom_commands table<string, Commands.ExpCommand> A dictionary of commands to search
--- @param custom_commands table<string, ExpCommand> A dictionary of commands to search
--- @param locale string? The local to search, default is english ("en")
--- @return table<string, Commands.Command> # A dictionary of commands
function Search.search_commands(keyword, custom_commands, locale)

203
exp_gui/docs/motivation.md Normal file
View File

@@ -0,0 +1,203 @@
# Design motivation
This document outlines why I created this framework, and the reasoning behind some of the opinionated decisions that shaped its design.
The motivation came from my experience with existing libraries, which often enforced a strict separation between element definitions, event handling, and GUI-related data.
In many cases, these libraries focused solely on element creation, leaving developers to manually manage event filtering and data scoping themselves.
I found that approach cumbersome and unintuitive.
I believed there was a better way, one that embraced a different kind of encapsulation, making the conceptual model easier to understand and work with.
And so I created a framework with four distinct and independent parts that all come together with a sense of locality not seen in our libraries.
Additionally, the guide places greater emphasis on naming conventions and calling patterns, rather than just listing what each function does.
These conventions are key to how the framework is expected to be used and are intended to make development feel more cohesive and intuitive.
At the heart of the framework are four core concepts that bring everything together:
## ExpElement
ExpElement serves as the prototype for all element definitions.
It's intentionally designed as a wrapper around LuaGuiElement.add and associated event handlers.
It takes in definition tables and functions, and returns a function that can be used to create a LuaGuiElement.
This focused purpose makes it easier to reason about.
It also reduces boilerplate, allowing you to concentrate on functionality rather than repetitive setup.
You can optionally add methods to the definition, such as `add_row` or `refresh`.
While these could technically be local functions, including them directly in the definition makes it immediately clear which data they interact with or modify.
This enhances both readability and maintainability.
For example, the following two snippets are conceptually equivalent:
```lua
Elements.my_label = Gui.define("my_label")
:draw{
type = "label",
caption = "Hello, World!",
}
:style{
font_color = { r = 1, g = 0, b = 0 },
width = Gui.from_argument(1),
}
:element_data{
foo = "bar"
}
:on_click(function(def, player, element, event)
element.caption = "Clicked!"
end)
function Elements.my_label.reset(my_label)
my_label.caption = "Hello, World!"
end
```
```lua
local my_label_data = GuiData.create("my_label")
function Elements.my_label(parent, width)
-- :draw
local element = parent.add{
type = "label",
caption = "Hello, World!",
}
-- :style
local style = element.style
style.font_color = { r = 1, g = 0, b = 0 }
style.width = width
-- :element_data
my_label_data[element] = {
foo = "bar"
}
-- event handlers
local tags = element.tags or {}
local event_tags = tags.event_tags or {}
event_tags[#event_tags + 1] = "my_label"
element.tags = tags
return element
end
local function my_label_reset(my_label)
my_label.caption = "Hello, World!"
end
local function on_gui_click(event)
local element = event.element
if is_my_label(element) then -- pseudo function to check event_tags
element.caption = "Clicked!"
end
end
```
In the example, I use table-style definitions, which are the most common approach for simple elements and are encouraged wherever possible.
Internally, these tables are converted into draw functions, which can also be passed directly if needed.
You could, of course, write everything into a single "create" function, or even place all logic inside a `:draw` method, but maintaining a separation between these responsibilities serves as a form of clear signposting.
This improves readability and makes the structure of your code easier to follow at a glance.
```lua
Elements.my_label = Gui.define("my_label")
:draw(function(def, parent, width)
return parent.add{
type = "label",
caption = "Hello, World!",
}
end)
:style(function(def, element, parent, width)
return {
font_color = { r = 1, g = 0, b = 0 },
width = width,
}
end)
:element_data(function(def, element, parent, width)
return {
foo = "bar"
}
end)
:on_click(function()
print("Clicked!")
end)
```
## GuiData
Building on the goal of keeping GUI data close to where its used and displayed, I introduced `GuiData`, which is integrated as `ExpElement.data`.
Like the other components, its purpose is focused and singular, and it can even be used standalone if it's the only part of the framework you find useful.
In simple terms, GuiData creates a table in `storage` with a custom `__index` metamethod that enables automatic scoping of data.
It also cleans up data when the associated key is destroyed, helping to reduce unnecessary memory usage.
One common use case, explained earlier in this guide, is storing references to other elements.
This approach removes the tight coupling between event handlers and the GUI structure by giving handlers direct access to what they need.
Additionally, it encourages you to make assumptions explicit by requiring references as arguments.
While this pattern can take some getting used to, it makes dependencies much easier to identify and reason about.
While this example exposes some of the internal mechanics, it should help you understand the convenience and clarity that scoped data access provides.
```lua
local data = GuiData.create("my_data")
-- data[element] = "foo"
storage.gui_data.scopes["my_data"].element_data[element.player_index][element.index] = "foo"
-- data[player] = "bar"
storage.gui_data.scopes["my_data"].player_data[player.index] = "bar"
-- data[force] = "baz"
storage.gui_data.scopes["my_data"].force_data[force.index] = "baz"
```
## GuiIter
With scoped data easily accessible, it became straightforward to track elements belonging to a specific player, especially for updates or state changes.
However, this pattern became so common (and often cluttered `GuiData`) that I created a dedicated iterator: `GuiIter`.
As with the other modules, GuiIter can be used independently if you like what it offers, or through its integration with ExpElement.
Whenever an element is created, or at any point, really, it can be registered with the iterator for future access.
Retrieval is then handled by applying a filter across all tracked elements, returning them one by one.
Dont worry, the underlying data structure is designed for efficient lookup and automatic cleanup.
This can be incredibly powerful.
It gives you direct access to GUI elements without having to manually navigate from `player.gui`, and the filtering makes it simple to, for example, target only elements belonging to online players in a specific force.
Below is an example of how GuiIter can be used as a standalone utility:
```lua
local function teammate_counter(player)
local frame = player.gui.left.add{ type = "frame" }
local label = frame.add{ type = "label", caption = tostring(#player.force.players) }
GuiIter.add_element("teammate_counter", label)
end
local function on_player_changed_force(event)
local old_force = event.old_force
local old_force_count = tostring(#old_force.players)
for player, label in GuiIter.get_online_elements("teammate_counter", old_force) do
label.caption = caption
end
local new_force = game.get_player(event.player_index).force
local new_force_count = tostring(#new_force.players)
for player, label in GuiIter.get_online_elements("teammate_counter", new_force) do
label.caption = caption
end
end
```
## Toolbar
While ExpElement ties individual components together into self-contained units, the Toolbar acts as a singleton that manages them all.
From an implementation standpoint, its split into two parts: one that handles drawing elements when a player joins, and an optional settings menu named "Toolbox".
The element-drawing functionality is the final piece of the puzzle for eliminating boilerplate and letting you focus on functionality.
You simply register an element at a given location, and it gets drawn automatically on player join, it really is that straightforward.
The optional settings menu provides a standardised way to manage button behaviour, while also giving players control over which buttons are visible.
This was born out of necessity: as the number of GUI modules grew, having all of them visible by default became overwhelming.
The settings menu solves that by letting players hide modules they dont need.
![toolbox](./docs/toolbox.png)

457
exp_gui/docs/reference.md Normal file
View File

@@ -0,0 +1,457 @@
# ExpGui API Reference
This is a streamlined version of the API reference, highlighting the methods you're most likely to use day-to-day.
It includes extra detail and practical examples to help you get up and running quickly.
If you havent already, we recommend starting with the [framework guide](../readme.md).
It lays the groundwork and gives you the context youll need to understand how these methods work together.
A [full reference](./reference_full.md) is also available.
But if you plan to rely on it, we strongly suggest reviewing and familiarizing yourself with the underlying implementation of the functions.
## Utility Functions
These helper methods are designed to simplify common tasks when working with GUI elements, like toggling states or safely destroying elements.
### `Gui.get_player`
Retrieves the player associated with a given context. This can be:
- A LuaGuiElement
- An event containing `event.player_index`
- An event containing `event.element`
The method includes a not-nil assertion to handle rare cases where a player might have become invalid.
### `Gui.toggle_*_state`
Refers to `Gui.toggle_enabled_state` and `Gui.toggle_visible_state`.
These functions toggle the specified state (either enabled or visible) for a LuaGuiElement, and return the new state after toggling.
If you provide a second argument (true or false), the function becomes `set_*_state` and sets the state directly instead of toggling.
Each function checks if the element is non-nil and still valid before performing any action.
If those checks fail, nothing happens.
### `Gui.destroy_if_valid`
Destroys a given LuaGuiElement, but only if it exists and is valid.
If either condition is not met, the function exits quietly without performing any action.
## Element Definations
This section covers how to define, register, and manage GUI elements using the framework.
These definitions form the foundation of how your GUI behaves, persists, and responds to player interaction.
### `Gui.define`
All GUI elements begin with a call to `Gui.define`, where you provide a unique name for your element.
This name only needs to be unique within your own mod or module.
When you're first setting up a GUI, you may not know its full structure right away.
In these cases, it can be useful to use `:empty()` child elements until you are ready to define them.
```lua
Elements.my_button = Gui.define("my_button")
:empty()
```
### `Gui.add_*_element`
Refers to `Gui.add_top_element`, `Gui.add_left_element`, and `Gui.add_relative_element`.
These functions register a root element to a GUI flow, ensuring its created for all players when they join.
Once registered, the framework takes ownership of the elements lifetime, guaranteeing that it always exists.
This makes them ideal entry points for GUIs that maintain persistent state.
You can retrieve the created instance using `Gui.get_*_element`, passing in the definition as the first argument.
From there, you can perform operations like [`Gui.toggle_visible_state`](#guitoggle__state) or apply custom logic.
If you're using the Toolbar or GuiIter, you likely wont need to manually retrieve your element at all.
For example, the Toolbar provides convenience methods like [`Toolbar.get_left_element_visible_state`](#guitoolbarset_left_element_visible_state) and [`Toolbar.set_button_toggled_state`](#guitoolbarset_button_toggled_state), both of which accept your element definition directly.
### `ExpElement:draw`
When you're ready to define an element, the first required step is to call `:draw()`.
This method accepts either:
- A table that defines the GUI structure
- A function that returns a LuaGuiElement
Using a table is encouraged and it supports dynamic values via `Gui.from_argument`, allowing you to pass data through arguments easily.
While this doesn't cover every use case, it handles the majority of common needs.
```lua
Elements.my_label = Gui.define("my_label")
:draw{
caption = Gui.from_argument(1),
style = "heading_2_label",
}
```
For elements with many optional values, it's recommended to use an "options" table.
Simply provide named keys instead of using array indexes.
The options table is always assumed to be the final argument, so required values can still be passed by index.
You can also define default values.
```lua
Elements.my_camera = Gui.define("my_camera")
:draw{
surface_index = Gui.from_argument(1),
position = Gui.from_argument("position", { 0, 0 }),
zoom = Gui.from_argument("zoom"),
}
```
When an element is a composite of other elements, i.e. it has children, then you need to use the function defination method.
This is because child elements may need arguments to be supplied to them which is more easily done in a function body rather than inventing a new table syntax.
Your draw function should always return the most "meaningful" LuaGuiElement, this can mean: the element that raises events, the element that children should be added to, or the root element for those registered to a gui flow (top / left / relative).
If multiple of these conditions apply to different children then you will need to look into manually calling `:link_element` for event handling or clearly document where children should be added by callers.
If none of these apply, then consider if you should be using an element defination at all, if you must then you can return `Gui.no_return`.
If your element is a composite (i.e. it contains child elements), then using a function is required.
This is because you may need to pass arguments to children which is more cleanly done in a function body rather than a complex table structure.
Your draw function should return the most “meaningful” LuaGuiElement. This might be:
- The element that raises GUI events
- The element where children should be added
- The root element registered to a GUI flow (top, left, or relative)
If these responsibilities apply to different children, youll need to either manually link elements to events using `:link_element` or document clearly where children should be added by callers.
If none of these conditions apply, consider whether a standalone element definition is appropriate.
If you still need one, you can return `Gui.no_return`.
```lua
Elements.my_frame = Gui.define("my_Frame")
:draw(function(def, parent)
local player = Gui.get_player(parent)
local frame = parent.add{ type = "frame" }
Elements.my_button(frame)
Element.my_label(frame, "Hello, " .. player.name)
Element.my_camera(frame, player.surface.index, {
zoom = 0.5,
})
return frame
end)
Elements.no_return = Gui.define("no_Return")
:draw(function(def, parent)
return Gui.no_return()
end)
```
### `ExpElement:style`
This method defines the style of your element, and is very similar to `:draw`.
Styling is only applied to the element returned from `:draw`.
If you use a function to define styles, the signature is `fun(def, element, parent)`.
If you return a table from this function, it should mimic the structure of a LuaStyle object.
However, you are not required to return anything.
You can also apply styles directly to the element, which is useful for read-only properties like `LuaStyle.column_alignments`.
```lua
Elements.my_label_big = Gui.define("my_label_big")
:draw{
caption = Gui.from_argument(1),
style = "heading_2_label",
}
:style{
width = 400,
}
Elements.right_aligned_numbers = Gui.define("right_aligned_numbers")
:draw{
caption = Gui.from_argument(1),
}
:style(function(def, element, parent, caption)
return {
width = 400,
horizontal_align = tonumber(caption) and "right" or "left"
}
end)
```
### `ExpElement:*_data`
Refers to `ExpElement:element_data`, `ExpElement:player_data`, `ExpElement:force_data`, and `ExpElement:global_data`; standlone use of `GuiData` is not covered.
These methods initialize GUI-related data within your element definition.
You can access the data later through `ExpElement.data`, or more commonly as `def.data` in event handlers.
If you pass a non-function value, it will be deep-copied to create the initial data (if it does not already exist).
If you pass a function, it will be called to either mutate existing data or return a new value to be used as the inital data.
For complex data structures, especially those involving references to child elements, its often better to assign data directly inside your `:draw` method.
Keep in mind that initializers do not run until after `:draw` completes, so you cannot rely on data being in a consistent state during draw.
```lua
Elements.my_primaitve_data = Gui.define("my_primaitve_data")
:empty()
:element_data(0)
Elements.my_table_data = Gui.define("my_table_data")
:empty()
:element_data{
count = 0,
}
Elements.my_function_data = Gui.define("my_function_data")
:empty()
:element_data(function(def, element, parent)
return {
seed = math.random()
}
end)
Elements.shared_counter = Gui.define("shared_counter")
:draw(function(def, parent)
local player = Gui.get_player(parent)
local count = def.data[player.force] or 0
return parent.add{
type = "button",
caption = tostring(count),
}
end)
:force_data(0)
:on_click(function(def, player, element, event)
local old_count = def.data[player.force]
local new_count = old_count + 1
def.data[player.force] = new_count
element.caption = tostring(new_count)
end)
Elements.composite = Gui.define("composite")
:draw(function(def, parent)
local frame = parent.add{ type = "frame" }
def.data[frame] = {
shared_counter = Elements.shared_counter(frame),
label = frame.add{ type = "label" },
}
return frame
end)
```
### `ExpElement:on_*`
Refers to all gui events and `ExpElement:on_event` for other arbitary events.
Gui events are converted from `on_gui_` to `on_` for examplse `on_gui_clicked` to `on_clicked`.
These methods allow you to attach event handlers to your element definition.
For general `on_event` usage, theres no extra filtering, the handler will be called when the event occurs.
For GUI-specific events, the handler is only called for linked elements.
Handlers are automatically linked to any element returned from `:draw`.
You can manually link other elements using `:link_element`.
If you want to prevent an element from being linked automatically, you can call and return `:unlink_element`.
However, needing to do this might suggest a misalignment in how your functions responsibilities are structured.
In the example below, it would be best practice to introduce `Elements.title_label` which has an `on_click` handler.
```lua
Elements.my_clickable_button = Gui.define("my_clickable_button")
:draw{
type = "button",
caption = "Click Me",
}
:on_click(function(def, player, element, event)
player.print("Clicked!")
end)
Elements.my_clickable_title = Gui.define("my_clickable_title")
:draw(function(def, parent)
local frame = parent.add{ type = "frame" }
local title = frame.add{ type = "label", caption = "Click Me" }
def:link_element(title)
return def:unlink_element(frame)
end)
:on_click(function(def, player, element, event)
player.print("The title was clicked!")
end)
```
### `ExpElement:track_all_elements`
The most common use of the GUI iterator is to track all created elements.
Therefore `track_all_elements` was added which will track every element that is returned from your draw function.
You can also manually track additional elements with `:track_element`, and you can exclude elements from tracking using `:untrack_element`.
```lua
Elements.my_tracked_label = Gui.define("my_tracked_label")
:track_all_elements()
:draw{
type = "label",
caption = "Im tracked by GuiIter",
}
Element.my_tracked_children = Gui.define("my_tracked_children")
:draw(function(def, parent)
local frame = parent.add{ type = "frame" }
def:track_element(frame.add{ type = "label", caption = "Im tracked 1" })
def:track_element(frame.add{ type = "label", caption = "Im tracked 2" })
def:track_element(frame.add{ type = "label", caption = "Im tracked 3" })
return frame
end)
Elements.my_sometimes_tracked_label = Gui.define("my_sometimes_tracked_label")
:track_all_elements()
:draw(function(def, parent)
local player = Gui.get_player(parent)
if player.admin then
return parent.add{ type = "label", caption = "Im tracked" }
end
return def:untrack_element(parent.add{ type = "label", caption = "Im tracked" })
end)
```
## Gui Iterator
Refers to `ExpElement:tracked_elements` and `ExpElement:online_elements`; standalone use of `GuiIter` is not covered.
Once an element has been tracked using `:track_all_elements` or `:track_element`, it can be iterated over using these custom GUI iterators.
Each method accepts an optional filter parameter, which can be: LuaPlayer, LuaForce, or an array of LuaPlayer.
As their names suggest, `:tracked_elements` returns all tracked elements while `:online_elements` limits the results to online players only.
If you're caching data per force, it is more efficient to use a single unfiltered iteration rather than multiple filtered ones.
The naming convention to be followed is:
- `refresh` when the first argument is an instance of the element.
- `refresh_all` when there using `:tracked_elements` without a filter.
- `refresh_online` when there using `:online_elements` without a filter.
- `refresh_*` when there using `:tracked_elements` with a filter, e.g. `refresh_force`.
- `refresh_*_online` when there using `:online_elements` with a filter, e.g. `refresh_force_online`.
- `update` can be used when the new state is dependend on the old state, e.g. incrementing a counter.
- `reset` can be used when the element has a default state it can be returned to.
- `save` can be used when the current state is stored in some way.
- `*_row` when the action applies to a row within a table rather than an element.
- The name does not indicate if a cache is used, this is because a cache should be used where possible.
```lua
function Elements.my_tracked_label.calculate_force_data(force)
return {
caption = "I was refreshed: " .. force.name,
}
end
function Elements.my_tracked_label.refresh_all()
for player, element in Elements.my_tracked_label:tracked_elements() do
element.caption = "I was refreshed"
end
end
function Elements.my_tracked_label.refresh_online()
for player, element in Elements.my_tracked_label:online_elements() do
element.caption = "I was refreshed: online"
end
end
function Elements.my_tracked_label.refresh_force(force)
local force_data = Elements.my_tracked_label.calculate_force_data(force)
for player, element in Elements.my_tracked_label:tracked_elements(force) do
element.caption = force_data.caption
end
end
-- a different implimention of refresh all with a force cache
function Elements.my_tracked_label.refresh_all()
local _force_data = {}
for _, force in pairs(game.forces) do
_force_data[force.name] = Elements.my_tracked_label.calculate_force_data(force)
end
for player, element in Elements.my_tracked_label:tracked_elements() do
local force_data = _force_data[player.force.name]
element.caption = force_data.caption
end
end
-- a different implimention of refresh online with a force cache
function Elements.my_tracked_label.refresh_online()
local _force_data = {}
for _, force in pairs(game.forces) do
if next(force.connected_players) then
_force_data[force.name] = Elements.my_tracked_label.calculate_force_data(force)
end
end
for player, element in Elements.my_tracked_label:online_elements() do
local force_data = _force_data[player.force.name]
element.caption = force_data.caption
end
end
```
## Toolbar
The toolbar API provides convenience methods for adding toggleable buttons and syncing them with GUI elements in the left flow.
This is especially useful for building persistent interfaces.
### `Gui.toolbar.create_button`
Creates a new button on the toolbar, with the option to link it to an element defined in the left flow.
This method also creates a new element definition for the button, so the provided name must be unique within your mod or module.
You can attach event handlers (such as `on_click` or `on_button_toggled`) to the button as needed.
If a left-side element definition is provided, the button is automatically set to toggle the visibility of that element when clicked.
The button type is set automatically based on the presence of a `sprite` option.
If `sprite` is defined, it creates a sprite button; otherwise, a standard button is used.
```lua
Gui.toolbar.create_button{
name = "click_me",
caption = "Click Me!",
}
:on_click(function(def, player, element, event)
player.print("Clicked!")
end)
Elements.toggle_me =
Gui.toolbar.create_button{
name = "toggle_me",
caption = "Toggle Me!",
auto_toggle = true,
}
:on_button_toggled(function(def, player, element, event)
player.print("I am now: " .. event.state)
end)
Gui.add_left_element(Elements.my_frame)
Gui.toolbar.create_button{
name = "toggle_my_frame",
caption = "Toggle Me!",
left_element = Elements.my_frame,
}
```
### `Gui.toolbar.set_button_toggled_state`
Sets the toggled state of a toolbar button, keeping it in sync with a linked left-side element if one is defined.
The element definition must have been previously returned by `create_button`.
If a state argument is not given then this becomes `toggle_button_toggled_state`.
This method does not raise `on_click`; instead, it raises `on_button_toggled`
```lua
Gui.toolbar.set_button_toggled_state(Elements.toggle_me, true)
```
### `Gui.toolbar.set_left_element_visible_state`
Sets the visibility state of a left-flow GUI element, and updates the state of a linked toolbar button if one is defined.
The element definition must have been previously passed to `Gui.add_left_element`.
If a state argument is not given then this becomes `toggle_left_element_visible_state`.
When a toolbar button is linked, this method also raises `on_button_toggled` for that button to reflect the change.
```lua
Gui.toolbar.set_button_toggled_state(Elements.my_frame, true)
```

View File

@@ -0,0 +1,485 @@
# ExpGui Full API Reference
If you havent already, please read the [framework guide](../readme.md) and [condensed reference](./reference.md) first, this full reference wont be very useful without that context.
Additionally, if you find yourself needing to rely heavily on this document, I strongly recommend reading the actual implementation of each function.
It will give you a far better understanding than the limited comments provided here.
[Pre-defined elements](../module/elements.lua), [styles](../module/styles.lua), and elements defined by the toolbar are not included in this reference.
## Control ([`control.lua`](../module/control.lua))
It is expected this file is required as `modules/exp_gui` and is assigned to the variable `Gui`.
### `Gui.define`
This is a reference to [`ExpElement.new`](#expelementnew)
It creates new element definations.
The name provided must be unqiue to your mod.
### `Gui.from_argument`
This is a reference to [`ExpElement.from_argument`](#expelementfrom_argument)
It is used within defiantion tables (draw, style, and data) to use a value from an argument.
The argument can be named or positional, and a default value can be provided.
### `Gui.from_name`
This is a reference to [`ExpElement.from_name`](#expelementfrom_name)
It is used within defiantion tables (draw, style, and data) to use a value from the defination name. The most common use case is `name = Gui.from_name` within a draw table.
### `Gui.no_return`
This is a reference to [`ExpElement.no_return`](#expelementno_return)
It is used exclusively within [`ExpElement:draw`](#expelementdraw) to signal the intental lack of a return value.
### `Gui.top_elements`
This is a table of all registered elements for the top flow.
The keys are ExpElement and the values are the default visibltiy callback / boolean.
### `Gui.left_elements`
This is a table of all registered elements for the left flow.
The keys are ExpElement and the values are the default visibltiy callback / boolean.
### `Gui.relative_elements`
This is a table of all registered elements for relative locations.
The keys are ExpElement and the values are the default visibltiy callback / boolean.
### `Gui.get_top_flow`
This is a reference to `mod_gui.get_button_flow`
It gets the flow where top elements are added to.
### `Gui.get_left_flow`
This is a reference to `mod_gui.get_frame_flow`
It gets the flow where left elements are added to.
### `Gui._debug`
After this function is called, all players GUIs will be redrawn every join.
This is helpful for structure and style debugging.
### `Gui.get_player`
A version of `game.get_player` that accepts LuaGuiElement and events containing `event.element` as a property.
### `Gui.toggle_enabled_state`
Toggles the enabled state of the passed LuaGuiElement.
It will return the new enabled state.
The second argument makes this `set_enabled_state`
### `Gui.toggle_visible_state`
Toggles the visible state of the passed LuaGuiElement.
It will return the new visible state.
The second argument makes this `set_visible_state`
### `destroy_if_valid`
Destories the passed LuaGuiElement.
Does nothing if the element is nil or an invalid reference.
### `Gui.add_top_element`
Registers an ExpElement to be drawn on the top flow.
The second argument is the default visible state, which can be a function.
### `Gui.add_left_element`
Registers an ExpElement to be drawn on the left flow.
The second argument is the default visible state, which can be a function.
### `Gui.add_relative_element`
Registers an ExpElement to be drawn relative to a core GUI.
The second argument is the default visible state, which can be a function.
The core gui is defined with `:draw{ anchor: GuiAnchor }`
### `Gui.get_top_element`
Returns the LuaGuiElement for the passed ExpElement.
Errors if the element is not registered to the top flow.
### `Gui.get_left_element`
Returns the LuaGuiElement for the passed ExpElement.
Errors if the element is not registered to the left flow.
### `Gui.get_relative_element`
Returns the LuaGuiElement for the passed ExpElement.
Errors if the element is not registered to the relative flow.
### `Gui._ensure_consistency`
If for any reason registered elements need to be redrawn, this method will handle it.
One example is updates within a custom permission system.
## Gui Data ([`data.lua`](../module/data.lua))
It is expected this file is required as `modules/exp_gui/data` and is assigned to the variable `GuiData`.
Alternativly, instances of GuiData exist on all ExpElements as `ExpElement.data`.
### `GuiData:__index`
No data is directly stored within an instance of GuiData, instead `__index` will be called and fetch the data from `GuiData._raw`.
Currently accepted indexes are: LuaGuiElement, LuaPlayer, LuaForce, and "global_data".
### `GuiData:__newindex`
No data is directly stored within an instance of GuiData, instead `__newindex` will be called and store the data in `GuiData._raw`.
Currently accepted indexes are: LuaGuiElement, LuaPlayer, LuaForce.
Setting "global_data" is not supported, although settings keys of "global_data" is permitted.
### `GuiData.create`
Creates a new instance of GuiData with a given scope.
Only a single instance can exist for any scope, use [`GuiData.get`](#guidataget) to retrive existing instances.
### `GuiData.get`
Retrives and existing instance of GuiData for a given scope.
Only use this if you have a circular dependency, otherwise you should be passing by reference.
## Gui Iter ([`iter.lua`](../module/iter.lua))
It is expected this file is required as `modules/exp_gui/iter` and is assigned to the variable `GuiIter`.
Alternativly, references to GuiIter exist on all ExpElements as `ExpElement:track_element`, `ExpElement:untrack_element`, `ExpElement:tracked_elements`, `ExpElement:online_elements`, and `ExpElement:track_all_elements`
### `GuiIter.player_elements`
Iterates all elements for a single player in a given scope.
The returned tupple is `LuaPlayer, LuaGuiElement`.
### `GuiIter.filtered_elements`
Iterates all elements for the provided players in a given scope.
The returned tupple is `LuaPlayer, LuaGuiElement`.
This method is named "filtered" because it is expected that the player list provided has been filtered on some condition and only elements for players in this list are returned.
The optional third argument can be provided to filter to online only if you have not done so yourself.
### `GuiIter.all_element`
Iterates all elements for all players in a given scope.
The returned tupple is `LuaPlayer, LuaGuiElement`.
### `GuiIter.get_tracked_elements`
Iterates all elements for all players in a given scope who pass the provided filter.
The returned tupple is `LuaPlayer, LuaGuiElement`.
The accepted filters are: nil, LuaPlayer, LuaPlayer[], and LuaForce.
Functions are NOT supported, instead pass an array of players you have pre-filtered.
### `GuiIter.get_online_elements`
Iterates all elements for online players in a given scope who pass the provided filter.
The returned tupple is `LuaPlayer, LuaGuiElement`.
The accepted filters are: nil, LuaPlayer, LuaPlayer[], and LuaForce.
Functions are NOT supported, instead pass an array of players you have pre-filtered.
### `GuiIter.add_element`
Adds an element to be tracked within a scope.
Elements can be tracked in multiple scopes.
### `GuiIter.remove_element`
Remove an element from a scope.
Does nothing if the element was not tracked.
Elements are automatically removed when destoryed.
## ExpElement ([`prototype.lua`](../module/prototype.lua))
It is expected this file is required as `modules/exp_gui/prototype` and is assigned to the variable `ExpElement`.
Alternativly, instances of ExpElement can be created with [`Gui.define`](#guidefine).
### `ExpElement.no_return`
It is used exclusively within `ExpElement:draw` to signal the intental lack of a return value.
Also accessible through [`Gui.no_return`](#guino_return)
### `ExpElement.from_name`
It is used within defiantion tables (draw, style, and data) to use a value from the defination name.
Also accessible through [`Gui.from_name`](#guifrom_name)
### `ExpElement.from_argument`
It is used within defiantion tables (draw, style, and data) to use a value from an argument.
The argument can be named or positional, and a default value can be provided.
Also accessible through [`Gui.from_argument`](#guifrom_argument)
### `ExpElement.new`
It creates new element definations.
The name provided must be unqiue to your mod.
Also accessible through [`Gui.define`](#guidefine)
### `ExpElement.get`
Gets the existing ExpElement with the given name.
Only use this if you have a circular dependency, otherwise you should be passing by reference.
### `ExpElement:create`
Creates a LuaGuiElement following the element defination.
Also accessible through `__call` allowing direct calls of this table to create an element.
Order of operations is: [draw](#expelementdraw), [style](#expelementstyle), [element_data](#expelementelement_data), [player_data](#expelementplayer_data), [force_data](#expelementforce_data), [global_data](#expelementglobal_data), [track_element](#expelementtrack_element), [link_element](#expelementlink_element).
### `ExpElement:track_all_elements`
When called, `ExpElement:track_element` will be called for all elements at the end of [`ExpElement:create`](#expelementcreate)
### `ExpElement:empty`
Defines [`ExpElement:draw`](#expelementdraw) as an empty flow.
This is intended to be used when you are first setting up the structure of your gui.
When used warnings will be logged, do not rely on this when you want an empty flow.
### `ExpElement:draw`
Defines the draw function for you element defination.
Successive calls will overwrite previous calls.
Accepts either a table to be passed to `LuaGuiElement.add` or a function that returns a LuaGuiElement.
### `ExpElement:style`
Defines the style function for you element defination.
Successive calls will overwrite previous calls.
Accepts either a table with key values equlaient to LuaStyle, or a function that can return this table, or [`ExpElement:from_argument`](#expelementfrom_argument).
### `ExpElement:element_data`
Defines the element data init function for you element defination.
Successive calls will overwrite previous calls.
Accepts any non-function value to deep copy, or a function that can return this value, or [`ExpElement:from_argument`](#expelementfrom_argument).
When a non-function value is used or returned, it will not overwrite existing data.
If you want this behaviour then modify the data directly in your function rather than returning a value.
### `ExpElement:player_data`
Defines the player data init function for you element defination.
Successive calls will overwrite previous calls.
Accepts any non-function value to deep copy, or a function that can return this value, or [`ExpElement:from_argument`](#expelementfrom_argument).
When a non-function value is used or returned, it will not overwrite existing data.
If you want this behaviour then modify the data directly in your function rather than returning a value.
### `ExpElement:force_data`
Defines the force data init function for you element defination.
Successive calls will overwrite previous calls.
Accepts any non-function value to deep copy, or a function that can return this value, or [`ExpElement:from_argument`](#expelementfrom_argument).
When a non-function value is used or returned, it will not overwrite existing data.
If you want this behaviour then modify the data directly in your function rather than returning a value.
### `ExpElement:global_data`
Defines the global data init function for you element defination.
Successive calls will overwrite previous calls.
Accepts only a table value to deep copy, or a function that can return a table, or [`ExpElement:from_argument`](#expelementfrom_argument).
When a table is used or returned, it will not overwrite existing data.
If you want this behaviour then modify the data directly in your function rather than returning a table.
### `ExpElement:tracked_elements`
A proxy call to [`GuiIter.get_tracked_elements`](#guiiterget_tracked_elements) with the scope pre-populated.
### `ExpElement:online_elements`
A proxy call to [`GuiIter.get_online_elements`](#guiiterget_online_elements) with the scope pre-populated.
### `ExpElement:track_element`
A proxy call to [`GuiIter.add_element`](#guiiteradd_element) with the scope pre-populated.
### `ExpElement:untrack_element`
A proxy call to [`GuiIter.remove_element`](#guiiterremove_element) with the scope pre-populated.
If returned from a draw function then [`ExpElement:track_all_elements`](#expelementtrack_all_elements) is ignored.
### `ExpElement:link_element`
Links an element to this define in order to trigger event handlers.
Should only be used to link additional elements because elements returned from draw are linked automatically.
### `ExpElement:unlink_element`
Unlinks an element from this define in order to prevent event handlers triggering.
If returned from a draw function then automatic linking will be prevented.
### `ExpElement:raise_event`
Raise an event on this define.
This can be useful for defering events to other definiations or for raising custom events.
### `ExpElement:on_event`
Allows connecting to arbitary events.
Multiple handlers are supported.
### `ExpElement:on_checked_state_changed`
Connects a handler to `defines.events.on_gui_checked_state_changed`.
### `ExpElement:on_click`
Connects a handler to `defines.events.on_gui_click`.
### `ExpElement:on_closed`
Connects a handler to `defines.events.on_gui_closed`.
### `ExpElement:on_confirmed`
Connects a handler to `defines.events.on_gui_confirmed`.
### `ExpElement:on_elem_changed`
Connects a handler to `defines.events.on_gui_elem_changed`.
### `ExpElement:on_hover`
Connects a handler to `defines.events.on_gui_hover`.
### `ExpElement:on_leave`
Connects a handler to `defines.events.on_gui_leave`.
### `ExpElement:on_location_changed`
Connects a handler to `defines.events.on_gui_location_changed`.
### `ExpElement:on_opened`
Connects a handler to `defines.events.on_gui_opened`.
### `ExpElement:on_selected_tab_changed`
Connects a handler to `defines.events.on_gui_selected_tab_changed`.
### `ExpElement:on_selection_state_changed`
Connects a handler to `defines.events.on_gui_selection_state_changed`.
### `ExpElement:on_switch_state_changed`
Connects a handler to `defines.events.on_gui_switch_state_changed`.
### `ExpElement:on_text_changed`
Connects a handler to `defines.events.on_gui_text_changed`.
### `ExpElement:on_value_changed`
Connects a handler to `defines.events.on_gui_value_changed`.
## Toolbar ([`toolbar.lua`](../module/toolbar.lua))
It is expected this file is required as `modules/exp_gui/toolbar` and is assigned to the variable `Toolbar`.
Alternativly, it can be accessed through `Gui.toolbar`.
A point of clarifcation, the "toolbar" in this framework refers to the top flow which may also be refered to as the "favoruites bar", while the "toolbox" is the custom gui for configruing the toolbar.
### `Toolbar.set_visible_state`
Sets the visible state of the toolbar for a player.
If a state is not given then this becomes `toggle_visible_state` and returns the new visible state.
The name difference compared to [`Gui.toggle_visible_state`](#guitoggle_visible_state) despite same beaviour is due to the expected use case for each function.
### `Toolbar.get_visible_state`
Gets the visible state of the toolbar for a player.
### `Toolbar.set_button_toggled_state`
Sets the toggled state for a toolbar button. It is expected that the element define is given not the LuaGuiElement because all instances of a toolbar button should be in sync, and the toolbar does not expose the LuaGuiElement except though [`Gui.get_top_element`](#guiget_top_element).
If a state is not given then this becomes `toggle_button_toggled_state` and returns the new visible state.
### `Toolbar.get_button_toggled_state`
Gets the toggled state for a toolbar button. It is expected that the element define is given not the LuaGuiElement because all instances of a toolbar button should be in sync, and the toolbar does not expose the LuaGuiElement except though [`Gui.get_top_element`](#guiget_top_element).
### `Toolbar.set_left_element_visible_state`
Sets the visible state for a left element. It is expected that the element define is given not the LuaGuiElement because only a single left element can exist, and the toolbar does not expose the LuaGuiElement except though [`Gui.get_left_element`](#guiget_left_element).
If a state is not given then this becomes `toggle_left_element_visible_state` and returns the new visible state.
### `Toolbar.get_left_element_visible_state`
Gets the visible state for a left element. It is expected that the element define is given not the LuaGuiElement because only a single left element can exist, and the toolbar does not expose the LuaGuiElement except though [`Gui.get_left_element`](#guiget_left_element).
### `Toolbar.has_visible_buttons`
Returns true if the player has any visible toolbar buttons.
### `Toolbar.has_visible_left_elements`
Returns true if the player has any visible left elements.
### `Toolbar.create_button`
Creates a new element define representing a toolbar button.
The new button is automaticaly registered to the top flow, has the option to auto toggle, and the option to have a left element linked to it. As this creates a new element define the name provided must be unqiue to your mod.
### `Toolbar.set_state`
Sets the whole state of the toolbar for a player, the value given should be a value previously returned from [`Toolbar.get_state`](#toolbarget_state).
### `Toolbar.get_state`
Gets the whol state of the toolbar for a player which can later be restored with [`Toolbar.set_state`](#toolbarset_state).

BIN
exp_gui/docs/toolbox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

View File

@@ -3,37 +3,48 @@ local Storage = require("modules/exp_util/storage")
local ExpElement = require("modules/exp_gui/prototype")
--- @alias ExpGui.VisibleCallback fun(player: LuaPlayer, element: LuaGuiElement): boolean
--- @alias Gui.VisibleCallback fun(player: LuaPlayer, element: LuaGuiElement): boolean
--- @class ExpGui.player_elements
--- @class Gui.player_elements
--- @field top table<string, LuaGuiElement?>
--- @field left table<string, LuaGuiElement?>
--- @field relative table<string, LuaGuiElement?>
--- @type table<uint, ExpGui.player_elements>
--- @type table<uint, Gui.player_elements> | table<"_debug", boolean>
local player_elements = {}
Storage.register(player_elements, function(tbl)
player_elements = tbl
end)
--- @class ExpGui
local ExpGui = {
element = ExpElement.create,
property_from_arg = ExpElement.property_from_arg,
property_from_name = ExpElement.property_from_name,
top_elements = {}, --- @type table<ExpElement, ExpGui.VisibleCallback | boolean>
left_elements = {}, --- @type table<ExpElement, ExpGui.VisibleCallback | boolean>
relative_elements = {}, --- @type table<ExpElement, ExpGui.VisibleCallback | boolean>
--- @class Gui
local Gui = {
define = ExpElement.new,
from_argument = ExpElement.from_argument,
from_name = ExpElement.from_name,
no_return = ExpElement.no_return,
top_elements = {}, --- @type table<ExpElement, Gui.VisibleCallback | boolean>
left_elements = {}, --- @type table<ExpElement, Gui.VisibleCallback | boolean>
relative_elements = {}, --- @type table<ExpElement, Gui.VisibleCallback | boolean>
}
local mod_gui = require("mod-gui")
ExpGui.get_top_flow = mod_gui.get_button_flow
ExpGui.get_left_flow = mod_gui.get_frame_flow
Gui.get_top_flow = mod_gui.get_button_flow
Gui.get_left_flow = mod_gui.get_frame_flow
--- Set the gui to redraw all elements on join, helps with style debugging
--- @param state boolean
function Gui._debug(state)
if state == nil then
player_elements._debug = true
else
player_elements._debug = state
end
end
--- Get a player from an element or gui event
--- @param input LuaGuiElement | { player_index: uint } | { element: LuaGuiElement }
--- @return LuaPlayer
function ExpGui.get_player(input)
function Gui.get_player(input)
if type(input) == "table" and not input.player_index then
return assert(game.get_player(input.element.player_index))
end
@@ -44,7 +55,7 @@ end
--- @param element LuaGuiElement
--- @param state boolean?
--- @return boolean
function ExpGui.toggle_enabled_state(element, state)
function Gui.toggle_enabled_state(element, state)
if not element or not element.valid then return false end
if state == nil then
state = not element.enabled
@@ -57,7 +68,7 @@ end
--- @param element LuaGuiElement
--- @param state boolean?
--- @return boolean
function ExpGui.toggle_visible_state(element, state)
function Gui.toggle_visible_state(element, state)
if not element or not element.valid then return false end
if state == nil then
state = not element.visible
@@ -68,41 +79,41 @@ end
--- Destroy an element if it exists and is valid
--- @param element LuaGuiElement?
function ExpGui.destroy_if_valid(element)
function Gui.destroy_if_valid(element)
if not element or not element.valid then return end
element.destroy()
end
--- Register a element define to be drawn to the top flow on join
--- @param define ExpElement
--- @param visible ExpGui.VisibleCallback | boolean | nil
function ExpGui.add_top_element(define, visible)
assert(ExpGui.top_elements[define.name] == nil, "Element is already added to the top flow")
ExpGui.top_elements[define] = visible or false
--- @param visible Gui.VisibleCallback | boolean | nil
function Gui.add_top_element(define, visible)
assert(Gui.top_elements[define.name] == nil, "Element is already added to the top flow")
Gui.top_elements[define] = visible or false
end
--- Register a element define to be drawn to the left flow on join
--- @param define ExpElement
--- @param visible ExpGui.VisibleCallback | boolean | nil
function ExpGui.add_left_element(define, visible)
assert(ExpGui.left_elements[define.name] == nil, "Element is already added to the left flow")
ExpGui.left_elements[define] = visible or false
--- @param visible Gui.VisibleCallback | boolean | nil
function Gui.add_left_element(define, visible)
assert(Gui.left_elements[define.name] == nil, "Element is already added to the left flow")
Gui.left_elements[define] = visible or false
end
--- Register a element define to be drawn to the relative flow on join
--- @param define ExpElement
--- @param visible ExpGui.VisibleCallback | boolean | nil
function ExpGui.add_relative_element(define, visible)
assert(ExpGui.relative_elements[define.name] == nil, "Element is already added to the relative flow")
ExpGui.relative_elements[define] = visible or false
--- @param visible Gui.VisibleCallback | boolean | nil
function Gui.add_relative_element(define, visible)
assert(Gui.relative_elements[define.name] == nil, "Element is already added to the relative flow")
Gui.relative_elements[define] = visible or false
end
--- Register a element define to be drawn to the top flow on join
--- @param define ExpElement
--- @param player LuaPlayer
--- @return LuaGuiElement
function ExpGui.get_top_element(define, player)
function Gui.get_top_element(define, player)
return assert(player_elements[player.index].top[define.name], "Element is not on the top flow")
end
@@ -110,7 +121,7 @@ end
--- @param define ExpElement
--- @param player LuaPlayer
--- @return LuaGuiElement
function ExpGui.get_left_element(define, player)
function Gui.get_left_element(define, player)
return assert(player_elements[player.index].left[define.name], "Element is not on the left flow")
end
@@ -118,13 +129,13 @@ end
--- @param define ExpElement
--- @param player LuaPlayer
--- @return LuaGuiElement
function ExpGui.get_relative_element(define, player)
function Gui.get_relative_element(define, player)
return assert(player_elements[player.index].relative[define.name], "Element is not on the relative flow")
end
--- Ensure all the correct elements are visible and exist
--- @param player LuaPlayer
--- @param element_defines table<ExpElement, ExpGui.VisibleCallback | boolean>
--- @param element_defines table<ExpElement, Gui.VisibleCallback | boolean>
--- @param elements table<string, LuaGuiElement?>
--- @param parent LuaGuiElement
local function ensure_elements(player, element_defines, elements, parent)
@@ -135,7 +146,7 @@ local function ensure_elements(player, element_defines, elements, parent)
if not element or not element.valid then
element = assert(define(parent), "Element define did not return an element: " .. define.name)
elements[define.name] = element
if type(visible) == "function" then
visible = visible(player, element)
end
@@ -153,39 +164,45 @@ end
--- Ensure all elements have been created
--- @param event EventData.on_player_created | EventData.on_player_joined_game
function ExpGui._ensure_consistency(event)
function Gui._ensure_consistency(event)
local player = assert(game.get_player(event.player_index))
local elements = player_elements[event.player_index]
if not elements then
elements = {
top = {},
left = {},
relative = {},
}
player_elements[event.player_index] = elements
local elements = player_elements[event.player_index] or {
top = {},
left = {},
relative = {},
}
player_elements[event.player_index] = elements
if player_elements._debug and event.name == defines.events.on_player_joined_game then
log("Gui debug active, clearing gui for: " .. player.name)
player.gui.relative.clear()
player.gui.screen.clear()
player.gui.center.clear()
player.gui.left.clear()
player.gui.top.clear()
end
ensure_elements(player, ExpGui.top_elements, elements.top, ExpGui.get_top_flow(player))
ensure_elements(player, ExpGui.left_elements, elements.left, ExpGui.get_left_flow(player))
ensure_elements(player, ExpGui.relative_elements, elements.relative, player.gui.relative)
ensure_elements(player, Gui.top_elements, elements.top, Gui.get_top_flow(player))
ensure_elements(player, Gui.left_elements, elements.left, Gui.get_left_flow(player))
ensure_elements(player, Gui.relative_elements, elements.relative, player.gui.relative)
-- This check isn't needed, but allows the toolbar file to be deleted without modifying any lib code
if ExpGui.toolbar then
if Gui.toolbar then
--- @diagnostic disable-next-line invisible
ExpGui.toolbar._create_elements(player)
Gui.toolbar._create_elements(player)
--- @diagnostic disable-next-line invisible
ExpGui.toolbar._ensure_consistency(player)
Gui.toolbar._ensure_consistency(player)
end
end
--- Rerun the visible check for relative elements
--- @param event EventData.on_gui_opened
local function on_gui_opened(event)
local player = ExpGui.get_player(event)
local player = Gui.get_player(event)
local original_element = event.element
for define, visible in pairs(ExpGui.relative_elements) do
local element = ExpGui.get_relative_element(define, player)
for define, visible in pairs(Gui.relative_elements) do
local element = Gui.get_relative_element(define, player)
if type(visible) == "function" then
visible = visible(player, element)
@@ -204,10 +221,10 @@ end
local e = defines.events
local events = {
[e.on_player_created] = ExpGui._ensure_consistency,
[e.on_player_joined_game] = ExpGui._ensure_consistency,
[e.on_player_created] = Gui._ensure_consistency,
[e.on_player_joined_game] = Gui._ensure_consistency,
[e.on_gui_opened] = on_gui_opened,
}
ExpGui.events = events
return ExpGui
Gui.events = events
return Gui

View File

@@ -1,4 +1,4 @@
--[[-- ExpGui - GuiData
--[[-- Gui - GuiData
Provides a method of storing data for elements, players, and forces under a given scope.
This is not limited to GUI element definitions but this is the most common use case.
]]
@@ -6,10 +6,10 @@ This is not limited to GUI element definitions but this is the most common use c
local ExpUtil = require("modules/exp_util")
local Storage = require("modules/exp_util/storage")
--- @type table<string, ExpGui.GuiData_Raw>
--- @type table<string, GuiData_Raw>
local scope_data = {}
--- @type table<string, ExpGui.GuiData_Internal>
--- @type table<string, GuiData_Internal>
local registered_scopes = {}
--- @type table<uint, uint> Reg -> Player Index
@@ -38,12 +38,12 @@ Storage.register({
end
end)
--- @class ExpGui_GuiData
--- @class _GuiData
local GuiData = {
_scopes = registered_scopes,
}
--- @class ExpGui.GuiData_Raw
--- @class GuiData_Raw
--- @field element_data table<uint, table<uint, any>?>?
--- @field player_data table<uint, any>?
--- @field force_data table<uint, any>?
@@ -51,13 +51,13 @@ local GuiData = {
-- This class has no prototype methods
-- Keep this in sync with DataKeys to block arbitrary strings
--- @class ExpGui.GuiData_Internal
--- @class GuiData_Internal
--- @field _scope string
--- @field _raw ExpGui.GuiData_Raw
--- @field _raw GuiData_Raw
-- This class has no prototype methods
-- Do add keys to _raw without also referencing scope_data
--- @class ExpGui.GuiData: ExpGui.GuiData_Internal
--- @class GuiData: GuiData_Internal
--- @field element_data table<uint, table<uint, any>>
--- @field player_data table<uint, any>
--- @field force_data table<uint, any>
@@ -70,7 +70,7 @@ GuiData._metatable = {
}
--- Return the index for a given key
--- @param self ExpGui.GuiData_Internal
--- @param self GuiData_Internal
--- @param key DataKeys | DataKey
--- @return any
function GuiData._metatable.__index(self, key)
@@ -105,7 +105,7 @@ end
--- Set the value index of a given key
-- Internal type is not used here to allow for creation of storage
--- @param self ExpGui.GuiData
--- @param self GuiData
--- @param key DataKey
--- @param value unknown
function GuiData._metatable.__newindex(self, key, value)
@@ -134,7 +134,7 @@ end
--- Create the data object for a given scope
--- @param scope string
--- @return ExpGui.GuiData
--- @return GuiData
function GuiData.create(scope)
ExpUtil.assert_not_runtime()
assert(GuiData._scopes[scope] == nil, "Scope already exists with name: " .. scope)
@@ -145,15 +145,15 @@ function GuiData.create(scope)
}
GuiData._scopes[scope] = instance
--- @cast instance ExpGui.GuiData
--- @cast instance GuiData
return setmetatable(instance, GuiData._metatable)
end
--- Get the link to an existing data scope
--- @param scope string
--- @return ExpGui.GuiData
--- @return GuiData
function GuiData.get(scope)
return GuiData._scopes[scope] --[[ @as ExpGui.GuiData ]]
return GuiData._scopes[scope] --[[ @as GuiData ]]
end
--- Used to clean up data from destroyed elements

View File

@@ -1,18 +1,24 @@
--- @class ExpGui
local ExpGui = require("modules/exp_gui")
--- @class Gui
local Gui = require("modules/exp_gui")
--- @class ExpGui.elements
local elements = {}
ExpGui.elements = elements
--- @class Gui.elements
local Elements = {}
Gui.elements = Elements
--- @class Gui.elements.aligned_flow.opts
--- @field horizontal_align ("left" | "center" | "right")?
--- @field vertical_align ("top" | "center" | "bottom")?
--- A flow which aligns its content as specified
elements.aligned_flow = ExpGui.element("aligned_flow")
--- @class Gui.elements.aligned_flow: ExpElement
--- @overload fun(parent: LuaGuiElement, opts: Gui.elements.aligned_flow.opts?): LuaGuiElement
Elements.aligned_flow = Gui.define("aligned_flow")
:draw{
type = "flow",
name = ExpGui.property_from_arg("name"),
name = Gui.from_argument("name"),
}
:style(function(def, element, parent, opts)
opts = opts or {}
opts = opts or {} --- @cast opts Gui.elements.aligned_flow.opts
local vertical_align = opts.vertical_align or "center"
local horizontal_align = opts.horizontal_align or "right"
return {
@@ -22,15 +28,18 @@ elements.aligned_flow = ExpGui.element("aligned_flow")
vertically_stretchable = vertical_align ~= "center",
horizontally_stretchable = horizontal_align ~= "center",
}
end)
end) --[[ @as any ]]
--- A solid horizontal white bar element
elements.bar = ExpGui.element("bar")
--- @class Gui.elements.bar: ExpElement
--- @overload fun(parent: LuaGuiElement, width: number?): LuaGuiElement
Elements.bar = Gui.define("bar")
:draw{
type = "progressbar",
value = 1,
}
:style(function(def, element, parent, width)
--- @cast width number?
local style = element.style
style.color = { r = 255, g = 255, b = 255 }
style.height = 4
@@ -39,50 +48,58 @@ elements.bar = ExpGui.element("bar")
else
style.horizontally_stretchable = true
end
end)
end) --[[ @as any ]]
--- A label which is centered
elements.centered_label = ExpGui.element("centered_label")
--- @class Gui.elements.centered_label: ExpElement
--- @overload fun(parent: LuaGuiElement, width: number, caption: LocalisedString, tooltip: LocalisedString?): LuaGuiElement
Elements.centered_label = Gui.define("centered_label")
:draw{
type = "label",
caption = ExpGui.property_from_arg(2),
tooltip = ExpGui.property_from_arg(3),
caption = Gui.from_argument(2),
tooltip = Gui.from_argument(3),
}
:style{
horizontal_align = "center",
single_line = false,
width = ExpGui.property_from_arg(1),
}
width = Gui.from_argument(1),
} --[[ @as any ]]
--- A label which has two white bars on either side of it
elements.title_label = ExpGui.element("title_label")
--- @class Gui.elements.title_label: ExpElement
--- @overload fun(parent: LuaGuiElement, width: number, caption: LocalisedString, tooltip: LocalisedString?): LuaGuiElement
Elements.title_label = Gui.define("title_label")
:draw(function(def, parent, width, caption, tooltip)
local flow =
parent.add{
type = "flow"
}
--- @cast width number
--- @cast caption LocalisedString
--- @cast tooltip LocalisedString?
local flow = parent.add{
type = "flow"
}
flow.style.vertical_align = "center"
elements.bar(flow, width)
local label =
flow.add{
type = "label",
style = "frame_title",
caption = caption,
tooltip = tooltip,
}
Elements.bar(flow, width)
elements.bar(flow)
local label = flow.add{
type = "label",
style = "frame_title",
caption = caption,
tooltip = tooltip,
}
Elements.bar(flow)
return label
end)
end) --[[ @as any ]]
--- A fixed size vertical scroll pane
elements.scroll_pane = ExpGui.element("scroll_pane")
--- @class Gui.elements.scroll_pane: ExpElement
--- @overload fun(parent: LuaGuiElement, maximal_height: number, name: string?): LuaGuiElement
Elements.scroll_pane = Gui.define("scroll_pane")
:draw{
type = "scroll-pane",
name = ExpGui.property_from_arg(2),
name = Gui.from_argument(2),
direction = "vertical",
horizontal_scroll_policy = "never",
vertical_scroll_policy = "auto",
@@ -90,14 +107,19 @@ elements.scroll_pane = ExpGui.element("scroll_pane")
}
:style{
padding = { 1, 3 },
maximal_height = ExpGui.property_from_arg(1),
maximal_height = Gui.from_argument(1),
horizontally_stretchable = true,
}
} --[[ @as any ]]
--- A fixed size vertical scroll pane containing a table
elements.scroll_table = ExpGui.element("scroll_table")
:draw(function(def, parent, height, column_count, scroll_name)
local scroll_pane = elements.scroll_pane(parent, height, scroll_name)
--- @class Gui.elements.scroll_table: ExpElement
--- @overload fun(parent: LuaGuiElement, maximal_height: number, column_count: number, scroll_name: string?): LuaGuiElement
Elements.scroll_table = Gui.define("scroll_table")
:draw(function(def, parent, maximal_height, column_count, scroll_name)
--- @cast maximal_height number
--- @cast column_count number
--- @cast scroll_name string?
local scroll_pane = Elements.scroll_pane(parent, maximal_height, scroll_name)
return scroll_pane.add{
type = "table",
@@ -106,25 +128,25 @@ elements.scroll_table = ExpGui.element("scroll_table")
}
end)
:style{
padding = 0,
cell_padding = 0,
padding = { 3, 2 },
cell_padding = 1,
vertical_align = "center",
horizontally_stretchable = true,
}
} --[[ @as any ]]
--- A container frame
elements.container = ExpGui.element("container")
:draw(function(def, parent, width, container_name)
local container =
parent.add{
type = "frame",
name = container_name,
}
--- @class Gui.elements.container: ExpElement
--- @overload fun(parent: LuaGuiElement, minimal_width: number?, name: string?): LuaGuiElement
Elements.container = Gui.define("container")
:draw(function(def, parent, minimal_width, name)
--- @cast minimal_width number?
--- @cast name string?
local container = parent.add{
type = "frame",
name = name,
}
local style = container.style
style.horizontally_stretchable = false
style.minimal_width = width
style.padding = 2
container.style.padding = 2
return container.add{
type = "frame",
@@ -135,59 +157,176 @@ elements.container = ExpGui.element("container")
end)
:style{
vertically_stretchable = false,
}
horizontally_stretchable = false,
minimal_width = Gui.from_argument(1),
} --[[ @as any ]]
--- Get the root element of a container
--- @param container LuaGuiElement
--- @return LuaGuiElement
function Elements.container.get_root_element(container)
return container.parent
end
--- A frame within a container
elements.subframe_base = ExpGui.element("container_subframe")
--- @class Gui.elements.subframe_base: ExpElement
--- @overload fun(parent: LuaGuiElement, style: string, name: string?): LuaGuiElement
Elements.subframe_base = Gui.define("container_subframe")
:draw{
type = "frame",
name = ExpGui.property_from_arg(2),
style = ExpGui.property_from_arg(1),
name = Gui.from_argument(2),
style = Gui.from_argument(1),
}
:style{
height = 0,
minimal_height = 36,
padding = { 3, 4 },
padding = { 3, 6, 0, 6 },
use_header_filler = false,
horizontally_stretchable = true,
}
} --[[ @as any ]]
--- @class Gui.elements.header.opts
--- @field name string?
--- @field caption LocalisedString?
--- @field tooltip LocalisedString?
--- A header frame within a container
elements.header = ExpGui.element("container_header")
--- @class Gui.elements.header: ExpElement
--- @field label LuaGuiElement
--- @overload fun(parent: LuaGuiElement, opts: Gui.elements.header.opts?): LuaGuiElement
Elements.header = Gui.define("container_header")
:draw(function(def, parent, opts)
opts = opts or {}
local subframe = elements.subframe_base(parent, "subheader_frame", opts.name)
opts = opts or {} --- @cast opts Gui.elements.header.opts
local subframe = Elements.subframe_base(parent, "subheader_frame", opts.name)
if opts.caption then
subframe.add{
type = "label",
name = opts.label_name,
name = "label",
caption = opts.caption,
tooltip = opts.tooltip,
style = "frame_title",
}
end
return opts.no_flow and subframe or elements.aligned_flow(subframe, { name = "flow" })
end)
subframe.add{ type = "empty-widget" }.style.horizontally_stretchable = true
return subframe
end) --[[ @as any ]]
--- @class Gui.elements.footer.opts
--- @field name string?
--- @field caption LocalisedString?
--- @field tooltip LocalisedString?
--- A footer frame within a container
elements.footer = ExpGui.element("container_footer")
--- @class Gui.elements.footer: ExpElement
--- @field label LuaGuiElement
--- @overload fun(parent: LuaGuiElement, opts: Gui.elements.footer.opts?): LuaGuiElement
Elements.footer = Gui.define("container_footer")
:draw(function(def, parent, opts)
opts = opts or {}
local subframe = elements.subframe_base(parent, "subfooter_frame", opts.name)
opts = opts or {} --- @cast opts Gui.elements.footer.opts
local subframe = Elements.subframe_base(parent, "subfooter_frame", opts.name)
if opts.caption then
subframe.add{
type = "label",
name = opts.label_name,
name = "label",
caption = opts.caption,
tooltip = opts.tooltip,
style = "frame_title",
}
end
return opts.no_flow and subframe or elements.aligned_flow(subframe, { name = "flow" })
end)
subframe.add{ type = "empty-widget" }.style.horizontally_stretchable = true
return elements
return subframe
end) --[[ @as any ]]
--- A button used to destroy its target when clicked, intended for screen frames
--- @class Gui.elements.screen_frame_close: ExpElement
--- @field data table<LuaGuiElement, LuaGuiElement>
--- @overload fun(parent: LuaGuiElement, target: LuaGuiElement): LuaGuiElement
Elements.screen_frame_close = Gui.define("screen_frame_close")
:draw{
type = "sprite-button",
style = "frame_action_button",
sprite = "utility/close",
}
:element_data(
Gui.from_argument(1)
)
:on_click(function(def, player, element, event)
--- @cast def Gui.elements.screen_frame_close
Gui.destroy_if_valid(def.data[element])
end) --[[ @as any ]]
--- A draggable frame with close button and button flow
--- @class Gui.elements.screen_frame: ExpElement
--- @field data table<LuaGuiElement, LuaGuiElement?>
--- @overload fun(parent: LuaGuiElement, caption: LocalisedString?, button_flow: boolean?): LuaGuiElement
Elements.screen_frame = Gui.define("screen_frame")
:draw(function(def, parent, caption, button_flow)
local container = parent.add{
type = "frame",
direction = "vertical",
}
container.style.padding = 2
local header = container.add{
type = "flow",
}
if caption then
local label = header.add{
type = "label",
caption = caption,
style = "frame_title"
}
label.style.top_margin = -3
label.style.bottom_padding = 3
end
local filler = header.add{
type = "empty-widget",
style = "draggable_space_header",
}
filler.drag_target = container
local filler_style = filler.style
filler_style.horizontally_stretchable = true
filler_style.vertically_stretchable = true
filler_style.left_margin = caption and 4 or 0
filler_style.natural_height = 24
filler_style.height = 24
if button_flow then
local _button_flow = header.add{ type = "flow" }
def.data[container] = _button_flow
_button_flow.style.padding = 0
end
Elements.screen_frame_close(header, container)
return container.add{
type = "frame",
direction = "vertical",
style = "inside_shallow_frame_packed",
}
end) --[[ @as any ]]
--- Get the button flow for a screen frame
--- @param screen_frame LuaGuiElement
--- @return LuaGuiElement
function Elements.screen_frame.get_button_flow(screen_frame)
return assert(Elements.screen_frame.data[screen_frame.parent], "Screen frame has no button flow")
end
--- Get the root element of a screen frame
--- @param screen_frame LuaGuiElement
--- @return LuaGuiElement
function Elements.screen_frame.get_root_element(screen_frame)
return screen_frame.parent
end
return Elements

View File

@@ -1,12 +1,12 @@
--[[-- ExpGui - GuiData
--[[-- Gui - GuiData
Provides a method of storing elements created for a player and provide a global iterator for them.
]]
local ExpUtil = require("modules/exp_util")
local Storage = require("modules/exp_util/storage")
--- @alias ExpGui_GuiIter.FilterType LuaPlayer | LuaForce | LuaPlayer[] | nil
--- @alias ExpGui_GuiIter.ReturnType fun(): LuaPlayer?, LuaGuiElement?
--- @alias GuiIter.FilterType LuaPlayer | LuaForce | LuaPlayer[] | nil
--- @alias GuiIter.ReturnType fun(): LuaPlayer?, LuaGuiElement?
--- @type table<string, table<uint, table<uint, LuaGuiElement>>>
local registered_scopes = {}
@@ -23,7 +23,7 @@ Storage.register({
registration_numbers = tbl.registration_numbers
end)
--- @class ExpGui_GuiIter
--- @class GuiIter
local GuiIter = {
_scopes = registered_scopes,
}
@@ -76,7 +76,7 @@ end
--- Iterate over all valid elements for a player
--- @param scope string
--- @param player LuaPlayer
--- @return ExpGui_GuiIter.ReturnType
--- @return GuiIter.ReturnType
function GuiIter.player_elements(scope, player)
if not player.valid then return no_loop end
@@ -98,7 +98,7 @@ end
--- @param scope string
--- @param players LuaPlayer[]
--- @param online boolean?
--- @return ExpGui_GuiIter.ReturnType
--- @return GuiIter.ReturnType
function GuiIter.filtered_elements(scope, players, online)
local scope_elements = registered_scopes[scope]
if not scope_elements then return no_loop end
@@ -125,7 +125,7 @@ end
--- Iterate over all valid elements
--- @param scope string
--- @return ExpGui_GuiIter.ReturnType
--- @return GuiIter.ReturnType
function GuiIter.all_elements(scope)
local scope_elements = registered_scopes[scope]
if not scope_elements then return no_loop end
@@ -161,8 +161,8 @@ end
--- Iterate over all valid gui elements for all players
--- @param scope string
--- @param filter ExpGui_GuiIter.FilterType
--- @return ExpGui_GuiIter.ReturnType
--- @param filter GuiIter.FilterType
--- @return GuiIter.ReturnType
function GuiIter.get_tracked_elements(scope, filter)
local class_name = ExpUtil.get_class_name(filter)
if class_name == "nil" then
@@ -184,8 +184,8 @@ end
--- Iterate over all valid gui elements for all online players
--- @param scope string
--- @param filter ExpGui_GuiIter.FilterType
--- @return ExpGui_GuiIter.ReturnType
--- @param filter GuiIter.FilterType
--- @return GuiIter.ReturnType
function GuiIter.get_online_elements(scope, filter)
local class_name = ExpUtil.get_class_name(filter)
if class_name == "nil" then

View File

@@ -4,7 +4,7 @@ local ExpUtil = require("modules/exp_util")
local GuiData = require("modules/exp_gui/data")
local GuiIter = require("modules/exp_gui/iter")
--- @class ExpGui_ExpElement
--- @class _ExpElement
local ExpElement = {
_elements = {}, --- @type table<string, ExpElement>
}
@@ -13,14 +13,14 @@ ExpElement.events = {}
--- @alias ExpElement.DrawCallback fun(def: ExpElement, parent: LuaGuiElement, ...): LuaGuiElement?, function?
--- @alias ExpElement.PostDrawCallback fun(def: ExpElement, element: LuaGuiElement?, parent: LuaGuiElement, ...): table?
--- @alias ExpElement.PostDrawCallbackAdder fun(self: ExpElement, definition: table | ExpElement.PostDrawCallback): ExpElement
--- @alias ExpElement.PostDrawCallbackAdder fun(self: ExpElement, definition: table | number | string | boolean | ExpElement.PostDrawCallback): ExpElement
--- @alias ExpElement.EventHandler<E> fun(def: ExpElement, player: LuaPlayer, element: LuaGuiElement, event: E)
--- @alias ExpElement.OnEventAdder<E> fun(self: ExpElement, handler: fun(def: ExpElement, player: LuaPlayer, element: LuaGuiElement, event: E)): ExpElement
--- @class ExpElement._debug
--- @field defined_at string
--- @field draw_definition table?
--- @field draw_from_args table?
--- @field draw_signals table?
--- @field style_definition table?
--- @field style_from_args table?
--- @field element_data_definition table?
@@ -34,7 +34,7 @@ ExpElement.events = {}
--- @class ExpElement
--- @field name string
--- @field data ExpGui.GuiData
--- @field data GuiData
--- @field _debug ExpElement._debug
--- @field _draw ExpElement.DrawCallback?
--- @field _style ExpElement.PostDrawCallback?
@@ -55,40 +55,95 @@ ExpElement._metatable = {
__class = "ExpElement",
}
--- Used to signal the intentional lack of a return value from draw
--- @return nil, function
function ExpElement.no_return()
return nil, ExpElement.no_return
end
--- Used to signal that the property should be the same as the define name
--- @return function
function ExpElement.property_from_name()
return ExpElement.property_from_name
function ExpElement.from_name()
return ExpElement.from_name
end
--- Used to signal that a property should be taken from the arguments, a string means key of last arg
--- @param arg number|string|nil
--- @return [function, number|string|nil]
function ExpElement.property_from_arg(arg)
return { ExpElement.property_from_arg, arg }
--- @generic A: number|string, D: any
--- @param arg A
--- @param default D?
--- @return [function, A, D]
function ExpElement.from_argument(arg, default)
return { ExpElement.from_argument, assert(arg), default }
end
--- @alias ExpElement._signals table<string|number, [string, any]> | [function, string|number, any|nil]
--- Extract the from args properties from a definition
--- @param definition table
--- @return table<string|number, string>
--- @return ExpElement._signals
function ExpElement._prototype:_extract_signals(definition)
local from_args = {}
for k, v in pairs(definition) do
if v == ExpElement.property_from_arg then
from_args[#from_args + 1] = k
elseif v == ExpElement.property_from_name then
definition[k] = self.name
elseif type(v) == "table" and rawget(v, 1) == ExpElement.property_from_arg then
from_args[v[2] or (#from_args + 1)] = k
-- Check if the definition is from_argument
if definition[1] == ExpElement.from_argument then
return definition
end
-- Otherwise check if any of the values are from_argument or from_name
local signals = {}
for prop, value in pairs(definition) do
if value == ExpElement.from_argument then
error("ExpElement.from_argument must be called with an argument index / key")
elseif value == ExpElement.from_name then
definition[prop] = self.name
elseif type(value) == "table" and rawget(value, 1) == ExpElement.from_argument then
local key = value[2] or (#signals + 1)
signals[key] = { prop, value[3] }
end
end
return from_args
return signals
end
--- Apply the previously extracted signals to a definition using the create args
--- @param definition table
--- @param signals ExpElement._signals
--- @param args table
--- @return any
function ExpElement._prototype:_apply_signals(definition, signals, args)
local last = args[#args] or args -- 'or args' used instead of empty table
-- Check if the root is from_argument
if signals[1] == ExpElement.from_argument then
local key, rtn = signals[2], nil
if type(key) == "string" then
rtn = last[key]
else
rtn = args[key]
end
if rtn == nil then
return signals[3]
end
return rtn
end
-- Otherwise set the properties of the definition
for i, pair in pairs(signals) do
local key = pair[1]
if type(i) == "string" then
definition[key] = last[i]
else
definition[key] = args[i]
end
if definition[key] == nil then
definition[key] = pair[2]
end
end
return definition
end
--- Register a new instance of a prototype
--- @param name string
--- @return ExpElement
function ExpElement.create(name)
function ExpElement.new(name)
ExpUtil.assert_not_runtime()
local module_name = ExpUtil.get_module_name(2)
local element_name = module_name .. "/" .. name
@@ -103,7 +158,7 @@ function ExpElement.create(name)
},
}
ExpElement._elements[element_name] = instance
ExpElement._elements[element_name] = instance --[[ @as ExpElement ]]
return setmetatable(instance, ExpElement._metatable)
end
@@ -122,11 +177,14 @@ function ExpElement._prototype:create(parent, ...)
assert(self._draw, "Element does not have a draw definition")
local element, status = self:_draw(parent, ...)
local player = assert(game.get_player(parent.player_index))
if status ~= ExpElement.no_return then
assert(element and element.object_name == "LuaGuiElement", "Draw did not return a LuaGuiElement")
end
if self._style then
assert(element, "Cannot set style when no element was returned by draw definition")
local style = self:_style(element, parent, ...)
if style then
assert(element, "Cannot set style when no element was returned by draw definition")
local element_style = element.style
for k, v in pairs(style) do
element_style[k] = v
@@ -135,23 +193,23 @@ function ExpElement._prototype:create(parent, ...)
end
if self._element_data then
assert(element, "Cannot set element data when no element was returned by draw definition")
local data = self:_element_data(element, parent, ...)
if data then
assert(element, "Cannot set element data when no element was returned by draw definition")
if data ~= nil and self.data[element] == nil then
self.data[element] = data
end
end
if self._player_data then
local data = self:_player_data(element, parent, ...)
if data then
if data ~= nil and self.data[player] == nil then
self.data[player] = data
end
end
if self._force_data then
local data = self:_force_data(element, parent, ...)
if data then
if data ~= nil and self.data[player.force] == nil then
self.data[player.force] = data
end
end
@@ -161,7 +219,9 @@ function ExpElement._prototype:create(parent, ...)
if data then
local global_data = self.data.global_data
for k, v in pairs(data) do
global_data[k] = v
if global_data[k] == nil then
global_data[k] = v
end
end
end
end
@@ -189,8 +249,8 @@ end
--- Add a temp draw definition, useful for when working top down
--- @return ExpElement
function ExpElement._prototype:dev()
log("Dev draw used for " .. self.name)
function ExpElement._prototype:empty()
log("empty draw used for " .. self.name)
return self:draw{
type = "flow"
}
@@ -203,71 +263,67 @@ end
--- @return ExpElement
function ExpElement._prototype:draw(definition)
ExpUtil.assert_not_runtime()
if type(definition) == "function" then
if type(definition) == "function" and definition ~= ExpElement.from_argument then
self._draw = definition
return self
end
assert(type(definition) == "table", "Definition is not a table or function")
local from_args = self:_extract_signals(definition)
local signals = self:_extract_signals(definition)
self._debug.draw_definition = definition
if not next(from_args) then
if not next(signals) then
-- If no signals then skip var arg
self._draw = function(_, parent)
return parent.add(definition)
end
return self
end
self._debug.draw_from_args = from_args
self._debug.draw_signals = signals
self._draw = function(_, parent, ...)
local args = { ... }
local last = args[#args] or args
-- 'or args' used instead of empty table
for i, k in pairs(from_args) do
if type(i) == "string" then
definition[k] = last[i]
else
definition[k] = args[i]
end
end
return parent.add(definition)
return parent.add(self:_apply_signals(definition, signals, { ... }))
end
return self
end
--- Create a definition adder for anything other than draaw
--- Create a definition adder for anything other than draw
--- @param prop_name string
--- @param debug_def string
--- @param debug_args string
--- @param debug_signals string
--- @return ExpElement.PostDrawCallbackAdder
local function definition_factory(prop_name, debug_def, debug_args)
local function definition_factory(prop_name, debug_def, debug_signals)
return function(self, definition)
ExpUtil.assert_not_runtime()
if type(definition) == "function" then
if type(definition) == "function" and definition ~= ExpElement.from_argument then
self[prop_name] = definition
return self
end
assert(type(definition) == "table", "Definition is not a table or function")
local from_args = self:_extract_signals(definition)
self._debug[debug_def] = definition
if #from_args == 0 then
if type(definition) ~= "table" and definition ~= ExpElement.from_argument then
-- Primitive value so we can just return it
self[prop_name] = function(_, _, _)
return definition
end
return self
end
self._debug[debug_args] = from_args
self[prop_name] = function(_, _, _, ...)
local args = { ... }
for i, k in pairs(from_args) do
definition[k] = args[i]
assert(type(definition) == "table", "Definition is not a table or function")
local signals = self:_extract_signals(definition)
self._debug[debug_def] = definition
if not next(signals) then
-- If no signals then skip var arg
self[prop_name] = function(_, _, _)
return table.deep_copy(definition)
end
return definition
return self
end
self._debug[debug_signals] = signals
self[prop_name] = function(_, _, _, ...)
return self:_apply_signals(table.deep_copy(definition), signals, { ... })
end
return self
@@ -276,34 +332,34 @@ end
--- Set the style definition
--- @type ExpElement.PostDrawCallbackAdder
ExpElement._prototype.style = definition_factory("_style", "style_definition", "style_from_args")
ExpElement._prototype.style = definition_factory("_style", "style_definition", "style_signals")
--- Set the default element data
--- @type ExpElement.PostDrawCallbackAdder
ExpElement._prototype.element_data = definition_factory("_element_data", "element_data_definition", "element_data_from_args")
ExpElement._prototype.element_data = definition_factory("_element_data", "element_data_definition", "element_data_signals")
--- Set the default player data
--- @type ExpElement.PostDrawCallbackAdder
ExpElement._prototype.player_data = definition_factory("_player_data", "player_data_definition", "player_data_from_args")
ExpElement._prototype.player_data = definition_factory("_player_data", "player_data_definition", "player_data_signals")
--- Set the default force data
--- @type ExpElement.PostDrawCallbackAdder
ExpElement._prototype.force_data = definition_factory("_force_data", "force_data_definition", "force_data_from_args")
ExpElement._prototype.force_data = definition_factory("_force_data", "force_data_definition", "force_data_signals")
--- Set the default global data
--- @type ExpElement.PostDrawCallbackAdder
ExpElement._prototype.global_data = definition_factory("_global_data", "global_data_definition", "global_data_from_args")
ExpElement._prototype.global_data = definition_factory("_global_data", "global_data_definition", "global_data_signals")
--- Iterate the tracked elements of all players
--- @param filter ExpGui_GuiIter.FilterType
--- @return ExpGui_GuiIter.ReturnType
--- @param filter GuiIter.FilterType
--- @return GuiIter.ReturnType
function ExpElement._prototype:tracked_elements(filter)
return GuiIter.get_tracked_elements(self.name, filter)
end
--- Iterate the tracked elements of all online players
--- @param filter ExpGui_GuiIter.FilterType
--- @return ExpGui_GuiIter.ReturnType
--- @param filter GuiIter.FilterType
--- @return GuiIter.ReturnType
function ExpElement._prototype:online_elements(filter)
return GuiIter.get_online_elements(self.name, filter)
end
@@ -337,10 +393,10 @@ function ExpElement._prototype:link_element(element)
element_tags = {}
end
local event_tags = element_tags["ExpGui"]
local event_tags = element_tags["Gui"]
if not event_tags then
event_tags = {}
element_tags["ExpGui"] = event_tags
element_tags["Gui"] = event_tags
end
--- @cast event_tags string[]
@@ -363,10 +419,10 @@ function ExpElement._prototype:unlink_element(element)
return element, ExpElement._prototype.unlink_element
end
local event_tags = element_tags["ExpGui"]
local event_tags = element_tags["Gui"]
if not event_tags then
event_tags = {}
element_tags["ExpGui"] = event_tags
element_tags["Gui"] = event_tags
end
--- @cast event_tags string[]
@@ -381,7 +437,7 @@ local function event_handler(event)
local element = event.element
if not element or not element.valid then return end
local event_tags = element.tags and element.tags["ExpGui"]
local event_tags = element.tags and element.tags["Gui"]
if not event_tags then return end
--- @cast event_tags string[]

View File

@@ -1,9 +1,9 @@
--- @class ExpGui
local ExpGui = require("modules/exp_gui")
--- @class Gui
local Gui = require("modules/exp_gui")
--- @class ExpGui.styles
--- @class Gui.styles
local styles = {}
ExpGui.styles = styles
Gui.styles = styles
function styles.sprite(style)
style = style or {}

View File

@@ -1,6 +1,6 @@
--- @class ExpGui
local ExpGui = require("modules/exp_gui")
--- @class Gui
local Gui = require("modules/exp_gui")
local ExpElement = require("modules/exp_gui/prototype")
local mod_gui = require("mod-gui")
@@ -9,9 +9,9 @@ local toolbar_button_active_style = "menu_button_continue"
local toolbar_button_size = 36
local toolbar_button_small = 20
--- @class ExpGui.toolbar
--- @class Gui.toolbar
local Toolbar = {}
ExpGui.toolbar = Toolbar
Gui.toolbar = Toolbar
local elements = {}
Toolbar.elements = elements
@@ -20,13 +20,27 @@ local toolbar_buttons = {} --- @type ExpElement[]
local left_elements_with_button = {} --- @type table<ExpElement, ExpElement>
local buttons_with_left_element = {} --- @type table<string, ExpElement>
--- Called when toolbar button toggle state is changed.
Toolbar.on_gui_button_toggled = script.generate_event_name()
--- @class (exact) EventData.on_gui_button_toggled: EventData
--- @field element LuaGuiElement
--- @field state boolean
--- @class _ExpElement._prototype
--- @field on_button_toggled ExpElement.OnEventAdder<EventData.on_gui_button_toggled>
--- @diagnostic disable-next-line: invisible, inject-field
function ExpElement._prototype.on_button_toggled(self, handler)
return self:on_event(Toolbar.on_gui_button_toggled, handler)
end
--- Set the visible state of the toolbar
--- @param player LuaPlayer
--- @param state boolean? toggles if nil
--- @return boolean
function Toolbar.set_visible_state(player, state)
-- Update the top flow
local top_flow = assert(ExpGui.get_top_flow(player).parent)
local top_flow = assert(Gui.get_top_flow(player).parent)
if state == nil then state = not top_flow.visible end
top_flow.visible = state
@@ -47,33 +61,46 @@ end
--- @param player LuaPlayer
--- @return boolean
function Toolbar.get_visible_state(player)
local top_flow = assert(ExpGui.get_top_flow(player).parent)
local top_flow = assert(Gui.get_top_flow(player).parent)
return top_flow.visible
end
--- Set the toggle state of a toolbar button, does not check for a linked left element
--- Set the toggle state of a toolbar button
--- @param define ExpElement
--- @param player LuaPlayer
--- @param state boolean? toggles if nil
--- @param _from_left boolean?
--- @return boolean
function Toolbar.set_button_toggled_state(define, player, state)
local top_element = assert(ExpGui.get_top_element(define, player), "Element is not on the top flow")
function Toolbar.set_button_toggled_state(define, player, state, _from_left)
local top_element = assert(Gui.get_top_element(define, player), "Element is not on the top flow")
if state == nil then state = top_element.style.name == toolbar_button_default_style end
for _, element in define:tracked_elements(player) do
local original_width, original_height = element.style.minimal_width, element.style.maximal_height
element.style = state and toolbar_button_active_style or toolbar_button_default_style
local left_element = buttons_with_left_element[define.name]
if left_element and not _from_left then
return Toolbar.set_left_element_visible_state(left_element, player, state)
end
for _, button in define:tracked_elements(player) do
local original_width, original_height = button.style.minimal_width, button.style.maximal_height
button.style = state and toolbar_button_active_style or toolbar_button_default_style
-- Make the extra required adjustments
local style = element.style
local style = button.style
style.minimal_width = original_width
style.maximal_height = original_height
if element.type == "sprite-button" then
if button.type == "sprite-button" then
style.padding = -2
else
style.font = "default-semibold"
style.padding = 0
end
script.raise_event(Toolbar.on_gui_button_toggled, {
name = Toolbar.on_gui_button_toggled,
tick = game.tick,
element = button,
state = state,
})
end
return state
@@ -84,7 +111,7 @@ end
--- @param player LuaPlayer
--- @return boolean
function Toolbar.get_button_toggled_state(define, player)
local element = assert(ExpGui.get_top_element(define, player), "Element is not on the top flow")
local element = assert(Gui.get_top_element(define, player), "Element is not on the top flow")
return element.style.name == toolbar_button_active_style
end
@@ -95,14 +122,14 @@ end
--- @param _skip_consistency boolean?
--- @return boolean
function Toolbar.set_left_element_visible_state(define, player, state, _skip_consistency)
local element = assert(ExpGui.get_left_element(define, player), "Element is not on the left flow")
local element = assert(Gui.get_left_element(define, player), "Element is not on the left flow")
if state == nil then state = not element.visible end
element.visible = state
-- Check if there is a linked toolbar button and update it
local button = left_elements_with_button[define]
if button then
Toolbar.set_button_toggled_state(button, player, state)
Toolbar.set_button_toggled_state(button, player, state, true)
end
-- This check is O(n^2) when setting all left elements to hidden, so internals can it
@@ -122,7 +149,7 @@ end
--- @param player LuaPlayer
--- @return boolean
function Toolbar.get_left_element_visible_state(define, player)
local element = assert(ExpGui.get_left_element(define, player), "Element is not on the left flow")
local element = assert(Gui.get_left_element(define, player), "Element is not on the left flow")
return element.visible
end
@@ -130,11 +157,11 @@ end
--- @param player any
--- @return boolean
function Toolbar.has_visible_buttons(player)
local top_flow = ExpGui.get_top_flow(player)
local settings_button = ExpGui.get_top_element(elements.close_toolbar, player)
local top_flow = Gui.get_top_flow(player)
local settings_button = Gui.get_top_element(elements.close_toolbar, player)
for _, element in pairs(top_flow.children) do
if element.visible and element ~= settings_button then
for _, top_element in pairs(top_flow.children) do
if top_element.visible and top_element ~= settings_button then
return true
end
end
@@ -146,11 +173,11 @@ end
--- @param player any
--- @return boolean
function Toolbar.has_visible_left_elements(player)
local left_flow = ExpGui.get_left_flow(player)
local core_button_flow = ExpGui.get_left_element(elements.core_button_flow, player)
local left_flow = Gui.get_left_flow(player)
local core_button_flow = Gui.get_left_element(elements.core_button_flow, player)
for _, element in pairs(left_flow.children) do
if element.visible and element ~= core_button_flow then
for _, left_element in pairs(left_flow.children) do
if left_element.visible and left_element ~= core_button_flow then
return true
end
end
@@ -158,14 +185,14 @@ function Toolbar.has_visible_left_elements(player)
return false
end
--- @class ExpGui.toolbar.create_button__param: LuaGuiElement.add_param.sprite_button, LuaGuiElement.add_param.button
--- @class Gui.toolbar.create_button__param: LuaGuiElement.add_param.sprite_button, LuaGuiElement.add_param.button
--- @field name string
--- @field type nil
--- @field left_element ExpElement| nil
--- @field visible ExpGui.VisibleCallback | boolean | nil
--- @field visible Gui.VisibleCallback | boolean | nil
--- Create a toolbar button
--- @param options ExpGui.toolbar.create_button__param
--- @param options Gui.toolbar.create_button__param
--- @return ExpElement
function Toolbar.create_button(options)
-- Extract the custom options from the element.add options
@@ -187,7 +214,7 @@ function Toolbar.create_button(options)
end
-- Create the new element define
local toolbar_button = ExpGui.element(name)
local toolbar_button = Gui.define(name)
:track_all_elements()
:draw(options)
:style{
@@ -215,12 +242,12 @@ function Toolbar.create_button(options)
-- Add the define to the top flow and return
toolbar_buttons[#toolbar_buttons + 1] = toolbar_button
ExpGui.add_top_element(toolbar_button, visible)
Gui.add_top_element(toolbar_button, visible)
return toolbar_button
end
--- Toggles the toolbar settings, RMB will instead hide the toolbar
elements.close_toolbar = ExpGui.element("close_toolbar")
elements.close_toolbar = Gui.define("close_toolbar")
:draw{
type = "sprite-button",
sprite = "utility/preset",
@@ -241,7 +268,7 @@ elements.close_toolbar = ExpGui.element("close_toolbar")
end)
--- Shows the toolbar, if no buttons are visible then it shows the settings instead
elements.open_toolbar = ExpGui.element("open_toolbar")
elements.open_toolbar = Gui.define("open_toolbar")
:track_all_elements()
:draw{
type = "sprite-button",
@@ -263,7 +290,7 @@ elements.open_toolbar = ExpGui.element("open_toolbar")
end)
--- Hides all left elements when clicked
elements.clear_left_flow = ExpGui.element("clear_left_flow")
elements.clear_left_flow = Gui.define("clear_left_flow")
:track_all_elements()
:draw{
type = "sprite-button",
@@ -278,7 +305,7 @@ elements.clear_left_flow = ExpGui.element("clear_left_flow")
}
:on_click(function(def, player, element)
element.visible = false
for define in pairs(ExpGui.left_elements) do
for define in pairs(Gui.left_elements) do
if define ~= elements.core_button_flow then
Toolbar.set_left_element_visible_state(define, player, false, true)
end
@@ -286,7 +313,7 @@ elements.clear_left_flow = ExpGui.element("clear_left_flow")
end)
--- Contains the two buttons on the left flow
elements.core_button_flow = ExpGui.element("core_button_flow")
elements.core_button_flow = Gui.define("core_button_flow")
:draw(function(def, parent)
local flow = parent.add{
type = "flow",
@@ -327,15 +354,15 @@ local function move_toolbar_button(player, item, offset)
list.swap_children(old_index, new_index)
-- Swap the position in the top flow, offset by 1 because of settings button
local top_flow = ExpGui.get_top_flow(player)
local top_flow = Gui.get_top_flow(player)
top_flow.swap_children(old_index + 1, new_index + 1)
-- Check if the element has a left element to move
local left_element = buttons_with_left_element[item.name]
local other_left_element = buttons_with_left_element[other_item.name]
if left_element and other_left_element then
local element = ExpGui.get_left_element(left_element, player)
local other_element = ExpGui.get_left_element(other_left_element, player)
local element = Gui.get_left_element(left_element, player)
local other_element = Gui.get_left_element(other_left_element, player)
local left_index = element.get_index_in_parent()
local other_index = other_element.get_index_in_parent()
element.parent.swap_children(left_index, other_index)
@@ -360,15 +387,15 @@ local function move_toolbar_button(player, item, offset)
end
end
--- @alias ExpGui.ToolbarOrder { name: string, favourite: boolean }[]
--- @alias Gui.ToolbarOrder { name: string, favourite: boolean }[]
--- Reorder the toolbar buttons
--- @param player LuaPlayer
--- @param order ExpGui.ToolbarOrder
--- @param order Gui.ToolbarOrder
function Toolbar.set_order(player, order)
local list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]]
local left_flow = ExpGui.get_left_flow(player)
local top_flow = ExpGui.get_top_flow(player)
local left_flow = Gui.get_left_flow(player)
local top_flow = Gui.get_top_flow(player)
-- Reorder the buttons
local left_index = 1
@@ -380,7 +407,7 @@ function Toolbar.set_order(player, order)
-- Switch the toolbar button order
local element_define = ExpElement.get(item_state.name)
local toolbar_button = ExpGui.get_top_element(element_define, player)
local toolbar_button = Gui.get_top_element(element_define, player)
top_flow.swap_children(index + 1, toolbar_button.get_index_in_parent())
-- Update the children buttons
@@ -392,21 +419,21 @@ function Toolbar.set_order(player, order)
-- Switch the left element order
local left_define = buttons_with_left_element[item_state.name]
if left_define then
local left_element = ExpGui.get_left_element(left_define, player)
local left_element = Gui.get_left_element(left_define, player)
left_flow.swap_children(left_index, left_element.get_index_in_parent())
left_index = left_index + 1
end
end
end
--- @class (exact) ExpGui.ToolbarState
--- @field order ExpGui.ToolbarOrder
--- @class (exact) Gui.ToolbarState
--- @field order Gui.ToolbarOrder
--- @field open string[]
--- @field visible boolean
--- Reorder the toolbar buttons and set the open state of the left flows
--- @param player LuaPlayer
--- @param state ExpGui.ToolbarState
--- @param state Gui.ToolbarState
function Toolbar.set_state(player, state)
Toolbar.set_order(player, state.order)
Toolbar.set_visible_state(player, state.visible)
@@ -420,7 +447,7 @@ function Toolbar.set_state(player, state)
end
-- Make all other elements hidden
for left_element in pairs(ExpGui.left_elements) do
for left_element in pairs(Gui.left_elements) do
if not done[left_element] then
Toolbar.set_left_element_visible_state(left_element, player, false, true)
end
@@ -435,7 +462,7 @@ end
--- Get the full toolbar state for a player
--- @param player LuaPlayer
--- @return ExpGui.ToolbarState
--- @return Gui.ToolbarState
function Toolbar.get_state(player)
-- Get the order of toolbar buttons
local order = {}
@@ -446,7 +473,7 @@ function Toolbar.get_state(player)
-- Get the names of all open left elements
local open, open_index = {}, 1
for left_element in pairs(ExpGui.left_elements) do
for left_element in pairs(Gui.left_elements) do
if Toolbar.get_left_element_visible_state(left_element, player) then
open[open_index] = left_element.name
open_index = open_index + 1
@@ -462,10 +489,10 @@ function Toolbar._create_elements(player)
-- Add any missing items to the gui
local toolbar_list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]]
local previous_last_index = #toolbar_list.children_names
for define in pairs(ExpGui.top_elements) do
for define in pairs(Gui.top_elements) do
if define ~= elements.close_toolbar and toolbar_list[define.name] == nil then
local element = elements.toolbar_list_item(toolbar_list, define)
element.visible = ExpGui.get_top_element(define, player).visible
element.visible = Gui.get_top_element(define, player).visible
end
end
@@ -489,8 +516,8 @@ function Toolbar._ensure_consistency(player)
local list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]]
for _, button in ipairs(toolbar_buttons) do
-- Update the visible state based on if the player is allowed the button
local element = ExpGui.get_top_element(button, player)
local allowed = ExpGui.top_elements[button]
local element = Gui.get_top_element(button, player)
local allowed = Gui.top_elements[button]
if type(allowed) == "function" then
allowed = allowed(player, element)
end
@@ -500,9 +527,10 @@ function Toolbar._ensure_consistency(player)
-- Update the toggle state and hide the linked left element if the button is not allowed
local left_define = buttons_with_left_element[button.name]
if left_define then
local left_element = ExpGui.get_left_element(left_define, player)
Toolbar.set_button_toggled_state(button, player, left_element.visible)
if not allowed then
if allowed then
local left_element = Gui.get_left_element(left_define, player)
Toolbar.set_button_toggled_state(button, player, left_element.visible, true)
else
Toolbar.set_left_element_visible_state(left_define, player, false)
end
end
@@ -515,7 +543,7 @@ function Toolbar._ensure_consistency(player)
end
-- Update open_toolbar
local top_flow = assert(ExpGui.get_top_flow(player).parent)
local top_flow = assert(Gui.get_top_flow(player).parent)
for _, open_toolbar in elements.open_toolbar:tracked_elements(player) do
open_toolbar.visible = not top_flow.visible
end
@@ -528,15 +556,15 @@ function Toolbar._ensure_consistency(player)
end
do
local default_order --- @type ExpGui.ToolbarOrder
local default_order --- @type Gui.ToolbarOrder
--- Gets the default order for the toolbar
--- @return ExpGui.ToolbarOrder
--- @return Gui.ToolbarOrder
function Toolbar.get_default_order()
if default_order then return default_order end
local index = 1
default_order = {}
for define in pairs(ExpGui.top_elements) do
for define in pairs(Gui.top_elements) do
if define ~= elements.close_toolbar then
default_order[index] = { name = define.name, favourite = true }
index = index + 1
@@ -548,7 +576,7 @@ do
end
--- Toggle the visibility of the toolbar, does not care if buttons are visible
elements.toggle_toolbar = ExpGui.element("toggle_toolbar")
elements.toggle_toolbar = Gui.define("toggle_toolbar")
:track_all_elements()
:draw{
type = "sprite-button",
@@ -557,7 +585,7 @@ elements.toggle_toolbar = ExpGui.element("toggle_toolbar")
style = "tool_button",
auto_toggle = true,
}
:style(ExpGui.styles.sprite{
:style(Gui.styles.sprite{
size = 22,
})
:on_click(function(def, player, element)
@@ -565,14 +593,14 @@ elements.toggle_toolbar = ExpGui.element("toggle_toolbar")
end)
--- Reset the toolbar to its default state
elements.reset_toolbar = ExpGui.element("reset_toolbar")
elements.reset_toolbar = Gui.define("reset_toolbar")
:draw{
type = "sprite-button",
sprite = "utility/reset",
style = "shortcut_bar_button_red",
tooltip = { "exp-gui_toolbar-settings.reset" },
}
:style(ExpGui.styles.sprite{
:style(Gui.styles.sprite{
size = 22,
padding = -1,
})
@@ -581,13 +609,13 @@ elements.reset_toolbar = ExpGui.element("reset_toolbar")
end)
--- Move an item up/left on the toolbar
elements.move_item_up = ExpGui.element("move_item_up")
elements.move_item_up = Gui.define("move_item_up")
:draw{
type = "sprite-button",
sprite = "utility/speed_up",
tooltip = { "exp-gui_toolbar-settings.move-up" },
}
:style(ExpGui.styles.sprite{
:style(Gui.styles.sprite{
size = toolbar_button_small,
})
:on_click(function(def, player, element)
@@ -596,13 +624,13 @@ elements.move_item_up = ExpGui.element("move_item_up")
end)
--- Move an item down/right on the toolbar
elements.move_item_down = ExpGui.element("move_item_down")
elements.move_item_down = Gui.define("move_item_down")
:draw{
type = "sprite-button",
sprite = "utility/speed_down",
tooltip = { "exp-gui_toolbar-settings.move-down" },
}
:style(ExpGui.styles.sprite{
:style(Gui.styles.sprite{
size = toolbar_button_small,
})
:on_click(function(def, player, element)
@@ -611,11 +639,11 @@ elements.move_item_down = ExpGui.element("move_item_down")
end)
--- Set an item as a favourite, making it appear on the toolbar
elements.set_favourite = ExpGui.element("set_favourite")
elements.set_favourite = Gui.define("set_favourite")
:draw(function(def, parent, item_define)
--- @cast item_define ExpElement
local player = ExpGui.get_player(parent)
local top_element = ExpGui.get_top_element(item_define, player)
local player = Gui.get_player(parent)
local top_element = Gui.get_top_element(item_define, player)
return parent.add{
type = "checkbox",
@@ -631,7 +659,7 @@ elements.set_favourite = ExpGui.element("set_favourite")
}
:on_checked_state_changed(function(def, player, element)
local define = ExpElement.get(element.tags.element_name --[[ @as string ]])
local top_element = ExpGui.get_top_element(define, player)
local top_element = Gui.get_top_element(define, player)
local had_visible = Toolbar.has_visible_buttons(player)
top_element.visible = element.state
@@ -649,7 +677,7 @@ elements.set_favourite = ExpGui.element("set_favourite")
end
end)
elements.toolbar_list_item = ExpGui.element("toolbar_list_item")
elements.toolbar_list_item = Gui.define("toolbar_list_item")
:draw(function(def, parent, item_define)
--- @cast item_define ExpElement
local data = {}
@@ -665,8 +693,8 @@ elements.toolbar_list_item = ExpGui.element("toolbar_list_item")
-- Add the button and the icon edit button
local element = item_define(flow)
local player = ExpGui.get_player(parent)
local top_element = ExpGui.get_top_element(item_define, player)
local player = Gui.get_player(parent)
local top_element = Gui.get_top_element(item_define, player)
copy_style(top_element, element)
-- Add the favourite checkbox and label
@@ -683,7 +711,7 @@ elements.toolbar_list_item = ExpGui.element("toolbar_list_item")
end)
--- Main list for all toolbar items
elements.toolbar_list = ExpGui.element("toolbar_list")
elements.toolbar_list = Gui.define("toolbar_list")
:draw(function(def, parent)
local scroll = parent.add{
type = "scroll-pane",
@@ -708,16 +736,16 @@ elements.toolbar_list = ExpGui.element("toolbar_list")
}
-- The main container for the toolbar settings
elements.toolbar_settings = ExpGui.element("toolbar_settings")
elements.toolbar_settings = Gui.define("toolbar_settings")
:draw(function(def, parent)
-- Draw the container
local frame = ExpGui.elements.container(parent, 268)
local frame = Gui.elements.container(parent, 268)
frame.style.maximal_width = 268
frame.style.minimal_width = 268
-- Draw the header
local player = ExpGui.get_player(parent)
local header = ExpGui.elements.header(frame, {
local player = Gui.get_player(parent)
local header = Gui.elements.header(frame, {
caption = { "exp-gui_toolbar-settings.main-caption" },
tooltip = { "exp-gui_toolbar-settings.main-tooltip" },
})
@@ -732,8 +760,8 @@ elements.toolbar_settings = ExpGui.element("toolbar_settings")
return frame.parent
end)
ExpGui.add_left_element(elements.core_button_flow, true)
ExpGui.add_left_element(elements.toolbar_settings, false)
ExpGui.add_top_element(elements.close_toolbar, true)
Gui.add_left_element(elements.core_button_flow, true)
Gui.add_left_element(elements.toolbar_settings, false)
Gui.add_top_element(elements.close_toolbar, true)
return Toolbar

573
exp_gui/readme.md Normal file
View File

@@ -0,0 +1,573 @@
# ExpGui Framework Guide
This guide presents best practices for creating GUIs in Factorio and demonstrates how the ExpGui framework can help streamline development.
If youre new to GUI creation in Factorio, we recommend starting with [Therenas' helpful tutorial](https://github.com/ClaudeMetz/UntitledGuiGuide/wiki) to build a solid foundation.
We also recommend [Raiguard's style guide](https://man.sr.ht/~raiguard/factorio-gui-style-guide/) if you are looking to mimic the design principles employed by the game's own guis.
Additional details for the methods available in this library can be found in the [api reference](./docs/reference.md).
All examples in this guide assume you are using popular VSCode extensions for Factorio development, such as FMTK (`justarandomgeek.factoriomod-debug`) and LuaLs (`sumneko.lua`).
## Motivation
This section explains the motivation behind the framework and the thinking that shaped its design.
A full breakdown of my thoughts can be found in the [motivation document](./docs/motivation.md).
It began with a frustration I had using existing libraries, which often required separating elements, event logic, and data into different parts of the codebase.
While that may work for some, I found it unintuitive and overly sparse; jumping between element definitions and event handlers became tiring.
I wanted something that felt more cohesive, where all the code for a GUI component could live in one place, and where data and behaviour were naturally scoped to that component.
The result is a framework built around four focused modules: [ExpElement](./docs/motivation.md#expelement), [GuiData](./docs/motivation.md#guidata), [GuiIter](./docs/motivation.md#guiiter), and [Toolbar](./docs/motivation.md#toolbar).
Each part solves a specific problem but is designed to work seamlessly with the others.
Additionally, naming conventions and calling patterns play a key role in this framework; theyre not just style preferences, but fundamental to how everything fits together conceptually.
The goal throughout has been to reduce boilerplate, increase clarity, and make GUI development feel more natural.
If youve ever felt like writing GUI code was harder than it needed to be, this framework might be what youre looking for.
## Glossary
- "element": A `LuaGuiElement` instance representing a GUI component in Factorio.
- "element definition": An instance of `ExpElement` containing the details and logic for creating a GUI element.
- "element method": A custom method added to an element definition, the first argument should be a LuaGuiElement belonging to the definition.
- "gui event handler": A function called in response to a GUI-specific event (e.g., a button click).
- "event handler": A function called in response to any event occurring in the game.
- "gui data": Persistent data stored and accessed by GUI event handlers, typically linked to specific elements.
- "pre-defined element": An element provided by the framework that includes built-in styles and layout logic for common GUI patterns.
- "draw function": A function defined within an element definition that constructs and returns the corresponding `LuaGuiElement`.
- "draw table": A plain Lua table used as a shorthand for a draw function, typically employed when no dynamic logic or nested elements are required.
- "toolbar": A shared GUI area for displaying module buttons, often positioned at the top-left of the screen.
- "cache": Temporarily stored computed data used to avoid repeated expensive calculations; cached variables are often prefixed with an underscore to indicate they should not be accessed directly.
### Naming conventions
- `Elements`: A common local variable name used to store returned element definitions from a GUI module, capitalised to avoid shadowing more generic variable names like `elements`.
- `Elements.container`: A common name for the variable holding the root element definition, especially when registering it to be drawn on the left flow.
- `scroll_table`: A built-in composite element in the framework designed to display tabular data with scrolling support.
- `calculate`: A method that generates the data required to draw or refresh GUI elements.
- `refresh`: A method that updates the GUI elements to reflect the current data without fully reconstructing the interface.
- `link`: A method used to associate elements with data or other elements after initial creation.
- `element_data`: A lua table containing all data required to draw or refresh an specific element.
- `row_elements`: A lua table containing all elements in a row of a gui table.
- `row_data`: A lua table containing all data required to draw or refresh a row of a table.
- `add_row`: A method added to table element definitions that appends a single row based on provided data.
- `tooltip-`: Prefix used for locale string keys to indicate their intended display location, improving clarity and organisation. Others include `caption-` and `error-`
## The boiler plate
### Tip 1: Use `event_handler.add_lib`
When working with multiple GUI modules, it's a good practice to use `event_handler.add_lib` to register them.
This approach helps avoid conflicts between event handlers and makes it easier to scale your mod's interface across different components.
`event_handler` is a core lualib provided by factorio, it's full path is `__core__.lualib.event_handler` but should be required as `event_handler`
The ExpGui framework relies on this same mechanism to register your GUI event handlers, so following this pattern ensures compatibility and consistency.
By organising your code into libraries and registering them via add_lib, you also improve modularity; making it easier to reason about each part of your GUI separately and debug issues when they arise.
```lua
local add_lib = require("event_handler").add_lib
add_lib(require("modules/your_module/gui_foo"))
```
### Tip 2: Return your element definitions
Each GUI module should return its element definitions as part of its public interface.
Doing so offers two major advantages:
- It makes debugging easier; you can inspect the associated GUI data stored for each element definition, which helps track down issues with state or layout.
- It allows your definitions to be reused across other modules, encouraging modularity and reducing duplication.
Following both Tip 1 and Tip 2 gives you a clean boilerplate structure for starting any GUI module.
In most cases, the returned definitions are assigned to a local variable named `Elements` (capitalised).
This helps avoid naming conflicts with local variables like `elements`, which are commonly used within the same scope.
```lua
local ExpUtil = require("modules/exp_util")
local Gui = require("modules/exp_gui")
--- @class ExampleGui.elements
local Elements = {}
local e = defines.events
return {
elements = Elements,
events = {
},
on_nth_tick = {
},
}
```
## Creating a root element
### Tip 3: Register a single root element definition to be drawn when a player is created
Most GUI modules are built around a single root element; an element that contains all other child elements and acts as the entry point to your interface.
In typical usage, youll want this root element to be created automatically when a new player joins the game because it allows for a persistent GUI state.
Manually creating it in your own `on_player_created` handler can lead to redundant code and inconsistency.
Instead, the framework provides a way to register your root element definition, and it will handle drawing it for every newly created player.
This ensures the element is always present and your GUI state is initialised properly without extra boilerplate.
To create a new element definition you should call `Gui.define` with the name of your element, this name must be unique within your mod.
In the below example `Elements.container` is defined, this will be expanded in the next tip as all defines should have a draw method rather than using empty.
```lua
--- The root element of the example gui
Elements.container = Gui.define("container")
:empty()
--- Add the element to the left flow with it hidden by default
Gui.add_left_element(Elements.container, false)
```
### Tip 4: Use pre-defined elements as a starting point
The framework includes several [pre defined elements](./module/elements.lua) that help maintain a consistent appearance and layout across GUI modules.
These elements simplify the process of creating common interface structures while encouraging visual consistency throughout your mod.
When using a pre defined element, or when defining your own element that contains other elements, you should include a `draw` method in the element definition.
This method is responsible for building the GUI structure at runtime.
The return value is used by the framework to attach event handlers and track elements, after which it is given to the caller.
For left side frames, such as in the example module, the `container` element is a good place to start.
It provides a standard layout that works well for persistent side panels.
```lua
Elements.container = Gui.define("container")
:draw(function(def, parent)
-- def is self reference to the element definition
-- parent is where you should add your new element
-- to create an element you call its definition passing a parent element
local container = Gui.elements.container(parent)
-- header is another common pre-defined element, footer exists too
-- note, adding custom draw arguments will be covered later
local header = Gui.elements.header(container, {
caption = { "example-gui.caption-main" }
})
-- for elements registered to be drawn on join, the root element should be returned
-- note that container.parent ~= parent because container is a composite element, so use get root
return Gui.elements.container.get_root_element(container)
end)
```
### Tip 5: Use the toolbar for buttons
The framework includes a shared toolbar that allows GUI modules to register buttons in a consistent and user friendly way. These buttons follow a standard style and layout, helping your interface stay visually unified across modules.
The toolbar also supports player customisation via the toolbox.
Players can choose which buttons are visible and rearrange their order according to personal preference.
![toolbox](./docs/toolbox.png)
A toolbar button does not require a `left_element`, but if one is provided, the framework will automatically register an `on_click` handler.
This handler toggles the visibility of the named element.
You can optionally define a `visible` function as part of the button definition.
This function is called when the button is first drawn and determines whether a specific player is allowed to see the button.
```lua
Gui.toolbar.create_button{
name = "toggle_example_gui",
sprite = "item/iron-plate",
tooltip = { "example-gui.tooltip-main" },
left_element = Elements.container,
visible = function(player, element)
-- this button will only be visible to admins
return player.admin
end,
}
```
## Receiving user input
### Tip 6: For simpler elements use draw tables
If an element does not contain any child elements and all of its properties are static, you can define it using a draw table instead of a full function.
A draw table is simply a Lua table that describes the structure and properties of a single GUI element.
The framework automatically converts this into a draw function internally, making it a convenient shorthand for simple elements.
A direct comparison of the two can be found in the [motivation section](./docs/motivation.md#expelement).
This approach helps reduce boilerplate and improves readability when creating basic buttons, labels, flows, or other standalone GUI elements.
### Tip 7: Table definitions also work for applying styles
Styles can be applied to an element using the `:style(function(def, parent, element) end)` method.
However, for simpler elements like buttons and labels, you can also define the style directly as a table.
This shorthand approach allows you to include static style properties (such as font, padding, or alignment) in the same table format used to define the element itself.
It helps keep simple element definitions concise and easy to read.
### Tip 8: Use gui event handler methods
Instead of writing separate event handlers and manually routing events, you can define GUI event handler methods directly on your element definitions.
The framework will automatically register these methods and filter incoming events, calling the correct handler based on the element involved.
This approach simplifies your code by keeping the event logic close to the element it concerns.
It also reduces boilerplate and improves maintainability by leveraging the frameworks built-in event dispatch system.
All gui event handlers are supported following the naming convention of `on_gui_click` -> `on_click`
```lua
Elements.example_button = Gui.define("example_button")
:draw{
caption = "Hi",
tooltip = { "example-gui.tooltip-example-button" },
-- string styles are applied during draw
style = "shortcut_bar_button",
}
:style{
size = 24,
}
:on_click(function(def, player, element, event)
player.print("Hello, World!")
end)
-- within Elements.container:draw
Elements.example_button(header)
```
## Displaying data
### Tip 9: Scroll tables are your friend
Displaying data in a scrollable table is a common GUI pattern, and this framework includes a pre defined composite element specifically for this purpose.
In the upcoming examples, you will see type annotations used with the element definitions.
These annotations are necessary due to limitations in LuaLS, including the explicit type casts (using as) used to help the language server correctly interpret overloaded functions.
```lua
--- @class ExpGui_Example.elements.display_table: ExpElement
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.display_table = Gui.define("display_table")
:draw(function(def, parent)
-- 2nd arg is max vertical size, 3rd arg is column count
return Gui.elements.scroll_table(parent, 200, 3)
end) --[[ @as any ]]
```
### Tip 10: Separate data calculation and drawing
To avoid repeating code, its best to calculate the data you want to display in a separate function from the one that creates the row elements.
This separation makes your code cleaner and more modular.
It also allows you to reuse the calculated data in other methods such as `refresh`, where the GUI needs to update without rebuilding everything from scratch.
The return type of this function will typically be a collection of locale strings and other values that will be displayed in your GUI.
For tables, this should be named `row_data` but other elements should be `display_data` or include more specific details like `team_data`.
The value should then be passed to create or refresh an element or table row, tip 14 has an example of this.
For intensive calculations called frequently, you can incorporate passing a previous table allocation, see second example.
```lua
--- @class Elements.display_table.row_data
--- @field name string
--- @field sprite string
--- @field caption LocalisedString
--- @field count number
--- @param inventory LuaInventory
--- @param item_name string
--- @return Elements.display_table.row_data
function Elements.display_table.calculate_row_data(inventory, item_name)
return {
name = item_name,
sprite = "item/" .. item_name,
name = { "item-name." .. item_name },
count = inventory.get_item_count(item_name)
}
end
--- @param inventory LuaInventory
--- @param item_name string
--- @param row_data Elements.display_table.row_data
--- @return Elements.display_table.row_data
function Elements.display_table.calculate_row_data(inventory, item_name, row_data)
row_data = row_data or { name = {} }
row_data.name = item_name
row_data.sprite = "item/" .. item_name
row_data.name[1] = "item-name." .. item_name
row_data.count = inventory.get_item_count(item_name)
return row_data
end
```
### Tip 11: Use a function to add rows rather than an element define
Element definitions are intended for creating single elements or composite elements with a clear structure.
When they are used to create multiple rows in tables, managing data ownership and state can become confusing.
To keep your code clean and your data flow clear, its recommended to extend your table element definition with an `add_row` method.
This method handles adding new rows one at a time, keeping row creation logic separate from element definition and making it easier to manage dynamic content.
Tip 14 shows an example of `add_row` being used within the container draw function.
### Tip 12: Store row elements in gui data
When adding elements to a row, you will often need to reference those elements later for updates or interaction.
To manage this, use gui data to store references to these elements.
The framework provides a convenient initialiser method, `:element_data{}`, which creates an empty table at `def.data[element]`.
This table can be used to store per-row GUI element references or other related data.
For good encapsulation, it is best practice to access gui data only within the methods of the element definition it belongs to.
This keeps your data management organised and reduces the risk of unintended side effects.
```lua
--- @class Elements.display_table.row_elements
--- @field sprite LuaGuiElement
--- @field label_name LuaGuiElement
--- @field label_count LuaGuiElement
--- @param display_table LuaGuiElement
--- @param row_data Elements.display_table.row_data
function Elements.display_table.add_row(display_table, row_data)
local rows = Elements.display_table.data[display_table]
assert(rows[row_data.name] == nil, "Row already exists")
local visible = row_data.count > 0
rows[row_data.name] = {
sprite = display_table.add{
type = "sprite",
sprite = row_data.sprite,
visible = visible
},
label_name = display_table.add{
type = "label",
caption = row_data.name,
visible = visible
},
label_count = display_table.add{
type = "label",
caption = tostring(row_data.count),
visible = visible
},
}
end
```
## Refreshing displayed data
### Tip 13: Use 'refresh' functions to optimise updates
Instead of clearing and rebuilding the entire table every time it changes, its more efficient to update the existing GUI elements directly.
To keep your code clean and modular, place this update logic inside a `refresh` function.
This function adjusts the current elements to match the new data state without unnecessary reconstruction.
You may also encounter variants like `refresh_all`, `refresh_online`, `refresh_force` to indicate different scopes or contexts for the update.
```lua
--- @param display_table LuaGuiElement
--- @param row_data Elements.display_table.row_data
function Elements.display_table.refresh_row(display_table, row_data)
local row = assert(Elements.display_table.data[display_table][row_data.name])
row.label_count.caption = tostring(row_data.count)
local visible = row_data.count > 0
for _, element in pairs(row) do
element.visible = visible
end
end
```
### Tip 14: Pass references rather than names
Instead of using element names to identify GUI elements, its better to pass direct references to those elements whenever possible.
Using references reduces the impact of GUI restructuring and improves performance by avoiding lookups.
However, be cautious not to use references to elements that might be destroyed, as this can lead to invalid references and crashes.
To maintain encapsulation and avoid tight coupling, passing references often means youll need to design your methods to accept custom arguments explicitly.
For example, updating a buttons event handler to receive element references directly rather than traversing the GUI tree.
```lua
--- @class ExpGui_Example.elements.example_button: ExpElement
--- @field data table<LuaGuiElement, LuaGuiElement>
--- @overload fun(parent: LuaGuiElement, display_table: LuaGuiElement): LuaGuiElement
Elements.example_button = Gui.define("example_button")
:draw{
caption = "Refresh",
tooltip = { "example-gui.tooltip-example-button" },
style = "shortcut_bar_button",
}
:style{
size = 24,
}
:element_data(
-- Set the element data to the first argument given
Gui.from_argument(1)
)
:on_click(function(def, player, element, event)
--- @cast def ExpGui_Example.elements.example_button
local display_table = def.data[element]
for _, item_name in pairs{ "iron-place", "copper-plate", "coal", "stone" } do
local row_data = Elements.display_table.calculate_row_data(inventory, item_name)
Elements.display_table.refresh(display_table, row_data)
end
end) --[[ @as any ]]
-- within Elements.container:draw
local inventory = Gui.get_player(container).get_main_inventory()
local display_table = Elements.display_table(container)
for _, item_name in pairs{ "iron-place", "copper-plate", "coal", "stone" } do
local row_data = Elements.display_table.calculate_row_data(inventory, item_name)
Elements.display_table.add_row(display_table, row_data)
end
Elements.example_button(header, display_table)
```
### Tip 15: Use the custom gui iterator to optimise refreshes
When your data requires frequent updates, whether triggered by events or on every nth game tick, its efficient to use the frameworks custom GUI iterator.
This iterator filters and returns only the specific elements that need refreshing, reducing unnecessary work.
To enable this, you must tell your element definition which GUI elements to track.
In most cases, calling `:track_all_elements()` is sufficient to track all relevant elements automatically.
For updates that happen every nth tick, its better to use `:online_elements()` instead of `:tracked_elements()`.
The `online_elements()` iterator returns only elements associated with players currently online, which helps avoid updating GUI elements for disconnected players unnecessarily.
```lua
--- @param event EventData.on_player_main_inventory_changed
local function on_player_main_inventory_changed(event)
local player = assert(game.get_player(event.player_index))
for _player, display_table in Elements.display_table:tracked_elements(player) do
for _, item_name in pairs{ "iron-place", "copper-plate", "coal", "stone" } do
local row_data = Elements.display_table.calculate_row_data(inventory, item_name)
Elements.display_table.refresh(display_table, row_data)
end
end
end
```
## Miscellaneous
### Tip 16: Don't set sizes and instead use horizontally stretchable
Rather than explicitly setting fixed sizes on GUI elements, it is better to leave sizes undetermined and enable the horizontally stretchable property on the appropriate elements within your GUI.
You dont need to set this property on every element, only on those that are at the deepest level of your GUI hierarchy where flexible spacing is required.
A common and effective use case is employing stretchable empty widgets to create flexible space between elements.
This approach leads to cleaner, more adaptive layouts that adjust gracefully to different languages.
### Tip 17: Cache data where possible
If the data you display is common to all players on a force or surface, its best to cache this data rather than recalculating it for each player individually.
You can store cached data as a local variable within a `refresh_all` function to limit its scope and lifetime. Alternatively, if youre confident in your data management, you may cache it in a higher scope to reuse across multiple refresh cycles.
Be cautious when caching in higher scopes, as improper management can lead to desyncs issues between players.
```lua
function Elements.unnamed_element.refresh_all()
local force_data = {}
for player, unnamed_element in Elements.unnamed_element:online_elements() do
local force = player.force --[[ @as LuaForce ]]
local element_data = force_data[force.name] or Elements.unnamed_element.calculate_data(force)
force_data[force.name] = element_data
Elements.unnamed_element.refresh(unnamed_element, element_data)
end
end
```
```lua
local _force_data = {}
function Elements.here_be_desyncs.get_data(force)
local data = _force_data[force.name] or Elements.here_be_desyncs.calculate_data(force)
_force_data[force.name] = data
return data
end
```
### Tip 18: Use named arguments when many are optional
For elements like `header` that have many optional arguments, it is better to provide those arguments as named values in a table rather than relying on positional order.
This can be done by passing a string key to `Gui.from_argument("key_name", default_value)`, which treats the final argument as a table of named parameters.
Positional arguments still support default values, but using named arguments improves readability and reduces errors when many options are involved.
```lua
--- @class ExpGui_Example.elements.label: ExpElement
--- @overload fun(parent: LuaGuiElement, opts: { caption: string?, width: number? }): LuaGuiElement
Elements.label = Gui.define("label")
:draw{
caption = Gui.from_argument("caption"),
}
:style{
width = Gui.from_argument("width", 25),
} --[[ @as any ]]
```
### Tip 19: Use force and player data within gui data
GUI data is not limited to just individual elements—you can also store and share data at the force or player level.
This allows multiple elements to access common data relevant to a specific player or force, improving consistency and reducing duplication.
Using force and player scoped gui data helps manage state effectively across complex interfaces.
All GUI data initialisers also accept functions, similar to the `:style` method, enabling you to define dynamic starting states that can change based on the current context.
```lua
:force_data{
clicked_time = 0,
clicked_by = "No one."
}
:on_click(function(def, player, element, event)
local force = player.force --[[ @as LuaForce ]]
local force_data = def.data[force]
force_data.clicked_time = event.tick
force_data.clicked_by = player.name
end)
```
### Tip 20: Have clear data ownership
Store GUI data in the highest-level element definition where it is needed, then pass references to child elements.
This allows children to access and modify the shared data as necessary while keeping ownership clear and centralized.
```lua
-- on the settings button
:element_data(
Gui.from_argument(1)
)
-- on the parent
:draw(function(def, parent)
local player = Gui.get_player(parent)
local player_data = def.data[player] or {}
def.data[player] = player_data
local flow = parent.add{ type = "flow" }
for _, setting in pairs(player_data) do
Elements.settings_button(flow, setting)
end
end)
```
Sometimes, due to the order in which elements are drawn, passing references at creation time isnt possible.
In these cases, a `link` method should be used after creation to connect child elements together.
Its also common to pass a table of elements that can be populated incrementally, helping to manage collections of related GUI components cleanly.
```lua
-- on toggle_enabled
:on_click(function(def, player, element, event)
--- @cast def ExpGui_Example.elements.toggle_enabled
local other_element = def.data[element]
if other_element then
other_element.enabled = not other_element.enabled
end
end)
function Elements.toggle_enabled.link_element(toggle_enabled, other_element)
Elements.toggle_enabled.data[toggle_enabled] = other_element
end
-- on the parent
:draw(function(def, parent)
local flow = parent.add{ type = "flow" }
local toggle_enabled = Elements.toggle_enabled(flow)
local other_button = Elements.other_button(flow)
Elements.toggle_enabled.link_element(toggle_enabled, other_button)
end)
```

View File

@@ -46,19 +46,10 @@ return {
--- GUI
"modules.gui.readme",
-- "modules.gui.rocket-info",
-- "modules.gui.science-info",
"modules.gui.autofill",
"modules.gui.task-list",
"modules.gui.warp-list",
"modules.gui.player-list",
"modules.gui.bonus",
"modules.gui.vlayer",
"modules.gui.research",
"modules.gui.module",
"modules.gui.tool",
"modules.gui.production",
"modules.gui.playerdata",
"modules.gui.surveillance",
"modules.gui._role_updates",
-- "modules.graftorio.require", -- graftorio

View File

@@ -16,115 +16,99 @@ return {
MAX 60 180 64 40 32 24 120
= 480
]]
pts = {
points = {
base = 260,
increase_percentage_per_role_level = 0.03,
role_name = "Member",
},
gui_display_width = {
half = 150,
label = 70,
slider = 180,
count = 50,
},
conversion = {
["cmms"] = "character_mining_speed_modifier",
["crs"] = "character_running_speed_modifier",
["ccs"] = "character_crafting_speed_modifier",
["cisb"] = "character_inventory_slots_bonus",
["chb"] = "character_health_bonus",
["crdb"] = "character_reach_distance_bonus",
--[[
['cpdb'] = 'character_item_pickup_distance_bonus',
]]
},
player_special_bonus_rate = 300,
player_special_bonus = {
["personal_battery_recharge"] = {
-- 1 MW
value = 6,
max = 12,
scale = 1,
cost_scale = 4,
cost = 40,
is_percentage = false,
},
},
periodic_bonus_rate = 300,
player_bonus = {
["character_mining_speed_modifier"] = {
value = 3,
max = 6,
scale = 0.5,
cost_scale = 1,
cost = 10,
is_percentage = true,
},
["character_running_speed_modifier"] = {
value = 1.5,
max = 3,
scale = 0.25,
cost_scale = 1,
cost = 60,
is_percentage = true,
},
["character_crafting_speed_modifier"] = {
value = 8,
max = 16,
{
name = "character_mining_speed_modifier",
scale = 1,
cost_scale = 1,
cost = 4,
cost = 10,
max_value = 6,
initial_value = 3,
value_step = 0.5,
is_percentage = true,
},
["character_inventory_slots_bonus"] = {
value = 100,
max = 200,
scale = 10,
cost_scale = 10,
cost = 2,
is_percentage = false,
{
name = "character_running_speed_modifier",
scale = 1,
cost = 60,
max_value = 3,
initial_value = 1.5,
value_step = 0.25,
is_percentage = true,
},
["character_health_bonus"] = {
value = 200,
max = 400,
scale = 50,
cost_scale = 50,
{
name = "character_crafting_speed_modifier",
scale = 1,
cost = 4,
is_percentage = false,
max_value = 16,
initial_value = 8,
value_step = 1,
is_percentage = true,
},
["character_reach_distance_bonus"] = {
value = 12,
max = 24,
scale = 2,
cost_scale = 1,
{
name = "character_inventory_slots_bonus",
cost = 2,
scale = 10,
max_value = 200,
initial_value = 100,
value_step = 10,
},
{
name = "character_health_bonus",
scale = 50,
cost = 4,
max_value = 400,
initial_value = 200,
value_step = 50,
},
{
name = "character_reach_distance_bonus",
cost = 1,
is_percentage = false,
scale = 1,
max_value = 24,
initial_value = 12,
value_step = 2,
combined_bonus = {
"character_resource_reach_distance_bonus",
"character_build_distance_bonus",
},
},
{
name = "personal_battery_recharge",
initial_value = 6,
max_value = 12,
value_step = 1,
scale = 4,
cost = 40,
is_special = true,
},
--[[
['character_item_pickup_distance_bonus'] = {
value = 0,
max = 20,
initial_value = 0,
max_value = 20,
value_step = 1,
scale = 1,
cost_scale = 1,
cost = 1,
is_percentage = false,
},
['character_loot_pickup_distance_bonus'] = {
value = 0,
max = 20,
initial_value = 0,
max_value = 20,
value_step = 1,
scale = 1,
cost_scale = 1,
cost = 1,
is_percentage = false,
},
['character_item_drop_distance_bonus'] = {
value = 0,
max = 20,
initial_value = 0,
max_value = 20,
value_step = 1,
scale = 1,
cost_scale = 1,
cost = 1,
is_percentage = false,
},
@@ -133,49 +117,49 @@ return {
force_bonus = {
--[[
['character_mining_speed_modifier'] = {
value = 0,
max = 6,
scale = 0.5,
cost_scale = 1,
initial_value = 0,
max_value = 6,
value_step = 0.5,
scale = 1,
cost = 10,
is_percentage = true,
},
['character_running_speed_modifier'] = {
value = 0,
max = 3,
scale = 0.25,
cost_scale = 1,
initial_value = 0,
max_value = 3,
value_step = 0.25,
scale = 1,
cost = 40,
is_percentage = true,
},
['character_crafting_speed_modifier'] = {
value = 0,
max = 16,
initial_value = 0,
max_value = 16,
value_step = 1,
scale = 1,
cost_scale = 1,
cost = 4,
is_percentage = true,
},
['character_inventory_slots_bonus'] = {
value = 0,
max = 200,
scale = 10,
cost_scale = 100,
initial_value = 0,
max_value = 200,
value_step = 10,
scale = 100,
cost = 2,
is_percentage = false,
},
['character_health_bonus'] = {
value = 0,
max = 400,
scale = 50,
initial_value = 0,
max_value = 400,
value_step = 50,
cost = 4,
is_percentage = false,
},
['character_reach_distance_bonus'] = {
value = 0,
max = 24,
scale = 2,
cost_scale = 1,
initial_value = 0,
max_value = 24,
value_step = 2,
scale = 1,
cost = 1,
is_percentage = false,
combined_bonus = {
@@ -184,124 +168,124 @@ return {
},
},
['worker_robots_speed_modifier'] = {
value = 0,
max = 0,
scale = 0,
cost_scale = 1,
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false,
},
]]
["worker_robots_battery_modifier"] = {
value = 1,
max = 1,
initial_value = 1,
max_value = 1,
value_step = 1,
scale = 1,
cost_scale = 1,
cost = 1,
is_percentage = false,
},
["worker_robots_storage_bonus"] = {
value = 1,
max = 1,
initial_value = 1,
max_value = 1,
value_step = 1,
scale = 1,
cost_scale = 1,
cost = 1,
is_percentage = false,
},
["following_robots_lifetime_modifier"] = {
value = 1,
max = 1,
initial_value = 1,
max_value = 1,
value_step = 1,
scale = 1,
cost_scale = 1,
cost = 1,
is_percentage = false,
},
--[[
['character_item_pickup_distance_bonus'] = {
value = 0,
max = 20,
initial_value = 0,
max_value = 20,
value_step = 1,
scale = 1,
cost_scale = 1,
cost = 1,
is_percentage = false,
},
['character_loot_pickup_distance_bonus'] = {
value = 0,
max = 20,
initial_value = 0,
max_value = 20,
value_step = 1,
scale = 1,
cost_scale = 1,
cost = 1,
is_percentage = false,
},
['character_item_drop_distance_bonus'] = {
value = 0,
max = 20,
initial_value = 0,
max_value = 20,
value_step = 1,
scale = 1,
cost_scale = 1,
cost = 1,
is_percentage = false,
},
['character_trash_slot_count'] = {
value = 0,
max = 0,
scale = 0,
cost_scale = 1,
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false,
},
['mining_drill_productivity_bonus'] = {
value = 0,
max = 0,
scale = 0,
cost_scale = 1,
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false,
},
['train_braking_force_bonus'] = {
value = 0,
max = 0,
scale = 0,
cost_scale = 1,
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false,
},
['laboratory_speed_modifier'] = {
value = 0,
max = 0,
scale = 0,
cost_scale = 1,
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false,
},
['laboratory_productivity_bonus'] = {
value = 0,
max = 0,
scale = 0,
cost_scale = 1,
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false,
},
['inserter_stack_size_bonus'] = {
value = 0,
max = 0,
scale = 0,
cost_scale = 1,
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false,
},
['stack_inserter_capacity_bonus'] = {
value = 0,
max = 0,
scale = 0,
cost_scale = 1,
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false,
},
['artillery_range_modifier'] = {
value = 0,
max = 0,
scale = 0,
cost_scale = 1,
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false,
},
@@ -310,10 +294,10 @@ return {
surface_bonus = {
--[[
['solar_power_multiplier'] = {
value = 1,
max = 1000,
initial_value = 1,
max_value = 1000,
value_step = 1,
scale = 1,
cost_scale = 1,
cost = 1,
is_percentage = false,
},

View File

@@ -47,7 +47,7 @@ local function teleport(from_player, to_player)
end
local function new_button(sprite, tooltip)
return Gui.element(tooltip[1])
return Gui.define(tooltip[1])
:draw{
type = "sprite-button",
style = "tool_button",

View File

@@ -1,13 +1,11 @@
return {
-- type of machine to handle together
default_module_row_count = 9,
module_slots_per_row = 4,
module_slot_max = 8,
copy_paste_module = false,
copy_paste_module = true,
copy_paste_rotation = false,
machine = {
machines = {
},
machine_set = {
machine_sets = {
["base"] = {
["electric-mining-drill"] = {
["module"] = "efficiency-module",

View File

@@ -129,7 +129,6 @@ end)
Event.add(defines.events.on_player_joined_game, function(event)
local player = game.players[event.player_index]
check_data_loaded_async:start_after(300, player)
PlayerData:raw_set(player.name)
PlayerData:request(player)
end)
@@ -139,8 +138,6 @@ Event.add(defines.events.on_player_left_game, function(event)
local player_data = PlayerData:get(player)
if player_data and player_data.valid == true then
PlayerData:unload(player)
else
PlayerData:raw_set(player.name)
end
end)

View File

@@ -604,7 +604,6 @@ local has_flag = Roles.player_has_flag(game.player, 'is_donator')
]]
function Roles.player_allowed(player, action)
local roles = Roles.get_player_roles(player)
if not roles then return end
for _, role in ipairs(roles) do
if role:is_allowed(action) then
return true

View File

@@ -54,18 +54,6 @@ progress-caption=__1__%
progress-tooltip=This silo has launched __1__ rockets
launch-failed=Failed to launch rocket, please wait a few seconds and try again.
[science-info]
main-caption=Science Packs
main-tooltip=Science Info
eta-caption=ETA:
eta-tooltip=The estimated time left for the current research
eta-time=T- __1__
unit=__1__ spm
pos-tooltip=Total made: __1__
neg-tooltip=Total used: __1__
net-tooltip=Total net: __1__
no-packs=You have not made any science packs yet
[task-list]
main-caption=Task List [img=info]
main-tooltip=Task List
@@ -88,17 +76,6 @@ create-footer-header=Create task
edit-footer-header=Edit task
view-footer-header=Task details
[autofill]
main-tooltip=Autofill settings
toggle-section-caption=__1__ __2__
toggle-section-tooltip=Expand Section
toggle-section-collapse-tooltip=Collapse Section
toggle-entity-tooltip=Toggle the autofill of __1__
toggle-tooltip=Toggle the autofill of __1__ into __2__ slots
amount-tooltip=Amount of items to insert into the __1__ slots
invalid=Autofill set to maximum amount: __1__ __2__ for __3__
inserted=Inserted __1__ __2__ into __3__
[warp-list]
main-caption=Warp List [img=info]
main-tooltip=Warp List
@@ -210,27 +187,6 @@ arg-amount=Amount to set your bonus to, 0 will disable bonus.
set=Your bonus has been set to __1__.
get=Your bonus is __1__.
perm=You dont have enough permission to set more than __1__.
main-tooltip=Bonus
control-pts-a=Points available
control-pts-n=Points needed
control-pts-r=Points remaining
control-reset=Reset
control-apply=Apply
control-pts-exceed=Points allocated exceeded allowance
display-cmms=Mining
display-cmms-tooltip=Character manual mining speed
display-crs=Running
display-crs-tooltip=Character running speed
display-ccs=Crafting
display-ccs-tooltip=Character crafting speed
display-cisb=Inventory
display-cisb-tooltip=Character inventory slots bonus
display-chb=Health
display-chb-tooltip=Character health bonus
display-crdb=Reach
display-crdb-tooltip=Character reach distance bonus
display-personal-battery-recharge=Battery
display-personal-battery-recharge-tooltip=Player battery recharge
display-fmms=Mining
display-fmms-tooltip=Force manual mining speed
display-frs=Running
@@ -292,51 +248,3 @@ control-type-storage-output=Storage Output
power-on-space-research=You need to research [technology=__1__] at __2__ level for the space platform vlayer feature.
enter=Entered vlayer selection mode.
exit=Exited vlayer selection mode.
[module]
main-tooltip=Module GUI
apply=Apply
[landfill]
main-tooltip=Blueprint Landfill GUI
cursor-none=You need to hold the blueprint in cursor
[production]
main-tooltip=Production GUI
label-prod=Production
label-con=Consumption
label-bal=Balance
tooltip-per-second=Items per second
[surveillance]
main-tooltip=Surveillance GUI
status-enable=Enable
status-disable=Disable
func-set=Set
type-player=Player
type-static=Static
type-player-loop=Player loop
[research]
msg=[color=255, 255, 255] Research completed at __1__ - [technology=__2__][/color]
inf=[color=255, 255, 255] Research completed at __1__ - [technology=__2__] - __3__[/color]
res-name=[technology=__1__] __2__
name=Name
target=Target
attempt=Attempt
difference=Diff
main-tooltip=Research GUI
[tool]
main-tooltip=Tool
apply=Apply
artillery=Artillery
artillery-tooltip=Artillery Target Remote
waterfill=Waterfill
waterfill-tooltip=Change tile to water
train=Train
train-tooltip=Set All Trains to Automatic
research=ARES
research-tooltip=Automatically queue up research
spawn=Teleport spawn
spawn-tooltip=Teleport to spawn

View File

@@ -54,18 +54,6 @@ progress-caption=__1__ %
progress-tooltip=該火箭發射井發射了 __1__ 次
launch-failed=火箭發射失敗, 請過一會再試。
[science-info]
main-caption=研究瓶
main-tooltip=研究資訊
eta-caption=預計時間:
eta-tooltip=餘下研究所需時間
eta-time=T- __1__
unit=__1__ 瓶每分鐘
pos-tooltip=製造: __1__
neg-tooltip=使用: __1__
net-tooltip=淨: __1__
no-packs=你未製造任何研究瓶
[task-list]
main-caption=工作流程 [img=info]
main-tooltip=工作流程
@@ -88,17 +76,6 @@ create-footer-header=加入工作流程
edit-footer-header=修改工作流程
view-footer-header=工作流程細節
[autofill]
main-tooltip=自動填入設定
toggle-section-caption=__1__ __2__
toggle-section-tooltip=擴張欄
toggle-section-collapse-tooltip=收縮欄
toggle-entity-tooltip=自動填入設定 - __1__
toggle-tooltip=自動填入設定 - __2__ 的 __1__
amount-tooltip=自動填入 __1__ 的數量
invalid=自動填入最大值 __1__ __2__ 給 __3__
inserted=自動填入 __1__ __2__ 到 __3__
[warp-list]
main-caption=傳送陣清單 [img=info]
main-tooltip=傳送陣清單
@@ -210,27 +187,6 @@ arg-amount=Bonus 數量, 0 來停用。
set=你的 Bonus 已設為 __1__。
get=你的 Bonus 為 __1__。
perm=你沒有足夠權限設多過 __1__。
main-tooltip=Bonus 介面
control-pts-a=可用分數
control-pts-n=必要分數
control-pts-r=餘下分數
control-reset=重置
control-apply=應用
control-pts-exceed=分數超出可用上限
display-cmms=挖掘速度
display-cmms-tooltip=個人挖掘速度
display-crs=跑步速度
display-crs-tooltip=個人跑步速度
display-ccs=合成速度
display-ccs-tooltip=個人合成速度
display-cisb=儲存位
display-cisb-tooltip=個人儲存位
display-chb=生命
display-chb-tooltip=個人生命
display-crdb=到達距離
display-crdb-tooltip=個人到達距離
display-personal-battery-recharge=電池充電
display-personal-battery-recharge-tooltip=為玩家電池充電
display-fmms=挖掘速度
display-fmms-tooltip=勢力挖掘速度
display-frs=跑步速度
@@ -293,30 +249,6 @@ power-on-space-research=你要研究 [technology=__1__] 在 __2__ 級 才可使
enter=現在進入 vlayer 區域選擇
exit=已進入 vlayer 區域選擇
[module]
main-tooltip=模組介面
apply=套用
[landfill]
main-tooltip=藍圖填海介面
cursor-none=您需要將藍圖保持在遊標處
[production]
main-tooltip=製造介面
label-prod=製造
label-con=消耗
label-bal=淨值
tooltip-per-second=物品每秒
[surveillance]
main-tooltip=監控介面
status-enable=啟用
status-disable=停用
func-set=
type-player=用戶
type-static=靜態
type-player-loop=用戶循環
[toolbar]
main-caption=工具箱
main-tooltip=工具箱設定\n選上來設定喜好
@@ -324,27 +256,3 @@ reset=重設
toggle=啟用喜好
move-up=向上
move-down=向下
[research]
msg=[color=255, 255, 255] 研究完成在 __1__ - [technology=__2__][/color]
inf=[color=255, 255, 255] 研究完成在 __1__ - [technology=__2__] - __3__[/color]
res-name=[technology=__1__] __2__
name=名稱
target=目標
attempt=用時
difference=差距
main-tooltip=研究介面
[tool]
main-tooltip=工具
apply=應用
artillery=火炮遙控
artillery-tooltip=火炮遙控
waterfill=挖水
waterfill-tooltip=把地換為水。
train=火車
train-tooltip=把火車設置為自動模式
research=研究
research-tooltip=啟用自動研究
spawn=傳送出生
spawn-tooltip=傳送到出生點

View File

@@ -54,18 +54,6 @@ progress-caption=__1__ %
progress-tooltip=該火箭發射井發射了 __1__ 次
launch-failed=火箭發射失敗, 請過一會再試。
[science-info]
main-caption=研究瓶
main-tooltip=研究資訊
eta-caption=預計時間:
eta-tooltip=餘下研究所需時間
eta-time=T- __1__
unit=__1__ 瓶每分鐘
pos-tooltip=製造: __1__
neg-tooltip=使用: __1__
net-tooltip=淨: __1__
no-packs=你未製造任何研究瓶
[task-list]
main-caption=工作流程 [img=info]
main-tooltip=工作流程
@@ -88,17 +76,6 @@ create-footer-header=加入工作流程
edit-footer-header=修改工作流程
view-footer-header=工作流程細節
[autofill]
main-tooltip=自動填入設定
toggle-section-caption=__1__ __2__
toggle-section-tooltip=擴張欄
toggle-section-collapse-tooltip=收縮欄
toggle-entity-tooltip=自動填入設定 - __1__
toggle-tooltip=自動填入設定 - __2__ 的 __1__
amount-tooltip=自動填入 __1__ 的數量
invalid=自動填入最大值 __1__ __2__ 給 __3__
inserted=自動填入 __1__ __2__ 到 __3__
[warp-list]
main-caption=傳送陣清單 [img=info]
main-tooltip=傳送陣清單
@@ -210,27 +187,6 @@ arg-amount=Bonus 數量, 0 來停用。
set=你的 Bonus 已設為 __1__。
get=你的 Bonus 為 __1__。
perm=你沒有足夠權限設多過 __1__。
main-tooltip=Bonus 介面
control-pts-a=可用分數
control-pts-n=必要分數
control-pts-r=餘下分數
control-reset=重置
control-apply=應用
control-pts-exceed=分數超出可用上限
display-cmms=挖掘速度
display-cmms-tooltip=個人挖掘速度
display-crs=跑步速度
display-crs-tooltip=個人跑步速度
display-ccs=合成速度
display-ccs-tooltip=個人合成速度
display-cisb=儲存位
display-cisb-tooltip=個人儲存位
display-chb=生命
display-chb-tooltip=個人生命
display-crdb=到達距離
display-crdb-tooltip=個人到達距離
display-personal-battery-recharge=電池充電
display-personal-battery-recharge-tooltip=為玩家電池充電
display-fmms=挖掘速度
display-fmms-tooltip=勢力挖掘速度
display-frs=跑步速度
@@ -293,30 +249,6 @@ power-on-space-research=你要研究 [technology=__1__] 在 __2__ 級才可使
enter=現在進入 vlayer 區域選擇
exit=已進入 vlayer 區域選擇
[module]
main-tooltip=模組介面
apply=套用
[landfill]
main-tooltip=藍圖填海介面
cursor-none=您需要將藍圖保持在遊標處
[production]
main-tooltip=製造介面
label-prod=製造
label-con=消耗
label-bal=淨值
tooltip-per-second=物品每秒
[surveillance]
main-tooltip=監控介面
status-enable=啟用
status-disable=停用
func-set=
type-player=用戶
type-static=靜態
type-player-loop=用戶循環
[toolbar]
main-caption=工具箱
main-tooltip=工具箱設定\n選上來設定喜好
@@ -324,27 +256,3 @@ reset=重設
toggle=啟用喜好
move-up=向上
move-down=向下
[research]
msg=[color=255, 255, 255] 研究完成在 __1__ - [technology=__2__][/color]
inf=[color=255, 255, 255] 研究完成在 __1__ - [technology=__2__] - __3__[/color]
res-name=[technology=__1__] __2__
name=名稱
target=目標
attempt=用時
difference=差距
main-tooltip=研究介面
[tool]
main-tooltip=工具
apply=應用
artillery=火炮遙控
artillery-tooltip=火炮遙控
waterfill=挖水
waterfill-tooltip=把地換為水。
train=火車
train-tooltip=把火車設置為自動模式
research=研究
research-tooltip=啟用自動研究
spawn=傳送出生
spawn-tooltip=傳送到出生點

View File

@@ -1,257 +0,0 @@
--[[-- Control Module - Production
- Common functions used to track production of items
@control Production
@alias Production
@usage
-- import the module from the control modules
local Production = require("modules.exp_legacy.modules.control.production") --- @dep modules.control.production
-- This will return the less precise index from the one given
-- this means that one_second will return one_minute or ten_hours will return fifty_hours
-- the other precision work like wise
Production.precision_up(defines.flow_precision_index.one_second)
-- The get production function is used to get production, consumion and net
-- it may be used for any item and with any precision level, use total for total
Production.get_production(game.forces.player, 'iron-plate', defines.flow_precision_index.one_minute)
-- The fluctuations works by compearing recent production with the average over time
-- again any precision may be used, apart from one_thousand_hours as there would be no valid average
Production.get_fluctuations(game.forces.player, 'iron-plate', defines.flow_precision_index.one_minute)
-- ETA is calculated based on what function you use but all share a similar method
-- for production eta it will take current production average given by the precision
-- and work out how many ticks it will require to make the required amount (1000 by default)
Production.get_production_eta(game.forces.player, 'iron-plate', defines.flow_precision_index.one_minute, 250000)
-- Both get_color and format_number are helper functions to help format production stats
-- get_color will return green, orange, red, or grey based on the active_value
-- the passive_value is used when active_value is 0 and can only return orange, red, or grey
Production.get_color(clamp, active_value, passive_value)
]]
local Colors = require("modules/exp_util/include/color")
local format_number = require("util").format_number --- @dep util
local precision_index = defines.flow_precision_index
local Production = {}
--- Precision.
-- Functions which are used to do basic things
-- @section precision
--- Gets the next lesser precision index value, eg 5 seconds -> 1 minute
-- @tparam defines.flow_precision_index precision
-- @treturn[1] defines.flow_precision_index the next precision value
-- @treturn[1] number the multiplicive difference between the values
function Production.precision_up(precision)
if precision == precision_index.five_seconds then
return precision_index.one_minute, 60
elseif precision == precision_index.one_minute then
return precision_index.ten_minutes, 10
elseif precision == precision_index.ten_minutes then
return precision_index.one_hour, 6
elseif precision == precision_index.one_hour then
return precision_index.ten_hours, 10
elseif precision == precision_index.ten_hours then
return precision_index.fifty_hours, 5
elseif precision == precision_index.fifty_hours then
return precision_index.two_hundred_fifty_hours, 5
elseif precision == precision_index.two_hundred_fifty_hours then
return precision_index.one_thousand_hours, 4
end
end
--- Gets the next greater precision index value, eg 1 minute -> 5 seconds
-- @tparam defines.flow_precision_index precision
-- @treturn[1] defines.flow_precision_index the next precision value
-- @treturn[1] number the multiplicive difference between the values
function Production.precision_down(precision)
if precision == precision_index.one_minute then
return precision_index.five_seconds, 60
elseif precision == precision_index.ten_minutes then
return precision_index.one_minute, 10
elseif precision == precision_index.one_hour then
return precision_index.ten_minutes, 6
elseif precision == precision_index.ten_hours then
return precision_index.one_hour, 10
elseif precision == precision_index.fifty_hours then
return precision_index.ten_hours, 5
elseif precision == precision_index.two_hundred_fifty_hours then
return precision_index.fifty_hours, 5
elseif precision == precision_index.one_thousand_hours then
return precision_index.two_hundred_fifty_hours, 4
end
end
--- Gets the number of tick that precision is given over, eg 1 minute -> 3600 ticks
-- @tparam defines.flow_precision_index precision
-- @treturn number the number of ticks in this time
function Production.precision_ticks(precision)
if precision == precision_index.five_seconds then
return 300
elseif precision == precision_index.one_minute then
return 3600
elseif precision == precision_index.ten_minutes then
return 36000
elseif precision == precision_index.one_hour then
return 216000
elseif precision == precision_index.ten_hours then
return 2160000
elseif precision == precision_index.fifty_hours then
return 10800000
elseif precision == precision_index.two_hundred_fifty_hours then
return 54000000
elseif precision == precision_index.one_thousand_hours then
return 216000000
end
end
--- Statistics.
-- Functions used to get information about production
-- @section stats
--- Returns the production data for the whole game time
-- @tparam LuaForce force the force to get the data for
-- @tparam string item_name the name of the item that you want the data about
-- @treturn table contains total made, used and net
function Production.get_production_total(force, item_name)
local made, used = 0, 0
for _, surface in pairs(game.surfaces) do
local stats = force.get_item_production_statistics(surface)
made = made + stats.get_input_count(item_name)
used = used + stats.get_output_count(item_name)
end
return {
made = made,
used = used,
net = made - used,
}
end
--- Returns the production data for the given precision game time
-- @tparam LuaForce force the force to get the data for
-- @tparam string item_name the name of the item that you want the data about
-- @tparam defines.flow_precision_index precision the precision that you want the data given to
-- @treturn table contains made, used and net
function Production.get_production(force, item_name, precision)
local made, used = 0, 0
for _, surface in pairs(game.surfaces) do
local stats = force.get_item_production_statistics(surface).get_flow_count
made = made + stats{ name = item_name, category = "input", precision_index = precision }
used = used + stats{ name = item_name, category = "output", precision_index = precision }
end
return {
made = made,
used = used,
net = made - used,
}
end
--- Returns the current fluctuation from the average
-- @tparam LuaForce force the force to get the data for
-- @tparam string item_name the name of the item that you want the data about
-- @tparam defines.flow_precision_index precision the precision that you want the data given to
-- @treturn table contains made, used and net
function Production.get_fluctuations(force, item_name, precision)
local precision_up = Production.precision_up(precision)
local current = Production.get_production(force, item_name, precision)
local previous = Production.get_production(force, item_name, precision_up)
return {
made = (current.made / previous.made) - 1,
used = (current.used / previous.used) - 1,
net = (current.net / previous.net) - 1,
}
end
--- Returns the amount of ticks required to produce a certain amount
-- @tparam LuaForce force the force to get the data for
-- @tparam string item_name the name of the item that you want the data about
-- @tparam defines.flow_precision_index precision the precision that you want the data given to
-- @tparam[opt=1000] number required the number of items that are required to be made
-- @treturn number the number of ticks required to produce this ammount of items
function Production.get_production_eta(force, item_name, precision, required)
required = required or 1000
local ticks = Production.precision_ticks(precision)
local production = Production.get_production(force, item_name, precision)
return production.made == 0 and -1 or ticks * required / production.made
end
--- Returns the amount of ticks required to consume a certain amount
-- @tparam LuaForce force the force to get the data for
-- @tparam string item_name the name of the item that you want the data about
-- @tparam defines.flow_precision_index precision the precision that you want the data given to
-- @tparam[opt=1000] number required the number of items that are required to be consumed
-- @treturn number the number of ticks required to consume this ammount of items
function Production.get_consumsion_eta(force, item_name, precision, required)
required = required or 1000
local ticks = Production.precision_ticks(precision)
local production = Production.get_production(force, item_name, precision)
return production.used == 0 and -1 or ticks * required / production.used
end
--- Returns the amount of ticks required to produce but not consume a certain amount
-- @tparam LuaForce force the force to get the data for
-- @tparam string item_name the name of the item that you want the data about
-- @tparam defines.flow_precision_index precision the precision that you want the data given to
-- @tparam[opt=1000] number required the number of items that are required to be made but not used
-- @treturn number the number of ticks required to produce, but not use, this ammount of items
function Production.get_net_eta(force, item_name, precision, required)
required = required or 1000
local ticks = Production.precision_ticks(precision)
local production = Production.get_production(force, item_name, precision)
return production.net == 0 and -1 or ticks * required / production.net
end
--- Formating.
-- Functions used to format production values
-- @section formating
--- Returns a color value based on the value that was given
-- @tparam number cutoff value which separates the different colours
-- @tparam number active_value first value tested, tested against cutoff
-- @tparam number passive_value second value tested, tested against 0 when active is 0
-- @treturn table contains r,g,b keys
function Production.get_color(cutoff, active_value, passive_value)
if active_value > cutoff then
return Colors.light_green
elseif active_value < -cutoff then
return Colors.indian_red
elseif active_value ~= 0 then
return Colors.orange
elseif passive_value and passive_value > 0 then
return Colors.orange
elseif passive_value and passive_value < 0 then
return Colors.indian_red
else
return Colors.grey
end
end
--- Returns three parts used to format a number
-- @tparam number value the value to format
-- @treturn[1] string the sign for the number
-- @treturn[1] string the surfix for any unit used
function Production.format_number(value)
local rtn = format_number(math.round(value, 1), true)
local surfix = rtn:sub(-1)
if value > 0 then
rtn = "+" .. rtn
elseif value == 0 and rtn:sub(1, 1) == "-" then
rtn = rtn:sub(2)
end
if not tonumber(surfix) then
return surfix, rtn:sub(1, -2)
else
return "", rtn
end
end
return Production

View File

@@ -103,7 +103,7 @@ end
----- Gui -----
--- Label used to show that the player is following, also used to allow esc to stop following
follow_label = Gui.element("follow-label")
follow_label = Gui.define("follow-label")
:draw(function(def, parent, target)
Gui.destroy_if_valid(parent.follow_label)

View File

@@ -521,8 +521,12 @@ function vlayer.get_statistics()
end
--- add or reduce vlayer power
--- @param power number
--- @return number
function vlayer.energy_changed(power)
vlayer_data.storage.energy = vlayer_data.storage.energy + power
local new_value = vlayer_data.storage.energy + power
vlayer_data.storage.energy = new_value
return new_value
end
--- Circuit signals used for the statistics

View File

@@ -18,7 +18,7 @@ ToolbarState:on_load(function(player_name, value)
local decompressed = helpers.json_to_table(assert(helpers.decode_string(value), "Failed String Decode"))
local player = assert(game.get_player(player_name))
Gui.toolbar.set_state(player, decompressed --[[ @as ExpGui.ToolbarState ]])
Gui.toolbar.set_state(player, decompressed --[[ @as Gui.ToolbarState ]])
return nil -- We don't save the state, use Gui.toolbar.get_state
end)

View File

@@ -1,373 +0,0 @@
--[[-- Gui Module - Autofill
- Adds a button to enable Autofill
@gui Autofill
@alias autofill
]]
local Storage = require("modules/exp_util/storage")
local FlyingText = require("modules/exp_util/flying_text")
local Gui = require("modules/exp_gui")
local Roles = require("modules.exp_legacy.expcore.roles")
local config = require("modules.exp_legacy.config.gui.autofill") -- @dep config.gui.autofill
local Event = require("modules/exp_legacy/utils/event") -- @dep utils.event
--- Table that stores if autofill is enabled or not
local autofill_player_settings = {}
Storage.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 entity section visibility
-- @element toggle_item_button
local toggle_section = Gui.element("autofill_toggle_section")
:draw{
type = "sprite-button",
sprite = "utility/expand",
tooltip = { "autofill.toggle-section-tooltip" },
style = "frame_action_button",
name = Gui.property_from_name,
}
:style(Gui.styles.sprite{
size = 20
})
:on_click(function(def, player, element)
local header_flow = assert(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"
element.tooltip = { "autofill.toggle-section-collapse-tooltip" }
else
element.sprite = "utility/expand"
element.tooltip = { "autofill.toggle-section-tooltip" }
end
end)
--- Toggle enitity button, used for toggling autofill for the specific entity
-- All entity autofill settings will be ignored if its disabled
-- @element entity_toggle
local entity_toggle = Gui.element("entity_toggle")
:draw(function(_, parent, entity_name)
return parent.add{
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.styles.sprite{
size = 22
})
:on_click(function(def, player, element)
local entity_name = string.match(element.parent.parent.name, "(.*)%-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
-- Correct the button size
local style = element.style
style.padding = -2
style.height = 22
style.width = 22
end)
--- Draw a section header and main scroll
-- @element autofill_section_container
local section = Gui.element("autofill_section")
:draw(function(def, parent, section_name, table_size)
-- Draw the header for the section
local header = Gui.elements.header(parent, {
name = section_name .. "-header",
caption = { "autofill.toggle-section-caption", rich_img("item", section_name), { "entity-name." .. section_name } },
tooltip = { "autofill.toggle-section-tooltip" },
label_name = "label",
})
def:link_element(header.parent.label)
-- 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 def:unlink_element(section_table)
end)
:on_click(function(def, player, element, event)
event.element = element.parent.flow[toggle_section.name]
toggle_section:raise_event(event)
end)
--- Toggle item button, used for toggling autofill for the specific item
-- @element toggle_item_button
local toggle_item_button = Gui.element("toggle_item_button")
:draw(function(_, parent, item)
return parent.add{
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.styles.sprite{
size = 32,
right_margin = -3,
})
:on_click(function(def, player, element)
local item_name = element.parent.tooltip
local entity_name = element.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
-- Correct the button size
local style = element.style
style.right_margin = -3
style.padding = -2
style.height = 32
style.width = 32
end)
--- Amount text field for a autofill item
-- @element amount_textfield
local amount_textfield = Gui.element("amount_textfield")
:draw(function(_, parent, item)
return parent.add{
type = "textfield",
text = item.amount,
tooltip = { "autofill.amount-tooltip", item.category },
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(def, player, element)
local value = tonumber(element.text)
if not value then value = 0 end
local clamped = math.clamp(value, 0, 1000)
local item_name = element.parent.tooltip
local entity_name = element.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 = tostring(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("add_autofill_setting")
:draw(function(_, parent, item)
local toggle_flow = parent.add{ type = "flow", name = "toggle-setting-" .. item.name, tooltip = item.name }
local amount_flow = parent.add{ type = "flow", name = "amount-setting-" .. item.name, tooltip = item.name }
toggle_flow.style.padding = 0
amount_flow.style.padding = 0
toggle_item_button(toggle_flow, item)
amount_textfield(amount_flow, item)
end)
--- Autofill setting empty, contains filler button and textfield gui elements
-- @element add_empty_autofill_setting
local add_empty_autofill_setting = Gui.element("add_empty_autofill_setting")
:draw(function(_, parent)
local toggle_element = parent.add{
type = "sprite-button",
}
toggle_element.style.right_margin = -3
toggle_element.style.width = 32
toggle_element.style.height = 32
toggle_element.enabled = false
local amount_element = parent.add{
type = "textfield",
}
amount_element.style.maximal_width = 40
amount_element.style.height = 31
amount_element.style.padding = -2
amount_element.enabled = false
end)
--- Main gui container for the left flow
-- @element autofill_container
autofill_container = Gui.element("autofill_container")
:draw(function(def, parent)
-- Draw the internal container
local container = Gui.elements.container(parent)
-- Draw the scroll container
local scroll_table = Gui.elements.scroll_table(container, 400, 1, "autofill-scroll-table")
-- Set the scroll panel to always show the scrollbar (not doing this will result in a changing gui size)
scroll_table.parent.vertical_scroll_policy = "always"
-- Scroll panel has by default padding
scroll_table.parent.style.padding = 0
-- Remove the default gap that is added in a table between elements
scroll_table.style.vertical_spacing = 0
-- Center the first column in the table
scroll_table.style.column_alignments[1] = "center"
-- 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)
-- Add some padding around the table
entity_table.style.padding = 3
-- Make sure each column is alignment top center
entity_table.style.column_alignments[1] = "top-center"
entity_table.style.column_alignments[2] = "top-center"
entity_table.style.column_alignments[3] = "top-center"
-- Loop over each item category
for _, category in pairs(config.categories) do
if not table_sizes[category] then table_sizes[category] = 0 end
-- Draw table
local category_table = entity_table.add{
type = "table",
name = category .. "-category",
column_count = 2,
}
-- Add padding between each item
category_table.style.vertical_spacing = 1
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])
end
end
end
-- Return the external container
return container.parent
end)
--- Add the element to the left flow with a toolbar button
Gui.add_left_element(autofill_container, false)
Gui.toolbar.create_button{
name = "autofill_toggle",
left_element = autofill_container,
sprite = config.icon,
tooltip = { "autofill.main-tooltip" },
visible = function(player, element)
return Roles.player_allowed(player, "gui/autofill")
end
}
--- 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.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() --- @cast player_inventory -nil
local offset = { x = 0, y = 0 }
-- 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.inv)
if not entity_inventory then goto end_item end
local preferred_amount = item.amount
local item_amount = player_inventory.get_item_count(item.name)
if item_amount ~= 0 then
local inserted
local color = { r = 0, g = 255, b = 0, a = 255 }
if item_amount >= preferred_amount then
-- Can item be inserted? no, goto next item!
if not entity_inventory.can_insert{ name = item.name, count = preferred_amount } then
goto end_item
end
inserted = entity_inventory.insert{ name = item.name, count = preferred_amount }
else
inserted = entity_inventory.insert{ name = item.name, count = item_amount }
color = { r = 255, g = 165, b = 0, a = 255 }
end
player_inventory.remove{ name = item.name, count = inserted }
FlyingText.create_above_entity{
target_entity = entity,
text = { "autofill.inserted", inserted, rich_img("item", item.name), rich_img("entity", entity.name) },
offset = offset,
player = player,
color = color,
}
offset.y = offset.y - 0.33
end
::end_item::
end
end
Event.add(defines.events.on_built_entity, entity_build)

View File

@@ -1,347 +0,0 @@
--[[-- Gui Module - Bonus
@gui Bonus
@alias bonus_container
]]
local Gui = require("modules/exp_gui")
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
local config = require("modules.exp_legacy.config.bonus") --- @dep config.bonus
local vlayer = require("modules.exp_legacy.modules.control.vlayer")
local format_number = require("util").format_number --- @dep util
local bonus_container
--- @param player LuaPlayer
--- @param container LuaGuiElement?
--- @return number
local function bonus_gui_pts_needed(player, container)
container = container or Gui.get_left_element(bonus_container, player)
local disp = container.frame["bonus_st_2"].disp.table
local total = 0
for k, v in pairs(config.conversion) do
total = total + (disp["bonus_display_" .. k .. "_slider"].slider_value / config.player_bonus[v].cost_scale * config.player_bonus[v].cost)
end
total = total + (
disp["bonus_display_personal_battery_recharge_slider"].slider_value
/ config.player_special_bonus["personal_battery_recharge"].cost_scale
* config.player_special_bonus["personal_battery_recharge"].cost
)
return total
end
--- @param player LuaPlayer
--- @param reset boolean?
local function apply_bonus(player, reset)
if reset or not Roles.player_allowed(player, "gui/bonus") then
for k, v in pairs(config.player_bonus) do
player[k] = 0
if v.combined_bonus then
for i = 1, #v.combined_bonus do
player[v.combined_bonus[i]] = 0
end
end
end
return
end
if not player.character then
return
end
local container = Gui.get_left_element(bonus_container, player)
local disp = container.frame["bonus_st_2"].disp.table
for k, v in pairs(config.conversion) do
player[v] = disp["bonus_display_" .. k .. "_slider"].slider_value
if config.player_bonus[v].combined_bonus then
for i = 1, #config.player_bonus[v].combined_bonus do
player[config.player_bonus[v].combined_bonus[i]] = disp["bonus_display_" .. k .. "_slider"].slider_value
end
end
end
end
local function apply_periodic_bonus(player)
if not Roles.player_allowed(player, "gui/bonus") then
return
end
if not player.character then
return
end
local container = Gui.get_left_element(bonus_container, player)
local disp = container.frame["bonus_st_2"].disp.table
if vlayer.get_statistics()["energy_sustained"] > 0 then
local armor = player.get_inventory(defines.inventory.character_armor)
if armor and armor[1] and armor[1].valid_for_read and armor[1].grid then
local armor_grid = armor[1].grid
if armor_grid and armor_grid.available_in_batteries and armor_grid.battery_capacity and armor_grid.available_in_batteries < armor_grid.battery_capacity then
local slider = disp["bonus_display_personal_battery_recharge_slider"].slider_value * 100000 * config.player_special_bonus_rate / 6
for i = 1, #armor_grid.equipment do
if armor_grid.equipment[i].energy < armor_grid.equipment[i].max_energy then
local energy_required = math.min(math.floor(armor_grid.equipment[i].max_energy - armor_grid.equipment[i].energy), vlayer.get_statistics()["energy_storage"], slider)
armor_grid.equipment[i].energy = armor_grid.equipment[i].energy + energy_required
vlayer.energy_changed(-energy_required)
slider = slider - energy_required
end
end
end
end
end
end
local bonus_data_score_limit = {}
local function get_bonus_score_limit(player)
if not bonus_data_score_limit[player] then
bonus_data_score_limit[player] = math.floor(config.pts.base * (1 + config.pts.increase_percentage_per_role_level * (Roles.get_role_by_name(config.pts.role_name).index - Roles.get_player_highest_role(player).index)))
end
return bonus_data_score_limit[player]
end
--- Control label for the bonus points available
-- @element bonus_gui_control_pts
local bonus_gui_control_pts = Gui.element("bonus_gui_control_pts")
:draw{
type = "label",
name = Gui.property_from_name,
caption = { "bonus.control-pts-a" },
style = "heading_2_label",
}:style{
width = config.gui_display_width["half"],
}
local bonus_gui_control_pts_count = Gui.element("bonus_gui_control_pts_count")
:draw{
type = "progressbar",
name = Gui.property_from_name,
caption = "0 / 0",
value = 0,
style = "electric_satisfaction_statistics_progressbar",
}:style{
width = config.gui_display_width["half"],
font = "heading-2",
color = { 1, 0, 0 },
}
--- A button used for pts calculations
-- @element bonus_gui_control_refresh
local bonus_gui_control_reset = Gui.element("bonus_gui_control_reset")
:draw{
type = "button",
name = Gui.property_from_name,
caption = { "bonus.control-reset" },
}:style{
width = config.gui_display_width["half"],
}:on_click(function(def, player, element)
local container = Gui.get_left_element(bonus_container, player)
local disp = container.frame["bonus_st_2"].disp.table
for k, v in pairs(config.conversion) do
local s = "bonus_display_" .. k .. "_slider"
disp[s].slider_value = config.player_bonus[v].value
disp[disp[s].tags.counter].caption = (config.player_bonus[v].is_percentage and (format_number(disp[s].slider_value * 100, false) .. " %")) or format_number(disp[s].slider_value, false)
end
local slider = disp["bonus_display_personal_battery_recharge_slider"]
slider.slider_value = config.player_special_bonus["personal_battery_recharge"].value
disp[slider.tags.counter].caption = format_number(slider.slider_value, false)
local n = bonus_gui_pts_needed(player)
local limit = get_bonus_score_limit(player)
element.parent[bonus_gui_control_pts_count.name].caption = n .. " / " .. limit
element.parent[bonus_gui_control_pts_count.name].value = n / limit
end)
--- A button used for pts apply
-- @element bonus_gui_control_apply
local bonus_gui_control_apply = Gui.element("bonus_gui_control_apply")
:draw{
type = "button",
name = Gui.property_from_name,
caption = { "bonus.control-apply" },
}:style{
width = config.gui_display_width["half"],
}:on_click(function(def, player, element)
local n = bonus_gui_pts_needed(player)
local limit = get_bonus_score_limit(player)
element.parent[bonus_gui_control_pts_count.name].caption = n .. " / " .. limit
element.parent[bonus_gui_control_pts_count.name].value = n / limit
if n <= limit then
apply_bonus(player)
end
end)
--- A vertical flow containing all the bonus control
-- @element bonus_control_set
local bonus_control_set = Gui.element("bonus_control_set")
:draw(function(_, parent, name)
local bonus_set = parent.add{ type = "flow", direction = "vertical", name = name }
local disp = Gui.elements.scroll_table(bonus_set, config.gui_display_width["half"] * 2, 2, "disp")
bonus_gui_control_pts(disp)
bonus_gui_control_pts_count(disp)
bonus_gui_control_reset(disp)
bonus_gui_control_apply(disp)
return bonus_set
end)
--- Display group
-- @element bonus_gui_slider
local bonus_gui_slider = Gui.element("bonus_gui_slider")
:draw(function(def, parent, name, caption, tooltip, bonus)
local label = parent.add{
type = "label",
caption = caption,
tooltip = tooltip,
style = "heading_2_label",
}
label.style.width = config.gui_display_width["label"]
local slider = parent.add{
type = "slider",
name = name .. "_slider",
value = bonus.value,
maximum_value = bonus.max,
value_step = bonus.scale,
discrete_values = true,
style = "notched_slider",
tags = {
counter = name .. "_count",
is_percentage = bonus.is_percentage,
},
}
slider.style.width = config.gui_display_width["slider"]
slider.style.horizontally_stretchable = true
local count = parent.add{
type = "label",
name = name .. "_count",
caption = (bonus.is_percentage and format_number(bonus.value * 100, false) .. " %") or format_number(bonus.value, false),
style = "heading_2_label",
}
count.style.width = config.gui_display_width["count"]
return slider
end)
:on_value_changed(function(def, player, element)
element.parent[element.tags.counter].caption = (element.tags.is_percentage and format_number(element.slider_value * 100, false) .. " %") or format_number(element.slider_value, false)
local container = Gui.get_left_element(bonus_container, player)
local disp = container.frame["bonus_st_1"].disp.table
local n = bonus_gui_pts_needed(player)
local limit = get_bonus_score_limit(player)
disp[bonus_gui_control_pts_count.name].caption = n .. " / " .. limit
disp[bonus_gui_control_pts_count.name].value = n / limit
end)
--- A vertical flow containing all the bonus data
-- @element bonus_data_set
local bonus_data_set = Gui.element("bonus_data_set")
:draw(function(_, parent, name)
local bonus_set = parent.add{ type = "flow", direction = "vertical", name = name }
local disp = Gui.elements.scroll_table(bonus_set, config.gui_display_width["half"] * 2, 3, "disp")
for k, v in pairs(config.conversion) do
bonus_gui_slider(disp, "bonus_display_" .. k, { "bonus.display-" .. k }, { "bonus.display-" .. k .. "-tooltip" }, config.player_bonus[v])
end
bonus_gui_slider(disp, "bonus_display_personal_battery_recharge", { "bonus.display-personal-battery-recharge" }, { "bonus.display-personal-battery-recharge-tooltip" }, config.player_special_bonus["personal_battery_recharge"])
return bonus_set
end)
--- The main container for the bonus gui
-- @element bonus_container
bonus_container = Gui.element("bonus_container")
:draw(function(def, parent)
local player = Gui.get_player(parent)
local container = Gui.elements.container(parent, config.gui_display_width["half"] * 2)
bonus_control_set(container, "bonus_st_1")
bonus_data_set(container, "bonus_st_2")
local disp = container["bonus_st_1"].disp.table
local n = bonus_gui_pts_needed(player, container.parent)
local limit = get_bonus_score_limit(player)
disp[bonus_gui_control_pts_count.name].caption = n .. " / " .. limit
disp[bonus_gui_control_pts_count.name].value = n / limit
return container.parent
end)
--- Add the element to the left flow with a toolbar button
Gui.add_left_element(bonus_container, false)
Gui.toolbar.create_button{
name = "bonus_toggle",
left_element = bonus_container,
sprite = "item/exoskeleton-equipment",
tooltip = { "bonus.main-tooltip" },
visible = function(player, element)
return Roles.player_allowed(player, "gui/bonus")
end
}
Event.add(defines.events.on_player_created, function(event)
if event.player_index ~= 1 then
return
end
for k, v in pairs(config.force_bonus) do
game.players[event.player_index].force[k] = v.value
end
for k, v in pairs(config.surface_bonus) do
game.players[event.player_index].surface[k] = v.value
end
end)
local function recalculate_bonus(event)
local player = game.players[event.player_index]
if event.name == Roles.events.on_role_assigned or event.name == Roles.events.on_role_unassigned then
-- If the player's roles changed then we need to recalculate their limit
bonus_data_score_limit[player] = nil
end
local container = Gui.get_left_element(bonus_container, player)
local disp = container.frame["bonus_st_1"].disp.table
local n = bonus_gui_pts_needed(player)
local limit = get_bonus_score_limit(player)
disp[bonus_gui_control_pts_count.name].caption = n .. " / " .. limit
disp[bonus_gui_control_pts_count.name].value = n / limit
apply_bonus(player, n > limit)
end
Event.add(Roles.events.on_role_assigned, recalculate_bonus)
Event.add(Roles.events.on_role_unassigned, recalculate_bonus)
Event.add(defines.events.on_player_respawned, recalculate_bonus)
--- When a player dies allow them to have instant respawn
Event.add(defines.events.on_player_died, function(event)
local player = game.players[event.player_index]
if Roles.player_has_flag(player, "instant-respawn") then
player.ticks_to_respawn = 120
end
end)
Event.on_nth_tick(config.player_special_bonus_rate, function(_)
for _, player in pairs(game.connected_players) do
if player.character then
apply_periodic_bonus(player)
end
end
end)

View File

@@ -61,12 +61,8 @@ function Public.dump_text(text, player)
return false
end
rawset(game, "player", player)
local suc, var = pcall(func)
rawset(game, "player", nil)
if not suc then
return false
end

View File

@@ -1,394 +0,0 @@
---- module inserter
-- @gui Module
local Gui = require("modules/exp_gui")
local AABB = require("modules/exp_util/aabb")
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
local config = require("modules.exp_legacy.config.module") --- @dep config.module
local Selection = require("modules.exp_legacy.modules.control.selection") --- @dep modules.control.selection
local SelectionModuleArea = "ModuleArea"
local module_container -- Container for this GUI
local machine_names = {}
for mod_name, machine_set in pairs(config.machine_set) do
if script.active_mods[mod_name] then
for machine_name, v in pairs(machine_set) do
config.machine[machine_name] = v
table.insert(machine_names, machine_name)
end
end
end
local prod_module_names = {}
for name, item in pairs(prototypes.item) do
if item.module_effects and item.module_effects.productivity and item.module_effects.productivity > 0 then
prod_module_names[#prod_module_names + 1] = name
end
end
local elem_filter = {
machine_name = { {
filter = "name",
name = machine_names,
} },
no_prod = { {
filter = "type",
type = "module",
}, {
filter = "name",
name = prod_module_names,
mode = "and",
invert = true,
} },
with_prod = { {
filter = "type",
type = "module",
} },
}
--- Apply module changes to a crafting machine
--- @param player LuaPlayer
--- @param area BoundingBox
--- @param machine_name string
--- @param planner_with_prod LuaItemStack
--- @param planner_no_prod LuaItemStack
local function apply_module_to_crafter(player, area, machine_name, planner_with_prod, planner_no_prod)
local force = player.force
local surface = player.surface
local upgrade_area = surface.upgrade_area
--- @type BoundingBox
local param_area = { left_top = {}, right_bottom = {} }
--- @type LuaSurface.upgrade_area_param
local params = {
area = param_area,
item = planner_with_prod,
player = player,
force = force,
}
for _, entity in pairs(surface.find_entities_filtered{ area = area, name = machine_name, force = force }) do
local pos = entity.position
param_area.left_top = pos
param_area.right_bottom = pos
local m_current_recipe = entity.get_recipe()
local r_proto = m_current_recipe and m_current_recipe.prototype
if r_proto and (r_proto.maximum_productivity or (r_proto.allowed_effects and r_proto.allowed_effects["productivity"])) then
params.item = planner_with_prod
upgrade_area(params)
else
params.item = planner_no_prod
upgrade_area(params)
end
end
end
--- when an area is selected to add protection to the area
--- @param event EventData.on_player_selected_area
Selection.on_selection(SelectionModuleArea, function(event)
local area = AABB.expand(event.area)
local player = game.players[event.player_index]
local container = Gui.get_left_element(module_container, player)
local scroll_table = container.frame.scroll.table
-- Create an inventory with three upgrade planners
local inventory = game.create_inventory(3)
inventory.insert{ name = "upgrade-planner", count = 3 }
local planner_all = inventory[1]
local planner_with_prod = inventory[2]
local planner_no_prod = inventory[3]
local mapper_index = 1
for row = 1, config.default_module_row_count do
local machine_name = scroll_table["module_mm_" .. row .. "_0"].elem_value --[[@as string]]
local entity_proto = prototypes.entity[machine_name]
if machine_name then
local is_prod_crafter = false
local module_index = 1
local modules = {}
local no_prod = {}
-- Add all the modules selected
for column = 1, entity_proto.module_inventory_size do
local module_name = scroll_table["module_mm_" .. row .. "_" .. column].elem_value --[[ @as {name:string, quality:string} ]]
if module_name then
local not_prod = module_name.name:gsub("productivity", "efficiency")
modules[module_index] = module_name
no_prod[module_index] = { name = not_prod, quality = module_name.quality }
module_index = module_index + 1
if not is_prod_crafter and module_name ~= not_prod and entity_proto.get_crafting_speed() then
is_prod_crafter = true
end
else
modules[module_index] = {}
no_prod[module_index] = {}
module_index = module_index + 1
end
end
if is_prod_crafter then
-- Crafting machines with prod need to be handled on a case by case biases
local i = 0
for quality_name in pairs(prototypes.quality) do
i = i + 1
planner_with_prod.set_mapper(i, "from", {
type = "entity",
name = machine_name,
quality = quality_name,
comparator = "=",
})
planner_no_prod.set_mapper(i, "from", {
type = "entity",
name = machine_name,
quality = quality_name,
comparator = "=",
})
planner_with_prod.set_mapper(i, "to", {
type = "entity",
name = machine_name,
module_slots = modules,
quality = quality_name,
comparator = "=",
})
planner_no_prod.set_mapper(i, "to", {
type = "entity",
name = machine_name,
module_slots = no_prod,
quality = quality_name,
comparator = "=",
})
end
apply_module_to_crafter(player, area, machine_name, planner_with_prod, planner_no_prod)
else
-- All other machines can be applied in a single upgrade planner
for quality_name in pairs(prototypes.quality) do
planner_all.set_mapper(mapper_index, "from", {
type = "entity",
name = machine_name,
quality = quality_name,
comparator = "=",
})
planner_all.set_mapper(mapper_index, "to", {
type = "entity",
name = machine_name,
module_slots = modules,
quality = quality_name,
comparator = "=",
})
mapper_index = mapper_index + 1
end
end
end
end
-- Apply module changes for non crafting (or without prod selected)
if mapper_index > 1 then
player.surface.upgrade_area{
area = area,
item = planner_all,
force = player.force,
player = player,
}
end
inventory.destroy()
end)
--- Set the state of all elem selectors on a row
--- @param player LuaPlayer
--- @param element_name string
local function row_set(player, element_name)
local container = Gui.get_left_element(module_container, player)
local scroll_table = container.frame.scroll.table
local machine_name = scroll_table[element_name .. "0"].elem_value --[[ @as string ]]
if machine_name then
local active_to = prototypes.entity[machine_name].module_inventory_size
local row_count = math.ceil(active_to / config.module_slots_per_row)
local visible_to = row_count * config.module_slots_per_row
for i = 1, config.module_slot_max do
local element = scroll_table[element_name .. i]
if i <= active_to then
if config.machine[machine_name].prod then
element.elem_filters = elem_filter.with_prod
else
element.elem_filters = elem_filter.no_prod
end
element.visible = true
element.enabled = true
element.elem_value = { name = config.machine[machine_name].module }
else
element.visible = i <= visible_to
element.enabled = false
element.elem_value = nil
end
if i % (config.module_slots_per_row + 1) == 0 then
scroll_table[element_name .. "pad" .. i].visible = element.visible
end
end
else
for i = 1, config.module_slot_max do
local element = scroll_table[element_name .. i]
element.visible = i <= config.module_slots_per_row
element.enabled = false
element.elem_value = nil
if i % (config.module_slots_per_row + 1) == 0 then
scroll_table[element_name .. "pad" .. i].visible = false
end
end
end
end
local button_apply = Gui.element("button_apply")
:draw{
type = "button",
caption = { "module.apply" },
style = "button",
}:on_click(function(def, player, element)
if Selection.is_selecting(player, SelectionModuleArea) then
Selection.stop(player)
else
Selection.start(player, SelectionModuleArea)
end
end)
module_container = Gui.element("module_container")
:draw(function(definition, parent)
local container = Gui.elements.container(parent, (config.module_slots_per_row + 2) * 36)
Gui.elements.header(container, {
caption = "Module Inserter",
})
local slots_per_row = config.module_slots_per_row + 1
local scroll_table = Gui.elements.scroll_table(container, (config.module_slots_per_row + 2) * 36, slots_per_row, "scroll")
for i = 1, config.default_module_row_count do
scroll_table.add{
name = "module_mm_" .. i .. "_0",
type = "choose-elem-button",
elem_type = "entity",
elem_filters = elem_filter.machine_name,
style = "slot_button",
}
for j = 1, config.module_slot_max do
if j % slots_per_row == 0 then
scroll_table.add{
type = "flow",
name = "module_mm_" .. i .. "_pad" .. j,
visible = false,
}
end
scroll_table.add{
name = "module_mm_" .. i .. "_" .. j,
type = "choose-elem-button",
elem_type = "item-with-quality",
elem_filters = elem_filter.no_prod,
style = "slot_button",
enabled = false,
visible = j <= config.module_slots_per_row,
}
end
end
button_apply(container)
return container.parent
end)
--- Add the element to the left flow with a toolbar button
Gui.add_left_element(module_container, false)
Gui.toolbar.create_button{
name = "module_toggle",
left_element = module_container,
sprite = "item/productivity-module-3",
tooltip = { "module.main-tooltip" },
visible = function(player, element)
return Roles.player_allowed(player, "gui/module")
end
}
--- @param event EventData.on_gui_elem_changed
Event.add(defines.events.on_gui_elem_changed, function(event)
if event.element.name:sub(1, 10) == "module_mm_" then
if event.element.name:sub(-1) == "0" then
row_set(game.players[event.player_index], "module_mm_" .. event.element.name:sub(-3):sub(1, 1) .. "_")
end
end
end)
--- @param event EventData.on_entity_settings_pasted
Event.add(defines.events.on_entity_settings_pasted, function(event)
local source = event.source
local destination = event.destination
local player = game.players[event.player_index]
if not player then
return
end
if not source or not source.valid then
return
end
if not destination or not destination.valid then
return
end
-- rotate machine also
if config.copy_paste_rotation then
if (source.name == destination.name or source.prototype.fast_replaceable_group == destination.prototype.fast_replaceable_group) then
if source.supports_direction and destination.supports_direction and source.type ~= "transport-belt" then
local destination_box = destination.bounding_box
local ltx = destination_box.left_top.x
local lty = destination_box.left_top.y
local rbx = destination_box.right_bottom.x
local rby = destination_box.right_bottom.y
local old_direction = destination.direction
destination.direction = source.direction
if ltx ~= destination_box.left_top.x or lty ~= destination_box.left_top.y or rbx ~= destination_box.right_bottom.x or rby ~= destination_box.right_bottom.y then
destination.direction = old_direction
end
end
end
end
--[[
TODO handle later as may need using global to reduce creation of upgrade plans
if config.copy_paste_module then
if source.name ~= destination.name then
return
end
local source_inventory = source.get_module_inventory()
if not source_inventory then
return
end
local source_inventory_content = source_inventory.get_contents()
if not source_inventory_content then
return
end
clear_module(player, destination.bounding_box, destination.name)
if next(source_inventory_content) ~= nil then
apply_module(player, destination.bounding_box, destination.name, { ["n"] = source_inventory_content, ["p"] = source_inventory_content })
end
end
]]
end)

View File

@@ -23,13 +23,13 @@ config.set_datastores(SelectedPlayer, SelectedAction)
--- Button used to open the action bar
-- @element open_action_bar
local open_action_bar = Gui.element("open_action_bar")
local open_action_bar = Gui.define("open_action_bar")
:draw{
type = "sprite-button",
sprite = "utility/expand_dots",
tooltip = { "player-list.open-action-bar" },
style = "frame_button",
name = Gui.property_from_name,
name = Gui.from_name,
}
:style{
padding = -2,
@@ -48,7 +48,7 @@ local open_action_bar = Gui.element("open_action_bar")
--- Button used to close the action bar
-- @element close_action_bar
local close_action_bar = Gui.element("close_action_bar")
local close_action_bar = Gui.define("close_action_bar")
:draw{
type = "sprite-button",
sprite = "utility/close_black",
@@ -68,7 +68,7 @@ local close_action_bar = Gui.element("close_action_bar")
--- Button used to confirm a reason
-- @element reason_confirm
local reason_confirm = Gui.element("reason_confirm")
local reason_confirm = Gui.define("reason_confirm")
:draw{
type = "sprite-button",
sprite = "utility/confirm_slot",
@@ -94,7 +94,7 @@ local reason_confirm = Gui.element("reason_confirm")
--- Set of elements that are used to make up a row of the player table
-- @element add_player_base
local add_player_base = Gui.element("add_player_base")
local add_player_base = Gui.define("add_player_base")
:draw(function(_, parent, player_data)
-- Add the button to open the action bar
local toggle_action_bar_flow = parent.add{ type = "flow", name = player_data.name }
@@ -162,7 +162,7 @@ end
--- Adds all the buttons and flows that make up the action bar
-- @element add_action_bar
local add_action_bar_buttons = Gui.element("add_action_bar_buttons")
local add_action_bar_buttons = Gui.define("add_action_bar_buttons")
:draw(function(_, parent)
close_action_bar(parent)
-- Loop over all the buttons in the config
@@ -210,10 +210,10 @@ end
--- Main player list container for the left flow
-- @element player_list_container
local player_list_container = Gui.element("player_list_container")
local player_list_container = Gui.define("player_list_container")
:draw(function(definition, parent)
-- Draw the internal container
local container = Gui.elements.container(parent, 200)
local container = Gui.elements.container(parent)
-- Draw the scroll table for the players
local scroll_table = Gui.elements.scroll_table(container, 184, 3, "scroll")

View File

@@ -1,224 +0,0 @@
---- module pd
-- @gui PlayerData
local ExpUtil = require("modules/exp_util")
local Gui = require("modules/exp_gui")
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data
require("modules.exp_legacy.modules.data.statistics")
local format_number = require("util").format_number --- @dep util
local pd_container
local label_width = {
["name"] = 135,
["count"] = 105,
["total"] = 480,
}
local short_time_format = ExpUtil.format_time_factory_locale{ format = "short", coefficient = 3600, hours = true, minutes = true }
local function format_number_n(n)
return format_number(math.floor(n), false) .. string.format("%.2f", n % 1):sub(2)
end
local PlayerStats = PlayerData.Statistics
local computed_stats = {
DamageDeathRatio = {
default = format_number_n(0),
calculate = function(player_name)
return format_number_n(PlayerStats["DamageDealt"]:get(player_name, 0) / PlayerStats["Deaths"]:get(player_name, 1))
end,
},
KillDeathRatio = {
default = format_number_n(0),
calculate = function(player_name)
return format_number_n(PlayerStats["Kills"]:get(player_name, 0) / PlayerStats["Deaths"]:get(player_name, 1))
end,
},
SessionTime = {
default = short_time_format(0),
calculate = function(player_name)
return short_time_format((PlayerStats["Playtime"]:get(player_name, 0) - PlayerStats["AfkTime"]:get(player_name, 0)) / PlayerStats["JoinCount"]:get(player_name, 1))
end,
},
BuildRatio = {
default = format_number_n(0),
calculate = function(player_name)
return format_number_n(PlayerStats["MachinesBuilt"]:get(player_name, 0) / PlayerStats["MachinesRemoved"]:get(player_name, 1))
end,
},
RocketPerHour = {
default = format_number_n(0),
calculate = function(player_name)
return format_number_n(PlayerStats["RocketsLaunched"]:get(player_name, 0) * 60 / PlayerStats["Playtime"]:get(player_name, 1))
end,
},
TreeKillPerMinute = {
default = format_number_n(0),
calculate = function(player_name)
return format_number_n(PlayerStats["TreesDestroyed"]:get(player_name, 0) / PlayerStats["Playtime"]:get(player_name, 1))
end,
},
NetPlayTime = {
default = short_time_format(0),
calculate = function(player_name)
return short_time_format((PlayerStats["Playtime"]:get(player_name, 0) - PlayerStats["AfkTime"]:get(player_name, 0)))
end,
},
AFKTimeRatio = {
default = format_number_n(0),
calculate = function(player_name)
return format_number_n(PlayerStats["AfkTime"]:get(player_name, 0) * 100 / PlayerStats["Playtime"]:get(player_name, 1))
end,
},
Locale = {
default = "en",
calculate = function(player)
return player.locale
end,
},
}
local label = Gui.element("label")
:draw(function(_, parent, width, caption, tooltip, name)
local new_label = parent.add{
type = "label",
caption = caption,
tooltip = tooltip,
name = name,
style = "heading_2_label",
}
new_label.style.width = width
return new_label
end)
local pd_data_set = Gui.element("pd_data_set")
:draw(function(_, parent, name)
local pd_data_set = parent.add{ type = "flow", direction = "vertical", name = name }
local disp = Gui.elements.scroll_table(pd_data_set, label_width["total"], 4, "disp")
for _, stat_name in pairs(PlayerData.Statistics.metadata.display_order) do
local child = PlayerData.Statistics[stat_name]
local metadata = child.metadata
local value = metadata.stringify_short and metadata.stringify_short(0) or metadata.stringify and metadata.stringify(0) or format_number(0, false)
label(disp, label_width["name"], metadata.name or { "exp-statistics." .. stat_name }, metadata.tooltip or { "exp-statistics." .. stat_name .. "-tooltip" })
label(disp, label_width["count"], { "readme.data-format", value, metadata.unit or "" }, metadata.value_tooltip or { "exp-statistics." .. stat_name .. "-tooltip" }, stat_name)
end
for stat_name, data in pairs(computed_stats) do
label(disp, label_width["name"], { "exp-statistics." .. stat_name }, { "exp-statistics." .. stat_name .. "-tooltip" })
label(disp, label_width["count"], { "readme.data-format", data.default, "" }, { "exp-statistics." .. stat_name .. "-tooltip" }, stat_name)
end
return pd_data_set
end)
local function pd_update(table, player_name)
for _, stat_name in pairs(PlayerData.Statistics.metadata.display_order) do
local child = PlayerData.Statistics[stat_name]
local metadata = child.metadata
local value = child:get(player_name)
if metadata.stringify_short then
value = metadata.stringify_short(value or 0)
elseif metadata.stringify then
value = metadata.stringify(value or 0)
else
value = format_number(value or 0, false)
end
table[stat_name].caption = { "readme.data-format", value, metadata.unit or "" }
end
for stat_name, data in pairs(computed_stats) do
table[stat_name].caption = { "readme.data-format", data.calculate(player_name), "" }
end
end
local pd_username_player = Gui.element("pd_username_player")
:draw(function(def, parent, player_list)
return parent.add{
name = def.name,
type = "drop-down",
items = player_list,
selected_index = #player_list > 0 and 1 or nil,
}
end)
:style{
horizontally_stretchable = true,
}:on_selection_state_changed(function(def, player, element)
local player_name = game.connected_players[element.selected_index]
local table = element.parent.parent.parent.parent["pd_st_2"].disp.table
pd_update(table, player_name)
end)
local pd_username_update = Gui.element("pd_username_update")
:draw{
type = "button",
name = Gui.property_from_name,
caption = "update",
}:style{
width = 128,
}:on_click(function(def, player, element)
local player_index = element.parent[pd_username_player.name].selected_index
if player_index > 0 then
local player_name = game.connected_players[player_index]
local table = element.parent.parent.parent.parent["pd_st_2"].disp.table
pd_update(table, player_name)
end
end)
local pd_username_set = Gui.element("pd_username_set")
:draw(function(_, parent, name, player_list)
local pd_username_set = parent.add{ type = "flow", direction = "vertical", name = name }
local disp = Gui.elements.scroll_table(pd_username_set, label_width["total"], 2, "disp")
pd_username_player(disp, player_list)
pd_username_update(disp)
return pd_username_set
end)
pd_container = Gui.element("pd_container")
:draw(function(def, parent)
local container = Gui.elements.container(parent, label_width["total"])
local player_list = {}
for _, player in pairs(game.connected_players) do
table.insert(player_list, player.name)
end
pd_username_set(container, "pd_st_1", player_list)
pd_data_set(container, "pd_st_2")
return container.parent
end)
--- Add the element to the left flow with a toolbar button
Gui.add_left_element(pd_container, false)
Gui.toolbar.create_button{
name = "player_data_toggle",
left_element = pd_container,
sprite = "item/power-armor-mk2",
tooltip = "Player Data GUI",
visible = function(player, element)
return Roles.player_allowed(player, "gui/playerdata")
end
}
local function gui_player_list_update()
local player_list = {}
for _, player in pairs(game.connected_players) do
table.insert(player_list, player.name)
end
for _, player in pairs(game.connected_players) do
local container = Gui.get_left_element(pd_container, player)
container.frame["pd_st_1"].disp.table[pd_username_player.name].items = player_list
end
end
Event.add(defines.events.on_player_joined_game, gui_player_list_update)
Event.add(defines.events.on_player_left_game, gui_player_list_update)

View File

@@ -1,152 +0,0 @@
---- Production Data
-- @gui Production
local Gui = require("modules/exp_gui")
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
local production_container
local precision = {
[1] = defines.flow_precision_index.five_seconds,
[2] = defines.flow_precision_index.one_minute,
[3] = defines.flow_precision_index.ten_minutes,
[4] = defines.flow_precision_index.one_hour,
[5] = defines.flow_precision_index.ten_hours,
}
local font_color = {
["positive"] = { r = 0.3, g = 1, b = 0.3 },
["negative"] = { r = 1, g = 0.3, b = 0.3 },
}
local function format_n(amount)
if math.abs(amount) < 0.009 then
return "0.00"
end
local suffix = ""
local suffix_list = {
[" G"] = 1e9,
[" M"] = 1e6,
[" k"] = 1e3
}
local scale = 1
for letter, limit in pairs(suffix_list) do
if math.abs(amount) >= limit then
scale = limit
suffix = letter
break
end
end
local formatted = string.format("%.2f%s", amount / scale, suffix)
-- Split into integer and fractional parts
local integer_part, fractional_part = formatted:match("^(%-?%d+)%.(%d+)(.*)$")
-- Add commas to integer part
return string.format("%s.%s%s", (integer_part or formatted):reverse():gsub("(%d%d%d)", "%1,"):reverse():gsub("^,", ""):gsub("-,", "-"), fractional_part or "00", suffix)
end
--- Display group
-- @element production_data_group
local production_data_group = Gui.element("production_data_group")
:draw(function(_def, parent, i)
local item
if i == 0 then
item = parent.add{
type = "drop-down",
name = "production_0_e",
items = { "5s", "1m", "10m", "1h", "10h" },
selected_index = 3,
}
item.style.width = 80
else
item = parent.add{
type = "choose-elem-button",
name = "production_" .. i .. "_e",
elem_type = "item",
style = "slot_button",
}
item.style.height = 32
item.style.width = 32
end
for j = 1, 3 do
local data = parent.add{
type = "label",
name = "production_" .. i .. "_" .. j,
caption = "0.00",
style = "heading_2_label",
}
data.style.font_color = font_color["positive"]
end
return item
end)
--- A vertical flow containing all the production data
-- @element production_data_set
local production_data_set = Gui.element("production_data_set")
:draw(function(_, parent, name)
local production_set = parent.add{ type = "flow", direction = "vertical", name = name }
local disp = Gui.elements.scroll_table(production_set, 320, 4, "disp")
for i = 2, 4 do
disp.style.column_alignments[i] = "right"
end
production_data_group(disp, 0)
disp["production_0_1"].caption = { "production.label-prod" }
disp["production_0_1"].tooltip = { "production.tooltip-per-second" }
disp["production_0_2"].caption = { "production.label-con" }
disp["production_0_2"].tooltip = { "production.tooltip-per-second" }
disp["production_0_3"].caption = { "production.label-bal" }
disp["production_0_3"].tooltip = { "production.tooltip-per-second" }
for i = 1, 8 do
production_data_group(disp, i)
end
return production_set
end)
production_container = Gui.element("production_container")
:draw(function(def, parent)
local container = Gui.elements.container(parent, 320)
production_data_set(container, "production_st")
return container.parent
end)
--- Add the element to the left flow with a toolbar button
Gui.add_left_element(production_container, false)
Gui.toolbar.create_button{
name = "production_toggle",
left_element = production_container,
sprite = "entity/assembling-machine-3",
tooltip = { "production.main-tooltip" },
visible = function(player, element)
return Roles.player_allowed(player, "gui/production")
end
}
Event.on_nth_tick(60, function()
for _, player in pairs(game.connected_players) do
local container = Gui.get_left_element(production_container, player)
local stat = player.force.get_item_production_statistics(player.surface) -- Allow remote view
local precision_value = precision[container.frame["production_st"].disp.table["production_0_e"].selected_index]
local table = container.frame["production_st"].disp.table
for i = 1, 8 do
local production_prefix = "production_" .. i
local item = table[production_prefix .. "_e"].elem_value --[[ @as string ]]
if item then
local add = math.floor(stat.get_flow_count{ name = item, category = "input", precision_index = precision_value, count = false } / 6) / 10
local minus = math.floor(stat.get_flow_count{ name = item, category = "output", precision_index = precision_value, count = false } / 6) / 10
local sum = add - minus
table[production_prefix .. "_1"].caption = format_n(add)
table[production_prefix .. "_2"].caption = format_n(minus)
table[production_prefix .. "_3"].caption = format_n(sum)
table[production_prefix .. "_3"].style.font_color = (sum < 0 and font_color["negative"]) or font_color["positive"]
else
table[production_prefix .. "_1"].caption = "0.00"
table[production_prefix .. "_2"].caption = "0.00"
table[production_prefix .. "_3"].caption = "0.00"
table[production_prefix .. "_3"].style.font_color = font_color["positive"]
end
end
end
end)

View File

@@ -23,7 +23,7 @@ local title_width = 270 -- controls the centering of the titles
local scroll_height = 275 -- controls the height of the scrolls
--- Sub content area used within the content areas
local sub_content = Gui.element("readme_sub_content")
local sub_content = Gui.define("readme_sub_content")
:draw{
type = "frame",
direction = "vertical",
@@ -37,7 +37,7 @@ local sub_content = Gui.element("readme_sub_content")
}
--- Table which has a title above it above it
local title_table = Gui.element("readme_title_table")
local title_table = Gui.define("readme_title_table")
:draw(function(_, parent, bar_size, caption, column_count)
Gui.elements.title_label(parent, bar_size, caption)
@@ -55,7 +55,7 @@ local title_table = Gui.element("readme_title_table")
}
--- Scroll to be used with Gui.elements.title_label tables
local title_table_scroll = Gui.element("readme_title_table_scroll")
local title_table_scroll = Gui.define("readme_title_table_scroll")
:draw{
type = "scroll-pane",
direction = "vertical",
@@ -70,7 +70,7 @@ local title_table_scroll = Gui.element("readme_title_table_scroll")
}
--- Used to connect to servers in server list
local join_server = Gui.element("readme_join_server")
local join_server = Gui.define("readme_join_server")
:draw(function(_, parent, server_id, wrong_version)
local status = External.get_server_status(server_id) or "Offline"
if wrong_version then status = "Version" end
@@ -111,7 +111,7 @@ local join_server = Gui.element("readme_join_server")
local welcome_time_format = ExpUtil.format_time_factory_locale{ format = "long", days = true, hours = true, minutes = true }
--- Content area for the welcome tab
define_tab({ "readme.welcome-tab" }, { "readme.welcome-tooltip" }, Gui.element("readme_welcome")
define_tab({ "readme.welcome-tab" }, { "readme.welcome-tooltip" }, Gui.define("readme_welcome")
:draw(function(_, parent)
local server_details = { name = "APERX S0 - Local", welcome = "Failed to load description: disconnected from external api.", reset_time = "Non Set", branch = "Unknown" }
if External.valid() then server_details = External.get_current_server() end
@@ -148,7 +148,7 @@ define_tab({ "readme.welcome-tab" }, { "readme.welcome-tooltip" }, Gui.element("
end))
--- Content area for the rules tab
define_tab({ "readme.rules-tab" }, { "readme.rules-tooltip" }, Gui.element("readme_rules")
define_tab({ "readme.rules-tab" }, { "readme.rules-tooltip" }, Gui.define("readme_rules")
:draw(function(_, parent)
local container = parent.add{ type = "flow", direction = "vertical" }
@@ -172,7 +172,7 @@ define_tab({ "readme.rules-tab" }, { "readme.rules-tooltip" }, Gui.element("read
end))
--- Content area for the commands tab
define_tab({ "readme.commands-tab" }, { "readme.commands-tooltip" }, Gui.element("readme_commands")
define_tab({ "readme.commands-tab" }, { "readme.commands-tooltip" }, Gui.define("readme_commands")
:draw(function(_, parent)
local container = parent.add{ type = "flow", direction = "vertical" }
local player = Gui.get_player(parent)
@@ -198,7 +198,7 @@ define_tab({ "readme.commands-tab" }, { "readme.commands-tooltip" }, Gui.element
end))
--- Content area for the servers tab
define_tab({ "readme.servers-tab" }, { "readme.servers-tooltip" }, Gui.element("readme_servers")
define_tab({ "readme.servers-tab" }, { "readme.servers-tooltip" }, Gui.define("readme_servers")
:draw(function(_, parent)
local container = parent.add{ type = "flow", direction = "vertical" }
@@ -241,7 +241,7 @@ define_tab({ "readme.servers-tab" }, { "readme.servers-tooltip" }, Gui.element("
end))
--- Content area for the servers tab
define_tab({ "readme.backers-tab" }, { "readme.backers-tooltip" }, Gui.element("readme_backers")
define_tab({ "readme.backers-tab" }, { "readme.backers-tooltip" }, Gui.define("readme_backers")
:draw(function(_, parent)
local container = parent.add{ type = "flow", direction = "vertical" }
@@ -304,7 +304,7 @@ define_tab({ "readme.backers-tab" }, { "readme.backers-tooltip" }, Gui.element("
end))
--- Content area for the player data tab
define_tab({ "readme.data-tab" }, { "readme.data-tooltip" }, Gui.element("readme_data")
define_tab({ "readme.data-tab" }, { "readme.data-tooltip" }, Gui.define("readme_data")
:draw(function(_, parent)
local container = parent.add{ type = "flow", direction = "vertical" }
local player = Gui.get_player(parent)
@@ -399,7 +399,7 @@ define_tab({ "readme.data-tab" }, { "readme.data-tooltip" }, Gui.element("readme
--- Main readme container for the center flow
local readme_toggle
local readme = Gui.element("readme")
local readme = Gui.define("readme")
:draw(function(def, parent)
local container = parent.add{
name = def.name,

View File

@@ -1,292 +0,0 @@
--- research gui
-- @gui Research
local ExpUtil = require("modules/exp_util")
local Gui = require("modules/exp_gui")
local Storage = require("modules/exp_util/storage")
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
local config = require("modules.exp_legacy.config.research") --- @dep config.research
local table_to_json = helpers.table_to_json
local write_file = helpers.write_file
local research = {
time = {},
res_queue_enable = false
}
Storage.register(research, function(tbl)
research = tbl
end)
for _, mod_name in ipairs(config.mod_set_lookup) do
if script.active_mods[mod_name] then
config.mod_set = mod_name
break
end
end
local research_time_format = ExpUtil.format_time_factory{ format = "clock", hours = true, minutes = true, seconds = true }
local empty_time = research_time_format(nil)
local font_color = {
["neutral"] = { r = 1, g = 1, b = 1 },
["positive"] = { r = 0.3, g = 1, b = 0.3 },
["negative"] = { r = 1, g = 0.3, b = 0.3 },
}
local res = {
["lookup_name"] = {},
["disp"] = {},
}
do
local res_total = 0
local i = 1
for k, v in pairs(config.milestone[config.mod_set]) do
research.time[i] = 0
res["lookup_name"][k] = i
res_total = res_total + v * 60
res["disp"][i] = {
raw_name = k,
target = res_total,
target_disp = research_time_format(res_total),
}
i = i + 1
end
end
local function research_add_log()
local result_data = {}
for i = 1, #research.time do
result_data[res["disp"][i]["raw_name"]] = research.time[i]
end
write_file(config.file_name, table_to_json(result_data) .. "\n", true, 0)
end
local function research_res_n()
local current = #res.disp + 1
for i = 1, #res.disp do
if research.time[i] == 0 then
current = i
break
end
end
return math.clamp(current - 3, 1, math.max(1, #res.disp - 7))
end
local function research_notification(event)
if config.inf_res[config.mod_set][event.research.name] then
if event.research.name == config.bonus_inventory.log[config.mod_set].name and event.research.level == config.bonus_inventory.log[config.mod_set].level + 1 then
-- Add run result to log
research_add_log()
end
if not (event.by_script) then
game.print{ "research.inf", research_time_format(game.tick), event.research.name, event.research.level - 1 }
end
elseif not (event.by_script) then
game.print{ "research.msg", research_time_format(game.tick), event.research.name }
end
if config.bonus_inventory.enabled and config.bonus_inventory.res[event.research.name] then
event.research.force[config.bonus_inventory.name] = math.min((event.research.level - 1) * config.bonus_inventory.rate, config.bonus_inventory.limit)
end
if config.pollution_ageing_by_research and config.bonus_inventory.res[event.research.name] then
game.map_settings.pollution.ageing = math.min(10, event.research.level / 5)
end
end
local function research_gui_update()
local res_disp = {}
local res_n = research_res_n()
for i = 1, 8 do
local res_i = res_n + i - 1
local entry = res.disp[res_i] or {}
local data = {
name = "",
target = "",
attempt = "",
difference = "",
color = font_color["positive"]
}
if entry.raw_name then
assert(prototypes.technology[entry.raw_name], "Invalid Research: " .. tostring(entry.raw_name))
data.name = { "research.res-name", entry.raw_name, prototypes.technology[entry.raw_name].localised_name }
data.target = entry.target_disp
if research.time[res_i] == 0 then
data.attempt = empty_time
data.difference = empty_time
else
data.attempt = research_time_format(research.time[res_i])
local diff = research.time[res_i] - entry.target
data.difference = (diff < 0 and "-" or "") .. research_time_format(math.abs(diff))
data.color = (diff < 0 and font_color["positive"]) or font_color["negative"]
end
end
res_disp[i] = data
end
return res_disp
end
--- Display label for the clock display
-- @element research_gui_clock_display
local research_gui_clock = Gui.element("research_gui_clock")
:draw{
type = "label",
name = Gui.property_from_name,
caption = empty_time,
style = "heading_2_label",
}
--- A vertical flow containing the clock
-- @element research_clock_set
local research_clock_set = Gui.element("research_clock_set")
:draw(function(_, parent, name)
local research_set = parent.add{ type = "flow", direction = "vertical", name = name }
local disp = Gui.elements.scroll_table(research_set, 390, 1, "disp")
research_gui_clock(disp)
return research_set
end)
--- Display group
-- @element research_data_group
local research_data_group = Gui.element("research_data_group")
:draw(function(_def, parent, i)
local labels = { "name", "target", "attempt", "difference" }
for _, label in ipairs(labels) do
local elem = parent.add{
type = "label",
name = "research_" .. i .. "_" .. label,
caption = "",
style = "heading_2_label"
}
elem.style.minimal_width = (label == "name" and 180) or 70
elem.style.horizontal_align = (label == "name" and "left") or "right"
elem.style.font_color = (label == "difference" and font_color["positive"]) or font_color["neutral"]
end
end)
--- A vertical flow containing the data
-- @element research_data_set
local research_data_set = Gui.element("research_data_set")
:draw(function(_, parent, name)
local research_set = parent.add{ type = "flow", direction = "vertical", name = name }
local disp = Gui.elements.scroll_table(research_set, 390, 4, "disp")
local res_disp = research_gui_update()
research_data_group(disp, 0)
disp["research_0_name"].caption = { "research.name" }
disp["research_0_target"].caption = { "research.target" }
disp["research_0_attempt"].caption = { "research.attempt" }
disp["research_0_difference"].caption = { "research.difference" }
disp["research_0_difference"].style.font_color = font_color["neutral"]
for i = 1, 8 do
research_data_group(disp, i)
local research_name_i = "research_" .. i
disp[research_name_i .. "_name"].caption = res_disp[i]["name"]
disp[research_name_i .. "_target"].caption = res_disp[i]["target"]
disp[research_name_i .. "_attempt"].caption = res_disp[i]["attempt"]
disp[research_name_i .. "_difference"].caption = res_disp[i]["difference"]
disp[research_name_i .. "_difference"].style.font_color = res_disp[i]["color"]
end
return research_set
end)
local research_container = Gui.element("research_container")
:draw(function(def, parent)
local container = Gui.elements.container(parent, 390)
research_clock_set(container, "research_st_1")
research_data_set(container, "research_st_2")
return container.parent
end)
--- Add the element to the left flow with a toolbar button
Gui.add_left_element(research_container, false)
Gui.toolbar.create_button{
name = "research_toggle",
left_element = research_container,
sprite = "item/space-science-pack",
tooltip = { "research.main-tooltip" },
visible = function(player, element)
return Roles.player_allowed(player, "gui/research")
end
}
Event.add(defines.events.on_research_finished, function(event)
research_notification(event)
local research_name = event.research.name
if not res["lookup_name"][research_name] then
return
end
research.time[res.lookup_name[research_name]] = game.tick
local res_disp = research_gui_update()
for _, player in pairs(game.connected_players) do
if Roles.player_allowed(player, "gui/research") then
local container = Gui.get_left_element(research_container, player)
local disp = container.frame["research_st_2"].disp.table
for i = 1, 8 do
local research_name_i = "research_" .. i
disp[research_name_i .. "_name"].caption = res_disp[i]["name"]
disp[research_name_i .. "_target"].caption = res_disp[i]["target"]
disp[research_name_i .. "_attempt"].caption = res_disp[i]["attempt"]
disp[research_name_i .. "_difference"].caption = res_disp[i]["difference"]
disp[research_name_i .. "_difference"].style.font_color = res_disp[i]["color"]
end
end
end
end)
Event.add(defines.events.on_research_started, function(event)
if config.limit_res[event.research.name] and event.research.level > config.limit_res[event.research.name] then
event.research.enabled = false
event.research.visible_when_disabled = true
local rq = event.research.force.research_queue
for i = #rq, 1, -1 do
if rq[i] == event.research.name then
table.remove(rq, i)
end
end
event.research.force.cancel_current_research()
event.research.force.research_queue = rq
end
end)
Event.on_nth_tick(60, function()
local current_time = research_time_format(game.tick)
for _, player in pairs(game.connected_players) do
local container = Gui.get_left_element(research_container, player)
local disp = container.frame["research_st_1"].disp.table
disp[research_gui_clock.name].caption = current_time
end
end)

View File

@@ -39,12 +39,12 @@ end
--- Button to toggle the auto launch on a rocket silo
-- @element toggle_launch
local toggle_launch = Gui.element("toggle_launch")
local toggle_launch = Gui.define("toggle_launch")
:draw{
type = "sprite-button",
sprite = "utility/play",
tooltip = { "rocket-info.toggle-rocket-tooltip" },
name = Gui.property_from_name,
name = Gui.from_name,
}
:style(Gui.styles.sprite{
size = 16,
@@ -65,7 +65,7 @@ local toggle_launch = Gui.element("toggle_launch")
--- XY cords that allow zoom to map when pressed
-- @element silo_cords
local silo_cords = Gui.element("silo_cords")
local silo_cords = Gui.define("silo_cords")
:draw(function(definition, parent, silo_data)
local silo_name = silo_data.silo_name
local pos = silo_data.position
@@ -105,6 +105,8 @@ local silo_cords = Gui.element("silo_cords")
definition:link_element(label_x)
definition:link_element(label_y)
end
return Gui.no_return()
end)
:on_click(function(def, player, element)
local rocket_silo_name = element.parent.caption
@@ -114,7 +116,7 @@ local silo_cords = Gui.element("silo_cords")
--- Base element for each rocket in the progress list
-- @element rocket_entry
local rocket_entry = Gui.element("rocket_entry")
local rocket_entry = Gui.define("rocket_entry")
:draw(function(_, parent, silo_data)
local silo_name = silo_data.silo_name
local player = Gui.get_player(parent)
@@ -149,7 +151,7 @@ local rocket_entry = Gui.element("rocket_entry")
--- Data label which contains a name and a value label pair
-- @element data_label
local data_label = Gui.element("data_label")
local data_label = Gui.define("data_label")
:draw(function(_, parent, label_data)
local data_name = label_data.name
local data_subname = label_data.subname
@@ -392,14 +394,14 @@ end
-- Button to toggle a section dropdown
-- @element toggle_section
local toggle_section = Gui.element("rocket_info_toggle_section")
local toggle_section = Gui.define("rocket_info_toggle_section")
:draw{
type = "sprite-button",
sprite = "utility/expand",
hovered_sprite = "utility/expand",
tooltip = { "rocket-info.toggle-section-tooltip" },
style = "frame_action_button",
name = Gui.property_from_name,
name = Gui.from_name,
}
:style(Gui.styles.sprite{
size = 20,
@@ -419,17 +421,16 @@ local toggle_section = Gui.element("rocket_info_toggle_section")
-- Draw a section header and main scroll
-- @element rocket_list_container
local section = Gui.element("rocket_info_section")
local section = Gui.define("rocket_info_section")
:draw(function(definition, parent, section_name, table_size)
-- Draw the header for the section
local header = Gui.elements.header(parent, {
name = section_name .. "-header",
caption = { "rocket-info.section-caption-" .. section_name },
tooltip = { "rocket-info.section-tooltip-" .. section_name },
label_name = "label",
})
definition:link_element(header.parent.label)
definition:link_element(header.label)
-- Right aligned button to toggle the section
header.caption = section_name
@@ -449,7 +450,7 @@ local section = Gui.element("rocket_info_section")
--- Main gui container for the left flow
-- @element rocket_list_container
local rocket_list_container = Gui.element("rocket_list_container")
local rocket_list_container = Gui.define("rocket_list_container")
:draw(function(definition, parent)
-- Draw the internal container
local container = Gui.elements.container(parent, 200)

View File

@@ -1,388 +0,0 @@
--[[-- Gui Module - Science Info
- Adds a science info gui that shows production usage and net for the different science packs as well as an eta
@gui Science-Info
@alias science_info
]]
local ExpUtil = require("modules/exp_util")
local Gui = require("modules/exp_gui")
local Roles = require("modules.exp_legacy.expcore.roles")
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
local config = require("modules.exp_legacy.config.gui.science") --- @dep config.gui.science
local Production = require("modules.exp_legacy.modules.control.production") --- @dep modules.control.production
local clock_time_format = ExpUtil.format_time_factory_locale{ format = "clock", hours = true, minutes = true, seconds = true }
local long_time_format = ExpUtil.format_time_factory_locale{ format = "long", hours = true, minutes = true, seconds = true }
local null_time_clock = { "science-info.eta-time", clock_time_format(nil) }
local null_time_long = long_time_format(nil)
--- Remove invalid science packs, this can result from a certain mod not being loaded
for i = #config, 1, -1 do
if not prototypes.item[config[i]] then
table.remove(config, i)
end
end
--- Data label that contains the value and the suffix
-- @element production_label
local production_label = Gui.element("science_info_production_label")
:draw(function(_, parent, production_label_data)
local name = production_label_data.name
local tooltip = production_label_data.tooltip
local color = production_label_data.color
-- Add an alignment for the number
local alignment = Gui.elements.aligned_flow(parent, { name = name })
-- Add the main value label
local element =
alignment.add{
name = "label",
type = "label",
caption = production_label_data.caption,
tooltip = tooltip,
}
-- Change the style
element.style.font_color = color
-- Add the suffix label
local suffix_element =
parent.add{
name = "suffix-" .. name,
type = "label",
caption = { "science-info.unit", production_label_data.suffix },
tooltip = tooltip,
}
-- Change the style
local suffix_element_style = suffix_element.style
suffix_element_style.font_color = color
suffix_element_style.right_margin = 1
-- Return the value label
return element
end)
-- Get the data that is used with the production label
local function get_production_label_data(name, tooltip, value, cutout, secondary)
local data_colour = Production.get_color(config.color_cutoff * cutout, value, secondary)
local suffix, caption = Production.format_number(value)
return {
name = name,
caption = caption,
suffix = suffix,
tooltip = tooltip,
color = data_colour,
}
end
-- Updates a prodution label to match the current data
local function update_production_label(parent, production_label_data)
local name = production_label_data.name
local tooltip = production_label_data.tooltip
local color = production_label_data.color
-- Update the production label
local production_label_element = parent[name] and parent[name].label or production_label(parent, production_label_data)
production_label_element.caption = production_label_data.caption
production_label_element.tooltip = production_label_data.tooltip
production_label_element.style.font_color = color
-- Update the suffix label
local suffix_element = parent["suffix-" .. name]
suffix_element.caption = { "science-info.unit", production_label_data.suffix }
suffix_element.tooltip = tooltip
suffix_element.style.font_color = color
end
--- Adds 4 elements that show the data for a science pack
-- @element science_pack_base
local science_pack_base = Gui.element("science_info_science_pack_base")
:draw(function(_, parent, science_pack_data)
local science_pack = science_pack_data.science_pack
-- Draw the icon for the science pack
local icon_style = science_pack_data.icon_style
local pack_icon =
parent.add{
name = "icon-" .. science_pack,
type = "sprite-button",
sprite = "item/" .. science_pack,
tooltip = { "item-name." .. science_pack },
style = icon_style,
}
-- Change the style of the icon
local pack_icon_style = pack_icon.style
pack_icon.ignored_by_interaction = true
pack_icon_style.height = 55
if icon_style == "slot_button" then
pack_icon_style.padding = { 0, -2 }
pack_icon_style.width = 36
end
-- Draw the delta flow
local delta_flow =
parent.add{
name = "delta-" .. science_pack,
type = "frame",
style = "bordered_frame",
}
delta_flow.style.padding = { 0, 3 }
-- Draw the delta flow table
local delta_table =
delta_flow.add{
name = "table",
type = "table",
column_count = 2,
}
delta_table.style.padding = 0
-- Draw the production labels
update_production_label(delta_table, science_pack_data.positive)
update_production_label(delta_table, science_pack_data.negative)
update_production_label(parent, science_pack_data.net)
-- Return the pack icon
return pack_icon
end)
local function get_science_pack_data(player, science_pack)
local force = player.force
-- Check that some packs have been made
local total = Production.get_production_total(force, science_pack)
if total.made == 0 then return end
local minute = Production.get_production(force, science_pack, defines.flow_precision_index.one_minute)
local hour = Production.get_production(force, science_pack, defines.flow_precision_index.one_hour)
-- Get the icon style
local icon_style = "slot_button"
local flux = Production.get_fluctuations(force, science_pack, defines.flow_precision_index.one_minute)
if minute.net > 0 and flux.net > -config.color_flux / 2 then
icon_style = "slot_sized_button_green"
elseif flux.net < -config.color_flux then
icon_style = "slot_sized_button_red"
elseif minute.made > 0 then
icon_style = "yellow_slot_button"
end
-- Return the pack data
return {
science_pack = science_pack,
icon_style = icon_style,
positive = get_production_label_data(
"pos-" .. science_pack,
{ "science-info.pos-tooltip", total.made },
minute.made, hour.made
),
negative = get_production_label_data(
"neg-" .. science_pack,
{ "science-info.neg-tooltip", total.used },
-minute.used, hour.used
),
net = get_production_label_data(
"net-" .. science_pack,
{ "science-info.net-tooltip", total.net },
minute.net, minute.net > 0 and hour.net or 0,
minute.made + minute.used
),
}
end
local function update_science_pack(pack_table, science_pack_data)
if not science_pack_data then return end
local science_pack = science_pack_data.science_pack
pack_table.parent.non_made.visible = false
-- Update the icon
--- @type LuaGuiElement
local pack_icon = pack_table["icon-" .. science_pack] or science_pack_base(pack_table, science_pack_data)
local icon_style = science_pack_data.icon_style
pack_icon.style = icon_style
local pack_icon_style = pack_icon.style
pack_icon_style.height = 55
if icon_style == "slot_button" then
pack_icon_style.padding = { 0, -2 }
pack_icon_style.width = 36
end
-- Update the production labels
local delta_table = pack_table["delta-" .. science_pack].table
update_production_label(delta_table, science_pack_data.positive)
update_production_label(delta_table, science_pack_data.negative)
update_production_label(pack_table, science_pack_data.net)
end
--- Gets the data that is used with the eta label
local function get_eta_label_data(player)
local force = player.force
-- If there is no current research then return no research
local research = force.current_research
if not research then
return { research = false }
end
local limit
local progress = force.research_progress
local remaining = research.research_unit_count * (1 - progress)
-- Check for the limiting science pack
for _, ingredient in pairs(research.research_unit_ingredients) do
local pack_name = ingredient.name
local required = ingredient.amount * remaining
local time = Production.get_consumsion_eta(force, pack_name, defines.flow_precision_index.one_minute, required)
if not limit or limit < time then
limit = time
end
end
-- Return the caption and tooltip
return limit and limit > 0 and {
research = true,
caption = clock_time_format(limit),
tooltip = long_time_format(limit),
} or {
research = false
}
end
-- Updates the eta label
local function update_eta_label(element, eta_label_data)
-- If no research selected show null
if not eta_label_data.research then
element.caption = null_time_clock
element.tooltip = null_time_long
return
end
-- Update the element
element.caption = { "science-info.eta-time", eta_label_data.caption }
element.tooltip = eta_label_data.tooltip
end
--- Main task list container for the left flow
-- @element task_list_container
local science_info = Gui.element("science_info")
:draw(function(def, parent)
local player = Gui.get_player(parent)
-- Draw the internal container
local container = Gui.elements.container(parent, 200)
-- Draw the header
Gui.elements.header(container, {
caption = { "science-info.main-caption" },
tooltip = { "science-info.main-tooltip" },
})
-- Draw the scroll table for the tasks
local scroll_table = Gui.elements.scroll_table(container, 178, 4, "scroll")
-- Draw the no packs label
local no_packs_label =
scroll_table.parent.add{
name = "non_made",
type = "label",
caption = { "science-info.no-packs" },
}
-- Change the style of the no packs label
local no_packs_style = no_packs_label.style
no_packs_style.padding = { 2, 4 }
no_packs_style.single_line = false
no_packs_style.width = 200
-- Add the footer and eta
if config.show_eta then
-- Draw the footer
local footer = Gui.elements.footer(container, {
name = "footer",
caption = { "science-info.eta-caption" },
tooltip = { "science-info.eta-tooltip" },
})
-- Draw the eta label
local eta_label =
footer.add{
name = "label",
type = "label",
caption = null_time_clock,
tooltip = null_time_long,
style = "frame_title",
}
-- Update the eta
update_eta_label(eta_label, get_eta_label_data(player))
end
-- Add packs which have been made
for _, science_pack in ipairs(config) do
update_science_pack(scroll_table, get_science_pack_data(player, science_pack))
end
-- Return the external container
return container.parent
end)
--- Add the element to the left flow with a toolbar button
Gui.add_left_element(science_info, false)
Gui.toolbar.create_button{
name = "science_info_toggle",
left_element = science_info,
sprite = "entity/lab",
tooltip = { "science-info.main-tooltip" },
visible = function(player, element)
return Roles.player_allowed(player, "gui/science-info")
end
}
--- Updates the gui every 1 second
Event.on_nth_tick(60, function()
local force_pack_data = {}
local force_eta_data = {}
for _, player in pairs(game.connected_players) do
local force_name = player.force.name
local container = Gui.get_left_element(science_info, player)
local frame = container.frame
-- Update the science packs
local scroll_table = frame.scroll.table
local pack_data = force_pack_data[force_name]
if not pack_data then
-- No data in cache so it needs to be generated
pack_data = {}
force_pack_data[force_name] = pack_data
for _, science_pack in ipairs(config) do
local next_data = get_science_pack_data(player, science_pack)
pack_data[science_pack] = next_data
update_science_pack(scroll_table, next_data)
end
else
-- Data found in cache is no need to generate it
for _, next_data in pairs(pack_data) do
update_science_pack(scroll_table, next_data)
end
end
-- Update the eta times
if not config.show_eta then return end
local eta_label = frame.footer.flow.label
local eta_data = force_eta_data[force_name]
if not eta_data then
-- No data in chache so it needs to be generated
eta_data = get_eta_label_data(player)
force_eta_data[force_name] = eta_data
update_eta_label(eta_label, eta_data)
else
-- Data found in chache is no need to generate it
update_eta_label(eta_label, eta_data)
end
end
end)

View File

@@ -1,202 +0,0 @@
---- module surveillance
-- @gui surveillance
local Gui = require("modules/exp_gui")
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
local cctv_player = Gui.element("cctv_player")
:draw(function(def, parent, player_list)
return parent.add{
name = def.name,
type = "drop-down",
items = player_list,
selected_index = #player_list > 0 and 1 or nil,
}
end)
:style{
horizontally_stretchable = true,
}
local cctv_status = Gui.element("cctv_status")
:draw{
type = "drop-down",
items = { { "surveillance.status-enable" }, { "surveillance.status-disable" } },
selected_index = 2,
}:style{
width = 96,
}:on_selection_state_changed(function(def, player, element)
if element.selected_index == 1 then
element.parent.parent.parent.cctv_display.visible = true
else
element.parent.parent.parent.cctv_display.visible = false
end
end)
local cctv_type = Gui.element("cctv_type")
:draw{
type = "drop-down",
name = Gui.property_from_name,
items = { { "surveillance.type-player" }, { "surveillance.type-static" }, { "surveillance.type-player-loop" } },
selected_index = 1,
}:style{
width = 96,
}
local cctv_location = Gui.element("cctv_location")
:draw{
type = "button",
name = Gui.property_from_name,
caption = { "surveillance.func-set" },
}:style{
width = 48,
}:on_click(function(def, player, element)
element.parent.parent.parent.cctv_display.position = player.physical_position
end)
local zoom_in = Gui.element("zoom_in")
:draw{
type = "button",
name = Gui.property_from_name,
caption = "+",
}:style{
width = 32,
}:on_click(function(def, player, element)
local display = element.parent.parent.parent.cctv_display
if display.zoom < 2.0 then
display.zoom = display.zoom + 0.05
end
end)
local zoom_out = Gui.element("zoom_out")
:draw{
type = "button",
name = Gui.property_from_name,
caption = "-",
}:style{
width = 32,
}:on_click(function(def, player, element)
local display = element.parent.parent.parent.cctv_display
if display.zoom > 0.2 then
display.zoom = display.zoom - 0.05
end
end)
local camera_set = Gui.element("camera_set")
:draw(function(_, parent, name, player_list)
local camera_set = parent.add{ type = "flow", direction = "vertical", name = name }
local buttons = Gui.elements.scroll_table(camera_set, 480, 6, "buttons")
cctv_player(buttons, player_list)
cctv_status(buttons)
cctv_type(buttons)
cctv_location(buttons)
zoom_out(buttons)
zoom_in(buttons)
local camera = camera_set.add{
type = "camera",
name = "cctv_display",
position = { x = 0, y = 0 },
surface_index = game.surfaces["nauvis"].index,
zoom = 0.75,
}
camera.visible = false
camera.style.minimal_width = 480
camera.style.minimal_height = 290
return camera_set
end)
local cctv_container = Gui.element("cctv_container")
:draw(function(def, parent)
local container = Gui.elements.container(parent, 480)
local scroll = container.add{ name = "scroll", type = "scroll-pane", direction = "vertical" }
scroll.style.maximal_height = 704
local player_list = {}
for _, player in pairs(game.connected_players) do
table.insert(player_list, player.name)
end
camera_set(scroll, "cctv_st_1", player_list)
camera_set(scroll, "cctv_st_2", player_list)
return container.parent
end)
--- Add the element to the left flow with a toolbar button
Gui.add_left_element(cctv_container, false)
Gui.toolbar.create_button{
name = "cctv_toggle",
left_element = cctv_container,
sprite = "entity/radar",
tooltip = { "surveillance.main-tooltip" },
visible = function(player, element)
return Roles.player_allowed(player, "gui/surveillance")
end
}
local function gui_update()
local player_list = {}
for _, player in pairs(game.connected_players) do
table.insert(player_list, player.name)
end
for _, player in pairs(game.connected_players) do
local container = Gui.get_left_element(cctv_container, player)
container.frame.scroll["cctv_st_1"].buttons.table[cctv_player.name].items = player_list
container.frame.scroll["cctv_st_2"].buttons.table[cctv_player.name].items = player_list
end
end
Event.add(defines.events.on_player_joined_game, gui_update)
Event.add(defines.events.on_player_left_game, gui_update)
Event.add(defines.events.on_tick, function(_)
for _, player in pairs(game.connected_players) do
local container = Gui.get_left_element(cctv_container, player)
for i = 1, 2 do
local scroll_table_name = "cctv_st_" .. i
local current_camera_set = container.frame.scroll[scroll_table_name]
local switch_index = current_camera_set.buttons.table[cctv_type.name].selected_index
if (switch_index == 1) or (switch_index == 3) then
local selected_index = current_camera_set.buttons.table[cctv_player.name].selected_index
if selected_index ~= 0 then
selected_index = current_camera_set.buttons.table[cctv_player.name].items[selected_index] --[[ @as number ]]
current_camera_set["cctv_display"].position = game.players[selected_index].physical_position
current_camera_set["cctv_display"].surface_index = game.players[selected_index].surface_index
else
current_camera_set["cctv_display"].position = { x = 0, y = 0 }
current_camera_set["cctv_display"].surface_index = game.surfaces["nauvis"].index
end
end
end
end
end)
Event.on_nth_tick(600, function(_)
for _, player in pairs(game.connected_players) do
local container = Gui.get_left_element(cctv_container, player)
for i = 1, 2 do
local current_camera_set = container.frame.scroll["cctv_st_" .. i]
if current_camera_set.buttons.table[cctv_type.name].selected_index == 3 then
local item_n = #current_camera_set.buttons.table[cctv_player.name].items
if item_n ~= 0 then
if current_camera_set.buttons.table[cctv_player.name].selected_index < item_n then
current_camera_set.buttons.table[cctv_player.name].selected_index = current_camera_set.buttons.table[cctv_player.name].selected_index + 1
else
current_camera_set.buttons.table[cctv_player.name].selected_index = 1
end
end
end
end
end
end)

View File

@@ -83,13 +83,13 @@ end
--- Button displayed in the header bar, used to add a new task
-- @element add_new_task
local add_new_task = Gui.element("add_new_task")
local add_new_task = Gui.define("add_new_task")
:draw{
type = "sprite-button",
sprite = "utility/add",
tooltip = { "task-list.add-tooltip" },
style = "tool_button",
name = Gui.property_from_name,
name = Gui.from_name,
}
:style(Styles.sprite22)
:on_click(
@@ -105,7 +105,7 @@ local add_new_task = Gui.element("add_new_task")
--- Header displayed when no tasks are in the task list
-- @element no_tasks_found
local no_tasks_found = Gui.element("no_tasks_found")
local no_tasks_found = Gui.define("no_tasks_found")
:draw(
function(_, parent)
local header =
@@ -137,10 +137,10 @@ local no_tasks_found = Gui.element("no_tasks_found")
--- Frame element with the right styling
-- @element subfooter_frame
local subfooter_frame = Gui.element("task_list_subfooter_frame")
local subfooter_frame = Gui.define("task_list_subfooter_frame")
:draw{
type = "frame",
name = Gui.property_from_arg(1),
name = Gui.from_argument(1),
direction = "vertical",
style = "subfooter_frame",
}
@@ -153,17 +153,17 @@ local subfooter_frame = Gui.element("task_list_subfooter_frame")
--- Label element preset
-- @element subfooter_label
local subfooter_label = Gui.element("task_list_subfooter_label")
local subfooter_label = Gui.define("task_list_subfooter_label")
:draw{
name = "footer_label",
type = "label",
style = "frame_title",
caption = Gui.property_from_arg(1),
caption = Gui.from_argument(1),
}
--- Action flow that contains action buttons
-- @element subfooter_actions
local subfooter_actions = Gui.element("task_list_subfooter_actions")
local subfooter_actions = Gui.define("task_list_subfooter_actions")
:draw{
type = "flow",
name = "actions",
@@ -171,7 +171,7 @@ local subfooter_actions = Gui.element("task_list_subfooter_actions")
--- Button element with a flow around it to fix duplicate name inside of the scroll flow
-- @element task_list_item
local task_list_item = Gui.element("task_list_item")
local task_list_item = Gui.define("task_list_item")
:draw(
function(def, parent, task)
local flow = parent.add{
@@ -205,7 +205,7 @@ local task_list_item = Gui.element("task_list_item")
--- Scrollable list of all tasks
-- @element task_list
local task_list = Gui.element("task_list")
local task_list = Gui.define("task_list")
:draw(
function(_, parent)
local scroll_pane =
@@ -236,10 +236,10 @@ local task_list = Gui.element("task_list")
--- Button element inside the task view footer to start editing a task
-- @element task_view_edit_button
local task_view_edit_button = Gui.element("task_view_edit_button")
local task_view_edit_button = Gui.define("task_view_edit_button")
:draw{
type = "button",
name = Gui.property_from_name,
name = Gui.from_name,
caption = { "", "[img=utility/rename_icon] ", { "task-list.edit" } },
tooltip = { "task-list.edit-tooltip" },
style = "shortcut_bar_button",
@@ -254,7 +254,7 @@ local task_view_edit_button = Gui.element("task_view_edit_button")
--- Button to close the task view footer
-- @element task_view_close_button
local task_view_close_button = Gui.element("task_view_close_button")
local task_view_close_button = Gui.define("task_view_close_button")
:draw{
type = "sprite-button",
sprite = "utility/collapse",
@@ -270,10 +270,10 @@ local task_view_close_button = Gui.element("task_view_close_button")
--- Button to delete the task inside the task view footer
-- @element task_view_delete_button
local task_view_delete_button = Gui.element("task_view_delete_button")
local task_view_delete_button = Gui.define("task_view_delete_button")
:draw{
type = "button",
name = Gui.property_from_name,
name = Gui.from_name,
caption = { "", "[img=utility/trash] ", { "task-list.delete" } },
tooltip = { "task-list.delete-tooltip" },
style = "shortcut_bar_button_red",
@@ -289,7 +289,7 @@ local task_view_delete_button = Gui.element("task_view_delete_button")
--- Subfooter inside the tasklist container that holds all the elements for viewing a task
-- @element task_view_footer
local task_view_footer = Gui.element("task_view_footer")
local task_view_footer = Gui.define("task_view_footer")
:draw(
function(_, parent)
local footer = subfooter_frame(parent, "view")
@@ -345,9 +345,9 @@ local task_create_confirm_button
--- Textfield element used in both the task create and edit footers
-- @element task_message_textfield
local task_message_textfield = Gui.element("task_message_textfield")
local task_message_textfield = Gui.define("task_message_textfield")
:draw{
name = Gui.property_from_name,
name = Gui.from_name,
type = "text-box",
text = "",
}:style{
@@ -372,10 +372,10 @@ local task_message_textfield = Gui.element("task_message_textfield")
--- Button to confirm the changes inside the task edit footer
-- @element task_edit_confirm_button
task_edit_confirm_button = Gui.element("task_edit_confirm_button")
task_edit_confirm_button = Gui.define("task_edit_confirm_button")
:draw{
type = "button",
name = Gui.property_from_name,
name = Gui.from_name,
caption = { "", "[img=utility/check_mark] ", { "task-list.confirm" } },
tooltip = { "task-list.confirm-tooltip" },
style = "shortcut_bar_button_green",
@@ -394,7 +394,7 @@ task_edit_confirm_button = Gui.element("task_edit_confirm_button")
--- Button to discard the changes inside the task edit footer
-- @element edit_task_discard_button
local edit_task_discard_button = Gui.element("edit_task_discard_button")
local edit_task_discard_button = Gui.define("edit_task_discard_button")
:draw{
type = "button",
caption = { "", "[img=utility/close_black] ", { "task-list.discard" } },
@@ -412,7 +412,7 @@ local edit_task_discard_button = Gui.element("edit_task_discard_button")
--- Subfooter inside the tasklist container that holds all the elements for editing a task
-- @element task_edit_footer
local task_edit_footer = Gui.element("task_edit_footer")
local task_edit_footer = Gui.define("task_edit_footer")
:draw(
function(_, parent)
local footer = subfooter_frame(parent, "edit")
@@ -431,10 +431,10 @@ local task_edit_footer = Gui.element("task_edit_footer")
--- Button to confirm the changes inside the task create footer
-- @element task_create_confirm_button
task_create_confirm_button = Gui.element("task_create_confirm_button")
task_create_confirm_button = Gui.define("task_create_confirm_button")
:draw{
type = "button",
name = Gui.property_from_name,
name = Gui.from_name,
caption = { "", "[img=utility/check_mark] ", { "task-list.confirm" } },
tooltip = { "task-list.confirm-tooltip" },
style = "shortcut_bar_button_green",
@@ -453,7 +453,7 @@ task_create_confirm_button = Gui.element("task_create_confirm_button")
--- Button to discard the changes inside the task create footer
-- @element task_create_discard_button
local task_create_discard_button = Gui.element("task_create_discard_button")
local task_create_discard_button = Gui.define("task_create_discard_button")
:draw{
type = "button",
caption = { "", "[img=utility/close_black] ", { "task-list.discard" } },
@@ -469,7 +469,7 @@ local task_create_discard_button = Gui.element("task_create_discard_button")
--- Subfooter inside the tasklist container that holds all the elements to create a new task
-- @element task_create_footer
local task_create_footer = Gui.element("task_create_footer")
local task_create_footer = Gui.define("task_create_footer")
:draw(
function(_, parent)
local footer = subfooter_frame(parent, "create")
@@ -505,7 +505,7 @@ end
--- Main task list container for the left flow
-- @element task_list_container
local task_list_container = Gui.element("task_list_container")
local task_list_container = Gui.define("task_list_container")
:draw(
function(def, parent)
-- Draw the internal container
@@ -762,7 +762,7 @@ local function role_update_event(event)
-- Update the new task button and create footer in case the user can now add them
local has_permission = check_player_permissions(player)
local add_new_task_element = frame.header.flow[add_new_task.name]
local add_new_task_element = frame.header[add_new_task.name]
add_new_task_element.visible = has_permission
local is_creating = PlayerIsCreating:get(player)
if is_creating and not has_permission then

View File

@@ -1,272 +0,0 @@
--[[-- Gui Module - Tool
@gui Tool
@alias tool_container
]]
local ExpUtil = require("modules/exp_util")
local Gui = require("modules/exp_gui")
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
local Selection = require("modules/exp_legacy/modules/control/selection") --- @dep modules.control.selection
local addon_train = require("modules/exp_scenario/commands/trains")
local addon_research = require("modules/exp_scenario/commands/research")
local tool_container
local SelectionArtyArea = "ExpCommand_Artillery"
local SelectionWaterfillArea = "ExpCommand_Waterfill"
local style = {
label = {
width = 160
},
button = {
width = 80
}
}
--- Arty label
-- @element tool_gui_arty_l
local tool_gui_arty_l = Gui.element("tool_gui_arty_l")
:draw{
type = "label",
name = Gui.property_from_name,
caption = { "tool.artillery" },
tooltip = { "tool.artillery-tooltip" },
style = "heading_2_label"
}:style(
style.label
)
--- Arty button
-- @element tool_gui_arty_b
local tool_gui_arty_b = Gui.element("tool_gui_arty_b")
:draw{
type = "button",
name = Gui.property_from_name,
caption = { "tool.apply" }
}:style(
style.button
):on_click(function(def, player, element)
if Selection.is_selecting(player, SelectionArtyArea) then
Selection.stop(player)
player.print{ "exp-commands_artillery.exit" }
else
Selection.start(player, SelectionArtyArea)
player.print{ "exp-commands_artillery.enter" }
end
end)
--- Waterfill label
-- @element tool_gui_waterfill_l
local tool_gui_waterfill_l = Gui.element("tool_gui_waterfill_l")
:draw{
type = "label",
name = Gui.property_from_name,
caption = { "tool.waterfill" },
tooltip = { "tool.waterfill-tooltip" },
style = "heading_2_label"
}:style(
style.label
)
--- Waterfill button
-- @element tool_gui_waterfill_b
local tool_gui_waterfill_b = Gui.element("tool_gui_waterfill_b")
:draw{
type = "button",
name = Gui.property_from_name,
caption = { "tool.apply" }
}:style(
style.button
):on_click(function(def, player, element)
if Selection.is_selecting(player, SelectionWaterfillArea) then
Selection.stop(player)
return player.print{ "exp-commands_waterfill.exit" }
else
local item_count_cliff = player.get_item_count("cliff-explosives")
local item_count_craft = math.min(math.floor(player.get_item_count("explosives") / 10), player.get_item_count("barrel"), player.get_item_count("grenade"))
local item_count_total = item_count_cliff + item_count_craft
if item_count_total == 0 then
return player.print{ "exp-commands_waterfill.requires-explosives" }
else
Selection.start(player, SelectionWaterfillArea)
return player.print{ "exp-commands_waterfill.enter" }
end
end
end)
--- Train label
-- @element tool_gui_train_l
local tool_gui_train_l = Gui.element("tool_gui_train_l")
:draw{
type = "label",
name = Gui.property_from_name,
caption = { "tool.train" },
tooltip = { "tool.train-tooltip" },
style = "heading_2_label"
}:style(
style.label
)
--- Train button
-- @element tool_gui_train_b
local tool_gui_train_b = Gui.element("tool_gui_train_b")
:draw{
type = "button",
name = Gui.property_from_name,
caption = { "tool.apply" }
}:style(
style.button
):on_click(function(def, player, element)
addon_train.manual(player)
end)
--- Research label
-- @element tool_gui_research_l
local tool_gui_research_l = Gui.element("tool_gui_research_l")
:draw{
type = "label",
name = Gui.property_from_name,
caption = { "tool.research" },
tooltip = { "tool.research-tooltip" },
style = "heading_2_label"
}:style(
style.label
)
--- Research button
-- @element tool_gui_research_b
local tool_gui_research_b = Gui.element("tool_gui_research_b")
:draw{
type = "button",
name = Gui.property_from_name,
caption = { "tool.apply" }
}:style(
style.button
):on_click(function(def, player, element)
local enabled = addon_research.set_auto_research()
if enabled then
addon_research.res_queue(player.force --[[ @as LuaForce ]], true)
end
local player_name = ExpUtil.format_player_name_locale(player)
game.print{ "exp-commands_research.auto-research", player_name, enabled }
end)
--- Spawn label
-- @element tool_gui_spawn_l
local tool_gui_spawn_l = Gui.element("tool_gui_spawn_l")
:draw{
type = "label",
name = Gui.property_from_name,
caption = { "tool.spawn" },
tooltip = { "tool.spawn-tooltip" },
style = "heading_2_label"
}:style(
style.label
)
--- Spawn button
-- @element tool_gui_spawn_b
local tool_gui_spawn_b = Gui.element("tool_gui_spawn_b")
:draw{
type = "button",
name = Gui.property_from_name,
caption = { "tool.apply" }
}:style(
style.button
):on_click(function(def, player, element)
if not player.character
or player.character.health <= 0
or not ExpUtil.teleport_player(player, game.surfaces.nauvis, { 0, 0 }, "dismount") then
return player.print{ "exp-commands_teleport.unavailable" }
end
end)
local function tool_perm(player, container)
container = container or Gui.get_left_element(tool_container, player)
local disp = container.frame.tool_st.disp.table
local allowed
allowed = Roles.player_allowed(player, "command/artillery")
disp[tool_gui_arty_l.name].visible = allowed
disp[tool_gui_arty_b.name].visible = allowed
allowed = Roles.player_allowed(player, "command/waterfill")
disp[tool_gui_waterfill_l.name].visible = allowed
disp[tool_gui_waterfill_b.name].visible = allowed
allowed = Roles.player_allowed(player, "command/set-trains-to-automatic")
disp[tool_gui_train_l.name].visible = allowed
disp[tool_gui_train_b.name].visible = allowed
allowed = Roles.player_allowed(player, "command/set-auto-research")
disp[tool_gui_research_l.name].visible = allowed
disp[tool_gui_research_b.name].visible = allowed
allowed = Roles.player_allowed(player, "command/spawn")
disp[tool_gui_spawn_l.name].visible = allowed
disp[tool_gui_spawn_b.name].visible = allowed
end
--- A vertical flow containing all the tool
-- @element tool_set
local tool_set = Gui.element("tool_set")
:draw(function(_, parent, name)
local tool_set = parent.add{ type = "flow", direction = "vertical", name = name }
local disp = Gui.elements.scroll_table(tool_set, 240, 2, "disp")
tool_gui_arty_l(disp)
tool_gui_arty_b(disp)
tool_gui_waterfill_l(disp)
tool_gui_waterfill_b(disp)
tool_gui_train_l(disp)
tool_gui_train_b(disp)
tool_gui_research_l(disp)
tool_gui_research_b(disp)
tool_gui_spawn_l(disp)
tool_gui_spawn_b(disp)
return tool_set
end)
--- The main container for the tool gui
-- @element tool_container
tool_container = Gui.element("tool_container")
:draw(function(def, parent)
local player = Gui.get_player(parent)
local container = Gui.elements.container(parent, 240)
tool_set(container, "tool_st")
tool_perm(player, container.parent)
return container.parent
end)
--- Add the element to the left flow with a toolbar button
Gui.add_left_element(tool_container, false)
Gui.toolbar.create_button{
name = "tool_toggle",
left_element = tool_container,
sprite = "item/repair-pack",
tooltip = { "tool.main-tooltip" },
visible = function(player, element)
return Roles.player_allowed(player, "gui/tool")
end
}
Event.add(Roles.events.on_role_assigned, function(event)
tool_perm(game.players[event.player_index])
end)
Event.add(Roles.events.on_role_unassigned, function(event)
tool_perm(game.players[event.player_index])
end)

View File

@@ -143,20 +143,20 @@ end)
--- Display label for the number of solar panels
-- @element vlayer_gui_display_item_solar_name
local vlayer_gui_display_item_solar_name = Gui.element("vlayer_gui_display_item_solar_name")
local vlayer_gui_display_item_solar_name = Gui.define("vlayer_gui_display_item_solar_name")
:draw{
type = "label",
name = Gui.property_from_name,
name = Gui.from_name,
caption = { "vlayer.display-item-solar" },
style = "heading_2_label",
}:style{
width = 200,
}
local vlayer_gui_display_item_solar_count = Gui.element("vlayer_gui_display_item_solar_count")
local vlayer_gui_display_item_solar_count = Gui.define("vlayer_gui_display_item_solar_count")
:draw{
type = "progressbar",
name = Gui.property_from_name,
name = Gui.from_name,
caption = "",
value = 0,
style = "electric_satisfaction_statistics_progressbar",
@@ -167,20 +167,20 @@ local vlayer_gui_display_item_solar_count = Gui.element("vlayer_gui_display_item
--- Display label for the number of accumulators
-- @element vlayer_gui_display_item_accumulator_name
local vlayer_gui_display_item_accumulator_name = Gui.element("vlayer_gui_display_item_accumulator_name")
local vlayer_gui_display_item_accumulator_name = Gui.define("vlayer_gui_display_item_accumulator_name")
:draw{
type = "label",
name = Gui.property_from_name,
name = Gui.from_name,
caption = { "vlayer.display-item-accumulator" },
style = "heading_2_label",
}:style{
width = 200,
}
local vlayer_gui_display_item_accumulator_count = Gui.element("vlayer_gui_display_item_accumulator_count")
local vlayer_gui_display_item_accumulator_count = Gui.define("vlayer_gui_display_item_accumulator_count")
:draw{
type = "progressbar",
name = Gui.property_from_name,
name = Gui.from_name,
caption = "",
value = 0,
style = "electric_satisfaction_statistics_progressbar",
@@ -191,10 +191,10 @@ local vlayer_gui_display_item_accumulator_count = Gui.element("vlayer_gui_displa
--- Display label for the surface area
-- @element vlayer_gui_display_signal_surface_area_name
local vlayer_gui_display_signal_surface_area_name = Gui.element("vlayer_gui_display_signal_surface_area_name")
local vlayer_gui_display_signal_surface_area_name = Gui.define("vlayer_gui_display_signal_surface_area_name")
:draw{
type = "label",
name = Gui.property_from_name,
name = Gui.from_name,
caption = { "vlayer.display-remaining-surface-area" },
tooltip = { "vlayer.display-remaining-surface-area-tooltip" },
style = "heading_2_label",
@@ -202,10 +202,10 @@ local vlayer_gui_display_signal_surface_area_name = Gui.element("vlayer_gui_disp
width = 200,
}
local vlayer_gui_display_signal_surface_area_count = Gui.element("vlayer_gui_display_signal_surface_area_count")
local vlayer_gui_display_signal_surface_area_count = Gui.define("vlayer_gui_display_signal_surface_area_count")
:draw{
type = "progressbar",
name = Gui.property_from_name,
name = Gui.from_name,
caption = "",
value = 0,
style = "electric_satisfaction_statistics_progressbar",
@@ -216,10 +216,10 @@ local vlayer_gui_display_signal_surface_area_count = Gui.element("vlayer_gui_dis
--- Display label for the sustained energy production
-- @element vlayer_gui_display_signal_sustained_name
local vlayer_gui_display_signal_sustained_name = Gui.element("vlayer_gui_display_signal_sustained_name")
local vlayer_gui_display_signal_sustained_name = Gui.define("vlayer_gui_display_signal_sustained_name")
:draw{
type = "label",
name = Gui.property_from_name,
name = Gui.from_name,
caption = { "vlayer.display-sustained-production" },
tooltip = { "vlayer.display-sustained-production-tooltip" },
style = "heading_2_label",
@@ -227,10 +227,10 @@ local vlayer_gui_display_signal_sustained_name = Gui.element("vlayer_gui_display
width = 200,
}
local vlayer_gui_display_signal_sustained_count = Gui.element("vlayer_gui_display_signal_sustained_count")
local vlayer_gui_display_signal_sustained_count = Gui.define("vlayer_gui_display_signal_sustained_count")
:draw{
type = "progressbar",
name = Gui.property_from_name,
name = Gui.from_name,
caption = "",
value = 0,
style = "electric_satisfaction_statistics_progressbar",
@@ -241,10 +241,10 @@ local vlayer_gui_display_signal_sustained_count = Gui.element("vlayer_gui_displa
--- Display label for the current energy production
-- @element vlayer_gui_display_signal_production_name
local vlayer_gui_display_signal_production_name = Gui.element("vlayer_gui_display_signal_production_name")
local vlayer_gui_display_signal_production_name = Gui.define("vlayer_gui_display_signal_production_name")
:draw{
type = "label",
name = Gui.property_from_name,
name = Gui.from_name,
caption = { "vlayer.display-current-production" },
tooltip = { "vlayer.display-current-production-tooltip" },
style = "heading_2_label",
@@ -252,10 +252,10 @@ local vlayer_gui_display_signal_production_name = Gui.element("vlayer_gui_displa
width = 200,
}
local vlayer_gui_display_signal_production_count = Gui.element("vlayer_gui_display_signal_production_count")
local vlayer_gui_display_signal_production_count = Gui.define("vlayer_gui_display_signal_production_count")
:draw{
type = "progressbar",
name = Gui.property_from_name,
name = Gui.from_name,
caption = "",
value = 0,
style = "electric_satisfaction_statistics_progressbar",
@@ -266,10 +266,10 @@ local vlayer_gui_display_signal_production_count = Gui.element("vlayer_gui_displ
--- Display label for the sustained energy capacity
-- @element vlayer_gui_display_signal_capacity_name
local vlayer_gui_display_signal_capacity_name = Gui.element("vlayer_gui_display_signal_capacity_name")
local vlayer_gui_display_signal_capacity_name = Gui.define("vlayer_gui_display_signal_capacity_name")
:draw{
type = "label",
name = Gui.property_from_name,
name = Gui.from_name,
caption = { "vlayer.display-current-capacity" },
tooltip = { "vlayer.display-current-capacity-tooltip" },
style = "heading_2_label",
@@ -277,10 +277,10 @@ local vlayer_gui_display_signal_capacity_name = Gui.element("vlayer_gui_display_
width = 200,
}
local vlayer_gui_display_signal_capacity_count = Gui.element("vlayer_gui_display_signal_capacity_count")
local vlayer_gui_display_signal_capacity_count = Gui.define("vlayer_gui_display_signal_capacity_count")
:draw{
type = "progressbar",
name = Gui.property_from_name,
name = Gui.from_name,
caption = "",
value = 0,
style = "electric_satisfaction_statistics_progressbar",
@@ -291,7 +291,7 @@ local vlayer_gui_display_signal_capacity_count = Gui.element("vlayer_gui_display
--- A vertical flow containing all the displays labels and their counts
-- @element vlayer_display_set
local vlayer_display_set = Gui.element("vlayer_display_set")
local vlayer_display_set = Gui.define("vlayer_display_set")
:draw(function(_, parent, name)
local vlayer_set = parent.add{ type = "flow", direction = "vertical", name = name }
local disp = Gui.elements.scroll_table(vlayer_set, 400, 2, "disp")
@@ -331,10 +331,10 @@ end
--- A drop down list filter by this type
-- @element vlayer_gui_control_type
vlayer_gui_control_type = Gui.element("vlayer_gui_control_type")
vlayer_gui_control_type = Gui.define("vlayer_gui_control_type")
:draw{
type = "drop-down",
name = Gui.property_from_name,
name = Gui.from_name,
items = { { "vlayer.control-type-energy" }, { "vlayer.control-type-circuit" }, { "vlayer.control-type-storage-input" }, { "vlayer.control-type-storage-output" } },
selected_index = 1,
}:style{
@@ -345,20 +345,20 @@ vlayer_gui_control_type = Gui.element("vlayer_gui_control_type")
--- A drop down list to see the exact item to remove
-- @element vlayer_gui_control_list
vlayer_gui_control_list = Gui.element("vlayer_gui_control_list")
vlayer_gui_control_list = Gui.define("vlayer_gui_control_list")
:draw{
type = "drop-down",
name = Gui.property_from_name,
name = Gui.from_name,
}:style{
width = 200,
}
--- A button to refresh the remove list
-- @element vlayer_gui_control_refresh
local vlayer_gui_control_refresh = Gui.element("vlayer_gui_control_refresh")
local vlayer_gui_control_refresh = Gui.define("vlayer_gui_control_refresh")
:draw{
type = "button",
name = Gui.property_from_name,
name = Gui.from_name,
caption = { "vlayer.control-refresh" },
}:style{
width = 200,
@@ -368,10 +368,10 @@ local vlayer_gui_control_refresh = Gui.element("vlayer_gui_control_refresh")
--- A button to check if the item is the one wanted to remove
-- @element vlayer_gui_control_see
local vlayer_gui_control_see = Gui.element("vlayer_gui_control_see")
local vlayer_gui_control_see = Gui.define("vlayer_gui_control_see")
:draw{
type = "button",
name = Gui.property_from_name,
name = Gui.from_name,
caption = { "vlayer.control-see" },
}:style{
width = 200,
@@ -392,10 +392,10 @@ local vlayer_gui_control_see = Gui.element("vlayer_gui_control_see")
--- A button used to build the vlayer interface
-- @element vlayer_gui_control_build
local vlayer_gui_control_build = Gui.element("vlayer_gui_control_build")
local vlayer_gui_control_build = Gui.define("vlayer_gui_control_build")
:draw{
type = "button",
name = Gui.property_from_name,
name = Gui.from_name,
caption = { "vlayer.control-build" },
}:style{
width = 200,
@@ -413,10 +413,10 @@ local vlayer_gui_control_build = Gui.element("vlayer_gui_control_build")
--- A button used to remove the vlayer interface
-- @element vlayer_gui_control_remove
local vlayer_gui_control_remove = Gui.element("vlayer_gui_control_remove")
local vlayer_gui_control_remove = Gui.define("vlayer_gui_control_remove")
:draw{
type = "button",
name = Gui.property_from_name,
name = Gui.from_name,
caption = { "vlayer.control-remove" },
}:style{
width = 200,
@@ -441,7 +441,7 @@ local vlayer_gui_control_remove = Gui.element("vlayer_gui_control_remove")
--- A vertical flow containing all the control buttons
-- @element vlayer_control_set
local vlayer_control_set = Gui.element("vlayer_control_set")
local vlayer_control_set = Gui.define("vlayer_control_set")
:draw(function(_, parent, name)
local player = Gui.get_player(parent)
local vlayer_set = parent.add{ type = "flow", direction = "vertical", name = name }
@@ -462,7 +462,7 @@ local vlayer_control_set = Gui.element("vlayer_control_set")
--- The main container for the vlayer gui
-- @element vlayer_container
vlayer_container = Gui.element("vlayer_container")
vlayer_container = Gui.define("vlayer_container")
:draw(function(definition, parent)
local container = Gui.elements.container(parent, 400)

View File

@@ -78,13 +78,13 @@ end
--- Will add a new warp to the list, checks if the player is too close to an existing one
-- @element add_new_warp
local add_new_warp = Gui.element("add_new_warp")
local add_new_warp = Gui.define("add_new_warp")
:draw{
type = "sprite-button",
sprite = "utility/add",
tooltip = { "warp-list.add-tooltip" },
style = "shortcut_bar_button",
name = Gui.property_from_name,
name = Gui.from_name,
}
:style(Styles.sprite22)
:on_click(function(def, player, element)
@@ -158,7 +158,7 @@ local add_new_warp = Gui.element("add_new_warp")
--- Warp icon button, this will trigger a warp when the player is able to
-- @element warp_icon_button
local warp_icon_button = Gui.element("warp_icon_button")
local warp_icon_button = Gui.define("warp_icon_button")
:draw(function(def, parent, warp)
local warp_position = warp.position
@@ -193,7 +193,7 @@ local warp_icon_button = Gui.element("warp_icon_button")
--- The button that is visible when the warp is in edit state
-- @element warp_icon_editing
local warp_icon_editing = Gui.element("warp_icon_editing")
local warp_icon_editing = Gui.define("warp_icon_editing")
:draw(function(def, parent, warp)
return parent.add{
name = def.name,
@@ -207,7 +207,7 @@ local warp_icon_editing = Gui.element("warp_icon_editing")
--- Warp label, visible if the player is not in edit state
-- @element warp_label
local warp_label = Gui.element("warp_label")
local warp_label = Gui.define("warp_label")
:draw(function(def, parent, warp)
local last_edit_name = warp.last_edit_name
local last_edit_time = warp.last_edit_time
@@ -234,11 +234,11 @@ local warp_label = Gui.element("warp_label")
--- Warp status, visible if the player is not in edit state
--- This will show if the warp is connected or not
-- @element warp_status
local warp_status = Gui.element("warp_status")
local warp_status = Gui.define("warp_status")
:draw{
type = "label",
caption = "[img=utility/electricity_icon_unplugged]", -- Temporary icon
name = Gui.property_from_name,
name = Gui.from_name,
}
:style{
-- When editing mode because textbox is larger the icon would move up.
@@ -248,7 +248,7 @@ local warp_status = Gui.element("warp_status")
--- Warp textfield, visible if the player is in edit state
-- @element warp_textfield
local warp_textfield = Gui.element("warp_textfield")
local warp_textfield = Gui.define("warp_textfield")
:draw(function(def, parent, warp)
-- Draw the element
return parent.add{
@@ -281,13 +281,13 @@ local warp_textfield = Gui.element("warp_textfield")
--- Confirms the edit to name or icon of the warp
-- @element confirm_edit_button
local confirm_edit_button = Gui.element("confirm_edit_button")
local confirm_edit_button = Gui.define("confirm_edit_button")
:draw{
type = "sprite-button",
sprite = "utility/confirm_slot",
tooltip = { "warp-list.confirm-tooltip" },
style = "shortcut_bar_button_green",
name = Gui.property_from_name,
name = Gui.from_name,
}
:style(Styles.sprite22)
:on_click(function(def, player, element)
@@ -301,13 +301,13 @@ local confirm_edit_button = Gui.element("confirm_edit_button")
--- Cancels the editing changes of the selected warp name or icon
-- @element cancel_edit_button
local cancel_edit_button = Gui.element("cancel_edit_button")
local cancel_edit_button = Gui.define("cancel_edit_button")
:draw{
type = "sprite-button",
sprite = "utility/close_black",
tooltip = { "warp-list.cancel-tooltip" },
style = "shortcut_bar_button_red",
name = Gui.property_from_name,
name = Gui.from_name,
}
:style(Styles.sprite22)
:on_click(function(def, player, element)
@@ -323,13 +323,13 @@ local cancel_edit_button = Gui.element("cancel_edit_button")
--- Removes a warp from the list, including the physical area and map tag
-- @element remove_warp_button
local remove_warp_button = Gui.element("remove_warp_button")
local remove_warp_button = Gui.define("remove_warp_button")
:draw{
type = "sprite-button",
sprite = "utility/trash",
tooltip = { "warp-list.remove-tooltip" },
style = "shortcut_bar_button_red",
name = Gui.property_from_name,
name = Gui.from_name,
}
:style(Styles.sprite22)
:on_click(function(def, player, element)
@@ -339,13 +339,13 @@ local remove_warp_button = Gui.element("remove_warp_button")
--- Opens edit mode for the warp
-- @element edit_warp_button
local edit_warp_button = Gui.element("edit_warp_button")
local edit_warp_button = Gui.define("edit_warp_button")
:draw{
type = "sprite-button",
sprite = "utility/rename_icon",
tooltip = { "warp-list.edit-tooltip-none" },
style = "shortcut_bar_button",
name = Gui.property_from_name,
name = Gui.from_name,
}
:style(Styles.sprite22)
:on_click(function(def, player, element)
@@ -356,7 +356,7 @@ local edit_warp_button = Gui.element("edit_warp_button")
local update_all_warp_elements
--- Set of three elements which make up each row of the warp table
-- @element add_warp_elements
local add_warp_elements = Gui.element("add_warp_elements")
local add_warp_elements = Gui.define("add_warp_elements")
:draw(function(_, parent, warp)
-- Add icon flow, this will contain the warp button and warp icon edit button
local icon_flow = parent.add{
@@ -396,6 +396,8 @@ local add_warp_elements = Gui.element("add_warp_elements")
cancel_edit_button(button_flow)
edit_warp_button(button_flow)
remove_warp_button(button_flow)
return Gui.no_return()
end)
-- Removes the three elements that are added as part of the warp base
@@ -407,10 +409,10 @@ end
--- This timer controls when a player is able to warp, eg every 60 seconds
-- @element warp_timer
local warp_timer = Gui.element("warp_timer")
local warp_timer = Gui.define("warp_timer")
:draw{
type = "progressbar",
name = Gui.property_from_name,
name = Gui.from_name,
tooltip = { "warp-list.timer-tooltip-zero", config.cooldown_duration },
minimum_value = 0,
maximum_value = config.cooldown_duration * config.update_smoothing,
@@ -634,7 +636,7 @@ end
--- Main warp list container for the left flow
-- @element warp_list_container
warp_list_container = Gui.element("warp_list_container")
warp_list_container = Gui.define("warp_list_container")
:draw(function(def, parent)
local player = Gui.get_player(parent)
-- Check if user has permission to add warps
@@ -816,7 +818,7 @@ Event.on_nth_tick(math.floor(60 / config.update_smoothing), function()
-- Change the enabled state of the add warp button
local container = Gui.get_left_element(warp_list_container, player)
local add_warp_element = container.frame.header.flow[add_new_warp.name]
local add_warp_element = container.frame.header[add_new_warp.name]
local old_closest_warp_name = add_warp_element.tooltip[2] or closest_warp and closest_warp.name
local was_able_to_make_warp = add_warp_element.enabled
local can_make_warp = closest_distance == nil or closest_distance > mr2
@@ -875,7 +877,7 @@ local function role_update_event(event)
update_all_warps(player, scroll_table)
-- Update the new warp button in case the user can now add them
local add_new_warp_element = frame.header.flow[add_new_warp.name]
local add_new_warp_element = frame.header[add_new_warp.name]
add_new_warp_element.visible = allow_add_warp
end

View File

@@ -10,6 +10,9 @@ local SelectionName = "ExpCommand_Artillery"
local floor = math.floor
local abs = math.abs
--- @class ExpCommand_Artillery.commands
local commands = {}
--- @param player LuaPlayer
--- @param area BoundingBox
--- @return boolean
@@ -30,7 +33,9 @@ local function location_break(player, area)
end
--- Toggle player selection mode for artillery
Commands.new("artillery", { "exp-commands_artillery.description" })
--- @class ExpCommand_Artillery.commands.artillery: ExpCommand
--- @overload fun(player: LuaPlayer)
commands.artillery = Commands.new("artillery", { "exp-commands_artillery.description" })
:register(function(player)
if Selection.is_selecting(player, SelectionName) then
Selection.stop(player)
@@ -39,7 +44,7 @@ Commands.new("artillery", { "exp-commands_artillery.description" })
Selection.start(player, SelectionName)
return Commands.status.success{ "exp-commands_artillery.enter" }
end
end)
end) --[[ @as any ]]
--- when an area is selected to add protection to the area
Selection.on_selection(SelectionName, function(event)
@@ -94,3 +99,7 @@ Selection.on_selection(SelectionName, function(event)
end
end
end)
return {
commands = commands,
}

View File

@@ -8,8 +8,8 @@ local format_player_name = Commands.format_player_name_locale
local config = require("modules.exp_legacy.config.research") --- @dep config.research
--- @class Command.Research
local module = {}
--- @class ExpCommands_Research.commands
local commands = {}
local research = {
res_queue_enable = false
@@ -34,7 +34,7 @@ end
--- @param force LuaForce
--- @param silent boolean True when no message should be printed
function module.res_queue(force, silent)
local function queue_research(force, silent)
local res_q = force.research_queue
local res = force.technologies[config.bonus_inventory.log[mod_set].name]
@@ -51,7 +51,7 @@ end
--- @param state boolean? use nil to toggle current state
--- @return boolean # New auto research state
function module.set_auto_research(state)
local function set_auto_research(state)
local new_state
if state == nil then
new_state = not research.res_queue_enable
@@ -64,35 +64,40 @@ function module.set_auto_research(state)
end
--- Sets the auto research state
Commands.new("set-auto-research", { "exp-commands_research.description" })
--- @class ExpCommand_Artillery.commands.artillery: ExpCommand
--- @overload fun(player: LuaPlayer, state: boolean?)
commands.set_auto_research = Commands.new("set-auto-research", { "exp-commands_research.description" })
:optional("state", { "exp-commands_research.arg-state" }, Commands.types.boolean)
:add_aliases{ "auto-research" }
:register(function(player, state)
--- @cast state boolean?
local enabled = module.set_auto_research(state)
local enabled = set_auto_research(state)
if enabled then
module.res_queue(player.force --[[@as LuaForce]], true)
queue_research(player.force --[[@as LuaForce]], true)
end
local player_name = format_player_name(player)
game.print{ "exp-commands_research.auto-research", player_name, enabled }
end)
end) --[[ @as any ]]
--- @param event EventData.on_research_finished
local function on_research_finished(event)
if not research.res_queue_enable then return end
local force = event.research.force
if config.bonus_inventory.log[mod_set] and force.technologies[config.bonus_inventory.log[mod_set].name] and force.technologies[config.bonus_inventory.log[mod_set].name].level > config.bonus_inventory.log[mod_set].level then
module.res_queue(force, event.by_script)
local log_research = assert(config.bonus_inventory.log[config.mod_set], "Unknown mod set: " .. tostring(config.mod_set))
local technology = assert(force.technologies[log_research.name], "Unknown technology: " .. tostring(log_research.name))
if technology.level > log_research.level then
queue_research(force, event.by_script)
end
end
local e = defines.events
--- @package
module.events = {
[e.on_research_finished] = on_research_finished,
}
return module
return {
commands = commands,
events = {
[e.on_research_finished] = on_research_finished,
},
}

View File

@@ -10,8 +10,13 @@ local Commands = require("modules/exp_commands")
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
local player_allowed = Roles.player_allowed
--- @class ExpCommands_Teleport.commands
local commands = {}
--- Teleports a player to another player.
Commands.new("teleport", { "exp-commands_teleport.description-teleport" })
--- @class ExpCommands_Teleport.commands.teleport: ExpCommand
--- @overload fun(player: LuaPlayer, other_player: LuaPlayer, target_player: LuaPlayer?)
commands.teleport = Commands.new("teleport", { "exp-commands_teleport.description-teleport" })
:argument("player", { "exp-commands_teleport.arg-player-teleport" }, Commands.types.player_alive)
:optional("target", { "exp-commands_teleport.arg-player-to" }, Commands.types.player_alive)
:add_aliases{ "tp" }
@@ -29,10 +34,12 @@ Commands.new("teleport", { "exp-commands_teleport.description-teleport" })
elseif not teleport_player(other_player, target_player.physical_surface, target_player.physical_position) then
return Commands.status.error{ "exp-commands_teleport.unavailable" }
end
end)
end) --[[ @as any ]]
--- Teleports a player to you.
Commands.new("bring", { "exp-commands_teleport.description-bring" })
--- @class ExpCommands_Teleport.commands.bring: ExpCommand
--- @overload fun(player: LuaPlayer, other_player: LuaPlayer)
commands.bring = Commands.new("bring", { "exp-commands_teleport.description-bring" })
:argument("player", { "exp-commands_teleport.arg-player-from" }, Commands.types.player_alive)
:add_flags{ "admin_only" }
:register(function(player, other_player)
@@ -42,10 +49,12 @@ Commands.new("bring", { "exp-commands_teleport.description-bring" })
elseif not teleport_player(other_player, player.physical_surface, player.physical_position) then
return Commands.status.error{ "exp-commands_teleport.unavailable" }
end
end)
end) --[[ @as any ]]
--- Teleports you to a player.
Commands.new("goto", { "exp-commands_teleport.description-goto" })
--- @class ExpCommands_Teleport.commands.goto: ExpCommand
--- @overload fun(player: LuaPlayer, other_player: LuaPlayer)
commands["goto"] = Commands.new("goto", { "exp-commands_teleport.description-goto" })
:argument("player", { "exp-commands_teleport.arg-player-to" }, Commands.types.player_alive)
:add_flags{ "admin_only" }
:register(function(player, other_player)
@@ -55,10 +64,12 @@ Commands.new("goto", { "exp-commands_teleport.description-goto" })
elseif not teleport_player(player, other_player.physical_surface, other_player.physical_position) then
return Commands.status.error{ "exp-commands_teleport.unavailable" }
end
end)
end) --[[ @as any ]]
--- Teleport to spawn
Commands.new("spawn", { "exp-commands_teleport.description-spawn" })
--- @class ExpCommands_Teleport.commands.spawn: ExpCommand
--- @overload fun(player: LuaPlayer, other_player: LuaPlayer)
commands.spawn = Commands.new("spawn", { "exp-commands_teleport.description-spawn" })
:optional("player", { "exp-commands_teleport.arg-player-from" }, Commands.types.player_alive)
:defaults{
player = function(player)
@@ -81,4 +92,8 @@ Commands.new("spawn", { "exp-commands_teleport.description-spawn" })
else
return Commands.status.unauthorised()
end
end)
end) --[[ @as any ]]
return {
commands = commands,
}

View File

@@ -6,34 +6,34 @@ local Commands = require("modules/exp_commands")
local format_player_name = Commands.format_player_name_locale
local format_number = require("util").format_number
--- @class Command.Trains
local module = {}
function module.manual(player, surface, force)
local trains = game.train_manager.get_trains{
stock = "locomotive",
has_passenger = false,
is_manual = true,
is_moving = false,
surface = surface,
force = force,
}
for _, train in ipairs(trains) do
train.manual_mode = false
end
game.print{ "exp-commands_trains.response", format_player_name(player), format_number(#trains, false) }
end
--- @class ExpCommand_Trains.commands
local commands = {}
--- Set all trains to automatic
Commands.new("set-trains-to-automatic", { "exp-commands_trains.description" })
--- @class ExpCommand_Artillery.commands.artillery: ExpCommand
--- @overload fun(player: LuaPlayer, surface: LuaSurface?, force: LuaForce?)
commands.set_trains_to_automatic = Commands.new("set-trains-to-automatic", { "exp-commands_trains.description" })
:optional("surface", { "exp-commands_trains.arg-surface" }, Commands.types.surface)
:optional("force", { "exp-commands_trains.arg-force" }, Commands.types.force)
:register(function(player, surface, force)
--- @cast surface LuaSurface?
--- @cast force LuaForce?
module.manual(player, surface, force)
end)
local trains = game.train_manager.get_trains{
stock = "locomotive",
has_passenger = false,
is_manual = true,
is_moving = false,
surface = surface,
force = force,
}
return module
for _, train in ipairs(trains) do
train.manual_mode = false
end
game.print{ "exp-commands_trains.response", format_player_name(player), format_number(#trains, false) }
end) --[[ @as any ]]
return {
commands = commands,
}

View File

@@ -15,8 +15,13 @@ local planet = {
["aquilo"] = "ammoniacal-ocean"
}
--- @class ExpCommand_Waterfill.commands
local commands = {}
--- Toggle player selection mode for artillery
Commands.new("waterfill", { "exp-commands_waterfill.description" })
--- @class ExpCommands_Waterfill.commands.waterfill: ExpCommand
--- @overload fun(player: LuaPlayer)
commands.waterfill = Commands.new("waterfill", { "exp-commands_waterfill.description" })
:register(function(player)
if Selection.is_selecting(player, SelectionName) then
Selection.stop(player)
@@ -26,13 +31,13 @@ Commands.new("waterfill", { "exp-commands_waterfill.description" })
local item_count_craft = math.min(math.floor(player.get_item_count("explosives") / 10), player.get_item_count("barrel"), player.get_item_count("grenade"))
local item_count_total = item_count_cliff + item_count_craft
if item_count_total == 0 then
return player.print{ "exp-commands_waterfill.requires-explosives" }
return Commands.status.error{ "exp-commands_waterfill.requires-explosives" }
else
Selection.start(player, SelectionName)
return player.print{ "exp-commands_waterfill.enter" }
return Commands.status.success{ "exp-commands_waterfill.enter" }
end
end
end)
end) --[[ @as any ]]
--- When an area is selected to be converted to water
Selection.on_selection(SelectionName, function(event)
@@ -116,3 +121,7 @@ Selection.on_selection(SelectionName, function(event)
player.print({ "exp-commands_waterfill.complete", tile_count }, Commands.print_settings.default)
end
end)
return {
commands = commands,
}

View File

@@ -41,3 +41,20 @@ require("modules/exp_scenario/commands/trains")
require("modules/exp_scenario/commands/vlayer")
require("modules/exp_scenario/commands/warnings")
require("modules/exp_scenario/commands/waterfill")
--- Control
add(require("modules/exp_scenario/control/bonus"))
add(require("modules/exp_scenario/control/research"))
--- Guis
add(require("modules/exp_scenario/gui/autofill"))
add(require("modules/exp_scenario/gui/elements"))
add(require("modules/exp_scenario/gui/landfill_blueprint"))
add(require("modules/exp_scenario/gui/module_inserter"))
add(require("modules/exp_scenario/gui/player_bonus"))
add(require("modules/exp_scenario/gui/player_stats"))
add(require("modules/exp_scenario/gui/production_stats"))
add(require("modules/exp_scenario/gui/quick_actions"))
add(require("modules/exp_scenario/gui/research_milestones"))
add(require("modules/exp_scenario/gui/science_production"))
add(require("modules/exp_scenario/gui/surveillance"))

View File

@@ -0,0 +1,41 @@
--[[ Control - Bonus
Various bonus related event handlers
TODO Refactor this fully, this is temp to get it out of the player bonus gui file
]]
local Roles = require("modules/exp_legacy/expcore/roles")
local config = require("modules/exp_legacy/config/bonus")
--- @param event EventData.on_force_created
local function apply_force_bonus(event)
for k, v in pairs(config.force_bonus) do
event.force[k] = v.initial_value
end
end
--- @param event EventData.on_surface_created
local function apply_surface_bonus(event)
local surface = assert(game.get_surface(event.surface_index))
for k, v in pairs(config.force_bonus) do
surface[k] = v.initial_value
end
end
--- @param event EventData.on_player_died
local function fast_respawn(event)
local player = assert(game.get_player(event.player_index))
if Roles.player_has_flag(player, "instant-respawn") then
player.ticks_to_respawn = 120
end
end
local e = defines.events
return {
events = {
[e.on_force_created] = apply_force_bonus,
[e.on_surface_created] = apply_surface_bonus,
[e.on_player_died] = fast_respawn,
}
}

View File

@@ -0,0 +1,46 @@
--[[ Control - Research
Various research related event handlers
TODO Refactor this fully, this is temp to get it out of the research times gui file
]]
local config = require("modules/exp_legacy/config/research")
--- @param event EventData.on_research_finished
local function on_research_finished(event)
local research_name = event.research.name
if config.bonus_inventory.enabled and config.bonus_inventory.res[research_name] then
event.research.force[config.bonus_inventory.name] = math.min((event.research.level - 1) * config.bonus_inventory.rate, config.bonus_inventory.limit)
end
if config.pollution_ageing_by_research and config.bonus_inventory.res[research_name] then
game.map_settings.pollution.ageing = math.min(10, event.research.level / 5)
end
end
--- @param event EventData.on_research_started
local function on_research_started(event)
if config.limit_res[event.research.name] and event.research.level > config.limit_res[event.research.name] then
event.research.enabled = false
event.research.visible_when_disabled = true
local rq = event.research.force.research_queue
for i = #rq, 1, -1 do
if rq[i] == event.research.name then
table.remove(rq, i)
end
end
event.research.force.cancel_current_research()
event.research.force.research_queue = rq
end
end
local e = defines.events
return {
events = {
[e.on_research_finished] = on_research_finished,
[e.on_research_started] = on_research_started,
}
}

View File

@@ -0,0 +1,404 @@
--[[-- Gui - Autofill
Adds a config menu for setting autofill of placed entities
]]
local Gui = require("modules/exp_gui")
local Roles = require("modules.exp_legacy.expcore.roles")
local config = require("modules.exp_legacy.config.gui.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<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 = assert(game.get_player(parent.player_index))
--- @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 Roles.player_allowed(player, "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 {
elements = Elements,
events = {
[e.on_built_entity] = on_built_entity,
}
}

View File

@@ -0,0 +1,83 @@
--[[-- Gui - Elements
A collection of standalone elements that are reused between GUIs
]]
local Gui = require("modules/exp_gui")
--- @class ExpGui_Elements
local Elements = {}
--- To help with caching and avoid context changes the player list from the previous update is remembered
--- @type (string?)[]
local _player_names = {}
--- Dropdown which allows selecting an online player
--- @class ExpGui_Elements.online_player_dropdown: ExpElement
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.online_player_dropdown = Gui.define("player_dropdown")
:track_all_elements()
:draw(function(def, parent)
return parent.add{
type = "drop-down",
items = _player_names,
selected_index = #_player_names > 0 and 1 or nil,
}
end)
:style{
height = 24,
} --[[ @as any ]]
--- Get the selected player name from a online player dropdown
--- @param online_player_dropdown LuaGuiElement
--- @return string
function Elements.online_player_dropdown.get_selected_name(online_player_dropdown)
local name = _player_names[online_player_dropdown.selected_index]
if not name then
online_player_dropdown.selected_index = 1
name = _player_names[1] --- @cast name -nil
end
return name
end
--- Get the selected player from a online player dropdown
--- @param online_player_dropdown LuaGuiElement
--- @return LuaPlayer
function Elements.online_player_dropdown.get_selected(online_player_dropdown)
local name = _player_names[online_player_dropdown.selected_index]
if not name then
online_player_dropdown.selected_index = 1
name = _player_names[1] --- @cast name -nil
end
return assert(game.get_player(name))
end
--- Get the number of players in the dropdown
--- @return number
function Elements.online_player_dropdown.get_player_count()
return #_player_names
end
--- Update all player dropdowns to match the currently online players
--- We don't split join and leave because the order would be inconsistent between players and cause desyncs
function Elements.online_player_dropdown.refresh_online()
_player_names[#_player_names] = nil -- Nil last element to account for player leave
for i, player in pairs(game.connected_players) do
_player_names[i] = player.name
end
for _, online_player_dropdown in Elements.online_player_dropdown:online_elements() do
online_player_dropdown.items = _player_names
end
end
local e = defines.events
--- @package
Elements.events = {
[e.on_player_joined_game] = Elements.online_player_dropdown.refresh_online,
[e.on_player_left_game] = Elements.online_player_dropdown.refresh_online,
}
return Elements

View File

@@ -0,0 +1,175 @@
--[[-- Gui - Landfill Blueprint
Adds a button to the toolbar which adds landfill to the held blueprint
]]
local Gui = require("modules/exp_gui")
local Roles = require("modules/exp_legacy/expcore/roles")
--- @param box BoundingBox
local function rotate_bounding_box(box)
box.left_top.x, box.left_top.y, box.right_bottom.x, box.right_bottom.y
= -box.right_bottom.y, box.left_top.x, -box.left_top.y, box.right_bottom.x
end
local function curve_flip_lr(oc)
local nc = table.deep_copy(oc)
for r = 1, 8 do
for c = 1, 8 do
nc[r][c] = oc[r][9 - c]
end
end
return nc
end
local function curve_flip_d(oc)
local nc = table.deep_copy(oc)
for r = 1, 8 do
for c = 1, 8 do
nc[r][c] = oc[c][r]
end
end
return nc
end
local curve_masks = {} do
local curves = { {
{ 0, 0, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 0, 0, 1, 1, 1, 0 },
{ 0, 0, 0, 1, 1, 1, 1, 0 },
{ 0, 0, 0, 1, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 1, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 0, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 0, 0 },
} }
curves[6] = curve_flip_d(curves[1])
curves[3] = curve_flip_lr(curves[6])
curves[4] = curve_flip_d(curves[3])
curves[5] = curve_flip_lr(curves[4])
curves[2] = curve_flip_d(curves[5])
curves[7] = curve_flip_lr(curves[2])
curves[8] = curve_flip_d(curves[7])
for i, map in ipairs(curves) do
local index = 0
local mask = {}
curve_masks[i] = mask
for row = 1, 8 do
for col = 1, 8 do
if map[row][col] == 1 then
index = index + 1
mask[index] = {
x = col - 5,
y = row - 5,
}
end
end
end
end
end
local rolling_stocks = {}
for name, _ in pairs(prototypes.get_entity_filtered{ { filter = "rolling-stock" } }) do
rolling_stocks[name] = true
end
--- @param blueprint LuaItemStack
--- @return table
local function landfill_gui_add_landfill(blueprint)
local entities = assert(blueprint.get_blueprint_entities())
local tile_index = 0
local new_tiles = {}
for _, entity in pairs(entities) do
if rolling_stocks[entity.name] or entity.name == "offshore-pump" then
goto continue
end
if entity.name == "curved-rail" then
-- Curved rail
local curve_mask = curve_masks[entity.direction or 8]
for _, offset in ipairs(curve_mask) do
tile_index = tile_index + 1
new_tiles[tile_index] = {
name = "landfill",
position = { entity.position.x + offset.x, entity.position.y + offset.y },
}
end
else
-- Any other entity
local proto = prototypes.entity[entity.name]
if proto.collision_mask["ground-tile"] ~= nil then
goto continue
end
-- Rotate the collision box to be north facing
local box = proto.collision_box or proto.selection_box
if entity.direction then
if entity.direction ~= defines.direction.north then
rotate_bounding_box(box)
if entity.direction ~= defines.direction.east then
rotate_bounding_box(box)
if entity.direction ~= defines.direction.south then
rotate_bounding_box(box)
end
end
end
end
-- Add the landfill
for y = math.floor(entity.position.y + box.left_top.y), math.floor(entity.position.y + box.right_bottom.y), 1 do
for x = math.floor(entity.position.x + box.left_top.x), math.floor(entity.position.x + box.right_bottom.x), 1 do
tile_index = tile_index + 1
new_tiles[tile_index] = {
name = "landfill",
position = { x, y },
}
end
end
end
::continue::
end
local old_tiles = blueprint.get_blueprint_tiles()
if old_tiles then
for _, old_tile in pairs(old_tiles) do
tile_index = tile_index + 1
new_tiles[tile_index] = {
name = "landfill",
position = old_tile.position,
}
end
end
return { tiles = new_tiles }
end
--- Add the toolbar button
Gui.toolbar.create_button{
name = "trigger_landfill_blueprint",
sprite = "item/landfill",
tooltip = { "exp-gui_landfill-blueprint.tooltip-main" },
visible = function(player, element)
return Roles.player_allowed(player, "gui/landfill")
end
}:on_click(function(def, player, element)
local stack = player.cursor_stack
if stack and stack.valid_for_read and stack.type == "blueprint" and stack.is_blueprint_setup() then
local modified = landfill_gui_add_landfill(stack)
if modified and next(modified.tiles) then
stack.set_blueprint_tiles(modified.tiles)
end
else
player.print{ "exp-gui_landfill-blueprint.error-no-blueprint" }
end
end)
return {}

View File

@@ -0,0 +1,506 @@
--[[-- Gui - Module Inserter
Adds a Gui which creates an selection planner to insert modules into buildings
]]
local Gui = require("modules/exp_gui")
local AABB = require("modules/exp_util/aabb")
local Roles = require("modules/exp_legacy/expcore/roles")
local Selection = require("modules/exp_legacy/modules/control/selection")
local SelectionModuleArea = "ModuleArea"
local config = require("modules/exp_legacy/config/module")
--- @class ExpGui_ModuleInserter.elements
local Elements = {}
--- Load all the valid machines from the config file
local machine_names = {}
for mod_name, machine_set in pairs(config.machine_sets) do
if script.active_mods[mod_name] then
for machine_name, v in pairs(machine_set) do
config.machines[machine_name] = v
table.insert(machine_names, machine_name)
end
end
end
--- Load all the modules which provide productivity bonus
local prod_module_names = {}
for name, item in pairs(prototypes.item) do
if item.module_effects and item.module_effects.productivity and item.module_effects.productivity > 0 then
prod_module_names[#prod_module_names + 1] = name
end
end
--- Filters used for the different elem buttons
local elem_filter = {
-- Select only valid machines
machine_name = { {
filter = "name",
name = machine_names,
} },
-- Select modules that don't give productivity
no_prod = { {
filter = "type",
type = "module",
}, {
filter = "name",
name = prod_module_names,
mode = "and",
invert = true,
} },
-- Select any modules
with_prod = { {
filter = "type",
type = "module",
} },
}
--- Button used to create a selection planner from a module table
--- @class ExpGui_ModuleInserter.elements.create_selection_planner: ExpElement
--- @field data table<LuaGuiElement, LuaGuiElement>
--- @overload fun(parent: LuaGuiElement, module_table: LuaGuiElement): LuaGuiElement
Elements.create_selection_planner = Gui.define("module_inserter/create_selection_planner")
:draw{
type = "sprite-button",
sprite = "item/upgrade-planner",
tooltip = { "exp-gui_module-inserter.tooltip-apply" },
style = "shortcut_bar_button",
}
:style{
size = 28,
padding = 0,
}
:element_data(
Gui.from_argument(1)
)
:on_click(function(def, player, element)
--- @cast def ExpGui_ModuleInserter.elements.create_selection_planner
Selection.start(player, SelectionModuleArea, false, def.data[element])
end) --[[ @as any ]]
--- Used to select the machine to apply modules to
--- @class ExpGui_ModuleInserter.elements.machine_selector: ExpElement
--- @field data table<LuaGuiElement, { on_last_row: boolean, module_table: LuaGuiElement }>
--- @overload fun(parent: LuaGuiElement, module_table: LuaGuiElement): LuaGuiElement
Elements.machine_selector = Gui.define("module_inserter/machine_selector")
:draw{
type = "choose-elem-button",
elem_type = "entity",
elem_filters = elem_filter.machine_name,
style = "slot_button",
}
:element_data{
on_last_row = true,
module_table = Gui.from_argument(1),
}
:on_elem_changed(function(def, player, element, event)
--- @cast def ExpGui_ModuleInserter.elements.machine_selector
local element_data = def.data[element]
local machine_name = element.elem_value --[[ @as string? ]]
if not machine_name then
if element_data.on_last_row then
Elements.module_table.reset_row(element_data.module_table, element)
else
Elements.module_table.remove_row(element_data.module_table, element)
end
else
Elements.module_table.refresh_row(element_data.module_table, element, machine_name)
if element_data.on_last_row then
element_data.on_last_row = false
Elements.module_table.add_row(element_data.module_table)
end
end
end) --[[ @as any ]]
--- Used to select the modules to be applied
Elements.module_selector = Gui.define("module_inserter/module_selector")
:draw{
type = "choose-elem-button",
elem_type = "item-with-quality",
elem_filters = elem_filter.no_prod,
visible = Gui.from_argument(1),
enabled = false,
style = "slot_button",
}
--- @class ExpGui_ModuleInserter.elements.module_table.row_elements
--- @field machine_selector LuaGuiElement
--- @field row_separators LuaGuiElement[]
--- @field module_selectors LuaGuiElement[]
--- A table that allows selecting modules
--- @class ExpGui_ModuleInserter.elements.module_table: ExpElement
--- @field data table<LuaGuiElement, ExpGui_ModuleInserter.elements.module_table.row_elements[]>
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.module_table = Gui.define("module_inserter/module_table")
:draw(function(def, parent)
--- @cast def ExpGui_ModuleInserter.elements.module_table
local slots_per_row = config.module_slots_per_row + 1
return Gui.elements.scroll_table(parent, 280, slots_per_row)
end)
:element_data{} --[[ @as any ]]
--- Get all the rows in a module table
--- @param module_table LuaGuiElement
--- @return ExpGui_ModuleInserter.elements.module_table.row_elements[]
function Elements.module_table.get_rows(module_table)
return Elements.module_table.data[module_table]
end
--- Add a row to a module table
--- @param module_table LuaGuiElement
function Elements.module_table.add_row(module_table)
local machine_selector = Elements.machine_selector(module_table, module_table)
local rows = Elements.module_table.data[module_table]
local module_selectors, row_separators = {}, {}
rows[machine_selector.index] = {
machine_selector = machine_selector,
module_selectors = module_selectors,
row_separators = row_separators,
}
-- Add the module selectors and row separators
local slots_per_row = config.module_slots_per_row + 1
for i = 1, config.module_slot_max do
if i % slots_per_row == 0 then
row_separators[#row_separators + 1] = module_table.add{ type = "flow", visible = false }
end
module_selectors[i] = Elements.module_selector(module_table, i <= config.module_slots_per_row)
end
end
--- Remove a row from a module table
--- @param module_table LuaGuiElement
--- @param machine_selector LuaGuiElement
function Elements.module_table.remove_row(module_table, machine_selector)
local rows = Elements.module_table.data[module_table]
local row = rows[machine_selector.index]
row[machine_selector.index] = nil
Gui.destroy_if_valid(machine_selector)
for _, separator in pairs(row.row_separators) do
Gui.destroy_if_valid(separator)
end
for _, selector in pairs(row.module_selectors) do
Gui.destroy_if_valid(selector)
end
end
--- Reset a row to be empty
--- @param module_table LuaGuiElement
--- @param machine_selector LuaGuiElement
function Elements.module_table.reset_row(module_table, machine_selector)
local rows = Elements.module_table.data[module_table]
local row = rows[machine_selector.index]
for _, separator in pairs(row.row_separators) do
separator.visible = false
end
for i, selector in pairs(row.module_selectors) do
selector.visible = i <= config.module_slots_per_row
selector.enabled = false
selector.elem_value = nil
end
end
--- Refresh a row to match the config required for a given machine
--- @param module_table LuaGuiElement
--- @param machine_selector LuaGuiElement
--- @param machine_name string
function Elements.module_table.refresh_row(module_table, machine_selector, machine_name)
local rows = Elements.module_table.data[module_table]
local row = rows[machine_selector.index]
local active_module_count = prototypes.entity[machine_name].module_inventory_size
local visible_row_count = math.ceil(active_module_count / config.module_slots_per_row)
local visible_module_count = visible_row_count * config.module_slots_per_row
local module_elem_value = { name = config.machines[machine_name].module }
for i, separator in pairs(row.row_separators) do
separator.visible = i < visible_row_count
end
for i, selector in pairs(row.module_selectors) do
if i <= active_module_count then
if config.machines[machine_name].prod then
selector.elem_filters = elem_filter.with_prod
else
selector.elem_filters = elem_filter.no_prod
end
selector.visible = true
selector.enabled = true
selector.elem_value = module_elem_value
else
selector.visible = i <= visible_module_count
selector.enabled = false
selector.elem_value = nil
end
end
end
--- Container added to the left gui flow
Elements.container = Gui.define("module_inserter/container")
:draw(function(def, parent)
local container = Gui.elements.container(parent)
local header = Gui.elements.header(container, { caption = { "exp-gui_module-inserter.caption-main" } })
local module_table = Elements.module_table(container)
Elements.module_table.add_row(module_table)
Elements.create_selection_planner(header, module_table)
return Gui.elements.container.get_root_element(container)
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_module_inserter",
left_element = Elements.container,
sprite = "item/productivity-module-3",
tooltip = { "exp-gui_module-inserter.tooltip-main" },
visible = function(player, element)
return Roles.player_allowed(player, "gui/module")
end
}
--- Apply module changes to a crafting machine
--- @param player LuaPlayer
--- @param area BoundingBox
--- @param machine_name string
--- @param planner_with_prod LuaItemStack
--- @param planner_no_prod LuaItemStack
local function apply_planners_in_area(player, area, machine_name, planner_with_prod, planner_no_prod)
local force = player.force
local surface = player.surface
local upgrade_area = surface.upgrade_area
-- Bounding box table to be reused in the loop below
--- @type BoundingBox
local param_area = {
left_top = {},
right_bottom = {}
}
-- Update area param table to be reused in the loop below
--- @type LuaSurface.upgrade_area_param
local params = {
area = param_area,
item = planner_with_prod,
player = player,
force = force,
}
-- Find all required entities in the area and apply the correct module planner to them
for _, entity in pairs(surface.find_entities_filtered{ area = area, name = machine_name, force = force }) do
local pos = entity.position
param_area.left_top = pos
param_area.right_bottom = pos
local m_current_recipe = entity.get_recipe()
local r_proto = m_current_recipe and m_current_recipe.prototype
if r_proto and r_proto.allowed_effects and r_proto.allowed_effects["productivity"] then
params.item = planner_with_prod
upgrade_area(params)
else
params.item = planner_no_prod
upgrade_area(params)
end
end
end
--- When an area is selected to have module changes applied to it
--- @param event EventData.on_player_selected_area
--- @param module_table LuaGuiElement
Selection.on_selection(SelectionModuleArea, function(event, module_table)
local player = Gui.get_player(event)
local area = AABB.expand(event.area)
-- Create an inventory with three upgrade planners
local inventory = game.create_inventory(3)
inventory.insert{ name = "upgrade-planner", count = 3 }
local bulk_mapper_index = 1
local planner_bulk = inventory[1]
local planner_with_prod = inventory[2]
local planner_no_prod = inventory[3]
-- Create a table to be reused when setting mappers
local mapper_table = {
type = "entity",
name = "",
module_slots = {},
quality = "",
comparator = "=",
}
for _, row in pairs(Elements.module_table.get_rows(module_table)) do
local machine_name = row.machine_selector.elem_value --[[ @as string? ]]
if not machine_name then
goto continue
end
local module_selectors = row.module_selectors
local entity_prototype = prototypes.entity[machine_name]
local wants_prod_modules = false
local module_index = 1
local all_modules = {}
local no_prod = {}
-- Get all the modules selected
for i = 1, entity_prototype.module_inventory_size do
local module_selector = module_selectors[i]
local module = module_selector.elem_value --[[ @as { name: string, quality: string }? ]]
if module then
-- Module selected, add it the module arrays
local no_prod_name = module.name:gsub("productivity", "efficiency")
wants_prod_modules = wants_prod_modules or module.name ~= no_prod_name
no_prod[module_index] = { name = no_prod_name, quality = module.quality }
all_modules[module_index] = module
module_index = module_index + 1
else
-- No module selected, insert blanks
no_prod[module_index] = {}
all_modules[module_index] = {}
module_index = module_index + 1
end
end
if wants_prod_modules and entity_prototype.get_crafting_speed() then
-- Crafting machines wanting prod modules must be handled on a case by case biases
local i = 0
mapper_table.name = machine_name
for quality_name in pairs(prototypes.quality) do
i = i + 1
mapper_table.module_slots = nil
mapper_table.quality = quality_name
planner_with_prod.set_mapper(i, "from", mapper_table)
planner_no_prod.set_mapper(i, "from", mapper_table)
mapper_table.module_slots = all_modules
planner_with_prod.set_mapper(i, "to", mapper_table)
mapper_table.module_slots = no_prod
planner_no_prod.set_mapper(i, "to", mapper_table)
end
apply_planners_in_area(player, area, machine_name, planner_with_prod, planner_no_prod)
else
-- All other machines can be applied in a single upgrade planner
mapper_table.name = machine_name
for quality_name in pairs(prototypes.quality) do
mapper_table.module_slots = nil
mapper_table.quality = quality_name
planner_bulk.set_mapper(bulk_mapper_index, "from", mapper_table)
mapper_table.module_slots = all_modules
planner_bulk.set_mapper(bulk_mapper_index, "to", mapper_table)
bulk_mapper_index = bulk_mapper_index + 1
end
end
::continue::
end
-- Apply remaining module changes using the bulk planner
if bulk_mapper_index > 1 then
player.surface.upgrade_area{
area = area,
item = planner_bulk,
force = player.force,
player = player,
}
end
inventory.destroy()
end)
--- Apply rotation and modules to machines after their settings are pasted
--- @param event EventData.on_entity_settings_pasted
local function on_entity_settings_pasted(event)
local source = event.source
if not source or not source.valid then
return
end
local destination = event.destination
if not destination or not destination.valid then
return
end
if config.copy_paste_rotation then
-- Attempt to rotate a machine to match the source machine
if (source.name == destination.name or source.prototype.fast_replaceable_group == destination.prototype.fast_replaceable_group) then
if source.supports_direction and destination.supports_direction and source.type ~= "transport-belt" then
local destination_box = destination.bounding_box
local ltx = destination_box.left_top.x
local lty = destination_box.left_top.y
local rbx = destination_box.right_bottom.x
local rby = destination_box.right_bottom.y
local old_direction = destination.direction
destination.direction = source.direction
if ltx ~= destination_box.left_top.x or lty ~= destination_box.left_top.y
or rbx ~= destination_box.right_bottom.x or rby ~= destination_box.right_bottom.y then
destination.direction = old_direction
end
end
end
end
if config.copy_paste_module then
-- Attempt to copy the modules from the source machine
if source.name ~= destination.name then
goto end_copy_paste_module
end
local module_inventory = source.get_module_inventory()
if not module_inventory then
goto end_copy_paste_module
end
-- Get the modules and add them to the planner
local all_modules = {}
for i = 1, #module_inventory do
local slot = module_inventory[i]
if slot.valid_for_read and slot.count > 0 then
all_modules[i] = { name = slot.name, quality = slot.quality.name }
else
all_modules[i] = {}
end
end
-- Create an inventory with an upgrade planner
local inventory = game.create_inventory(1)
inventory.insert{ name = "upgrade-planner", count = 3 }
-- Set the mapping for the planner
local planner = inventory[1]
local mapper = {
type = "entity",
name = destination.name,
quality = destination.quality.name,
comparator = "=",
}
planner.set_mapper(1, "from", mapper)
mapper.module_slots = all_modules
planner.set_mapper(1, "to", mapper)
-- Apply the planner
local player = assert(game.get_player(event.player_index))
player.surface.upgrade_area{
area = destination.bounding_box,
item = planner,
player = player,
force = player.force,
}
inventory.destroy()
::end_copy_paste_module::
end
end
local e = defines.events
return {
elements = Elements,
events = {
[e.on_entity_settings_pasted] = on_entity_settings_pasted,
}
}

View File

@@ -0,0 +1,527 @@
--[[-- Gui - Player Bonus
Adds a gui that allows players to apply various bonuses
]]
local Gui = require("modules/exp_gui")
local Roles = require("modules/exp_legacy/expcore/roles")
local config = require("modules/exp_legacy/config/bonus")
local vlayer = require("modules/exp_legacy/modules/control/vlayer")
local format_number = require("util").format_number
--- @class ExpGui_PlayerBonus.elements
local Elements = {}
--- @class ExpGui_PlayerBonus.bonus_data
--- @field name string
--- @field cost number
--- @field scale number
--- @field max_value number
--- @field initial_value number
--- @field is_percentage boolean
--- @field is_special boolean
--- @field value_step number
--- @field _cost_scale number
--- For perf calculate the division of scale against cost ahead of time
for _, bonus_data in pairs(config.player_bonus) do
bonus_data._cost_scale = bonus_data.cost / bonus_data.scale
end
--- Progress bar which displays how much of a bonus has been used
--- @class ExpGui_PlayerBonus.elements.bonus_used: ExpElement
--- @field data number
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.bonus_used = Gui.define("player_bonus/bonus_used")
:track_all_elements()
:draw{
type = "progressbar",
caption = "0 / 0",
value = 0,
style = "electric_satisfaction_statistics_progressbar",
}
:style{
width = 150,
height = 24,
font = "heading-2",
color = { 1, 0, 0 },
}
:element_data(0) --[[ @as any ]]
--- Value is cached to save perf
--- @type table<number, number>
do local _points_limit = {}
--- Clear the cache for points limit
--- @param player LuaPlayer
function Elements.bonus_used._clear_points_limit_cache(player)
_points_limit[player.index] = nil
end
--- Clear the cache for points limit
--- @param player LuaPlayer
--- @return number
function Elements.bonus_used.calculate_points_limit(player)
return _points_limit[player.index] or Elements.bonus_used._calculate_points_limit(player)
end
--- Calculate the bonus limit for a player
--- @param player LuaPlayer
--- @return number
function Elements.bonus_used._calculate_points_limit(player)
local role_diff = Roles.get_role_by_name(config.points.role_name).index - Roles.get_player_highest_role(player).index
local points_limit = math.floor(config.points.base * (1 + config.points.increase_percentage_per_role_level * role_diff))
_points_limit[player.index] = points_limit
return points_limit
end
end
--- Refresh a bonus used slider to the current bonus cost
--- @param bonus_used LuaGuiElement
--- @param bonus_cost number
--- @return boolean
function Elements.bonus_used.refresh(bonus_used, bonus_cost)
local player = Gui.get_player(bonus_used)
local limit = Elements.bonus_used.calculate_points_limit(player)
Elements.bonus_used.data[bonus_used] = bonus_cost
bonus_used.caption = bonus_cost .. " / " .. limit
bonus_used.value = bonus_cost / limit
return bonus_cost <= limit
end
--- Refresh all bonus used sliders for a player
--- @param player LuaPlayer
--- @param bonus_cost number
--- @return boolean
function Elements.bonus_used.refresh_player(player, bonus_cost)
local limit = Elements.bonus_used.calculate_points_limit(player)
for _, bonus_used in Elements.bonus_used:tracked_elements(player) do
Elements.bonus_used.data[bonus_used] = bonus_cost
bonus_used.caption = bonus_cost .. " / " .. limit
bonus_used.value = bonus_cost / limit
end
return bonus_cost <= limit
end
--- Update the element caption and value with a delta bonus cost
--- @param bonus_used LuaGuiElement
--- @param delta number
--- @return boolean
function Elements.bonus_used.update(bonus_used, delta)
local player = Gui.get_player(bonus_used)
local limit = Elements.bonus_used.calculate_points_limit(player)
local bonus_cost = Elements.bonus_used.data[bonus_used] + delta
Elements.bonus_used.data[bonus_used] = bonus_cost
bonus_used.caption = bonus_cost .. " / " .. limit
bonus_used.value = bonus_cost / limit
return bonus_cost <= limit
end
--- Reset all sliders to before they were edited
--- @class ExpGui_PlayerBonus.elements.reset_button: ExpElement
--- @field data table<LuaGuiElement, { bonus_table: LuaGuiElement, bonus_used: LuaGuiElement, apply_button: LuaGuiElement? }>
--- @overload fun(parent: LuaGuiElement, bonus_table: LuaGuiElement, bonus_used: LuaGuiElement): LuaGuiElement
Elements.reset_button = Gui.define("player_bonus/reset_button")
:draw{
type = "sprite-button",
sprite = "utility/reset",
tooltip = { "exp-gui_player-bonus.tooltip-reset" },
style = "shortcut_bar_button_red",
enabled = false,
}
:style{
size = 26,
}
:element_data{
bonus_table = Gui.from_argument(1),
bonus_used = Gui.from_argument(2),
}
:on_click(function(def, player, element)
--- @cast def ExpGui_PlayerBonus.elements.reset_button
element.enabled = false
local element_data = def.data[element]
if element_data.apply_button then
element_data.apply_button.enabled = false
end
Elements.bonus_table.reset_sliders(element_data.bonus_table)
local bonus_cost = Elements.bonus_table.calculate_cost(element_data.bonus_table)
Elements.bonus_used.refresh(element_data.bonus_used, bonus_cost)
end) --[[ @as any ]]
--- Link an apply button to this reset button so that it will be disabled after being pressed
--- @param reset_button LuaGuiElement
--- @param apply_button LuaGuiElement
function Elements.reset_button.link_apply_button(reset_button, apply_button)
Elements.reset_button.data[reset_button].apply_button = apply_button
end
--- Apply the bonus for a player
--- @class ExpGui_PlayerBonus.elements.apply_button: ExpElement
--- @field data table<LuaGuiElement, { bonus_table: LuaGuiElement, bonus_used: LuaGuiElement, reset_button: LuaGuiElement? }>
--- @overload fun(parent: LuaGuiElement, bonus_table: LuaGuiElement, bonus_used: LuaGuiElement): LuaGuiElement
Elements.apply_button = Gui.define("player_bonus/apply_button")
:draw{
type = "sprite-button",
sprite = "utility/confirm_slot",
tooltip = { "exp-gui_player-bonus.tooltip-apply" },
style = "shortcut_bar_button_green",
enabled = false,
}
:style{
size = 26,
}
:element_data{
bonus_table = Gui.from_argument(1),
bonus_used = Gui.from_argument(2),
}
:on_click(function(def, player, element)
--- @cast def ExpGui_PlayerBonus.elements.apply_button
element.enabled = false
local element_data = def.data[element]
if element_data.reset_button then
element_data.reset_button.enabled = false
end
local bonus_cost = Elements.bonus_table.calculate_cost(element_data.bonus_table)
if Elements.bonus_used.refresh(element_data.bonus_used, bonus_cost) then
Elements.bonus_table.save_sliders(element_data.bonus_table)
Elements.container.apply_player_bonus(player)
end
end) --[[ @as any ]]
--- Link an apply button to this reset button so that it will be disabled after being pressed
--- @param apply_button LuaGuiElement
--- @param reset_button LuaGuiElement
function Elements.apply_button.link_reset_button(apply_button, reset_button)
Elements.apply_button.data[apply_button].reset_button = reset_button
end
--- Label used within the bonus table
--- @class ExpGui_PlayerBonus.elements.bonus_table_label: ExpElement
--- @overload fun(parent: LuaGuiElement, caption: LocalisedString?, tooltip: LocalisedString?, width: number?)
Elements.bonus_table_label = Gui.define("player_bonus/table_label")
:draw{
type = "label",
caption = Gui.from_argument(1),
tooltip = Gui.from_argument(2),
style = "heading_2_label",
}
:style{
width = Gui.from_argument(3, 70),
} --[[ @as any ]]
--- @class ExpGui_PlayerBonus.elements.bonus_slider.elements
--- @field bonus_used LuaGuiElement
--- @field reset_button LuaGuiElement
--- @field apply_button LuaGuiElement
--- @class ExpGui_PlayerBonus.elements.bonus_slider.data: ExpGui_PlayerBonus.elements.bonus_slider.elements
--- @field previous_value number
--- @field label LuaGuiElement
--- @field bonus_data ExpGui_PlayerBonus.bonus_data
--- Slider and label pair used for selecting bonus amount
--- @class ExpGui_PlayerBonus.elements.bonus_slider: ExpElement
--- @field data table<LuaGuiElement, ExpGui_PlayerBonus.elements.bonus_slider.data>
--- @overload fun(parent: LuaGuiElement, bonus_data: ExpGui_PlayerBonus.bonus_data, elements: ExpGui_PlayerBonus.elements.bonus_slider.elements)
Elements.bonus_slider = Gui.define("player_bonus/bonus_slider")
:draw(function(def, parent, bonus_data, elements)
local player = Gui.get_player(parent)
local value = Elements.container.get_player_bonus(player, bonus_data.name)
if not value then
value = bonus_data.initial_value
elements.apply_button.enabled = true
end
local slider = parent.add{
type = "slider",
value = value,
maximum_value = bonus_data.max_value,
value_step = bonus_data.value_step,
discrete_values = true,
style = "notched_slider",
}
slider.style.width = 180
slider.style.horizontally_stretchable = true
local slider_caption = Elements.bonus_slider.calculate_slider_caption(bonus_data, value)
def.data[slider] = {
label = Elements.bonus_table_label(parent, slider_caption, nil, 50),
previous_value = value,
bonus_data = bonus_data,
bonus_used = elements.bonus_used,
reset_button = elements.reset_button,
apply_button = elements.apply_button,
}
return slider
end)
:on_value_changed(function(def, player, element, event)
--- @cast def ExpGui_PlayerBonus.elements.bonus_slider
local value = element.slider_value
local element_data = def.data[element]
local bonus_data = element_data.bonus_data
local value_change = value - element_data.previous_value
element_data.previous_value = value
element_data.label.caption = Elements.bonus_slider.calculate_slider_caption(bonus_data, value)
element_data.apply_button.enabled = Elements.bonus_used.update(element_data.bonus_used, value_change * bonus_data._cost_scale)
element_data.reset_button.enabled = true
end) --[[ @as any ]]
--- Get the caption of the slider label
--- @param bonus_data ExpGui_PlayerBonus.bonus_data
--- @param value number
--- @return LocalisedString
function Elements.bonus_slider.calculate_slider_caption(bonus_data, value)
return bonus_data.is_percentage and format_number(value * 100, false) .. " %" or format_number(value, false)
end
--- Calculate the cost of a slider
--- @param slider LuaGuiElement
--- @return number
function Elements.bonus_slider.calculate_cost(slider)
local bonus_data = Elements.bonus_slider.data[slider].bonus_data
return slider.slider_value * bonus_data._cost_scale
end
--- Reset a slider to its original value
--- @param slider LuaGuiElement
function Elements.bonus_slider.reset_value(slider)
local player = Gui.get_player(slider)
local element_data = Elements.bonus_slider.data[slider]
local bonus_data = element_data.bonus_data
local value = Elements.container.get_player_bonus(player, bonus_data.name) or bonus_data.initial_value
slider.slider_value = value
element_data.label.caption = Elements.bonus_slider.calculate_slider_caption(bonus_data, value)
element_data.previous_value = value
end
--- Save a slider at its current value
--- @param slider LuaGuiElement
function Elements.bonus_slider.save_value(slider)
local player = Gui.get_player(slider)
local bonus_data = Elements.bonus_slider.data[slider].bonus_data
Elements.container.set_player_bonus(player, bonus_data.name, slider.slider_value)
end
--- A table containing all of the bonus sliders and their label
--- @class ExpGui_PlayerBonus.elements.bonus_table: ExpElement
--- @field data table<LuaGuiElement, LuaGuiElement[]>
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.bonus_table = Gui.define("player_bonus/bonus_table")
:draw(function(_, parent)
return Gui.elements.scroll_table(parent, 300, 3)
end)
:element_data{} --[[ @as any ]]
--- Adds a row to the milestone table
--- @param bonus_table LuaGuiElement
--- @param elements ExpGui_PlayerBonus.elements.bonus_slider.elements
--- @param bonus_data ExpGui_PlayerBonus.bonus_data
function Elements.bonus_table.add_row(bonus_table, bonus_data, elements)
local rows = Elements.bonus_table.data[bonus_table]
Elements.bonus_table_label(bonus_table, { "exp-gui_player-bonus.caption-" .. bonus_data.name }, { "exp-gui_player-bonus.tooltip-" .. bonus_data.name })
rows[#rows + 1] = Elements.bonus_slider(bonus_table, bonus_data, elements)
end
--- Calculate the total cost of a table
--- @param bonus_table LuaGuiElement
--- @return number
function Elements.bonus_table.calculate_cost(bonus_table)
local cost = 0
for _, slider in pairs(Elements.bonus_table.data[bonus_table]) do
cost = cost + Elements.bonus_slider.calculate_cost(slider)
end
return cost
end
--- Reset all sliders in the table to their original positions
--- @param bonus_table LuaGuiElement
function Elements.bonus_table.reset_sliders(bonus_table)
for _, slider in pairs(Elements.bonus_table.data[bonus_table]) do
Elements.bonus_slider.reset_value(slider)
end
end
--- Save all sliders at their current position
--- @param bonus_table LuaGuiElement
function Elements.bonus_table.save_sliders(bonus_table)
for _, slider in pairs(Elements.bonus_table.data[bonus_table]) do
Elements.bonus_slider.save_value(slider)
end
end
--- Container added to the left gui flow
--- @class ExpGui_PlayerBonus.elements.container: ExpElement
--- @field data table<LuaPlayer, { [string]: number }>
Elements.container = Gui.define("player_bonus/container")
:draw(function(def, parent)
local container = Gui.elements.container(parent)
local header = Gui.elements.header(container, { caption = { "exp-gui_player-bonus.caption-main" } })
local elements = {} --- @cast elements ExpGui_PlayerBonus.elements.bonus_slider.elements
local bonus_table = Elements.bonus_table(container)
elements.bonus_used = Elements.bonus_used(header)
elements.reset_button = Elements.reset_button(header, bonus_table, elements.bonus_used)
elements.apply_button = Elements.apply_button(header, bonus_table, elements.bonus_used)
Elements.reset_button.link_apply_button(elements.reset_button, elements.apply_button)
Elements.apply_button.link_reset_button(elements.apply_button, elements.reset_button)
for _, bonus_data in pairs(config.player_bonus) do
--- @cast bonus_data ExpGui_PlayerBonus.bonus_data
Elements.bonus_table.add_row(bonus_table, bonus_data, elements)
end
local bonus_cost = Elements.bonus_table.calculate_cost(bonus_table)
Elements.bonus_used.refresh(elements.bonus_used, bonus_cost)
return Gui.elements.container.get_root_element(container)
end)
:player_data{} --[[ @as any ]]
--- Set the bonus value for a player
--- @param player LuaPlayer
--- @param name string
--- @param value number
function Elements.container.set_player_bonus(player, name, value)
Elements.container.data[player][name] = value
end
--- Get the bonus value for a player
--- @param player LuaPlayer
--- @param name string
--- @return number
function Elements.container.get_player_bonus(player, name)
return Elements.container.data[player][name]
end
--- Clear all bonus values for a player
--- @param player LuaPlayer
function Elements.container.clear_player_bonus(player)
Elements.container.data[player] = {}
for _, bonus_data in pairs(config.player_bonus) do
if not bonus_data.is_special then
player[bonus_data.name] = 0
if bonus_data.combined_bonus then
for _, name in ipairs(bonus_data.combined_bonus) do
player[name] = 0
end
end
end
end
end
--- Apply all bonus values for a player
--- @param player LuaPlayer
function Elements.container.apply_player_bonus(player)
if not player.character then
return
end
local player_data = Elements.container.data[player]
for _, bonus_data in pairs(config.player_bonus) do
if not bonus_data.is_special then
local value = player_data[bonus_data.name] or 0
player[bonus_data.name] = value
if bonus_data.combined_bonus then
for _, name in ipairs(bonus_data.combined_bonus) do
player[name] = value
end
end
end
end
end
--- Calculate the current cost for a player
--- @param player LuaPlayer
--- @return number
function Elements.container.calculate_cost(player)
local cost = 0
local player_data = Elements.container.data[player]
for _, bonus_data in pairs(config.player_bonus) do
cost = cost + (player_data[bonus_data.name] or 0) * bonus_data._cost_scale
end
return cost
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_player_bonus",
left_element = Elements.container,
sprite = "item/exoskeleton-equipment",
tooltip = { "exp-gui_player-bonus.tooltip-main" },
visible = function(player, element)
return Roles.player_allowed(player, "gui/bonus")
end
}
--- Recalculate and apply the bonus for a player
local function recalculate_bonus(event)
local player = assert(game.get_player(event.player_index))
if event.name == Roles.events.on_role_assigned or event.name == Roles.events.on_role_unassigned then
-- If the player's roles changed then we will need to recalculate their limit
Elements.bonus_used._clear_points_limit_cache(player)
local bonus_cost = Elements.container.calculate_cost(player)
local within_limit = Elements.bonus_used.refresh_player(player, bonus_cost)
if not within_limit or not Roles.player_allowed(player, "gui/bonus") then
Elements.container.clear_player_bonus(player)
return
end
end
Elements.container.apply_player_bonus(player)
end
--- Apply periodic bonus to a player
--- @param player LuaPlayer
local function apply_personal_battery_recharge(player)
local available_energy = vlayer.get_statistics()["energy_storage"]
if available_energy <= 0 then
return -- No power to give
end
local armor = player.get_inventory(defines.inventory.character_armor)
if not armor or not armor[1] or not armor[1].valid_for_read then
return -- No armor
end
local grid = armor[1].grid
if not grid or grid.available_in_batteries >= grid.battery_capacity then
return -- No grid or already full
end
local recharge_amount = Elements.container.get_player_bonus(player, "personal_battery_recharge") * 100000 * config.periodic_bonus_rate / 6
for _, equipment in pairs(grid.equipment) do
if equipment.energy < equipment.max_energy then
local energy_to_give = math.min(math.floor(equipment.max_energy - equipment.energy), available_energy, recharge_amount)
equipment.energy = equipment.energy + energy_to_give
recharge_amount = recharge_amount - energy_to_give
available_energy = vlayer.energy_changed(-energy_to_give)
end
end
end
--- Apply the periodic bonus to all players
local function apply_periodic_bonus_online()
for _, player in pairs(game.connected_players) do
if player.character and Roles.player_allowed(player, "gui/bonus") then
apply_personal_battery_recharge(player)
end
end
end
local e = defines.events
return {
elements = Elements,
events = {
[e.on_player_respawned] = recalculate_bonus,
[Roles.events.on_role_assigned] = recalculate_bonus,
[Roles.events.on_role_unassigned] = recalculate_bonus,
},
on_nth_tick = {
[config.periodic_bonus_rate] = apply_periodic_bonus_online,
}
}

View File

@@ -0,0 +1,226 @@
--[[-- Gui - Player Data
Displays the player data for a player
]]
local ExpUtil = require("modules/exp_util")
local Gui = require("modules/exp_gui")
local ElementsExtra = require("modules/exp_scenario/gui/elements")
local Roles = require("modules/exp_legacy/expcore/roles")
require("modules/exp_legacy/modules/data/statistics")
local PlayerData = require("modules/exp_legacy/expcore/player_data")
local PlayerStats = PlayerData.Statistics
--- @class ExpGui_PlayerStats.elements
local Elements = {}
local short_time_format = ExpUtil.format_time_factory_locale{ format = "short", coefficient = 3600, hours = true, minutes = true }
local format_number = require("util").format_number
local function format_number_2dp(n)
return format_number(math.floor(n), false) .. string.format("%.2f", n % 1):sub(2)
end
local short_time_zero, format_number_zero = short_time_format(0), format_number_2dp(0)
--- @type table<string, { default: LocalisedString, calculate: fun(player: LuaPlayer): LocalisedString }>
local computed_stats = {
DamageDeathRatio = {
default = format_number_zero,
calculate = function(player)
return format_number_2dp(PlayerStats["DamageDealt"]:get(player, 0) / PlayerStats["Deaths"]:get(player, 1))
end,
},
KillDeathRatio = {
default = format_number_zero,
calculate = function(player)
return format_number_2dp(PlayerStats["Kills"]:get(player, 0) / PlayerStats["Deaths"]:get(player, 1))
end,
},
SessionTime = {
default = short_time_zero,
calculate = function(player)
return short_time_format((PlayerStats["Playtime"]:get(player, 0) - PlayerStats["AfkTime"]:get(player, 0)) / PlayerStats["JoinCount"]:get(player, 1))
end,
},
BuildRatio = {
default = format_number_zero,
calculate = function(player)
return format_number_2dp(PlayerStats["MachinesBuilt"]:get(player, 0) / PlayerStats["MachinesRemoved"]:get(player, 1))
end,
},
RocketPerHour = {
default = format_number_zero,
calculate = function(player)
return format_number_2dp(PlayerStats["RocketsLaunched"]:get(player, 0) * 60 / PlayerStats["Playtime"]:get(player, 1))
end,
},
TreeKillPerMinute = {
default = format_number_zero,
calculate = function(player)
return format_number_2dp(PlayerStats["TreesDestroyed"]:get(player, 0) / PlayerStats["Playtime"]:get(player, 1))
end,
},
NetPlayTime = {
default = short_time_zero,
calculate = function(player)
return short_time_format((PlayerStats["Playtime"]:get(player, 0) - PlayerStats["AfkTime"]:get(player, 0)))
end,
},
AFKTimeRatio = {
default = format_number_zero,
calculate = function(player)
return format_number_2dp(PlayerStats["AfkTime"]:get(player, 0) * 100 / PlayerStats["Playtime"]:get(player, 1))
end,
},
Locale = {
default = "en",
calculate = function(player)
return player.locale
end,
},
}
--- Label used for all data in the data table
--- @class ExpGui_PlayerStats.elements.table_label: ExpElement
--- @overload fun(parent: LuaGuiElement, opts: { caption: LocalisedString, tooltip: LocalisedString, width: number })
Elements.table_label = Gui.define("player_stats/table_label")
:draw{
type = "label",
caption = Gui.from_argument("caption"),
tooltip = Gui.from_argument("tooltip"),
style = "heading_2_label",
}
:style{
width = Gui.from_argument("width"),
} --[[ @as any ]]
--- Data table that shows all data for a player
--- @class ExpGui_PlayerStats.elements.player_stats_table: ExpElement
--- @field data table<LuaGuiElement, { [string]: LuaGuiElement }>
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.player_stats_table = Gui.define("player_stats/data_table")
:draw(function(def, parent)
--- @cast def ExpGui_PlayerStats.elements.player_stats_table
local data_table = Gui.elements.scroll_table(parent, 240, 4)
local labels = {}
-- Add all standalone stats
for _, stat_name in pairs(PlayerData.Statistics.metadata.display_order) do
local metadata = PlayerData.Statistics[stat_name].metadata
local value = metadata.stringify_short and metadata.stringify_short(0)
or metadata.stringify and metadata.stringify(0)
or format_number(0, false)
Elements.table_label(data_table, {
caption = metadata.name or { "exp-statistics." .. stat_name },
tooltip = metadata.tooltip or { "exp-statistics." .. stat_name .. "-tooltip" },
width = 135,
})
labels[stat_name] = Elements.table_label(data_table, {
caption = { "readme.data-format", value, metadata.unit or "" },
tooltip = metadata.value_tooltip or { "exp-statistics." .. stat_name .. "-tooltip" },
width = 105,
})
end
-- Add all computed stats
for stat_name, data in pairs(computed_stats) do
Elements.table_label(data_table, {
caption = { "exp-statistics." .. stat_name },
tooltip = { "exp-statistics." .. stat_name .. "-tooltip" },
width = 135,
})
labels[stat_name] = Elements.table_label(data_table, {
caption = { "readme.data-format", data.default, "" },
tooltip = { "exp-statistics." .. stat_name .. "-tooltip" },
width = 105,
})
end
def.data[data_table] = labels
return data_table
end) --[[ @as any ]]
--- Refresh a data table with the most recent stats for a player
--- @param data_table LuaGuiElement
--- @param player LuaPlayer
function Elements.player_stats_table.refresh(data_table, player)
local labels = Elements.player_stats_table.data[data_table]
-- Update all standalone stats
for _, stat_name in pairs(PlayerStats.metadata.display_order) do
local stat = PlayerStats[stat_name]
local metadata = stat.metadata
local value = stat:get(player, 0)
if metadata.stringify_short then
value = metadata.stringify_short(value)
elseif metadata.stringify then
value = metadata.stringify(value)
else
value = format_number(value, false)
end
labels[stat_name].caption = { "readme.data-format", value, metadata.unit or "" }
end
-- Update all computed stats
for stat_name, data in pairs(computed_stats) do
labels[stat_name].caption = { "readme.data-format", data.calculate(player), "" }
end
end
--- Dropdown which sets the target player
--- @class ExpGui_PlayerStats.elements.player_dropdown: ExpElement
--- @field data table<LuaGuiElement, LuaGuiElement>
--- @overload fun(parent: LuaGuiElement, camera: LuaGuiElement): LuaGuiElement
Elements.player_dropdown = Gui.define("player_stats/player_dropdown")
:track_all_elements()
:draw(function(def, parent)
return ElementsExtra.online_player_dropdown(parent)
end)
:element_data(
Gui.from_argument(1)
)
:on_selection_state_changed(function(def, player, element, event)
--- @cast def ExpGui_PlayerStats.elements.player_dropdown
local data_table = def.data[element]
local target_player = ElementsExtra.online_player_dropdown.get_selected(element)
Elements.player_stats_table.refresh(data_table, target_player)
end) --[[ @as any ]]
--- Refresh all stats tables associated with a player dropdown
function Elements.player_dropdown.refresh_online()
for _, player_dropdown in Elements.player_dropdown:online_elements() do
local target_player = ElementsExtra.online_player_dropdown.get_selected(player_dropdown)
local data_table = Elements.player_dropdown.data[player_dropdown]
Elements.player_stats_table.refresh(data_table, target_player)
end
end
--- Container added to the left gui flow
Elements.container = Gui.define("player_stats/container")
:draw(function(def, parent)
local container = Gui.elements.container(parent)
local header = Gui.elements.header(container, { caption = { "exp-gui_player-stats.caption-main" } })
local data_table = Elements.player_stats_table(container)
Elements.player_dropdown(header, data_table)
return Gui.elements.container.get_root_element(container)
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_player_stats",
sprite = "item/power-armor-mk2",
tooltip = { "exp-gui_player-stats.tooltip-main" },
left_element = Elements.container,
visible = function(player, element)
return Roles.player_allowed(player, "gui/playerdata")
end
}
return {
elements = Elements,
on_nth_tick = {
[300] = Elements.player_dropdown.refresh_online
}
}

View File

@@ -0,0 +1,261 @@
--[[-- Gui - Production Data
Adds a Gui for displaying item production stats
]]
local Gui = require("modules/exp_gui")
local Roles = require("modules/exp_legacy/expcore/roles")
--- @class ExpGui_ProductionStats.elements
local Elements = {}
--- The flow precision values in the same order as production_precision_dropdown.items
local precision_indexes = {
defines.flow_precision_index.five_seconds,
defines.flow_precision_index.one_minute,
defines.flow_precision_index.ten_minutes,
defines.flow_precision_index.one_hour,
defines.flow_precision_index.ten_hours,
}
--- The font colours used for number labels
local font_color = {
positive = { r = 0.3, g = 1, b = 0.3 },
negative = { r = 1, g = 0.3, b = 0.3 },
}
--- Format a number to include commas and a suffix
local function format_number(amount)
if math.abs(amount) < 0.009 then
return "0.00"
end
local scaler = 1
local suffix = ""
local suffix_list = {
[" G"] = 1e9,
[" M"] = 1e6,
[" k"] = 1e3
}
-- Select which suffix and scaler to use
for _suffix, _scaler in pairs(suffix_list) do
if math.abs(amount) >= _scaler then
scaler = _scaler
suffix = _suffix
break
end
end
local formatted = string.format("%.2f%s", amount / scaler, suffix)
-- Split into integer and fractional parts
local integer_part, fractional_part = formatted:match("^(%-?%d+)%.(%d+)(.*)$")
-- Add commas to integer part
return string.format("%s.%s%s", (integer_part or formatted):reverse():gsub("(%d%d%d)", "%1,"):reverse():gsub("^,", ""):gsub("-,", "-"), fractional_part or "00", suffix)
end
--- Used to select the precision of the production table
Elements.precision_dropdown = Gui.define("production_stats/precision_dropdown")
:draw{
type = "drop-down",
items = { "5s", "1m", "10m", "1h", "10h" },
selected_index = 3,
}
:style{
width = 80,
}
--- Used to select the item to be displayed on a row
--- @class ExpGui_ProductionStats.elements.item_selector: ExpElement
--- @field data table<LuaGuiElement, { on_last_row: boolean, production_table: LuaGuiElement }>
--- @overload fun(parent: LuaGuiElement, production_table: LuaGuiElement): LuaGuiElement
Elements.item_selector = Gui.define("production_stats/item_selector")
:draw{
type = "choose-elem-button",
elem_type = "item",
style = "slot_button",
}
:style{
size = 32,
}
:element_data{
on_last_row = true,
production_table = Gui.from_argument(1),
}
:on_elem_changed(function(def, player, element, event)
--- @cast def ExpGui_ProductionStats.elements.item_selector
local element_data = def.data[element]
if not element.elem_value then
if element_data.on_last_row then
Elements.production_table.reset_row(element_data.production_table, element)
else
Elements.production_table.remove_row(element_data.production_table, element)
end
elseif element_data.on_last_row then
element_data.on_last_row = false
Elements.production_table.add_row(element_data.production_table)
end
end) --[[ @as any ]]
--- Label used for every element in the production table
Elements.table_label = Gui.define("production_stats/table_label")
:draw{
type = "label",
caption = Gui.from_argument(1, "0.00"),
tooltip = Gui.from_argument(2),
style = Gui.from_argument(3),
}
:style{
horizontal_align = "right",
minimal_width = 60,
}
--- @class ExpGui_ProductionStats.elements.production_table.row_elements
--- @field item_selector LuaGuiElement
--- @field production LuaGuiElement
--- @field consumption LuaGuiElement
--- @field net LuaGuiElement
--- @class ExpGui_ProductionStats.elements.production_table.row_data
--- @field production LocalisedString
--- @field consumption LocalisedString
--- @field net LocalisedString
--- @field font_color Color
--- A table that allows selecting items
--- @class ExpGui_ProductionStats.elements.production_table: ExpElement
--- @field data table<LuaGuiElement, { precision_dropdown: LuaGuiElement, rows: ExpGui_ProductionStats.elements.production_table.row_elements[] }>
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.production_table = Gui.define("production_stats/production_table")
:track_all_elements()
:draw(function(def, parent)
local scroll_table = Gui.elements.scroll_table(parent, 304, 4)
local display_alignments = scroll_table.style.column_alignments
for i = 2, 4 do
display_alignments[i] = "right"
end
def.data[scroll_table] = {
precision_dropdown = Elements.precision_dropdown(scroll_table),
rows = {},
}
Elements.table_label(scroll_table, { "gui-production.production" }, { "exp-gui_production-stats.tooltip-per-second" }, "heading_2_label")
Elements.table_label(scroll_table, { "gui-production.consumption" }, { "exp-gui_production-stats.tooltip-per-second" }, "heading_2_label")
Elements.table_label(scroll_table, { "exp-gui_production-stats.caption-net" }, { "exp-gui_production-stats.tooltip-per-second" }, "heading_2_label")
return scroll_table
end) --[[ @as any ]]
--- Calculate the row data for a production table
--- @param force LuaForce
--- @param surface LuaSurface
--- @param item_name string
--- @param precision_index defines.flow_precision_index
--- @return ExpGui_ProductionStats.elements.production_table.row_data
function Elements.production_table.calculate_row_data(force, surface, item_name, precision_index)
local get_flow_count = force.get_item_production_statistics(surface).get_flow_count
local production = math.floor(get_flow_count{ name = item_name, category = "input", precision_index = precision_index, count = false } / 6) / 10
local consumption = math.floor(get_flow_count{ name = item_name, category = "output", precision_index = precision_index, count = false } / 6) / 10
local net = production - consumption
return {
production = format_number(production),
consumption = format_number(consumption),
net = format_number(net),
font_color = net < 0 and font_color.negative or font_color.positive,
}
end
--- A single row of a production table, the parent must be a production table
--- @param production_table LuaGuiElement
function Elements.production_table.add_row(production_table)
local rows = Elements.production_table.data[production_table].rows
local item_selector = Elements.item_selector(production_table, production_table)
rows[item_selector.index] = {
item_selector = item_selector,
production = Elements.table_label(production_table, "0.00"),
consumption = Elements.table_label(production_table, "0.00"),
net = Elements.table_label(production_table, "0.00"),
}
end
--- Remove a row from a production table
--- @param production_table LuaGuiElement
--- @param item_selector LuaGuiElement
function Elements.production_table.remove_row(production_table, item_selector)
local rows = Elements.production_table.data[production_table].rows
local row = rows[item_selector.index]
rows[item_selector.index] = nil
Gui.destroy_if_valid(item_selector)
for _, element in pairs(row) do
Gui.destroy_if_valid(element)
end
end
--- Reset a row in a production table
--- @param production_table LuaGuiElement
--- @param item_selector LuaGuiElement
function Elements.production_table.reset_row(production_table, item_selector)
local rows = Elements.production_table.data[production_table].rows
local row = rows[item_selector.index]
row.production.caption = "0.00"
row.consumption.caption = "0.00"
row.net.caption = "0.00"
row.net.style.font_color = font_color.positive
end
--- Refresh the data on a row
--- @param production_table LuaGuiElement
--- @param item_selector LuaGuiElement
--- @param row_data ExpGui_ProductionStats.elements.production_table.row_data
function Elements.production_table.refresh_row(production_table, item_selector, row_data)
local rows = Elements.production_table.data[production_table].rows
local row = rows[item_selector.index]
row.production.caption = row_data.production
row.consumption.caption = row_data.consumption
row.net.caption = row_data.net
row.net.style.font_color = row_data.font_color
end
--- Refresh all online tables
function Elements.production_table.refresh_online()
for player, production_table in Elements.production_table:online_elements() do
local element_data = Elements.production_table.data[production_table]
local precision_index = precision_indexes[element_data.precision_dropdown.selected_index]
for _, row in pairs(element_data.rows) do
local item_selector = row.item_selector
local item_name = item_selector.elem_value --[[ @as string? ]]
if item_name then
local row_data = Elements.production_table.calculate_row_data(player.force --[[ @as LuaForce ]], player.surface, item_name, precision_index)
Elements.production_table.refresh_row(production_table, item_selector, row_data)
end
end
end
end
--- Container added to the left gui flow
Elements.container = Gui.define("production_stats/container")
:draw(function(def, parent)
local container = Gui.elements.container(parent)
local production_table = Elements.production_table(container)
Elements.production_table.add_row(production_table)
return Gui.elements.container.get_root_element(container)
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_production_stats",
left_element = Elements.container,
sprite = "entity/assembling-machine-3",
tooltip = { "exp-gui_production-stats.tooltip-main" },
visible = function(player, element)
return Roles.player_allowed(player, "gui/production")
end
}
return {
elements = Elements,
on_nth_tick = {
[60] = Elements.production_table.refresh_online,
}
}

View File

@@ -0,0 +1,114 @@
--[[-- Gui - Quick Actions
Adds a few buttons for common actions
]]
local Gui = require("modules/exp_gui")
local Commands = require("modules/exp_commands")
local Roles = require("modules/exp_legacy/expcore/roles")
local addon_artillery = require("modules/exp_scenario/commands/artillery")
local addon_research = require("modules/exp_scenario/commands/research")
local addon_trains = require("modules/exp_scenario/commands/trains")
local addon_teleport = require("modules/exp_scenario/commands/teleport")
local addon_waterfill = require("modules/exp_scenario/commands/waterfill")
--- @class ExpGui_QuickActions.elements
local Elements = {}
--- @type table<string, { command: ExpCommand, element: ExpElement }>
local Actions = {}
--- @param name string
--- @param command ExpCommand | function (this is needed because of the overload on commands)
--- @param on_click? ExpElement.EventHandler<EventData.on_gui_click>
local function new_quick_action(name, command, on_click)
local element = Gui.define("quick_actions/" .. name)
:draw{
type = "button",
caption = { "exp-gui_quick-actions.caption-" .. name },
tooltip = { "exp-gui_quick-actions.tooltip-" .. name },
}
:style{
width = 160,
}
:on_click(on_click or function(def, player, element, event)
command(player)
end)
Elements[name] = element
Actions[name] = {
command = command --[[ @as ExpCommand ]],
element = element,
}
end
new_quick_action("artillery", addon_artillery.commands.artillery)
new_quick_action("trains", addon_trains.commands.set_trains_to_automatic)
new_quick_action("research", addon_research.commands.set_auto_research)
new_quick_action("spawn", addon_teleport.commands.spawn, function(def, player, element, event)
addon_teleport.commands.spawn(player, player)
end)
new_quick_action("waterfill", addon_waterfill.commands.waterfill)
--- Container added to the left gui flow
--- @class ExpGui_QuickActions.elements.container: ExpElement
--- @field data table<LuaGuiElement, { [string]: LuaGuiElement }>
Elements.container = Gui.define("quick_actions/container")
:draw(function(def, parent)
--- @cast def ExpGui_QuickActions.elements.container
local player = Gui.get_player(parent)
local container = Gui.elements.container(parent)
local buttons = {}
for name, action in pairs(Actions) do
local button = action.element(container)
button.visible = Commands.player_has_permission(player, action.command)
buttons[name] = button
end
def.data[container] = buttons
return container.parent
end)
--- Refresh all containers for a player
function Elements.container.refresh_player(player)
local allowed = {}
for name, action in pairs(Actions) do
allowed[name] = Commands.player_has_permission(player, action.command)
end
for _, container in Elements.container:tracked_elements(player) do
local buttons = Elements.container.data[container]
for name, visible in pairs(allowed) do
buttons[name].visible = visible
end
end
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_quick_actions",
left_element = Elements.container,
sprite = "item/repair-pack",
tooltip = { "exp-gui_quick-actions.tooltip-main" },
visible = function(player, element)
return Roles.player_allowed(player, "gui/tool")
end
}
--- @param event { player_index: number }
local function on_role_changed(event)
local player = Gui.get_player(event)
Elements.container.refresh_player(player)
end
return {
elements = Elements,
events = {
[Roles.events.on_role_assigned] = on_role_changed,
[Roles.events.on_role_unassigned] = on_role_changed,
}
}

View File

@@ -0,0 +1,402 @@
--[[-- Gui - Research Milestones
Adds a gui for tracking research milestones
]]
local ExpUtil = require("modules/exp_util")
local Gui = require("modules/exp_gui")
local Roles = require("modules/exp_legacy/expcore/roles")
local config = require("modules/exp_legacy/config/research")
local table_to_json = helpers.table_to_json
local write_file = helpers.write_file
local string_format = string.format
local display_size = 8
--- @class ExpGui_ResearchMilestones.elements
local Elements = {}
local research_time_format = ExpUtil.format_time_factory{ format = "clock", hours = true, minutes = true, seconds = true }
local research_time_format_nil = research_time_format(nil)
local font_color = {
neutral = { r = 1, g = 1, b = 1 },
positive = { r = 0.3, g = 1, b = 0.3 },
negative = { r = 1, g = 0.3, b = 0.3 },
}
--- @class ExpGui_ResearchMilestones.research_targets
--- @field index_lookup table<string, number>
--- @field target_times table<number, { name: string, target: number, label: LocalisedString }>
local research_targets = {
index_lookup = {},
target_times = {},
max_start_index = 0,
length = 0,
}
--- Select the mod set to be used for milestones
for _, mod_name in ipairs(config.mod_set_lookup) do
if script.active_mods[mod_name] then
config.mod_set = mod_name
break
end
end
do --- Calculate the research targets
local research_index = 1
local total_time = 0
for name, time in pairs(config.milestone[config.mod_set]) do
research_targets.index_lookup[name] = research_index
total_time = total_time + time * 60
research_targets.target_times[research_index] = {
name = name,
target = total_time,
label = research_time_format(total_time),
}
research_index = research_index + 1
end
research_targets.length = research_index - 1
research_targets.max_start_index = math.max(1, research_index - display_size)
end
--- Display label for the clock display
--- @class ExpGui_ResearchMilestones.elements.clock_label: ExpElement
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.clock_label = Gui.define("research_milestones/clock_label")
:track_all_elements()
:draw{
type = "label",
caption = research_time_format_nil,
style = "heading_2_label",
} --[[ @as any ]]
--- Update the clock label for all online players
function Elements.clock_label.refresh_online()
local current_time = research_time_format(game.tick)
for _, clock_label in Elements.clock_label:online_elements() do
clock_label.caption = current_time
end
end
--- Label used for all parts of the table
--- @class ExpGui_ResearchMilestones.elements.milestone_table_label: ExpElement
--- @overload fun(parent: LuaGuiElement, caption: LocalisedString?, minimal_width: number?, horizontal_align: string?): LuaGuiElement
Elements.milestone_table_label = Gui.define("research_milestones/table_label")
:draw{
type = "label",
caption = Gui.from_argument(1),
style = "heading_2_label",
}
:style{
minimal_width = Gui.from_argument(2, 70),
horizontal_align = Gui.from_argument(3, "right"),
font_color = font_color.neutral,
} --[[ @as any ]]
--- @class ExpGui_ResearchMilestones.elements.milestone_table.row_elements
--- @field name LuaGuiElement
--- @field target LuaGuiElement
--- @field achieved LuaGuiElement
--- @field difference LuaGuiElement
--- @class ExpGui_ResearchMilestones.elements.milestone_table.row_data
--- @field name LocalisedString
--- @field target LocalisedString
--- @field achieved LocalisedString
--- @field difference LocalisedString
--- @field color Color
--- A table containing all of the current researches and their times / targets
--- @class ExpGui_ResearchMilestones.elements.milestone_table: ExpElement
--- @field data table<LuaGuiElement, ExpGui_ResearchMilestones.elements.milestone_table.row_elements[]>
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.milestone_table = Gui.define("research_milestones/milestone_table")
:track_all_elements()
:draw(function(_, parent)
local milestone_table = Gui.elements.scroll_table(parent, 390, 4)
Elements.milestone_table_label(milestone_table, { "exp-gui_research-milestones.caption-name" }, 180, "left")
Elements.milestone_table_label(milestone_table, { "exp-gui_research-milestones.caption-target" })
Elements.milestone_table_label(milestone_table, { "exp-gui_research-milestones.caption-achieved" })
Elements.milestone_table_label(milestone_table, { "exp-gui_research-milestones.caption-difference" })
return milestone_table
end)
:element_data{} --[[ @as any ]]
do local _row_data = {}
--- @type ExpGui_ResearchMilestones.elements.milestone_table.row_data
local empty_row_data = { color = font_color.positive }
--- Get the row data for a force and research
--- @param force LuaForce
--- @param research_index number
function Elements.milestone_table._clear_row_data_cache(force, research_index)
local row_key = string_format("%s:%s", force.name, research_index)
_row_data[row_key] = nil
end
--- Get the row data for a force and research
--- @param force LuaForce
--- @param research_index number
--- @return ExpGui_ResearchMilestones.elements.milestone_table.row_data
function Elements.milestone_table.calculate_row_data(force, research_index)
local row_key = string_format("%s:%s", force.name, research_index)
return _row_data[row_key] or Elements.milestone_table._calculate_row_data(force, research_index)
end
--- Calculate the row data for a force and research
--- @param force LuaForce
--- @param research_index number
--- @return ExpGui_ResearchMilestones.elements.milestone_table.row_data
function Elements.milestone_table._calculate_row_data(force, research_index)
local row_key = string_format("%s:%s", force.name, research_index)
-- If there is no target entry then return empty row data
local entry = research_targets.target_times[research_index]
if not entry then
_row_data[row_key] = empty_row_data
return empty_row_data
end
-- Otherwise calculate the row data
assert(prototypes.technology[entry.name], "Invalid Research: " .. tostring(entry.name))
local row_data = {} --- @cast row_data ExpGui_ResearchMilestones.elements.milestone_table.row_data
row_data.name = { "exp-gui_research-milestones.caption-research-name", entry.name, prototypes.technology[entry.name].localised_name }
row_data.target = entry.label
local time = Elements.container.get_achieved_time(force, research_index)
if not time then
row_data.achieved = research_time_format_nil
row_data.difference = research_time_format_nil
row_data.color = font_color.neutral
else
row_data.achieved = research_time_format(time)
local diff = time - entry.target
row_data.difference = (diff < 0 and "-" or "+") .. research_time_format(math.abs(diff))
row_data.color = (diff < 0 and font_color.positive) or font_color.negative
end
-- Store it in the cache for faster access next time
_row_data[row_key] = row_data
return row_data
end
end
--- Adds a row to the milestone table
--- @param milestone_table LuaGuiElement
--- @param row_data ExpGui_ResearchMilestones.elements.milestone_table.row_data
function Elements.milestone_table.add_row(milestone_table, row_data)
local rows = Elements.milestone_table.data[milestone_table]
rows[#rows + 1] = {
name = Elements.milestone_table_label(milestone_table, row_data.name, 180, "left"),
target = Elements.milestone_table_label(milestone_table, row_data.target),
achieved = Elements.milestone_table_label(milestone_table, row_data.achieved),
difference = Elements.milestone_table_label(milestone_table, row_data.difference),
}
end
--- Update a row to match the given data
--- @param milestone_table LuaGuiElement
--- @param row_index number
--- @param row_data ExpGui_ResearchMilestones.elements.milestone_table.row_data
function Elements.milestone_table.refresh_row(milestone_table, row_index, row_data)
local row = Elements.milestone_table.data[milestone_table][row_index]
row.name.caption = row_data.name
row.target.caption = row_data.target
row.achieved.caption = row_data.achieved
row.difference.caption = row_data.difference
row.difference.style.font_color = row_data.color
end
--- Update a row to match the given data for all players on a force
--- @param force LuaForce
--- @param row_index number
--- @param row_data ExpGui_ResearchMilestones.elements.milestone_table.row_data
function Elements.milestone_table.refresh_force_online_row(force, row_index, row_data)
for _, milestone_table in Elements.milestone_table:online_elements(force) do
Elements.milestone_table.refresh_row(milestone_table, row_index, row_data)
end
end
--- Refresh all the labels on the table
--- @param milestone_table LuaGuiElement
function Elements.milestone_table.refresh(milestone_table)
local force = Gui.get_player(milestone_table).force --[[ @as LuaForce ]]
local start_index = Elements.container.calculate_starting_research_index(force)
for row_index = 1, display_size do
local row_data = Elements.milestone_table.calculate_row_data(force, start_index + row_index - 1)
Elements.milestone_table.refresh_row(milestone_table, row_index, row_data)
end
end
--- Refresh all tables for a player
function Elements.milestone_table.refresh_player(player)
local force = player.force --[[ @as LuaForce ]]
local start_index = Elements.container.calculate_starting_research_index(force)
for _, milestone_table in Elements.milestone_table:online_elements(player) do
for row_index = 1, display_size do
local row_data = Elements.milestone_table.calculate_row_data(force, start_index + row_index - 1)
Elements.milestone_table.refresh_row(milestone_table, row_index, row_data)
end
end
end
--- Refresh all tables for online players on a force
function Elements.milestone_table.refresh_force_online(force)
local row_data = {}
local start_index = Elements.container.calculate_starting_research_index(force)
for row_index = 1, display_size do
row_data[row_index] = Elements.milestone_table.calculate_row_data(force, start_index + row_index - 1)
end
for _, milestone_table in Elements.milestone_table:online_elements(force) do
for row_index = 1, display_size do
Elements.milestone_table.refresh_row(milestone_table, row_index, row_data[row_index])
end
end
end
--- Container added to the left gui flow
--- @class ExpGui_ResearchMilestones.elements.container: ExpElement
--- @field data table<LuaForce, number[]>
Elements.container = Gui.define("research_milestones/container")
:draw(function(def, parent)
local container = Gui.elements.container(parent)
local header = Gui.elements.header(container, { caption = { "exp-gui_research-milestones.caption-main" } })
local milestone_table = Elements.milestone_table(container)
Elements.clock_label(header)
local force = Gui.get_player(parent).force --[[ @as LuaForce ]]
local start_index = Elements.container.calculate_starting_research_index(force)
for research_index = start_index, start_index + display_size - 1 do
local row_data = Elements.milestone_table.calculate_row_data(force, research_index)
Elements.milestone_table.add_row(milestone_table, row_data)
end
return Gui.elements.container.get_root_element(container)
end)
:force_data{} --[[ @as any ]]
--- Set the achieved time for a force
--- @param force LuaForce
--- @param research_index number
--- @param time number
function Elements.container.set_achieved_time(force, research_index, time)
Elements.milestone_table._clear_row_data_cache(force, research_index)
Elements.container.data[force][research_index] = time
end
--- Get the achieved time for a force
--- @param force LuaForce
--- @param research_index number
--- @return number
function Elements.container.get_achieved_time(force, research_index)
return Elements.container.data[force][research_index]
end
--- Calculate the starting research index for a force
--- @param force LuaForce
--- @return number
function Elements.container.calculate_starting_research_index(force)
local force_data = Elements.container.data[force]
local research_index = research_targets.length
-- # does not work here because it returned the array alloc size
for i = 1, research_targets.length do
if not force_data[i] then
research_index = i
break
end
end
return math.clamp(research_index - 2, 1, research_targets.max_start_index)
end
--- Append all research times to the research log
--- @param force LuaForce
function Elements.container.append_log_line(force)
local result_data = {}
local force_data = Elements.container.data[force]
for name, research_index in pairs(research_targets.index_lookup) do
result_data[name] = force_data[research_index]
end
write_file(config.file_name, table_to_json(result_data) .. "\n", true, 0)
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_research_milestones",
left_element = Elements.container,
sprite = "item/space-science-pack",
tooltip = { "exp-gui_research-milestones.tooltip-main" },
visible = function(player, element)
return Roles.player_allowed(player, "gui/research")
end
}
--- @param event EventData.on_research_finished
local function on_research_finished(event)
local research_name = event.research.name
local research_level = event.research.level
local force = event.research.force
-- Check if the log should be updated and print a message to chat
if config.inf_res[config.mod_set][research_name] then
local log_requirement = config.bonus_inventory.log[config.mod_set]
if research_name == log_requirement.name and research_level == log_requirement.level + 1 then
Elements.container.append_log_line(force)
end
if not (event.by_script) then
game.print{ "exp-gui_research-milestones.notice-inf", research_time_format(game.tick), research_name, research_level - 1 }
end
elseif not (event.by_script) then
game.print{ "exp-gui_research-milestones.notice", research_time_format(game.tick), research_name }
end
-- If the research does not have a milestone we don't need to update the gui
local research_index = research_targets.index_lookup[research_name]
if not research_index then
return
end
-- Calculate the various difference indexes
local previous_start_index = Elements.container.calculate_starting_research_index(force)
Elements.container.set_achieved_time(force, research_index, event.tick)
local start_index = Elements.container.calculate_starting_research_index(force)
if start_index == previous_start_index then
-- No change in start index so only need to update one row
local row_index = research_index - start_index + 1
if row_index > 0 and row_index <= 8 then
local row_data = Elements.milestone_table.calculate_row_data(force, research_index)
Elements.milestone_table.refresh_force_online_row(force, row_index, row_data)
end
else
-- Start index changed so we need to refresh the table
Elements.milestone_table.refresh_force_online(force)
end
end
--- Force a refresh of the research table when a player joins or changes force
--- @param event EventData.on_player_joined_game | EventData.on_player_changed_force
local function refresh_for_player(event)
Elements.milestone_table.refresh_player(Gui.get_player(event))
end
local e = defines.events
return {
elements = Elements,
events = {
[e.on_research_finished] = on_research_finished,
[e.on_player_joined_game] = refresh_for_player,
[e.on_player_changed_force] = refresh_for_player,
},
on_nth_tick = {
[60] = Elements.clock_label.refresh_online,
}
}

View File

@@ -0,0 +1,589 @@
--[[-- Gui - Science Info
Adds a science info gui that shows production usage and net for the different science packs as well as an eta
]]
local ExpUtil = require("modules/exp_util")
local Gui = require("modules/exp_gui")
local Colors = require("modules/exp_util/include/color")
local Roles = require("modules/exp_legacy/expcore/roles")
local config = require("modules/exp_legacy/config/gui/science")
local _format_number = require("util").format_number
local clock_time_format = ExpUtil.format_time_factory_locale{ format = "clock", hours = true, minutes = true, seconds = true }
local long_time_format = ExpUtil.format_time_factory_locale{ format = "long", hours = true, minutes = true, seconds = true }
local clock_time_format_nil = { "exp-gui_science-production.caption-eta-time", clock_time_format(nil) }
local long_time_format_nil = long_time_format(nil)
--- Remove invalid science packs, this can result from a certain mod not being loaded
for i = #config, 1, -1 do
if not prototypes.item[config[i]] then
table.remove(config, i)
end
end
--- Returns the two parts used to format a number
--- @param value number
--- @return string, string
local function format_number(value)
local rtn = _format_number(math.round(value, 1), true)
local suffix = rtn:sub(-1)
if value > 0 then
rtn = "+" .. rtn
elseif value == 0 and rtn:sub(1, 1) == "-" then
rtn = rtn:sub(2)
end
if not tonumber(suffix) then
return suffix, rtn:sub(1, -2)
else
return "", rtn
end
end
--- @class ExpGui_ScienceProduction.elements
local Elements = {}
--- A pair of labels representing production of an idea
--- @class ExpGui_ScienceProduction.elements.production_label: ExpElement
--- @field data table<LuaGuiElement, LuaGuiElement>
--- @overload fun(parent: LuaGuiElement, production_label_strings: Elements.production_label.display_data): LuaGuiElement
Elements.production_label = Gui.define("science_production/production_label")
:draw(function(def, parent, production_label_strings)
--- @cast def ExpGui_ScienceProduction.elements.production_label
--- @cast production_label_strings Elements.production_label.display_data
-- Add the main value label
local label = parent.add{
type = "label",
caption = production_label_strings.caption,
tooltip = production_label_strings.tooltip,
}
local style = label.style
style.font_color = production_label_strings.color
style.horizontal_align = "right"
style.minimal_width = 40
-- Add the suffix label, this is intentionally being added to the parent
local suffix = parent.add{
type = "label",
caption = { "exp-gui_science-production.caption-spm", production_label_strings.suffix },
tooltip = production_label_strings.tooltip,
}
local suffix_style = suffix.style
suffix_style.font_color = production_label_strings.color
suffix_style.right_margin = 1
def.data[label] = suffix
return label
end) --[[ @as any ]]
--- @class Elements.production_label.display_data
--- @field caption LocalisedString
--- @field suffix LocalisedString
--- @field tooltip LocalisedString
--- @field color Color
--- Get the data that is used with the production label
--- @param tooltip LocalisedString
--- @param value number
--- @param cutoff number
--- @param passive_value number?
--- @param display_data Elements.production_label.display_data?
--- @return Elements.production_label.display_data
function Elements.production_label.calculate_display_data(tooltip, value, cutoff, passive_value, display_data)
local color = Colors.grey
if value > cutoff then
color = Colors.light_green
elseif value < -cutoff then
color = Colors.indian_red
elseif value ~= 0 then
color = Colors.orange
elseif passive_value and passive_value > 0 then
color = Colors.orange
elseif passive_value and passive_value < 0 then
color = Colors.indian_red
end
local suffix, caption = format_number(value)
display_data = display_data or {}
display_data.caption = caption
display_data.suffix = suffix
display_data.tooltip = tooltip
display_data.color = color
return display_data
end
--- Refresh a production label with the given production labels
--- @param production_label LuaGuiElement
--- @param display_data Elements.production_label.display_data
function Elements.production_label.refresh(production_label, display_data)
production_label.caption = display_data.caption
production_label.tooltip = display_data.tooltip
production_label.style.font_color = display_data.color
local suffix = Elements.production_label.data[production_label]
suffix.caption = { "exp-gui_science-production.caption-spm", display_data.suffix }
suffix.tooltip = display_data.tooltip
suffix.style.font_color = display_data.color
end
--- Label used to signal that no packs have been produced by the force
--- @class ExpGui_ScienceProduction.elements.no_production_label: ExpElement
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.no_production_label = Gui.define("science_production/no_production_label")
:track_all_elements()
:draw{
type = "label",
caption = { "exp-gui_science-production.caption-no-production" },
}
:style{
padding = { 2, 4 },
single_line = false,
width = 200,
} --[[ @as any ]]
--- Refresh a no production label
--- @param no_production_label LuaGuiElement
function Elements.no_production_label.refresh(no_production_label)
local force = Gui.get_player(no_production_label).force --[[ @as LuaForce ]]
no_production_label.visible = not Elements.container.has_production(force)
end
--- Refresh the no production label for all online players
function Elements.no_production_label.refresh_online()
local force_data = {}
for player, no_production_label in Elements.no_production_label:online_elements() do
local force = player.force --[[ @as LuaForce ]]
local visible = force_data[force.name]
if visible == nil then
visible = not Elements.container.has_production(force)
force_data[player.force.name] = visible
end
no_production_label.visible = visible
end
end
--- @class ExpGui_ScienceProduction.elements.science_table.row_elements
--- @field delta_flow LuaGuiElement
--- @field net_suffix LuaGuiElement
--- @field net LuaGuiElement
--- @field made LuaGuiElement
--- @field used LuaGuiElement
--- @field icon LuaGuiElement
--- @class ExpGui_ScienceProduction.elements.science_table.row_data
--- @field visible boolean
--- @field science_pack string
--- @field icon_style string
--- @field made Elements.production_label.display_data
--- @field used Elements.production_label.display_data
--- @field net Elements.production_label.display_data
--- A table containing all of the current science packs
--- @class ExpGui_ScienceProduction.elements.science_table: ExpElement
--- @field data table<LuaGuiElement, { [string]: ExpGui_ScienceProduction.elements.science_table.row_elements }>
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.science_table = Gui.define("science_production/science_table")
:track_all_elements()
:draw(function(_, parent)
local science_table = Gui.elements.scroll_table(parent, 190, 4)
local no_production_label = Elements.no_production_label(science_table)
Elements.no_production_label.refresh(no_production_label)
science_table.style.column_alignments[3] = "right"
return science_table
end)
:element_data{} --[[ @as any ]]
--- Calculate the data needed to add or refresh a row
--- @param force LuaForce
--- @param science_pack string
--- @param row_data ExpGui_ScienceProduction.elements.science_table.row_data?
--- @return ExpGui_ScienceProduction.elements.science_table.row_data
function Elements.science_table.calculate_row_data(force, science_pack, row_data)
local production = Elements.container.get_production_data(force)[science_pack]
local total, one_hour = production.total, production.one_hour
local one_minute, ten_minutes = production.one_minute, production.ten_minutes
-- Get the icon style
local icon_style = "slot_button"
local flux = (one_minute.net / ten_minutes.net) - 1
if one_minute.net > 0 and flux > -config.color_flux / 2 then
icon_style = "slot_sized_button_green"
elseif flux < -config.color_flux then
icon_style = "slot_sized_button_red"
elseif one_minute.made > 0 then
icon_style = "yellow_slot_button"
end
-- Return the pack data
row_data = row_data or {}
row_data.visible = production.total.made > 0
row_data.science_pack = science_pack
row_data.icon_style = icon_style
row_data.made = Elements.production_label.calculate_display_data(
{ "exp-gui_science-production.tooltip-made", total.made },
one_minute.made, one_hour.made,
nil, row_data.made
)
row_data.used = Elements.production_label.calculate_display_data(
{ "exp-gui_science-production.tooltip-used", total.used },
-one_minute.used, one_hour.used,
nil, row_data.used
)
row_data.net = Elements.production_label.calculate_display_data(
{ "exp-gui_science-production.tooltip-net", total.net },
one_minute.net, one_minute.net > 0 and one_hour.net or 0,
one_minute.made + one_minute.used, row_data.net
)
return row_data
end
--- Add a new row to the table
--- @param science_table LuaGuiElement
--- @param row_data ExpGui_ScienceProduction.elements.science_table.row_data
function Elements.science_table.add_row(science_table, row_data)
if Elements.science_table.data[science_table][row_data.science_pack] then
error("Cannot add multiple rows of the same type to the table")
end
-- Draw the icon for the science pack
local visible = row_data.visible
local icon_style = row_data.icon_style
local pack_icon = science_table.add{
type = "sprite-button",
sprite = "item/" .. row_data.science_pack,
tooltip = { "item-name." .. row_data.science_pack },
style = icon_style,
visible = visible,
}
-- Change the style of the icon
local pack_icon_style = pack_icon.style
pack_icon.ignored_by_interaction = true
pack_icon_style.height = 55
-- Draw the delta flow
local delta_flow = science_table.add{
type = "frame",
style = "bordered_frame",
visible = visible,
}
delta_flow.style.padding = { 0, 3 }
-- Draw the delta flow table
local delta_table = delta_flow.add{
type = "table",
column_count = 2,
}
delta_table.style.padding = 0
delta_table.style.column_alignments[1] = "right"
-- Draw the net production label
local net = Elements.production_label(science_table, row_data.net)
local net_suffix = Elements.production_label.data[net]
net_suffix.visible = visible
net.visible = visible
-- Draw the other two production labels
Elements.science_table.data[science_table][row_data.science_pack] = {
made = Elements.production_label(delta_table, row_data.made),
used = Elements.production_label(delta_table, row_data.used),
delta_flow = delta_flow,
net_suffix = net_suffix,
icon = pack_icon,
net = net,
}
end
--- Refresh a row on a table
--- @param science_table LuaGuiElement
--- @param row_data ExpGui_ScienceProduction.elements.science_table.row_data
function Elements.science_table.refresh_row(science_table, row_data)
if not row_data.visible then
return -- Rows start as not visible, then once visible they remain always visible
end
local row = assert(Elements.science_table.data[science_table][row_data.science_pack])
-- Update the icon
local icon = row.icon
icon.style = row_data.icon_style
icon.style.height = 55
-- Update the element visibility
row.net_suffix.visible = true
row.delta_flow.visible = true
row.net.visible = true
icon.visible = true
-- Update the production labels
Elements.production_label.refresh(row.net, row_data.net)
Elements.production_label.refresh(row.made, row_data.made)
Elements.production_label.refresh(row.used, row_data.used)
end
--- @type table<string, { [string]: ExpGui_ScienceProduction.elements.science_table.row_data }>
do local _row_data = {}
--- Refresh the production tables for all online players
function Elements.science_table.refresh_online()
-- Refresh the row data for online forces
for _, force in pairs(game.forces) do
if next(force.connected_players) then
local row_data = _row_data[force.name] or {}
_row_data[force.name] = row_data
for i, science_pack in ipairs(config) do
--- @cast science_pack any
row_data[i] = Elements.science_table.calculate_row_data(force, science_pack, row_data[i])
end
end
end
-- Update the tables
for player, science_table in Elements.science_table:online_elements() do
for _, row_data in ipairs(_row_data[player.force.name]) do
Elements.science_table.refresh_row(science_table, row_data)
end
end
end
end
--- Displays the eta until research completion
--- @class ExpGui_ScienceProduction.elements.eta_label: ExpElement
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.eta_label = Gui.define("science_production/eta_label")
:track_all_elements()
:draw{
type = "label",
caption = clock_time_format_nil,
tooltip = long_time_format_nil,
style = "frame_title",
} --[[ @as any ]]
--- @class Elements.eta_label.display_data
--- @field caption LocalisedString
--- @field tooltip LocalisedString
--- Avoid creating new tables for nil time
--- @type Elements.eta_label.display_data
local _nil_eta_strings = {
caption = clock_time_format_nil,
tooltip = long_time_format_nil,
}
--- Calculate the eta time for a force to complete a research
--- @param force LuaForce
--- @return Elements.eta_label.display_data
function Elements.eta_label.calculate_display_data(force)
-- If there is no current research then return no research
local research = force.current_research
if not research then
return _nil_eta_strings
end
local limit = 0
local progress = force.research_progress
local remaining = research.research_unit_count * (1 - progress)
-- Check for the limiting science pack
local force_data = Elements.container.get_production_data(force)
for _, ingredient in pairs(research.research_unit_ingredients) do
local pack_name = ingredient.name
local required = ingredient.amount * remaining
local production = force_data[pack_name].one_minute
local time = production.used == 0 and -1 or 3600 * required / production.used
if limit < time then
limit = time
end
end
-- Return the caption and tooltip
return limit == 0 and _nil_eta_strings or {
caption = { "exp-gui_science-production.caption-eta-time", clock_time_format(limit) },
tooltip = long_time_format(limit),
}
end
--- Refresh an eta label
--- @param eta_label LuaGuiElement
function Elements.eta_label.refresh(eta_label)
local force = Gui.get_player(eta_label).force --[[ @as LuaForce ]]
local display_data = Elements.eta_label.calculate_display_data(force)
eta_label.caption = display_data.caption
eta_label.tooltip = display_data.tooltip
end
--- @type Elements.eta_label.display_data
do local _display_data = {}
--- Refresh the eta label for all online players
function Elements.eta_label.refresh_online()
-- Refresh the row data for online forces
for _, force in pairs(game.forces) do
if next(force.connected_players) then
_display_data[force.name] = Elements.eta_label.calculate_display_data(force)
end
end
-- Update the eta labels
for player, eta_label in Elements.eta_label:online_elements() do
local display_data = _display_data[player.force.name]
eta_label.caption = display_data.caption
eta_label.tooltip = display_data.tooltip
end
end
end
--- Container added to the left gui flow
--- @class ExpGui_ScienceProduction.elements.container: ExpElement
Elements.container = Gui.define("science_production/container")
:draw(function(def, parent)
local container = Gui.elements.container(parent)
Gui.elements.header(container, { caption = { "exp-gui_science-production.caption-main" } })
local force = Gui.get_player(parent).force --[[ @as LuaForce ]]
local science_table = Elements.science_table(container)
for _, science_pack in ipairs(config) do
--- @cast science_pack any
local row_data = Elements.science_table.calculate_row_data(force, science_pack)
Elements.science_table.add_row(science_table, row_data)
end
if config.show_eta then
local footer = Gui.elements.footer(container, {
caption = { "exp-gui_science-production.caption-eta" },
tooltip = { "exp-gui_science-production.tooltip-eta" },
})
local eta_label = Elements.eta_label(footer)
Elements.eta_label.refresh(eta_label)
end
return Gui.elements.container.get_root_element(container)
end) --[[ @as any ]]
--- Cached mostly because they are long names
local _fp_one_minute = defines.flow_precision_index.one_minute
local _fp_ten_minutes = defines.flow_precision_index.ten_minutes
local _fp_one_hour = defines.flow_precision_index.one_hour
--- @alias ExpGui_ScienceProduction._item_data { made: number, used: number, net: number }
--- @class ExpGui_ScienceProduction.item_production_data
--- @field total ExpGui_ScienceProduction._item_data
--- @field one_minute ExpGui_ScienceProduction._item_data
--- @field ten_minutes ExpGui_ScienceProduction._item_data
--- @field one_hour ExpGui_ScienceProduction._item_data
--- @type table<string, { [string]: ExpGui_ScienceProduction.item_production_data }>
do local _production_data = {}
--- Get the production stats for a force
--- @param flow_stats any
--- @param item_name string
--- @param precision defines.flow_precision_index
--- @return ExpGui_ScienceProduction._item_data
local function get_production(flow_stats, item_name, precision)
local made, used = 0, 0
for _, get_flow_count in pairs(flow_stats) do
made = made + get_flow_count{ name = item_name, category = "input", precision_index = precision }
used = used + get_flow_count{ name = item_name, category = "output", precision_index = precision }
end
return { made = made, used = used, net = made - used }
end
--- Get the production data for a force
--- @param force LuaForce
--- @return { [string]: ExpGui_ScienceProduction.item_production_data }
function Elements.container.get_production_data(force)
return _production_data[force.name] or Elements.container.calculate_production_data(force)
end
--- Calculate the production data for a force
--- @param force LuaForce
--- @return { [string]: ExpGui_ScienceProduction.item_production_data }
function Elements.container.calculate_production_data(force)
-- Setup the force data
local force_data = _production_data[force.name] or {}
_production_data[force.name] = force_data
-- Cache the various stats calls for the force
local flow_stats = {}
local production_stats = {}
local get_stats = force.get_item_production_statistics
for name, surface in pairs(game.surfaces) do
local stats = get_stats(surface)
flow_stats[name] = stats.get_flow_count
production_stats[name] = stats
end
-- Calculate the production data for each science pack
for _, science_pack in ipairs(config) do
--- @cast science_pack any
local made, used = 0, 0
for _, stats in pairs(production_stats) do
made = made + stats.get_input_count(science_pack)
used = used + stats.get_output_count(science_pack)
end
local item_data = force_data[science_pack] or {}
force_data[science_pack] = item_data
item_data.total = { made = made, used = used, net = made - used }
item_data.one_minute = get_production(flow_stats, science_pack, _fp_one_minute)
item_data.ten_minutes = get_production(flow_stats, science_pack, _fp_ten_minutes)
item_data.one_hour = get_production(flow_stats, science_pack, _fp_one_hour)
end
return force_data
end
end
--- Returns true if any science packs have been produced by a force
--- @param force LuaForce
--- @return boolean
function Elements.container.has_production(force)
local production_data = Elements.container.get_production_data(force)
for _, data in pairs(production_data) do
if data.total.made > 0 then
return true
end
end
return false
end
--- Refresh the production data for all online forces, must be called before any other refresh
function Elements.container.refresh_online()
for _, force in pairs(game.forces) do
if next(force.connected_players) then
Elements.container.calculate_production_data(force)
end
end
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_science_info",
left_element = Elements.container,
sprite = "entity/lab",
tooltip = { "exp-gui_science-production.tooltip-main" },
visible = function(player, element)
return Roles.player_allowed(player, "gui/science-info")
end
}
--- Updates the gui every 1 second
local function update_gui()
Elements.container.refresh_online()
Elements.eta_label.refresh_online()
Elements.science_table.refresh_online()
Elements.no_production_label.refresh_online()
end
return {
elements = Elements,
on_nth_tick = {
[60] = update_gui,
}
}

View File

@@ -0,0 +1,253 @@
--[[-- Gui - Surveillance
Adds cameras which can be used to view players and locations
]]
local Gui = require("modules/exp_gui")
local ElementsExtra = require("modules/exp_scenario/gui/elements")
local Roles = require("modules/exp_legacy/expcore/roles")
--- @class ExpGui_Surveillance.elements
local Elements = {}
--- Dropdown which sets the target of a camera to a player
--- @class ExpGui_Surveillance.elements.player_dropdown: ExpElement
--- @field data table<LuaGuiElement, LuaGuiElement>
--- @overload fun(parent: LuaGuiElement, camera: LuaGuiElement): LuaGuiElement
Elements.player_dropdown = Gui.define("surveillance/player_dropdown")
:draw(function(def, parent)
return ElementsExtra.online_player_dropdown(parent)
end)
:element_data(
Gui.from_argument(1)
)
:on_selection_state_changed(function(def, player, element, event)
--- @cast def ExpGui_Surveillance.elements.player_dropdown
local camera = def.data[element]
local target_player = assert(ElementsExtra.online_player_dropdown.get_selected(element))
Elements.camera.set_target_player(camera, target_player)
end) --[[ @as any ]]
--- Button which sets the target of a camera to the current location
--- @class ExpGui_Surveillance.elements.set_location_button: ExpElement
--- @field data table<LuaGuiElement, LuaGuiElement>
--- @overload fun(parent: LuaGuiElement, camera: LuaGuiElement): LuaGuiElement
Elements.set_location_button = Gui.define("surveillance/set_location_button")
:draw{
type = "button",
caption = { "exp-gui_surveillance.caption-set-location" },
visible = false,
}
:style{
width = 48,
height = 24,
}
:element_data(
Gui.from_argument(1)
)
:on_click(function(def, player, element)
--- @cast def ExpGui_Surveillance.elements.set_location_button
local camera = def.data[element]
Elements.camera.set_target_position(camera, player.physical_surface_index, player.physical_position)
end) --[[ @as any ]]
--- @class ExpGui_Surveillance.elements.type_dropdown.data
--- @field player_dropdown LuaGuiElement
--- @field location_button LuaGuiElement
--- @field camera LuaGuiElement
--- Selects the type of camera to display, actually just controls the visible buttons
--- @class ExpGui_Surveillance.elements.type_dropdown: ExpElement
--- @field data table<LuaGuiElement, ExpGui_Surveillance.elements.type_dropdown.data>
--- @overload fun(parent: LuaGuiElement, data: ExpGui_Surveillance.elements.type_dropdown.data): LuaGuiElement
Elements.type_dropdown = Gui.define("surveillance/type_dropdown")
:track_all_elements()
:draw{
type = "drop-down",
items = { { "exp-gui_surveillance.type-player" }, { "exp-gui_surveillance.type-static" }, { "exp-gui_surveillance.type-loop" } },
selected_index = 1,
}
:style{
width = 96,
height = 24,
}
:element_data(
Gui.from_argument(1)
)
:on_selection_state_changed(function(def, player, element, event)
--- @cast def ExpGui_Surveillance.elements.type_dropdown
local element_data = def.data[element]
local selected_index = element.selected_index
element_data.player_dropdown.visible = selected_index == 1
element_data.location_button.visible = selected_index == 2
if selected_index == 2 then
-- Static is selected
Elements.camera.set_target_position(element_data.camera, player.physical_surface_index, player.physical_position)
else
-- Player or loop is selected
local target_player = ElementsExtra.online_player_dropdown.get_selected(element_data.player_dropdown)
Elements.camera.set_target_player(element_data.camera, target_player)
end
end) --[[ @as any ]]
--- Refresh all online type dropdowns by cycling the associated player dropdown
function Elements.type_dropdown.refresh_online()
local player_count = ElementsExtra.online_player_dropdown.get_player_count()
for _, type_dropdown in Elements.type_dropdown:online_elements() do
if type_dropdown.selected_index == 3 then
-- Loop is selected
local element_data = Elements.type_dropdown.data[type_dropdown]
local player_dropdown = element_data.player_dropdown
if player_dropdown.selected_index < player_count then
player_dropdown.selected_index = player_dropdown.selected_index + 1
else
player_dropdown.selected_index = 1
end
local target_player = ElementsExtra.online_player_dropdown.get_selected(player_dropdown)
Elements.camera.set_target_player(element_data.camera, target_player)
end
end
end
--- Buttons which decreases zoom by 5%
--- @class ExpGui_Surveillance.elements.zoom_out_button: ExpElement
--- @field data table<LuaGuiElement, LuaGuiElement>
--- @overload fun(parent: LuaGuiElement, camera: LuaGuiElement): LuaGuiElement
Elements.zoom_out_button = Gui.define("surveillance/zoom_out_button")
:draw{
type = "sprite-button",
sprite = "utility/controller_joycon_back", -- -
style = "frame_action_button",
}
:style{
height = 24,
width = 24,
}
:element_data(
Gui.from_argument(1)
)
:on_click(function(def, player, element)
--- @cast def ExpGui_Surveillance.elements.zoom_out_button
local camera = def.data[element]
if camera.zoom > 0.2 then
camera.zoom = camera.zoom - 0.05
end
end) --[[ @as any ]]
--- Buttons which increases zoom by 5%
--- @class ExpGui_Surveillance.elements.zoom_in_button: ExpElement
--- @field data table<LuaGuiElement, LuaGuiElement>
--- @overload fun(parent: LuaGuiElement, camera: LuaGuiElement): LuaGuiElement
Elements.zoom_in_button = Gui.define("surveillance/zoom_in_button")
:draw{
type = "sprite-button",
sprite = "utility/controller_joycon_start", -- +
style = "frame_action_button",
}
:style{
height = 24,
width = 24,
}
:element_data(
Gui.from_argument(1)
)
:on_click(function(def, player, element)
--- @cast def ExpGui_Surveillance.elements.zoom_in_button
local camera = def.data[element]
if camera.zoom < 2.0 then
camera.zoom = camera.zoom + 0.05
end
end) --[[ @as any ]]
--- Camera which tracks a target with a physical_position and surface_index
--- @class ExpGui_Surveillance.elements.camera: ExpElement
--- @field data table<LuaGuiElement, LuaPlayer?>
--- @overload fun(parent: LuaGuiElement, target: LuaPlayer?): LuaGuiElement
Elements.camera = Gui.define("surveillance/camera")
:track_all_elements()
:draw{
type = "camera",
position = { x = 0, y = 0 },
surface_index = 1,
zoom = 0.75,
}
:style{
width = 480,
height = 290,
}
:element_data(
Gui.from_argument(1)
) --[[ @as any ]]
--- Set the target player for the camera
--- @param camera LuaGuiElement
--- @param player LuaPlayer
function Elements.camera.set_target_player(camera, player)
Elements.camera.data[camera] = player
end
--- Set the target position for the camera
--- @param camera LuaGuiElement
--- @param surface_index number
--- @param position MapPosition
function Elements.camera.set_target_position(camera, surface_index, position)
Elements.camera.data[camera] = nil
camera.surface_index = surface_index
camera.position = position
end
--- Refresh the position for all cameras targeting a player
function Elements.camera.refresh_online()
for _, camera in Elements.camera:online_elements() do
local target_player = Elements.camera.data[camera]
if target_player then
camera.position = target_player.physical_position
camera.surface_index = target_player.physical_surface_index
end
end
end
--- Container added to the screen
Elements.container = Gui.define("surveillance/container")
:draw(function(def, parent)
local screen_frame = Gui.elements.screen_frame(parent, nil, true)
local button_flow = Gui.elements.screen_frame.get_button_flow(screen_frame)
local target_player = Gui.get_player(parent)
local camera = Elements.camera(screen_frame, target_player)
local type_dropdown_data = {
camera = camera,
player_dropdown = Elements.player_dropdown(button_flow, camera),
location_button = Elements.set_location_button(button_flow, camera),
}
Elements.type_dropdown(button_flow, type_dropdown_data)
Elements.zoom_out_button(button_flow, camera)
Elements.zoom_in_button(button_flow, camera)
return Gui.elements.screen_frame.get_root_element(screen_frame)
end)
--- Add a button to create the container
Gui.toolbar.create_button{
name = "open_surveillance",
sprite = "entity/radar",
tooltip = { "exp-gui_surveillance.tooltip-main" },
visible = function(player, element)
return Roles.player_allowed(player, "gui/surveillance")
end
}:on_click(function(def, player, element, event)
Elements.container(player.gui.screen)
end)
local e = defines.events
return {
elements = Elements,
events = {
[e.on_tick] = Elements.camera.refresh_online,
},
on_nth_tick = {
[600] = Elements.type_dropdown.refresh_online,
}
}

View File

@@ -281,3 +281,97 @@ area-too-large=Selected area is too large, must be less than __1__ tiles, select
too-few-explosives=Requires __1__ __ITEM__cliff-explosives__ or its ingredients, you have __2__.
part-complete=__1__ tiles were filled with water, but entities are blocking __2__ tiles.
complete=__1__ tiles were filled with water.
[exp-gui_autofill]
tooltip-main=Autofill
caption-main=Autofill
caption-section-header=__1__ __2__
tooltip-toggle-section=Toggle section
tooltip-toggle-section-expand=Expand section
tooltip-toggle-section-collapse=Collapse section
tooltip-toggle-entity=Toggle the autofill of __1__
tooltip-toggle-item=Toggle the autofill of __1__ into __2__ slots
tooltip-amount=Amount of items to insert into the __1__ slots
invalid=Autofill set to maximum amount: __1__ __2__ for __3__
inserted=Inserted __1__ __2__ into __3__
[exp-gui_landfill-blueprint]
tooltip-main=Landfill Blueprint
error-no-blueprint=You need to hold the blueprint in cursor
[exp-gui_module-inserter]
tooltip-main=Module Inserter
caption-main=Modules
tooltip-apply=Apply
[exp-gui_player-bonus]
tooltip-main=Player Bonus
caption-main=Bonus
tooltip-reset=Reset sliders
tooltip-apply=Apply bonus
caption-character_mining_speed_modifier=Mining
tooltip-character_mining_speed_modifier=Character manual mining speed
caption-character_running_speed_modifier=Running
tooltip-character_running_speed_modifier=Character running speed
caption-character_crafting_speed_modifier=Crafting
tooltip-character_crafting_speed_modifier=Character crafting speed
caption-character_inventory_slots_bonus=Inventory
tooltip-character_inventory_slots_bonus=Character inventory slots bonus
caption-character_health_bonus=Health
tooltip-character_health_bonus=Character health bonus
caption-character_reach_distance_bonus=Reach
tooltip-character_reach_distance_bonus=Character reach distance bonus
caption-personal_battery_recharge=Battery
tooltip-personal_battery_recharge=Armor battery recharge
[exp-gui_player-stats]
tooltip-main=Player Stats
caption-main=Player Stats
[exp-gui_production-stats]
tooltip-main=Production Stats
tooltip-per-second=Items per second
caption-net=Net
[exp-gui_quick-actions]
tooltip-main=Quick Actions
caption-artillery=Artillery
tooltip-artillery=Select artillery targets
caption-research=Auto Research
tooltip-research=Toggle auto research queue
caption-spawn=Teleport Spawn
tooltip-spawn-tooltip=Teleport to spawn
caption-trains=Set Auto Train
tooltip-trains=Set all trains to automatic
caption-waterfill=Waterfill
tooltip-waterfill=Change tiles to water
[exp-gui_research-milestones]
tooltip-main=Research Milestones
caption-main=Milestones
caption-name=Name
caption-target=Target
caption-achieved=Achieved
caption-difference=Difference
caption-research-name=[technology=__1__] __2__
notice-inf=[color=255, 255, 255] Research completed at __1__ - [technology=__2__] - __3__[/color]
notice=[color=255, 255, 255] Research completed at __1__ - [technology=__2__][/color]
[exp-gui_science-production]
tooltip-main=Science Production
caption-main=Science
caption-spm=__1__ spm
caption-eta=ETA:
caption-eta-time=T- __1__
tooltip-eta=The estimated time left for the current research
caption-no-production=You have not made any science packs yet
tooltip-made=Total made: __1__
tooltip-used=Total used: __1__
tooltip-net=Total net: __1__
[exp-gui_surveillance]
tooltip-main=Surveillance
caption-set-location=Set
type-player=Player
type-static=Static
type-loop=Loop

View File

@@ -281,3 +281,97 @@ area-too-large=區域太大了,需少過 __1__ 格,你選了 __2__ 格。
too-few-explosives=需要 __1__ 個 __ITEM__cliff-explosives__ 或其材料,你現在有 __2__ 個。
part-complete=__1__ 格已填水,但有 __2__ 格有東西擋著。
complete=__1__ 格已經轉換好。
[exp-gui_autofill]
tooltip-main=自動填入設定
caption-main=自動填入設定
caption-section-header=__1__ __2__
tooltip-toggle-section=Toggle section
tooltip-toggle-section-expand=擴張欄
tooltip-toggle-section-collapse=收縮欄
tooltip-toggle-entity=自動填入設定 - __1__
tooltip-toggle-item=自動填入設定 - __2__ 的 __1__
tooltip-amount=自動填入 __1__ 的數量
invalid=自動填入最大值 __1__ __2__ 給 __3__
inserted=自動填入 __1__ __2__ 到 __3__
[exp-gui_landfill-blueprint]
tooltip-main=藍圖填海
error-no-blueprint=您需要將藍圖保持在遊標處
[exp-gui_module-inserter]
tooltip-main=模組
caption-main=Modules
tooltip-apply=套用
[exp-gui_player-bonus]
tooltip-main=Bonus 介面
caption-main=Bonus
tooltip-reset=重置
tooltip-apply=應用
caption-character_mining_speed_modifier=挖掘速度
tooltip-character_mining_speed_modifier=個人挖掘速度
caption-character_running_speed_modifier=跑步速度
tooltip-character_running_speed_modifier=個人跑步速度
caption-character_crafting_speed_modifier=合成速度
tooltip-character_crafting_speed_modifier=個人合成速度
caption-character_inventory_slots_bonus=儲存位
tooltip-character_inventory_slots_bonus=個人儲存位
caption-character_health_bonus=生命
tooltip-character_health_bonus=個人生命
caption-character_reach_distance_bonus=到達距離
tooltip-character_reach_distance_bonus=個人到達距離
caption-personal_battery_recharge=電池充電
tooltip-personal_battery_recharge=為玩家電池充電
[exp-gui_player-stats]
tooltip-main=Player Stats
caption-main=Player Stats
[exp-gui_production-stats]
tooltip-main=製造
tooltip-per-second=物品每秒
caption-net=淨值
[exp-gui_quick-actions]
tooltip-main=工具
caption-artillery=火炮遙控
tooltip-artillery=火炮遙控
caption-research=研究
tooltip-research=啟用自動研究
caption-spawn=傳送出生
tooltip-spawn-tooltip=傳送到出生點
caption-trains=火車
tooltip-trains=把火車設置為自動模式
caption-waterfill=挖水
tooltip-waterfill=把地換為水。
[exp-gui_research-milestones]
tooltip-main=研究介面
caption-main=研究介面
caption-name=名稱
caption-target=目標
caption-achieved=用時
caption-difference=差距
caption-research-name=[technology=__1__] __2__
notice-inf=[color=255, 255, 255] 研究完成在 __1__ - [technology=__2__] - __3__[/color]
notice=[color=255, 255, 255] 研究完成在 __1__ - [technology=__2__][/color]
[exp-gui_science-production]
tooltip-main=研究資訊
caption-main=研究瓶
caption-spm=__1__ 瓶每分鐘
caption-eta=預計時間:
caption-eta-time=T- __1__
tooltip-eta=餘下研究所需時間
caption-no-production=你未製造任何研究瓶
tooltip-made=製造: __1__
tooltip-used=使用: __1__
tooltip-net=淨: __1__
[exp-gui_surveillance]
tooltip-main=監控
caption-set-location=
type-player=用戶
type-static=靜態
type-loop=循環

View File

@@ -281,3 +281,97 @@ area-too-large=區域太大了,需少過 __1__ 格,你選了 __2__ 格。
too-few-explosives=需要 __1__ 個 __ITEM__cliff-explosives__ 或其材料,你現在有 __2__ 個。
part-complete=__1__ 格已填水,但有 __2__ 格有東西擋著。
complete=__1__ 格已經轉換好。
[exp-gui_autofill]
tooltip-main=自動填入設定
caption-main=自動填入設定
caption-section-header=__1__ __2__
tooltip-toggle-section=Toggle section
tooltip-toggle-section-expand=擴張欄
tooltip-toggle-section-collapse=收縮欄
tooltip-toggle-entity=自動填入設定 - __1__
tooltip-toggle-item=自動填入設定 - __2__ 的 __1__
tooltip-amount=自動填入 __1__ 的數量
invalid=自動填入最大值 __1__ __2__ 給 __3__
inserted=自動填入 __1__ __2__ 到 __3__
[exp-gui_landfill-blueprint]
tooltip-main=藍圖填海
error-no-blueprint=您需要將藍圖保持在遊標處
[exp-gui_module-inserter]
tooltip-main=模組
caption-main=Modules
tooltip-apply=套用
[exp-gui_player-bonus]
tooltip-main=Bonus 介面
caption-main=Bonus
tooltip-reset=重置
tooltip-apply=應用
caption-character_mining_speed_modifier=挖掘速度
tooltip-character_mining_speed_modifier=個人挖掘速度
caption-character_running_speed_modifier=跑步速度
tooltip-character_running_speed_modifier=個人跑步速度
caption-character_crafting_speed_modifier=合成速度
tooltip-character_crafting_speed_modifier=個人合成速度
caption-character_inventory_slots_bonus=儲存位
tooltip-character_inventory_slots_bonus=個人儲存位
caption-character_health_bonus=生命
tooltip-character_health_bonus=個人生命
caption-character_reach_distance_bonus=到達距離
tooltip-character_reach_distance_bonus=個人到達距離
caption-personal_battery_recharge=電池充電
tooltip-personal_battery_recharge=為玩家電池充電
[exp-gui_player-stats]
tooltip-main=Player Stats
caption-main=Player Stats
[exp-gui_production-stats]
tooltip-main=製造
tooltip-per-second=物品每秒
caption-net=淨值
[exp-gui_quick-actions]
tooltip-main=工具
caption-artillery=火炮遙控
tooltip-artillery=火炮遙控
caption-research=研究
tooltip-research=啟用自動研究
caption-spawn=傳送出生
tooltip-spawn-tooltip=傳送到出生點
caption-trains=火車
tooltip-trains=把火車設置為自動模式
caption-waterfill=挖水
tooltip-waterfill=把地換為水。
[exp-gui_research-milestones]
tooltip-main=研究介面
caption-main=研究介面
caption-name=名稱
caption-target=目標
caption-achieved=用時
caption-difference=差距
caption-research-name=[technology=__1__] __2__
notice-inf=[color=255, 255, 255] 研究完成在 __1__ - [technology=__2__] - __3__[/color]
notice=[color=255, 255, 255] 研究完成在 __1__ - [technology=__2__][/color]
[exp-gui_science-production]
tooltip-main=研究資訊
caption-main=研究瓶
caption-spm=__1__ 瓶每分鐘
caption-eta=預計時間:
caption-eta-time=T- __1__
tooltip-eta=餘下研究所需時間
caption-no-production=你未製造任何研究瓶
tooltip-made=製造: __1__
tooltip-used=使用: __1__
tooltip-net=淨: __1__
[exp-gui_surveillance]
tooltip-main=監控
caption-set-location=
type-player=用戶
type-static=靜態
type-loop=循環

View File

@@ -8,6 +8,7 @@
"dependencies": {
"clusterio": "*",
"exp_util": "*",
"exp_gui": "*",
"exp_commands": "*"
}
}

View File

@@ -43,7 +43,7 @@ export class InstancePlugin extends BaseInstancePlugin {
}
try {
const newGameTime = await this.sendRcon(`/_rcon return exp_server_ups.update(${ups})`);
const newGameTime = await this.sendRcon(`/_rcon return exp_server_ups.refresh(${ups})`);
this.gameTimes.push(Number(newGameTime));
} catch (error: any) {
this.logger.error(`Failed to receive new game time: ${error}`);

View File

@@ -6,32 +6,8 @@ local Gui = require("modules/exp_gui")
local ExpUtil = require("modules/exp_util")
local Commands = require("modules/exp_commands")
--- Label to show the server ups, drawn to screen on join
local server_ups = Gui.element("server_ups")
:track_all_elements()
:draw{
type = "label",
name = Gui.property_from_name,
}
:style{
font = "default-game",
}
:player_data(function(def, element)
local player = Gui.get_player(element)
local existing = def.data[player]
if not existing or not existing.valid then
return element -- Only set if no previous
end
end)
--- Update the caption for all online players
--- @param ups number The UPS to be displayed
local function update_server_ups(ups)
local caption = ("%.1f (%.1f%%)"):format(ups, ups * 5 / 3)
for _, element in server_ups:online_elements() do
element.caption = caption
end
end
--- @class ExpServerUps.elements
local Elements = {}
--- Stores the visible state of server ups element for a player
local PlayerData = require("modules/exp_legacy/expcore/player_data")
@@ -45,35 +21,74 @@ UsesServerUps:set_metadata{
--- Change the visible state when your data loads
UsesServerUps:on_load(function(player_name, visible)
local player = assert(game.get_player(player_name))
server_ups.data[player].visible = visible or false
Elements.server_ups.set_visible(player, visible or false)
end)
--- Label to show the server ups, drawn to screen on join
--- @class ExpServerUps.elements.server_ups: ExpElement
--- @overload fun(parent: LuaGuiElement, visible: boolean?): LuaGuiElement
Elements.server_ups = Gui.define("server_ups")
:track_all_elements()
:draw{
type = "label",
visible = Gui.from_argument(1),
}
:style{
font = "default-game",
}
:player_data(function(def, element)
local player = Gui.get_player(element)
local existing = def.data[player]
if not existing or not existing.valid then
def.data[player] = element -- Only set if previous is invalid
end
end) --[[ @as any ]]
--- Refresh the caption for all online players
--- @param ups number The UPS to be displayed
function Elements.server_ups.refresh_online(ups)
local caption = ("%.1f (%.1f%%)"):format(ups, ups * 5 / 3)
for _, server_ups in Elements.server_ups:online_elements() do
server_ups.caption = caption
end
end
--- Get the main label for a player
--- @param player LuaPlayer
--- @return LuaGuiElement
function Elements.server_ups.get_main_label(player)
return Elements.server_ups.data[player] or Elements.server_ups(player.gui.screen, UsesServerUps:get(player))
end
--- Set the visible state of the main label
--- @param player LuaPlayer
--- @param visible boolean
function Elements.server_ups.set_visible(player, visible)
Elements.server_ups.get_main_label(player).visible = visible
end
--- Toggles if the server ups is visbile
Commands.new("server-ups", { "exp_server-ups.description" })
:add_aliases{ "sups", "ups" }
:register(function(player)
local visible = not UsesServerUps:get(player)
server_ups.data[player].visible = visible
Elements.server_ups.set_visible(player, visible)
UsesServerUps:set(player, visible)
end)
--- Add an interface which can be called from rcon
Commands.add_rcon_static("exp_server_ups", {
update = function(ups)
refresh = function(ups)
ExpUtil.assert_argument_type(ups, "number", 1, "ups")
update_server_ups(ups)
Elements.server_ups.refresh_online(ups)
return game.tick
end
})
--- Set the location of the label
local function set_location(event)
local player = game.players[event.player_index]
local element = server_ups.data[player]
if not element then
element = server_ups(player.gui.screen)
element.visible = UsesServerUps:get(player)
end
local player = Gui.get_player(event)
local element = Elements.server_ups.get_main_label(player)
local uis = player.display_scale
local res = player.display_resolution
@@ -83,9 +98,7 @@ end
local e = defines.events
return {
elements = {
server_ups = server_ups,
},
elements = Elements,
events = {
[e.on_player_created] = set_location,
[e.on_player_joined_game] = set_location,

View File

@@ -0,0 +1,71 @@
--[[-- ExpUtil - Flow Precision
Simple lookup tables for working with flow precisions
]]
local fp = defines.flow_precision_index
--- @class ExpUtil_FlowPrecision
return {
--- The defines index
index = fp,
--- The number of ticks represented by a precision index
--- @type table<defines.flow_precision_index, number>
ticks = {
[fp.five_seconds] = 300,
[fp.one_minute] = 3600,
[fp.ten_minutes] = 36000,
[fp.one_hour] = 216000,
[fp.ten_hours] = 2160000,
[fp.fifty_hours] = 10800000,
[fp.two_hundred_fifty_hours] = 54000000,
[fp.one_thousand_hours] = 216000000,
},
--- The next larger interval precision index
--- @type table<defines.flow_precision_index, defines.flow_precision_index>
next = {
[fp.five_seconds] = fp.one_minute,
[fp.one_minute] = fp.ten_minutes,
[fp.ten_minutes] = fp.one_hour,
[fp.one_hour] = fp.ten_hours,
[fp.ten_hours] = fp.fifty_hours,
[fp.fifty_hours] = fp.two_hundred_fifty_hours,
[fp.two_hundred_fifty_hours] = fp.one_thousand_hours,
[fp.one_thousand_hours] = fp.one_thousand_hours,
},
--- The previous smaller interval precision index
--- @type table<defines.flow_precision_index, defines.flow_precision_index>
prev = {
[fp.five_seconds] = fp.five_seconds,
[fp.one_minute] = fp.five_seconds,
[fp.ten_minutes] = fp.one_minute,
[fp.one_hour] = fp.ten_minutes,
[fp.ten_hours] = fp.one_hour,
[fp.fifty_hours] = fp.ten_hours,
[fp.two_hundred_fifty_hours] = fp.fifty_hours,
[fp.one_thousand_hours] = fp.two_hundred_fifty_hours,
},
--- The multiplicative increase to the next larger interval
--- @type table<defines.flow_precision_index, number>
next_change = {
[fp.five_seconds] = 60,
[fp.one_minute] = 10,
[fp.ten_minutes] = 6,
[fp.one_hour] = 10,
[fp.ten_hours] = 5,
[fp.fifty_hours] = 5,
[fp.two_hundred_fifty_hours] = 4,
[fp.one_thousand_hours] = 1,
},
--- The multiplicative decrease to the previous smaller interval
--- @type table<defines.flow_precision_index, number>
prev_change = {
[fp.five_seconds] = 1,
[fp.one_minute] = 60,
[fp.ten_minutes] = 10,
[fp.one_hour] = 6,
[fp.ten_hours] = 10,
[fp.fifty_hours] = 5,
[fp.two_hundred_fifty_hours] = 5,
[fp.one_thousand_hours] = 4,
},
}