2019-03-14 05:25:54 +02:00
local event = require ' utils.event '
2019-03-16 08:31:34 +02:00
local math_random = math.random
2019-03-14 19:06:39 +02:00
local ai = { }
2019-03-14 05:25:54 +02:00
2019-03-16 08:31:34 +02:00
local threat_values = {
2019-03-21 11:06:12 +02:00
[ " small-spitter " ] = 2 ,
[ " small-biter " ] = 2 ,
2019-03-16 08:31:34 +02:00
[ " medium-spitter " ] = 4 ,
[ " medium-biter " ] = 4 ,
[ " big-spitter " ] = 8 ,
[ " big-biter " ] = 8 ,
2019-03-21 11:06:12 +02:00
[ " behemoth-spitter " ] = 24 ,
2019-07-13 19:12:18 +02:00
[ " behemoth-biter " ] = 24 ,
[ " small-worm-turret " ] = 8 ,
[ " medium-worm-turret " ] = 12 ,
[ " big-worm-turret " ] = 16 ,
[ " behemoth-worm-turret " ] = 16 ,
[ " biter-spawner " ] = 16 ,
[ " spitter-spawner " ] = 16
2019-03-16 08:31:34 +02:00
}
2019-07-13 11:20:31 +02:00
local function get_active_biter_count ( biter_force_name )
local count = 0
for _ , biter in pairs ( global.active_biters [ biter_force_name ] ) do
count = count + 1
end
return count
end
2019-07-25 02:43:59 +02:00
local function get_threat_ratio ( biter_force_name )
if global.bb_threat [ biter_force_name ] <= 0 then return 0 end
local t1 = global.bb_threat [ " north_biters " ]
local t2 = global.bb_threat [ " south_biters " ]
if t1 == 0 and t2 == 0 then return 0.5 end
if t1 < 0 then t1 = 0 end
if t2 < 0 then t2 = 0 end
local total_threat = t1 + t2
local ratio = global.bb_threat [ biter_force_name ] / total_threat
return ratio
end
2019-07-23 17:15:32 +02:00
local function is_biter_inactive ( biter , unit_number , biter_force_name )
if not biter.entity . valid then return true end
if game.tick - biter.active_since < bb_config.biter_timeout then return false end
if biter.entity . surface.count_entities_filtered ( { area = { { biter.entity . position.x - 16 , biter.entity . position.y - 16 } , { biter.entity . position.x + 16 , biter.entity . position.y + 16 } } , force = { " north " , " south " } } ) ~= 0 then
global.active_biters [ biter_force_name ] [ unit_number ] . active_since = game.tick
return false
end
if global.bb_debug then game.print ( biter_force_name .. " unit " .. unit_number .. " timed out at tick age " .. game.tick - biter.active_since ) end
biter.entity . destroy ( )
return true
end
2019-07-13 11:20:31 +02:00
ai.destroy_inactive_biters = function ( )
for _ , biter_force_name in pairs ( { " north_biters " , " south_biters " } ) do
for unit_number , biter in pairs ( global.active_biters [ biter_force_name ] ) do
2019-07-23 17:15:32 +02:00
if is_biter_inactive ( biter , unit_number , biter_force_name ) then
global.active_biters [ biter_force_name ] [ unit_number ] = nil
2019-07-13 11:20:31 +02:00
end
end
end
end
2019-03-16 09:40:36 +02:00
ai.send_near_biters_to_silo = function ( )
if game.tick < 108000 then return end
if not global.rocket_silo [ " north " ] then return end
if not global.rocket_silo [ " south " ] then return end
game.surfaces [ " biter_battles " ] . set_multi_command ( {
2019-03-14 05:25:54 +02:00
command = {
type = defines.command . attack ,
target = global.rocket_silo [ " north " ] ,
distraction = defines.distraction . none
} ,
2019-07-18 13:54:22 +02:00
unit_count = 16 ,
2019-03-16 09:40:36 +02:00
force = " north_biters " ,
2019-03-17 03:35:58 +02:00
unit_search_distance = 128
2019-03-14 05:25:54 +02:00
} )
2019-03-16 09:40:36 +02:00
game.surfaces [ " biter_battles " ] . set_multi_command ( {
2019-03-14 05:25:54 +02:00
command = {
type = defines.command . attack ,
target = global.rocket_silo [ " south " ] ,
distraction = defines.distraction . none
} ,
2019-07-18 13:54:22 +02:00
unit_count = 16 ,
2019-03-16 09:40:36 +02:00
force = " south_biters " ,
2019-03-17 03:35:58 +02:00
unit_search_distance = 128
2019-03-14 05:25:54 +02:00
} )
end
2019-03-16 08:31:34 +02:00
local function get_random_close_spawner ( surface , biter_force_name )
local spawners = surface.find_entities_filtered ( { type = " unit-spawner " , force = biter_force_name } )
2019-03-19 23:48:29 +02:00
if not spawners [ 1 ] then return false end
local spawner = spawners [ math_random ( 1 , # spawners ) ]
2019-07-18 13:54:22 +02:00
for i = 1 , 7 , 1 do
2019-03-19 23:48:29 +02:00
local spawner_2 = spawners [ math_random ( 1 , # spawners ) ]
if spawner_2.position . x ^ 2 + spawner_2.position . y ^ 2 < spawner.position . x ^ 2 + spawner.position . y ^ 2 then spawner = spawner_2 end
2019-03-16 08:31:34 +02:00
end
2019-03-19 23:48:29 +02:00
2019-03-16 08:31:34 +02:00
return spawner
end
local function select_units_around_spawner ( spawner , force_name , biter_force_name )
2019-07-18 13:54:22 +02:00
local biters = spawner.surface . find_enemy_units ( spawner.position , 256 , force_name )
2019-03-16 08:31:34 +02:00
if not biters [ 1 ] then return false end
local valid_biters = { }
2019-07-18 13:54:22 +02:00
2019-07-25 05:49:27 +02:00
local threat = global.bb_threat [ biter_force_name ] * math_random ( 11 , 22 ) * 0.01
2019-07-18 13:54:22 +02:00
local unit_count = 0
local max_unit_count = math.ceil ( global.bb_threat [ biter_force_name ] * 0.25 ) + math_random ( 6 , 12 )
if max_unit_count > bb_config.max_group_size then max_unit_count = bb_config.max_group_size end
2019-03-16 08:31:34 +02:00
for _ , biter in pairs ( biters ) do
2019-07-18 13:54:22 +02:00
if unit_count >= max_unit_count then break end
2019-07-14 21:37:12 +02:00
if biter.force . name == biter_force_name and global.active_biters [ biter.force . name ] [ biter.unit_number ] == nil then
2019-07-13 11:20:31 +02:00
valid_biters [ # valid_biters + 1 ] = biter
global.active_biters [ biter.force . name ] [ biter.unit_number ] = { entity = biter , active_since = game.tick }
2019-07-18 13:54:22 +02:00
unit_count = unit_count + 1
2019-03-17 03:35:58 +02:00
threat = threat - threat_values [ biter.name ]
2019-07-13 11:20:31 +02:00
end
2019-03-16 09:40:36 +02:00
if threat < 0 then break end
2019-03-16 08:31:34 +02:00
end
2019-07-13 11:20:31 +02:00
2019-07-18 13:54:22 +02:00
if global.bb_debug then game.print ( get_active_biter_count ( biter_force_name ) .. " active units for " .. biter_force_name ) end
2019-07-13 11:20:31 +02:00
2019-03-16 09:40:36 +02:00
return valid_biters
2019-03-16 08:31:34 +02:00
end
local function send_group ( unit_group , force_name , nearest_player_unit )
2019-03-20 01:05:19 +02:00
local target = nearest_player_unit.position
if math_random ( 1 , 2 ) == 1 then target = global.rocket_silo [ force_name ] . position end
2019-03-16 08:31:34 +02:00
unit_group.set_command ( {
type = defines.command . compound ,
structure_type = defines.compound_command . return_last ,
commands = {
{
type = defines.command . attack_area ,
2019-03-20 01:05:19 +02:00
destination = target ,
2019-03-16 08:31:34 +02:00
radius = 32 ,
distraction = defines.distraction . by_enemy
} ,
{
type = defines.command . attack ,
target = global.rocket_silo [ force_name ] ,
distraction = defines.distraction . by_enemy
}
}
} )
return true
end
2019-07-25 16:50:34 +02:00
local function is_chunk_empty ( surface , area )
if surface.count_entities_filtered ( { type = { " unit-spawner " , " unit " } , area = area } ) ~= 0 then return false end
if surface.count_entities_filtered ( { force = { " north " , " south " } , area = area } ) ~= 0 then return false end
if surface.count_tiles_filtered ( { name = { " water " , " deepwater " } , area = area } ) ~= 0 then return false end
return true
end
local function get_unit_group_position ( surface , nearest_player_unit , spawner )
local spawner_chunk_position = { x = math.floor ( spawner.position . x / 32 ) , y = math.floor ( spawner.position . y / 32 ) }
local valid_chunks = { }
for x = - 2 , 2 , 1 do
for y = - 2 , 2 , 1 do
local chunk = { x = spawner_chunk_position.x + x , y = spawner_chunk_position.y + y }
local area = { { chunk.x * 32 , chunk.y * 32 } , { chunk.x * 32 + 32 , chunk.y * 32 + 32 } }
if is_chunk_empty ( surface , area ) then
valid_chunks [ # valid_chunks + 1 ] = chunk
end
end
end
if # valid_chunks > 0 then
local chunk = valid_chunks [ math_random ( 1 , # valid_chunks ) ]
return { x = chunk.x * 32 + 16 , y = chunk.y * 32 + 16 }
end
local unit_group_position = { x = ( spawner.position . x + nearest_player_unit.position . x ) * 0.5 , y = ( spawner.position . y + nearest_player_unit.position . y ) * 0.5 }
local pos = surface.find_non_colliding_position ( " rocket-silo " , unit_group_position , 256 , 1 )
if pos then unit_group_position = pos end
if not unit_group_position then
if global.bb_debug then game.print ( " No unit_group_position found for team " .. force_name ) end
return false
end
return unit_group_position
end
2019-03-16 08:31:34 +02:00
local function create_attack_group ( surface , force_name , biter_force_name )
if global.bb_threat [ biter_force_name ] <= 0 then return false end
2019-07-18 13:54:22 +02:00
if bb_config.max_active_biters - get_active_biter_count ( biter_force_name ) < bb_config.max_group_size then
if global.bb_debug then game.print ( " Not enough slots for biters for team " .. force_name .. " . Available slots: " .. bb_config.max_active_biters - get_active_biter_count ( biter_force_name ) ) end
return false
end
2019-03-16 08:31:34 +02:00
local spawner = get_random_close_spawner ( surface , biter_force_name )
2019-07-18 13:54:22 +02:00
if not spawner then
if global.bb_debug then game.print ( " No spawner found for team " .. force_name ) end
return false
end
2019-03-16 08:31:34 +02:00
local nearest_player_unit = surface.find_nearest_enemy ( { position = spawner.position , max_distance = 2048 , force = biter_force_name } )
if not nearest_player_unit then nearest_player_unit = global.rocket_silo [ force_name ] end
2019-07-18 13:54:22 +02:00
2019-07-25 16:50:34 +02:00
local unit_group_position = get_unit_group_position ( surface , nearest_player_unit , spawner )
2019-07-18 13:54:22 +02:00
2019-03-16 08:31:34 +02:00
local units = select_units_around_spawner ( spawner , force_name , biter_force_name )
if not units then return false end
local unit_group = surface.create_unit_group ( { position = unit_group_position , force = biter_force_name } )
for _ , unit in pairs ( units ) do unit_group.add_member ( unit ) end
send_group ( unit_group , force_name , nearest_player_unit )
end
2019-07-13 11:20:31 +02:00
ai.main_attack = function ( )
2019-03-16 08:31:34 +02:00
local surface = game.surfaces [ " biter_battles " ]
2019-07-25 02:43:59 +02:00
2019-07-25 05:49:27 +02:00
for c = 1 , math.ceil ( get_threat_ratio ( global.next_attack .. " _biters " ) * 7 ) , 1 do
create_attack_group ( surface , global.next_attack , global.next_attack .. " _biters " )
2019-07-25 02:43:59 +02:00
end
2019-07-25 05:49:27 +02:00
if global.bb_debug then game.print ( math.ceil ( get_threat_ratio ( global.next_attack .. " _biters " ) * 7 ) .. " unit groups designated for " .. global.next_attack .. " biters. " ) end
2019-07-25 02:43:59 +02:00
2019-07-25 05:49:27 +02:00
if global.next_attack == " north " then
global.next_attack = " south "
else
global.next_attack = " north "
end
2019-03-16 08:31:34 +02:00
end
2019-03-20 07:40:14 +02:00
--Prevent Players from damaging Rocket Silos
local function protect_silo ( event )
if event.cause then
if event.cause . type == " unit " then return end
end
if event.entity . name ~= " rocket-silo " then return end
event.entity . health = event.entity . health + event.final_damage_amount
end
2019-04-12 02:57:25 +02:00
--Prevent Players from doing direct pvp combat
local function ignore_pvp ( event )
if not event.cause then return end
if event.cause . force.name == " north " then
if event.entity . force.name == " south " then
if not event.entity . valid then return end
event.entity . health = event.entity . health + event.final_damage_amount
return
end
end
if event.cause . force.name == " south " then
if event.entity . force.name == " north " then
if not event.entity . valid then return end
event.entity . health = event.entity . health + event.final_damage_amount
return
end
end
end
2019-03-16 08:31:34 +02:00
--Biter Evasion
2019-04-21 02:45:24 +02:00
local random_max = 10000
local function get_evade_chance ( force_name )
return random_max - ( random_max / global.bb_evasion [ force_name ] )
end
2019-03-20 07:40:14 +02:00
local function evade ( event )
2019-03-16 08:31:34 +02:00
if not event.entity . valid then return end
2019-04-21 02:45:24 +02:00
if not global.bb_evasion [ event.entity . force.name ] then return end
if event.final_damage_amount > event.entity . prototype.max_health * global.bb_evasion [ event.entity . force.name ] then return end
if math_random ( 1 , random_max ) > get_evade_chance ( event.entity . force.name ) then return end
event.entity . health = event.entity . health + event.final_damage_amount
2019-03-16 08:31:34 +02:00
end
2019-03-20 07:40:14 +02:00
local function on_entity_damaged ( event )
evade ( event )
protect_silo ( event )
2019-04-12 02:57:25 +02:00
--ignore_pvp(event)
2019-03-20 07:40:14 +02:00
end
2019-03-16 08:31:34 +02:00
--Biter Threat Value Substraction
local function on_entity_died ( event )
if not event.entity . valid then return end
2019-07-13 19:12:18 +02:00
if not threat_values [ event.entity . name ] then return end
if event.entity . type == " unit " then
global.active_biters [ event.entity . force.name ] [ event.entity . unit_number ] = nil
end
2019-07-13 11:20:31 +02:00
global.bb_threat [ event.entity . force.name ] = global.bb_threat [ event.entity . force.name ] - threat_values [ event.entity . name ]
2019-03-16 08:31:34 +02:00
end
2019-03-21 00:46:28 +02:00
--Flamethrower Turret Nerf
local function on_research_finished ( event )
local research = event.research
local force_name = research.force . name
if research.name == " flamethrower " then
if not global.flamethrower_damage then global.flamethrower_damage = { } end
2019-03-27 04:58:22 +02:00
global.flamethrower_damage [ force_name ] = - 0.6
2019-03-21 00:46:28 +02:00
game.forces [ force_name ] . set_turret_attack_modifier ( " flamethrower-turret " , global.flamethrower_damage [ force_name ] )
game.forces [ force_name ] . set_ammo_damage_modifier ( " flamethrower " , global.flamethrower_damage [ force_name ] )
end
if string.sub ( research.name , 0 , 18 ) == " refined-flammables " then
2019-03-22 20:42:59 +02:00
global.flamethrower_damage [ force_name ] = global.flamethrower_damage [ force_name ] + 0.05
2019-03-21 00:46:28 +02:00
game.forces [ force_name ] . set_turret_attack_modifier ( " flamethrower-turret " , global.flamethrower_damage [ force_name ] )
game.forces [ force_name ] . set_ammo_damage_modifier ( " flamethrower " , global.flamethrower_damage [ force_name ] )
end
end
2019-03-16 08:31:34 +02:00
event.add ( defines.events . on_entity_damaged , on_entity_damaged )
event.add ( defines.events . on_entity_died , on_entity_died )
2019-03-21 00:46:28 +02:00
event.add ( defines.events . on_research_finished , on_research_finished )
2019-03-16 08:31:34 +02:00
2019-03-14 19:06:39 +02:00
return ai