z_mirror/EditorExtensions

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

Caleb HeuerGet working on 2.12c4827c

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