mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2025-12-27 03:25:23 +09:00
Merge branch 'main' of https://github.com/PHIDIAS0303/ExpCluster
This commit is contained in:
@@ -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": "*"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user