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