z_mirror/Krastorio2

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

Caleb HeuerChange to four-space indentation07409ca6

four-space-indentation
21.1 KiB617 linesraw
1local flib_gui = require("__flib__.gui")
2local flib_math = require("__flib__.math")
3local flib_position = require("__flib__.position")
4
5local util = require("scripts.util")
6
7--- @alias IntergalacticTransceiverStatus
8--- | "charging"
9--- | "discharging"
10--- | "not_enough_input"
11--- | "ready"
12
13local defines_to_status = {
14    [defines.entity_status.charging] = "charging",
15    [defines.entity_status.discharging] = "discharging",
16}
17
18--- @type table<IntergalacticTransceiverStatus, { label: LocalisedString, sprite: SpritePath }>
19local status_properties = {
20    charging = {
21        label = { "entity-status.charging" },
22        icon = "utility/status_working",
23    },
24    discharging = {
25        alert_label = { "gui.kr-intergalactic-transceiver-alert-discharging" },
26        label = { "entity-status.discharging" },
27        icon = "utility/status_not_working",
28    },
29    not_enough_input = {
30        alert_label = { "gui.kr-intergalactic-transceiver-alert-not-enough-input" },
31        label = { "entity-status.kr-not-enough-input" },
32        icon = "utility/status_not_working",
33        tooltip = { "entity-status-tooltip.kr-not-enough-input" },
34    },
35    no_power = {
36        alert_label = { "gui.kr-intergalactic-transceiver-alert-no-power" },
37        label = { "entity-status.no-power" },
38        icon = "utility/status_not_working",
39    },
40    ready = {
41        alert_label = { "gui.kr-intergalactic-transceiver-alert-ready" },
42        label = { "entity-status.kr-ready" },
43        icon = "utility/status_working",
44    },
45}
46
47local min_charge_delta = 2.5e8 -- Requires charge rate of 15 GW
48local max_charge_delta = 2e9
49---@type table<string, number>
50local max_energy = {}
51local default_buffer_capacity =
52    prototypes.entity["kr-intergalactic-transceiver"].electric_energy_source_prototype.buffer_capacity
53for _, quality in pairs(prototypes.quality) do
54    max_energy[quality.name] = (default_buffer_capacity * quality.accumulator_capacity_multiplier) - max_charge_delta
55end
56local energy_drain = 50e9
57
58--- @param transceiver_data IntergalacticTransceiverForceData
59local function activate_transceiver(transceiver_data)
60    local entity = transceiver_data.entity
61    local entity_surface = entity.surface
62    local entity_position = entity.position
63    for _, player in pairs(entity.force.connected_players) do
64        if player.surface == entity_surface and flib_position.distance(entity_position, player.position) <= 100 then
65            storage.intergalactic_transceiver.player_in_cutscene[player.index] = true
66            if player.controller_type == defines.controllers.character then
67                player.character.destructible = false
68            end
69            player.set_controller({
70                type = defines.controllers.cutscene,
71                waypoints = {
72                    {
73                        position = entity.position,
74                        zoom = 1.2,
75                        transition_time = 70,
76                        time_to_wait = 30,
77                    },
78                    {
79                        position = entity.position,
80                        zoom = 1.2,
81                        transition_time = 0,
82                        time_to_wait = 15,
83                    },
84                    {
85                        position = entity.position,
86                        zoom = 0.8,
87                        transition_time = 130,
88                        time_to_wait = 10,
89                    },
90                    {
91                        position = entity.position,
92                        zoom = 1.2,
93                        transition_time = 200,
94                        time_to_wait = 60,
95                    },
96                },
97                final_transition_time = 60,
98            })
99        end
100    end
101    storage.intergalactic_transceiver.in_cutscene[entity.force_index] = true
102end
103
104--- @param e EventData.on_cutscene_finished
105local function on_cutscene_finished(e)
106    local player = game.get_player(e.player_index)
107    if not player then
108        return
109    end
110    if not storage.intergalactic_transceiver then
111        return
112    end
113
114    if storage.intergalactic_transceiver.player_in_cutscene[e.player_index] then
115        storage.intergalactic_transceiver.player_in_cutscene[e.player_index] = nil
116        if player.character then
117            player.character.destructible = true
118        end
119    end
120
121    if not storage.intergalactic_transceiver.in_cutscene[player.force.index] then
122        return
123    end
124    storage.intergalactic_transceiver.in_cutscene[player.force_index] = nil
125
126    player.force.technologies["kr-logo"].enabled = true
127
128    if not storage.intergalactic_transceiver.is_victory then
129        return
130    end
131
132    game.set_win_ending_info({
133        title = { "gui.kr-victory-title" },
134        message = { "gui.kr-victory-message" },
135        bullet_points = {
136            { "victory-bullet-point-1" },
137            { "victory-bullet-point-2" },
138            { "victory-bullet-point-3" },
139            { "victory-bullet-point-4" },
140        },
141        final_message = { "gui.kr-victory-final-message" },
142    })
143    game.set_game_state({
144        can_continue = true,
145        game_finished = true,
146        victorious_force = player.force,
147        player_won = true,
148    })
149    player.force.play_sound({ path = "kr-win-joke-voice" })
150end
151
152--- @param e EventData.on_cutscene_waypoint_reached
153local function on_cutscene_waypoint_reached(e)
154    if not storage.intergalactic_transceiver then
155        return
156    end
157    local player = game.get_player(e.player_index)
158    if not player or not storage.intergalactic_transceiver.in_cutscene[player.force_index] then
159        return
160    end
161
162    local transceiver_data = storage.intergalactic_transceiver.forces[player.force_index]
163    if not transceiver_data then
164        return
165    end
166    local entity = transceiver_data.entity
167    if not entity.valid then
168        return
169    end
170
171    if e.waypoint_index == 1 then
172        entity.surface.create_entity({
173            name = "kr-intergalactic-transceiver-wave",
174            force = entity.force,
175            position = entity.position,
176            speed = 0,
177            max_range = 100,
178            target = entity,
179            create_build_effect_smoke = false,
180            raise_built = true,
181        })
182    elseif e.waypoint_index == 2 then
183        local entity_force = entity.force
184        local entity_player = entity.last_user
185        local entity_position = entity.position
186        local entity_quality = entity.quality
187        local entity_surface = entity.surface
188        entity.destroy()
189        transceiver_data.activating = true
190        local new_entity = entity_surface.create_entity({
191            name = "kr-activated-intergalactic-transceiver",
192            position = entity_position,
193            force = entity_force,
194            player = entity_player,
195            quality = entity_quality,
196            create_build_effect_smoke = false,
197            raise_built = true,
198        })
199        transceiver_data.activating = false
200        if not new_entity or not new_entity.valid then
201            return
202        end
203        if entity_quality.name ~= "normal" then
204            new_entity.power_production = new_entity.power_production * entity_quality.accumulator_capacity_multiplier
205            new_entity.electric_buffer_size = new_entity.electric_buffer_size
206                * entity_quality.accumulator_capacity_multiplier
207        end
208        storage.intergalactic_transceiver.forces[entity_force.index] = { entity = new_entity }
209    end
210end
211
212--- @param e EventData.on_gui_click
213local function on_gui_window_closed(e)
214    local self = storage.intergalactic_transceiver.guis[e.player_index]
215    if not self then
216        return
217    end
218    self.elems.kr_intergalactic_transceiver_window.destroy()
219    self.player.opened = nil
220    self.player.play_sound({ path = "entity-close/kr-intergalactic-transceiver" })
221    storage.intergalactic_transceiver.guis[e.player_index] = nil
222end
223
224--- @param entity LuaEntity
225local function close_open_guis(entity)
226    for player_index, self in pairs(storage.intergalactic_transceiver.guis) do
227        if self.entity == entity or not self.entity.valid then
228            on_gui_window_closed({ player_index = player_index })
229        end
230    end
231end
232
233--- @param e EventData.on_gui_click
234local function on_gui_activate_button_clicked(e)
235    local player = game.get_player(e.player_index)
236    if not player then
237        return
238    end
239    local transceiver_data = storage.intergalactic_transceiver.forces[player.force_index]
240    close_open_guis(transceiver_data.entity)
241    activate_transceiver(transceiver_data)
242end
243
244--- @param self IntergalacticTransceiverGuiData
245local function update_gui(self)
246    local charged = flib_math.round(self.entity.energy / max_energy[self.entity.quality.name], 0.01)
247    local transceiver_data = storage.intergalactic_transceiver.forces[self.player.force_index]
248    local props = status_properties[transceiver_data.status]
249    self.elems.status_icon.sprite = props.icon
250    self.elems.status_label.caption = props.label
251    self.elems.status_label.tooltip = props.tooltip
252    self.elems.charge_progressbar.value = charged
253    self.elems.charge_progressbar.caption = math.floor(charged * 100) .. " %"
254    self.elems.activate_button.enabled = charged == 1
255end
256
257--- @param player LuaPlayer
258--- @param entity LuaEntity
259local function create_gui(player, entity)
260    local elems = flib_gui.add(player.gui.screen, {
261        type = "frame",
262        name = "kr_intergalactic_transceiver_window",
263        direction = "vertical",
264        elem_mods = { auto_center = true }, --- @diagnostic disable-line: missing-fields
265        handler = { [defines.events.on_gui_closed] = on_gui_window_closed },
266        {
267            type = "flow",
268            style = "flib_titlebar_flow",
269            drag_target = "kr_intergalactic_transceiver_window",
270            {
271                type = "label",
272                style = "flib_frame_title",
273                caption = { "entity-name.kr-intergalactic-transceiver" },
274                ignored_by_interaction = true,
275            },
276            { type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true },
277            {
278                type = "sprite-button",
279                style = "frame_action_button",
280                sprite = "utility/close",
281                mouse_button_filter = { "left" },
282                handler = { [defines.events.on_gui_click] = on_gui_window_closed },
283            },
284        },
285        {
286            type = "frame",
287            style = "entity_frame",
288            style_mods = { width = 424 }, --- @diagnostic disable-line: missing-fields
289            direction = "vertical",
290            {
291                type = "flow",
292                style_mods = { vertical_align = "center" },
293                { type = "sprite", name = "status_icon", style = "flib_indicator" },
294                { type = "label", name = "status_label" },
295            },
296            {
297                type = "frame",
298                style = "deep_frame_in_shallow_frame",
299                { type = "entity-preview", style = "wide_entity_button", elem_mods = { entity = entity } },
300            },
301            {
302                type = "progressbar",
303                name = "charge_progressbar",
304                style = "production_progressbar",
305                style_mods = { horizontally_stretchable = true },
306                value = 0,
307            },
308            {
309                type = "button",
310                name = "activate_button",
311                style_mods = { horizontally_stretchable = true },
312                caption = { "gui.kr-intergalactic-transceiver-activate" },
313                enabled = false,
314                handler = { [defines.events.on_gui_click] = on_gui_activate_button_clicked },
315            },
316        },
317    })
318
319    --- @class IntergalacticTransceiverGuiData
320    storage.intergalactic_transceiver.guis[player.index] = {
321        elems = elems,
322        entity = entity,
323        player = player,
324    }
325
326    player.opened = elems.kr_intergalactic_transceiver_window
327    if not game.is_multiplayer() then
328        player.play_sound({ path = "entity-open/kr-intergalactic-transceiver" })
329    end
330end
331
332--- @param entity LuaEntity
333local function on_transceiver_destroyed(entity)
334    local force_index = entity.force.index
335    storage.intergalactic_transceiver.forces[force_index] = nil
336    close_open_guis(entity)
337end
338
339--- @param entity LuaEntity
340local function on_inactive_transceiver_destroyed(entity)
341    storage.intergalactic_transceiver.inactive[entity.unit_number] = nil
342end
343
344--- @param e EntityDestroyedEvent
345local function on_entity_destroyed(e)
346    local entity = e.entity
347    if not entity or not entity.valid then
348        return
349    end
350
351    if entity.name == "kr-intergalactic-transceiver" then
352        on_transceiver_destroyed(entity)
353    elseif entity.name == "kr-inactive-intergalactic-transceiver" then
354        on_inactive_transceiver_destroyed(entity)
355    end
356end
357
358--- @param entity LuaEntity
359local function on_transceiver_built(entity)
360    local existing = storage.intergalactic_transceiver.forces[entity.force.index]
361    if existing then
362        local position = entity.position
363        local force = entity.force
364        local player = entity.last_user
365        local surface = entity.surface
366        entity.destroy()
367
368        local new_entity = surface.create_entity({
369            name = "kr-inactive-intergalactic-transceiver",
370            position = position,
371            force = force,
372            player = player,
373            create_build_effect_smoke = false,
374            raise_built = true,
375        })
376        if new_entity and new_entity.valid then
377            storage.intergalactic_transceiver.inactive[new_entity.unit_number] = new_entity
378        end
379
380        return
381    end
382
383    --- @class IntergalacticTransceiverForceData
384    storage.intergalactic_transceiver.forces[entity.force.index] = {
385        activating = false,
386        entity = entity,
387        last_alert_tick = game.tick,
388        last_energy = entity.energy,
389        status = "empty",
390    }
391end
392
393--- @param entity LuaEntity
394local function on_active_transceiver_built(entity)
395    local existing = storage.intergalactic_transceiver.forces[entity.force.index]
396    if not existing or not existing.activating then
397        local position = entity.position
398        local force = entity.force
399        local player = entity.last_user
400        local surface = entity.surface
401        entity.destroy()
402
403        local new_entity = surface.create_entity({
404            name = "kr-intergalactic-transceiver",
405            position = position,
406            force = force,
407            player = player,
408            create_build_effect_smoke = false,
409            raise_built = true,
410        })
411
412        if new_entity and new_entity.valid then
413            on_transceiver_built(new_entity)
414        end
415    end
416end
417
418--- @param e EntityBuiltEvent
419local function on_entity_built(e)
420    local entity = e.entity or e.destination
421    if not entity or not entity.valid then
422        return
423    end
424
425    if entity.name == "kr-intergalactic-transceiver" then
426        on_transceiver_built(entity)
427    elseif entity.name == "kr-activated-intergalactic-transceiver" then
428        on_active_transceiver_built(entity)
429    end
430end
431
432--- @param data IntergalacticTransceiverForceData
433--- @param entity LuaEntity
434local function update_transceiver(data, entity)
435    if not data.last_energy then
436        return
437    end
438    local current_energy = entity.energy
439    local new_energy = current_energy
440    local difference = current_energy - data.last_energy
441    --- @type IntergalacticTransceiverStatus
442    local status = "charging"
443    -- If we're not receiving enough power
444    if difference < min_charge_delta and current_energy > 0 then
445        -- Drain the transceiver at 3 TJ / sec
446        new_energy = math.max(0, current_energy - energy_drain)
447        -- Update status
448        if new_energy > 0 then
449            status = "discharging"
450        else
451            status = "not_enough_input"
452        end
453    else
454        if current_energy > max_energy[entity.quality.name] then
455            -- XXX: If the energy remains constant, the entity status will change to "normal", so give it a random offset.
456            new_energy = max_energy[entity.quality.name] + math.random(0, 20) * 1000000
457            status = "ready"
458        else
459            local current_status = defines_to_status[entity.status]
460            if status_properties[current_status] then
461                status = current_status
462            end
463        end
464    end
465
466    if new_energy ~= current_energy then
467        entity.energy = new_energy
468    end
469    local last_status = data.status
470    data.last_energy = new_energy
471    data.status = status
472
473    local status_properties = status_properties[status]
474    if status_properties and status_properties.alert_label and game.tick - data.last_alert_tick >= 60 then
475        data.last_alert_tick = game.tick
476        for _, player in pairs(entity.force.players) do
477            player.add_custom_alert(
478                entity,
479                { type = "item", name = "kr-intergalactic-transceiver" },
480                status_properties.alert_label,
481                true
482            )
483            if status == "discharging" and last_status ~= status then
484                player.play_sound({
485                    path = "kr-intergalactic-transceiver-discharging-warning",
486                })
487            end
488        end
489    end
490end
491
492--- @param e EventData.on_gui_opened
493local function on_gui_opened(e)
494    if storage.intergalactic_transceiver.guis[e.player_index] then
495        return
496    end
497    if e.gui_type ~= defines.gui_type.entity then
498        return
499    end
500    local entity = e.entity
501    if not entity or entity.name ~= "kr-intergalactic-transceiver" then
502        return
503    end
504    local player = game.get_player(e.player_index)
505    if not player then
506        return
507    end
508    create_gui(player, entity)
509end
510
511local function on_tick()
512    if not storage.intergalactic_transceiver then
513        return
514    end
515
516    local transceivers = storage.intergalactic_transceiver.forces
517    for force_index, data in pairs(transceivers) do
518        local entity = data.entity
519        if entity and entity.valid then
520            update_transceiver(data, entity)
521        else
522            transceivers[force_index] = nil
523        end
524    end
525
526    for _, self in pairs(storage.intergalactic_transceiver.guis) do
527        update_gui(self)
528    end
529end
530
531local function on_180th_tick()
532    if not storage.intergalactic_transceiver then
533        return
534    end
535    for unit_number, entity in pairs(storage.intergalactic_transceiver.inactive) do
536        if entity.valid then
537            util.entity_flying_text(entity, { "message.kr-transceiver-is-inactive" }, { r = 1 })
538        else
539            storage.intergalactic_transceiver.inactive[unit_number] = nil
540        end
541    end
542end
543
544--- @class IntergalacticTransceiverHandler : event_handler
545local intergalactic_transceiver = {}
546
547function intergalactic_transceiver.on_init()
548    -- Can be already initialised through remote interface
549    if storage.intergalactic_transceiver then
550        return
551    end
552    storage.intergalactic_transceiver = {
553        --- @type table<uint, IntergalacticTransceiverForceData>
554        forces = {},
555        --- @type table<uint, IntergalacticTransceiverGuiData>
556        guis = {},
557        -- Force index
558        --- @type table<uint, boolean>
559        in_cutscene = {},
560        -- Player index
561        --- @type table<uint, boolean>
562        player_in_cutscene = {},
563        --- @type table<uint, LuaEntity>
564        inactive = {},
565        is_victory = true,
566    }
567end
568
569intergalactic_transceiver.events = {
570    [defines.events.on_built_entity] = on_entity_built,
571    [defines.events.on_cutscene_cancelled] = on_cutscene_finished,
572    [defines.events.on_cutscene_finished] = on_cutscene_finished,
573    [defines.events.on_cutscene_waypoint_reached] = on_cutscene_waypoint_reached,
574    [defines.events.on_entity_cloned] = on_entity_built,
575    [defines.events.on_entity_died] = on_entity_destroyed,
576    [defines.events.on_gui_opened] = on_gui_opened,
577    [defines.events.on_player_mined_entity] = on_entity_destroyed,
578    [defines.events.on_robot_built_entity] = on_entity_built,
579    [defines.events.on_robot_mined_entity] = on_entity_destroyed,
580    [defines.events.on_tick] = on_tick,
581    [defines.events.script_raised_built] = on_entity_built,
582    [defines.events.script_raised_destroy] = on_entity_destroyed,
583    [defines.events.script_raised_revive] = on_entity_built,
584    [defines.events.on_space_platform_built_entity] = on_entity_built,
585    [defines.events.on_space_platform_mined_entity] = on_entity_destroyed,
586}
587
588intergalactic_transceiver.on_nth_tick = {
589    [180] = on_180th_tick,
590}
591
592-- REMOTE
593
594remote.add_interface("kr-intergalactic-transceiver", {
595    get_is_victory = function()
596        if storage.intergalactic_transceiver then
597            intergalactic_transceiver.on_init()
598        end
599        return storage.intergalactic_transceiver.is_victory
600    end,
601    set_is_victory = function(to_state)
602        if to_state == nil or type(to_state) ~= "boolean" then
603            error("`to_state` must be a boolean")
604        end
605        if not storage.intergalactic_transceiver then
606            intergalactic_transceiver.on_init()
607        end
608        storage.intergalactic_transceiver.is_victory = to_state
609    end,
610})
611
612flib_gui.add_handlers({
613    transceiver_on_window_closed = on_gui_window_closed,
614    transceiver_on_activate_button_clicked = on_gui_activate_button_clicked,
615})
616
617return intergalactic_transceiver