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_AttackDebounceForWeap