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->tem