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
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,
|
|
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
|
|
|