123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912 |
- // leave this line at the top of all AI_xxxx.cpp files for PCH reasons...
- #include "g_headers.h"
- #include "b_local.h"
- #include "g_nav.h"
- #include "anims.h"
- #include "g_navigator.h"
- extern void CG_DrawAlert( vec3_t origin, float rating );
- extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
- extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
- extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask );
- extern qboolean FlyingCreature( gentity_t *ent );
- extern void Saboteur_Cloak( gentity_t *self );
- //extern CNavigator navigator;
- #define SPF_NO_HIDE 2
- #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 )
- extern void NPC_Tusken_Taunt( void );
- qboolean NPC_CheckPlayerTeamStealth( void );
- static qboolean enemyLOS;
- static qboolean enemyCS;
- static qboolean faceEnemy;
- static qboolean move;
- static qboolean shoot;
- static float enemyDist;
- //Local state enums
- enum
- {
- LSTATE_NONE = 0,
- LSTATE_UNDERFIRE,
- LSTATE_INVESTIGATE,
- };
- void Sniper_ClearTimers( gentity_t *ent )
- {
- TIMER_Set( ent, "chatter", 0 );
- TIMER_Set( ent, "duck", 0 );
- TIMER_Set( ent, "stand", 0 );
- TIMER_Set( ent, "shuffleTime", 0 );
- TIMER_Set( ent, "sleepTime", 0 );
- TIMER_Set( ent, "enemyLastVisible", 0 );
- TIMER_Set( ent, "roamTime", 0 );
- TIMER_Set( ent, "hideTime", 0 );
- TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels
- TIMER_Set( ent, "stick", 0 );
- TIMER_Set( ent, "scoutTime", 0 );
- TIMER_Set( ent, "flee", 0 );
- TIMER_Set( ent, "taunting", 0 );
- }
- void NPC_Sniper_PlayConfusionSound( gentity_t *self )
- {//FIXME: make this a custom sound in sound set
- if ( self->health > 0 )
- {
- G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 );
- }
- //reset him to be totally unaware again
- TIMER_Set( self, "enemyLastVisible", 0 );
- TIMER_Set( self, "flee", 0 );
- self->NPC->squadState = SQUAD_IDLE;
- self->NPC->tempBehavior = BS_DEFAULT;
- //self->NPC->behaviorState = BS_PATROL;
- G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;?
- self->NPC->investigateCount = 0;
- }
- /*
- -------------------------
- NPC_ST_Pain
- -------------------------
- */
- void NPC_Sniper_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod )
- {
- self->NPC->localState = LSTATE_UNDERFIRE;
- if ( self->client->NPC_class == CLASS_SABOTEUR )
- {
- Saboteur_Decloak( self );
- }
- TIMER_Set( self, "duck", -1 );
- TIMER_Set( self, "stand", 2000 );
- NPC_Pain( self, inflictor, other, point, damage, mod );
- if ( !damage && self->health > 0 )
- {//FIXME: better way to know I was pushed
- G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
- }
- }
- /*
- -------------------------
- ST_HoldPosition
- -------------------------
- */
- static void Sniper_HoldPosition( void )
- {
- NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue );
- NPCInfo->goalEntity = NULL;
-
- /*if ( TIMER_Done( NPC, "stand" ) )
- {//FIXME: what if can't shoot from this pos?
- TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) );
- }
- */
- }
- /*
- -------------------------
- ST_Move
- -------------------------
- */
- static qboolean Sniper_Move( void )
- {
- NPCInfo->combatMove = qtrue;//always move straight toward our goal
- qboolean moved = NPC_MoveToGoal( qtrue );
- // navInfo_t info;
-
- //Get the move info
- // NAV_GetLastMove( info );
- //FIXME: if we bump into another one of our guys and can't get around him, just stop!
- //If we hit our target, then stop and fire!
- // if ( info.flags & NIF_COLLISION )
- // {
- // if ( info.blocker == NPC->enemy )
- // {
- // Sniper_HoldPosition();
- // }
- // }
- //If our move failed, then reset
- if ( moved == qfalse )
- {//couldn't get to enemy
- if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy )
- {//we were running after enemy
- //Try to find a combat point that can hit the enemy
- int cpFlags = (CP_CLEAR|CP_HAS_ROUTE);
- if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST )
- {
- cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST);
- cpFlags |= CP_NEAREST;
- }
- int cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->currentOrigin, cpFlags, 32 );
- if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) )
- {//okay, try one by the enemy
- cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->enemy->currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32 );
- }
- //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him...
- if ( cp != -1 )
- {//found a combat point that has a clear shot to enemy
- NPC_SetCombatPoint( cp );
- NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp );
- return moved;
- }
- }
- //just hang here
- Sniper_HoldPosition();
- }
- return moved;
- }
- /*
- -------------------------
- NPC_BSSniper_Patrol
- -------------------------
- */
- void NPC_BSSniper_Patrol( void )
- {//FIXME: pick up on bodies of dead buddies?
- NPC->count = 0;
- if ( NPCInfo->confusionTime < level.time )
- {
- //Look for any enemies
- if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
- {
- if ( NPC_CheckPlayerTeamStealth() )
- {
- //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//Should be auto now
- //NPC_AngerSound();
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- }
- if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
- {
- //Is there danger nearby
- int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS );
- if ( NPC_CheckForDanger( alertEvent ) )
- {
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- else
- {//check for other alert events
- //There is an event to look at
- if ( alertEvent >= 0 )//&& level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID )
- {
- //NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID;
- if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED )
- {
- if ( level.alertEvents[alertEvent].owner &&
- level.alertEvents[alertEvent].owner->client &&
- level.alertEvents[alertEvent].owner->health >= 0 &&
- level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam )
- {//an enemy
- G_SetEnemy( NPC, level.alertEvents[alertEvent].owner );
- //NPCInfo->enemyLastSeenTime = level.time;
- TIMER_Set( NPC, "attackDelay", Q_irand( (6-NPCInfo->stats.aim)*100, (6-NPCInfo->stats.aim)*500 ) );
- }
- }
- else
- {//FIXME: get more suspicious over time?
- //Save the position for movement (if necessary)
- //FIXME: sound?
- VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal );
- NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 );
- if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS )
- {//suspicious looks longer
- NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 );
- }
- }
- }
- }
- if ( NPCInfo->investigateDebounceTime > level.time )
- {//FIXME: walk over to it, maybe? Not if not chase enemies flag
- //NOTE: stops walking or doing anything else below
- vec3_t dir, angles;
- float o_yaw, o_pitch;
-
- VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir );
- vectoangles( dir, angles );
-
- o_yaw = NPCInfo->desiredYaw;
- o_pitch = NPCInfo->desiredPitch;
- NPCInfo->desiredYaw = angles[YAW];
- NPCInfo->desiredPitch = angles[PITCH];
-
- NPC_UpdateAngles( qtrue, qtrue );
- NPCInfo->desiredYaw = o_yaw;
- NPCInfo->desiredPitch = o_pitch;
- return;
- }
- }
- }
- //If we have somewhere to go, then do that
- if ( UpdateGoal() )
- {
- ucmd.buttons |= BUTTON_WALKING;
- NPC_MoveToGoal( qtrue );
- }
- NPC_UpdateAngles( qtrue, qtrue );
- }
- /*
- -------------------------
- NPC_BSSniper_Idle
- -------------------------
- */
- /*
- void NPC_BSSniper_Idle( void )
- {
- //reset our shotcount
- NPC->count = 0;
- //FIXME: check for other alert events?
- //Is there danger nearby?
- if ( NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue ) ) )
- {
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) );
- NPC_UpdateAngles( qtrue, qtrue );
- }
- */
- /*
- -------------------------
- ST_CheckMoveState
- -------------------------
- */
- static void Sniper_CheckMoveState( void )
- {
- //See if we're a scout
- if ( !(NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )//NPCInfo->behaviorState == BS_STAND_AND_SHOOT )
- {
- if ( NPCInfo->goalEntity == NPC->enemy )
- {
- move = qfalse;
- return;
- }
- }
- //See if we're running away
- else if ( NPCInfo->squadState == SQUAD_RETREAT )
- {
- if ( TIMER_Done( NPC, "flee" ) )
- {
- NPCInfo->squadState = SQUAD_IDLE;
- }
- else
- {
- faceEnemy = qfalse;
- }
- }
- else if ( NPCInfo->squadState == SQUAD_IDLE )
- {
- if ( !NPCInfo->goalEntity )
- {
- move = qfalse;
- return;
- }
- }
- if ( !TIMER_Done( NPC, "taunting" ) )
- {//no move while taunting
- move = qfalse;
- return;
- }
- //See if we're moving towards a goal, not the enemy
- if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) )
- {
- //Did we make it?
- if ( STEER::Reached(NPC, NPCInfo->goalEntity, 16, !!FlyingCreature(NPC)) ||
- ( NPCInfo->squadState == SQUAD_SCOUT && enemyLOS && enemyDist <= 10000 ) )
- {
- int newSquadState = SQUAD_STAND_AND_SHOOT;
- //we got where we wanted to go, set timers based on why we were running
- switch ( NPCInfo->squadState )
- {
- case SQUAD_RETREAT://was running away
- if ( NPC->client->NPC_class == CLASS_SABOTEUR )
- {
- Saboteur_Cloak( NPC );
- }
- TIMER_Set( NPC, "duck", (NPC->max_health - NPC->health) * 100 );
- TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) );
- newSquadState = SQUAD_COVER;
- break;
- case SQUAD_TRANSITION://was heading for a combat point
- TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) );
- break;
- case SQUAD_SCOUT://was running after player
- break;
- default:
- break;
- }
- NPC_ReachedGoal();
- //don't attack right away
- TIMER_Set( NPC, "attackDelay", Q_irand( (6-NPCInfo->stats.aim)*50, (6-NPCInfo->stats.aim)*100 ) ); //FIXME: Slant for difficulty levels, too?
- //don't do something else just yet
- TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) );
- //stop fleeing
- if ( NPCInfo->squadState == SQUAD_RETREAT )
- {
- TIMER_Set( NPC, "flee", -level.time );
- NPCInfo->squadState = SQUAD_IDLE;
- }
- return;
- }
- //keep going, hold of roamTimer until we get there
- TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) );
- }
- }
- static void Sniper_ResolveBlockedShot( void )
- {
- if ( TIMER_Done( NPC, "duck" ) )
- {//we're not ducking
- if ( TIMER_Done( NPC, "roamTime" ) )
- {//not roaming
- //FIXME: try to find another spot from which to hit the enemy
- if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && (!NPCInfo->goalEntity || NPCInfo->goalEntity == NPC->enemy) )
- {//we were running after enemy
- //Try to find a combat point that can hit the enemy
- int cpFlags = (CP_CLEAR|CP_HAS_ROUTE);
- if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST )
- {
- cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST);
- cpFlags |= CP_NEAREST;
- }
- int cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->currentOrigin, cpFlags, 32 );
- if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) )
- {//okay, try one by the enemy
- cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->enemy->currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32 );
- }
- //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him...
- if ( cp != -1 )
- {//found a combat point that has a clear shot to enemy
- NPC_SetCombatPoint( cp );
- NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp );
- TIMER_Set( NPC, "duck", -1 );
- if ( NPC->client->NPC_class == CLASS_SABOTEUR )
- {
- Saboteur_Decloak( NPC );
- }
- TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000 ) );
- return;
- }
- }
- }
- }
- /*
- else
- {//maybe we should stand
- if ( TIMER_Done( NPC, "stand" ) )
- {//stand for as long as we'll be here
- TIMER_Set( NPC, "stand", Q_irand( 500, 2000 ) );
- return;
- }
- }
- //Hmm, can't resolve this by telling them to duck or telling me to stand
- //We need to move!
- TIMER_Set( NPC, "roamTime", -1 );
- TIMER_Set( NPC, "stick", -1 );
- TIMER_Set( NPC, "duck", -1 );
- TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000 ) );
- */
- }
- /*
- -------------------------
- ST_CheckFireState
- -------------------------
- */
- static void Sniper_CheckFireState( void )
- {
- if ( enemyCS )
- {//if have a clear shot, always try
- return;
- }
- if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT )
- {//runners never try to fire at the last pos
- return;
- }
- if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) )
- {//if moving at all, don't do this
- return;
- }
- if ( !TIMER_Done( NPC, "taunting" ) )
- {//no shoot while taunting
- return;
- }
- //continue to fire on their last position
- if ( !Q_irand( 0, 1 )
- && NPCInfo->enemyLastSeenTime
- && level.time - NPCInfo->enemyLastSeenTime < ((5-NPCInfo->stats.aim)*1000) )//FIXME: incorporate skill too?
- {
- if ( !VectorCompare( vec3_origin, NPCInfo->enemyLastSeenLocation ) )
- {
- //Fire on the last known position
- vec3_t muzzle, dir, angles;
- CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
- VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir );
- VectorNormalize( dir );
- vectoangles( dir, angles );
- NPCInfo->desiredYaw = angles[YAW];
- NPCInfo->desiredPitch = angles[PITCH];
- shoot = qtrue;
- //faceEnemy = qfalse;
- }
- return;
- }
- else if ( level.time - NPCInfo->enemyLastSeenTime > 10000 )
- {//next time we see him, we'll miss few times first
- NPC->count = 0;
- }
- }
- qboolean Sniper_EvaluateShot( int hit )
- {
- if ( !NPC->enemy )
- {
- return qfalse;
- }
- gentity_t *hitEnt = &g_entities[hit];
- if ( hit == NPC->enemy->s.number
- || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam )
- || ( hitEnt && hitEnt->takedamage && ((hitEnt->svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) )
- || ( hitEnt && (hitEnt->svFlags&SVF_GLASS_BRUSH)) )
- {//can hit enemy or will hit glass, so shoot anyway
- return qtrue;
- }
- return qfalse;
- }
- void Sniper_FaceEnemy( void )
- {
- //FIXME: the ones behind kill holes are facing some arbitrary direction and not firing
- //FIXME: If actually trying to hit enemy, don't fire unless enemy is at least in front of me?
- //FIXME: need to give designers option to make them not miss first few shots
- if ( NPC->enemy )
- {
- vec3_t muzzle, target, angles, forward, right, up;
- //Get the positions
- AngleVectors( NPC->client->ps.viewangles, forward, right, up );
- CalcMuzzlePoint( NPC, forward, right, up, muzzle, 0 );
- //CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
- CalcEntitySpot( NPC->enemy, SPOT_ORIGIN, target );
- if ( enemyDist > 65536 && NPCInfo->stats.aim < 5 )//is 256 squared, was 16384 (128*128)
- {
- if ( NPC->count < (5-NPCInfo->stats.aim) )
- {//miss a few times first
- if ( shoot && TIMER_Done( NPC, "attackDelay" ) && level.time >= NPCInfo->shotTime )
- {//ready to fire again
- qboolean aimError = qfalse;
- qboolean hit = qtrue;
- int tryMissCount = 0;
- trace_t trace;
- GetAnglesForDirection( muzzle, target, angles );
- AngleVectors( angles, forward, right, up );
- while ( hit && tryMissCount < 10 )
- {
- tryMissCount++;
- if ( !Q_irand( 0, 1 ) )
- {
- aimError = qtrue;
- if ( !Q_irand( 0, 1 ) )
- {
- VectorMA( target, NPC->enemy->maxs[2]*Q_flrand(1.5, 4), right, target );
- }
- else
- {
- VectorMA( target, NPC->enemy->mins[2]*Q_flrand(1.5, 4), right, target );
- }
- }
- if ( !aimError || !Q_irand( 0, 1 ) )
- {
- if ( !Q_irand( 0, 1 ) )
- {
- VectorMA( target, NPC->enemy->maxs[2]*Q_flrand(1.5, 4), up, target );
- }
- else
- {
- VectorMA( target, NPC->enemy->mins[2]*Q_flrand(1.5, 4), up, target );
- }
- }
- gi.trace( &trace, muzzle, vec3_origin, vec3_origin, target, NPC->s.number, MASK_SHOT );
- hit = Sniper_EvaluateShot( trace.entityNum );
- }
- NPC->count++;
- }
- else
- {
- if ( !enemyLOS )
- {
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- }
- }
- else
- {//based on distance, aim value, difficulty and enemy movement, miss
- //FIXME: incorporate distance as a factor?
- int missFactor = 8-(NPCInfo->stats.aim+g_spskill->integer) * 3;
- if ( missFactor > ENEMY_POS_LAG_STEPS )
- {
- missFactor = ENEMY_POS_LAG_STEPS;
- }
- else if ( missFactor < 0 )
- {//???
- missFactor = 0 ;
- }
- VectorCopy( NPCInfo->enemyLaggedPos[missFactor], target );
- }
- GetAnglesForDirection( muzzle, target, angles );
- }
- else
- {
- target[2] += Q_flrand( 0, NPC->enemy->maxs[2] );
- //CalcEntitySpot( NPC->enemy, SPOT_HEAD_LEAN, target );
- GetAnglesForDirection( muzzle, target, angles );
- }
- NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] );
- NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] );
- }
- NPC_UpdateAngles( qtrue, qtrue );
- }
- void Sniper_UpdateEnemyPos( void )
- {
- int index;
- for ( int i = MAX_ENEMY_POS_LAG-ENEMY_POS_LAG_INTERVAL; i >= 0; i -= ENEMY_POS_LAG_INTERVAL )
- {
- index = i/ENEMY_POS_LAG_INTERVAL;
- if ( !index )
- {
- CalcEntitySpot( NPC->enemy, SPOT_HEAD_LEAN, NPCInfo->enemyLaggedPos[index] );
- NPCInfo->enemyLaggedPos[index][2] -= Q_flrand( 2, 16 );
- }
- else
- {
- VectorCopy( NPCInfo->enemyLaggedPos[index-1], NPCInfo->enemyLaggedPos[index] );
- }
- }
- }
- /*
- -------------------------
- NPC_BSSniper_Attack
- -------------------------
- */
- void Sniper_StartHide( void )
- {
- int duckTime = Q_irand( 2000, 5000 );
-
- TIMER_Set( NPC, "duck", duckTime );
- if ( NPC->client->NPC_class == CLASS_SABOTEUR )
- {
- Saboteur_Cloak( NPC );
- }
- TIMER_Set( NPC, "watch", 500 );
- TIMER_Set( NPC, "attackDelay", duckTime + Q_irand( 500, 2000 ) );
- }
- void NPC_BSSniper_Attack( void )
- {
- //Don't do anything if we're hurt
- if ( NPC->painDebounceTime > level.time )
- {
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- //NPC_CheckEnemy( qtrue, qfalse );
- //If we don't have an enemy, just idle
- if ( NPC_CheckEnemyExt() == qfalse )//!NPC->enemy )//
- {
- NPC_BSSniper_Patrol();//FIXME: or patrol?
- return;
- }
- if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) )
- {//going to run
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- if ( !NPC->enemy )
- {//WTF? somehow we lost our enemy?
- NPC_BSSniper_Patrol();//FIXME: or patrol?
- return;
- }
- enemyLOS = enemyCS = qfalse;
- move = qtrue;
- faceEnemy = qfalse;
- shoot = qfalse;
- enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
- if ( enemyDist < 16384 )//128 squared
- {//too close, so switch to primary fire
- if ( NPC->client->ps.weapon == WP_DISRUPTOR
- || NPC->client->ps.weapon == WP_TUSKEN_RIFLE )
- {//sniping... should be assumed
- if ( NPCInfo->scriptFlags & SCF_ALT_FIRE )
- {//use primary fire
- trace_t trace;
- gi.trace ( &trace, NPC->enemy->currentOrigin, NPC->enemy->mins, NPC->enemy->maxs, NPC->currentOrigin, NPC->enemy->s.number, NPC->enemy->clipmask );
- if ( !trace.allsolid && !trace.startsolid && (trace.fraction == 1.0 || trace.entityNum == NPC->s.number ) )
- {//he can get right to me
- NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
- //reset fire-timing variables
- NPC_ChangeWeapon( NPC->client->ps.weapon );
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- }
- //FIXME: switch back if he gets far away again?
- }
- }
- else if ( enemyDist > 65536 )//256 squared
- {
- if ( NPC->client->ps.weapon == WP_DISRUPTOR
- || NPC->client->ps.weapon == WP_TUSKEN_RIFLE )
- {//sniping... should be assumed
- if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
- {//use primary fire
- NPCInfo->scriptFlags |= SCF_ALT_FIRE;
- //reset fire-timing variables
- NPC_ChangeWeapon( NPC->client->ps.weapon );
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- }
- }
- Sniper_UpdateEnemyPos();
- //can we see our target?
- if ( NPC_ClearLOS( NPC->enemy ) )//|| (NPCInfo->stats.aim >= 5 && gi.inPVS( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin )) )
- {
- NPCInfo->enemyLastSeenTime = level.time;
- VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation );
- enemyLOS = qtrue;
- float maxShootDist = NPC_MaxDistSquaredForWeapon();
- if ( enemyDist < maxShootDist )
- {
- vec3_t fwd, right, up, muzzle, end;
- trace_t tr;
- AngleVectors( NPC->client->ps.viewangles, fwd, right, up );
- CalcMuzzlePoint( NPC, fwd, right, up, muzzle, 0 );
- VectorMA( muzzle, 8192, fwd, end );
- gi.trace ( &tr, muzzle, NULL, NULL, end, NPC->s.number, MASK_SHOT, G2_RETURNONHIT, 0 );
- int hit = tr.entityNum;
- //can we shoot our target?
- if ( Sniper_EvaluateShot( hit ) )
- {
- enemyCS = qtrue;
- }
- }
- }
- /*
- else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) )
- {
- NPCInfo->enemyLastSeenTime = level.time;
- faceEnemy = qtrue;
- }
- */
- if ( enemyLOS )
- {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot?
- faceEnemy = qtrue;
- }
- if ( !TIMER_Done( NPC, "taunting" ) )
- {
- move = qfalse;
- shoot = qfalse;
- }
- else if ( enemyCS )
- {
- shoot = qtrue;
- }
- else if ( level.time - NPCInfo->enemyLastSeenTime > 3000 )
- {//Hmm, have to get around this bastard... FIXME: this NPCInfo->enemyLastSeenTime builds up when ducked seems to make them want to run when they uncrouch
- Sniper_ResolveBlockedShot();
- }
- else if ( NPC->client->ps.weapon == WP_TUSKEN_RIFLE && !Q_irand( 0, 100 ) )
- {//start a taunt
- NPC_Tusken_Taunt();
- TIMER_Set( NPC, "duck", -1 );
- move = qfalse;
- }
- //Check for movement to take care of
- Sniper_CheckMoveState();
- //See if we should override shooting decision with any special considerations
- Sniper_CheckFireState();
- if ( move )
- {//move toward goal
- if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist > 10000 ) )//100 squared
- {
- move = Sniper_Move();
- }
- else
- {
- move = qfalse;
- }
- }
- if ( !move )
- {
- if ( !TIMER_Done( NPC, "duck" ) )
- {
- if ( TIMER_Done( NPC, "watch" ) )
- {//not while watching
- ucmd.upmove = -127;
- if ( NPC->client->NPC_class == CLASS_SABOTEUR )
- {
- Saboteur_Cloak( NPC );
- }
- }
- }
- //FIXME: what about leaning?
- //FIXME: also, when stop ducking, start looking, if enemy can see me, chance of ducking back down again
- }
- else
- {//stop ducking!
- TIMER_Set( NPC, "duck", -1 );
- if ( NPC->client->NPC_class == CLASS_SABOTEUR )
- {
- Saboteur_Decloak( NPC );
- }
- }
- if ( TIMER_Done( NPC, "duck" )
- && TIMER_Done( NPC, "watch" )
- && (TIMER_Get( NPC, "attackDelay" )-level.time) > 1000
- && NPC->attackDebounceTime < level.time )
- {
- if ( enemyLOS && (NPCInfo->scriptFlags&SCF_ALT_FIRE) )
- {
- if ( NPC->fly_sound_debounce_time < level.time )
- {
- NPC->fly_sound_debounce_time = level.time + 2000;
- }
- }
- }
- if ( !faceEnemy )
- {//we want to face in the dir we're running
- if ( move )
- {//don't run away and shoot
- NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW];
- NPCInfo->desiredPitch = 0;
- shoot = qfalse;
- }
- NPC_UpdateAngles( qtrue, qtrue );
- }
- else// if ( faceEnemy )
- {//face the enemy
- Sniper_FaceEnemy();
- }
- if ( NPCInfo->scriptFlags&SCF_DONT_FIRE )
- {
- shoot = qfalse;
- }
- //FIXME: don't shoot right away!
- if ( shoot )
- {//try to shoot if it's time
- if ( TIMER_Done( NPC, "attackDelay" ) )
- {
- WeaponThink( qtrue );
- if ( ucmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK) )
- {
- G_SoundOnEnt( NPC, CHAN_WEAPON, "sound/null.wav" );
- }
- //took a shot, now hide
- if ( !(NPC->spawnflags&SPF_NO_HIDE) && !Q_irand( 0, 1 ) )
- {
- //FIXME: do this if in combat point and combat point has duck-type cover... also handle lean-type cover
- Sniper_StartHide();
- }
- else
- {
- TIMER_Set( NPC, "attackDelay", NPCInfo->shotTime-level.time );
- }
- }
- }
- }
- void NPC_BSSniper_Default( void )
- {
- if( !NPC->enemy )
- {//don't have an enemy, look for one
- NPC_BSSniper_Patrol();
- }
- else//if ( NPC->enemy )
- {//have an enemy
- NPC_BSSniper_Attack();
- }
- }
|