2021-03-24 18:22:45 +01:00
--luacheck:ignore
2020-01-02 16:28:12 +01:00
local Public = { }
2021-03-24 16:46:00 +01:00
local BiterRaffle = require ' maps.biter_battles_v2.biter_raffle '
local Functions = require ' maps.biter_battles_v2.functions '
local bb_config = require ' maps.biter_battles_v2.config '
2019-03-16 07:31:34 +01:00
local math_random = math.random
2020-04-18 12:10:54 +02:00
local math_abs = math.abs
2019-03-14 04:25:54 +01:00
2020-04-18 08:14:45 +02:00
local vector_radius = 512
2019-12-21 00:22:01 +01:00
local attack_vectors = { }
attack_vectors.north = { }
attack_vectors.south = { }
for x = vector_radius * - 1 , vector_radius , 1 do
2021-03-24 16:46:00 +01:00
for y = 0 , vector_radius , 1 do
local r = math.sqrt ( x ^ 2 + y ^ 2 )
if r < vector_radius and r > vector_radius - 1 then
attack_vectors.north [ # attack_vectors.north + 1 ] = { x , y * - 1 }
attack_vectors.south [ # attack_vectors.south + 1 ] = { x , y }
end
end
2019-12-21 00:22:01 +01:00
end
local size_of_vectors = # attack_vectors.north
2020-04-13 06:31:39 +02:00
2021-03-24 16:46:00 +01:00
local unit_type_raffle = { ' biter ' , ' biter ' , ' biter ' , ' mixed ' , ' mixed ' , ' spitter ' }
2020-04-18 12:10:54 +02:00
local size_of_unit_type_raffle = # unit_type_raffle
2019-12-21 00:22:01 +01:00
2019-03-16 07:31:34 +01:00
local threat_values = {
2021-03-24 16:46:00 +01:00
[ ' small-spitter ' ] = 1.5 ,
[ ' small-biter ' ] = 1.5 ,
[ ' medium-spitter ' ] = 4.5 ,
[ ' medium-biter ' ] = 4.5 ,
[ ' big-spitter ' ] = 13 ,
[ ' big-biter ' ] = 13 ,
[ ' behemoth-spitter ' ] = 38.5 ,
[ ' behemoth-biter ' ] = 38.5 ,
[ ' small-worm-turret ' ] = 8 ,
[ ' medium-worm-turret ' ] = 16 ,
[ ' big-worm-turret ' ] = 24 ,
[ ' behemoth-worm-turret ' ] = 32 ,
[ ' biter-spawner ' ] = 32 ,
[ ' spitter-spawner ' ] = 32
2019-03-16 07:31:34 +01:00
}
2019-07-13 11:20:31 +02:00
local function get_active_biter_count ( biter_force_name )
2021-03-24 16:46:00 +01:00
local count = 0
2021-03-24 18:22:45 +01:00
for _ , _ in pairs ( global.active_biters [ biter_force_name ] ) do
2021-03-24 16:46:00 +01:00
count = count + 1
end
return count
2019-07-13 11:20:31 +02:00
end
2020-04-18 12:10:54 +02:00
local function get_target_entity ( force_name )
2021-03-24 16:46:00 +01:00
local force_index = game.forces [ force_name ] . index
local target_entity = Functions.get_random_target_entity ( force_index )
if not target_entity then
print ( ' Unable to get target entity for ' .. force_name .. ' . ' )
return
end
for _ = 1 , 2 , 1 do
local e = Functions.get_random_target_entity ( force_index )
if math_abs ( e.position . x ) < math_abs ( target_entity.position . x ) then
target_entity = e
end
end
if not target_entity then
print ( ' Unable to get target entity for ' .. force_name .. ' . ' )
return
end
--print("Target entity for " .. force_name .. ": " .. target_entity.name .. " at x=" .. target_entity.position.x .. " y=" .. target_entity.position.y)
return target_entity
2019-07-29 15:24:02 +02:00
end
2019-07-25 02:43:59 +02:00
local function get_threat_ratio ( biter_force_name )
2021-03-24 16:46:00 +01:00
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
2019-07-25 02:43:59 +02:00
end
2019-07-23 17:15:32 +02:00
local function is_biter_inactive ( biter , unit_number , biter_force_name )
2021-03-24 16:46:00 +01:00
if not biter.entity then
if global.bb_debug then
print ( ' BiterBattles: active unit ' .. unit_number .. ' removed, possibly died. ' )
end
return true
end
if not biter.entity . valid then
if global.bb_debug then
print ( ' BiterBattles: active unit ' .. unit_number .. ' removed, biter invalid. ' )
end
return true
end
if not biter.entity . unit_group then
if global.bb_debug then
print ( ' BiterBattles: active unit ' .. unit_number .. ' at x ' .. biter.entity . position.x .. ' y ' .. biter.entity . position.y .. ' removed, had no unit group. ' )
end
return true
end
if not biter.entity . unit_group.valid then
if global.bb_debug then
print ( ' BiterBattles: active unit ' .. unit_number .. ' removed, unit group invalid. ' )
end
return true
end
if game.tick - biter.active_since > bb_config.biter_timeout then
if global.bb_debug then
print ( ' BiterBattles: ' .. 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-23 17:15:32 +02:00
end
2020-01-19 10:26:11 +01:00
local function set_active_biters ( group )
2021-03-24 16:46:00 +01:00
if not group.valid then
return
end
local active_biters = global.active_biters [ group.force . name ]
for _ , unit in pairs ( group.members ) do
if not active_biters [ unit.unit_number ] then
active_biters [ unit.unit_number ] = { entity = unit , active_since = game.tick }
end
end
2020-01-19 10:26:11 +01:00
end
Public.destroy_inactive_biters = function ( )
2021-03-24 16:46:00 +01:00
local biter_force_name = global.next_attack .. ' _biters '
for _ , group in pairs ( global.unit_groups ) do
set_active_biters ( group )
end
for unit_number , biter in pairs ( global.active_biters [ biter_force_name ] ) do
if is_biter_inactive ( biter , unit_number , biter_force_name ) then
global.active_biters [ biter_force_name ] [ unit_number ] = nil
end
end
2019-07-13 11:20:31 +02:00
end
2020-01-02 16:28:12 +01:00
Public.send_near_biters_to_silo = function ( )
2021-03-24 16:46:00 +01:00
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 (
{
command = {
type = defines.command . attack ,
target = global.rocket_silo [ ' north ' ] ,
distraction = defines.distraction . none
} ,
unit_count = 8 ,
force = ' north_biters ' ,
unit_search_distance = 64
}
)
game.surfaces [ ' biter_battles ' ] . set_multi_command (
{
command = {
type = defines.command . attack ,
target = global.rocket_silo [ ' south ' ] ,
distraction = defines.distraction . none
} ,
unit_count = 8 ,
force = ' south_biters ' ,
unit_search_distance = 64
}
)
2019-03-14 04:25:54 +01:00
end
2020-02-08 09:32:10 +01:00
local function get_random_spawner ( biter_force_name )
2021-03-24 16:46:00 +01:00
local spawners = global.unit_spawners [ biter_force_name ]
local size_of_spawners = # spawners
for _ = 1 , 256 , 1 do
if size_of_spawners == 0 then
return
end
local index = math_random ( 1 , size_of_spawners )
local spawner = spawners [ index ]
if spawner and spawner.valid then
return spawner
else
table.remove ( spawners , index )
size_of_spawners = size_of_spawners - 1
end
end
2020-02-08 09:32:10 +01:00
end
2020-01-07 04:35:18 +01:00
2020-04-18 12:10:54 +02:00
local function select_units_around_spawner ( spawner , force_name , side_target )
2021-03-24 16:46:00 +01:00
local biter_force_name = spawner.force . name
local valid_biters = { }
local i = 0
local threat = global.bb_threat [ biter_force_name ] * math_random ( 8 , 32 ) * 0.01
--threat modifier for outposts
local m = math_abs ( side_target.position . x ) - 512
if m < 0 then
m = 0
end
m = 1 - m * 0.001
if m < 0.5 then
m = 0.5
end
threat = threat * m
local unit_count = 0
local max_unit_count = math.floor ( 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
--Collect biters around spawners
if math_random ( 1 , 2 ) == 1 then
local biters = spawner.surface . find_enemy_units ( spawner.position , 160 , force_name )
if biters [ 1 ] then
for _ , biter in pairs ( biters ) do
if unit_count >= max_unit_count then
break
end
if biter.force . name == biter_force_name and global.active_biters [ biter.force . name ] [ biter.unit_number ] == nil then
i = i + 1
valid_biters [ i ] = biter
global.active_biters [ biter.force . name ] [ biter.unit_number ] = { entity = biter , active_since = game.tick }
unit_count = unit_count + 1
threat = threat - threat_values [ biter.name ]
end
if threat < 0 then
break
end
end
end
end
--Manual spawning of units
local roll_type = unit_type_raffle [ math_random ( 1 , size_of_unit_type_raffle ) ]
2021-03-24 18:22:45 +01:00
for _ = 1 , max_unit_count - unit_count , 1 do
2021-03-24 16:46:00 +01:00
if threat < 0 then
break
end
local unit_name = BiterRaffle.roll ( roll_type , global.bb_evolution [ biter_force_name ] )
local position = spawner.surface . find_non_colliding_position ( unit_name , spawner.position , 128 , 2 )
if not position then
break
end
local biter = spawner.surface . create_entity ( { name = unit_name , force = biter_force_name , position = position } )
threat = threat - threat_values [ biter.name ]
i = i + 1
valid_biters [ i ] = biter
global.active_biters [ biter.force . name ] [ biter.unit_number ] = { entity = biter , active_since = game.tick }
end
if global.bb_debug then
game.print ( get_active_biter_count ( biter_force_name ) .. ' active units for ' .. biter_force_name )
end
return valid_biters
2019-03-16 07:31:34 +01:00
end
2020-04-18 12:10:54 +02:00
local function send_group ( unit_group , force_name , side_target )
2021-03-24 16:46:00 +01:00
local target
if side_target then
target = side_target
else
target = get_target_entity ( force_name )
end
if not target then
print ( ' No target for ' .. force_name .. ' biters. ' )
return
end
target = target.position
local commands = { }
local vector = attack_vectors [ force_name ] [ math_random ( 1 , size_of_vectors ) ]
local distance_modifier = math_random ( 25 , 100 ) * 0.01
local position = { target.x + ( vector [ 1 ] * distance_modifier ) , target.y + ( vector [ 2 ] * distance_modifier ) }
position = unit_group.surface . find_non_colliding_position ( ' stone-furnace ' , position , 96 , 1 )
if position then
if math.abs ( position.y ) < math.abs ( unit_group.position . y ) then
commands [ # commands + 1 ] = {
type = defines.command . attack_area ,
destination = position ,
radius = 16 ,
distraction = defines.distraction . by_enemy
}
end
end
commands [ # commands + 1 ] = {
type = defines.command . attack_area ,
destination = target ,
radius = 32 ,
distraction = defines.distraction . by_enemy
}
commands [ # commands + 1 ] = {
type = defines.command . attack ,
target = global.rocket_silo [ force_name ] ,
distraction = defines.distraction . by_enemy
}
unit_group.set_command (
{
type = defines.command . compound ,
structure_type = defines.compound_command . logical_and ,
commands = commands
}
)
return true
2019-03-16 07:31:34 +01:00
end
2020-04-18 12:10:54 +02:00
local function get_unit_group_position ( spawner )
2021-03-24 16:46:00 +01:00
local p
if spawner.force . name == ' north_biters ' then
p = { x = spawner.position . x , y = spawner.position . y + 4 }
else
p = { x = spawner.position . x , y = spawner.position . y - 4 }
end
p = spawner.surface . find_non_colliding_position ( ' electric-furnace ' , p , 512 , 1 )
if not p then
if global.bb_debug then
game.print ( ' No unit_group_position found for team ' .. spawner.force . name )
end
return
end
return p
2019-07-25 16:50:34 +02:00
end
2019-12-24 12:43:47 +01:00
local function get_active_threat ( biter_force_name )
2021-03-24 16:46:00 +01:00
local active_threat = 0
2021-03-24 18:22:45 +01:00
for _ , biter in pairs ( global.active_biters [ biter_force_name ] ) do
2021-03-24 16:46:00 +01:00
if biter.entity then
if biter.entity . valid then
active_threat = active_threat + threat_values [ biter.entity . name ]
end
end
end
return active_threat
2019-12-24 12:43:47 +01:00
end
2020-04-18 12:10:54 +02:00
local function get_nearby_biter_nest ( target_entity )
2021-03-24 16:46:00 +01:00
local center = target_entity.position
local biter_force_name = target_entity.force . name .. ' _biters '
local spawner = get_random_spawner ( biter_force_name )
if not spawner then
return
end
local best_distance = ( center.x - spawner.position . x ) ^ 2 + ( center.y - spawner.position . y ) ^ 2
for i = 1 , 16 , 1 do
local new_spawner = get_random_spawner ( biter_force_name )
local new_distance = ( center.x - new_spawner.position . x ) ^ 2 + ( center.y - new_spawner.position . y ) ^ 2
if new_distance < best_distance then
spawner = new_spawner
best_distance = new_distance
end
end
if not spawner then
return
end
--print("Nearby biter nest found at x=" .. spawner.position.x .. " y=" .. spawner.position.y .. ".")
return spawner
2020-04-18 12:10:54 +02:00
end
2019-03-16 07:31:34 +01:00
local function create_attack_group ( surface , force_name , biter_force_name )
2021-03-24 16:46:00 +01:00
local threat = global.bb_threat [ biter_force_name ]
if get_active_threat ( biter_force_name ) > threat * 1.20 then
return
end
if threat <= 0 then
return false
end
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
local side_target = get_target_entity ( force_name )
if not side_target then
print ( ' No side target found for ' .. force_name .. ' . ' )
return
end
local spawner = get_nearby_biter_nest ( side_target )
if not spawner then
print ( ' No spawner found for ' .. force_name .. ' . ' )
return
end
local unit_group_position = get_unit_group_position ( spawner )
if not unit_group_position then
return
end
local units = select_units_around_spawner ( spawner , force_name , side_target )
if not units then
return
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 , side_target )
global.unit_groups [ unit_group.group_number ] = unit_group
2019-03-16 07:31:34 +01:00
end
2020-01-07 04:35:18 +01:00
Public.pre_main_attack = function ( )
2021-03-24 16:46:00 +01:00
local force_name = global.next_attack
2020-01-07 04:35:18 +01:00
2021-03-24 16:46:00 +01:00
if not global.training_mode or ( global.training_mode and # game.forces [ force_name ] . connected_players > 0 ) then
local biter_force_name = force_name .. ' _biters '
global.main_attack_wave_amount = math.ceil ( get_threat_ratio ( biter_force_name ) * 7 )
2020-01-07 04:35:18 +01:00
2021-03-24 16:46:00 +01:00
if global.bb_debug then
game.print ( global.main_attack_wave_amount .. ' unit groups designated for ' .. force_name .. ' biters. ' )
end
else
global.main_attack_wave_amount = 0
end
2020-01-07 04:35:18 +01:00
end
Public.perform_main_attack = function ( )
2021-03-24 16:46:00 +01:00
if global.main_attack_wave_amount > 0 then
local surface = game.surfaces [ ' biter_battles ' ]
local force_name = global.next_attack
local biter_force_name = force_name .. ' _biters '
create_attack_group ( surface , force_name , biter_force_name )
global.main_attack_wave_amount = global.main_attack_wave_amount - 1
end
2020-01-07 04:35:18 +01:00
end
Public.post_main_attack = function ( )
2021-03-24 16:46:00 +01:00
global.main_attack_wave_amount = 0
if global.next_attack == ' north ' then
global.next_attack = ' south '
else
global.next_attack = ' north '
end
2019-03-16 07:31:34 +01:00
end
2020-01-02 16:28:12 +01:00
Public.wake_up_sleepy_groups = function ( )
2021-03-24 16:46:00 +01:00
local force_name = global.next_attack
local biter_force_name = force_name .. ' _biters '
local entity
local unit_group
2021-03-24 18:22:45 +01:00
for _ , biter in pairs ( global.active_biters [ biter_force_name ] ) do
2021-03-24 16:46:00 +01:00
entity = biter.entity
if entity then
if entity.valid then
unit_group = entity.unit_group
if unit_group then
if unit_group.valid then
if unit_group.state == defines.group_state . finished then
send_group ( unit_group , force_name )
--print("BiterBattles: Woke up Unit Group at x" .. unit_group.position.x .. " y" .. unit_group.position.y .. ".")
return
end
end
end
end
end
end
2020-01-01 23:56:38 +01:00
end
2020-01-02 16:28:12 +01:00
Public.raise_evo = function ( )
2021-03-24 16:46:00 +01:00
if global.freeze_players then
return
end
if not global.training_mode and ( # game.forces . north.connected_players == 0 or # game.forces . south.connected_players == 0 ) then
return
end
local amount = math.ceil ( global.difficulty_vote_value * global.evo_raise_counter * 0.75 )
if not global.total_passive_feed_redpotion then
global.total_passive_feed_redpotion = 0
end
global.total_passive_feed_redpotion = global.total_passive_feed_redpotion + amount
local biter_teams = { [ ' north_biters ' ] = ' north ' , [ ' south_biters ' ] = ' south ' }
local a_team_has_players = false
for bf , pf in pairs ( biter_teams ) do
if # game.forces [ pf ] . connected_players > 0 then
set_evo_and_threat ( amount , ' automation-science-pack ' , bf )
a_team_has_players = true
end
end
if not a_team_has_players then
return
end
global.evo_raise_counter = global.evo_raise_counter + ( 1 * 0.50 )
2019-08-14 21:16:56 +02:00
end
2019-03-16 07:31:34 +01:00
--Biter Threat Value Substraction
2020-01-02 16:28:12 +01:00
function Public . subtract_threat ( entity )
2021-03-24 16:46:00 +01:00
if not threat_values [ entity.name ] then
return
end
if entity.type == ' unit ' then
global.active_biters [ entity.force . name ] [ entity.unit_number ] = nil
end
global.bb_threat [ entity.force . name ] = global.bb_threat [ entity.force . name ] - threat_values [ entity.name ]
return true
2019-03-16 07:31:34 +01:00
end
2020-01-07 04:35:18 +01:00
return Public