mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2025-12-27 03:25:23 +09:00
Merge pull request #18 from PHIDIAS0303/main
Refactor some of the Guis from the legacy plugin (#399)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
203
exp_gui/docs/motivation.md
Normal 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 it’s 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.
|
||||
Don’t 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, it’s 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 don’t need.
|
||||
|
||||

|
||||
457
exp_gui/docs/reference.md
Normal file
457
exp_gui/docs/reference.md
Normal 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 haven’t already, we recommend starting with the [framework guide](../readme.md).
|
||||
It lays the groundwork and gives you the context you’ll 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 it’s created for all players when they join.
|
||||
Once registered, the framework takes ownership of the element’s 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 won’t 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, you’ll 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, it’s 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, there’s 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)
|
||||
```
|
||||
485
exp_gui/docs/reference_full.md
Normal file
485
exp_gui/docs/reference_full.md
Normal file
@@ -0,0 +1,485 @@
|
||||
# ExpGui Full API Reference
|
||||
|
||||
If you haven’t already, please read the [framework guide](../readme.md) and [condensed reference](./reference.md) first, this full reference won’t 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
BIN
exp_gui/docs/toolbox.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 241 KiB |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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[]
|
||||
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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
573
exp_gui/readme.md
Normal 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 you’re 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; they’re 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 you’ve ever felt like writing GUI code was harder than it needed to be, this framework might be what you’re 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, you’ll 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.
|
||||
|
||||

|
||||
|
||||
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 framework’s 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, it’s 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, it’s 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, it’s 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, it’s 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 you’ll need to design your methods to accept custom arguments explicitly.
|
||||
For example, updating a button’s 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, it’s efficient to use the framework’s 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, it’s 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 don’t 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, it’s 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 you’re 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 isn’t possible.
|
||||
In these cases, a `link` method should be used after creation to connect child elements together.
|
||||
It’s 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)
|
||||
```
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=傳送到出生點
|
||||
|
||||
@@ -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=跑步速度
|
||||
@@ -289,34 +245,10 @@ control-type-energy=電力
|
||||
control-type-circuit=回路
|
||||
control-type-storage-input=放入箱
|
||||
control-type-storage-output=提取箱
|
||||
power-on-space-research=你要研究 [technology=__1__] 在 __2__ 級才可使用太空平台 vlayer 功能。
|
||||
power-on-space-research=你要研究 [technology=__1__] 在 __2__ 級 才可使用太空平台 vlayer 功能。
|
||||
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=傳送到出生點
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
|
||||
41
exp_scenario/module/control/bonus.lua
Normal file
41
exp_scenario/module/control/bonus.lua
Normal 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,
|
||||
}
|
||||
}
|
||||
46
exp_scenario/module/control/research.lua
Normal file
46
exp_scenario/module/control/research.lua
Normal 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,
|
||||
}
|
||||
}
|
||||
404
exp_scenario/module/gui/autofill.lua
Normal file
404
exp_scenario/module/gui/autofill.lua
Normal 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,
|
||||
}
|
||||
}
|
||||
83
exp_scenario/module/gui/elements.lua
Normal file
83
exp_scenario/module/gui/elements.lua
Normal 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
|
||||
175
exp_scenario/module/gui/landfill_blueprint.lua
Normal file
175
exp_scenario/module/gui/landfill_blueprint.lua
Normal 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 {}
|
||||
506
exp_scenario/module/gui/module_inserter.lua
Normal file
506
exp_scenario/module/gui/module_inserter.lua
Normal 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,
|
||||
}
|
||||
}
|
||||
527
exp_scenario/module/gui/player_bonus.lua
Normal file
527
exp_scenario/module/gui/player_bonus.lua
Normal 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,
|
||||
}
|
||||
}
|
||||
226
exp_scenario/module/gui/player_stats.lua
Normal file
226
exp_scenario/module/gui/player_stats.lua
Normal 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
|
||||
}
|
||||
}
|
||||
261
exp_scenario/module/gui/production_stats.lua
Normal file
261
exp_scenario/module/gui/production_stats.lua
Normal 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,
|
||||
}
|
||||
}
|
||||
114
exp_scenario/module/gui/quick_actions.lua
Normal file
114
exp_scenario/module/gui/quick_actions.lua
Normal 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,
|
||||
}
|
||||
}
|
||||
402
exp_scenario/module/gui/research_milestones.lua
Normal file
402
exp_scenario/module/gui/research_milestones.lua
Normal 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,
|
||||
}
|
||||
}
|
||||
589
exp_scenario/module/gui/science_production.lua
Normal file
589
exp_scenario/module/gui/science_production.lua
Normal 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,
|
||||
}
|
||||
}
|
||||
253
exp_scenario/module/gui/surveillance.lua
Normal file
253
exp_scenario/module/gui/surveillance.lua
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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=循環
|
||||
|
||||
@@ -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=循環
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"dependencies": {
|
||||
"clusterio": "*",
|
||||
"exp_util": "*",
|
||||
"exp_gui": "*",
|
||||
"exp_commands": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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,
|
||||
|
||||
71
exp_util/module/include/flow_precision.lua
Normal file
71
exp_util/module/include/flow_precision.lua
Normal 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,
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user