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.

394 lines
15 KiB

local SolarFlare = {}
SolarFlare.name_glaive_beam = mod_prefix.."energy-glaive-beam" -- projectile
SolarFlare.name_glaive_beam_sprite = mod_prefix.."energy-glaive-beam-sprite" -- sprite
SolarFlare.name_glaive_path_fx = mod_prefix.."energy-glaive-path-fx"
SolarFlare.name_glaive_damage_projectile = mod_prefix.."energy-glaive-damage-projectile"
SolarFlare.name_glaive_damage_aoe = mod_prefix.."energy-glaive-damage-aoe"
SolarFlare.name_glaive_damage_aoe_large = mod_prefix.."energy-glaive-damage-aoe-large"
SolarFlare.name_fire = mod_prefix .. "fire-flame-on-tree-no-pollution"
SolarFlare.max_age = 60*60*2 -- 5 minutes
SolarFlare.speed = 0.10
SolarFlare.damage_interval = 20
SolarFlare.warning_time = 60*60*60*24 -- 24 hours
SolarFlare.base_power = 2000000000
function SolarFlare.default_flare_power(zone)
local power_multiplier = Zone.get_solar(zone)
if zone.radius then
power_multiplier = power_multiplier * zone.radius / 5000
else
power_multiplier = 0.2 * power_multiplier
end
return power_multiplier
end
function SolarFlare.begin_flare(zone, targeting, power_multiplier, delay, max_age)
if not power_multiplier then
power_multiplier = SolarFlare.default_flare_power(zone)
end
delay = delay or 0
local tick_task = new_tick_task("solar-flare")
tick_task.zone = zone
tick_task.start_tick = game.tick + delay
tick_task.beams = {}
tick_task.power_multiplier = power_multiplier
tick_task.targeting = targeting
tick_task.max_age = max_age or SolarFlare.max_age
Log.trace("SolarFlare on zone: " .. zone.name.." power: "..power_multiplier)
end
function SolarFlare.tick_flare(tick_task)
if game.tick < tick_task.start_tick then return end
if not tick_task.pressure_applied then
tick_task.pressure_applied = true
EnergyBeamDefence.zone_add_pressure(tick_task.zone, tick_task)
tick_task.defended = EnergyBeamDefence.zone_is_defended(tick_task.zone)
end
if not tick_task.start_warning_sent then
tick_task.start_warning_sent = true
for _, force in pairs(game.forces) do
if Zone.is_visible_to_force(tick_task.zone, force.name) then
force.print({"space-exploration.alert-cme-arrived", tick_task.zone.name})
end
end
end
if game.tick > tick_task.start_tick + (tick_task.max_age or SolarFlare.max_age) or settings.global["se-cmes-max-frequency"].value == 0 then
for _, beam in pairs(tick_task.beams) do
if beam.beam and beam.beam.valid then
beam.beam.destroy()
end
if beam.markers then
for _, marker in pairs(beam.markers) do
if marker and marker.valid then
marker.destroy()
end
end
end
end
tick_task.valid = false
Log.trace("SolarFlare end")
local surface = Zone.get_surface(tick_task.zone)
surface.print({"space-exploration.alert-cme-passed"})
return
end
local age = game.tick - tick_task.start_tick
local age_p = age / (tick_task.max_age or SolarFlare.max_age)
--tick_task.age_peak = math.min(age_p, 1-age_p) * 2
tick_task.age_peak = math.sin(age_p*math.pi)
if game.tick % 60 == 0 then
tick_task.defended = EnergyBeamDefence.zone_is_defended(tick_task.zone)
end
if tick_task.defended then
while #tick_task.beams > 0 do
local beam = tick_task.beams[#tick_task.beams]
if beam.beam and beam.beam.valid then
beam.beam.destroy()
end
if beam.markers then
for _, marker in pairs(beam.markers) do
if marker and marker.valid then
marker.destroy()
end
end
end
table.remove(tick_task.beams, #tick_task.beams)
end
return
end
local surface = Zone.get_surface(tick_task.zone)
if not surface then
Log.trace("SolarFlare.begin_flare: No surface")
tick_task.valid = false
return
end
if not tick_task.chunk_count then
tick_task.chunk_count = 0
for chunk in surface.get_chunks() do
tick_task.chunk_count = tick_task.chunk_count + 1
end
end
-- tiny -> 2
-- 5k small = 1.9 -> 4
-- 50k medium = 11.18 -> 14
local target_beams = math.min(20, math.ceil(tick_task.age_peak * (2 + math.pow(tick_task.chunk_count / 2000, 0.75))))
while #tick_task.beams < target_beams do
local chunk = surface.get_random_chunk()
local position = {x = (chunk.x+math.random())*32, y = (chunk.y+math.random())*32}
chunk = surface.get_random_chunk()
local target_position = {x = (chunk.x+math.random())*32, y = (chunk.y+math.random())*32}
if #tick_task.beams == 0 then
if tick_task.targeting == "basic" then
local entities = surface.find_entities_filtered{type={"furnace","electric-pole","roboport","reactor"}}
if entities and #entities > 0 then
position = entities[math.random(#entities)].position
end
elseif tick_task.targeting == "crash" then
position = Util.vector_set_length(position, 80)
target_position = {x = 0, y = 0}
end
end
local beam = {
position = position,
target_position = target_position
}
beam.beam = surface.create_entity{
name = SolarFlare.name_glaive_beam,
position = position,
target = {0,0},
speed = 0,
force = "neutral"
}
if not (beam.beam_sprite_id and rendering.is_valid(beam.beam_sprite_id)) then
beam.beam_sprite_id = rendering.draw_sprite{
target = beam.beam,
sprite = SolarFlare.name_glaive_beam_sprite,
surface = surface,
}
end
rendering.set_y_scale(beam.beam_sprite_id, 1.5)
rendering.set_x_scale(beam.beam_sprite_id, 2)
rendering.set_color(beam.beam_sprite_id, {
r = 1,
g = 0.7,
b = 0.7
})
surface.create_entity{
name = SolarFlare.name_fire,
position = position,
}
table.insert(tick_task.beams, beam)
Log.trace("SolarFlare "..#tick_task.beams.." for tick_task " .. tick_task.id)
surface.print({"space-exploration.alert-cme-stream", "[gps="..math.floor(position.x)..","..math.floor(position.y)..","..surface.name.."]"})
end
while #tick_task.beams > target_beams do
local beam = tick_task.beams[#tick_task.beams]
if beam.beam and beam.beam.valid then
beam.beam.destroy()
end
if beam.markers then
for _, marker in pairs(beam.markers) do
if marker and marker.valid then
marker.destroy()
end
end
end
table.remove(tick_task.beams, #tick_task.beams)
end
if game.tick % 60 == 0 then
for i, beam in pairs(tick_task.beams) do
if beam.markers then
for _, marker in pairs(beam.markers) do
if marker and marker.valid then
marker.destroy()
end
end
end
beam.markers = {}
for force_name, forcedata in pairs(global.forces) do
if game.forces[force_name] and force_name ~= "friendly" and force_name ~= "ignore" and force_name ~= "capture" then
beam.markers[force_name] = game.forces[force_name].add_chart_tag(surface, {
icon = {type = "virtual", name = mod_prefix.."heat"},
position = beam.position,
text = "CME"
})
end
end
if (tick_task.targeting == "basic" or tick_task.targeting == "crash") and (i == 1 or i == 3) then
local chunk = surface.get_random_chunk()
beam.target_position = {x = (chunk.x+math.random())*32, y = (chunk.y+math.random())*32}
if math.random() < 0.1 then
local entities = surface.find_entities_filtered{type={"character", "furnace","electric-pole","roboport","reactor"}}
if entities and #entities > 0 and math.random() < 0.1 then
beam.target_position = entities[math.random(#entities)].position
end
end
else
if math.random() < 0.4 then
beam.target_position = Util.vectors_add(beam.beam.position, {x = (math.random()-0.5)*64, y = (math.random()-0.5)*64})
else
local chunk = surface.get_random_chunk()
beam.target_position = {x = (chunk.x+math.random())*32, y = (chunk.y+math.random())*32}
end
end
end
end
for _, beam in pairs(tick_task.beams) do
if not(beam.beam and beam.beam.valid) then
beam.beam = surface.create_entity{
name = SolarFlare.name_glaive_beam,
position = beam.position,
target = {0,0},
speed = 0,
force = "neutral"
}
end
if not (beam.beam_sprite_id and rendering.is_valid(beam.beam_sprite_id)) then
beam.beam_sprite_id = rendering.draw_sprite{
target = beam.beam,
sprite = SolarFlare.name_glaive_beam_sprite,
surface = surface,
}
end
rendering.set_y_scale(beam.beam_sprite_id, 1.5)
rendering.set_x_scale(beam.beam_sprite_id, 2)
rendering.set_color(beam.beam_sprite_id, {
r = 1,
g = 0.7,
b = 0.7
})
local new_position = util.move_to(beam.position, beam.target_position, SolarFlare.speed, false)
beam.position = new_position
beam.beam.teleport(new_position)
if math.random() < 0.1 then
surface.create_entity{
name = SolarFlare.name_fire,
position = new_position,
}
surface.create_entity{
name = SolarFlare.name_glaive_path_fx,
position = new_position,
force = "neutral"
}
end
if game.tick % math.ceil(SolarFlare.damage_interval / math.min(tick_task.power_multiplier or 1, 2)) == 0 then
surface.create_entity{
name = SolarFlare.name_glaive_damage_aoe_large,
position = new_position,
force = "neutral"
}
end
end
end
function SolarFlare.arrival_snap(zone, arrival_tick)
if Zone.is_solid(zone) then
local targeted_time_of_day = (arrival_tick / zone.ticks_per_day) % 1
-- target morning (0.75), snapped to the next minute
arrival_tick = arrival_tick + (0.75 - targeted_time_of_day) * zone.ticks_per_day
end
-- snap to minute so timer does not have a random delay
arrival_tick = math.ceil(arrival_tick / 3600) * 3600
return arrival_tick
end
function SolarFlare.on_nth_tick_3600()
if settings.global["se-cmes-max-frequency"].value == 0 then
if global.forces then
for force_name, forcedata in pairs(global.forces) do
if forcedata.solar_flare then
forcedata.solar_flare = nil
end
end
end
else
if game.tick == 0 then
local entities = game.surfaces[1].find_entities_filtered{
area = {{-64,-64},{64,64}}
}
for _, entity in pairs(entities) do
if string.find(entity.name, "wreck", 1, true) or string.find(entity.name, "crash", 1, true)then
entity.destructible = false
end
end
SolarFlare.begin_flare(Zone.from_name("Nauvis"), "crash", 0.25, 60*13, 45*60) -- 13s delay
end
if global.forces then
for force_name, forcedata in pairs(global.forces) do
local force = game.forces[force_name]
if force and forcedata.has_players and forcedata.homeworld_index
and force_name ~= "friendly" and force_name ~= "ignore" and force_name ~= "capture" then
--Log.trace("SolarFlare.on_nth_tick_3600 "..force_name)
if not forcedata.solar_flare then
-- first one, on homeworld, has a 48 hour delay + 1 hour warning
local arrival_tick = math.max(
48*60*60*60 + math.random()*60*60*60, -- 48 hours + up to 1 hour
game.tick + settings.global["se-cmes-max-frequency"].value * (0.5 + 0.5 * math.random()) * 60*60*60 ) -- 12 to 24 hours normally
local zone = Zone.from_zone_index(forcedata.homeworld_index)
arrival_tick = SolarFlare.arrival_snap(zone, arrival_tick)
forcedata.solar_flare = {
type = "solar-flare",
zone = zone,
tick = arrival_tick
}
end
if forcedata.solar_flare then
if SolarFlare.warning_time + game.tick >= forcedata.solar_flare.tick
and forcedata.solar_flare.tick - game.tick >= 0
and (forcedata.solar_flare.last_warned == nil or game.tick - forcedata.solar_flare.last_warned >= forcedata.solar_flare.tick - game.tick) then
for force_name, force in pairs(game.forces) do
if Zone.is_visible_to_force(forcedata.solar_flare.zone, force.name) then
force.print({"space-exploration.alert-cme-eta", forcedata.solar_flare.zone.name, Util.seconds_to_clock((forcedata.solar_flare.tick - game.tick)/60, true)})
end
end
forcedata.solar_flare.last_warned = game.tick
end
if game.tick >= forcedata.solar_flare.tick then
-- do the solar flare tick task setup
SolarFlare.begin_flare(forcedata.solar_flare.zone, forcedata.solar_flare.targeting)
-- set the next one
local zone_options = {forcedata.homeworld_index}
if forcedata.zone_assets then
for zone_index, force_zone_assets in pairs(forcedata.zone_assets) do
if (force_zone_assets.rocket_launch_pad_names and table_size(force_zone_assets.rocket_launch_pad_names) > 0) or
(force_zone_assets.rocket_landing_pad_names and table_size(force_zone_assets.rocket_landing_pad_names) > 0) then
local try_zone = Zone.from_zone_index(zone_index)
if try_zone.type ~= "asteroid-field" and try_zone.type ~= "anomaly" and try_zone.type ~= "spaceship" then
table.insert(zone_options, zone_index)
end
end
end
end
if (#zone_options <= 1 and math.random() < 0.6) or math.random() < 0.3 then
for zone_index, discovered_data in pairs(forcedata.zones_discovered) do
local try_zone = Zone.from_zone_index(zone_index)
if try_zone.type ~= "asteroid-field" and try_zone.type ~= "anomaly" and try_zone.type ~= "spaceship" then
table.insert(zone_options, zone_index)
end
end
end
local zone = Zone.from_zone_index(zone_options[math.random(#zone_options)])
-- other solar flares have 12-24 hour interval
local arrival_tick = game.tick + settings.global["se-cmes-max-frequency"].value * (0.5 + 0.5 * math.random()) * 60*60*60 -- 12-24 hours
arrival_tick = SolarFlare.arrival_snap(zone, arrival_tick)
forcedata.solar_flare = {
type = "solar-flare",
zone = Zone.from_zone_index(zone_options[math.random(#zone_options)]),
tick = arrival_tick
}
if forcedata.solar_flare.zone.type == "star" then forcedata.solar_flare.zone = forcedata.solar_flare.zone.orbit end
if forcedata.solar_flare.zone.index == forcedata.homeworld_index then
forcedata.solar_flare.targeting = "basic"
end
end
end
else
forcedata.solar_flare = nil
end
end
end
end
end
Event.addListener("on_nth_tick_3600", SolarFlare.on_nth_tick_3600) -- 1 minute
return SolarFlare