2023-04-13 17:30:38 +02:00

1726 lines
51 KiB
Plaintext

#using scripts\codescripts\struct;
#using scripts\shared\gameskill_shared;
#using scripts\shared\hostmigration_shared;
#using scripts\shared\math_shared;
#using scripts\shared\array_shared;
#using scripts\shared\statemachine_shared;
#using scripts\shared\system_shared;
#using scripts\shared\util_shared;
#using scripts\shared\vehicle_shared;
#using scripts\shared\vehicle_death_shared;
#using scripts\shared\vehicle_ai_shared;
#using scripts\shared\vehicles\_attack_drone;
#using scripts\shared\flag_shared;
#using scripts\shared\turret_shared;
#using scripts\shared\ai_shared;
#using scripts\shared\ai\systems\ai_interface;
//#using scripts\cp\_util; // for getOtherTeam function
// Speeds
// scanner
// 0 as horizontal, 90 as straight down
//90
// weapon
// tunable length of persistent sight on target before fire rocket
// drone
// will wait this amount of time after player run into a non-reachable space to deploy attack drones
// other
// pain
// 0 to disable
// feature control
// if true, side turret will not choose same target as main turret (so the player being targeted won't get cross fired by all three turrets)
#namespace hunter;
function autoexec __init__sytem__() { system::register("hunter",&__init__,undefined,undefined); }
#using_animtree( "generic" );
// ----------------------------------------------
// initialization
// ----------------------------------------------
function __init__()
{
RegisterInterfaceAttributes( "hunter" );
vehicle::add_main_callback( "hunter", &hunter_initialize );
}
function RegisterInterfaceAttributes( archetype )
{
vehicle_ai::RegisterSharedInterfaceAttributes( archetype );
// Strafe distance
ai::RegisterNumericInterface(
archetype,
"strafe_speed",
0,
0, 100 );
ai::RegisterNumericInterface(
archetype,
"strafe_distance",
0,
0, 10000 );
}
function hunter_initTagArrays()
{
self.weakSpotTags = [];
if ( false )
{
if ( !isdefined( self.weakSpotTags ) ) self.weakSpotTags = []; else if ( !IsArray( self.weakSpotTags ) ) self.weakSpotTags = array( self.weakSpotTags ); self.weakSpotTags[self.weakSpotTags.size]="tag_target_l";;
if ( !isdefined( self.weakSpotTags ) ) self.weakSpotTags = []; else if ( !IsArray( self.weakSpotTags ) ) self.weakSpotTags = array( self.weakSpotTags ); self.weakSpotTags[self.weakSpotTags.size]="tag_target_r";;
}
self.explosiveWeakSpotTags = [];
if ( false )
{
if ( !isdefined( self.explosiveWeakSpotTags ) ) self.explosiveWeakSpotTags = []; else if ( !IsArray( self.explosiveWeakSpotTags ) ) self.explosiveWeakSpotTags = array( self.explosiveWeakSpotTags ); self.explosiveWeakSpotTags[self.explosiveWeakSpotTags.size]="tag_fan_base_l";;
if ( !isdefined( self.explosiveWeakSpotTags ) ) self.explosiveWeakSpotTags = []; else if ( !IsArray( self.explosiveWeakSpotTags ) ) self.explosiveWeakSpotTags = array( self.explosiveWeakSpotTags ); self.explosiveWeakSpotTags[self.explosiveWeakSpotTags.size]="tag_fan_base_r";;
}
self.missileTags = [];
if ( !isdefined( self.missileTags ) ) self.missileTags = []; else if ( !IsArray( self.missileTags ) ) self.missileTags = array( self.missileTags ); self.missileTags[self.missileTags.size]="tag_rocket1";;
if ( !isdefined( self.missileTags ) ) self.missileTags = []; else if ( !IsArray( self.missileTags ) ) self.missileTags = array( self.missileTags ); self.missileTags[self.missileTags.size]="tag_rocket2";;
self.droneAttachTags = [];
if ( false )
{
if ( !isdefined( self.droneAttachTags ) ) self.droneAttachTags = []; else if ( !IsArray( self.droneAttachTags ) ) self.droneAttachTags = array( self.droneAttachTags ); self.droneAttachTags[self.droneAttachTags.size]="tag_drone_attach_l";;
if ( !isdefined( self.droneAttachTags ) ) self.droneAttachTags = []; else if ( !IsArray( self.droneAttachTags ) ) self.droneAttachTags = array( self.droneAttachTags ); self.droneAttachTags[self.droneAttachTags.size]="tag_drone_attach_r";;
}
}
function hunter_SpawnDrones()
{
self.dronesOwned = [];
if ( false )
{
foreach( droneTag in self.droneAttachTags )
{
origin = self GetTagOrigin( droneTag );
angles = self GetTagAngles( droneTag );
drone = SpawnVehicle( "spawner_bo3_attack_drone_enemy", origin, angles );
drone.owner = self;
drone.attachTag = droneTag;
drone.team = self.team;
if ( !isdefined( self.dronesOwned ) ) self.dronesOwned = []; else if ( !IsArray( self.dronesOwned ) ) self.dronesOwned = array( self.dronesOwned ); self.dronesOwned[self.dronesOwned.size]=drone;;
}
}
}
function hunter_initialize()
{
self endon( "death" );
self UseAnimTree( #animtree );
Target_Set( self, ( 0, 0, 90 ) );
ai::CreateInterfaceForEntity( self );
self.health = self.healthdefault;
self vehicle::friendly_fire_shield();
//self EnableAimAssist();
self SetNearGoalNotifyDist( 50 );
self SetHoverParams( 15, 100, 40 );
self.flyheight = GetDvarFloat( "g_quadrotorFlyHeight" );
self.fovcosine = 0; // +/-90 degrees = 180
self.fovcosinebusy = 0.574; //+/- 55 degrees = 110 fov
self.vehAirCraftCollisionEnabled = true;
self.original_vehicle_type = self.vehicletype;
self.settings = struct::get_script_bundle( "vehiclecustomsettings", self.scriptbundlesettings );
self.goalRadius = 999999;
self.goalHeight = 999999;
self SetGoal( self.origin, false, self.goalRadius, self.goalHeight );
self hunter_initTagArrays();
self.overrideVehicleDamage = &HunterCallback_VehicleDamage;
self thread vehicle_ai::nudge_collision();
//disable some cybercom abilities
if( IsDefined( level.vehicle_initializer_cb ) )
{
[[level.vehicle_initializer_cb]]( self );
}
self.ignoreFireFly = true;
self.ignoreDecoy = true;
self vehicle_ai::InitThreatBias();
// self thread hunter_frontScanning();
// self hunter_SpawnDrones();
//
// wait 0.5;
//
// foreach ( drone in self.dronesOwned )
// {
// if ( isalive( drone ) )
// {
// drone vehicle_ai::set_state( "attached" );
// }
// }
self turret::_init_turret( 1 );
self turret::_init_turret( 2 );
self turret::set_best_target_func( &side_turret_get_best_target, 1 );
self turret::set_best_target_func( &side_turret_get_best_target, 2 );
self turret::set_burst_parameters( 1, 2, 1, 2, 1 );
self turret::set_burst_parameters( 1, 2, 1, 2, 2 );
self turret::set_target_flags( 1 | 2, 1 );
self turret::set_target_flags( 1 | 2, 2 );
self side_turrets_forward();
self PathVariableOffset( (10, 10, -30), 1 );
defaultRole();
}
function defaultRole()
{
self vehicle_ai::init_state_machine_for_role();
self vehicle_ai::get_state_callbacks( "combat" ).enter_func = &state_combat_enter;
self vehicle_ai::get_state_callbacks( "combat" ).update_func = &state_combat_update;
self vehicle_ai::get_state_callbacks( "combat" ).exit_func = &state_combat_exit;
self vehicle_ai::get_state_callbacks( "driving" ).enter_func = &hunter_scripted;
self vehicle_ai::get_state_callbacks( "scripted" ).enter_func = &hunter_scripted;
self vehicle_ai::get_state_callbacks( "death" ).enter_func = &state_death_enter;
self vehicle_ai::get_state_callbacks( "death" ).update_func = &state_death_update;
self vehicle_ai::get_state_callbacks( "emped" ).update_func = &hunter_emped;
self vehicle_ai::add_state( "unaware",
undefined,
&state_unaware_update,
&state_unaware_exit );
vehicle_ai::add_interrupt_connection( "unaware", "scripted", "enter_scripted" );
vehicle_ai::add_interrupt_connection( "unaware", "emped", "emped" );
vehicle_ai::add_interrupt_connection( "unaware", "off", "shut_off" );
vehicle_ai::add_interrupt_connection( "unaware", "driving", "enter_vehicle" );
vehicle_ai::add_interrupt_connection( "unaware", "pain", "pain" );
self vehicle_ai::add_state( "strafe",
&state_strafe_enter,
&state_strafe_update,
&state_strafe_exit );
vehicle_ai::add_interrupt_connection( "strafe", "scripted", "enter_scripted" );
vehicle_ai::add_interrupt_connection( "strafe", "emped", "emped" );
vehicle_ai::add_interrupt_connection( "strafe", "off", "shut_off" );
vehicle_ai::add_interrupt_connection( "strafe", "driving", "enter_vehicle" );
vehicle_ai::add_interrupt_connection( "strafe", "pain", "pain" );
vehicle_ai::add_utility_connection( "strafe", "combat" );
vehicle_ai::add_utility_connection( "emped", "strafe" );
vehicle_ai::add_utility_connection( "pain", "strafe" );
vehicle_ai::StartInitialState();
}
// ----------------------------------------------
// State: death
// ----------------------------------------------
function shut_off_fx()
{
self endon( "death" );
self notify( "death_shut_off" );
if ( isdefined( self.frontScanner ) )
{
self.frontScanner.sndScanningEnt delete();
self.frontScanner delete();
}
}
function kill_drones()
{
self endon( "death" );
foreach ( drone in self.dronesOwned )
{
if ( isalive( drone ) && Distance2DSquared( self.origin, drone.origin ) < ( (80) * (80) ) )
{
damageOrigin = self.origin + (0,0,1);
drone finishVehicleRadiusDamage(self.death_info.attacker, self.death_info.attacker, 32000, 32000, 10, 0, "MOD_EXPLOSIVE", level.weaponNone, damageOrigin, 400, -1, (0,0,1), 0);
}
}
}
function state_death_enter( params )
{
self endon( "death" );
if ( isdefined( self.fakeTargetEnt ) )
{
self.fakeTargetEnt Delete();
}
vehicle_ai::defaultstate_death_enter();
self.inpain = true;
self thread shut_off_fx();
//self thread kill_drones();
}
function state_death_update( params )
{
self endon( "death" );
death_type = vehicle_ai::get_death_type( params );
if ( !isdefined( death_type ) )
{
params.death_type = "gibbed";
death_type = params.death_type;
}
self vehicle_ai::ClearAllLookingAndTargeting();
self vehicle_ai::ClearAllMovement();
self CancelAIMove();
self SetSpeedImmediate( 0 );
self SetVehVelocity( ( 0, 0, 0 ) );
self SetPhysAcceleration( ( 0, 0, 0 ) );
self SetAngularVelocity( ( 0, 0, 0 ) );
self vehicle_ai::defaultstate_death_update( params );
}
// ----------------------------------------------
// ----------------------------------------------
// State: unaware
// ----------------------------------------------
function state_unaware_enter( params )
{
ratio = 0.5;
accel = self GetDefaultAcceleration();
self SetSpeed( ratio * self.settings.defaultmovespeed, ratio * accel, ratio * accel );
}
function state_unaware_update( params )
{
self endon( "change_state" );
self endon( "death" );
if ( isdefined( self.enemy ) )
{
self vehicle_ai::set_state( "combat" );
}
self ClearLookAtEnt();
self disable_turrets();
self thread Movement_Thread_Wander();
while ( 1 )
{
self waittill( "enemy" );
self vehicle_ai::set_state( "combat" );
}
}
function state_unaware_exit( params )
{
self notify( "end_movement_thread" );
}
function Movement_Thread_Wander()
{
self endon( "death" );
self notify( "end_movement_thread" );
self endon( "end_movement_thread" );
constMinSearchRadius = 120;
constMaxSearchRadius = 800;
minSearchRadius = math::clamp( constMinSearchRadius, 0, self.goalRadius );
maxSearchRadius = math::clamp( constMaxSearchRadius, constMinSearchRadius, self.goalRadius );
halfHeight = 400;
innerSpacing = 80;
outerSpacing = 50;
maxGoalTimeout = 15;
timeAtSamePosition = 2.5 + randomfloat( 1 );
while ( true )
{
queryResult = PositionQuery_Source_Navigation( self.origin, minSearchRadius, maxSearchRadius, halfHeight, innerSpacing, self, outerSpacing );
PositionQuery_Filter_DistanceToGoal( queryResult, self );
vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult );
vehicle_ai::PositionQuery_Filter_Random( queryResult, 0, 10 );
vehicle_ai::PositionQuery_PostProcess_SortScore( queryResult );
stayAtGoal = timeAtSamePosition > 0.2;
foundpath = false;
for ( i = 0; i < queryResult.data.size && !foundpath; i++ )
{
goalPos = queryResult.data[i].origin;
foundpath = self SetVehGoalPos( goalPos, stayAtGoal, true );
}
if ( foundPath )
{
msg = self util::waittill_any_timeout( maxGoalTimeout, "near_goal", "force_goal", "reached_end_node", "goal" );
if ( stayAtGoal )
{
wait randomFloatRange( 0.5 * timeAtSamePosition, timeAtSamePosition );
}
}
else
{
wait 1;
}
}
}
// ----------------------------------------------
// ----------------------------------------------
// State: combat
// ----------------------------------------------
function enable_turrets()
{
//self turret::enable( 0, false );
self turret::enable( 1, false );
self turret::enable( 2, false );
}
function disable_turrets()
{
//self turret::disable( 0 );
self turret::disable( 1 );
self turret::disable( 2 );
self side_turrets_forward();
}
function side_turrets_forward()
{
self SetTurretTargetRelativeAngles( (10, -90, 0), 1 );
self SetTurretTargetRelativeAngles( (10, 90, 0), 2 );
}
function state_combat_enter( params )
{
ratio = 1.0;
accel = self GetDefaultAcceleration();
self SetSpeed( ratio * self.settings.defaultmovespeed, ratio * accel, ratio * accel );
self hunter_lockon_fx();
self enable_turrets();
}
function state_combat_update( params )
{
self endon( "change_state" );
self endon( "death" );
if ( !isdefined( self.enemy ) )
{
self vehicle_ai::set_state( "unaware" );
}
self thread Movement_Thread_StayInDistance();
self thread Attack_Thread_MainTurret();
self thread Attack_Thread_rocket();
while ( 1 )
{
self waittill( "no_enemy" );
self vehicle_ai::set_state( "unaware" );
}
}
function state_combat_exit( params )
{
self notify( "end_attack_thread" );
self notify( "end_movement_thread" );
self ClearTurretTarget();
}
// ----------------------------------------------
// ----------------------------------------------
// State: strafe
// ----------------------------------------------
function state_strafe_enter( params )
{
ratio = 2.0;
accel = ratio * self GetDefaultAcceleration();
speed = ratio * self.settings.defaultmovespeed;
strafe_speed_attribute = ai::get_behavior_attribute("strafe_speed");
if ( strafe_speed_attribute > 0 )
{
speed = strafe_speed_attribute;
}
self SetSpeed( speed , accel, accel );
}
function state_strafe_update( params )
{
self endon( "change_state" );
self endon( "death" );
self ClearVehGoalPos();
distanceToTarget = 0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax );
target = self.origin + AnglesToForward( self.angles ) * distanceToTarget;
if ( isdefined( self.enemy ) )
{
distanceToTarget = Distance( self.origin, self.enemy.origin );
}
distanceThreshold = 500 + distanceToTarget * 0.08;
strafe_distance_attribute = ai::get_behavior_attribute("strafe_distance");
if ( strafe_distance_attribute > 0 )
{
distanceThreshold = strafe_distance_attribute;
}
maxSearchRadius = distanceThreshold * 1.5;
halfHeight = 300;
outerSpacing = maxSearchRadius * 0.05;
innerSpacing = outerSpacing * 2;
queryResult = PositionQuery_Source_Navigation( self.origin, 0, maxSearchRadius, halfHeight, innerSpacing, self, outerSpacing );
PositionQuery_Filter_Directness( queryResult, self.origin, target );
PositionQuery_Filter_DistanceToGoal( queryResult, self );
PositionQuery_Filter_InClaimedLocation( queryResult, self );
self vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult, 200 );
foreach ( point in queryResult.data )
{
distanceToPointSqr = distanceSquared( point.origin, self.origin );
if( distanceToPointSqr < distanceThreshold * 0.5 )
{
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "distAway" ] = -distanceThreshold; #/ point.score += -distanceThreshold;;
}
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "distAway" ] = sqrt( distanceToPointSqr ); #/ point.score += sqrt( distanceToPointSqr );;
diffToPreferedDirectness = abs( point.directness - 0 );
directnessScore = MapFloat( 0, 1, 1000, 0, diffToPreferedDirectness );
if ( diffToPreferedDirectness > 0.1 )
{
directnessScore -= 500;
}
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "directnessRaw" ] = point.directness; #/ point.score += point.directness;;
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "directness" ] = directnessScore; #/ point.score += directnessScore;;
if ( point.directionChange < 0.6 )
{
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "directionChange" ] = -2000; #/ point.score += -2000;;
}
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "directionChangeRaw" ] = point.directionChange; #/ point.score += point.directionChange;;
}
vehicle_ai::PositionQuery_PostProcess_SortScore( queryResult );
self vehicle_ai::PositionQuery_DebugScores( queryResult );
foreach ( point in queryResult.data )
{
self.current_pathto_pos = point.origin;
foundpath = self SetVehGoalPos( self.current_pathto_pos, true, true );
if ( foundPath )
{
msg = self util::waittill_any_timeout( 5, "near_goal", "force_goal", "goal", "enemy_visible" );
break;
}
}
previous_state = self vehicle_ai::get_previous_state();
if ( !isdefined( previous_state ) || previous_state == "strafe" )
{
previous_state = "combat";
}
self vehicle_ai::set_state( previous_state );
}
function state_strafe_exit( params )
{
vehicle_ai::Cooldown( "strafe_again", 2.0 );
}
// ----------------------------------------------
function GetNextMovePosition_tactical( enemy )
{
if( self.goalforced )
{
return self.goalpos;
}
selfDistToEnemy = Distance2D( self.origin, enemy.origin );
// distance based multipliers
goodDist = 0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax );
tooCloseDist = 0.8 * goodDist;
closeDist = 1.2 * goodDist;
farDist = 3 * goodDist;
queryMultiplier = MapFloat( closeDist, farDist, 1, 3, selfDistToEnemy );
preferedDistAwayFromOrigin = 150;
maxSearchRadius = 1000 * queryMultiplier;
halfHeight = 300 * queryMultiplier;
innerSpacing = 80 * queryMultiplier;
outerSpacing = 80 * queryMultiplier;
queryResult = PositionQuery_Source_Navigation( self.origin, 0, maxSearchRadius, halfHeight, innerSpacing, self, outerSpacing );
PositionQuery_Filter_DistanceToGoal( queryResult, self );
PositionQuery_Filter_InClaimedLocation( queryResult, self );
PositionQuery_Filter_Sight( queryResult, enemy.origin, self GetEye() - self.origin, self, 0, enemy );
self vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult, 200 );
self vehicle_ai::PositionQuery_Filter_EngagementDist( queryResult, enemy, self.settings.engagementDistMin, self.settings.engagementDistMax );
self vehicle_ai::PositionQuery_Filter_Random( queryResult, 0, 30 );
goalHeight = enemy.origin[2] + 0.5 * ( self.settings.engagementHeightMin + self.settings.engagementHeightMax );
foreach ( point in queryResult.data )
{
if ( !point.visibility )
{
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "no visibility" ] = -600; #/ point.score += -600;;
}
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "engagementDist" ] = -point.distAwayFromEngagementArea; #/ point.score += -point.distAwayFromEngagementArea;;
// distance from origin
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "distToOrigin" ] = MapFloat( 0, preferedDistAwayFromOrigin, 0, 600, point.distToOrigin2D ); #/ point.score += MapFloat( 0, preferedDistAwayFromOrigin, 0, 600, point.distToOrigin2D );;
if( point.inClaimedLocation )
{
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "inClaimedLocation" ] = -500; #/ point.score += -500;;
}
// height
preferedHeightRange = 75;
distFromPreferredHeight = abs( point.origin[2] - goalHeight );
if ( distFromPreferredHeight > preferedHeightRange )
{
heightScore = -MapFloat( preferedHeightRange, 5000, 0, 9000, distFromPreferredHeight );
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "height" ] = heightScore; #/ point.score += heightScore;;
}
}
self vehicle_ai::PositionQuery_DebugScores( queryResult );
vehicle_ai::PositionQuery_PostProcess_SortScore( queryResult );
if( queryResult.data.size )
{
return queryResult.data[0].origin;
}
return self.origin;
}
function Movement_Thread_StayInDistance()
{
self endon( "death" );
self notify( "end_movement_thread" );
self endon( "end_movement_thread" );
maxGoalTimeout = 10;
stuckCount = 0;
while ( true )
{
enemy = self.enemy;
if ( !isdefined( enemy ) )
{
wait 1;
continue;
}
usePathfinding = true;
onNavVolume = IsPointInNavVolume( self.origin, "navvolume_big" );
if ( !onNavVolume )
{
// off nav volume, try getting back
getbackPoint = undefined;
pointOnNavVolume = self GetClosestPointOnNavVolume( self.origin, 500 );
if ( isdefined( pointOnNavVolume ) )
{
if ( SightTracePassed( self.origin, pointOnNavVolume, false, self ) )
{
getbackPoint = pointOnNavVolume;
}
}
if ( !isdefined( getbackPoint ) )
{
queryResult = PositionQuery_Source_Navigation( self.origin, 0, 800, 400, 1.5 * self.radius );
PositionQuery_Filter_Sight( queryResult, self.origin, (0, 0, 0), self, 1 );
getbackPoint = undefined;
foreach( point in queryResult.data )
{
if ( point.visibility === true )
{
getbackPoint = point.origin;
break;
}
}
}
if ( isdefined( getbackPoint ) )
{
self.current_pathto_pos = getbackPoint;
usePathfinding = false;
}
else
{
stuckCount++;
if ( stuckCount == 1 )
{
stuckLocation = self.origin;
}
else if ( stuckCount > 10 )
{
/#
assert( false, "Hunter fall outside of NavVolume at " + self.origin );
v_box_min = ( -self.radius, -self.radius, -self.radius );
v_box_max = ( self.radius, self.radius, self.radius );
Box( self.origin, v_box_min, v_box_max, self.angles[1], (1,0,0), 1, false, 1000000 );
if ( isdefined( stuckLocation ) )
{
Line( stuckLocation, self.origin, (1,0,0), 1, true, 1000000 );
}
#/
self Kill();
}
}
}
else
{
stuckCount = 0;
if( self.goalforced )
{
goalpos = self GetClosestPointOnNavVolume( self.goalpos, 200 );
if ( isdefined( goalpos ) )
{
self.current_pathto_pos = goalpos;
usePathfinding = true;
}
else
{
self.current_pathto_pos = self.goalpos;
usePathfinding = false;
}
}
else
{
self.current_pathto_pos = GetNextMovePosition_tactical( enemy );
usePathfinding = true;
}
}
if ( !isDefined( self.current_pathto_pos ) )
{
wait 0.5;
continue;
}
distanceToGoalSq = DistanceSquared( self.current_pathto_pos, self.origin );
if ( distanceToGoalSq > ( (0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax )) * (0.5 * ( self.settings.engagementDistMin + self.settings.engagementDistMax )) ) )
{
self SetSpeed( self.settings.defaultMoveSpeed * 2 );
}
else
{
self SetSpeed( self.settings.defaultMoveSpeed );
}
self SetLookAtEnt( enemy );
foundpath = self SetVehGoalPos( self.current_pathto_pos, true, usePathfinding );
if ( foundPath )
{
/#
if ( ( isdefined( GetDvarInt("hkai_debugPositionQuery") ) && GetDvarInt("hkai_debugPositionQuery") ) )
{
recordLine( self.origin, self.current_pathto_pos, (0.3,1,0) );
recordLine( self.origin, enemy.origin, (1,0,0.4) );
}
#/
msg = self util::waittill_any_timeout( maxGoalTimeout, "near_goal", "force_goal", "goal" );
}
else
{
wait 0.5;
}
enemy = self.enemy;
if ( isdefined( enemy ) )
{
goalHeight = enemy.origin[2] + 0.5 * ( self.settings.engagementHeightMin + self.settings.engagementHeightMax );
distFromPreferredHeight = abs( self.origin[2] - goalHeight );
farDist = self.settings.engagementDistMax;
nearDist = self.settings.engagementDistMin;
selfDistToEnemy = Distance2D( self.origin, enemy.origin );
if ( self VehCanSee( enemy ) && selfDistToEnemy < farDist && selfDistToEnemy > nearDist && distFromPreferredHeight < 230 )
{
msg = self util::waittill_any_timeout( RandomFloatRange( 2, 4 ), "enemy_not_visible" );
if ( msg == "enemy_not_visible" )
{
msg = self util::waittill_any_timeout( 1.0, "enemy_visible" );
if ( msg != "timeout" )
{
wait 1;
}
}
}
}
else
{
wait 1;
}
}
}
function Delay_Target_ToEnemy_Thread( point, enemy, timeToHit )
{
self endon( "death" );
self endon( "change_state" );
self endon( "end_attack_thread" );
self endon( "faketarget_stop_moving" );
enemy endon( "death" );
if ( !isdefined( self.fakeTargetEnt ) )
{
self.fakeTargetEnt = Spawn( "script_origin", point );
}
self.fakeTargetEnt Unlink();
self.fakeTargetEnt.origin = point;
self SetTurretTargetEnt( self.fakeTargetEnt );
self waittill( "turret_on_target" );
timeStart = GetTime();
offset = (0, 0, 0);
if( IsSentient( enemy ) )
{
offset = enemy GetEye() - enemy.origin;
}
while( GetTime() < timeStart + timeToHit * 1000 )
{
self.fakeTargetEnt.origin = LerpVector( point, enemy.origin + offset, ( GetTime() - timeStart ) / ( timeToHit * 1000 ) );
///#debugstar( self.fakeTargetEnt.origin, 1000, (0,1,0) ); #/
{wait(.05);};
}
self.fakeTargetEnt.origin = enemy.origin + offset;
{wait(.05);};
self.fakeTargetEnt LinkTo( enemy );
}
function Attack_Thread_MainTurret()
{
self endon( "death" );
self endon( "change_state" );
self endon( "end_attack_thread" );
while( 1 )
{
enemy = self.enemy;
if( isdefined( enemy ) )
{
self SetLookAtEnt( enemy );
if( self VehCanSee( enemy ) )
{
vectorFromEnemy = VectorNormalize( ( (self.origin - enemy.origin)[0], (self.origin - enemy.origin)[1], 0 ) );
self thread Delay_Target_ToEnemy_Thread( enemy.origin + vectorFromEnemy * 300, enemy, 1.5 );
self waittill( "turret_on_target" );
self vehicle_ai::fire_for_time( 2 + RandomFloat( 0.8 ) );
self ClearTurretTarget();
self SetTurretTargetRelativeAngles( (15,0,0), 0 );
if( isdefined( enemy ) && IsAI( enemy ) )
{
wait( 2.5 + RandomFloat( 0.5 ) );
}
else
{
wait( 2.0 + RandomFloat( 0.4 ) );
}
}
else
{
wait 0.4;
}
}
else
{
self ClearTurretTarget();
self ClearLookAtEnt();
wait 0.4;
}
}
}
function Attack_Thread_rocket()
{
self endon( "death" );
self endon( "change_state" );
self endon( "end_attack_thread" );
while( true )
{
enemy = self.enemy;
if ( !isdefined( enemy ) )
{
wait 1;
continue;
}
if ( isdefined( enemy ) && self VehCanSee( enemy ) && vehicle_ai::IsCooldownReady( "rocket_launcher" ) )
{
vehicle_ai::Cooldown( "rocket_launcher", 8 );
self notify( "end_movement_thread" );
self ClearVehGoalPos();
self SetVehGoalPos( self.origin, true, false );
target = enemy.origin;
self SetLookAtEnt( enemy );
self hunter_lockon_fx();
wait 1.5;
eye = self GetTagOrigin( "tag_eye" );
if ( isdefined( enemy ) )
{
anglesToTarget = VectorToAngles( enemy.origin - eye );
angles = anglesToTarget - self.angles;
if ( -30 < angles[0] && angles[0] < 60 && -70 < angles[1] && angles[1] < 70 )
{
target = enemy.origin;
}
else
{
anglesToTarget = VectorToAngles( target - eye );
}
}
else
{
anglesToTarget = VectorToAngles( target - eye );
}
rightDir = AnglesToRight( anglesToTarget );
randomRange = 30;
offset = [];
offset[0] = -rightDir * randomRange * 2 + ( RandomFloatRange( -randomRange, randomRange ), RandomFloatRange( -randomRange, randomRange ), 0 );
offset[1] = rightDir * randomRange * 2 + ( RandomFloatRange( -randomRange, randomRange ), RandomFloatRange( -randomRange, randomRange ), 0 );
self hunter_fire_one_missile( 0, target, offset[0] );
wait 0.5;
if ( isdefined( enemy ) )
{
eye = self GetTagOrigin( "tag_eye" );
angles = VectorToAngles( enemy.origin - eye ) - self.angles;
if ( -30 < angles[0] && angles[0] < 60 && -70 < angles[1] && angles[1] < 70 )
{
target = enemy.origin;
}
}
self hunter_fire_one_missile( 1, target, offset[1] );
wait 1;
self thread Movement_Thread_StayInDistance();
}
wait 0.5;
}
}
// ----------------------------------------------
// best target of side turrets: closest, can hit, and not target of main turret or other turret
function side_turret_get_best_target( a_potential_targets, n_index )
{
if ( self.ignoreall === true )
{
return undefined;
}
shouldYield = true && level.gameskill < 3;
main_turret_target = self.enemy;
if ( n_index === 2 )
{
other_turret_target = turret::get_target( 1 );
}
if ( shouldYield )
{
ArrayRemoveValue( a_potential_targets, main_turret_target );
ArrayRemoveValue( a_potential_targets, other_turret_target );
}
e_best_target = undefined;
while ( !isdefined( e_best_target ) && ( a_potential_targets.size > 0 ) )
{
e_closest_target = ArrayGetClosest( self.origin, a_potential_targets );
if( self turret::can_hit_target( e_closest_target, n_index ) )
{
e_best_target = e_closest_target;
}
else
{
ArrayRemoveValue( a_potential_targets, e_closest_target );
}
}
return e_best_target;
}
// ----------------------------------------------
// missile
// ----------------------------------------------
function hunter_fire_one_missile( launcher_index, target, offset, blinkLights, waittimeAfterBlinkLights )
{
self endon( "death" );
if ( ( isdefined( blinkLights ) && blinkLights ) )
{
self vehicle_ai::blink_lights_for_time( 1 );
if ( isdefined( waittimeAfterBlinkLights ) && waittimeAfterBlinkLights > 0 )
{
wait waittimeAfterBlinkLights;
}
}
if ( !isdefined( offset ) )
{
offset = ( 0, 0, 0 );
}
spawnTag = self.missileTags[ launcher_index ];
origin = self GetTagOrigin( spawnTag );
angles = self GetTagAngles( spawnTag );
forward = AnglesToForward( angles );
up = AnglesToUp( angles );
if ( isdefined( spawnTag ) && isdefined( target ) )
{
weapon = GetWeapon( "hunter_rocket_turret" );
//only do the full MagicBullet parameter list if target is a real entity, and not a script_struct
if ( IsEntity( target ) )
{
missile = MagicBullet( weapon, origin, target.origin + offset, self, target, offset );
//missile thread remote_missile_life();
}
else if ( IsVec( target ) )
{
missile = MagicBullet( weapon, origin, target + offset, self );
}
else
{
missile = MagicBullet( weapon, origin, target.origin + offset, self );
}
}
}
function remote_missile_life()
{
self endon( "death" );
hostmigration::waitLongDurationWithHostMigrationPause( 10 );
playFX( level.remote_mortar_fx["missileExplode"], self.origin );
self playlocalsound( "mpl_ks_reaper_explosion" );
self delete();
}
function hunter_lockon_fx()
{
self thread vehicle_ai::blink_lights_for_time( 1.5 );
self playsound( "veh_hunter_alarm_target" );
}
//self == hunter
function getEnemyArray( include_ai, include_player )
{
enemyArray = [];
enemy_team = "allies";//util::getOtherTeam( self.team );
if ( ( isdefined( include_ai ) && include_ai ) )
{
aiArray = GetAITeamArray( enemy_team );
enemyArray = ArrayCombine( enemyArray, aiArray, false, false );
}
if ( ( isdefined( include_player ) && include_player ) )
{
playerArray = GetPlayers( enemy_team );
enemyArray = ArrayCombine( enemyArray, playerArray, false, false );
}
return enemyArray;
}
// ----------------------------------------------
// scanner
// ----------------------------------------------
//self == hunter
function is_point_in_view( point, do_trace )
{
if ( !isdefined( point ) )
{
return false;
}
scanner = self.frontScanner;
vector_to_point = point - scanner.origin;
in_view = ( LengthSquared( vector_to_point ) <= ( (10000) * (10000) ) );
if ( in_view )
{
in_view = util::within_fov( scanner.origin, scanner.angles, point, Cos( 190 ) );
}
if ( in_view && ( isdefined( do_trace ) && do_trace ) && isdefined( self.enemy ) )
{
in_view = SightTracePassed( scanner.origin, point, false, self.enemy );
}
return in_view;
}
//self == hunter
function is_valid_target( target, do_trace )
{
target_is_valid = true;
// check script properties
if ( ( isdefined( target.ignoreme ) && target.ignoreme ) || ( target.health <= 0 ) )
{
target_is_valid = false;
}
// check sentient properties
else if ( IsSentient( target ) && ( ( target IsNoTarget() ) || ( target ai::is_dead_sentient() ) ) )
{
target_is_valid = false;
}
// check fov
else if ( isdefined( target.origin ) && !is_point_in_view( target.origin, do_trace ) )
{
target_is_valid = false;
}
return target_is_valid;
}
//self == hunter
function get_enemies_in_view( do_trace )
{
validEnemyArray = [];
enemyArray = getEnemyArray( true, true );
foreach( enemy in enemyArray )
{
if ( is_valid_target( enemy, do_trace ) )
{
if ( !isdefined( validEnemyArray ) ) validEnemyArray = []; else if ( !IsArray( validEnemyArray ) ) validEnemyArray = array( validEnemyArray ); validEnemyArray[validEnemyArray.size]=enemy;;
}
}
return validEnemyArray;
}
// self == hunter
function hunter_scanner_init()
{
self.frontScanner = Spawn( "script_model", self GetTagOrigin( "tag_gunner_flash3" ) );
self.frontScanner SetModel( "tag_origin" );
self.frontScanner.angles = self GetTagAngles( "tag_gunner_flash3" );
self.frontScanner LinkTo( self, "tag_gunner_flash3" );
self.frontScanner.owner = self;
self.frontScanner.hasTargetEnt = false;
self.frontScanner.sndScanningEnt = spawn( "script_origin", self.frontScanner.origin + anglesToForward( self.angles ) * 1000 );
self.frontScanner.sndScanningEnt linkto( self.frontScanner );
wait 0.25;
//self.frontScanner thread hunter_scanner_lookTarget( self );
if ( false )
{
PlayFxOnTag( self.settings.spotlightfx, self.frontScanner, "tag_origin" );
}
}
// self == hunter
function hunter_scanner_SetTargetEntity( targetEnt, offset )
{
if ( !isdefined( offset ) )
{
offset = ( 0, 0, 0 );
}
if( IsDefined( targetEnt ) )
{
self.frontScanner.targetEnt = targetEnt;
self.frontScanner.hasTargetEnt = true;
self SetGunnerTargetEnt( self.frontScanner.targetEnt, offset, 2 );
}
}
// self == hunter
function hunter_scanner_ClearLookTarget()
{
self.frontScanner.hasTargetEnt = false;
self ClearGunnerTarget( 2 );
}
// self == hunter
function hunter_scanner_SetTargetPosition( targetPos )
{
if( IsDefined( targetPos ) )
{
self.frontScanner.targetPos = targetPos;
self SetGunnerTargetVec( self.frontScanner.targetPos, 2 );
}
}
function hunter_frontScanning()
{
self endon( "death_shut_off" );
self endon( "crash_done" );
self endon( "death" );
hunter_scanner_init();
offsetFactorPitch = 0;
offsetFactorYaw = 0;
// use 2 different irrational numbers here to help avoiding repetitive patterns
pitchStep = 1 * 2.23606797749978969640; // irrational number sqrt(5)
yawStep = 1 * 3.14159265358979323846; // irrational number PI
pitchRange = 20;
yawRange = 45;
scannerDirection = undefined;
while ( 1 )
{
scannerOrigin = self.frontScanner.origin;
if ( ( isdefined( self.inpain ) && self.inpain ) )
{
wait 0.3;
offset = ( 50, 0, 0 ) + ( math::randomSign() * RandomFloatRange( 1, 2 ) * pitchRange, math::randomSign() * RandomFloatRange( 1, 2 ) * yawRange, 0 );
scannerDirection = anglesToForward( self.angles + offset );
}
else if ( !IsDefined( self.enemy ) )
{
if ( false )
{
self.frontScanner.sndScanningEnt playloopsound( "veh_hunter_scanner_loop", 1 );
}
offsetFactorPitch = offsetFactorPitch + pitchStep;
offsetFactorYaw = offsetFactorYaw + yawStep;
offset = ( 50, 0, 0 ) + ( Sin( offsetFactorPitch ) * pitchRange, Cos( offsetFactorYaw ) * yawRange, 0 );
scannerDirection = anglesToForward( self.angles + offset );
enemies = get_enemies_in_view( true );
if ( enemies.size > 0 )
{
closest_enemy = ArrayGetClosest( self.origin, enemies );
self.favoriteEnemy = closest_enemy;
/# line( scannerOrigin, closest_enemy.origin, ( 0, 1, 0 ), 1, 3 ); #/
}
}
else
{
if ( self is_point_in_view( self.enemy.origin, true ) )
{
self notify ( "hunter_lockOnTargetInSight" );
}
else
{
self notify ( "hunter_lockOnTargetOutSight" );
}
scannerDirection = VectorNormalize( self.enemy.origin - scannerOrigin );
if ( false )
{
self.frontScanner.sndScanningEnt stoploopsound( 1 );
}
}
targetLocation = scannerOrigin + scannerDirection * 1000; // any big value will do
self hunter_scanner_SetTargetPosition( targetLocation );
/# line( scannerOrigin, self.frontScanner.targetPos, ( 0, 1, 0 ), 1, 1000 ); #/
wait 0.1;
}
}
function hunter_exit_vehicle()
{
self waittill( "exit_vehicle", player );
player.ignoreme = false;
player DisableInvulnerability();
self SetHeliHeightLock( false );
self EnableAimAssist();
self SetVehicleType( self.original_vehicle_type );
self.attachedpath = undefined;
self SetGoal( self.origin, false, 4096, 512 );
}
function hunter_scripted( params )
{
// do nothing state
params.driver = self GetSeatOccupant( 0 );
if ( isdefined( params.driver ) )
{
self DisableAimAssist();
self thread vehicle_death::vehicle_damage_filter( "firestorm_turret" );
//self thread hunter_set_team( driver.team );
params.driver.ignoreme = true;
params.driver EnableInvulnerability();
if ( isdefined( self.vehicle_weapon_override ) )
{
self SetVehWeapon( self.vehicle_weapon_override );
}
self thread hunter_exit_vehicle();
//self thread hunter_update_rumble();
self thread hunter_collision_player();
//self thread hunter_self_destruct();
self thread player_fire_update_side_turret_1();
self thread player_fire_update_side_turret_2();
self thread player_fire_update_rocket();
}
if ( isdefined( self.goal_node ) && isdefined( self.goal_node.hunter_claimed ) )
{
self.goal_node.hunter_claimed = undefined;
}
self ClearTargetEntity();
self ClearVehGoalPos();
self PathVariableOffsetClear();
self PathFixedOffsetClear();
self ClearLookAtEnt();
self ResumeSpeed();
}
function player_fire_update_side_turret_1()
{
self endon( "death" );
self endon( "exit_vehicle" );
weapon = self SeatGetWeapon( 1 );
fireTime = weapon.fireTime;
while( 1 )
{
self SetGunnerTargetVec( self GetTurretTargetVec( 0 ), 0 );
if( self IsDriverFiring( ) )
{
self FireWeapon( 1 );
}
wait fireTime;
}
}
function player_fire_update_side_turret_2()
{
self endon( "death" );
self endon( "exit_vehicle" );
weapon = self SeatGetWeapon( 2 );
fireTime = weapon.fireTime;
while( 1 )
{
self SetGunnerTargetVec( self GetTurretTargetVec( 0 ), 1 );
if( self IsDriverFiring( ) )
{
self FireWeapon( 2 );
}
wait fireTime;
}
}
function player_fire_update_rocket()
{
self endon( "death" );
self endon( "exit_vehicle" );
weapon = GetWeapon( "hunter_rocket_turret_player" );
fireTime = weapon.fireTime;
driver = self GetSeatOccupant( 0 );
while( 1 )
{
if( driver ButtonPressed( "BUTTON_A" ) )
{
spawnTag0 = self.missileTags[ 0 ];
spawnTag1 = self.missileTags[ 1 ];
origin0 = self GetTagOrigin( spawnTag0 );
origin1 = self GetTagOrigin( spawnTag1 );
target = self GetTurretTargetVec( 0 );
MagicBullet( weapon, origin0, target );
MagicBullet( weapon, origin1, target );
wait fireTime;
}
Wait 0.05;
}
}
function hunter_collision_player()
{
self endon( "change_state" );
self endon( "crash_done" );
self endon( "death" );
while ( 1 )
{
self waittill( "veh_collision", velocity, normal );
driver = self GetSeatOccupant( 0 );
if ( isdefined( driver ) && LengthSquared( velocity ) > 70 * 70 )
{
Earthquake( 0.25, 0.25, driver.origin, 50 );
driver PlayRumbleOnEntity( "damage_heavy" );
}
}
}
// Lots of gross hardcoded values! :(
function hunter_update_rumble()
{
self endon( "death" );
self endon( "exit_vehicle" );
while ( 1 )
{
vr = Abs( self GetSpeed() / self GetMaxSpeed() );
if ( vr < 0.1 )
{
level.player PlayRumbleOnEntity( "hunter_fly" );
wait 0.35;
}
else
{
time = RandomFloatRange( 0.1, 0.2 );
Earthquake( RandomFloatRange( 0.1, 0.15 ), time, self.origin, 200 );
level.player PlayRumbleOnEntity( "hunter_fly" );
wait time;
}
}
}
function hunter_self_destruct()
{
self endon( "death" );
self endon( "exit_vehicle" );
const max_self_destruct_time = 5;
self_destruct = false;
self_destruct_time = 0;
while ( 1 )
{
if ( !self_destruct )
{
if ( level.player MeleeButtonPressed() )
{
self_destruct = true;
self_destruct_time = max_self_destruct_time;
}
{wait(.05);};
continue;
}
else
{
IPrintLnBold( self_destruct_time );
wait 1;
self_destruct_time -= 1;
if ( self_destruct_time == 0 )
{
driver = self GetSeatOccupant( 0 );
if ( isdefined( driver ) )
{
driver DisableInvulnerability();
}
Earthquake( 3, 1, self.origin, 256 );
RadiusDamage( self.origin, 1000, 15000, 15000, level.player, "MOD_EXPLOSIVE" );
self DoDamage( self.health + 1000, self.origin );
}
continue;
}
}
}
function hunter_level_out_for_landing()
{
self endon( "death" );
self endon( "emped" );
self endon( "landed" );
while ( isdefined( self.emped ) )
{
velocity = self.velocity; // setting the angles clears the velocity so we save it off and set it back
self.angles = ( self.angles[0] * 0.85, self.angles[1], self.angles[2] * 0.85 );
ang_vel = self GetAngularVelocity() * 0.85;
self SetAngularVelocity( ang_vel );
self SetVehVelocity( velocity );
{wait(.05);};
}
}
function hunter_emped( params )
{
self endon( "death" );
self endon( "emped" );
self.emped = true;
wait RandomFloatRange( 4, 7 );
self vehicle_ai::evaluate_connections();
/*
PlaySoundAtPosition( "veh_qrdrone_emp_down", self.origin );
if ( !isdefined( self.stun_fx ) )
{
self.stun_fx = Spawn( "script_model", self.origin );
self.stun_fx SetModel( "tag_origin" );
self.stun_fx LinkTo( self, "tag_origin", ( 0, 0, 0 ), ( 0, 0, 0 ) );
PlayFXOnTag( level._effect[ "hunter_stun" ], self.stun_fx, "tag_origin" );
}
wait RandomFloatRange( 4, 7 );
self.stun_fx delete();
self playsound ( "veh_qrdrone_boot_qr" );
*/
}
// ----------------------------------------------
// pain/hit reaction
// ----------------------------------------------
function hunter_pain_for_time( time, velocityStablizeParam, rotationStablizeParam, restoreLookPoint )
{
self endon( "death" );
self.painStartTime = GetTime();
if ( !( isdefined( self.inpain ) && self.inpain ) )
{
self.inpain = true;
while ( GetTime() < self.painStartTime + time * 1000 )
{
self SetVehVelocity( self.velocity * velocityStablizeParam );
self SetAngularVelocity( self GetAngularVelocity() * rotationStablizeParam );
wait 0.1;
}
if ( isdefined( restoreLookPoint ) )
{
restoreLookEnt = Spawn( "script_model", restoreLookPoint );
restoreLookEnt SetModel( "tag_origin" );
self ClearLookAtEnt();
self SetLookAtEnt( restoreLookEnt );
self setTurretTargetEnt( restoreLookEnt );
wait 1.5;
self ClearLookAtEnt();
self ClearTurretTarget();
restoreLookEnt delete();
}
self.inpain = false;
}
}
function hunter_pain_small( eAttacker, damageType, hitPoint, hitDirection, hitLocationInfo, partName )
{
if( !isdefined( hitPoint ) || !isdefined( hitDirection ) )
{
return;
}
self SetVehVelocity( self.velocity + VectorNormalize( hitDirection ) * 20 );
if ( !( isdefined( self.inpain ) && self.inpain ) )
{
vecRight = anglesToRight( self.angles );
sign = math::sign( vectorDot( vecRight, hitDirection ) );
yaw_vel = sign * RandomFloatRange( 100, 140 );
ang_vel = self GetAngularVelocity();
ang_vel += ( RandomFloatRange( -120, -100 ), yaw_vel, RandomFloatRange( -100, 100 ) );
self SetAngularVelocity( ang_vel );
self thread hunter_pain_for_time( 1.5, 1.0, 0.8 );
}
self vehicle_ai::set_state( "strafe" );
}
function HunterCallback_VehicleDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal )
{
driver = self GetSeatOccupant( 0 );
// no friendly fire
if ( isdefined( eAttacker ) && eAttacker.team == self.team )
{
return 0;
}
num_players = GetPlayers().size;
maxDamage = self.healthdefault * ( 0.35 - 0.025 * num_players );
if ( sMeansOfDeath !== "MOD_UNKNOWN" && iDamage > maxDamage )
{
iDamage = maxDamage;
}
if ( !isdefined( self.damageLevel ) )
{
self.damageLevel = 0;
self.newDamageLevel = self.damageLevel;
}
newDamageLevel = vehicle::should_update_damage_fx_level( self.health, iDamage, self.healthdefault );
if ( newDamageLevel > self.damageLevel )
{
self.newDamageLevel = newDamageLevel;
}
if ( self.newDamageLevel > self.damageLevel )
{
self.damageLevel = self.newDamageLevel;
if ( self.pain_when_damagelevel_change === true )
{
hunter_pain_small( eAttacker, sMeansOfDeath, vPoint, vDir, sHitLoc, partName );
}
vehicle::set_damage_fx_level( self.damageLevel );
}
if ( vehicle_ai::should_emp( self, weapon, sMeansOfDeath, eInflictor, eAttacker ) )
{
hunter_pain_small( eAttacker, sMeansOfDeath, vPoint, vDir, sHitLoc, partName );
}
return iDamage;
}