z_mirror/Krastorio2

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

Polka37Space age fixes + K2SO compatibility (#724)c13e3614

trunk
17.5 KiB532 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.valid ~= true then
289      goto continue
290    end
291    --Limit to same surface if space age is not enabled
292    if not script.active_mods["space-age"] and data.surface ~= entity.surface then
293      goto continue
294    end
295    destination_buttons[#destination_buttons + 1] = {
296      type = "frame",
297      name = unit_number,
298      style = "train_with_minimap_frame",
299      direction = "vertical",
300      {
301        type = "frame",
302        name = "minimap_frame",
303        style = "deep_frame_in_shallow_frame",
304        {
305          type = "minimap",
306          name = "minimap",
307          style = "kr_planetary_teleporter_destination_minimap",
308          position = data.entities.base.position,
309          surface_index = data.surface.index,
310          {
311            type = "button",
312            name = "minimap_button",
313            style = "kr_planetary_teleporter_destination_minimap_button",
314            tags = { unit_number = unit_number },
315            handler = { [defines.events.on_gui_click] = on_teleport_button_clicked },
316          },
317          {
318            type = "progressbar",
319            name = "progressbar",
320            style = "kr_planetary_teleporter_destination_charge_bar",
321            ignored_by_interaction = true,
322          },
323          {
324            type = "label",
325            style = "kr_planetary_teleporter_destination_label",
326            style_mods = { top_margin = 8, right_margin = 8 },
327            caption = data.name or { "gui.kr-planetary-teleporter-unnamed" },
328            ignored_by_interaction = true,
329          },
330        },
331      },
332    }
333    ::continue::
334  end
335
336  local elems = flib_gui.add(player.gui.screen, {
337    type = "frame",
338    name = "kr_planetary_teleporter_window",
339    direction = "vertical",
340    elem_mods = { auto_center = true },
341    handler = { [defines.events.on_gui_closed] = on_window_closed },
342    {
343      type = "flow",
344      style = "flib_titlebar_flow",
345      drag_target = "kr_planetary_teleporter_window",
346      {
347        type = "label",
348        style = "flib_frame_title",
349        caption = { "entity-name.kr-planetary-teleporter" },
350        ignored_by_interaction = true,
351      },
352      { type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true },
353      {
354        type = "textfield",
355        name = "search_textfield",
356        style = "flib_titlebar_search_textfield",
357        visible = false,
358        handler = { [defines.events.on_gui_text_changed] = on_search_textfield_text_changed },
359      },
360      {
361        type = "sprite-button",
362        name = "search_button",
363        style = "frame_action_button",
364        sprite = "utility/search",
365        tooltip = { "gui.flib-search-instruction" },
366        auto_toggle = true,
367        handler = { [defines.events.on_gui_click] = on_search_button_clicked },
368      },
369      {
370        type = "sprite-button",
371        style = "frame_action_button",
372        sprite = "utility/close",
373        tooltip = { "gui.close-instruction" },
374        handler = { [defines.events.on_gui_click] = on_window_closed },
375      },
376    },
377    {
378      type = "flow",
379      style_mods = { horizontal_spacing = 12 },
380      {
381        type = "frame",
382        style = "inside_shallow_frame",
383        direction = "vertical",
384        {
385          type = "frame",
386          style = "subheader_frame",
387          style_mods = { width = 424 },
388          {
389            type = "label",
390            name = "name_label",
391            style = "subheader_caption_label",
392            style_mods = { horizontally_squashable = true },
393          },
394          {
395            type = "textfield",
396            name = "name_textfield",
397            visible = false,
398            handler = { [defines.events.on_gui_confirmed] = on_name_textfield_confirmed },
399          },
400          {
401            type = "sprite-button",
402            style = "mini_button_aligned_to_text_vertically_when_centered",
403            sprite = "utility/rename_icon",
404            handler = { [defines.events.on_gui_click] = on_rename_button_clicked },
405          },
406        },
407        {
408          type = "flow",
409          style_mods = { padding = 12, top_padding = 8, vertical_spacing = 8 },
410          direction = "vertical",
411          {
412            type = "flow",
413            style_mods = { vertical_align = "center" },
414            { type = "sprite", name = "status_icon", style = "flib_indicator" },
415            { type = "label", name = "status_label" },
416          },
417          {
418            type = "frame",
419            style = "deep_frame_in_shallow_frame",
420            { type = "entity-preview", style = "wide_entity_button", elem_mods = { entity = entity } },
421          },
422          {
423            type = "progressbar",
424            name = "charge_progressbar",
425            style = "production_progressbar",
426            style_mods = { horizontally_stretchable = true },
427            caption = "foo",
428            value = 0.2,
429          },
430          { type = "empty-widget", style = "entity_frame_filler" },
431        },
432      },
433      {
434        type = "frame",
435        style = "inside_deep_frame",
436        direction = "vertical",
437        {
438          type = "frame",
439          style = "subheader_frame",
440          {
441            type = "label",
442            style = "subheader_caption_label",
443            caption = { "gui.kr-planetary-teleporter-destinations" },
444          },
445          { type = "empty-widget", style = "flib_horizontal_pusher" },
446          {
447            type = "flow",
448            name = "destinations_status_flow",
449            style_mods = { vertical_align = "center", right_margin = 8 },
450            {
451              type = "sprite",
452              name = "destinations_status_icon",
453              style = "flib_indicator",
454              sprite = "flib_indicator_red",
455            },
456            {
457              type = "label",
458              name = "destinations_status_label",
459              caption = { "gui.kr-planetary-teleporter-not-on-teleporter" },
460            },
461          },
462        },
463        {
464          type = "scroll-pane",
465          style = "kr_planetary_teleporter_destinations_scroll_pane",
466          vertical_scroll_policy = "auto-and-reserve-space",
467          {
468            type = "table",
469            name = "destinations_table",
470            style = "slot_table",
471            column_count = 3,
472            children = destination_buttons,
473          },
474        },
475      },
476    },
477  })
478
479  player.opened = elems.kr_planetary_teleporter_window
480
481  --- @type PlanetaryTeleporterGui
482  storage.planetary_teleporter_gui[e.player_index] = {
483    elems = elems,
484    data = storage.planetary_teleporter[entity.unit_number],
485    entity = entity,
486    player = player,
487    search_open = false,
488    search_query = "",
489  }
490end
491
492--- @param e EventData.CustomInputEvent
493local function on_focus_search(e)
494  local self = storage.planetary_teleporter_gui[e.player_index]
495  if self then
496    toggle_search(self, false)
497  end
498end
499
500--- @class PlanetaryTeleporterGuiHandler : event_handler
501local planetary_teleporter_gui = {}
502
503function planetary_teleporter_gui.on_init()
504  --- @type table<uint, PlanetaryTeleporterGui>
505  storage.planetary_teleporter_gui = {}
506end
507
508function planetary_teleporter_gui.on_configuration_changed()
509  for _, gui in pairs(storage.planetary_teleporter_gui or {}) do
510    if gui.elems.window.valid then
511      gui.elems.window.destroy()
512    end
513  end
514  storage.planetary_teleporter_gui = {}
515end
516
517planetary_teleporter_gui.events = {
518  [defines.events.on_gui_opened] = on_gui_opened,
519  [defines.events.on_tick] = on_tick,
520  ["kr-linked-focus-search"] = on_focus_search,
521}
522
523flib_gui.add_handlers({
524  planetary_teleporter_on_name_textfield_confirmed = on_name_textfield_confirmed,
525  planetary_teleporter_on_rename_button_clicked = on_rename_button_clicked,
526  planetary_teleporter_on_search_button_clicked = on_search_button_clicked,
527  planetary_teleporter_on_search_textfield_text_changed = on_search_textfield_text_changed,
528  planetary_teleporter_on_teleport_button_clicked = on_teleport_button_clicked,
529  planetary_teleporter_on_window_closed = on_window_closed,
530})
531
532return planetary_teleporter_gui