z_mirror/EditorExtensions

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

Caleb HeuerRe-add pipe <-> combinator copy/paste626d2b5

hidden-items
23.6 KiB768 linesraw
1local format = require("__flib__/format")
2local gui = require("__flib__/gui-lite")
3local math = require("__flib__/math")
4local table = require("__flib__/table")
5
6local shared_constants = require("__EditorExtensions__/shared-constants")
7
8local util = require("__EditorExtensions__/scripts/util")
9
10local crafter_snapping_types = {
11  ["assembling-machine"] = true,
12  ["furnace"] = true,
13  ["rocket-silo"] = true,
14}
15
16--- @alias InfinityPipeMode
17--- | "at-least",
18--- | "at-most",
19--- | "exactly",
20--- | "add",
21--- | "remove",
22
23--- @type table<string, uint>
24local defines_amount_type = {
25  percent = 1,
26  units = 2,
27}
28
29--- @param entity LuaEntity
30--- @param amount_type uint?
31local function store_amount_type(entity, amount_type)
32  global.infinity_pipe_amount_type[entity.unit_number] = amount_type
33end
34
35--- @param entity LuaEntity
36--- @return uint?
37local function get_stored_amount_type(entity)
38  return global.infinity_pipe_amount_type[entity.unit_number]
39end
40
41--- @param entity LuaEntity
42--- @return uint?
43local function remove_stored_amount_type(entity)
44  local value = global.infinity_pipe_amount_type[entity.unit_number]
45  global.infinity_pipe_amount_type[entity.unit_number] = nil
46  return value
47end
48
49--- @param entity LuaEntity
50--- @param new_capacity double
51--- @return LuaEntity?
52local function swap_entity(entity, new_capacity)
53  local amount_type = remove_stored_amount_type(entity)
54  local new_entity = entity.surface.create_entity({
55    name = "ee-infinity-pipe-" .. new_capacity,
56    position = entity.position,
57    direction = entity.direction,
58    force = entity.force,
59    fast_replace = true,
60    create_build_effect_smoke = false,
61    spill = false,
62  })
63  if not new_entity then
64    return
65  end
66  store_amount_type(new_entity, amount_type)
67  return new_entity
68end
69
70--- @param entity BlueprintEntity|LuaEntity
71local function check_is_our_pipe(entity)
72  return string.find(entity.name, "^ee%-infinity%-pipe%-%d+$")
73end
74
75--- @param entity LuaEntity
76local function snap(entity)
77  local own_id = entity.unit_number
78  for _, fluidbox in pairs(entity.fluidbox.get_connections(1)) do
79    if crafter_snapping_types[fluidbox.owner.type] then
80      for i = 1, #fluidbox do
81        --- @cast i uint
82        local connections = fluidbox.get_connections(i)
83        local prototype = fluidbox.get_prototype(i)
84        for j = 1, #connections do
85          if prototype.production_type == "input" and connections[j].owner.unit_number == own_id then
86            local fluid = fluidbox.get_locked_fluid(i)
87            if fluid then
88              entity.set_infinity_pipe_filter({ name = fluid, percentage = 1, mode = "at-least" })
89              return
90            end
91          end
92        end
93      end
94    end
95  end
96end
97
98--- @param filter InfinityPipeFilter?
99--- @return double, double
100local function get_temperature_limits(filter)
101  if filter and filter.name then
102    local prototype = game.fluid_prototypes[filter.name]
103    return prototype.default_temperature, prototype.max_temperature
104  end
105  return 0, 100
106end
107
108-- GUI
109
110--- @class InfinityPipeGui
111--- @field elems InfinityPipeGuiElems
112--- @field entity LuaEntity
113--- @field mode InfinityPipeMode
114--- @field player LuaPlayer
115--- @field temperature double
116
117--- @class InfinityPipeGuiElems
118--- @field ee_infinity_pipe_window LuaGuiElement
119--- @field titlebar_flow LuaGuiElement
120--- @field drag_handle LuaGuiElement
121--- @field capacity_dropdown LuaGuiElement
122--- @field amount_progressbar LuaGuiElement
123--- @field entity_preview LuaGuiElement
124--- @field filter_button LuaGuiElement
125--- @field amount_slider LuaGuiElement
126--- @field amount_textfield LuaGuiElement
127--- @field amount_type_dropdown LuaGuiElement
128--- @field mode_radio_button_at_least LuaGuiElement
129--- @field mode_radio_button_at_most LuaGuiElement
130--- @field mode_radio_button_exactly LuaGuiElement
131--- @field mode_radio_button_add LuaGuiElement
132--- @field mode_radio_button_remove LuaGuiElement
133--- @field temperature_slider LuaGuiElement
134--- @field temperature_textfield LuaGuiElement
135
136--- @param player_index uint
137local function destroy_gui(player_index)
138  local self = global.infinity_pipe_gui[player_index]
139  if not self then
140    return
141  end
142  global.infinity_pipe_gui[player_index] = nil
143  local window = self.elems.ee_infinity_pipe_window
144  if window.valid then
145    window.destroy()
146  end
147end
148
149--- @param self InfinityPipeGui
150local function update_fluid_content_bar(self)
151  local bar = self.elems.amount_progressbar
152  local fluidbox = self.entity.fluidbox
153  local capacity = fluidbox.get_capacity(1)
154  local content = fluidbox[1] or { amount = 0 }
155
156  local caption
157  if content.amount < 100 then
158    caption = string.format("%.1f", content.amount)
159  else
160    caption = format.number(math.floor(content.amount))
161  end
162  bar.caption = caption
163  bar.value = content.amount / capacity
164
165  if not content.name then
166    return
167  end
168
169  local bar_color = game.fluid_prototypes[content.name].base_color
170  -- Calculate luminance of the background color
171  -- Source: https://stackoverflow.com/questions/596216/formula-to-determine-perceived-brightness-of-rgb-color
172  local luminance = (0.2126 * bar_color.r) + (0.7152 * bar_color.g) + (0.0722 * bar_color.b)
173  if luminance > 0.55 then
174    bar.style = "ee_infinity_pipe_progressbar"
175  else
176    bar.style = "ee_infinity_pipe_progressbar_light_text"
177  end
178  bar.style.color = bar_color
179end
180
181--- @param self InfinityPipeGui
182--- @param new_entity LuaEntity?
183--- @param reset_temperature boolean?
184local function update_gui(self, new_entity, reset_temperature)
185  if not new_entity and not self.entity.valid then
186    destroy_gui(self.player.index)
187    return
188  end
189  local elems = self.elems
190  if new_entity then
191    elems.entity_preview.entity = new_entity
192    self.entity = new_entity
193  end
194  local entity = self.entity
195  local filter = entity.get_infinity_pipe_filter() or {}
196  if filter.mode then
197    self.mode = filter.mode
198  end
199  if filter.temperature then
200    self.temperature = filter.temperature
201  elseif reset_temperature then
202    self.temperature = 0
203  end
204
205  local capacity = entity.fluidbox.get_capacity(1)
206  elems.capacity_dropdown.selected_index = table.find(shared_constants.infinity_pipe_capacities, capacity) --[[@as uint]]
207
208  elems.filter_button.elem_value = filter.name
209
210  local amount_type = get_stored_amount_type(entity) or 1
211  elems.amount_slider.slider_value = filter.percentage or 0
212  elems.amount_textfield.style = "ee_slider_textfield"
213  local percentage = filter.percentage or 0
214  if amount_type == defines_amount_type.percent then
215    elems.amount_textfield.text = tostring(math.floor(percentage * 100))
216  else
217    elems.amount_textfield.text = tostring(math.floor(percentage * capacity))
218  end
219  elems.amount_type_dropdown.selected_index = amount_type
220
221  local mode = self.mode
222  elems.mode_radio_button_at_least.state = mode == "at-least"
223  elems.mode_radio_button_at_most.state = mode == "at-most"
224  elems.mode_radio_button_exactly.state = mode == "exactly"
225  elems.mode_radio_button_add.state = mode == "add"
226  elems.mode_radio_button_remove.state = mode == "remove"
227
228  local min, max = get_temperature_limits(filter)
229  if min == max then
230    elems.temperature_slider.enabled = false
231    elems.temperature_textfield.enabled = false
232    elems.temperature_slider.set_slider_minimum_maximum(min, max + 1)
233    elems.temperature_slider.slider_value = min
234  else
235    elems.temperature_slider.enabled = true
236    elems.temperature_textfield.enabled = true
237    elems.temperature_slider.set_slider_minimum_maximum(min, max)
238    elems.temperature_slider.slider_value = self.temperature
239  end
240  elems.temperature_textfield.style = "ee_slider_textfield"
241  elems.temperature_textfield.text = tostring(self.temperature)
242
243  update_fluid_content_bar(self)
244end
245
246--- @param entity LuaEntity
247--- @param reset_temperature boolean?
248local function update_all_guis(entity, reset_temperature)
249  for _, gui in pairs(global.infinity_pipe_gui) do
250    if not gui.entity.valid then
251      update_gui(gui, entity, reset_temperature)
252    elseif gui.entity == entity then
253      update_gui(gui, nil, reset_temperature)
254    end
255  end
256end
257
258local handlers = {
259  --- @param self InfinityPipeGui
260  --- @param e EventData.on_gui_closed|EventData.on_gui_click
261  on_ip_gui_closed = function(self, e)
262    destroy_gui(e.player_index)
263    local player = self.player
264    if not player.valid then
265      return
266    end
267    player.play_sound({ path = "entity-close/ee-infinity-accumulator-tertiary-buffer" })
268  end,
269
270  --- @param self InfinityPipeGui
271  --- @param e EventData.on_gui_selection_state_changed
272  on_ip_gui_capacity_dropdown_selection_state_changed = function(self, e)
273    local entity = self.entity
274    local new_capacity = shared_constants.infinity_pipe_capacities[e.element.selected_index]
275    if new_capacity == entity.fluidbox.get_capacity(1) then
276      return
277    end
278    local new_entity = swap_entity(entity, new_capacity)
279    if not new_entity then
280      return
281    end
282    update_all_guis(new_entity)
283  end,
284
285  --- @param self InfinityPipeGui
286  --- @param e EventData.on_gui_elem_changed
287  on_ip_gui_filter_changed = function(self, e)
288    local entity = self.entity
289    local elem = e.element.elem_value --[[@as string?]]
290    if elem then
291      if not filter then
292        filter = { mode = self.mode, percentage = 1 }
293      end
294      filter.name = elem
295      entity.set_infinity_pipe_filter(filter)
296    else
297      entity.set_infinity_pipe_filter(nil)
298    end
299    update_all_guis(entity, not elem)
300  end,
301
302  --- @param self InfinityPipeGui
303  --- @param e EventData.on_gui_elem_changed
304  on_ip_gui_amount_slider_value_changed = function(self, e)
305    local entity = self.entity
306    local value = e.element.slider_value
307    local filter = entity.get_infinity_pipe_filter()
308    if not filter then
309      -- Textfield should always match slider
310      local textfield = self.elems.amount_textfield
311      textfield.style = "ee_slider_textfield"
312      textfield.text = tostring(math.floor(value * 100))
313      return
314    end
315    filter.percentage = value
316    entity.set_infinity_pipe_filter(filter)
317    update_all_guis(entity)
318  end,
319
320  --- @param self InfinityPipeGui
321  --- @param e EventData.on_gui_text_changed
322  on_ip_gui_amount_textfield_changed = function(self, e)
323    local entity = self.entity
324    local textfield = e.element
325    local text = textfield.text
326    local value = tonumber(text)
327    local max = 100
328    local amount_type = get_stored_amount_type(entity)
329    if amount_type == defines_amount_type.units then
330      max = entity.fluidbox.get_capacity(1)
331    end
332    if not value or value < 0 or value > max then
333      textfield.style = "ee_invalid_slider_textfield"
334      return
335    end
336    textfield.style = "ee_slider_textfield"
337
338    local percentage = value / 100
339    if amount_type == defines_amount_type.units then
340      local capacity = entity.fluidbox.get_capacity(1)
341      percentage = value / capacity
342    end
343
344    self.elems.amount_slider.slider_value = percentage
345
346    local filter = entity.get_infinity_pipe_filter()
347    if filter then
348      filter.percentage = percentage
349      entity.set_infinity_pipe_filter(filter)
350      update_all_guis(entity)
351    end
352  end,
353
354  --- @param self InfinityPipeGui
355  --- @param e EventData.on_gui_selection_state_changed
356  on_ip_gui_amount_type_changed = function(self, e)
357    local amount_type = e.element.selected_index
358    store_amount_type(self.entity, amount_type)
359    update_all_guis(self.entity)
360  end,
361
362  --- @param self InfinityPipeGui
363  --- @param e EventData.on_gui_checked_state_changed
364  on_ip_gui_mode_radio_button_selected = function(self, e)
365    local mode = e.element.tags.mode --[[@as string]]
366    self.mode = mode
367
368    local entity = self.entity
369    local filter = entity.get_infinity_pipe_filter()
370    if filter then
371      filter.mode = mode
372      entity.set_infinity_pipe_filter(filter)
373    end
374
375    update_all_guis(entity)
376  end,
377
378  --- @param self InfinityPipeGui
379  --- @param e EventData.on_gui_value_changed
380  on_ip_gui_temperature_slider_value_changed = function(self, e)
381    local entity = self.entity
382    local value = math.floor(e.element.slider_value)
383    local filter = entity.get_infinity_pipe_filter()
384    if not filter then
385      -- Textfield should always match slider
386      local textfield = self.elems.temperature_textfield
387      textfield.style = "ee_slider_textfield"
388      textfield.text = tostring(value)
389      return
390    end
391    filter.temperature = value
392    entity.set_infinity_pipe_filter(filter)
393    update_all_guis(entity)
394  end,
395
396  --- @param self InfinityPipeGui
397  --- @param e EventData.on_gui_text_changed
398  on_ip_gui_temperature_textfield_changed = function(self, e)
399    local entity = self.entity
400    local textfield = e.element
401    local text = textfield.text
402    local value = tonumber(text)
403
404    local filter = entity.get_infinity_pipe_filter()
405    local min, max = get_temperature_limits(filter)
406
407    if not value or value < min or value > max then
408      textfield.style = "ee_invalid_slider_textfield"
409      return
410    end
411    textfield.style = "ee_slider_textfield"
412
413    self.elems.temperature_slider.slider_value = value
414
415    if filter then
416      filter.temperature = value
417      entity.set_infinity_pipe_filter(filter)
418      update_all_guis(entity)
419    end
420  end,
421}
422
423gui.add_handlers(handlers, function(e, handler)
424  local self = global.infinity_pipe_gui[e.player_index]
425  if not self then
426    return
427  end
428  if not self.entity.valid then
429    return
430  end
431  handler(self, e)
432end)
433
434--- @param player LuaPlayer
435--- @param entity LuaEntity
436local function create_gui(player, entity)
437  destroy_gui(player.index)
438
439  --- @type InfinityPipeGuiElems
440  local elems = gui.add(player.gui.screen, {
441    type = "frame",
442    name = "ee_infinity_pipe_window",
443    direction = "vertical",
444    elem_mods = { auto_center = true },
445    handler = { [defines.events.on_gui_closed] = handlers.on_ip_gui_closed },
446    {
447      type = "flow",
448      name = "titlebar_flow",
449      style = "flib_titlebar_flow",
450      drag_target = "ee_infinity_pipe_window",
451      {
452        type = "label",
453        style = "frame_title",
454        caption = { "entity-name.ee-infinity-pipe" },
455        ignored_by_interaction = true,
456      },
457      { type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true },
458      util.close_button(handlers.on_ip_gui_closed),
459    },
460    {
461      type = "frame",
462      style = "entity_frame",
463      direction = "vertical",
464      {
465        type = "frame",
466        style = "deep_frame_in_shallow_frame",
467        style_mods = { bottom_margin = 4 },
468        {
469          type = "entity-preview",
470          name = "entity_preview",
471          style = "wide_entity_button",
472          elem_mods = { entity = entity },
473        },
474      },
475      {
476        type = "flow",
477        style_mods = { horizontal_spacing = 8, vertical_align = "center" },
478        {
479          type = "progressbar",
480          name = "amount_progressbar",
481          style = "ee_infinity_pipe_progressbar",
482        },
483        { type = "label", caption = "/" },
484        {
485          type = "drop-down",
486          name = "capacity_dropdown",
487          items = table.map(shared_constants.infinity_pipe_capacities, function(capacity)
488            return format.number(capacity)
489          end),
490          selected_index = 0,
491          handler = {
492            [defines.events.on_gui_selection_state_changed] = handlers.on_ip_gui_capacity_dropdown_selection_state_changed,
493          },
494        },
495      },
496      { type = "line", direction = "horizontal" },
497      {
498        type = "flow",
499        style = "centering_horizontal_flow",
500        direction = "horizontal",
501        {
502          type = "choose-elem-button",
503          name = "filter_button",
504          style = "flib_standalone_slot_button_default",
505          elem_type = "fluid",
506          handler = { [defines.events.on_gui_elem_changed] = handlers.on_ip_gui_filter_changed },
507        },
508        {
509          type = "slider",
510          name = "amount_slider",
511          style_mods = { horizontally_stretchable = true, margin = { 0, 8, 0, 8 } },
512          minimum_value = 0,
513          maximum_value = 1,
514          value_step = 0.01,
515          value = 0,
516          handler = { [defines.events.on_gui_value_changed] = handlers.on_ip_gui_amount_slider_value_changed },
517        },
518        {
519          type = "textfield",
520          name = "amount_textfield",
521          style = "slider_value_textfield",
522          numeric = true,
523          allow_decimal = true,
524          clear_and_focus_on_right_click = true,
525          text = "0",
526          handler = { [defines.events.on_gui_text_changed] = handlers.on_ip_gui_amount_textfield_changed },
527        },
528        {
529          type = "drop-down",
530          name = "amount_type_dropdown",
531          style_mods = { width = 55 },
532          items = { { "gui-infinity-pipe.percent" }, { "gui-infinity-pipe.ee-units" } },
533          selected_index = 1,
534          handler = { [defines.events.on_gui_selection_state_changed] = handlers.on_ip_gui_amount_type_changed },
535        },
536      },
537      {
538        type = "flow",
539        style_mods = { horizontal_spacing = 0 },
540        direction = "horizontal",
541        util.mode_radio_button("at-least", handlers.on_ip_gui_mode_radio_button_selected),
542        util.pusher(),
543        util.mode_radio_button("at-most", handlers.on_ip_gui_mode_radio_button_selected),
544        util.pusher(),
545        util.mode_radio_button("exactly", handlers.on_ip_gui_mode_radio_button_selected),
546        util.pusher(),
547        util.mode_radio_button("add", handlers.on_ip_gui_mode_radio_button_selected),
548        util.pusher(),
549        util.mode_radio_button("remove", handlers.on_ip_gui_mode_radio_button_selected),
550      },
551      { type = "line", direction = "horizontal" },
552      {
553        type = "flow",
554        style = "centering_horizontal_flow",
555        direction = "horizontal",
556        { type = "label", caption = { "gui-infinity-pipe.temperature" } },
557        {
558          type = "slider",
559          name = "temperature_slider",
560          style_mods = { horizontally_stretchable = true, margin = { 0, 8, 0, 8 } },
561          minimum_value = 0,
562          maximum_value = 100,
563          value = 0,
564          handler = {
565            [defines.events.on_gui_value_changed] = handlers.on_ip_gui_temperature_slider_value_changed,
566          },
567        },
568        {
569          type = "textfield",
570          name = "temperature_textfield",
571          style = "slider_value_textfield",
572          clear_and_focus_on_right_click = true,
573          text = "0",
574          handler = {
575            [defines.events.on_gui_text_changed] = handlers.on_ip_gui_temperature_textfield_changed,
576          },
577        },
578      },
579    },
580  })
581
582  player.opened = elems.ee_infinity_pipe_window
583
584  --- @type InfinityPipeGui
585  local self = {
586    elems = elems,
587    entity = entity,
588    mode = "at-least",
589    temperature = 0,
590    player = player,
591  }
592  global.infinity_pipe_gui[player.index] = self
593
594  update_gui(self)
595end
596
597-- COPY/PASTE
598
599--- @param pipe LuaEntity
600--- @param combinator LuaEntity
601local function copy_from_pipe_to_combinator(pipe, combinator)
602  local filter = pipe.get_infinity_pipe_filter()
603  if not filter then
604    return
605  end
606  local cb = combinator.get_or_create_control_behavior() --[[@as LuaConstantCombinatorControlBehavior]]
607  cb.set_signal(1, {
608    signal = { type = "fluid", name = filter.name },
609    count = 1,
610  })
611  update_all_guis(pipe)
612end
613
614--- @param combinator LuaEntity
615--- @param pipe LuaEntity
616local function copy_from_combinator_to_pipe(combinator, pipe)
617  local cb = combinator.get_control_behavior() --[[@as LuaConstantCombinatorControlBehavior?]]
618  if not cb then
619    return
620  end
621  local signal = cb.get_signal(1)
622  if signal.signal and signal.signal.type == "fluid" then
623    pipe.set_infinity_pipe_filter({ type = "fluid", name = signal.signal.name, percentage = 1 })
624  else
625    pipe.set_infinity_pipe_filter(nil)
626  end
627  update_all_guis(pipe)
628end
629
630--- @param source LuaEntity
631--- @param destination LuaEntity
632local function copy_from_pipe_to_pipe(source, destination)
633  if source.name ~= destination.name then
634    local new_destination = swap_entity(destination, source.fluidbox.get_capacity(1))
635    if not new_destination then
636      return
637    end
638    destination = new_destination
639  end
640  local source_amount_type = get_stored_amount_type(source)
641  local destination_amount_type = get_stored_amount_type(destination)
642  if source_amount_type ~= destination_amount_type then
643    store_amount_type(destination, source_amount_type)
644  end
645  update_all_guis(destination)
646end
647
648-- EVENTS
649
650--- @param e BuiltEvent
651local function on_entity_built(e)
652  local entity = e.created_entity or e.entity or e.destination
653  if not entity or not entity.valid or not check_is_our_pipe(entity) then
654    return
655  end
656
657  local tags = e.tags
658  if tags and tags.EditorExtensions then
659    store_amount_type(entity, tags.EditorExtensions.amount_type --[[@as uint]])
660  end
661
662  if e.name == defines.events.on_built_entity then
663    snap(entity)
664  end
665end
666
667--- @param e DestroyedEvent
668local function on_entity_destroyed(e)
669  local entity = e.entity
670  if not entity.valid or not check_is_our_pipe(entity) then
671    return
672  end
673  remove_stored_amount_type(e.entity)
674end
675
676--- @param e EventData.on_gui_opened
677local function on_gui_opened(e)
678  local entity = e.entity
679  if not entity or not entity.valid or not check_is_our_pipe(entity) then
680    return
681  end
682  local player = game.get_player(e.player_index)
683  if not player then
684    return
685  end
686  create_gui(player, entity)
687end
688
689--- @param e EventData.on_player_setup_blueprint
690local function on_player_setup_blueprint(e)
691  local blueprint = util.get_blueprint(e)
692  if not blueprint then
693    return
694  end
695
696  local entities = blueprint.get_blueprint_entities()
697  if not entities then
698    return
699  end
700  for i, entity in pairs(entities) do
701    --- @cast i uint
702    if not check_is_our_pipe(entity) then
703      goto continue
704    end
705    local real_entity = e.surface.find_entity(entity.name, entity.position)
706    if not real_entity then
707      goto continue
708    end
709    blueprint.set_blueprint_entity_tag(
710      i,
711      "EditorExtensions",
712      { amount_type = global.infinity_pipe_amount_type[real_entity.unit_number] }
713    )
714    ::continue::
715  end
716end
717
718--- @param e EventData.on_entity_settings_pasted
719local function on_entity_settings_pasted(e)
720  local source, destination = e.source, e.destination
721  local source_is_pipe, destination_is_pipe = check_is_our_pipe(source), check_is_our_pipe(destination)
722  if source_is_pipe and destination.name == "constant-combinator" then
723    copy_from_pipe_to_combinator(source, destination)
724  elseif source.name == "constant-combinator" and destination_is_pipe then
725    copy_from_combinator_to_pipe(source, destination)
726  elseif source_is_pipe and destination_is_pipe then
727    copy_from_pipe_to_pipe(source, destination)
728  end
729end
730
731local infinity_pipe = {}
732
733infinity_pipe.on_init = function()
734  global.infinity_pipe_gui = {}
735  --- @type table<uint, uint>
736  global.infinity_pipe_amount_type = {}
737end
738
739infinity_pipe.on_configuration_changed = function()
740  for player_index in pairs(game.players) do
741    destroy_gui(player_index --[[@as uint]])
742  end
743end
744
745infinity_pipe.events = {
746  [defines.events.on_built_entity] = on_entity_built,
747  [defines.events.on_entity_cloned] = on_entity_built,
748  [defines.events.on_entity_died] = on_entity_destroyed,
749  [defines.events.on_entity_settings_pasted] = on_entity_settings_pasted,
750  [defines.events.on_gui_opened] = on_gui_opened,
751  [defines.events.on_player_mined_entity] = on_entity_destroyed,
752  [defines.events.on_player_setup_blueprint] = on_player_setup_blueprint,
753  [defines.events.on_robot_built_entity] = on_entity_built,
754  [defines.events.on_robot_mined_entity] = on_entity_destroyed,
755  [defines.events.script_raised_built] = on_entity_built,
756  [defines.events.script_raised_destroy] = on_entity_destroyed,
757  [defines.events.script_raised_revive] = on_entity_built,
758}
759
760infinity_pipe.on_nth_tick = {
761  [1] = function()
762    for _, gui in pairs(global.infinity_pipe_gui) do
763      update_fluid_content_bar(gui)
764    end
765  end,
766}
767
768return infinity_pipe