codemp/game/NPC_combat.c

Go to the documentation of this file.
00001 //NPC_combat.cpp
00002 #include "b_local.h"
00003 #include "g_nav.h"
00004 
00005 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
00006 extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
00007 extern qboolean NPC_CheckLookTarget( gentity_t *self );
00008 extern void NPC_ClearLookTarget( gentity_t *self );
00009 extern void NPC_Jedi_RateNewEnemy( gentity_t *self, gentity_t *enemy );
00010 extern int NAV_FindClosestWaypointForPoint2( vec3_t point );
00011 extern int NAV_GetNearestNode( gentity_t *self, int lastNode );
00012 extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel, int boltNum, int weaponNum );
00013 extern qboolean PM_DroidMelee( int npc_class );
00014 
00015 void ChangeWeapon( gentity_t *ent, int newWeapon );
00016 
00017 void G_ClearEnemy (gentity_t *self)
00018 {
00019         NPC_CheckLookTarget( self );
00020 
00021         if ( self->enemy )
00022         {
00023                 if(     self->client && self->client->renderInfo.lookTarget == self->enemy->s.number )
00024                 {
00025                         NPC_ClearLookTarget( self );
00026                 }
00027 
00028                 if ( self->NPC && self->enemy == self->NPC->goalEntity )
00029                 {
00030                         self->NPC->goalEntity = NULL;
00031                 }
00032                 //FIXME: set last enemy?
00033         }
00034 
00035         self->enemy = NULL;
00036 }
00037 
00038 /*
00039 -------------------------
00040 NPC_AngerAlert
00041 -------------------------
00042 */
00043 
00044 #define ANGER_ALERT_RADIUS                      512
00045 #define ANGER_ALERT_SOUND_RADIUS        256
00046 
00047 void G_AngerAlert( gentity_t *self )
00048 {
00049         if ( self && self->NPC && (self->NPC->scriptFlags&SCF_NO_GROUPS) )
00050         {//I'm not a team playa...
00051                 return;
00052         }
00053         if ( !TIMER_Done( self, "interrogating" ) )
00054         {//I'm interrogating, don't wake everyone else up yet... FIXME: this may never wake everyone else up, though!
00055                 return;
00056         }
00057         //FIXME: hmm.... with all the other new alerts now, is this still neccesary or even a good idea...?
00058         G_AlertTeam( self, self->enemy, ANGER_ALERT_RADIUS, ANGER_ALERT_SOUND_RADIUS ); 
00059 }
00060 
00061 /*
00062 -------------------------
00063 G_TeamEnemy
00064 -------------------------
00065 */
00066 
00067 qboolean G_TeamEnemy( gentity_t *self )
00068 {//FIXME: Probably a better way to do this, is a linked list of your teammates already available?
00069         int     i;
00070         gentity_t       *ent;
00071 
00072         if ( !self->client || self->client->playerTeam == TEAM_FREE )
00073         {
00074                 return qfalse;
00075         }
00076         if ( self && self->NPC && (self->NPC->scriptFlags&SCF_NO_GROUPS) )
00077         {//I'm not a team playa...
00078                 return qfalse;
00079         }
00080 
00081         for( i = 1; i < level.num_entities; i++ )
00082         {
00083                 ent = &g_entities[i];
00084 
00085                 if ( ent == self )
00086                 {
00087                         continue;
00088                 }
00089 
00090                 if ( ent->health <= 0 )
00091                 {
00092                         continue;
00093                 }
00094 
00095                 if ( !ent->client )
00096                 {
00097                         continue;
00098                 }
00099 
00100                 if ( ent->client->playerTeam != self->client->playerTeam )
00101                 {//ent is not on my team
00102                         continue;
00103                 }
00104 
00105                 if ( ent->enemy )
00106                 {//they have an enemy
00107                         if ( !ent->enemy->client || ent->enemy->client->playerTeam != self->client->playerTeam )
00108                         {//the ent's enemy is either a normal ent or is a player/NPC that is not on my team
00109                                 return qtrue;
00110                         }
00111                 }
00112         }
00113 
00114         return qfalse;
00115 }
00116 
00117 void G_AttackDelay( gentity_t *self, gentity_t *enemy )
00118 {
00119         if ( enemy && self->client && self->NPC )
00120         {//delay their attack based on how far away they're facing from enemy
00121                 vec3_t          fwd, dir;
00122                 int                     attDelay;
00123 
00124                 VectorSubtract( self->client->renderInfo.eyePoint, enemy->r.currentOrigin, dir );//purposely backwards
00125                 VectorNormalize( dir );
00126                 AngleVectors( self->client->renderInfo.eyeAngles, fwd, NULL, NULL );
00127                 //dir[2] = fwd[2] = 0;//ignore z diff?
00128                 
00129                 attDelay = (4-g_spskill.integer)*500;//initial: from 1000ms delay on hard to 2000ms delay on easy
00130                 if ( self->client->playerTeam == NPCTEAM_PLAYER )
00131                 {//invert
00132                         attDelay = 2000-attDelay;
00133                 }
00134                 attDelay += floor( (DotProduct( fwd, dir )+1.0f) * 2000.0f );//add up to 4000ms delay if they're facing away
00135 
00136                 //FIXME: should distance matter, too?
00137 
00138                 //Now modify the delay based on NPC_class, weapon, and team
00139                 //NOTE: attDelay should be somewhere between 1000 to 6000 milliseconds
00140                 switch ( self->client->NPC_class )
00141                 {
00142                 case CLASS_IMPERIAL://they give orders and hang back
00143                         attDelay += Q_irand( 500, 1500 );
00144                         break;
00145                 case CLASS_STORMTROOPER://stormtroopers shoot sooner
00146                         if ( self->NPC->rank >= RANK_LT )
00147                         {//officers shoot even sooner
00148                                 attDelay -= Q_irand( 500, 1500 );
00149                         }
00150                         else
00151                         {//normal stormtroopers don't have as fast reflexes as officers
00152                                 attDelay -= Q_irand( 0, 1000 );
00153                         }
00154                         break;
00155                 case CLASS_SWAMPTROOPER://shoot very quickly?  What about guys in water?
00156                         attDelay -= Q_irand( 1000, 2000 );
00157                         break;
00158                 case CLASS_IMPWORKER://they panic, don't fire right away
00159                         attDelay += Q_irand( 1000, 2500 );
00160                         break;
00161                 case CLASS_TRANDOSHAN:
00162                         attDelay -= Q_irand( 500, 1500 );
00163                         break;
00164                 case CLASS_JAN:                         
00165                 case CLASS_LANDO:                       
00166                 case CLASS_PRISONER:
00167                 case CLASS_REBEL:
00168                         attDelay -= Q_irand( 500, 1500 );
00169                         break;
00170                 case CLASS_GALAKMECH:   
00171                 case CLASS_ATST:                
00172                         attDelay -= Q_irand( 1000, 2000 );
00173                         break;
00174                 case CLASS_REELO:
00175                 case CLASS_UGNAUGHT:
00176                 case CLASS_JAWA:
00177                         return;
00178                         break;
00179                 case CLASS_MINEMONSTER:
00180                 case CLASS_MURJJ:
00181                         return;
00182                         break;
00183                 case CLASS_INTERROGATOR:
00184                 case CLASS_PROBE:               
00185                 case CLASS_MARK1:               
00186                 case CLASS_MARK2:               
00187                 case CLASS_SENTRY:              
00188                         return;
00189                         break;
00190                 case CLASS_REMOTE:
00191                 case CLASS_SEEKER:
00192                         return;
00193                         break;
00194                 /*
00195                 case CLASS_GRAN:
00196                 case CLASS_RODIAN:
00197                 case CLASS_WEEQUAY:
00198                         break;
00199                 case CLASS_JEDI:                                
00200                 case CLASS_SHADOWTROOPER:
00201                 case CLASS_TAVION:
00202                 case CLASS_REBORN:
00203                 case CLASS_LUKE:                                
00204                 case CLASS_DESANN:                      
00205                         break;
00206                 */
00207                 }
00208 
00209                 switch ( self->s.weapon )
00210                 {
00211                 case WP_NONE:
00212                 case WP_SABER:
00213                         return;
00214                         break;
00215                 case WP_BRYAR_PISTOL:
00216                         break;
00217                 case WP_BLASTER:
00218                         if ( self->NPC->scriptFlags & SCF_ALT_FIRE )
00219                         {//rapid-fire blasters
00220                                 attDelay += Q_irand( 0, 500 );
00221                         }
00222                         else
00223                         {//regular blaster
00224                                 attDelay -= Q_irand( 0, 500 );
00225                         }
00226                         break;
00227                 case WP_BOWCASTER:
00228                         attDelay += Q_irand( 0, 500 );
00229                         break;
00230                 case WP_REPEATER:
00231                         if ( !(self->NPC->scriptFlags&SCF_ALT_FIRE) )
00232                         {//rapid-fire blasters
00233                                 attDelay += Q_irand( 0, 500 );
00234                         }
00235                         break;
00236                 case WP_FLECHETTE:
00237                         attDelay += Q_irand( 500, 1500 );
00238                         break;
00239                 case WP_ROCKET_LAUNCHER:
00240                         attDelay += Q_irand( 500, 1500 );
00241                         break;
00242 //              case WP_BLASTER_PISTOL: // apparently some enemy only version of the blaster
00243 //                      attDelay -= Q_irand( 500, 1500 );
00244 //                      break;
00245                         //rwwFIXMEFIXME: Have this weapon for NPCs?
00246                 case WP_DISRUPTOR://sniper's don't delay?
00247                         return;
00248                         break;
00249                 case WP_THERMAL://grenade-throwing has a built-in delay
00250                         return;
00251                         break;
00252                 case WP_STUN_BATON:                     // Any ol' melee attack
00253                         return;
00254                         break;
00255                 case WP_EMPLACED_GUN:
00256                         return;
00257                         break;
00258                 case WP_TURRET:                 // turret guns 
00259                         return;
00260                         break;
00261 //              case WP_BOT_LASER:              // Probe droid  - Laser blast
00262 //                      return; //rwwFIXMEFIXME: Have this weapon for NPCs?
00263                         break;
00264                 /*
00265                 case WP_DEMP2:
00266                         break;
00267                 case WP_TRIP_MINE:
00268                         break;
00269                 case WP_DET_PACK:
00270                         break;
00271                 case WP_STUN_BATON:
00272                         break;
00273                 case WP_ATST_MAIN:
00274                         break;
00275                 case WP_ATST_SIDE:
00276                         break;
00277                 case WP_TIE_FIGHTER:
00278                         break;
00279                 case WP_RAPID_FIRE_CONC:
00280                         break;
00281                 */
00282                 }
00283 
00284                 if ( self->client->playerTeam == NPCTEAM_PLAYER )
00285                 {//clamp it
00286                         if ( attDelay > 2000 )
00287                         {
00288                                 attDelay = 2000;
00289                         }
00290                 }
00291 
00292                 //don't shoot right away
00293                 if ( attDelay > 4000+((2-g_spskill.integer)*3000) )
00294                 {
00295                         attDelay = 4000+((2-g_spskill.integer)*3000);
00296                 }
00297                 TIMER_Set( self, "attackDelay", attDelay );//Q_irand( 1500, 4500 ) );
00298                 //don't move right away either
00299                 if ( attDelay > 4000 )
00300                 {
00301                         attDelay = 4000 - Q_irand(500, 1500);
00302                 }
00303                 else
00304                 {
00305                         attDelay -= Q_irand(500, 1500);
00306                 }
00307 
00308                 TIMER_Set( self, "roamTime", attDelay );//was Q_irand( 1000, 3500 );
00309         }
00310 }
00311 
00312 void G_ForceSaberOn(gentity_t *ent)
00313 {
00314         if (ent->client->ps.saberInFlight)
00315         { //alright, can't turn it on now in any case, so forget it.
00316                 return;
00317         }
00318 
00319         if (!ent->client->ps.saberHolstered)
00320         { //it's already on!
00321                 return;
00322         }
00323 
00324         if (ent->client->ps.weapon != WP_SABER)
00325         { //This probably should never happen. But if it does we'll just return without complaining.
00326                 return;
00327         }
00328 
00329         //Well then, turn it on.
00330         ent->client->ps.saberHolstered = 0;
00331 
00332         if (ent->client->saber[0].soundOn)
00333         {
00334                 G_Sound(ent, CHAN_AUTO, ent->client->saber[0].soundOn);
00335         }
00336         if (ent->client->saber[1].soundOn)
00337         {
00338                 G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOn);
00339         }
00340 }
00341 
00342 
00343 /*
00344 -------------------------
00345 G_SetEnemy
00346 -------------------------
00347 */
00348 void G_AimSet( gentity_t *self, int aim );
00349 void G_SetEnemy( gentity_t *self, gentity_t *enemy )
00350 {
00351         int     event = 0;
00352         
00353         //Must be valid
00354         if ( enemy == NULL )
00355                 return;
00356 
00357         //Must be valid
00358         if ( enemy->inuse == 0 )
00359         {
00360                 return;
00361         }
00362 
00363         //Don't take the enemy if in notarget
00364         if ( enemy->flags & FL_NOTARGET )
00365                 return;
00366 
00367         if ( !self->NPC )
00368         {
00369                 self->enemy = enemy;
00370                 return;
00371         }
00372 
00373         if ( self->NPC->confusionTime > level.time )
00374         {//can't pick up enemies if confused
00375                 return;
00376         }
00377 
00378 #ifdef _DEBUG
00379         if ( self->s.number )
00380         {
00381                 assert( enemy != self );
00382         }
00383 #endif// _DEBUG
00384         
00385 //      if ( enemy->client && enemy->client->playerTeam == TEAM_DISGUISE )
00386 //      {//unmask the player
00387 //              enemy->client->playerTeam = TEAM_PLAYER;
00388 //      }
00389         
00390         if ( self->client && self->NPC && enemy->client && enemy->client->playerTeam == self->client->playerTeam )
00391         {//Probably a damn script!
00392                 if ( self->NPC->charmedTime > level.time )
00393                 {//Probably a damn script!
00394                         return;
00395                 }
00396         }
00397 
00398         if ( self->NPC && self->client && self->client->ps.weapon == WP_SABER )
00399         {
00400                 //when get new enemy, set a base aggression based on what that enemy is using, how far they are, etc.
00401                 NPC_Jedi_RateNewEnemy( self, enemy );
00402         }
00403 
00404         //NOTE: this is not necessarily true!
00405         //self->NPC->enemyLastSeenTime = level.time;
00406 
00407         if ( self->enemy == NULL )
00408         {
00409                 //TEMP HACK: turn on our saber
00410                 if ( self->health > 0 )
00411                 {
00412                         G_ForceSaberOn(self);
00413                 }
00414 
00415                 //FIXME: Have to do this to prevent alert cascading
00416                 G_ClearEnemy( self );
00417                 self->enemy = enemy;
00418 
00419                 //Special case- if player is being hunted by his own people, set their enemy team correctly
00420                 if ( self->client->playerTeam == NPCTEAM_PLAYER && enemy->s.number == 0 )
00421                 {
00422                         self->client->enemyTeam = NPCTEAM_PLAYER;
00423                 }
00424 
00425                 //If have an anger script, run that instead of yelling
00426                 if( G_ActivateBehavior( self, BSET_ANGER ) )
00427                 {
00428                 }
00429                 else if ( self->client && enemy->client && self->client->playerTeam != enemy->client->playerTeam )
00430                 {
00431                         //FIXME: Use anger when entire team has no enemy.
00432                         //               Basically, you're first one to notice enemies
00433                         //if ( self->forcePushTime < level.time ) // not currently being pushed
00434                         if (1) //rwwFIXMEFIXME: Set forcePushTime
00435                         {
00436                                 if ( !G_TeamEnemy( self ) )
00437                                 {//team did not have an enemy previously
00438                                         event = Q_irand(EV_ANGER1, EV_ANGER3);
00439                                 }
00440                         }
00441                         
00442                         if ( event )
00443                         {//yell
00444                                 G_AddVoiceEvent( self, event, 2000 );
00445                         }
00446                 }
00447                 
00448                 if ( self->s.weapon == WP_BLASTER || self->s.weapon == WP_REPEATER ||
00449                         self->s.weapon == WP_THERMAL /*|| self->s.weapon == WP_BLASTER_PISTOL */ //rwwFIXMEFIXME: Blaster pistol useable by npcs?
00450                         || self->s.weapon == WP_BOWCASTER )
00451                 {//Hmm, how about sniper and bowcaster?
00452                         //When first get mad, aim is bad
00453                         //Hmm, base on game difficulty, too?  Rank?
00454                         if ( self->client->playerTeam == NPCTEAM_PLAYER )
00455                         {
00456                                 G_AimSet( self, Q_irand( self->NPC->stats.aim - (5*(g_spskill.integer)), self->NPC->stats.aim - g_spskill.integer ) );
00457                         }
00458                         else
00459                         {
00460                                 int minErr = 3;
00461                                 int maxErr = 12;
00462                                 if ( self->client->NPC_class == CLASS_IMPWORKER )
00463                                 {
00464                                         minErr = 15;
00465                                         maxErr = 30;
00466                                 }
00467                                 else if ( self->client->NPC_class == CLASS_STORMTROOPER && self->NPC && self->NPC->rank <= RANK_CREWMAN )
00468                                 {
00469                                         minErr = 5;
00470                                         maxErr = 15;
00471                                 }
00472 
00473                                 G_AimSet( self, Q_irand( self->NPC->stats.aim - (maxErr*(3-g_spskill.integer)), self->NPC->stats.aim - (minErr*(3-g_spskill.integer)) ) );
00474                         }
00475                 }
00476                 
00477                 //Alert anyone else in the area
00478                 if ( Q_stricmp( "desperado", self->NPC_type ) != 0 && Q_stricmp( "paladin", self->NPC_type ) != 0 )
00479                 {//special holodeck enemies exception
00480                         if ( self->client->ps.fd.forceGripBeingGripped < level.time )
00481                         {//gripped people can't call for help
00482                                 G_AngerAlert( self );
00483                         }
00484                 }
00485 
00486                 //Stormtroopers don't fire right away!
00487                 G_AttackDelay( self, enemy );
00488 
00489                 //rwwFIXMEFIXME: Deal with this some other way.
00490                 /*
00491                 //FIXME: this is a disgusting hack that is supposed to make the Imperials start with their weapon holstered- need a better way
00492                 if ( self->client->ps.weapon == WP_NONE && !Q_strncmp( self->NPC_type, "imp", 3 ) && !(self->NPC->scriptFlags & SCF_FORCED_MARCH)  )
00493                 {
00494                         if ( self->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BLASTER ) )
00495                         {
00496                                 ChangeWeapon( self, WP_BLASTER );
00497                                 self->client->ps.weapon = WP_BLASTER;
00498                                 self->client->ps.weaponstate = WEAPON_READY;
00499                                 G_CreateG2AttachedWeaponModel( self, weaponData[WP_BLASTER].weaponMdl, self->handRBolt, 0 );
00500                         }
00501                         else if ( self->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BLASTER_PISTOL ) )
00502                         {
00503                                 ChangeWeapon( self, WP_BLASTER_PISTOL );
00504                                 self->client->ps.weapon = WP_BLASTER_PISTOL;
00505                                 self->client->ps.weaponstate = WEAPON_READY;
00506                                 G_CreateG2AttachedWeaponModel( self, weaponData[WP_BLASTER_PISTOL].weaponMdl, self->handRBolt, 0 );
00507                         }
00508                 }
00509                 */
00510                 return;
00511         }
00512         
00513         //Otherwise, just picking up another enemy
00514 
00515         if ( event )
00516         {
00517                 G_AddVoiceEvent( self, event, 2000 );
00518         }
00519 
00520         //Take the enemy
00521         G_ClearEnemy(self);
00522         self->enemy = enemy;
00523 }
00524 
00525 /*
00526 int ChooseBestWeapon( void ) 
00527 {
00528         int             n;
00529         int             weapon;
00530 
00531         // check weapons in the NPC's weapon preference order
00532         for ( n = 0; n < MAX_WEAPONS; n++ ) 
00533         {
00534                 weapon = NPCInfo->weaponOrder[n];
00535 
00536                 if ( weapon == WP_NONE ) 
00537                 {
00538                         break;
00539                 }
00540 
00541                 if ( !HaveWeapon( weapon ) ) 
00542                 {
00543                         continue;
00544                 }
00545 
00546                 if ( client->ps.ammo[weaponData[weapon].ammoIndex] ) 
00547                 {
00548                         return weapon;
00549                 }
00550         }
00551 
00552         // check weapons serially (mainly in case a weapon is not on the NPC's list)
00553         for ( weapon = 1; weapon < WP_NUM_WEAPONS; weapon++ ) 
00554         {
00555                 if ( !HaveWeapon( weapon ) ) 
00556                 {
00557                         continue;
00558                 }
00559 
00560                 if ( client->ps.ammo[weaponData[weapon].ammoIndex] ) 
00561                 {
00562                         return weapon;
00563                 }
00564         }
00565 
00566         return client->ps.weapon;
00567 }
00568 */
00569 
00570 void ChangeWeapon( gentity_t *ent, int newWeapon ) 
00571 {
00572         if ( !ent || !ent->client || !ent->NPC )
00573         {
00574                 return;
00575         }
00576 
00577         ent->client->ps.weapon = newWeapon;
00578         ent->client->pers.cmd.weapon = newWeapon;
00579         ent->NPC->shotTime = 0;
00580         ent->NPC->burstCount = 0;
00581         ent->NPC->attackHold = 0;
00582         ent->NPC->currentAmmo = ent->client->ps.ammo[weaponData[newWeapon].ammoIndex];
00583 
00584         switch ( newWeapon ) 
00585         {
00586         case WP_BRYAR_PISTOL://prifle
00587                 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00588                 ent->NPC->burstSpacing = 1000;//attackdebounce
00589                 break;
00590 
00591                 /*
00592         case WP_BLASTER_PISTOL:
00593                 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00594         //      ent->NPC->burstSpacing = 1000;//attackdebounce
00595                 if ( g_spskill.integer == 0 )
00596                         ent->NPC->burstSpacing = 1000;//attack debounce
00597                 else if ( g_spskill.integer == 1 )
00598                         ent->NPC->burstSpacing = 750;//attack debounce
00599                 else 
00600                         ent->NPC->burstSpacing = 500;//attack debounce
00601                 break;
00602                 */
00603                 //rwwFIXMEFIXME: support WP_BLASTER_PISTOL and WP_BOT_LASER
00604 
00605                 /*
00606         case WP_BOT_LASER://probe attack
00607                 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00608         //      ent->NPC->burstSpacing = 600;//attackdebounce
00609                 if ( g_spskill.integer == 0 )
00610                         ent->NPC->burstSpacing = 600;//attack debounce
00611                 else if ( g_spskill.integer == 1 )
00612                         ent->NPC->burstSpacing = 400;//attack debounce
00613                 else 
00614                         ent->NPC->burstSpacing = 200;//attack debounce
00615                 break;
00616                 */
00617 
00618         case WP_SABER:
00619                 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00620                 ent->NPC->burstSpacing = 0;//attackdebounce
00621                 break;
00622 
00623         case WP_DISRUPTOR:
00624                 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00625                 if ( ent->NPC->scriptFlags & SCF_ALT_FIRE )
00626                 {
00627                         switch( g_spskill.integer )
00628                         {
00629                         case 0:
00630                                 ent->NPC->burstSpacing = 2500;//attackdebounce
00631                                 break;
00632                         case 1:
00633                                 ent->NPC->burstSpacing = 2000;//attackdebounce
00634                                 break;
00635                         case 2:
00636                                 ent->NPC->burstSpacing = 1500;//attackdebounce
00637                                 break;
00638                         }
00639                 }
00640                 else
00641                 {
00642                         ent->NPC->burstSpacing = 1000;//attackdebounce
00643                 }
00644                 break;
00645 
00646         case WP_BOWCASTER:
00647                 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00648         //      ent->NPC->burstSpacing = 1000;//attackdebounce
00649                 if ( g_spskill.integer == 0 )
00650                         ent->NPC->burstSpacing = 1000;//attack debounce
00651                 else if ( g_spskill.integer == 1 )
00652                         ent->NPC->burstSpacing = 750;//attack debounce
00653                 else 
00654                         ent->NPC->burstSpacing = 500;//attack debounce
00655                 break;
00656 
00657         case WP_REPEATER:
00658                 if ( ent->NPC->scriptFlags & SCF_ALT_FIRE )
00659                 {
00660                         ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00661                         ent->NPC->burstSpacing = 2000;//attackdebounce
00662                 }
00663                 else
00664                 {
00665                         ent->NPC->aiFlags |= NPCAI_BURST_WEAPON;
00666                         ent->NPC->burstMin = 3;
00667                         ent->NPC->burstMean = 6;
00668                         ent->NPC->burstMax = 10;
00669                         if ( g_spskill.integer == 0 )
00670                                 ent->NPC->burstSpacing = 1500;//attack debounce
00671                         else if ( g_spskill.integer == 1 )
00672                                 ent->NPC->burstSpacing = 1000;//attack debounce
00673                         else 
00674                                 ent->NPC->burstSpacing = 500;//attack debounce
00675                 }
00676                 break;
00677 
00678         case WP_DEMP2:
00679                 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00680                 ent->NPC->burstSpacing = 1000;//attackdebounce
00681                 break;
00682 
00683         case WP_FLECHETTE:
00684                 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00685                 if ( ent->NPC->scriptFlags & SCF_ALT_FIRE )
00686                 {
00687                         ent->NPC->burstSpacing = 2000;//attackdebounce
00688                 }
00689                 else
00690                 {
00691                         ent->NPC->burstSpacing = 1000;//attackdebounce
00692                 }
00693                 break;
00694 
00695         case WP_ROCKET_LAUNCHER:
00696                 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00697         //      ent->NPC->burstSpacing = 2500;//attackdebounce
00698                 if ( g_spskill.integer == 0 )
00699                         ent->NPC->burstSpacing = 2500;//attack debounce
00700                 else if ( g_spskill.integer == 1 )
00701                         ent->NPC->burstSpacing = 2000;//attack debounce
00702                 else 
00703                         ent->NPC->burstSpacing = 1500;//attack debounce
00704                 break;
00705 
00706         case WP_THERMAL:
00707                 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00708         //      ent->NPC->burstSpacing = 3000;//attackdebounce
00709                 if ( g_spskill.integer == 0 )
00710                         ent->NPC->burstSpacing = 3000;//attack debounce
00711                 else if ( g_spskill.integer == 1 )
00712                         ent->NPC->burstSpacing = 2500;//attack debounce
00713                 else 
00714                         ent->NPC->burstSpacing = 2000;//attack debounce
00715                 break;
00716 
00717         /*
00718         case WP_SABER:
00719                 ent->NPC->aiFlags |= NPCAI_BURST_WEAPON;
00720                 ent->NPC->burstMin = 5;//0.5 sec
00721                 ent->NPC->burstMean = 10;//1 second
00722                 ent->NPC->burstMax = 20;//3 seconds
00723                 ent->NPC->burstSpacing = 2000;//2 seconds
00724                 ent->NPC->attackHold = 1000;//Hold attack button for a 1-second burst
00725                 break;
00726         
00727         case WP_TRICORDER:
00728                 ent->NPC->aiFlags |= NPCAI_BURST_WEAPON;
00729                 ent->NPC->burstMin = 5;
00730                 ent->NPC->burstMean = 10;
00731                 ent->NPC->burstMax = 30;
00732                 ent->NPC->burstSpacing = 1000;
00733                 break;
00734         */
00735 
00736         case WP_BLASTER:
00737                 if ( ent->NPC->scriptFlags & SCF_ALT_FIRE )
00738                 {
00739                         ent->NPC->aiFlags |= NPCAI_BURST_WEAPON;
00740                         ent->NPC->burstMin = 3;
00741                         ent->NPC->burstMean = 3;
00742                         ent->NPC->burstMax = 3;
00743                         if ( g_spskill.integer == 0 )
00744                                 ent->NPC->burstSpacing = 1500;//attack debounce
00745                         else if ( g_spskill.integer == 1 )
00746                                 ent->NPC->burstSpacing = 1000;//attack debounce
00747                         else 
00748                                 ent->NPC->burstSpacing = 500;//attack debounce
00749                 }
00750                 else
00751                 {
00752                         ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00753                         if ( g_spskill.integer == 0 )
00754                                 ent->NPC->burstSpacing = 1000;//attack debounce
00755                         else if ( g_spskill.integer == 1 )
00756                                 ent->NPC->burstSpacing = 750;//attack debounce
00757                         else 
00758                                 ent->NPC->burstSpacing = 500;//attack debounce
00759                 //      ent->NPC->burstSpacing = 1000;//attackdebounce
00760                 }
00761                 break;
00762 
00763         case WP_STUN_BATON:
00764                 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00765                 ent->NPC->burstSpacing = 1000;//attackdebounce
00766                 break;
00767 
00768                 /*
00769         case WP_ATST_MAIN:
00770         case WP_ATST_SIDE:
00771                 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00772         //      ent->NPC->burstSpacing = 1000;//attackdebounce
00773                         if ( g_spskill.integer == 0 )
00774                                 ent->NPC->burstSpacing = 1000;//attack debounce
00775                         else if ( g_spskill.integer == 1 )
00776                                 ent->NPC->burstSpacing = 750;//attack debounce
00777                         else 
00778                                 ent->NPC->burstSpacing = 500;//attack debounce
00779                 break;
00780                 */
00781                 //rwwFIXMEFIXME: support for atst weaps
00782 
00783         case WP_EMPLACED_GUN:
00784                 //FIXME: give some designer-control over this?
00785                 if ( ent->client && ent->client->NPC_class == CLASS_REELO )
00786                 {
00787                         ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00788                         ent->NPC->burstSpacing = 1000;//attack debounce
00789         //              if ( g_spskill.integer == 0 )
00790         //                      ent->NPC->burstSpacing = 300;//attack debounce
00791         //              else if ( g_spskill.integer == 1 )
00792         //                      ent->NPC->burstSpacing = 200;//attack debounce
00793         //              else 
00794         //                      ent->NPC->burstSpacing = 100;//attack debounce
00795                 }
00796                 else
00797                 {
00798                         ent->NPC->aiFlags |= NPCAI_BURST_WEAPON;
00799                         ent->NPC->burstMin = 2; // 3 shots, really
00800                         ent->NPC->burstMean = 2;
00801                         ent->NPC->burstMax = 2;
00802 
00803                         if ( ent->parent ) // if we have an owner, it should be the chair at this point...so query the chair for its shot debounce times, etc.
00804                         {
00805                                 if ( g_spskill.integer == 0 )
00806                                 {
00807                                         ent->NPC->burstSpacing = ent->parent->wait + 400;//attack debounce
00808                                         ent->NPC->burstMin = ent->NPC->burstMax = 1; // two shots
00809                                 }
00810                                 else if ( g_spskill.integer == 1 )
00811                                 {
00812                                         ent->NPC->burstSpacing = ent->parent->wait + 200;//attack debounce
00813                                 }
00814                                 else 
00815                                 {
00816                                         ent->NPC->burstSpacing = ent->parent->wait;//attack debounce
00817                                 }
00818                         }
00819                         else
00820                         {
00821                                 if ( g_spskill.integer == 0 )
00822                                 {
00823                                         ent->NPC->burstSpacing = 1200;//attack debounce
00824                                         ent->NPC->burstMin = ent->NPC->burstMax = 1; // two shots
00825                                 }
00826                                 else if ( g_spskill.integer == 1 )
00827                                 {
00828                                         ent->NPC->burstSpacing = 1000;//attack debounce
00829                                 }
00830                                 else 
00831                                 {
00832                                         ent->NPC->burstSpacing = 800;//attack debounce
00833                                 }
00834                         }
00835                 }
00836                 break;
00837 
00838         default:
00839                 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
00840                 break;
00841         }
00842 }
00843 
00844 void NPC_ChangeWeapon( int newWeapon ) 
00845 {
00846         /*
00847         qboolean        changing = qfalse;
00848         if ( newWeapon != NPC->client->ps.weapon )
00849         {
00850                 changing = qtrue;
00851         }
00852         if ( changing && NPC->weaponModel[0] > 9 )
00853         {
00854                 trap_G2API_RemoveGhoul2Model( NPC->ghoul2, NPC->weaponModel[0] );
00855         }
00856         ChangeWeapon( NPC, newWeapon );
00857         if ( changing && NPC->client->ps.weapon != WP_NONE )
00858         {
00859                 if ( NPC->client->ps.weapon == WP_SABER )
00860                 {
00861                         G_CreateG2AttachedWeaponModel( NPC, NPC->client->ps.saber[0].model, NPC->handRBolt, 0 );
00862                         if ( NPC->client->ps.dualSabers )
00863                         {
00864                                 G_CreateG2AttachedWeaponModel( NPC, NPC->client->ps.saber[1].model, NPC->handLBolt, 0 );
00865                         }
00866                 }
00867                 else
00868                 {
00869                         G_CreateG2AttachedWeaponModel( NPC, weaponData[NPC->client->ps.weapon].weaponMdl, NPC->handRBolt, 0 );
00870                 }
00871         }*/
00872         //rwwFIXMEFIXME: Change the same way as players, all this stuff is just crazy.
00873 }
00874 /*
00875 void NPC_ApplyWeaponFireDelay(void)
00876 How long, if at all, in msec the actual fire should delay from the time the attack was started
00877 */
00878 void NPC_ApplyWeaponFireDelay(void)
00879 {       
00880         if ( NPC->attackDebounceTime > level.time )
00881         {//Just fired, if attacking again, must be a burst fire, so don't add delay
00882                 //NOTE: Borg AI uses attackDebounceTime "incorrectly", so this will always return for them!
00883                 return;
00884         }
00885         
00886         switch(client->ps.weapon)
00887         {
00888                 /*
00889         case WP_BOT_LASER:
00890                 NPCInfo->burstCount = 0;
00891                 client->ps.weaponTime = 500;
00892                 break;
00893                 */ //rwwFIXMEFIXME: support for this
00894 
00895         case WP_THERMAL:
00896                 if ( client->ps.clientNum )
00897                 {//NPCs delay... 
00898                         //FIXME: player should, too, but would feel weird in 1st person, even though it
00899                         //                      would look right in 3rd person.  Really should have a wind-up anim
00900                         //                      for player as he holds down the fire button to throw, then play
00901                         //                      the actual throw when he lets go...
00902                         client->ps.weaponTime = 700;
00903                 }
00904                 break;
00905 
00906         case WP_STUN_BATON:
00907                 //if ( !PM_DroidMelee( client->NPC_class ) )
00908                 if (1) //rwwFIXMEFIXME: ...
00909                 {//FIXME: should be unique per melee anim
00910                         client->ps.weaponTime = 300;
00911                 }
00912                 break;
00913 
00914         default:
00915                 client->ps.weaponTime = 0;
00916                 break;
00917         }
00918 }
00919 
00920 /*
00921 -------------------------
00922 ShootThink
00923 -------------------------
00924 */
00925 void ShootThink( void ) 
00926 {
00927         int                     delay;
00928 
00929         ucmd.buttons &= ~BUTTON_ATTACK;
00930 /*
00931         if ( enemyVisibility != VIS_SHOOT) 
00932                 return;
00933 */
00934 
00935         if ( client->ps.weapon == WP_NONE )
00936                 return;
00937 
00938         if ( client->ps.weaponstate != WEAPON_READY && client->ps.weaponstate != WEAPON_FIRING && client->ps.weaponstate != WEAPON_IDLE) 
00939                 return;
00940 
00941         if ( level.time < NPCInfo->shotTime ) 
00942         {
00943                 return;
00944         }
00945 
00946         ucmd.buttons |= BUTTON_ATTACK;
00947 
00948         NPCInfo->currentAmmo = client->ps.ammo[weaponData[client->ps.weapon].ammoIndex];        // checkme
00949 
00950         NPC_ApplyWeaponFireDelay();
00951 
00952         if ( NPCInfo->aiFlags & NPCAI_BURST_WEAPON ) 
00953         {
00954                 if ( !NPCInfo->burstCount ) 
00955                 {
00956                         NPCInfo->burstCount = Q_irand( NPCInfo->burstMin, NPCInfo->burstMax );
00957                         /*
00958                         NPCInfo->burstCount = erandom( NPCInfo->burstMean );
00959                         if ( NPCInfo->burstCount < NPCInfo->burstMin ) 
00960                         {
00961                                 NPCInfo->burstCount = NPCInfo->burstMin;
00962                         }
00963                         else if ( NPCInfo->burstCount > NPCInfo->burstMax ) 
00964                         {
00965                                 NPCInfo->burstCount = NPCInfo->burstMax;
00966                         }
00967                         */
00968                         delay = 0;
00969                 }
00970                 else 
00971                 {
00972                         NPCInfo->burstCount--;
00973                         if ( NPCInfo->burstCount == 0 ) 
00974                         {
00975                                 delay = NPCInfo->burstSpacing;
00976                         }
00977                         else 
00978                         {
00979                                 delay = 0;
00980                         }
00981                 }
00982 
00983                 if ( !delay )
00984                 {
00985                         // HACK: dirty little emplaced bits, but is done because it would otherwise require some sort of new variable...
00986                         if ( client->ps.weapon == WP_EMPLACED_GUN )
00987                         {
00988                                 if ( NPC->parent ) // try and get the debounce values from the chair if we can
00989                                 {
00990                                         if ( g_spskill.integer == 0 )
00991                                         {
00992                                                 delay = NPC->parent->random + 150;
00993                                         }
00994                                         else if ( g_spskill.integer == 1 )
00995                                         {
00996                                                 delay = NPC->parent->random + 100;
00997                                         }
00998                                         else 
00999                                         {
01000                                                 delay = NPC->parent->random;
01001                                         }
01002                                 }
01003                                 else
01004                                 {
01005                                         if ( g_spskill.integer == 0 )
01006                                         {
01007                                                 delay = 350;
01008                                         }
01009                                         else if ( g_spskill.integer == 1 )
01010                                         {
01011                                                 delay = 300;
01012                                         }
01013                                         else 
01014                                         {
01015                                                 delay = 200;
01016                                         }
01017                                 }
01018                         }
01019                 }
01020         }
01021         else 
01022         {
01023                 delay = NPCInfo->burstSpacing;
01024         }
01025 
01026         NPCInfo->shotTime = level.time + delay;
01027         NPC->attackDebounceTime = level.time + NPC_AttackDebounceForWeapon();
01028 }
01029 
01030 /*
01031 static void WeaponThink( qboolean inCombat ) 
01032 FIXME makes this so there's a delay from event that caused us to check to actually doing it
01033 
01034 Added: hacks for Borg
01035 */
01036 void WeaponThink( qboolean inCombat ) 
01037 {
01038         if ( client->ps.weaponstate == WEAPON_RAISING || client->ps.weaponstate == WEAPON_DROPPING ) 
01039         {
01040                 ucmd.weapon = client->ps.weapon;
01041                 ucmd.buttons &= ~BUTTON_ATTACK;
01042                 return;
01043         }
01044 
01045 //MCG - Begin
01046         //For now, no-one runs out of ammo      
01047         if(NPC->client->ps.ammo[ weaponData[client->ps.weapon].ammoIndex ] < 10)        // checkme      
01048 //      if(NPC->client->ps.ammo[ client->ps.weapon ] < 10)
01049         {
01050                 Add_Ammo (NPC, client->ps.weapon, 100);
01051         }
01052 
01053         /*if ( NPC->playerTeam == TEAM_BORG )
01054         {//HACK!!!
01055                 if(!(NPC->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BORG_WEAPON )))
01056                         NPC->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BORG_WEAPON );
01057 
01058                 if ( client->ps.weapon != WP_BORG_WEAPON ) 
01059                 {
01060                         NPC_ChangeWeapon( WP_BORG_WEAPON );
01061                         Add_Ammo (NPC, client->ps.weapon, 10);
01062                         NPCInfo->currentAmmo = client->ps.ammo[client->ps.weapon];
01063                 }
01064         }
01065         else */
01066         
01067         /*if ( NPC->client->playerTeam == TEAM_SCAVENGERS )
01068         {//HACK!!!
01069                 if(!(NPC->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BLASTER )))
01070                         NPC->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BLASTER );
01071 
01072                 if ( client->ps.weapon != WP_BLASTER )
01073                          
01074                 {
01075                         NPC_ChangeWeapon( WP_BLASTER );
01076                         Add_Ammo (NPC, client->ps.weapon, 10);
01077 //                      NPCInfo->currentAmmo = client->ps.ammo[client->ps.weapon];
01078                         NPCInfo->currentAmmo = client->ps.ammo[weaponData[client->ps.weapon].ammoIndex];        // checkme
01079                 }
01080         }
01081         else*/
01082 //MCG - End
01083         {
01084                 // if the gun in our hands is out of ammo, we need to change
01085                 /*if ( client->ps.ammo[client->ps.weapon] == 0 ) 
01086                 {
01087                         NPCInfo->aiFlags |= NPCAI_CHECK_WEAPON;
01088                 }
01089 
01090                 if ( NPCInfo->aiFlags & NPCAI_CHECK_WEAPON ) 
01091                 {
01092                         NPCInfo->aiFlags &= ~NPCAI_CHECK_WEAPON;
01093                         bestWeapon = ChooseBestWeapon();
01094                         if ( bestWeapon != client->ps.weapon ) 
01095                         {
01096                                 NPC_ChangeWeapon( bestWeapon );
01097                         }
01098                 }*/
01099         }
01100 
01101         ucmd.weapon = client->ps.weapon;
01102         ShootThink();
01103 }
01104 
01105 /*
01106 HaveWeapon
01107 */
01108 
01109 qboolean HaveWeapon( int weapon ) 
01110 {
01111         return ( client->ps.stats[STAT_WEAPONS] & ( 1 << weapon ) );
01112 }
01113 
01114 qboolean EntIsGlass (gentity_t *check)
01115 {
01116         if(check->classname &&
01117                 !Q_stricmp("func_breakable", check->classname) &&
01118                 check->count == 1 && check->health <= 100)
01119         {
01120                 return qtrue;
01121         }
01122 
01123         return qfalse;
01124 }
01125 
01126 qboolean ShotThroughGlass (trace_t *tr, gentity_t *target, vec3_t spot, int mask)
01127 {
01128         gentity_t       *hit = &g_entities[ tr->entityNum ];
01129         if(hit != target && EntIsGlass(hit))
01130         {//ok to shoot through breakable glass
01131                 int                     skip = hit->s.number;
01132                 vec3_t          muzzle;
01133 
01134                 VectorCopy(tr->endpos, muzzle);
01135                 trap_Trace (tr, muzzle, NULL, NULL, spot, skip, mask );
01136                 return qtrue;
01137         }
01138 
01139         return qfalse;
01140 }
01141 
01142 /*
01143 CanShoot
01144 determine if NPC can directly target enemy
01145 
01146 this function does not check teams, invulnerability, notarget, etc....
01147 
01148 Added: If can't shoot center, try head, if not, see if it's close enough to try anyway.
01149 */
01150 qboolean CanShoot ( gentity_t *ent, gentity_t *shooter ) 
01151 {
01152         trace_t         tr;
01153         vec3_t          muzzle;
01154         vec3_t          spot, diff;
01155         gentity_t       *traceEnt;
01156 
01157         CalcEntitySpot( shooter, SPOT_WEAPON, muzzle );
01158         CalcEntitySpot( ent, SPOT_ORIGIN, spot );               //FIXME preferred target locations for some weapons (feet for R/L)
01159 
01160         trap_Trace ( &tr, muzzle, NULL, NULL, spot, shooter->s.number, MASK_SHOT );
01161         traceEnt = &g_entities[ tr.entityNum ];
01162 
01163         // point blank, baby!
01164         if (tr.startsolid && (shooter->NPC) && (shooter->NPC->touchedByPlayer) ) 
01165         {
01166                 traceEnt = shooter->NPC->touchedByPlayer;
01167         }
01168         
01169         if ( ShotThroughGlass( &tr, ent, spot, MASK_SHOT ) )
01170         {
01171                 traceEnt = &g_entities[ tr.entityNum ];
01172         }
01173 
01174         // shot is dead on
01175         if ( traceEnt == ent ) 
01176         {
01177                 return qtrue;
01178         }
01179 //MCG - Begin
01180         else
01181         {//ok, can't hit them in center, try their head
01182                 CalcEntitySpot( ent, SPOT_HEAD, spot );
01183                 trap_Trace ( &tr, muzzle, NULL, NULL, spot, shooter->s.number, MASK_SHOT );
01184                 traceEnt = &g_entities[ tr.entityNum ];
01185                 if ( traceEnt == ent) 
01186                 {
01187                         return qtrue;
01188                 }
01189         }
01190 
01191         //Actually, we should just check to fire in dir we're facing and if it's close enough,
01192         //and we didn't hit someone on our own team, shoot
01193         VectorSubtract(spot, tr.endpos, diff);
01194         if(VectorLength(diff) < random() * 32)
01195         {
01196                 return qtrue;
01197         }
01198 //MCG - End
01199         // shot would hit a non-client
01200         if ( !traceEnt->client ) 
01201         {
01202                 return qfalse;
01203         }
01204 
01205         // shot is blocked by another player
01206 
01207         // he's already dead, so go ahead
01208         if ( traceEnt->health <= 0 ) 
01209         {
01210                 return qtrue;
01211         }
01212 
01213         // don't deliberately shoot a teammate
01214         if ( traceEnt->client && ( traceEnt->client->playerTeam == shooter->client->playerTeam ) ) 
01215         {
01216                 return qfalse;
01217         }
01218 
01219         // he's just in the wrong place, go ahead
01220         return qtrue;
01221 }
01222 
01223 
01224 /*
01225 void NPC_CheckPossibleEnemy( gentity_t *other, visibility_t vis ) 
01226 
01227 Added: hacks for scripted NPCs
01228 */
01229 void NPC_CheckPossibleEnemy( gentity_t *other, visibility_t vis ) 
01230 {
01231         // is he is already our enemy?
01232         if ( other == NPC->enemy ) 
01233                 return;
01234 
01235         if ( other->flags & FL_NOTARGET ) 
01236                 return;
01237 
01238         // we already have an enemy and this guy is in our FOV, see if this guy would be better
01239         if ( NPC->enemy && vis == VIS_FOV ) 
01240         {
01241                 if ( NPCInfo->enemyLastSeenTime - level.time < 2000 ) 
01242                 {
01243                         return;
01244                 }
01245                 if ( enemyVisibility == VIS_UNKNOWN ) 
01246                 {
01247                         enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_360|CHECK_FOV );
01248                 }
01249                 if ( enemyVisibility == VIS_FOV ) 
01250                 {
01251                         return;
01252                 }
01253         }
01254 
01255         if ( !NPC->enemy )
01256         {//only take an enemy if you don't have one yet
01257                 G_SetEnemy( NPC, other );
01258         }
01259 
01260         if ( vis == VIS_FOV ) 
01261         {
01262                 NPCInfo->enemyLastSeenTime = level.time;
01263                 VectorCopy( other->r.currentOrigin, NPCInfo->enemyLastSeenLocation );
01264                 NPCInfo->enemyLastHeardTime = 0;
01265                 VectorClear( NPCInfo->enemyLastHeardLocation );
01266         } 
01267         else 
01268         {
01269                 NPCInfo->enemyLastSeenTime = 0;
01270                 VectorClear( NPCInfo->enemyLastSeenLocation );
01271                 NPCInfo->enemyLastHeardTime = level.time;
01272                 VectorCopy( other->r.currentOrigin, NPCInfo->enemyLastHeardLocation );
01273         }
01274 }
01275 
01276 
01277 //==========================================
01278 //MCG Added functions:
01279 //==========================================
01280 
01281 /*
01282 int NPC_AttackDebounceForWeapon (void)
01283 
01284 DOES NOT control how fast you can fire
01285 Only makes you keep your weapon up after you fire
01286 
01287 */
01288 int NPC_AttackDebounceForWeapon (void)
01289 {
01290         switch ( NPC->client->ps.weapon ) 
01291         {
01292 /*
01293         case WP_BLASTER://scav rifle
01294                 return 1000;
01295                 break;
01296 
01297         case WP_BRYAR_PISTOL://prifle
01298                 return 3000;
01299                 break;
01300 
01301         case WP_SABER:
01302                 return 100;
01303                 break;
01304         
01305 
01306         case WP_TRICORDER:
01307                 return 0;//tricorder
01308                 break;
01309 */
01310         case WP_SABER:
01311                 return 0;
01312                 break;
01313 
01314                 /*
01315         case WP_BOT_LASER:
01316                 
01317                 if ( g_spskill.integer == 0 )
01318                         return 2000;
01319 
01320                 if ( g_spskill.integer == 1 )
01321                         return 1500;
01322 
01323                 return 1000;
01324                 break;
01325                 */
01326                 //rwwFIXMEFIXME: support
01327         default:
01328                 return NPCInfo->burstSpacing;//was 100 by default
01329                 break;
01330         }
01331 }
01332 
01333 //FIXME: need a mindist for explosive weapons
01334 float NPC_MaxDistSquaredForWeapon (void)
01335 {
01336         if(NPCInfo->stats.shootDistance > 0)
01337         {//overrides default weapon dist
01338                 return NPCInfo->stats.shootDistance * NPCInfo->stats.shootDistance;
01339         }
01340 
01341         switch ( NPC->s.weapon ) 
01342         {
01343         case WP_BLASTER://scav rifle
01344                 return 1024 * 1024;//should be shorter?
01345                 break;
01346 
01347         case WP_BRYAR_PISTOL://prifle
01348                 return 1024 * 1024;
01349                 break;
01350 
01351                 /*
01352         case WP_BLASTER_PISTOL://prifle
01353                 return 1024 * 1024;
01354                 break;
01355                 */
01356 
01357         case WP_DISRUPTOR://disruptor
01358                 if ( NPCInfo->scriptFlags & SCF_ALT_FIRE )
01359                 {
01360                         return ( 4096 * 4096 );
01361                 }
01362                 else
01363                 {
01364                         return 1024 * 1024;
01365                 }
01366                 break;
01367 /*
01368         case WP_SABER:
01369                 return 1024 * 1024;
01370                 break;
01371         
01372 
01373         case WP_TRICORDER:
01374                 return 0;//tricorder
01375                 break;
01376 */
01377         case WP_SABER:
01378                 if ( NPC->client && NPC->client->saber[0].blade[0].lengthMax )
01379                 {//FIXME: account for whether enemy and I are heading towards each other!
01380                         return (NPC->client->saber[0].blade[0].lengthMax + NPC->r.maxs[0]*1.5)*(NPC->client->saber[0].blade[0].lengthMax + NPC->r.maxs[0]*1.5);
01381                 }
01382                 else
01383                 {
01384                         return 48*48;
01385                 }
01386                 break;
01387 
01388         default:
01389                 return 1024 * 1024;//was 0
01390                 break;
01391         }
01392 }
01393 
01394 /*
01395 -------------------------
01396 ValidEnemy
01397 -------------------------
01398 */
01399 
01400 qboolean ValidEnemy(gentity_t *ent)
01401 {
01402         if ( ent == NULL )
01403                 return qfalse;
01404 
01405         if ( ent == NPC )
01406                 return qfalse;
01407 
01408         //if team_free, maybe everyone is an enemy?
01409         //if ( !NPC->client->enemyTeam )
01410         //      return qfalse;
01411 
01412         if ( !(ent->flags & FL_NOTARGET) )
01413         {
01414                 if( ent->health > 0 )
01415                 {
01416                         if( !ent->client )
01417                         {
01418                                 return qtrue;
01419                         }
01420                         else if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR )
01421                         {//don't go after spectators
01422                                 return qfalse;
01423                         }
01424                         else
01425                         {
01426                                 int entTeam = TEAM_FREE;
01427                                 if ( ent->NPC && ent->client )
01428                                 {
01429                                         entTeam = ent->client->playerTeam;
01430                                 }
01431                                 else if ( ent->client )
01432                                 {
01433                                         if ( ent->client->sess.sessionTeam == TEAM_BLUE )
01434                                         {
01435                                                 entTeam = NPCTEAM_PLAYER;
01436                                         }
01437                                         else if ( ent->client->sess.sessionTeam == TEAM_RED )
01438                                         {
01439                                                 entTeam = NPCTEAM_ENEMY;
01440                                         }
01441                                         else
01442                                         {
01443                                                 entTeam = NPCTEAM_NEUTRAL;
01444                                         }
01445                                 }
01446                                 if( entTeam == NPCTEAM_FREE 
01447                                         || NPC->client->enemyTeam == NPCTEAM_FREE 
01448                                         || entTeam == NPC->client->enemyTeam )
01449                                 {
01450                                         if ( entTeam != NPC->client->playerTeam )
01451                                         {
01452                                                 return qtrue;
01453                                         }
01454                                 }
01455                         }
01456                 }
01457         }
01458 
01459         return qfalse;
01460 }
01461 
01462 qboolean NPC_EnemyTooFar(gentity_t *enemy, float dist, qboolean toShoot)
01463 {
01464         vec3_t  vec;
01465 
01466         
01467         if ( !toShoot )
01468         {//Not trying to actually press fire button with this check
01469                 if ( NPC->client->ps.weapon == WP_SABER )
01470                 {//Just have to get to him
01471                         return qfalse;
01472                 }
01473         }
01474         
01475 
01476         if(!dist)
01477         {
01478                 VectorSubtract(NPC->r.currentOrigin, enemy->r.currentOrigin, vec);
01479                 dist = VectorLengthSquared(vec);
01480         }
01481 
01482         if(dist > NPC_MaxDistSquaredForWeapon())
01483                 return qtrue;
01484 
01485         return qfalse;
01486 }
01487 
01488 /*
01489 NPC_PickEnemy
01490 
01491 Randomly picks a living enemy from the specified team and returns it
01492 
01493 FIXME: For now, you MUST specify an enemy team
01494 
01495 If you specify choose closest, it will find only the closest enemy
01496 
01497 If you specify checkVis, it will return and enemy that is visible
01498 
01499 If you specify findPlayersFirst, it will try to find players first
01500 
01501 You can mix and match any of those options (example: find closest visible players first)
01502 
01503 FIXME: this should go through the snapshot and find the closest enemy
01504 */
01505 gentity_t *NPC_PickEnemy( gentity_t *closestTo, int enemyTeam, qboolean checkVis, qboolean findPlayersFirst, qboolean findClosest )
01506 {
01507         int                     num_choices = 0;
01508         int                     choice[128];//FIXME: need a different way to determine how many choices?
01509         gentity_t       *newenemy = NULL;
01510         gentity_t       *closestEnemy = NULL;
01511         int                     entNum;
01512         vec3_t          diff;
01513         float           relDist;
01514         float           bestDist = Q3_INFINITE;
01515         qboolean        failed = qfalse;
01516         int                     visChecks = (CHECK_360|CHECK_FOV|CHECK_VISRANGE);
01517         int                     minVis = VIS_FOV;
01518 
01519         if ( enemyTeam == NPCTEAM_NEUTRAL )
01520         {
01521                 return NULL;
01522         }
01523 
01524         if ( NPCInfo->behaviorState == BS_STAND_AND_SHOOT || 
01525                 NPCInfo->behaviorState == BS_HUNT_AND_KILL ) 
01526         {//Formations guys don't require inFov to pick up a target
01527                 //These other behavior states are active battle states and should not
01528                 //use FOV.  FOV checks are for enemies who are patrolling, guarding, etc.
01529                 visChecks &= ~CHECK_FOV;
01530                 minVis = VIS_360;
01531         }
01532 
01533         if( findPlayersFirst )
01534         {//try to find a player first
01535                 newenemy = &g_entities[0];
01536                 if( newenemy->client && !(newenemy->flags & FL_NOTARGET) && !(newenemy->s.eFlags & EF_NODRAW))
01537                 {
01538                         if( newenemy->health > 0 )
01539                         {
01540                                 if( NPC_ValidEnemy( newenemy) )//enemyTeam == TEAM_PLAYER || newenemy->client->playerTeam == enemyTeam || ( enemyTeam == TEAM_PLAYER ) )
01541                                 {//FIXME:  check for range and FOV or vis?
01542                                         if( newenemy != NPC->lastEnemy )
01543                                         {//Make sure we're not just going back and forth here
01544                                                 if ( trap_InPVS(newenemy->r.currentOrigin, NPC->r.currentOrigin) )
01545                                                 {
01546                                                         if(NPCInfo->behaviorState == BS_INVESTIGATE ||  NPCInfo->behaviorState == BS_PATROL)
01547                                                         {
01548                                                                 if(!NPC->enemy)
01549                                                                 {
01550                                                                         if(!InVisrange(newenemy))
01551                                                                         {
01552                                                                                 failed = qtrue;
01553                                                                         }
01554                                                                         else if(NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) != VIS_FOV)
01555                                                                         {
01556                                                                                 failed = qtrue;
01557                                                                         }
01558                                                                 }
01559                                                         }
01560 
01561                                                         if ( !failed )
01562                                                         {
01563                                                                 VectorSubtract( closestTo->r.currentOrigin, newenemy->r.currentOrigin, diff );
01564                                                                 relDist = VectorLengthSquared(diff);
01565                                                                 if ( newenemy->client->hiddenDist > 0 )
01566                                                                 {
01567                                                                         if( relDist > newenemy->client->hiddenDist*newenemy->client->hiddenDist )
01568                                                                         {
01569                                                                                 //out of hidden range
01570                                                                                 if ( VectorLengthSquared( newenemy->client->hiddenDir ) )
01571                                                                                 {//They're only hidden from a certain direction, check
01572                                                                                         float   dot;
01573                                                                                         VectorNormalize( diff );
01574                                                                                         dot = DotProduct( newenemy->client->hiddenDir, diff ); 
01575                                                                                         if ( dot > 0.5 )
01576                                                                                         {//I'm not looking in the right dir toward them to see them 
01577                                                                                                 failed = qtrue;
01578                                                                                         }
01579                                                                                         else
01580                                                                                         {
01581                                                                                                 Debug_Printf(&debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDir %s targetDir %s dot %f\n", NPC->targetname, newenemy->targetname, vtos(newenemy->client->hiddenDir), vtos(diff), dot );
01582                                                                                         }
01583                                                                                 }
01584                                                                                 else
01585                                                                                 {
01586                                                                                         failed = qtrue;
01587                                                                                 }
01588                                                                         }
01589                                                                         else
01590                                                                         {
01591                                                                                 Debug_Printf(&debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDist %f\n", NPC->targetname, newenemy->targetname, newenemy->client->hiddenDist );
01592                                                                         }
01593                                                                 }
01594 
01595                                                                 if(!failed)
01596                                                                 {
01597                                                                         if(findClosest)
01598                                                                         {
01599                                                                                 if(relDist < bestDist)
01600                                                                                 {
01601                                                                                         if(!NPC_EnemyTooFar(newenemy, relDist, qfalse))
01602                                                                                         {
01603                                                                                                 if(checkVis)
01604                                                                                                 {
01605                                                                                                         if( NPC_CheckVisibility ( newenemy, visChecks ) == minVis )
01606                                                                                                         {
01607                                                                                                                 bestDist = relDist;
01608                                                                                                                 closestEnemy = newenemy;
01609                                                                                                         }
01610                                                                                                 }
01611                                                                                                 else
01612                                                                                                 {
01613                                                                                                         bestDist = relDist;
01614                                                                                                         closestEnemy = newenemy;
01615                                                                                                 }
01616                                                                                         }
01617                                                                                 }
01618                                                                         }
01619                                                                         else if(!NPC_EnemyTooFar(newenemy, 0, qfalse))
01620                                                                         {
01621                                                                                 if(checkVis)
01622                                                                                 {
01623                                                                                         if( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) == VIS_FOV )
01624                                                                                         {
01625                                                                                                 choice[num_choices++] = newenemy->s.number;
01626                                                                                         }
01627                                                                                 }
01628                                                                                 else
01629                                                                                 {
01630                                                                                         choice[num_choices++] = newenemy->s.number;
01631                                                                                 }
01632                                                                         }
01633                                                                 }
01634                                                         }
01635                                                 }
01636                                         }
01637                                 }
01638                         }
01639                 }
01640         }
01641 
01642         if (findClosest && closestEnemy)
01643         {
01644                 return closestEnemy;
01645         }
01646 
01647         if (num_choices)
01648         {
01649                 return &g_entities[ choice[rand() % num_choices] ];
01650         }
01651 
01652         /*
01653         //FIXME: used to have an option to look *only* for the player... now...?  Still need it?
01654         if ( enemyTeam == TEAM_PLAYER )
01655         {//couldn't find the player
01656                 return NULL;
01657         }
01658         */
01659 
01660         num_choices = 0;
01661         bestDist = Q3_INFINITE;
01662         closestEnemy = NULL;
01663 
01664         for ( entNum = 0; entNum < level.num_entities; entNum++ )
01665         {
01666                 newenemy = &g_entities[entNum];
01667 
01668                 if ( newenemy != NPC && (newenemy->client /*|| newenemy->svFlags & SVF_NONNPC_ENEMY*/) && !(newenemy->flags & FL_NOTARGET) && !(newenemy->s.eFlags & EF_NODRAW))
01669                 {
01670                         if ( newenemy->health > 0 )
01671                         {
01672                                 if ( (newenemy->client && NPC_ValidEnemy( newenemy))
01673                                         || (!newenemy->client && newenemy->alliedTeam == enemyTeam) )
01674                                 {//FIXME:  check for range and FOV or vis?
01675                                         if ( NPC->client->playerTeam == NPCTEAM_PLAYER && enemyTeam == NPCTEAM_PLAYER )
01676                                         {//player allies turning on ourselves?  How?
01677                                                 if ( newenemy->s.number )
01678                                                 {//only turn on the player, not other player allies
01679                                                         continue;
01680                                                 }
01681                                         }
01682 
01683                                         if ( newenemy != NPC->lastEnemy )
01684                                         {//Make sure we're not just going back and forth here
01685                                                 if(!trap_InPVS(newenemy->r.currentOrigin, NPC->r.currentOrigin))
01686                                                 {
01687                                                         continue;
01688                                                 }
01689 
01690                                                 if ( NPCInfo->behaviorState == BS_INVESTIGATE || NPCInfo->behaviorState == BS_PATROL )
01691                                                 {
01692                                                         if ( !NPC->enemy )
01693                                                         {
01694                                                                 if ( !InVisrange( newenemy ) )
01695                                                                 {
01696                                                                         continue;
01697                                                                 }
01698                                                                 else if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) != VIS_FOV )
01699                                                                 {
01700                                                                         continue;
01701                                                                 }
01702                                                         }
01703                                                 }
01704 
01705                                                 VectorSubtract( closestTo->r.currentOrigin, newenemy->r.currentOrigin, diff );
01706                                                 relDist = VectorLengthSquared(diff);
01707                                                 if ( newenemy->client && newenemy->client->hiddenDist > 0 )
01708                                                 {
01709                                                         if( relDist > newenemy->client->hiddenDist*newenemy->client->hiddenDist )
01710                                                         {
01711                                                                 //out of hidden range
01712                                                                 if ( VectorLengthSquared( newenemy->client->hiddenDir ) )
01713                                                                 {//They're only hidden from a certain direction, check
01714                                                                         float   dot;
01715 
01716                                                                         VectorNormalize( diff );
01717                                                                         dot = DotProduct( newenemy->client->hiddenDir, diff ); 
01718                                                                         if ( dot > 0.5 )
01719                                                                         {//I'm not looking in the right dir toward them to see them 
01720                                                                                 continue;
01721                                                                         }
01722                                                                         else
01723                                                                         {
01724                                                                                 Debug_Printf(&debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDir %s targetDir %s dot %f\n", NPC->targetname, newenemy->targetname, vtos(newenemy->client->hiddenDir), vtos(diff), dot );
01725                                                                         }
01726                                                                 }
01727                                                                 else
01728                                                                 {
01729                                                                         continue;
01730                                                                 }
01731                                                         }
01732                                                         else
01733                                                         {
01734                                                                 Debug_Printf(&debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDist %f\n", NPC->targetname, newenemy->targetname, newenemy->client->hiddenDist );
01735                                                         }
01736                                                 }
01737 
01738                                                 if ( findClosest )
01739                                                 {
01740                                                         if ( relDist < bestDist )
01741                                                         {
01742                                                                 if ( !NPC_EnemyTooFar( newenemy, relDist, qfalse ) )
01743                                                                 {
01744                                                                         if ( checkVis )
01745                                                                         {
01746                                                                                 //FIXME: NPCs need to be able to pick up other NPCs behind them,
01747                                                                                 //but for now, commented out because it was picking up enemies it shouldn't
01748                                                                                 //if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_VISRANGE ) >= VIS_360 )
01749                                                                                 if ( NPC_CheckVisibility ( newenemy, visChecks ) == minVis )
01750                                                                                 {
01751                                                                                         bestDist = relDist;
01752                                                                                         closestEnemy = newenemy;
01753                                                                                 }
01754                                                                         }
01755                                                                         else
01756                                                                         {
01757                                                                                 bestDist = relDist;
01758                                                                                 closestEnemy = newenemy;
01759                                                                         }
01760                                                                 }
01761                                                         }
01762                                                 }
01763                                                 else if ( !NPC_EnemyTooFar( newenemy, 0, qfalse ) )
01764                                                 {
01765                                                         if ( checkVis )
01766                                                         {
01767                                                                 //if( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) == VIS_FOV )
01768                                                                 if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_VISRANGE ) >= VIS_360 )
01769                                                                 {
01770                                                                         choice[num_choices++] = newenemy->s.number;
01771                                                                 }
01772                                                         }
01773                                                         else
01774                                                         {
01775                                                                 choice[num_choices++] = newenemy->s.number;
01776                                                         }
01777                                                 }
01778                                         }
01779                                 }
01780                         }
01781                 }
01782         }
01783 
01784         
01785         if (findClosest)
01786         {//FIXME: you can pick up an enemy around a corner this way.
01787                 return closestEnemy;
01788         }
01789 
01790         if (!num_choices)
01791         {
01792                 return NULL;
01793         }
01794 
01795         return &g_entities[ choice[rand() % num_choices] ];
01796 }
01797 
01798 /*
01799 gentity_t *NPC_PickAlly ( void )
01800 
01801   Simply returns closest visible ally
01802 */
01803 
01804 gentity_t *NPC_PickAlly ( qboolean facingEachOther, float range, qboolean ignoreGroup, qboolean movingOnly )
01805 {
01806         gentity_t       *ally = NULL;
01807         gentity_t       *closestAlly = NULL;
01808         int                     entNum;
01809         vec3_t          diff;
01810         float           relDist;
01811         float           bestDist = range;
01812 
01813         for ( entNum = 0; entNum < level.num_entities; entNum++ )
01814         {
01815                 ally = &g_entities[entNum];
01816 
01817                 if ( ally->client )
01818                 {
01819                         if ( ally->health > 0 )
01820                         {
01821                                 if ( ally->client && ( ally->client->playerTeam == NPC->client->playerTeam ||
01822                                          NPC->client->playerTeam == NPCTEAM_ENEMY ) )// && ally->client->playerTeam == TEAM_DISGUISE ) ) )
01823                                 {//if on same team or if player is disguised as your team
01824                                         if ( ignoreGroup )
01825                                         {
01826                                                 if ( ally == NPC->client->leader )
01827                                                 {
01828                                                         //reject
01829                                                         continue;
01830                                                 }
01831                                                 if ( ally->client && ally->client->leader && ally->client->leader == NPC )
01832                                                 {
01833                                                         //reject
01834                                                         continue;
01835                                                 }
01836                                         }
01837 
01838                                         if(!trap_InPVS(ally->r.currentOrigin, NPC->r.currentOrigin))
01839                                         {
01840                                                 continue;
01841                                         }
01842 
01843                                         if ( movingOnly && ally->client && NPC->client )
01844                                         {//They have to be moving relative to each other
01845                                                 if ( !DistanceSquared( ally->client->ps.velocity, NPC->client->ps.velocity ) )
01846                                                 {
01847                                                         continue;
01848                                                 }
01849                                         }
01850 
01851                                         VectorSubtract( NPC->r.currentOrigin, ally->r.currentOrigin, diff );
01852                                         relDist = VectorNormalize( diff );
01853                                         if ( relDist < bestDist )
01854                                         {
01855                                                 if ( facingEachOther )
01856                                                 {
01857                                                         vec3_t  vf;
01858                                                         float   dot;
01859 
01860                                                         AngleVectors( ally->client->ps.viewangles, vf, NULL, NULL );
01861                                                         VectorNormalize(vf);
01862                                                         dot = DotProduct(diff, vf);
01863 
01864                                                         if ( dot < 0.5 )
01865                                                         {//Not facing in dir to me
01866                                                                 continue;
01867                                                         }
01868                                                         //He's facing me, am I facing him?
01869                                                         AngleVectors( NPC->client->ps.viewangles, vf, NULL, NULL );
01870                                                         VectorNormalize(vf);
01871                                                         dot = DotProduct(diff, vf);
01872 
01873                                                         if ( dot > -0.5 )
01874                                                         {//I'm not facing opposite of dir to me
01875                                                                 continue;
01876                                                         }
01877                                                         //I am facing him
01878                                                 }
01879 
01880                                                 if ( NPC_CheckVisibility ( ally, CHECK_360|CHECK_VISRANGE ) >= VIS_360 )
01881                                                 {
01882                                                         bestDist = relDist;
01883                                                         closestAlly = ally;
01884                                                 }
01885                                         }
01886                                 }
01887                         }
01888                 }
01889         }
01890 
01891         
01892         return closestAlly;
01893 }
01894 
01895 gentity_t *NPC_CheckEnemy( qboolean findNew, qboolean tooFarOk, qboolean setEnemy )
01896 {
01897         qboolean        forcefindNew = qfalse;
01898         gentity_t       *closestTo;
01899         gentity_t       *newEnemy = NULL;
01900         //FIXME: have a "NPCInfo->persistance" we can set to determine how long to try to shoot
01901         //someone we can't hit?  Rather than hard-coded 10?
01902 
01903         //FIXME they shouldn't recognize enemy's death instantly
01904 
01905         //TEMP FIX:
01906         //if(NPC->enemy->client)
01907         //{
01908         //      NPC->enemy->health = NPC->enemy->client->ps.stats[STAT_HEALTH];
01909         //}
01910 
01911         if ( NPC->enemy )
01912         {
01913                 if ( !NPC->enemy->inuse )//|| NPC->enemy == NPC )//wtf?  NPCs should never get mad at themselves!
01914                 {
01915                         if ( setEnemy )
01916                         {
01917                                 G_ClearEnemy( NPC );
01918                         }
01919                 }
01920         }
01921 
01922         //if ( NPC->svFlags & SVF_IGNORE_ENEMIES )
01923         if (0) //rwwFIXMEFIXME: support for this flag
01924         {//We're ignoring all enemies for now
01925                 if ( setEnemy )
01926                 {
01927                         G_ClearEnemy( NPC );
01928                 }
01929                 return NULL;
01930         }
01931 
01932         //rwwFIXMEFIXME: support for this flag
01933         /*
01934         if ( NPC->svFlags & SVF_LOCKEDENEMY )
01935         {//keep this enemy until dead
01936                 if ( NPC->enemy )
01937                 {
01938                         if ( (!NPC->NPC && !(NPC->svFlags & SVF_NONNPC_ENEMY) ) || NPC->enemy->health > 0 )
01939                         {//Enemy never had health (a train or info_not_null, etc) or did and is now dead (NPCs, turrets, etc)
01940                                 return NULL;
01941                         }
01942                 }
01943                 NPC->svFlags &= ~SVF_LOCKEDENEMY;
01944         }
01945         */
01946 
01947         if ( NPC->enemy )
01948         {
01949                 if ( NPC_EnemyTooFar(NPC->enemy, 0, qfalse) )
01950                 {
01951                         if(findNew)
01952                         {//See if there is a close one and take it if so, else keep this one
01953                                 forcefindNew = qtrue;
01954                         }
01955                         else if(!tooFarOk)//FIXME: don't need this extra bool any more
01956                         {
01957                                 if ( setEnemy )
01958                                 {
01959                                         G_ClearEnemy( NPC );
01960                                 }
01961                         }
01962                 }
01963                 else if ( !trap_InPVS(NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) )
01964                 {//FIXME: should this be a line-of site check?
01965                         //FIXME: a lot of things check PVS AGAIN when deciding whether 
01966                         //or not to shoot, redundant!
01967                         //Should we lose the enemy?
01968                         //FIXME: if lose enemy, run lostenemyscript
01969                         if ( NPC->enemy->client && NPC->enemy->client->hiddenDist )
01970                         {//He ducked into shadow while we weren't looking
01971                                 //Drop enemy and see if we should search for him
01972                                 NPC_LostEnemyDecideChase();
01973                         }
01974                         else
01975                         {//If we're not chasing him, we need to lose him
01976                                 //NOTE: since we no longer have bStates, really, this logic doesn't work, so never give him up
01977 
01978                                 /*
01979                                 switch( NPCInfo->behaviorState )
01980                                 {
01981                                 case BS_HUNT_AND_KILL:
01982                                         //Okay to lose PVS, we're chasing them
01983                                         break;
01984                                 case BS_RUN_AND_SHOOT:
01985                                 //FIXME: only do this if !(NPCInfo->scriptFlags&SCF_CHASE_ENEMY)
01986                                         //If he's not our goalEntity, we're running somewhere else, so lose him
01987                                         if ( NPC->enemy != NPCInfo->goalEntity )
01988                                         {
01989                                                 G_ClearEnemy( NPC );
01990                                         }
01991                                         break;
01992                                 default:
01993                                         //We're not chasing him, so lose him as an enemy
01994                                         G_ClearEnemy( NPC );
01995                                         break;
01996                                 }
01997                                 */
01998                         }
01999                 }
02000         }
02001 
02002         if ( NPC->enemy )
02003         {
02004                 if ( NPC->enemy->health <= 0 || NPC->enemy->flags & FL_NOTARGET )
02005                 {
02006                         if ( setEnemy )
02007                         {
02008                                 G_ClearEnemy( NPC );
02009                         }
02010                 }
02011         }
02012 
02013         closestTo = NPC;
02014         //FIXME: check your defendEnt, if you have one, see if their enemy is different 
02015         //than yours, or, if they don't have one, pick the closest enemy to THEM?
02016         if ( NPCInfo->defendEnt )
02017         {//Trying to protect someone
02018                 if ( NPCInfo->defendEnt->health > 0 )
02019                 {//Still alive, We presume we're close to them, navigation should handle this?
02020                         if ( NPCInfo->defendEnt->enemy )
02021                         {//They were shot or acquired an enemy
02022                                 if ( NPC->enemy != NPCInfo->defendEnt->enemy )
02023                                 {//They have a different enemy, take it!
02024                                         newEnemy = NPCInfo->defendEnt->enemy;
02025                                         if ( setEnemy )
02026                                         {
02027                                                 G_SetEnemy( NPC, NPCInfo->defendEnt->enemy );
02028                                         }
02029                                 }
02030                         }
02031                         else if ( NPC->enemy == NULL )
02032                         {//We don't have an enemy, so find closest to defendEnt
02033                                 closestTo = NPCInfo->defendEnt;
02034                         }
02035                 }
02036         }
02037 
02038         if (!NPC->enemy || ( NPC->enemy && NPC->enemy->health <= 0 ) || forcefindNew )
02039         {//FIXME: NPCs that are moving after an enemy should ignore the can't hit enemy counter- that should only be for NPCs that are standing still
02040                 //NOTE: cantHitEnemyCounter >= 100 means we couldn't hit enemy for a full
02041                 //      10 seconds, so give up.  This means even if we're chasing him, we would
02042                 //      try to find another enemy after 10 seconds (assuming the cantHitEnemyCounter
02043                 //      is allowed to increment in a chasing bState)
02044                 qboolean        foundenemy = qfalse;
02045 
02046                 if(!findNew)
02047                 {
02048                         if ( setEnemy )
02049                         {
02050                                 NPC->lastEnemy = NPC->enemy;
02051                                 G_ClearEnemy(NPC);
02052                         }
02053                         return NULL;
02054                 }
02055 
02056                 //If enemy dead or unshootable, look for others on out enemy's team
02057                 if ( NPC->client->enemyTeam != NPCTEAM_NEUTRAL )
02058                 {
02059                         //NOTE:  this only checks vis if can't hit enemy for 10 tries, which I suppose
02060                         //                      means they need to find one that in more than just PVS
02061                         //newenemy = NPC_PickEnemy( closestTo, NPC->client->enemyTeam, (NPC->cantHitEnemyCounter > 10), qfalse, qtrue );//3rd parm was (NPC->enemyTeam == TEAM_STARFLEET)
02062                         //For now, made it so you ALWAYS have to check VIS
02063                         newEnemy = NPC_PickEnemy( closestTo, NPC->client->enemyTeam, qtrue, qfalse, qtrue );//3rd parm was (NPC->enemyTeam == TEAM_STARFLEET)
02064                         if ( newEnemy )
02065                         {
02066                                 foundenemy = qtrue;
02067                                 if ( setEnemy )
02068                                 {
02069                                         G_SetEnemy( NPC, newEnemy );
02070                                 }
02071                         }
02072                 }
02073                 
02074                 if ( !forcefindNew )
02075                 {
02076                         if ( !foundenemy )
02077                         {
02078                                 if ( setEnemy )
02079                                 {
02080                                         NPC->lastEnemy = NPC->enemy;
02081                                         G_ClearEnemy(NPC);
02082                                 }
02083                         }
02084                         
02085                         NPC->cantHitEnemyCounter = 0;
02086                 }
02087                 //FIXME: if we can't find any at all, go into INdependant NPC AI, pursue and kill
02088         }
02089 
02090         if ( NPC->enemy && NPC->enemy->client ) 
02091         {
02092                 if(NPC->enemy->client->playerTeam)
02093                 {
02094 //                      assert( NPC->client->playerTeam != NPC->enemy->client->playerTeam);
02095                         if( NPC->client->playerTeam != NPC->enemy->client->playerTeam )
02096                         {
02097                                 NPC->client->enemyTeam = NPC->enemy->client->playerTeam;
02098                         }
02099                 }
02100         }
02101         return newEnemy;
02102 }
02103 
02104 /*
02105 -------------------------
02106 NPC_ClearShot
02107 -------------------------
02108 */
02109 
02110 qboolean NPC_ClearShot( gentity_t *ent )
02111 {
02112         vec3_t  muzzle;
02113         trace_t tr;
02114 
02115         if ( ( NPC == NULL ) || ( ent == NULL ) )
02116                 return qfalse;
02117 
02118         CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
02119 
02120         // add aim error
02121         // use weapon instead of specific npc types, although you could add certain npc classes if you wanted
02122 //      if ( NPC->client->playerTeam == TEAM_SCAVENGERS )
02123         if( NPC->s.weapon == WP_BLASTER /*|| NPC->s.weapon == WP_BLASTER_PISTOL*/ ) // any other guns to check for?
02124         {
02125                 vec3_t  mins = { -2, -2, -2 };
02126                 vec3_t  maxs = {  2,  2,  2 };
02127 
02128                 trap_Trace ( &tr, muzzle, mins, maxs, ent->r.currentOrigin, NPC->s.number, MASK_SHOT );
02129         }
02130         else
02131         {
02132                 trap_Trace ( &tr, muzzle, NULL, NULL, ent->r.currentOrigin, NPC->s.number, MASK_SHOT );
02133         }
02134         
02135         if ( tr.startsolid || tr.allsolid )
02136         {
02137                 return qfalse;
02138         }
02139 
02140         if ( tr.entityNum == ent->s.number ) 
02141                 return qtrue;
02142         
02143         return qfalse;
02144 }
02145 
02146 /*
02147 -------------------------
02148 NPC_ShotEntity
02149 -------------------------
02150 */
02151 
02152 int NPC_ShotEntity( gentity_t *ent, vec3_t impactPos )
02153 {
02154         vec3_t  muzzle;
02155         vec3_t targ;
02156         trace_t tr;
02157 
02158         if ( ( NPC == NULL ) || ( ent == NULL ) )
02159                 return qfalse;
02160 
02161         if ( NPC->s.weapon == WP_THERMAL )
02162         {//thermal aims from slightly above head
02163                 //FIXME: what about low-angle shots, rolling the thermal under something?
02164                 vec3_t  angles, forward, end;
02165 
02166                 CalcEntitySpot( NPC, SPOT_HEAD, muzzle );
02167                 VectorSet( angles, 0, NPC->client->ps.viewangles[1], 0 );
02168                 AngleVectors( angles, forward, NULL, NULL );
02169                 VectorMA( muzzle, 8, forward, end );
02170                 end[2] += 24;
02171                 trap_Trace ( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT );
02172                 VectorCopy( tr.endpos, muzzle );
02173         }
02174         else
02175         {
02176                 CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
02177         }
02178         CalcEntitySpot( ent, SPOT_CHEST, targ );
02179         
02180         // add aim error
02181         // use weapon instead of specific npc types, although you could add certain npc classes if you wanted
02182 //      if ( NPC->client->playerTeam == TEAM_SCAVENGERS )
02183         if( NPC->s.weapon == WP_BLASTER /*|| NPC->s.weapon == WP_BLASTER_PISTOL*/ ) // any other guns to check for?
02184         {
02185                 vec3_t  mins = { -2, -2, -2 };
02186                 vec3_t  maxs = {  2,  2,  2 };
02187 
02188                 trap_Trace ( &tr, muzzle, mins, maxs, targ, NPC->s.number, MASK_SHOT );
02189         }
02190         else
02191         {
02192                 trap_Trace ( &tr, muzzle, NULL, NULL, targ, NPC->s.number, MASK_SHOT );
02193         }
02194         //FIXME: if using a bouncing weapon like the bowcaster, should we check the reflection of the wall, too?
02195         if ( impactPos )
02196         {//they want to know *where* the hit would be, too
02197                 VectorCopy( tr.endpos, impactPos );
02198         }
02199 /* // NPCs should be able to shoot even if the muzzle would be inside their target
02200         if ( tr.startsolid || tr.allsolid )
02201         {
02202                 return ENTITYNUM_NONE;
02203         }
02204 */
02205         return tr.entityNum;
02206 }
02207 
02208 qboolean NPC_EvaluateShot( int hit, qboolean glassOK )
02209 {
02210         if ( !NPC->enemy )
02211         {
02212                 return qfalse;
02213         }
02214 
02215         if ( hit == NPC->enemy->s.number || (&g_entities[hit] != NULL && (g_entities[hit].r.svFlags&SVF_GLASS_BRUSH)) )
02216         {//can hit enemy or will hit glass, so shoot anyway
02217                 return qtrue;
02218         }
02219         return qfalse;
02220 }
02221 
02222 /*
02223 NPC_CheckAttack
02224 
02225 Simply checks aggression and returns true or false
02226 */
02227 
02228 qboolean NPC_CheckAttack (float scale)
02229 {
02230         if(!scale)
02231                 scale = 1.0;
02232 
02233         if(((float)NPCInfo->stats.aggression) * scale < flrand(0, 4))
02234         {
02235                 return qfalse;
02236         }
02237 
02238         if(NPCInfo->shotTime > level.time)
02239                 return qfalse;
02240 
02241         return qtrue;
02242 }
02243 
02244 /*
02245 NPC_CheckDefend
02246 
02247 Simply checks evasion and returns true or false
02248 */
02249 
02250 qboolean NPC_CheckDefend (float scale)
02251 {
02252         if(!scale)
02253                 scale = 1.0;
02254 
02255         if((float)(NPCInfo->stats.evasion) > random() * 4 * scale)
02256                 return qtrue;
02257 
02258         return qfalse;
02259 }
02260 
02261 
02262 //NOTE: BE SURE TO CHECK PVS BEFORE THIS!
02263 qboolean NPC_CheckCanAttack (float attack_scale, qboolean stationary)
02264 {
02265         vec3_t          delta, forward;
02266         vec3_t          angleToEnemy;
02267         vec3_t          hitspot, muzzle, diff, enemy_org;//, enemy_head;
02268         float           distanceToEnemy;
02269         qboolean        attack_ok = qfalse;
02270 //      qboolean        duck_ok = qfalse;
02271         qboolean        dead_on = qfalse;
02272         float           aim_off;
02273         float           max_aim_off = 128 - (16 * (float)NPCInfo->stats.aim);
02274         trace_t         tr;
02275         gentity_t       *traceEnt = NULL;
02276 
02277         if(NPC->enemy->flags & FL_NOTARGET)
02278         {
02279                 return qfalse;
02280         }
02281 
02282         //FIXME: only check to see if should duck if that provides cover from the
02283         //enemy!!!
02284         if(!attack_scale)
02285         {
02286                 attack_scale = 1.0;
02287         }
02288         //Yaw to enemy
02289         CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org );
02290         NPC_AimWiggle( enemy_org );
02291 
02292         CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
02293         
02294         VectorSubtract (enemy_org, muzzle, delta);
02295         vectoangles ( delta, angleToEnemy );
02296         distanceToEnemy = VectorNormalize(delta);
02297 
02298         NPC->NPC->desiredYaw = angleToEnemy[YAW];
02299         NPC_UpdateFiringAngles(qfalse, qtrue);
02300 
02301         if( NPC_EnemyTooFar(NPC->enemy, distanceToEnemy*distanceToEnemy, qtrue) )
02302         {//Too far away?  Do not attack
02303                 return qfalse;
02304         }
02305 
02306         if(client->ps.weaponTime > 0)
02307         {//already waiting for a shot to fire
02308                 NPC->NPC->desiredPitch = angleToEnemy[PITCH];
02309                 NPC_UpdateFiringAngles(qtrue, qfalse);
02310                 return qfalse;
02311         }
02312 
02313         if(NPCInfo->scriptFlags & SCF_DONT_FIRE)
02314         {
02315                 return qfalse;
02316         }
02317 
02318         NPCInfo->enemyLastVisibility = enemyVisibility;
02319         //See if they're in our FOV and we have a clear shot to them
02320         enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_360|CHECK_FOV);
02321 
02322         if(enemyVisibility >= VIS_FOV)
02323         {//He's in our FOV
02324                 
02325                 attack_ok = qtrue;
02326                 //CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_head);
02327 
02328                 //Check to duck
02329                 if ( NPC->enemy->client )
02330                 {
02331                         if ( NPC->enemy->enemy == NPC )
02332                         {
02333                                 if ( NPC->enemy->client->buttons & BUTTON_ATTACK )
02334                                 {//FIXME: determine if enemy fire angles would hit me or get close
02335                                         if ( NPC_CheckDefend( 1.0 ) )//FIXME: Check self-preservation?  Health?
02336                                         {//duck and don't shoot
02337                                                 attack_ok = qfalse;
02338                                                 ucmd.upmove = -127;
02339                                         }
02340                                 }
02341                         }
02342                 }
02343 
02344                 if(attack_ok)
02345                 {
02346                         //are we gonna hit him
02347                         //NEW: use actual forward facing
02348                         AngleVectors( client->ps.viewangles, forward, NULL, NULL );
02349                         VectorMA( muzzle, distanceToEnemy, forward, hitspot );
02350                         trap_Trace( &tr, muzzle, NULL, NULL, hitspot, NPC->s.number, MASK_SHOT );
02351                         ShotThroughGlass( &tr, NPC->enemy, hitspot, MASK_SHOT );
02352                         /*
02353                         //OLD: trace regardless of facing
02354                         trap_Trace ( &tr, muzzle, NULL, NULL, enemy_org, NPC->s.number, MASK_SHOT );
02355                         ShotThroughGlass(&tr, NPC->enemy, enemy_org, MASK_SHOT);
02356                         */
02357 
02358                         traceEnt = &g_entities[tr.entityNum];
02359 
02360                         /*
02361                         if( traceEnt != NPC->enemy &&//FIXME: if someone on our team is in the way, suggest that they duck if possible
02362                                 (!traceEnt || !traceEnt->client || !NPC->client->enemyTeam || NPC->client->enemyTeam != traceEnt->client->playerTeam) )
02363                         {//no, so shoot for somewhere between the head and torso
02364                                 //NOTE: yes, I know this looks weird, but it works
02365                                 enemy_org[0] += 0.3*Q_flrand(NPC->enemy->r.mins[0], NPC->enemy->r.maxs[0]);
02366                                 enemy_org[1] += 0.3*Q_flrand(NPC->enemy->r.mins[1], NPC->enemy->r.maxs[1]);
02367                                 enemy_org[2] -= NPC->enemy->r.maxs[2]*Q_flrand(0.0f, 1.0f);
02368 
02369                                 attack_scale *= 0.75;
02370                                 trap_Trace ( &tr, muzzle, NULL, NULL, enemy_org, NPC->s.number, MASK_SHOT );
02371                                 ShotThroughGlass(&tr, NPC->enemy, enemy_org, MASK_SHOT);
02372                                 traceEnt = &g_entities[tr.entityNum];
02373                         }
02374                         */
02375 
02376                         VectorCopy( tr.endpos, hitspot );
02377 
02378                         if( traceEnt == NPC->enemy || (traceEnt->client && NPC->client->enemyTeam && NPC->client->enemyTeam == traceEnt->client->playerTeam) )
02379                         {
02380                                 dead_on = qtrue;
02381                         }
02382                         else
02383                         {
02384                                 attack_scale *= 0.5;
02385                                 if(NPC->client->playerTeam)
02386                                 {
02387                                         if(traceEnt && traceEnt->client && traceEnt->client->playerTeam)
02388                                         {
02389                                                 if(NPC->client->playerTeam == traceEnt->client->playerTeam)
02390                                                 {//Don't shoot our own team
02391                                                         attack_ok = qfalse;
02392                                                 }
02393                                         }
02394                                 }
02395                         }
02396                 }
02397 
02398                 if( attack_ok )
02399                 {
02400                         //ok, now adjust pitch aim
02401                         VectorSubtract (hitspot, muzzle, delta);
02402                         vectoangles ( delta, angleToEnemy );
02403                         NPC->NPC->desiredPitch = angleToEnemy[PITCH];
02404                         NPC_UpdateFiringAngles(qtrue, qfalse);
02405 
02406                         if( !dead_on )
02407                         {//We're not going to hit him directly, try a suppressing fire
02408                                 //see if where we're going to shoot is too far from his origin
02409                                 if(traceEnt && (traceEnt->health <= 30 || EntIsGlass(traceEnt)))
02410                                 {//easy to kill - go for it
02411                                         //if(traceEnt->die == ExplodeDeath_Wait && traceEnt->splashDamage)
02412                                         if (0) //rwwFIXMEFIXME: ExplodeDeath_Wait?
02413                                         {//going to explode, don't shoot if close to self
02414                                                 VectorSubtract(NPC->r.currentOrigin, traceEnt->r.currentOrigin, diff);
02415                                                 if(VectorLengthSquared(diff) < traceEnt->splashRadius*traceEnt->splashRadius)
02416                                                 {//Too close to shoot!
02417                                                         attack_ok = qfalse;
02418                                                 }
02419                                                 else 
02420                                                 {//Hey, it might kill him, do it!
02421                                                         attack_scale *= 2;//
02422                                                 }
02423                                         }
02424                                 }
02425                                 else
02426                                 {
02427                                         AngleVectors (client->ps.viewangles, forward, NULL, NULL);
02428                                         VectorMA ( muzzle, distanceToEnemy, forward, hitspot);
02429                                         VectorSubtract(hitspot, enemy_org, diff);
02430                                         aim_off = VectorLength(diff);
02431                                         if(aim_off > random() * max_aim_off)//FIXME: use aim value to allow poor aim?
02432                                         {
02433                                                 attack_scale *= 0.75;
02434                                                 //see if where we're going to shoot is too far from his head
02435                                                 VectorSubtract(hitspot, enemy_org, diff);
02436                                                 aim_off = VectorLength(diff);
02437                                                 if(aim_off > random() * max_aim_off)
02438                                                 {
02439                                                         attack_ok = qfalse;
02440                                                 }
02441                                         }
02442                                         attack_scale *= (max_aim_off - aim_off + 1)/max_aim_off;
02443                                 }
02444                         }
02445                 }
02446         }
02447         else
02448         {//Update pitch anyway
02449                 NPC->NPC->desiredPitch = angleToEnemy[PITCH];
02450                 NPC_UpdateFiringAngles(qtrue, qfalse);
02451         }
02452 
02453         if( attack_ok )
02454         {
02455                 if( NPC_CheckAttack( attack_scale ))
02456                 {//check aggression to decide if we should shoot
02457                         enemyVisibility = VIS_SHOOT;
02458                         WeaponThink(qtrue);
02459                 }
02460                 else
02461                         attack_ok = qfalse;
02462         }
02463 
02464         return attack_ok;
02465 }
02466 //========================================================================================
02467 //OLD id-style hunt and kill
02468 //========================================================================================
02469 /*
02470 IdealDistance
02471 
02472 determines what the NPC's ideal distance from it's enemy should
02473 be in the current situation
02474 */
02475 float IdealDistance ( gentity_t *self ) 
02476 {
02477         float   ideal;
02478 
02479         ideal = 225 - 20 * NPCInfo->stats.aggression;
02480         switch ( NPC->s.weapon ) 
02481         {
02482         case WP_ROCKET_LAUNCHER:
02483                 ideal += 200;
02484                 break;
02485 
02486         case WP_THERMAL:
02487                 ideal += 50;
02488                 break;
02489 
02490 /*      case WP_TRICORDER:
02491                 ideal = 0;
02492                 break;
02493 */
02494         case WP_SABER:
02495         case WP_BRYAR_PISTOL:
02496 //      case WP_BLASTER_PISTOL:
02497         case WP_BLASTER:
02498         default:
02499                 break;
02500         }
02501 
02502         return ideal;
02503 }
02504 
02505 /*QUAKED point_combat (0.7 0 0.7) (-16 -16 -24) (16 16 32) DUCK FLEE INVESTIGATE SQUAD LEAN SNIPE
02506 NPCs in bState BS_COMBAT_POINT will find their closest empty combat_point
02507 
02508 DUCK - NPC will duck and fire from this point, NOT IMPLEMENTED?
02509 FLEE - Will choose this point when running
02510 INVESTIGATE - Will look here if a sound is heard near it
02511 SQUAD - NOT IMPLEMENTED
02512 LEAN - Lean-type cover, NOT IMPLEMENTED
02513 SNIPE - Snipers look for these first, NOT IMPLEMENTED
02514 */
02515 
02516 void SP_point_combat( gentity_t *self )
02517 {
02518         if(level.numCombatPoints >= MAX_COMBAT_POINTS)
02519         {
02520 #ifndef FINAL_BUILD
02521                 Com_Printf(S_COLOR_RED"ERROR:  Too many combat points, limit is %d\n", MAX_COMBAT_POINTS);
02522 #endif
02523                 G_FreeEntity(self);
02524                 return;
02525         }
02526 
02527         self->s.origin[2] += 0.125;
02528         G_SetOrigin(self, self->s.origin);
02529         trap_LinkEntity(self);
02530 
02531         if ( G_CheckInSolid( self, qtrue ) )
02532         {
02533 #ifndef FINAL_BUILD
02534                 Com_Printf( S_COLOR_RED"ERROR: combat point at %s in solid!\n", vtos(self->r.currentOrigin) );
02535 #endif
02536         }
02537 
02538         VectorCopy( self->r.currentOrigin, level.combatPoints[level.numCombatPoints].origin );
02539         
02540         level.combatPoints[level.numCombatPoints].flags = self->spawnflags;
02541         level.combatPoints[level.numCombatPoints].occupied = qfalse;
02542 
02543         level.numCombatPoints++;
02544 
02545         G_FreeEntity(self);
02546 }
02547 
02548 void CP_FindCombatPointWaypoints( void )
02549 {
02550         int i;
02551 
02552         for ( i = 0; i < level.numCombatPoints; i++ )
02553         {
02554                 level.combatPoints[i].waypoint = NAV_FindClosestWaypointForPoint2( level.combatPoints[i].origin );
02555 #ifndef FINAL_BUILD
02556                 if ( level.combatPoints[i].waypoint == WAYPOINT_NONE )
02557                 {
02558                         Com_Printf( S_COLOR_RED"ERROR: Combat Point at %s has no waypoint!\n", vtos(level.combatPoints[i].origin) );
02559                 }
02560 #endif
02561         }
02562 }
02563 
02564 
02565 /*
02566 -------------------------
02567 NPC_CollectCombatPoints
02568 -------------------------
02569 */
02570 typedef struct
02571 {
02572         float dist;
02573         int index;
02574 } combatPt_t;
02575 static int NPC_CollectCombatPoints( const vec3_t origin, const float radius, combatPt_t *points, const int flags )
02576 {
02577         float   radiusSqr = (radius*radius);
02578         float   distance;
02579         float   bestDistance = Q3_INFINITE;
02580         int             bestPoint = 0;
02581         int             numPoints = 0;
02582         int             i;
02583 
02584         //Collect all nearest
02585         for ( i = 0; i < level.numCombatPoints; i++ )
02586         {
02587                 if (numPoints >= MAX_COMBAT_POINTS)
02588                 {
02589                         break;
02590                 }
02591 
02592                 //Must be vacant
02593                 if ( level.combatPoints[i].occupied == (int) qtrue )
02594                         continue;
02595 
02596                 //If we want a duck space, make sure this is one
02597                 if ( ( flags & CP_DUCK ) && ( level.combatPoints[i].flags & CPF_DUCK ) )
02598                         continue;
02599 
02600                 //If we want a duck space, make sure this is one
02601                 if ( ( flags & CP_FLEE ) && ( level.combatPoints[i].flags & CPF_FLEE ) )
02602                         continue;
02603 
02605                 if ( ( flags & CP_INVESTIGATE ) && ( level.combatPoints[i].flags & CPF_INVESTIGATE ) )
02606                         continue;
02607                 
02608                 //Squad points are only valid if we're looking for them
02609                 if ( ( level.combatPoints[i].flags & CPF_SQUAD ) && ( ( flags & CP_SQUAD ) == qfalse ) )
02610                         continue;
02611 
02612                 if ( flags&CP_NO_PVS )
02613                 {//must not be within PVS of mu current origin
02614                         if ( trap_InPVS( origin, level.combatPoints[i].origin ) )
02615                         {
02616                                 continue;
02617                         }
02618                 }
02619 
02620                 if ( flags&CP_HORZ_DIST_COLL )
02621                 {
02622                         distance =      DistanceHorizontalSquared( origin, level.combatPoints[i].origin );
02623                 }
02624                 else
02625                 {
02626                         distance =      DistanceSquared( origin, level.combatPoints[i].origin );
02627                 }
02628 
02629                 if ( distance < radiusSqr )
02630                 {
02631                         if (distance < bestDistance)
02632                         {
02633                                 bestDistance = distance;
02634                                 bestPoint = numPoints;
02635                         }
02636 
02637                         points[numPoints].dist = distance;
02638                         points[numPoints].index = i;
02639                         numPoints++;
02640                 }
02641         }
02642 
02643         return numPoints;//bestPoint;
02644 }
02645 
02646 /*
02647 -------------------------
02648 NPC_FindCombatPoint
02649 -------------------------
02650 */
02651 
02652 #define MIN_AVOID_DOT                           0.75f
02653 #define MIN_AVOID_DISTANCE                      128
02654 #define MIN_AVOID_DISTANCE_SQUARED      ( MIN_AVOID_DISTANCE * MIN_AVOID_DISTANCE )
02655 #define CP_COLLECT_RADIUS                       512.0f
02656 
02657 int NPC_FindCombatPoint( const vec3_t position, const vec3_t avoidPosition, vec3_t enemyPosition, const int flags, const float avoidDist, const int ignorePoint )
02658 {
02659         combatPt_t              points[MAX_COMBAT_POINTS];
02660         int                             best = -1, cost, bestCost = Q3_INFINITE, waypoint = WAYPOINT_NONE;
02661         float                   dist;
02662         trace_t                 tr;
02663         float                   collRad = CP_COLLECT_RADIUS;
02664         int                             numPoints;
02665         int                             j = 0;
02666         float                   modifiedAvoidDist = avoidDist;
02667 
02668         if ( modifiedAvoidDist <= 0 )
02669         {
02670                 modifiedAvoidDist = MIN_AVOID_DISTANCE_SQUARED;
02671         }
02672         else
02673         {
02674                 modifiedAvoidDist *= modifiedAvoidDist;
02675         }
02676 
02677         if ( (flags & CP_HAS_ROUTE) || (flags & CP_NEAREST) )
02678         {//going to be doing macro nav tests
02679                 if ( NPC->waypoint == WAYPOINT_NONE )
02680                 {
02681                         waypoint = NAV_GetNearestNode( NPC, NPC->lastWaypoint );
02682                 }
02683                 else
02684                 {
02685                         waypoint = NPC->waypoint;
02686                 }
02687         }
02688 
02689         //Collect our nearest points
02690         if ( flags & CP_NO_PVS )
02691         {//much larger radius since most will be dropped?
02692                 collRad = CP_COLLECT_RADIUS*4;
02693         }
02694         numPoints = NPC_CollectCombatPoints( enemyPosition, collRad, points, flags );//position
02695 
02696 
02697         for ( j = 0; j < numPoints; j++ )
02698         {
02699                 //const int i = (*cpi).second;
02700                 const int i = points[j].index;
02701                 const float pdist = points[j].dist;
02702 
02703                 //Must not be one we want to ignore
02704                 if ( i == ignorePoint )
02705                         continue;
02706 
02707                 //FIXME: able to mark certain ones as too dangerous to go to for now?  Like a tripmine/thermal/detpack is near or something?
02708                 //If we need a cover point, check this point
02709                 if ( ( flags & CP_COVER ) && ( NPC_ClearLOS( level.combatPoints[i].origin, enemyPosition ) == qtrue ) )//Used to use NPC->enemy
02710                         continue;
02711 
02712                 //Need a clear LOS to our target... and be within shot range to enemy position (FIXME: make this a separate CS_ flag? and pass in a range?)
02713                 if ( flags & CP_CLEAR )
02714                 {
02715                         if ( NPC_ClearLOS3( level.combatPoints[i].origin, NPC->enemy ) == qfalse )
02716                         {
02717                                 continue;
02718                         }
02719                         if ( NPC->s.weapon == WP_THERMAL )
02720                         {//horizontal
02721                                 dist = DistanceHorizontalSquared( level.combatPoints[i].origin, NPC->enemy->r.currentOrigin );
02722                         }
02723                         else
02724                         {//actual
02725                                 dist = DistanceSquared( level.combatPoints[i].origin, NPC->enemy->r.currentOrigin );
02726                         }
02727                         if ( dist > (NPCInfo->stats.visrange*NPCInfo->stats.visrange) )
02728                         {
02729                                 continue;
02730                         }
02731                 }
02732 
02733                 //Avoid this position?
02734                 if ( ( flags & CP_AVOID ) && ( DistanceSquared( level.combatPoints[i].origin, position ) < modifiedAvoidDist ) )//was using MIN_AVOID_DISTANCE_SQUARED, not passed in modifiedAvoidDist
02735                         continue;
02736 
02737                 //Try to find a point closer to the enemy than where we are
02738                 if ( flags & CP_APPROACH_ENEMY )
02739                 {
02740                         if ( flags&CP_HORZ_DIST_COLL )
02741                         {
02742                                 if ( pdist > DistanceHorizontalSquared( position, enemyPosition ) )
02743                                 {
02744                                         continue;
02745                                 }
02746                         }
02747                         else 
02748                         {
02749                                 if ( pdist > DistanceSquared( position, enemyPosition ) )
02750                                 {
02751                                         continue;
02752                                 }
02753                         }
02754                 }
02755                 //Try to find a point farther from the enemy than where we are
02756                 if ( flags & CP_RETREAT )
02757                 {
02758                         if ( flags&CP_HORZ_DIST_COLL )
02759                         {
02760                                 if ( pdist < DistanceHorizontalSquared( position, enemyPosition ) )
02761                                 {//it's closer, don't use it
02762                                         continue;
02763                                 }
02764                         }
02765                         else
02766                         {
02767                                 if ( pdist < DistanceSquared( position, enemyPosition ) )
02768                                 {//it's closer, don't use it
02769                                         continue;
02770                                 }
02771                         }
02772                 }
02773 
02774                 //We want a point on other side of the enemy from current pos
02775                 if ( flags & CP_FLANK  )
02776                 {
02777                         vec3_t  eDir2Me, eDir2CP;
02778                         float dot;
02779                         
02780                         VectorSubtract( position, enemyPosition, eDir2Me );
02781                         VectorNormalize( eDir2Me );
02782 
02783                         VectorSubtract( level.combatPoints[i].origin, enemyPosition, eDir2CP );
02784                         VectorNormalize( eDir2CP );
02785 
02786                         dot = DotProduct( eDir2Me, eDir2CP );
02787                         
02788                         //Not far enough behind enemy from current pos
02789                         if ( dot >= 0.4 )
02790                                 continue;
02791                 }
02792 
02793                 //See if we're trying to avoid our enemy
02794                 //FIXME: this needs to check for the waypoint you'll be taking to get to that combat point
02795                 if ( flags & CP_AVOID_ENEMY  )
02796                 {
02797                         vec3_t  eDir, gDir;
02798                         vec3_t  wpOrg;
02799                         float dot;
02800                         
02801                         VectorSubtract( position, enemyPosition, eDir );
02802                         VectorNormalize( eDir );
02803 
02804                         /*
02805                         NAV_FindClosestWaypointForEnt( NPC, level.combatPoints[i].waypoint );
02806                         if ( NPC->waypoint != WAYPOINT_NONE && NPC->waypoint != level.combatPoints[i].waypoint )
02807                         {
02808                                 trap_Nav_GetNodePosition( NPC->waypoint, wpOrg );
02809                         }
02810                         else
02811                         */
02812                         {
02813                                 VectorCopy( level.combatPoints[i].origin, wpOrg );
02814                         }
02815                         VectorSubtract( position, wpOrg, gDir );
02816                         VectorNormalize( gDir );
02817 
02818                         dot = DotProduct( gDir, eDir );
02819                         
02820                         //Don't want to run at enemy
02821                         if ( dot >= MIN_AVOID_DOT )
02822                                 continue;
02823 
02824                         //Can't be too close to the enemy
02825                         if ( DistanceSquared( wpOrg, enemyPosition ) < modifiedAvoidDist )
02826                                 continue;
02827                 }
02828                 
02829                 //Okay, now make sure it's not blocked
02830                 trap_Trace( &tr, level.combatPoints[i].origin, NPC->r.mins, NPC->r.maxs, level.combatPoints[i].origin, NPC->s.number, NPC->clipmask );
02831                 if ( tr.allsolid || tr.startsolid )
02832                 {
02833                         continue;
02834                 }
02835 
02836                 //we must have a route to the combat point
02837                 if ( flags & CP_HAS_ROUTE )
02838                 {
02839                         /*
02840                         if ( level.combatPoints[i].waypoint == WAYPOINT_NONE )
02841                         {
02842                                 level.combatPoints[i].waypoint = NAV_FindClosestWaypointForPoint( level.combatPoints[i].origin );
02843                         }
02844                         */
02845 
02846                         if ( waypoint == WAYPOINT_NONE || level.combatPoints[i].waypoint == WAYPOINT_NONE || trap_Nav_GetBestNodeAltRoute2( waypoint, level.combatPoints[i].waypoint, NODE_NONE ) == WAYPOINT_NONE )
02847                         {//can't possibly have a route to any OR can't possibly have a route to this one OR don't have a route to this one
02848                                 if ( !NAV_ClearPathToPoint( NPC, NPC->r.mins, NPC->r.maxs, level.combatPoints[i].origin, NPC->clipmask, ENTITYNUM_NONE ) )
02849                                 {//don't even have a clear straight path to this one
02850                                         continue;
02851                                 }
02852                         }
02853                 }
02854 
02855                 //We want the one with the shortest path from current pos
02856                 if ( flags & CP_NEAREST && waypoint != WAYPOINT_NONE && level.combatPoints[i].waypoint != WAYPOINT_NONE )
02857                 {
02858                         cost = trap_Nav_GetPathCost( waypoint, level.combatPoints[i].waypoint );
02859                         if ( cost < bestCost )
02860                         {
02861                                 bestCost = cost;
02862                                 best = i;
02863                         }
02864                         continue;
02865                 }
02866 
02867                 //we want the combat point closest to the enemy
02868                 //if ( flags & CP_CLOSEST )
02869                 //they are sorted by this distance, so the first one to get this far is the closest
02870                 return i;
02871         }
02872 
02873         return best;
02874 }
02875 
02876 /*
02877 -------------------------
02878 NPC_FindSquadPoint
02879 -------------------------
02880 */
02881 
02882 int NPC_FindSquadPoint( vec3_t position )
02883 {
02884         float   dist, nearestDist = (float)WORLD_SIZE*(float)WORLD_SIZE;
02885         int             nearestPoint = -1;
02886         int             i;
02887 
02888         //float                 playerDist = DistanceSquared( g_entities[0].currentOrigin, NPC->r.currentOrigin );
02889 
02890         for ( i = 0; i < level.numCombatPoints; i++ )
02891         {
02892                 //Squad points are only valid if we're looking for them
02893                 if ( ( level.combatPoints[i].flags & CPF_SQUAD ) == qfalse )
02894                         continue;
02895 
02896                 //Must be vacant
02897                 if ( level.combatPoints[i].occupied == qtrue )
02898                         continue;
02899                 
02900                 dist = DistanceSquared( position, level.combatPoints[i].origin );
02901 
02902                 //The point cannot take us past the player
02903                 //if ( dist > ( playerDist * DotProduct( dirToPlayer, playerDir ) ) )   //FIXME: Retain this
02904                 //      continue;
02905 
02906                 //See if this is closer than the others
02907                 if ( dist < nearestDist )
02908                 {
02909                         nearestPoint = i;
02910                         nearestDist = dist;
02911                 }
02912         }
02913 
02914         return nearestPoint;
02915 }
02916 
02917 /*
02918 -------------------------
02919 NPC_ReserveCombatPoint
02920 -------------------------
02921 */
02922 
02923 qboolean NPC_ReserveCombatPoint( int combatPointID )
02924 {
02925         //Make sure it's valid
02926         if ( combatPointID > level.numCombatPoints )
02927                 return qfalse;
02928 
02929         //Make sure it's not already occupied
02930         if ( level.combatPoints[combatPointID].occupied )
02931                 return qfalse;
02932 
02933         //Reserve it
02934         level.combatPoints[combatPointID].occupied = qtrue;
02935 
02936         return qtrue;
02937 }
02938 
02939 /*
02940 -------------------------
02941 NPC_FreeCombatPoint
02942 -------------------------
02943 */
02944 
02945 qboolean NPC_FreeCombatPoint( int combatPointID, qboolean failed )
02946 {
02947         if ( failed )
02948         {//remember that this one failed for us
02949                 NPCInfo->lastFailedCombatPoint = combatPointID;
02950         }
02951         //Make sure it's valid
02952         if ( combatPointID > level.numCombatPoints )
02953                 return qfalse;
02954 
02955         //Make sure it's currently occupied
02956         if ( level.combatPoints[combatPointID].occupied == qfalse )
02957                 return qfalse;
02958 
02959         //Free it
02960         level.combatPoints[combatPointID].occupied = qfalse;
02961         
02962         return qtrue;
02963 }
02964 
02965 /*
02966 -------------------------
02967 NPC_SetCombatPoint
02968 -------------------------
02969 */
02970 
02971 qboolean NPC_SetCombatPoint( int combatPointID )
02972 {
02973         //Free a combat point if we already have one
02974         if ( NPCInfo->combatPoint != -1 )
02975         {
02976                 NPC_FreeCombatPoint( NPCInfo->combatPoint, qfalse );
02977         }
02978 
02979         if ( NPC_ReserveCombatPoint( combatPointID ) == qfalse )
02980                 return qfalse;
02981 
02982         NPCInfo->combatPoint = combatPointID;
02983 
02984         return qtrue;
02985 }
02986 
02987 extern qboolean CheckItemCanBePickedUpByNPC( gentity_t *item, gentity_t *pickerupper );
02988 gentity_t *NPC_SearchForWeapons( void )
02989 {
02990         gentity_t *found = g_entities, *bestFound = NULL;
02991         float           dist, bestDist = Q3_INFINITE;
02992         int i;
02993 //      for ( found = g_entities; found < &g_entities[globals.num_entities] ; found++)
02994         for ( i = 0; i<level.num_entities; i++)
02995         {
02996 //              if ( !found->inuse )
02997 //              {
02998 //                      continue;
02999 //              }
03000                 if(!g_entities[i].inuse)
03001                         continue;
03002                 
03003                 found=&g_entities[i];
03004                 
03005                 //FIXME: Also look for ammo_racks that have weapons on them?
03006                 if ( found->s.eType != ET_ITEM )
03007                 {
03008                         continue;
03009                 }
03010                 if ( found->item->giType != IT_WEAPON )
03011                 {
03012                         continue;
03013                 }
03014                 if ( found->s.eFlags & EF_NODRAW )
03015                 {
03016                         continue;
03017                 }
03018                 if ( CheckItemCanBePickedUpByNPC( found, NPC ) )
03019                 {
03020                         if ( trap_InPVS( found->r.currentOrigin, NPC->r.currentOrigin ) )
03021                         {
03022                                 dist = DistanceSquared( found->r.currentOrigin, NPC->r.currentOrigin );
03023                                 if ( dist < bestDist )
03024                                 {
03025                                         if ( !trap_Nav_GetBestPathBetweenEnts( NPC, found, NF_CLEAR_PATH ) 
03026                                                 || trap_Nav_GetBestNodeAltRoute2( NPC->waypoint, found->waypoint, NODE_NONE ) == WAYPOINT_NONE )
03027                                         {//can't possibly have a route to any OR can't possibly have a route to this one OR don't have a route to this one
03028                                                 if ( NAV_ClearPathToPoint( NPC, NPC->r.mins, NPC->r.maxs, found->r.currentOrigin, NPC->clipmask, ENTITYNUM_NONE ) )
03029                                                 {//have a clear straight path to this one
03030                                                         bestDist = dist;
03031                                                         bestFound = found;
03032                                                 }
03033                                         }
03034                                         else
03035                                         {//can nav to it
03036                                                 bestDist = dist;
03037                                                 bestFound = found;
03038                                         }
03039                                 }
03040                         }
03041                 }
03042         }
03043 
03044         return bestFound;
03045 }
03046 
03047 void NPC_SetPickUpGoal( gentity_t *foundWeap )
03048 {
03049         vec3_t org;
03050 
03051         //NPCInfo->goalEntity = foundWeap;
03052         VectorCopy( foundWeap->r.currentOrigin, org );
03053         org[2] += 24 - (foundWeap->r.mins[2]*-1);//adjust the origin so that I am on the ground
03054         NPC_SetMoveGoal( NPC, org, foundWeap->r.maxs[0]*0.75, qfalse, -1, foundWeap );
03055         NPCInfo->tempGoal->waypoint = foundWeap->waypoint;
03056         NPCInfo->tempBehavior = BS_DEFAULT;
03057         NPCInfo->squadState = SQUAD_TRANSITION;
03058 }
03059 
03060 void NPC_CheckGetNewWeapon( void )
03061 {
03062         if ( NPC->s.weapon == WP_NONE && NPC->enemy )
03063         {//if running away because dropped weapon...
03064                 if ( NPCInfo->goalEntity 
03065                         && NPCInfo->goalEntity == NPCInfo->tempGoal
03066                         && NPCInfo->goalEntity->enemy
03067                         && !NPCInfo->goalEntity->enemy->inuse )
03068                 {//maybe was running at a weapon that was picked up
03069                         NPCInfo->goalEntity = NULL;
03070                 }
03071                 if ( TIMER_Done( NPC, "panic" ) && NPCInfo->goalEntity == NULL )
03072                 {//need a weapon, any lying around?
03073                         gentity_t *foundWeap = NPC_SearchForWeapons();
03074                         if ( foundWeap )
03075                         {//try to nav to it
03076                                 /*
03077                                 if ( !trap_Nav_GetBestPathBetweenEnts( NPC, foundWeap, NF_CLEAR_PATH ) 
03078                                         || trap_Nav_GetBestNodeAltRoute( NPC->waypoint, foundWeap->waypoint ) == WAYPOINT_NONE )
03079                                 {//can't possibly have a route to any OR can't possibly have a route to this one OR don't have a route to this one
03080                                         if ( !NAV_ClearPathToPoint( NPC, NPC->r.mins, NPC->r.maxs, foundWeap->r.currentOrigin, NPC->clipmask, ENTITYNUM_NONE ) )
03081                                         {//don't even have a clear straight path to this one
03082                                         }
03083                                         else
03084                                         {
03085                                                 NPC_SetPickUpGoal( foundWeap );
03086                                         }
03087                                 }
03088                                 else
03089                                 */
03090                                 {
03091                                         NPC_SetPickUpGoal( foundWeap );
03092                                 }
03093                         }
03094                 }
03095         }
03096 }
03097 
03098 void NPC_AimAdjust( int change )
03099 {
03100         if ( !TIMER_Exists( NPC, "aimDebounce" ) )
03101         {
03102                 int debounce = 500+(3-g_spskill.integer)*100;
03103                 TIMER_Set( NPC, "aimDebounce", Q_irand( debounce,debounce+1000 ) );
03104                 //int debounce = 1000+(3-g_spskill.integer)*500;
03105                 //TIMER_Set( NPC, "aimDebounce", Q_irand( debounce, debounce+2000 ) );
03106                 return;
03107         }
03108         if ( TIMER_Done( NPC, "aimDebounce" ) )
03109         {
03110                 int debounce;
03111 
03112                 NPCInfo->currentAim += change;
03113                 if ( NPCInfo->currentAim > NPCInfo->stats.aim )
03114                 {//can never be better than max aim
03115                         NPCInfo->currentAim = NPCInfo->stats.aim;
03116                 }
03117                 else if ( NPCInfo->currentAim < -30 )
03118                 {//can never be worse than this
03119                         NPCInfo->currentAim = -30;
03120                 }
03121 
03122                 //Com_Printf( "%s new aim = %d\n", NPC->NPC_type, NPCInfo->currentAim );
03123 
03124                 debounce = 500+(3-g_spskill.integer)*100;
03125                 TIMER_Set( NPC, "aimDebounce", Q_irand( debounce,debounce+1000 ) );
03126                 //int debounce = 1000+(3-g_spskill.integer)*500;
03127                 //TIMER_Set( NPC, "aimDebounce", Q_irand( debounce, debounce+2000 ) );
03128         }
03129 }
03130 
03131 void G_AimSet( gentity_t *self, int aim )
03132 {
03133         if ( self->NPC )
03134         {
03135                 int debounce;
03136 
03137                 self->NPC->currentAim = aim;
03138                 //Com_Printf( "%s new aim = %d\n", self->NPC_type, self->NPC->currentAim );
03139 
03140                 debounce = 500+(3-g_spskill.integer)*100;
03141                 TIMER_Set( self, "aimDebounce", Q_irand( debounce,debounce+1000 ) );
03142         //      int debounce = 1000+(3-g_spskill.integer)*500;
03143         //      TIMER_Set( self, "aimDebounce", Q_irand( debounce,debounce+2000 ) );
03144         }
03145 }