z_mirror/Krastorio2

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

Caleb HeuerRun stylua02265e00

k2so-fixes
19.1 KiB616 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 * entity_quality.accumulator_capacity_multiplier
206    end
207    storage.intergalactic_transceiver.forces[entity_force.index] = { entity = new_entity }
208  end
209end
210
211--- @param e EventData.on_gui_click
212local function on_gui_window_closed(e)
213  local self = storage.intergalactic_transceiver.guis[e.player_index]
214  if not self then
215    return
216  end
217  self.elems.kr_intergalactic_transceiver_window.destroy()
218  self.player.opened = nil
219  self.player.play_sound({ path = "entity-close/kr-intergalactic-transceiver" })
220  storage.intergalactic_transceiver.guis[e.player_index] = nil
221end
222
223--- @param entity LuaEntity
224local function close_open_guis(entity)
225  for player_index, self in pairs(storage.intergalactic_transceiver.guis) do
226    if self.entity == entity or not self.entity.valid then
227      on_gui_window_closed({ player_index = player_index })
228    end
229  end
230end
231
232--- @param e EventData.on_gui_click
233local function on_gui_activate_button_clicked(e)
234  local player = game.get_player(e.player_index)
235  if not player then
236    return
237  end
238  local transceiver_data = storage.intergalactic_transceiver.forces[player.force_index]
239  close_open_guis(transceiver_data.entity)
240  activate_transceiver(transceiver_data)
241end
242
243--- @param self IntergalacticTransceiverGuiData
244local function update_gui(self)
245  local charged = flib_math.round(self.entity.energy / max_energy[self.entity.quality.name], 0.01)
246  local transceiver_data = storage.intergalactic_transceiver.forces[self.player.force_index]
247  local props = status_properties[transceiver_data.status]
248  self.elems.status_icon.sprite = props.icon
249  self.elems.status_label.caption = props.label
250  self.elems.status_label.tooltip = props.tooltip
251  self.elems.charge_progressbar.value = charged
252  self.elems.charge_progressbar.caption = math.floor(charged * 100) .. " %"
253  self.elems.activate_button.enabled = charged == 1
254end
255
256--- @param player LuaPlayer
257--- @param entity LuaEntity
258local function create_gui(player, entity)
259  local elems = flib_gui.add(player.gui.screen, {
260    type = "frame",
261    name = "kr_intergalactic_transceiver_window",
262    direction = "vertical",
263    elem_mods = { auto_center = true }, --- @diagnostic disable-line: missing-fields
264    handler = { [defines.events.on_gui_closed] = on_gui_window_closed },
265    {
266      type = "flow",
267      style = "flib_titlebar_flow",
268      drag_target = "kr_intergalactic_transceiver_window",
269      {
270        type = "label",
271        style = "flib_frame_title",
272        caption = { "entity-name.kr-intergalactic-transceiver" },
273        ignored_by_interaction = true,
274      },
275      { type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true },
276      {
277        type = "sprite-button",
278        style = "frame_action_button",
279        sprite = "utility/close",
280        mouse_button_filter = { "left" },
281        handler = { [defines.events.on_gui_click] = on_gui_window_closed },
282      },
283    },
284    {
285      type = "frame",
286      style = "entity_frame",
287      style_mods = { width = 424 }, --- @diagnostic disable-line: missing-fields
288      direction = "vertical",
289      {
290        type = "flow",
291        style_mods = { vertical_align = "center" },
292        { type = "sprite", name = "status_icon", style = "flib_indicator" },
293        { type = "label", name = "status_label" },
294      },
295      {
296        type = "frame",
297        style = "deep_frame_in_shallow_frame",
298        { type = "entity-preview", style = "wide_entity_button", elem_mods = { entity = entity } },
299      },
300      {
301        type = "progressbar",
302        name = "charge_progressbar",
303        style = "production_progressbar",
304        style_mods = { horizontally_stretchable = true },
305        value = 0,
306      },
307      {
308        type = "button",
309        name = "activate_button",
310        style_mods = { horizontally_stretchable = true },
311        caption = { "gui.kr-intergalactic-transceiver-activate" },
312        enabled = false,
313        handler = { [defines.events.on_gui_click] = on_gui_activate_button_clicked },
314      },
315    },
316  })
317
318  --- @class IntergalacticTransceiverGuiData
319  storage.intergalactic_transceiver.guis[player.index] = {
320    elems = elems,
321    entity = entity,
322    player = player,
323  }
324
325  player.opened = elems.kr_intergalactic_transceiver_window
326  if not game.is_multiplayer() then
327    player.play_sound({ path = "entity-open/kr-intergalactic-transceiver" })
328  end
329end
330
331--- @param entity LuaEntity
332local function on_transceiver_destroyed(entity)
333  local force_index = entity.force.index
334  storage.intergalactic_transceiver.forces[force_index] = nil
335  close_open_guis(entity)
336end
337
338--- @param entity LuaEntity
339local function on_inactive_transceiver_destroyed(entity)
340  storage.intergalactic_transceiver.inactive[entity.unit_number] = nil
341end
342
343--- @param e EntityDestroyedEvent
344local function on_entity_destroyed(e)
345  local entity = e.entity
346  if not entity or not entity.valid then
347    return
348  end
349
350  if entity.name == "kr-intergalactic-transceiver" then
351    on_transceiver_destroyed(entity)
352  elseif entity.name == "kr-inactive-intergalactic-transceiver" then
353    on_inactive_transceiver_destroyed(entity)
354  end
355end
356
357--- @param entity LuaEntity
358local function on_transceiver_built(entity)
359  local existing = storage.intergalactic_transceiver.forces[entity.force.index]
360  if existing then
361    local position = entity.position
362    local force = entity.force
363    local player = entity.last_user
364    local surface = entity.surface
365    entity.destroy()
366
367    local new_entity = surface.create_entity({
368      name = "kr-inactive-intergalactic-transceiver",
369      position = position,
370      force = force,
371      player = player,
372      create_build_effect_smoke = false,
373      raise_built = true,
374    })
375    if new_entity and new_entity.valid then
376      storage.intergalactic_transceiver.inactive[new_entity.unit_number] = new_entity
377    end
378
379    return
380  end
381
382  --- @class IntergalacticTransceiverForceData
383  storage.intergalactic_transceiver.forces[entity.force.index] = {
384    activating = false,
385    entity = entity,
386    last_alert_tick = game.tick,
387    last_energy = entity.energy,
388    status = "empty",
389  }
390end
391
392--- @param entity LuaEntity
393local function on_active_transceiver_built(entity)
394  local existing = storage.intergalactic_transceiver.forces[entity.force.index]
395  if not existing or not existing.activating then
396    local position = entity.position
397    local force = entity.force
398    local player = entity.last_user
399    local surface = entity.surface
400    entity.destroy()
401
402    local new_entity = surface.create_entity({
403      name = "kr-intergalactic-transceiver",
404      position = position,
405      force = force,
406      player = player,
407      create_build_effect_smoke = false,
408      raise_built = true,
409    })
410
411    if new_entity and new_entity.valid then
412      on_transceiver_built(new_entity)
413    end
414  end
415end
416
417--- @param e EntityBuiltEvent
418local function on_entity_built(e)
419  local entity = e.entity or e.destination
420  if not entity or not entity.valid then
421    return
422  end
423
424  if entity.name == "kr-intergalactic-transceiver" then
425    on_transceiver_built(entity)
426  elseif entity.name == "kr-activated-intergalactic-transceiver" then
427    on_active_transceiver_built(entity)
428  end
429end
430
431--- @param data IntergalacticTransceiverForceData
432--- @param entity LuaEntity
433local function update_transceiver(data, entity)
434  if not data.last_energy then
435    return
436  end
437  local current_energy = entity.energy
438  local new_energy = current_energy
439  local difference = current_energy - data.last_energy
440  --- @type IntergalacticTransceiverStatus
441  local status = "charging"
442  -- If we're not receiving enough power
443  if difference < min_charge_delta and current_energy > 0 then
444    -- Drain the transceiver at 3 TJ / sec
445    new_energy = math.max(0, current_energy - energy_drain)
446    -- Update status
447    if new_energy > 0 then
448      status = "discharging"
449    else
450      status = "not_enough_input"
451    end
452  else
453    if current_energy > max_energy[entity.quality.name] then
454      -- XXX: If the energy remains constant, the entity status will change to "normal", so give it a random offset.
455      new_energy = max_energy[entity.quality.name] + math.random(0, 20) * 1000000
456      status = "ready"
457    else
458      local current_status = defines_to_status[entity.status]
459      if status_properties[current_status] then
460        status = current_status
461      end
462    end
463  end
464
465  if new_energy ~= current_energy then
466    entity.energy = new_energy
467  end
468  local last_status = data.status
469  data.last_energy = new_energy
470  data.status = status
471
472  local status_properties = status_properties[status]
473  if status_properties and status_properties.alert_label and game.tick - data.last_alert_tick >= 60 then
474    data.last_alert_tick = game.tick
475    for _, player in pairs(entity.force.players) do
476      player.add_custom_alert(
477        entity,
478        { type = "item", name = "kr-intergalactic-transceiver" },
479        status_properties.alert_label,
480        true
481      )
482      if status == "discharging" and last_status ~= status then
483        player.play_sound({
484          path = "kr-intergalactic-transceiver-discharging-warning",
485        })
486      end
487    end
488  end
489end
490
491--- @param e EventData.on_gui_opened
492local function on_gui_opened(e)
493  if storage.intergalactic_transceiver.guis[e.player_index] then
494    return
495  end
496  if e.gui_type ~= defines.gui_type.entity then
497    return
498  end
499  local entity = e.entity
500  if not entity or entity.name ~= "kr-intergalactic-transceiver" then
501    return
502  end
503  local player = game.get_player(e.player_index)
504  if not player then
505    return
506  end
507  create_gui(player, entity)
508end
509
510local function on_tick()
511  if not storage.intergalactic_transceiver then
512    return
513  end
514
515  local transceivers = storage.intergalactic_transceiver.forces
516  for force_index, data in pairs(transceivers) do
517    local entity = data.entity
518    if entity and entity.valid then
519      update_transceiver(data, entity)
520    else
521      transceivers[force_index] = nil
522    end
523  end
524
525  for _, self in pairs(storage.intergalactic_transceiver.guis) do
526    update_gui(self)
527  end
528end
529
530local function on_180th_tick()
531  if not storage.intergalactic_transceiver then
532    return
533  end
534  for unit_number, entity in pairs(storage.intergalactic_transceiver.inactive) do
535    if entity.valid then
536      util.entity_flying_text(entity, { "message.kr-transceiver-is-inactive" }, { r = 1 })
537    else
538      storage.intergalactic_transceiver.inactive[unit_number] = nil
539    end
540  end
541end
542
543--- @class IntergalacticTransceiverHandler : event_handler
544local intergalactic_transceiver = {}
545
546function intergalactic_transceiver.on_init()
547  -- Can be already initialised through remote interface
548  if storage.intergalactic_transceiver then
549    return
550  end
551  storage.intergalactic_transceiver = {
552    --- @type table<uint, IntergalacticTransceiverForceData>
553    forces = {},
554    --- @type table<uint, IntergalacticTransceiverGuiData>
555    guis = {},
556    -- Force index
557    --- @type table<uint, boolean>
558    in_cutscene = {},
559    -- Player index
560    --- @type table<uint, boolean>
561    player_in_cutscene = {},
562    --- @type table<uint, LuaEntity>
563    inactive = {},
564    is_victory = true,
565  }
566end
567
568intergalactic_transceiver.events = {
569  [defines.events.on_built_entity] = on_entity_built,
570  [defines.events.on_cutscene_cancelled] = on_cutscene_finished,
571  [defines.events.on_cutscene_finished] = on_cutscene_finished,
572  [defines.events.on_cutscene_waypoint_reached] = on_cutscene_waypoint_reached,
573  [defines.events.on_entity_cloned] = on_entity_built,
574  [defines.events.on_entity_died] = on_entity_destroyed,
575  [defines.events.on_gui_opened] = on_gui_opened,
576  [defines.events.on_player_mined_entity] = on_entity_destroyed,
577  [defines.events.on_robot_built_entity] = on_entity_built,
578  [defines.events.on_robot_mined_entity] = on_entity_destroyed,
579  [defines.events.on_tick] = on_tick,
580  [defines.events.script_raised_built] = on_entity_built,
581  [defines.events.script_raised_destroy] = on_entity_destroyed,
582  [defines.events.script_raised_revive] = on_entity_built,
583  [defines.events.on_space_platform_built_entity] = on_entity_built,
584  [defines.events.on_space_platform_mined_entity] = on_entity_destroyed,
585}
586
587intergalactic_transceiver.on_nth_tick = {
588  [180] = on_180th_tick,
589}
590
591-- REMOTE
592
593remote.add_interface("kr-intergalactic-transceiver", {
594  get_is_victory = function()
595    if storage.intergalactic_transceiver then
596      intergalactic_transceiver.on_init()
597    end
598    return storage.intergalactic_transceiver.is_victory
599  end,
600  set_is_victory = function(to_state)
601    if to_state == nil or type(to_state) ~= "boolean" then
602      error("`to_state` must be a boolean")
603    end
604    if not storage.intergalactic_transceiver then
605      intergalactic_transceiver.on_init()
606    end
607    storage.intergalactic_transceiver.is_victory = to_state
608  end,
609})
610
611flib_gui.add_handlers({
612  transceiver_on_window_closed = on_gui_window_closed,
613  transceiver_on_activate_button_clicked = on_gui_activate_button_clicked,
614})
615
616return intergalactic_transceiver