mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-07-26 18:36:23 +09:00
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:
co-authored by
Claude Opus 4.8
parent
1fcf351566
commit
0ead1a51f9
@@ -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 ExpUtil = require("modules/exp_util")
|
||||||
local Storage = require("modules/exp_util/storage")
|
|
||||||
local Gui = require("modules/exp_gui")
|
local Gui = require("modules/exp_gui")
|
||||||
local Roles = require("modules/exp_legacy/expcore/roles")
|
local Roles = require("modules/exp_legacy/expcore/roles")
|
||||||
local config = require("modules/exp_legacy/config/gui/rockets")
|
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 },
|
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
|
-- The largest rolling average is used to know when an old launch time can be discarded
|
||||||
local largest_rolling_avg = 0
|
local largest_rolling_avg = 0
|
||||||
for _, avg_over in pairs(config.stats.rolling_avg) do
|
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
|
||||||
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
|
The per force rocket data is stored on the container element data using the force scope, this means it
|
||||||
--- @return ExpGui_RocketInfo.silo_data[]
|
persists with the rest of the gui data and is cleaned up when forces are merged. It holds the launch
|
||||||
local function get_silos(force_name)
|
stats, the tick each rocket was launched on, and the silos that belong to the force.
|
||||||
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
|
|
||||||
|
|
||||||
return rtn
|
--- @class ExpGui_RocketInfo.silo_data
|
||||||
end
|
--- @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
|
--- @class ExpGui_RocketInfo.force_data
|
||||||
--- @param force_name string Name of the force to get the count for
|
--- @field stats { first_launch: number?, last_launch: number?, fastest_launch: number? } Launch stats for the force
|
||||||
--- @return number
|
--- @field times table<number, number> Launch tick indexed by rocket number
|
||||||
local function get_rocket_count(force_name)
|
--- @field silos table<number, ExpGui_RocketInfo.silo_data> Silo data indexed by unit number
|
||||||
return game.forces[force_name].rockets_launched
|
|
||||||
|
--- 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
|
end
|
||||||
|
|
||||||
--- Get the total number of rockets launched by all forces
|
--- Get the total number of rockets launched by all forces
|
||||||
@@ -97,14 +73,14 @@ local function get_game_rocket_count()
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Get the rolling average time to launch a rocket based on the last count rockets
|
--- 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
|
--- @param count number Number of rockets to average over
|
||||||
--- @return number # Number of ticks required to launch one rocket
|
--- @return number # Number of ticks required to launch one rocket
|
||||||
local function get_rolling_average(force_name, count)
|
local function get_rolling_average(force, count)
|
||||||
local times = rocket_times[force_name]
|
local rocket_count = force.rockets_launched
|
||||||
local rocket_count = game.forces[force_name].rockets_launched
|
if rocket_count == 0 then return 0 end
|
||||||
if rocket_count == 0 or not times then return 0 end
|
|
||||||
|
|
||||||
|
local times = get_force_data(force).times
|
||||||
local last_launch_time = times[rocket_count] or 0
|
local last_launch_time = times[rocket_count] or 0
|
||||||
local start_rocket_time = 0
|
local start_rocket_time = 0
|
||||||
if count < rocket_count then
|
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)
|
return math.floor((last_launch_time - start_rocket_time) / rocket_count)
|
||||||
end
|
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.
|
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
|
--- A clickable coordinate label which opens the silo location on the map when pressed
|
||||||
--- @class ExpGui_RocketInfo.elements.position_label: ExpElement
|
--- @class ExpGui_RocketInfo.elements.position_label: ExpElement
|
||||||
--- @field data table<LuaGuiElement, number>
|
--- @field data table<LuaGuiElement, LuaEntity>
|
||||||
--- @overload fun(parent: LuaGuiElement, opts: { caption: LocalisedString, tooltip: LocalisedString?, unit_number: number }): LuaGuiElement
|
--- @overload fun(parent: LuaGuiElement, opts: { caption: LocalisedString, tooltip: LocalisedString?, entity: LuaEntity }): LuaGuiElement
|
||||||
Elements.position_label = Gui.define("rocket_info/position_label")
|
Elements.position_label = Gui.define("rocket_info/position_label")
|
||||||
:draw{
|
:draw{
|
||||||
type = "label",
|
type = "label",
|
||||||
@@ -163,189 +252,244 @@ Elements.position_label = Gui.define("rocket_info/position_label")
|
|||||||
padding = { 0, 2 },
|
padding = { 0, 2 },
|
||||||
}
|
}
|
||||||
:element_data(
|
:element_data(
|
||||||
Gui.from_argument("unit_number")
|
Gui.from_argument("entity")
|
||||||
)
|
)
|
||||||
:on_click(function(def, player, element)
|
:on_click(function(def, player, element)
|
||||||
--- @cast def ExpGui_RocketInfo.elements.position_label
|
--- @cast def ExpGui_RocketInfo.elements.position_label
|
||||||
if not config.progress.allow_zoom_to_map then return end
|
if not config.progress.allow_zoom_to_map then return end
|
||||||
local silo_data = rocket_silos[def.data[element]]
|
local entity = def.data[element]
|
||||||
if not silo_data or not silo_data.entity.valid then return end
|
if not entity or not entity.valid then return end
|
||||||
local entity = silo_data.entity
|
|
||||||
player.set_controller{ type = defines.controllers.remote, position = entity.position, surface = entity.surface }
|
player.set_controller{ type = defines.controllers.remote, position = entity.position, surface = entity.surface }
|
||||||
end) --[[ @as any ]]
|
end) --[[ @as any ]]
|
||||||
|
|
||||||
--- Add a name and value label pair to a data table
|
--- Data table showing the launch statistics for a force
|
||||||
--- @param data_table LuaGuiElement The two column table to add the pair to
|
--- @class ExpGui_RocketInfo.elements.stats_table: ExpElement
|
||||||
--- @param name string The data name, used to select the locale key
|
--- @field data table<LuaGuiElement, table<string, LuaGuiElement>>
|
||||||
--- @param subname number? Optional subname passed as a parameter to the locale string
|
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
|
||||||
--- @param value LocalisedString The value to display
|
Elements.stats_table = Gui.define("rocket_info/stats_table")
|
||||||
--- @param value_tooltip LocalisedString? Optional tooltip for the value label
|
:track_all_elements()
|
||||||
local function add_data_label(data_table, name, subname, value, value_tooltip)
|
: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{
|
local name_label = data_table.add{
|
||||||
type = "label",
|
type = "label",
|
||||||
caption = { "exp-gui_rocket-info.data-caption-" .. name, subname },
|
caption = { "exp-gui_rocket-info.data-caption-" .. row.name, row.subname },
|
||||||
tooltip = { "exp-gui_rocket-info.data-tooltip-" .. name, subname },
|
tooltip = { "exp-gui_rocket-info.data-tooltip-" .. row.name, row.subname },
|
||||||
}
|
}
|
||||||
name_label.style.padding = { 0, 2 }
|
name_label.style.padding = { 0, 2 }
|
||||||
|
|
||||||
local value_label = data_table.add{
|
local value_label = data_table.add{
|
||||||
type = "label",
|
type = "label",
|
||||||
caption = value,
|
caption = row.value,
|
||||||
tooltip = value_tooltip,
|
tooltip = row.tooltip,
|
||||||
}
|
}
|
||||||
value_label.style.padding = { 0, 2 }
|
value_label.style.padding = { 0, 2 }
|
||||||
|
|
||||||
|
labels[row.key] = value_label
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Data table showing the launch statistics for a force
|
--- Refresh the stats table for a force, adding any rows that do not yet exist
|
||||||
--- @class ExpGui_RocketInfo.elements.stats_table: ExpElement
|
--- @param data_table LuaGuiElement
|
||||||
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
|
--- @param force LuaForce
|
||||||
Elements.stats_table = Gui.define("rocket_info/stats_table")
|
function Elements.stats_table.refresh(data_table, force)
|
||||||
:track_all_elements()
|
local labels = Elements.stats_table.data[data_table]
|
||||||
:draw(function(def, parent)
|
for _, row in ipairs(compute_stat_rows(force)) do
|
||||||
return Gui.elements.scroll_table(parent, 215, 2)
|
local value_label = labels[row.key]
|
||||||
end) --[[ @as any ]]
|
if value_label then
|
||||||
|
value_label.caption = row.value
|
||||||
--- Refresh a stats table with the most recent data for a force
|
value_label.tooltip = row.tooltip
|
||||||
--- @param stats_table LuaGuiElement
|
else
|
||||||
--- @param force_name string
|
Elements.stats_table.add_row(data_table, row)
|
||||||
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
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Data table showing the milestones for a force
|
--- Data table showing the milestones for a force
|
||||||
--- @class ExpGui_RocketInfo.elements.milestones_table: ExpElement
|
--- @class ExpGui_RocketInfo.elements.milestones_table: ExpElement
|
||||||
|
--- @field data table<LuaGuiElement, table<number, LuaGuiElement>>
|
||||||
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
|
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
|
||||||
Elements.milestones_table = Gui.define("rocket_info/milestones_table")
|
Elements.milestones_table = Gui.define("rocket_info/milestones_table")
|
||||||
:track_all_elements()
|
:track_all_elements()
|
||||||
:draw(function(def, parent)
|
: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 ]]
|
end) --[[ @as any ]]
|
||||||
|
|
||||||
--- Refresh a milestones table with the most recent data for a force
|
--- Add a milestone row to the table and store its value label
|
||||||
--- @param milestones_table LuaGuiElement
|
--- @param data_table LuaGuiElement
|
||||||
--- @param force_name string
|
--- @param row ExpGui_RocketInfo.milestone_row
|
||||||
function Elements.milestones_table.refresh(milestones_table, force_name)
|
function Elements.milestones_table.add_row(data_table, row)
|
||||||
milestones_table.clear()
|
local labels = Elements.milestones_table.data[data_table]
|
||||||
local force_rockets = get_rocket_count(force_name)
|
local name_label = data_table.add{
|
||||||
local times = rocket_times[force_name] or {}
|
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
|
local value_label = data_table.add{
|
||||||
if milestone <= force_rockets then
|
type = "label",
|
||||||
local time = times[milestone] or 0
|
caption = row.value,
|
||||||
add_data_label(milestones_table, "milestone-n", milestone, time_formats.caption_hours(time), time_formats.tooltip_hours(time))
|
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
|
else
|
||||||
-- The first unachieved milestone is shown as the next milestone then we stop
|
Elements.milestones_table.add_row(data_table, row)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
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
|
--- Data table showing the build progress of each silo for a force
|
||||||
--- @class ExpGui_RocketInfo.elements.progress_table: ExpElement
|
--- @class ExpGui_RocketInfo.elements.progress_table: ExpElement
|
||||||
|
--- @field data table<LuaGuiElement, ExpGui_RocketInfo.elements.progress_table.data>
|
||||||
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
|
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
|
||||||
Elements.progress_table = Gui.define("rocket_info/progress_table")
|
Elements.progress_table = Gui.define("rocket_info/progress_table")
|
||||||
:track_all_elements()
|
:track_all_elements()
|
||||||
:draw(function(def, parent)
|
:draw(function(def, parent)
|
||||||
return Gui.elements.scroll_table(parent, 215, 3)
|
--- @cast def ExpGui_RocketInfo.elements.progress_table
|
||||||
end) --[[ @as any ]]
|
local data_table = Gui.elements.scroll_table(parent, 215, 3)
|
||||||
|
|
||||||
--- Refresh a progress table with the most recent data for a force
|
-- The no silos label lives next to the table inside the same collapsible scroll pane
|
||||||
--- @param progress_table LuaGuiElement
|
local no_silos = assert(data_table.parent).add{
|
||||||
--- @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{
|
|
||||||
type = "label",
|
type = "label",
|
||||||
caption = { "exp-gui_rocket-info.progress-no-silos" },
|
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 }
|
no_silos.style.padding = { 1, 2 }
|
||||||
progress_label.style.font_color = progress_color
|
|
||||||
end
|
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
|
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 container LuaGuiElement The container frame to add the section to
|
||||||
--- @param section_name string Used to select the locale keys for the header
|
--- @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 table_define any The data table element define to add
|
||||||
--- @param force_name string The force to populate the table with
|
|
||||||
--- @return LuaGuiElement
|
--- @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, {
|
local header = Gui.elements.header(container, {
|
||||||
caption = { "exp-gui_rocket-info.section-caption-" .. section_name },
|
caption = { "exp-gui_rocket-info.section-caption-" .. section_name },
|
||||||
tooltip = { "exp-gui_rocket-info.section-tooltip-" .. section_name },
|
tooltip = { "exp-gui_rocket-info.section-tooltip-" .. section_name },
|
||||||
})
|
})
|
||||||
|
|
||||||
local data_table = table_define(container)
|
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
|
-- The scroll pane is the parent of the table, it is what gets collapsed
|
||||||
local scroll_pane = assert(data_table.parent)
|
local scroll_pane = assert(data_table.parent)
|
||||||
@@ -357,28 +501,28 @@ end
|
|||||||
|
|
||||||
--- Container added to the left gui flow
|
--- Container added to the left gui flow
|
||||||
--- @class ExpGui_RocketInfo.elements.container: ExpElement
|
--- @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")
|
Elements.container = Gui.define("rocket_info/container")
|
||||||
:draw(function(def, parent)
|
:draw(function(def, parent)
|
||||||
local container = Gui.elements.container(parent, 200)
|
local container = Gui.elements.container(parent, 200)
|
||||||
container.style.padding = 0
|
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
|
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
|
end
|
||||||
|
|
||||||
if config.milestones.show_milestones then
|
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
|
end
|
||||||
|
|
||||||
if config.progress.show_progress then
|
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
|
end
|
||||||
|
|
||||||
return Gui.elements.container.get_root_element(container)
|
return Gui.elements.container.get_root_element(container)
|
||||||
end) --[[ @as any ]]
|
end)
|
||||||
|
|
||||||
--- Add the element to the left flow with a toolbar button
|
--- Add the element to the left flow with a toolbar button
|
||||||
Gui.add_left_element(Elements.container, false)
|
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
|
--- Refresh the stats and milestones tables for all online players on a force
|
||||||
--- @param force_name string
|
--- @param force LuaForce
|
||||||
local function refresh_force_stats(force_name)
|
local function refresh_force_stats(force)
|
||||||
local force = game.forces[force_name]
|
for _, data_table in Elements.stats_table:online_elements(force) do
|
||||||
for _, stats_table in Elements.stats_table:online_elements(force) do
|
Elements.stats_table.refresh(data_table, force)
|
||||||
Elements.stats_table.refresh(stats_table, force_name)
|
|
||||||
end
|
end
|
||||||
for _, milestones_table in Elements.milestones_table:online_elements(force) do
|
for _, data_table in Elements.milestones_table:online_elements(force) do
|
||||||
Elements.milestones_table.refresh(milestones_table, force_name)
|
Elements.milestones_table.refresh(data_table, force)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Refresh the progress table for all online players on a force
|
--- Refresh the progress table for all online players on a force
|
||||||
--- @param force_name string
|
--- @param force LuaForce
|
||||||
local function refresh_force_progress(force_name)
|
local function refresh_force_progress(force)
|
||||||
local force = game.forces[force_name]
|
for _, data_table in Elements.progress_table:online_elements(force) do
|
||||||
for _, progress_table in Elements.progress_table:online_elements(force) do
|
Elements.progress_table.refresh(data_table, force)
|
||||||
Elements.progress_table.refresh(progress_table, force_name)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -421,16 +563,11 @@ end
|
|||||||
--- @param event EventData.on_cargo_pod_finished_ascending
|
--- @param event EventData.on_cargo_pod_finished_ascending
|
||||||
local function on_cargo_pod_finished_ascending(event)
|
local function on_cargo_pod_finished_ascending(event)
|
||||||
local force = event.cargo_pod.force --[[ @as LuaForce ]]
|
local force = event.cargo_pod.force --[[ @as LuaForce ]]
|
||||||
local force_name = force.name
|
|
||||||
local rockets_launched = force.rockets_launched
|
local rockets_launched = force.rockets_launched
|
||||||
|
local force_data = get_force_data(force)
|
||||||
|
|
||||||
-- Update the launch stats for the force
|
-- Update the launch stats for the force
|
||||||
local stats = rocket_stats[force_name]
|
local stats = force_data.stats
|
||||||
if not stats then
|
|
||||||
stats = {}
|
|
||||||
rocket_stats[force_name] = stats
|
|
||||||
end
|
|
||||||
|
|
||||||
if rockets_launched == 1 then
|
if rockets_launched == 1 then
|
||||||
stats.first_launch = event.tick
|
stats.first_launch = event.tick
|
||||||
stats.fastest_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
|
stats.last_launch = event.tick
|
||||||
|
|
||||||
-- Append the launch tick into the times array
|
-- Append the launch tick into the times array
|
||||||
local times = rocket_times[force_name]
|
force_data.times[rockets_launched] = event.tick
|
||||||
if not times then
|
|
||||||
times = {}
|
|
||||||
rocket_times[force_name] = times
|
|
||||||
end
|
|
||||||
|
|
||||||
times[rockets_launched] = event.tick
|
|
||||||
|
|
||||||
-- Discard the launch time that is no longer needed by any rolling average unless it is a milestone
|
-- 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
|
local remove_rocket = rockets_launched - largest_rolling_avg
|
||||||
if remove_rocket > 0 and not table.array_contains(config.milestones, remove_rocket) then
|
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
|
end
|
||||||
|
|
||||||
refresh_force_stats(force_name)
|
refresh_force_stats(force)
|
||||||
refresh_force_progress(force_name)
|
refresh_force_progress(force)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Mark a silo as awaiting reset when a launch is ordered
|
--- Mark a silo as awaiting reset when a launch is ordered
|
||||||
--- @param event EventData.on_rocket_launch_ordered
|
--- @param event EventData.on_rocket_launch_ordered
|
||||||
local function on_rocket_launch_ordered(event)
|
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
|
if not silo_data then return end
|
||||||
silo_data.launched = silo_data.launched + 1
|
silo_data.launched = silo_data.launched + 1
|
||||||
silo_data.awaiting_reset = true
|
silo_data.awaiting_reset = true
|
||||||
refresh_force_progress(event.rocket_silo.force.name)
|
refresh_force_progress(silo.force --[[ @as LuaForce ]])
|
||||||
end
|
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
|
--- @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 function on_built(event)
|
||||||
local entity = event.entity
|
local entity = event.entity
|
||||||
if not entity.valid or entity.name ~= "rocket-silo" then return end
|
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,
|
entity = entity,
|
||||||
force = entity.force.name,
|
|
||||||
launched = 0,
|
launched = 0,
|
||||||
awaiting_reset = false,
|
awaiting_reset = false,
|
||||||
}
|
}
|
||||||
refresh_force_progress(entity.force.name)
|
refresh_force_progress(entity.force --[[ @as LuaForce ]])
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Refresh the progress for all forces that own at least one silo
|
--- Refresh the progress for all forces that own at least one silo
|
||||||
local function refresh_all_progress()
|
local function refresh_all_progress()
|
||||||
for _, force in pairs(game.forces) do
|
for _, force in pairs(game.forces) do
|
||||||
if #get_silos(force.name) > 0 then
|
if next(get_force_data(force).silos) then
|
||||||
refresh_force_progress(force.name)
|
refresh_force_progress(force)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user