You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

952 lines
38 KiB

-- Used for energy transmission and cross-surface energy beam weapons.
local EnergyBeam = {}
EnergyBeam.name_emitter = mod_prefix.."energy-transmitter-emitter"
EnergyBeam.name_chamber = mod_prefix.."energy-transmitter-chamber"
EnergyBeam.name_injector = mod_prefix.."energy-transmitter-injector"
EnergyBeam.name_injector_reactor = mod_prefix.."energy-transmitter-injector-reactor"
EnergyBeam.name_receiver = mod_prefix.."energy-receiver"
EnergyBeam.name_receiver_beam = mod_prefix.."energy-receiver-beam" -- projectile
EnergyBeam.name_glaive_beam = mod_prefix.."energy-glaive-beam" -- projectile
EnergyBeam.name_glaive_beam_sprite = mod_prefix.."energy-glaive-beam-sprite" -- sprite
EnergyBeam.name_glaive_path_fx = mod_prefix.."energy-glaive-path-fx"
EnergyBeam.name_glaive_damage_projectile = mod_prefix.."energy-glaive-damage-projectile"
EnergyBeam.name_glaive_damage_aoe = mod_prefix.."energy-glaive-damage-aoe"
EnergyBeam.name_glaive_damage_aoe_large = mod_prefix.."energy-glaive-damage-aoe-large"
EnergyBeam.emitter_width = 10
EnergyBeam.chamber_box = {x=8, y=4}
EnergyBeam.injector_length = 5
EnergyBeam.name_transmitter_targeter = mod_prefix .. "energy-transmitter-targeter"
EnergyBeam.name_target_activity_type = "energy-transmitter-target"
EnergyBeam.name_transmitter_effect = mod_prefix .. "energy-transmitter-beam"
EnergyBeam.transfer_interval = 60
EnergyBeam.specific_heat = 1000000000 -- 1GJ
EnergyBeam.injector_donate_rate = 0.05
EnergyBeam.injector_waste_rate = 0.005
EnergyBeam.beam_speed_base = 0.05
EnergyBeam.beam_speed_temperature_multiplier = 0.05
EnergyBeam.beam_speed_temperature_exponent = 0.9
EnergyBeam.large_aoe_heat = 100
EnergyBeam.glaive_cost_aoe = 0.5
EnergyBeam.glaive_cost_aoe_large = 1.2
--- Gets the EnergyBeam for this unit_number
---@param unit_number number
function EnergyBeam.from_unit_number (unit_number)
if not unit_number then Log.trace("EnergyBeam.from_unit_number: invalid unit_number: nil") return end
unit_number = tonumber(unit_number)
if global.energy_transmitters[unit_number] then
return global.energy_transmitters[unit_number]
else
Log.trace("EnergyBeam.from_unit_number: invalid unit_number: " .. unit_number)
end
end
--- Gets the EnergyBeam for this entity
---@param entity LuaEntity
function EnergyBeam.from_entity (entity)
if not(entity and entity.valid) then
Log.trace("EnergyBeam.from_entity: invalid entity")
return
end
return EnergyBeam.from_unit_number(entity.unit_number)
end
--- Does the enery beamer have a destination set
---@param tree any
function EnergyBeam.has_destination(tree)
return tree and tree.destination and tree.destination.coordinate and tree.destination.zone
end
function EnergyBeam.get_transmission_efficiency(origin, destination)
if origin and destination then
local delta_v = Zone.get_launch_delta_v(origin) + Zone.get_travel_delta_v(origin, destination)
local efficiency = 1- delta_v/(delta_v+10000)
local origin_stellar_position = Zone.get_stellar_position(origin)
local destination_stellar_position = Zone.get_stellar_position(destination)
if destination.type == "anomaly" or origin.type == "anomaly" then
efficiency = efficiency * 0.01
else
if origin_stellar_position and destination_stellar_position then -- interstellar less effective
if Util.vectors_delta_length(origin_stellar_position, destination_stellar_position) > 0.001 then
efficiency = efficiency * 0.5
end
else
efficiency = efficiency * 0.5
end
end
if Zone.is_solid(origin) then
efficiency = efficiency * 0.5
end
return efficiency
end
end
function EnergyBeam.get_coordinate(tree)
if tree.destination and tree.destination.coordinate then
return tree.destination.coordinate
end
--return tree.emitter.position -- return nil
end
function EnergyBeam.clear_end_beam(tree)
if tree.receiver_beam and tree.receiver_beam.valid then
tree.receiver_beam.destroy()
tree.receiver_beam = nil
end
if tree.glaive_beam and tree.glaive_beam.valid then
tree.glaive_beam.destroy()
tree.glaive_beam = nil
end
EnergyBeam.remove_markers(tree)
end
function EnergyBeam.collect_heat(tree, waste)
local temperature = 0
for _, injector_reactor in pairs(tree.injector_reactors) do
if injector_reactor.valid then
local heat = (injector_reactor.temperature - 15) * EnergyBeam.injector_donate_rate
if heat > 1 then heat = math.pow(heat, 0.5) end -- reduce overload charge efficiency
local remove = (injector_reactor.temperature - 15) * (waste and EnergyBeam.injector_waste_rate or EnergyBeam.injector_donate_rate)
injector_reactor.temperature = injector_reactor.temperature - remove
temperature = temperature + heat
end
end
return temperature
end
function EnergyBeam.beam_step_idle(tree)
EnergyBeam.clear_end_beam(tree)
if tree.emitter_beam and tree.emitter_beam.valid then
tree.emitter_beam.destroy()
end
local temperature = EnergyBeam.collect_heat(tree, true) -- waste
tree.last_temperature = temperature
if tree.destination and tree.destination.zone then
local efficiency = EnergyBeam.get_transmission_efficiency(tree.zone, tree.destination.zone)
tree.last_efficiency = efficiency
else
tree.last_efficiency = 0
end
end
function EnergyBeam.find_enemy(tree)
local destination_surface = Zone.get_make_surface(tree.destination.zone)
local position = tree.destination.coordinate or {x = 0, y = 0}
local force = game.forces[tree.force_name]
local enemy = find_enemy(force, destination_surface, position)
if enemy then
tree.destination.coordinate = enemy.position
tree.destination.enemy = enemy
force.chart(destination_surface, Util.position_to_area(enemy.position, 32))
return
end
-- nothing found
game.forces[tree.force_name].print({
"space-exploration.energy-transmitter-no-enemies-found",
"[gps="..math.floor(tree.emitter.position.x)..","..math.floor(tree.emitter.position.y)..","..destination_surface.name.."]"
})
tree.mode = "off"
end
function EnergyBeam.scale_glaive_sprite(tree)
local position = EnergyBeam.get_coordinate(tree)
if not position then
if not (tree.glaive_beam and tree.glaive_beam.valid) then return end
position = tree.glaive_beam.position
end
if not (tree.glaive_beam and tree.glaive_beam.valid) then
local destination_surface = Zone.get_make_surface(tree.destination.zone)
tree.glaive_beam = destination_surface.create_entity{
name = EnergyBeam.name_glaive_beam,
position = position,
target = Util.vectors_add(position, {x=0,y=-1}),
speed = 0,
force = tree.force_name
}
end
if not (tree.glaive_beam_sprite_id and rendering.is_valid(tree.glaive_beam_sprite_id)) then
tree.glaive_beam_sprite_id = rendering.draw_sprite{
target = tree.glaive_beam,
sprite = EnergyBeam.name_glaive_beam_sprite,
surface = tree.glaive_beam.surface,
}
end
rendering.set_y_scale(tree.glaive_beam_sprite_id, 0.5 + 1.5 * math.min(1, tree.last_temperature / 100))
rendering.set_x_scale(tree.glaive_beam_sprite_id, 0.4 + 2 * math.min(1, tree.last_temperature / 100))
rendering.set_color(tree.glaive_beam_sprite_id, {
r = 1,
g = 0.5 + 0.5 * math.min(1, tree.last_temperature / 100),
b = 0.5 + 0.5 * math.min(1, tree.last_temperature / 100) })
end
function EnergyBeam.remove_markers(tree)
if tree.markers then
for _, marker in pairs(tree.markers) do
if marker and marker.valid then
marker.destroy()
end
end
end
tree.markers = nil
end
function EnergyBeam.add_markers(tree)
EnergyBeam.remove_markers(tree)
if tree.glaive_beam and tree.glaive_beam.valid then
tree.markers = {}
for force_name, forcedata in pairs(global.forces) do
if force_name ~= "friendly" and force_name ~= "ignore" and force_name ~= "capture" then
tree.markers[force_name] = game.forces[force_name].add_chart_tag(tree.glaive_beam.surface, {
icon = {type = "virtual", name = mod_prefix.."heat"},
position = tree.glaive_beam.position,
text = "Energy Glaive"
})
end
end
end
end
function EnergyBeam.beam_step(tree)
local mode = tree.mode or "off"
local position = EnergyBeam.get_coordinate(tree)
if position == nil and mode == "auto-glaive" then
EnergyBeam.find_enemy(tree)
position = EnergyBeam.get_coordinate(tree)
end
if mode == "off" or (not position) or not (tree.destination and tree.destination.zone) then
EnergyBeam.beam_step_idle(tree)
else
local zone = tree.zone
local efficiency = EnergyBeam.get_transmission_efficiency(zone, tree.destination.zone)
tree.last_efficiency = efficiency
local destination_surface = Zone.get_make_surface(tree.destination.zone)
local emitter_beam = false
if mode == "energise" then
local receiver = destination_surface.find_entity(EnergyBeam.name_receiver, position)
if receiver then
local temperature = EnergyBeam.collect_heat(tree)
tree.last_temperature = temperature
if temperature > 0.1 then
emitter_beam = true
if tree.glaive_beam then EnergyBeam.clear_end_beam(tree) end
receiver.temperature = receiver.temperature + temperature * efficiency
if not (tree.receiver_beam and tree.receiver_beam.valid) then
tree.receiver_beam = destination_surface.create_entity{
name = EnergyBeam.name_receiver_beam,
position = receiver.position,
target = Util.vectors_add(receiver.position, {x=0,y=-1}),
speed = 0
}
end
end
else
EnergyBeam.beam_step_idle(tree)
end
if emitter_beam == false then EnergyBeam.clear_end_beam(tree) end
elseif mode == "glaive" or mode == "auto-glaive" then
local temperature = EnergyBeam.collect_heat(tree)
tree.last_temperature = temperature
if temperature > 0.1 then
EnergyBeamDefence.zone_add_pressure(tree.destination.zone, tree)
if tree.receiver_beam then EnergyBeam.clear_end_beam(tree) end
tree.glaive_energy = (tree.glaive_energy or 0) + temperature * efficiency
if EnergyBeamDefence.zone_is_defended(tree.destination.zone, tree.force_name) then
-- the beam is blocked but pressure is being applied
if tree.glaive_beam and tree.glaive_beam.valid then
tree.glaive_beam.destroy()
tree.glaive_beam = nil
end
tree.glaive_energy = 0
emitter_beam = true
else
if tree.glaive_energy > 0.1 then
emitter_beam = true
end
if emitter_beam == false then
EnergyBeam.beam_move(tree) -- removes?
else
EnergyBeam.scale_glaive_sprite(tree)
if mode == "auto-glaive" then
-- find target
if tree.destination.coordinate == nil or Util.vectors_delta_length(tree.destination.coordinate, tree.glaive_beam.position) < 0.1 then
EnergyBeam.find_enemy(tree)
end
end
tree.glaive_beam.surface.create_entity{
name = "fire-flame-on-tree",
position = tree.glaive_beam.position,
force = tree.force_name
}
EnergyBeam.add_markers(tree)
end
end
end
end
if emitter_beam then
if not (tree.emitter_beam and tree.emitter_beam.valid) then
tree.emitter_beam = tree.emitter.surface.create_entity{
name = EnergyBeam.name_transmitter_effect,
position = tree.emitter.position,
target = {x = tree.emitter.position.x, y = tree.emitter.position.y - 1},
speed = 0}
end
else
if tree.emitter_beam and tree.emitter_beam.valid then
tree.emitter_beam.destroy()
end
end
end
end
function EnergyBeam.beam_move(tree)
if not tree.destination.zone then
tree.mode = "off"
EnergyBeam.beam_step_idle(tree)
return
end
local position = EnergyBeam.get_coordinate(tree)
if not position then
if not (tree.glaive_beam and tree.glaive_beam.valid) then return end
position = tree.glaive_beam.position
end
EnergyBeam.scale_glaive_sprite(tree)
local curent_position = tree.glaive_beam.position
local move_speed = EnergyBeam.beam_speed_base * (1 + math.pow(EnergyBeam.beam_speed_temperature_multiplier * tree.last_temperature, EnergyBeam.beam_speed_temperature_exponent))
local new_position = util.move_to(curent_position, position, move_speed, false)
tree.glaive_beam.teleport(new_position)
-- update the target position to a potentially moving enemy
local target_valid = tree.destination.enemy and tree.destination.enemy.valid and tree.destination.enemy.destructible
if target_valid then
tree.destination.coordinate = tree.destination.enemy.position
end
-- find a new enemy if we are at the destination and the enemy is not still alive
if Util.vectors_delta_length(new_position, position) < 0.1 and tree.mode == "auto-glaive" and not target_valid then
EnergyBeam.find_enemy(tree)
end
tree.glaive_energy_2 = tree.glaive_energy_2 or 0
if tree.glaive_energy_2 < tree.glaive_energy then
tree.glaive_energy_2 = tree.glaive_energy_2 + tree.glaive_energy / 600
tree.glaive_energy = tree.glaive_energy - tree.glaive_energy / 600
end
local energy_to_use = tree.glaive_energy_2 / 100
local energy_used = 0
--local cost_projectile = 0.05
if new_position.x ~= curent_position.x and new_position.y ~= curent_position.y and math.random() < 0.1 then
tree.glaive_beam.surface.create_entity{
name = "fire-flame-on-tree",
position = tree.glaive_beam.position,
force = tree.force_name
}
tree.glaive_beam.surface.create_entity{
name = EnergyBeam.name_glaive_path_fx,
position = tree.glaive_beam.position,
force = tree.force_name
}
end
while energy_used + EnergyBeam.glaive_cost_aoe_large <= energy_to_use do
local r = math.random()
if r < tree.last_temperature/EnergyBeam.large_aoe_heat then -- transition the damage to lower direct dps but larger area as it gets towards 100 GW
energy_used = energy_used + EnergyBeam.glaive_cost_aoe_large
tree.glaive_beam.surface.create_entity{
name = EnergyBeam.name_glaive_damage_aoe_large,
position = tree.glaive_beam.position,
force = tree.force_name
}
else
energy_used = energy_used + EnergyBeam.glaive_cost_aoe
tree.glaive_beam.surface.create_entity{
name = EnergyBeam.name_glaive_damage_aoe,
position = tree.glaive_beam.position,
force = tree.force_name
}
end
--[[else
energy_used = energy_used + cost_projectile
tree.glaive_beam.surface.create_entity{
name = EnergyBeam.name_glaive_damage_projectile,
position = tree.glaive_beam.position,
target = Util.vectors_add(tree.glaive_beam.position, Util.orientation_to_vector(math.random(), 5)),
speed = 1,
max_range = 5,
force = tree.force_name
}
end]]
end
tree.glaive_energy_2 = tree.glaive_energy_2 - energy_used
end
function EnergyBeam.destroy(tree)
if not tree then
Log.trace("tree_destroy: no tree")
return
end
tree.valid = false
EnergyBeam.clear_end_beam(tree)
if tree.emitter_beam and tree.emitter_beam.valid then
tree.emitter_beam.destroy()
end
global.energy_transmitters[tree.unit_number] = nil
-- if a player has this gui open then close item-layer
local gui_name = EnergyBeamGUI.name_transmitter_gui_root
for _, player in pairs(game.connected_players) do
local root = player.gui.relative[gui_name]
if root and root.tags and root.tags.unit_number == tree.unit_number then
root.destroy()
end
end
end
function EnergyBeam.on_tick(tree)
if global.energy_transmitters then
-- beam update
for _, tree in pairs(global.energy_transmitters) do
if (game.tick + tree.unit_number) % 60 == 0 then
EnergyBeam.beam_step(tree)
elseif tree.glaive_beam then
EnergyBeam.beam_move(tree)
end
end
-- gui update
if game.tick % 60 == 0 then
for _, player in pairs(game.connected_players) do
EnergyBeamGUI.gui_update(player)
end
end
end
end
Event.addListener(defines.events.on_tick, EnergyBeam.on_tick)
function EnergyBeam.on_player_selected_area(event)
if (event.item == EnergyBeam.name_transmitter_targeter) then
local player = game.players[event.player_index]
local playerdata = get_make_playerdata(player)
if playerdata.remote_view_activity and playerdata.remote_view_activity.type == EnergyBeam.name_target_activity_type then
local tree = playerdata.remote_view_activity.tree
if tree.emitter and tree.emitter.valid then
tree.destination.coordinate = {
x = (event.area.left_top.x + event.area.right_bottom.x) / 2,
y = (event.area.left_top.y + event.area.right_bottom.y) / 2
}
tree.destination.zone = Zone.from_surface(player.surface)
player.print({"space-exploration.energy-transmitter-coordinates-set", math.floor(tree.destination.coordinate.x), math.floor(tree.destination.coordinate.y)})
if tree.mode == "energise" then
EnergyBeam.clear_end_beam(tree)
end
-- reopned the gui and stop targetting
player.opened = tree.emitter
player.cursor_stack.set_stack(nil)
end
end
end
end
Event.addListener(defines.events.on_player_selected_area, EnergyBeam.on_player_selected_area)
function EnergyBeam.on_init(event)
global.energy_transmitters = {}
end
Event.addListener("on_init", EnergyBeam.on_init, true)
function EnergyBeam.chamber_connections(entity, update_neighbours)
local surface = entity.surface
local is_horizontal = entity.direction == defines.direction.east or entity.direction == defines.direction.west
local x = (is_horizontal and EnergyBeam.chamber_box.x or EnergyBeam.chamber_box.y)/2
local y = (is_horizontal and EnergyBeam.chamber_box.y or EnergyBeam.chamber_box.x)/2
local north = surface.find_entity(EnergyBeam.name_chamber, Util.vectors_add(entity.position, {x=0, y= -y -1}))
if north and north.position.x == entity.position.x then
if north.position.y == entity.position.y - y - EnergyBeam.chamber_box.y / 2 then -- north is connected by side
--Log.trace("north is connected by side")
if not is_horizontal then
surface.create_entity{
name=mod_prefix .. "energy-transmitter-chamber-addon-bottom",
position= Util.vectors_add(north.position, {x=0, y=1}),
surface=surface,
}
end
elseif north.position.y == entity.position.y - y - EnergyBeam.chamber_box.x / 2 then -- north is connected by end
--Log.trace("north is connected by end")
if is_horizontal then
surface.create_entity{
name=mod_prefix .. "energy-transmitter-chamber-addon-top",
position= Util.vectors_add(entity.position, {x=0, y=0.5}),
surface=surface,
}
end
end
end
local south = surface.find_entity(EnergyBeam.name_chamber, Util.vectors_add(entity.position, {x=0, y= y +1}))
if south and south.position.x == south.position.x then
if south.position.y == entity.position.y + y + EnergyBeam.chamber_box.y / 2 then -- south is connected by side
--Log.trace("south is connected by side")
if not is_horizontal then
surface.create_entity{
name=mod_prefix .. "energy-transmitter-chamber-addon-top",
position= Util.vectors_add(south.position, {x=0, y=0.5}),
surface=surface,
}
end
elseif south.position.y == entity.position.y + y + EnergyBeam.chamber_box.x / 2 then -- south is connected by end
--Log.trace("south is connected by end")
if is_horizontal then
surface.create_entity{
name=mod_prefix .. "energy-transmitter-chamber-addon-bottom",
position= Util.vectors_add(entity.position, {x=0, y=1}),
surface=surface,
}
end
end
end
local east = surface.find_entity(EnergyBeam.name_chamber, Util.vectors_add(entity.position, {x=x+1, y=0}))
if east and east.position.y == entity.position.y then
if east.position.x == entity.position.x + x + EnergyBeam.chamber_box.y / 2 then -- east is connected by side
--Log.trace("east is connected by side")
if is_horizontal then
surface.create_entity{
name=mod_prefix .. "energy-transmitter-chamber-addon-left",
position= Util.vectors_add(east.position, {x=-1, y=0.01}),
surface=surface,
}
end
elseif east.position.x == entity.position.x + x + EnergyBeam.chamber_box.x / 2 then -- east is connected by end
--Log.trace("east is connected by end")
if not is_horizontal then
surface.create_entity{
name=mod_prefix .. "energy-transmitter-chamber-addon-right",
position= Util.vectors_add(entity.position, {x=1, y=0}),
surface=surface,
}
end
end
end
local west = surface.find_entity(EnergyBeam.name_chamber, Util.vectors_add(entity.position, {x=-x-1, y=0}))
if west and west.position.y == entity.position.y then
if west.position.x == entity.position.x - x - EnergyBeam.chamber_box.y / 2 then -- west is connected by side
--Log.trace("west is connected by side")
if is_horizontal then
surface.create_entity{
name=mod_prefix .. "energy-transmitter-chamber-addon-right",
position= Util.vectors_add(west.position, {x=1, y=0}),
surface=surface,
}
end
elseif west.position.x == entity.position.x - x - EnergyBeam.chamber_box.x / 2 then -- west is connected by end
--Log.trace("west is connected by end")
if not is_horizontal then
surface.create_entity{
name=mod_prefix .. "energy-transmitter-chamber-addon-left",
position= Util.vectors_add(entity.position, {x=-1, y=0.01}),
surface=surface,
}
end
end
end
-- injector connections
local leaves
if is_horizontal then
leaves = surface.find_entities_filtered{
name = EnergyBeam.name_injector,
area = Util.area_add_position({{-EnergyBeam.chamber_box.x / 2 + 1, -EnergyBeam.chamber_box.y / 2 - 1}, {EnergyBeam.chamber_box.x / 2 - 1, EnergyBeam.chamber_box.y / 2 + 1}}, entity.position)
}
else
leaves = surface.find_entities_filtered{
name = EnergyBeam.name_injector,
area = Util.area_add_position({{-EnergyBeam.chamber_box.y / 2 - 1, -EnergyBeam.chamber_box.x / 2 + 1}, {EnergyBeam.chamber_box.y / 2 + 1, EnergyBeam.chamber_box.x / 2 - 1}}, entity.position)
}
end
for _, injector in pairs(leaves) do
-- update
EnergyBeam.remove_injector_connections(injector)
EnergyBeam.injector_connections(injector)
end
end
function EnergyBeam.remove_chamber_connections(entity)
local surface = entity.surface
local is_horizontal = entity.direction == defines.direction.east or entity.direction == defines.direction.west
local x = is_horizontal and (EnergyBeam.chamber_box.x / 2 + 1) or (EnergyBeam.chamber_box.y / 2)
local y = is_horizontal and (EnergyBeam.chamber_box.y / 2) or (EnergyBeam.chamber_box.x / 2 + 1)
local addons = surface.find_entities_filtered{
name = {
mod_prefix .. "energy-transmitter-chamber-addon-top",
mod_prefix .. "energy-transmitter-chamber-addon-bottom",
mod_prefix .. "energy-transmitter-chamber-addon-left",
mod_prefix .. "energy-transmitter-chamber-addon-right",
},
area = {{entity.position.x-x, entity.position.y-y}, {entity.position.x+x,entity.position.y+y+1.5}}
}
for _, addon in pairs(addons) do
addon.destroy()
end
end
function EnergyBeam.injector_connections(entity)
local surface = entity.surface
local direction = entity.direction
local is_horizontal = direction == defines.direction.east or direction == defines.direction.west
local vector = Util.direction_to_vector(direction)
local chamber = surface.find_entity(EnergyBeam.name_chamber, Util.vectors_add(entity.position, Util.vector_multiply(vector, (EnergyBeam.injector_length + 1)/2)))
if chamber then
local chamber_is_horizontal = chamber.direction == defines.direction.east or chamber.direction == defines.direction.west
if is_horizontal ~= chamber_is_horizontal then
local addon = surface.create_entity{
name = mod_prefix .. "energy-transmitter-injector-addon",
position = Util.vectors_add(entity.position, Util.vector_multiply(vector, (EnergyBeam.injector_length)/2))
}
addon.graphics_variation = (direction + 2) / 2
end
end
end
function EnergyBeam.remove_injector_connections(entity)
local surface = entity.surface
local direction = entity.direction
local addons
if entity.name == EnergyBeam.name_injector then
local vector = Util.direction_to_vector(direction)
addons = {surface.find_entity(mod_prefix .. "energy-transmitter-injector-addon", Util.vectors_add(entity.position, Util.vector_multiply(vector, (EnergyBeam.injector_length)/2)))}
elseif entity.name == EnergyBeam.name_chamber then
local is_horizontal = direction == defines.direction.east or direction == defines.direction.west
local x = (is_horizontal and EnergyBeam.chamber_box.x or EnergyBeam.chamber_box.y)/2+0.5
local y = (is_horizontal and EnergyBeam.chamber_box.y or EnergyBeam.chamber_box.x)/2+0.5
addons = surface.find_entities_filtered{
name = mod_prefix .. "energy-transmitter-injector-addon",
area = Util.area_add_position({{-x,-y},{x,y}}, entity.position)
}
end
if addons then
for _, addon in pairs(addons) do
addon.destroy()
end
end
end
function EnergyBeam.rebuild_trees(zone, immediate)
-- remove old tick tasks
if global.tick_tasks then
for _, tick_task in pairs(global.tick_tasks) do
if tick_task.type == "grow_energy_tree" and tick_task.zone == zone then
tick_task.valid = false
global.tick_tasks[tick_task.id] = nil
end
end
end
if zone then
local tick_task = new_tick_task("grow_energy_tree")
tick_task.zone = zone
if immediate then
EnergyBeam.grow_energy_tree(tick_task)
end
end
end
function EnergyBeam.grow_energy_tree(tick_task)
local zone = tick_task.zone
local surface = Zone.get_make_surface(zone)
if not tick_task.emitters then
-- init
zone.energy_transmitters = zone.energy_transmitters or {}
global.energy_transmitters = global.energy_transmitters or {}
for unit_number, tree in pairs(zone.energy_transmitters) do
if not tree.emitter.valid then
-- remove invalid trees
zone.energy_transmitters[unit_number] = nil
global.energy_transmitters[unit_number] = nil
else
tree.chamberes = {}
tree.leaves = {}
tree.injector_reactors = {}
tree.open = {tree.emitter}
end
end
tick_task.emitters = surface.find_entities_filtered{name = EnergyBeam.name_emitter}
for _, emitter in pairs(tick_task.emitters) do
if not zone.energy_transmitters[emitter.unit_number] then
zone.energy_transmitters[emitter.unit_number] = {
type = "energy-beam-emitter-tree",
unit_number = emitter.unit_number,
valid = true,
zone = zone,
force_name = emitter.force.name,
emitter = emitter,
chamberes = {},
leaves = {},
injector_reactors = {},
open = {emitter},
destination = {type = "zone", zone = zone}
}
global.energy_transmitters[emitter.unit_number] = zone.energy_transmitters[emitter.unit_number]
end
end
-- chamberes are moreved from the list as they are assigned to trees
tick_task.chamberes = surface.find_entities_filtered{name = EnergyBeam.name_chamber}
-- when a chamber is added to a tree, trach the tree for the chamber (so leaves can find the tree by finding the chamber)
tick_task.chamber_trees = {}
end
tick_task.valid = false -- end unless chamberes are found
for _, tree in pairs(zone.energy_transmitters) do
local open = tree.open
tree.open = {}
for _, entity in pairs(open) do
if entity.name == EnergyBeam.name_emitter then
-- find chamberes connected end-on
local extent = (EnergyBeam.emitter_width + EnergyBeam.chamber_box.x)/2
for i, chamber in pairs(tick_task.chamberes) do
if (chamber.position.x == entity.position.x and (chamber.position.y == entity.position.y + extent or chamber.position.y == entity.position.y - extent))
or (chamber.position.y == entity.position.y and (chamber.position.x == entity.position.x + extent or chamber.position.x == entity.position.x - extent)) then
tree.chamberes[chamber.unit_number] = chamber
tree.open[chamber.unit_number] = chamber
tick_task.chamber_trees[chamber.unit_number] = tree
tick_task.chamberes[i] = nil
tick_task.valid = true
end
end
elseif entity.name == EnergyBeam.name_chamber then
-- find chamberes connected
local extent_short = (EnergyBeam.chamber_box.x + EnergyBeam.chamber_box.y) / 2
local extent_long = (EnergyBeam.chamber_box.x + EnergyBeam.chamber_box.x) / 2
local is_horizontal = entity.direction == defines.direction.east or entity.direction == defines.direction.west
for i, chamber in pairs(tick_task.chamberes) do
local chamber_is_horizontal = chamber.direction == defines.direction.east or chamber.direction == defines.direction.west
if (is_horizontal ~= chamber_is_horizontal and ((chamber.position.x == entity.position.x and (chamber.position.y == entity.position.y + extent_short or chamber.position.y == entity.position.y - extent_short))
or (chamber.position.y == entity.position.y and (chamber.position.x == entity.position.x + extent_short or chamber.position.x == entity.position.x - extent_short))))
or (is_horizontal == chamber_is_horizontal and ((chamber.position.x == entity.position.x and (chamber.position.y == entity.position.y + extent_long or chamber.position.y == entity.position.y - extent_long))
or (chamber.position.y == entity.position.y and (chamber.position.x == entity.position.x + extent_long or chamber.position.x == entity.position.x - extent_long)))) then
tree.chamberes[chamber.unit_number] = chamber
tick_task.chamber_trees[chamber.unit_number] = tree
tree.open[chamber.unit_number] = chamber
tick_task.chamberes[i] = nil
tick_task.valid = true
end
end
end
end
end
if not tick_task.valid then -- finished the chamberes, add the leaves
for _, entity in pairs(surface.find_entities_filtered{name = EnergyBeam.name_injector}) do
local direction = entity.direction
local is_horizontal = direction == defines.direction.east or direction == defines.direction.west
local vector = Util.direction_to_vector(direction)
local chamber = surface.find_entity(EnergyBeam.name_chamber, Util.vectors_add(entity.position, Util.vector_multiply(vector, (EnergyBeam.injector_length + 1)/2)))
if chamber then
local chamber_is_horizontal = chamber.direction == defines.direction.east or chamber.direction == defines.direction.west
if is_horizontal ~= chamber_is_horizontal then
-- if the chamber is part of a tree
if tick_task.chamber_trees[chamber.unit_number] then
-- add the injector to the tree
local injector_reactor = surface.find_entity(EnergyBeam.name_injector_reactor, entity.position)
if injector_reactor then
tick_task.chamber_trees[chamber.unit_number].leaves[entity.unit_number] = entity
tick_task.chamber_trees[chamber.unit_number].injector_reactors[injector_reactor.unit_number] = injector_reactor
end
end
end
end
end
for _, tree in pairs(zone.energy_transmitters) do
Log.trace(table_size(tree.leaves))
end
end
end
function EnergyBeam.on_entity_created(event)
local entity
if event.entity and event.entity.valid then
entity = event.entity
end
if event.created_entity and event.created_entity.valid then
entity = event.created_entity
end
if not entity then return end
if entity.name == EnergyBeam.name_chamber or entity.name == EnergyBeam.name_injector or entity.name == EnergyBeam.name_emitter then
local zone = Zone.from_surface(entity.surface)
if zone and zone.type ~= "spaceship" then
if entity.name == EnergyBeam.name_chamber then
-- check connections
EnergyBeam.chamber_connections(entity, true)
elseif entity.name == EnergyBeam.name_injector then
-- check connections
EnergyBeam.injector_connections(entity, true)
local reactor = entity.surface.create_entity{ name = EnergyBeam.name_injector_reactor, position = entity.position, force = entity.force}
reactor.destructible = false
end
EnergyBeam.rebuild_trees(zone, true)
local tree = global.energy_transmitters[entity.unit_number]
-- set settings
if tree and event.tags then
tree.destination.coordinate = event.tags.destination.coordinate
tree.destination.zone = Zone.from_name(event.tags.destination.zone_name)
tree.mode = event.tags.mode
end
if tree and entity.name == EnergyBeam.name_emitter and event.player_index and game.players[event.player_index] and game.players[event.player_index].connected then
EnergyBeamGUI.gui_open(game.players[event.player_index], tree)
end
else
cancel_entity_creation(entity, event.player_index, "Invalid surface.")
return
end
end
end
Event.addListener(defines.events.on_built_entity, EnergyBeam.on_entity_created)
Event.addListener(defines.events.on_robot_built_entity, EnergyBeam.on_entity_created)
Event.addListener(defines.events.script_raised_built, EnergyBeam.on_entity_created)
Event.addListener(defines.events.script_raised_revive, EnergyBeam.on_entity_created)
function EnergyBeam.on_entity_removed(event)
local entity
if event.entity and event.entity.valid then
entity = event.entity
end
if event.created_entity and event.created_entity.valid then
entity = event.created_entity
end
if not entity then return end
if entity.name == EnergyBeam.name_chamber then
EnergyBeam.remove_chamber_connections(entity, true)
EnergyBeam.remove_injector_connections(entity, true)
EnergyBeam.rebuild_trees(Zone.from_surface(entity.surface))
elseif entity.name == EnergyBeam.name_injector then
EnergyBeam.remove_injector_connections(entity, true)
EnergyBeam.rebuild_trees(Zone.from_surface(entity.surface))
local reactor = entity.surface.find_entity(EnergyBeam.name_injector_reactor, entity.position)
if reactor then reactor.destroy() end
elseif entity.name == EnergyBeam.name_emitter then
EnergyBeam.rebuild_trees(Zone.from_surface(entity.surface))
local tree = EnergyBeam.from_entity(entity)
if tree then
EnergyBeam.destroy(tree)
end
elseif entity.name == EnergyBeam.name_receiver then
local beam = entity.surface.find_entity(EnergyBeam.name_receiver_beam, entity.position)
if beam then beam.destroy() end
end
end
Event.addListener(defines.events.on_player_mined_entity, EnergyBeam.on_entity_removed)
Event.addListener(defines.events.on_robot_mined_entity, EnergyBeam.on_entity_removed)
Event.addListener(defines.events.on_entity_died, EnergyBeam.on_entity_removed)
Event.addListener(defines.events.script_raised_destroy, EnergyBeam.on_entity_removed)
--- Handles the player creating a blueprint by setting tags to store the state of energy emitters
---@param event any
function EnergyBeam.on_player_setup_blueprint(event)
local player_index = event.player_index
if player_index and game.players[player_index] and game.players[player_index].connected then
local player = game.players[player_index]
-- this setup code and checks is a workaround for the fact that the event doesn't specify the blueprint on the event
-- and the player.blueprint_to_setup isn't actually set in the case of copy/paste or blueprint library or select new contents
local blueprint = nil
if player and player.blueprint_to_setup and player.blueprint_to_setup.valid_for_read then blueprint = player.blueprint_to_setup
elseif player and player.cursor_stack.valid_for_read and player.cursor_stack.is_blueprint then blueprint = player.cursor_stack end
if blueprint and blueprint.is_blueprint_setup() then
local mapping = event.mapping.get()
local blueprint_entities = blueprint.get_blueprint_entities()
if blueprint_entities then
for _, blueprint_entity in pairs(blueprint_entities) do
if blueprint_entity.name == EnergyBeam.name_emitter then
local entity = mapping[blueprint_entity.entity_number]
if entity then
local tree = EnergyBeam.from_entity(entity)
if tree then
local tags = {}
tags.destination = {
coordinate = tree.destination.coordinate,
zone_name = tree.destination.zone and tree.destination.zone.name
}
tags.mode = tree.mode
blueprint.set_blueprint_entity_tags(blueprint_entity.entity_number, tags)
end
end
end
end
end
end
end
end
Event.addListener(defines.events.on_player_setup_blueprint, EnergyBeam.on_player_setup_blueprint)
--- Handles the player copy/pasting settings between energy emitters
---@param event any
function EnergyBeam.on_entity_settings_pasted(event)
local player_index = event.player_index
if player_index and game.players[player_index] and game.players[player_index].connected
and event.source and event.source.valid and event.destination and event.destination.valid then
if not (event.source.name == EnergyBeam.name_emitter) then return end
if not (event.destination.name == EnergyBeam.name_emitter) then return end
local player = game.players[player_index]
local tree_from = EnergyBeam.from_entity(event.source)
local tree_to = EnergyBeam.from_entity(event.destination)
if tree_from and tree_to then
-- actual settings copy
tree_to.destination.coordinate = table.deepcopy(tree_from.destination.coordinate)
tree_to.destination.zone = tree_from.destination.zone
tree_to.mode = tree_from.mode
if tree_to.destination.coordinate then
player.print({"space-exploration.energy-transmitter-coordinates-pasted", math.floor(tree_to.destination.coordinate.x), math.floor(tree_to.destination.coordinate.y)})
end
end
end
end
Event.addListener(defines.events.on_entity_settings_pasted, EnergyBeam.on_entity_settings_pasted)
return EnergyBeam