178 lines
6.5 KiB
Lua

local flib_dictionary = require("__flib__/dictionary-lite")
local flib_math = require("__flib__/math")
--- @alias DivisorSource
--- | "inserter_divisor"
--- | "materials_divisor",
--- | "transport_belt_divisor"
--- @class TimescaleData
--- @field divisor_required boolean?
--- @field divisor_source DivisorSource
--- @field multiplier double?
--- @field prefer_si boolean?
--- @field type_filter string?
--- @field suffix LocalisedString?
--- @class GuiUtil
local gui_util = {}
function gui_util.build_divisor_filters()
--- @type EntityPrototypeFilter[]
local materials = {}
for _, entity in
pairs(game.get_filtered_entity_prototypes({
{ filter = "type", type = "container" },
{ filter = "type", type = "logistic-container" },
}))
do
local stacks = entity.get_inventory_size(defines.inventory.chest)
if stacks and stacks > 0 and entity.group.name ~= "other" and entity.group.name ~= "environment" then
materials[#materials + 1] = { filter = "name", name = entity.name }
end
end
for _, entity in pairs(game.get_filtered_entity_prototypes({ { filter = "type", type = "cargo-wagon" } })) do
local stacks = entity.get_inventory_size(defines.inventory.cargo_wagon)
if stacks > 0 and entity.group.name ~= "other" and entity.group.name ~= "environment" then
materials[#materials + 1] = { filter = "name", name = entity.name }
end
end
for _, entity in
pairs(game.get_filtered_entity_prototypes({
{ filter = "type", type = "storage-tank" },
{ filter = "type", type = "fluid-wagon" },
}))
do
local capacity = entity.fluid_capacity
if capacity > 0 and entity.group.name ~= "other" and entity.group.name ~= "environment" then
materials[#materials + 1] = { filter = "name", name = entity.name }
end
end
--- @type table<DivisorSource, EntityPrototypeFilter[]>
global.elem_filters = {
inserter_divisor = { { filter = "type", type = "inserter" } },
materials_divisor = materials,
transport_belt_divisor = { { filter = "type", type = "transport-belt" } },
}
end
function gui_util.build_dictionaries()
flib_dictionary.new("search")
for name, prototype in pairs(game.fluid_prototypes) do
flib_dictionary.add("search", "fluid/" .. name, prototype.localised_name)
end
for name, prototype in pairs(game.item_prototypes) do
flib_dictionary.add("search", "item/" .. name, prototype.localised_name)
end
end
--- @param inserter LuaEntityPrototype
--- @return double
function gui_util.calc_inserter_cycles_per_second(inserter)
local pickup_vector = inserter.inserter_pickup_position --[[@as Vector]]
local drop_vector = inserter.inserter_drop_position --[[@as Vector]]
local pickup_x, pickup_y, drop_x, drop_y = pickup_vector[1], pickup_vector[2], drop_vector[1], drop_vector[2]
local pickup_length = math.sqrt(pickup_x * pickup_x + pickup_y * pickup_y)
local drop_length = math.sqrt(drop_x * drop_x + drop_y * drop_y)
-- Get angle from the dot product
-- XXX: Imprecision can make this return slightly outside the allowed bounds for acos, so clamp it
local norm_dot = flib_math.clamp((pickup_x * drop_x + pickup_y * drop_y) / (pickup_length * drop_length), -1, 1)
local angle = math.acos(norm_dot)
-- Rotation speed is in full circles per tick
local ticks_per_cycle = 2 * math.ceil(angle / (math.pi * 2) / inserter.inserter_rotation_speed)
local extension_time = 2 * math.ceil(math.abs(pickup_length - drop_length) / inserter.inserter_extension_speed)
if ticks_per_cycle < extension_time then
ticks_per_cycle = extension_time
end
return 60 / ticks_per_cycle -- 60 = ticks per second
end
--- @param self GuiData
--- @return double|uint?, string?, boolean?, uint?
function gui_util.get_divisor(self)
local timescale_data = gui_util.timescale_data[self.selected_timescale]
local type_filter
--- @type double|uint?
local divisor
--- @type string?
local divisor_source = timescale_data.divisor_source
if not divisor_source then
return
end
--- @type string?
local divisor_name = self[divisor_source]
if not divisor_name then
return
end
if timescale_data.divisor_required and not divisor_name then
local entities = game.get_filtered_entity_prototypes(global.elem_filters[timescale_data.divisor_source])
-- LuaCustomTable does not work with next()
for name in pairs(entities) do
divisor_name = name
break
end
end
local inserter_stack_size = 0
local divide_stacks = false
if divisor_name then
local prototype = game.entity_prototypes[divisor_name]
if prototype.type == "container" or prototype.type == "logistic-container" then
divisor = prototype.get_inventory_size(defines.inventory.chest)
type_filter = "item"
divide_stacks = true
elseif prototype.type == "cargo-wagon" then
divisor = prototype.get_inventory_size(defines.inventory.cargo_wagon)
type_filter = "item"
divide_stacks = true
elseif prototype.type == "storage-tank" or prototype.type == "fluid-wagon" then
divisor = prototype.fluid_capacity
type_filter = "fluid"
elseif prototype.type == "transport-belt" then
divisor = prototype.belt_speed * 480
type_filter = "item"
elseif prototype.type == "inserter" then
divisor = gui_util.calc_inserter_cycles_per_second(prototype)
if prototype.stack then
inserter_stack_size = 1 + prototype.inserter_stack_size_bonus + self.player.force.stack_inserter_capacity_bonus
else
inserter_stack_size = 1 + prototype.inserter_stack_size_bonus + self.player.force.inserter_stack_size_bonus
end
type_filter = "item"
end
end
return divisor, type_filter, divide_stacks, inserter_stack_size
end
--- @param filters EntityPrototypeFilter[]
function gui_util.get_first_prototype(filters)
-- XXX: next() doesn't work on LuaCustomTable
for name in pairs(game.get_filtered_entity_prototypes(filters)) do
return name
end
end
--- @type table<Timescale, TimescaleData>
gui_util.timescale_data = {
["per-second"] = { divisor_source = "materials_divisor", multiplier = 1 },
["per-minute"] = { divisor_source = "materials_divisor", multiplier = 60 },
["per-hour"] = { divisor_source = "materials_divisor", multiplier = 60 * 60 },
["transport-belts"] = { divisor_required = true, divisor_source = "transport_belt_divisor", type_filter = "item" },
["inserters"] = { divisor_required = true, divisor_source = "inserter_divisor", type_filter = "item" },
}
--- @type Timescale[]
gui_util.ordered_timescales = {
"per-second",
"per-minute",
"per-hour",
"transport-belts",
"inserters",
}
return gui_util