z_mirror/Krastorio2

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

Caleb HeuerFinish re-implementing planetary teleporter functionality2aad641c

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