codemp/game/NPC_reactions.c

Go to the documentation of this file.
00001 //NPC_reactions.cpp
00002 #include "b_local.h"
00003 #include "anims.h"
00004 #include "w_saber.h"
00005 
00006 extern qboolean G_CheckForStrongAttackMomentum( gentity_t *self );
00007 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
00008 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
00009 extern void cgi_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx );
00010 extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType );
00011 extern qboolean NPC_CheckLookTarget( gentity_t *self );
00012 extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime );
00013 extern qboolean Jedi_WaitingAmbush( gentity_t *self );
00014 extern void Jedi_Ambush( gentity_t *self );
00015 extern qboolean NPC_SomeoneLookingAtMe(gentity_t *ent);
00016 
00017 #include "../namespace_begin.h"
00018 extern qboolean BG_SaberInSpecialAttack( int anim );
00019 extern qboolean PM_SpinningSaberAnim( int anim );
00020 extern qboolean PM_SpinningAnim( int anim );
00021 extern qboolean PM_InKnockDown( playerState_t *ps );
00022 extern qboolean BG_FlippingAnim( int anim );
00023 extern qboolean PM_RollingAnim( int anim );
00024 extern qboolean PM_InCartwheel( int anim );
00025 extern qboolean BG_CrouchAnim( int anim );
00026 #include "../namespace_end.h"
00027 
00028 extern int      teamLastEnemyTime[];
00029 extern int killPlayerTimer;
00030 
00031 //float g_crosshairEntDist = Q3_INFINITE;
00032 //int g_crosshairSameEntTime = 0;
00033 //int g_crosshairEntNum = ENTITYNUM_NONE;
00034 //int g_crosshairEntTime = 0;
00035 
00036 /*
00037 -------------------------
00038 NPC_CheckAttacker
00039 -------------------------
00040 */
00041 
00042 static void NPC_CheckAttacker( gentity_t *other, int mod )
00043 {
00044         //FIXME: I don't see anything in here that would stop teammates from taking a teammate
00045         //                      as an enemy.  Ideally, there would be code before this to prevent that from
00046         //                      happening, but that is presumptuous.
00047         
00048         //valid ent - FIXME: a VALIDENT macro would be nice here
00049         if ( !other )
00050                 return;
00051 
00052         if ( other == NPC )
00053                 return;
00054 
00055         if ( !other->inuse )
00056                 return;
00057 
00058         //Don't take a target that doesn't want to be
00059         if ( other->flags & FL_NOTARGET ) 
00060                 return;
00061 
00062 //      if ( NPC->svFlags & SVF_LOCKEDENEMY )
00063 //      {//IF LOCKED, CANNOT CHANGE ENEMY!!!!!
00064 //              return;
00065 //      }
00066         //rwwFIXMEFIXME: support this
00067 
00068         //If we haven't taken a target, just get mad
00069         if ( NPC->enemy == NULL )//was using "other", fixed to NPC
00070         {
00071                 G_SetEnemy( NPC, other );
00072                 return;
00073         }
00074 
00075         //we have an enemy, see if he's dead
00076         if ( NPC->enemy->health <= 0 )
00077         {
00078                 G_ClearEnemy( NPC );
00079                 G_SetEnemy( NPC, other );
00080                 return;
00081         }
00082 
00083         //Don't take the same enemy again
00084         if ( other == NPC->enemy )
00085                 return;
00086 
00087         if ( NPC->client->ps.weapon == WP_SABER )
00088         {//I'm a jedi
00089                 if ( mod == MOD_SABER )
00090                 {//I was hit by a saber  FIXME: what if this was a thrown saber?
00091                         //always switch to this enemy if I'm a jedi and hit by another saber
00092                         G_ClearEnemy( NPC );
00093                         G_SetEnemy( NPC, other );
00094                         return;
00095                 }
00096         }
00097         //Special case player interactions
00098         if ( other == &g_entities[0] )
00099         {
00100                 //Account for the skill level to skew the results
00101                 float   luckThreshold;
00102 
00103                 switch ( g_spskill.integer )
00104                 {
00105                 //Easiest difficulty, mild chance of picking up the player
00106                 case 0:
00107                         luckThreshold = 0.9f;
00108                         break;
00109 
00110                 //Medium difficulty, half-half chance of picking up the player
00111                 case 1:
00112                         luckThreshold = 0.5f;
00113                         break;
00114 
00115                 //Hardest difficulty, always turn on attacking player
00116                 case 2:
00117                 default:
00118                         luckThreshold = 0.0f;
00119                         break;
00120                 }
00121 
00122                 //Randomly pick up the target
00123                 if ( random() > luckThreshold )
00124                 {
00125                         G_ClearEnemy( other );
00126                         other->enemy = NPC;
00127                 }
00128 
00129                 return;
00130         }
00131 }
00132 
00133 void NPC_SetPainEvent( gentity_t *self )
00134 {
00135         if ( !self->NPC || !(self->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
00136         {
00137         // no more borg
00138         //      if( self->client->playerTeam != TEAM_BORG )
00139         //      {
00140                         //if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) )
00141                         if (!trap_ICARUS_TaskIDPending(self, TID_CHAN_VOICE) && self->client)
00142                         {
00143                                 //G_AddEvent( self, EV_PAIN, floor((float)self->health/self->max_health*100.0f) );
00144                                 G_AddEvent( self, EV_PAIN, floor((float)self->health/self->client->ps.stats[STAT_MAX_HEALTH]*100.0f) );
00145                                 //rwwFIXMEFIXME: Do this properly?
00146                         }
00147         //      }
00148         }
00149 }
00150 
00151 /*
00152 -------------------------
00153 NPC_GetPainChance
00154 -------------------------
00155 */
00156 
00157 float NPC_GetPainChance( gentity_t *self, int damage )
00158 {
00159         float pain_chance;
00160         if ( !self->enemy )
00161         {//surprised, always take pain
00162                 return 1.0f;
00163         }
00164 
00165         if (!self->client)
00166         {
00167                 return 1.0f;
00168         }
00169 
00170         //if ( damage > self->max_health/2.0f )
00171         if (damage > self->client->ps.stats[STAT_MAX_HEALTH]/2.0f)
00172         {
00173                 return 1.0f;
00174         }
00175 
00176         pain_chance = (float)(self->client->ps.stats[STAT_MAX_HEALTH]-self->health)/(self->client->ps.stats[STAT_MAX_HEALTH]*2.0f) + (float)damage/(self->client->ps.stats[STAT_MAX_HEALTH]/2.0f);
00177         switch ( g_spskill.integer )
00178         {
00179         case 0: //easy
00180                 //return 0.75f;
00181                 break;
00182 
00183         case 1://med
00184                 pain_chance *= 0.5f;
00185                 //return 0.35f;
00186                 break;
00187 
00188         case 2://hard
00189         default:
00190                 pain_chance *= 0.1f;
00191                 //return 0.05f;
00192                 break;
00193         }
00194         //Com_Printf( "%s: %4.2f\n", self->NPC_type, pain_chance );
00195         return pain_chance;
00196 }
00197 
00198 /*
00199 -------------------------
00200 NPC_ChoosePainAnimation
00201 -------------------------
00202 */
00203 
00204 #define MIN_PAIN_TIME   200
00205 
00206 extern int G_PickPainAnim( gentity_t *self, vec3_t point, int damage, int hitLoc );
00207 void NPC_ChoosePainAnimation( gentity_t *self, gentity_t *other, vec3_t point, int damage, int mod, int hitLoc, int voiceEvent )
00208 {
00209         int             pain_anim = -1;
00210         float   pain_chance;
00211         
00212         //If we've already taken pain, then don't take it again
00213         if ( level.time < self->painDebounceTime && /*mod != MOD_ELECTROCUTE &&*/ mod != MOD_MELEE ) //rwwFIXMEFIXME: MOD_ELECTROCUTE
00214         {//FIXME: if hit while recoving from losing a saber lock, we should still play a pain anim?
00215                 return;
00216         }
00217         
00218         if ( self->s.weapon == WP_THERMAL && self->client->ps.weaponTime > 0 )
00219         {//don't interrupt thermal throwing anim
00220                 return;
00221         }
00222         else if ( self->client->NPC_class == CLASS_GALAKMECH )
00223         {
00224                 if ( hitLoc == HL_GENERIC1 )
00225                 {//hit the antenna!
00226                         pain_chance = 1.0f;
00227                 //      self->s.powerups |= ( 1 << PW_SHOCKED );
00228                 //      self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 );
00229                         //rwwFIXMEFIXME: support for this
00230                 }
00231         //      else if ( self->client->ps.powerups[PW_GALAK_SHIELD] )
00232         //      {//shield up
00233         //              return;
00234         //      }
00235                 //rwwFIXMEFIXME: and this
00236                 else if ( self->health > 200 && damage < 100 )
00237                 {//have a *lot* of health
00238                         pain_chance = 0.05f;
00239                 }
00240                 else
00241                 {//the lower my health and greater the damage, the more likely I am to play a pain anim
00242                         pain_chance = (200.0f-self->health)/100.0f + damage/50.0f;
00243                 }
00244         }
00245         else if ( self->client && self->client->playerTeam == NPCTEAM_PLAYER && other && !other->s.number )
00246         {//ally shot by player always complains
00247                 pain_chance = 1.1f;
00248         }
00249         else 
00250         {
00251                 if ( other && other->s.weapon == WP_SABER || /*mod == MOD_ELECTROCUTE ||*/ mod == MOD_CRUSH/*FIXME:MOD_FORCE_GRIP*/ )
00252                 {
00253                         pain_chance = 1.0f;//always take pain from saber
00254                 }
00255                 else if ( mod == MOD_MELEE )
00256                 {//higher in rank (skill) we are, less likely we are to be fazed by a punch
00257                         pain_chance = 1.0f - ((RANK_CAPTAIN-self->NPC->rank)/(float)RANK_CAPTAIN);
00258                 }
00259                 else if ( self->client->NPC_class == CLASS_PROTOCOL )
00260                 {
00261                         pain_chance = 1.0f;
00262                 }
00263                 else
00264                 {
00265                         pain_chance = NPC_GetPainChance( self, damage );
00266                 }
00267                 if ( self->client->NPC_class == CLASS_DESANN )
00268                 {
00269                         pain_chance *= 0.5f;
00270                 }
00271         }
00272 
00273         //See if we're going to flinch
00274         if ( random() < pain_chance )
00275         {
00276                 int animLength;
00277 
00278                 //Pick and play our animation
00279                 if ( self->client->ps.fd.forceGripBeingGripped < level.time )
00280                 {//not being force-gripped or force-drained
00281                         if ( /*G_CheckForStrongAttackMomentum( self ) //rwwFIXMEFIXME: Is this needed?
00282                                 ||*/ PM_SpinningAnim( self->client->ps.legsAnim )
00283                                 || BG_SaberInSpecialAttack( self->client->ps.torsoAnim )
00284                                 || PM_InKnockDown( &self->client->ps )
00285                                 || PM_RollingAnim( self->client->ps.legsAnim )
00286                                 || (BG_FlippingAnim( self->client->ps.legsAnim )&&!PM_InCartwheel( self->client->ps.legsAnim )) )
00287                         {//strong attacks, rolls, knockdowns, flips and spins cannot be interrupted by pain
00288                         }
00289                         else
00290                         {//play an anim
00291                                 int parts;
00292 
00293                                 if ( self->client->NPC_class == CLASS_GALAKMECH )
00294                                 {//only has 1 for now
00295                                         //FIXME: never plays this, it seems...
00296                                         pain_anim = BOTH_PAIN1;
00297                                 }
00298                                 else if ( mod == MOD_MELEE )
00299                                 {
00300                                         pain_anim = BG_PickAnim( self->localAnimIndex, BOTH_PAIN2, BOTH_PAIN3 );
00301                                 }
00302                                 else if ( self->s.weapon == WP_SABER )
00303                                 {//temp HACK: these are the only 2 pain anims that look good when holding a saber
00304                                         pain_anim = BG_PickAnim( self->localAnimIndex, BOTH_PAIN2, BOTH_PAIN3 );
00305                                 }
00306                                 /*
00307                                 else if ( mod != MOD_ELECTROCUTE )
00308                                 {
00309                                         pain_anim = G_PickPainAnim( self, point, damage, hitLoc );
00310                                 }
00311                                 */
00312 
00313                                 if ( pain_anim == -1 )
00314                                 {
00315                                         pain_anim = BG_PickAnim( self->localAnimIndex, BOTH_PAIN1, BOTH_PAIN18 );
00316                                 }
00317                                 self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1;//next attack must be a quick attack
00318                                 self->client->ps.saberMove = LS_READY;//don't finish whatever saber move you may have been in
00319                                 parts = SETANIM_BOTH;
00320                                 if ( BG_CrouchAnim( self->client->ps.legsAnim ) || PM_InCartwheel( self->client->ps.legsAnim ) )
00321                                 {
00322                                         parts = SETANIM_LEGS;
00323                                 }
00324 
00325                                 if (pain_anim != -1)
00326                                 {
00327                                         NPC_SetAnim( self, parts, pain_anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00328                                 }
00329                         }
00330                         if ( voiceEvent != -1 )
00331                         {
00332                                 G_AddVoiceEvent( self, voiceEvent, Q_irand( 2000, 4000 ) );
00333                         }
00334                         else
00335                         {
00336                                 NPC_SetPainEvent( self );
00337                         }
00338                 }
00339                 else
00340                 {
00341                         G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 );
00342                 }
00343                 
00344                 //Setup the timing for it
00345                 /*
00346                 if ( mod == MOD_ELECTROCUTE )
00347                 {
00348                         self->painDebounceTime = level.time + 4000;
00349                 }
00350                 */
00351                 animLength = bgAllAnims[self->localAnimIndex].anims[pain_anim].numFrames * fabs((float)(bgHumanoidAnimations[pain_anim].frameLerp));
00352 
00353                 self->painDebounceTime = level.time + animLength;
00354                 self->client->ps.weaponTime = 0;
00355         }
00356 }
00357 
00358 /*
00359 ===============
00360 NPC_Pain
00361 ===============
00362 */
00363 void NPC_Pain(gentity_t *self, gentity_t *attacker, int damage)
00364 {
00365         team_t otherTeam = TEAM_FREE;
00366         int             voiceEvent = -1;
00367         gentity_t *other = attacker;
00368         int mod = gPainMOD;
00369         int hitLoc = gPainHitLoc;
00370         vec3_t point;
00371 
00372         VectorCopy(gPainPoint, point);
00373 
00374         if ( self->NPC == NULL ) 
00375                 return;
00376 
00377         if ( other == NULL ) 
00378                 return;
00379 
00380         //or just remove ->pain in player_die?
00381         if ( self->client->ps.pm_type == PM_DEAD )
00382                 return;
00383 
00384         if ( other == self ) 
00385                 return;
00386 
00387         //MCG: Ignore damage from your own team for now
00388         if ( other->client )
00389         {
00390                 otherTeam = other->client->playerTeam;
00391         //      if ( otherTeam == TEAM_DISGUISE )
00392         //      {
00393         //              otherTeam = TEAM_PLAYER;
00394         //      }
00395         }
00396 
00397         if ( self->client->playerTeam 
00398                 && other->client 
00399                 && otherTeam == self->client->playerTeam 
00400         /*      && (!player->client->ps.viewEntity || other->s.number != player->client->ps.viewEntity)*/) 
00401         //rwwFIXMEFIXME: Will need modification when player controllable npcs are done
00402         {//hit by a teammate
00403                 if ( other != self->enemy && self != other->enemy )
00404                 {//we weren't already enemies
00405                         if ( self->enemy || other->enemy 
00406                                 
00407                                 //|| (other->s.number&&other->s.number!=player->client->ps.viewEntity) 
00408                                 //rwwFIXMEFIXME: same
00409 
00410                                 /*|| (!other->s.number&&Q_irand( 0, 3 ))*/ )
00411                         {//if one of us actually has an enemy already, it's okay, just an accident OR wasn't hit by player or someone controlled by player OR player hit ally and didn't get 25% chance of getting mad (FIXME:accumulate anger+base on diff?)
00412                                 //FIXME: player should have to do a certain amount of damage to ally or hit them several times to make them mad
00413                                 //Still run pain and flee scripts
00414                                 if ( self->client && self->NPC )
00415                                 {//Run any pain instructions
00416                                         if ( self->health <= (self->client->ps.stats[STAT_MAX_HEALTH]/3) && G_ActivateBehavior(self, BSET_FLEE) )
00417                                         {
00418                                                 
00419                                         }
00420                                         else// if( VALIDSTRING( self->behaviorSet[BSET_PAIN] ) )
00421                                         {
00422                                                 G_ActivateBehavior(self, BSET_PAIN);
00423                                         }
00424                                 }
00425                                 if ( damage != -1 )
00426                                 {//-1 == don't play pain anim
00427                                         //Set our proper pain animation
00428                                         if ( Q_irand( 0, 1 ) )
00429                                         {
00430                                                 NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, EV_FFWARN );
00431                                         }
00432                                         else
00433                                         {
00434                                                 NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, -1 );
00435                                         }
00436                                 }
00437                                 return;
00438                         }
00439                         else if ( self->NPC && !other->s.number )//should be assumed, but...
00440                         {//dammit, stop that!
00441                                 if ( self->NPC->charmedTime )
00442                                 {//mindtricked
00443                                         return;
00444                                 }
00445                                 else if ( self->NPC->ffireCount < 3+((2-g_spskill.integer)*2) )
00446                                 {//not mad enough yet
00447                                         //Com_Printf( "chck: %d < %d\n", self->NPC->ffireCount, 3+((2-g_spskill.integer)*2) );
00448                                         if ( damage != -1 )
00449                                         {//-1 == don't play pain anim
00450                                                 //Set our proper pain animation
00451                                                 if ( Q_irand( 0, 1 ) )
00452                                                 {
00453                                                         NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, EV_FFWARN );
00454                                                 }
00455                                                 else
00456                                                 {
00457                                                         NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, -1 );
00458                                                 }
00459                                         }
00460                                         return;
00461                                 }
00462                                 else if ( G_ActivateBehavior( self, BSET_FFIRE ) )
00463                                 {//we have a specific script to run, so do that instead
00464                                         return;
00465                                 }
00466                                 else
00467                                 {//okay, we're going to turn on our ally, we need to set and lock our enemy and put ourselves in a bstate that lets us attack him (and clear any flags that would stop us)
00468                                         self->NPC->blockedSpeechDebounceTime = 0;
00469                                         voiceEvent = EV_FFTURN;
00470                                         self->NPC->behaviorState = self->NPC->tempBehavior = self->NPC->defaultBehavior = BS_DEFAULT;
00471                                         other->flags &= ~FL_NOTARGET;
00472                                         //self->svFlags &= ~(SVF_IGNORE_ENEMIES|SVF_ICARUS_FREEZE|SVF_NO_COMBAT_SOUNDS);
00473                                         self->r.svFlags &= ~SVF_ICARUS_FREEZE;
00474                                         G_SetEnemy( self, other );
00475                                         //self->svFlags |= SVF_LOCKEDENEMY; //rwwFIXMEFIXME: proper support for these flags.
00476                                         self->NPC->scriptFlags &= ~(SCF_DONT_FIRE|SCF_CROUCHED|SCF_WALKING|SCF_NO_COMBAT_TALK|SCF_FORCED_MARCH);
00477                                         self->NPC->scriptFlags |= (SCF_CHASE_ENEMIES|SCF_NO_MIND_TRICK);
00478                                         //NOTE: we also stop ICARUS altogether
00479                                         //stop_icarus = qtrue;
00480                                         //rwwFIXMEFIXME: stop icarus?
00481                                         if ( !killPlayerTimer )
00482                                         {
00483                                                 killPlayerTimer = level.time + 10000;
00484                                         }
00485                                 }
00486                         }
00487                 }
00488         }
00489 
00490         SaveNPCGlobals();
00491         SetNPCGlobals( self );
00492 
00493         //Do extra bits
00494         if ( NPCInfo->ignorePain == qfalse )
00495         {
00496                 NPCInfo->confusionTime = 0;//clear any charm or confusion, regardless
00497                 if ( damage != -1 )
00498                 {//-1 == don't play pain anim
00499                         //Set our proper pain animation
00500                         NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, voiceEvent );
00501                 }
00502                 //Check to take a new enemy
00503                 if ( NPC->enemy != other && NPC != other )
00504                 {//not already mad at them
00505                         NPC_CheckAttacker( other, mod );
00506                 }
00507         }
00508 
00509         //Attempt to run any pain instructions
00510         if ( self->client && self->NPC )
00511         {
00512                 //FIXME: This needs better heuristics perhaps
00513                 if(self->health <= (self->client->ps.stats[STAT_MAX_HEALTH]/3) && G_ActivateBehavior(self, BSET_FLEE) )
00514                 {
00515                 }
00516                 else //if( VALIDSTRING( self->behaviorSet[BSET_PAIN] ) )
00517                 {
00518                         G_ActivateBehavior(self, BSET_PAIN);
00519                 }
00520         }
00521 
00522         //Attempt to fire any paintargets we might have
00523         if( self->paintarget && self->paintarget[0] )
00524         {
00525                 G_UseTargets2(self, other, self->paintarget);
00526         }
00527 
00528         RestoreNPCGlobals();
00529 }
00530 
00531 /*
00532 -------------------------
00533 NPC_Touch
00534 -------------------------
00535 */
00536 extern qboolean INV_SecurityKeyGive( gentity_t *target, const char *keyname );
00537 void NPC_Touch(gentity_t *self, gentity_t *other, trace_t *trace) 
00538 {
00539         if(!self->NPC)
00540                 return;
00541 
00542         SaveNPCGlobals();
00543         SetNPCGlobals( self );
00544 
00545         if ( self->message && self->health <= 0 )
00546         {//I am dead and carrying a key
00547                 //if ( other && player && player->health > 0 && other == player )
00548                 if (other && other->client && other->s.number < MAX_CLIENTS)
00549                 {//player touched me
00550                         /*
00551                         char *text;
00552                         qboolean        keyTaken;
00553                         //give him my key
00554                         if ( Q_stricmp( "goodie", self->message ) == 0 )
00555                         {//a goodie key
00556                                 if ( (keyTaken = INV_GoodieKeyGive( other )) == qtrue )
00557                                 {
00558                                         text = "cp @SP_INGAME_TOOK_IMPERIAL_GOODIE_KEY";
00559                                         G_AddEvent( other, EV_ITEM_PICKUP, (FindItemForInventory( INV_GOODIE_KEY )-bg_itemlist) );
00560                                 }
00561                                 else
00562                                 {
00563                                         text = "cp @SP_INGAME_CANT_CARRY_GOODIE_KEY";
00564                                 }
00565                         }
00566                         else
00567                         {//a named security key
00568                                 if ( (keyTaken = INV_SecurityKeyGive( player, self->message )) == qtrue )
00569                                 {
00570                                         text = "cp @SP_INGAME_TOOK_IMPERIAL_SECURITY_KEY";
00571                                         G_AddEvent( other, EV_ITEM_PICKUP, (FindItemForInventory( INV_SECURITY_KEY )-bg_itemlist) );
00572                                 }
00573                                 else
00574                                 {
00575                                         text = "cp @SP_INGAME_CANT_CARRY_SECURITY_KEY";
00576                                 }
00577                         }
00578                         */
00579                         //rwwFIXMEFIXME: support for goodie/security keys?
00580                         /*
00581                         if ( keyTaken )
00582                         {//remove my key
00583                                 NPC_SetSurfaceOnOff( self, "l_arm_key", 0x00000002 );
00584                                 self->message = NULL;
00585                                 //FIXME: temp pickup sound
00586                                 G_Sound( player, G_SoundIndex( "sound/weapons/key_pkup.wav" ) );
00587                                 //FIXME: need some event to pass to cgame for sound/graphic/message?
00588                         }
00589                         //FIXME: temp message
00590                         gi.SendServerCommand( NULL, text );
00591                         */
00592                 }
00593         }
00594 
00595         if ( other->client ) 
00596         {//FIXME:  if pushing against another bot, both ucmd.rightmove = 127???
00597                 //Except if not facing one another...
00598                 if ( other->health > 0 ) 
00599                 {
00600                         NPCInfo->touchedByPlayer = other;
00601                 }
00602 
00603                 if ( other == NPCInfo->goalEntity ) 
00604                 {
00605                         NPCInfo->aiFlags |= NPCAI_TOUCHED_GOAL;
00606                 }
00607 
00608                 if(  !(other->flags & FL_NOTARGET) )
00609                 {
00610                         if ( self->client->enemyTeam )
00611                         {//See if we bumped into an enemy
00612                                 if ( other->client->playerTeam == self->client->enemyTeam )
00613                                 {//bumped into an enemy
00614                                         if( NPCInfo->behaviorState != BS_HUNT_AND_KILL && !NPCInfo->tempBehavior )
00615                                         {//MCG - Begin: checking specific BS mode here, this is bad, a HACK
00616                                                 //FIXME: not medics?
00617                                                 if ( NPC->enemy != other )
00618                                                 {//not already mad at them
00619                                                         G_SetEnemy( NPC, other );
00620                                                 }
00621                 //                              NPCInfo->tempBehavior = BS_HUNT_AND_KILL;
00622                                         }
00623                                 }
00624                         }
00625                 }
00626 
00627                 //FIXME: do this if player is moving toward me and with a certain dist?
00628                 /*
00629                 if ( other->s.number == 0 && self->client->playerTeam == other->client->playerTeam )
00630                 {
00631                         VectorAdd( self->client->pushVec, other->client->ps.velocity, self->client->pushVec );
00632                 }
00633                 */
00634         }
00635         else 
00636         {//FIXME: check for SVF_NONNPC_ENEMY flag here?
00637                 if ( other->health > 0 ) 
00638                 {
00639                         //if ( NPC->enemy == other && (other->svFlags&SVF_NONNPC_ENEMY) )
00640                         if (0) //rwwFIXMEFIXME: Can probably just check if num < MAX_CLIENTS for non-npc enemy stuff
00641                         {
00642                                 NPCInfo->touchedByPlayer = other;
00643                         }
00644                 }
00645 
00646                 if ( other == NPCInfo->goalEntity ) 
00647                 {
00648                         NPCInfo->aiFlags |= NPCAI_TOUCHED_GOAL;
00649                 }
00650         }
00651 
00652         RestoreNPCGlobals();
00653 }
00654 
00655 /*
00656 -------------------------
00657 NPC_TempLookTarget
00658 -------------------------
00659 */
00660 
00661 void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime )
00662 {
00663         if ( !self->client )
00664         {
00665                 return;
00666         }
00667 
00668         if ( (self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
00669         {//lookTarget is set by and to the monster that's holding you, no other operations can change that
00670                 return;
00671         }
00672 
00673         if ( !minLookTime )
00674         {
00675                 minLookTime = 1000;
00676         }
00677 
00678         if ( !maxLookTime )
00679         {
00680                 maxLookTime = 1000;
00681         }
00682 
00683         if ( !NPC_CheckLookTarget( self ) )
00684         {//Not already looking at something else
00685                 //Look at him for 1 to 3 seconds
00686                 NPC_SetLookTarget( self, lookEntNum, level.time + Q_irand( minLookTime, maxLookTime ) );
00687         }
00688 }
00689 
00690 void NPC_Respond( gentity_t *self, int userNum )
00691 {
00692         int event = -1;
00693         /*
00694 
00695         if ( Q_irand( 0, 1 ) )
00696         {
00697                 event = Q_irand(EV_RESPOND1, EV_RESPOND3);
00698         }
00699         else
00700         {
00701                 event = Q_irand(EV_BUSY1, EV_BUSY3);
00702         }
00703         */
00704 
00705         if ( !Q_irand( 0, 1 ) )
00706         {//set looktarget to them for a second or two
00707                 NPC_TempLookTarget( self, userNum, 1000, 3000 );
00708         }
00709 
00710         //some last-minute hacked in responses
00711         switch ( self->client->NPC_class )
00712         {
00713         case CLASS_JAN:
00714                 if ( self->enemy )
00715                 {
00716                         if ( !Q_irand( 0, 2 ) )
00717                         {
00718                                 event = Q_irand( EV_CHASE1, EV_CHASE3 );
00719                         }
00720                         else if ( Q_irand( 0, 1 ) )
00721                         {
00722                                 event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 );
00723                         }
00724                         else
00725                         {
00726                                 event = Q_irand( EV_COVER1, EV_COVER5 );
00727                         }
00728                 }
00729                 else if ( !Q_irand( 0, 2 ) )
00730                 {
00731                         event = EV_SUSPICIOUS4;
00732                 }
00733                 else if ( !Q_irand( 0, 1 ) )
00734                 {
00735                         event = EV_SOUND1;
00736                 }
00737                 else
00738                 {
00739                         event = EV_CONFUSE1;
00740                 }
00741                 break;
00742         case CLASS_LANDO:
00743                 if ( self->enemy )
00744                 {
00745                         if ( !Q_irand( 0, 2 ) )
00746                         {
00747                                 event = Q_irand( EV_CHASE1, EV_CHASE3 );
00748                         }
00749                         else if ( Q_irand( 0, 1 ) )
00750                         {
00751                                 event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 );
00752                         }
00753                         else
00754                         {
00755                                 event = Q_irand( EV_COVER1, EV_COVER5 );
00756                         }
00757                 }
00758                 else if ( !Q_irand( 0, 6 ) )
00759                 {
00760                         event = EV_SIGHT2;
00761                 }
00762                 else if ( !Q_irand( 0, 5 ) )
00763                 {
00764                         event = EV_GIVEUP4;
00765                 }
00766                 else if ( Q_irand( 0, 4 ) > 1 )
00767                 {
00768                         event = Q_irand( EV_SOUND1, EV_SOUND3 );
00769                 }
00770                 else
00771                 {
00772                         event = Q_irand( EV_JDETECTED1, EV_JDETECTED2 );
00773                 }
00774                 break;
00775         case CLASS_LUKE:
00776                 if ( self->enemy )
00777                 {
00778                         event = EV_COVER1;
00779                 }
00780                 else
00781                 {
00782                         event = Q_irand( EV_SOUND1, EV_SOUND3 );
00783                 }
00784                 break;
00785         case CLASS_JEDI:
00786                 if ( !self->enemy )
00787                 {
00788                         /*
00789                         if ( !(self->svFlags&SVF_IGNORE_ENEMIES) 
00790                                 && (self->NPC->scriptFlags&SCF_LOOK_FOR_ENEMIES)
00791                                 && self->client->enemyTeam == TEAM_ENEMY )
00792                                 */
00793                         if (0) //rwwFIXMEFIXME: support flags!
00794                         {
00795                                 event = Q_irand( EV_ANGER1, EV_ANGER3 );
00796                         }
00797                         else
00798                         {
00799                                 event = Q_irand( EV_TAUNT1, EV_TAUNT2 );
00800                         }
00801                 }
00802                 break;
00803         case CLASS_PRISONER:
00804                 if ( self->enemy )
00805                 {
00806                         if ( Q_irand( 0, 1 ) )
00807                         {
00808                                 event = Q_irand( EV_CHASE1, EV_CHASE3 );
00809                         }
00810                         else
00811                         {
00812                                 event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 );
00813                         }
00814                 }
00815                 else
00816                 {
00817                         event = Q_irand( EV_SOUND1, EV_SOUND3 );
00818                 }
00819                 break;
00820         case CLASS_REBEL:
00821                 if ( self->enemy )
00822                 {
00823                         if ( !Q_irand( 0, 2 ) )
00824                         {
00825                                 event = Q_irand( EV_CHASE1, EV_CHASE3 );
00826                         }
00827                         else
00828                         {
00829                                 event = Q_irand( EV_DETECTED1, EV_DETECTED5 );
00830                         }
00831                 }
00832                 else
00833                 {
00834                         event = Q_irand( EV_SOUND1, EV_SOUND3 );
00835                 }
00836                 break;
00837         case CLASS_BESPIN_COP:
00838                 if ( !Q_stricmp( "bespincop", self->NPC_type ) )
00839                 {//variant 1
00840                         if ( self->enemy )
00841                         {
00842                                 if ( Q_irand( 0, 9 ) > 6 )
00843                                 {
00844                                         event = Q_irand( EV_CHASE1, EV_CHASE3 );
00845                                 }
00846                                 else if ( Q_irand( 0, 6 ) > 4 )
00847                                 {
00848                                         event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 );
00849                                 }
00850                                 else
00851                                 {
00852                                         event = Q_irand( EV_COVER1, EV_COVER5 );
00853                                 }
00854                         }
00855                         else if ( !Q_irand( 0, 3 ) )
00856                         {
00857                                 event = Q_irand( EV_SIGHT2, EV_SIGHT3 );
00858                         }
00859                         else if ( !Q_irand( 0, 1 ) )
00860                         {
00861                                 event = Q_irand( EV_SOUND1, EV_SOUND3 );
00862                         }
00863                         else if ( !Q_irand( 0, 2 ) )
00864                         {
00865                                 event = EV_LOST1;
00866                         }
00867                         else if ( !Q_irand( 0, 1 ) )
00868                         {
00869                                 event = EV_ESCAPING2;
00870                         }
00871                         else
00872                         {
00873                                 event = EV_GIVEUP4;
00874                         }
00875                 }
00876                 else
00877                 {//variant2
00878                         if ( self->enemy )
00879                         {
00880                                 if ( Q_irand( 0, 9 ) > 6 )
00881                                 {
00882                                         event = Q_irand( EV_CHASE1, EV_CHASE3 );
00883                                 }
00884                                 else if ( Q_irand( 0, 6 ) > 4 )
00885                                 {
00886                                         event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 );
00887                                 }
00888                                 else
00889                                 {
00890                                         event = Q_irand( EV_COVER1, EV_COVER5 );
00891                                 }
00892                         }
00893                         else if ( !Q_irand( 0, 3 ) )
00894                         {
00895                                 event = Q_irand( EV_SIGHT1, EV_SIGHT2 );
00896                         }
00897                         else if ( !Q_irand( 0, 1 ) )
00898                         {
00899                                 event = Q_irand( EV_SOUND1, EV_SOUND3 );
00900                         }
00901                         else if ( !Q_irand( 0, 2 ) )
00902                         {
00903                                 event = EV_LOST1;
00904                         }
00905                         else if ( !Q_irand( 0, 1 ) )
00906                         {
00907                                 event = EV_GIVEUP3;
00908                         }
00909                         else
00910                         {
00911                                 event = EV_CONFUSE1;
00912                         }
00913                 }
00914                 break;
00915         case CLASS_R2D2:                                // droid
00916                 G_Sound(self, CHAN_AUTO, G_SoundIndex(va("sound/chars/r2d2/misc/r2d2talk0%d.wav",Q_irand(1, 3))));
00917                 break;
00918         case CLASS_R5D2:                                // droid
00919                 G_Sound(self, CHAN_AUTO, G_SoundIndex(va("sound/chars/r5d2/misc/r5talk%d.wav",Q_irand(1, 4))));
00920                 break;
00921         case CLASS_MOUSE:                               // droid
00922                 G_Sound(self, CHAN_AUTO, G_SoundIndex(va("sound/chars/mouse/misc/mousego%d.wav",Q_irand(1, 3))));
00923                 break;
00924         case CLASS_GONK:                                // droid
00925                 G_Sound(self, CHAN_AUTO, G_SoundIndex(va("sound/chars/gonk/misc/gonktalk%d.wav",Q_irand(1, 2))));
00926                 break;
00927         }
00928         
00929         if ( event != -1 )
00930         {
00931                 //hack here because we reuse some "combat" and "extra" sounds
00932                 qboolean addFlag = (self->NPC->scriptFlags&SCF_NO_COMBAT_TALK);
00933                 self->NPC->scriptFlags &= ~SCF_NO_COMBAT_TALK;
00934 
00935                 G_AddVoiceEvent( self, event, 3000 );
00936 
00937                 if ( addFlag )
00938                 {
00939                         self->NPC->scriptFlags |= SCF_NO_COMBAT_TALK;
00940                 }
00941         }
00942 }
00943 
00944 /*
00945 -------------------------
00946 NPC_UseResponse
00947 -------------------------
00948 */
00949 
00950 void NPC_UseResponse( gentity_t *self, gentity_t *user, qboolean useWhenDone )
00951 {
00952         if ( !self->NPC || !self->client )
00953         {
00954                 return;
00955         }
00956 
00957         if ( user->s.number != 0 )
00958         {//not used by the player
00959                 if ( useWhenDone )
00960                 {
00961                         G_ActivateBehavior( self, BSET_USE );
00962                 }
00963                 return;
00964         }
00965 
00966         if ( user->client && self->client->playerTeam != user->client->playerTeam && self->client->playerTeam != NPCTEAM_NEUTRAL )
00967         {//only those on the same team react
00968                 if ( useWhenDone )
00969                 {
00970                         G_ActivateBehavior( self, BSET_USE );
00971                 }
00972                 return;
00973         }
00974 
00975         if ( self->NPC->blockedSpeechDebounceTime > level.time )
00976         {//I'm not responding right now
00977                 return;
00978         }
00979 
00980         /*
00981         if ( gi.VoiceVolume[self->s.number] )
00982         {//I'm talking already
00983                 if ( !useWhenDone )
00984                 {//you're not trying to use me
00985                         return;
00986                 }
00987         }
00988         */
00989         //rwwFIXMEFIXME: Support for this?
00990 
00991         if ( useWhenDone )
00992         {
00993                 G_ActivateBehavior( self, BSET_USE );
00994         }
00995         else
00996         {
00997                 NPC_Respond( self, user->s.number );
00998         }
00999 }
01000 
01001 /*
01002 -------------------------
01003 NPC_Use
01004 -------------------------
01005 */
01006 extern void Add_Batteries( gentity_t *ent, int *count );
01007 
01008 void NPC_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) 
01009 {
01010         if (self->client->ps.pm_type == PM_DEAD)
01011         {//or just remove ->pain in player_die?
01012                 return;
01013         }
01014 
01015         SaveNPCGlobals();
01016         SetNPCGlobals( self );
01017 
01018         if(self->client && self->NPC)
01019         {
01020                 // If this is a vehicle, let the other guy board it. Added 12/14/02 by AReis.
01021                 if ( self->client->NPC_class == CLASS_VEHICLE )
01022                 {
01023                         Vehicle_t *pVeh = self->m_pVehicle;
01024 
01025                         if ( pVeh && pVeh->m_pVehicleInfo )
01026                         {
01027                                 //if I used myself, eject everyone on me
01028                                 if ( other == self )
01029                                 {
01030                                         pVeh->m_pVehicleInfo->EjectAll( pVeh );
01031                                 }
01032                                 // If other is already riding this vehicle (self), eject him.
01033                                 else if ( other->s.owner == self->s.number )
01034                                 {
01035                                         pVeh->m_pVehicleInfo->Eject( pVeh, (bgEntity_t *)other, qfalse );
01036                                 }
01037                                 // Otherwise board this vehicle.
01038                                 else
01039                                 {
01040                                         pVeh->m_pVehicleInfo->Board( pVeh, (bgEntity_t *)other );
01041                                 }
01042                         }
01043                 }
01044                 else if ( Jedi_WaitingAmbush( NPC ) )
01045                 {
01046                         Jedi_Ambush( NPC );
01047                 }
01048                 //Run any use instructions
01049                 if ( activator && activator->s.number == 0 && self->client->NPC_class == CLASS_GONK )
01050                 {
01051                         // must be using the gonk, so attempt to give battery power.
01052                         // NOTE: this will steal up to MAX_BATTERIES for the activator, leaving the residual on the gonk for potential later use.
01053 //                      Add_Batteries( activator, &self->client->ps.batteryCharge );
01054                         //rwwFIXMEFIXME: support for this?
01055                 }
01056                 // Not using MEDICs anymore
01057 /*
01058                 if ( self->NPC->behaviorState == BS_MEDIC_HIDE && activator->client )
01059                 {//Heal me NOW, dammit!
01060                         if ( activator->health < activator->client->ps.stats[STAT_MAX_HEALTH] )
01061                         {//person needs help
01062                                 if ( self->NPC->eventualGoal != activator )
01063                                 {//not my current patient already
01064                                         NPC_TakePatient( activator );
01065                                         G_ActivateBehavior( self, BSET_USE );
01066                                 }
01067                         }
01068                         else if ( !self->enemy && activator->s.number == 0 && !gi.VoiceVolume[self->s.number] && !(self->NPC->scriptFlags&SCF_NO_RESPONSE) )
01069                         {//I don't have an enemy and I'm not talking and I was used by the player
01070                                 NPC_UseResponse( self, other, qfalse );
01071                         }
01072                 }
01073 */
01074 //              else if ( self->behaviorSet[BSET_USE] )
01075                 if ( self->behaviorSet[BSET_USE] )
01076                 {
01077                         NPC_UseResponse( self, other, qtrue );
01078                 }
01079 //              else if ( isMedic( self ) )
01080 //              {//Heal me NOW, dammit!
01081 //                      NPC_TakePatient( activator );
01082 //              }
01083                 else if ( !self->enemy 
01084                         && activator->s.number == 0 
01085                         &&  !(self->NPC->scriptFlags&SCF_NO_RESPONSE) )
01086                         //rwwFIXMEFIXME: voice volume support?
01087                 {//I don't have an enemy and I'm not talking and I was used by the player
01088                         NPC_UseResponse( self, other, qfalse );
01089                 }
01090         }
01091 
01092         RestoreNPCGlobals();
01093 }
01094 
01095 void NPC_CheckPlayerAim( void )
01096 {
01097         //FIXME: need appropriate dialogue
01098         /*
01099         gentity_t *player = &g_entities[0];
01100 
01101         if ( player && player->client && player->client->ps.weapon > (int)(WP_NONE) && player->client->ps.weapon < (int)(WP_TRICORDER) )
01102         {//player has a weapon ready
01103                 if ( g_crosshairEntNum == NPC->s.number && level.time - g_crosshairEntTime < 200 
01104                         && g_crosshairSameEntTime >= 3000 && g_crosshairEntDist < 256 )
01105                 {//if the player holds the crosshair on you for a few seconds
01106                         //ask them what the fuck they're doing
01107                         G_AddVoiceEvent( NPC, Q_irand( EV_FF_1A, EV_FF_1C ), 0 );
01108                 }
01109         }
01110         */
01111 }
01112 
01113 void NPC_CheckAllClear( void )
01114 {
01115         //FIXME: need to make this happen only once after losing enemies, not over and over again
01116         /*
01117         if ( NPC->client && !NPC->enemy && level.time - teamLastEnemyTime[NPC->client->playerTeam] > 10000 )
01118         {//Team hasn't seen an enemy in 10 seconds
01119                 if ( !Q_irand( 0, 2 ) )
01120                 {
01121                         G_AddVoiceEvent( NPC, Q_irand(EV_SETTLE1, EV_SETTLE3), 3000 );
01122                 }
01123         }
01124         */
01125 }