2019-10-08 06:54:42 +02:00
require " modules.wave_defense.biter_rolls "
2019-10-08 17:41:15 +02:00
require " modules.wave_defense.threat_events "
local threat_values = require " modules.wave_defense.threat_values "
2019-10-07 01:46:26 +02:00
local math_random = math.random
2019-10-10 01:42:38 +02:00
local side_target_types = { " accumulator " , " assembling-machine " , " beacon " , " boiler " , " container " , " electric-pole " , " furnace " , " lamp " , " lab " ,
" logistic-container " , " mining-drill " , " container " , " pump " , " radar " , " reactor " , " roboport " , " rocket-silo " , " solar-panel " , " storage-tank " , }
2019-10-07 01:46:26 +02:00
2019-10-08 01:17:00 +02:00
local function debug_print ( msg )
2019-10-11 21:52:32 +02:00
if not global.wave_defense . debug then return end
print ( " WaveDefense: " .. msg )
2019-10-08 01:17:00 +02:00
end
2019-10-15 04:20:40 +02:00
local function is_unit_valid ( biter )
2019-10-08 17:41:15 +02:00
if not biter.entity then debug_print ( " is_unit_valid - unit destroyed - does no longer exist " ) return false end
if not biter.entity . valid then debug_print ( " is_unit_valid - unit destroyed - invalid " ) return false end
if not biter.entity . unit_group then debug_print ( " is_unit_valid - unit destroyed - no unitgroup " ) return false end
if biter.spawn_tick + global.wave_defense . max_biter_age < game.tick then debug_print ( " is_unit_valid - unit destroyed - timed out " ) return false end
2019-10-07 22:40:05 +02:00
return true
end
2019-10-14 06:52:17 +02:00
local function time_out_biters ( )
debug_print ( " time_out_biters " )
2019-10-15 04:20:40 +02:00
for k , biter in pairs ( global.wave_defense . active_biters ) do
2019-10-07 22:40:05 +02:00
if not is_unit_valid ( biter ) then
global.wave_defense . active_biter_count = global.wave_defense . active_biter_count - 1
if biter.entity then
if biter.entity . valid then
global.wave_defense . threat = global.wave_defense . threat + threat_values [ biter.entity . name ]
2019-10-14 06:52:17 +02:00
if biter.entity . force.index == 2 then
biter.entity . destroy ( )
end
2019-10-07 22:40:05 +02:00
end
end
2019-10-15 04:20:40 +02:00
global.wave_defense . active_biters [ k ] = nil
2019-10-07 04:37:23 +02:00
end
end
end
2019-10-12 04:06:48 +02:00
local function get_random_close_spawner ( surface )
2019-10-14 06:52:17 +02:00
debug_print ( " get_random_close_spawner " )
2019-10-12 04:06:48 +02:00
local spawners = surface.find_entities_filtered ( { type = " unit-spawner " } )
2019-10-07 01:46:26 +02:00
if not spawners [ 1 ] then return false end
local center = global.wave_defense . target.position
local spawner = spawners [ math_random ( 1 , # spawners ) ]
2019-10-09 03:25:00 +02:00
for i = 1 , global.wave_defense . get_random_close_spawner_attempts , 1 do
2019-10-07 01:46:26 +02:00
local spawner_2 = spawners [ math_random ( 1 , # spawners ) ]
if ( center.x - spawner_2.position . x ) ^ 2 + ( center.y - spawner_2.position . y ) ^ 2 < ( center.x - spawner.position . x ) ^ 2 + ( center.y - spawner.position . y ) ^ 2 then spawner = spawner_2 end
end
return spawner
end
2019-10-10 01:42:38 +02:00
local function set_main_target ( )
2019-10-08 01:17:00 +02:00
if global.wave_defense . target then
2019-10-15 04:20:40 +02:00
if global.wave_defense . target.valid then return end
2019-10-07 01:46:26 +02:00
end
local characters = { }
for i = 1 , # game.connected_players , 1 do
if game.connected_players [ i ] . character then
if game.connected_players [ i ] . character.valid then
2019-10-12 04:06:48 +02:00
if game.connected_players [ i ] . surface.index == global.wave_defense . surface_index then
characters [ # characters + 1 ] = game.connected_players [ i ] . character
end
2019-10-07 01:46:26 +02:00
end
end
end
2019-10-15 04:20:40 +02:00
if # characters == 0 then return end
2019-10-07 01:46:26 +02:00
global.wave_defense . target = characters [ math_random ( 1 , # characters ) ]
end
2019-10-10 05:40:39 +02:00
local function set_side_target_list ( )
2019-10-14 06:52:17 +02:00
debug_print ( " set_side_target_list " )
2019-10-10 01:42:38 +02:00
if not global.wave_defense . target then return false end
if not global.wave_defense . target.valid then return false end
2019-10-10 05:40:39 +02:00
global.wave_defense . side_targets = global.wave_defense . target.surface . find_entities_filtered ( {
2019-10-10 01:42:38 +02:00
area = {
2019-10-10 05:40:39 +02:00
{ global.wave_defense . target.position . x - global.wave_defense . side_target_search_radius , global.wave_defense . target.position . y - global.wave_defense . side_target_search_radius } ,
{ global.wave_defense . target.position . x + global.wave_defense . side_target_search_radius , global.wave_defense . target.position . y + global.wave_defense . side_target_search_radius }
2019-10-10 01:42:38 +02:00
} ,
force = global.wave_defense . target.force ,
type = side_target_types
} )
2019-10-10 05:40:39 +02:00
end
2019-10-10 01:42:38 +02:00
2019-10-10 05:40:39 +02:00
local function get_side_target ( )
2019-10-14 06:52:17 +02:00
debug_print ( " get_side_target " )
2019-10-10 05:40:39 +02:00
if not global.wave_defense . side_targets then return false end
if # global.wave_defense . side_targets < 2 then return false end
local side_target = global.wave_defense . side_targets [ math_random ( 1 , # global.wave_defense . side_targets ) ]
if not side_target then return false end
if not side_target.valid then return false end
for _ = 1 , 4 , 1 do
local new_target = global.wave_defense . side_targets [ math_random ( 1 , # global.wave_defense . side_targets ) ]
if new_target then
if new_target.valid then
local side_target_distance = ( global.wave_defense . target.position . x - side_target.position . x ) ^ 2 + ( global.wave_defense . target.position . y - side_target.position . y ) ^ 2
local new_target_distance = ( global.wave_defense . target.position . x - new_target.position . x ) ^ 2 + ( global.wave_defense . target.position . y - new_target.position . y ) ^ 2
if new_target_distance > side_target_distance then side_target = new_target end
end
end
2019-10-10 01:42:38 +02:00
end
return side_target
end
2019-10-12 04:06:48 +02:00
local function set_group_spawn_position ( surface )
2019-10-15 04:20:40 +02:00
debug_print ( " set_group_spawn_position " )
2019-10-12 04:06:48 +02:00
local spawner = get_random_close_spawner ( surface )
2019-10-07 01:46:26 +02:00
if not spawner then return end
2019-10-12 04:06:48 +02:00
local position = surface.find_non_colliding_position ( " rocket-silo " , spawner.position , 48 , 1 )
2019-10-07 01:46:26 +02:00
if not position then return end
2019-10-11 21:52:32 +02:00
global.wave_defense . spawn_position = { x = math.floor ( position.x ) , y = math.floor ( position.y ) }
2019-10-07 01:46:26 +02:00
end
local function set_enemy_evolution ( )
2019-10-13 05:24:29 +02:00
local evolution_factor = global.wave_defense . wave_number * 0.001
local evasion_factor = 1
local damage_increase = 0
if evolution_factor > 1 then
2019-10-21 07:16:03 +02:00
evasion_factor = evasion_factor + ( evolution_factor - 1 ) * 2
2019-10-13 05:24:29 +02:00
evolution_factor = 1
end
if global.wave_defense . threat > 0 then
local m = math.round ( global.wave_defense . threat / 50000 , 3 )
evasion_factor = evasion_factor + m
2019-10-21 07:16:03 +02:00
damage_increase = damage_increase + m * 0.5
2019-10-07 09:48:17 +02:00
end
2019-10-13 05:24:29 +02:00
global.biter_evasion_health_increase_factor = evasion_factor
game.forces . enemy.set_ammo_damage_modifier ( " melee " , damage_increase )
game.forces . enemy.set_ammo_damage_modifier ( " biological " , damage_increase )
game.forces . enemy.evolution_factor = evolution_factor
debug_print ( " evolution_factor: " .. evolution_factor )
debug_print ( " evasion_factor: " .. global.biter_evasion_health_increase_factor )
debug_print ( " damage_increase: " .. game.forces . enemy.get_ammo_damage_modifier ( " melee " ) )
2019-10-07 01:46:26 +02:00
end
2019-10-12 04:06:48 +02:00
local function spawn_biter ( surface )
2019-10-07 01:46:26 +02:00
if global.wave_defense . threat <= 0 then return false end
if global.wave_defense . active_biter_count >= global.wave_defense . max_active_biters then return false end
2019-10-12 04:06:48 +02:00
local name
if math.random ( 1 , 100 ) > 73 then
name = wave_defense_roll_spitter_name ( )
else
name = wave_defense_roll_biter_name ( )
end
local position = surface.find_non_colliding_position ( name , global.wave_defense . spawn_position , 48 , 2 )
2019-10-07 01:46:26 +02:00
if not position then return false end
2019-10-12 04:06:48 +02:00
local biter = surface.create_entity ( { name = name , position = position , force = " enemy " } )
2019-10-07 01:46:26 +02:00
biter.ai_settings . allow_destroy_when_commands_fail = false
biter.ai_settings . allow_try_return_to_spawner = false
2019-10-15 04:20:40 +02:00
global.wave_defense . active_biters [ biter.unit_number ] = { entity = biter , spawn_tick = game.tick }
2019-10-07 01:46:26 +02:00
global.wave_defense . active_biter_count = global.wave_defense . active_biter_count + 1
global.wave_defense . threat = global.wave_defense . threat - threat_values [ name ]
2019-10-14 06:52:17 +02:00
return biter
2019-10-07 01:46:26 +02:00
end
2019-10-15 04:20:40 +02:00
local function spawn_unit_group ( )
if global.wave_defense . threat <= 0 then return false end
if global.wave_defense . active_biter_count >= global.wave_defense . max_active_biters then return false end
local surface = game.surfaces [ global.wave_defense . surface_index ]
set_group_spawn_position ( surface )
debug_print ( " Spawning unit group at position: " .. global.wave_defense . spawn_position.x .. " " .. global.wave_defense . spawn_position.y )
local unit_group = surface.create_unit_group ( { position = global.wave_defense . spawn_position , force = " enemy " } )
for a = 1 , global.wave_defense . group_size , 1 do
local biter = spawn_biter ( surface )
if not biter then break end
unit_group.add_member ( biter )
end
for i = 1 , # global.wave_defense . unit_groups , 1 do
if not global.wave_defense . unit_groups [ i ] then
global.wave_defense . unit_groups [ i ] = unit_group
return true
end
end
global.wave_defense . unit_groups [ # global.wave_defense . unit_groups + 1 ] = unit_group
return true
end
local function get_active_unit_groups_count ( )
local count = 0
for _ , g in pairs ( global.wave_defense . unit_groups ) do
if g.valid then
count = count + 1
end
end
debug_print ( " Active unit groups count: " .. count )
return count
end
local function spawn_attack_groups ( )
debug_print ( " spawn_attack_groups " )
if global.wave_defense . active_biter_count >= global.wave_defense . max_active_biters then return false end
if global.wave_defense . threat <= 0 then return false end
wave_defense_set_unit_raffle ( global.wave_defense . wave_number )
local count = get_active_unit_groups_count ( )
if count >= global.wave_defense . max_active_unit_groups then return end
spawn_unit_group ( )
end
2019-10-07 22:40:05 +02:00
local function set_next_wave ( )
2019-10-15 04:20:40 +02:00
debug_print ( " set_next_wave " )
2019-10-07 22:40:05 +02:00
global.wave_defense . wave_number = global.wave_defense . wave_number + 1
2019-10-08 05:50:32 +02:00
global.wave_defense . group_size = global.wave_defense . wave_number * 2
2019-10-07 22:40:05 +02:00
if global.wave_defense . group_size > global.wave_defense . max_group_size then global.wave_defense . group_size = global.wave_defense . max_group_size end
2019-10-11 21:52:32 +02:00
global.wave_defense . threat = global.wave_defense . threat + global.wave_defense . wave_number * 2
2019-10-07 22:40:05 +02:00
global.wave_defense . last_wave = global.wave_defense . next_wave
global.wave_defense . next_wave = game.tick + global.wave_defense . wave_interval
end
2019-10-08 01:17:00 +02:00
local function get_commmands ( group )
2019-10-14 06:52:17 +02:00
debug_print ( " get_commmands " )
2019-10-08 01:17:00 +02:00
local commands = { }
local group_position = { x = group.position . x , y = group.position . y }
local step_length = global.wave_defense . unit_group_command_step_length
2019-10-10 01:42:38 +02:00
2019-10-11 21:52:32 +02:00
if math_random ( 1 , 3 ) ~= 1 then
local side_target = false
for _ = 1 , 3 , 1 do
side_target = get_side_target ( )
if side_target then break end
2019-10-10 01:42:38 +02:00
end
2019-10-11 21:52:32 +02:00
if side_target then
local target_position = side_target.position
local distance_to_target = math.floor ( math.sqrt ( ( target_position.x - group_position.x ) ^ 2 + ( target_position.y - group_position.y ) ^ 2 ) )
local steps = math.floor ( distance_to_target / step_length ) + 1
local vector = { math.round ( ( target_position.x - group_position.x ) / steps , 3 ) , math.round ( ( target_position.y - group_position.y ) / steps , 3 ) }
2019-10-15 04:20:40 +02:00
if global.wave_defense . debug then
print ( " side_target x " .. side_target.position . x .. " y " .. side_target.position . y )
print ( " distance_to_target " .. distance_to_target )
print ( " steps " .. steps )
print ( " vector " .. vector [ 1 ] .. " _ " .. vector [ 2 ] )
end
2019-10-11 21:52:32 +02:00
for i = 1 , steps , 1 do
group_position.x = group_position.x + vector [ 1 ]
group_position.y = group_position.y + vector [ 2 ]
local position = group.surface . find_non_colliding_position ( " small-biter " , group_position , 64 , 2 )
if position then
commands [ # commands + 1 ] = {
type = defines.command . attack_area ,
destination = { x = position.x , y = position.y } ,
radius = 16 ,
distraction = defines.distraction . by_anything
}
2019-10-14 06:52:17 +02:00
--if global.wave_defense.debug then print(position) end
2019-10-11 21:52:32 +02:00
end
end
commands [ # commands + 1 ] = {
type = defines.command . attack ,
target = side_target ,
distraction = defines.distraction . by_enemy ,
}
2019-10-10 01:42:38 +02:00
end
end
2019-10-11 21:52:32 +02:00
2019-10-10 01:42:38 +02:00
local target_position = global.wave_defense . target.position
2019-10-08 01:17:00 +02:00
local distance_to_target = math.floor ( math.sqrt ( ( target_position.x - group_position.x ) ^ 2 + ( target_position.y - group_position.y ) ^ 2 ) )
local steps = math.floor ( distance_to_target / step_length ) + 1
local vector = { math.round ( ( target_position.x - group_position.x ) / steps , 3 ) , math.round ( ( target_position.y - group_position.y ) / steps , 3 ) }
2019-10-15 04:20:40 +02:00
if global.wave_defense . debug then
print ( " main_target " )
print ( " distance_to_target " .. distance_to_target )
print ( " steps " .. steps )
print ( " vector " .. vector [ 1 ] .. " _ " .. vector [ 2 ] )
end
2019-10-08 01:17:00 +02:00
for i = 1 , steps , 1 do
group_position.x = group_position.x + vector [ 1 ]
2019-10-10 01:42:38 +02:00
group_position.y = group_position.y + vector [ 2 ]
2019-10-11 07:13:08 +02:00
local position = group.surface . find_non_colliding_position ( " small-biter " , group_position , 64 , 1 )
2019-10-08 01:17:00 +02:00
if position then
commands [ # commands + 1 ] = {
type = defines.command . attack_area ,
destination = { x = position.x , y = position.y } ,
radius = 16 ,
2019-10-08 05:50:32 +02:00
distraction = defines.distraction . by_anything
2019-10-08 01:17:00 +02:00
}
2019-10-14 06:52:17 +02:00
--if global.wave_defense.debug then print(position) end
2019-10-10 01:42:38 +02:00
end
2019-10-08 01:17:00 +02:00
end
commands [ # commands + 1 ] = {
type = defines.command . attack_area ,
2019-10-18 07:01:14 +02:00
destination = { x = target_position.x , y = target_position.y } ,
2019-10-08 01:17:00 +02:00
radius = 8 ,
distraction = defines.distraction . by_enemy
}
commands [ # commands + 1 ] = {
type = defines.command . attack ,
target = global.wave_defense . target ,
distraction = defines.distraction . by_enemy ,
}
return commands
end
2019-10-07 22:40:05 +02:00
local function command_unit_group ( group )
2019-10-16 00:23:54 +02:00
if not global.wave_defense . unit_group_last_command [ group.group_number ] then
global.wave_defense . unit_group_last_command [ group.group_number ] = game.tick - ( global.wave_defense . unit_group_command_delay + 1 )
end
2019-10-15 04:20:40 +02:00
if global.wave_defense . unit_group_last_command [ group.group_number ] + global.wave_defense . unit_group_command_delay > game.tick then return end
global.wave_defense . unit_group_last_command [ group.group_number ] = game.tick
2019-10-07 22:40:05 +02:00
group.set_command ( {
type = defines.command . compound ,
structure_type = defines.compound_command . return_last ,
2019-10-08 01:17:00 +02:00
commands = get_commmands ( group )
2019-10-07 22:40:05 +02:00
} )
end
2019-10-15 04:20:40 +02:00
local function give_commands_to_unit_groups ( )
if # global.wave_defense . unit_groups == 0 then return end
if not global.wave_defense . target then return end
if not global.wave_defense . target.valid then return end
debug_print ( " give_commands_to_unit_groups " )
for k , group in pairs ( global.wave_defense . unit_groups ) do
if group.valid then
command_unit_group ( group )
else
global.wave_defense . unit_groups [ k ] = nil
end
end
2019-10-07 04:37:23 +02:00
end
2019-10-15 04:20:40 +02:00
local function create_gui ( player )
local frame = player.gui . top.add ( { type = " frame " , name = " wave_defense " } )
frame.style . maximal_height = 38
local label = frame.add ( { type = " label " , caption = " " , name = " label " } )
label.style . font_color = { r = 0.88 , g = 0.88 , b = 0.88 }
label.style . font = " default-bold "
label.style . left_padding = 4
label.style . right_padding = 4
label.style . minimal_width = 68
label.style . font_color = { r = 0.33 , g = 0.66 , b = 0.9 }
local progressbar = frame.add ( { type = " progressbar " , name = " progressbar " , value = 0 } )
progressbar.style . minimal_width = 128
progressbar.style . maximal_width = 128
progressbar.style . top_padding = 10
local line = frame.add ( { type = " line " , direction = " vertical " } )
line.style . left_padding = 4
line.style . right_padding = 4
local label = frame.add ( { type = " label " , caption = " " , name = " threat " , tooltip = " high threat may empower biters " } )
label.style . font_color = { r = 0.88 , g = 0.88 , b = 0.88 }
label.style . font = " default-bold "
label.style . left_padding = 4
label.style . right_padding = 4
label.style . minimal_width = 10
label.style . font_color = { r = 0.99 , g = 0.0 , b = 0.5 }
end
local function update_gui ( player )
if not player.gui . top.wave_defense then create_gui ( player ) end
player.gui . top.wave_defense . label.caption = " Wave: " .. global.wave_defense . wave_number
if global.wave_defense . wave_number == 0 then player.gui . top.wave_defense . label.caption = " First wave in " .. math.floor ( ( global.wave_defense . next_wave - game.tick ) / 60 ) + 1 end
local interval = global.wave_defense . next_wave - global.wave_defense . last_wave
player.gui . top.wave_defense . progressbar.value = 1 - ( global.wave_defense . next_wave - game.tick ) / interval
local value = global.wave_defense . threat
if value < 0 then value = 0 end
player.gui . top.wave_defense . threat.caption = " Threat: " .. value
end
local tick_tasks = {
[ 30 ] = set_main_target ,
[ 60 ] = set_enemy_evolution ,
[ 90 ] = spawn_attack_groups ,
[ 120 ] = give_commands_to_unit_groups ,
[ 150 ] = build_nest ,
[ 180 ] = build_worm ,
}
2019-10-07 01:46:26 +02:00
local function on_tick ( )
2019-10-15 04:20:40 +02:00
if global.wave_defense . game_lost then return end
for _ , player in pairs ( game.connected_players ) do update_gui ( player ) end
2019-10-07 04:37:23 +02:00
2019-10-07 22:40:05 +02:00
if game.tick > global.wave_defense . next_wave then set_next_wave ( ) end
2019-10-15 04:20:40 +02:00
local t = game.tick % 300
2019-10-07 04:37:23 +02:00
2019-10-15 04:20:40 +02:00
if tick_tasks [ t ] then tick_tasks [ t ] ( ) end
if game.tick % 1800 == 0 then time_out_biters ( ) end
if game.tick % 7200 == 0 then set_side_target_list ( ) end
2019-10-07 01:46:26 +02:00
end
2019-10-07 16:40:52 +02:00
function reset_wave_defense ( )
2019-10-07 01:46:26 +02:00
global.wave_defense = {
2019-10-07 22:40:05 +02:00
debug = false ,
2019-10-12 04:06:48 +02:00
surface_index = 1 ,
2019-10-15 04:20:40 +02:00
active_biters = { } ,
unit_groups = { } ,
unit_group_last_command = { } ,
unit_group_command_delay = 3600 * 8 ,
2019-10-11 07:13:08 +02:00
unit_group_command_step_length = 64 ,
2019-10-13 05:24:29 +02:00
group_size = 2 ,
2019-10-15 04:20:40 +02:00
max_group_size = 192 ,
max_active_unit_groups = 8 ,
2019-10-10 19:33:32 +02:00
max_active_biters = 1024 ,
2019-10-08 05:50:32 +02:00
max_biter_age = 3600 * 60 ,
2019-10-07 01:46:26 +02:00
active_biter_count = 0 ,
2019-10-11 07:13:08 +02:00
get_random_close_spawner_attempts = 5 ,
2019-10-16 03:57:53 +02:00
side_target_search_radius = 768 ,
2019-10-08 17:41:15 +02:00
spawn_position = { x = 0 , y = 64 } ,
2019-10-07 16:40:52 +02:00
last_wave = game.tick ,
2019-10-22 08:29:30 +02:00
next_wave = game.tick + 3600 * 15 ,
wave_interval = 3600 ,
2019-10-07 01:46:26 +02:00
wave_number = 0 ,
2019-10-07 04:37:23 +02:00
game_lost = false ,
2019-10-07 01:46:26 +02:00
threat = 0 ,
2019-10-09 03:25:00 +02:00
simple_entity_shredding_count_modifier = 0.0003 ,
2019-10-12 02:19:37 +02:00
simple_entity_shredding_cost_modifier = 0.007 , --threat cost for one health
2019-10-10 21:33:54 +02:00
nest_building_density = 64 , --lower values = more dense building
2019-10-11 07:13:08 +02:00
nest_building_chance = 4 , --high value = less chance
2019-10-10 07:24:49 +02:00
worm_building_density = 8 , --lower values = more dense building
2019-10-10 21:33:54 +02:00
worm_building_chance = 2 , --high value = less chance
2019-10-07 01:46:26 +02:00
}
end
2019-10-07 16:40:52 +02:00
local function on_init ( )
reset_wave_defense ( )
end
2019-10-07 01:46:26 +02:00
local event = require ' utils.event '
2019-10-07 04:37:23 +02:00
event.on_nth_tick ( 30 , on_tick )
2019-10-08 17:41:15 +02:00
event.on_init ( on_init )