z_mirror/RateCalculator

git clone https://git.raiguard.me/z_mirror/RateCalculator

Caleb HeuerRevert back to previous dictionary logic75c18ca

trunk
6.9 KiB184 linesraw
1local flib_dictionary = require("__flib__.dictionary")
2local flib_math = require("__flib__.math")
3
4--- @alias DivisorSource
5--- | "inserter_divisor"
6--- | "materials_divisor",
7--- | "transport_belt_divisor"
8
9--- @class TimescaleData
10--- @field divisor_required boolean?
11--- @field divisor_source DivisorSource
12--- @field multiplier double?
13--- @field prefer_si boolean?
14--- @field type_filter string?
15--- @field suffix LocalisedString?
16
17--- @class GuiUtil
18local gui_util = {}
19
20-- TODO: Move these to root scope
21function gui_util.build_divisor_filters()
22  --- @type EntityPrototypeFilter[]
23  local materials = {}
24  for _, entity in
25    pairs(prototypes.get_entity_filtered({
26      { filter = "type", type = "container" },
27      { filter = "type", type = "logistic-container" },
28    }))
29  do
30    local stacks = entity.get_inventory_size(defines.inventory.chest)
31    if stacks and stacks > 0 and entity.group.name ~= "other" and entity.group.name ~= "environment" then
32      materials[#materials + 1] = { filter = "name", name = entity.name }
33    end
34  end
35  for _, entity in pairs(prototypes.get_entity_filtered({ { filter = "type", type = "cargo-wagon" } })) do
36    local stacks = entity.get_inventory_size(defines.inventory.cargo_wagon)
37    if stacks > 0 and entity.group.name ~= "other" and entity.group.name ~= "environment" then
38      materials[#materials + 1] = { filter = "name", name = entity.name }
39    end
40  end
41  for _, entity in
42    pairs(prototypes.get_entity_filtered({
43      { filter = "type", type = "storage-tank" },
44      { filter = "type", type = "fluid-wagon" },
45    }))
46  do
47    local capacity = entity.fluid_capacity
48    if capacity > 0 and entity.group.name ~= "other" and entity.group.name ~= "environment" then
49      materials[#materials + 1] = { filter = "name", name = entity.name }
50    end
51  end
52
53  --- @type table<DivisorSource, EntityPrototypeFilter[]>
54  storage.elem_filters = {
55    inserter_divisor = { { filter = "type", type = "inserter" } },
56    materials_divisor = materials,
57    transport_belt_divisor = { { filter = "type", type = "transport-belt" } },
58  }
59end
60
61function gui_util.build_dictionaries()
62  flib_dictionary.new("search")
63  for name, prototype in pairs(prototypes.fluid) do
64    flib_dictionary.add("search", "fluid/" .. name, prototype.localised_name)
65  end
66  for name, prototype in pairs(prototypes.item) do
67    flib_dictionary.add("search", "item/" .. name, prototype.localised_name)
68  end
69end
70
71--- @param inserter LuaEntityPrototype
72--- @param quality QualityID
73--- @return double
74function gui_util.calc_inserter_cycles_per_second(inserter, quality)
75  local pickup_vector = inserter.inserter_pickup_position --[[@as Vector]]
76  local drop_vector = inserter.inserter_drop_position --[[@as Vector]]
77  local pickup_x, pickup_y, drop_x, drop_y = pickup_vector[1], pickup_vector[2], drop_vector[1], drop_vector[2]
78  local pickup_length = math.sqrt(pickup_x * pickup_x + pickup_y * pickup_y)
79  local drop_length = math.sqrt(drop_x * drop_x + drop_y * drop_y)
80  -- Get angle from the dot product
81  -- XXX: Imprecision can make this return slightly outside the allowed bounds for acos, so clamp it
82  local norm_dot = flib_math.clamp((pickup_x * drop_x + pickup_y * drop_y) / (pickup_length * drop_length), -1, 1)
83  local angle = math.acos(norm_dot)
84  -- Rotation speed is in full circles per tick
85  local rotation_speed = inserter.get_inserter_rotation_speed(quality)
86  local ticks_per_cycle = 2 * math.ceil(angle / (math.pi * 2) / rotation_speed)
87  local extension_speed = inserter.get_inserter_extension_speed(quality)
88  local extension_time = 2 * math.ceil(math.abs(pickup_length - drop_length) / extension_speed)
89  if ticks_per_cycle < extension_time then
90    ticks_per_cycle = extension_time
91  end
92  return 60 / ticks_per_cycle -- 60 = ticks per second
93end
94
95--- @param self GuiData
96--- @return double|uint?, string?, boolean?, uint?
97function gui_util.get_divisor(self)
98  local timescale_data = gui_util.timescale_data[self.selected_timescale]
99  local type_filter
100
101  --- @type double|uint?
102  local divisor
103  --- @type string?
104  local divisor_source = timescale_data.divisor_source
105  if not divisor_source then
106    return
107  end
108
109  --- @type {name: string, quality: string}?
110  local divisor_id = self[divisor_source]
111  if not divisor_id then
112    return
113  end
114  if timescale_data.divisor_required and not divisor_id then
115    local entities = prototypes.get_entity_filtered(storage.elem_filters[timescale_data.divisor_source])
116    -- LuaCustomTable does not work with next()
117    for name in pairs(entities) do
118      divisor_id = { name = name, quality = "normal" }
119      break
120    end
121  end
122
123  local inserter_stack_size = 0
124  local divide_stacks = false
125  if divisor_id then
126    --- @type LuaEntityPrototype
127    local prototype = prototypes.entity[divisor_id.name]
128    if prototype.type == "container" or prototype.type == "logistic-container" then
129      divisor = prototype.get_inventory_size(defines.inventory.chest)
130      type_filter = "item"
131      divide_stacks = true
132    elseif prototype.type == "cargo-wagon" then
133      divisor = prototype.get_inventory_size(defines.inventory.cargo_wagon)
134      type_filter = "item"
135      divide_stacks = true
136    elseif prototype.type == "storage-tank" or prototype.type == "fluid-wagon" then
137      divisor = prototype.fluid_capacity
138      type_filter = "fluid"
139    elseif prototype.type == "transport-belt" then
140      divisor = prototype.belt_speed * 480
141      type_filter = "item"
142    elseif prototype.type == "inserter" then
143      local cycles_per_second = gui_util.calc_inserter_cycles_per_second(prototype, divisor_id.quality)
144      if prototype.bulk then
145        inserter_stack_size = 1 + prototype.inserter_stack_size_bonus + self.player.force.bulk_inserter_capacity_bonus
146      else
147        inserter_stack_size = 1 + prototype.inserter_stack_size_bonus + self.player.force.inserter_stack_size_bonus
148      end
149      divisor = cycles_per_second * inserter_stack_size
150      type_filter = "item"
151    end
152  end
153
154  return divisor, type_filter, divide_stacks, inserter_stack_size
155end
156
157--- @param filters EntityPrototypeFilter[]
158--- @return EntityWithQualityID
159function gui_util.get_first_prototype(filters)
160  -- XXX: next() doesn't work on LuaCustomTable
161  for name in pairs(prototypes.get_entity_filtered(filters)) do
162    return { name = name, quality = "normal" }
163  end
164end
165
166--- @type table<Timescale, TimescaleData>
167gui_util.timescale_data = {
168  ["per-second"] = { divisor_source = "materials_divisor", multiplier = 1 },
169  ["per-minute"] = { divisor_source = "materials_divisor", multiplier = 60 },
170  ["per-hour"] = { divisor_source = "materials_divisor", multiplier = 60 * 60 },
171  ["transport-belts"] = { divisor_required = true, divisor_source = "transport_belt_divisor", type_filter = "item" },
172  ["inserters"] = { divisor_required = true, divisor_source = "inserter_divisor", type_filter = "item" },
173}
174
175--- @type Timescale[]
176gui_util.ordered_timescales = {
177  "per-second",
178  "per-minute",
179  "per-hour",
180  "transport-belts",
181  "inserters",
182}
183
184return gui_util