codemp/game/NPC_behavior.c

Go to the documentation of this file.
00001 //NPC_behavior.cpp
00002 /*
00003 FIXME - MCG:
00004 These all need to make use of the snapshots.  Write something that can look for only specific
00005 things in a snapshot or just go through the snapshot every frame and save the info in case
00006 we need it...
00007 */
00008 
00009 #include "b_local.h"
00010 #include "g_nav.h"
00011 #include "../icarus/Q3_Interface.h"
00012 
00013 extern  qboolean        showBBoxes;
00014 extern vec3_t NPCDEBUG_BLUE;
00015 extern void G_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha );
00016 extern void NPC_CheckGetNewWeapon( void );
00017 
00018 #include "../namespace_begin.h"
00019 extern qboolean PM_InKnockDown( playerState_t *ps );
00020 #include "../namespace_end.h"
00021 
00022 extern void NPC_AimAdjust( int change );
00023 extern qboolean NPC_SomeoneLookingAtMe(gentity_t *ent);
00024 /*
00025  void NPC_BSAdvanceFight (void)
00026 
00027 Advance towards your captureGoal and shoot anyone you can along the way.
00028 */
00029 void NPC_BSAdvanceFight (void)
00030 {//FIXME: IMPLEMENT
00031 //Head to Goal if I can
00032 
00033         //Make sure we're still headed where we want to capture
00034         if ( NPCInfo->captureGoal )
00035         {//FIXME: if no captureGoal, what do we do?
00036                 //VectorCopy( NPCInfo->captureGoal->r.currentOrigin, NPCInfo->tempGoal->r.currentOrigin );
00037                 //NPCInfo->goalEntity = NPCInfo->tempGoal;
00038 
00039                 NPC_SetMoveGoal( NPC, NPCInfo->captureGoal->r.currentOrigin, 16, qtrue, -1, NULL );
00040 
00041 //              NAV_ClearLastRoute(NPC);
00042                 NPCInfo->goalTime = level.time + 100000;
00043         }
00044 
00045 //      NPC_BSRun();
00046 
00047         NPC_CheckEnemy(qtrue, qfalse, qtrue);
00048 
00049         //FIXME: Need melee code
00050         if( NPC->enemy )
00051         {//See if we can shoot him
00052                 vec3_t          delta, forward;
00053                 vec3_t          angleToEnemy;
00054                 vec3_t          hitspot, muzzle, diff, enemy_org, enemy_head;
00055                 float           distanceToEnemy;
00056                 qboolean        attack_ok = qfalse;
00057                 qboolean        dead_on = qfalse;
00058                 float           attack_scale = 1.0;
00059                 float           aim_off;
00060                 float           max_aim_off = 64;
00061 
00062                 //Yaw to enemy
00063                 VectorMA(NPC->enemy->r.absmin, 0.5, NPC->enemy->r.maxs, enemy_org);
00064                 CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
00065                 
00066                 VectorSubtract (enemy_org, muzzle, delta);
00067                 vectoangles ( delta, angleToEnemy );
00068                 distanceToEnemy = VectorNormalize(delta);
00069 
00070                 if(!NPC_EnemyTooFar(NPC->enemy, distanceToEnemy*distanceToEnemy, qtrue))
00071                 {
00072                         attack_ok = qtrue;
00073                 }
00074 
00075                 if(attack_ok)
00076                 {
00077                         NPC_UpdateShootAngles(angleToEnemy, qfalse, qtrue);
00078 
00079                         NPCInfo->enemyLastVisibility = enemyVisibility;
00080                         enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_FOV);//CHECK_360|//CHECK_PVS|
00081 
00082                         if(enemyVisibility == VIS_FOV)
00083                         {//He's in our FOV
00084                                 
00085                                 attack_ok = qtrue;
00086                                 CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_head);
00087 
00088                                 if(attack_ok)
00089                                 {
00090                                         trace_t         tr;
00091                                         gentity_t       *traceEnt;
00092                                         //are we gonna hit him if we shoot at his center?
00093                                         trap_Trace ( &tr, muzzle, NULL, NULL, enemy_org, NPC->s.number, MASK_SHOT );
00094                                         traceEnt = &g_entities[tr.entityNum];
00095                                         if( traceEnt != NPC->enemy &&
00096                                                 (!traceEnt || !traceEnt->client || !NPC->client->enemyTeam || NPC->client->enemyTeam != traceEnt->client->playerTeam) )
00097                                         {//no, so shoot for the head
00098                                                 attack_scale *= 0.75;
00099                                                 trap_Trace ( &tr, muzzle, NULL, NULL, enemy_head, NPC->s.number, MASK_SHOT );
00100                                                 traceEnt = &g_entities[tr.entityNum];
00101                                         }
00102 
00103                                         VectorCopy( tr.endpos, hitspot );
00104 
00105                                         if( traceEnt == NPC->enemy || (traceEnt->client && NPC->client->enemyTeam && NPC->client->enemyTeam == traceEnt->client->playerTeam) )
00106                                         {
00107                                                 dead_on = qtrue;
00108                                         }
00109                                         else
00110                                         {
00111                                                 attack_scale *= 0.5;
00112                                                 if(NPC->client->playerTeam)
00113                                                 {
00114                                                         if(traceEnt && traceEnt->client && traceEnt->client->playerTeam)
00115                                                         {
00116                                                                 if(NPC->client->playerTeam == traceEnt->client->playerTeam)
00117                                                                 {//Don't shoot our own team
00118                                                                         attack_ok = qfalse;
00119                                                                 }
00120                                                         }
00121                                                 }
00122                                         }
00123                                 }
00124 
00125                                 if( attack_ok )
00126                                 {
00127                                         //ok, now adjust pitch aim
00128                                         VectorSubtract (hitspot, muzzle, delta);
00129                                         vectoangles ( delta, angleToEnemy );
00130                                         NPC->NPC->desiredPitch = angleToEnemy[PITCH];
00131                                         NPC_UpdateShootAngles(angleToEnemy, qtrue, qfalse);
00132 
00133                                         if( !dead_on )
00134                                         {//We're not going to hit him directly, try a suppressing fire
00135                                                 //see if where we're going to shoot is too far from his origin
00136                                                 AngleVectors (NPCInfo->shootAngles, forward, NULL, NULL);
00137                                                 VectorMA ( muzzle, distanceToEnemy, forward, hitspot);
00138                                                 VectorSubtract(hitspot, enemy_org, diff);
00139                                                 aim_off = VectorLength(diff);
00140                                                 if(aim_off > random() * max_aim_off)//FIXME: use aim value to allow poor aim?
00141                                                 {
00142                                                         attack_scale *= 0.75;
00143                                                         //see if where we're going to shoot is too far from his head
00144                                                         VectorSubtract(hitspot, enemy_head, diff);
00145                                                         aim_off = VectorLength(diff);
00146                                                         if(aim_off > random() * max_aim_off)
00147                                                         {
00148                                                                 attack_ok = qfalse;
00149                                                         }
00150                                                 }
00151                                                 attack_scale *= (max_aim_off - aim_off + 1)/max_aim_off;
00152                                         }
00153                                 }
00154                         }
00155                 }
00156 
00157                 if( attack_ok )
00158                 {
00159                         if( NPC_CheckAttack( attack_scale ))
00160                         {//check aggression to decide if we should shoot
00161                                 enemyVisibility = VIS_SHOOT;
00162                                 WeaponThink(qtrue);
00163                         }
00164                         else
00165                                 attack_ok = qfalse;
00166                 }
00167 //Don't do this- only for when stationary and trying to shoot an enemy
00168 //              else
00169 //                      NPC->cantHitEnemyCounter++;
00170         }
00171         else
00172         {//FIXME: 
00173                 NPC_UpdateShootAngles(NPC->client->ps.viewangles, qtrue, qtrue);
00174         }
00175 
00176         if(!ucmd.forwardmove && !ucmd.rightmove)
00177         {//We reached our captureGoal
00178                 if(trap_ICARUS_IsInitialized(NPC->s.number))
00179                 {
00180                         trap_ICARUS_TaskIDComplete( NPC, TID_BSTATE );
00181                 }
00182         }
00183 }
00184 
00185 void Disappear(gentity_t *self)
00186 {
00187 //      ClientDisconnect(self);
00188         self->s.eFlags |= EF_NODRAW;
00189         self->think = 0;
00190         self->nextthink = -1;
00191 }
00192 
00193 void MakeOwnerInvis (gentity_t *self);
00194 void BeamOut (gentity_t *self)
00195 {
00196 //      gentity_t *tent = G_Spawn();
00197         
00198 /*
00199         tent->owner = self;
00200         tent->think = MakeOwnerInvis;
00201         tent->nextthink = level.time + 1800;
00202         //G_AddEvent( ent, EV_PLAYER_TELEPORT, 0 );
00203         tent = G_TempEntity( self->client->pcurrentOrigin, EV_PLAYER_TELEPORT );
00204 */
00205         //fixme: doesn't actually go away!
00206         self->nextthink = level.time + 1500;
00207         self->think = Disappear;
00208         self->client->squadname = NULL;
00209         self->client->playerTeam = self->s.teamowner = TEAM_FREE;
00210         //self->r.svFlags |= SVF_BEAMING; //this appears unused in SP as well
00211 }
00212 
00213 void NPC_BSCinematic( void ) 
00214 {
00215 
00216         if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON )
00217         {
00218                 WeaponThink( qtrue );
00219         }
00220 
00221         if ( UpdateGoal() )
00222         {//have a goalEntity
00223                 //move toward goal, should also face that goal
00224                 NPC_MoveToGoal( qtrue );
00225         }
00226 
00227         if ( NPCInfo->watchTarget )
00228         {//have an entity which we want to keep facing
00229                 //NOTE: this will override any angles set by NPC_MoveToGoal
00230                 vec3_t eyes, viewSpot, viewvec, viewangles;
00231 
00232                 CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes );
00233                 CalcEntitySpot( NPCInfo->watchTarget, SPOT_HEAD_LEAN, viewSpot );
00234 
00235                 VectorSubtract( viewSpot, eyes, viewvec );
00236                 
00237                 vectoangles( viewvec, viewangles );
00238 
00239                 NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw = viewangles[YAW];
00240                 NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch = viewangles[PITCH];
00241         }
00242 
00243         NPC_UpdateAngles( qtrue, qtrue );
00244 }
00245 
00246 void NPC_BSWait( void ) 
00247 {
00248         NPC_UpdateAngles( qtrue, qtrue );
00249 }
00250 
00251 
00252 void NPC_BSInvestigate (void)
00253 {
00254 /*
00255         //FIXME: maybe allow this to be set as a tempBState in a script?  Just specify the
00256         //investigateGoal, investigateDebounceTime and investigateCount? (Needs a macro)
00257         vec3_t          invDir, invAngles, spot;
00258         gentity_t       *saveGoal;
00259         //BS_INVESTIGATE would turn toward goal, maybe take a couple steps towards it,
00260         //look for enemies, then turn away after your investigate counter was down-
00261         //investigate counter goes up every time you set it...
00262 
00263         if(level.time > NPCInfo->enemyCheckDebounceTime)
00264         {
00265                 NPCInfo->enemyCheckDebounceTime = level.time + (NPCInfo->stats.vigilance * 1000);
00266                 NPC_CheckEnemy(qtrue, qfalse);
00267                 if(NPC->enemy)
00268                 {//FIXME: do anger script
00269                         NPCInfo->goalEntity = NPC->enemy;
00270 //                      NAV_ClearLastRoute(NPC);
00271                         NPCInfo->behaviorState = BS_RUN_AND_SHOOT;
00272                         NPCInfo->tempBehavior = BS_DEFAULT;
00273                         NPC_AngerSound();
00274                         return;
00275                 }
00276         }
00277 
00278         NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_WEAPONREADY3, SETANIM_FLAG_NORMAL );
00279 
00280         if(NPCInfo->stats.vigilance <= 1.0 && NPCInfo->eventOwner)
00281         {
00282                 VectorCopy(NPCInfo->eventOwner->r.currentOrigin, NPCInfo->investigateGoal);
00283         }
00284 
00285         saveGoal = NPCInfo->goalEntity;
00286         if(     level.time > NPCInfo->walkDebounceTime )
00287         {
00288                 vec3_t  vec;
00289 
00290                 VectorSubtract(NPCInfo->investigateGoal, NPC->r.currentOrigin, vec);
00291                 vec[2] = 0;
00292                 if(VectorLength(vec) > 64)
00293                 {
00294                         if(Q_irand(0, 100) < NPCInfo->investigateCount)
00295                         {//take a full step
00296                                 //NPCInfo->walkDebounceTime = level.time + 1400;
00297                                 //actually finds length of my BOTH_WALK anim
00298                                 NPCInfo->walkDebounceTime = PM_AnimLength( NPC->client->clientInfo.animFileIndex, BOTH_WALK1 );
00299                         }
00300                 }
00301         }
00302 
00303         if(     level.time < NPCInfo->walkDebounceTime )
00304         {//walk toward investigateGoal
00305                 
00306                 /*
00307                 NPCInfo->goalEntity = NPCInfo->tempGoal;
00308 //              NAV_ClearLastRoute(NPC);
00309                 VectorCopy(NPCInfo->investigateGoal, NPCInfo->tempGoal->r.currentOrigin);
00310                 */
00311 
00312 /*              NPC_SetMoveGoal( NPC, NPCInfo->investigateGoal, 16, qtrue );
00313 
00314                 NPC_MoveToGoal( qtrue );
00315 
00316                 //FIXME: walk2?
00317                 NPC_SetAnim(NPC,SETANIM_LEGS,BOTH_WALK1,SETANIM_FLAG_NORMAL);
00318 
00319                 ucmd.buttons |= BUTTON_WALKING;
00320         }
00321         else
00322         {
00323 
00324                 NPC_SetAnim(NPC,SETANIM_LEGS,BOTH_STAND1,SETANIM_FLAG_NORMAL);
00325 
00326                 if(NPCInfo->hlookCount > 30)
00327                 {
00328                         if(Q_irand(0, 10) > 7) 
00329                         {
00330                                 NPCInfo->hlookCount = 0;
00331                         }
00332                 }
00333                 else if(NPCInfo->hlookCount < -30)
00334                 {
00335                         if(Q_irand(0, 10) > 7) 
00336                         {
00337                                 NPCInfo->hlookCount = 0;
00338                         }
00339                 }
00340                 else if(NPCInfo->hlookCount == 0)
00341                 {
00342                         NPCInfo->hlookCount = Q_irand(-1, 1);
00343                 }
00344                 else if(Q_irand(0, 10) > 7) 
00345                 {
00346                         if(NPCInfo->hlookCount > 0)
00347                         {
00348                                 NPCInfo->hlookCount++;
00349                         }
00350                         else//lookCount < 0
00351                         {
00352                                 NPCInfo->hlookCount--;
00353                         }
00354                 }
00355 
00356                 if(NPCInfo->vlookCount >= 15)
00357                 {
00358                         if(Q_irand(0, 10) > 7) 
00359                         {
00360                                 NPCInfo->vlookCount = 0;
00361                         }
00362                 }
00363                 else if(NPCInfo->vlookCount <= -15)
00364                 {
00365                         if(Q_irand(0, 10) > 7) 
00366                         {
00367                                 NPCInfo->vlookCount = 0;
00368                         }
00369                 }
00370                 else if(NPCInfo->vlookCount == 0)
00371                 {
00372                         NPCInfo->vlookCount = Q_irand(-1, 1);
00373                 }
00374                 else if(Q_irand(0, 10) > 8) 
00375                 {
00376                         if(NPCInfo->vlookCount > 0)
00377                         {
00378                                 NPCInfo->vlookCount++;
00379                         }
00380                         else//lookCount < 0
00381                         {
00382                                 NPCInfo->vlookCount--;
00383                         }
00384                 }
00385 
00386                 //turn toward investigateGoal
00387                 CalcEntitySpot( NPC, SPOT_HEAD, spot );
00388                 VectorSubtract(NPCInfo->investigateGoal, spot, invDir);
00389                 VectorNormalize(invDir);
00390                 vectoangles(invDir, invAngles);
00391                 NPCInfo->desiredYaw = AngleNormalize360(invAngles[YAW] + NPCInfo->hlookCount);
00392                 NPCInfo->desiredPitch = AngleNormalize360(invAngles[PITCH] + NPCInfo->hlookCount);
00393         }
00394 
00395         NPC_UpdateAngles(qtrue, qtrue);
00396 
00397         NPCInfo->goalEntity = saveGoal;
00398 //      NAV_ClearLastRoute(NPC);
00399 
00400         if(level.time > NPCInfo->investigateDebounceTime)
00401         {
00402                 NPCInfo->tempBehavior = BS_DEFAULT;
00403         }
00404 
00405         NPC_CheckSoundEvents();
00406         */
00407 }
00408 
00409 qboolean NPC_CheckInvestigate( int alertEventNum )
00410 {
00411         gentity_t       *owner = level.alertEvents[alertEventNum].owner;
00412         int             invAdd = level.alertEvents[alertEventNum].level;
00413         vec3_t  soundPos;
00414         float   soundRad = level.alertEvents[alertEventNum].radius;
00415         float   earshot = NPCInfo->stats.earshot;
00416 
00417         VectorCopy( level.alertEvents[alertEventNum].position, soundPos );
00418 
00419         //NOTE: Trying to preserve previous investigation behavior
00420         if ( !owner )
00421         {
00422                 return qfalse;
00423         }
00424 
00425         if ( owner->s.eType != ET_PLAYER && owner->s.eType != ET_NPC && owner == NPCInfo->goalEntity ) 
00426         {
00427                 return qfalse;
00428         }
00429 
00430         if ( owner->s.eFlags & EF_NODRAW ) 
00431         {
00432                 return qfalse;
00433         }
00434 
00435         if ( owner->flags & FL_NOTARGET ) 
00436         {
00437                 return qfalse;
00438         }
00439 
00440         if ( soundRad < earshot )
00441         {
00442                 return qfalse;
00443         }
00444 
00445         //if(!trap_InPVSIgnorePortals(ent->r.currentOrigin, NPC->r.currentOrigin))//should we be able to hear through areaportals?
00446         if ( !trap_InPVS( soundPos, NPC->r.currentOrigin ) )
00447         {//can hear through doors?
00448                 return qfalse;
00449         }
00450 
00451         if ( owner->client && owner->client->playerTeam && NPC->client->playerTeam && owner->client->playerTeam != NPC->client->playerTeam )
00452         {
00453                 if( (float)NPCInfo->investigateCount >= (NPCInfo->stats.vigilance*200) && owner )
00454                 {//If investigateCount == 10, just take it as enemy and go
00455                         if ( ValidEnemy( owner ) )
00456                         {//FIXME: run angerscript
00457                                 G_SetEnemy( NPC, owner );
00458                                 NPCInfo->goalEntity = NPC->enemy;
00459                                 NPCInfo->goalRadius = 12;
00460                                 NPCInfo->behaviorState = BS_HUNT_AND_KILL;
00461                                 return qtrue;
00462                         }
00463                 }
00464                 else
00465                 {
00466                         NPCInfo->investigateCount += invAdd;
00467                 }
00468                 //run awakescript
00469                 G_ActivateBehavior(NPC, BSET_AWAKE);
00470 
00471                 /*
00472                 if ( Q_irand(0, 10) > 7 )
00473                 {
00474                         NPC_AngerSound();
00475                 }
00476                 */
00477 
00478                 //NPCInfo->hlookCount = NPCInfo->vlookCount = 0;
00479                 NPCInfo->eventOwner = owner;
00480                 VectorCopy( soundPos, NPCInfo->investigateGoal );
00481                 if ( NPCInfo->investigateCount > 20 )
00482                 {
00483                         NPCInfo->investigateDebounceTime = level.time + 10000;
00484                 }
00485                 else
00486                 {
00487                         NPCInfo->investigateDebounceTime = level.time + (NPCInfo->investigateCount*500);
00488                 }
00489                 NPCInfo->tempBehavior = BS_INVESTIGATE;
00490                 return qtrue;
00491         }
00492 
00493         return qfalse;
00494 }
00495 
00496 
00497 /*
00498 void NPC_BSSleep( void ) 
00499 */
00500 void NPC_BSSleep( void ) 
00501 {
00502         int alertEvent = NPC_CheckAlertEvents( qtrue, qfalse, -1, qfalse, AEL_MINOR );
00503 
00504         //There is an event to look at
00505         if ( alertEvent >= 0 )
00506         {
00507                 G_ActivateBehavior(NPC, BSET_AWAKE);
00508                 return;
00509         }
00510 
00511         /*
00512         if ( level.time > NPCInfo->enemyCheckDebounceTime )
00513         {
00514                 if ( NPC_CheckSoundEvents() != -1 )
00515                 {//only 1 alert per second per 0.1 of vigilance
00516                         NPCInfo->enemyCheckDebounceTime = level.time + (NPCInfo->stats.vigilance * 10000);
00517                         G_ActivateBehavior(NPC, BSET_AWAKE);
00518                 }
00519         }
00520         */
00521 }
00522 
00523 extern qboolean NPC_MoveDirClear( int forwardmove, int rightmove, qboolean reset );
00524 void NPC_BSFollowLeader (void)
00525 {
00526         vec3_t          vec;
00527         float           leaderDist;
00528         visibility_t    leaderVis;
00529         int                     curAnim;
00530 
00531         if ( !NPC->client->leader )
00532         {//ok, stand guard until we find an enemy
00533                 if( NPCInfo->tempBehavior == BS_HUNT_AND_KILL )
00534                 {
00535                         NPCInfo->tempBehavior = BS_DEFAULT;
00536                 }
00537                 else
00538                 {
00539                         NPCInfo->tempBehavior = BS_STAND_GUARD;
00540                         NPC_BSStandGuard();
00541                 }
00542                 return;
00543         }
00544 
00545         if ( !NPC->enemy  )
00546         {//no enemy, find one
00547                 NPC_CheckEnemy( NPCInfo->confusionTime<level.time, qfalse, qtrue );//don't find new enemy if this is tempbehav
00548                 if ( NPC->enemy )
00549                 {//just found one
00550                         NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 );
00551                 }
00552                 else
00553                 {
00554                         if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
00555                         {
00556                                 int eventID = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_MINOR );
00557                                 if ( level.alertEvents[eventID].level >= AEL_SUSPICIOUS && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
00558                                 {
00559                                         NPCInfo->lastAlertID = level.alertEvents[eventID].ID;
00560                                         if ( !level.alertEvents[eventID].owner || 
00561                                                 !level.alertEvents[eventID].owner->client || 
00562                                                 level.alertEvents[eventID].owner->health <= 0 ||
00563                                                 level.alertEvents[eventID].owner->client->playerTeam != NPC->client->enemyTeam )
00564                                         {//not an enemy
00565                                         }
00566                                         else
00567                                         {
00568                                                 //FIXME: what if can't actually see enemy, don't know where he is... should we make them just become very alert and start looking for him?  Or just let combat AI handle this... (act as if you lost him)
00569                                                 G_SetEnemy( NPC, level.alertEvents[eventID].owner );
00570                                                 NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 );
00571                                                 NPCInfo->enemyLastSeenTime = level.time;
00572                                                 TIMER_Set( NPC, "attackDelay", Q_irand( 500, 1000 ) );
00573                                         }
00574                                 }
00575 
00576                         }
00577                 }
00578                 if ( !NPC->enemy )
00579                 {
00580                         if ( NPC->client->leader 
00581                                 && NPC->client->leader->enemy 
00582                                 && NPC->client->leader->enemy != NPC
00583                                 && ( (NPC->client->leader->enemy->client&&NPC->client->leader->enemy->client->playerTeam==NPC->client->enemyTeam)
00584                                         ||(/*NPC->client->leader->enemy->r.svFlags&SVF_NONNPC_ENEMY*/0&&NPC->client->leader->enemy->alliedTeam==NPC->client->enemyTeam) )
00585                                 && NPC->client->leader->enemy->health > 0 )
00586                         { //rwwFIXMEFIXME: use SVF_NONNPC_ENEMY?
00587                                 G_SetEnemy( NPC, NPC->client->leader->enemy );
00588                                 NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 );
00589                                 NPCInfo->enemyLastSeenTime = level.time;
00590                         }
00591                 }
00592         }
00593         else 
00594         {
00595                 if ( NPC->enemy->health <= 0 || (NPC->enemy->flags&FL_NOTARGET) )
00596                 {
00597                         G_ClearEnemy( NPC );
00598                         if ( NPCInfo->enemyCheckDebounceTime > level.time + 1000 )
00599                         {
00600                                 NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 1000, 2000 );
00601                         }
00602                 }
00603                 else if ( NPC->client->ps.weapon && NPCInfo->enemyCheckDebounceTime < level.time )
00604                 {
00605                         NPC_CheckEnemy( (NPCInfo->confusionTime<level.time||NPCInfo->tempBehavior!=BS_FOLLOW_LEADER), qfalse, qtrue );//don't find new enemy if this is tempbehav
00606                 }
00607         }
00608         
00609         if ( NPC->enemy && NPC->client->ps.weapon )
00610         {//If have an enemy, face him and fire
00611                 if ( NPC->client->ps.weapon == WP_SABER )//|| NPCInfo->confusionTime>level.time )
00612                 {//lightsaber user or charmed enemy
00613                         if ( NPCInfo->tempBehavior != BS_FOLLOW_LEADER )
00614                         {//not already in a temp bState
00615                                 //go after the guy
00616                                 NPCInfo->tempBehavior = BS_HUNT_AND_KILL;
00617                                 NPC_UpdateAngles(qtrue, qtrue);
00618                                 return;
00619                         }
00620                 }
00621 
00622                 enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_FOV|CHECK_SHOOT );//CHECK_360|CHECK_PVS|
00623                 if ( enemyVisibility > VIS_PVS )
00624                 {//face
00625                         vec3_t  enemy_org, muzzle, delta, angleToEnemy;
00626                         float   distanceToEnemy;
00627 
00628                         CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org );
00629                         NPC_AimWiggle( enemy_org );
00630 
00631                         CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
00632                         
00633                         VectorSubtract( enemy_org, muzzle, delta);
00634                         vectoangles( delta, angleToEnemy );
00635                         distanceToEnemy = VectorNormalize( delta );
00636 
00637                         NPCInfo->desiredYaw = angleToEnemy[YAW];
00638                         NPCInfo->desiredPitch = angleToEnemy[PITCH];
00639                         NPC_UpdateFiringAngles( qtrue, qtrue );
00640 
00641                         if ( enemyVisibility >= VIS_SHOOT )
00642                         {//shoot
00643                                 NPC_AimAdjust( 2 );
00644                                 if ( NPC_GetHFOVPercentage( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, NPCInfo->stats.hfov ) > 0.6f 
00645                                         && NPC_GetHFOVPercentage( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, NPCInfo->stats.vfov ) > 0.5f )
00646                                 {//actually withing our front cone
00647                                         WeaponThink( qtrue );
00648                                 }
00649                         }
00650                         else
00651                         {
00652                                 NPC_AimAdjust( 1 );
00653                         }
00654                         
00655                         //NPC_CheckCanAttack(1.0, qfalse);
00656                 }
00657                 else
00658                 {
00659                         NPC_AimAdjust( -1 );
00660                 }
00661         }
00662         else
00663         {//FIXME: combine with vector calc below
00664                 vec3_t  head, leaderHead, delta, angleToLeader;
00665 
00666                 CalcEntitySpot( NPC->client->leader, SPOT_HEAD, leaderHead );
00667                 CalcEntitySpot( NPC, SPOT_HEAD, head );
00668                 VectorSubtract (leaderHead, head, delta);
00669                 vectoangles ( delta, angleToLeader );
00670                 VectorNormalize(delta);
00671                 NPC->NPC->desiredYaw = angleToLeader[YAW];
00672                 NPC->NPC->desiredPitch = angleToLeader[PITCH];
00673                 
00674                 NPC_UpdateAngles(qtrue, qtrue);
00675         }
00676 
00677         //leader visible?
00678         leaderVis = NPC_CheckVisibility( NPC->client->leader, CHECK_PVS|CHECK_360|CHECK_SHOOT );//                      ent->e_UseFunc = useF_NULL;
00679 
00680 
00681         //Follow leader, stay within visibility and a certain distance, maintain a distance from.
00682         curAnim = NPC->client->ps.legsAnim;
00683         if ( curAnim != BOTH_ATTACK1 && curAnim != BOTH_ATTACK2 && curAnim != BOTH_ATTACK3 && curAnim != BOTH_MELEE1 && curAnim != BOTH_MELEE2 )
00684         {//Don't move toward leader if we're in a full-body attack anim
00685                 //FIXME, use IdealDistance to determine if we need to close distance
00686                 float   followDist = 96.0f;//FIXME:  If there are enmies, make this larger?
00687                 float   backupdist, walkdist, minrundist;
00688                 float   leaderHDist;
00689 
00690                 if ( NPCInfo->followDist )
00691                 {
00692                         followDist = NPCInfo->followDist;
00693                 }
00694                 backupdist = followDist/2.0f;
00695                 walkdist = followDist*0.83;
00696                 minrundist = followDist*1.33;
00697 
00698                 VectorSubtract(NPC->client->leader->r.currentOrigin, NPC->r.currentOrigin, vec);
00699                 leaderDist = VectorLength( vec );//FIXME: make this just nav distance?
00700                 //never get within their radius horizontally
00701                 vec[2] = 0;
00702                 leaderHDist = VectorLength( vec );
00703                 if( leaderHDist > backupdist && (leaderVis != VIS_SHOOT || leaderDist > walkdist) )
00704                 {//We should close in?
00705                         NPCInfo->goalEntity = NPC->client->leader;
00706 
00707                         NPC_SlideMoveToGoal();
00708                         if ( leaderVis == VIS_SHOOT && leaderDist < minrundist )
00709                         {
00710                                 ucmd.buttons |= BUTTON_WALKING;
00711                         }
00712                 }
00713                 else if ( leaderDist < backupdist )
00714                 {//We should back off?
00715                         NPCInfo->goalEntity = NPC->client->leader;
00716                         NPC_SlideMoveToGoal();
00717 
00718                         //reversing direction
00719                         ucmd.forwardmove = -ucmd.forwardmove;
00720                         ucmd.rightmove   = -ucmd.rightmove;
00721                         VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir );
00722                 }//otherwise, stay where we are
00723                 //check for do not enter and stop if there's one there...
00724                 if ( ucmd.forwardmove || ucmd.rightmove || VectorCompare( vec3_origin, NPC->client->ps.moveDir ) )
00725                 {
00726                         NPC_MoveDirClear( ucmd.forwardmove, ucmd.rightmove, qtrue );
00727                 }
00728         }
00729 }
00730 #define APEX_HEIGHT             200.0f
00731 #define PARA_WIDTH              (sqrt(APEX_HEIGHT)+sqrt(APEX_HEIGHT))
00732 #define JUMP_SPEED              200.0f
00733 void NPC_BSJump (void)
00734 {
00735         vec3_t          dir, angles, p1, p2, apex;
00736         float           time, height, forward, z, xy, dist, yawError, apexHeight;
00737 
00738         if( !NPCInfo->goalEntity )
00739         {//Should have task completed the navgoal
00740                 return;
00741         }
00742 
00743         if ( NPCInfo->jumpState != JS_JUMPING && NPCInfo->jumpState != JS_LANDING )
00744         {
00745                 //Face navgoal
00746                 VectorSubtract(NPCInfo->goalEntity->r.currentOrigin, NPC->r.currentOrigin, dir);
00747                 vectoangles(dir, angles);
00748                 NPCInfo->desiredPitch = NPCInfo->lockedDesiredPitch = AngleNormalize360(angles[PITCH]);
00749                 NPCInfo->desiredYaw = NPCInfo->lockedDesiredYaw = AngleNormalize360(angles[YAW]);
00750         }
00751 
00752         NPC_UpdateAngles ( qtrue, qtrue );
00753         yawError = AngleDelta ( NPC->client->ps.viewangles[YAW], NPCInfo->desiredYaw );
00754         //We don't really care about pitch here
00755 
00756         switch ( NPCInfo->jumpState )
00757         {
00758         case JS_FACING:
00759                 if ( yawError < MIN_ANGLE_ERROR )
00760                 {//Facing it, Start crouching
00761                         NPC_SetAnim(NPC, SETANIM_LEGS, BOTH_CROUCH1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
00762                         NPCInfo->jumpState = JS_CROUCHING;
00763                 }
00764                 break;
00765         case JS_CROUCHING:
00766                 if ( NPC->client->ps.legsTimer > 0 )
00767                 {//Still playing crouching anim
00768                         return;
00769                 }
00770 
00771                 //Create a parabola
00772 
00773                 if ( NPC->r.currentOrigin[2] > NPCInfo->goalEntity->r.currentOrigin[2] )
00774                 {
00775                         VectorCopy( NPC->r.currentOrigin, p1 );
00776                         VectorCopy( NPCInfo->goalEntity->r.currentOrigin, p2 );
00777                 }
00778                 else if ( NPC->r.currentOrigin[2] < NPCInfo->goalEntity->r.currentOrigin[2] )
00779                 {
00780                         VectorCopy( NPCInfo->goalEntity->r.currentOrigin, p1 );
00781                         VectorCopy( NPC->r.currentOrigin, p2 );
00782                 }
00783                 else
00784                 {
00785                         VectorCopy( NPC->r.currentOrigin, p1 );
00786                         VectorCopy( NPCInfo->goalEntity->r.currentOrigin, p2 );
00787                 }
00788 
00789                 //z = xy*xy
00790                 VectorSubtract( p2, p1, dir );
00791                 dir[2] = 0;
00792 
00793                 //Get xy and z diffs
00794                 xy = VectorNormalize( dir );
00795                 z = p1[2] - p2[2];
00796 
00797                 apexHeight = APEX_HEIGHT/2;
00798                 /*
00799                 //Determine most desirable apex height
00800                 apexHeight = (APEX_HEIGHT * PARA_WIDTH/xy) + (APEX_HEIGHT * z/128);
00801                 if ( apexHeight < APEX_HEIGHT * 0.5 )
00802                 {
00803                         apexHeight = APEX_HEIGHT*0.5;
00804                 }
00805                 else if ( apexHeight > APEX_HEIGHT * 2 )
00806                 {
00807                         apexHeight = APEX_HEIGHT*2;
00808                 }
00809                 */
00810 
00811                 //FIXME: length of xy will change curve of parabola, need to account for this
00812                 //somewhere... PARA_WIDTH
00813                 
00814                 z = (sqrt(apexHeight + z) - sqrt(apexHeight));
00815 
00816                 assert(z >= 0);
00817 
00818 //              Com_Printf("apex is %4.2f percent from p1: ", (xy-z)*0.5/xy*100.0f);
00819 
00820                 xy -= z;
00821                 xy *= 0.5;
00822                 
00823                 assert(xy > 0);
00824 
00825                 VectorMA( p1, xy, dir, apex );
00826                 apex[2] += apexHeight;
00827         
00828                 VectorCopy(apex, NPC->pos1);
00829                 
00830                 //Now we have the apex, aim for it
00831                 height = apex[2] - NPC->r.currentOrigin[2];
00832                 time = sqrt( height / ( .5 * NPC->client->ps.gravity ) );
00833                 if ( !time ) 
00834                 {
00835 //                      Com_Printf("ERROR no time in jump\n");
00836                         return;
00837                 }
00838 
00839                 // set s.origin2 to the push velocity
00840                 VectorSubtract ( apex, NPC->r.currentOrigin, NPC->client->ps.velocity );
00841                 NPC->client->ps.velocity[2] = 0;
00842                 dist = VectorNormalize( NPC->client->ps.velocity );
00843 
00844                 forward = dist / time;
00845                 VectorScale( NPC->client->ps.velocity, forward, NPC->client->ps.velocity );
00846 
00847                 NPC->client->ps.velocity[2] = time * NPC->client->ps.gravity;
00848 
00849 //              Com_Printf( "%s jumping %s, gravity at %4.0f percent\n", NPC->targetname, vtos(NPC->client->ps.velocity), NPC->client->ps.gravity/8.0f );
00850 
00851                 NPC->flags |= FL_NO_KNOCKBACK;
00852                 NPCInfo->jumpState = JS_JUMPING;
00853                 //FIXME: jumpsound?
00854                 break;
00855         case JS_JUMPING:
00856 
00857                 if ( showBBoxes )
00858                 {
00859                         VectorAdd(NPC->r.mins, NPC->pos1, p1);
00860                         VectorAdd(NPC->r.maxs, NPC->pos1, p2);
00861                         G_Cube( p1, p2, NPCDEBUG_BLUE, 0.5 );
00862                 }
00863 
00864                 if ( NPC->s.groundEntityNum != ENTITYNUM_NONE)
00865                 {//Landed, start landing anim
00866                         //FIXME: if the 
00867                         VectorClear(NPC->client->ps.velocity);
00868                         NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_LAND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
00869                         NPCInfo->jumpState = JS_LANDING;
00870                         //FIXME: landsound?
00871                 }
00872                 else if ( NPC->client->ps.legsTimer > 0 )
00873                 {//Still playing jumping anim
00874                         //FIXME: apply jump velocity here, a couple frames after start, not right away
00875                         return;
00876                 }
00877                 else
00878                 {//still in air, but done with jump anim, play inair anim
00879                         NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_INAIR1, SETANIM_FLAG_OVERRIDE);
00880                 }
00881                 break;
00882         case JS_LANDING:
00883                 if ( NPC->client->ps.legsTimer > 0 )
00884                 {//Still playing landing anim
00885                         return;
00886                 }
00887                 else
00888                 {
00889                         NPCInfo->jumpState = JS_WAITING;
00890 
00891                         
00892                         //task complete no matter what...  
00893                         NPC_ClearGoal();
00894                         NPCInfo->goalTime = level.time;
00895                         NPCInfo->aiFlags &= ~NPCAI_MOVING;
00896                         ucmd.forwardmove = 0;
00897                         NPC->flags &= ~FL_NO_KNOCKBACK;
00898                         //Return that the goal was reached
00899                         trap_ICARUS_TaskIDComplete( NPC, TID_MOVE_NAV );
00900                         
00901                         //Or should we keep jumping until reached goal?
00902                         
00903                         /*
00904                         NPCInfo->goalEntity = UpdateGoal();
00905                         if ( !NPCInfo->goalEntity )
00906                         {
00907                                 NPC->flags &= ~FL_NO_KNOCKBACK;
00908                                 Q3_TaskIDComplete( NPC, TID_MOVE_NAV );
00909                         }
00910                         */
00911                         
00912                 }
00913                 break;
00914         case JS_WAITING:
00915         default:
00916                 NPCInfo->jumpState = JS_FACING;
00917                 break;
00918         }
00919 }
00920 
00921 void NPC_BSRemove (void)
00922 {
00923         NPC_UpdateAngles ( qtrue, qtrue );
00924         if( !trap_InPVS( NPC->r.currentOrigin, g_entities[0].r.currentOrigin ) )//FIXME: use cg.vieworg?
00925         { //rwwFIXMEFIXME: Care about all clients instead of just 0?
00926                 G_UseTargets2( NPC, NPC, NPC->target3 );
00927                 NPC->s.eFlags |= EF_NODRAW;
00928                 NPC->s.eType = ET_INVISIBLE;
00929                 NPC->r.contents = 0;
00930                 NPC->health = 0;
00931                 NPC->targetname = NULL;
00932 
00933                 //Disappear in half a second
00934                 NPC->think = G_FreeEntity;
00935                 NPC->nextthink = level.time + FRAMETIME;
00936         }//FIXME: else allow for out of FOV???
00937 }
00938 
00939 void NPC_BSSearch (void)
00940 {
00941         NPC_CheckEnemy(qtrue, qfalse, qtrue);
00942         //Look for enemies, if find one:
00943         if ( NPC->enemy )
00944         {
00945                 if( NPCInfo->tempBehavior == BS_SEARCH )
00946                 {//if tempbehavior, set tempbehavior to default
00947                         NPCInfo->tempBehavior = BS_DEFAULT;
00948                 }
00949                 else
00950                 {//if bState, change to run and shoot
00951                         NPCInfo->behaviorState = BS_HUNT_AND_KILL;
00952                         NPC_BSRunAndShoot();
00953                 }
00954                 return;
00955         }
00956 
00957         //FIXME: what if our goalEntity is not NULL and NOT our tempGoal - they must
00958         //want us to do something else?  If tempBehavior, just default, else set
00959         //to run and shoot...?
00960 
00961         //FIXME: Reimplement
00962 
00963         if ( !NPCInfo->investigateDebounceTime )
00964         {//On our way to a tempGoal
00965                 float   minGoalReachedDistSquared = 32*32;
00966                 vec3_t  vec;
00967 
00968                 //Keep moving toward our tempGoal
00969                 NPCInfo->goalEntity = NPCInfo->tempGoal;
00970 
00971                 VectorSubtract ( NPCInfo->tempGoal->r.currentOrigin, NPC->r.currentOrigin, vec);
00972                 if ( vec[2] < 24 )
00973                 {
00974                         vec[2] = 0;
00975                 }
00976 
00977                 if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE )
00978                 {
00979                         /*
00980                         //FIXME: can't get the radius...
00981                         float   wpRadSq = waypoints[NPCInfo->tempGoal->waypoint].radius * waypoints[NPCInfo->tempGoal->waypoint].radius;
00982                         if ( minGoalReachedDistSquared > wpRadSq )
00983                         {
00984                                 minGoalReachedDistSquared = wpRadSq;
00985                         }
00986                         */
00987 
00988                         minGoalReachedDistSquared = 32*32;//12*12;
00989                 }
00990 
00991                 if ( VectorLengthSquared( vec ) < minGoalReachedDistSquared )
00992                 {
00993                         //Close enough, just got there
00994                         NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE );
00995 
00996                         if ( ( NPCInfo->homeWp == WAYPOINT_NONE ) || ( NPC->waypoint == WAYPOINT_NONE ) )
00997                         {
00998                                 //Heading for or at an invalid waypoint, get out of this bState
00999                                 if( NPCInfo->tempBehavior == BS_SEARCH )
01000                                 {//if tempbehavior, set tempbehavior to default
01001                                         NPCInfo->tempBehavior = BS_DEFAULT;
01002                                 }
01003                                 else
01004                                 {//if bState, change to stand guard
01005                                         NPCInfo->behaviorState = BS_STAND_GUARD;
01006                                         NPC_BSRunAndShoot();
01007                                 }
01008                                 return;
01009                         }
01010 
01011                         if ( NPC->waypoint == NPCInfo->homeWp )
01012                         {
01013                                 //Just Reached our homeWp, if this is the first time, run your lostenemyscript
01014                                 if ( NPCInfo->aiFlags & NPCAI_ENROUTE_TO_HOMEWP )
01015                                 {
01016                                         NPCInfo->aiFlags &= ~NPCAI_ENROUTE_TO_HOMEWP;
01017                                         G_ActivateBehavior( NPC, BSET_LOSTENEMY );
01018                                 }
01019 
01020                         }
01021 
01022                         //Com_Printf("Got there.\n");
01023                         //Com_Printf("Looking...");
01024                         if( !Q_irand(0, 1) )
01025                         {
01026                                 NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_LOOKAROUND1, SETANIM_FLAG_NORMAL);
01027                         }
01028                         else
01029                         {
01030                                 NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_IDLE1, SETANIM_FLAG_NORMAL);
01031                         }
01032                         NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000);
01033                 }
01034                 else
01035                 {
01036                         NPC_MoveToGoal( qtrue );
01037                 }
01038         }
01039         else
01040         {
01041                 //We're there
01042                 if ( NPCInfo->investigateDebounceTime > level.time )
01043                 {
01044                         //Still waiting around for a bit
01045                         //Turn angles every now and then to look around
01046                         if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE )
01047                         {
01048                                 if ( !Q_irand( 0, 30 ) )
01049                                 {
01050                                         int     numEdges = trap_Nav_GetNodeNumEdges( NPCInfo->tempGoal->waypoint );
01051 
01052                                         if ( numEdges != WAYPOINT_NONE )
01053                                         {
01054                                                 int branchNum = Q_irand( 0, numEdges - 1 );
01055 
01056                                                 vec3_t  branchPos, lookDir;
01057 
01058                                                 int nextWp = trap_Nav_GetNodeEdge( NPCInfo->tempGoal->waypoint, branchNum );
01059                                                 trap_Nav_GetNodePosition( nextWp, branchPos );
01060 
01061                                                 VectorSubtract( branchPos, NPCInfo->tempGoal->r.currentOrigin, lookDir );
01062                                                 NPCInfo->desiredYaw = AngleNormalize360( vectoyaw( lookDir ) + flrand( -45, 45 ) );
01063                                         }
01064 
01065                                         //pick an angle +-45 degrees off of the dir of a random branch
01066                                         //from NPCInfo->tempGoal->waypoint
01067                                         //int branch = Q_irand( 0, (waypoints[NPCInfo->tempGoal->waypoint].numNeighbors - 1) );
01068                                         //int   nextWp = waypoints[NPCInfo->tempGoal->waypoint].nextWaypoint[branch][NPC->client->moveType];
01069                                         //vec3_t        lookDir;
01070 
01071                                         //VectorSubtract( waypoints[nextWp].origin, NPCInfo->tempGoal->r.currentOrigin, lookDir );
01072                                         //Look in that direction +- 45 degrees
01073                                         //NPCInfo->desiredYaw = AngleNormalize360( vectoyaw( lookDir ) + Q_flrand( -45, 45 ) );
01074                                 }
01075                         }
01076                         //Com_Printf(".");
01077                 }
01078                 else
01079                 {//Just finished waiting
01080                         NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE );
01081                         
01082                         if ( NPC->waypoint == NPCInfo->homeWp )
01083                         {
01084                                 int     numEdges = trap_Nav_GetNodeNumEdges( NPCInfo->tempGoal->waypoint );
01085 
01086                                 if ( numEdges != WAYPOINT_NONE )
01087                                 {
01088                                         int branchNum = Q_irand( 0, numEdges - 1 );
01089 
01090                                         int nextWp = trap_Nav_GetNodeEdge( NPCInfo->homeWp, branchNum );
01091                                         trap_Nav_GetNodePosition( nextWp, NPCInfo->tempGoal->r.currentOrigin );
01092                                         NPCInfo->tempGoal->waypoint = nextWp;
01093                                 }
01094 
01095                                 /*
01096                                 //Pick a random branch
01097                                 int branch = Q_irand( 0, (waypoints[NPCInfo->homeWp].numNeighbors - 1) );
01098                                 int     nextWp = waypoints[NPCInfo->homeWp].nextWaypoint[branch][NPC->client->moveType];
01099 
01100                                 VectorCopy( waypoints[nextWp].origin, NPCInfo->tempGoal->r.currentOrigin );
01101                                 NPCInfo->tempGoal->waypoint = nextWp;
01102                                 //Com_Printf("\nHeading for wp %d...\n", waypoints[NPCInfo->homeWp].nextWaypoint[branch][NPC->client->moveType]);
01103                                 */
01104                         }
01105                         else
01106                         {//At a branch, so return home
01107                                 trap_Nav_GetNodePosition( NPCInfo->homeWp, NPCInfo->tempGoal->r.currentOrigin );
01108                                 NPCInfo->tempGoal->waypoint = NPCInfo->homeWp;
01109                                 /*
01110                                 VectorCopy( waypoints[NPCInfo->homeWp].origin, NPCInfo->tempGoal->r.currentOrigin );
01111                                 NPCInfo->tempGoal->waypoint = NPCInfo->homeWp;
01112                                 //Com_Printf("\nHeading for wp %d...\n", NPCInfo->homeWp);
01113                                 */
01114                         }
01115 
01116                         NPCInfo->investigateDebounceTime = 0;
01117                         //Start moving toward our tempGoal
01118                         NPCInfo->goalEntity = NPCInfo->tempGoal;
01119                         NPC_MoveToGoal( qtrue );
01120                 }
01121         }
01122 
01123         NPC_UpdateAngles( qtrue, qtrue );
01124 }
01125 
01126 /*
01127 -------------------------
01128 NPC_BSSearchStart
01129 -------------------------
01130 */
01131 
01132 void NPC_BSSearchStart( int homeWp, bState_t bState )
01133 {
01134         //FIXME: Reimplement
01135         if ( homeWp == WAYPOINT_NONE )
01136         {
01137                 homeWp = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE );
01138                 if( NPC->waypoint == WAYPOINT_NONE )
01139                 {
01140                         NPC->waypoint = homeWp;
01141                 }
01142         }
01143         NPCInfo->homeWp = homeWp;
01144         NPCInfo->tempBehavior = bState;
01145         NPCInfo->aiFlags |= NPCAI_ENROUTE_TO_HOMEWP;
01146         NPCInfo->investigateDebounceTime = 0;
01147         trap_Nav_GetNodePosition( homeWp, NPCInfo->tempGoal->r.currentOrigin );
01148         NPCInfo->tempGoal->waypoint = homeWp;
01149         //Com_Printf("\nHeading for wp %d...\n", NPCInfo->homeWp);
01150 }
01151 
01152 /*
01153 -------------------------
01154 NPC_BSNoClip
01155 
01156   Use in extreme circumstances only
01157 -------------------------
01158 */
01159 
01160 void NPC_BSNoClip ( void )
01161 {
01162         if ( UpdateGoal() )
01163         {
01164                 vec3_t  dir, forward, right, angles, up = {0, 0, 1};
01165                 float   fDot, rDot, uDot;
01166 
01167                 VectorSubtract( NPCInfo->goalEntity->r.currentOrigin, NPC->r.currentOrigin, dir );
01168                 
01169                 vectoangles( dir, angles );
01170                 NPCInfo->desiredYaw = angles[YAW];
01171 
01172                 AngleVectors( NPC->r.currentAngles, forward, right, NULL );
01173 
01174                 VectorNormalize( dir );
01175 
01176                 fDot = DotProduct(forward, dir) * 127;
01177                 rDot = DotProduct(right, dir) * 127;
01178                 uDot = DotProduct(up, dir) * 127;
01179 
01180                 ucmd.forwardmove = floor(fDot);
01181                 ucmd.rightmove = floor(rDot);
01182                 ucmd.upmove = floor(uDot);
01183         }
01184         else
01185         {
01186                 //Cut velocity?
01187                 VectorClear( NPC->client->ps.velocity );
01188         }
01189 
01190         NPC_UpdateAngles( qtrue, qtrue );
01191 }
01192 
01193 void NPC_BSWander (void)
01194 {//FIXME: don't actually go all the way to the next waypoint, just move in fits and jerks...?
01195         if ( !NPCInfo->investigateDebounceTime )
01196         {//Starting out
01197                 float   minGoalReachedDistSquared = 64;//32*32;
01198                 vec3_t  vec;
01199 
01200                 //Keep moving toward our tempGoal
01201                 NPCInfo->goalEntity = NPCInfo->tempGoal;
01202 
01203                 VectorSubtract ( NPCInfo->tempGoal->r.currentOrigin, NPC->r.currentOrigin, vec);
01204 
01205                 if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE )
01206                 {
01207                         minGoalReachedDistSquared = 64;
01208                 }
01209 
01210                 if ( VectorLengthSquared( vec ) < minGoalReachedDistSquared )
01211                 {
01212                         //Close enough, just got there
01213                         NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE );
01214 
01215                         if( !Q_irand(0, 1) )
01216                         {
01217                                 NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_LOOKAROUND1, SETANIM_FLAG_NORMAL);
01218                         }
01219                         else
01220                         {
01221                                 NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_IDLE1, SETANIM_FLAG_NORMAL);
01222                         }
01223                         //Just got here, so Look around for a while
01224                         NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000);
01225                 }
01226                 else
01227                 {
01228                         //Keep moving toward goal
01229                         NPC_MoveToGoal( qtrue );
01230                 }
01231         }
01232         else
01233         {
01234                 //We're there
01235                 if ( NPCInfo->investigateDebounceTime > level.time )
01236                 {
01237                         //Still waiting around for a bit
01238                         //Turn angles every now and then to look around
01239                         if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE )
01240                         {
01241                                 if ( !Q_irand( 0, 30 ) )
01242                                 {
01243                                         int     numEdges = trap_Nav_GetNodeNumEdges( NPCInfo->tempGoal->waypoint );
01244 
01245                                         if ( numEdges != WAYPOINT_NONE )
01246                                         {
01247                                                 int branchNum = Q_irand( 0, numEdges - 1 );
01248 
01249                                                 vec3_t  branchPos, lookDir;
01250 
01251                                                 int     nextWp = trap_Nav_GetNodeEdge( NPCInfo->tempGoal->waypoint, branchNum );
01252                                                 trap_Nav_GetNodePosition( nextWp, branchPos );
01253 
01254                                                 VectorSubtract( branchPos, NPCInfo->tempGoal->r.currentOrigin, lookDir );
01255                                                 NPCInfo->desiredYaw = AngleNormalize360( vectoyaw( lookDir ) + flrand( -45, 45 ) );
01256                                         }
01257                                 }
01258                         }
01259                 }
01260                 else
01261                 {//Just finished waiting
01262                         NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE );
01263                         
01264                         if ( NPC->waypoint != WAYPOINT_NONE )
01265                         {
01266                                 int     numEdges = trap_Nav_GetNodeNumEdges( NPC->waypoint );
01267 
01268                                 if ( numEdges != WAYPOINT_NONE )
01269                                 {
01270                                         int branchNum = Q_irand( 0, numEdges - 1 );
01271 
01272                                         int nextWp = trap_Nav_GetNodeEdge( NPC->waypoint, branchNum );
01273                                         trap_Nav_GetNodePosition( nextWp, NPCInfo->tempGoal->r.currentOrigin );
01274                                         NPCInfo->tempGoal->waypoint = nextWp;
01275                                 }
01276 
01277                                 NPCInfo->investigateDebounceTime = 0;
01278                                 //Start moving toward our tempGoal
01279                                 NPCInfo->goalEntity = NPCInfo->tempGoal;
01280                                 NPC_MoveToGoal( qtrue );
01281                         }
01282                 }
01283         }
01284 
01285         NPC_UpdateAngles( qtrue, qtrue );
01286 }
01287 
01288 /*
01289 void NPC_BSFaceLeader (void)
01290 {
01291         vec3_t  head, leaderHead, delta, angleToLeader;
01292 
01293         if ( !NPC->client->leader )
01294         {//uh.... okay.
01295                 return;
01296         }
01297 
01298         CalcEntitySpot( NPC->client->leader, SPOT_HEAD, leaderHead );
01299         CalcEntitySpot( NPC, SPOT_HEAD, head );
01300         VectorSubtract( leaderHead, head, delta );
01301         vectoangles( delta, angleToLeader );
01302         VectorNormalize( delta );
01303         NPC->NPC->desiredYaw = angleToLeader[YAW];
01304         NPC->NPC->desiredPitch = angleToLeader[PITCH];
01305         
01306         NPC_UpdateAngles(qtrue, qtrue);
01307 }
01308 */
01309 /*
01310 -------------------------
01311 NPC_BSFlee
01312 -------------------------
01313 */
01314 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
01315 extern void WP_DropWeapon( gentity_t *dropper, vec3_t velocity );
01316 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
01317 void NPC_Surrender( void )
01318 {//FIXME: say "don't shoot!" if we weren't already surrendering
01319         if ( NPC->client->ps.weaponTime || PM_InKnockDown( &NPC->client->ps ) )
01320         {
01321                 return;
01322         }
01323         if ( NPC->s.weapon != WP_NONE && 
01324                 NPC->s.weapon != WP_STUN_BATON &&
01325                 NPC->s.weapon != WP_SABER )
01326         {
01327                 //WP_DropWeapon( NPC, NULL ); //rwwFIXMEFIXME: Do this (gonna need a system for notifying client of removal)
01328         }
01329         if ( NPCInfo->surrenderTime < level.time - 5000 )
01330         {//haven't surrendered for at least 6 seconds, tell them what you're doing
01331                 //FIXME: need real dialogue EV_SURRENDER
01332                 NPCInfo->blockedSpeechDebounceTime = 0;//make sure we say this
01333                 G_AddVoiceEvent( NPC, Q_irand( EV_PUSHED1, EV_PUSHED3 ), 3000 );
01334         }
01335 //      NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_SURRENDER_START, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE );
01336 //      NPC->client->ps.torsoTimer = 1000;
01337         NPCInfo->surrenderTime = level.time + 1000;//stay surrendered for at least 1 second
01338         //FIXME: while surrendering, make a big sight/sound alert? Or G_AlertTeam?
01339 }
01340 
01341 qboolean NPC_CheckSurrender( void )
01342 {
01343         if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) 
01344                 && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE 
01345                 && !NPC->client->ps.weaponTime && !PM_InKnockDown( &NPC->client->ps )
01346                 && NPC->enemy && NPC->enemy->client && NPC->enemy->enemy == NPC && NPC->enemy->s.weapon != WP_NONE && NPC->enemy->s.weapon != WP_STUN_BATON 
01347                 && NPC->enemy->health > 20 && NPC->enemy->painDebounceTime < level.time - 3000 && NPC->enemy->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] < level.time - 1000 )
01348         {//don't surrender if scripted to run somewhere or if we're in the air or if we're busy or if we don't have an enemy or if the enemy is not mad at me or is hurt or not a threat or busy being attacked
01349                 //FIXME: even if not in a group, don't surrender if there are other enemies in the PVS and within a certain range?
01350                 if ( NPC->s.weapon != WP_ROCKET_LAUNCHER 
01351                         && NPC->s.weapon != WP_REPEATER
01352                         && NPC->s.weapon != WP_FLECHETTE
01353                         && NPC->s.weapon != WP_SABER )
01354                 {//jedi and heavy weapons guys never surrender
01355                         //FIXME: rework all this logic into some orderly fashion!!!
01356                         if ( NPC->s.weapon != WP_NONE )
01357                         {//they have a weapon so they'd have to drop it to surrender
01358                                 //don't give up unless low on health
01359                                 if ( NPC->health > 25 /*|| NPC->health >= NPC->max_health*/ )
01360                                 { //rwwFIXMEFIXME: Keep max health not a ps state?
01361                                         return qfalse;
01362                                 }
01363                                 //if ( g_crosshairEntNum == NPC->s.number && NPC->painDebounceTime > level.time )
01364                                 if (NPC_SomeoneLookingAtMe(NPC) && NPC->painDebounceTime > level.time)
01365                                 {//if he just shot me, always give up
01366                                         //fall through
01367                                 }
01368                                 else
01369                                 {//don't give up unless facing enemy and he's very close
01370                                         if ( !InFOV( NPC->enemy, NPC, 60, 30 ) )
01371                                         {//I'm not looking at them
01372                                                 return qfalse;
01373                                         }
01374                                         else if ( DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) < 65536/*256*256*/ )
01375                                         {//they're not close
01376                                                 return qfalse;
01377                                         }
01378                                         else if ( !trap_InPVS( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) )
01379                                         {//they're not in the same room
01380                                                 return qfalse;
01381                                         }
01382                                 }
01383                         }
01384                         //fixme: this logic keeps making npc's randomly surrender
01385                         /*
01386                         if ( NPCInfo->group && NPCInfo->group->numGroup <= 1 )
01387                         {//I'm alone but I was in a group//FIXME: surrender anyway if just melee or no weap?
01388                                 if ( NPC->s.weapon == WP_NONE 
01389                                         //NPC has a weapon
01390                                         || (NPC->enemy && NPC->enemy->s.number < MAX_CLIENTS)
01391                                         || (NPC->enemy->s.weapon == WP_SABER&&NPC->enemy->client&&!NPC->enemy->client->ps.saberHolstered)
01392                                         || (NPC->enemy->NPC && NPC->enemy->NPC->group && NPC->enemy->NPC->group->numGroup > 2) )
01393                                 {//surrender only if have no weapon or fighting a player or jedi or if we are outnumbered at least 3 to 1
01394                                         if ( (NPC->enemy && NPC->enemy->s.number < MAX_CLIENTS) )
01395                                         {//player is the guy I'm running from
01396                                                 //if ( g_crosshairEntNum == NPC->s.number )
01397                                                 if (NPC_SomeoneLookingAtMe(NPC))
01398                                                 {//give up if player is aiming at me
01399                                                         NPC_Surrender();
01400                                                         NPC_UpdateAngles( qtrue, qtrue );
01401                                                         return qtrue;
01402                                                 }
01403                                                 else if ( NPC->enemy->s.weapon == WP_SABER )
01404                                                 {//player is using saber
01405                                                         if ( InFOV( NPC, NPC->enemy, 60, 30 ) )
01406                                                         {//they're looking at me
01407                                                                 if ( DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) < 16384 )
01408                                                                 {//they're close
01409                                                                         if ( trap_InPVS( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) )
01410                                                                         {//they're in the same room
01411                                                                                 NPC_Surrender();
01412                                                                                 NPC_UpdateAngles( qtrue, qtrue );
01413                                                                                 return qtrue;
01414                                                                         }
01415                                                                 }
01416                                                         }
01417                                                 }
01418                                         }
01419                                         else if ( NPC->enemy )
01420                                         {//???
01421                                                 //should NPC's surrender to others?
01422                                                 if ( InFOV( NPC, NPC->enemy, 30, 30 ) )
01423                                                 {//they're looking at me
01424                                                         if ( DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) < 4096 )
01425                                                         {//they're close
01426                                                                 if ( trap_InPVS( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) )
01427                                                                 {//they're in the same room
01428                                                                         //FIXME: should player-team NPCs not fire on surrendered NPCs?
01429                                                                         NPC_Surrender();
01430                                                                         NPC_UpdateAngles( qtrue, qtrue );
01431                                                                         return qtrue;
01432                                                                 }
01433                                                         }
01434                                                 }
01435                                         }
01436                                 }
01437                         }
01438                         */
01439                 }
01440         }
01441         return qfalse;
01442 }
01443 
01444 void NPC_BSFlee( void )
01445 {//FIXME: keep checking for danger
01446         gentity_t *goal;
01447 
01448         if ( TIMER_Done( NPC, "flee" ) && NPCInfo->tempBehavior == BS_FLEE )
01449         {
01450                 NPCInfo->tempBehavior = BS_DEFAULT;
01451                 NPCInfo->squadState = SQUAD_IDLE;
01452                 //FIXME: should we set some timer to make him stay in this spot for a bit, 
01453                 //so he doesn't just suddenly turn around and come back at the enemy?
01454                 //OR, just stop running toward goal for last second or so of flee?
01455         }
01456         if ( NPC_CheckSurrender() )
01457         {
01458                 return;
01459         }
01460         goal = NPCInfo->goalEntity;
01461         if ( !goal )
01462         {
01463                 goal = NPCInfo->lastGoalEntity;
01464                 if ( !goal )
01465                 {//???!!!
01466                         goal = NPCInfo->tempGoal;
01467                 }
01468         }
01469 
01470         if ( goal )
01471         {
01472                 qboolean moved;
01473                 qboolean reverseCourse = qtrue;
01474 
01475                 //FIXME: if no weapon, find one and run to pick it up?
01476 
01477                 //Let's try to find a waypoint that gets me away from this thing
01478                 if ( NPC->waypoint == WAYPOINT_NONE )
01479                 {
01480                         NPC->waypoint = NAV_GetNearestNode( NPC, NPC->lastWaypoint );
01481                 }
01482                 if ( NPC->waypoint != WAYPOINT_NONE )
01483                 {
01484                         int     numEdges = trap_Nav_GetNodeNumEdges( NPC->waypoint );
01485 
01486                         if ( numEdges != WAYPOINT_NONE )
01487                         {
01488                                 vec3_t  dangerDir;
01489                                 int             nextWp;
01490                                 int             branchNum;
01491 
01492                                 VectorSubtract( NPCInfo->investigateGoal, NPC->r.currentOrigin, dangerDir );
01493                                 VectorNormalize( dangerDir );
01494 
01495                                 for ( branchNum = 0; branchNum < numEdges; branchNum++ )
01496                                 {
01497                                         vec3_t  branchPos, runDir;
01498 
01499                                         nextWp = trap_Nav_GetNodeEdge( NPC->waypoint, branchNum );
01500                                         trap_Nav_GetNodePosition( nextWp, branchPos );
01501 
01502                                         VectorSubtract( branchPos, NPC->r.currentOrigin, runDir );
01503                                         VectorNormalize( runDir );
01504                                         if ( DotProduct( runDir, dangerDir ) > flrand( 0, 0.5 ) )
01505                                         {//don't run toward danger
01506                                                 continue;
01507                                         }
01508                                         //FIXME: don't want to ping-pong back and forth
01509                                         NPC_SetMoveGoal( NPC, branchPos, 0, qtrue, -1, NULL );
01510                                         reverseCourse = qfalse;
01511                                         break;
01512                                 }
01513                         }
01514                 }
01515 
01516                 moved = NPC_MoveToGoal( qfalse );//qtrue? (do try to move straight to (away from) goal)
01517 
01518                 if ( NPC->s.weapon == WP_NONE && (moved == qfalse || reverseCourse) )
01519                 {//No weapon and no escape route... Just cower?  Need anim.
01520                         NPC_Surrender();
01521                         NPC_UpdateAngles( qtrue, qtrue );
01522                         return;
01523                 }
01524                 //If our move failed, then just run straight away from our goal
01525                 //FIXME: We really shouldn't do this.
01526                 if ( moved == qfalse )
01527                 {
01528                         vec3_t  dir;
01529                         float   dist;
01530                         if ( reverseCourse )
01531                         {
01532                                 VectorSubtract( NPC->r.currentOrigin, goal->r.currentOrigin, dir );
01533                         }
01534                         else
01535                         {
01536                                 VectorSubtract( goal->r.currentOrigin, NPC->r.currentOrigin, dir );
01537                         }
01538                         NPCInfo->distToGoal     = dist = VectorNormalize( dir );
01539                         NPCInfo->desiredYaw = vectoyaw( dir );
01540                         NPCInfo->desiredPitch = 0;
01541                         ucmd.forwardmove = 127;
01542                 }
01543                 else if ( reverseCourse )
01544                 {
01545                         //ucmd.forwardmove *= -1;
01546                         //ucmd.rightmove *= -1;
01547                         //VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir );
01548                         NPCInfo->desiredYaw *= -1;
01549                 }
01550                 //FIXME: can stop after a safe distance?
01551                 //ucmd.upmove = 0;
01552                 ucmd.buttons &= ~BUTTON_WALKING;
01553                 //FIXME: what do we do once we've gotten to our goal?
01554         }
01555         NPC_UpdateAngles( qtrue, qtrue );
01556 
01557         NPC_CheckGetNewWeapon();
01558 }
01559 
01560 void NPC_StartFlee( gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int fleeTimeMin, int fleeTimeMax )
01561 {
01562         int cp = -1;
01563 
01564         if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
01565         {//running somewhere that a script requires us to go, don't interrupt that!
01566                 return;
01567         }
01568 
01569         //if have a fleescript, run that instead
01570         if ( G_ActivateBehavior( NPC, BSET_FLEE ) )
01571         {
01572                 return;
01573         }
01574         //FIXME: play a flee sound?  Appropriate to situation?
01575         if ( enemy )
01576         {
01577                 G_SetEnemy( NPC, enemy );
01578         }
01579         
01580         //FIXME: if don't have a weapon, find nearest one we have a route to and run for it?
01581         if ( dangerLevel > AEL_DANGER || NPC->s.weapon == WP_NONE || ((!NPCInfo->group || NPCInfo->group->numGroup <= 1) && NPC->health <= 10 ) )
01582         {//IF either great danger OR I have no weapon OR I'm alone and low on health, THEN try to find a combat point out of PVS
01583                 cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, dangerPoint, CP_COVER|CP_AVOID|CP_HAS_ROUTE|CP_NO_PVS, 128, -1 );
01584         }
01585         //FIXME: still happens too often...
01586         if ( cp == -1 )
01587         {//okay give up on the no PVS thing
01588                 cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, dangerPoint, CP_COVER|CP_AVOID|CP_HAS_ROUTE, 128, -1 );
01589                 if ( cp == -1 )
01590                 {//okay give up on the avoid
01591                         cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, dangerPoint, CP_COVER|CP_HAS_ROUTE, 128, -1 );
01592                         if ( cp == -1 )
01593                         {//okay give up on the cover
01594                                 cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, dangerPoint, CP_HAS_ROUTE, 128, -1 );
01595                         }
01596                 }
01597         }
01598 
01599         //see if we got a valid one
01600         if ( cp != -1 )
01601         {//found a combat point
01602                 NPC_SetCombatPoint( cp );
01603                 NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL );
01604                 NPCInfo->behaviorState = BS_HUNT_AND_KILL;
01605                 NPCInfo->tempBehavior = BS_DEFAULT;
01606         }
01607         else
01608         {//need to just run like hell!
01609                 if ( NPC->s.weapon != WP_NONE )
01610                 {
01611                         return;//let's just not flee?
01612                 }
01613                 else
01614                 {
01615                         //FIXME: other evasion AI?  Duck?  Strafe?  Dodge?
01616                         NPCInfo->tempBehavior = BS_FLEE;
01617                         //Run straight away from here... FIXME: really want to find farthest waypoint/navgoal from this pos... maybe based on alert event radius?
01618                         NPC_SetMoveGoal( NPC, dangerPoint, 0, qtrue, -1, NULL );
01619                         //store the danger point
01620                         VectorCopy( dangerPoint, NPCInfo->investigateGoal );//FIXME: make a new field for this?
01621                 }
01622         }
01623         //FIXME: localize this Timer?
01624         TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
01625         //FIXME: is this always applicable?
01626         NPCInfo->squadState = SQUAD_RETREAT;
01627         TIMER_Set( NPC, "flee", Q_irand( fleeTimeMin, fleeTimeMax ) );
01628         TIMER_Set( NPC, "panic", Q_irand( 1000, 4000 ) );//how long to wait before trying to nav to a dropped weapon
01629         
01630         if (NPC->client->NPC_class != CLASS_PROTOCOL)
01631         {
01632                 TIMER_Set( NPC, "duck", 0 );
01633         }
01634 }
01635 
01636 void G_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int fleeTimeMin, int fleeTimeMax )
01637 {
01638         if ( !self->NPC )
01639         {//player
01640                 return;
01641         }
01642         SaveNPCGlobals();
01643         SetNPCGlobals( self );
01644 
01645         NPC_StartFlee( enemy, dangerPoint, dangerLevel, fleeTimeMin, fleeTimeMax );
01646 
01647         RestoreNPCGlobals();
01648 }
01649 
01650 void NPC_BSEmplaced( void )
01651 {
01652         qboolean enemyLOS = qfalse;
01653         qboolean enemyCS = qfalse;
01654         qboolean faceEnemy = qfalse;
01655         qboolean shoot = qfalse;
01656         vec3_t  impactPos;
01657 
01658         //Don't do anything if we're hurt
01659         if ( NPC->painDebounceTime > level.time )
01660         {
01661                 NPC_UpdateAngles( qtrue, qtrue );
01662                 return;
01663         }
01664 
01665         if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON )
01666         {
01667                 WeaponThink( qtrue );
01668         }
01669 
01670         //If we don't have an enemy, just idle
01671         if ( NPC_CheckEnemyExt(qfalse) == qfalse )
01672         {
01673                 if ( !Q_irand( 0, 30 ) )
01674                 {
01675                         NPCInfo->desiredYaw = NPC->s.angles[1] + Q_irand( -90, 90 );
01676                 }
01677                 if ( !Q_irand( 0, 30 ) )
01678                 {
01679                         NPCInfo->desiredPitch = Q_irand( -20, 20 );
01680                 }
01681                 NPC_UpdateAngles( qtrue, qtrue );
01682                 return;
01683         }
01684 
01685         if ( NPC_ClearLOS4( NPC->enemy ) )
01686         {
01687                 int hit;
01688                 gentity_t *hitEnt;
01689 
01690                 enemyLOS = qtrue;
01691 
01692                 hit = NPC_ShotEntity( NPC->enemy, impactPos );
01693                 hitEnt = &g_entities[hit];
01694 
01695                 if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->takedamage ) )
01696                 {//can hit enemy or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway
01697                         enemyCS = qtrue;
01698                         NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy
01699                         VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation );
01700                 }
01701         }
01702 /*
01703         else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) )
01704         {
01705                 NPCInfo->enemyLastSeenTime = level.time;
01706                 faceEnemy = qtrue;
01707                 NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy
01708         }
01709 */
01710 
01711         if ( enemyLOS )
01712         {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot?
01713                 faceEnemy = qtrue;
01714         }
01715         if ( enemyCS )
01716         {
01717                 shoot = qtrue;
01718         }
01719 
01720         if ( faceEnemy )
01721         {//face the enemy
01722                 NPC_FaceEnemy( qtrue );
01723         }
01724         else
01725         {//we want to face in the dir we're running
01726                 NPC_UpdateAngles( qtrue, qtrue );
01727         }
01728 
01729         if ( NPCInfo->scriptFlags & SCF_DONT_FIRE )
01730         {
01731                 shoot = qfalse;
01732         }
01733 
01734         if ( NPC->enemy && NPC->enemy->enemy )
01735         {
01736                 if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER )
01737                 {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH)
01738                         shoot = qfalse;
01739                 }
01740         }
01741         if ( shoot )
01742         {//try to shoot if it's time
01743                 if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here
01744                 {
01745                         WeaponThink( qtrue );
01746                 }
01747         }
01748 }