550 lines
16 KiB
Lua
550 lines
16 KiB
Lua
local flib_dictionary = require("__flib__/dictionary-lite")
|
|
local flib_format = require("__flib__/format")
|
|
local flib_gui = require("__flib__/gui-lite")
|
|
local flib_math = require("__flib__/math")
|
|
local flib_table = require("__flib__/table")
|
|
|
|
local gui_util = require("__RateCalculator__/scripts/gui-util")
|
|
|
|
--- @alias DisplayCategory
|
|
--- | "products"
|
|
--- | "intermediates"
|
|
--- | "ingredients"
|
|
|
|
--- @alias GenericPrototype LuaEntityPrototype|LuaFluidPrototype|LuaItemPrototype
|
|
|
|
--- @class RatesDisplayData: Rates
|
|
--- @field category DisplayCategory
|
|
--- @field path SpritePath
|
|
--- @field sorting_rate double
|
|
--- @field completed boolean
|
|
--- @field is_watts boolean
|
|
|
|
--- @alias CategoryDisplayData table<DisplayCategory, RatesDisplayData>
|
|
--- @alias DisplayDataLookup table<string, RatesDisplayData>
|
|
|
|
--- @class RATESTEMP
|
|
--- @field path SpritePath
|
|
--- @field icon SpritePath
|
|
--- @field temperature double?
|
|
--- @field machines_caption LocalisedString
|
|
--- @field rate_caption LocalisedString
|
|
--- @field rate_color string
|
|
--- @field rate_suffix LocalisedString?
|
|
--- @field rate double
|
|
--- @field completed boolean
|
|
|
|
local colors = {
|
|
green = "150,255,150",
|
|
red = "255,150,150",
|
|
white = "255,255,255",
|
|
}
|
|
|
|
--- @param amount number
|
|
--- @param prefer_si boolean
|
|
--- @param positive_prefix boolean
|
|
--- @return string
|
|
local function format_number(amount, prefer_si, positive_prefix)
|
|
local formatted = ""
|
|
if prefer_si or math.abs(amount) >= 10000 then
|
|
formatted = flib_format.number(amount, true)
|
|
else
|
|
local precision = 0.001
|
|
if math.abs(amount) >= 1000 then
|
|
precision = 1
|
|
elseif math.abs(amount) >= 100 then
|
|
precision = 0.1
|
|
elseif math.abs(amount) >= 10 then
|
|
precision = 0.01
|
|
end
|
|
formatted = flib_format.number(flib_math.round(amount, precision))
|
|
end
|
|
if positive_prefix and amount > 0 then
|
|
formatted = "+" .. formatted
|
|
end
|
|
return formatted
|
|
end
|
|
|
|
--- @param counts MachineCounts
|
|
--- @param include_numbers boolean
|
|
--- @return string
|
|
local function build_machine_icons(counts, include_numbers)
|
|
local output = ""
|
|
for name, count in pairs(counts) do
|
|
output = output .. "[entity=" .. name .. "] "
|
|
if include_numbers then
|
|
output = output .. count .. " "
|
|
end
|
|
end
|
|
return output
|
|
end
|
|
|
|
--- @param rate Rate
|
|
--- @param color string
|
|
--- @param label LocalisedString
|
|
--- @param suffix LocalisedString
|
|
local function build_rate_tooltip(rate, color, label, suffix)
|
|
return {
|
|
"gui.rcalc-colored-caption",
|
|
{
|
|
"",
|
|
{
|
|
"gui.rcalc-tooltip-entry",
|
|
label,
|
|
{ "", format_number(rate.rate, false, false), suffix },
|
|
},
|
|
{
|
|
"gui.rcalc-parenthesized-caption",
|
|
{
|
|
"gui.rcalc-machines-caption",
|
|
{ "", format_number(rate.rate / rate.machines, false, false), suffix },
|
|
{
|
|
"gui.rcalc-caption-with-suffix",
|
|
format_number(rate.machines, false, false),
|
|
{ "gui.rcalc-machines" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
color,
|
|
}
|
|
end
|
|
|
|
--- @param num number
|
|
--- @return string
|
|
local function get_net_color(num)
|
|
if num > 0 then
|
|
return colors.green
|
|
elseif num < 0 then
|
|
return colors.red
|
|
else
|
|
return colors.white
|
|
end
|
|
end
|
|
|
|
--- @param e EventData.on_gui_click
|
|
local function on_completion_checkbox_checked(e)
|
|
local self = global.gui[e.player_index]
|
|
if not self then
|
|
return
|
|
end
|
|
local set = self.sets[self.selected_set_index]
|
|
if set then
|
|
set.completed[e.element.name] = e.element.state or nil
|
|
end
|
|
end
|
|
|
|
--- @param e EventData.on_gui_click
|
|
local function on_rates_flow_clicked(e)
|
|
if not remote.interfaces["RecipeBook"] or not e.alt then
|
|
return
|
|
end
|
|
local sprite = e.element.icon.sprite
|
|
local type, name = string.match(sprite, "(.*)/(.*)")
|
|
if not type or not name then
|
|
return
|
|
end
|
|
remote.call("RecipeBook", "open_page", e.player_index, type, name)
|
|
end
|
|
|
|
--- @param e EventData.on_gui_hover
|
|
local function on_rates_flow_hovered(e)
|
|
local self = global.gui[e.player_index]
|
|
if not self or not self.elems.rcalc_window.valid then
|
|
return
|
|
end
|
|
|
|
local elem = e.element
|
|
local path = elem.name
|
|
local data = self.display_data_lookup[path]
|
|
if not data then
|
|
return
|
|
end
|
|
|
|
local category = data.category
|
|
local output = data.output
|
|
local input = data.input
|
|
local is_watts = data.is_watts
|
|
local suffix = is_watts and { "si-unit-symbol-watt" } or { "gui.rcalc-timescale-suffix-" .. self.selected_timescale }
|
|
--- @type Rate
|
|
local category_rate = category == "ingredients" and input or output
|
|
|
|
--- @type GenericPrototype
|
|
local prototype = game[data.type .. "_prototypes"][data.name]
|
|
|
|
local name = prototype.localised_name
|
|
if data.temperature then
|
|
name = {
|
|
"",
|
|
name,
|
|
" (",
|
|
{ "format-degrees-c-compact", format_number(data.temperature, false, false) },
|
|
")",
|
|
}
|
|
end
|
|
|
|
local machine_counts_caption = build_machine_icons(category_rate.machine_counts, true)
|
|
|
|
--- @type LocalisedString
|
|
local intermediate_breakdown_caption = { "" }
|
|
if category == "intermediates" then
|
|
machine_counts_caption = machine_counts_caption .. "→ " .. build_machine_icons(input.machine_counts, true)
|
|
|
|
local net_rate = output.rate - input.rate
|
|
rate_caption = {
|
|
"gui.rcalc-colored-caption",
|
|
{
|
|
"",
|
|
{
|
|
"gui.rcalc-tooltip-entry",
|
|
{ "gui.rcalc-net-rate" },
|
|
{ "", format_number(net_rate, false, true), suffix },
|
|
},
|
|
{
|
|
"gui.rcalc-parenthesized-caption",
|
|
{
|
|
"gui.rcalc-caption-with-suffix",
|
|
format_number(net_rate / (output.rate / output.machines), false, true),
|
|
{ "gui.rcalc-machines" },
|
|
},
|
|
},
|
|
},
|
|
get_net_color(net_rate),
|
|
}
|
|
|
|
intermediate_breakdown_caption = {
|
|
"",
|
|
"\n\n",
|
|
build_rate_tooltip(output, colors.green, { "gui.rcalc-production" }, suffix),
|
|
"\n",
|
|
build_rate_tooltip(input, colors.red, { "gui.rcalc-consumption" }, suffix),
|
|
}
|
|
else
|
|
rate_caption = build_rate_tooltip(category_rate, colors.white, { "gui.rcalc-rate" }, suffix)
|
|
end
|
|
|
|
machine_counts_caption = machine_counts_caption .. "\n"
|
|
|
|
elem.tooltip = {
|
|
"",
|
|
{ "gui.rcalc-tooltip-title", name },
|
|
machine_counts_caption,
|
|
rate_caption,
|
|
intermediate_breakdown_caption,
|
|
remote.interfaces["RecipeBook"]
|
|
and { "", "\n\n", { "gui.rcalc-open-in-recipe-book-instruction", { "mod-name.RecipeBook" } } }
|
|
or nil,
|
|
}
|
|
end
|
|
|
|
--- @param e EventData.on_gui_leave
|
|
local function on_rates_flow_left(e)
|
|
e.element.tooltip = ""
|
|
end
|
|
|
|
flib_gui.add_handlers({
|
|
on_completion_checkbox_checked = on_completion_checkbox_checked,
|
|
on_rates_flow_clicked = on_rates_flow_clicked,
|
|
on_rates_flow_hovered = on_rates_flow_hovered,
|
|
on_rates_flow_left = on_rates_flow_left,
|
|
})
|
|
|
|
--- @param parent LuaGuiElement
|
|
--- @param category DisplayCategory
|
|
--- @param rates RatesDisplayData[]
|
|
--- @param show_machines boolean
|
|
--- @param show_checkboxes boolean
|
|
--- @param show_breakdown boolean
|
|
local function build_rates_table(parent, category, rates, show_machines, show_checkboxes, show_breakdown)
|
|
--- @type GuiElemDef
|
|
local rates_table = { type = "table", style = "slot_table", column_count = 1 }
|
|
|
|
for _, data in pairs(rates) do
|
|
local output = data.output
|
|
local input = data.input
|
|
local category_rate = data.category == "ingredients" and input or output
|
|
local raw_rate = category_rate.rate
|
|
|
|
local machines = format_number(category_rate.machines, false, false)
|
|
local machines_caption = {
|
|
"gui.rcalc-machines-caption",
|
|
build_machine_icons(category_rate.machine_counts, false),
|
|
machines,
|
|
}
|
|
|
|
local rate_color = colors.white
|
|
if category == "intermediates" then
|
|
raw_rate = flib_math.round(output.rate - input.rate, 0.00001) -- Floating point sucks
|
|
rate_color = get_net_color(raw_rate)
|
|
local net_machines = raw_rate / (output.rate / output.machines)
|
|
machines_caption = {
|
|
"gui.rcalc-net-machines-caption",
|
|
machines_caption,
|
|
rate_color,
|
|
format_number(net_machines, false, true),
|
|
}
|
|
end
|
|
|
|
local rate_caption = format_number(raw_rate, data.is_watts, category == "intermediates")
|
|
|
|
local flow = {
|
|
type = "flow",
|
|
name = data.path,
|
|
style = "rcalc_rates_table_row_flow",
|
|
raise_hover_events = true,
|
|
game_controller_interaction = defines.game_controller_interaction and defines.game_controller_interaction.always,
|
|
handler = {
|
|
[defines.events.on_gui_click] = on_rates_flow_clicked,
|
|
[defines.events.on_gui_hover] = on_rates_flow_hovered,
|
|
[defines.events.on_gui_leave] = on_rates_flow_left,
|
|
},
|
|
}
|
|
|
|
if show_checkboxes then
|
|
flow[#flow + 1] = {
|
|
type = "checkbox",
|
|
name = data.path,
|
|
state = data.completed,
|
|
handler = {
|
|
[defines.events.on_gui_checked_state_changed] = on_completion_checkbox_checked,
|
|
},
|
|
}
|
|
end
|
|
|
|
local button_style = "rcalc_transparent_slot"
|
|
if data.path == "item/rcalc-heat-dummy" then
|
|
button_style = "rcalc_transparent_slot_no_shadow"
|
|
end
|
|
|
|
flow[#flow + 1] = {
|
|
type = "sprite-button",
|
|
name = "icon",
|
|
style = button_style,
|
|
sprite = data.type .. "/" .. data.name,
|
|
number = data.temperature,
|
|
ignored_by_interaction = true,
|
|
}
|
|
|
|
if show_machines then
|
|
flow[#flow + 1] = {
|
|
type = "label",
|
|
style = "rcalc_machines_label",
|
|
caption = machines_caption,
|
|
ignored_by_interaction = true,
|
|
}
|
|
flow[#flow + 1] = { type = "empty-widget", style = "flib_horizontal_pusher", ignored_by_interaction = true }
|
|
end
|
|
|
|
if category == "intermediates" and show_breakdown then
|
|
flow[#flow + 1] = {
|
|
type = "label",
|
|
style = "rcalc_intermediate_breakdown_label",
|
|
caption = {
|
|
"",
|
|
{ "gui.rcalc-colored-caption", format_number(output.rate, data.is_watts, false), colors.green },
|
|
" - ",
|
|
{ "gui.rcalc-colored-caption", format_number(input.rate, data.is_watts, false), colors.red },
|
|
},
|
|
ignored_by_interaction = true,
|
|
}
|
|
end
|
|
|
|
flow[#flow + 1] = {
|
|
type = "label",
|
|
style = "rcalc_rate_label",
|
|
caption = {
|
|
"gui.rcalc-colored-caption",
|
|
{ "", rate_caption, data.is_watts and { "si-unit-symbol-watt" } or "" },
|
|
rate_color,
|
|
},
|
|
ignored_by_interaction = true,
|
|
}
|
|
rates_table[#rates_table + 1] = flow
|
|
end
|
|
|
|
flib_gui.add(parent, {
|
|
type = "flow",
|
|
direction = "vertical",
|
|
{ type = "label", style = "caption_label", caption = { "gui.rcalc-" .. category } },
|
|
rates_table,
|
|
})
|
|
end
|
|
|
|
local gui_rates = {}
|
|
|
|
--- @param self GuiData
|
|
--- @param set CalculationSet
|
|
--- @return CategoryDisplayData
|
|
function gui_rates.update_display_data(self, set)
|
|
local timescale_data = gui_util.timescale_data[self.selected_timescale]
|
|
local manual_multiplier = self.manual_multiplier
|
|
local multiplier = timescale_data.multiplier or 1
|
|
local divisor, type_filter, divide_stacks, inserter_stack_size = gui_util.get_divisor(self)
|
|
local dictionary = flib_dictionary.get(self.player.index, "search") or {}
|
|
local show_power_input = self.player.mod_settings["rcalc-show-power-consumption"].value --[[@as boolean]]
|
|
local show_pollution = self.player.mod_settings["rcalc-show-pollution"].value --[[@as boolean]]
|
|
local search_query = self.search_query
|
|
|
|
--- @param rate Rate
|
|
--- @param is_watts boolean
|
|
--- @return Rate
|
|
local function scale_rate(rate, is_watts)
|
|
local multiplier = is_watts and 1 or multiplier
|
|
local divisor = is_watts and 1 or divisor
|
|
return {
|
|
machine_counts = flib_table.map(rate.machine_counts, function(count)
|
|
return count * manual_multiplier
|
|
end),
|
|
machines = rate.machines * manual_multiplier,
|
|
rate = rate.rate / (divisor or 1) * multiplier * manual_multiplier,
|
|
}
|
|
end
|
|
|
|
--- @type table<DisplayCategory, RatesDisplayData[]>
|
|
local category_display_data = {
|
|
products = {},
|
|
intermediates = {},
|
|
ingredients = {},
|
|
}
|
|
--- @type DisplayDataLookup
|
|
local display_data_lookup = {}
|
|
|
|
for path, rates in pairs(set.rates) do
|
|
local is_watts = path == "item/rcalc-power-dummy" or path == "item/rcalc-heat-dummy"
|
|
local output = scale_rate(rates.output, is_watts)
|
|
local input = scale_rate(rates.input, is_watts)
|
|
|
|
if divide_stacks and rates.type == "item" and not is_watts then
|
|
local stack_size = game.item_prototypes[rates.name].stack_size
|
|
output.rate = output.rate / stack_size
|
|
input.rate = input.rate / stack_size
|
|
end
|
|
|
|
if inserter_stack_size and inserter_stack_size > 0 and rates.type == "item" and not is_watts then
|
|
local stack_size = math.min(game.item_prototypes[rates.name].stack_size, inserter_stack_size)
|
|
output.rate = output.rate / stack_size
|
|
input.rate = input.rate / stack_size
|
|
end
|
|
|
|
--- @type DisplayCategory
|
|
local category = "products"
|
|
local sorting_rate = output.rate
|
|
if output.rate > 0 and input.rate > 0 then
|
|
category = "intermediates"
|
|
sorting_rate = output.rate - input.rate
|
|
elseif input.rate > 0 then
|
|
category = "ingredients"
|
|
sorting_rate = input.rate
|
|
end
|
|
|
|
if type_filter and (type_filter ~= rates.type or is_watts or path == "item/rcalc-pollution-dummy") then
|
|
goto continue
|
|
end
|
|
if path == "item/rcalc-power-dummy" and not show_power_input then
|
|
if output.rate > 0 then
|
|
category = "products"
|
|
else
|
|
goto continue
|
|
end
|
|
end
|
|
if path == "item/rcalc-pollution-dummy" and not show_pollution then
|
|
goto continue
|
|
end
|
|
local to_search = string.lower(dictionary[path] or rates.name)
|
|
if not string.find(to_search, search_query, nil, true) then
|
|
goto continue
|
|
end
|
|
|
|
local data = {
|
|
type = rates.type,
|
|
name = rates.name,
|
|
temperature = rates.temperature,
|
|
output = output,
|
|
input = input,
|
|
path = path,
|
|
category = category,
|
|
sorting_rate = sorting_rate,
|
|
completed = set.completed[path] or false,
|
|
is_watts = is_watts,
|
|
}
|
|
local category_data = category_display_data[category]
|
|
--- @type RatesDisplayData
|
|
category_data[#category_data + 1] = data
|
|
display_data_lookup[path] = data
|
|
|
|
::continue::
|
|
end
|
|
|
|
for _, rates in pairs(category_display_data) do
|
|
table.sort(rates, function(a, b)
|
|
return a.sorting_rate > b.sorting_rate
|
|
end)
|
|
end
|
|
|
|
self.display_data_lookup = display_data_lookup
|
|
|
|
return category_display_data
|
|
end
|
|
|
|
--- @param self GuiData
|
|
--- @param category_display_data CategoryDisplayData
|
|
function gui_rates.update_gui(self, category_display_data)
|
|
local show_checkboxes = self.player.mod_settings["rcalc-show-completion-checkboxes"].value --[[@as boolean]]
|
|
local show_intermediate_breakdowns = self.player.mod_settings["rcalc-show-intermediate-breakdowns"].value --[[@as boolean]]
|
|
|
|
local has_ingredients = #category_display_data.ingredients > 0
|
|
local has_intermediates = #category_display_data.intermediates > 0
|
|
local has_products = #category_display_data.products > 0
|
|
|
|
local rates_flow = self.elems.rates_flow
|
|
rates_flow.clear()
|
|
|
|
if has_ingredients then
|
|
build_rates_table(
|
|
rates_flow,
|
|
"ingredients",
|
|
category_display_data.ingredients,
|
|
not has_intermediates and not has_products,
|
|
show_checkboxes,
|
|
show_intermediate_breakdowns
|
|
)
|
|
if has_intermediates or has_products then
|
|
rates_flow.add({ type = "line", direction = "vertical" })
|
|
end
|
|
end
|
|
if has_intermediates or has_products then
|
|
rates_flow = rates_flow.add({ type = "flow", style = "rcalc_rates_table_vertical_flow", direction = "vertical" })
|
|
end
|
|
|
|
if has_products then
|
|
build_rates_table(
|
|
rates_flow,
|
|
"products",
|
|
category_display_data.products,
|
|
true,
|
|
show_checkboxes,
|
|
show_intermediate_breakdowns
|
|
)
|
|
if has_intermediates then
|
|
rates_flow.add({ type = "line", direction = "horizontal" })
|
|
end
|
|
end
|
|
|
|
if has_intermediates then
|
|
build_rates_table(
|
|
rates_flow,
|
|
"intermediates",
|
|
category_display_data.intermediates,
|
|
true,
|
|
show_checkboxes,
|
|
show_intermediate_breakdowns
|
|
)
|
|
end
|
|
|
|
if not has_ingredients and not has_intermediates and not has_products then
|
|
rates_flow.add({ type = "label", style = "rcalc_machines_label", caption = { "gui.rcalc-no-rates-to-display" } })
|
|
end
|
|
end
|
|
|
|
return gui_rates
|