s1-scripts-dev/raw/maps/mp/killstreaks/_assaultdrone_ai.gsc
2025-05-21 16:23:17 +02:00

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);
}