Use gui force data and add_row pattern for rocket info

Address review feedback: store the per-force rocket stats, launch times
and silos on the container's force data instead of a separate storage
registration, and rebuild the stats, milestone and progress tables with
add_row/refresh_row helpers that update label references in place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
bbassie
2026-06-18 18:53:23 +00:00
co-authored by Claude Opus 4.8
parent 1fcf351566
commit 0ead1a51f9
+354 -223
View File
@@ -4,7 +4,6 @@ The auto launch and remote launch controls were removed because the api no longe
]]
local ExpUtil = require("modules/exp_util")
local Storage = require("modules/exp_util/storage")
local Gui = require("modules/exp_gui")
local Roles = require("modules/exp_legacy/expcore/roles")
local config = require("modules/exp_legacy/config/gui/rockets")
@@ -26,34 +25,6 @@ local font_color = {
launched = { r = 0.3, g = 1, b = 0.3 },
}
--[[
Below here is the rocket data tracking, it stores per force stats and the times each rocket was launched.
This used to live in modules/control/rockets but it is only used by this gui so it has been folded in.
]]
--- @class ExpGui_RocketInfo.silo_data
--- @field entity LuaEntity The rocket silo entity
--- @field force string The name of the force that owns the silo
--- @field launched number The number of rockets launched from this silo
--- @field awaiting_reset boolean True when a launch is ordered but the silo has not reset yet
--- @type table<string, table<number, number>> Force name to an array of launch ticks indexed by rocket number
local rocket_times = {}
--- @type table<string, { first_launch: number?, last_launch: number?, fastest_launch: number? }> Force name to launch stats
local rocket_stats = {}
--- @type table<number, ExpGui_RocketInfo.silo_data> Silo unit number to its data
local rocket_silos = {}
Storage.register({
rocket_times = rocket_times,
rocket_stats = rocket_stats,
rocket_silos = rocket_silos,
}, function(tbl)
rocket_times = tbl.rocket_times
rocket_stats = tbl.rocket_stats
rocket_silos = tbl.rocket_silos
end)
-- The largest rolling average is used to know when an old launch time can be discarded
local largest_rolling_avg = 0
for _, avg_over in pairs(config.stats.rolling_avg) do
@@ -62,27 +33,32 @@ for _, avg_over in pairs(config.stats.rolling_avg) do
end
end
--- Get all the valid rocket silos that belong to a force, pruning any that are no longer valid
--- @param force_name string Name of the force to get the silos for
--- @return ExpGui_RocketInfo.silo_data[]
local function get_silos(force_name)
local rtn = {}
for unit_number, silo_data in pairs(rocket_silos) do
if not silo_data.entity.valid then
rocket_silos[unit_number] = nil
elseif silo_data.force == force_name then
rtn[#rtn + 1] = silo_data
end
end
--[[
The per force rocket data is stored on the container element data using the force scope, this means it
persists with the rest of the gui data and is cleaned up when forces are merged. It holds the launch
stats, the tick each rocket was launched on, and the silos that belong to the force.
]]
return rtn
end
--- @class ExpGui_RocketInfo.silo_data
--- @field entity LuaEntity The rocket silo entity
--- @field launched number The number of rockets launched from this silo
--- @field awaiting_reset boolean True when a launch is ordered but the silo has not reset yet
--- Get the number of rockets that a force has launched
--- @param force_name string Name of the force to get the count for
--- @return number
local function get_rocket_count(force_name)
return game.forces[force_name].rockets_launched
--- @class ExpGui_RocketInfo.force_data
--- @field stats { first_launch: number?, last_launch: number?, fastest_launch: number? } Launch stats for the force
--- @field times table<number, number> Launch tick indexed by rocket number
--- @field silos table<number, ExpGui_RocketInfo.silo_data> Silo data indexed by unit number
--- Get the rocket data for a force, creating it if it does not exist
--- @param force LuaForce
--- @return ExpGui_RocketInfo.force_data
local function get_force_data(force)
local data = Elements.container.data[force]
if not data then
data = { stats = {}, times = {}, silos = {} }
Elements.container.data[force] = data
end
return data
end
--- Get the total number of rockets launched by all forces
@@ -97,14 +73,14 @@ local function get_game_rocket_count()
end
--- Get the rolling average time to launch a rocket based on the last count rockets
--- @param force_name string Name of the force to get the average for
--- @param force LuaForce
--- @param count number Number of rockets to average over
--- @return number # Number of ticks required to launch one rocket
local function get_rolling_average(force_name, count)
local times = rocket_times[force_name]
local rocket_count = game.forces[force_name].rockets_launched
if rocket_count == 0 or not times then return 0 end
local function get_rolling_average(force, count)
local rocket_count = force.rockets_launched
if rocket_count == 0 then return 0 end
local times = get_force_data(force).times
local last_launch_time = times[rocket_count] or 0
local start_rocket_time = 0
if count < rocket_count then
@@ -115,6 +91,119 @@ local function get_rolling_average(force_name, count)
return math.floor((last_launch_time - start_rocket_time) / rocket_count)
end
--- @class ExpGui_RocketInfo.stat_row
--- @field key string Unique key used to look up the value label
--- @field name string Data name used to select the locale keys
--- @field subname number? Optional subname passed as a locale parameter
--- @field value LocalisedString
--- @field tooltip LocalisedString?
--- Compute the ordered stat rows for a force
--- @param force LuaForce
--- @return ExpGui_RocketInfo.stat_row[]
local function compute_stat_rows(force)
local stats = get_force_data(force).stats
local force_rockets = force.rockets_launched
local rows = {}
if config.stats.show_first_rocket then
local value = stats.first_launch or 0
rows[#rows + 1] = { key = "first-launch", name = "first-launch", value = time_formats.caption_hours(value), tooltip = time_formats.tooltip_hours(value) }
end
if config.stats.show_last_rocket then
local value = stats.last_launch or 0
rows[#rows + 1] = { key = "last-launch", name = "last-launch", value = time_formats.caption_hours(value), tooltip = time_formats.tooltip_hours(value) }
end
if config.stats.show_fastest_rocket then
local value = stats.fastest_launch or 0
rows[#rows + 1] = { key = "fastest-launch", name = "fastest-launch", value = time_formats.caption_hours(value), tooltip = time_formats.tooltip_hours(value) }
end
if config.stats.show_total_rockets then
local total_rockets = get_game_rocket_count()
if total_rockets == 0 then total_rockets = 1 end
local percentage = math.floor(force_rockets / total_rockets * 1000) / 10
rows[#rows + 1] = { key = "total-rockets", name = "total-rockets", value = tostring(force_rockets), tooltip = { "exp-gui_rocket-info.value-tooltip-total-rockets", percentage } }
end
if config.stats.show_game_avg then
local avg = force_rockets > 0 and math.floor(game.tick / force_rockets) or 0
rows[#rows + 1] = { key = "avg-launch", name = "avg-launch", value = time_formats.caption(avg), tooltip = time_formats.tooltip(avg) }
end
for _, avg_over in pairs(config.stats.rolling_avg) do
local avg = get_rolling_average(force, avg_over)
rows[#rows + 1] = { key = "avg-launch-n-" .. avg_over, name = "avg-launch-n", subname = avg_over, value = time_formats.caption(avg), tooltip = time_formats.tooltip(avg) }
end
return rows
end
--- @class ExpGui_RocketInfo.milestone_row
--- @field milestone number The milestone this row represents
--- @field value LocalisedString
--- @field tooltip LocalisedString
--- Compute the ordered milestone rows for a force, up to and including the next unachieved milestone
--- @param force LuaForce
--- @return ExpGui_RocketInfo.milestone_row[]
local function compute_milestone_rows(force)
local times = get_force_data(force).times
local force_rockets = force.rockets_launched
local rows = {}
for _, milestone in ipairs(config.milestones) do
--- @cast milestone number
if milestone <= force_rockets then
local time = times[milestone] or 0
rows[#rows + 1] = { milestone = milestone, value = time_formats.caption_hours(time), tooltip = time_formats.tooltip_hours(time) }
else
rows[#rows + 1] = { milestone = milestone, value = { "exp-gui_rocket-info.data-caption-milestone-next" }, tooltip = { "exp-gui_rocket-info.data-tooltip-milestone-next" } }
break
end
end
return rows
end
--- @class ExpGui_RocketInfo.progress_row
--- @field x LocalisedString
--- @field y LocalisedString
--- @field caption LocalisedString
--- @field tooltip LocalisedString
--- @field color Color
--- Compute the progress row data for a silo, clears awaiting_reset once the silo is no longer waiting
--- @param silo_data ExpGui_RocketInfo.silo_data
--- @return ExpGui_RocketInfo.progress_row
local function compute_progress_row(silo_data)
local entity = silo_data.entity
local position = entity.position
local waiting = entity.status == defines.entity_status.waiting_to_launch_rocket
local caption = { "exp-gui_rocket-info.progress-caption", entity.rocket_parts }
local color = font_color.neutral
if waiting and silo_data.awaiting_reset then
caption = { "exp-gui_rocket-info.progress-launched" }
color = font_color.launched
elseif waiting then
caption = { "exp-gui_rocket-info.progress-caption", 100 }
color = font_color.waiting
else
silo_data.awaiting_reset = false
end
return {
x = { "exp-gui_rocket-info.progress-x-pos", position.x },
y = { "exp-gui_rocket-info.progress-y-pos", position.y },
caption = caption,
tooltip = { "exp-gui_rocket-info.progress-tooltip", silo_data.launched },
color = color,
}
end
--[[
Below here is the gui, it is split into three collapsible sections; stats, milestones, and build progress.
]]
@@ -151,8 +240,8 @@ Elements.toggle_section_button = Gui.define("rocket_info/toggle_section_button")
--- A clickable coordinate label which opens the silo location on the map when pressed
--- @class ExpGui_RocketInfo.elements.position_label: ExpElement
--- @field data table<LuaGuiElement, number>
--- @overload fun(parent: LuaGuiElement, opts: { caption: LocalisedString, tooltip: LocalisedString?, unit_number: number }): LuaGuiElement
--- @field data table<LuaGuiElement, LuaEntity>
--- @overload fun(parent: LuaGuiElement, opts: { caption: LocalisedString, tooltip: LocalisedString?, entity: LuaEntity }): LuaGuiElement
Elements.position_label = Gui.define("rocket_info/position_label")
:draw{
type = "label",
@@ -163,189 +252,244 @@ Elements.position_label = Gui.define("rocket_info/position_label")
padding = { 0, 2 },
}
:element_data(
Gui.from_argument("unit_number")
Gui.from_argument("entity")
)
:on_click(function(def, player, element)
--- @cast def ExpGui_RocketInfo.elements.position_label
if not config.progress.allow_zoom_to_map then return end
local silo_data = rocket_silos[def.data[element]]
if not silo_data or not silo_data.entity.valid then return end
local entity = silo_data.entity
local entity = def.data[element]
if not entity or not entity.valid then return end
player.set_controller{ type = defines.controllers.remote, position = entity.position, surface = entity.surface }
end) --[[ @as any ]]
--- Add a name and value label pair to a data table
--- @param data_table LuaGuiElement The two column table to add the pair to
--- @param name string The data name, used to select the locale key
--- @param subname number? Optional subname passed as a parameter to the locale string
--- @param value LocalisedString The value to display
--- @param value_tooltip LocalisedString? Optional tooltip for the value label
local function add_data_label(data_table, name, subname, value, value_tooltip)
--- Data table showing the launch statistics for a force
--- @class ExpGui_RocketInfo.elements.stats_table: ExpElement
--- @field data table<LuaGuiElement, table<string, LuaGuiElement>>
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.stats_table = Gui.define("rocket_info/stats_table")
:track_all_elements()
:draw(function(def, parent)
--- @cast def ExpGui_RocketInfo.elements.stats_table
local data_table = Gui.elements.scroll_table(parent, 215, 2)
def.data[data_table] = {}
return data_table
end) --[[ @as any ]]
--- Add a stat row to the table and store its value label
--- @param data_table LuaGuiElement
--- @param row ExpGui_RocketInfo.stat_row
function Elements.stats_table.add_row(data_table, row)
local labels = Elements.stats_table.data[data_table]
local name_label = data_table.add{
type = "label",
caption = { "exp-gui_rocket-info.data-caption-" .. name, subname },
tooltip = { "exp-gui_rocket-info.data-tooltip-" .. name, subname },
caption = { "exp-gui_rocket-info.data-caption-" .. row.name, row.subname },
tooltip = { "exp-gui_rocket-info.data-tooltip-" .. row.name, row.subname },
}
name_label.style.padding = { 0, 2 }
local value_label = data_table.add{
type = "label",
caption = value,
tooltip = value_tooltip,
caption = row.value,
tooltip = row.tooltip,
}
value_label.style.padding = { 0, 2 }
labels[row.key] = value_label
end
--- Data table showing the launch statistics for a force
--- @class ExpGui_RocketInfo.elements.stats_table: ExpElement
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.stats_table = Gui.define("rocket_info/stats_table")
:track_all_elements()
:draw(function(def, parent)
return Gui.elements.scroll_table(parent, 215, 2)
end) --[[ @as any ]]
--- Refresh a stats table with the most recent data for a force
--- @param stats_table LuaGuiElement
--- @param force_name string
function Elements.stats_table.refresh(stats_table, force_name)
stats_table.clear()
local stats = rocket_stats[force_name] or {}
local force_rockets = get_rocket_count(force_name)
if config.stats.show_first_rocket then
local value = stats.first_launch or 0
add_data_label(stats_table, "first-launch", nil, time_formats.caption_hours(value), time_formats.tooltip_hours(value))
--- Refresh the stats table for a force, adding any rows that do not yet exist
--- @param data_table LuaGuiElement
--- @param force LuaForce
function Elements.stats_table.refresh(data_table, force)
local labels = Elements.stats_table.data[data_table]
for _, row in ipairs(compute_stat_rows(force)) do
local value_label = labels[row.key]
if value_label then
value_label.caption = row.value
value_label.tooltip = row.tooltip
else
Elements.stats_table.add_row(data_table, row)
end
if config.stats.show_last_rocket then
local value = stats.last_launch or 0
add_data_label(stats_table, "last-launch", nil, time_formats.caption_hours(value), time_formats.tooltip_hours(value))
end
if config.stats.show_fastest_rocket then
local value = stats.fastest_launch or 0
add_data_label(stats_table, "fastest-launch", nil, time_formats.caption_hours(value), time_formats.tooltip_hours(value))
end
if config.stats.show_total_rockets then
local total_rockets = get_game_rocket_count()
if total_rockets == 0 then total_rockets = 1 end
local percentage = math.floor(force_rockets / total_rockets * 1000) / 10
add_data_label(stats_table, "total-rockets", nil, tostring(force_rockets), { "exp-gui_rocket-info.value-tooltip-total-rockets", percentage })
end
if config.stats.show_game_avg then
local avg = force_rockets > 0 and math.floor(game.tick / force_rockets) or 0
add_data_label(stats_table, "avg-launch", nil, time_formats.caption(avg), time_formats.tooltip(avg))
end
for _, avg_over in pairs(config.stats.rolling_avg) do
local avg = get_rolling_average(force_name, avg_over)
add_data_label(stats_table, "avg-launch-n", avg_over, time_formats.caption(avg), time_formats.tooltip(avg))
end
end
--- Data table showing the milestones for a force
--- @class ExpGui_RocketInfo.elements.milestones_table: ExpElement
--- @field data table<LuaGuiElement, table<number, LuaGuiElement>>
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.milestones_table = Gui.define("rocket_info/milestones_table")
:track_all_elements()
:draw(function(def, parent)
return Gui.elements.scroll_table(parent, 215, 2)
--- @cast def ExpGui_RocketInfo.elements.milestones_table
local data_table = Gui.elements.scroll_table(parent, 215, 2)
def.data[data_table] = {}
return data_table
end) --[[ @as any ]]
--- Refresh a milestones table with the most recent data for a force
--- @param milestones_table LuaGuiElement
--- @param force_name string
function Elements.milestones_table.refresh(milestones_table, force_name)
milestones_table.clear()
local force_rockets = get_rocket_count(force_name)
local times = rocket_times[force_name] or {}
--- Add a milestone row to the table and store its value label
--- @param data_table LuaGuiElement
--- @param row ExpGui_RocketInfo.milestone_row
function Elements.milestones_table.add_row(data_table, row)
local labels = Elements.milestones_table.data[data_table]
local name_label = data_table.add{
type = "label",
caption = { "exp-gui_rocket-info.data-caption-milestone-n", row.milestone },
tooltip = { "exp-gui_rocket-info.data-tooltip-milestone-n", row.milestone },
}
name_label.style.padding = { 0, 2 }
for _, milestone in ipairs(config.milestones) do
if milestone <= force_rockets then
local time = times[milestone] or 0
add_data_label(milestones_table, "milestone-n", milestone, time_formats.caption_hours(time), time_formats.tooltip_hours(time))
local value_label = data_table.add{
type = "label",
caption = row.value,
tooltip = row.tooltip,
}
value_label.style.padding = { 0, 2 }
labels[row.milestone] = value_label
end
--- Refresh the milestones table for a force, adding rows as new milestones become visible
--- @param data_table LuaGuiElement
--- @param force LuaForce
function Elements.milestones_table.refresh(data_table, force)
local labels = Elements.milestones_table.data[data_table]
for _, row in ipairs(compute_milestone_rows(force)) do
local value_label = labels[row.milestone]
if value_label then
value_label.caption = row.value
value_label.tooltip = row.tooltip
else
-- The first unachieved milestone is shown as the next milestone then we stop
add_data_label(milestones_table, "milestone-n", milestone, { "exp-gui_rocket-info.data-caption-milestone-next" }, { "exp-gui_rocket-info.data-tooltip-milestone-next" })
break
Elements.milestones_table.add_row(data_table, row)
end
end
end
--- @class ExpGui_RocketInfo.elements.progress_table.row
--- @field x LuaGuiElement
--- @field y LuaGuiElement
--- @field progress LuaGuiElement
--- @class ExpGui_RocketInfo.elements.progress_table.data
--- @field rows table<number, ExpGui_RocketInfo.elements.progress_table.row>
--- @field no_silos LuaGuiElement
--- Data table showing the build progress of each silo for a force
--- @class ExpGui_RocketInfo.elements.progress_table: ExpElement
--- @field data table<LuaGuiElement, ExpGui_RocketInfo.elements.progress_table.data>
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
Elements.progress_table = Gui.define("rocket_info/progress_table")
:track_all_elements()
:draw(function(def, parent)
return Gui.elements.scroll_table(parent, 215, 3)
end) --[[ @as any ]]
--- @cast def ExpGui_RocketInfo.elements.progress_table
local data_table = Gui.elements.scroll_table(parent, 215, 3)
--- Refresh a progress table with the most recent data for a force
--- @param progress_table LuaGuiElement
--- @param force_name string
function Elements.progress_table.refresh(progress_table, force_name)
progress_table.clear()
local silos = get_silos(force_name)
if #silos == 0 then
progress_table.add{
-- The no silos label lives next to the table inside the same collapsible scroll pane
local no_silos = assert(data_table.parent).add{
type = "label",
caption = { "exp-gui_rocket-info.progress-no-silos" },
}.style.padding = { 1, 2 }
return
end
local zoom_tooltip = config.progress.allow_zoom_to_map and { "exp-gui_rocket-info.progress-label-tooltip" } or nil
for _, silo_data in pairs(silos) do
local entity = silo_data.entity
local position = entity.position
-- Work out the progress caption, tooltip and colour
local waiting = entity.status == defines.entity_status.waiting_to_launch_rocket
local progress_caption = { "exp-gui_rocket-info.progress-caption", entity.rocket_parts }
local progress_tooltip = { "exp-gui_rocket-info.progress-tooltip", silo_data.launched }
local progress_color = font_color.neutral
if waiting and silo_data.awaiting_reset then
progress_caption = { "exp-gui_rocket-info.progress-launched" }
progress_color = font_color.launched
elseif waiting then
progress_caption = { "exp-gui_rocket-info.progress-caption", 100 }
progress_color = font_color.waiting
else
silo_data.awaiting_reset = false
end
-- Add the clickable coordinates and the progress label
Elements.position_label(progress_table, { caption = { "exp-gui_rocket-info.progress-x-pos", position.x }, tooltip = zoom_tooltip, unit_number = entity.unit_number })
Elements.position_label(progress_table, { caption = { "exp-gui_rocket-info.progress-y-pos", position.y }, tooltip = zoom_tooltip, unit_number = entity.unit_number })
local progress_label = progress_table.add{
type = "label",
caption = progress_caption,
tooltip = progress_tooltip,
}
progress_label.style.padding = { 0, 2 }
progress_label.style.font_color = progress_color
no_silos.style.padding = { 1, 2 }
def.data[data_table] = { rows = {}, no_silos = no_silos }
return data_table
end) --[[ @as any ]]
--- Add a silo row to the progress table and store its labels
--- @param data_table LuaGuiElement
--- @param silo_data ExpGui_RocketInfo.silo_data
function Elements.progress_table.add_row(data_table, silo_data)
local rows = Elements.progress_table.data[data_table].rows
local entity = silo_data.entity
local row_data = compute_progress_row(silo_data)
local zoom_tooltip = config.progress.allow_zoom_to_map and { "exp-gui_rocket-info.progress-label-tooltip" } or nil
local x = Elements.position_label(data_table, { caption = row_data.x, tooltip = zoom_tooltip, entity = entity })
local y = Elements.position_label(data_table, { caption = row_data.y, tooltip = zoom_tooltip, entity = entity })
local progress = data_table.add{
type = "label",
caption = row_data.caption,
tooltip = row_data.tooltip,
}
progress.style.padding = { 0, 2 }
progress.style.font_color = row_data.color
rows[entity.unit_number] = { x = x, y = y, progress = progress }
end
--- Update an existing silo row to match the latest data
--- @param data_table LuaGuiElement
--- @param silo_data ExpGui_RocketInfo.silo_data
function Elements.progress_table.refresh_row(data_table, silo_data)
local row = Elements.progress_table.data[data_table].rows[silo_data.entity.unit_number]
local row_data = compute_progress_row(silo_data)
row.x.caption = row_data.x
row.y.caption = row_data.y
row.progress.caption = row_data.caption
row.progress.tooltip = row_data.tooltip
row.progress.style.font_color = row_data.color
end
--- Remove a silo row from the progress table
--- @param data_table LuaGuiElement
--- @param unit_number number
function Elements.progress_table.remove_row(data_table, unit_number)
local rows = Elements.progress_table.data[data_table].rows
local row = rows[unit_number]
if not row then return end
rows[unit_number] = nil
Gui.destroy_if_valid(row.x)
Gui.destroy_if_valid(row.y)
Gui.destroy_if_valid(row.progress)
end
--- Refresh the progress table for a force, reconciling rows with the current set of silos
--- @param data_table LuaGuiElement
--- @param force LuaForce
function Elements.progress_table.refresh(data_table, force)
local silos = get_force_data(force).silos
local data = Elements.progress_table.data[data_table]
local rows = data.rows
local seen = {}
local has_silos = false
for unit_number, silo_data in pairs(silos) do
if not silo_data.entity.valid then
-- Prune silos that are no longer valid
silos[unit_number] = nil
else
has_silos = true
seen[unit_number] = true
if rows[unit_number] then
Elements.progress_table.refresh_row(data_table, silo_data)
else
Elements.progress_table.add_row(data_table, silo_data)
end
end
end
--- Add a collapsible section to a container, returns the populated data table
-- Remove rows for silos that are no longer present
for unit_number in pairs(rows) do
if not seen[unit_number] then
Elements.progress_table.remove_row(data_table, unit_number)
end
end
data.no_silos.visible = not has_silos
data_table.visible = has_silos
end
--- Add a collapsible section to a container and return its data table
--- @param container LuaGuiElement The container frame to add the section to
--- @param section_name string Used to select the locale keys for the header
--- @param table_define ExpElement The data table element define to add
--- @param force_name string The force to populate the table with
--- @param table_define any The data table element define to add
--- @return LuaGuiElement
local function add_section(container, section_name, table_define, force_name)
local function add_section(container, section_name, table_define)
local header = Gui.elements.header(container, {
caption = { "exp-gui_rocket-info.section-caption-" .. section_name },
tooltip = { "exp-gui_rocket-info.section-tooltip-" .. section_name },
})
local data_table = table_define(container)
table_define.refresh(data_table, force_name)
-- The scroll pane is the parent of the table, it is what gets collapsed
local scroll_pane = assert(data_table.parent)
@@ -357,28 +501,28 @@ end
--- Container added to the left gui flow
--- @class ExpGui_RocketInfo.elements.container: ExpElement
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
--- @field data table<LuaForce, ExpGui_RocketInfo.force_data>
Elements.container = Gui.define("rocket_info/container")
:draw(function(def, parent)
local container = Gui.elements.container(parent, 200)
container.style.padding = 0
local force_name = Gui.get_player(parent).force.name --[[ @as string ]]
local force = Gui.get_player(parent).force --[[ @as LuaForce ]]
if config.stats.show_stats then
add_section(container, "stats", Elements.stats_table, force_name)
Elements.stats_table.refresh(add_section(container, "stats", Elements.stats_table), force)
end
if config.milestones.show_milestones then
add_section(container, "milestones", Elements.milestones_table, force_name)
Elements.milestones_table.refresh(add_section(container, "milestones", Elements.milestones_table), force)
end
if config.progress.show_progress then
add_section(container, "progress", Elements.progress_table, force_name)
Elements.progress_table.refresh(add_section(container, "progress", Elements.progress_table), force)
end
return Gui.elements.container.get_root_element(container)
end) --[[ @as any ]]
end)
--- Add the element to the left flow with a toolbar button
Gui.add_left_element(Elements.container, false)
@@ -397,23 +541,21 @@ Below here is the event handling, the data tracking and gui refreshing are wired
]]
--- Refresh the stats and milestones tables for all online players on a force
--- @param force_name string
local function refresh_force_stats(force_name)
local force = game.forces[force_name]
for _, stats_table in Elements.stats_table:online_elements(force) do
Elements.stats_table.refresh(stats_table, force_name)
--- @param force LuaForce
local function refresh_force_stats(force)
for _, data_table in Elements.stats_table:online_elements(force) do
Elements.stats_table.refresh(data_table, force)
end
for _, milestones_table in Elements.milestones_table:online_elements(force) do
Elements.milestones_table.refresh(milestones_table, force_name)
for _, data_table in Elements.milestones_table:online_elements(force) do
Elements.milestones_table.refresh(data_table, force)
end
end
--- Refresh the progress table for all online players on a force
--- @param force_name string
local function refresh_force_progress(force_name)
local force = game.forces[force_name]
for _, progress_table in Elements.progress_table:online_elements(force) do
Elements.progress_table.refresh(progress_table, force_name)
--- @param force LuaForce
local function refresh_force_progress(force)
for _, data_table in Elements.progress_table:online_elements(force) do
Elements.progress_table.refresh(data_table, force)
end
end
@@ -421,16 +563,11 @@ end
--- @param event EventData.on_cargo_pod_finished_ascending
local function on_cargo_pod_finished_ascending(event)
local force = event.cargo_pod.force --[[ @as LuaForce ]]
local force_name = force.name
local rockets_launched = force.rockets_launched
local force_data = get_force_data(force)
-- Update the launch stats for the force
local stats = rocket_stats[force_name]
if not stats then
stats = {}
rocket_stats[force_name] = stats
end
local stats = force_data.stats
if rockets_launched == 1 then
stats.first_launch = event.tick
stats.fastest_launch = event.tick
@@ -441,53 +578,47 @@ local function on_cargo_pod_finished_ascending(event)
stats.last_launch = event.tick
-- Append the launch tick into the times array
local times = rocket_times[force_name]
if not times then
times = {}
rocket_times[force_name] = times
end
times[rockets_launched] = event.tick
force_data.times[rockets_launched] = event.tick
-- Discard the launch time that is no longer needed by any rolling average unless it is a milestone
local remove_rocket = rockets_launched - largest_rolling_avg
if remove_rocket > 0 and not table.array_contains(config.milestones, remove_rocket) then
times[remove_rocket] = nil
force_data.times[remove_rocket] = nil
end
refresh_force_stats(force_name)
refresh_force_progress(force_name)
refresh_force_stats(force)
refresh_force_progress(force)
end
--- Mark a silo as awaiting reset when a launch is ordered
--- @param event EventData.on_rocket_launch_ordered
local function on_rocket_launch_ordered(event)
local silo_data = rocket_silos[event.rocket_silo.unit_number]
local silo = event.rocket_silo
local silo_data = get_force_data(silo.force --[[ @as LuaForce ]]).silos[silo.unit_number]
if not silo_data then return end
silo_data.launched = silo_data.launched + 1
silo_data.awaiting_reset = true
refresh_force_progress(event.rocket_silo.force.name)
refresh_force_progress(silo.force --[[ @as LuaForce ]])
end
--- Add a silo to the list when it is built
--- Add a silo to the force data when it is built
--- @param event EventData.on_built_entity | EventData.on_robot_built_entity | EventData.script_raised_built | EventData.script_raised_revive
local function on_built(event)
local entity = event.entity
if not entity.valid or entity.name ~= "rocket-silo" then return end
rocket_silos[entity.unit_number] = {
get_force_data(entity.force --[[ @as LuaForce ]]).silos[entity.unit_number] = {
entity = entity,
force = entity.force.name,
launched = 0,
awaiting_reset = false,
}
refresh_force_progress(entity.force.name)
refresh_force_progress(entity.force --[[ @as LuaForce ]])
end
--- Refresh the progress for all forces that own at least one silo
local function refresh_all_progress()
for _, force in pairs(game.forces) do
if #get_silos(force.name) > 0 then
refresh_force_progress(force.name)
if next(get_force_data(force).silos) then
refresh_force_progress(force)
end
end
end