z_mirror/Krastorio2

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

Caleb HeuerClean up legacy event parameter041f29c4

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