z_mirror/EditorExtensions

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

Caleb HeuerUse util function for pushers0ef36a1

1.14
14.7 KiB476 linesraw
1local gui = require("__flib__/gui-lite")
2local math = require("__flib__/math")
3local table = require("__flib__/table")
4
5local util = require("__EditorExtensions__/scripts/util")
6
7local si_suffixes_joule = { "kJ", "MJ", "GJ", "TJ", "PJ", "EJ", "ZJ", "YJ" }
8local si_suffixes_watt = { "kW", "MW", "GW", "TW", "PW", "EW", "ZW", "YW" }
9
10--- @type InfinityAccumulatorMode[]
11local modes = { "output", "input", "buffer" }
12--- @type InfinityAccumulatorPriority[]
13local priorities = { "primary", "secondary", "tertiary" }
14
15local entity_names = {
16  ["ee-infinity-accumulator-primary-input"] = true,
17  ["ee-infinity-accumulator-primary-output"] = true,
18  ["ee-infinity-accumulator-secondary-input"] = true,
19  ["ee-infinity-accumulator-secondary-output"] = true,
20  ["ee-infinity-accumulator-tertiary-buffer"] = true,
21  ["ee-infinity-accumulator-tertiary-input"] = true,
22  ["ee-infinity-accumulator-tertiary-output"] = true,
23}
24
25--- @alias InfinityAccumulatorMode
26--- | "output"
27--- | "input"
28--- | "buffer"
29
30--- @alias InfinityAccumulatorPriority
31--- | "primary"
32--- | "secondary"
33--- | "tertiary"
34
35--- @param name string
36--- @return InfinityAccumulatorPriority, InfinityAccumulatorMode
37local function get_settings_from_name(name)
38  local priority, mode = string.match(name, "^ee%-infinity%-accumulator%-(%a+)%-(%a+)$")
39  return priority, mode
40end
41
42--- @param entity LuaEntity
43--- @param mode string
44--- @param buffer_size double?
45local function set_entity_settings(entity, mode, buffer_size)
46  local watts = util.parse_energy((buffer_size * 60) .. "W")
47
48  if mode == "output" then
49    entity.power_production = watts
50    entity.power_usage = 0
51    entity.electric_buffer_size = buffer_size
52  elseif mode == "input" then
53    entity.power_production = 0
54    entity.power_usage = watts
55    entity.electric_buffer_size = buffer_size
56  elseif mode == "buffer" then
57    entity.power_production = 0
58    entity.power_usage = 0
59    entity.electric_buffer_size = buffer_size * 60
60  end
61end
62
63--- @param entity LuaEntity
64--- @param priority string
65--- @param mode string
66--- @return LuaEntity?
67local function change_entity(entity, priority, mode)
68  local new_entity = entity.surface.create_entity({
69    name = "ee-infinity-accumulator-" .. priority .. "-" .. mode,
70    position = entity.position,
71    force = entity.force,
72    last_user = entity.last_user,
73    create_build_effect_smoke = false,
74  })
75  if not new_entity then
76    return
77  end
78
79  -- Calculate new buffer size
80  local buffer_size = entity.electric_buffer_size
81  local _, old_mode = get_settings_from_name(entity.name)
82  if old_mode ~= mode and old_mode == "buffer" then
83    -- coming from buffer, divide by 60
84    buffer_size = buffer_size / 60
85  end
86
87  set_entity_settings(new_entity, mode, buffer_size)
88  entity.destroy()
89
90  return new_entity
91end
92
93--- Returns the power_slider value and dropdown selected index based on the entity's buffer size
94--- @param buffer_size double
95--- @param mode InfinityAccumulatorMode
96--- @return uint, uint
97local function get_slider_values(buffer_size, mode)
98  if mode ~= "buffer" then
99    -- The power_slider is controlling watts, so inflate the buffer size by 60x to get that value
100    buffer_size = buffer_size * 60
101  end
102  -- Determine how many orders of magnitude there are
103  local len = string.len(string.format("%.0f", math.floor(buffer_size)))
104  -- `power` is the dropdown value - how many sets of three orders of magnitude there are, rounded down
105  local power = math.floor((len - 1) / 3)
106  -- Slider value is the buffer size scaled to its base-three order of magnitude
107  return math.floor(buffer_size / 10 ^ (power * 3)) --[[@as uint]],
108    math.max(power, 1) --[[@as uint]]
109end
110
111--- Returns the entity buffer size based on the power_slider value and dropdown selected index
112local function calc_buffer_size(slider_value, dropdown_index)
113  return util.parse_energy(slider_value .. si_suffixes_joule[dropdown_index]) / 60
114end
115
116--- @param player_index uint
117local function destroy_gui(player_index)
118  local self = global.infinity_accumulator_gui[player_index]
119  if not self then
120    return
121  end
122  global.infinity_accumulator_gui[player_index] = nil
123  local window = self.elems.ee_infinity_accumulator_window
124  if not window.valid then
125    return
126  end
127  window.destroy()
128end
129
130--- @param self InfinityAccumulatorGui
131--- @param new_entity LuaEntity?
132local function update_gui(self, new_entity)
133  if not new_entity and not self.entity.valid then
134    destroy_gui(self.player.index)
135    return
136  end
137  if new_entity then
138    self.elems.entity_preview.entity = new_entity
139    self.entity = new_entity
140  end
141
142  local entity = self.entity
143  local priority, mode = get_settings_from_name(entity.name)
144
145  local mode_dropdown = self.elems.mode_dropdown
146  mode_dropdown.selected_index = table.find(modes, mode) --[[@as uint]]
147
148  local priority_dropdown = self.elems.priority_dropdown
149  priority_dropdown.selected_index = table.find(priorities, priority) --[[@as uint]]
150  priority_dropdown.enabled = mode ~= "buffer"
151
152  local slider_value, dropdown_index = get_slider_values(entity.electric_buffer_size, mode)
153
154  local power_slider = self.elems.power_slider
155  power_slider.slider_value = slider_value
156  local textfield = self.elems.power_textfield
157  textfield.text = tostring(slider_value)
158  local dropdown = self.elems.power_dropdown
159  if mode == "buffer" then
160    dropdown.items = si_suffixes_joule
161  else
162    dropdown.items = si_suffixes_watt
163  end
164  dropdown.selected_index = dropdown_index
165end
166
167--- @param entity LuaEntity
168local function update_all_guis(entity)
169  for _, gui in pairs(global.infinity_accumulator_gui) do
170    if not gui.entity.valid or gui.entity == entity then
171      update_gui(gui, entity)
172    end
173  end
174end
175
176local handlers = {
177  --- @param self InfinityAccumulatorGui
178  --- @param e EventData.on_gui_closed|EventData.on_gui_click
179  on_ia_gui_closed = function(self, e)
180    destroy_gui(e.player_index)
181    local player = self.player
182    if not player.valid then
183      return
184    end
185    player.play_sound({ path = "entity-close/ee-infinity-accumulator-tertiary-buffer" })
186  end,
187
188  --- @param self InfinityAccumulatorGui
189  --- @param e EventData.on_gui_selection_state_changed
190  on_ia_gui_mode_dropdown_changed = function(self, e)
191    local entity = self.entity
192    local mode = modes[e.element.selected_index]
193    local priority = "tertiary"
194    if mode ~= "buffer" then
195      priority = get_settings_from_name(entity.name)
196    end
197    local new_entity = change_entity(entity, priority, mode)
198    if not new_entity then
199      return
200    end
201    update_all_guis(new_entity)
202  end,
203
204  --- @param self InfinityAccumulatorGui
205  --- @param e EventData.on_gui_selection_state_changed
206  on_ia_gui_priority_dropdown_changed = function(self, e)
207    local entity = self.entity
208    local priority = priorities[e.element.selected_index]
209    local _, mode = get_settings_from_name(entity.name)
210    local new_entity = change_entity(entity, priority, mode)
211    if not new_entity then
212      return
213    end
214    update_all_guis(new_entity)
215  end,
216
217  --- @param self InfinityAccumulatorGui
218  --- @param e EventData.on_gui_selection_state_changed
219  on_ia_gui_power_slider_changed = function(self, e)
220    local entity = self.entity
221    local slider_value = e.element.slider_value
222    local dropdown_index = self.elems.power_dropdown.selected_index
223    local buffer_size = calc_buffer_size(slider_value, dropdown_index)
224    local _, mode = get_settings_from_name(entity.name)
225    set_entity_settings(entity, mode, buffer_size)
226    update_all_guis(entity)
227  end,
228
229  --- @param self InfinityAccumulatorGui
230  --- @param e EventData.on_gui_text_changed
231  on_ia_gui_power_textfield_changed = function(self, e)
232    local entity = self.entity
233    local textfield = e.element
234    local text = textfield.text
235    local value = tonumber(text)
236    if not value or value < 0 or value >= 1000 then
237      textfield.style = "ee_invalid_slider_textfield"
238      return
239    end
240    textfield.style = "ee_slider_textfield"
241
242    self.elems.power_slider.slider_value = value
243
244    local _, mode = get_settings_from_name(entity.name)
245    local buffer_size = calc_buffer_size(value, self.elems.power_dropdown.selected_index)
246    set_entity_settings(entity, mode, buffer_size)
247    update_all_guis(entity)
248  end,
249
250  --- @param self InfinityAccumulatorGui
251  --- @param e EventData.on_gui_selection_state_changed
252  on_ia_gui_power_dropdown_changed = function(self, e)
253    local entity = self.entity
254    local _, mode = get_settings_from_name(entity.name)
255    local buffer_size = calc_buffer_size(self.elems.power_slider.slider_value, e.element.selected_index)
256    set_entity_settings(entity, mode, buffer_size)
257    update_all_guis(entity)
258  end,
259}
260
261gui.add_handlers(handlers, function(e, handler)
262  local self = global.infinity_accumulator_gui[e.player_index]
263  if not self then
264    return
265  end
266  if not self.entity.valid then
267    return
268  end
269
270  handler(self, e)
271end)
272
273--- @param player LuaPlayer
274--- @param entity LuaEntity
275local function create_gui(player, entity)
276  destroy_gui(player.index)
277
278  local elems = gui.add(player.gui.screen, {
279    type = "frame",
280    name = "ee_infinity_accumulator_window",
281    direction = "vertical",
282    elem_mods = { auto_center = true },
283    handler = { [defines.events.on_gui_closed] = handlers.on_ia_gui_closed },
284    {
285      type = "flow",
286      style = "flib_titlebar_flow",
287      drag_target = "ee_infinity_accumulator_window",
288      {
289        type = "label",
290        style = "frame_title",
291        caption = { "entity-name.ee-infinity-accumulator" },
292        ignored_by_interaction = true,
293      },
294      { type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true },
295      util.close_button(handlers.on_ia_gui_closed),
296    },
297    {
298      type = "frame",
299      style = "entity_frame",
300      direction = "vertical",
301      {
302        type = "frame",
303        style = "deep_frame_in_shallow_frame",
304        {
305          type = "entity-preview",
306          name = "entity_preview",
307          style = "wide_entity_button",
308          elem_mods = { entity = entity },
309        },
310      },
311      {
312        type = "flow",
313        style_mods = { top_margin = 4, vertical_align = "center" },
314        { type = "label", caption = { "gui.ee-mode" } },
315        util.pusher(),
316        {
317          type = "drop-down",
318          name = "mode_dropdown",
319          items = { { "gui.ee-output" }, { "gui.ee-input" }, { "gui.ee-buffer" } },
320          selected_index = 0,
321          handler = {
322            [defines.events.on_gui_selection_state_changed] = handlers.on_ia_gui_mode_dropdown_changed,
323          },
324        },
325      },
326      { type = "line", direction = "horizontal" },
327      {
328        type = "flow",
329        style_mods = { vertical_align = "center" },
330        {
331          type = "label",
332          caption = { "", { "gui.ee-priority" }, " [img=info]" },
333          tooltip = { "gui.ee-ia-priority-description" },
334        },
335        util.pusher(),
336        {
337          type = "drop-down",
338          name = "priority_dropdown",
339          items = { { "gui.ee-primary" }, { "gui.ee-secondary" }, { "gui.ee-tertiary" } },
340          selected_index = 0,
341          handler = {
342            [defines.events.on_gui_selection_state_changed] = handlers.on_ia_gui_priority_dropdown_changed,
343          },
344        },
345      },
346      { type = "line", direction = "horizontal" },
347      {
348        type = "flow",
349        style_mods = { vertical_align = "center" },
350        {
351          type = "label",
352          style_mods = { right_margin = 6 },
353          caption = { "gui.ee-power" },
354        },
355        {
356          type = "slider",
357          name = "power_slider",
358          style_mods = { horizontally_stretchable = true },
359          minimum_value = 0,
360          maximum_value = 999,
361          value = 0,
362          handler = { [defines.events.on_gui_value_changed] = handlers.on_ia_gui_power_slider_changed },
363        },
364        {
365          type = "textfield",
366          name = "power_textfield",
367          style = "ee_slider_textfield",
368          text = "",
369          numeric = true,
370          allow_decimal = true,
371          clear_and_focus_on_right_click = true,
372          handler = { [defines.events.on_gui_text_changed] = handlers.on_ia_gui_power_textfield_changed },
373        },
374        {
375          type = "drop-down",
376          name = "power_dropdown",
377          style_mods = { width = 69 },
378          selected_index = 0,
379          handler = {
380            [defines.events.on_gui_selection_state_changed] = handlers.on_ia_gui_power_dropdown_changed,
381          },
382        },
383      },
384    },
385  })
386
387  player.opened = elems.ee_infinity_accumulator_window
388
389  --- @class InfinityAccumulatorGui
390  local self = {
391    elems = elems,
392    entity = entity,
393    player = player,
394  }
395  global.infinity_accumulator_gui[player.index] = self
396
397  update_gui(self)
398end
399
400--- @param e DestroyedEvent
401local function on_entity_destroyed(e)
402  local entity = e.entity
403  if not entity.valid or not entity_names[entity.name] then
404    return
405  end
406
407  for player_index, gui in pairs(global.infinity_accumulator_gui) do
408    if gui.entity == entity then
409      destroy_gui(player_index)
410    end
411  end
412end
413
414--- @param e EventData.on_entity_settings_pasted
415local function on_entity_settings_pasted(e)
416  local source = e.source
417  if not source.valid or not entity_names[source.name] then
418    return
419  end
420  local destination = e.destination
421  if not destination.valid or not entity_names[destination.name] then
422    return
423  end
424
425  local source_priority, source_mode = get_settings_from_name(source.name)
426  local destination_priority, destination_mode = get_settings_from_name(destination.name)
427  if source_priority == destination_priority and source_mode == destination_mode then
428    return
429  end
430  local new_entity = change_entity(destination, source_priority, source_mode)
431  if not new_entity then
432    return
433  end
434
435  update_all_guis(new_entity)
436end
437
438--- @param e EventData.on_gui_opened
439local function on_gui_opened(e)
440  if e.gui_type ~= defines.gui_type.entity then
441    return
442  end
443  local entity = e.entity
444  if not entity or not entity.valid or not entity_names[entity.name] then
445    return
446  end
447  local player = game.get_player(e.player_index)
448  if not player then
449    return
450  end
451  create_gui(player, entity)
452end
453
454local infinity_accumulator = {}
455
456infinity_accumulator.on_init = function()
457  --- @type table<uint, InfinityAccumulatorGui>
458  global.infinity_accumulator_gui = {}
459end
460
461infinity_accumulator.on_configuration_changed = function()
462  for player_index in pairs(game.players) do
463    destroy_gui(player_index --[[@as uint]])
464  end
465end
466
467infinity_accumulator.events = {
468  [defines.events.on_entity_died] = on_entity_destroyed,
469  [defines.events.on_gui_opened] = on_gui_opened,
470  [defines.events.on_player_mined_entity] = on_entity_destroyed,
471  [defines.events.on_robot_mined_entity] = on_entity_destroyed,
472  [defines.events.script_raised_destroy] = on_entity_destroyed,
473  [defines.events.on_entity_settings_pasted] = on_entity_settings_pasted,
474}
475
476return infinity_accumulator