codemp/game/NPC_AI_Default.c

Go to the documentation of this file.
00001 #include "b_local.h"
00002 #include "g_nav.h"
00003 #include "../icarus/Q3_Interface.h"
00004 
00005 //#include "anims.h"
00006 //extern int PM_AnimLength( int index, animNumber_t anim );
00007 //extern int PM_AnimLength( int index, animNumber_t anim );
00008 //#define       MAX_IDLE_ANIMS  8
00009 
00010 extern qboolean NPC_SomeoneLookingAtMe(gentity_t *ent);
00011 
00012 /*
00013 void NPC_LostEnemyDecideChase(void)
00014 
00015   We lost our enemy and want to drop him but see if we should chase him if we are in the proper bState
00016 */
00017 
00018 void NPC_LostEnemyDecideChase(void)
00019 {
00020         switch( NPCInfo->behaviorState )
00021         {
00022         case BS_HUNT_AND_KILL:
00023                 //We were chasing him and lost him, so try to find him
00024                 if ( NPC->enemy == NPCInfo->goalEntity && NPC->enemy->lastWaypoint != WAYPOINT_NONE )
00025                 {//Remember his last valid Wp, then check it out
00026                         //FIXME: Should we only do this if there's no other enemies or we've got LOCKED_ENEMY on?
00027                         NPC_BSSearchStart( NPC->enemy->lastWaypoint, BS_SEARCH );
00028                 }
00029                 //If he's not our goalEntity, we're running somewhere else, so lose him
00030                 break;
00031         default:
00032                 break;
00033         }
00034         G_ClearEnemy( NPC );
00035 }
00036 /*
00037 -------------------------
00038 NPC_StandIdle
00039 -------------------------
00040 */
00041 
00042 void NPC_StandIdle( void )
00043 {
00044 /*
00045         //Must be done with any other animations
00046         if ( NPC->client->ps.legsAnimTimer != 0 )
00047                 return;
00048 
00049         //Not ready to do another one
00050         if ( TIMER_Done( NPC, "idleAnim" ) == false )
00051                 return;
00052 
00053         int anim = NPC->client->ps.legsAnim;
00054 
00055         if ( anim != BOTH_STAND1 && anim != BOTH_STAND2 )
00056                 return;
00057 
00058         //FIXME: Account for STAND1 or STAND2 here and set the base anim accordingly
00059         int     baseSeq = ( anim == BOTH_STAND1 ) ? BOTH_STAND1_RANDOM1 : BOTH_STAND2_RANDOM1;
00060 
00061         //Must have at least one random idle animation
00062         //NOTENOTE: This relies on proper ordering of animations, which SHOULD be okay
00063         if ( PM_HasAnimation( NPC, baseSeq ) == false )
00064                 return;
00065 
00066         int     newIdle = Q_irand( 0, MAX_IDLE_ANIMS-1 );
00067 
00068         //FIXME: Technically this could never complete.. but that's not really too likely
00069         while( 1 )
00070         {
00071                 if ( PM_HasAnimation( NPC, baseSeq + newIdle ) )
00072                         break;
00073 
00074                 newIdle = Q_irand( 0, MAX_IDLE_ANIMS );
00075         }
00076         
00077         //Start that animation going
00078         NPC_SetAnim( NPC, SETANIM_BOTH, baseSeq + newIdle, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00079         
00080         int newTime = PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t) (baseSeq + newIdle) );
00081 
00082         //Don't do this again for a random amount of time
00083         TIMER_Set( NPC, "idleAnim", newTime + Q_irand( 2000, 10000 ) );
00084 */
00085 }
00086 
00087 qboolean NPC_StandTrackAndShoot (gentity_t *NPC, qboolean canDuck)
00088 {
00089         qboolean        attack_ok = qfalse;
00090         qboolean        duck_ok = qfalse;
00091         qboolean        faced = qfalse;
00092         float           attack_scale = 1.0;
00093 
00094         //First see if we're hurt bad- if so, duck
00095         //FIXME: if even when ducked, we can shoot someone, we should.
00096         //Maybe is can be shot even when ducked, we should run away to the nearest cover?
00097         if ( canDuck )
00098         {
00099                 if ( NPC->health < 20 )
00100                 {
00101                 //      if( NPC->svFlags&SVF_HEALING || random() )
00102                         if( random() )
00103                         {
00104                                 duck_ok = qtrue;
00105                         }
00106                 }
00107                 else if ( NPC->health < 40 )
00108                 {
00109 //                      if ( NPC->svFlags&SVF_HEALING )
00110 //                      {//Medic is on the way, get down!
00111 //                              duck_ok = qtrue;
00112 //                      }
00113                         // no more borg
00115 //                      {//Borg don't care if they're about to die
00116                                 //attack_scale will be a max of .66
00117 //                              attack_scale = NPC->health/60;
00118 //                      }
00119                 }
00120         }
00121 
00122         //NPC_CheckEnemy( qtrue, qfalse, qtrue );
00123 
00124         if ( !duck_ok )
00125         {//made this whole part a function call
00126                 attack_ok = NPC_CheckCanAttack( attack_scale, qtrue );
00127                 faced = qtrue;
00128         }
00129 
00130         if ( canDuck && (duck_ok || (!attack_ok && client->ps.weaponTime <= 0)) && ucmd.upmove != -127 )
00131         {//if we didn't attack check to duck if we're not already
00132                 if( !duck_ok )
00133                 {
00134                         if ( NPC->enemy->client )
00135                         {
00136                                 if ( NPC->enemy->enemy == NPC )
00137                                 {
00138                                         if ( NPC->enemy->client->buttons & BUTTON_ATTACK )
00139                                         {//FIXME: determine if enemy fire angles would hit me or get close
00140                                                 if ( NPC_CheckDefend( 1.0 ) )//FIXME: Check self-preservation?  Health?
00141                                                 {
00142                                                         duck_ok = qtrue;
00143                                                 }
00144                                         }
00145                                 }
00146                         }
00147                 }
00148 
00149                 if ( duck_ok )
00150                 {//duck and don't shoot
00151                         attack_ok = qfalse;
00152                         ucmd.upmove = -127;
00153                         NPCInfo->duckDebounceTime = level.time + 1000;//duck for a full second
00154                 }
00155         }
00156 
00157         return faced;
00158 }
00159 
00160 
00161 void NPC_BSIdle( void ) 
00162 {
00163         //FIXME if there is no nav data, we need to do something else
00164         // if we're stuck, try to move around it
00165         if ( UpdateGoal() )
00166         {
00167                 NPC_MoveToGoal( qtrue );
00168         }
00169 
00170         if ( ( ucmd.forwardmove == 0 ) && ( ucmd.rightmove == 0 ) && ( ucmd.upmove == 0 ) )
00171         {
00172 //              NPC_StandIdle();
00173         }
00174 
00175         NPC_UpdateAngles( qtrue, qtrue );
00176         ucmd.buttons |= BUTTON_WALKING;
00177 }
00178 
00179 void NPC_BSRun (void)
00180 {
00181         //FIXME if there is no nav data, we need to do something else
00182         // if we're stuck, try to move around it
00183         if ( UpdateGoal() )
00184         {
00185                 NPC_MoveToGoal( qtrue );
00186         }
00187 
00188         NPC_UpdateAngles( qtrue, qtrue );
00189 }
00190 
00191 void NPC_BSStandGuard (void)
00192 {
00193         //FIXME: Use Snapshot info
00194         if ( NPC->enemy == NULL )
00195         {//Possible to pick one up by being shot
00196                 if( random() < 0.5 )
00197                 {
00198                         if(NPC->client->enemyTeam)
00199                         {
00200                                 gentity_t *newenemy = NPC_PickEnemy(NPC, NPC->client->enemyTeam, (NPC->cantHitEnemyCounter < 10), (NPC->client->enemyTeam == NPCTEAM_PLAYER), qtrue);
00201                                 //only checks for vis if couldn't hit last enemy
00202                                 if(newenemy)
00203                                 {
00204                                         G_SetEnemy( NPC, newenemy );
00205                                 }
00206                         }
00207                 }
00208         }
00209 
00210         if ( NPC->enemy != NULL )
00211         {
00212                 if( NPCInfo->tempBehavior == BS_STAND_GUARD )
00213                 {
00214                         NPCInfo->tempBehavior = BS_DEFAULT;
00215                 }
00216                 
00217                 if( NPCInfo->behaviorState == BS_STAND_GUARD )
00218                 {
00219                         NPCInfo->behaviorState = BS_STAND_AND_SHOOT;
00220                 }
00221         }
00222 
00223         NPC_UpdateAngles( qtrue, qtrue );
00224 }
00225 
00226 /*
00227 -------------------------
00228 NPC_BSHuntAndKill
00229 -------------------------
00230 */
00231 
00232 void NPC_BSHuntAndKill( void )
00233 {
00234         qboolean        turned = qfalse;
00235         vec3_t          vec;
00236         float           enemyDist;
00237         visibility_t    oEVis;
00238         int                     curAnim;
00239 
00240         NPC_CheckEnemy( NPCInfo->tempBehavior != BS_HUNT_AND_KILL, qfalse, qtrue );//don't find new enemy if this is tempbehav
00241 
00242         if ( NPC->enemy )
00243         {
00244                 oEVis = enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_FOV|CHECK_SHOOT );//CHECK_360|//CHECK_PVS|
00245                 if(enemyVisibility > VIS_PVS)
00246                 {
00247                         if ( !NPC_EnemyTooFar( NPC->enemy, 0, qtrue ) )
00248                         {//Enemy is close enough to shoot - FIXME: this next func does this also, but need to know here for info on whether ot not to turn later
00249                                 NPC_CheckCanAttack( 1.0, qfalse );
00250                                 turned = qtrue;
00251                         }
00252                 }
00253 
00254                 curAnim = NPC->client->ps.legsAnim;
00255                 if(curAnim != BOTH_ATTACK1 && curAnim != BOTH_ATTACK2 && curAnim != BOTH_ATTACK3 && curAnim != BOTH_MELEE1 && curAnim != BOTH_MELEE2 )
00256                 {//Don't move toward enemy if we're in a full-body attack anim
00257                         //FIXME, use IdealDistance to determin if we need to close distance
00258                         VectorSubtract(NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, vec);
00259                         enemyDist = VectorLength(vec);
00260                         if( enemyDist > 48 && ((enemyDist*1.5)*(enemyDist*1.5) >= NPC_MaxDistSquaredForWeapon() ||
00261                                 oEVis != VIS_SHOOT ||
00263                                 enemyDist > IdealDistance(NPC)*3 ) )
00264                         {//We should close in?
00265                                 NPCInfo->goalEntity = NPC->enemy;
00266 
00267                                 NPC_MoveToGoal( qtrue );
00268                         }
00269                         else if(enemyDist < IdealDistance(NPC))
00270                         {//We should back off?
00271                                 //if(ucmd.buttons & BUTTON_ATTACK)
00272                                 {
00273                                         NPCInfo->goalEntity = NPC->enemy;
00274                                         NPCInfo->goalRadius = 12;
00275                                         NPC_MoveToGoal( qtrue );
00276 
00277                                         ucmd.forwardmove *= -1;
00278                                         ucmd.rightmove *= -1;
00279                                         VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir );
00280 
00281                                         ucmd.buttons |= BUTTON_WALKING;
00282                                 }
00283                         }//otherwise, stay where we are
00284                 }
00285         }
00286         else 
00287         {//ok, stand guard until we find an enemy
00288                 if( NPCInfo->tempBehavior == BS_HUNT_AND_KILL )
00289                 {
00290                         NPCInfo->tempBehavior = BS_DEFAULT;
00291                 }
00292                 else
00293                 {
00294                         NPCInfo->tempBehavior = BS_STAND_GUARD;
00295                         NPC_BSStandGuard();
00296                 }
00297                 return;
00298         }
00299 
00300         if(!turned)
00301         {
00302                 NPC_UpdateAngles(qtrue, qtrue);
00303         }
00304 }
00305 
00306 void NPC_BSStandAndShoot (void)
00307 {
00308         //FIXME:
00309         //When our numbers outnumber enemies 3 to 1, or only one of them,
00310         //go into hunt and kill mode
00311 
00312         //FIXME:
00313         //When they're all dead, go to some script or wander off to sickbay?
00314         
00315         if(NPC->client->playerTeam && NPC->client->enemyTeam)
00316         {
00317                 //FIXME: don't realize this right away- or else enemies show up and we're standing around
00318                 /*
00319                 if( teamNumbers[NPC->enemyTeam] == 0 )
00320                 {//ok, stand guard until we find another enemy
00321                         //reset our rush counter
00322                         teamCounter[NPC->playerTeam] = 0;
00323                         NPCInfo->tempBehavior = BS_STAND_GUARD;
00324                         NPC_BSStandGuard();
00325                         return;
00326                 }*/
00327                 /*
00328                 //FIXME: whether to do this or not should be settable
00329                 else if( NPC->playerTeam != TEAM_BORG )//Borg don't rush
00330                 {
00331                 //FIXME: In case reinforcements show up, we should wait a few seconds
00332                 //and keep checking before rushing!
00333                 //Also: what if not everyone on our team is going after playerTeam?
00334                 //Also: our team count includes medics!
00335                         if(NPC->health > 25)
00336                         {//Can we rush the enemy?
00337                                 if(teamNumbers[NPC->enemyTeam] == 1 ||
00338                                         teamNumbers[NPC->playerTeam] >= teamNumbers[NPC->enemyTeam]*3)
00339                                 {//Only one of them or we outnumber 3 to 1
00340                                         if(teamStrength[NPC->playerTeam] >= 75 ||
00341                                                 (teamStrength[NPC->playerTeam] >= 50 && teamStrength[NPC->playerTeam] > teamStrength[NPC->enemyTeam]))
00342                                         {//Our team is strong enough to rush
00343                                                 teamCounter[NPC->playerTeam]++;
00344                                                 if(teamNumbers[NPC->playerTeam] * 17 <= teamCounter[NPC->playerTeam])
00345                                                 {//ok, we waited 1.7 think cycles on average and everyone is go, let's do it!
00346                                                         //FIXME: Should we do this to everyone on our team?
00347                                                         NPCInfo->behaviorState = BS_HUNT_AND_KILL;
00348                                                         //FIXME: if the tide changes, we should retreat!
00349                                                         //FIXME: when do we reset the counter?
00350                                                         NPC_BSHuntAndKill ();
00351                                                         return;
00352                                                 }
00353                                         }
00354                                         else//Oops!  Something's wrong, reset the counter to rush
00355                                                 teamCounter[NPC->playerTeam] = 0;
00356                                 }
00357                                 else//Oops!  Something's wrong, reset the counter to rush
00358                                         teamCounter[NPC->playerTeam] = 0;
00359                         }
00360                 }
00361                 */
00362         }
00363 
00364         NPC_CheckEnemy(qtrue, qfalse, qtrue);
00365         
00366         if(NPCInfo->duckDebounceTime > level.time && NPC->client->ps.weapon != WP_SABER )
00367         {
00368                 ucmd.upmove = -127;
00369                 if(NPC->enemy)
00370                 {
00371                         NPC_CheckCanAttack(1.0, qtrue);
00372                 }
00373                 return;         
00374         }
00375 
00376         if(NPC->enemy)
00377         {
00378                 if(!NPC_StandTrackAndShoot( NPC, qtrue ))
00379                 {//That func didn't update our angles
00380                         NPCInfo->desiredYaw = NPC->client->ps.viewangles[YAW];
00381                         NPCInfo->desiredPitch = NPC->client->ps.viewangles[PITCH];
00382                         NPC_UpdateAngles(qtrue, qtrue);
00383                 }
00384         }
00385         else
00386         {
00387                 NPCInfo->desiredYaw = NPC->client->ps.viewangles[YAW];
00388                 NPCInfo->desiredPitch = NPC->client->ps.viewangles[PITCH];
00389                 NPC_UpdateAngles(qtrue, qtrue);
00390 //              NPC_BSIdle();//only moves if we have a goal
00391         }
00392 }
00393 
00394 void NPC_BSRunAndShoot (void)
00395 {
00396         /*if(NPC->playerTeam && NPC->enemyTeam)
00397         {
00398                 //FIXME: don't realize this right away- or else enemies show up and we're standing around
00399                 if( teamNumbers[NPC->enemyTeam] == 0 )
00400                 {//ok, stand guard until we find another enemy
00401                         //reset our rush counter
00402                         teamCounter[NPC->playerTeam] = 0;
00403                         NPCInfo->tempBehavior = BS_STAND_GUARD;
00404                         NPC_BSStandGuard();
00405                         return;
00406                 }
00407         }*/
00408 
00409         //NOTE: are we sure we want ALL run and shoot people to move this way?
00410         //Shouldn't it check to see if we have an enemy and our enemy is our goal?!
00411         //Moved that check into NPC_MoveToGoal
00412         //NPCInfo->combatMove = qtrue;
00413 
00414         NPC_CheckEnemy( qtrue, qfalse, qtrue );
00415         
00416         if ( NPCInfo->duckDebounceTime > level.time ) // && NPCInfo->hidingGoal )
00417         {
00418                 ucmd.upmove = -127;
00419                 if ( NPC->enemy )
00420                 {
00421                         NPC_CheckCanAttack( 1.0, qfalse );
00422                 }
00423                 return;         
00424         }
00425 
00426         if ( NPC->enemy )
00427         {
00428                 int monitor = NPC->cantHitEnemyCounter;
00429                 NPC_StandTrackAndShoot( NPC, qfalse );//(NPCInfo->hidingGoal != NULL) );
00430 
00431                 if ( !(ucmd.buttons & BUTTON_ATTACK) && ucmd.upmove >= 0 && NPC->cantHitEnemyCounter > monitor )
00432                 {//not crouching and not firing
00433                         vec3_t  vec;
00434 
00435                         VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, vec );
00436                         vec[2] = 0;
00437                         if ( VectorLength( vec ) > 128 || NPC->cantHitEnemyCounter >= 10 )
00438                         {//run at enemy if too far away
00439                                 //The cantHitEnemyCounter getting high has other repercussions
00440                                 //100 (10 seconds) will make you try to pick a new enemy... 
00441                                 //But we're chasing, so we clamp it at 50 here
00442                                 if ( NPC->cantHitEnemyCounter > 60 )
00443                                 {
00444                                         NPC->cantHitEnemyCounter = 60;
00445                                 }
00446                                 
00447                                 if ( NPC->cantHitEnemyCounter >= (NPCInfo->stats.aggression+1) * 10 )
00448                                 {
00449                                         NPC_LostEnemyDecideChase();
00450                                 }
00451 
00452                                 //chase and face
00453                                 ucmd.angles[YAW] = 0;
00454                                 ucmd.angles[PITCH] = 0;
00455                                 NPCInfo->goalEntity = NPC->enemy;
00456                                 NPCInfo->goalRadius = 12;
00457                                 //NAV_ClearLastRoute(NPC);
00458                                 NPC_MoveToGoal( qtrue );
00459                                 NPC_UpdateAngles(qtrue, qtrue);
00460                         }
00461                         else
00462                         {
00463                                 //FIXME: this could happen if they're just on the other side
00464                                 //of a thin wall or something else blocking out shot.  That
00465                                 //would make us just stand there and not go around it...
00466                                 //but maybe it's okay- might look like we're waiting for
00467                                 //him to come out...?  
00468                                 //Current solution: runs around if cantHitEnemyCounter gets
00469                                 //to 10 (1 second).  
00470                         }
00471                 }
00472                 else
00473                 {//Clear the can't hit enemy counter here
00474                         NPC->cantHitEnemyCounter = 0;
00475                 }
00476         }
00477         else
00478         {
00479                 if ( NPCInfo->tempBehavior == BS_HUNT_AND_KILL )
00480                 {//lost him, go back to what we were doing before
00481                         NPCInfo->tempBehavior = BS_DEFAULT;
00482                         return;
00483                 }
00484 
00485 //              NPC_BSRun();//only moves if we have a goal
00486         }
00487 }
00488 
00489 //Simply turn until facing desired angles
00490 void NPC_BSFace (void)
00491 {
00492         //FIXME: once you stop sending turning info, they reset to whatever their delta_angles was last????
00493         //Once this is over, it snaps back to what it was facing before- WHY???
00494         if( NPC_UpdateAngles ( qtrue, qtrue ) )
00495         {
00496                 trap_ICARUS_TaskIDComplete( NPC, TID_BSTATE );
00497                 
00498                 NPCInfo->desiredYaw = client->ps.viewangles[YAW];
00499                 NPCInfo->desiredPitch = client->ps.viewangles[PITCH];
00500 
00501                 NPCInfo->aimTime = 0;//ok to turn normally now
00502         }
00503 }
00504 
00505 void NPC_BSPointShoot (qboolean shoot)
00506 {//FIXME: doesn't check for clear shot...
00507         vec3_t  muzzle, dir, angles, org;
00508 
00509         if ( !NPC->enemy || !NPC->enemy->inuse || (NPC->enemy->NPC && NPC->enemy->health <= 0) )
00510         {//FIXME: should still keep shooting for a second or two after they actually die...
00511                 trap_ICARUS_TaskIDComplete( NPC, TID_BSTATE );
00512                 goto finished;
00513                 return;
00514         }
00515 
00516         CalcEntitySpot(NPC, SPOT_WEAPON, muzzle);
00517         CalcEntitySpot(NPC->enemy, SPOT_HEAD, org);//Was spot_org
00518         //Head is a little high, so let's aim for the chest:
00519         if ( NPC->enemy->client )
00520         {
00521                 org[2] -= 12;//NOTE: is this enough?
00522         }
00523 
00524         VectorSubtract(org, muzzle, dir);
00525         vectoangles(dir, angles);
00526 
00527         switch( NPC->client->ps.weapon )
00528         {
00529         case WP_NONE:
00530 //      case WP_TRICORDER:
00531         case WP_STUN_BATON:
00532         case WP_SABER:
00533                 //don't do any pitch change if not holding a firing weapon
00534                 break;
00535         default:
00536                 NPCInfo->desiredPitch = NPCInfo->lockedDesiredPitch = AngleNormalize360(angles[PITCH]);
00537                 break;
00538         }
00539 
00540         NPCInfo->desiredYaw = NPCInfo->lockedDesiredYaw = AngleNormalize360(angles[YAW]);
00541 
00542         if ( NPC_UpdateAngles ( qtrue, qtrue ) )
00543         {//FIXME: if angles clamped, this may never work!
00544                 //NPCInfo->shotTime = NPC->attackDebounceTime = 0;
00545 
00546                 if ( shoot )
00547                 {//FIXME: needs to hold this down if using a weapon that requires it, like phaser...
00548                         ucmd.buttons |= BUTTON_ATTACK;
00549                 }
00550                 
00551                 //if ( !shoot || !(NPC->svFlags & SVF_LOCKEDENEMY) )
00552                 if (1)
00553                 {//If locked_enemy is on, dont complete until it is destroyed...
00554                         trap_ICARUS_TaskIDComplete( NPC, TID_BSTATE );
00555                         goto finished;
00556                 }
00557         }
00558         //else if ( shoot && (NPC->svFlags & SVF_LOCKEDENEMY) )
00559         if (0)
00560         {//shooting them till their dead, not aiming right at them yet...
00561                 /*
00562                 qboolean movingTarget = qfalse;
00563 
00564                 if ( NPC->enemy->client )
00565                 {
00566                         if ( VectorLengthSquared( NPC->enemy->client->ps.velocity ) )
00567                         {
00568                                 movingTarget = qtrue;
00569                         }
00570                 }
00571                 else if ( VectorLengthSquared( NPC->enemy->s.pos.trDelta ) )
00572                 {
00573                         movingTarget = qtrue;
00574                 }
00575 
00576                 if (movingTarget )
00577                 */
00578                 {
00579                         float   dist = VectorLength( dir );
00580                         float   yawMiss, yawMissAllow = NPC->enemy->r.maxs[0];
00581                         float   pitchMiss, pitchMissAllow = (NPC->enemy->r.maxs[2] - NPC->enemy->r.mins[2])/2;
00582                         
00583                         if ( yawMissAllow < 8.0f )
00584                         {
00585                                 yawMissAllow = 8.0f;
00586                         }
00587 
00588                         if ( pitchMissAllow < 8.0f )
00589                         {
00590                                 pitchMissAllow = 8.0f;
00591                         }
00592 
00593                         yawMiss = tan(DEG2RAD(AngleDelta ( NPC->client->ps.viewangles[YAW], NPCInfo->desiredYaw ))) * dist;
00594                         pitchMiss = tan(DEG2RAD(AngleDelta ( NPC->client->ps.viewangles[PITCH], NPCInfo->desiredPitch))) * dist;
00595 
00596                         if ( yawMissAllow >= yawMiss && pitchMissAllow > pitchMiss )
00597                         {
00598                                 ucmd.buttons |= BUTTON_ATTACK;
00599                         }
00600                 }
00601         }
00602         
00603         return;
00604                 
00605 finished:
00606         NPCInfo->desiredYaw = client->ps.viewangles[YAW];
00607         NPCInfo->desiredPitch = client->ps.viewangles[PITCH];
00608 
00609         NPCInfo->aimTime = 0;//ok to turn normally now
00610 }
00611 
00612 /*
00613 void NPC_BSMove(void)
00614 Move in a direction, face another
00615 */
00616 void NPC_BSMove(void)
00617 {
00618         gentity_t       *goal = NULL;
00619 
00620         NPC_CheckEnemy(qtrue, qfalse, qtrue);
00621         if(NPC->enemy)
00622         {
00623                 NPC_CheckCanAttack(1.0, qfalse);
00624         }
00625         else
00626         {
00627                 NPC_UpdateAngles(qtrue, qtrue);
00628         }
00629 
00630         goal = UpdateGoal();
00631         if(goal)
00632         {
00633 //              NPCInfo->moveToGoalMod = 1.0;
00634 
00635                 NPC_SlideMoveToGoal();
00636         }
00637 }
00638 
00639 /*
00640 void NPC_BSShoot(void)
00641 Move in a direction, face another
00642 */
00643 
00644 void NPC_BSShoot(void)
00645 {
00646 //      NPC_BSMove();
00647 
00648         enemyVisibility = VIS_SHOOT;
00649 
00650         if ( client->ps.weaponstate != WEAPON_READY && client->ps.weaponstate != WEAPON_FIRING ) 
00651         {
00652                 client->ps.weaponstate = WEAPON_READY;
00653         }
00654 
00655         WeaponThink(qtrue);
00656 }
00657 
00658 /*
00659 void NPC_BSPatrol( void ) 
00660 
00661   Same as idle, but you look for enemies every "vigilance"
00662   using your angles, HFOV, VFOV and visrange, and listen for sounds within earshot...
00663 */
00664 void NPC_BSPatrol( void ) 
00665 {
00666         //int   alertEventNum;
00667 
00668         if(level.time > NPCInfo->enemyCheckDebounceTime)
00669         {
00670                 NPCInfo->enemyCheckDebounceTime = level.time + (NPCInfo->stats.vigilance * 1000);
00671                 NPC_CheckEnemy(qtrue, qfalse, qtrue);
00672                 if(NPC->enemy)
00673                 {//FIXME: do anger script
00674                         NPCInfo->behaviorState = BS_HUNT_AND_KILL;
00675                         //NPC_AngerSound();
00676                         return;
00677                 }
00678         }
00679 
00680         //FIXME: Implement generic sound alerts
00681         /*
00682         alertEventNum = NPC_CheckAlertEvents( qtrue, qtrue );
00683         if( alertEventNum != -1 )
00684         {//If we heard something, see if we should check it out
00685                 if ( NPC_CheckInvestigate( alertEventNum ) )
00686                 {
00687                         return;
00688                 }
00689         }
00690         */
00691 
00692         NPCInfo->investigateSoundDebounceTime = 0;
00693         //FIXME if there is no nav data, we need to do something else
00694         // if we're stuck, try to move around it
00695         if ( UpdateGoal() )
00696         {
00697                 NPC_MoveToGoal( qtrue );
00698         }
00699 
00700         NPC_UpdateAngles( qtrue, qtrue );
00701 
00702         ucmd.buttons |= BUTTON_WALKING;
00703 }
00704 
00705 /*
00706 void NPC_BSDefault(void)
00707         uses various scriptflags to determine how an npc should behave
00708 */
00709 extern void NPC_CheckGetNewWeapon( void );
00710 extern void NPC_BSST_Attack( void );
00711 
00712 void NPC_BSDefault( void ) 
00713 {
00714 //      vec3_t          enemyDir;
00715 //      float           enemyDist;
00716 //      float           shootDist;
00717 //      qboolean        enemyFOV = qfalse;
00718 //      qboolean        enemyShotFOV = qfalse;
00719 //      qboolean        enemyPVS = qfalse;
00720 //      vec3_t          enemyHead;
00721 //      vec3_t          muzzle;
00722 //      qboolean        enemyLOS = qfalse;
00723 //      qboolean        enemyCS = qfalse;
00724         qboolean        move = qtrue;
00725 //      qboolean        shoot = qfalse;
00726 
00727         
00728         if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON )
00729         {
00730                 WeaponThink( qtrue );
00731         }
00732 
00733         if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH )
00734         {//being forced to walk
00735                 if( NPC->client->ps.torsoAnim != TORSO_SURRENDER_START )
00736                 {
00737                         NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_SURRENDER_START, SETANIM_FLAG_HOLD );
00738                 }
00739         }
00740         //look for a new enemy if don't have one and are allowed to look, validate current enemy if have one
00741         NPC_CheckEnemy( (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES), qfalse, qtrue );
00742         if ( !NPC->enemy )
00743         {//still don't have an enemy
00744                 if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
00745                 {//check for alert events
00746                         //FIXME: Check Alert events, see if we should investigate or just look at it
00747                         int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qtrue, AEL_DISCOVERED );
00748 
00749                         //There is an event to look at
00750                         if ( alertEvent >= 0 && level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID )
00751                         {//heard/saw something
00752                                 if ( level.alertEvents[alertEvent].level >= AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
00753                                 {//was a big event
00754                                         if ( level.alertEvents[alertEvent].owner && level.alertEvents[alertEvent].owner->client && level.alertEvents[alertEvent].owner->health >= 0 && level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam )
00755                                         {//an enemy
00756                                                 G_SetEnemy( NPC, level.alertEvents[alertEvent].owner );
00757                                         }
00758                                 }
00759                                 else
00760                                 {//FIXME: investigate lesser events
00761                                 }
00762                         }
00763                         //FIXME: also check our allies' condition?
00764                 }
00765         }
00766 
00767         if ( NPC->enemy && !(NPCInfo->scriptFlags&SCF_FORCED_MARCH) )
00768         {
00769                 // just use the stormtrooper attack AI...
00770                 NPC_CheckGetNewWeapon();
00771                 if ( NPC->client->leader 
00772                         && NPCInfo->goalEntity == NPC->client->leader 
00773                         && !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
00774                 {
00775                         NPC_ClearGoal();
00776                 }
00777                         NPC_BSST_Attack();
00778                 return;
00779 /*
00780                 //have an enemy
00781                 //FIXME: if one of these fails, meaning we can't shoot, do we really need to do the rest?
00782                 VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, enemyDir );
00783                 enemyDist = VectorNormalize( enemyDir );
00784                 enemyDist *= enemyDist;
00785                 shootDist = NPC_MaxDistSquaredForWeapon();
00786 
00787                 enemyFOV = InFOV( NPC->enemy, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov );
00788                 enemyShotFOV = InFOV( NPC->enemy, NPC, 20, 20 );
00789                 enemyPVS = gi.inPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin );
00790 
00791                 if ( enemyPVS )
00792                 {//in the pvs
00793                         trace_t tr;
00794 
00795                         CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemyHead );
00796                         enemyHead[2] -= Q_flrand( 0.0f, NPC->enemy->maxs[2]*0.5f );
00797                         CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
00798                         enemyLOS = NPC_ClearLOS( muzzle, enemyHead );
00799 
00800                         gi.trace ( &tr, muzzle, vec3_origin, vec3_origin, enemyHead, NPC->s.number, MASK_SHOT );
00801                         enemyCS = NPC_EvaluateShot( tr.entityNum, qtrue );
00802                 }
00803                 else
00804                 {//skip thr 2 traces since they would have to fail
00805                         enemyLOS = qfalse;
00806                         enemyCS = qfalse;
00807                 }
00808                 
00809                 if ( enemyCS && enemyShotFOV )
00810                 {//can hit enemy if we want
00811                         NPC->cantHitEnemyCounter = 0;
00812                 }
00813                 else
00814                 {//can't hit
00815                         NPC->cantHitEnemyCounter++;
00816                 }
00817 
00818                 if ( enemyCS && enemyShotFOV && enemyDist < shootDist )
00819                 {//can shoot
00820                         shoot = qtrue;
00821                         if ( NPCInfo->goalEntity == NPC->enemy )
00822                         {//my goal is my enemy and I have a clear shot, no need to chase right now
00823                                 move = qfalse;
00824                         }
00825                 }
00826                 else
00827                 {//don't shoot yet, keep chasing
00828                         shoot = qfalse;
00829                         move = qtrue;
00830                 }
00831 
00832                 //shoot decision
00833                 if ( !(NPCInfo->scriptFlags&SCF_DONT_FIRE) )
00834                 {//try to shoot
00835                         if ( NPC->enemy )
00836                         {
00837                                 if ( shoot )
00838                                 {
00839                                         if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here
00840                                         {
00841                                                 WeaponThink( qtrue );
00842                                         }
00843                                 }
00844                         }
00845                 }
00846 
00847                 //chase decision
00848                 if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
00849                 {//go after him
00850                         NPCInfo->goalEntity = NPC->enemy;
00851                         //FIXME: don't need to chase when have a clear shot and in range?
00852                         if ( !enemyCS && NPC->cantHitEnemyCounter > 60 )
00853                         {//haven't been able to shoot enemy for about 6 seconds, need to do something
00854                                 //FIXME: combat points?  Just chase?
00855                                 if ( enemyPVS )
00856                                 {//in my PVS, just pick a combat point
00857                                         //FIXME: implement
00858                                 }
00859                                 else
00860                                 {//just chase him
00861                                 }
00862                         }
00863                         //FIXME: in normal behavior, should we use combat Points?  Do we care?  Is anyone actually going to ever use this AI?
00864                 }
00865                 else if ( NPC->cantHitEnemyCounter > 60 )
00866                 {//pick a new one
00867                         NPC_CheckEnemy( qtrue, qfalse, qtrue );
00868                 }
00869                 
00870                 if ( enemyPVS && enemyLOS )//&& !enemyShotFOV )
00871                 {//have a clear LOS to him//, but not looking at him
00872                         //Find the desired angles
00873                         vec3_t  angles;
00874 
00875                         GetAnglesForDirection( muzzle, enemyHead, angles );
00876 
00877                         NPCInfo->desiredYaw             = AngleNormalize180( angles[YAW] );
00878                         NPCInfo->desiredPitch   = AngleNormalize180( angles[PITCH] );
00879                 }
00880                 */
00881         }
00882 
00883         if ( UpdateGoal() )
00884         {//have a goal
00885                 if ( !NPC->enemy 
00886                         && NPC->client->leader 
00887                         && NPCInfo->goalEntity == NPC->client->leader 
00888                         && !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
00889                 {
00890                         NPC_BSFollowLeader();
00891                 }
00892                 else
00893                 {
00894                         //set angles
00895                         if ( (NPCInfo->scriptFlags & SCF_FACE_MOVE_DIR) || NPCInfo->goalEntity != NPC->enemy )
00896                         {//face direction of movement, NOTE: default behavior when not chasing enemy
00897                                 NPCInfo->combatMove = qfalse;
00898                         }
00899                         else
00900                         {//face goal.. FIXME: what if have a navgoal but want to face enemy while moving?  Will this do that?
00901                                 vec3_t  dir, angles;
00902 
00903                                 NPCInfo->combatMove = qfalse;
00904 
00905                                 VectorSubtract( NPCInfo->goalEntity->r.currentOrigin, NPC->r.currentOrigin, dir );
00906                                 vectoangles( dir, angles );
00907                                 NPCInfo->desiredYaw = angles[YAW];
00908                                 if ( NPCInfo->goalEntity == NPC->enemy )
00909                                 {
00910                                         NPCInfo->desiredPitch = angles[PITCH];
00911                                 }
00912                         }
00913 
00914                         //set movement
00915                         //override default walk/run behavior
00916                         //NOTE: redundant, done in NPC_ApplyScriptFlags
00917                         if ( NPCInfo->scriptFlags & SCF_RUNNING )
00918                         {
00919                                 ucmd.buttons &= ~BUTTON_WALKING;
00920                         }
00921                         else if ( NPCInfo->scriptFlags & SCF_WALKING )
00922                         {
00923                                 ucmd.buttons |= BUTTON_WALKING;
00924                         }
00925                         else if ( NPCInfo->goalEntity == NPC->enemy )
00926                         {
00927                                 ucmd.buttons &= ~BUTTON_WALKING;
00928                         }
00929                         else
00930                         {
00931                                 ucmd.buttons |= BUTTON_WALKING;
00932                         }
00933 
00934                         if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH )
00935                         {//being forced to walk
00936                                 //if ( g_crosshairEntNum != NPC->s.number )
00937                                 if (!NPC_SomeoneLookingAtMe(NPC))
00938                                 {//don't walk if player isn't aiming at me
00939                                         move = qfalse;
00940                                 }
00941                         }
00942 
00943                         if ( move )
00944                         {
00945                                 //move toward goal
00946                                 NPC_MoveToGoal( qtrue );
00947                         }
00948                 }
00949         }
00950         else if ( !NPC->enemy && NPC->client->leader )
00951         {
00952                 NPC_BSFollowLeader();
00953         }
00954 
00955         //update angles
00956         NPC_UpdateAngles( qtrue, qtrue );
00957 }