z_mirror/Krastorio2

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

Caleb HeuerChange to four-space indentation07409ca6

four-space-indentation
14.3 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 = 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--- @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_buffer_capacity then
317        -- Calculate how much to add
318        local to_add = charging_rate / 60 * cooldown
319        local result = energy + to_add
320        local tower = tower_data.entities.tower
321
322        if result >= absorber_buffer_capacity then
323            absorber.energy = absorber_buffer_capacity
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]]