319 lines
15 KiB
Lua
319 lines
15 KiB
Lua
local misc = require("__flib__.misc")
|
|
require("scripts.milestones_util")
|
|
|
|
local empty_set_label = {type="label", caption={"", "[color=100,100,100]", {"milestones.no_visible_milestones"}, "[/color]"}, style="caption_label"}
|
|
|
|
local function get_timestamp(ticks, print_milliseconds)
|
|
if print_milliseconds then
|
|
local remaining_ticks = ticks % 60
|
|
local milliseconds = math.floor((16.66666 * remaining_ticks) + 0.5) -- 16.666666 milliseconds per tick, rounded to int
|
|
return misc.ticks_to_timestring(ticks) .. "." .. string.format("%03d", milliseconds)
|
|
else
|
|
return misc.ticks_to_timestring(ticks)
|
|
end
|
|
end
|
|
|
|
local function add_milestone_label(milestone_flow, milestone, compact_list, show_estimations, print_milliseconds)
|
|
local caption
|
|
local tooltip
|
|
local show_edit_button = false
|
|
if milestone.completion_tick == nil then
|
|
caption = {"", "[color=100,100,100]", {"milestones.incomplete_label"}, "[/color]"}
|
|
else
|
|
local precision_window_in_minutes = 0
|
|
if milestone.lower_bound_tick ~= nil then
|
|
precision_window_in_minutes = math.ceil((milestone.completion_tick - milestone.lower_bound_tick) / 2 / 60 / 60)
|
|
end
|
|
|
|
local label_name
|
|
if compact_list then
|
|
label_name = ""
|
|
elseif milestone.type == "kill" then
|
|
label_name = {"", {"milestones.killed_label"}, " "}
|
|
elseif milestone.type == "technology" then
|
|
label_name = {"", {"milestones.researched_label"}, " "}
|
|
else
|
|
label_name = {"", {"milestones.completed_label"}, " "}
|
|
end
|
|
|
|
if precision_window_in_minutes < 1 or (not show_estimations and precision_window_in_minutes < 60) then -- <1 minute, or doesn't want estimations shown. Just print the normal time.
|
|
caption = {"", label_name, "[font=default-bold]", get_timestamp(milestone.completion_tick, print_milliseconds), "[img=quantity-time][/font]"}
|
|
elseif precision_window_in_minutes < 60 then --<1 hour, print the in-between time then ± X minutes
|
|
tooltip = milestone.type == "technology" and {"milestones.estimation_tooltip_technology"} or {"milestones.estimation_tooltip"}
|
|
local in_between_tick = ceil_to_nearest_minute(milestone.lower_bound_tick - 1 + (milestone.completion_tick - (milestone.lower_bound_tick -1)) / 2)
|
|
caption = {"", label_name, "[font=default-bold]", get_timestamp(in_between_tick, print_milliseconds), "[img=quantity-time][/font] ",
|
|
{"milestones.plus_minus_minutes", precision_window_in_minutes}}
|
|
else -- Big window, print min-max
|
|
tooltip = milestone.type == "technology" and {"milestones.estimation_tooltip_technology"} or {"milestones.estimation_tooltip"}
|
|
local lower_tick = floor_to_nearest_minute(milestone.lower_bound_tick)
|
|
local upper_tick = ceil_to_nearest_minute(milestone.completion_tick)
|
|
caption = "[font=default-bold]" ..get_timestamp(lower_tick, false).. "[img=quantity-time][/font] - " ..
|
|
"[font=default-bold]" ..get_timestamp(upper_tick, false).. "[img=quantity-time][/font]"
|
|
show_edit_button = true
|
|
end
|
|
end
|
|
|
|
milestone_flow.add{type="label", name="milestones_display_time", caption=caption, tooltip=tooltip}
|
|
|
|
-- Optional edit button
|
|
if show_edit_button then
|
|
milestone_flow.add{type="sprite-button", name="milestones_edit_time", sprite="utility/rename_icon_small_white", style="milestones_small_button",
|
|
tooltip={"milestones.edit_time_tooltip"}, tags={action="milestones_edit_time"}}
|
|
end
|
|
end
|
|
|
|
local function add_milestone_item(gui_table, milestone, print_milliseconds, compact_list, show_estimations)
|
|
local milestone_flow = gui_table.add{type="flow", direction="horizontal", style="milestones_horizontal_flow_big_display", tags={index=milestone.sort_index}}
|
|
local prototype = nil
|
|
if milestone.type == "item" or milestone.type == "item_consumption" then
|
|
prototype = game.item_prototypes[milestone.name]
|
|
elseif milestone.type == "fluid" or milestone.type == "fluid_consumption" then
|
|
prototype = game.fluid_prototypes[milestone.name]
|
|
elseif milestone.type == "technology" then
|
|
prototype = game.technology_prototypes[milestone.name]
|
|
elseif milestone.type == "kill" then
|
|
prototype = game.entity_prototypes[milestone.name]
|
|
end
|
|
|
|
if prototype == nil then
|
|
log("Milestones error! Invalid milestone: " .. serpent.line(milestone))
|
|
milestone_flow.add{type="label", caption={"", "[color=red]", {"", {"milestones.invalid_entry"}, " "}, milestone.name, "[/color]"}}
|
|
return
|
|
end
|
|
|
|
-- Sprite
|
|
local sprite_path = sprite_prefix(milestone) .. "/" .. milestone.name
|
|
local sprite_number
|
|
local tooltip = milestone.tooltip -- Milestone tooltip has precedence
|
|
if milestone.quantity > 1 then
|
|
sprite_number = milestone.quantity
|
|
end
|
|
if milestone.type == "technology" then
|
|
local postfix = milestone.quantity == 1 and {"milestones.type_technology"} or "Level "..milestone.quantity
|
|
tooltip = tooltip or {"", prototype.localised_name, " (", postfix, ")"}
|
|
elseif milestone.type == "kill" then
|
|
local prefix = milestone.quantity == 1 and "" or milestone.quantity .."x "
|
|
tooltip = tooltip or {"", prefix, prototype.localised_name, " (", {"milestones.type_kill"}, ")"}
|
|
else
|
|
local prefix = milestone.quantity == 1 and "" or milestone.quantity .."x "
|
|
local postfix = ""
|
|
if milestone.type == "item_consumption" or milestone.type == "fluid_consumption" then
|
|
postfix = {"", " (", {"milestones.type_consumption"}, ")"}
|
|
end
|
|
tooltip = tooltip or {"", prefix, prototype.localised_name, postfix}
|
|
end
|
|
milestone_flow.add{type="sprite-button", sprite=sprite_path, number=sprite_number, tooltip=tooltip, style="transparent_slot"}
|
|
|
|
-- Item name
|
|
add_milestone_label(milestone_flow, milestone, compact_list, show_estimations, print_milliseconds)
|
|
end
|
|
|
|
local function find_complete_milestone_from_UI_flow(milestone_flow, global_force)
|
|
local milestone_index = milestone_flow.tags.index
|
|
for _, milestone in pairs(global_force.complete_milestones) do
|
|
if approximately_equal(milestone.sort_index, milestone_index) then
|
|
return milestone
|
|
end
|
|
end
|
|
error("Couldn't find milestone from UI flow")
|
|
end
|
|
|
|
function enable_edit_time(player_index, element)
|
|
local force = game.players[player_index].force
|
|
local milestone_flow = element.parent
|
|
local milestone = find_complete_milestone_from_UI_flow(milestone_flow, global.forces[force.name])
|
|
|
|
milestone_flow.milestones_display_time.destroy()
|
|
milestone_flow.milestones_edit_time.destroy()
|
|
|
|
local default_value = misc.ticks_to_timestring(ceil_to_nearest_minute(milestone.completion_tick))
|
|
local textfield = milestone_flow.add{type="textfield", name="milestones_edit_time_field",
|
|
text=default_value, numeric=false,
|
|
tags={action="milestones_confirm_edit_time_textfield"}, style="milestones_small_textfield"}
|
|
textfield.focus()
|
|
textfield.select_all()
|
|
|
|
milestone_flow.add{type="sprite-button", name="milestones_confirm_edit_time", sprite="utility/check_mark_white", style="milestones_confirm_button",
|
|
tooltip={"milestones.edit_time_confirm"}, tags={action="milestones_confirm_edit_time"}}
|
|
end
|
|
|
|
local function parse_exact_time_to_ticks(time_string)
|
|
local time_parts = {}
|
|
for part in string.gmatch(time_string, "[^:]+") do
|
|
local number = tonumber(part)
|
|
if not number or number < 0 then return nil end
|
|
table.insert(time_parts, number)
|
|
end
|
|
if #time_parts == 3 then
|
|
return time_parts[1]*60*60*60 + time_parts[2]*60*60 + time_parts[3]*60
|
|
elseif #time_parts == 2 then
|
|
return time_parts[1]*60*60 + time_parts[2]*60
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
function confirm_edit_time(player_index, element)
|
|
local force = game.players[player_index].force
|
|
local milestone_flow = element.parent
|
|
local milestone = find_complete_milestone_from_UI_flow(milestone_flow, global.forces[force.name])
|
|
|
|
local time_quantity = milestone_flow.milestones_edit_time_field.text
|
|
if time_quantity ~= nil then
|
|
local completion_tick
|
|
completion_tick = parse_exact_time_to_ticks(time_quantity)
|
|
if completion_tick then -- Could still be nil in case of parse error
|
|
milestone.completion_tick = completion_tick
|
|
milestone.lower_bound_tick = nil
|
|
sort_milestones(global.forces[force.name].complete_milestones)
|
|
sort_milestones(global.forces[force.name].milestones_by_group[milestone.group])
|
|
end
|
|
end
|
|
|
|
refresh_gui_for_force(force)
|
|
end
|
|
|
|
local function get_row_count(milestone_counts_by_group, column_count)
|
|
row_count = 0
|
|
for _, milestone_count_in_group in pairs(milestone_counts_by_group) do
|
|
row_count = row_count + math.ceil(milestone_count_in_group / column_count) + 1
|
|
end
|
|
return row_count
|
|
end
|
|
|
|
local function get_column_count_with_groups(player, milestones_by_group, compact_list, show_estimations)
|
|
local real_width = player.display_resolution.width * (1 / player.display_scale)
|
|
local target_width = real_width * 0.9
|
|
-- 278px is about the max width of one column (3-digit hours time and 2-digit estimation)
|
|
local max_column_width = 283
|
|
if compact_list then
|
|
max_column_width = max_column_width - 76
|
|
end
|
|
if show_estimations then
|
|
max_column_width = math.max(max_column_width, 264) -- "XXX - XXX" estimation window is 264px
|
|
else
|
|
max_column_width = max_column_width - 47
|
|
end
|
|
local max_nb_columns = math.ceil(target_width / max_column_width) - 1
|
|
local column_count = 1
|
|
local milestone_counts_by_group = {}
|
|
for _group_name, group_milestones in pairs(milestones_by_group) do
|
|
table.insert(milestone_counts_by_group, #group_milestones)
|
|
column_count = math.max(column_count, #group_milestones)
|
|
|
|
if column_count >= max_nb_columns then
|
|
column_count = max_nb_columns
|
|
end
|
|
end
|
|
|
|
-- This tries to keep 3 rows per column, which results in roughly 16:9 shape
|
|
local row_count = get_row_count(milestone_counts_by_group, column_count)
|
|
while row_count < column_count * 3 and column_count > 1 do
|
|
column_count = column_count - 1
|
|
row_count = get_row_count(milestone_counts_by_group, column_count)
|
|
end
|
|
|
|
return column_count
|
|
end
|
|
|
|
local function add_n_empty_widgets(table, n)
|
|
for i = 1, n, 1 do
|
|
table.add({type="empty-widget"})
|
|
end
|
|
end
|
|
|
|
function build_display_page(player)
|
|
local main_frame = get_main_frame(player.index)
|
|
main_frame.milestones_titlebar.milestones_main_label.caption = {"milestones.title"}
|
|
main_frame.milestones_titlebar.milestones_settings_button.visible = true
|
|
main_frame.milestones_titlebar.milestones_close_button.visible = true
|
|
main_frame.milestones_dialog_buttons.visible = false
|
|
|
|
local inner_frame = get_inner_frame(player.index)
|
|
inner_frame.clear() -- Just in case the GUI didn't close through close_gui
|
|
local display_scroll = inner_frame.add{type="scroll-pane", name="milestones_display_scroll", style="flib_naked_scroll_pane"}
|
|
|
|
local global_force = global.forces[player.force.name]
|
|
|
|
local print_milliseconds = settings.global["milestones_check_frequency"].value < 60
|
|
local player_settings = settings.get_player_settings(player)
|
|
local compact_list = player_settings["milestones_compact_list"].value
|
|
local view_by_group = player_settings["milestones_list_by_group"].value
|
|
local show_estimations = player_settings["milestones_show_estimations"].value
|
|
local show_incomplete = player_settings["milestones_show_incomplete"].value
|
|
|
|
local nb_groups = table_size(global_force.milestones_by_group)
|
|
if view_by_group and nb_groups > 1 then
|
|
local visible_milestones_per_group = {}
|
|
for group_name, group_milestones in pairs(global_force.milestones_by_group) do
|
|
visible_milestones_per_group[group_name] = filter_hidden_milestones(group_milestones, show_incomplete)
|
|
if not next(visible_milestones_per_group[group_name]) then
|
|
visible_milestones_per_group[group_name] = nil
|
|
end
|
|
end
|
|
|
|
-- No milestones, exit early
|
|
if not next(visible_milestones_per_group) then
|
|
display_scroll.add(empty_set_label)
|
|
return
|
|
end
|
|
|
|
local column_count = get_column_count_with_groups(player, visible_milestones_per_group, compact_list, show_estimations)
|
|
local milestones_table = display_scroll.add{type="table", column_count=column_count, style="milestones_table_style"}
|
|
local i = 1
|
|
for group_name, group_milestones in pairs(visible_milestones_per_group) do
|
|
-- Group title
|
|
milestones_table.add({type="label", caption=group_name, style="caption_label"})
|
|
add_n_empty_widgets(milestones_table, column_count-1)
|
|
|
|
for _, milestone in pairs(group_milestones) do
|
|
add_milestone_item(milestones_table, milestone, print_milliseconds, compact_list, show_estimations)
|
|
end
|
|
add_n_empty_widgets(milestones_table, column_count - (#group_milestones % column_count))
|
|
|
|
-- Lines
|
|
if i < nb_groups then -- Don't add line after the last group
|
|
if column_count == 1 then
|
|
milestones_table.add({type="line"})
|
|
else
|
|
milestones_table.add({type="line", style="milestones_line_left"})
|
|
for j = 2, column_count-1 do
|
|
milestones_table.add({type="line", style="milestones_line_center"})
|
|
end
|
|
milestones_table.add({type="line", style="milestones_line_right"})
|
|
end
|
|
i = i + 1
|
|
end
|
|
end
|
|
else
|
|
local visible_incomplete_milestones = filter_hidden_milestones(global_force.incomplete_milestones, show_incomplete)
|
|
|
|
-- No milestones, exit early
|
|
if not next(visible_incomplete_milestones) then
|
|
display_scroll.add(empty_set_label)
|
|
return
|
|
end
|
|
|
|
-- This tries to keep 3 rows per column, which results in roughly 16:9 shape
|
|
local nb_milestones = #global_force.complete_milestones + #visible_incomplete_milestones
|
|
local column_count = math.max(
|
|
math.min(
|
|
math.ceil(math.sqrt(nb_milestones / 3)),
|
|
8),
|
|
1)
|
|
|
|
local content_table = display_scroll.add{type="table", column_count=column_count, style="milestones_table_style"}
|
|
for _, milestone in pairs(global_force.complete_milestones) do
|
|
add_milestone_item(content_table, milestone, print_milliseconds, compact_list, show_estimations)
|
|
end
|
|
|
|
for _, milestone in pairs(visible_incomplete_milestones) do
|
|
add_milestone_item(content_table, milestone, print_milliseconds, compact_list, show_estimations)
|
|
end
|
|
end
|
|
end
|
|
|
|
function is_display_page_visible(player_index)
|
|
return get_inner_frame(player_index).milestones_display_scroll ~= nil
|
|
end
|