843 lines
25 KiB
Plaintext
843 lines
25 KiB
Plaintext
#include maps\mp\_utility;
|
|
#include maps\mp\gametypes\_hud_util;
|
|
#include common_scripts\utility;
|
|
#include maps\mp\_aerial_pathnodes;
|
|
|
|
SCR_CONST_PATH_TIMEOUT_TIME_AIR = 7.5;
|
|
SCR_CONST_REACHED_NODE_RADIUS = 24.0;
|
|
SCR_CONST_ENEMY_MOVED_FAR_FROM_GOAL_DIST = 128.0;
|
|
SCR_CONST_WEAPON_RADAR_PING_TIME = 3.0;
|
|
SCR_CONST_ENEMY_TOO_CLOSE_FOR_ROCKETS_DIST_SQ = 132 * 132;
|
|
SCR_CONST_ENEMY_TARGET_OFFSET = (0,0,40);
|
|
|
|
/#
|
|
SCR_CONST_ALWAYS_SHOW_VEHICLE_PATH_DEBUG = false;
|
|
SCR_CONST_SHOW_AI_TARGET = false;
|
|
SCR_CONST_ONLY_ASCEND_NO_ADDITIONAL_MOVEMENT = false;
|
|
#/
|
|
|
|
/*
|
|
TODO
|
|
|
|
Rockets sometimes blow up in face
|
|
*/
|
|
|
|
assault_vehicle_ai_init()
|
|
{
|
|
thread maps\mp\_aerial_pathnodes::calculate_aerial_pathnodes();
|
|
}
|
|
|
|
AIStartUsingAssaultVehicle( AssaultVehicle )
|
|
{
|
|
self thread assault_vehicle_ai_end_on_owner_disconnect( AssaultVehicle );
|
|
|
|
wait(2.0); // give time for the vehicle to appear
|
|
|
|
waittill_aerial_pathnodes_calculated();
|
|
|
|
if ( IsDefined(AssaultVehicle) )
|
|
{
|
|
AssaultVehicle.enemy_target_last_vis_time = 0;
|
|
AssaultVehicle.enemy_target_visible = false;
|
|
|
|
AssaultVehicle thread assault_vehicle_ai_aerial_movement();
|
|
|
|
AssaultVehicle thread assault_vehicle_ai_threat();
|
|
AssaultVehicle thread assault_vehicle_ai_weapons( !AssaultVehicle.hasTurret );
|
|
|
|
if( IsDefined( level.isHorde ) && level.isHorde )
|
|
{
|
|
self.aerialAssaultDrone = AssaultVehicle;
|
|
AssaultVehicle.isAerialAssaultDrone = true;
|
|
AssaultVehicle thread assault_vehicle_horde_monitor_death( self );
|
|
}
|
|
}
|
|
}
|
|
|
|
assault_vehicle_horde_monitor_death( player ) // self == aerial assault drone
|
|
{
|
|
self waittill( "death" );
|
|
self.aerialAssaultDrone = undefined;
|
|
}
|
|
|
|
assault_vehicle_ai_end_on_owner_disconnect( AssaultVehicle ) // self == owner who called in the AI vehicle
|
|
{
|
|
AssaultVehicle endon("death");
|
|
|
|
self waittill("disconnect");
|
|
AssaultVehicle notify("death");
|
|
}
|
|
|
|
assault_vehicle_ai_aerial_movement() // self == Assault Vehicle
|
|
{
|
|
self notify("assault_vehicle_ai_aerial_movement");
|
|
self endon("assault_vehicle_ai_aerial_movement");
|
|
self endon("death");
|
|
|
|
speed = self Vehicle_GetTopSpeed();
|
|
self Vehicle_SetSpeed(speed,8,60);
|
|
self SetHoverParams(0,0,0);
|
|
|
|
// First try to get the vehicle outside
|
|
|
|
// Find the closest outside node and calculate a path to it
|
|
path = undefined;
|
|
nearest_node_to_vehicle = assault_vehicle_ai_get_nearest_node();
|
|
if ( !IsDefined(nearest_node_to_vehicle) )
|
|
{
|
|
// Couldn't find a nearest node to the vehicle, so movement will be impossible. Assert and return.
|
|
AssertMsg("AI Aerial Assault Vehicle unable to find nearby node near pos " + self.origin );
|
|
return;
|
|
}
|
|
|
|
valid_close_aerial_node = nearest_node_to_vehicle;
|
|
if ( !node_is_aerial(valid_close_aerial_node) )
|
|
{
|
|
node = get_ent_closest_aerial_node(64, 0);
|
|
if ( IsDefined(node) )
|
|
valid_close_aerial_node = node;
|
|
}
|
|
|
|
if ( node_is_aerial(valid_close_aerial_node) )
|
|
path = GetNodesOnPath( self.origin, valid_close_aerial_node.origin, true, nearest_node_to_vehicle );
|
|
|
|
if ( !IsDefined(path) )
|
|
{
|
|
max_dist_search = 1500;
|
|
min_dist_search = 0;
|
|
|
|
while(!IsDefined(path) && min_dist_search < max_dist_search)
|
|
{
|
|
nearest_node = get_ent_closest_aerial_node(max_dist_search, min_dist_search);
|
|
if ( IsDefined(nearest_node) )
|
|
{
|
|
path = GetNodesOnPath( self.origin, nearest_node.origin, true, nearest_node_to_vehicle );
|
|
if ( !IsDefined(path) )
|
|
min_dist_search = Distance(self.origin,nearest_node.origin) + 1;
|
|
}
|
|
else
|
|
{
|
|
min_dist_search = max_dist_search + 1;
|
|
}
|
|
}
|
|
|
|
if ( min_dist_search > max_dist_search )
|
|
{
|
|
// Couldn't find any outside nodes (or couldn't find a path to them), so movement will be impossible. Assert and return.
|
|
AssertMsg("AI Aerial Assault Vehicle unable to find any nearby aerial nodes near pos " + self.origin );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Now follow the path to the closest outside node
|
|
end_node = assault_vehicle_ai_aerial_follow_path_outside(path);
|
|
|
|
// Then path up to the aerial version of this pathnode
|
|
assault_vehicle_ai_move_to_aerial_node(end_node);
|
|
wait(0.85); // Settle at the node before continuing
|
|
|
|
should_aerial_move = true;
|
|
/#
|
|
if ( SCR_CONST_ONLY_ASCEND_NO_ADDITIONAL_MOVEMENT )
|
|
should_aerial_move = false;
|
|
#/
|
|
if ( should_aerial_move )
|
|
{
|
|
self notify("in_air");
|
|
self assault_vehicle_ai_aerial_pathing_turret(end_node);
|
|
}
|
|
}
|
|
|
|
assault_vehicle_ai_aerial_follow_path_outside( path )
|
|
{
|
|
node_offset = (0,0,40);
|
|
|
|
for( i=0; i<path.size; i++ )
|
|
{
|
|
next_node = path[i];
|
|
self assault_vehicle_ai_air_movement_func(next_node.origin + node_offset);
|
|
drive_time = 0;
|
|
|
|
while( Distance2DSquared( next_node.origin, self.origin ) > squared(SCR_CONST_REACHED_NODE_RADIUS) )
|
|
{
|
|
/#
|
|
self assault_vehicle_ai_draw_debug_path(path,i,node_offset);
|
|
#/
|
|
drive_time += 0.05;
|
|
if ( drive_time > self assault_vehicle_ai_path_timeout_time() )
|
|
return;
|
|
|
|
wait(0.05);
|
|
}
|
|
|
|
// Vehicle is really close to next_node. If it is aerial, just bail so we can ascend
|
|
if ( node_is_aerial( next_node ) )
|
|
return next_node;
|
|
}
|
|
|
|
return path[path.size-1];
|
|
}
|
|
|
|
assault_vehicle_ai_move_to_aerial_node( node )
|
|
{
|
|
Assert(node_is_aerial(node));
|
|
// We're assuming here that the vehicle is currently at a node that can reach this node by moving directly toward it
|
|
|
|
current_node_origin = node.origin + get_aerial_offset();
|
|
self assault_vehicle_ai_air_movement_func( current_node_origin );
|
|
while ( DistanceSquared( self.origin, current_node_origin ) > SCR_CONST_REACHED_NODE_RADIUS * SCR_CONST_REACHED_NODE_RADIUS )
|
|
{
|
|
/#
|
|
if ( GetDvarInt("ai_showpaths") == 1 || SCR_CONST_ALWAYS_SHOW_VEHICLE_PATH_DEBUG )
|
|
line(self.origin, current_node_origin, (0,0,1), 1.0, false, 4);
|
|
#/
|
|
wait(0.05);
|
|
}
|
|
}
|
|
|
|
assault_vehicle_ai_aerial_pathing_turret( start_node )
|
|
{
|
|
Assert(node_is_aerial(start_node));
|
|
// We're assuming here that the vehicle is already at the start_node and needs to pick a new node right away
|
|
|
|
current_aerial_node = start_node;
|
|
visited_nodes = [];
|
|
visited_nodes[current_aerial_node GetNodeNumber()] = 1;
|
|
|
|
while(1)
|
|
{
|
|
// Pick a new node to visit
|
|
current_aerial_node = assault_vehicle_ai_pick_aerial_node( current_aerial_node, visited_nodes );
|
|
|
|
// Move to the current node
|
|
assault_vehicle_ai_move_to_aerial_node(current_aerial_node);
|
|
|
|
// Mark the current node as visited
|
|
current_node_number = current_aerial_node GetNodeNumber();
|
|
if ( !IsDefined(visited_nodes[current_node_number]) )
|
|
visited_nodes[current_node_number] = 0;
|
|
visited_nodes[current_node_number]++;
|
|
|
|
// Wait some amount of time at this node
|
|
wait(RandomFloatRange(0.05,2.0));
|
|
}
|
|
}
|
|
|
|
assault_vehicle_ai_aerial_pathing_c4()
|
|
{
|
|
while(1)
|
|
{
|
|
node_goal = undefined;
|
|
if ( self assault_vehicle_ai_enemy_exists_and_is_alive() )
|
|
node_goal = self.enemy_target get_ent_closest_aerial_node();
|
|
|
|
if ( !IsDefined(node_goal) )
|
|
node_goal = Random(level.aerial_pathnodes);
|
|
|
|
start_node = self get_ent_closest_aerial_node();
|
|
path = find_path_between_aerial_nodes( start_node, node_goal );
|
|
if ( IsDefined(path) )
|
|
assault_vehicle_ai_follow_path(path, ::assault_vehicle_ai_air_movement_func, ::assault_vehicle_ai_enemy_moved_air, get_aerial_offset()[2]);
|
|
|
|
if ( self assault_vehicle_ai_enemy_exists_and_is_alive() )
|
|
{
|
|
if ( !self assault_vehicle_ai_enemy_moved_air(node_goal) || Distance2DSquared(self.origin,self.enemy_target.origin) < squared(200) )
|
|
{
|
|
// Descend to enemy and proceed on the ground
|
|
ground_target_origin = self.enemy_target.origin + SCR_CONST_ENEMY_TARGET_OFFSET;
|
|
self SetVehGoalPos( ground_target_origin, true );
|
|
while( self assault_vehicle_ai_enemy_exists_and_is_alive() && DistanceSquared( ground_target_origin, self.origin ) > squared(SCR_CONST_REACHED_NODE_RADIUS) )
|
|
wait(0.05);
|
|
wait(0.8);
|
|
|
|
self assault_vehicle_ai_ground_movement( ::assault_vehicle_ai_air_movement_func, ::assault_vehicle_ai_enemy_moved_ground );
|
|
}
|
|
}
|
|
|
|
wait(0.05);
|
|
}
|
|
}
|
|
|
|
assault_vehicle_ai_pick_aerial_node( current_node, visited_nodes )
|
|
{
|
|
best_node = undefined;
|
|
best_node_visits = 9999;
|
|
neighbors_randomized = array_randomize(current_node.aerial_neighbors);
|
|
foreach( neighbor_node in neighbors_randomized )
|
|
{
|
|
neighbor_node_num = neighbor_node GetNodeNumber();
|
|
neighbor_node_visits = visited_nodes[neighbor_node_num];
|
|
if ( !IsDefined(neighbor_node_visits) )
|
|
return neighbor_node; // Node has never been visited, just return it
|
|
|
|
if ( neighbor_node_visits < best_node_visits )
|
|
{
|
|
best_node = neighbor_node;
|
|
best_node_visits = neighbor_node_visits;
|
|
}
|
|
}
|
|
|
|
Assert(IsDefined(best_node)); // There should always be a node returned from this function
|
|
return best_node;
|
|
}
|
|
|
|
assault_vehicle_ai_get_nearest_node()
|
|
{
|
|
nearest_node_to_vehicle = GetClosestNodeInSight( self.origin, true );
|
|
if ( !IsDefined(nearest_node_to_vehicle) )
|
|
{
|
|
nodes_near_vehicle_sorted = GetNodesInRadiusSorted(self.origin,1000,0);
|
|
if ( nodes_near_vehicle_sorted.size > 0 )
|
|
nearest_node_to_vehicle = nodes_near_vehicle_sorted[0];
|
|
}
|
|
|
|
return nearest_node_to_vehicle;
|
|
}
|
|
|
|
assault_vehicle_ai_ground_movement( movement_func, enemy_moved_func ) // self == Assault Vehicle
|
|
{
|
|
self endon("death");
|
|
|
|
// Test to see if there is a nearest node
|
|
nearest_node_to_vehicle = assault_vehicle_ai_get_nearest_node();
|
|
if ( !IsDefined(nearest_node_to_vehicle) )
|
|
{
|
|
// Couldn't find a nearest node to the vehicle, so movement will be impossible. Assert and return.
|
|
AssertMsg("AI Ground Assault Vehicle unable to find nearby node near pos " + self.origin );
|
|
return;
|
|
}
|
|
|
|
while(1)
|
|
{
|
|
self childthread assault_vehicle_ai_ground_movement_loop( movement_func, enemy_moved_func );
|
|
self waittill_any("enemy");
|
|
}
|
|
}
|
|
|
|
assault_vehicle_ai_ground_movement_loop( movement_func, enemy_moved_func ) // self == Assault Vehicle
|
|
{
|
|
self notify("assault_vehicle_ai_ground_movement_loop");
|
|
self endon("assault_vehicle_ai_ground_movement_loop");
|
|
|
|
unreachable_nodes = [];
|
|
|
|
while(1)
|
|
{
|
|
randomNode = undefined;
|
|
goal_pos = undefined;
|
|
if ( self assault_vehicle_ai_enemy_exists_and_is_alive() )
|
|
{
|
|
goal_pos = self.enemy_target.origin;
|
|
}
|
|
else
|
|
{
|
|
count = 0;
|
|
while( !IsDefined(randomNode) && count < 20 )
|
|
{
|
|
randomNode = GetRandomNodeDestination( self.origin, self.angles );
|
|
if ( IsDefined(randomNode) )
|
|
{
|
|
if ( array_contains(unreachable_nodes,randomNode) )
|
|
randomNode = undefined; // Node was already marked as unreachable, so ignore it
|
|
else
|
|
goal_pos = randomNode.origin;
|
|
}
|
|
|
|
count++;
|
|
wait(0.05);
|
|
}
|
|
}
|
|
|
|
if ( IsDefined( goal_pos ) )
|
|
{
|
|
start_node = assault_vehicle_ai_get_nearest_node();
|
|
if ( !IsDefined(start_node) )
|
|
{
|
|
// Couldn't find a nearest node to the vehicle, so movement will be impossible. Assert and return.
|
|
AssertMsg("AI Ground Assault Vehicle unable to find nearby node near pos " + self.origin );
|
|
return;
|
|
}
|
|
|
|
path = GetNodesOnPath( self.origin, goal_pos, false, start_node );
|
|
if ( IsDefined(path) )
|
|
assault_vehicle_ai_follow_path(path, movement_func, enemy_moved_func );
|
|
else
|
|
unreachable_nodes[unreachable_nodes.size] = randomNode;
|
|
}
|
|
|
|
wait(0.05);
|
|
}
|
|
}
|
|
|
|
assault_vehicle_ai_get_camera_position() // self == Assault Vehicle
|
|
{
|
|
camera_offset = self VehicleGet3PCameraOffset();
|
|
camera_position = self.origin + RotateVector( camera_offset, self.angles );
|
|
return camera_position;
|
|
}
|
|
|
|
assault_vehicle_ai_threat() // self == Assault Vehicle
|
|
{
|
|
self endon("death");
|
|
/#
|
|
self childthread assault_vehicle_ai_debug_threat_lines();
|
|
#/
|
|
while(1)
|
|
{
|
|
known_enemies = [];
|
|
wait_this_frame = false;
|
|
|
|
if ( IsDefined(self.enemy_target) && !IsAlive(self.enemy_target) )
|
|
{
|
|
last_inflictor = self.enemy_target.lastInflictor;
|
|
if ( IsDefined(last_inflictor) )
|
|
{
|
|
if ( last_inflictor == self || ( IsDefined(last_inflictor.tank) && last_inflictor.tank == self ) )
|
|
{
|
|
self.fire_at_dead_time = GetTime() + 1000;
|
|
wait(1.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach( character in level.characters )
|
|
{
|
|
if ( IsAlive(character) && !IsAlliedSentient(self,character) && self.owner != character )
|
|
{
|
|
if ( character _hasPerk( "specialty_blindeye" ) )
|
|
continue;
|
|
|
|
is_position_known = false;
|
|
|
|
trace_start = self assault_vehicle_ai_get_camera_position();
|
|
trace_end = character.origin + SCR_CONST_ENEMY_TARGET_OFFSET;
|
|
|
|
if ( self.hasARHud )
|
|
is_position_known = true;
|
|
if ( IsDefined(character.lastshotfiredtime) && GetTime() - character.lastshotfiredtime < SCR_CONST_WEAPON_RADAR_PING_TIME )
|
|
is_position_known = true;
|
|
else if ( GetTeamRadarStrength(self.team) > GetUAVStrengthLevelNeutral() )
|
|
is_position_known = true;
|
|
else if ( SightTracePassed( trace_start, trace_end, false, self, character ) )
|
|
is_position_known = true;
|
|
|
|
if ( is_position_known && self.hasTurret )
|
|
{
|
|
// Don't target an enemy directly out of my turret's firing angle
|
|
pitch_max = self VehicleGet3PPitchClamp();
|
|
|
|
vec_to_target = trace_end - trace_start;
|
|
angles_to_target = VectorToAngles(vec_to_target);
|
|
pitch = AngleClamp180(angles_to_target[0]);
|
|
|
|
if ( pitch > pitch_max || pitch < -1*pitch_max )
|
|
is_position_known = false;
|
|
}
|
|
|
|
if ( is_position_known )
|
|
known_enemies[known_enemies.size] = character;
|
|
|
|
if ( wait_this_frame )
|
|
wait(0.05);
|
|
wait_this_frame = !wait_this_frame;
|
|
}
|
|
}
|
|
|
|
// Add enemy drones and sentries to the list
|
|
if( IsDefined( level.isHorde ) && level.isHorde )
|
|
{
|
|
// Add enemy drones
|
|
foreach( drone in level.flying_attack_drones )
|
|
{
|
|
is_position_known = false;
|
|
|
|
trace_start = self assault_vehicle_ai_get_camera_position();
|
|
trace_end = (0,0,0);
|
|
if( IsDefined( drone.origin ) )
|
|
{
|
|
trace_end = drone.origin;
|
|
if ( SightTracePassed( trace_start, trace_end, false, self, drone ) )
|
|
is_position_known = true;
|
|
}
|
|
|
|
if ( is_position_known )
|
|
{
|
|
// Don't target an enemy directly out of my turret's firing angle
|
|
pitch_max = self VehicleGet3PPitchClamp();
|
|
|
|
vec_to_target = trace_end - trace_start;
|
|
angles_to_target = VectorToAngles(vec_to_target);
|
|
pitch = AngleClamp180(angles_to_target[0]);
|
|
|
|
if ( pitch > pitch_max || pitch < -1*pitch_max )
|
|
is_position_known = false;
|
|
}
|
|
|
|
if ( is_position_known )
|
|
{
|
|
known_enemies[known_enemies.size] = drone;
|
|
}
|
|
|
|
// Wait so that we don't overthink who our closest target is in any given frame
|
|
if ( wait_this_frame )
|
|
waitframe;
|
|
wait_this_frame = !wait_this_frame;
|
|
}
|
|
|
|
// Add enemy sentries
|
|
foreach( sentry in level.hordeSentryArray )
|
|
{
|
|
is_position_known = false;
|
|
|
|
trace_start = self assault_vehicle_ai_get_camera_position();
|
|
trace_end = (0,0,0);
|
|
|
|
if( IsDefined( sentry.origin ) )
|
|
{
|
|
trace_end = sentry.origin + SCR_CONST_ENEMY_TARGET_OFFSET;
|
|
if ( SightTracePassed( trace_start, trace_end, false, self, sentry ) )
|
|
is_position_known = true;
|
|
}
|
|
|
|
if ( is_position_known )
|
|
{
|
|
// Don't target an enemy directly out of my turret's firing angle
|
|
pitch_max = self VehicleGet3PPitchClamp();
|
|
|
|
vec_to_target = trace_end - trace_start;
|
|
angles_to_target = VectorToAngles(vec_to_target);
|
|
pitch = AngleClamp180(angles_to_target[0]);
|
|
|
|
if ( pitch > pitch_max || pitch < -1*pitch_max )
|
|
is_position_known = false;
|
|
}
|
|
|
|
if ( is_position_known )
|
|
{
|
|
known_enemies[known_enemies.size] = sentry;
|
|
}
|
|
|
|
// Wait so that we don't overthink who our closest target is in any given frame
|
|
if ( wait_this_frame )
|
|
waitframe;
|
|
wait_this_frame = !wait_this_frame;
|
|
}
|
|
|
|
// Remove dead entities (enemies that have been killed so no longer exist)
|
|
removeTheseGuys = [];
|
|
|
|
foreach( guy in known_enemies )
|
|
{
|
|
if( !IsDefined( guy ) )
|
|
{
|
|
removeTheseGuys[ removeTheseGuys.size ] = guy;
|
|
}
|
|
else if( ( IsDefined( guy.ishordedrone ) && guy.ishordedrone ) )
|
|
{
|
|
if( !IsDefined( guy.damageTaken ) || !IsDefined( guy.maxHealth ) || ( guy.damageTaken > guy.maxHealth ) )
|
|
{
|
|
removeTheseGuys[ removeTheseGuys.size ] = guy;
|
|
}
|
|
}
|
|
else if( IsDefined( guy.ishordeenemysentry ) && guy.ishordeenemysentry )
|
|
{
|
|
if( ! guy.isAlive )
|
|
{
|
|
removeTheseGuys[ removeTheseGuys.size ] = guy;
|
|
}
|
|
}
|
|
}
|
|
foreach( guy in removeTheseGuys )
|
|
{
|
|
known_enemies = array_remove( known_enemies, guy );
|
|
}
|
|
}
|
|
|
|
// Choose the closest living enemy
|
|
if ( known_enemies.size > 0 )
|
|
{
|
|
known_enemies_sorted = get_array_of_closest( self.origin, known_enemies );
|
|
old_enemy = self.enemy_target;
|
|
self.enemy_target = known_enemies_sorted[0];
|
|
|
|
if ( !IsDefined(old_enemy) || old_enemy != self.enemy_target )
|
|
self notify("enemy");
|
|
}
|
|
else if ( IsDefined(self.enemy_target) )
|
|
{
|
|
// Current enemy is not visible, so clear it out but don't change the path
|
|
self.enemy_target = undefined;
|
|
}
|
|
|
|
wait(0.05);
|
|
}
|
|
}
|
|
|
|
/#
|
|
assault_vehicle_ai_debug_threat_lines()
|
|
{
|
|
while(1)
|
|
{
|
|
if ( SCR_CONST_SHOW_AI_TARGET )
|
|
{
|
|
if ( self assault_vehicle_ai_enemy_exists_and_is_alive() )
|
|
{
|
|
debug_line_color = (0,1,0);
|
|
if ( self assault_vehicle_ai_can_see_living_enemy() )
|
|
debug_line_color = (1,0,0);
|
|
|
|
if( IsDefined( level.isHorde ) && level.isHorde )
|
|
{
|
|
if( ( IsDefined( self.enemy_target.ishordedrone ) && self.enemy_target.ishordedrone ) )
|
|
line(self.origin,self.enemy_target.origin,debug_line_color,1.0,false);
|
|
else
|
|
line(self.origin,self.enemy_target.origin + SCR_CONST_ENEMY_TARGET_OFFSET,debug_line_color,1.0,false);
|
|
}
|
|
else
|
|
line(self.origin,self.enemy_target.origin + SCR_CONST_ENEMY_TARGET_OFFSET,debug_line_color,1.0,false);
|
|
}
|
|
|
|
if ( IsDefined(self.TargetEnt) )
|
|
line(self.origin,self.TargetEnt.origin,(0,0,1),1.0,false);
|
|
}
|
|
|
|
wait(0.05);
|
|
}
|
|
}
|
|
#/
|
|
|
|
assault_vehicle_ai_weapons( wait_till_in_air ) // self == Assault Vehicle
|
|
{
|
|
self endon("death");
|
|
|
|
if ( wait_till_in_air )
|
|
self waittill("in_air");
|
|
|
|
self.last_rocket_time = 0;
|
|
self.initial_enemy_target = true;
|
|
c4_radius_sq = squared(maps\mp\killstreaks\_drone_assault::GetAssaultVehicleC4Radius() * 0.75);
|
|
|
|
while(1)
|
|
{
|
|
if ( IsDefined(self.TargetEnt) )
|
|
{
|
|
if ( self assault_vehicle_ai_enemy_exists_and_is_alive() )
|
|
{
|
|
if ( self assault_vehicle_ai_can_see_living_enemy() )
|
|
{
|
|
if( IsDefined( level.isHorde ) && level.isHorde )
|
|
{
|
|
if( ( IsDefined( self.enemy_target.ishordedrone ) && self.enemy_target.ishordedrone ) )
|
|
self.TargetEnt.origin = self.enemy_target.origin;
|
|
else
|
|
self.TargetEnt.origin = self.enemy_target.origin + SCR_CONST_ENEMY_TARGET_OFFSET;
|
|
}
|
|
else
|
|
{
|
|
if ( self.hasTurret )
|
|
self.TargetEnt.origin = self.enemy_target.origin + SCR_CONST_ENEMY_TARGET_OFFSET;
|
|
else
|
|
self.TargetEnt.origin = self.enemy_target.origin + (AnglesToForward(self.enemy_target.angles) * 100);
|
|
}
|
|
|
|
desired_angles = VectorToAngles(self.TargetEnt.origin - self.origin);
|
|
yaw_change = desired_angles[1] - self.angles[1];
|
|
while(yaw_change > 180)
|
|
yaw_change -= 360;
|
|
while(yaw_change < -180)
|
|
yaw_change += 360;
|
|
|
|
yaw_change_per_frame = 10;
|
|
if ( abs(yaw_change) < yaw_change_per_frame )
|
|
new_yaw = desired_angles[1];
|
|
else
|
|
new_yaw = self.angles[1] + yaw_change_per_frame * (yaw_change/abs(yaw_change));
|
|
self Vehicle_Teleport( self.origin, (desired_angles[0], new_yaw, self.angles[2]), true, true );
|
|
|
|
if ( self.initial_enemy_target )
|
|
{
|
|
// First time we've targeted this enemy, so allow ForceTurretAimAt to run before we continue
|
|
wait(0.1);
|
|
self.initial_enemy_target = false;
|
|
if ( !self assault_vehicle_ai_can_see_living_enemy() )
|
|
continue;
|
|
}
|
|
|
|
should_use_rockets = self.hasRockets && self.RocketAmmo > 0;
|
|
if ( self.hasTurret )
|
|
attack_start_loc = self.mgTurret GetTagOrigin( "tag_flash" );
|
|
else
|
|
attack_start_loc = self.origin;
|
|
if ( should_use_rockets )
|
|
should_use_rockets = DistanceSquared(attack_start_loc, self.enemy_target.origin) > SCR_CONST_ENEMY_TOO_CLOSE_FOR_ROCKETS_DIST_SQ;
|
|
|
|
vec_to_target = (self.TargetEnt.origin - attack_start_loc);
|
|
angles_to_target = VectorToAngles(vec_to_target);
|
|
pitch_max = self VehicleGet3PPitchClamp();
|
|
pitch_to_target = AngleClamp180(angles_to_target[0]);
|
|
is_within_pitch_limitations = pitch_to_target < pitch_max && pitch_to_target > -1 * pitch_max;
|
|
|
|
vehicle_forward_2d = VectorNormalize(AnglesToForward(self.angles) * (1,1,0));
|
|
vec_to_target_2d = VectorNormalize(vec_to_target * (1,1,0));
|
|
is_in_front_of_vehicle = VectorDot(vehicle_forward_2d, vec_to_target_2d) > 0.90;
|
|
|
|
if ( is_within_pitch_limitations && is_in_front_of_vehicle )
|
|
{
|
|
if ( self.hasRockets && should_use_rockets )
|
|
{
|
|
if ( GetTime() > self.last_rocket_time + 1000 )
|
|
{
|
|
if ( self.hasMG )
|
|
self notify("FireSecondaryWeapon");
|
|
else
|
|
self notify("FirePrimaryWeapon");
|
|
self.last_rocket_time = GetTime();
|
|
}
|
|
}
|
|
else if ( self.hasMG )
|
|
{
|
|
self.mgTurret ShootTurret();
|
|
}
|
|
else if ( !self.hasTurret )
|
|
{
|
|
if ( SightTracePassed( attack_start_loc, self.TargetEnt.origin, false, self, self.enemy_target ) )
|
|
self notify("FirePrimaryWeapon");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self.initial_enemy_target = true;
|
|
}
|
|
}
|
|
else if ( IsDefined(self.enemy_target) && !IsAlive(self.enemy_target) )
|
|
{
|
|
if ( self.hasMG )
|
|
{
|
|
if ( IsDefined( self.fire_at_dead_time ) && GetTime() < self.fire_at_dead_time )
|
|
self.mgTurret ShootTurret();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( self.hasCloak && !maps\mp\killstreaks\_drone_common::droneIsCloaked( self ) )
|
|
{
|
|
if ( !IsDefined(self.CloakCooldown) || self.CloakCooldown == 0 )
|
|
self notify("Cloak");
|
|
}
|
|
|
|
wait(0.05);
|
|
}
|
|
}
|
|
|
|
assault_vehicle_ai_enemy_exists_and_is_alive()
|
|
{
|
|
if( IsDefined( level.isHorde ) && level.isHorde )
|
|
{
|
|
if( IsDefined(self.enemy_target) && IsDefined( self.enemy_target.ishordeenemysentry ) && self.enemy_target.ishordeenemysentry )
|
|
return self.enemy_target.isAlive;
|
|
else
|
|
return ( IsDefined(self.enemy_target) && IsAlive(self.enemy_target) );
|
|
}
|
|
else
|
|
return ( IsDefined(self.enemy_target) && IsAlive(self.enemy_target) );
|
|
}
|
|
|
|
assault_vehicle_ai_can_see_living_enemy()
|
|
{
|
|
if ( !self assault_vehicle_ai_enemy_exists_and_is_alive() )
|
|
return false;
|
|
|
|
if ( GetTime() > self.enemy_target_last_vis_time )
|
|
{
|
|
self.enemy_target_last_vis_time = GetTime();
|
|
|
|
if( IsDefined( level.isHorde ) && level.isHorde )
|
|
{
|
|
if( IsDefined( self.enemy_target.ishordedrone ) && self.enemy_target.ishordedrone )
|
|
self.enemy_target_visible = SightTracePassed( self assault_vehicle_ai_get_camera_position(), self.enemy_target.origin, false, self, self.enemy_target );
|
|
else
|
|
self.enemy_target_visible = SightTracePassed( self assault_vehicle_ai_get_camera_position(), self.enemy_target.origin + SCR_CONST_ENEMY_TARGET_OFFSET, false, self, self.enemy_target );
|
|
}
|
|
else
|
|
self.enemy_target_visible = SightTracePassed( self assault_vehicle_ai_get_camera_position(), self.enemy_target.origin, false, self, self.enemy_target );
|
|
}
|
|
|
|
return self.enemy_target_visible;
|
|
}
|
|
|
|
assault_vehicle_ai_follow_path( path, movement_func, enemy_moved_func, node_z_offset )
|
|
{
|
|
if ( !IsDefined(node_z_offset) )
|
|
node_z_offset = 0;
|
|
node_offset = (0,0,node_z_offset);
|
|
|
|
for( i=0; i<path.size; i++ )
|
|
{
|
|
next_node = path[i];
|
|
self [[movement_func]](next_node.origin + node_offset);
|
|
drive_time = 0;
|
|
|
|
while( Distance2DSquared( next_node.origin, self.origin ) > squared(SCR_CONST_REACHED_NODE_RADIUS) )
|
|
{
|
|
/#
|
|
self assault_vehicle_ai_draw_debug_path(path,i,node_offset);
|
|
#/
|
|
drive_time += 0.05;
|
|
needs_new_path = ( drive_time > self assault_vehicle_ai_path_timeout_time() );
|
|
|
|
if ( !needs_new_path && self assault_vehicle_ai_enemy_exists_and_is_alive() )
|
|
needs_new_path = self [[enemy_moved_func]](path[path.size-1]);
|
|
|
|
if ( needs_new_path )
|
|
return;
|
|
|
|
if ( self.hasTurret && self assault_vehicle_ai_can_see_living_enemy() )
|
|
{
|
|
self [[movement_func]](self.origin); // don't move when enemy is visible
|
|
while( self assault_vehicle_ai_can_see_living_enemy() )
|
|
wait(0.05);
|
|
|
|
// Can no longer see the enemy, continue on path
|
|
self [[movement_func]](next_node.origin);
|
|
}
|
|
|
|
wait(0.05);
|
|
}
|
|
}
|
|
}
|
|
|
|
assault_vehicle_ai_enemy_moved_air( path_end_node )
|
|
{
|
|
enemy_nearest_aerial_node = self.enemy_target get_ent_closest_aerial_node();
|
|
return ( enemy_nearest_aerial_node != path_end_node );
|
|
}
|
|
|
|
assault_vehicle_ai_enemy_moved_ground( path_end_node )
|
|
{
|
|
return DistanceSquared(path_end_node.origin, self.enemy_target.origin) > squared(SCR_CONST_ENEMY_MOVED_FAR_FROM_GOAL_DIST);
|
|
}
|
|
|
|
/#
|
|
assault_vehicle_ai_draw_debug_path(path, start_index, node_offset)
|
|
{
|
|
if ( GetDvarInt("ai_showpaths") == 1 || SCR_CONST_ALWAYS_SHOW_VEHICLE_PATH_DEBUG )
|
|
{
|
|
line(self.origin + (0,0,10), path[start_index].origin + (0,0,10) + node_offset, (0,0,1), 1.0, false, 4);
|
|
for( j=start_index; j<path.size-1; j++ )
|
|
{
|
|
line(path[j].origin + (0,0,10) + node_offset, path[j+1].origin + (0,0,10) + node_offset, (0,0,1), 1.0, false, 4);
|
|
}
|
|
}
|
|
}
|
|
#/
|
|
|
|
assault_vehicle_ai_path_timeout_time()
|
|
{
|
|
return SCR_CONST_PATH_TIMEOUT_TIME_AIR;
|
|
}
|
|
|
|
assault_vehicle_ai_air_movement_func( dest )
|
|
{
|
|
self SetVehGoalPos(dest, true);
|
|
} |