z_mirror/Krastorio2

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

masterlink720fix: Update tesla coil script to use new equivalent for LuaEntity::active (#758)1bad270e

trunk
13.5 KiB474 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_fraction = 0.1 -- % of max energy per tick
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.disabled_by_script = true
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--- @param grid LuaEquipmentGrid
137--- @param equipment_name string
138--- @return LuaEquipment?
139local function grid_find_any(grid, equipment_name)
140  for _, equipment in pairs(grid.equipment) do
141    if equipment.name == equipment_name then
142      return equipment
143    end
144  end
145end
146
147-- TARGET
148-- An entity that will receive energy from a tesla coil
149
150--- @param target LuaEntity
151--- @return GridData?
152local function get_grid_data(target)
153  --- @type LuaEquipmentGrid
154  local grid
155  if target.type == "character" then
156    local armor_inventory = target.get_inventory(defines.inventory.character_armor)
157    if armor_inventory and armor_inventory.valid then
158      local armor = armor_inventory[1]
159      if armor and armor.valid_for_read then
160        grid = armor.grid
161      end
162    end
163  else
164    grid = target.grid
165  end
166
167  if grid then
168    return {
169      absorber = grid_find_any(grid, "kr-energy-absorber-equipment"),
170      grid = grid,
171    }
172  end
173end
174
175--- Updates the absorber object in a target's equipment grid
176--- @param grid LuaEquipmentGrid
177local function update_target_grid(grid)
178  for _, target_data in pairs(storage.tesla_coil.targets) do
179    local grid_data = target_data.grid_data
180    if grid_data.grid.valid and grid_data.grid == grid then
181      grid_data.absorber = grid_find_any(grid, "kr-energy-absorber-equipment")
182    end
183  end
184end
185
186--- @param target LuaEntity
187--- @param tower_data TowerData
188local function add_target(target, tower_data)
189  local target_unit_number = target.unit_number
190  --- @cast target_unit_number -?
191  local tower = tower_data.entities.tower
192  if not tower.valid or tower.energy < required_energy then
193    return
194  end
195  -- Check the target's equipment grid for an energy absorber
196  local grid_data = get_grid_data(target)
197  if grid_data and grid_data.absorber and grid_data.absorber.valid then
198    --- @type TargetData
199    local target_data = {
200      connections = {
201        --- @type table<number, ConnectionData>
202        by_beam = {},
203        --- @type table<number, ConnectionData>
204        by_tower = {},
205      },
206      entity = target,
207      --- @type number?
208      full_tick = nil,
209      grid_data = grid_data,
210      unit_number = target_unit_number,
211    }
212    storage.tesla_coil.targets[target_unit_number] = target_data
213    return target_data
214  end
215end
216
217--- @param target_unit_number number
218local function remove_target(target_unit_number)
219  storage.tesla_coil.targets[target_unit_number] = nil
220end
221
222-- CONNECTION
223-- A connection between a tower and a target, comprising of a beam
224-- There can be unlimited connections per target
225
226--- @param target_data TargetData
227--- @param tower_data TowerData
228--- @return boolean
229local function add_connection(target_data, tower_data)
230  -- Check if the absorber has space
231  local absorber = target_data.grid_data.absorber
232  if not absorber or not absorber.valid then
233    remove_target(target_data.unit_number)
234    return false
235  end
236
237  local tower = tower_data.entities.tower
238  if not tower.valid then
239    remove_target(target_data.unit_number)
240    return false
241  end
242
243  if absorber.energy >= absorber_buffer_capacity then
244    return false
245  end
246
247  -- Create beam entity
248  local beam = tower_data.entities.tower.surface.create_entity({
249    name = "kr-tesla-coil-electric-beam",
250    source = tower_data.entities.tower,
251    source_offset = { 0, -2.2 },
252    position = tower_data.entities.tower.position,
253    target = target_data.entity,
254    duration = 0,
255    max_length = range,
256    force = tower_data.entities.tower.force,
257    raise_built = true,
258  })
259  if not beam then
260    return false
261  end
262  local beam_number = script.register_on_object_destroyed(beam)
263
264  storage.tesla_coil.beams[beam_number] = {
265    beam = beam,
266    beam_number = beam_number,
267    target_data = target_data,
268    tower_data = tower_data,
269  }
270
271  --- @class ConnectionData
272  local connection_data = {
273    beam = beam,
274    beam_number = beam_number,
275    tower_data = tower_data,
276  }
277  target_data.connections.by_beam[beam_number] = connection_data
278  target_data.connections.by_tower[tower_data.tower_unit_number] = connection_data
279
280  return true
281end
282
283--- @param target_data TargetData
284--- @param tower_data TowerData
285local function remove_connection(target_data, tower_data)
286  local connection_data = target_data.connections.by_tower[tower_data.tower_unit_number]
287
288  -- Destroy beam if it still exists
289  if connection_data.beam.valid then
290    connection_data.beam.destroy()
291  end
292
293  local beam_number = connection_data.beam_number
294  storage.tesla_coil.beams[beam_number] = nil
295
296  target_data.connections.by_beam[beam_number] = nil
297  target_data.connections.by_tower[tower_data.tower_unit_number] = nil
298
299  if table_size(target_data.connections.by_beam) == 0 then
300    remove_target(target_data.unit_number)
301  end
302end
303
304--- @param target_data TargetData
305--- @param tower_data TowerData
306local function update_connection(target_data, tower_data)
307  local absorber = target_data.grid_data.absorber
308
309  -- Check if the tower is powered
310  if not absorber or not absorber.valid or tower_data.entities.tower.energy < required_energy then
311    remove_connection(target_data, tower_data)
312    return
313  end
314
315  local energy = absorber.energy
316  if energy < absorber.max_energy then
317    -- Calculate how much to add
318    local to_add = charging_rate_fraction * absorber.max_energy
319    local result = energy + to_add
320    local tower = tower_data.entities.tower
321
322    if result >= absorber.max_energy then
323      absorber.energy = absorber.max_energy
324      target_data.full_tick = game.tick
325    else
326      absorber.energy = result
327      target_data.full_tick = nil
328    end
329
330    tower.energy = tower.energy - (to_add * loss_multiplier)
331  elseif target_data.full_tick and target_data.full_tick + cooldown <= game.tick then
332    remove_connection(target_data, tower_data)
333  end
334end
335
336--- @param target LuaEntity
337--- @param turret LuaEntity
338local function process_turret_fire(target, turret)
339  local tower_data = storage.tesla_coil.turrets[turret.unit_number]
340  if not tower_data then
341    return
342  end
343
344  local target_data = storage.tesla_coil.targets[target.unit_number]
345  if not target_data then
346    target_data = add_target(target, tower_data)
347  end
348
349  if target_data then
350    -- Just in case (#182)
351    if not target_data.entity.valid then
352      target_data.entity = target
353    end
354    local connection = target_data.connections.by_tower[tower_data.tower_unit_number]
355    if connection or add_connection(target_data, tower_data) then
356      update_connection(target_data, tower_data)
357    end
358  end
359end
360
361-- EVENTS
362
363--- @param e EventData.on_object_destroyed
364local function on_object_destroyed(e)
365  local beam_data = storage.tesla_coil.beams[e.registration_number]
366  if beam_data then
367    remove_connection(beam_data.target_data, beam_data.tower_data)
368  end
369end
370
371--- @param e EventData.on_equipment_inserted
372local function on_equipment_inserted(e)
373  if not e.equipment.valid or e.equipment.name ~= "kr-energy-absorber-equipment" then
374    return
375  end
376  update_target_grid(e.grid)
377end
378
379--- @param e EventData.on_equipment_removed
380local function on_equipment_removed(e)
381  if e.equipment == "kr-energy-absorber-equipment" then
382    update_target_grid(e.grid)
383  end
384end
385
386--- @param e EventData.on_script_trigger_effect
387local function on_script_trigger_effect(e)
388  if e.effect_id == "kr-tesla-coil-trigger" then
389    process_turret_fire(e.target_entity, e.source_entity)
390  end
391end
392
393--- @class TeslaCoilHandler : event_handler
394local tesla_coil = {}
395
396function tesla_coil.on_init()
397  storage.tesla_coil = {
398    --- @type table<number, BeamData>
399    beams = {},
400    --- @type table<number, TowerData>
401    turrets = {},
402    --- @type table<number, TowerData>
403    towers = {},
404    --- @type table<number, TargetData>
405    targets = {},
406  }
407end
408
409tesla_coil.events = {
410  [defines.events.on_built_entity] = on_entity_built,
411  [defines.events.on_entity_cloned] = on_entity_built,
412  [defines.events.on_entity_died] = on_entity_destroyed,
413  [defines.events.on_equipment_inserted] = on_equipment_inserted,
414  [defines.events.on_equipment_removed] = on_equipment_removed,
415  [defines.events.on_object_destroyed] = on_object_destroyed,
416  [defines.events.on_player_mined_entity] = on_entity_destroyed,
417  [defines.events.on_robot_built_entity] = on_entity_built,
418  [defines.events.on_robot_mined_entity] = on_entity_destroyed,
419  [defines.events.on_script_trigger_effect] = on_script_trigger_effect,
420  [defines.events.script_raised_built] = on_entity_built,
421  [defines.events.script_raised_destroy] = on_entity_destroyed,
422  [defines.events.script_raised_revive] = on_entity_built,
423  [defines.events.on_space_platform_built_entity] = on_entity_built,
424  [defines.events.on_space_platform_mined_entity] = on_entity_destroyed,
425}
426
427return tesla_coil
428
429--[[
430  -- Quick testing code
431  event.register(remote.call("EditorExtensions", "debug_world_ready_event"), function()
432    local surface = game.surfaces.nauvis
433
434    local coils = {
435      { x = -9.5, y = -9.5 },
436      { x = 10.5, y = -9.5 },
437      { x = -9.5, y = 10.5 },
438      { x = 10.5, y = 10.5 },
439    }
440    for _, position in pairs(coils) do
441      surface.create_entity({
442        name = "kr-tesla-coil",
443        direction = defines.direction.north,
444        position = position,
445        force = game.forces.player,
446        raise_built = true,
447        create_build_effect_smoke = false,
448      })
449    end
450    surface.create_entity({
451      name = "ee-infinity-accumulator-primary-output",
452      position = { x = -1, y = -9 },
453      force = game.forces.player,
454      raise_built = true,
455      create_build_effect_smoke = false,
456    })
457    surface.create_entity({
458      name = "ee-super-substation",
459      position = { x = 1, y = -9 },
460      force = game.forces.player,
461      raise_built = true,
462      create_build_effect_smoke = false,
463    })
464
465    local character = game.get_player(1).character
466    local armor_inventory = character.get_inventory(defines.inventory.character_armor)
467    if armor_inventory and armor_inventory.valid then
468      local armor = armor_inventory[1]
469      if armor and armor.valid_for_read then
470        armor.grid.put({ name = "kr-energy-absorber-equipment", position = { 5, 5 } })
471      end
472    end
473  end)
474]]