z_mirror/EditorExtensions

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

Caleb HeuerFix being unablet to put decimals in the accumulator GUI6242597

hidden-items
14.7 KiB479 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 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    if string.sub(text, #text) == "." then
242      return
243    end
244
245    self.elems.power_slider.slider_value = value
246
247    local _, mode = get_settings_from_name(entity.name)
248    local buffer_size = calc_buffer_size(value, self.elems.power_dropdown.selected_index)
249    set_entity_settings(entity, mode, buffer_size)
250    update_all_guis(entity)
251  end,
252
253  --- @param self InfinityAccumulatorGui
254  --- @param e EventData.on_gui_selection_state_changed
255  on_ia_gui_power_dropdown_changed = function(self, e)
256    local entity = self.entity
257    local _, mode = get_settings_from_name(entity.name)
258    local buffer_size = calc_buffer_size(self.elems.power_slider.slider_value, e.element.selected_index)
259    set_entity_settings(entity, mode, buffer_size)
260    update_all_guis(entity)
261  end,
262}
263
264gui.add_handlers(handlers, function(e, handler)
265  local self = global.infinity_accumulator_gui[e.player_index]
266  if not self then
267    return
268  end
269  if not self.entity.valid then
270    return
271  end
272
273  handler(self, e)
274end)
275
276--- @param player LuaPlayer
277--- @param entity LuaEntity
278local function create_gui(player, entity)
279  destroy_gui(player.index)
280
281  local elems = gui.add(player.gui.screen, {
282    type = "frame",
283    name = "ee_infinity_accumulator_window",
284    direction = "vertical",
285    elem_mods = { auto_center = true },
286    handler = { [defines.events.on_gui_closed] = handlers.on_ia_gui_closed },
287    {
288      type = "flow",
289      style = "flib_titlebar_flow",
290      drag_target = "ee_infinity_accumulator_window",
291      {
292        type = "label",
293        style = "frame_title",
294        caption = { "entity-name.ee-infinity-accumulator" },
295        ignored_by_interaction = true,
296      },
297      { type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true },
298      util.close_button(handlers.on_ia_gui_closed),
299    },
300    {
301      type = "frame",
302      style = "entity_frame",
303      direction = "vertical",
304      {
305        type = "frame",
306        style = "deep_frame_in_shallow_frame",
307        {
308          type = "entity-preview",
309          name = "entity_preview",
310          style = "wide_entity_button",
311          elem_mods = { entity = entity },
312        },
313      },
314      {
315        type = "flow",
316        style_mods = { top_margin = 4, vertical_align = "center" },
317        { type = "label", caption = { "gui.ee-mode" } },
318        util.pusher(),
319        {
320          type = "drop-down",
321          name = "mode_dropdown",
322          items = { { "gui.ee-output" }, { "gui.ee-input" }, { "gui.ee-buffer" } },
323          selected_index = 0,
324          handler = {
325            [defines.events.on_gui_selection_state_changed] = handlers.on_ia_gui_mode_dropdown_changed,
326          },
327        },
328      },
329      { type = "line", direction = "horizontal" },
330      {
331        type = "flow",
332        style_mods = { vertical_align = "center" },
333        {
334          type = "label",
335          caption = { "", { "gui.ee-priority" }, " [img=info]" },
336          tooltip = { "gui.ee-ia-priority-description" },
337        },
338        util.pusher(),
339        {
340          type = "drop-down",
341          name = "priority_dropdown",
342          items = { { "gui.ee-primary" }, { "gui.ee-secondary" }, { "gui.ee-tertiary" } },
343          selected_index = 0,
344          handler = {
345            [defines.events.on_gui_selection_state_changed] = handlers.on_ia_gui_priority_dropdown_changed,
346          },
347        },
348      },
349      { type = "line", direction = "horizontal" },
350      {
351        type = "flow",
352        style_mods = { vertical_align = "center" },
353        {
354          type = "label",
355          style_mods = { right_margin = 6 },
356          caption = { "gui.ee-power" },
357        },
358        {
359          type = "slider",
360          name = "power_slider",
361          style_mods = { horizontally_stretchable = true },
362          minimum_value = 0,
363          maximum_value = 999,
364          value = 0,
365          handler = { [defines.events.on_gui_value_changed] = handlers.on_ia_gui_power_slider_changed },
366        },
367        {
368          type = "textfield",
369          name = "power_textfield",
370          style = "ee_slider_textfield",
371          text = "",
372          numeric = true,
373          allow_decimal = true,
374          clear_and_focus_on_right_click = true,
375          handler = { [defines.events.on_gui_text_changed] = handlers.on_ia_gui_power_textfield_changed },
376        },
377        {
378          type = "drop-down",
379          name = "power_dropdown",
380          style_mods = { width = 69 },
381          selected_index = 0,
382          handler = {
383            [defines.events.on_gui_selection_state_changed] = handlers.on_ia_gui_power_dropdown_changed,
384          },
385        },
386      },
387    },
388  })
389
390  player.opened = elems.ee_infinity_accumulator_window
391
392  --- @class InfinityAccumulatorGui
393  local self = {
394    elems = elems,
395    entity = entity,
396    player = player,
397  }
398  global.infinity_accumulator_gui[player.index] = self
399
400  update_gui(self)
401end
402
403--- @param e DestroyedEvent
404local function on_entity_destroyed(e)
405  local entity = e.entity
406  if not entity.valid or not entity_names[entity.name] then
407    return
408  end
409
410  for player_index, gui in pairs(global.infinity_accumulator_gui) do
411    if gui.entity == entity then
412      destroy_gui(player_index)
413    end
414  end
415end
416
417--- @param e EventData.on_entity_settings_pasted
418local function on_entity_settings_pasted(e)
419  local source = e.source
420  if not source.valid or not entity_names[source.name] then
421    return
422  end
423  local destination = e.destination
424  if not destination.valid or not entity_names[destination.name] then
425    return
426  end
427
428  local source_priority, source_mode = get_settings_from_name(source.name)
429  local destination_priority, destination_mode = get_settings_from_name(destination.name)
430  if source_priority == destination_priority and source_mode == destination_mode then
431    return
432  end
433  local new_entity = change_entity(destination, source_priority, source_mode)
434  if not new_entity then
435    return
436  end
437
438  update_all_guis(new_entity)
439end
440
441--- @param e EventData.on_gui_opened
442local function on_gui_opened(e)
443  if e.gui_type ~= defines.gui_type.entity then
444    return
445  end
446  local entity = e.entity
447  if not entity or not entity.valid or not entity_names[entity.name] then
448    return
449  end
450  local player = game.get_player(e.player_index)
451  if not player then
452    return
453  end
454  create_gui(player, entity)
455end
456
457local infinity_accumulator = {}
458
459infinity_accumulator.on_init = function()
460  --- @type table<uint, InfinityAccumulatorGui>
461  global.infinity_accumulator_gui = {}
462end
463
464infinity_accumulator.on_configuration_changed = function()
465  for player_index in pairs(game.players) do
466    destroy_gui(player_index --[[@as uint]])
467  end
468end
469
470infinity_accumulator.events = {
471  [defines.events.on_entity_died] = on_entity_destroyed,
472  [defines.events.on_entity_settings_pasted] = on_entity_settings_pasted,
473  [defines.events.on_gui_opened] = on_gui_opened,
474  [defines.events.on_player_mined_entity] = on_entity_destroyed,
475  [defines.events.on_robot_mined_entity] = on_entity_destroyed,
476  [defines.events.script_raised_destroy] = on_entity_destroyed,
477}
478
479return infinity_accumulator