z_mirror/Krastorio2

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

Caleb HeuerRun stylua02265e00

k2so-fixes
17.3 KiB527 linesraw
1local flib_gui = require("__flib__.gui")
2local flib_math = require("__flib__.math")
3local flib_table = require("__flib__.table")
4
5local util = require("scripts.util")
6
7-- the full energy buffer on normal quality
8local energy_per_teleport =
9  prototypes.entity["kr-planetary-teleporter"].electric_energy_source_prototype.buffer_capacity
10
11--- @class PlanetaryTeleporterGui
12--- @field elems table<string, LuaGuiElement>
13--- @field entity LuaEntity
14--- @field data PlanetaryTeleporterData
15--- @field player LuaPlayer
16--- @field search_open boolean
17--- @field search_query string
18
19--- @param player LuaPlayer
20--- @param from LuaEntity
21--- @param to LuaEntity
22local function teleport_player(player, from, to)
23  from.energy = from.energy - energy_per_teleport
24  to.energy = to.energy - energy_per_teleport
25  local position = to.position
26  position.y = position.y + 1.1
27  player.teleport(position, to.surface)
28  from.surface.play_sound({
29    path = "kr-planetary-teleporter-effect-sound",
30    volume_modifier = 0.8,
31    position = from.position,
32  })
33  to.surface.play_sound({
34    path = "kr-planetary-teleporter-effect-sound",
35    position = to.position,
36    volume_modifier = 1,
37  })
38end
39
40--- @param self PlanetaryTeleporterGui
41local function filter_destinations(self)
42  local search_query = self.search_query
43  for _, frame in pairs(self.elems.destinations_table.children) do
44    if search_query == "" then
45      frame.visible = true
46      goto continue
47    end
48    local unit_number = tonumber(frame.name) --[[@as uint]]
49    local data = storage.planetary_teleporter[unit_number]
50    if not data then
51      frame.destroy()
52      goto continue
53    end
54    if data.name and string.find(string.lower(data.name), search_query, nil, true) then
55      frame.visible = true
56    else
57      frame.visible = false
58    end
59    ::continue::
60  end
61end
62
63--- @param self PlanetaryTeleporterGui
64--- @param by_click boolean
65local function toggle_search(self, by_click)
66  local search_button = self.elems.search_button
67  if not by_click then
68    search_button.toggled = not search_button.toggled
69  end
70  self.search_open = search_button.toggled
71
72  local search_textfield = self.elems.search_textfield
73  search_textfield.visible = self.search_open
74  if self.search_open then
75    search_textfield.focus()
76    search_textfield.select_all()
77  else
78    self.search_query = ""
79  end
80
81  filter_destinations(self)
82end
83
84--- @param self PlanetaryTeleporterGui
85local function destroy_gui(self)
86  local window = self.elems.kr_planetary_teleporter_window
87  if window.valid then
88    window.destroy()
89    if self.player.opened == window then
90      self.player.opened = nil
91    end
92  end
93  storage.planetary_teleporter_gui[self.player.index] = nil
94end
95
96--- @param self PlanetaryTeleporterGui
97local function update_name_label(self)
98  self.elems.name_label.caption = self.data.name or { "gui.kr-planetary-teleporter-unnamed" }
99end
100
101--- @param e EventData.on_gui_closed|EventData.on_gui_click
102local function on_window_closed(e)
103  local self = storage.planetary_teleporter_gui[e.player_index]
104  if not self then
105    return
106  end
107  if self.search_open then
108    toggle_search(self, false)
109    self.player.opened = self.elems.kr_planetary_teleporter_window
110    return
111  end
112  destroy_gui(self)
113end
114
115--- @param e EventData.on_gui_text_changed
116local function on_search_textfield_text_changed(e)
117  local self = storage.planetary_teleporter_gui[e.player_index]
118  if not self then
119    return
120  end
121  self.search_query = e.element.text
122  filter_destinations(self)
123end
124
125--- @param e EventData.on_gui_click|EventData.CustomInputEvent
126local function on_search_button_clicked(e)
127  local self = storage.planetary_teleporter_gui[e.player_index]
128  if not self then
129    return
130  end
131  toggle_search(self, true)
132end
133
134--- @param e EventData.on_gui_click
135local function on_rename_button_clicked(e)
136  local self = storage.planetary_teleporter_gui[e.player_index]
137  if not self or not self.elems.kr_planetary_teleporter_window.valid then
138    return
139  end
140  local textfield = self.elems.name_textfield
141  local label = self.elems.name_label
142  if textfield.visible then
143    local text = textfield.text
144    if #text > 0 then
145      self.data.name = text
146    else
147      self.data.name = nil
148    end
149    update_name_label(self)
150    label.visible = true
151    textfield.visible = false
152  else
153    label.visible = false
154    textfield.text = self.data.name or ""
155    textfield.visible = true
156    textfield.select_all()
157    textfield.focus()
158  end
159end
160
161--- @param e EventData.on_gui_confirmed
162local function on_name_textfield_confirmed(e)
163  -- Just pass through the logic because it's all the same anyway.
164  on_rename_button_clicked(e --[[@as EventData.on_gui_click]])
165end
166
167--- @param e EventData.on_gui_click
168local function on_teleport_button_clicked(e)
169  local self = storage.planetary_teleporter_gui[e.player_index]
170  if not self or not self.elems.kr_planetary_teleporter_window.valid then
171    return
172  end
173  local target_unit_number = e.element.tags.unit_number --[[@as UnitNumber]]
174  assert(target_unit_number, "target_unit_number is nil")
175  local target_data = storage.planetary_teleporter[target_unit_number]
176  assert(target_data, "target_data is nil")
177  local players = storage.planetary_teleporter_players[
178    target_data.entities.turret.unit_number --[[@as uint]]
179  ]
180  if players then
181    util.flying_text_with_sound(self.player, { "gui.kr-planetary-teleporter-destination-blocked" })
182    return
183  end
184  local source_unit_number = self.entity.unit_number
185  assert(source_unit_number, "source_unit_number is nil")
186  local source_data = storage.planetary_teleporter[source_unit_number]
187  teleport_player(self.player, source_data.entities.base, target_data.entities.base)
188  destroy_gui(self)
189end
190
191--- @type table<defines.entity_status, { sprite: SpritePath, label: LocalisedString }>
192local status_sprites = {
193  [defines.entity_status.charging] = { label = { "entity-status.charging" }, sprite = "utility/status_yellow" },
194  [defines.entity_status.fully_charged] = {
195    label = { "entity-status.fully-charged" },
196    sprite = "utility/status_working",
197  },
198  [defines.entity_status.no_power] = { label = { "entity-status.no-power" }, sprite = "utility/status_not_working" },
199  [defines.entity_status.normal] = { label = { "entity-status.normal" }, sprite = "utility/status_working" },
200  [defines.entity_status.marked_for_deconstruction] = {
201    label = { "entity-status.marked-for-deconstruction" },
202    sprite = "utility/status_not_working",
203  },
204}
205
206--- @param self PlanetaryTeleporterGui
207local function update_gui(self)
208  if not self.entity.valid then
209    destroy_gui(self)
210    return
211  end
212  local buffer_capacity = self.entity.prototype.electric_energy_source_prototype.buffer_capacity
213  local status = self.entity.status
214  local status_properties = status_sprites[status] or {}
215  self.elems.status_icon.sprite = status_properties.sprite
216  self.elems.status_label.caption = status_properties.label
217
218  local charge = self.entity.energy / buffer_capacity
219  self.elems.charge_progressbar.value = charge
220  self.elems.charge_progressbar.caption = { "format-percent", flib_math.round(charge * 100) }
221
222  local players = storage.planetary_teleporter_players[self.data.entities.turret.unit_number]
223  local standing_on_teleporter = players and flib_table.find(players, self.player.index) or false
224  local too_many_players = players and #players > 1 or false
225  local source_fully_charged = flib_math.round(charge, 0.1) >= 1
226  local fully_operational = standing_on_teleporter and source_fully_charged and not too_many_players
227
228  if fully_operational then
229    self.elems.destinations_status_icon.sprite = "flib_indicator_green"
230    self.elems.destinations_status_label.caption = { "gui.kr-planetary-teleporter-ready" }
231  else
232    self.elems.destinations_status_icon.sprite = "flib_indicator_red"
233    if not standing_on_teleporter then
234      self.elems.destinations_status_label.caption = { "gui.kr-planetary-teleporter-not-on-teleporter" }
235    elseif too_many_players then
236      self.elems.destinations_status_label.caption = { "gui.kr-planetary-teleporter-too-many-players" }
237    else
238      self.elems.destinations_status_label.caption = { "gui.kr-planetary-teleporter-not-fully-charged" }
239    end
240  end
241
242  update_name_label(self)
243
244  for _, frame in pairs(self.elems.destinations_table.children) do
245    local unit_number = tonumber(frame.name) --[[@as uint]]
246    local dest_data = storage.planetary_teleporter[unit_number]
247    if not dest_data then
248      goto continue
249    end
250    local dest_entity = dest_data.entities.base
251    local dest_charge = dest_entity.energy / buffer_capacity
252    local dest_fully_charged = flib_math.round(dest_charge, 0.1) >= 1
253    local minimap = frame.minimap_frame.minimap --[[@as LuaGuiElement]]
254    minimap.progressbar.value = dest_charge
255    minimap.progressbar.visible = not dest_fully_charged
256    minimap.minimap_button.enabled = fully_operational and dest_fully_charged
257    if not dest_fully_charged then
258      minimap.minimap_button.tooltip = { "format-percent", flib_math.round(dest_charge * 100) }
259    elseif fully_operational then
260      minimap.minimap_button.tooltip = { "gui.kr-planetary-teleporter-teleport" }
261    else
262      minimap.minimap_button.tooltip = nil
263    end
264    ::continue::
265  end
266end
267
268local function on_tick()
269  for _, gui in pairs(storage.planetary_teleporter_gui) do
270    update_gui(gui)
271  end
272end
273
274--- @param e EventData.on_gui_opened
275local function on_gui_opened(e)
276  local entity = e.entity
277  if not entity or not entity.valid or entity.name ~= "kr-planetary-teleporter" then
278    return
279  end
280
281  local player = game.get_player(e.player_index)
282  if not player then
283    return
284  end
285
286  local destination_buttons = {}
287  for unit_number, data in pairs(storage.planetary_teleporter) do
288    if unit_number == entity.unit_number or data.force ~= entity.force or data.surface ~= entity.surface then
289      goto continue
290    end
291    destination_buttons[#destination_buttons + 1] = {
292      type = "frame",
293      name = unit_number,
294      style = "train_with_minimap_frame",
295      direction = "vertical",
296      {
297        type = "frame",
298        name = "minimap_frame",
299        style = "deep_frame_in_shallow_frame",
300        {
301          type = "minimap",
302          name = "minimap",
303          style = "kr_planetary_teleporter_destination_minimap",
304          position = data.entities.base.position,
305          {
306            type = "button",
307            name = "minimap_button",
308            style = "kr_planetary_teleporter_destination_minimap_button",
309            tags = { unit_number = unit_number },
310            handler = { [defines.events.on_gui_click] = on_teleport_button_clicked },
311          },
312          {
313            type = "progressbar",
314            name = "progressbar",
315            style = "kr_planetary_teleporter_destination_charge_bar",
316            ignored_by_interaction = true,
317          },
318          {
319            type = "label",
320            style = "kr_planetary_teleporter_destination_label",
321            style_mods = { top_margin = 8, right_margin = 8 },
322            caption = data.name or { "gui.kr-planetary-teleporter-unnamed" },
323            ignored_by_interaction = true,
324          },
325        },
326      },
327    }
328    ::continue::
329  end
330
331  local elems = flib_gui.add(player.gui.screen, {
332    type = "frame",
333    name = "kr_planetary_teleporter_window",
334    direction = "vertical",
335    elem_mods = { auto_center = true },
336    handler = { [defines.events.on_gui_closed] = on_window_closed },
337    {
338      type = "flow",
339      style = "flib_titlebar_flow",
340      drag_target = "kr_planetary_teleporter_window",
341      {
342        type = "label",
343        style = "flib_frame_title",
344        caption = { "entity-name.kr-planetary-teleporter" },
345        ignored_by_interaction = true,
346      },
347      { type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true },
348      {
349        type = "textfield",
350        name = "search_textfield",
351        style = "flib_titlebar_search_textfield",
352        visible = false,
353        handler = { [defines.events.on_gui_text_changed] = on_search_textfield_text_changed },
354      },
355      {
356        type = "sprite-button",
357        name = "search_button",
358        style = "frame_action_button",
359        sprite = "utility/search",
360        tooltip = { "gui.flib-search-instruction" },
361        auto_toggle = true,
362        handler = { [defines.events.on_gui_click] = on_search_button_clicked },
363      },
364      {
365        type = "sprite-button",
366        style = "frame_action_button",
367        sprite = "utility/close",
368        tooltip = { "gui.close-instruction" },
369        handler = { [defines.events.on_gui_click] = on_window_closed },
370      },
371    },
372    {
373      type = "flow",
374      style_mods = { horizontal_spacing = 12 },
375      {
376        type = "frame",
377        style = "inside_shallow_frame",
378        direction = "vertical",
379        {
380          type = "frame",
381          style = "subheader_frame",
382          style_mods = { width = 424 },
383          {
384            type = "label",
385            name = "name_label",
386            style = "subheader_caption_label",
387            style_mods = { horizontally_squashable = true },
388          },
389          {
390            type = "textfield",
391            name = "name_textfield",
392            visible = false,
393            handler = { [defines.events.on_gui_confirmed] = on_name_textfield_confirmed },
394          },
395          {
396            type = "sprite-button",
397            style = "mini_button_aligned_to_text_vertically_when_centered",
398            sprite = "utility/rename_icon",
399            handler = { [defines.events.on_gui_click] = on_rename_button_clicked },
400          },
401        },
402        {
403          type = "flow",
404          style_mods = { padding = 12, top_padding = 8, vertical_spacing = 8 },
405          direction = "vertical",
406          {
407            type = "flow",
408            style_mods = { vertical_align = "center" },
409            { type = "sprite", name = "status_icon", style = "flib_indicator" },
410            { type = "label", name = "status_label" },
411          },
412          {
413            type = "frame",
414            style = "deep_frame_in_shallow_frame",
415            { type = "entity-preview", style = "wide_entity_button", elem_mods = { entity = entity } },
416          },
417          {
418            type = "progressbar",
419            name = "charge_progressbar",
420            style = "production_progressbar",
421            style_mods = { horizontally_stretchable = true },
422            caption = "foo",
423            value = 0.2,
424          },
425          { type = "empty-widget", style = "entity_frame_filler" },
426        },
427      },
428      {
429        type = "frame",
430        style = "inside_deep_frame",
431        direction = "vertical",
432        {
433          type = "frame",
434          style = "subheader_frame",
435          {
436            type = "label",
437            style = "subheader_caption_label",
438            caption = { "gui.kr-planetary-teleporter-destinations" },
439          },
440          { type = "empty-widget", style = "flib_horizontal_pusher" },
441          {
442            type = "flow",
443            name = "destinations_status_flow",
444            style_mods = { vertical_align = "center", right_margin = 8 },
445            {
446              type = "sprite",
447              name = "destinations_status_icon",
448              style = "flib_indicator",
449              sprite = "flib_indicator_red",
450            },
451            {
452              type = "label",
453              name = "destinations_status_label",
454              caption = { "gui.kr-planetary-teleporter-not-on-teleporter" },
455            },
456          },
457        },
458        {
459          type = "scroll-pane",
460          style = "kr_planetary_teleporter_destinations_scroll_pane",
461          vertical_scroll_policy = "auto-and-reserve-space",
462          {
463            type = "table",
464            name = "destinations_table",
465            style = "slot_table",
466            column_count = 3,
467            children = destination_buttons,
468          },
469        },
470      },
471    },
472  })
473
474  player.opened = elems.kr_planetary_teleporter_window
475
476  --- @type PlanetaryTeleporterGui
477  storage.planetary_teleporter_gui[e.player_index] = {
478    elems = elems,
479    data = storage.planetary_teleporter[entity.unit_number],
480    entity = entity,
481    player = player,
482    search_open = false,
483    search_query = "",
484  }
485end
486
487--- @param e EventData.CustomInputEvent
488local function on_focus_search(e)
489  local self = storage.planetary_teleporter_gui[e.player_index]
490  if self then
491    toggle_search(self, false)
492  end
493end
494
495--- @class PlanetaryTeleporterGuiHandler : event_handler
496local planetary_teleporter_gui = {}
497
498function planetary_teleporter_gui.on_init()
499  --- @type table<uint, PlanetaryTeleporterGui>
500  storage.planetary_teleporter_gui = {}
501end
502
503function planetary_teleporter_gui.on_configuration_changed()
504  for _, gui in pairs(storage.planetary_teleporter_gui or {}) do
505    if gui.elems.window.valid then
506      gui.elems.window.destroy()
507    end
508  end
509  storage.planetary_teleporter_gui = {}
510end
511
512planetary_teleporter_gui.events = {
513  [defines.events.on_gui_opened] = on_gui_opened,
514  [defines.events.on_tick] = on_tick,
515  ["kr-linked-focus-search"] = on_focus_search,
516}
517
518flib_gui.add_handlers({
519  planetary_teleporter_on_name_textfield_confirmed = on_name_textfield_confirmed,
520  planetary_teleporter_on_rename_button_clicked = on_rename_button_clicked,
521  planetary_teleporter_on_search_button_clicked = on_search_button_clicked,
522  planetary_teleporter_on_search_textfield_text_changed = on_search_textfield_text_changed,
523  planetary_teleporter_on_teleport_button_clicked = on_teleport_button_clicked,
524  planetary_teleporter_on_window_closed = on_window_closed,
525})
526
527return planetary_teleporter_gui