stvoy-sp-sdk/game/AI_Scavenger.cpp

1034 lines
23 KiB
C++
Raw Permalink Normal View History

2002-11-22 00:00:00 +00:00
#include "b_local.h"
#include "g_nav.h"
#include "anims.h"
extern void CG_DrawAlert( vec3_t origin, float rating );
extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
#define MAX_SUSPECT_COUNT 5
#define INVESTIGATE_RADIUS 128
#define INVESTIGATE_RADIUS_SQR ( INVESTIGATE_RADIUS * INVESTIGATE_RADIUS )
#define MAX_LAST_SEEN_DIST 128
#define MAX_LAST_SEEN_DIST_SQR ( MAX_LAST_SEEN_DIST * MAX_LAST_SEEN_DIST )
#define MAX_VIEW_DIST 1024
#define MAX_VIEW_SPEED 250
#define MAX_LIGHT_INTENSITY 255
#define MIN_LIGHT_THRESHOLD 0.1
#define DISTANCE_SCALE 0.25f
#define DISTANCE_THRESHOLD 0.075f
#define SPEED_SCALE 0.25f
#define FOV_SCALE 0.5f
#define LIGHT_SCALE 0.25f
#define REALIZE_THRESHOLD 0.6f
#define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 )
#define PURSUE_DELAY 2000
qboolean NPC_CheckEnemyStealth( void );
extern qboolean NPC_CheckDisguise( gentity_t *ent );
extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
//Local state enums
enum
{
LSTATE_NONE = 0,
LSTATE_UNDERFIRE,
LSTATE_INVESTIGATE,
};
/*
-------------------------
Scav_Speak
-------------------------
*/
static void Scav_Speak( int ID, float chance )
{
//Check to see if we pass
if ( random() < chance )
return;
G_AddVoiceEvent( NPC, ID, 0 );
}
/*
-------------------------
Scav_Mark
-------------------------
*/
#if 0
static void Scav_Mark( void )
{
vec3_t end;
VectorCopy( NPC->currentOrigin, end );
end[2] += 64;
CG_DrawNode( end, NODE_START );
}
#endif
/*
-------------------------
NPC_Scav_BattleChatter
-------------------------
*/
#if 1
static void NPC_Scav_BattleChatter( void )
{
if ( TIMER_Done( NPC, "chatter" ) )
{
Scav_Speak( Q_irand(EV_CHATTER1, EV_CHATTER14), 0.8f );
TIMER_Set( NPC, "chatter", Q_irand( 5000, 10000 ) );
}
}
#endif
/*
-------------------------
NPC_Scav_Pain
-------------------------
*/
void NPC_Scav_Pain( gentity_t *self, gentity_t *other, int damage )
{
if ( Q_stricmp( self->NPC_type, "hirogenalpha" ) != 0 )
{
self->NPC->localState = LSTATE_UNDERFIRE;
TIMER_Set( self, "duck", 0 );
TIMER_Set( self, "stand", 2000 );
}
NPC_Pain( self, other, damage );
}
/*
-------------------------
Scav_HoldPosition
-------------------------
*/
static void Scav_HoldPosition( void )
{
NPCInfo->squadState = SQUAD_STAND_AND_SHOOT;
NPCInfo->goalEntity = NULL;
if ( TIMER_Done( NPC, "stand" ) )
{
TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) );
}
}
/*
-------------------------
Scav_Move
-------------------------
*/
static void Scav_Move( gentity_t *goal )
{
NPCInfo->combatMove = qtrue;
NPCInfo->goalEntity = goal;
qboolean moved = NPC_MoveToGoal();
navInfo_t info;
//Get the move info
NAV_GetLastMove( info );
//If we hit our target, then stop and fire!
if ( ( info.flags & NIF_COLLISION ) && ( info.blocker == NPC->enemy ) )
{
Scav_HoldPosition();
}
//If our move failed, then reset
if ( moved == qfalse )
{
Scav_HoldPosition();
}
NPC_UpdateAngles( qtrue, qtrue );
}
/*
-------------------------
Scav_Flee
-------------------------
*/
static qboolean Scav_Flee( void )
{
return qfalse;
}
/*
-------------------------
Scav_TakeCover
-------------------------
*/
static qboolean Scav_TakeCover( void )
{
int cp;
//Find a cover point
if ( ( cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->enemy->currentOrigin, (CP_AVOID|CP_AVOID_ENEMY|CP_COVER) ) ) != -1 )
{
//Reserve it, and then move there
if ( NPC_SetCombatPoint( cp ) )
{
//Setup our retreat information
NPCInfo->squadState = SQUAD_RETREAT;
ucmd.buttons |= BUTTON_CAREFUL;
NPCInfo->combatPoint = cp;
//Yell for cover
Scav_Speak( EV_CHATTER1, 0.9f );
TIMER_Set( NPC, "chatter", Q_irand( 5000, 10000 ) );
if ( TIMER_Done( NPC, "stand" ) )
{
TIMER_Set( NPC, "duck", Q_irand( 3000, 5000 ) );
}
NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue );
Scav_Move( NPCInfo->goalEntity );
return qtrue;
}
}
return qfalse;
}
/*
-------------------------
Scav_TakePosition
-------------------------
*/
static qboolean Scav_TakePosition( void )
{
int cp;
//Find a cover point
if ( ( cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->enemy->currentOrigin, (CP_AVOID|CP_AVOID_ENEMY|CP_CLEAR) ) ) != -1 )
{
//Reserve it, and then move there
if ( NPC_SetCombatPoint( cp ) )
{
//Setup our retreat information
NPCInfo->squadState = SQUAD_TRANSITION;
ucmd.buttons |= BUTTON_CAREFUL;
NPCInfo->combatPoint = cp;
TIMER_Set( NPC, "chatter", Q_irand( 5000, 10000 ) );
NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue );
Scav_Move( NPCInfo->goalEntity );
return qtrue;
}
}
return qfalse;
}
/*
-------------------------
NPC_Scav_SleepShuffle
-------------------------
*/
static void NPC_Scav_SleepShuffle( void )
{
//Play an awake script if we have one
if ( VALIDSTRING( NPC->behaviorSet[BSET_AWAKE] ) )
{
G_ActivateBehavior( NPC, BSET_AWAKE);
return;
}
//Automate some movement and noise
if ( TIMER_Done( NPC, "shuffleTime" ) )
{
//TODO: Play sleeping shuffle animation
//int soundIndex = Q_irand( 0, 1 );
/*
switch ( soundIndex )
{
case 0:
G_Sound( NPC, G_SoundIndex("sound/voice/imperialsleeper1/scav4/hunh.mp3") );
break;
case 1:
G_Sound( NPC, G_SoundIndex("sound/voice/imperialsleeper3/scav4/tryingtosleep.wav") );
break;
}
*/
TIMER_Set( NPC, "shuffleTime", 4000 );
TIMER_Set( NPC, "sleepTime", 2000 );
return;
}
//They made another noise while we were stirring, see if we can see them
if ( TIMER_Done( NPC, "sleepTime" ) )
{
NPC_CheckEnemyStealth();
TIMER_Set( NPC, "sleepTime", 2000 );
}
}
/*
-------------------------
NPC_Scav_Sleep
-------------------------
*/
void NPC_BSScav_Sleep( void )
{
int alertEvent = NPC_CheckAlertEvents();
//There is an event to look at
if ( alertEvent >= 0 )
{
//See if it was enough to wake us up
if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED )
{
G_SetEnemy( NPC, &g_entities[0] );
return;
}
//Otherwise just stir a bit
NPC_Scav_SleepShuffle();
return;
}
}
/*
-------------------------
NPC_CheckEnemyStealth
-------------------------
*/
qboolean NPC_CheckEnemyStealth( void )
{
//NOTENOTE: For now, all stealh checks go against the player, since
// he is the main focus. Squad members and rivals do not
// fall into this category and will be ignored.
gentity_t *target = &g_entities[0]; //Change this pointer to assess other entities
//In case we aquired one some other way
if ( NPC->enemy != NULL )
return qtrue;
//Ignore notarget
if ( target->flags & FL_NOTARGET )
return qfalse;
//Don't pick up on a disguised player
if ( NPC_CheckDisguise( target ) )
return qfalse;
//If the target is this close, then wake up regardless
if ( DistanceSquared( target->currentOrigin, NPC->currentOrigin ) < (32*32) )
{
G_SetEnemy( NPC, target );
return qtrue;
}
//Check FOV first
if ( InFOV( target, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse )
return qfalse;
qboolean clearLOS = ( target->client->ps.leanofs ) ? NPC_ClearLOS( target->currentOrigin ) : NPC_ClearLOS( target );
//Now check for clear line of vision
if ( clearLOS )
{
float hAngle_perc = NPC_GetHFOVPercentage( target->currentOrigin, NPC->currentOrigin, NPC->currentAngles, NPCInfo->stats.hfov );
float vAngle_perc = NPC_GetVFOVPercentage( target->currentOrigin, NPC->currentOrigin, NPC->currentAngles, NPCInfo->stats.vfov );
//Cap them vertically pretty harshly
vAngle_perc *= ( vAngle_perc * vAngle_perc );
hAngle_perc *= ( hAngle_perc * hAngle_perc );
//Cap our vertical vision severely
if ( vAngle_perc <= 0.5f )
return qfalse;
//Assess the player's current status
float target_speed = VectorLength( target->client->ps.velocity );
float target_dist = Distance( target->currentOrigin, NPC->currentOrigin );
int target_crouching = ( target->client->usercmd.upmove == -127 );
float dist_rating = ( target_dist / MAX_VIEW_DIST );
float speed_rating = ( target_speed / MAX_VIEW_SPEED );
float light_level = ( target->lightLevel / MAX_LIGHT_INTENSITY );
float FOV_perc = 1.0f - ( hAngle_perc + vAngle_perc ) * 0.5f; //FIXME: Dunno about the average...
//Too dark
if ( light_level < MIN_LIGHT_THRESHOLD )
return qfalse;
//Too close?
if ( dist_rating < DISTANCE_THRESHOLD )
{
//G_SetEnemy( NPC, target );
//return qtrue;
}
//Out of range
if ( dist_rating > 1.0f )
return qfalse;
//Cap our speed checks
if ( speed_rating > 1.0f )
speed_rating = 1.0f;
//...Visibilty linearly wanes over distance
//...As the percentage out of the FOV increases, straight perception suffers on an exponential scale
//Calculate the distance and fov influences
float dist_influence = DISTANCE_SCALE * ( ( 1.0f - dist_rating ) );
float fov_influence = FOV_SCALE * ( 1.0f - FOV_perc );
//Calculate our base rating
float target_rating = dist_influence + fov_influence;
//...Lack of light hides, abundance of light exposes
//Calculate the light influence
float light_influence = ( light_level - 0.5f ) * LIGHT_SCALE;
//Modify our base value by the light's influence
target_rating += light_influence;
//...Motion draws the eye quickly
target_rating += speed_rating * SPEED_SCALE;
//...Smaller targets are harder to indentify
//Now award any final bonuses to this number
if ( target_crouching )
{
target_rating *= 0.9f; //10% bonus
}
//If he's violated the threshold, then realize him
if ( target_rating > REALIZE_THRESHOLD )
{
G_SetEnemy( NPC, target );
return qtrue;
}
//If he's above the caution threshold, then realize him in a few seconds unless he moves to cover
if ( target_rating > CAUTIOUS_THRESHOLD )
{
//If we haven't already, start the counter
if ( TIMER_Done( NPC, "enemyLastVisible" ) )
{
//NPCInfo->timeEnemyLastVisible = level.time + 2000;
TIMER_Set( NPC, "enemyLastVisible", Q_irand( 4000, 8000 ) );
//TODO: Play a sound along the lines of, "Huh? What was that?"
}
else if ( TIMER_Get( NPC, "enemyLastVisible" ) == level.time ) //FIXME: Is this reliable?
{
G_SetEnemy( NPC, target );
return qtrue;
}
return qfalse;
}
}
return qfalse;
}
/*
-------------------------
Scav_NoiseAlert
-------------------------
*/
/*
#define NUM_ALERT_NOISES 8
static const char *noiseAlerts[NUM_ALERT_NOISES] =
{
"sound/voice/impchessman2/scav4/whatwasthat.mp3",
"sound/voice/imperialsleeper1/hunh.mp3",
"sound/voice/impguard4/scav5/heywhat.mp3",
"sound/voice/klingon1/scav3/what.mp3",
"sound/voice/klingoneng3/scav2/whatwasthat.mp3",
"sound/voice/klingoneng4/scav3/what.mp3",
"sound/voice/klingonguard/scav2/whatsthat.mp3",
"sound/voice/klingonguard/scav2/whosthere.mp3",
};
static void Scav_NoiseAlert( void )
{
//G_SoundOnEnt( NPC, CHAN_VOICE_ATTEN, noiseAlerts[Q_irand(0,NUM_ALERT_NOISES-1)] );
Scav_Speak( EV_CHATTER11, 0.0f );
}
/*
-------------------------
NPC_Scav_InvestigateEvent
-------------------------
*/
#define MAX_CHECK_THRESHOLD 1
static void NPC_Scav_InvestigateEvent( int eventID, bool extraSuspicious )
{
//If they've given themselves away, just take them as an enemy
if ( level.alertEvents[eventID].level == AEL_DISCOVERED )
{
G_SetEnemy( NPC, &g_entities[0] );
return;
}
/*
//Must be ready to take another sound event
if ( NPCInfo->investigateSoundDebounceTime > level.time )
return;
//Save the position for movement (if necessary)
VectorCopy( level.alertEvents[eventID].position, NPCInfo->investigateGoal );
//Say something
Scav_NoiseAlert();
//First awareness of it
NPCInfo->investigateCount += ( extraSuspicious ) ? 2 : 1;
//Clamp the value
if ( NPCInfo->investigateCount > 4 )
NPCInfo->investigateCount = 4;
//See if we should walk over and investigate
/*
if ( NPCInfo->investigateCount > MAX_CHECK_THRESHOLD )
{
int id = NPC_FindCombatPoint( NPCInfo->investigateGoal, NPCInfo->investigateGoal, NPCInfo->investigateGoal, CPF_INVESTIGATE );
if ( id != -1 )
{
NPC_SetMoveGoal( NPC, level.combatPoints[id].origin, 16, qtrue );
NPCInfo->localState = LSTATE_INVESTIGATE;
}
}
*/
//Setup the debounce info
/*
NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 5000;
NPCInfo->investigateSoundDebounceTime = level.time + 2000;
NPCInfo->pauseTime = level.time;
*/
//Start investigating
//NPCInfo->tempBehavior = BS_INVESTIGATE;
}
/*
-------------------------
Scav_OffsetLook
-------------------------
*/
static void Scav_OffsetLook( float offset, vec3_t out )
{
vec3_t angles, forward, temp;
GetAnglesForDirection( NPC->currentOrigin, NPCInfo->investigateGoal, angles );
angles[YAW] += offset;
AngleVectors( angles, forward, NULL, NULL );
VectorMA( NPC->currentOrigin, 64, forward, out );
CalcEntitySpot( NPC, SPOT_HEAD, temp );
out[2] = temp[2];
}
/*
-------------------------
Scav_LookAround
-------------------------
*/
static void Scav_LookAround( void )
{
vec3_t lookPos;
float perc = (float) ( level.time - NPCInfo->pauseTime ) / (float) NPCInfo->investigateDebounceTime;
//Keep looking at the spot
if ( perc < 0.25 )
{
VectorCopy( NPCInfo->investigateGoal, lookPos );
}
else if ( perc < 0.5f ) //Look up but straight ahead
{
Scav_OffsetLook( 0.0f, lookPos );
}
else if ( perc < 0.75f ) //Look right
{
Scav_OffsetLook( 45.0f, lookPos );
}
else //Look left
{
Scav_OffsetLook( -45.0f, lookPos );
}
NPC_FacePosition( lookPos );
}
/*
-------------------------
NPC_BSScav_Investigate
-------------------------
*/
void NPC_BSScav_Investigate( void )
{
//If we're done looking, then just return to what we were doing
if ( ( NPCInfo->investigateDebounceTime + NPCInfo->pauseTime ) < level.time )
{
NPCInfo->tempBehavior = BS_DEFAULT;
NPCInfo->goalEntity = UpdateGoal();
NPC_UpdateAngles( qtrue, qtrue );
return;
}
//See if we're searching for the noise's origin
if ( NPCInfo->localState == LSTATE_INVESTIGATE )
{
//See if we're there
if ( NAV_HitNavGoal( NPC->currentOrigin, NPC->mins, NPC->maxs, NPCInfo->goalEntity->currentOrigin, 32 ) == qfalse )
{
ucmd.buttons |= (BUTTON_CAREFUL|BUTTON_WALKING);
//Try and move there
if ( NPC_MoveToGoal() )
{
//Bump our times
NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 5000;
NPCInfo->pauseTime = level.time;
NPC_UpdateAngles( qtrue, qtrue );
return;
}
}
//Otherwise we're done or have given up
NPCInfo->localState = LSTATE_NONE;
}
//Look around
Scav_LookAround();
//Look for an enemy
if ( NPC_CheckEnemyStealth() )
{
NPCInfo->behaviorState = BS_RUN_AND_SHOOT;
NPCInfo->tempBehavior = BS_DEFAULT;
return;
}
}
/*
-------------------------
NPC_BSScav_Patrol
-------------------------
*/
void NPC_BSScav_Patrol( void )
{
//Look for any enemies
if ( NPC_CheckEnemyStealth() )
{
NPCInfo->behaviorState = BS_RUN_AND_SHOOT;
NPC_AngerSound();
return;
}
int alertEvent = NPC_CheckAlertEvents();
//There is an event to look at
if ( alertEvent >= 0 )
{
NPC_Scav_InvestigateEvent( alertEvent, qfalse );
return;
}
//If we have somewhere to go, then do that
if ( UpdateGoal() )
{
ucmd.buttons |= BUTTON_WALKING;
//Scav_Move( NPCInfo->goalEntity );
NPC_MoveToGoal();
}
NPC_UpdateAngles( qtrue, qtrue );
}
/*
-------------------------
NPC_BSScav_Idle
-------------------------
*/
void NPC_BSScav_Idle( void )
{
int alertEvent = NPC_CheckAlertEvents();
//There is an event to look at
if ( alertEvent >= 0 )
{
NPC_Scav_InvestigateEvent( alertEvent, qfalse );
return;
}
TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) );
}
/*
-------------------------
Scav_CheckMoveState
-------------------------
*/
static qboolean Scav_CheckMoveState( void )
{
//See if we're a scout
if ( NPCInfo->squadState == SQUAD_SCOUT )
{
//If we're supposed to stay put, then crouch and fire
if ( TIMER_Done( NPC, "stick" ) == qfalse )
{
NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_STAND2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
ucmd.upmove = -128;
//Check to fire
if ( NPC_CheckCanAttackExt() )
{
WeaponThink( qtrue );
}
return qtrue;
}
//Otherwise, if we can see our target, just shoot
if ( ( NPC_ClearLOS( NPC->enemy ) ) && ( NPC_ClearShot( NPC->enemy ) ) )
{
NPCInfo->squadState = SQUAD_STAND_AND_SHOOT;
return qfalse;
}
//Move to find our target
Scav_Move( NPC->enemy );
return qtrue;
}
//See if we're moving towards a goal
if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) )
{
//Did we make it?
if ( NAV_HitNavGoal( NPC->currentOrigin, NPC->mins, NPC->maxs, NPCInfo->goalEntity->currentOrigin, 16 ) )
{
TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) ); //FIXME: Slant for difficulty levels
NPCInfo->squadState = ( NPCInfo->squadState == SQUAD_RETREAT ) ? SQUAD_COVER : SQUAD_STAND_AND_SHOOT;
NPCInfo->goalEntity = NULL;
return qfalse;
}
//Keep running
NPCInfo->squadState = SQUAD_RETREAT;
ucmd.buttons |= BUTTON_CAREFUL;
Scav_Move( NPCInfo->goalEntity );
TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) );
return qtrue;
}
//See if we should be ducking
if ( TIMER_Done( NPC, "duck" ) == qfalse )
{
NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_STAND2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
ucmd.upmove = -128;
//Check to fire
if ( NPC_CheckCanAttackExt() )
{
WeaponThink( qtrue );
}
return qtrue;
}
return qfalse;
}
/*
-------------------------
Scav_CheckLocalState
-------------------------
*/
static qboolean Scav_CheckLocalState( AIGroupInfo_t &group )
{
int flee = 0;
switch ( NPCInfo->localState )
{
case LSTATE_UNDERFIRE: //Under attack
flee = ( ( group.numGroup ) && ( group.numFront == 0 ) && ( group.numState[SQUAD_POINT] == 0 ) ) ? qtrue : ( Q_irand( 0, 16 ) == 0 );
if ( flee )
{
//Make sure we're the only one doing this, or else we can have big navigational problems
if ( ( group.numGroup > 1 ) && ( group.numState[ SQUAD_TRANSITION ] > 2 ) && ( group.numState[ SQUAD_RETREAT ] > 2 ) )
return qfalse;
//Try and flee
if ( Scav_Flee() == qfalse )
{
//Try to at least find cover
if ( Scav_TakeCover() == qfalse )
return qfalse;
}
}
NPCInfo->localState = LSTATE_NONE;
NPC_UpdateAngles( qtrue, qtrue );
return qtrue;
break;
default:
break;
}
return qfalse;
}
/*
-------------------------
Scav_CheckSquadState
-------------------------
*/
static qboolean Scav_CheckSquadState( AIGroupInfo_t &group )
{
//If we're at point, fire away
if ( NPCInfo->squadState == SQUAD_POINT )
{
if ( TIMER_Done( NPC, "stuck" ) )
{
NPCInfo->squadState = SQUAD_STAND_AND_SHOOT;
return qfalse;
}
//Crouch and fire
NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_STAND2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
ucmd.upmove = -128;
//Check to fire
if ( NPC_CheckCanAttackExt() )
{
WeaponThink( qtrue );
}
return qtrue;
}
//If we're point, then get down
if ( ( group.numGroup ) && ( group.numFront == 0 ) && ( group.numState[SQUAD_POINT] == 0 ) )
{
NPCInfo->squadState = SQUAD_POINT;
ucmd.upmove = -128;
NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_STAND2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
TIMER_Set( NPC, "duck", Q_irand( 3000, 4000 ) );
//Check to fire
if ( NPC_CheckCanAttackExt() )
{
WeaponThink( qtrue );
}
return qtrue;
}
return qfalse;
}
/*
-------------------------
Scav_Hunt
-------------------------
*/
static void Scav_Hunt( AIGroupInfo_t &group )
{
//If we can see our enemy and hit them...
if ( ( NPC_ClearLOS( NPC->enemy ) ) && ( NPC_ClearShot( NPC->enemy ) ) )
{
//See if we should run to a new spot
if ( TIMER_Done( NPC, "roamTime" ) )
{
TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) );
if ( ( Q_irand( 0, 5 ) == 0 ) && ( group.numState[ SQUAD_COVER ] <= 1 ) )
{
if ( Scav_TakeCover() )
return;
}
else
{
if ( Scav_TakePosition() )
return;
}
}
//See if we're done waiting to fire
if ( TIMER_Done( NPC, "attackDelay" ) )
{
//See if we're able to attack
if ( NPC_CheckCanAttackExt() )
{
//Do random battle chatter
NPC_Scav_BattleChatter();
WeaponThink( qtrue );
}
}
//Update our seen enemy position
VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation );
NPCInfo->enemyLastSeenTime = level.time;
NPC_FaceEnemy( qtrue );
TIMER_Set( NPC, "stick", Q_irand( 2000, 4000 ) );
return;
}
//See if we should continue to fire on their last position
if ( ( TIMER_Done( NPC, "stick" ) == qfalse ) && ( group.numState[ SQUAD_SCOUT ] ) )
{
//Fire on the last known position
vec3_t dir, angles;
VectorSubtract( NPCInfo->enemyLastSeenLocation, NPC->currentOrigin, dir );
VectorNormalize( dir );
vectoangles( dir, angles );
NPCInfo->desiredYaw = angles[YAW];
NPCInfo->desiredPitch = angles[PITCH];
NPC_UpdateAngles( qtrue, qtrue );
WeaponThink( qtrue );
NPCInfo->squadState = SQUAD_STAND_AND_SHOOT;
return;
}
//Run after the player
if ( group.numState[ SQUAD_SCOUT ] == 0 )
{
NPCInfo->squadState = SQUAD_SCOUT;
Scav_Move( NPC->enemy );
TIMER_Set( NPC, "roamTime", Q_irand( 2000, 4000 ) );
TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) );
TIMER_Set( NPC, "duck", 0 );
TIMER_Set( NPC, "stick", Q_irand( 2000, 4000 ) );
NPC_FreeCombatPoint( NPCInfo->combatPoint );
return;
}
//Face the enemy
NPC_FaceEnemy( qtrue );
//Check to fire
if ( NPC_CheckCanAttackExt() )
{
WeaponThink( qtrue );
}
//FIXME: Temp stuff
NPC_UpdateAngles( qtrue, qtrue );
}
/*
-------------------------
NPC_BSScav_Attack
-------------------------
*/
void NPC_BSScav_Attack( void )
{
//Don't do anything if we're hurt
if ( NPC->painDebounceTime > level.time )
{
NPC_UpdateAngles( qtrue, qtrue );
return;
}
//If we don't have an enemy, just idle
if ( NPC_CheckEnemyExt() == qfalse )
{
NPC_BSScav_Idle();
return;
}
NPCInfo->combatMove = qtrue;
//Get our group info
AIGroupInfo_t group;
AI_GetGroup( group, NPC->currentOrigin, NPC->currentAngles, 45, 512, NPC->client->playerTeam, NPC, NPC->enemy );
//Check for local events to take care of
if ( Scav_CheckLocalState( group ) )
return;
//Check for movement to take care of
if ( Scav_CheckMoveState() )
return;
//Check our squad's status
if ( Scav_CheckSquadState( group ) )
return;
//Track the player and kill them if possible
Scav_Hunt( group );
}