iw6-scripts-dev/maps/mp/bots/_bots_ks_remote_vehicle.gsc
2024-12-11 11:28:08 +01:00

2432 lines
73 KiB
Plaintext

#include common_scripts\utility;
#include maps\mp\_utility;
#include maps\mp\bots\_bots;
#include maps\mp\bots\_bots_util;
#include maps\mp\bots\_bots_ks;
remote_vehicle_setup()
{
while(!IsDefined(level.bot_variables_initialized))
wait(0.05);
if ( IsDefined(level.bot_initialized_remote_vehicles) )
return;
level.bot_ks_heli_offset["heli_pilot"] = (0,0,350);
level.bot_ks_heli_offset["heli_sniper"] = (0,0,228);
level.bot_ks_funcs["isUsing"]["odin_assault"] = ::isUsingRemote;
level.bot_ks_funcs["isUsing"]["odin_support"] = ::isUsingRemote;
level.bot_ks_funcs["isUsing"]["heli_pilot"] = ::isUsingRemote;
level.bot_ks_funcs["isUsing"]["heli_sniper"] = maps\mp\killstreaks\_killstreaks::isUsingHeliSniper;
level.bot_ks_funcs["isUsing"]["switchblade_cluster"] = ::isUsingRemote;
level.bot_ks_funcs["isUsing"]["vanguard"] = ::isUsingVanguard;
level.bot_ks_funcs["waittill_initial_goal"]["heli_pilot"] = ::heli_pilot_waittill_initial_goal;
level.bot_ks_funcs["waittill_initial_goal"]["heli_sniper"] = ::heli_sniper_waittill_initial_goal;
level.bot_ks_funcs["control_aiming"]["heli_pilot"] = ::heli_pilot_control_heli_aiming;
level.bot_ks_funcs["control_aiming"]["heli_sniper"] = ::empty_init_func;
level.bot_ks_funcs["control_aiming"]["vanguard"] = ::vanguard_control_aiming;
level.bot_ks_funcs["control_other"]["heli_pilot"] = ::heli_pilot_monitor_flares;
level.bot_ks_funcs["heli_pick_node"]["heli_pilot"] = ::heli_pilot_pick_node;
level.bot_ks_funcs["heli_pick_node"]["heli_sniper"] = ::heli_sniper_pick_node;
level.bot_ks_funcs["heli_pick_node"]["vanguard"] = ::vanguard_pick_node;
level.bot_ks_funcs["heli_node_get_origin"]["heli_pilot"] = ::heli_get_node_origin;
level.bot_ks_funcs["heli_node_get_origin"]["heli_sniper"] = ::heli_get_node_origin;
level.bot_ks_funcs["heli_node_get_origin"]["vanguard"] = ::vanguard_get_node_origin;
level.bot_ks_funcs["odin_perform_action"]["odin_assault"] = ::odin_assault_perform_action;
level.bot_ks_funcs["odin_perform_action"]["odin_support"] = ::odin_support_perform_action;
level.bot_ks_funcs["odin_get_target"]["odin_assault"] = ::odin_assault_get_target;
level.bot_ks_funcs["odin_get_target"]["odin_support"] = ::odin_support_get_target;
all_heli_nodes = GetStructArray("so_chopper_boss_path_struct","script_noteworthy");
level.bot_heli_nodes = [];
foreach ( heli_node in all_heli_nodes )
{
// Start nodes (which don't have a script_linkname) aren't used by bots so don't validate them
if ( IsDefined(heli_node.script_linkname) )
level.bot_heli_nodes = array_add(level.bot_heli_nodes,heli_node);
}
level.bot_heli_pilot_traceOffset = getHeliPilotTraceOffset();
/#
thread bot_validate_heli_nodes( level.bot_heli_nodes );
#/
foreach( node in level.bot_heli_nodes )
{
node.vanguard_origin = node.origin;
test_point = node.origin + (0,0,50); // catch nodes that are *just* under the trigger but the vanguard would touch the trigger at those nodes
node.valid_for_vanguard = true;
while( !origin_is_valid_for_vanguard(test_point) && test_point[2] > (node.origin[2] - 1000) )
{
test_point = test_point - (0,0,25);
}
if ( test_point[2] <= (node.origin[2] - 1000) )
node.valid_for_vanguard = false;
test_point = test_point - (0,0,50);
node.vanguard_origin = test_point;
}
bot_vehicle_node_highest_height_value = -99999999;
foreach( node in level.bot_heli_nodes )
{
bot_vehicle_node_highest_height_value = max(bot_vehicle_node_highest_height_value,node.origin[2]);
}
level.bot_vanguard_height_trace_size = (bot_vehicle_node_highest_height_value - level.bot_map_min_z) + 100;
level.odin_large_rod_radius = GetWeaponExplosionRadius("odin_projectile_large_rod_mp");
level.odin_small_rod_radius = GetWeaponExplosionRadius("odin_projectile_small_rod_mp");
level.vanguard_missile_radius = GetWeaponExplosionRadius("remote_tank_projectile_mp");
level.heli_pilot_missile_radius = GetDvarFloat( "bg_bulletExplRadius" );
while ( !isDefined( level.odin_marking_flash_radius_max ) || !isDefined( level.odin_marking_flash_radius_min ) )
wait( 0.05 );
level.odin_flash_radius = (level.odin_marking_flash_radius_max + level.odin_marking_flash_radius_min) / 2;
level.outside_zones = [];
if ( IsDefined(level.teleportGetActivePathnodeZonesFunc) )
{
all_zones = [[level.teleportGetActivePathnodeZonesFunc]]();
}
else
{
all_zones = [];
for ( i = 0; i < level.zoneCount; i++ )
all_zones[all_zones.size] = i;
}
foreach( z in all_zones )
{
if ( BotZoneGetIndoorPercent( z ) < 0.25 )
level.outside_zones = array_add(level.outside_zones, z);
}
// After calculating a move target, move for this amount of time. Lower number means the bot is more reactive.
level.bot_odin_time_to_move["recruit"] = 1.0;
level.bot_odin_time_to_move["regular"] = 0.70;
level.bot_odin_time_to_move["hardened"] = 0.40;
level.bot_odin_time_to_move["veteran"] = 0.05;
level.bot_initialized_remote_vehicles = true;
}
/#
bot_validate_heli_nodes( nodes )
{
wait(5.0);
bot_waittill_bots_enabled( true );
// If bots are being used, validate that the heli mesh has been set up correctly
// First test that there are enough valid heli nodes in the map
AssertEx(nodes.size > 2, "Level needs more than 2 heli nodes");
heli_pilot_mesh_offset = getHeliPilotMeshOffset();
bot_heli_pilot_trace_offset = level.bot_heli_pilot_traceOffset;
// Next test that the node exists above/below the heli mesh
foreach ( heli_node in nodes )
{
traceStart = ( heli_node.origin ) + ( heli_pilot_mesh_offset + bot_heli_pilot_trace_offset );
traceEnd = ( heli_node.origin ) + ( heli_pilot_mesh_offset - bot_heli_pilot_trace_offset );
traceResult = BulletTrace( traceStart, traceEnd, false, undefined, false, false, true );
AssertEx( IsDefined( traceResult["entity"] ), "Unable to find heli mesh above or below heli_node at " + heli_node.origin );
wait(0.05);
}
thread bot_draw_debug_heli_nodes( nodes );
}
bot_draw_debug_heli_nodes( nodes )
{
self notify("bot_draw_debug_heli_nodes");
self endon("bot_draw_debug_heli_nodes");
level endon("teleport_to_zone");
while(1)
{
draw_debug_heli_nodes = GetDvar("bot_DrawDebugSpecial") == "heli_nodes";
draw_debug_heli_nodes_with_vanguard_info = GetDvar("bot_DrawDebugSpecial") == "heli_nodes_vanguard_info";
if ( draw_debug_heli_nodes || draw_debug_heli_nodes_with_vanguard_info )
{
current_nodes = nodes;
for ( i = 0; i < current_nodes.size; i++ )
{
// Normal node/link color is teal, but if it is red that means the vanguard can't use it
node_color = (0,1,1);
if ( draw_debug_heli_nodes_with_vanguard_info )
{
if ( !current_nodes[i].valid_for_vanguard )
{
node_color = (1,0,0);
}
else if ( !bot_vectors_are_equal( current_nodes[i].origin, current_nodes[i].vanguard_origin ) )
{
node_color = (1,0,0);
line( current_nodes[i].origin, current_nodes[i].vanguard_origin, (1,0,0), 1.0, true );
bot_draw_cylinder(current_nodes[i].vanguard_origin - (0,0,5), 10, 12, 0.05, undefined, (0,1,1), true, 4);
}
}
bot_draw_cylinder(current_nodes[i].origin - (0,0,5), 10, 12, 0.05, undefined, node_color, true, 4);
foreach( neighbor_node in current_nodes[i].neighbors )
{
if ( IsDefined(neighbor_node.script_linkname) && array_contains( current_nodes, neighbor_node ) )
{
// Neighbor node is valid and has not been visited yet, so draw a line to it
link_color = (0,1,1);
if ( draw_debug_heli_nodes_with_vanguard_info )
{
if ( current_nodes[i].valid_for_vanguard && neighbor_node.valid_for_vanguard )
{
if ( !bot_vectors_are_equal( current_nodes[i].origin, current_nodes[i].vanguard_origin ) ||
!bot_vectors_are_equal( neighbor_node.origin, neighbor_node.vanguard_origin ) )
{
// Draw a teal line between the actual valid vanguard node positions
line( current_nodes[i].vanguard_origin, neighbor_node.vanguard_origin, link_color, 1.0, true );
link_color = (1,0,0);
}
}
else
{
link_color = (1,0,0);
}
}
line( current_nodes[i].origin, neighbor_node.origin, link_color, 1.0, true );
}
}
// Once we've gone through all the neighbor nodes, remove this node from the list (so we don't get double lines)
current_nodes[i] = current_nodes[current_nodes.size-1];
current_nodes[current_nodes.size-1] = undefined;
i--;
}
}
wait(0.05);
}
}
#/
//========================================================
// bot_killstreak_remote_control
//========================================================
bot_killstreak_remote_control( killstreak_info, killstreaks_array, canUseFunc, controlFunc, prefer_place_outside )
{
// let control thread decide if we can use this killstreak now or not
if ( !IsDefined( controlFunc ) )
return false;
// Get to a place to hide around me first
needs_new_hide_goal = true;
needs_to_reach_hide_goal = true;
hideNode = undefined;
if ( IsDefined(self.node_ambushing_from) )
{
script_goal_radius = self BotGetScriptGoalRadius();
dist_sq_to_camp_goal = DistanceSquared( self.origin, self.node_ambushing_from.origin );
if ( dist_sq_to_camp_goal < squared(script_goal_radius) )
{
needs_new_hide_goal = false;
needs_to_reach_hide_goal = false;
}
else if ( dist_sq_to_camp_goal < squared(200) )
{
needs_new_hide_goal = false;
}
}
is_vanguard_in_inside_map = (killstreak_info.streakName == "vanguard" && is_indoor_map());
if ( is_vanguard_in_inside_map || needs_new_hide_goal )
{
nodesAroundMe = GetNodesInRadius( self.origin, 500, 0, 512 );
if ( IsDefined( nodesAroundMe ) && nodesAroundMe.size > 0 )
{
if ( IsDefined(prefer_place_outside) && prefer_place_outside )
{
nodesAroundMeOld = nodesAroundMe;
nodesAroundMe = [];
foreach( node in nodesAroundMeOld )
{
if ( NodeExposedToSky(node) )
{
linked_nodes = GetLinkedNodes( node );
num_linked_nodes_exposed = 0;
foreach( linked_node in linked_nodes )
{
if ( NodeExposedToSky(linked_node) )
num_linked_nodes_exposed++;
}
if ( num_linked_nodes_exposed / linked_nodes.size > 0.5 )
nodesAroundMe = array_add(nodesAroundMe,node);
}
}
}
if ( is_vanguard_in_inside_map )
{
nodes = self BotNodeScoreMultiple( nodesAroundMe, "node_exposed" );
foreach( node in nodes )
{
if ( BulletTracePassed(node.origin + (0,0,30), node.origin + (0,0,400), false, self ) )
{
hideNode = node;
break;
}
wait(0.05); // Wait a frame between traces
}
}
else if ( nodesAroundMe.size > 0 )
{
hideNode = self BotNodePick( nodesAroundMe, min(3,nodesAroundMe.size), "node_hide" );
}
if ( !IsDefined(hideNode) )
return false;
self BotSetScriptGoalNode( hideNode, "tactical" );
}
}
if ( needs_to_reach_hide_goal )
{
result = self bot_waittill_goal_or_fail();
if ( result != "goal" )
{
self try_clear_hide_goal(hideNode);
return true;
}
}
if ( IsDefined( canUseFunc ) && !self [[canUseFunc]]() )
{
self try_clear_hide_goal(hideNode);
return false;
}
if ( !self bot_allowed_to_use_killstreaks() )
{
// This may have become false during the wait, like an enemy appeared while we pathed to hiding spot
self try_clear_hide_goal(hideNode);
return true;
}
if ( !IsDefined(hideNode) )
{
if ( self GetStance() == "prone" )
self BotSetStance("prone");
else if ( self GetStance() == "crouch" )
self BotSetStance("crouch");
}
else if ( self BotGetDifficultySetting("strategyLevel") > 0 )
{
if ( RandomInt(100) > 50 )
self BotSetStance("prone");
else
self BotSetStance("crouch");
}
bot_switch_to_killstreak_weapon( killstreak_info, killstreaks_array, killstreak_info.weapon );
self.vehicle_controlling = undefined;
self thread [[controlFunc]]();
self thread bot_end_control_on_respawn();
self thread bot_end_control_watcher(hideNode);
self waittill("control_func_done");
return true;
}
bot_end_control_on_respawn()
{
self endon( "disconnect" );
self endon( "control_func_done" );
level endon( "game_ended" );
self waittill ("spawned_player");
self notify( "control_func_done" );
}
bot_end_control_watcher(hideNode)
{
self endon("disconnect");
self waittill("control_func_done");
self try_clear_hide_goal(hideNode);
self BotSetStance("none");
self BotSetScriptMove( 0, 0 ); // clear any script move leftover from controlling the vehicle
self BotSetFlag("disable_movement",false);
self BotSetFlag("disable_rotation",false);
self.vehicle_controlling = undefined;
}
try_clear_hide_goal(hideNode)
{
if ( IsDefined(hideNode) && self BotHasScriptGoal() && IsDefined( self BotGetScriptGoalNode() ) && self BotGetScriptGoalNode() == hideNode )
self BotClearScriptGoal();
}
bot_end_control_on_vehicle_death( vehicle )
{
vehicle waittill("death");
self notify("control_func_done");
}
bot_waittill_using_vehicle( type )
{
time_started = GetTime();
while( !self [[level.bot_ks_funcs["isUsing"][type]]]() )
{
wait(0.05);
if ( GetTime() - time_started > 5000 )
return false;
}
return true;
}
// ******************************************************************************************
// *********************************** Switchblade Cluster **********************************
// ******************************************************************************************
bot_control_switchblade_cluster()
{
self endon( "spawned_player" );
self endon( "disconnect" );
self endon( "control_func_done" );
level endon( "game_ended" );
self childthread handle_disable_rotation();
// First wait until bot is in the heli (or he times out)
result = self bot_waittill_using_vehicle( "switchblade_cluster" );
if ( !result )
self notify("control_func_done");
self thread switchblade_handle_awareness(); // The red boxes allow very easy enemy sight, so articially speed up the process
current_rocket = find_cluster_rocket_for_bot(self);
wait(0.1); // give time for the visionset to change, else the bot will be piloting the missile while the screen is still black
self.oldmaxsightdistsqrd = self.maxsightdistsqrd;
self.maxsightdistsqrd = 16000 * 16000;
self thread watch_end_switchblade();
current_random_zone_target = undefined;
num_mini_missiles_fired = 0;
next_missile_time = 0;
mini_missiles = [];
next_mini_missile_target = undefined;
has_boosted = false;
last_predict_enemy_time = 0;
target_loc = undefined;
locked_target = undefined;
lock_movement_permanently = false;
map_is_mostly_indoor = is_indoor_map();
while ( self [[level.bot_ks_funcs["isUsing"]["switchblade_cluster"]]]() && IsDefined(current_rocket) )
{
foreach ( rocket in level.rockets )
{
if ( IsDefined(rocket) && rocket.owner == self && rocket.weapon_name == "switch_blade_child_mp" )
{
should_add = true;
foreach( missile_struct in mini_missiles )
{
if ( missile_struct.rocket == rocket )
should_add = false;
}
if ( should_add )
{
new_missile_struct = SpawnStruct();
new_missile_struct.rocket = rocket;
new_missile_struct.target = next_mini_missile_target;
next_mini_missile_target = undefined;
mini_missiles = array_add(mini_missiles,new_missile_struct);
}
}
}
for ( i = 0; i < mini_missiles.size; i++ )
{
missile_struct = mini_missiles[i];
if ( IsDefined(missile_struct) && IsDefined(missile_struct.rocket) && !array_contains(level.rockets, missile_struct.rocket) )
{
mini_missiles[i] = mini_missiles[mini_missiles.size-1];
mini_missiles[mini_missiles.size-1] = undefined;
i--;
}
}
if ( lock_movement_permanently )
{
wait(0.05);
continue;
}
best_target = undefined;
if ( IsDefined(locked_target) )
{
best_target = locked_target;
if ( !IsAlive(locked_target) || !self BotCanSeeEntity(best_target) )
{
if ( !IsAlive(locked_target) )
lock_movement_permanently = true;
wait(0.05);
continue;
}
}
visible_enemies = [];
if ( !IsDefined(best_target) )
{
enemies = bot_killstreak_get_all_outside_enemies( false );
current_missile_targets = [];
foreach( missile_struct in mini_missiles )
{
if ( IsDefined(missile_struct.target) )
current_missile_targets = array_add(current_missile_targets, missile_struct.target);
}
enemies = array_remove_array(enemies,current_missile_targets);
foreach ( enemy in enemies )
{
if ( enemy _hasPerk( "specialty_noplayertarget" ) )
continue;
if ( self BotCanSeeEntity(enemy) || (map_is_mostly_indoor && within_fov( self GetEye(), current_rocket.angles, enemy.origin, self BotGetFovDot() )) )
{
if ( !self bot_body_is_dead() && DistanceSquared(enemy.origin,self.origin) < 200*200 )
continue;
visible_enemies = array_add(visible_enemies,enemy);
if ( !IsDefined(best_target) )
{
best_target = enemy;
}
else
{
rocket_to_best_target = VectorNormalize(best_target.origin - current_rocket.origin);
rocket_to_enemy = VectorNormalize(enemy.origin - current_rocket.origin);
rocket_orientation = AnglesToForward(current_rocket.angles);
dot_to_best_target = VectorDot( rocket_to_best_target, rocket_orientation );
dot_to_enemy = VectorDot( rocket_to_enemy, rocket_orientation );
if ( dot_to_enemy > dot_to_best_target )
best_target = enemy;
}
}
}
}
if ( IsDefined(best_target) )
{
current_random_zone_target = undefined;
dist_above_target = current_rocket.origin[2] - best_target.origin[2];
bot_difficulty = self BotGetDifficulty();
if ( bot_difficulty == "recruit" )
{
target_loc = best_target.origin;
}
else
{
if ( dist_above_target < 5000 )
{
target_loc = best_target.origin;
}
else if ( Length(best_target GetEntityVelocity()) < 25 )
{
target_loc = best_target.origin;
}
else if ( GetTime() - last_predict_enemy_time > 500 )
{
last_predict_enemy_time = GetTime();
predict_ahead_time = 3.0;
if ( bot_difficulty == "regular" )
predict_ahead_time = 1.0;
target_loc = GetPredictedEntityPosition( best_target, predict_ahead_time );
}
}
// find rocket target point (point on ground in center of screen)
desired_angles = missile_get_desired_angles_to_target( current_rocket, target_loc );
// Missile can move target 200 units per frame
dist_current_to_desired = missile_get_distance_to_target(current_rocket, target_loc);
if ( dist_current_to_desired < 30 )
speed = 0.00;
else if ( dist_current_to_desired < 100 )
speed = 0.15;
else if ( dist_current_to_desired < 200 )
speed = 0.30;
else if ( dist_current_to_desired < 400 )
speed = 0.60;
else
speed = 1.0;
if ( has_boosted )
speed = min(speed * 3, 1.0);
if ( speed > 0 )
{
self BotSetScriptMove( desired_angles[1], 0.05, speed, true, true );
}
else if ( GetTime() > next_missile_time )
{
if ( num_mini_missiles_fired < 2 )
{
self BotPressButton("attack");
num_mini_missiles_fired++;
next_missile_time = GetTime() + 200;
if ( (bot_difficulty == "regular" && num_mini_missiles_fired == 2 ) || bot_difficulty == "hardened" || bot_difficulty == "veteran" )
{
// If this is the first missile fired and there's only one visible enemy on screen, then the next missile should fire at him as well
// otherwise the next missile should find a different target
should_stay_on_target = (num_mini_missiles_fired == 1) && (visible_enemies.size == 1);
if ( !should_stay_on_target )
{
next_mini_missile_target = best_target;
next_missile_time += 800;
}
}
}
else if ( !has_boosted && (dist_above_target < 5000 || bot_difficulty == "recruit") )
{
has_boosted = true;
self BotPressButton("attack");
if ( bot_difficulty == "recruit" )
locked_target = best_target;
}
}
}
else
{
if ( !IsDefined(current_random_zone_target) )
current_random_zone_target = Random(level.outside_zones);
zone_origin = GetZoneNodeForIndex(current_random_zone_target).origin;
if ( missile_get_distance_to_target(current_rocket, zone_origin) < 200 )
{
current_random_zone_target = Random(level.outside_zones);
zone_origin = GetZoneNodeForIndex(current_random_zone_target).origin;
}
desired_angles = missile_get_desired_angles_to_target( current_rocket, zone_origin );
self BotSetScriptMove( desired_angles[1], 0.05, 0.75, true, true );
}
wait(0.05);
}
self notify("control_func_done");
}
missile_get_desired_angles_to_target( missile, target_origin )
{
current_target = missile_find_ground_target( missile, target_origin[2] );
missile_target_to_desired_target = VectorNormalize( target_origin - current_target );
return VectorToAngles(missile_target_to_desired_target);
}
missile_get_distance_to_target( missile, target_origin )
{
current_target = missile_find_ground_target( missile, target_origin[2] );
return Distance(current_target, target_origin);
}
handle_disable_rotation()
{
self BotSetFlag("disable_rotation",true);
self BotSetFlag("disable_movement",true);
find_cluster_rocket_for_bot(self);
self BotSetFlag("disable_rotation",false);
self BotSetFlag("disable_movement",false);
}
switchblade_handle_awareness()
{
self endon("disconnect");
self BotSetAwareness(2.5);
self waittill("control_func_done");
self BotSetAwareness(1.0);
}
missile_find_ground_target( missile, desired_target_height )
{
// Finds the point along the rocket's vector at the height of the target
// i.e. this is the current target that would be hit if the rocket did not move
missile_dir = AnglesToForward( missile.angles );
vec_to_ground_size = ( desired_target_height - missile.origin[2] ) / missile_dir[2];
pt_on_ground = missile.origin + missile_dir * vec_to_ground_size;
return pt_on_ground;
}
watch_end_switchblade()
{
self endon("disconnect");
self waittill("control_func_done");
self.maxsightdistsqrd = self.oldmaxsightdistsqrd;
}
find_cluster_rocket_for_bot( bot )
{
while(1)
{
foreach ( rocket in level.rockets )
{
if ( IsDefined(rocket) && rocket.owner == bot )
return rocket;
}
wait(0.05);
}
}
// ******************************************************************************************
// **************************************** Vanguard ****************************************
// ******************************************************************************************
vanguard_allowed()
{
if ( !self aerial_vehicle_allowed() )
return false;
if ( maps\mp\killstreaks\_vanguard::exceededMaxVanguards( self.team ) || (level.littleBirds.size >= 4) )
return false;
if ( isKillstreakBlockedForBots( "vanguard" ) )
return false;
return true;
}
bot_killstreak_vanguard_start( killstreak_info, killstreaks_array, canUseFunc, controlFunc )
{
bot_killstreak_remote_control( killstreak_info, killstreaks_array, canUseFunc, controlFunc, true );
}
isUsingVanguard()
{
return ( self isUsingRemote() && self.usingRemote == "vanguard" && IsDefined(self.remoteUAV) );
}
SCR_CONST_vanguard_desired_path_height = 64;
bot_control_vanguard()
{
self endon( "spawned_player" );
self endon( "disconnect" );
self endon( "control_func_done" );
level endon( "game_ended" );
// First wait until bot is in the vanguard (or he times out)
result = self bot_waittill_using_vehicle("vanguard");
if ( !result )
self notify("control_func_done");
self.vehicle_controlling = self.remoteUAV;
self childthread bot_end_control_on_vehicle_death( self.vehicle_controlling );
/#
self childthread vanguard_assert_on_range( self.vehicle_controlling );
#/
self.vehicle_controlling endon("death");
wait(0.5); // Wait for the fade-in from black
/#
if ( !self.vehicle_controlling maps\mp\killstreaks\_vanguard::vanguard_in_range() )
{
AssertMsg( "Vanguard at location " + self.vehicle_controlling.origin + " spawned in an invalid area for the Vanguard. Spawned by bot at location " + self.origin );
self BotPressButton("use", 4.0); // cancel vanguard
wait(4.0); // No need to return, will be handled by the vehicle dying
}
#/
// Next, get the vanguard outside
total_time_waited = 0;
try_to_get_outside = !self.vehicle_controlling vanguard_is_outside();
outside_target = undefined;
attempts_to_find_outside_target = 0;
map_is_mostly_indoor = is_indoor_map();
while ( try_to_get_outside && !map_is_mostly_indoor )
{
nodes = GetNodesInRadiusSorted( self.vehicle_controlling.origin, 1024, 64, 512, "path" );
if ( IsDefined(outside_target) )
nodes = array_remove(nodes, outside_target); // Second attempt, so remove the first node (since it failed to find a path to it)
foreach( potential_target in nodes )
{
if ( node_is_valid_outside_for_vanguard( potential_target ) )
{
outside_target = potential_target;
break;
}
wait(0.05);
total_time_waited += 0.05;
}
if ( total_time_waited < 1.0 )
wait( 1.0 - total_time_waited ); // Allow time for visionset change
if ( !IsDefined(outside_target) )
{
AssertMsg("Bot tried to use Vanguard from location " + self.origin + " but couldn't find an outside node within a 1024 unit radius");
self BotPressButton("use", 4.0); // cancel vanguard
wait(4.0); // No need to return, will be handled by the vehicle dying
}
path = bot_queued_process( "GetNodesOnPathVanguard", ::func_get_nodes_on_path, self.vehicle_controlling.origin, outside_target.origin );
if ( !IsDefined(path) )
{
if ( attempts_to_find_outside_target == 0 )
{
attempts_to_find_outside_target++;
wait(0.05);
continue;
}
else
{
//AssertMsg( "Vanguard at location " + self.vehicle_controlling.origin + " could not calculate a path to an outside target at " + outside_target.origin );
self BotPressButton("use", 4.0); // cancel vanguard
wait(4.0); // No need to return, will be handled by the vehicle dying
}
}
for( i=0; i<path.size; i++ )
{
next_node = path[i];
if ( i == 0 && DistanceSquared(self.origin, next_node.origin) < 40*40 )
{
// Initial node is directly under the bot's body, so just skip it
continue;
}
dist_wanted = 32;
if ( i == path.size - 1 )
dist_wanted = 16;
last_pos = self.vehicle_controlling.origin;
next_stuck_check_time = GetTime() + 2500;
while( Distance2DSquared( next_node.origin, self.vehicle_controlling.origin ) > dist_wanted * dist_wanted )
{
if ( self.vehicle_controlling vanguard_is_outside() )
{
// Already made it outside before we reached the end of the path
i = path.size;
break;
}
if ( GetTime() > next_stuck_check_time )
{
next_stuck_check_time = GetTime() + 2500;
dist_to_last_pos = DistanceSquared(self.vehicle_controlling.origin, last_pos );
if ( dist_to_last_pos < 1.0 )
{
// Stuck, so try to just skip to the next node
i++;
break;
}
last_pos = self.vehicle_controlling.origin;
}
dir_to_next_node = VectorNormalize(next_node.origin - self.vehicle_controlling.origin);
self BotSetScriptMove( VectorToAngles(dir_to_next_node)[1], 0.20 );
self BotLookAtPoint(next_node.origin, 0.20, "script_forced" );
desired_height = next_node.origin[2] + SCR_CONST_vanguard_desired_path_height;
height_difference = desired_height - self.vehicle_controlling.origin[2];
if ( height_difference > 10 )
self BotPressButton("lethal");
else if ( height_difference < -10 )
self BotPressButton("tactical");
/#
if ( GetDvarInt("ai_showpaths") == 1 )
{
line(self.vehicle_controlling.origin, path[i].origin + (0,0,SCR_CONST_vanguard_desired_path_height), (0,0,1), 1.0, false, 4);
for( j=i; j<path.size-1; j++ )
{
line(path[j].origin + (0,0,SCR_CONST_vanguard_desired_path_height), path[j+1].origin + (0,0,SCR_CONST_vanguard_desired_path_height), (0,0,1), 1.0, false, 4);
}
}
#/
wait(0.05);
}
}
try_to_get_outside = false;
if ( !self.vehicle_controlling vanguard_is_outside() )
try_to_get_outside = true;
}
self BotSetScriptMove( 0, 0 );
self BotLookAtPoint( undefined );
// Start looking around for targets while ascending
self childthread [[level.bot_ks_funcs["control_aiming"]["vanguard"]]]();
// Now that the vanguard is outside, ascend to the correct height
last_height = self.vehicle_controlling.origin[2];
sideways_dir = undefined;
next_sideways_check = GetTime() + 2000;
possible_directions = [];
possible_directions[0] = (1,0,0);
possible_directions[1] = (-1,0,0);
possible_directions[2] = (0,1,0);
possible_directions[3] = (0,-1,0);
possible_directions[4] = (1,1,0);
possible_directions[5] = (1,-1,0);
possible_directions[6] = (-1,1,0);
possible_directions[7] = (-1,-1,0);
closest_heli_node = find_closest_heli_node_2D( self.vehicle_controlling.origin, "vanguard" );
while( (closest_heli_node.vanguard_origin[2] - self.vehicle_controlling.origin[2]) > 20 )
{
if ( !self.vehicle_controlling maps\mp\killstreaks\_vanguard::vanguard_in_range() )
break;
if ( GetTime() > next_sideways_check )
{
next_sideways_check = GetTime() + 2000;
if ( IsDefined(sideways_dir) )
{
sideways_dir = undefined;
}
else
{
height_difference = self.vehicle_controlling.origin[2] - last_height;
if ( height_difference < 20 && !map_is_mostly_indoor )
{
// Stuck, so try to move sideways
directions = array_randomize(possible_directions);
foreach( direction in directions )
{
if ( pos_passes_sky_trace( self.vehicle_controlling.origin + direction * 64 ) )
{
if ( !BulletTracePassed( self.vehicle_controlling.origin, self.vehicle_controlling.origin + direction * 64, false, self.vehicle_controlling ) )
{
wait(0.05);
continue;
}
sideways_dir = direction;
break;
}
wait(0.05);
}
}
}
last_height = self.vehicle_controlling.origin[2];
}
if ( IsDefined(sideways_dir) )
{
/#
if ( GetDvarInt("ai_showpaths") == 1 )
line( self.vehicle_controlling.origin, self.vehicle_controlling.origin + sideways_dir * 64, (0,1,0), 1.0, true );
#/
self BotSetScriptMove( VectorToAngles(sideways_dir)[1], 0.05 );
if ( cointoss() )
self BotPressButton("tactical");
}
else
{
self BotPressButton("lethal");
}
wait(0.05);
}
wait(1.0); // wait for vertical movement to finish
while( !self.vehicle_controlling maps\mp\killstreaks\_vanguard::vanguard_in_range() )
{
self BotPressButton("tactical");
wait(0.1);
}
wait(1.0); // wait for vertical movement to finish
self BotSetFlag("disable_movement",false);
self bot_control_heli_main_move_loop( "vanguard", false );
self notify("control_func_done");
}
/#
vanguard_assert_on_range( vehicle )
{
vehicle waittill("death", reason);
if ( IsDefined(reason) )
{
AssertEx( reason != "range_death", "Bot drove vanguard out of range in " + getdvar("mapname") );
}
}
#/
pos_is_valid_outside_for_vanguard( pos )
{
nearest_node = GetClosestNodeInSight( pos );
if ( IsDefined(nearest_node) )
return node_is_valid_outside_for_vanguard(nearest_node);
return false;
}
node_is_valid_outside_for_vanguard( node )
{
if ( NodeExposedToSky(node) )
return pos_passes_sky_trace(node.origin);
return false;
}
pos_passes_sky_trace( pos )
{
// Trace directly up to determine sky
start = pos;
end = pos + (0,0,level.bot_vanguard_height_trace_size);
// If end is up inside the no-fly zone, move the trace end down until its in a safe spot
while( !origin_is_valid_for_vanguard(end) && end[2] > start[2] )
{
end = end - (0,0,50);
}
if ( end[2] <= start[2] )
return false;
result = BulletTracePassed( start, end, false, undefined );
//if ( result )
// line(start,end,(0,1,0),1.0,true,99999999);
return result;
}
SCR_CONST_VANGUARD_RADIUS = 18.0;
vanguard_is_outside() // self == vanguard
{
nearest_node = GetClosestNodeInSight( self.origin );
if ( IsDefined(nearest_node) && !NodeExposedToSky(nearest_node) )
return false;
wait(0.05);
if ( !pos_passes_sky_trace( self.origin + (SCR_CONST_VANGUARD_RADIUS,0,25) ) ) // offset a bit up so it doesn't collide with the vanguard model
return false;
wait(0.05);
if ( !pos_passes_sky_trace( self.origin + (-1*SCR_CONST_VANGUARD_RADIUS,0,25) ) ) // offset a bit up so it doesn't collide with the vanguard model
return false;
wait(0.05);
if ( !pos_passes_sky_trace( self.origin + (0,SCR_CONST_VANGUARD_RADIUS,25) ) ) // offset a bit up so it doesn't collide with the vanguard model
return false;
wait(0.05);
if ( !pos_passes_sky_trace( self.origin + (0,-1*SCR_CONST_VANGUARD_RADIUS,25) ) ) // offset a bit up so it doesn't collide with the vanguard model
return false;
return true;
}
vanguard_control_aiming()
{
self notify("vanguard_control_aiming");
self endon("vanguard_control_aiming");
target_loc = undefined;
next_random_node_time = 0;
last_time_fired = GetTime();
last_predict_enemy_time = 0;
last_enemy_predicted = undefined;
time_following_red_boxes = 0;
while( self [[level.bot_ks_funcs["isUsing"]["vanguard"]]]() )
{
enemy_chosen = undefined;
eye_pos = self GetEye();
eye_angles = self GetPlayerAngles();
bot_fov = self BotGetFovDot();
if ( IsAlive(self.enemy) && self BotCanSeeEntity(self.enemy) )
{
should_target_enemy = true;
enemy_chosen = self.enemy;
time_following_red_boxes = 0;
}
else if ( time_following_red_boxes < 10.0 )
{
foreach( character in level.characters )
{
if ( character == self || !IsAlive(character) )
continue;
if ( character _hasPerk( "specialty_noplayertarget" ) )
continue;
if ( !IsDefined( character.team ) )
continue;
if ( !level.teamBased || ( self.team != character.team ) )
{
if ( within_fov( eye_pos, eye_angles, character.origin, bot_fov ) )
{
time_following_red_boxes += 0.05; // After 5 seconds of following boxes, give up and search randomly around the map
if ( IsDefined(enemy_chosen) )
{
dist_vanguard_to_enemy_chosen_sq = DistanceSquared(self.vehicle_controlling.origin, enemy_chosen.origin);
dist_vanguard_to_this_character = DistanceSquared(self.vehicle_controlling.origin, character.origin);
if ( dist_vanguard_to_this_character < dist_vanguard_to_enemy_chosen_sq )
enemy_chosen = character;
}
else
{
enemy_chosen = character;
}
}
}
}
}
if ( IsDefined(enemy_chosen) )
{
if ( (IsAI(enemy_chosen) || IsPlayer(enemy_chosen)) && Length(enemy_chosen GetEntityVelocity()) < 25 )
{
target_loc = enemy_chosen.origin;
}
else if ( GetTime() - last_predict_enemy_time < 500 )
{
if ( last_enemy_predicted != enemy_chosen )
target_loc = enemy_chosen.origin;
}
else if ( GetTime() - last_predict_enemy_time > 500 )
{
last_predict_enemy_time = GetTime();
target_loc = GetPredictedEntityPosition( enemy_chosen, 3.0 );
last_enemy_predicted = enemy_chosen;
}
allowable_dist = 165;
if ( GetTime() - last_time_fired > 10000 )
allowable_dist = 200;
if ( DistanceSquared(self.vehicle_controlling.attackArrow.origin,target_loc) < level.vanguard_missile_radius * level.vanguard_missile_radius )
{
if ( self bot_body_is_dead() || DistanceSquared( self.vehicle_controlling.attackArrow.origin, self.origin ) > level.vanguard_missile_radius * level.vanguard_missile_radius )
{
last_time_fired = GetTime();
self BotPressButton("attack");
}
}
//bot_draw_cylinder(target_loc, 10, 10, 0.05, undefined, (0,1,0), false, 4);
//line( enemy_chosen.origin, target_loc, (0,1,0), 1, false );
}
else if ( GetTime() > next_random_node_time )
{
next_random_node_time = GetTime() + RandomIntRange(1000,2000);
target_loc = get_random_outside_target();
self.next_goal_time = GetTime();
}
if ( Length(target_loc) == 0 )
target_loc = (0,0,10);
self BotLookAtPoint( target_loc, 0.2, "script_forced");
wait(0.05);
}
}
vanguard_pick_node( current_node )
{
current_node.bot_visited_times[self.entity_number]++;
current_node_origin = [[level.bot_ks_funcs["heli_node_get_origin"]["vanguard"]]](current_node);
best_nodes = bot_vanguard_find_unvisited_nodes( current_node );
best_nodes_old = best_nodes;
best_nodes = [];
foreach( node in best_nodes_old )
{
if ( node.valid_for_vanguard )
{
if ( current_node.origin[2] != current_node.vanguard_origin[2] || node.origin[2] != node.vanguard_origin[2] )
{
node_origin = [[level.bot_ks_funcs["heli_node_get_origin"]["vanguard"]]](node);
hitPos = PlayerPhysicsTrace( current_node_origin, node_origin );
if ( DistanceSquared( hitPos, node_origin ) < 1 )
best_nodes = array_add(best_nodes, node);
wait(0.05);
}
else
{
best_nodes = array_add(best_nodes, node);
}
}
}
if ( best_nodes.size == 0 && best_nodes_old.size > 0 )
{
// All of the least-visited nodes that we found were not valid destinations
// So we need to mark each node as visited, otherwise the bot will just keep attempting to pick this same set over and over again and will never move
foreach ( node in best_nodes_old )
node.bot_visited_times[self.entity_number]++;
}
return heli_pick_node_furthest_from_center( best_nodes, "vanguard" );
}
bot_vanguard_find_unvisited_nodes(current_node)
{
lowest_visted_num = 99;
best_nodes = [];
foreach( node in current_node.neighbors )
{
// node.neighbors contains any nodes that this node links to, or that link to it. So it may contain start nodes that we need to ignore
if ( IsDefined(node.script_linkname) && node.valid_for_vanguard )
{
times_visited_this_node = node.bot_visited_times[self.entity_number];
if ( times_visited_this_node < lowest_visted_num )
{
best_nodes = [];
best_nodes[0] = node;
lowest_visted_num = times_visited_this_node;
}
else if ( times_visited_this_node == lowest_visted_num )
{
best_nodes[best_nodes.size] = node;
}
}
}
return best_nodes;
}
vanguard_get_node_origin( node )
{
return node.vanguard_origin;
}
origin_is_valid_for_vanguard( origin )
{
test_script_origin = spawn_tag_origin();
test_script_origin.origin = origin;
result = test_script_origin maps\mp\killstreaks\_vanguard::vanguard_in_range();
test_script_origin delete();
return result;
}
// ******************************************************************************************
// *************************************** Heli Sniper **************************************
// ******************************************************************************************
heli_sniper_allowed()
{
if ( !self aerial_vehicle_allowed() )
return false;
if ( maps\mp\killstreaks\_heliSniper::exceededMaxHeliSnipers() )
return false;
return true;
}
heli_sniper_waittill_initial_goal()
{
self.vehicle_controlling waittill( "near_goal" );
}
bot_control_heli_sniper()
{
self thread heli_sniper_clear_script_goal_on_ride();
bot_control_heli("heli_sniper");
}
heli_sniper_clear_script_goal_on_ride()
{
self endon( "spawned_player" );
self endon( "disconnect" );
self endon( "control_func_done" );
level endon( "game_ended" );
while( !(self maps\mp\killstreaks\_killstreaks::isUsingHeliSniper() && self IsLinked()) )
{
wait(0.05);
}
self BotClearScriptGoal(); // Clear goal because we are going to be picked up in the heli
}
heli_sniper_pick_node( current_node )
{
current_node.bot_visited_times[self.entity_number]++;
best_nodes = bot_heli_find_unvisited_nodes( current_node );
return heli_pick_node_furthest_from_center( best_nodes, "heli_sniper" );
}
// ******************************************************************************************
// *************************************** Heli Pilot ***************************************
// ******************************************************************************************
heli_pilot_allowed()
{
if ( !self aerial_vehicle_allowed() )
return false;
if ( maps\mp\killstreaks\_helicopter_pilot::exceededMaxHeliPilots( self.team ) )
return false;
return true;
}
heli_pilot_waittill_initial_goal()
{
self.vehicle_controlling waittill( "goal_reached" );
}
bot_control_heli_pilot()
{
bot_control_heli("heli_pilot");
}
heli_pilot_pick_node( current_node )
{
current_node.bot_visited_times[self.entity_number]++;
best_nodes = bot_heli_find_unvisited_nodes( current_node );
node_picked = Random(best_nodes);
return node_picked;
}
heli_pilot_monitor_flares()
{
self notify("heli_pilot_monitor_flares");
self endon("heli_pilot_monitor_flares");
missiles_checked = [];
while( self [[level.bot_ks_funcs["isUsing"]["heli_pilot"]]]() )
{
self.vehicle_controlling waittill("targeted_by_incoming_missile", missiles);
if ( !maps\mp\killstreaks\_flares::flares_areAvailable( self.vehicle_controlling ) )
break;
all_missiles_have_been_checked = true;
foreach( missile in missiles )
{
if ( IsDefined(missile) && !array_contains(missiles_checked, missile) )
all_missiles_have_been_checked = false;
}
if ( !all_missiles_have_been_checked )
{
chance_to_use_flare = Clamp(0.34 * self BotGetDifficultySetting("strategyLevel"), 0.0, 1.0);
if ( RandomFloat(1.0) < chance_to_use_flare )
self notify("manual_flare_popped");
missiles_checked = array_combine( missiles_checked, missiles );
missiles_checked = array_removeUndefined(missiles_checked);
wait(3.0); // Wait a bit to avoid popping both flares at once when it wouldn't be necessary
}
}
}
heli_pilot_control_heli_aiming()
{
self notify("heli_pilot_control_heli_aiming");
self endon("heli_pilot_control_heli_aiming");
last_enemy_loc = undefined;
last_enemy_seen = undefined;
target_loc = undefined;
next_random_node_time = 0;
last_inaccuracy_check = 0;
inaccuracy_vector = undefined;
bot_inaccuracy = (self BotGetDifficultySetting("minInaccuracy") + self BotGetDifficultySetting("maxInaccuracy")) / 2;
time_following_outlines = 0;
while( self [[level.bot_ks_funcs["isUsing"]["heli_pilot"]]]() )
{
should_aim_at_enemy = false;
should_fire_at_enemy = false;
if ( IsDefined(last_enemy_seen) && last_enemy_seen.health <= 0 && GetTime() - last_enemy_seen.deathtime < 2000 )
{
// Enemy just died, so force aim and fire at the last recorded position
should_aim_at_enemy = true;
should_fire_at_enemy = true;
}
else if ( IsAlive(self.enemy) && ( self BotCanSeeEntity(self.enemy) || (GetTime() - self LastKnownTime(self.enemy)) <= 300 ) )
{
// Enemy is either on screen or visible due to outline, etc
should_aim_at_enemy = true;
last_enemy_seen = self.enemy;
last_enemy_loc = self.enemy.origin;
if ( self BotCanSeeEntity(self.enemy) )
{
// Enemy is onscreen (i.e. direct line of sight)
time_following_outlines = 0;
should_fire_at_enemy = true;
last_time_actually_saw_enemy = GetTime();
}
else
{
time_following_outlines += 0.05;
if ( time_following_outlines > 5.0 )
{
// If we keep seeing enemies as outlines but they're never actually visible, then eventually stop following them and look around randomly
should_aim_at_enemy = false;
}
}
}
if ( should_aim_at_enemy )
{
target_loc = last_enemy_loc - (0,0,50); // heli tends to aim a bit high, so lower the target location a bit
if ( should_fire_at_enemy && ( self bot_body_is_dead() || DistanceSquared( target_loc, self.origin ) > level.heli_pilot_missile_radius * level.heli_pilot_missile_radius ) )
self BotPressButton("attack");
if ( GetTime() > last_inaccuracy_check + 500 )
{
random_x = RandomFloatRange(-1 * bot_inaccuracy/2, bot_inaccuracy/2);
random_y = RandomFloatRange(-1 * bot_inaccuracy/2, bot_inaccuracy/2);
random_z = RandomFloatRange(-1 * bot_inaccuracy/2, bot_inaccuracy/2);
inaccuracy_vector = (150*random_x, 150*random_y, 150*random_z);
last_inaccuracy_check = GetTime();
}
target_loc = target_loc + inaccuracy_vector;
heli_eye_pos = self.vehicle_controlling GetTagOrigin("tag_player");
heli_eye_to_enemy_norm = VectorNormalize(target_loc - heli_eye_pos);
player_angles = AnglesToForward(self GetPlayerAngles());
dot = VectorDot( heli_eye_to_enemy_norm, player_angles );
if ( dot > 0.5 )
self BotPressButton("ads",0.1);
}
else if ( GetTime() > next_random_node_time )
{
next_random_node_time = GetTime() + RandomIntRange(1000,2000);
target_loc = get_random_outside_target();
self.next_goal_time = GetTime();
}
// Make sure bot isn't breaking the 15 degree pitch limit (since nothing internally limits him)
heli_to_lookat = target_loc - self.vehicle_controlling.origin;
dist_heli_to_lookat = Length(heli_to_lookat);
heli_to_lookat_angles = VectorToAngles( heli_to_lookat );
vehicle_pitch = AngleClamp( self.vehicle_controlling.angles[0] );
heli_lookat_pitch = AngleClamp( heli_to_lookat_angles[0] );
dist = int(vehicle_pitch - heli_lookat_pitch) % 360;
if ( dist > 180 )
dist = 360 - dist;
else if ( dist < -180 )
dist = -360 + dist;
if ( dist > 15 )
heli_lookat_pitch = vehicle_pitch - 15;
else if ( dist < -15 )
heli_lookat_pitch = vehicle_pitch + 15;
heli_to_lookat_angles = (heli_lookat_pitch,heli_to_lookat_angles[1],heli_to_lookat_angles[2]);
heli_to_lookat = AnglesToForward(heli_to_lookat_angles);
target_loc = self.vehicle_controlling.origin + heli_to_lookat * dist_heli_to_lookat;
if ( Length(target_loc) == 0 )
target_loc = (0,0,10);
self BotLookAtPoint( target_loc, 0.2, "script_forced");
wait(0.05);
}
}
// ******************************************************************************************
// ************************************** Odin Assault **************************************
// ******************************************************************************************
bot_control_odin_assault()
{
bot_control_odin("odin_assault");
}
odin_assault_perform_action()
{
if ( self bot_odin_try_spawn_juggernaut() )
return true;
if ( self bot_odin_try_rods() )
return true;
if ( self bot_odin_try_airdrop() )
return true;
return false;
}
odin_assault_get_target()
{
// Assault Odin doesn't specifically try to find places to drop a care package,
// but he can still drop it if he happens across a good location while following targets
return self bot_odin_find_target_for_rods();
}
bot_odin_find_target_for_rods()
{
player_to_ignore = undefined;
if ( IsDefined(self.last_large_rod_target) && GetTime() - self.last_large_rod_time < 5000 )
{
// Just shot a large rod at this guy, don't continue following him since he's probably dead
player_to_ignore = self.last_large_rod_target;
}
return bot_odin_get_closest_visible_outside_player( "enemy", true, player_to_ignore );
}
bot_odin_try_rods()
{
rod_to_fire = bot_odin_should_fire_rod_at_marker();
if ( rod_to_fire == "large" )
{
self notify("large_rod_action");
return true;
}
if ( rod_to_fire == "small" )
{
self notify("small_rod_action");
return true;
}
return false;
}
bot_odin_should_fire_rod_at_marker()
{
large_rod_available = GetTime() >= self.odin.odin_LargeRodUseTime;
small_rod_available = GetTime() >= self.odin.odin_SmallRodUseTime;
if ( large_rod_available || small_rod_available )
{
outside_enemies = bot_odin_get_visible_outside_players( "enemy", false );
dist_enemy_to_marker_sq = [];
dist_body_to_marker_sq = DistanceSquared(self.origin, self.odin.targeting_marker.origin);
for ( i = 0; i < outside_enemies.size; i++ )
{
enemy_target_point = self bot_odin_get_player_target_point(outside_enemies[i]);
dist_enemy_to_marker_sq[i] = DistanceSquared(self.odin.targeting_marker.origin, enemy_target_point);
}
if ( large_rod_available )
{
if ( !self bot_body_is_dead() && dist_body_to_marker_sq < level.odin_large_rod_radius * level.odin_large_rod_radius )
return "none";
for ( i = 0; i < outside_enemies.size; i++ )
{
if ( dist_enemy_to_marker_sq[i] < squared(level.odin_large_rod_radius) )
{
self.last_large_rod_target = outside_enemies[i];
self.last_large_rod_time = GetTime();
return "large";
}
}
}
if ( small_rod_available )
{
if ( !self bot_body_is_dead() && dist_body_to_marker_sq < level.odin_small_rod_radius * level.odin_small_rod_radius )
return "none";
for ( i = 0; i < outside_enemies.size; i++ )
{
if ( dist_enemy_to_marker_sq[i] < squared(level.odin_small_rod_radius) )
{
if ( IsDefined(self.last_large_rod_target) && self.last_large_rod_target == outside_enemies[i] && GetTime() - self.last_large_rod_time < 5000 )
{
// Just shot a large rod at this guy, don't waste a small on him as well
continue;
}
return "small";
}
}
}
}
return "none";
}
// ******************************************************************************************
// ************************************** Odin Support **************************************
// ******************************************************************************************
bot_control_odin_support()
{
bot_control_odin("odin_support");
}
odin_support_perform_action()
{
if ( self bot_odin_try_spawn_juggernaut() )
return true;
if ( self bot_odin_try_airdrop() )
return true;
if ( self bot_odin_try_smoke() )
return true;
if ( self bot_odin_try_flash() )
return true;
return false;
}
bot_odin_try_flash()
{
if ( self bot_odin_should_fire_flash_at_marker() )
{
self notify("marking_action");
return true;
}
return false;
}
bot_odin_should_fire_flash_at_marker()
{
if ( GetTime() < self.odin.odin_markingUseTime )
return false;
outside_enemies = bot_odin_get_visible_outside_players( "enemy", false );
dist_enemy_to_marker_sq = [];
for ( i = 0; i < outside_enemies.size; i++ )
{
enemy_target_point = self bot_odin_get_player_target_point(outside_enemies[i]);
dist_enemy_to_marker_sq[i] = DistanceSquared(self.odin.targeting_marker.origin, enemy_target_point);
if ( dist_enemy_to_marker_sq[i] < squared(level.odin_flash_radius/2) )
return true;
}
return false;
}
bot_odin_try_smoke()
{
if ( self bot_odin_should_drop_smoke_at_marker() )
{
self notify("smoke_action");
return true;
}
return false;
}
bot_odin_should_drop_smoke_at_marker()
{
if ( GetTime() < self.odin.odin_smokeUseTime )
return false;
high_priority_locations = self bot_odin_get_high_priority_smoke_locations();
foreach( location in high_priority_locations )
{
if ( DistanceSquared(location,self.odin.targeting_marker.origin) < 50 * 50 )
return true;
}
// try to drop smoke randomly in an enemy zone
marker_zone = undefined;
if ( IsDefined(self.odin.targeting_marker.nearest_node) )
marker_zone = GetNodeZone(self.odin.targeting_marker.nearest_node);
if ( !IsDefined(marker_zone) )
return false;
zone_enemies = self bot_killstreak_get_zone_enemies_outside( true );
num_enemies_in_zone = zone_enemies[marker_zone].size;
if ( num_enemies_in_zone >= 2 )
return true;
return false;
}
bot_odin_get_high_priority_smoke_locations()
{
desired_locations = [];
if ( GetTime() < self.odin.odin_smokeUseTime )
return desired_locations;
// First, try to drop smoke on a care package to help my team in capturing it
foreach( package in level.carePackages )
{
if ( crate_landed_and_on_path_grid(package) )
{
excluders[0] = self;
players_sorted = get_array_of_closest( package.origin, level.players, excluders );
if ( players_sorted.size > 0 && players_sorted[0].team == self.team )
{
// Closest player to the crate is on my team, so place the smoke there to help him out
desired_locations = array_add(desired_locations, package.origin);
}
}
}
// Second, try to drop smoke on a player on my team who is capturing something (dom flag, etc)
outside_allies = bot_odin_get_visible_outside_players( "ally", false );
foreach( ally in outside_allies )
{
if ( IsAI(ally) && ally bot_is_capturing() )
{
desired_locations = array_add(desired_locations, ally.origin);
}
}
return desired_locations;
}
odin_support_get_target()
{
high_priority_smoke_locs = self bot_odin_get_high_priority_smoke_locations();
if ( high_priority_smoke_locs.size > 0 )
return high_priority_smoke_locs[0];
return bot_odin_get_closest_visible_outside_player( "enemy", true );
}
// ******************************************************************************************
// *************************************** Odin Shared **************************************
// ******************************************************************************************
monitor_odin_marker()
{
while(1)
{
self.odin.targeting_marker.nearest_node = GetClosestNodeInSight( self.odin.targeting_marker.origin );
if ( bot_point_is_on_pathgrid( self.odin.targeting_marker.origin, 200 ) )
self.odin.targeting_marker.nearest_point_on_pathgrid = self.odin.targeting_marker.origin;
else
self.odin.targeting_marker.nearest_point_on_pathgrid = undefined;
wait(0.25);
}
}
bot_control_odin(type)
{
self endon( "spawned_player" );
self endon( "disconnect" );
self endon( "control_func_done" );
level endon( "game_ended" );
// First wait until bot is in the Odin (or he times out)
result = self bot_waittill_using_vehicle( type );
if ( !result )
self notify("control_func_done");
self.vehicle_controlling = self.odin;
self childthread bot_end_control_on_vehicle_death( self.odin );
self.odin endon("death");
wait(1.4); // There's an additional 1.4 second (or so) wait until a human player can see from the Odin's eyes
self BotSetAwareness(0.7); // Harder to see enemies since you're so high up
self thread bot_end_odin_watcher();
self.odin_predicted_loc_for_player = [];
self.odin_predicted_loc_time_for_player = [];
self.odin_last_predict_position_time = 0;
current_random_zone_target = undefined;
next_move_time = 0;
move_loc = undefined;
self childthread monitor_odin_marker();
last_marker_loc = self.odin.targeting_marker.origin;
last_marker_check = GetTime();
while ( self [[level.bot_ks_funcs["isUsing"][type]]]() )
{
performed_action = self [[level.bot_ks_funcs["odin_perform_action"][type]]]();
if ( GetTime() > last_marker_check + 2000 )
{
last_marker_check = GetTime();
marker_moved = Distance(last_marker_loc, self.odin.targeting_marker.origin);
last_marker_loc = self.odin.targeting_marker.origin;
if ( marker_moved < 100 )
{
// stuck, find a new destination
move_loc = undefined;
current_random_zone_target = undefined;
}
}
if ( GetTime() > next_move_time || !IsDefined(move_loc) )
{
time_to_move = level.bot_odin_time_to_move[self BotGetDifficulty()];
next_move_time = GetTime() + time_to_move * 1000;
// Current location isn't good, so decide where to move
desired_target = self [[level.bot_ks_funcs["odin_get_target"][type]]]();
if ( IsDefined(desired_target) )
{
current_random_zone_target = undefined;
if ( IsPlayer(desired_target) )
move_loc = self bot_odin_get_player_target_point(desired_target);
else
move_loc = desired_target;
}
else
{
if ( !IsDefined(current_random_zone_target) )
current_random_zone_target = Random(level.outside_zones);
zone_origin = GetZoneNodeForIndex(current_random_zone_target).origin;
if ( Distance2DSquared( self.odin.targeting_marker.origin, zone_origin ) < 100 * 100 )
{
current_random_zone_target = Random(level.outside_zones);
zone_origin = GetZoneNodeForIndex(current_random_zone_target).origin;
last_marker_check = GetTime();
}
move_loc = zone_origin;
}
//bot_draw_cylinder(move_loc, 10, 10, time_to_move, undefined, (0,1,0), false, 4);
//if ( IsDefined(desired_target) && IsPlayer(desired_target) )
// line( desired_target.origin, move_loc, (0,1,0), 1, false, INT(time_to_move / 0.05) );
}
//line(move_loc, self.odin.targeting_marker.origin, (0,0,1), 1.0, false );
marker_to_move_loc = move_loc - self.odin.targeting_marker.origin;
if ( LengthSquared(marker_to_move_loc) > 100 )
{
marker_to_move_loc_angles = VectorToAngles( marker_to_move_loc );
self BotSetScriptMove( marker_to_move_loc_angles[1], 0.05 );
self BotLookAtPoint( move_loc, 0.1, "script_forced");
}
else
{
last_marker_check = GetTime();
}
wait(0.05);
}
self notify("control_func_done");
}
bot_end_odin_watcher(hideNode)
{
self endon("disconnect");
self waittill("control_func_done");
self.odin_predicted_loc_for_player = undefined;
self.odin_predicted_loc_time_for_player = undefined;
self.odin_last_predict_position_time = undefined;
self BotSetAwareness(1.0);
}
SCR_CONST_PLAYER_PREDICT_UPDATE_TIME = 400;
bot_odin_get_player_target_point( player )
{
if ( level.teamBased && self.team == player.team )
{
// This is an ally, no need to predict future position
return player.origin;
}
else
{
if ( Length(player GetEntityVelocity()) < 25 )
return player.origin;
player_ent_num = player GetEntityNumber();
if ( !IsDefined(self.odin_predicted_loc_time_for_player[player_ent_num]) )
self.odin_predicted_loc_time_for_player[player_ent_num] = 0;
cur_time = GetTime();
time_since_predicted = cur_time - self.odin_predicted_loc_time_for_player[player_ent_num];
if ( time_since_predicted <= SCR_CONST_PLAYER_PREDICT_UPDATE_TIME )
{
// If we can see that the prediction is totally wrong, then ignore it
player_velocity = VectorNormalize(player GetEntityVelocity());
player_to_predicted_loc = VectorNormalize(self.odin_predicted_loc_for_player[player_ent_num] - player.origin);
if ( VectorDot(player_velocity,player_to_predicted_loc) < -0.5 )
return player.origin;
}
if ( time_since_predicted > SCR_CONST_PLAYER_PREDICT_UPDATE_TIME )
{
if ( cur_time == self.odin_last_predict_position_time )
{
// Already predicted one player's position this frame, so don't predict again
// If the stored data on this guy is too old just return his position
if ( time_since_predicted > 1000 )
return player.origin;
}
else
{
self.odin_predicted_loc_for_player[player_ent_num] = GetPredictedEntityPosition( player, 1.5 );
self.odin_predicted_loc_time_for_player[player_ent_num] = cur_time;
self.odin_last_predict_position_time = cur_time;
}
}
return self.odin_predicted_loc_for_player[player_ent_num];
}
}
bot_odin_get_closest_visible_outside_player( type, only_players, player_to_ignore )
{
visible_players = self bot_odin_get_visible_outside_players( type, only_players );
if ( IsDefined(player_to_ignore) )
visible_players = array_remove( visible_players, player_to_ignore );
if ( visible_players.size > 0 )
{
visible_players_sorted = get_array_of_closest( self.odin.targeting_marker.origin, visible_players );
return visible_players_sorted[0];
}
return undefined;
}
bot_odin_try_spawn_juggernaut()
{
if ( GetTime() >= self.odin.odin_juggernautUseTime )
{
if ( !IsDefined(self.odin.targeting_marker.nearest_node) )
return false;
node_for_juggernaut = maps\mp\killstreaks\_odin::getJuggStartingPathNode( self.odin.targeting_marker.origin );
if ( IsDefined(node_for_juggernaut) )
{
self notify("juggernaut_action");
return true;
}
}
return false;
}
bot_odin_find_target_for_airdrop()
{
return bot_odin_get_closest_visible_outside_player( "ally", false);
}
bot_odin_try_airdrop()
{
if ( self bot_odin_should_airdrop_at_marker() )
{
self notify("airdrop_action");
self notify("juggernaut_action"); // Move Juggernaut to guard care package
return true;
}
return false;
}
bot_odin_should_airdrop_at_marker()
{
if ( GetTime() < self.odin.odin_airdropUseTime )
return false;
if ( !IsDefined(self.odin.targeting_marker.nearest_node) )
return false;
if ( bot_odin_get_num_valid_care_packages() > 2 )
{
// Too many care packages already - don't want to overwhelm my team and risk one of them falling into the wrong hands
return false;
}
if ( !IsDefined(self.odin.targeting_marker.nearest_point_on_pathgrid) )
{
// Marker is off the path grid, so don't drop a crate since it would be unreachable
return false;
}
marker_zone = GetNodeZone(self.odin.targeting_marker.nearest_node);
if ( !IsDefined(marker_zone) )
return false;
zone_allies = self bot_killstreak_get_zone_allies_outside( true );
num_allies_in_zone = zone_allies[marker_zone].size;
zone_enemies = self bot_killstreak_get_zone_enemies_outside( true );
num_enemies_in_zone = zone_enemies[marker_zone].size;
if ( num_allies_in_zone == 0 )
{
// Don't use airdrop in a zone with zero allies
return false;
}
if ( num_enemies_in_zone == 0 )
{
// Zone has at least one ally and no enemies, so allow airdrop if there are no close visible enemies (on the border of another zone for example)
enemy_nearby_marker = false;
visible_enemies = self bot_odin_get_visible_outside_players( "enemy", true );
foreach ( enemy in visible_enemies )
{
if ( DistanceSquared(enemy.origin, self.odin.targeting_marker.origin) < 120 * 120 )
enemy_nearby_marker = true;
}
if ( !enemy_nearby_marker )
return true;
}
if ( num_allies_in_zone - num_enemies_in_zone >= 2 )
{
// Zone has allies and enemies in it, so allow airdrop if allies in the current zone outnumber enemies by 2 or more
// But make sure that there's an ally significantly closer than an enemy
allies_sorted = get_array_of_closest( self.odin.targeting_marker.origin, zone_allies[marker_zone] );
enemies_sorted = get_array_of_closest( self.odin.targeting_marker.origin, zone_enemies[marker_zone] );
closest_ally_dist = Distance( self.odin.targeting_marker.origin, allies_sorted[0].origin );
closest_enemy_dist = Distance( self.odin.targeting_marker.origin, enemies_sorted[0].origin );
if ( closest_ally_dist + 120 < closest_enemy_dist )
return true;
}
return false;
}
bot_odin_get_num_valid_care_packages()
{
count = 0;
foreach( package in level.carePackages )
{
if ( IsDefined(package) && crate_landed_and_on_path_grid(package) )
count++;
}
return count;
}
bot_odin_get_visible_outside_players( type, only_players, only_check_fov )
{
outside_players = self bot_killstreak_get_outside_players( self.team, type, only_players );
fov_dot_base = self BotGetFovDot();
visible_outside_players = [];
foreach ( player in outside_players )
{
check_sight = false;
fov_dot = fov_dot_base;
if ( type == "enemy" )
{
if ( maps\mp\killstreaks\_odin::enemyNotAffectedByOdinOutline(player) )
check_sight = true;
else
fov_dot *= 0.9; // Can see enemy outline through the UI blackness on the left/right edges of the screen, so increase the FOV
}
if ( within_fov( self.vehicle_controlling.origin, self GetPlayerAngles(), player.origin, fov_dot ) )
{
if ( !check_sight || self BotCanSeeEntity(player) )
visible_outside_players = array_add( visible_outside_players, player );
}
}
return visible_outside_players;
}
// ******************************************************************************************
// ************************************ Shared Functions ************************************
// ******************************************************************************************
is_indoor_map()
{
return (level.script == "mp_sovereign");
}
bot_body_is_dead()
{
return (IsDefined(self.fauxDead) && self.fauxDead);
}
heli_pick_node_furthest_from_center( nodes, type )
{
farthest_node = undefined;
farthest_node_dist_sq = 0;
foreach( node in nodes )
{
dist_to_center_sq = DistanceSquared( level.bot_map_center, [[level.bot_ks_funcs["heli_node_get_origin"][type]]](node) );
if ( dist_to_center_sq > farthest_node_dist_sq )
{
farthest_node_dist_sq = dist_to_center_sq;
farthest_node = node;
}
}
if ( IsDefined(farthest_node) )
return farthest_node;
else
return Random(nodes);
}
heli_get_node_origin( node )
{
return node.origin;
}
find_closest_heli_node_2D( origin, type )
{
closest_node_2D = undefined;
closest_node_2D_dist = 99999999;
foreach( node in level.bot_heli_nodes )
{
dist_2D_sq = Distance2DSquared( origin, [[level.bot_ks_funcs["heli_node_get_origin"][type]]](node) );
if ( dist_2D_sq < closest_node_2D_dist )
{
closest_node_2D = node;
closest_node_2D_dist = dist_2D_sq;
}
}
return closest_node_2D;
}
bot_killstreak_get_zone_allies_outside( only_players )
{
outside_allies = self bot_killstreak_get_all_outside_allies( only_players );
zone_allies = [];
for ( i=0; i<level.zoneCount; i++ )
zone_allies[i] = [];
foreach ( ally in outside_allies )
{
nearest_node = ally GetNearestNode();
zone = GetNodeZone(nearest_node);
if ( IsDefined(zone) )
zone_allies[zone] = array_add( zone_allies[zone], ally );
}
return zone_allies;
}
bot_killstreak_get_zone_enemies_outside( only_players )
{
outside_enemies = self bot_killstreak_get_all_outside_enemies( only_players );
zone_enemies = [];
for ( i=0; i<level.zoneCount; i++ )
zone_enemies[i] = [];
foreach ( enemy in outside_enemies )
{
nearest_node = enemy GetNearestNode();
zone = GetNodeZone(nearest_node);
zone_enemies[zone] = array_add( zone_enemies[zone], enemy );
}
return zone_enemies;
}
bot_killstreak_get_all_outside_enemies( only_players )
{
return self bot_killstreak_get_outside_players( self.team, "enemy", only_players );
}
bot_killstreak_get_all_outside_allies( only_players )
{
return self bot_killstreak_get_outside_players( self.team, "ally", only_players );
}
bot_killstreak_get_outside_players( team, type, only_players )
{
outside_players = [];
all_players = level.participants;
if ( IsDefined(only_players) && only_players )
all_players = level.players;
foreach( player in all_players )
{
if ( player == self || !IsAlive(player) )
continue;
// should really have the same check as AI_IsAlliedSentient in code
is_valid_player = false;
if ( type == "ally" )
is_valid_player = level.teamBased && ( team == player.team );
else if ( type == "enemy" )
is_valid_player = !level.teamBased || ( team != player.team );
if ( is_valid_player )
{
nearest_node_player = player GetNearestNode();
if ( IsDefined( nearest_node_player ) && NodeExposedToSky( nearest_node_player ) )
outside_players = array_add(outside_players, player);
}
}
outside_players = array_remove(outside_players,self);
return outside_players;
}
bot_heli_find_unvisited_nodes(current_node)
{
lowest_visted_num = 99;
best_nodes = [];
foreach( node in current_node.neighbors )
{
// node.neighbors contains any nodes that this node links to, or that link to it. So it may contain start nodes that we need to ignore
if ( IsDefined(node.script_linkname) )
{
times_visited_this_node = node.bot_visited_times[self.entity_number];
if ( times_visited_this_node < lowest_visted_num )
{
best_nodes = [];
best_nodes[0] = node;
lowest_visted_num = times_visited_this_node;
}
else if ( times_visited_this_node == lowest_visted_num )
{
best_nodes[best_nodes.size] = node;
}
}
}
return best_nodes;
}
bot_control_heli( type )
{
self endon( "spawned_player" );
self endon( "disconnect" );
self endon( "control_func_done" );
level endon( "game_ended" );
// First wait until bot is in the heli (or he times out)
result = self bot_waittill_using_vehicle( type );
if ( !result )
self notify("control_func_done");
foreach ( bird in level.littleBirds )
{
if ( bird.owner == self )
self.vehicle_controlling = bird;
}
Assert(IsDefined(self.vehicle_controlling));
self childthread bot_end_control_on_vehicle_death( self.vehicle_controlling );
self.vehicle_controlling endon("death");
if ( IsDefined(level.bot_ks_funcs["control_other"][type]) )
self childthread [[level.bot_ks_funcs["control_other"][type]]]();
// Next wait until the heli reaches its target
self [[level.bot_ks_funcs["waittill_initial_goal"][type]]]();
self childthread [[level.bot_ks_funcs["control_aiming"][type]]]();
self bot_control_heli_main_move_loop( type, true );
self notify("control_func_done");
}
SCR_CONST_BOT_HELI_ON_MESH_GOAL_DIST = 100; // Vehicle that rides on the mesh will consider itself "at goal" within this distance
SCR_CONST_BOT_HELI_OFF_MESH_GOAL_DIST = 30; // Vehicle that doesn't ride on the mesh will consider itself "at goal" within this distance
SCR_CONST_BOT_HELI_SLOWDOWN_RANGE = 3; // At this scalar of GOAL_DIST, the vehicle will start to slow down
bot_get_heli_goal_dist_sq( rides_on_mesh )
{
if ( rides_on_mesh )
return squared(SCR_CONST_BOT_HELI_ON_MESH_GOAL_DIST);
else
return squared(SCR_CONST_BOT_HELI_OFF_MESH_GOAL_DIST);
}
bot_get_heli_slowdown_dist_sq( rides_on_mesh )
{
if ( rides_on_mesh )
return squared(SCR_CONST_BOT_HELI_ON_MESH_GOAL_DIST * SCR_CONST_BOT_HELI_SLOWDOWN_RANGE);
else
return squared(SCR_CONST_BOT_HELI_OFF_MESH_GOAL_DIST * SCR_CONST_BOT_HELI_SLOWDOWN_RANGE);
}
SCR_CONST_BOT_VANGUARD_STUCK_TIME = 3.0;
bot_control_heli_main_move_loop( type, rides_on_mesh )
{
foreach (node in level.bot_heli_nodes)
node.bot_visited_times[self.entity_number] = 0;
current_node = find_closest_heli_node_2D( self.vehicle_controlling.origin, type );
current_target = undefined;
self.next_goal_time = 0;
state = "needs_new_goal";
dist_to_goal_sq = undefined;
last_vehicle_origin = self.vehicle_controlling.origin;
time_till_next_stuck_check = SCR_CONST_BOT_VANGUARD_STUCK_TIME;
wait_time = 0.05;
while ( self [[level.bot_ks_funcs["isUsing"][type]]]() )
{
if ( GetTime() > self.next_goal_time && state == "needs_new_goal" )
{
// Pick a new node that is linked to the current one
prev_node = current_node;
current_node = [[level.bot_ks_funcs["heli_pick_node"][type]]](current_node);
current_target = undefined;
if ( IsDefined(current_node) )
{
current_node_origin = [[level.bot_ks_funcs["heli_node_get_origin"][type]]](current_node);
if ( rides_on_mesh )
{
// Find the "ground" position under the node (i.e. the position of the vehicle clip)
traceStart = ( current_node.origin ) + ( getHeliPilotMeshOffset() + level.bot_heli_pilot_traceOffset );
traceEnd = ( current_node.origin ) + ( getHeliPilotMeshOffset() - level.bot_heli_pilot_traceOffset );
traceResult = BulletTrace( traceStart, traceEnd, false, undefined, false, false, true );
AssertEx( IsDefined( traceResult["entity"] ), "Unable to find heli mesh above or below heli_node at " + current_node_origin );
current_target = ( traceResult[ "position" ] - getHeliPilotMeshOffset() ) + level.bot_ks_heli_offset[type];
}
else
{
current_target = current_node_origin;
}
}
if ( IsDefined(current_target) )
{
self BotSetFlag("disable_movement",false);
state = "waiting_till_goal";
time_till_next_stuck_check = SCR_CONST_BOT_VANGUARD_STUCK_TIME;
last_vehicle_origin = self.vehicle_controlling.origin;
}
else
{
current_node = prev_node;
self.next_goal_time = GetTime() + 2000;
}
}
else if ( state == "waiting_till_goal" )
{
if ( !rides_on_mesh )
{
// Need to manually adjust height
height_difference = current_target[2] - self.vehicle_controlling.origin[2];
if ( height_difference > 10 )
self BotPressButton("lethal");
else if ( height_difference < -10 )
self BotPressButton("tactical");
}
heli_to_current_target = (current_target - self.vehicle_controlling.origin);
if ( rides_on_mesh )
dist_to_goal_sq = Length2DSquared(heli_to_current_target);
else
dist_to_goal_sq = LengthSquared(heli_to_current_target);
if ( dist_to_goal_sq < bot_get_heli_goal_dist_sq( rides_on_mesh ) )
{
self BotSetScriptMove( 0, 0 ); // clear the script move
self BotSetFlag("disable_movement",true);
if ( self BotGetDifficulty() == "recruit" )
self.next_goal_time = GetTime() + RandomIntRange(5000,7000);
else
self.next_goal_time = GetTime() + RandomIntRange(3000,5000);
state = "needs_new_goal";
}
else
{
heli_to_current_target = (current_target - self.vehicle_controlling.origin);
heli_to_current_target_angles = VectorToAngles( heli_to_current_target );
stick_speed = ter_op( dist_to_goal_sq < bot_get_heli_slowdown_dist_sq( rides_on_mesh ), 0.5, 1.0 );
self BotSetScriptMove( heli_to_current_target_angles[1], wait_time, stick_speed );
time_till_next_stuck_check -= wait_time;
if ( time_till_next_stuck_check <= 0.0 )
{
if ( DistanceSquared(self.vehicle_controlling.origin, last_vehicle_origin) < 15*15 )
{
// Stuck, so mark the node as "visited" and try a new destination
current_node.bot_visited_times[self.entity_number]++;
state = "needs_new_goal";
}
last_vehicle_origin = self.vehicle_controlling.origin;
time_till_next_stuck_check = SCR_CONST_BOT_VANGUARD_STUCK_TIME;
}
}
}
/#
if ( GetDvarInt("ai_showpaths") == 1 )
{
if ( IsDefined(current_target) )
Line( self.vehicle_controlling.origin, current_target, (0,0,1), 1.0, false );
}
#/
wait(wait_time);
}
}
get_random_outside_target()
{
possible_zones = [];
foreach ( z in level.outside_zones )
{
enemies_in_zone = BotZoneGetCount( z, self.team, "enemy_predict" );
if ( enemies_in_zone > 0 )
possible_zones = array_add(possible_zones,z);
}
target_loc = undefined;
if ( possible_zones.size > 0 )
{
random_zone = Random(possible_zones);
random_node = Random(GetZoneNodes( random_zone ));
target_loc = random_node.origin;
}
else
{
if ( IsDefined(level.teleportGetActiveNodesFunc) )
all_nodes = [[level.teleportGetActiveNodesFunc]]();
else
all_nodes = GetAllNodes();
num_picked = 0;
while( num_picked < 10 )
{
num_picked++;
node_picked = all_nodes[RandomInt(all_nodes.size)];
target_loc = node_picked.origin;
if ( NodeExposedToSky(node_picked) && Distance2DSquared(node_picked.origin, self.vehicle_controlling.origin) > 250*250 )
break;
}
}
return target_loc;
}