z_mirror/Krastorio2

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

Caleb HeuerFix several issues with intergalactic transceiver GUIad8c3f3a

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