codemp/game/NPC.c

Go to the documentation of this file.
00001 //
00002 // NPC.cpp - generic functions
00003 //
00004 #include "b_local.h"
00005 #include "anims.h"
00006 #include "say.h"
00007 #include "../icarus/Q3_Interface.h"
00008 
00009 extern vec3_t playerMins;
00010 extern vec3_t playerMaxs;
00011 //extern void PM_SetAnimFinal(int *torsoAnim,int *legsAnim,int type,int anim,int priority,int *torsoAnimTimer,int *legsAnimTimer,gentity_t *gent);
00012 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
00013 extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time );
00014 extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time );
00015 extern void NPC_BSNoClip ( void );
00016 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
00017 extern void NPC_ApplyRoff (void);
00018 extern void NPC_TempLookTarget ( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
00019 extern void NPC_CheckPlayerAim ( void );
00020 extern void NPC_CheckAllClear ( void );
00021 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
00022 extern qboolean NPC_CheckLookTarget( gentity_t *self );
00023 extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime );
00024 extern void Mark1_dying( gentity_t *self );
00025 extern void NPC_BSCinematic( void );
00026 extern int GetTime ( int lastTime );
00027 extern void NPC_BSGM_Default( void );
00028 extern void NPC_CheckCharmed( void );
00029 extern qboolean Boba_Flying( gentity_t *self );
00030 
00031 extern vmCvar_t         g_saberRealisticCombat;
00032 
00033 //Local Variables
00034 gentity_t               *NPC;
00035 gNPC_t                  *NPCInfo;
00036 gclient_t               *client;
00037 usercmd_t               ucmd;
00038 visibility_t    enemyVisibility;
00039 
00040 void NPC_SetAnim(gentity_t      *ent,int type,int anim,int priority);
00041 void pitch_roll_for_slope( gentity_t *forwhom, vec3_t pass_slope );
00042 extern void GM_Dying( gentity_t *self );
00043 
00044 extern int eventClearTime;
00045 
00046 void CorpsePhysics( gentity_t *self )
00047 {
00048         // run the bot through the server like it was a real client
00049         memset( &ucmd, 0, sizeof( ucmd ) );
00050         ClientThink( self->s.number, &ucmd );
00051         //VectorCopy( self->s.origin, self->s.origin2 );
00052         //rww - don't get why this is happening.
00053         
00054         if ( self->client->NPC_class == CLASS_GALAKMECH )
00055         {
00056                 GM_Dying( self );
00057         }
00058         //FIXME: match my pitch and roll for the slope of my groundPlane
00059         if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && !(self->s.eFlags&EF_DISINTEGRATION) )
00060         {//on the ground
00061                 //FIXME: check 4 corners
00062                 pitch_roll_for_slope( self, NULL );
00063         }
00064 
00065         if ( eventClearTime == level.time + ALERT_CLEAR_TIME )
00066         {//events were just cleared out so add me again
00067                 if ( !(self->client->ps.eFlags&EF_NODRAW) )
00068                 {
00069                         AddSightEvent( self->enemy, self->r.currentOrigin, 384, AEL_DISCOVERED, 0.0f );
00070                 }
00071         }
00072 
00073         if ( level.time - self->s.time > 3000 )
00074         {//been dead for 3 seconds
00075                 if ( g_dismember.integer < 11381138 && !g_saberRealisticCombat.integer )
00076                 {//can't be dismembered once dead
00077                         if ( self->client->NPC_class != CLASS_PROTOCOL )
00078                         {
00079                         //      self->client->dismembered = qtrue;
00080                         }
00081                 }
00082         }
00083 
00084         //if ( level.time - self->s.time > 500 )
00085         if (self->client->respawnTime < (level.time+500))
00086         {//don't turn "nonsolid" until about 1 second after actual death
00087 
00088                 if (self->client->ps.eFlags & EF_DISINTEGRATION)
00089                 {
00090                         self->r.contents = 0;
00091                 }
00092                 else if ((self->client->NPC_class != CLASS_MARK1) && (self->client->NPC_class != CLASS_INTERROGATOR))   // The Mark1 & Interrogator stays solid.
00093                 {
00094                         self->r.contents = CONTENTS_CORPSE;
00095                         //self->r.maxs[2] = -8;
00096                 }
00097 
00098                 if ( self->message )
00099                 {
00100                         self->r.contents |= CONTENTS_TRIGGER;
00101                 }
00102         }
00103 }
00104 
00105 /*
00106 ----------------------------------------
00107 NPC_RemoveBody
00108 
00109 Determines when it's ok to ditch the corpse
00110 ----------------------------------------
00111 */
00112 #define REMOVE_DISTANCE         128
00113 #define REMOVE_DISTANCE_SQR (REMOVE_DISTANCE * REMOVE_DISTANCE)
00114 
00115 void NPC_RemoveBody( gentity_t *self )
00116 {
00117         CorpsePhysics( self );
00118 
00119         self->nextthink = level.time + FRAMETIME;
00120 
00121         if ( self->NPC->nextBStateThink <= level.time )
00122         {
00123                 trap_ICARUS_MaintainTaskManager(self->s.number);
00124         }
00125         self->NPC->nextBStateThink = level.time + FRAMETIME;
00126 
00127         if ( self->message )
00128         {//I still have a key
00129                 return;
00130         }
00131 
00132         // I don't consider this a hack, it's creative coding . . . 
00133         // I agree, very creative... need something like this for ATST and GALAKMECH too!
00134         if (self->client->NPC_class == CLASS_MARK1)
00135         {
00136                 Mark1_dying( self );
00137         }
00138 
00139         // Since these blow up, remove the bounding box.
00140         if ( self->client->NPC_class == CLASS_REMOTE 
00141                 || self->client->NPC_class == CLASS_SENTRY
00142                 || self->client->NPC_class == CLASS_PROBE
00143                 || self->client->NPC_class == CLASS_INTERROGATOR
00144                 || self->client->NPC_class == CLASS_PROBE
00145                 || self->client->NPC_class == CLASS_MARK2 )
00146         {
00147                 //if ( !self->taskManager || !self->taskManager->IsRunning() )
00148                 if (!trap_ICARUS_IsRunning(self->s.number))
00149                 {
00150                         if ( !self->activator || !self->activator->client || !(self->activator->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
00151                         {//not being held by a Rancor
00152                                 G_FreeEntity( self );
00153                         }
00154                 }
00155                 return;
00156         }
00157 
00158         //FIXME: don't ever inflate back up?
00159         self->r.maxs[2] = self->client->renderInfo.eyePoint[2] - self->r.currentOrigin[2] + 4;
00160         if ( self->r.maxs[2] < -8 )
00161         {
00162                 self->r.maxs[2] = -8;
00163         }
00164 
00165         if ( self->client->NPC_class == CLASS_GALAKMECH )
00166         {//never disappears
00167                 return;
00168         }
00169         if ( self->NPC && self->NPC->timeOfDeath <= level.time )
00170         {
00171                 self->NPC->timeOfDeath = level.time + 1000;
00172                 // Only do all of this nonsense for Scav boys ( and girls )
00174         //              || self->client->playerTeam == NPCTEAM_HIROGEN || self->client->playerTeam == NPCTEAM_MALON )
00175                 // should I check NPC_class here instead of TEAM ? - dmv
00176                 if( self->client->playerTeam == NPCTEAM_ENEMY || self->client->NPC_class == CLASS_PROTOCOL )
00177                 {
00178                         self->nextthink = level.time + FRAMETIME; // try back in a second
00179 
00180                         /*
00181                         if ( DistanceSquared( g_entities[0].r.currentOrigin, self->r.currentOrigin ) <= REMOVE_DISTANCE_SQR )
00182                         {
00183                                 return;
00184                         }
00185 
00186                         if ( (InFOV( self, &g_entities[0], 110, 90 )) ) // generous FOV check
00187                         {
00188                                 if ( (NPC_ClearLOS2( &g_entities[0], self->r.currentOrigin )) )
00189                                 {
00190                                         return;
00191                                 }
00192                         }
00193                         */
00194                         //Don't care about this for MP I guess.
00195                 }
00196 
00197                 //FIXME: there are some conditions - such as heavy combat - in which we want
00198                 //                      to remove the bodies... but in other cases it's just weird, like
00199                 //                      when they're right behind you in a closed room and when they've been
00200                 //                      placed as dead NPCs by a designer...
00201                 //                      For now we just assume that a corpse with no enemy was 
00202                 //                      placed in the map as a corpse
00203                 if ( self->enemy )
00204                 {
00205                         //if ( !self->taskManager || !self->taskManager->IsRunning() )
00206                         if (!trap_ICARUS_IsRunning(self->s.number))
00207                         {
00208                                 if ( !self->activator || !self->activator->client || !(self->activator->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
00209                                 {//not being held by a Rancor
00210                                         if ( self->client && self->client->ps.saberEntityNum > 0 && self->client->ps.saberEntityNum < ENTITYNUM_WORLD )
00211                                         {
00212                                                 gentity_t *saberent = &g_entities[self->client->ps.saberEntityNum];
00213                                                 if ( saberent )
00214                                                 {
00215                                                         G_FreeEntity( saberent );
00216                                                 }
00217                                         }
00218                                         G_FreeEntity( self );
00219                                 }
00220                         }
00221                 }
00222         }
00223 }
00224 
00225 /*
00226 ----------------------------------------
00227 NPC_RemoveBody
00228 
00229 Determines when it's ok to ditch the corpse
00230 ----------------------------------------
00231 */
00232 
00233 int BodyRemovalPadTime( gentity_t *ent )
00234 {
00235         int     time;
00236 
00237         if ( !ent || !ent->client )
00238                 return 0;
00239 /*
00240         switch ( ent->client->playerTeam )
00241         {
00242         case NPCTEAM_KLINGON:   // no effect, we just remove them when the player isn't looking
00243         case NPCTEAM_SCAVENGERS:
00244         case NPCTEAM_HIROGEN:
00245         case NPCTEAM_MALON:
00246         case NPCTEAM_IMPERIAL:
00247         case NPCTEAM_STARFLEET:
00248                 time = 10000; // 15 secs.
00249                 break;
00250 
00251         case NPCTEAM_BORG:
00252                 time = 2000;
00253                 break;
00254 
00255         case NPCTEAM_STASIS:
00256                 return qtrue;
00257                 break;
00258 
00259         case NPCTEAM_FORGE:
00260                 time = 1000;
00261                 break;
00262 
00263         case NPCTEAM_BOTS:
00264 //              if (!Q_stricmp( ent->NPC_type, "mouse" ))
00265 //              {
00266                         time = 0;
00267 //              }
00268 //              else
00269 //              {
00270 //                      time = 10000;
00271 //              }
00272                 break;
00273 
00274         case NPCTEAM_8472:
00275                 time = 2000;
00276                 break;
00277 
00278         default:
00279                 // never go away
00280                 time = Q3_INFINITE;
00281                 break;
00282         }
00283 */
00284         // team no longer indicates species/race, so in this case we'd use NPC_class, but
00285         switch( ent->client->NPC_class )
00286         {
00287         case CLASS_MOUSE:
00288         case CLASS_GONK:
00289         case CLASS_R2D2:
00290         case CLASS_R5D2:
00291         //case CLASS_PROTOCOL:
00292         case CLASS_MARK1:
00293         case CLASS_MARK2:
00294         case CLASS_PROBE:
00295         case CLASS_SEEKER:
00296         case CLASS_REMOTE:
00297         case CLASS_SENTRY:
00298         case CLASS_INTERROGATOR:
00299                 time = 0;
00300                 break;
00301         default:
00302                 // never go away
00303         //      time = Q3_INFINITE;
00304                 // for now I'm making default 10000
00305                 time = 10000;
00306                 break;
00307 
00308         }
00309         
00310 
00311         return time;
00312 }
00313 
00314 
00315 /*
00316 ----------------------------------------
00317 NPC_RemoveBodyEffect
00318 
00319 Effect to be applied when ditching the corpse
00320 ----------------------------------------
00321 */
00322 
00323 static void NPC_RemoveBodyEffect(void)
00324 {
00325 //      vec3_t          org;
00326 //      gentity_t       *tent;
00327 
00328         if ( !NPC || !NPC->client || (NPC->s.eFlags & EF_NODRAW) )
00329                 return;
00330 /*
00331         switch(NPC->client->playerTeam)
00332         {
00333         case NPCTEAM_STARFLEET:
00334                 //FIXME: Starfleet beam out
00335                 break;
00336 
00337         case NPCTEAM_BOTS:
00338 //              VectorCopy( NPC->r.currentOrigin, org );
00339 //              org[2] -= 16;
00340 //              tent = G_TempEntity( org, EV_BOT_EXPLODE );
00341 //              tent->owner = NPC;
00342 
00343                 break;
00344 
00345         default:
00346                 break;
00347         }
00348 */
00349 
00350 
00351         // team no longer indicates species/race, so in this case we'd use NPC_class, but
00352         
00353         // stub code
00354         switch(NPC->client->NPC_class)
00355         {
00356         case CLASS_PROBE:
00357         case CLASS_SEEKER:
00358         case CLASS_REMOTE:
00359         case CLASS_SENTRY:
00360         case CLASS_GONK:
00361         case CLASS_R2D2:
00362         case CLASS_R5D2:
00363         //case CLASS_PROTOCOL:
00364         case CLASS_MARK1:
00365         case CLASS_MARK2:
00366         case CLASS_INTERROGATOR:
00367         case CLASS_ATST: // yeah, this is a little weird, but for now I'm listing all droids
00368         //      VectorCopy( NPC->r.currentOrigin, org );
00369         //      org[2] -= 16;
00370         //      tent = G_TempEntity( org, EV_BOT_EXPLODE );
00371         //      tent->owner = NPC;
00372                 break;
00373         default:
00374                 break;
00375         }
00376 
00377 
00378 }
00379 
00380 
00381 /*
00382 ====================================================================
00383 void pitch_roll_for_slope( gentity_t *forwhom, vec3_t pass_slope )
00384 
00385 MG
00386 
00387 This will adjust the pitch and roll of a monster to match
00388 a given slope - if a non-'0 0 0' slope is passed, it will
00389 use that value, otherwise it will use the ground underneath
00390 the monster.  If it doesn't find a surface, it does nothinh\g
00391 and returns.
00392 ====================================================================
00393 */
00394 
00395 void pitch_roll_for_slope( gentity_t *forwhom, vec3_t pass_slope )
00396 {
00397         vec3_t  slope;
00398         vec3_t  nvf, ovf, ovr, startspot, endspot, new_angles = { 0, 0, 0 };
00399         float   pitch, mod, dot;
00400 
00401         //if we don't have a slope, get one
00402         if( !pass_slope || VectorCompare( vec3_origin, pass_slope ) )
00403         {
00404                 trace_t trace;
00405 
00406                 VectorCopy( forwhom->r.currentOrigin, startspot );
00407                 startspot[2] += forwhom->r.mins[2] + 4;
00408                 VectorCopy( startspot, endspot );
00409                 endspot[2] -= 300;
00410                 trap_Trace( &trace, forwhom->r.currentOrigin, vec3_origin, vec3_origin, endspot, forwhom->s.number, MASK_SOLID );
00411 //              if(trace_fraction>0.05&&forwhom.movetype==MOVETYPE_STEP)
00412 //                      forwhom.flags(-)FL_ONGROUND;
00413 
00414                 if ( trace.fraction >= 1.0 )
00415                         return;
00416 
00417                 if( !( &trace.plane ) )
00418                         return;
00419 
00420                 if ( VectorCompare( vec3_origin, trace.plane.normal ) )
00421                         return;
00422 
00423                 VectorCopy( trace.plane.normal, slope );
00424         }
00425         else
00426         {
00427                 VectorCopy( pass_slope, slope );
00428         }
00429 
00430 
00431         AngleVectors( forwhom->r.currentAngles, ovf, ovr, NULL );
00432 
00433         vectoangles( slope, new_angles );
00434         pitch = new_angles[PITCH] + 90;
00435         new_angles[ROLL] = new_angles[PITCH] = 0;
00436 
00437         AngleVectors( new_angles, nvf, NULL, NULL );
00438 
00439         mod = DotProduct( nvf, ovr );
00440 
00441         if ( mod<0 )
00442                 mod = -1;
00443         else
00444                 mod = 1;
00445 
00446         dot = DotProduct( nvf, ovf );
00447 
00448         if ( forwhom->client )
00449         {
00450                 float oldmins2;
00451 
00452                 forwhom->client->ps.viewangles[PITCH] = dot * pitch;
00453                 forwhom->client->ps.viewangles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod);
00454                 oldmins2 = forwhom->r.mins[2];
00455                 forwhom->r.mins[2] = -24 + 12 * fabs(forwhom->client->ps.viewangles[PITCH])/180.0f;
00456                 //FIXME: if it gets bigger, move up
00457                 if ( oldmins2 > forwhom->r.mins[2] )
00458                 {//our mins is now lower, need to move up
00459                         //FIXME: trace?
00460                         forwhom->client->ps.origin[2] += (oldmins2 - forwhom->r.mins[2]);
00461                         forwhom->r.currentOrigin[2] = forwhom->client->ps.origin[2];
00462                         trap_LinkEntity( forwhom );
00463                 }
00464         }
00465         else
00466         {
00467                 forwhom->r.currentAngles[PITCH] = dot * pitch;
00468                 forwhom->r.currentAngles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod);
00469         }
00470 }
00471 
00472 
00473 /*
00474 ----------------------------------------
00475 DeadThink
00476 ----------------------------------------
00477 */
00478 static void DeadThink ( void ) 
00479 {
00480         trace_t trace;
00481 
00482         //HACKHACKHACKHACKHACK
00483         //We should really have a seperate G2 bounding box (seperate from the physics bbox) for G2 collisions only
00484         //FIXME: don't ever inflate back up?
00485         NPC->r.maxs[2] = NPC->client->renderInfo.eyePoint[2] - NPC->r.currentOrigin[2] + 4;
00486         if ( NPC->r.maxs[2] < -8 )
00487         {
00488                 NPC->r.maxs[2] = -8;
00489         }
00490         if ( VectorCompare( NPC->client->ps.velocity, vec3_origin ) )
00491         {//not flying through the air
00492                 if ( NPC->r.mins[0] > -32 )
00493                 {
00494                         NPC->r.mins[0] -= 1;
00495                         trap_Trace (&trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPC->r.currentOrigin, NPC->s.number, NPC->clipmask );
00496                         if ( trace.allsolid )
00497                         {
00498                                 NPC->r.mins[0] += 1;
00499                         }
00500                 }
00501                 if ( NPC->r.maxs[0] < 32 )
00502                 {
00503                         NPC->r.maxs[0] += 1;
00504                         trap_Trace (&trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPC->r.currentOrigin, NPC->s.number, NPC->clipmask );
00505                         if ( trace.allsolid )
00506                         {
00507                                 NPC->r.maxs[0] -= 1;
00508                         }
00509                 }
00510                 if ( NPC->r.mins[1] > -32 )
00511                 {
00512                         NPC->r.mins[1] -= 1;
00513                         trap_Trace (&trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPC->r.currentOrigin, NPC->s.number, NPC->clipmask );
00514                         if ( trace.allsolid )
00515                         {
00516                                 NPC->r.mins[1] += 1;
00517                         }
00518                 }
00519                 if ( NPC->r.maxs[1] < 32 )
00520                 {
00521                         NPC->r.maxs[1] += 1;
00522                         trap_Trace (&trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPC->r.currentOrigin, NPC->s.number, NPC->clipmask );
00523                         if ( trace.allsolid )
00524                         {
00525                                 NPC->r.maxs[1] -= 1;
00526                         }
00527                 }
00528         }
00529         //HACKHACKHACKHACKHACK
00530 
00531         //FIXME: tilt and fall off of ledges?
00532         //NPC_PostDeathThink();
00533 
00534         /*
00535         if ( !NPCInfo->timeOfDeath && NPC->client != NULL && NPCInfo != NULL ) 
00536         {
00537                 //haven't finished death anim yet and were NOT given a specific amount of time to wait before removal
00538                 int                             legsAnim        = NPC->client->ps.legsAnim;
00539                 animation_t             *animations     = knownAnimFileSets[NPC->client->clientInfo.animFileIndex].animations;
00540 
00541                 NPC->bounceCount = -1; // This is a cheap hack for optimizing the pointcontents check below
00542 
00543                 //ghoul doesn't tell us this anymore
00544                 //if ( NPC->client->renderInfo.legsFrame == animations[legsAnim].firstFrame + (animations[legsAnim].numFrames - 1) )
00545                 {
00546                         //reached the end of the death anim
00547                         NPCInfo->timeOfDeath = level.time + BodyRemovalPadTime( NPC );
00548                 }
00549         }
00550         else
00551         */
00552         {
00553                 //death anim done (or were given a specific amount of time to wait before removal), wait the requisite amount of time them remove
00554                 if ( level.time >= NPCInfo->timeOfDeath + BodyRemovalPadTime( NPC ) )
00555                 {
00556                         if ( NPC->client->ps.eFlags & EF_NODRAW )
00557                         {
00558                                 if (!trap_ICARUS_IsRunning(NPC->s.number))
00559                                 //if ( !NPC->taskManager || !NPC->taskManager->IsRunning() )
00560                                 {
00561                                         NPC->think = G_FreeEntity;
00562                                         NPC->nextthink = level.time + FRAMETIME;
00563                                 }
00564                         }
00565                         else
00566                         {
00567                                 class_t npc_class;
00568 
00569                                 // Start the body effect first, then delay 400ms before ditching the corpse
00570                                 NPC_RemoveBodyEffect();
00571 
00572                                 //FIXME: keep it running through physics somehow?
00573                                 NPC->think = NPC_RemoveBody;
00574                                 NPC->nextthink = level.time + FRAMETIME;
00575                         //      if ( NPC->client->playerTeam == NPCTEAM_FORGE )
00576                         //              NPCInfo->timeOfDeath = level.time + FRAMETIME * 8;
00577                         //      else if ( NPC->client->playerTeam == NPCTEAM_BOTS )
00578                                 npc_class = NPC->client->NPC_class;
00579                                 // check for droids
00580                                 if ( npc_class == CLASS_SEEKER || npc_class == CLASS_REMOTE || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE ||
00581                                          npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 ||
00582                                          npc_class == CLASS_MARK2 || npc_class == CLASS_SENTRY )//npc_class == CLASS_PROTOCOL ||
00583                                 {
00584                                         NPC->client->ps.eFlags |= EF_NODRAW;
00585                                         NPCInfo->timeOfDeath = level.time + FRAMETIME * 8;
00586                                 }
00587                                 else
00588                                         NPCInfo->timeOfDeath = level.time + FRAMETIME * 4;
00589                         }
00590                         return;
00591                 }
00592         }
00593 
00594         // If the player is on the ground and the resting position contents haven't been set yet...(BounceCount tracks the contents)
00595         if ( NPC->bounceCount < 0 && NPC->s.groundEntityNum >= 0 )
00596         {
00597                 // if client is in a nodrop area, make him/her nodraw
00598                 int contents = NPC->bounceCount = trap_PointContents( NPC->r.currentOrigin, -1 );
00599 
00600                 if ( ( contents & CONTENTS_NODROP ) ) 
00601                 {
00602                         NPC->client->ps.eFlags |= EF_NODRAW;
00603                 }
00604         }
00605 
00606         CorpsePhysics( NPC );
00607 }
00608 
00609 
00610 /*
00611 ===============
00612 SetNPCGlobals
00613 
00614 local function to set globals used throughout the AI code
00615 ===============
00616 */
00617 void SetNPCGlobals( gentity_t *ent ) 
00618 {
00619         NPC = ent;
00620         NPCInfo = ent->NPC;
00621         client = ent->client;
00622         memset( &ucmd, 0, sizeof( usercmd_t ) );
00623 }
00624 
00625 gentity_t       *_saved_NPC;
00626 gNPC_t          *_saved_NPCInfo;
00627 gclient_t       *_saved_client;
00628 usercmd_t       _saved_ucmd;
00629 
00630 void SaveNPCGlobals(void) 
00631 {
00632         _saved_NPC = NPC;
00633         _saved_NPCInfo = NPCInfo;
00634         _saved_client = client;
00635         memcpy( &_saved_ucmd, &ucmd, sizeof( usercmd_t ) );
00636 }
00637 
00638 void RestoreNPCGlobals(void) 
00639 {
00640         NPC = _saved_NPC;
00641         NPCInfo = _saved_NPCInfo;
00642         client = _saved_client;
00643         memcpy( &ucmd, &_saved_ucmd, sizeof( usercmd_t ) );
00644 }
00645 
00646 //We MUST do this, other funcs were using NPC illegally when "self" wasn't the global NPC
00647 void ClearNPCGlobals( void ) 
00648 {
00649         NPC = NULL;
00650         NPCInfo = NULL;
00651         client = NULL;
00652 }
00653 //===============
00654 
00655 extern  qboolean        showBBoxes;
00656 vec3_t NPCDEBUG_RED = {1.0, 0.0, 0.0};
00657 vec3_t NPCDEBUG_GREEN = {0.0, 1.0, 0.0};
00658 vec3_t NPCDEBUG_BLUE = {0.0, 0.0, 1.0};
00659 vec3_t NPCDEBUG_LIGHT_BLUE = {0.3f, 0.7f, 1.0};
00660 extern void G_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha );
00661 extern void G_Line( vec3_t start, vec3_t end, vec3_t color, float alpha );
00662 extern void G_Cylinder( vec3_t start, vec3_t end, float radius, vec3_t color );
00663 
00664 void NPC_ShowDebugInfo (void)
00665 {
00666         if ( showBBoxes )
00667         {
00668                 gentity_t       *found = NULL;
00669                 vec3_t          mins, maxs;
00670 
00671                 while( (found = G_Find( found, FOFS(classname), "NPC" ) ) != NULL )
00672                 {
00673                         if ( trap_InPVS( found->r.currentOrigin, g_entities[0].r.currentOrigin ) )
00674                         {
00675                                 VectorAdd( found->r.currentOrigin, found->r.mins, mins );
00676                                 VectorAdd( found->r.currentOrigin, found->r.maxs, maxs );
00677                                 G_Cube( mins, maxs, NPCDEBUG_RED, 0.25 );
00678                         }
00679                 }
00680         }
00681 }
00682 
00683 void NPC_ApplyScriptFlags (void)
00684 {
00685         if ( NPCInfo->scriptFlags & SCF_CROUCHED )
00686         {
00687                 if ( NPCInfo->charmedTime > level.time && (ucmd.forwardmove || ucmd.rightmove) )
00688                 {//ugh, if charmed and moving, ignore the crouched command
00689                 }
00690                 else
00691                 {
00692                         ucmd.upmove = -127;
00693                 }
00694         }
00695 
00696         if(NPCInfo->scriptFlags & SCF_RUNNING)
00697         {
00698                 ucmd.buttons &= ~BUTTON_WALKING;
00699         }
00700         else if(NPCInfo->scriptFlags & SCF_WALKING)
00701         {
00702                 if ( NPCInfo->charmedTime > level.time && (ucmd.forwardmove || ucmd.rightmove) )
00703                 {//ugh, if charmed and moving, ignore the walking command
00704                 }
00705                 else
00706                 {
00707                         ucmd.buttons |= BUTTON_WALKING;
00708                 }
00709         }
00710 /*
00711         if(NPCInfo->scriptFlags & SCF_CAREFUL)
00712         {
00713                 ucmd.buttons |= BUTTON_CAREFUL;
00714         }
00715 */
00716         if(NPCInfo->scriptFlags & SCF_LEAN_RIGHT)
00717         {
00718                 ucmd.buttons |= BUTTON_USE;
00719                 ucmd.rightmove = 127;
00720                 ucmd.forwardmove = 0;
00721                 ucmd.upmove = 0;
00722         }
00723         else if(NPCInfo->scriptFlags & SCF_LEAN_LEFT)
00724         {
00725                 ucmd.buttons |= BUTTON_USE;
00726                 ucmd.rightmove = -127;
00727                 ucmd.forwardmove = 0;
00728                 ucmd.upmove = 0;
00729         }
00730 
00731         if ( (NPCInfo->scriptFlags & SCF_ALT_FIRE) && (ucmd.buttons & BUTTON_ATTACK) )
00732         {//Use altfire instead
00733                 ucmd.buttons |= BUTTON_ALT_ATTACK;
00734         }
00735 }
00736 
00737 void Q3_DebugPrint( int level, const char *format, ... );
00738 void NPC_HandleAIFlags (void)
00739 {
00740         //FIXME: make these flags checks a function call like NPC_CheckAIFlagsAndTimers
00741         if ( NPCInfo->aiFlags & NPCAI_LOST )
00742         {//Print that you need help!
00743                 //FIXME: shouldn't remove this just yet if cg_draw needs it
00744                 NPCInfo->aiFlags &= ~NPCAI_LOST;
00745                 
00746                 /*
00747                 if ( showWaypoints )
00748                 {
00749                         Q3_DebugPrint(WL_WARNING, "%s can't navigate to target %s (my wp: %d, goal wp: %d)\n", NPC->targetname, NPCInfo->goalEntity->targetname, NPC->waypoint, NPCInfo->goalEntity->waypoint );
00750                 }
00751                 */
00752 
00753                 if ( NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy )
00754                 {//We can't nav to our enemy
00755                         //Drop enemy and see if we should search for him
00756                         NPC_LostEnemyDecideChase();
00757                 }
00758         }
00759 
00760         //MRJ Request:
00761         /*
00762         if ( NPCInfo->aiFlags & NPCAI_GREET_ALLIES && !NPC->enemy )//what if "enemy" is the greetEnt?
00763         {//If no enemy, look for teammates to greet
00764                 //FIXME: don't say hi to the same guy over and over again.
00765                 if ( NPCInfo->greetingDebounceTime < level.time )
00766                 {//Has been at least 2 seconds since we greeted last
00767                         if ( !NPCInfo->greetEnt )
00768                         {//Find a teammate whom I'm facing and who is facing me and within 128
00769                                 NPCInfo->greetEnt = NPC_PickAlly( qtrue, 128, qtrue, qtrue );
00770                         }
00771 
00772                         if ( NPCInfo->greetEnt && !Q_irand(0, 5) )
00773                         {//Start greeting someone
00774                                 qboolean        greeted = qfalse;
00775 
00776                                 //TODO:  If have a greetscript, run that instead?
00777 
00778                                 //FIXME: make them greet back?
00779                                 if( !Q_irand( 0, 2 ) )
00780                                 {//Play gesture anim (press gesture button?)
00781                                         greeted = qtrue;
00782                                         NPC_SetAnim( NPC, SETANIM_TORSO, Q_irand( BOTH_GESTURE1, BOTH_GESTURE3 ), SETANIM_FLAG_NORMAL|SETANIM_FLAG_HOLD );
00783                                         //NOTE: play full-body gesture if not moving?
00784                                 }
00785 
00786                                 if( !Q_irand( 0, 2 ) )
00787                                 {//Play random voice greeting sound
00788                                         greeted = qtrue;
00789                                         //FIXME: need NPC sound sets
00790 
00791                                         //G_AddVoiceEvent( NPC, Q_irand(EV_GREET1, EV_GREET3), 2000 );
00792                                 }
00793 
00794                                 if( !Q_irand( 0, 1 ) )
00795                                 {//set looktarget to them for a second or two
00796                                         greeted = qtrue;
00797                                         NPC_TempLookTarget( NPC, NPCInfo->greetEnt->s.number, 1000, 3000 );
00798                                 }
00799 
00800                                 if ( greeted )
00801                                 {//Did at least one of the things above
00802                                         //Don't greet again for 2 - 4 seconds
00803                                         NPCInfo->greetingDebounceTime = level.time + Q_irand( 2000, 4000 );
00804                                         NPCInfo->greetEnt = NULL;
00805                                 }
00806                         }
00807                 }
00808         }
00809         */
00810         //been told to play a victory sound after a delay
00811         if ( NPCInfo->greetingDebounceTime && NPCInfo->greetingDebounceTime < level.time )
00812         {
00813                 G_AddVoiceEvent( NPC, Q_irand(EV_VICTORY1, EV_VICTORY3), Q_irand( 2000, 4000 ) );
00814                 NPCInfo->greetingDebounceTime = 0;
00815         }
00816 
00817         if ( NPCInfo->ffireCount > 0 )
00818         {
00819                 if ( NPCInfo->ffireFadeDebounce < level.time )
00820                 {
00821                         NPCInfo->ffireCount--;
00822                         //Com_Printf( "drop: %d < %d\n", NPCInfo->ffireCount, 3+((2-g_spskill.integer)*2) );
00823                         NPCInfo->ffireFadeDebounce = level.time + 3000;
00824                 }
00825         }
00826         if ( d_patched.integer )
00827         {//use patch-style navigation
00828                 if ( NPCInfo->consecutiveBlockedMoves > 20 )
00829                 {//been stuck for a while, try again?
00830                         NPCInfo->consecutiveBlockedMoves = 0;
00831                 }
00832         }
00833 }
00834 
00835 void NPC_AvoidWallsAndCliffs (void)
00836 {
00837         //...
00838 }
00839 
00840 void NPC_CheckAttackScript(void)
00841 {
00842         if(!(ucmd.buttons & BUTTON_ATTACK))
00843         {
00844                 return;
00845         }
00846 
00847         G_ActivateBehavior(NPC, BSET_ATTACK);
00848 }
00849 
00850 float NPC_MaxDistSquaredForWeapon (void);
00851 void NPC_CheckAttackHold(void)
00852 {
00853         vec3_t          vec;
00854 
00855         // If they don't have an enemy they shouldn't hold their attack anim.
00856         if ( !NPC->enemy )
00857         {
00858                 NPCInfo->attackHoldTime = 0;
00859                 return;
00860         }
00861 
00862 /*      if ( ( NPC->client->ps.weapon == WP_BORG_ASSIMILATOR ) || ( NPC->client->ps.weapon == WP_BORG_DRILL ) )
00863         {//FIXME: don't keep holding this if can't hit enemy?
00864 
00865                 // If they don't have shields ( been disabled) they shouldn't hold their attack anim.
00866                 if ( !(NPC->NPC->aiFlags & NPCAI_SHIELDS) )
00867                 {
00868                         NPCInfo->attackHoldTime = 0;
00869                         return;
00870                 }
00871 
00872                 VectorSubtract(NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, vec);
00873                 if( VectorLengthSquared(vec) > NPC_MaxDistSquaredForWeapon() )
00874                 {
00875                         NPCInfo->attackHoldTime = 0;
00876                         PM_SetTorsoAnimTimer(NPC, &NPC->client->ps.torsoAnimTimer, 0);
00877                 }
00878                 else if( NPCInfo->attackHoldTime && NPCInfo->attackHoldTime > level.time )
00879                 {
00880                         ucmd.buttons |= BUTTON_ATTACK;
00881                 }
00882                 else if ( ( NPCInfo->attackHold ) && ( ucmd.buttons & BUTTON_ATTACK ) )
00883                 {
00884                         NPCInfo->attackHoldTime = level.time + NPCInfo->attackHold;
00885                         PM_SetTorsoAnimTimer(NPC, &NPC->client->ps.torsoAnimTimer, NPCInfo->attackHold);
00886                 }
00887                 else
00888                 {
00889                         NPCInfo->attackHoldTime = 0;
00890                         PM_SetTorsoAnimTimer(NPC, &NPC->client->ps.torsoAnimTimer, 0);
00891                 }
00892         }
00893         else*/
00894         {//everyone else...?  FIXME: need to tie this into AI somehow?
00895                 VectorSubtract(NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, vec);
00896                 if( VectorLengthSquared(vec) > NPC_MaxDistSquaredForWeapon() )
00897                 {
00898                         NPCInfo->attackHoldTime = 0;
00899                 }
00900                 else if( NPCInfo->attackHoldTime && NPCInfo->attackHoldTime > level.time )
00901                 {
00902                         ucmd.buttons |= BUTTON_ATTACK;
00903                 }
00904                 else if ( ( NPCInfo->attackHold ) && ( ucmd.buttons & BUTTON_ATTACK ) )
00905                 {
00906                         NPCInfo->attackHoldTime = level.time + NPCInfo->attackHold;
00907                 }
00908                 else
00909                 {
00910                         NPCInfo->attackHoldTime = 0;
00911                 }
00912         }
00913 }
00914 
00915 /*
00916 void NPC_KeepCurrentFacing(void)
00917 
00918 Fills in a default ucmd to keep current angles facing
00919 */
00920 void NPC_KeepCurrentFacing(void)
00921 {
00922         if(!ucmd.angles[YAW])
00923         {
00924                 ucmd.angles[YAW] = ANGLE2SHORT( client->ps.viewangles[YAW] ) - client->ps.delta_angles[YAW];
00925         }
00926 
00927         if(!ucmd.angles[PITCH])
00928         {
00929                 ucmd.angles[PITCH] = ANGLE2SHORT( client->ps.viewangles[PITCH] ) - client->ps.delta_angles[PITCH];
00930         }
00931 }
00932 
00933 /*
00934 -------------------------
00935 NPC_BehaviorSet_Charmed
00936 -------------------------
00937 */
00938 
00939 void NPC_BehaviorSet_Charmed( int bState )
00940 {
00941         switch( bState )
00942         {
00943         case BS_FOLLOW_LEADER://# 40: Follow your leader and shoot any enemies you come across
00944                 NPC_BSFollowLeader();
00945                 break;
00946         case BS_REMOVE:
00947                 NPC_BSRemove();
00948                 break;
00949         case BS_SEARCH:                 //# 43: Using current waypoint as a base, search the immediate branches of waypoints for enemies
00950                 NPC_BSSearch();
00951                 break;
00952         case BS_WANDER:                 //# 46: Wander down random waypoint paths
00953                 NPC_BSWander();
00954                 break;
00955         case BS_FLEE:
00956                 NPC_BSFlee();
00957                 break;
00958         default:
00959         case BS_DEFAULT://whatever
00960                 NPC_BSDefault();
00961                 break;
00962         }
00963 }
00964 /*
00965 -------------------------
00966 NPC_BehaviorSet_Default
00967 -------------------------
00968 */
00969 
00970 void NPC_BehaviorSet_Default( int bState )
00971 {
00972         switch( bState )
00973         {
00974         case BS_ADVANCE_FIGHT://head toward captureGoal, shoot anything that gets in the way
00975                 NPC_BSAdvanceFight ();
00976                 break;
00977         case BS_SLEEP://Follow a path, looking for enemies
00978                 NPC_BSSleep ();
00979                 break;
00980         case BS_FOLLOW_LEADER://# 40: Follow your leader and shoot any enemies you come across
00981                 NPC_BSFollowLeader();
00982                 break;
00983         case BS_JUMP:                   //41: Face navgoal and jump to it.
00984                 NPC_BSJump();
00985                 break;
00986         case BS_REMOVE:
00987                 NPC_BSRemove();
00988                 break;
00989         case BS_SEARCH:                 //# 43: Using current waypoint as a base, search the immediate branches of waypoints for enemies
00990                 NPC_BSSearch();
00991                 break;
00992         case BS_NOCLIP:
00993                 NPC_BSNoClip();
00994                 break;
00995         case BS_WANDER:                 //# 46: Wander down random waypoint paths
00996                 NPC_BSWander();
00997                 break;
00998         case BS_FLEE:
00999                 NPC_BSFlee();
01000                 break;
01001         case BS_WAIT:
01002                 NPC_BSWait();
01003                 break;
01004         case BS_CINEMATIC:
01005                 NPC_BSCinematic();
01006                 break;
01007         default:
01008         case BS_DEFAULT://whatever
01009                 NPC_BSDefault();
01010                 break;
01011         }
01012 }
01013 
01014 /*
01015 -------------------------
01016 NPC_BehaviorSet_Interrogator
01017 -------------------------
01018 */
01019 void NPC_BehaviorSet_Interrogator( int bState )
01020 {
01021         switch( bState )
01022         {
01023         case BS_STAND_GUARD:
01024         case BS_PATROL:
01025         case BS_STAND_AND_SHOOT:
01026         case BS_HUNT_AND_KILL:
01027         case BS_DEFAULT:
01028                 NPC_BSInterrogator_Default();
01029                 break;
01030         default:
01031                 NPC_BehaviorSet_Default( bState );
01032                 break;
01033         }
01034 }
01035 
01036 void NPC_BSImperialProbe_Attack( void );
01037 void NPC_BSImperialProbe_Patrol( void );
01038 void NPC_BSImperialProbe_Wait(void);
01039 
01040 /*
01041 -------------------------
01042 NPC_BehaviorSet_ImperialProbe
01043 -------------------------
01044 */
01045 void NPC_BehaviorSet_ImperialProbe( int bState )
01046 {
01047         switch( bState )
01048         {
01049         case BS_STAND_GUARD:
01050         case BS_PATROL:
01051         case BS_STAND_AND_SHOOT:
01052         case BS_HUNT_AND_KILL:
01053         case BS_DEFAULT:
01054                 NPC_BSImperialProbe_Default();
01055                 break;
01056         default:
01057                 NPC_BehaviorSet_Default( bState );
01058                 break;
01059         }
01060 }
01061 
01062 
01063 void NPC_BSSeeker_Default( void );
01064 
01065 /*
01066 -------------------------
01067 NPC_BehaviorSet_Seeker
01068 -------------------------
01069 */
01070 void NPC_BehaviorSet_Seeker( int bState )
01071 {
01072         switch( bState )
01073         {
01074         case BS_STAND_GUARD:
01075         case BS_PATROL:
01076         case BS_STAND_AND_SHOOT:
01077         case BS_HUNT_AND_KILL:
01078         case BS_DEFAULT:
01079                 NPC_BSSeeker_Default();
01080                 break; 
01081         default:
01082                 NPC_BehaviorSet_Default( bState );
01083                 break;
01084         }
01085 }
01086 
01087 void NPC_BSRemote_Default( void );
01088 
01089 /*
01090 -------------------------
01091 NPC_BehaviorSet_Remote
01092 -------------------------
01093 */
01094 void NPC_BehaviorSet_Remote( int bState )
01095 {
01096         NPC_BSRemote_Default();
01097 }
01098 
01099 void NPC_BSSentry_Default( void );
01100 
01101 /*
01102 -------------------------
01103 NPC_BehaviorSet_Sentry
01104 -------------------------
01105 */
01106 void NPC_BehaviorSet_Sentry( int bState )
01107 {
01108         switch( bState )
01109         {
01110         case BS_STAND_GUARD:
01111         case BS_PATROL:
01112         case BS_STAND_AND_SHOOT:
01113         case BS_HUNT_AND_KILL:
01114         case BS_DEFAULT:
01115                 NPC_BSSentry_Default();
01116                 break; 
01117         default:
01118                 NPC_BehaviorSet_Default( bState );
01119                 break;
01120         }
01121 }
01122 
01123 /*
01124 -------------------------
01125 NPC_BehaviorSet_Grenadier
01126 -------------------------
01127 */
01128 void NPC_BehaviorSet_Grenadier( int bState )
01129 {
01130         switch( bState )
01131         {
01132         case BS_STAND_GUARD:
01133         case BS_PATROL:
01134         case BS_STAND_AND_SHOOT:
01135         case BS_HUNT_AND_KILL:
01136         case BS_DEFAULT:
01137                 NPC_BSGrenadier_Default();
01138                 break;
01139 
01140         default:
01141                 NPC_BehaviorSet_Default( bState );
01142                 break;
01143         }
01144 }
01145 /*
01146 -------------------------
01147 NPC_BehaviorSet_Sniper
01148 -------------------------
01149 */
01150 void NPC_BehaviorSet_Sniper( int bState )
01151 {
01152         switch( bState )
01153         {
01154         case BS_STAND_GUARD:
01155         case BS_PATROL:
01156         case BS_STAND_AND_SHOOT:
01157         case BS_HUNT_AND_KILL:
01158         case BS_DEFAULT:
01159                 NPC_BSSniper_Default();
01160                 break;
01161 
01162         default:
01163                 NPC_BehaviorSet_Default( bState );
01164                 break;
01165         }
01166 }
01167 /*
01168 -------------------------
01169 NPC_BehaviorSet_Stormtrooper
01170 -------------------------
01171 */
01172 
01173 void NPC_BehaviorSet_Stormtrooper( int bState )
01174 {
01175         switch( bState )
01176         {
01177         case BS_STAND_GUARD:
01178         case BS_PATROL:
01179         case BS_STAND_AND_SHOOT:
01180         case BS_HUNT_AND_KILL:
01181         case BS_DEFAULT:
01182                 NPC_BSST_Default();
01183                 break;
01184 
01185         case BS_INVESTIGATE:
01186                 NPC_BSST_Investigate();
01187                 break;
01188 
01189         case BS_SLEEP:
01190                 NPC_BSST_Sleep();
01191                 break;
01192 
01193         default:
01194                 NPC_BehaviorSet_Default( bState );
01195                 break;
01196         }
01197 }
01198 
01199 /*
01200 -------------------------
01201 NPC_BehaviorSet_Jedi
01202 -------------------------
01203 */
01204 
01205 void NPC_BehaviorSet_Jedi( int bState )
01206 {
01207         switch( bState )
01208         {
01209         case BS_STAND_GUARD:
01210         case BS_PATROL:
01211         case BS_STAND_AND_SHOOT:
01212         case BS_HUNT_AND_KILL:
01213         case BS_DEFAULT:
01214                 NPC_BSJedi_Default();
01215                 break;
01216 
01217         case BS_FOLLOW_LEADER:
01218                 NPC_BSJedi_FollowLeader();
01219                 break;
01220 
01221         default:
01222                 NPC_BehaviorSet_Default( bState );
01223                 break;
01224         }
01225 }
01226 
01227 /*
01228 -------------------------
01229 NPC_BehaviorSet_Droid
01230 -------------------------
01231 */
01232 void NPC_BehaviorSet_Droid( int bState )
01233 {
01234         switch( bState )
01235         {
01236         case BS_DEFAULT:
01237         case BS_STAND_GUARD:
01238         case BS_PATROL:
01239                 NPC_BSDroid_Default();
01240                 break;
01241         default:
01242                 NPC_BehaviorSet_Default( bState );
01243                 break;
01244         }
01245 }
01246 
01247 /*
01248 -------------------------
01249 NPC_BehaviorSet_Mark1
01250 -------------------------
01251 */
01252 void NPC_BehaviorSet_Mark1( int bState )
01253 {
01254         switch( bState )
01255         {
01256         case BS_DEFAULT:
01257         case BS_STAND_GUARD:
01258         case BS_PATROL:
01259                 NPC_BSMark1_Default();
01260                 break;
01261         default:
01262                 NPC_BehaviorSet_Default( bState );
01263                 break;
01264         }
01265 }
01266 
01267 /*
01268 -------------------------
01269 NPC_BehaviorSet_Mark2
01270 -------------------------
01271 */
01272 void NPC_BehaviorSet_Mark2( int bState )
01273 {
01274         switch( bState )
01275         {
01276         case BS_DEFAULT:
01277         case BS_PATROL:
01278         case BS_STAND_AND_SHOOT:
01279         case BS_HUNT_AND_KILL:
01280                 NPC_BSMark2_Default();
01281                 break;
01282         default:
01283                 NPC_BehaviorSet_Default( bState );
01284                 break;
01285         }
01286 }
01287 
01288 /*
01289 -------------------------
01290 NPC_BehaviorSet_ATST
01291 -------------------------
01292 */
01293 void NPC_BehaviorSet_ATST( int bState )
01294 {
01295         switch( bState )
01296         {
01297         case BS_DEFAULT:
01298         case BS_PATROL:
01299         case BS_STAND_AND_SHOOT:
01300         case BS_HUNT_AND_KILL:
01301                 NPC_BSATST_Default();
01302                 break;
01303         default:
01304                 NPC_BehaviorSet_Default( bState );
01305                 break;
01306         }
01307 }
01308 
01309 /*
01310 -------------------------
01311 NPC_BehaviorSet_MineMonster
01312 -------------------------
01313 */
01314 void NPC_BehaviorSet_MineMonster( int bState )
01315 {
01316         switch( bState )
01317         {
01318         case BS_STAND_GUARD:
01319         case BS_PATROL:
01320         case BS_STAND_AND_SHOOT:
01321         case BS_HUNT_AND_KILL:
01322         case BS_DEFAULT:
01323                 NPC_BSMineMonster_Default();
01324                 break;
01325         default:
01326                 NPC_BehaviorSet_Default( bState );
01327                 break;
01328         }
01329 }
01330 
01331 /*
01332 -------------------------
01333 NPC_BehaviorSet_Howler
01334 -------------------------
01335 */
01336 void NPC_BehaviorSet_Howler( int bState )
01337 {
01338         switch( bState )
01339         {
01340         case BS_STAND_GUARD:
01341         case BS_PATROL:
01342         case BS_STAND_AND_SHOOT:
01343         case BS_HUNT_AND_KILL:
01344         case BS_DEFAULT:
01345                 NPC_BSHowler_Default();
01346                 break;
01347         default:
01348                 NPC_BehaviorSet_Default( bState );
01349                 break;
01350         }
01351 }
01352 
01353 /*
01354 -------------------------
01355 NPC_BehaviorSet_Rancor
01356 -------------------------
01357 */
01358 void NPC_BehaviorSet_Rancor( int bState )
01359 {
01360         switch( bState )
01361         {
01362         case BS_STAND_GUARD:
01363         case BS_PATROL:
01364         case BS_STAND_AND_SHOOT:
01365         case BS_HUNT_AND_KILL:
01366         case BS_DEFAULT:
01367                 NPC_BSRancor_Default();
01368                 break;
01369         default:
01370                 NPC_BehaviorSet_Default( bState );
01371                 break;
01372         }
01373 }
01374 
01375 /*
01376 -------------------------
01377 NPC_RunBehavior
01378 -------------------------
01379 */
01380 extern void NPC_BSEmplaced( void );
01381 extern qboolean NPC_CheckSurrender( void );
01382 extern void Boba_FlyStop( gentity_t *self );
01383 extern void NPC_BSWampa_Default( void );
01384 void NPC_RunBehavior( int team, int bState )
01385 {
01386         qboolean dontSetAim = qfalse;
01387 
01388         if (NPC->s.NPC_class == CLASS_VEHICLE &&
01389                 NPC->m_pVehicle)
01390         { //vehicles don't do AI!
01391                 return;
01392         }
01393 
01394         if ( bState == BS_CINEMATIC )
01395         {
01396                 NPC_BSCinematic();
01397         }
01398         else if ( NPC->client->ps.weapon == WP_EMPLACED_GUN )
01399         {
01400                 NPC_BSEmplaced();
01401                 NPC_CheckCharmed();
01402                 return;
01403         }
01404         else if ( NPC->client->ps.weapon == WP_SABER )
01405         {//jedi
01406                 NPC_BehaviorSet_Jedi( bState );
01407                 dontSetAim = qtrue;
01408         }
01409         else if ( NPC->client->NPC_class == CLASS_WAMPA )
01410         {//wampa
01411                 NPC_BSWampa_Default();
01412         }
01413         else if ( NPC->client->NPC_class == CLASS_RANCOR )
01414         {//rancor
01415                 NPC_BehaviorSet_Rancor( bState );
01416         }
01417         else if ( NPC->client->NPC_class == CLASS_REMOTE )
01418         {
01419                 NPC_BehaviorSet_Remote( bState );
01420         }
01421         else if ( NPC->client->NPC_class == CLASS_SEEKER )
01422         {
01423                 NPC_BehaviorSet_Seeker( bState );
01424         }
01425         else if ( NPC->client->NPC_class == CLASS_BOBAFETT )
01426         {//bounty hunter
01427                 if ( Boba_Flying( NPC ) )
01428                 {
01429                         NPC_BehaviorSet_Seeker(bState);
01430                 }
01431                 else
01432                 {
01433                         NPC_BehaviorSet_Jedi( bState );
01434                 }
01435                 dontSetAim = qtrue;
01436         }
01437         else if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH )
01438         {//being forced to march
01439                 NPC_BSDefault();
01440         }
01441         else
01442         {
01443                 switch( team )
01444                 {
01445                 
01446         //      case NPCTEAM_SCAVENGERS:
01447         //      case NPCTEAM_IMPERIAL:
01448         //      case NPCTEAM_KLINGON:
01449         //      case NPCTEAM_HIROGEN:
01450         //      case NPCTEAM_MALON:
01451                 // not sure if TEAM_ENEMY is appropriate here, I think I should be using NPC_class to check for behavior - dmv
01452                 case NPCTEAM_ENEMY:
01453                         // special cases for enemy droids
01454                         switch( NPC->client->NPC_class)
01455                         {
01456                         case CLASS_ATST:
01457                                 NPC_BehaviorSet_ATST( bState );
01458                                 return;
01459                         case CLASS_PROBE:
01460                                 NPC_BehaviorSet_ImperialProbe(bState);
01461                                 return;
01462                         case CLASS_REMOTE:
01463                                 NPC_BehaviorSet_Remote( bState );
01464                                 return;
01465                         case CLASS_SENTRY:
01466                                 NPC_BehaviorSet_Sentry(bState);
01467                                 return;
01468                         case CLASS_INTERROGATOR:
01469                                 NPC_BehaviorSet_Interrogator( bState );
01470                                 return;
01471                         case CLASS_MINEMONSTER:
01472                                 NPC_BehaviorSet_MineMonster( bState );
01473                                 return;
01474                         case CLASS_HOWLER:
01475                                 NPC_BehaviorSet_Howler( bState );
01476                                 return;
01477                         case CLASS_MARK1:
01478                                 NPC_BehaviorSet_Mark1( bState );
01479                                 return;
01480                         case CLASS_MARK2:
01481                                 NPC_BehaviorSet_Mark2( bState );
01482                                 return;
01483                         case CLASS_GALAKMECH:
01484                                 NPC_BSGM_Default();
01485                                 return;
01486 
01487                         }
01488 
01489                         if ( NPC->enemy && NPC->s.weapon == WP_NONE && bState != BS_HUNT_AND_KILL && !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
01490                         {//if in battle and have no weapon, run away, fixme: when in BS_HUNT_AND_KILL, they just stand there
01491                                 if ( bState != BS_FLEE )
01492                                 {
01493                                         NPC_StartFlee( NPC->enemy, NPC->enemy->r.currentOrigin, AEL_DANGER_GREAT, 5000, 10000 );
01494                                 }
01495                                 else
01496                                 {
01497                                         NPC_BSFlee();
01498                                 }
01499                                 return;
01500                         }
01501                         if ( NPC->client->ps.weapon == WP_SABER )
01502                         {//special melee exception
01503                                 NPC_BehaviorSet_Default( bState );
01504                                 return;
01505                         }
01506                         if ( NPC->client->ps.weapon == WP_DISRUPTOR && (NPCInfo->scriptFlags & SCF_ALT_FIRE) )
01507                         {//a sniper
01508                                 NPC_BehaviorSet_Sniper( bState );
01509                                 return;
01510                         }
01511                         if ( NPC->client->ps.weapon == WP_THERMAL || NPC->client->ps.weapon == WP_STUN_BATON )//FIXME: separate AI for melee fighters
01512                         {//a grenadier
01513                                 NPC_BehaviorSet_Grenadier( bState );
01514                                 return;
01515                         }
01516                         if ( NPC_CheckSurrender() )
01517                         {
01518                                 return;
01519                         }
01520                         NPC_BehaviorSet_Stormtrooper( bState );
01521                         break;
01522 
01523                 case NPCTEAM_NEUTRAL: 
01524 
01525                         // special cases for enemy droids
01526                         if ( NPC->client->NPC_class == CLASS_PROTOCOL || NPC->client->NPC_class == CLASS_UGNAUGHT ||
01527                                 NPC->client->NPC_class == CLASS_JAWA)
01528                         {
01529                                 NPC_BehaviorSet_Default(bState);
01530                         }
01531                         else if ( NPC->client->NPC_class == CLASS_VEHICLE )
01532                         {
01533                                 // TODO: Add vehicle behaviors here.
01534                                 NPC_UpdateAngles( qtrue, qtrue );//just face our spawn angles for now
01535                         }
01536                         else
01537                         {
01538                                 // Just one of the average droids
01539                                 NPC_BehaviorSet_Droid( bState );
01540                         }
01541                         break;
01542 
01543                 default:
01544                         if ( NPC->client->NPC_class == CLASS_SEEKER )
01545                         {
01546                                 NPC_BehaviorSet_Seeker(bState);
01547                         }
01548                         else
01549                         {
01550                                 if ( NPCInfo->charmedTime > level.time )
01551                                 {
01552                                         NPC_BehaviorSet_Charmed( bState );
01553                                 }
01554                                 else
01555                                 {
01556                                         NPC_BehaviorSet_Default( bState );
01557                                 }
01558                                 NPC_CheckCharmed();
01559                                 dontSetAim = qtrue;
01560                         }
01561                         break;
01562                 }
01563         }
01564 }
01565 
01566 /*
01567 ===============
01568 NPC_ExecuteBState
01569 
01570   MCG
01571 
01572 NPC Behavior state thinking
01573 
01574 ===============
01575 */
01576 void NPC_ExecuteBState ( gentity_t *self)//, int msec ) 
01577 {
01578         bState_t        bState;
01579 
01580         NPC_HandleAIFlags();
01581 
01582         //FIXME: these next three bits could be a function call, some sort of setup/cleanup func
01583         //Lookmode must be reset every think cycle
01584         if(NPC->delayScriptTime && NPC->delayScriptTime <= level.time)
01585         {
01586                 G_ActivateBehavior( NPC, BSET_DELAYED);
01587                 NPC->delayScriptTime = 0;
01588         }
01589 
01590         //Clear this and let bState set it itself, so it automatically handles changing bStates... but we need a set bState wrapper func
01591         NPCInfo->combatMove = qfalse;
01592 
01593         //Execute our bState
01594         if(NPCInfo->tempBehavior)
01595         {//Overrides normal behavior until cleared
01596                 bState = NPCInfo->tempBehavior;
01597         }
01598         else
01599         {
01600                 if(!NPCInfo->behaviorState)
01601                         NPCInfo->behaviorState = NPCInfo->defaultBehavior;
01602 
01603                 bState = NPCInfo->behaviorState;
01604         }
01605 
01606         //Pick the proper bstate for us and run it
01607         NPC_RunBehavior( self->client->playerTeam, bState );
01608         
01609 
01610 //      if(bState != BS_POINT_COMBAT && NPCInfo->combatPoint != -1)
01611 //      {
01612                 //level.combatPoints[NPCInfo->combatPoint].occupied = qfalse;
01613                 //NPCInfo->combatPoint = -1;
01614 //      }
01615 
01616         //Here we need to see what the scripted stuff told us to do
01617 //Only process snapshot if independant and in combat mode- this would pick enemies and go after needed items
01618 //      ProcessSnapshot();
01619 
01620 //Ignore my needs if I'm under script control- this would set needs for items
01621 //      CheckSelf();
01622 
01623         //Back to normal?  All decisions made?
01624         
01625         //FIXME: don't walk off ledges unless we can get to our goal faster that way, or that's our goal's surface
01626         //NPCPredict();
01627 
01628         if ( NPC->enemy )
01629         {
01630                 if ( !NPC->enemy->inuse )
01631                 {//just in case bState doesn't catch this
01632                         G_ClearEnemy( NPC );
01633                 }
01634         }
01635 
01636         if ( NPC->client->ps.saberLockTime && NPC->client->ps.saberLockEnemy != ENTITYNUM_NONE )
01637         {
01638                 NPC_SetLookTarget( NPC, NPC->client->ps.saberLockEnemy, level.time+1000 );
01639         }
01640         else if ( !NPC_CheckLookTarget( NPC ) )
01641         {
01642                 if ( NPC->enemy )
01643                 {
01644                         NPC_SetLookTarget( NPC, NPC->enemy->s.number, 0 );
01645                 }
01646         }
01647 
01648         if ( NPC->enemy )
01649         {
01650                 if(NPC->enemy->flags & FL_DONT_SHOOT)
01651                 {
01652                         ucmd.buttons &= ~BUTTON_ATTACK;
01653                         ucmd.buttons &= ~BUTTON_ALT_ATTACK;
01654                 }
01655                 else if ( NPC->client->playerTeam != NPCTEAM_ENEMY && NPC->enemy->NPC && (NPC->enemy->NPC->surrenderTime > level.time || (NPC->enemy->NPC->scriptFlags&SCF_FORCED_MARCH)) )
01656                 {//don't shoot someone who's surrendering if you're a good guy
01657                         ucmd.buttons &= ~BUTTON_ATTACK;
01658                         ucmd.buttons &= ~BUTTON_ALT_ATTACK;
01659                 }
01660 
01661                 if(client->ps.weaponstate == WEAPON_IDLE)
01662                 {
01663                         client->ps.weaponstate = WEAPON_READY;
01664                 }
01665         }
01666         else 
01667         {
01668                 if(client->ps.weaponstate == WEAPON_READY)
01669                 {
01670                         client->ps.weaponstate = WEAPON_IDLE;
01671                 }
01672         }
01673 
01674         if(!(ucmd.buttons & BUTTON_ATTACK) && NPC->attackDebounceTime > level.time)
01675         {//We just shot but aren't still shooting, so hold the gun up for a while
01676                 if(client->ps.weapon == WP_SABER )
01677                 {//One-handed
01678                         NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY1,SETANIM_FLAG_NORMAL);
01679                 }
01680                 else if(client->ps.weapon == WP_BRYAR_PISTOL)
01681                 {//Sniper pose
01682                         NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL);
01683                 }
01684                 /*//FIXME: What's the proper solution here?
01685                 else
01686                 {//heavy weapon
01687                         NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL);
01688                 }
01689                 */
01690         }
01691         else if ( !NPC->enemy )//HACK!
01692         {
01693 //              if(client->ps.weapon != WP_TRICORDER)
01694                 {
01695                         if( NPC->s.torsoAnim == TORSO_WEAPONREADY1 || NPC->s.torsoAnim == TORSO_WEAPONREADY3 )
01696                         {//we look ready for action, using one of the first 2 weapon, let's rest our weapon on our shoulder
01697                                 NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONIDLE3,SETANIM_FLAG_NORMAL);
01698                         }
01699                 }
01700         }
01701 
01702         NPC_CheckAttackHold();
01703         NPC_ApplyScriptFlags();
01704         
01705         //cliff and wall avoidance
01706         NPC_AvoidWallsAndCliffs();
01707 
01708         // run the bot through the server like it was a real client
01709 //=== Save the ucmd for the second no-think Pmove ============================
01710         ucmd.serverTime = level.time - 50;
01711         memcpy( &NPCInfo->last_ucmd, &ucmd, sizeof( usercmd_t ) );
01712         if ( !NPCInfo->attackHoldTime )
01713         {
01714                 NPCInfo->last_ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK);//so we don't fire twice in one think
01715         }
01716 //============================================================================
01717         NPC_CheckAttackScript();
01718         NPC_KeepCurrentFacing();
01719 
01720         if ( !NPC->next_roff_time || NPC->next_roff_time < level.time )
01721         {//If we were following a roff, we don't do normal pmoves.
01722                 ClientThink( NPC->s.number, &ucmd );
01723         }
01724         else
01725         {
01726                 NPC_ApplyRoff();
01727         }
01728 
01729         // end of thinking cleanup
01730         NPCInfo->touchedByPlayer = NULL;
01731 
01732         NPC_CheckPlayerAim();
01733         NPC_CheckAllClear();
01734         
01735         /*if( ucmd.forwardmove || ucmd.rightmove )
01736         {
01737                 int     i, la = -1, ta = -1;
01738 
01739                 for(i = 0; i < MAX_ANIMATIONS; i++)
01740                 {
01741                         if( NPC->client->ps.legsAnim == i )
01742                         {
01743                                 la = i;
01744                         }
01745 
01746                         if( NPC->client->ps.torsoAnim == i )
01747                         {
01748                                 ta = i;
01749                         }
01750                         
01751                         if(la != -1 && ta != -1)
01752                         {
01753                                 break;
01754                         }
01755                 }
01756 
01757                 if(la != -1 && ta != -1)
01758                 {//FIXME: should never play same frame twice or restart an anim before finishing it
01759                         Com_Printf("LegsAnim: %s(%d) TorsoAnim: %s(%d)\n", animTable[la].name, NPC->renderInfo.legsFrame, animTable[ta].name, NPC->client->renderInfo.torsoFrame);
01760                 }
01761         }*/
01762 }
01763 
01764 void NPC_CheckInSolid(void)
01765 {
01766         trace_t trace;
01767         vec3_t  point;
01768         VectorCopy(NPC->r.currentOrigin, point);
01769         point[2] -= 0.25;
01770 
01771         trap_Trace(&trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, point, NPC->s.number, NPC->clipmask);
01772         if(!trace.startsolid && !trace.allsolid)
01773         {
01774                 VectorCopy(NPC->r.currentOrigin, NPCInfo->lastClearOrigin);
01775         }
01776         else
01777         {
01778                 if(VectorLengthSquared(NPCInfo->lastClearOrigin))
01779                 {
01780 //                      Com_Printf("%s stuck in solid at %s: fixing...\n", NPC->script_targetname, vtos(NPC->r.currentOrigin));
01781                         G_SetOrigin(NPC, NPCInfo->lastClearOrigin);
01782                         trap_LinkEntity(NPC);
01783                 }
01784         }
01785 }
01786 
01787 void G_DroidSounds( gentity_t *self )
01788 {
01789         if ( self->client )
01790         {//make the noises
01791                 if ( TIMER_Done( self, "patrolNoise" ) && !Q_irand( 0, 20 ) )
01792                 {
01793                         switch( self->client->NPC_class )
01794                         {
01795                         case CLASS_R2D2:                                // droid
01796                                 G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/r2d2/misc/r2d2talk0%d.wav",Q_irand(1, 3)) );
01797                                 break;
01798                         case CLASS_R5D2:                                // droid
01799                                 G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/r5d2/misc/r5talk%d.wav",Q_irand(1, 4)) );
01800                                 break;
01801                         case CLASS_PROBE:                               // droid
01802                                 G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d.wav",Q_irand(1, 3)) );
01803                                 break;
01804                         case CLASS_MOUSE:                               // droid
01805                                 G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/mouse/misc/mousego%d.wav",Q_irand(1, 3)) );
01806                                 break;
01807                         case CLASS_GONK:                                // droid
01808                                 G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/gonk/misc/gonktalk%d.wav",Q_irand(1, 2)) );
01809                                 break;
01810                         }
01811                         TIMER_Set( self, "patrolNoise", Q_irand( 2000, 4000 ) );
01812                 }
01813         }
01814 }
01815 
01816 /*
01817 ===============
01818 NPC_Think
01819 
01820 Main NPC AI - called once per frame
01821 ===============
01822 */
01823 #if     AI_TIMERS
01824 extern int AITime;
01825 #endif//        AI_TIMERS
01826 void NPC_Think ( gentity_t *self)//, int msec ) 
01827 {
01828         vec3_t  oldMoveDir;
01829         int i = 0;
01830         gentity_t *player;
01831 
01832         self->nextthink = level.time + FRAMETIME;
01833 
01834         SetNPCGlobals( self );
01835 
01836         memset( &ucmd, 0, sizeof( ucmd ) );
01837 
01838         VectorCopy( self->client->ps.moveDir, oldMoveDir );
01839         if (self->s.NPC_class != CLASS_VEHICLE)
01840         { //YOU ARE BREAKING MY PREDICTION. Bad clear.
01841                 VectorClear( self->client->ps.moveDir );
01842         }
01843 
01844         if(!self || !self->NPC || !self->client)
01845         {
01846                 return;
01847         }
01848 
01849         // dead NPCs have a special think, don't run scripts (for now)
01850         //FIXME: this breaks deathscripts
01851         if ( self->health <= 0 ) 
01852         {
01853                 DeadThink();
01854                 if ( NPCInfo->nextBStateThink <= level.time )
01855                 {
01856                         trap_ICARUS_MaintainTaskManager(self->s.number);
01857                 }
01858                 VectorCopy(self->r.currentOrigin, self->client->ps.origin);
01859                 return;
01860         }
01861 
01862         // see if NPC ai is frozen
01863         if ( debugNPCFreeze.value || (NPC->r.svFlags&SVF_ICARUS_FREEZE) ) 
01864         {
01865                 NPC_UpdateAngles( qtrue, qtrue );
01866                 ClientThink(self->s.number, &ucmd);
01867                 //VectorCopy(self->s.origin, self->s.origin2 );
01868                 VectorCopy(self->r.currentOrigin, self->client->ps.origin);
01869                 return;
01870         }
01871 
01872         self->nextthink = level.time + FRAMETIME/2;
01873 
01874 
01875         while (i < MAX_CLIENTS)
01876         {
01877                 player = &g_entities[i];
01878 
01879                 if (player->inuse && player->client && player->client->sess.sessionTeam != TEAM_SPECTATOR &&
01880                         !(player->client->ps.pm_flags & PMF_FOLLOW))
01881                 {
01882                         //if ( player->client->ps.viewEntity == self->s.number )
01883                         if (0) //rwwFIXMEFIXME: Allow controlling ents
01884                         {//being controlled by player
01885                                 G_DroidSounds( self );
01886                                 //FIXME: might want to at least make sounds or something?
01887                                 //NPC_UpdateAngles(qtrue, qtrue);
01888                                 //Which ucmd should we send?  Does it matter, since it gets overridden anyway?
01889                                 NPCInfo->last_ucmd.serverTime = level.time - 50;
01890                                 ClientThink( NPC->s.number, &ucmd );
01891                                 //VectorCopy(self->s.origin, self->s.origin2 );
01892                                 VectorCopy(self->r.currentOrigin, self->client->ps.origin);
01893                                 return;
01894                         }
01895                 }
01896                 i++;
01897         }
01898 
01899         if ( self->client->NPC_class == CLASS_VEHICLE)
01900         {
01901                 if (self->client->ps.m_iVehicleNum)
01902                 {//we don't think on our own
01903                         //well, run scripts, though...
01904                         trap_ICARUS_MaintainTaskManager(self->s.number);
01905                         return;
01906                 }
01907                 else
01908                 {
01909                         VectorClear(self->client->ps.moveDir);
01910                         self->client->pers.cmd.forwardmove = 0;
01911                         self->client->pers.cmd.rightmove = 0;
01912                         self->client->pers.cmd.upmove = 0;
01913                         self->client->pers.cmd.buttons = 0;
01914                         memcpy(&self->m_pVehicle->m_ucmd, &self->client->pers.cmd, sizeof(usercmd_t));
01915                 }
01916         }
01917         else if ( NPC->s.m_iVehicleNum )
01918         {//droid in a vehicle?
01919                 G_DroidSounds( self );
01920         }
01921 
01922         if ( NPCInfo->nextBStateThink <= level.time 
01923                 && !NPC->s.m_iVehicleNum )//NPCs sitting in Vehicles do NOTHING
01924         {
01925 #if     AI_TIMERS
01926                 int     startTime = GetTime(0);
01927 #endif//        AI_TIMERS
01928                 if ( NPC->s.eType != ET_NPC )
01929                 {//Something drastic happened in our script
01930                         return;
01931                 }
01932 
01933                 if ( NPC->s.weapon == WP_SABER && g_spskill.integer >= 2 && NPCInfo->rank > RANK_LT_JG )
01934                 {//Jedi think faster on hard difficulty, except low-rank (reborn)
01935                         NPCInfo->nextBStateThink = level.time + FRAMETIME/2;
01936                 }
01937                 else
01938                 {//Maybe even 200 ms?
01939                         NPCInfo->nextBStateThink = level.time + FRAMETIME;
01940                 }
01941 
01942                 //nextthink is set before this so something in here can override it
01943                 if (self->s.NPC_class != CLASS_VEHICLE ||
01944                         !self->m_pVehicle)
01945                 { //ok, let's not do this at all for vehicles.
01946                         NPC_ExecuteBState( self );
01947                 }
01948 
01949 #if     AI_TIMERS
01950                 int addTime = GetTime( startTime );
01951                 if ( addTime > 50 )
01952                 {
01953                         Com_Printf( S_COLOR_RED"ERROR: NPC number %d, %s %s at %s, weaponnum: %d, using %d of AI time!!!\n", NPC->s.number, NPC->NPC_type, NPC->targetname, vtos(NPC->r.currentOrigin), NPC->s.weapon, addTime );
01954                 }
01955                 AITime += addTime;
01956 #endif//        AI_TIMERS
01957         }
01958         else
01959         {
01960                 VectorCopy( oldMoveDir, self->client->ps.moveDir );
01961                 //or use client->pers.lastCommand?
01962                 NPCInfo->last_ucmd.serverTime = level.time - 50;
01963                 if ( !NPC->next_roff_time || NPC->next_roff_time < level.time )
01964                 {//If we were following a roff, we don't do normal pmoves.
01965                         //FIXME: firing angles (no aim offset) or regular angles?
01966                         NPC_UpdateAngles(qtrue, qtrue);
01967                         memcpy( &ucmd, &NPCInfo->last_ucmd, sizeof( usercmd_t ) );
01968                         ClientThink(NPC->s.number, &ucmd);
01969                 }
01970                 else
01971                 {
01972                         NPC_ApplyRoff();
01973                 }
01974                 //VectorCopy(self->s.origin, self->s.origin2 );
01975         }
01976         //must update icarus *every* frame because of certain animation completions in the pmove stuff that can leave a 50ms gap between ICARUS animation commands
01977         trap_ICARUS_MaintainTaskManager(self->s.number);
01978         VectorCopy(self->r.currentOrigin, self->client->ps.origin);
01979 }
01980 
01981 void NPC_InitAI ( void ) 
01982 {
01983         /*
01984         trap_Cvar_Register(&g_saberRealisticCombat, "g_saberRealisticCombat", "0", CVAR_CHEAT);
01985 
01986         trap_Cvar_Register(&debugNoRoam, "d_noroam", "0", CVAR_CHEAT);
01987         trap_Cvar_Register(&debugNPCAimingBeam, "d_npcaiming", "0", CVAR_CHEAT);
01988         trap_Cvar_Register(&debugBreak, "d_break", "0", CVAR_CHEAT);
01989         trap_Cvar_Register(&debugNPCAI, "d_npcai", "0", CVAR_CHEAT);
01990         trap_Cvar_Register(&debugNPCFreeze, "d_npcfreeze", "0", CVAR_CHEAT);
01991         trap_Cvar_Register(&d_JediAI, "d_JediAI", "0", CVAR_CHEAT);
01992         trap_Cvar_Register(&d_noGroupAI, "d_noGroupAI", "0", CVAR_CHEAT);
01993         trap_Cvar_Register(&d_asynchronousGroupAI, "d_asynchronousGroupAI", "0", CVAR_CHEAT);
01994         
01995         //0 = never (BORING)
01996         //1 = kyle only
01997         //2 = kyle and last enemy jedi
01998         //3 = kyle and any enemy jedi
01999         //4 = kyle and last enemy in a group
02000         //5 = kyle and any enemy
02001         //6 = also when kyle takes pain or enemy jedi dodges player saber swing or does an acrobatic evasion
02002 
02003         trap_Cvar_Register(&d_slowmodeath, "d_slowmodeath", "0", CVAR_CHEAT);
02004 
02005         trap_Cvar_Register(&d_saberCombat, "d_saberCombat", "0", CVAR_CHEAT);
02006 
02007         trap_Cvar_Register(&g_spskill, "g_npcspskill", "0", CVAR_ARCHIVE | CVAR_USERINFO);
02008         */
02009 }
02010 
02011 /*
02012 ==================================
02013 void NPC_InitAnimTable( void )
02014 
02015   Need to initialize this table.
02016   If someone tried to play an anim
02017   before table is filled in with
02018   values, causes tasks that wait for
02019   anim completion to never finish.
02020   (frameLerp of 0 * numFrames of 0 = 0)
02021 ==================================
02022 */
02023 /*
02024 void NPC_InitAnimTable( void )
02025 {
02026         int i;
02027 
02028         for ( i = 0; i < MAX_ANIM_FILES; i++ )
02029         {
02030                 for ( int j = 0; j < MAX_ANIMATIONS; j++ )
02031                 {
02032                         level.knownAnimFileSets[i].animations[j].firstFrame = 0;
02033                         level.knownAnimFileSets[i].animations[j].frameLerp = 100;
02034                         level.knownAnimFileSets[i].animations[j].initialLerp = 100;
02035                         level.knownAnimFileSets[i].animations[j].numFrames = 0;
02036                 }
02037         }
02038 }
02039 */
02040 
02041 void NPC_InitGame( void ) 
02042 {
02043 //      globals.NPCs = (gNPC_t *) gi.TagMalloc(game.maxclients * sizeof(game.bots[0]), TAG_GAME);
02044 //      trap_Cvar_Register(&debugNPCName, "d_npc", "0", CVAR_CHEAT);
02045 
02046         NPC_LoadParms();
02047         NPC_InitAI();
02048 //      NPC_InitAnimTable();
02049         /*
02050         ResetTeamCounters();
02051         for ( int team = NPCTEAM_FREE; team < NPCTEAM_NUM_TEAMS; team++ )
02052         {
02053                 teamLastEnemyTime[team] = -10000;
02054         }
02055         */
02056 }
02057 
02058 void NPC_SetAnim(gentity_t *ent, int setAnimParts, int anim, int setAnimFlags)
02059 {       // FIXME : once torsoAnim and legsAnim are in the same structure for NCP and Players
02060         // rename PM_SETAnimFinal to PM_SetAnim and have both NCP and Players call PM_SetAnim
02061         G_SetAnim(ent, NULL, setAnimParts, anim, setAnimFlags, 0);
02062 /*
02063         if(ent->client)
02064         {//Players, NPCs
02065                 if (setAnimFlags&SETANIM_FLAG_OVERRIDE)
02066                 {               
02067                         if (setAnimParts & SETANIM_TORSO)
02068                         {
02069                                 if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->client->ps.torsoAnim != anim )
02070                                 {
02071                                         PM_SetTorsoAnimTimer( ent, &ent->client->ps.torsoTimer, 0 );
02072                                 }
02073                         }
02074                         if (setAnimParts & SETANIM_LEGS)
02075                         {
02076                                 if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->client->ps.legsAnim != anim )
02077                                 {
02078                                         PM_SetLegsAnimTimer( ent, &ent->client->ps.legsAnimTimer, 0 );
02079                                 }
02080                         }
02081                 }
02082 
02083                 PM_SetAnimFinal(&ent->client->ps.torsoAnim,&ent->client->ps.legsAnim,setAnimParts,anim,setAnimFlags,
02084                         &ent->client->ps.torsoAnimTimer,&ent->client->ps.legsAnimTimer,ent);
02085         }
02086         else
02087         {//bodies, etc.
02088                 if (setAnimFlags&SETANIM_FLAG_OVERRIDE)
02089                 {               
02090                         if (setAnimParts & SETANIM_TORSO)
02091                         {
02092                                 if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->s.torsoAnim != anim )
02093                                 {
02094                                         PM_SetTorsoAnimTimer( ent, &ent->s.torsoAnimTimer, 0 );
02095                                 }
02096                         }
02097                         if (setAnimParts & SETANIM_LEGS)
02098                         {
02099                                 if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->s.legsAnim != anim )
02100                                 {
02101                                         PM_SetLegsAnimTimer( ent, &ent->s.legsAnimTimer, 0 );
02102                                 }
02103                         }
02104                 }
02105 
02106                 PM_SetAnimFinal(&ent->s.torsoAnim,&ent->s.legsAnim,setAnimParts,anim,setAnimFlags,
02107                         &ent->s.torsoAnimTimer,&ent->s.legsAnimTimer,ent);
02108         }
02109         */
02110 }