z_mirror/Krastorio2

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

Caleb HeuerRename: all equipment715d5ba0

real-fusion-reactor
13.1 KiB462 linesraw
1--- @class BeamData
2--- @field beam LuaEntity
3--- @field beam_number uint64
4--- @field target_data TargetData
5--- @field tower_data TowerData
6
7--- @class GridData
8--- @field absorber LuaEquipment?
9--- @field grid LuaEquipmentGrid
10
11--- @class TargetData
12--- @field connections TargetConnections
13--- @field entity LuaEntity
14--- @field full_tick uint64?
15--- @field grid_data GridData
16--- @field unit_number uint64
17
18--- @class TargetConnections
19--- @field by_beam table<number, ConnectionData>
20--- @field by_tower table<number, ConnectionData>
21
22--- @class TowerData
23--- @field entities TowerEntities
24--- @field tower_unit_number uint64
25--- @field turret_unit_number uint64
26
27--- @class TowerEntities
28--- @field collision LuaEntity
29--- @field tower LuaEntity
30--- @field turret LuaEntity
31
32local charging_rate = 3000000 -- 3 MW
33local cooldown = 10
34local loss_multiplier = 1.8
35local range = 20
36local required_energy = 10000000 -- 10 MW
37
38local absorber_buffer_capacity = prototypes.equipment["kr-energy-absorber-equipment"].energy_source.buffer_capacity
39
40-- TOWER
41-- The entity that is interacted with
42
43--- @param e EntityBuiltEvent
44local function on_entity_built(e)
45  local entity = e.entity or e.destination
46  if not entity or not entity.valid then
47    return
48  end
49  local entity_name = entity.name
50
51  -- Internal entities should not be cloned
52  if
53    e.name == defines.events.on_entity_cloned
54    and (entity_name == "kr-tesla-coil-turret" or entity_name == "kr-tesla-coil-collision")
55  then
56    entity.destroy()
57    return
58  end
59
60  if entity_name ~= "kr-tesla-coil" then
61    return
62  end
63
64  local surface = entity.surface
65  local unit_number = entity.unit_number --[[@as uint]]
66
67  local turret = surface.create_entity({
68    name = "kr-tesla-coil-turret",
69    position = entity.position,
70    force = game.forces["kr-internal-turrets"],
71    create_build_effect_smoke = false,
72    raise_built = true,
73  })
74  if not turret or not turret.valid then
75    game.print(
76      "Building tesla failed due to AAI Programmable Vehicles; This tesla coil will not function. To fix this, change the 'Deadzone construction denial range' map setting to zero."
77    )
78    entity.active = false
79    return
80  end
81  turret.destructible = false
82
83  local collision_entity = surface.create_entity({
84    name = "kr-tesla-coil-collision",
85    position = entity.position,
86    force = entity.force,
87    create_build_effect_smoke = false,
88    raise_built = true,
89  })
90  assert(collision_entity, "Tesla coil collision entity was destroyed")
91  collision_entity.destructible = false
92
93  --- @type TowerData
94  local data = {
95    entities = {
96      collision = collision_entity,
97      tower = entity,
98      turret = turret,
99    },
100    tower_unit_number = unit_number,
101    turret_unit_number = turret.unit_number,
102  }
103
104  storage.tesla_coil.turrets[data.turret_unit_number] = data
105  storage.tesla_coil.towers[unit_number] = data
106end
107
108--- @param entity LuaEntity
109local function destroy_if_valid(entity)
110  if entity.valid then
111    entity.destroy()
112  end
113end
114
115--- @param e EntityDestroyedEvent
116local function on_entity_destroyed(e)
117  local entity = e.entity
118  if not entity or not entity.valid or entity.name ~= "kr-tesla-coil" then
119    return
120  end
121
122  local unit_number = entity.unit_number
123  assert(unit_number)
124  local tower_data = storage.tesla_coil.towers[unit_number]
125  if not tower_data then
126    return
127  end
128
129  destroy_if_valid(tower_data.entities.collision)
130  destroy_if_valid(tower_data.entities.turret)
131
132  storage.tesla_coil.turrets[tower_data.turret_unit_number] = nil
133  storage.tesla_coil.towers[unit_number] = nil
134end
135
136-- TARGET
137-- An entity that will receive energy from a tesla coil
138
139--- @param target LuaEntity
140--- @return GridData?
141local function get_grid_data(target)
142  --- @type LuaEquipmentGrid
143  local grid
144  if target.type == "character" then
145    local armor_inventory = target.get_inventory(defines.inventory.character_armor)
146    if armor_inventory and armor_inventory.valid then
147      local armor = armor_inventory[1]
148      if armor and armor.valid_for_read then
149        grid = armor.grid
150      end
151    end
152  else
153    grid = target.grid
154  end
155
156  if grid then
157    return {
158      absorber = grid.find("kr-energy-absorber-equipment"),
159      grid = grid,
160    }
161  end
162end
163
164--- Updates the absorber object in a target's equipment grid
165--- @param grid LuaEquipmentGrid
166local function update_target_grid(grid)
167  for _, target_data in pairs(storage.tesla_coil.targets) do
168    local grid_data = target_data.grid_data
169    if grid_data.grid.valid and grid_data.grid == grid then
170      grid_data.absorber = grid.find("kr-energy-absorber-equipment")
171    end
172  end
173end
174
175--- @param target LuaEntity
176--- @param tower_data TowerData
177local function add_target(target, tower_data)
178  local target_unit_number = target.unit_number
179  --- @cast target_unit_number -?
180  local tower = tower_data.entities.tower
181  if not tower.valid or tower.energy < required_energy then
182    return
183  end
184  -- Check the target's equipment grid for an energy absorber
185  local grid_data = get_grid_data(target)
186  if grid_data and grid_data.absorber and grid_data.absorber.valid then
187    --- @type TargetData
188    local target_data = {
189      connections = {
190        --- @type table<number, ConnectionData>
191        by_beam = {},
192        --- @type table<number, ConnectionData>
193        by_tower = {},
194      },
195      entity = target,
196      --- @type number?
197      full_tick = nil,
198      grid_data = grid_data,
199      unit_number = target_unit_number,
200    }
201    storage.tesla_coil.targets[target_unit_number] = target_data
202    return target_data
203  end
204end
205
206--- @param target_unit_number number
207local function remove_target(target_unit_number)
208  storage.tesla_coil.targets[target_unit_number] = nil
209end
210
211-- CONNECTION
212-- A connection between a tower and a target, comprising of a beam
213-- There can be unlimited connections per target
214
215--- @param target_data TargetData
216--- @param tower_data TowerData
217--- @return boolean
218local function add_connection(target_data, tower_data)
219  -- Check if the absorber has space
220  local absorber = target_data.grid_data.absorber
221  if not absorber or not absorber.valid then
222    remove_target(target_data.unit_number)
223    return false
224  end
225
226  local tower = tower_data.entities.tower
227  if not tower.valid then
228    remove_target(target_data.unit_number)
229    return false
230  end
231
232  if absorber.energy >= absorber_buffer_capacity then
233    return false
234  end
235
236  -- Create beam entity
237  local beam = tower_data.entities.tower.surface.create_entity({
238    name = "kr-tesla-coil-electric-beam",
239    source = tower_data.entities.tower,
240    source_offset = { 0, -2.2 },
241    position = tower_data.entities.tower.position,
242    target = target_data.entity,
243    duration = 0,
244    max_length = range,
245    force = tower_data.entities.tower.force,
246    raise_built = true,
247  })
248  if not beam then
249    return false
250  end
251  local beam_number = script.register_on_object_destroyed(beam)
252
253  storage.tesla_coil.beams[beam_number] = {
254    beam = beam,
255    beam_number = beam_number,
256    target_data = target_data,
257    tower_data = tower_data,
258  }
259
260  --- @class ConnectionData
261  local connection_data = {
262    beam = beam,
263    beam_number = beam_number,
264    tower_data = tower_data,
265  }
266  target_data.connections.by_beam[beam_number] = connection_data
267  target_data.connections.by_tower[tower_data.tower_unit_number] = connection_data
268
269  return true
270end
271
272--- @param target_data TargetData
273--- @param tower_data TowerData
274local function remove_connection(target_data, tower_data)
275  local connection_data = target_data.connections.by_tower[tower_data.tower_unit_number]
276
277  -- Destroy beam if it still exists
278  if connection_data.beam.valid then
279    connection_data.beam.destroy()
280  end
281
282  local beam_number = connection_data.beam_number
283  storage.tesla_coil.beams[beam_number] = nil
284
285  target_data.connections.by_beam[beam_number] = nil
286  target_data.connections.by_tower[tower_data.tower_unit_number] = nil
287
288  if table_size(target_data.connections.by_beam) == 0 then
289    remove_target(target_data.unit_number)
290  end
291end
292
293--- @param target_data TargetData
294--- @param tower_data TowerData
295local function update_connection(target_data, tower_data)
296  local absorber = target_data.grid_data.absorber
297
298  -- Check if the tower is powered
299  if not absorber or not absorber.valid or tower_data.entities.tower.energy < required_energy then
300    remove_connection(target_data, tower_data)
301    return
302  end
303
304  local energy = absorber.energy
305  if energy < absorber_buffer_capacity then
306    -- Calculate how much to add
307    local to_add = charging_rate / 60 * cooldown
308    local result = energy + to_add
309    local tower = tower_data.entities.tower
310
311    if result >= absorber_buffer_capacity then
312      absorber.energy = absorber_buffer_capacity
313      target_data.full_tick = game.tick
314    else
315      absorber.energy = result
316      target_data.full_tick = nil
317    end
318
319    tower.energy = tower.energy - (to_add * loss_multiplier)
320  elseif target_data.full_tick and target_data.full_tick + cooldown <= game.tick then
321    remove_connection(target_data, tower_data)
322  end
323end
324
325--- @param target LuaEntity
326--- @param turret LuaEntity
327local function process_turret_fire(target, turret)
328  local tower_data = storage.tesla_coil.turrets[turret.unit_number]
329  if not tower_data then
330    return
331  end
332
333  local target_data = storage.tesla_coil.targets[target.unit_number]
334  if not target_data then
335    target_data = add_target(target, tower_data)
336  end
337
338  if target_data then
339    -- Just in case (#182)
340    if not target_data.entity.valid then
341      target_data.entity = target
342    end
343    local connection = target_data.connections.by_tower[tower_data.tower_unit_number]
344    if connection or add_connection(target_data, tower_data) then
345      update_connection(target_data, tower_data)
346    end
347  end
348end
349
350-- EVENTS
351
352--- @param e EventData.on_object_destroyed
353local function on_object_destroyed(e)
354  local beam_data = storage.tesla_coil.beams[e.registration_number]
355  if beam_data then
356    remove_connection(beam_data.target_data, beam_data.tower_data)
357  end
358end
359
360--- @param e EventData.on_equipment_inserted
361local function on_equipment_inserted(e)
362  if not e.equipment.valid or e.equipment.name ~= "kr-energy-absorber-equipment" then
363    return
364  end
365  update_target_grid(e.grid)
366end
367
368--- @param e EventData.on_equipment_removed
369local function on_equipment_removed(e)
370  if e.equipment == "kr-energy-absorber-equipment" then
371    update_target_grid(e.grid)
372  end
373end
374
375--- @param e EventData.on_script_trigger_effect
376local function on_script_trigger_effect(e)
377  if e.effect_id == "kr-tesla-coil-trigger" then
378    process_turret_fire(e.target_entity, e.source_entity)
379  end
380end
381
382local tesla_coil = {}
383
384function tesla_coil.on_init()
385  storage.tesla_coil = {
386    --- @type table<number, BeamData>
387    beams = {},
388    --- @type table<number, TowerData>
389    turrets = {},
390    --- @type table<number, TowerData>
391    towers = {},
392    --- @type table<number, TargetData>
393    targets = {},
394  }
395end
396
397tesla_coil.events = {
398  [defines.events.on_built_entity] = on_entity_built,
399  [defines.events.on_entity_cloned] = on_entity_built,
400  [defines.events.on_entity_died] = on_entity_destroyed,
401  [defines.events.on_equipment_inserted] = on_equipment_inserted,
402  [defines.events.on_equipment_removed] = on_equipment_removed,
403  [defines.events.on_object_destroyed] = on_object_destroyed,
404  [defines.events.on_player_mined_entity] = on_entity_destroyed,
405  [defines.events.on_robot_built_entity] = on_entity_built,
406  [defines.events.on_robot_mined_entity] = on_entity_destroyed,
407  [defines.events.on_script_trigger_effect] = on_script_trigger_effect,
408  [defines.events.script_raised_built] = on_entity_built,
409  [defines.events.script_raised_destroy] = on_entity_destroyed,
410  [defines.events.script_raised_revive] = on_entity_built,
411  [defines.events.on_space_platform_built_entity] = on_entity_built,
412  [defines.events.on_space_platform_mined_entity] = on_entity_destroyed,
413}
414
415return tesla_coil
416
417--[[
418  -- Quick testing code
419  event.register(remote.call("EditorExtensions", "debug_world_ready_event"), function()
420    local surface = game.surfaces.nauvis
421
422    local coils = {
423      { x = -9.5, y = -9.5 },
424      { x = 10.5, y = -9.5 },
425      { x = -9.5, y = 10.5 },
426      { x = 10.5, y = 10.5 },
427    }
428    for _, position in pairs(coils) do
429      surface.create_entity({
430        name = "kr-tesla-coil",
431        direction = defines.direction.north,
432        position = position,
433        force = game.forces.player,
434        raise_built = true,
435        create_build_effect_smoke = false,
436      })
437    end
438    surface.create_entity({
439      name = "ee-infinity-accumulator-primary-output",
440      position = { x = -1, y = -9 },
441      force = game.forces.player,
442      raise_built = true,
443      create_build_effect_smoke = false,
444    })
445    surface.create_entity({
446      name = "ee-super-substation",
447      position = { x = 1, y = -9 },
448      force = game.forces.player,
449      raise_built = true,
450      create_build_effect_smoke = false,
451    })
452
453    local character = game.get_player(1).character
454    local armor_inventory = character.get_inventory(defines.inventory.character_armor)
455    if armor_inventory and armor_inventory.valid then
456      local armor = armor_inventory[1]
457      if armor and armor.valid_for_read then
458        armor.grid.put({ name = "kr-energy-absorber-equipment", position = { 5, 5 } })
459      end
460    end
461  end)
462]]