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 <