diff --git a/exp_scenario/module/gui/rocket_info.lua b/exp_scenario/module/gui/rocket_info.lua index cacf897a..c6be45a4 100644 --- a/exp_scenario/module/gui/rocket_info.lua +++ b/exp_scenario/module/gui/rocket_info.lua @@ -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> Force name to an array of launch ticks indexed by rocket number -local rocket_times = {} ---- @type table Force name to launch stats -local rocket_stats = {} ---- @type table 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 +--[[ +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. +]] + +--- @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 + +--- @class ExpGui_RocketInfo.force_data +--- @field stats { first_launch: number?, last_launch: number?, fastest_launch: number? } Launch stats for the force +--- @field times table Launch tick indexed by rocket number +--- @field silos table 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 rtn -end - ---- 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 + 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 ---- @overload fun(parent: LuaGuiElement, opts: { caption: LocalisedString, tooltip: LocalisedString?, unit_number: number }): LuaGuiElement +--- @field data table +--- @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> +--- @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)) - 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)) +--- 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 end end --- Data table showing the milestones for a force --- @class ExpGui_RocketInfo.elements.milestones_table: ExpElement +--- @field data table> --- @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 +--- @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 --- @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 - end + 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 ---- Add a collapsible section to a container, returns the populated data table +--- 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 + + -- 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 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