z_mirror/Krastorio2

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

Caleb HeuerRename: all projectiles2d58b091

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