z_mirror/Krastorio2

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

Caleb HeuerChange to four-space indentation07409ca6

four-space-indentation
19.6 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